diff --git a/README.md b/README.md index 062f378c862a..9085b3e36b29 100644 --- a/README.md +++ b/README.md @@ -40,7 +40,7 @@ How To (Plugin Developers) io.papermc.paper paper-api - 1.21.1-R0.1-SNAPSHOT + 1.21.3-R0.1-SNAPSHOT provided ``` @@ -53,7 +53,7 @@ repositories { } dependencies { - compileOnly("io.papermc.paper:paper-api:1.21.1-R0.1-SNAPSHOT") + compileOnly("io.papermc.paper:paper-api:1.21.3-R0.1-SNAPSHOT") } java { diff --git a/build-data/reobf-mappings-patch.tiny b/build-data/reobf-mappings-patch.tiny index 6bd89f5e427e..b14d9ed34563 100644 --- a/build-data/reobf-mappings-patch.tiny +++ b/build-data/reobf-mappings-patch.tiny @@ -14,7 +14,7 @@ tiny 2 0 mojang+yarn spigot # CraftBukkit changes type c net/minecraft/server/level/ServerLevel net/minecraft/server/level/WorldServer - f Lnet/minecraft/world/level/storage/PrimaryLevelData; serverLevelData K + f Lnet/minecraft/world/level/storage/PrimaryLevelData; serverLevelData L c net/minecraft/world/level/chunk/LevelChunk net/minecraft/world/level/chunk/Chunk f Lnet/minecraft/server/level/ServerLevel; level r diff --git a/build.gradle.kts b/build.gradle.kts index c3d335bfce18..20ab99b72be3 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -11,7 +11,7 @@ import kotlin.io.path.* plugins { java `maven-publish` - id("io.papermc.paperweight.core") version "1.7.3" + id("io.papermc.paperweight.core") version "1.7.4" } allprojects { @@ -67,7 +67,7 @@ repositories { } dependencies { - paramMappings("net.fabricmc:yarn:1.21.1+build.3:mergedv2") + paramMappings("net.fabricmc:yarn:1.21.3+build.1:mergedv2") remapper("net.fabricmc:tiny-remapper:0.10.3:fat") decompiler("org.vineflower:vineflower:1.10.1") spigotDecompiler("io.papermc:patched-spigot-fernflower:0.1+build.13") diff --git a/gradle.properties b/gradle.properties index 4d595c30f355..37e2da14d9b1 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,9 +1,9 @@ group=io.papermc.paper -version=1.21.1-R0.1-SNAPSHOT -mcVersion=1.21.1 +version=1.21.3-R0.1-SNAPSHOT +mcVersion=1.21.3 # Set to true while updating Minecraft version -updatingMinecraft=false +updatingMinecraft=true org.gradle.caching=true org.gradle.parallel=true diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar index e6441136f3d4..a4b76b9530d6 100644 Binary files a/gradle/wrapper/gradle-wrapper.jar and b/gradle/wrapper/gradle-wrapper.jar differ diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index a4413138c96c..df97d72b8b91 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-8.8-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.10.2-bin.zip networkTimeout=10000 validateDistributionUrl=true zipStoreBase=GRADLE_USER_HOME diff --git a/gradlew b/gradlew index b740cf13397a..f5feea6d6b11 100755 --- a/gradlew +++ b/gradlew @@ -15,6 +15,8 @@ # See the License for the specific language governing permissions and # limitations under the License. # +# SPDX-License-Identifier: Apache-2.0 +# ############################################################################## # @@ -84,7 +86,8 @@ done # shellcheck disable=SC2034 APP_BASE_NAME=${0##*/} # Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036) -APP_HOME=$( cd "${APP_HOME:-./}" > /dev/null && pwd -P ) || exit +APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s +' "$PWD" ) || exit # Use the maximum available, or set MAX_FD != -1 to use that value. MAX_FD=maximum diff --git a/gradlew.bat b/gradlew.bat index 25da30dbdeee..9d21a21834d5 100644 --- a/gradlew.bat +++ b/gradlew.bat @@ -13,6 +13,8 @@ @rem See the License for the specific language governing permissions and @rem limitations under the License. @rem +@rem SPDX-License-Identifier: Apache-2.0 +@rem @if "%DEBUG%"=="" @echo off @rem ########################################################################## diff --git a/moonrise_update_1_21_2.txt b/moonrise_update_1_21_2.txt new file mode 100644 index 000000000000..48e29a4a8254 --- /dev/null +++ b/moonrise_update_1_21_2.txt @@ -0,0 +1,3 @@ +todo: +- in ChunkEntitySlices, implement modifySavedEntities() by copying from old +- port poi_lookup? diff --git a/paper-api-generator/build.gradle.kts b/paper-api-generator/build.gradle.kts index 05d16d0b9510..62ceb696a547 100644 --- a/paper-api-generator/build.gradle.kts +++ b/paper-api-generator/build.gradle.kts @@ -15,7 +15,7 @@ dependencies { implementation("com.squareup:javapoet:1.13.0") implementation(project(":paper-api")) implementation("io.github.classgraph:classgraph:4.8.47") - implementation("org.jetbrains:annotations:24.0.1") + implementation("org.jetbrains:annotations:24.1.0") testImplementation("org.junit.jupiter:junit-jupiter:5.10.2") testRuntimeOnly("org.junit.platform:junit-platform-launcher") } diff --git a/paper-api-generator/generated/com/destroystokyo/paper/entity/ai/VanillaGoal.java b/paper-api-generator/generated/com/destroystokyo/paper/entity/ai/VanillaGoal.java index 02411466bdcf..e0aa5b925cbd 100644 --- a/paper-api-generator/generated/com/destroystokyo/paper/entity/ai/VanillaGoal.java +++ b/paper-api-generator/generated/com/destroystokyo/paper/entity/ai/VanillaGoal.java @@ -66,7 +66,7 @@ "unused", "SpellCheckingInspection" }) -@GeneratedFrom("1.21.1") +@GeneratedFrom("1.21.3") public interface VanillaGoal extends Goal { GoalKey RANDOM_STAND = create("random_stand", AbstractHorse.class); @@ -102,6 +102,10 @@ public interface VanillaGoal extends Goal { GoalKey BEE_WANDER = create("bee_wander", Bee.class); + GoalKey VALIDATE_FLOWER = create("validate_flower", Bee.class); + + GoalKey VALIDATE_HIVE = create("validate_hive", Bee.class); + GoalKey BLAZE_ATTACK = create("blaze_attack", Blaze.class); GoalKey CAT_AVOID_ENTITY = create("cat_avoid_entity", Cat.class); diff --git a/paper-api-generator/generated/io/papermc/paper/registry/keys/BannerPatternKeys.java b/paper-api-generator/generated/io/papermc/paper/registry/keys/BannerPatternKeys.java index e585ebb00aba..33e501a6fa5e 100644 --- a/paper-api-generator/generated/io/papermc/paper/registry/keys/BannerPatternKeys.java +++ b/paper-api-generator/generated/io/papermc/paper/registry/keys/BannerPatternKeys.java @@ -23,7 +23,7 @@ "unused", "SpellCheckingInspection" }) -@GeneratedFrom("1.21.1") +@GeneratedFrom("1.21.3") @ApiStatus.Experimental public final class BannerPatternKeys { /** diff --git a/paper-api-generator/generated/io/papermc/paper/registry/keys/BiomeKeys.java b/paper-api-generator/generated/io/papermc/paper/registry/keys/BiomeKeys.java index f0c6abf8bf66..83a5d59ea072 100644 --- a/paper-api-generator/generated/io/papermc/paper/registry/keys/BiomeKeys.java +++ b/paper-api-generator/generated/io/papermc/paper/registry/keys/BiomeKeys.java @@ -6,6 +6,7 @@ import io.papermc.paper.registry.RegistryKey; import io.papermc.paper.registry.TypedKey; import net.kyori.adventure.key.Key; +import org.bukkit.MinecraftExperimental; import org.bukkit.block.Biome; import org.checkerframework.checker.nullness.qual.NonNull; import org.jetbrains.annotations.ApiStatus; @@ -23,7 +24,7 @@ "unused", "SpellCheckingInspection" }) -@GeneratedFrom("1.21.1") +@GeneratedFrom("1.21.3") @ApiStatus.Experimental public final class BiomeKeys { /** @@ -299,6 +300,15 @@ public final class BiomeKeys { */ public static final TypedKey OLD_GROWTH_SPRUCE_TAIGA = create(key("old_growth_spruce_taiga")); + /** + * {@code minecraft:pale_garden} + * + * @apiNote This field is version-dependant and may be removed in future Minecraft versions + */ + @ApiStatus.Experimental + @MinecraftExperimental(MinecraftExperimental.Requires.WINTER_DROP) + public static final TypedKey PALE_GARDEN = create(key("pale_garden")); + /** * {@code minecraft:plains} * diff --git a/paper-api-generator/generated/io/papermc/paper/registry/keys/BlockTypeKeys.java b/paper-api-generator/generated/io/papermc/paper/registry/keys/BlockTypeKeys.java index 3daa9b638cf3..d2b100a27a85 100644 --- a/paper-api-generator/generated/io/papermc/paper/registry/keys/BlockTypeKeys.java +++ b/paper-api-generator/generated/io/papermc/paper/registry/keys/BlockTypeKeys.java @@ -6,6 +6,7 @@ import io.papermc.paper.registry.RegistryKey; import io.papermc.paper.registry.TypedKey; import net.kyori.adventure.key.Key; +import org.bukkit.MinecraftExperimental; import org.bukkit.block.BlockType; import org.checkerframework.checker.nullness.qual.NonNull; import org.jetbrains.annotations.ApiStatus; @@ -23,7 +24,7 @@ "unused", "SpellCheckingInspection" }) -@GeneratedFrom("1.21.1") +@GeneratedFrom("1.21.3") @ApiStatus.Experimental public final class BlockTypeKeys { /** @@ -1622,6 +1623,15 @@ public final class BlockTypeKeys { */ public static final TypedKey CRAFTING_TABLE = create(key("crafting_table")); + /** + * {@code minecraft:creaking_heart} + * + * @apiNote This field is version-dependant and may be removed in future Minecraft versions + */ + @ApiStatus.Experimental + @MinecraftExperimental(MinecraftExperimental.Requires.WINTER_DROP) + public static final TypedKey CREAKING_HEART = create(key("creaking_heart")); + /** * {@code minecraft:creeper_head} * @@ -4562,6 +4572,186 @@ public final class BlockTypeKeys { */ public static final TypedKey PACKED_MUD = create(key("packed_mud")); + /** + * {@code minecraft:pale_hanging_moss} + * + * @apiNote This field is version-dependant and may be removed in future Minecraft versions + */ + @ApiStatus.Experimental + @MinecraftExperimental(MinecraftExperimental.Requires.WINTER_DROP) + public static final TypedKey PALE_HANGING_MOSS = create(key("pale_hanging_moss")); + + /** + * {@code minecraft:pale_moss_block} + * + * @apiNote This field is version-dependant and may be removed in future Minecraft versions + */ + @ApiStatus.Experimental + @MinecraftExperimental(MinecraftExperimental.Requires.WINTER_DROP) + public static final TypedKey PALE_MOSS_BLOCK = create(key("pale_moss_block")); + + /** + * {@code minecraft:pale_moss_carpet} + * + * @apiNote This field is version-dependant and may be removed in future Minecraft versions + */ + @ApiStatus.Experimental + @MinecraftExperimental(MinecraftExperimental.Requires.WINTER_DROP) + public static final TypedKey PALE_MOSS_CARPET = create(key("pale_moss_carpet")); + + /** + * {@code minecraft:pale_oak_button} + * + * @apiNote This field is version-dependant and may be removed in future Minecraft versions + */ + @ApiStatus.Experimental + @MinecraftExperimental(MinecraftExperimental.Requires.WINTER_DROP) + public static final TypedKey PALE_OAK_BUTTON = create(key("pale_oak_button")); + + /** + * {@code minecraft:pale_oak_door} + * + * @apiNote This field is version-dependant and may be removed in future Minecraft versions + */ + @ApiStatus.Experimental + @MinecraftExperimental(MinecraftExperimental.Requires.WINTER_DROP) + public static final TypedKey PALE_OAK_DOOR = create(key("pale_oak_door")); + + /** + * {@code minecraft:pale_oak_fence} + * + * @apiNote This field is version-dependant and may be removed in future Minecraft versions + */ + @ApiStatus.Experimental + @MinecraftExperimental(MinecraftExperimental.Requires.WINTER_DROP) + public static final TypedKey PALE_OAK_FENCE = create(key("pale_oak_fence")); + + /** + * {@code minecraft:pale_oak_fence_gate} + * + * @apiNote This field is version-dependant and may be removed in future Minecraft versions + */ + @ApiStatus.Experimental + @MinecraftExperimental(MinecraftExperimental.Requires.WINTER_DROP) + public static final TypedKey PALE_OAK_FENCE_GATE = create(key("pale_oak_fence_gate")); + + /** + * {@code minecraft:pale_oak_hanging_sign} + * + * @apiNote This field is version-dependant and may be removed in future Minecraft versions + */ + @ApiStatus.Experimental + @MinecraftExperimental(MinecraftExperimental.Requires.WINTER_DROP) + public static final TypedKey PALE_OAK_HANGING_SIGN = create(key("pale_oak_hanging_sign")); + + /** + * {@code minecraft:pale_oak_leaves} + * + * @apiNote This field is version-dependant and may be removed in future Minecraft versions + */ + @ApiStatus.Experimental + @MinecraftExperimental(MinecraftExperimental.Requires.WINTER_DROP) + public static final TypedKey PALE_OAK_LEAVES = create(key("pale_oak_leaves")); + + /** + * {@code minecraft:pale_oak_log} + * + * @apiNote This field is version-dependant and may be removed in future Minecraft versions + */ + @ApiStatus.Experimental + @MinecraftExperimental(MinecraftExperimental.Requires.WINTER_DROP) + public static final TypedKey PALE_OAK_LOG = create(key("pale_oak_log")); + + /** + * {@code minecraft:pale_oak_planks} + * + * @apiNote This field is version-dependant and may be removed in future Minecraft versions + */ + @ApiStatus.Experimental + @MinecraftExperimental(MinecraftExperimental.Requires.WINTER_DROP) + public static final TypedKey PALE_OAK_PLANKS = create(key("pale_oak_planks")); + + /** + * {@code minecraft:pale_oak_pressure_plate} + * + * @apiNote This field is version-dependant and may be removed in future Minecraft versions + */ + @ApiStatus.Experimental + @MinecraftExperimental(MinecraftExperimental.Requires.WINTER_DROP) + public static final TypedKey PALE_OAK_PRESSURE_PLATE = create(key("pale_oak_pressure_plate")); + + /** + * {@code minecraft:pale_oak_sapling} + * + * @apiNote This field is version-dependant and may be removed in future Minecraft versions + */ + @ApiStatus.Experimental + @MinecraftExperimental(MinecraftExperimental.Requires.WINTER_DROP) + public static final TypedKey PALE_OAK_SAPLING = create(key("pale_oak_sapling")); + + /** + * {@code minecraft:pale_oak_sign} + * + * @apiNote This field is version-dependant and may be removed in future Minecraft versions + */ + @ApiStatus.Experimental + @MinecraftExperimental(MinecraftExperimental.Requires.WINTER_DROP) + public static final TypedKey PALE_OAK_SIGN = create(key("pale_oak_sign")); + + /** + * {@code minecraft:pale_oak_slab} + * + * @apiNote This field is version-dependant and may be removed in future Minecraft versions + */ + @ApiStatus.Experimental + @MinecraftExperimental(MinecraftExperimental.Requires.WINTER_DROP) + public static final TypedKey PALE_OAK_SLAB = create(key("pale_oak_slab")); + + /** + * {@code minecraft:pale_oak_stairs} + * + * @apiNote This field is version-dependant and may be removed in future Minecraft versions + */ + @ApiStatus.Experimental + @MinecraftExperimental(MinecraftExperimental.Requires.WINTER_DROP) + public static final TypedKey PALE_OAK_STAIRS = create(key("pale_oak_stairs")); + + /** + * {@code minecraft:pale_oak_trapdoor} + * + * @apiNote This field is version-dependant and may be removed in future Minecraft versions + */ + @ApiStatus.Experimental + @MinecraftExperimental(MinecraftExperimental.Requires.WINTER_DROP) + public static final TypedKey PALE_OAK_TRAPDOOR = create(key("pale_oak_trapdoor")); + + /** + * {@code minecraft:pale_oak_wall_hanging_sign} + * + * @apiNote This field is version-dependant and may be removed in future Minecraft versions + */ + @ApiStatus.Experimental + @MinecraftExperimental(MinecraftExperimental.Requires.WINTER_DROP) + public static final TypedKey PALE_OAK_WALL_HANGING_SIGN = create(key("pale_oak_wall_hanging_sign")); + + /** + * {@code minecraft:pale_oak_wall_sign} + * + * @apiNote This field is version-dependant and may be removed in future Minecraft versions + */ + @ApiStatus.Experimental + @MinecraftExperimental(MinecraftExperimental.Requires.WINTER_DROP) + public static final TypedKey PALE_OAK_WALL_SIGN = create(key("pale_oak_wall_sign")); + + /** + * {@code minecraft:pale_oak_wood} + * + * @apiNote This field is version-dependant and may be removed in future Minecraft versions + */ + @ApiStatus.Experimental + @MinecraftExperimental(MinecraftExperimental.Requires.WINTER_DROP) + public static final TypedKey PALE_OAK_WOOD = create(key("pale_oak_wood")); + /** * {@code minecraft:pearlescent_froglight} * @@ -5143,6 +5333,15 @@ public final class BlockTypeKeys { */ public static final TypedKey POTTED_OXEYE_DAISY = create(key("potted_oxeye_daisy")); + /** + * {@code minecraft:potted_pale_oak_sapling} + * + * @apiNote This field is version-dependant and may be removed in future Minecraft versions + */ + @ApiStatus.Experimental + @MinecraftExperimental(MinecraftExperimental.Requires.WINTER_DROP) + public static final TypedKey POTTED_PALE_OAK_SAPLING = create(key("potted_pale_oak_sapling")); + /** * {@code minecraft:potted_pink_tulip} * @@ -6410,6 +6609,24 @@ public final class BlockTypeKeys { */ public static final TypedKey STRIPPED_OAK_WOOD = create(key("stripped_oak_wood")); + /** + * {@code minecraft:stripped_pale_oak_log} + * + * @apiNote This field is version-dependant and may be removed in future Minecraft versions + */ + @ApiStatus.Experimental + @MinecraftExperimental(MinecraftExperimental.Requires.WINTER_DROP) + public static final TypedKey STRIPPED_PALE_OAK_LOG = create(key("stripped_pale_oak_log")); + + /** + * {@code minecraft:stripped_pale_oak_wood} + * + * @apiNote This field is version-dependant and may be removed in future Minecraft versions + */ + @ApiStatus.Experimental + @MinecraftExperimental(MinecraftExperimental.Requires.WINTER_DROP) + public static final TypedKey STRIPPED_PALE_OAK_WOOD = create(key("stripped_pale_oak_wood")); + /** * {@code minecraft:stripped_spruce_log} * diff --git a/paper-api-generator/generated/io/papermc/paper/registry/keys/CatVariantKeys.java b/paper-api-generator/generated/io/papermc/paper/registry/keys/CatVariantKeys.java index a8cbc4c9a01c..98acbdf85800 100644 --- a/paper-api-generator/generated/io/papermc/paper/registry/keys/CatVariantKeys.java +++ b/paper-api-generator/generated/io/papermc/paper/registry/keys/CatVariantKeys.java @@ -23,7 +23,7 @@ "unused", "SpellCheckingInspection" }) -@GeneratedFrom("1.21.1") +@GeneratedFrom("1.21.3") @ApiStatus.Experimental public final class CatVariantKeys { /** diff --git a/paper-api-generator/generated/io/papermc/paper/registry/keys/DamageTypeKeys.java b/paper-api-generator/generated/io/papermc/paper/registry/keys/DamageTypeKeys.java index 9cb651fb14d2..d89a584a1e5d 100644 --- a/paper-api-generator/generated/io/papermc/paper/registry/keys/DamageTypeKeys.java +++ b/paper-api-generator/generated/io/papermc/paper/registry/keys/DamageTypeKeys.java @@ -23,7 +23,7 @@ "unused", "SpellCheckingInspection" }) -@GeneratedFrom("1.21.1") +@GeneratedFrom("1.21.3") @ApiStatus.Experimental public final class DamageTypeKeys { /** @@ -82,6 +82,13 @@ public final class DamageTypeKeys { */ public static final TypedKey DRY_OUT = create(key("dry_out")); + /** + * {@code minecraft:ender_pearl} + * + * @apiNote This field is version-dependant and may be removed in future Minecraft versions + */ + public static final TypedKey ENDER_PEARL = create(key("ender_pearl")); + /** * {@code minecraft:explosion} * @@ -201,6 +208,13 @@ public final class DamageTypeKeys { */ public static final TypedKey LIGHTNING_BOLT = create(key("lightning_bolt")); + /** + * {@code minecraft:mace_smash} + * + * @apiNote This field is version-dependant and may be removed in future Minecraft versions + */ + public static final TypedKey MACE_SMASH = create(key("mace_smash")); + /** * {@code minecraft:magic} * diff --git a/paper-api-generator/generated/io/papermc/paper/registry/keys/EnchantmentKeys.java b/paper-api-generator/generated/io/papermc/paper/registry/keys/EnchantmentKeys.java index 8a7b691af62a..cf19e146cdae 100644 --- a/paper-api-generator/generated/io/papermc/paper/registry/keys/EnchantmentKeys.java +++ b/paper-api-generator/generated/io/papermc/paper/registry/keys/EnchantmentKeys.java @@ -23,7 +23,7 @@ "unused", "SpellCheckingInspection" }) -@GeneratedFrom("1.21.1") +@GeneratedFrom("1.21.3") @ApiStatus.Experimental public final class EnchantmentKeys { /** diff --git a/paper-api-generator/generated/io/papermc/paper/registry/keys/FrogVariantKeys.java b/paper-api-generator/generated/io/papermc/paper/registry/keys/FrogVariantKeys.java index 2b9c007e4922..7cbe222657ec 100644 --- a/paper-api-generator/generated/io/papermc/paper/registry/keys/FrogVariantKeys.java +++ b/paper-api-generator/generated/io/papermc/paper/registry/keys/FrogVariantKeys.java @@ -23,7 +23,7 @@ "unused", "SpellCheckingInspection" }) -@GeneratedFrom("1.21.1") +@GeneratedFrom("1.21.3") @ApiStatus.Experimental public final class FrogVariantKeys { /** diff --git a/paper-api-generator/generated/io/papermc/paper/registry/keys/GameEventKeys.java b/paper-api-generator/generated/io/papermc/paper/registry/keys/GameEventKeys.java index e8a8e5f89a71..d3751e33e295 100644 --- a/paper-api-generator/generated/io/papermc/paper/registry/keys/GameEventKeys.java +++ b/paper-api-generator/generated/io/papermc/paper/registry/keys/GameEventKeys.java @@ -23,7 +23,7 @@ "unused", "SpellCheckingInspection" }) -@GeneratedFrom("1.21.1") +@GeneratedFrom("1.21.3") @ApiStatus.Experimental public final class GameEventKeys { /** diff --git a/paper-api-generator/generated/io/papermc/paper/registry/keys/InstrumentKeys.java b/paper-api-generator/generated/io/papermc/paper/registry/keys/InstrumentKeys.java index 4d59a6b6e1b2..98b44173be72 100644 --- a/paper-api-generator/generated/io/papermc/paper/registry/keys/InstrumentKeys.java +++ b/paper-api-generator/generated/io/papermc/paper/registry/keys/InstrumentKeys.java @@ -23,7 +23,7 @@ "unused", "SpellCheckingInspection" }) -@GeneratedFrom("1.21.1") +@GeneratedFrom("1.21.3") @ApiStatus.Experimental public final class InstrumentKeys { /** diff --git a/paper-api-generator/generated/io/papermc/paper/registry/keys/ItemTypeKeys.java b/paper-api-generator/generated/io/papermc/paper/registry/keys/ItemTypeKeys.java index 052d12963087..9750f7a2018e 100644 --- a/paper-api-generator/generated/io/papermc/paper/registry/keys/ItemTypeKeys.java +++ b/paper-api-generator/generated/io/papermc/paper/registry/keys/ItemTypeKeys.java @@ -6,6 +6,7 @@ import io.papermc.paper.registry.RegistryKey; import io.papermc.paper.registry.TypedKey; import net.kyori.adventure.key.Key; +import org.bukkit.MinecraftExperimental; import org.bukkit.inventory.ItemType; import org.checkerframework.checker.nullness.qual.NonNull; import org.jetbrains.annotations.ApiStatus; @@ -23,7 +24,7 @@ "unused", "SpellCheckingInspection" }) -@GeneratedFrom("1.21.1") +@GeneratedFrom("1.21.3") @ApiStatus.Experimental public final class ItemTypeKeys { /** @@ -698,6 +699,13 @@ public final class ItemTypeKeys { */ public static final TypedKey BLACK_BED = create(key("black_bed")); + /** + * {@code minecraft:black_bundle} + * + * @apiNote This field is version-dependant and may be removed in future Minecraft versions + */ + public static final TypedKey BLACK_BUNDLE = create(key("black_bundle")); + /** * {@code minecraft:black_candle} * @@ -852,6 +860,13 @@ public final class ItemTypeKeys { */ public static final TypedKey BLUE_BED = create(key("blue_bed")); + /** + * {@code minecraft:blue_bundle} + * + * @apiNote This field is version-dependant and may be removed in future Minecraft versions + */ + public static final TypedKey BLUE_BUNDLE = create(key("blue_bundle")); + /** * {@code minecraft:blue_candle} * @@ -992,6 +1007,13 @@ public final class ItemTypeKeys { */ public static final TypedKey BOOKSHELF = create(key("bookshelf")); + /** + * {@code minecraft:bordure_indented_banner_pattern} + * + * @apiNote This field is version-dependant and may be removed in future Minecraft versions + */ + public static final TypedKey BORDURE_INDENTED_BANNER_PATTERN = create(key("bordure_indented_banner_pattern")); + /** * {@code minecraft:bow} * @@ -1111,6 +1133,13 @@ public final class ItemTypeKeys { */ public static final TypedKey BROWN_BED = create(key("brown_bed")); + /** + * {@code minecraft:brown_bundle} + * + * @apiNote This field is version-dependant and may be removed in future Minecraft versions + */ + public static final TypedKey BROWN_BUNDLE = create(key("brown_bundle")); + /** * {@code minecraft:brown_candle} * @@ -2014,6 +2043,24 @@ public final class ItemTypeKeys { */ public static final TypedKey CRAFTING_TABLE = create(key("crafting_table")); + /** + * {@code minecraft:creaking_heart} + * + * @apiNote This field is version-dependant and may be removed in future Minecraft versions + */ + @ApiStatus.Experimental + @MinecraftExperimental(MinecraftExperimental.Requires.WINTER_DROP) + public static final TypedKey CREAKING_HEART = create(key("creaking_heart")); + + /** + * {@code minecraft:creaking_spawn_egg} + * + * @apiNote This field is version-dependant and may be removed in future Minecraft versions + */ + @ApiStatus.Experimental + @MinecraftExperimental(MinecraftExperimental.Requires.WINTER_DROP) + public static final TypedKey CREAKING_SPAWN_EGG = create(key("creaking_spawn_egg")); + /** * {@code minecraft:creeper_banner_pattern} * @@ -2224,6 +2271,13 @@ public final class ItemTypeKeys { */ public static final TypedKey CYAN_BED = create(key("cyan_bed")); + /** + * {@code minecraft:cyan_bundle} + * + * @apiNote This field is version-dependant and may be removed in future Minecraft versions + */ + public static final TypedKey CYAN_BUNDLE = create(key("cyan_bundle")); + /** * {@code minecraft:cyan_candle} * @@ -2889,6 +2943,20 @@ public final class ItemTypeKeys { */ public static final TypedKey DROWNED_SPAWN_EGG = create(key("drowned_spawn_egg")); + /** + * {@code minecraft:dune_armor_trim_smithing_template} + * + * @apiNote This field is version-dependant and may be removed in future Minecraft versions + */ + public static final TypedKey DUNE_ARMOR_TRIM_SMITHING_TEMPLATE = create(key("dune_armor_trim_smithing_template")); + + /** + * {@code minecraft:echo_shard} + * + * @apiNote This field is version-dependant and may be removed in future Minecraft versions + */ + public static final TypedKey ECHO_SHARD = create(key("echo_shard")); + /** * {@code minecraft:elder_guardian_spawn_egg} * @@ -2946,371 +3014,644 @@ public final class ItemTypeKeys { public static final TypedKey EXPERIENCE_BOTTLE = create(key("experience_bottle")); /** - * {@code minecraft:fire_charge} + * {@code minecraft:explorer_pottery_sherd} * * @apiNote This field is version-dependant and may be removed in future Minecraft versions */ - public static final TypedKey FIRE_CHARGE = create(key("fire_charge")); + public static final TypedKey EXPLORER_POTTERY_SHERD = create(key("explorer_pottery_sherd")); /** - * {@code minecraft:firework_rocket} + * {@code minecraft:exposed_copper_bulb} * * @apiNote This field is version-dependant and may be removed in future Minecraft versions */ - public static final TypedKey FIREWORK_ROCKET = create(key("firework_rocket")); + public static final TypedKey EXPOSED_COPPER_BULB = create(key("exposed_copper_bulb")); /** - * {@code minecraft:firework_star} + * {@code minecraft:exposed_copper_grate} * * @apiNote This field is version-dependant and may be removed in future Minecraft versions */ - public static final TypedKey FIREWORK_STAR = create(key("firework_star")); + public static final TypedKey EXPOSED_COPPER_GRATE = create(key("exposed_copper_grate")); /** - * {@code minecraft:flower_pot} + * {@code minecraft:eye_armor_trim_smithing_template} * * @apiNote This field is version-dependant and may be removed in future Minecraft versions */ - public static final TypedKey FLOWER_POT = create(key("flower_pot")); + public static final TypedKey EYE_ARMOR_TRIM_SMITHING_TEMPLATE = create(key("eye_armor_trim_smithing_template")); /** - * {@code minecraft:fox_spawn_egg} + * {@code minecraft:field_masoned_banner_pattern} * * @apiNote This field is version-dependant and may be removed in future Minecraft versions */ - public static final TypedKey FOX_SPAWN_EGG = create(key("fox_spawn_egg")); + public static final TypedKey FIELD_MASONED_BANNER_PATTERN = create(key("field_masoned_banner_pattern")); /** - * {@code minecraft:frog_spawn_egg} + * {@code minecraft:fire_charge} * * @apiNote This field is version-dependant and may be removed in future Minecraft versions */ - public static final TypedKey FROG_SPAWN_EGG = create(key("frog_spawn_egg")); + public static final TypedKey FIRE_CHARGE = create(key("fire_charge")); /** - * {@code minecraft:ghast_spawn_egg} + * {@code minecraft:firework_rocket} * * @apiNote This field is version-dependant and may be removed in future Minecraft versions */ - public static final TypedKey GHAST_SPAWN_EGG = create(key("ghast_spawn_egg")); + public static final TypedKey FIREWORK_ROCKET = create(key("firework_rocket")); /** - * {@code minecraft:glow_item_frame} + * {@code minecraft:firework_star} * * @apiNote This field is version-dependant and may be removed in future Minecraft versions */ - public static final TypedKey GLOW_ITEM_FRAME = create(key("glow_item_frame")); + public static final TypedKey FIREWORK_STAR = create(key("firework_star")); /** - * {@code minecraft:glow_squid_spawn_egg} + * {@code minecraft:fletching_table} * * @apiNote This field is version-dependant and may be removed in future Minecraft versions */ - public static final TypedKey GLOW_SQUID_SPAWN_EGG = create(key("glow_squid_spawn_egg")); + public static final TypedKey FLETCHING_TABLE = create(key("fletching_table")); /** - * {@code minecraft:goat_spawn_egg} + * {@code minecraft:flow_armor_trim_smithing_template} * * @apiNote This field is version-dependant and may be removed in future Minecraft versions */ - public static final TypedKey GOAT_SPAWN_EGG = create(key("goat_spawn_egg")); + public static final TypedKey FLOW_ARMOR_TRIM_SMITHING_TEMPLATE = create(key("flow_armor_trim_smithing_template")); /** - * {@code minecraft:golden_carrot} + * {@code minecraft:flow_banner_pattern} * * @apiNote This field is version-dependant and may be removed in future Minecraft versions */ - public static final TypedKey GOLDEN_CARROT = create(key("golden_carrot")); + public static final TypedKey FLOW_BANNER_PATTERN = create(key("flow_banner_pattern")); /** - * {@code minecraft:golden_horse_armor} + * {@code minecraft:flow_pottery_sherd} * * @apiNote This field is version-dependant and may be removed in future Minecraft versions */ - public static final TypedKey GOLDEN_HORSE_ARMOR = create(key("golden_horse_armor")); + public static final TypedKey FLOW_POTTERY_SHERD = create(key("flow_pottery_sherd")); /** - * {@code minecraft:gray_banner} + * {@code minecraft:flower_banner_pattern} * * @apiNote This field is version-dependant and may be removed in future Minecraft versions */ - public static final TypedKey GRAY_BANNER = create(key("gray_banner")); + public static final TypedKey FLOWER_BANNER_PATTERN = create(key("flower_banner_pattern")); /** - * {@code minecraft:green_banner} + * {@code minecraft:flower_pot} * * @apiNote This field is version-dependant and may be removed in future Minecraft versions */ - public static final TypedKey GREEN_BANNER = create(key("green_banner")); + public static final TypedKey FLOWER_POT = create(key("flower_pot")); /** - * {@code minecraft:guardian_spawn_egg} + * {@code minecraft:fox_spawn_egg} * * @apiNote This field is version-dependant and may be removed in future Minecraft versions */ - public static final TypedKey GUARDIAN_SPAWN_EGG = create(key("guardian_spawn_egg")); + public static final TypedKey FOX_SPAWN_EGG = create(key("fox_spawn_egg")); /** - * {@code minecraft:hoglin_spawn_egg} + * {@code minecraft:friend_pottery_sherd} * * @apiNote This field is version-dependant and may be removed in future Minecraft versions */ - public static final TypedKey HOGLIN_SPAWN_EGG = create(key("hoglin_spawn_egg")); + public static final TypedKey FRIEND_POTTERY_SHERD = create(key("friend_pottery_sherd")); /** - * {@code minecraft:horse_spawn_egg} + * {@code minecraft:frog_spawn_egg} * * @apiNote This field is version-dependant and may be removed in future Minecraft versions */ - public static final TypedKey HORSE_SPAWN_EGG = create(key("horse_spawn_egg")); + public static final TypedKey FROG_SPAWN_EGG = create(key("frog_spawn_egg")); /** - * {@code minecraft:husk_spawn_egg} + * {@code minecraft:frogspawn} * * @apiNote This field is version-dependant and may be removed in future Minecraft versions */ - public static final TypedKey HUSK_SPAWN_EGG = create(key("husk_spawn_egg")); + public static final TypedKey FROGSPAWN = create(key("frogspawn")); /** - * {@code minecraft:iron_golem_spawn_egg} + * {@code minecraft:ghast_spawn_egg} * * @apiNote This field is version-dependant and may be removed in future Minecraft versions */ - public static final TypedKey IRON_GOLEM_SPAWN_EGG = create(key("iron_golem_spawn_egg")); + public static final TypedKey GHAST_SPAWN_EGG = create(key("ghast_spawn_egg")); /** - * {@code minecraft:iron_horse_armor} + * {@code minecraft:gilded_blackstone} * * @apiNote This field is version-dependant and may be removed in future Minecraft versions */ - public static final TypedKey IRON_HORSE_ARMOR = create(key("iron_horse_armor")); + public static final TypedKey GILDED_BLACKSTONE = create(key("gilded_blackstone")); /** - * {@code minecraft:iron_nugget} + * {@code minecraft:globe_banner_pattern} * * @apiNote This field is version-dependant and may be removed in future Minecraft versions */ - public static final TypedKey IRON_NUGGET = create(key("iron_nugget")); + public static final TypedKey GLOBE_BANNER_PATTERN = create(key("globe_banner_pattern")); /** - * {@code minecraft:item_frame} + * {@code minecraft:glow_berries} * * @apiNote This field is version-dependant and may be removed in future Minecraft versions */ - public static final TypedKey ITEM_FRAME = create(key("item_frame")); + public static final TypedKey GLOW_BERRIES = create(key("glow_berries")); /** - * {@code minecraft:knowledge_book} + * {@code minecraft:glow_item_frame} * * @apiNote This field is version-dependant and may be removed in future Minecraft versions */ - public static final TypedKey KNOWLEDGE_BOOK = create(key("knowledge_book")); + public static final TypedKey GLOW_ITEM_FRAME = create(key("glow_item_frame")); /** - * {@code minecraft:lead} + * {@code minecraft:glow_squid_spawn_egg} * * @apiNote This field is version-dependant and may be removed in future Minecraft versions */ - public static final TypedKey LEAD = create(key("lead")); + public static final TypedKey GLOW_SQUID_SPAWN_EGG = create(key("glow_squid_spawn_egg")); /** - * {@code minecraft:leather_horse_armor} + * {@code minecraft:goat_horn} * * @apiNote This field is version-dependant and may be removed in future Minecraft versions */ - public static final TypedKey LEATHER_HORSE_ARMOR = create(key("leather_horse_armor")); + public static final TypedKey GOAT_HORN = create(key("goat_horn")); /** - * {@code minecraft:light_blue_banner} + * {@code minecraft:goat_spawn_egg} * * @apiNote This field is version-dependant and may be removed in future Minecraft versions */ - public static final TypedKey LIGHT_BLUE_BANNER = create(key("light_blue_banner")); + public static final TypedKey GOAT_SPAWN_EGG = create(key("goat_spawn_egg")); /** - * {@code minecraft:light_gray_banner} + * {@code minecraft:golden_carrot} * * @apiNote This field is version-dependant and may be removed in future Minecraft versions */ - public static final TypedKey LIGHT_GRAY_BANNER = create(key("light_gray_banner")); + public static final TypedKey GOLDEN_CARROT = create(key("golden_carrot")); /** - * {@code minecraft:lime_banner} + * {@code minecraft:golden_horse_armor} * * @apiNote This field is version-dependant and may be removed in future Minecraft versions */ - public static final TypedKey LIME_BANNER = create(key("lime_banner")); + public static final TypedKey GOLDEN_HORSE_ARMOR = create(key("golden_horse_armor")); /** - * {@code minecraft:lingering_potion} + * {@code minecraft:gray_banner} * * @apiNote This field is version-dependant and may be removed in future Minecraft versions */ - public static final TypedKey LINGERING_POTION = create(key("lingering_potion")); + public static final TypedKey GRAY_BANNER = create(key("gray_banner")); /** - * {@code minecraft:llama_spawn_egg} + * {@code minecraft:gray_candle} * * @apiNote This field is version-dependant and may be removed in future Minecraft versions */ - public static final TypedKey LLAMA_SPAWN_EGG = create(key("llama_spawn_egg")); + public static final TypedKey GRAY_CANDLE = create(key("gray_candle")); /** - * {@code minecraft:mace} + * {@code minecraft:green_banner} * * @apiNote This field is version-dependant and may be removed in future Minecraft versions */ - public static final TypedKey MACE = create(key("mace")); + public static final TypedKey GREEN_BANNER = create(key("green_banner")); /** - * {@code minecraft:magenta_banner} + * {@code minecraft:green_candle} * * @apiNote This field is version-dependant and may be removed in future Minecraft versions */ - public static final TypedKey MAGENTA_BANNER = create(key("magenta_banner")); + public static final TypedKey GREEN_CANDLE = create(key("green_candle")); /** - * {@code minecraft:magma_cube_spawn_egg} + * {@code minecraft:grindstone} * * @apiNote This field is version-dependant and may be removed in future Minecraft versions */ - public static final TypedKey MAGMA_CUBE_SPAWN_EGG = create(key("magma_cube_spawn_egg")); + public static final TypedKey GRINDSTONE = create(key("grindstone")); /** - * {@code minecraft:map} + * {@code minecraft:guardian_spawn_egg} * * @apiNote This field is version-dependant and may be removed in future Minecraft versions */ - public static final TypedKey MAP = create(key("map")); + public static final TypedKey GUARDIAN_SPAWN_EGG = create(key("guardian_spawn_egg")); /** - * {@code minecraft:mooshroom_spawn_egg} + * {@code minecraft:guster_banner_pattern} * * @apiNote This field is version-dependant and may be removed in future Minecraft versions */ - public static final TypedKey MOOSHROOM_SPAWN_EGG = create(key("mooshroom_spawn_egg")); + public static final TypedKey GUSTER_BANNER_PATTERN = create(key("guster_banner_pattern")); /** - * {@code minecraft:mule_spawn_egg} + * {@code minecraft:guster_pottery_sherd} * * @apiNote This field is version-dependant and may be removed in future Minecraft versions */ - public static final TypedKey MULE_SPAWN_EGG = create(key("mule_spawn_egg")); + public static final TypedKey GUSTER_POTTERY_SHERD = create(key("guster_pottery_sherd")); /** - * {@code minecraft:music_disc_5} + * {@code minecraft:heart_of_the_sea} * * @apiNote This field is version-dependant and may be removed in future Minecraft versions */ - public static final TypedKey MUSIC_DISC_5 = create(key("music_disc_5")); + public static final TypedKey HEART_OF_THE_SEA = create(key("heart_of_the_sea")); /** - * {@code minecraft:disc_fragment_5} + * {@code minecraft:heart_pottery_sherd} * * @apiNote This field is version-dependant and may be removed in future Minecraft versions */ - public static final TypedKey DISC_FRAGMENT_5 = create(key("disc_fragment_5")); + public static final TypedKey HEART_POTTERY_SHERD = create(key("heart_pottery_sherd")); /** - * {@code minecraft:dispenser} + * {@code minecraft:heartbreak_pottery_sherd} * * @apiNote This field is version-dependant and may be removed in future Minecraft versions */ - public static final TypedKey DISPENSER = create(key("dispenser")); + public static final TypedKey HEARTBREAK_POTTERY_SHERD = create(key("heartbreak_pottery_sherd")); /** - * {@code minecraft:dragon_egg} + * {@code minecraft:hoglin_spawn_egg} * * @apiNote This field is version-dependant and may be removed in future Minecraft versions */ - public static final TypedKey DRAGON_EGG = create(key("dragon_egg")); + public static final TypedKey HOGLIN_SPAWN_EGG = create(key("hoglin_spawn_egg")); /** - * {@code minecraft:dried_kelp} + * {@code minecraft:honey_bottle} * * @apiNote This field is version-dependant and may be removed in future Minecraft versions */ - public static final TypedKey DRIED_KELP = create(key("dried_kelp")); + public static final TypedKey HONEY_BOTTLE = create(key("honey_bottle")); /** - * {@code minecraft:dried_kelp_block} + * {@code minecraft:honeycomb} * * @apiNote This field is version-dependant and may be removed in future Minecraft versions */ - public static final TypedKey DRIED_KELP_BLOCK = create(key("dried_kelp_block")); + public static final TypedKey HONEYCOMB = create(key("honeycomb")); /** - * {@code minecraft:dripstone_block} + * {@code minecraft:honeycomb_block} * * @apiNote This field is version-dependant and may be removed in future Minecraft versions */ - public static final TypedKey DRIPSTONE_BLOCK = create(key("dripstone_block")); + public static final TypedKey HONEYCOMB_BLOCK = create(key("honeycomb_block")); /** - * {@code minecraft:dropper} + * {@code minecraft:horse_spawn_egg} * * @apiNote This field is version-dependant and may be removed in future Minecraft versions */ - public static final TypedKey DROPPER = create(key("dropper")); + public static final TypedKey HORSE_SPAWN_EGG = create(key("horse_spawn_egg")); /** - * {@code minecraft:dune_armor_trim_smithing_template} + * {@code minecraft:host_armor_trim_smithing_template} * * @apiNote This field is version-dependant and may be removed in future Minecraft versions */ - public static final TypedKey DUNE_ARMOR_TRIM_SMITHING_TEMPLATE = create(key("dune_armor_trim_smithing_template")); + public static final TypedKey HOST_ARMOR_TRIM_SMITHING_TEMPLATE = create(key("host_armor_trim_smithing_template")); /** - * {@code minecraft:echo_shard} + * {@code minecraft:howl_pottery_sherd} * * @apiNote This field is version-dependant and may be removed in future Minecraft versions */ - public static final TypedKey ECHO_SHARD = create(key("echo_shard")); + public static final TypedKey HOWL_POTTERY_SHERD = create(key("howl_pottery_sherd")); /** - * {@code minecraft:egg} + * {@code minecraft:husk_spawn_egg} * * @apiNote This field is version-dependant and may be removed in future Minecraft versions */ - public static final TypedKey EGG = create(key("egg")); + public static final TypedKey HUSK_SPAWN_EGG = create(key("husk_spawn_egg")); /** - * {@code minecraft:elytra} + * {@code minecraft:iron_golem_spawn_egg} * * @apiNote This field is version-dependant and may be removed in future Minecraft versions */ - public static final TypedKey ELYTRA = create(key("elytra")); + public static final TypedKey IRON_GOLEM_SPAWN_EGG = create(key("iron_golem_spawn_egg")); /** - * {@code minecraft:emerald} + * {@code minecraft:iron_horse_armor} * * @apiNote This field is version-dependant and may be removed in future Minecraft versions */ - public static final TypedKey EMERALD = create(key("emerald")); + public static final TypedKey IRON_HORSE_ARMOR = create(key("iron_horse_armor")); /** - * {@code minecraft:emerald_block} + * {@code minecraft:iron_nugget} * * @apiNote This field is version-dependant and may be removed in future Minecraft versions */ - public static final TypedKey EMERALD_BLOCK = create(key("emerald_block")); + public static final TypedKey IRON_NUGGET = create(key("iron_nugget")); /** - * {@code minecraft:emerald_ore} + * {@code minecraft:item_frame} * * @apiNote This field is version-dependant and may be removed in future Minecraft versions */ - public static final TypedKey EMERALD_ORE = create(key("emerald_ore")); + public static final TypedKey ITEM_FRAME = create(key("item_frame")); /** - * {@code minecraft:enchanted_golden_apple} + * {@code minecraft:knowledge_book} * * @apiNote This field is version-dependant and may be removed in future Minecraft versions */ - public static final TypedKey ENCHANTED_GOLDEN_APPLE = create(key("enchanted_golden_apple")); + public static final TypedKey KNOWLEDGE_BOOK = create(key("knowledge_book")); /** - * {@code minecraft:enchanting_table} + * {@code minecraft:lantern} + * + * @apiNote This field is version-dependant and may be removed in future Minecraft versions + */ + public static final TypedKey LANTERN = create(key("lantern")); + + /** + * {@code minecraft:large_amethyst_bud} + * + * @apiNote This field is version-dependant and may be removed in future Minecraft versions + */ + public static final TypedKey LARGE_AMETHYST_BUD = create(key("large_amethyst_bud")); + + /** + * {@code minecraft:lead} + * + * @apiNote This field is version-dependant and may be removed in future Minecraft versions + */ + public static final TypedKey LEAD = create(key("lead")); + + /** + * {@code minecraft:leather_horse_armor} + * + * @apiNote This field is version-dependant and may be removed in future Minecraft versions + */ + public static final TypedKey LEATHER_HORSE_ARMOR = create(key("leather_horse_armor")); + + /** + * {@code minecraft:light_blue_banner} + * + * @apiNote This field is version-dependant and may be removed in future Minecraft versions + */ + public static final TypedKey LIGHT_BLUE_BANNER = create(key("light_blue_banner")); + + /** + * {@code minecraft:light_blue_candle} + * + * @apiNote This field is version-dependant and may be removed in future Minecraft versions + */ + public static final TypedKey LIGHT_BLUE_CANDLE = create(key("light_blue_candle")); + + /** + * {@code minecraft:light_gray_banner} + * + * @apiNote This field is version-dependant and may be removed in future Minecraft versions + */ + public static final TypedKey LIGHT_GRAY_BANNER = create(key("light_gray_banner")); + + /** + * {@code minecraft:light_gray_candle} + * + * @apiNote This field is version-dependant and may be removed in future Minecraft versions + */ + public static final TypedKey LIGHT_GRAY_CANDLE = create(key("light_gray_candle")); + + /** + * {@code minecraft:lime_banner} + * + * @apiNote This field is version-dependant and may be removed in future Minecraft versions + */ + public static final TypedKey LIME_BANNER = create(key("lime_banner")); + + /** + * {@code minecraft:lime_candle} + * + * @apiNote This field is version-dependant and may be removed in future Minecraft versions + */ + public static final TypedKey LIME_CANDLE = create(key("lime_candle")); + + /** + * {@code minecraft:lingering_potion} + * + * @apiNote This field is version-dependant and may be removed in future Minecraft versions + */ + public static final TypedKey LINGERING_POTION = create(key("lingering_potion")); + + /** + * {@code minecraft:llama_spawn_egg} + * + * @apiNote This field is version-dependant and may be removed in future Minecraft versions + */ + public static final TypedKey LLAMA_SPAWN_EGG = create(key("llama_spawn_egg")); + + /** + * {@code minecraft:lodestone} + * + * @apiNote This field is version-dependant and may be removed in future Minecraft versions + */ + public static final TypedKey LODESTONE = create(key("lodestone")); + + /** + * {@code minecraft:loom} + * + * @apiNote This field is version-dependant and may be removed in future Minecraft versions + */ + public static final TypedKey LOOM = create(key("loom")); + + /** + * {@code minecraft:mace} + * + * @apiNote This field is version-dependant and may be removed in future Minecraft versions + */ + public static final TypedKey MACE = create(key("mace")); + + /** + * {@code minecraft:magenta_banner} + * + * @apiNote This field is version-dependant and may be removed in future Minecraft versions + */ + public static final TypedKey MAGENTA_BANNER = create(key("magenta_banner")); + + /** + * {@code minecraft:magenta_candle} + * + * @apiNote This field is version-dependant and may be removed in future Minecraft versions + */ + public static final TypedKey MAGENTA_CANDLE = create(key("magenta_candle")); + + /** + * {@code minecraft:magma_cube_spawn_egg} + * + * @apiNote This field is version-dependant and may be removed in future Minecraft versions + */ + public static final TypedKey MAGMA_CUBE_SPAWN_EGG = create(key("magma_cube_spawn_egg")); + + /** + * {@code minecraft:map} + * + * @apiNote This field is version-dependant and may be removed in future Minecraft versions + */ + public static final TypedKey MAP = create(key("map")); + + /** + * {@code minecraft:medium_amethyst_bud} + * + * @apiNote This field is version-dependant and may be removed in future Minecraft versions + */ + public static final TypedKey MEDIUM_AMETHYST_BUD = create(key("medium_amethyst_bud")); + + /** + * {@code minecraft:miner_pottery_sherd} + * + * @apiNote This field is version-dependant and may be removed in future Minecraft versions + */ + public static final TypedKey MINER_POTTERY_SHERD = create(key("miner_pottery_sherd")); + + /** + * {@code minecraft:mojang_banner_pattern} + * + * @apiNote This field is version-dependant and may be removed in future Minecraft versions + */ + public static final TypedKey MOJANG_BANNER_PATTERN = create(key("mojang_banner_pattern")); + + /** + * {@code minecraft:mooshroom_spawn_egg} + * + * @apiNote This field is version-dependant and may be removed in future Minecraft versions + */ + public static final TypedKey MOOSHROOM_SPAWN_EGG = create(key("mooshroom_spawn_egg")); + + /** + * {@code minecraft:mourner_pottery_sherd} + * + * @apiNote This field is version-dependant and may be removed in future Minecraft versions + */ + public static final TypedKey MOURNER_POTTERY_SHERD = create(key("mourner_pottery_sherd")); + + /** + * {@code minecraft:mule_spawn_egg} + * + * @apiNote This field is version-dependant and may be removed in future Minecraft versions + */ + public static final TypedKey MULE_SPAWN_EGG = create(key("mule_spawn_egg")); + + /** + * {@code minecraft:music_disc_5} + * + * @apiNote This field is version-dependant and may be removed in future Minecraft versions + */ + public static final TypedKey MUSIC_DISC_5 = create(key("music_disc_5")); + + /** + * {@code minecraft:disc_fragment_5} + * + * @apiNote This field is version-dependant and may be removed in future Minecraft versions + */ + public static final TypedKey DISC_FRAGMENT_5 = create(key("disc_fragment_5")); + + /** + * {@code minecraft:dispenser} + * + * @apiNote This field is version-dependant and may be removed in future Minecraft versions + */ + public static final TypedKey DISPENSER = create(key("dispenser")); + + /** + * {@code minecraft:dragon_egg} + * + * @apiNote This field is version-dependant and may be removed in future Minecraft versions + */ + public static final TypedKey DRAGON_EGG = create(key("dragon_egg")); + + /** + * {@code minecraft:dried_kelp} + * + * @apiNote This field is version-dependant and may be removed in future Minecraft versions + */ + public static final TypedKey DRIED_KELP = create(key("dried_kelp")); + + /** + * {@code minecraft:dried_kelp_block} + * + * @apiNote This field is version-dependant and may be removed in future Minecraft versions + */ + public static final TypedKey DRIED_KELP_BLOCK = create(key("dried_kelp_block")); + + /** + * {@code minecraft:dripstone_block} + * + * @apiNote This field is version-dependant and may be removed in future Minecraft versions + */ + public static final TypedKey DRIPSTONE_BLOCK = create(key("dripstone_block")); + + /** + * {@code minecraft:dropper} + * + * @apiNote This field is version-dependant and may be removed in future Minecraft versions + */ + public static final TypedKey DROPPER = create(key("dropper")); + + /** + * {@code minecraft:egg} + * + * @apiNote This field is version-dependant and may be removed in future Minecraft versions + */ + public static final TypedKey EGG = create(key("egg")); + + /** + * {@code minecraft:elytra} + * + * @apiNote This field is version-dependant and may be removed in future Minecraft versions + */ + public static final TypedKey ELYTRA = create(key("elytra")); + + /** + * {@code minecraft:emerald} + * + * @apiNote This field is version-dependant and may be removed in future Minecraft versions + */ + public static final TypedKey EMERALD = create(key("emerald")); + + /** + * {@code minecraft:emerald_block} + * + * @apiNote This field is version-dependant and may be removed in future Minecraft versions + */ + public static final TypedKey EMERALD_BLOCK = create(key("emerald_block")); + + /** + * {@code minecraft:emerald_ore} + * + * @apiNote This field is version-dependant and may be removed in future Minecraft versions + */ + public static final TypedKey EMERALD_ORE = create(key("emerald_ore")); + + /** + * {@code minecraft:enchanted_golden_apple} + * + * @apiNote This field is version-dependant and may be removed in future Minecraft versions + */ + public static final TypedKey ENCHANTED_GOLDEN_APPLE = create(key("enchanted_golden_apple")); + + /** + * {@code minecraft:enchanting_table} * * @apiNote This field is version-dependant and may be removed in future Minecraft versions */ @@ -3386,13 +3727,6 @@ public final class ItemTypeKeys { */ public static final TypedKey ENDER_PEARL = create(key("ender_pearl")); - /** - * {@code minecraft:explorer_pottery_sherd} - * - * @apiNote This field is version-dependant and may be removed in future Minecraft versions - */ - public static final TypedKey EXPLORER_POTTERY_SHERD = create(key("explorer_pottery_sherd")); - /** * {@code minecraft:exposed_chiseled_copper} * @@ -3407,13 +3741,6 @@ public final class ItemTypeKeys { */ public static final TypedKey EXPOSED_COPPER = create(key("exposed_copper")); - /** - * {@code minecraft:exposed_copper_bulb} - * - * @apiNote This field is version-dependant and may be removed in future Minecraft versions - */ - public static final TypedKey EXPOSED_COPPER_BULB = create(key("exposed_copper_bulb")); - /** * {@code minecraft:exposed_copper_door} * @@ -3421,13 +3748,6 @@ public final class ItemTypeKeys { */ public static final TypedKey EXPOSED_COPPER_DOOR = create(key("exposed_copper_door")); - /** - * {@code minecraft:exposed_copper_grate} - * - * @apiNote This field is version-dependant and may be removed in future Minecraft versions - */ - public static final TypedKey EXPOSED_COPPER_GRATE = create(key("exposed_copper_grate")); - /** * {@code minecraft:exposed_copper_trapdoor} * @@ -3456,13 +3776,6 @@ public final class ItemTypeKeys { */ public static final TypedKey EXPOSED_CUT_COPPER_STAIRS = create(key("exposed_cut_copper_stairs")); - /** - * {@code minecraft:eye_armor_trim_smithing_template} - * - * @apiNote This field is version-dependant and may be removed in future Minecraft versions - */ - public static final TypedKey EYE_ARMOR_TRIM_SMITHING_TEMPLATE = create(key("eye_armor_trim_smithing_template")); - /** * {@code minecraft:farmland} * @@ -3526,13 +3839,6 @@ public final class ItemTypeKeys { */ public static final TypedKey FISHING_ROD = create(key("fishing_rod")); - /** - * {@code minecraft:fletching_table} - * - * @apiNote This field is version-dependant and may be removed in future Minecraft versions - */ - public static final TypedKey FLETCHING_TABLE = create(key("fletching_table")); - /** * {@code minecraft:flint} * @@ -3547,34 +3853,6 @@ public final class ItemTypeKeys { */ public static final TypedKey FLINT_AND_STEEL = create(key("flint_and_steel")); - /** - * {@code minecraft:flow_armor_trim_smithing_template} - * - * @apiNote This field is version-dependant and may be removed in future Minecraft versions - */ - public static final TypedKey FLOW_ARMOR_TRIM_SMITHING_TEMPLATE = create(key("flow_armor_trim_smithing_template")); - - /** - * {@code minecraft:flow_banner_pattern} - * - * @apiNote This field is version-dependant and may be removed in future Minecraft versions - */ - public static final TypedKey FLOW_BANNER_PATTERN = create(key("flow_banner_pattern")); - - /** - * {@code minecraft:flow_pottery_sherd} - * - * @apiNote This field is version-dependant and may be removed in future Minecraft versions - */ - public static final TypedKey FLOW_POTTERY_SHERD = create(key("flow_pottery_sherd")); - - /** - * {@code minecraft:flower_banner_pattern} - * - * @apiNote This field is version-dependant and may be removed in future Minecraft versions - */ - public static final TypedKey FLOWER_BANNER_PATTERN = create(key("flower_banner_pattern")); - /** * {@code minecraft:flowering_azalea} * @@ -3589,20 +3867,6 @@ public final class ItemTypeKeys { */ public static final TypedKey FLOWERING_AZALEA_LEAVES = create(key("flowering_azalea_leaves")); - /** - * {@code minecraft:friend_pottery_sherd} - * - * @apiNote This field is version-dependant and may be removed in future Minecraft versions - */ - public static final TypedKey FRIEND_POTTERY_SHERD = create(key("friend_pottery_sherd")); - - /** - * {@code minecraft:frogspawn} - * - * @apiNote This field is version-dependant and may be removed in future Minecraft versions - */ - public static final TypedKey FROGSPAWN = create(key("frogspawn")); - /** * {@code minecraft:furnace} * @@ -3624,13 +3888,6 @@ public final class ItemTypeKeys { */ public static final TypedKey GHAST_TEAR = create(key("ghast_tear")); - /** - * {@code minecraft:gilded_blackstone} - * - * @apiNote This field is version-dependant and may be removed in future Minecraft versions - */ - public static final TypedKey GILDED_BLACKSTONE = create(key("gilded_blackstone")); - /** * {@code minecraft:glass} * @@ -3659,20 +3916,6 @@ public final class ItemTypeKeys { */ public static final TypedKey GLISTERING_MELON_SLICE = create(key("glistering_melon_slice")); - /** - * {@code minecraft:globe_banner_pattern} - * - * @apiNote This field is version-dependant and may be removed in future Minecraft versions - */ - public static final TypedKey GLOBE_BANNER_PATTERN = create(key("globe_banner_pattern")); - - /** - * {@code minecraft:glow_berries} - * - * @apiNote This field is version-dependant and may be removed in future Minecraft versions - */ - public static final TypedKey GLOW_BERRIES = create(key("glow_berries")); - /** * {@code minecraft:glow_ink_sac} * @@ -3701,13 +3944,6 @@ public final class ItemTypeKeys { */ public static final TypedKey GLOWSTONE_DUST = create(key("glowstone_dust")); - /** - * {@code minecraft:goat_horn} - * - * @apiNote This field is version-dependant and may be removed in future Minecraft versions - */ - public static final TypedKey GOAT_HORN = create(key("goat_horn")); - /** * {@code minecraft:gold_block} * @@ -3856,11 +4092,11 @@ public final class ItemTypeKeys { public static final TypedKey GRAY_BED = create(key("gray_bed")); /** - * {@code minecraft:gray_candle} + * {@code minecraft:gray_bundle} * * @apiNote This field is version-dependant and may be removed in future Minecraft versions */ - public static final TypedKey GRAY_CANDLE = create(key("gray_candle")); + public static final TypedKey GRAY_BUNDLE = create(key("gray_bundle")); /** * {@code minecraft:gray_carpet} @@ -3940,11 +4176,11 @@ public final class ItemTypeKeys { public static final TypedKey GREEN_BED = create(key("green_bed")); /** - * {@code minecraft:green_candle} + * {@code minecraft:green_bundle} * * @apiNote This field is version-dependant and may be removed in future Minecraft versions */ - public static final TypedKey GREEN_CANDLE = create(key("green_candle")); + public static final TypedKey GREEN_BUNDLE = create(key("green_bundle")); /** * {@code minecraft:green_carpet} @@ -3993,133 +4229,70 @@ public final class ItemTypeKeys { * * @apiNote This field is version-dependant and may be removed in future Minecraft versions */ - public static final TypedKey GREEN_STAINED_GLASS = create(key("green_stained_glass")); - - /** - * {@code minecraft:green_stained_glass_pane} - * - * @apiNote This field is version-dependant and may be removed in future Minecraft versions - */ - public static final TypedKey GREEN_STAINED_GLASS_PANE = create(key("green_stained_glass_pane")); - - /** - * {@code minecraft:green_terracotta} - * - * @apiNote This field is version-dependant and may be removed in future Minecraft versions - */ - public static final TypedKey GREEN_TERRACOTTA = create(key("green_terracotta")); - - /** - * {@code minecraft:green_wool} - * - * @apiNote This field is version-dependant and may be removed in future Minecraft versions - */ - public static final TypedKey GREEN_WOOL = create(key("green_wool")); - - /** - * {@code minecraft:grindstone} - * - * @apiNote This field is version-dependant and may be removed in future Minecraft versions - */ - public static final TypedKey GRINDSTONE = create(key("grindstone")); - - /** - * {@code minecraft:gunpowder} - * - * @apiNote This field is version-dependant and may be removed in future Minecraft versions - */ - public static final TypedKey GUNPOWDER = create(key("gunpowder")); - - /** - * {@code minecraft:guster_banner_pattern} - * - * @apiNote This field is version-dependant and may be removed in future Minecraft versions - */ - public static final TypedKey GUSTER_BANNER_PATTERN = create(key("guster_banner_pattern")); - - /** - * {@code minecraft:guster_pottery_sherd} - * - * @apiNote This field is version-dependant and may be removed in future Minecraft versions - */ - public static final TypedKey GUSTER_POTTERY_SHERD = create(key("guster_pottery_sherd")); - - /** - * {@code minecraft:hanging_roots} - * - * @apiNote This field is version-dependant and may be removed in future Minecraft versions - */ - public static final TypedKey HANGING_ROOTS = create(key("hanging_roots")); - - /** - * {@code minecraft:hay_block} - * - * @apiNote This field is version-dependant and may be removed in future Minecraft versions - */ - public static final TypedKey HAY_BLOCK = create(key("hay_block")); + public static final TypedKey GREEN_STAINED_GLASS = create(key("green_stained_glass")); /** - * {@code minecraft:heart_of_the_sea} + * {@code minecraft:green_stained_glass_pane} * * @apiNote This field is version-dependant and may be removed in future Minecraft versions */ - public static final TypedKey HEART_OF_THE_SEA = create(key("heart_of_the_sea")); + public static final TypedKey GREEN_STAINED_GLASS_PANE = create(key("green_stained_glass_pane")); /** - * {@code minecraft:heart_pottery_sherd} + * {@code minecraft:green_terracotta} * * @apiNote This field is version-dependant and may be removed in future Minecraft versions */ - public static final TypedKey HEART_POTTERY_SHERD = create(key("heart_pottery_sherd")); + public static final TypedKey GREEN_TERRACOTTA = create(key("green_terracotta")); /** - * {@code minecraft:heartbreak_pottery_sherd} + * {@code minecraft:green_wool} * * @apiNote This field is version-dependant and may be removed in future Minecraft versions */ - public static final TypedKey HEARTBREAK_POTTERY_SHERD = create(key("heartbreak_pottery_sherd")); + public static final TypedKey GREEN_WOOL = create(key("green_wool")); /** - * {@code minecraft:heavy_core} + * {@code minecraft:gunpowder} * * @apiNote This field is version-dependant and may be removed in future Minecraft versions */ - public static final TypedKey HEAVY_CORE = create(key("heavy_core")); + public static final TypedKey GUNPOWDER = create(key("gunpowder")); /** - * {@code minecraft:heavy_weighted_pressure_plate} + * {@code minecraft:hanging_roots} * * @apiNote This field is version-dependant and may be removed in future Minecraft versions */ - public static final TypedKey HEAVY_WEIGHTED_PRESSURE_PLATE = create(key("heavy_weighted_pressure_plate")); + public static final TypedKey HANGING_ROOTS = create(key("hanging_roots")); /** - * {@code minecraft:honey_block} + * {@code minecraft:hay_block} * * @apiNote This field is version-dependant and may be removed in future Minecraft versions */ - public static final TypedKey HONEY_BLOCK = create(key("honey_block")); + public static final TypedKey HAY_BLOCK = create(key("hay_block")); /** - * {@code minecraft:honey_bottle} + * {@code minecraft:heavy_core} * * @apiNote This field is version-dependant and may be removed in future Minecraft versions */ - public static final TypedKey HONEY_BOTTLE = create(key("honey_bottle")); + public static final TypedKey HEAVY_CORE = create(key("heavy_core")); /** - * {@code minecraft:honeycomb} + * {@code minecraft:heavy_weighted_pressure_plate} * * @apiNote This field is version-dependant and may be removed in future Minecraft versions */ - public static final TypedKey HONEYCOMB = create(key("honeycomb")); + public static final TypedKey HEAVY_WEIGHTED_PRESSURE_PLATE = create(key("heavy_weighted_pressure_plate")); /** - * {@code minecraft:honeycomb_block} + * {@code minecraft:honey_block} * * @apiNote This field is version-dependant and may be removed in future Minecraft versions */ - public static final TypedKey HONEYCOMB_BLOCK = create(key("honeycomb_block")); + public static final TypedKey HONEY_BLOCK = create(key("honey_block")); /** * {@code minecraft:hopper} @@ -4156,20 +4329,6 @@ public final class ItemTypeKeys { */ public static final TypedKey HORN_CORAL_FAN = create(key("horn_coral_fan")); - /** - * {@code minecraft:host_armor_trim_smithing_template} - * - * @apiNote This field is version-dependant and may be removed in future Minecraft versions - */ - public static final TypedKey HOST_ARMOR_TRIM_SMITHING_TEMPLATE = create(key("host_armor_trim_smithing_template")); - - /** - * {@code minecraft:howl_pottery_sherd} - * - * @apiNote This field is version-dependant and may be removed in future Minecraft versions - */ - public static final TypedKey HOWL_POTTERY_SHERD = create(key("howl_pottery_sherd")); - /** * {@code minecraft:ice} * @@ -4492,13 +4651,6 @@ public final class ItemTypeKeys { */ public static final TypedKey LADDER = create(key("ladder")); - /** - * {@code minecraft:lantern} - * - * @apiNote This field is version-dependant and may be removed in future Minecraft versions - */ - public static final TypedKey LANTERN = create(key("lantern")); - /** * {@code minecraft:lapis_block} * @@ -4520,13 +4672,6 @@ public final class ItemTypeKeys { */ public static final TypedKey LAPIS_ORE = create(key("lapis_ore")); - /** - * {@code minecraft:large_amethyst_bud} - * - * @apiNote This field is version-dependant and may be removed in future Minecraft versions - */ - public static final TypedKey LARGE_AMETHYST_BUD = create(key("large_amethyst_bud")); - /** * {@code minecraft:large_fern} * @@ -4605,11 +4750,11 @@ public final class ItemTypeKeys { public static final TypedKey LIGHT_BLUE_BED = create(key("light_blue_bed")); /** - * {@code minecraft:light_blue_candle} + * {@code minecraft:light_blue_bundle} * * @apiNote This field is version-dependant and may be removed in future Minecraft versions */ - public static final TypedKey LIGHT_BLUE_CANDLE = create(key("light_blue_candle")); + public static final TypedKey LIGHT_BLUE_BUNDLE = create(key("light_blue_bundle")); /** * {@code minecraft:light_blue_carpet} @@ -4689,11 +4834,11 @@ public final class ItemTypeKeys { public static final TypedKey LIGHT_GRAY_BED = create(key("light_gray_bed")); /** - * {@code minecraft:light_gray_candle} + * {@code minecraft:light_gray_bundle} * * @apiNote This field is version-dependant and may be removed in future Minecraft versions */ - public static final TypedKey LIGHT_GRAY_CANDLE = create(key("light_gray_candle")); + public static final TypedKey LIGHT_GRAY_BUNDLE = create(key("light_gray_bundle")); /** * {@code minecraft:light_gray_carpet} @@ -4808,11 +4953,11 @@ public final class ItemTypeKeys { public static final TypedKey LIME_BED = create(key("lime_bed")); /** - * {@code minecraft:lime_candle} + * {@code minecraft:lime_bundle} * * @apiNote This field is version-dependant and may be removed in future Minecraft versions */ - public static final TypedKey LIME_CANDLE = create(key("lime_candle")); + public static final TypedKey LIME_BUNDLE = create(key("lime_bundle")); /** * {@code minecraft:lime_carpet} @@ -4884,20 +5029,6 @@ public final class ItemTypeKeys { */ public static final TypedKey LIME_WOOL = create(key("lime_wool")); - /** - * {@code minecraft:lodestone} - * - * @apiNote This field is version-dependant and may be removed in future Minecraft versions - */ - public static final TypedKey LODESTONE = create(key("lodestone")); - - /** - * {@code minecraft:loom} - * - * @apiNote This field is version-dependant and may be removed in future Minecraft versions - */ - public static final TypedKey LOOM = create(key("loom")); - /** * {@code minecraft:magenta_bed} * @@ -4906,11 +5037,11 @@ public final class ItemTypeKeys { public static final TypedKey MAGENTA_BED = create(key("magenta_bed")); /** - * {@code minecraft:magenta_candle} + * {@code minecraft:magenta_bundle} * * @apiNote This field is version-dependant and may be removed in future Minecraft versions */ - public static final TypedKey MAGENTA_CANDLE = create(key("magenta_candle")); + public static final TypedKey MAGENTA_BUNDLE = create(key("magenta_bundle")); /** * {@code minecraft:magenta_carpet} @@ -5122,13 +5253,6 @@ public final class ItemTypeKeys { */ public static final TypedKey MANGROVE_WOOD = create(key("mangrove_wood")); - /** - * {@code minecraft:medium_amethyst_bud} - * - * @apiNote This field is version-dependant and may be removed in future Minecraft versions - */ - public static final TypedKey MEDIUM_AMETHYST_BUD = create(key("medium_amethyst_bud")); - /** * {@code minecraft:melon} * @@ -5164,20 +5288,6 @@ public final class ItemTypeKeys { */ public static final TypedKey MINECART = create(key("minecart")); - /** - * {@code minecraft:miner_pottery_sherd} - * - * @apiNote This field is version-dependant and may be removed in future Minecraft versions - */ - public static final TypedKey MINER_POTTERY_SHERD = create(key("miner_pottery_sherd")); - - /** - * {@code minecraft:mojang_banner_pattern} - * - * @apiNote This field is version-dependant and may be removed in future Minecraft versions - */ - public static final TypedKey MOJANG_BANNER_PATTERN = create(key("mojang_banner_pattern")); - /** * {@code minecraft:moss_block} * @@ -5248,13 +5358,6 @@ public final class ItemTypeKeys { */ public static final TypedKey MOSSY_STONE_BRICKS = create(key("mossy_stone_bricks")); - /** - * {@code minecraft:mourner_pottery_sherd} - * - * @apiNote This field is version-dependant and may be removed in future Minecraft versions - */ - public static final TypedKey MOURNER_POTTERY_SHERD = create(key("mourner_pottery_sherd")); - /** * {@code minecraft:mud} * @@ -5829,6 +5932,13 @@ public final class ItemTypeKeys { */ public static final TypedKey ORANGE_BED = create(key("orange_bed")); + /** + * {@code minecraft:orange_bundle} + * + * @apiNote This field is version-dependant and may be removed in future Minecraft versions + */ + public static final TypedKey ORANGE_BUNDLE = create(key("orange_bundle")); + /** * {@code minecraft:orange_candle} * @@ -6004,6 +6114,186 @@ public final class ItemTypeKeys { */ public static final TypedKey PAINTING = create(key("painting")); + /** + * {@code minecraft:pale_hanging_moss} + * + * @apiNote This field is version-dependant and may be removed in future Minecraft versions + */ + @ApiStatus.Experimental + @MinecraftExperimental(MinecraftExperimental.Requires.WINTER_DROP) + public static final TypedKey PALE_HANGING_MOSS = create(key("pale_hanging_moss")); + + /** + * {@code minecraft:pale_moss_block} + * + * @apiNote This field is version-dependant and may be removed in future Minecraft versions + */ + @ApiStatus.Experimental + @MinecraftExperimental(MinecraftExperimental.Requires.WINTER_DROP) + public static final TypedKey PALE_MOSS_BLOCK = create(key("pale_moss_block")); + + /** + * {@code minecraft:pale_moss_carpet} + * + * @apiNote This field is version-dependant and may be removed in future Minecraft versions + */ + @ApiStatus.Experimental + @MinecraftExperimental(MinecraftExperimental.Requires.WINTER_DROP) + public static final TypedKey PALE_MOSS_CARPET = create(key("pale_moss_carpet")); + + /** + * {@code minecraft:pale_oak_boat} + * + * @apiNote This field is version-dependant and may be removed in future Minecraft versions + */ + @ApiStatus.Experimental + @MinecraftExperimental(MinecraftExperimental.Requires.WINTER_DROP) + public static final TypedKey PALE_OAK_BOAT = create(key("pale_oak_boat")); + + /** + * {@code minecraft:pale_oak_button} + * + * @apiNote This field is version-dependant and may be removed in future Minecraft versions + */ + @ApiStatus.Experimental + @MinecraftExperimental(MinecraftExperimental.Requires.WINTER_DROP) + public static final TypedKey PALE_OAK_BUTTON = create(key("pale_oak_button")); + + /** + * {@code minecraft:pale_oak_chest_boat} + * + * @apiNote This field is version-dependant and may be removed in future Minecraft versions + */ + @ApiStatus.Experimental + @MinecraftExperimental(MinecraftExperimental.Requires.WINTER_DROP) + public static final TypedKey PALE_OAK_CHEST_BOAT = create(key("pale_oak_chest_boat")); + + /** + * {@code minecraft:pale_oak_door} + * + * @apiNote This field is version-dependant and may be removed in future Minecraft versions + */ + @ApiStatus.Experimental + @MinecraftExperimental(MinecraftExperimental.Requires.WINTER_DROP) + public static final TypedKey PALE_OAK_DOOR = create(key("pale_oak_door")); + + /** + * {@code minecraft:pale_oak_fence} + * + * @apiNote This field is version-dependant and may be removed in future Minecraft versions + */ + @ApiStatus.Experimental + @MinecraftExperimental(MinecraftExperimental.Requires.WINTER_DROP) + public static final TypedKey PALE_OAK_FENCE = create(key("pale_oak_fence")); + + /** + * {@code minecraft:pale_oak_fence_gate} + * + * @apiNote This field is version-dependant and may be removed in future Minecraft versions + */ + @ApiStatus.Experimental + @MinecraftExperimental(MinecraftExperimental.Requires.WINTER_DROP) + public static final TypedKey PALE_OAK_FENCE_GATE = create(key("pale_oak_fence_gate")); + + /** + * {@code minecraft:pale_oak_hanging_sign} + * + * @apiNote This field is version-dependant and may be removed in future Minecraft versions + */ + @ApiStatus.Experimental + @MinecraftExperimental(MinecraftExperimental.Requires.WINTER_DROP) + public static final TypedKey PALE_OAK_HANGING_SIGN = create(key("pale_oak_hanging_sign")); + + /** + * {@code minecraft:pale_oak_leaves} + * + * @apiNote This field is version-dependant and may be removed in future Minecraft versions + */ + @ApiStatus.Experimental + @MinecraftExperimental(MinecraftExperimental.Requires.WINTER_DROP) + public static final TypedKey PALE_OAK_LEAVES = create(key("pale_oak_leaves")); + + /** + * {@code minecraft:pale_oak_log} + * + * @apiNote This field is version-dependant and may be removed in future Minecraft versions + */ + @ApiStatus.Experimental + @MinecraftExperimental(MinecraftExperimental.Requires.WINTER_DROP) + public static final TypedKey PALE_OAK_LOG = create(key("pale_oak_log")); + + /** + * {@code minecraft:pale_oak_planks} + * + * @apiNote This field is version-dependant and may be removed in future Minecraft versions + */ + @ApiStatus.Experimental + @MinecraftExperimental(MinecraftExperimental.Requires.WINTER_DROP) + public static final TypedKey PALE_OAK_PLANKS = create(key("pale_oak_planks")); + + /** + * {@code minecraft:pale_oak_pressure_plate} + * + * @apiNote This field is version-dependant and may be removed in future Minecraft versions + */ + @ApiStatus.Experimental + @MinecraftExperimental(MinecraftExperimental.Requires.WINTER_DROP) + public static final TypedKey PALE_OAK_PRESSURE_PLATE = create(key("pale_oak_pressure_plate")); + + /** + * {@code minecraft:pale_oak_sapling} + * + * @apiNote This field is version-dependant and may be removed in future Minecraft versions + */ + @ApiStatus.Experimental + @MinecraftExperimental(MinecraftExperimental.Requires.WINTER_DROP) + public static final TypedKey PALE_OAK_SAPLING = create(key("pale_oak_sapling")); + + /** + * {@code minecraft:pale_oak_sign} + * + * @apiNote This field is version-dependant and may be removed in future Minecraft versions + */ + @ApiStatus.Experimental + @MinecraftExperimental(MinecraftExperimental.Requires.WINTER_DROP) + public static final TypedKey PALE_OAK_SIGN = create(key("pale_oak_sign")); + + /** + * {@code minecraft:pale_oak_slab} + * + * @apiNote This field is version-dependant and may be removed in future Minecraft versions + */ + @ApiStatus.Experimental + @MinecraftExperimental(MinecraftExperimental.Requires.WINTER_DROP) + public static final TypedKey PALE_OAK_SLAB = create(key("pale_oak_slab")); + + /** + * {@code minecraft:pale_oak_stairs} + * + * @apiNote This field is version-dependant and may be removed in future Minecraft versions + */ + @ApiStatus.Experimental + @MinecraftExperimental(MinecraftExperimental.Requires.WINTER_DROP) + public static final TypedKey PALE_OAK_STAIRS = create(key("pale_oak_stairs")); + + /** + * {@code minecraft:pale_oak_trapdoor} + * + * @apiNote This field is version-dependant and may be removed in future Minecraft versions + */ + @ApiStatus.Experimental + @MinecraftExperimental(MinecraftExperimental.Requires.WINTER_DROP) + public static final TypedKey PALE_OAK_TRAPDOOR = create(key("pale_oak_trapdoor")); + + /** + * {@code minecraft:pale_oak_wood} + * + * @apiNote This field is version-dependant and may be removed in future Minecraft versions + */ + @ApiStatus.Experimental + @MinecraftExperimental(MinecraftExperimental.Requires.WINTER_DROP) + public static final TypedKey PALE_OAK_WOOD = create(key("pale_oak_wood")); + /** * {@code minecraft:panda_spawn_egg} * @@ -6116,6 +6406,13 @@ public final class ItemTypeKeys { */ public static final TypedKey PINK_BED = create(key("pink_bed")); + /** + * {@code minecraft:pink_bundle} + * + * @apiNote This field is version-dependant and may be removed in future Minecraft versions + */ + public static final TypedKey PINK_BUNDLE = create(key("pink_bundle")); + /** * {@code minecraft:pink_candle} * @@ -6641,6 +6938,13 @@ public final class ItemTypeKeys { */ public static final TypedKey PURPLE_BED = create(key("purple_bed")); + /** + * {@code minecraft:purple_bundle} + * + * @apiNote This field is version-dependant and may be removed in future Minecraft versions + */ + public static final TypedKey PURPLE_BUNDLE = create(key("purple_bundle")); + /** * {@code minecraft:purple_candle} * @@ -6907,6 +7211,13 @@ public final class ItemTypeKeys { */ public static final TypedKey RED_BED = create(key("red_bed")); + /** + * {@code minecraft:red_bundle} + * + * @apiNote This field is version-dependant and may be removed in future Minecraft versions + */ + public static final TypedKey RED_BUNDLE = create(key("red_bundle")); + /** * {@code minecraft:red_candle} * @@ -8097,6 +8408,24 @@ public final class ItemTypeKeys { */ public static final TypedKey STRIPPED_OAK_WOOD = create(key("stripped_oak_wood")); + /** + * {@code minecraft:stripped_pale_oak_log} + * + * @apiNote This field is version-dependant and may be removed in future Minecraft versions + */ + @ApiStatus.Experimental + @MinecraftExperimental(MinecraftExperimental.Requires.WINTER_DROP) + public static final TypedKey STRIPPED_PALE_OAK_LOG = create(key("stripped_pale_oak_log")); + + /** + * {@code minecraft:stripped_pale_oak_wood} + * + * @apiNote This field is version-dependant and may be removed in future Minecraft versions + */ + @ApiStatus.Experimental + @MinecraftExperimental(MinecraftExperimental.Requires.WINTER_DROP) + public static final TypedKey STRIPPED_PALE_OAK_WOOD = create(key("stripped_pale_oak_wood")); + /** * {@code minecraft:stripped_spruce_log} * @@ -9028,6 +9357,13 @@ public final class ItemTypeKeys { */ public static final TypedKey WHITE_BED = create(key("white_bed")); + /** + * {@code minecraft:white_bundle} + * + * @apiNote This field is version-dependant and may be removed in future Minecraft versions + */ + public static final TypedKey WHITE_BUNDLE = create(key("white_bundle")); + /** * {@code minecraft:white_candle} * @@ -9238,6 +9574,13 @@ public final class ItemTypeKeys { */ public static final TypedKey YELLOW_BED = create(key("yellow_bed")); + /** + * {@code minecraft:yellow_bundle} + * + * @apiNote This field is version-dependant and may be removed in future Minecraft versions + */ + public static final TypedKey YELLOW_BUNDLE = create(key("yellow_bundle")); + /** * {@code minecraft:yellow_candle} * diff --git a/paper-api-generator/generated/io/papermc/paper/registry/keys/JukeboxSongKeys.java b/paper-api-generator/generated/io/papermc/paper/registry/keys/JukeboxSongKeys.java index 4789b7e5717f..c001a5b46e1e 100644 --- a/paper-api-generator/generated/io/papermc/paper/registry/keys/JukeboxSongKeys.java +++ b/paper-api-generator/generated/io/papermc/paper/registry/keys/JukeboxSongKeys.java @@ -23,7 +23,7 @@ "unused", "SpellCheckingInspection" }) -@GeneratedFrom("1.21.1") +@GeneratedFrom("1.21.3") @ApiStatus.Experimental public final class JukeboxSongKeys { /** diff --git a/paper-api-generator/generated/io/papermc/paper/registry/keys/MapDecorationTypeKeys.java b/paper-api-generator/generated/io/papermc/paper/registry/keys/MapDecorationTypeKeys.java index 6aa67743ec97..ac4d4b0c6422 100644 --- a/paper-api-generator/generated/io/papermc/paper/registry/keys/MapDecorationTypeKeys.java +++ b/paper-api-generator/generated/io/papermc/paper/registry/keys/MapDecorationTypeKeys.java @@ -23,7 +23,7 @@ "unused", "SpellCheckingInspection" }) -@GeneratedFrom("1.21.1") +@GeneratedFrom("1.21.3") @ApiStatus.Experimental public final class MapDecorationTypeKeys { /** diff --git a/paper-api-generator/generated/io/papermc/paper/registry/keys/MenuTypeKeys.java b/paper-api-generator/generated/io/papermc/paper/registry/keys/MenuTypeKeys.java index 98e9563029f9..b2ad502cc91a 100644 --- a/paper-api-generator/generated/io/papermc/paper/registry/keys/MenuTypeKeys.java +++ b/paper-api-generator/generated/io/papermc/paper/registry/keys/MenuTypeKeys.java @@ -23,7 +23,7 @@ "unused", "SpellCheckingInspection" }) -@GeneratedFrom("1.21.1") +@GeneratedFrom("1.21.3") @ApiStatus.Experimental public final class MenuTypeKeys { /** diff --git a/paper-api-generator/generated/io/papermc/paper/registry/keys/MobEffectKeys.java b/paper-api-generator/generated/io/papermc/paper/registry/keys/MobEffectKeys.java index 621705fe8ade..d4f7f8372d05 100644 --- a/paper-api-generator/generated/io/papermc/paper/registry/keys/MobEffectKeys.java +++ b/paper-api-generator/generated/io/papermc/paper/registry/keys/MobEffectKeys.java @@ -23,7 +23,7 @@ "unused", "SpellCheckingInspection" }) -@GeneratedFrom("1.21.1") +@GeneratedFrom("1.21.3") @ApiStatus.Experimental public final class MobEffectKeys { /** diff --git a/paper-api-generator/generated/io/papermc/paper/registry/keys/StructureKeys.java b/paper-api-generator/generated/io/papermc/paper/registry/keys/StructureKeys.java index a49c8cff2eb0..902e04a8c98b 100644 --- a/paper-api-generator/generated/io/papermc/paper/registry/keys/StructureKeys.java +++ b/paper-api-generator/generated/io/papermc/paper/registry/keys/StructureKeys.java @@ -23,7 +23,7 @@ "unused", "SpellCheckingInspection" }) -@GeneratedFrom("1.21.1") +@GeneratedFrom("1.21.3") @ApiStatus.Experimental public final class StructureKeys { /** diff --git a/paper-api-generator/generated/io/papermc/paper/registry/keys/StructureTypeKeys.java b/paper-api-generator/generated/io/papermc/paper/registry/keys/StructureTypeKeys.java index 353c0fa3022e..5a2547a6a1f1 100644 --- a/paper-api-generator/generated/io/papermc/paper/registry/keys/StructureTypeKeys.java +++ b/paper-api-generator/generated/io/papermc/paper/registry/keys/StructureTypeKeys.java @@ -23,7 +23,7 @@ "unused", "SpellCheckingInspection" }) -@GeneratedFrom("1.21.1") +@GeneratedFrom("1.21.3") @ApiStatus.Experimental public final class StructureTypeKeys { /** diff --git a/paper-api-generator/generated/io/papermc/paper/registry/keys/TrimMaterialKeys.java b/paper-api-generator/generated/io/papermc/paper/registry/keys/TrimMaterialKeys.java index e75f850a3169..775ee696bc0c 100644 --- a/paper-api-generator/generated/io/papermc/paper/registry/keys/TrimMaterialKeys.java +++ b/paper-api-generator/generated/io/papermc/paper/registry/keys/TrimMaterialKeys.java @@ -23,7 +23,7 @@ "unused", "SpellCheckingInspection" }) -@GeneratedFrom("1.21.1") +@GeneratedFrom("1.21.3") @ApiStatus.Experimental public final class TrimMaterialKeys { /** diff --git a/paper-api-generator/generated/io/papermc/paper/registry/keys/TrimPatternKeys.java b/paper-api-generator/generated/io/papermc/paper/registry/keys/TrimPatternKeys.java index 65d2d63e8541..f136c6079c08 100644 --- a/paper-api-generator/generated/io/papermc/paper/registry/keys/TrimPatternKeys.java +++ b/paper-api-generator/generated/io/papermc/paper/registry/keys/TrimPatternKeys.java @@ -23,7 +23,7 @@ "unused", "SpellCheckingInspection" }) -@GeneratedFrom("1.21.1") +@GeneratedFrom("1.21.3") @ApiStatus.Experimental public final class TrimPatternKeys { /** diff --git a/paper-api-generator/generated/io/papermc/paper/registry/keys/VillagerProfessionKeys.java b/paper-api-generator/generated/io/papermc/paper/registry/keys/VillagerProfessionKeys.java index 6d8d0801bfab..9942699938d6 100644 --- a/paper-api-generator/generated/io/papermc/paper/registry/keys/VillagerProfessionKeys.java +++ b/paper-api-generator/generated/io/papermc/paper/registry/keys/VillagerProfessionKeys.java @@ -23,7 +23,7 @@ "unused", "SpellCheckingInspection" }) -@GeneratedFrom("1.21.1") +@GeneratedFrom("1.21.3") @ApiStatus.Experimental public final class VillagerProfessionKeys { /** diff --git a/paper-api-generator/generated/io/papermc/paper/registry/keys/VillagerTypeKeys.java b/paper-api-generator/generated/io/papermc/paper/registry/keys/VillagerTypeKeys.java index 524880dbaa44..f2033c1164d6 100644 --- a/paper-api-generator/generated/io/papermc/paper/registry/keys/VillagerTypeKeys.java +++ b/paper-api-generator/generated/io/papermc/paper/registry/keys/VillagerTypeKeys.java @@ -23,7 +23,7 @@ "unused", "SpellCheckingInspection" }) -@GeneratedFrom("1.21.1") +@GeneratedFrom("1.21.3") @ApiStatus.Experimental public final class VillagerTypeKeys { /** diff --git a/paper-api-generator/generated/io/papermc/paper/registry/keys/WolfVariantKeys.java b/paper-api-generator/generated/io/papermc/paper/registry/keys/WolfVariantKeys.java index 7108d41c8d1f..ab6358053532 100644 --- a/paper-api-generator/generated/io/papermc/paper/registry/keys/WolfVariantKeys.java +++ b/paper-api-generator/generated/io/papermc/paper/registry/keys/WolfVariantKeys.java @@ -23,7 +23,7 @@ "unused", "SpellCheckingInspection" }) -@GeneratedFrom("1.21.1") +@GeneratedFrom("1.21.3") @ApiStatus.Experimental public final class WolfVariantKeys { /** diff --git a/paper-api-generator/generated/io/papermc/paper/registry/keys/tags/EnchantmentTagKeys.java b/paper-api-generator/generated/io/papermc/paper/registry/keys/tags/EnchantmentTagKeys.java index 5d2a22742f57..e1f25062d428 100644 --- a/paper-api-generator/generated/io/papermc/paper/registry/keys/tags/EnchantmentTagKeys.java +++ b/paper-api-generator/generated/io/papermc/paper/registry/keys/tags/EnchantmentTagKeys.java @@ -24,7 +24,7 @@ "unused", "SpellCheckingInspection" }) -@GeneratedFrom("1.21.1") +@GeneratedFrom("1.21.3") @ApiStatus.Experimental public final class EnchantmentTagKeys { /** diff --git a/paper-api-generator/generated/io/papermc/paper/registry/keys/tags/ItemTypeTagKeys.java b/paper-api-generator/generated/io/papermc/paper/registry/keys/tags/ItemTypeTagKeys.java index b0a153f67010..8ec601a311ea 100644 --- a/paper-api-generator/generated/io/papermc/paper/registry/keys/tags/ItemTypeTagKeys.java +++ b/paper-api-generator/generated/io/papermc/paper/registry/keys/tags/ItemTypeTagKeys.java @@ -6,6 +6,7 @@ import io.papermc.paper.registry.RegistryKey; import io.papermc.paper.registry.tag.TagKey; import net.kyori.adventure.key.Key; +import org.bukkit.MinecraftExperimental; import org.bukkit.inventory.ItemType; import org.checkerframework.checker.nullness.qual.NonNull; import org.jetbrains.annotations.ApiStatus; @@ -23,7 +24,7 @@ "unused", "SpellCheckingInspection" }) -@GeneratedFrom("1.21.1") +@GeneratedFrom("1.21.3") @ApiStatus.Experimental public final class ItemTypeTagKeys { /** @@ -131,6 +132,20 @@ public final class ItemTypeTagKeys { */ public static final TagKey BREAKS_DECORATED_POTS = create(key("breaks_decorated_pots")); + /** + * {@code #minecraft:brewing_fuel} + * + * @apiNote This field is version-dependant and may be removed in future Minecraft versions + */ + public static final TagKey BREWING_FUEL = create(key("brewing_fuel")); + + /** + * {@code #minecraft:bundles} + * + * @apiNote This field is version-dependant and may be removed in future Minecraft versions + */ + public static final TagKey BUNDLES = create(key("bundles")); + /** * {@code #minecraft:buttons} * @@ -292,6 +307,13 @@ public final class ItemTypeTagKeys { */ public static final TagKey DIAMOND_ORES = create(key("diamond_ores")); + /** + * {@code #minecraft:diamond_tool_materials} + * + * @apiNote This field is version-dependant and may be removed in future Minecraft versions + */ + public static final TagKey DIAMOND_TOOL_MATERIALS = create(key("diamond_tool_materials")); + /** * {@code #minecraft:dirt} * @@ -306,6 +328,13 @@ public final class ItemTypeTagKeys { */ public static final TagKey DOORS = create(key("doors")); + /** + * {@code #minecraft:duplicates_allays} + * + * @apiNote This field is version-dependant and may be removed in future Minecraft versions + */ + public static final TagKey DUPLICATES_ALLAYS = create(key("duplicates_allays")); + /** * {@code #minecraft:dyeable} * @@ -509,6 +538,20 @@ public final class ItemTypeTagKeys { */ public static final TagKey FROG_FOOD = create(key("frog_food")); + /** + * {@code #minecraft:furnace_minecart_fuel} + * + * @apiNote This field is version-dependant and may be removed in future Minecraft versions + */ + public static final TagKey FURNACE_MINECART_FUEL = create(key("furnace_minecart_fuel")); + + /** + * {@code #minecraft:gaze_disguise_equipment} + * + * @apiNote This field is version-dependant and may be removed in future Minecraft versions + */ + public static final TagKey GAZE_DISGUISE_EQUIPMENT = create(key("gaze_disguise_equipment")); + /** * {@code #minecraft:goat_food} * @@ -523,6 +566,13 @@ public final class ItemTypeTagKeys { */ public static final TagKey GOLD_ORES = create(key("gold_ores")); + /** + * {@code #minecraft:gold_tool_materials} + * + * @apiNote This field is version-dependant and may be removed in future Minecraft versions + */ + public static final TagKey GOLD_TOOL_MATERIALS = create(key("gold_tool_materials")); + /** * {@code #minecraft:hanging_signs} * @@ -579,6 +629,13 @@ public final class ItemTypeTagKeys { */ public static final TagKey IRON_ORES = create(key("iron_ores")); + /** + * {@code #minecraft:iron_tool_materials} + * + * @apiNote This field is version-dependant and may be removed in future Minecraft versions + */ + public static final TagKey IRON_TOOL_MATERIALS = create(key("iron_tool_materials")); + /** * {@code #minecraft:jungle_logs} * @@ -649,6 +706,13 @@ public final class ItemTypeTagKeys { */ public static final TagKey MANGROVE_LOGS = create(key("mangrove_logs")); + /** + * {@code #minecraft:map_invisibility_equipment} + * + * @apiNote This field is version-dependant and may be removed in future Minecraft versions + */ + public static final TagKey MAP_INVISIBILITY_EQUIPMENT = create(key("map_invisibility_equipment")); + /** * {@code #minecraft:meat} * @@ -656,6 +720,13 @@ public final class ItemTypeTagKeys { */ public static final TagKey MEAT = create(key("meat")); + /** + * {@code #minecraft:netherite_tool_materials} + * + * @apiNote This field is version-dependant and may be removed in future Minecraft versions + */ + public static final TagKey NETHERITE_TOOL_MATERIALS = create(key("netherite_tool_materials")); + /** * {@code #minecraft:non_flammable_wood} * @@ -684,6 +755,22 @@ public final class ItemTypeTagKeys { */ public static final TagKey OCELOT_FOOD = create(key("ocelot_food")); + /** + * {@code #minecraft:pale_oak_logs} + * + * @apiNote This field is version-dependant and may be removed in future Minecraft versions + */ + @ApiStatus.Experimental + @MinecraftExperimental(MinecraftExperimental.Requires.WINTER_DROP) + public static final TagKey PALE_OAK_LOGS = create(key("pale_oak_logs")); + + /** + * {@code #minecraft:panda_eats_from_ground} + * + * @apiNote This field is version-dependant and may be removed in future Minecraft versions + */ + public static final TagKey PANDA_EATS_FROM_GROUND = create(key("panda_eats_from_ground")); + /** * {@code #minecraft:panda_food} * @@ -740,6 +827,13 @@ public final class ItemTypeTagKeys { */ public static final TagKey PIGLIN_REPELLENTS = create(key("piglin_repellents")); + /** + * {@code #minecraft:piglin_safe_armor} + * + * @apiNote This field is version-dependant and may be removed in future Minecraft versions + */ + public static final TagKey PIGLIN_SAFE_ARMOR = create(key("piglin_safe_armor")); + /** * {@code #minecraft:planks} * @@ -768,6 +862,62 @@ public final class ItemTypeTagKeys { */ public static final TagKey REDSTONE_ORES = create(key("redstone_ores")); + /** + * {@code #minecraft:repairs_chain_armor} + * + * @apiNote This field is version-dependant and may be removed in future Minecraft versions + */ + public static final TagKey REPAIRS_CHAIN_ARMOR = create(key("repairs_chain_armor")); + + /** + * {@code #minecraft:repairs_diamond_armor} + * + * @apiNote This field is version-dependant and may be removed in future Minecraft versions + */ + public static final TagKey REPAIRS_DIAMOND_ARMOR = create(key("repairs_diamond_armor")); + + /** + * {@code #minecraft:repairs_gold_armor} + * + * @apiNote This field is version-dependant and may be removed in future Minecraft versions + */ + public static final TagKey REPAIRS_GOLD_ARMOR = create(key("repairs_gold_armor")); + + /** + * {@code #minecraft:repairs_iron_armor} + * + * @apiNote This field is version-dependant and may be removed in future Minecraft versions + */ + public static final TagKey REPAIRS_IRON_ARMOR = create(key("repairs_iron_armor")); + + /** + * {@code #minecraft:repairs_leather_armor} + * + * @apiNote This field is version-dependant and may be removed in future Minecraft versions + */ + public static final TagKey REPAIRS_LEATHER_ARMOR = create(key("repairs_leather_armor")); + + /** + * {@code #minecraft:repairs_netherite_armor} + * + * @apiNote This field is version-dependant and may be removed in future Minecraft versions + */ + public static final TagKey REPAIRS_NETHERITE_ARMOR = create(key("repairs_netherite_armor")); + + /** + * {@code #minecraft:repairs_turtle_helmet} + * + * @apiNote This field is version-dependant and may be removed in future Minecraft versions + */ + public static final TagKey REPAIRS_TURTLE_HELMET = create(key("repairs_turtle_helmet")); + + /** + * {@code #minecraft:repairs_wolf_armor} + * + * @apiNote This field is version-dependant and may be removed in future Minecraft versions + */ + public static final TagKey REPAIRS_WOLF_ARMOR = create(key("repairs_wolf_armor")); + /** * {@code #minecraft:sand} * @@ -796,6 +946,13 @@ public final class ItemTypeTagKeys { */ public static final TagKey SHOVELS = create(key("shovels")); + /** + * {@code #minecraft:shulker_boxes} + * + * @apiNote This field is version-dependant and may be removed in future Minecraft versions + */ + public static final TagKey SHULKER_BOXES = create(key("shulker_boxes")); + /** * {@code #minecraft:signs} * @@ -957,6 +1114,13 @@ public final class ItemTypeTagKeys { */ public static final TagKey TURTLE_FOOD = create(key("turtle_food")); + /** + * {@code #minecraft:villager_picks_up} + * + * @apiNote This field is version-dependant and may be removed in future Minecraft versions + */ + public static final TagKey VILLAGER_PICKS_UP = create(key("villager_picks_up")); + /** * {@code #minecraft:villager_plantable_seeds} * @@ -1034,6 +1198,13 @@ public final class ItemTypeTagKeys { */ public static final TagKey WOODEN_STAIRS = create(key("wooden_stairs")); + /** + * {@code #minecraft:wooden_tool_materials} + * + * @apiNote This field is version-dependant and may be removed in future Minecraft versions + */ + public static final TagKey WOODEN_TOOL_MATERIALS = create(key("wooden_tool_materials")); + /** * {@code #minecraft:wooden_trapdoors} * diff --git a/paper-api-generator/src/main/java/io/papermc/generator/Main.java b/paper-api-generator/src/main/java/io/papermc/generator/Main.java index bec7a0c37362..129009f519f1 100644 --- a/paper-api-generator/src/main/java/io/papermc/generator/Main.java +++ b/paper-api-generator/src/main/java/io/papermc/generator/Main.java @@ -7,22 +7,25 @@ import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; +import java.util.List; import java.util.Map; import net.minecraft.SharedConstants; import net.minecraft.commands.Commands; +import net.minecraft.core.HolderLookup; import net.minecraft.core.LayeredRegistryAccess; +import net.minecraft.core.Registry; import net.minecraft.core.RegistryAccess; import net.minecraft.resources.RegistryDataLoader; import net.minecraft.server.Bootstrap; import net.minecraft.server.RegistryLayer; import net.minecraft.server.ReloadableServerResources; -import net.minecraft.server.WorldLoader; import net.minecraft.server.packs.PackType; import net.minecraft.server.packs.repository.Pack; import net.minecraft.server.packs.repository.PackRepository; import net.minecraft.server.packs.repository.ServerPacksSource; import net.minecraft.server.packs.resources.MultiPackResourceManager; import net.minecraft.tags.TagKey; +import net.minecraft.tags.TagLoader; import net.minecraft.world.flag.FeatureFlags; import org.apache.commons.io.file.PathUtils; import org.slf4j.Logger; @@ -42,11 +45,22 @@ public final class Main { resourceRepository.reload(); final MultiPackResourceManager resourceManager = new MultiPackResourceManager(PackType.SERVER_DATA, resourceRepository.getAvailablePacks().stream().map(Pack::open).toList()); LayeredRegistryAccess layers = RegistryLayer.createRegistryAccess(); - layers = WorldLoader.loadAndReplaceLayer(resourceManager, layers, RegistryLayer.WORLDGEN, RegistryDataLoader.WORLDGEN_REGISTRIES); + final List> pendingTags = TagLoader.loadTagsForExistingRegistries(resourceManager, layers.getLayer(RegistryLayer.STATIC)); + final List> worldGenLayer = TagLoader.buildUpdatedLookups(layers.getAccessForLoading(RegistryLayer.WORLDGEN), pendingTags); + final RegistryAccess.Frozen frozenWorldgenRegistries = RegistryDataLoader.load(resourceManager, worldGenLayer, RegistryDataLoader.WORLDGEN_REGISTRIES); + layers = layers.replaceFrom(RegistryLayer.WORLDGEN, frozenWorldgenRegistries); REGISTRY_ACCESS = layers.compositeAccess().freeze(); - final ReloadableServerResources datapack = ReloadableServerResources.loadResources(resourceManager, layers, FeatureFlags.REGISTRY.allFlags(), Commands.CommandSelection.DEDICATED, 0, MoreExecutors.directExecutor(), MoreExecutors.directExecutor()).join(); - datapack.updateRegistryTags(); - + final ReloadableServerResources reloadableServerResources = ReloadableServerResources.loadResources( + resourceManager, + layers, + pendingTags, + FeatureFlags.VANILLA_SET, + Commands.CommandSelection.DEDICATED, + 0, + MoreExecutors.directExecutor(), + MoreExecutors.directExecutor() + ).join(); + reloadableServerResources.updateStaticRegistryTags(); EXPERIMENTAL_TAGS = TagCollector.grabExperimental(resourceManager); } diff --git a/paper-api-generator/src/main/java/io/papermc/generator/types/GeneratedKeyType.java b/paper-api-generator/src/main/java/io/papermc/generator/types/GeneratedKeyType.java index cf6846134a57..54ed67b2b7bf 100644 --- a/paper-api-generator/src/main/java/io/papermc/generator/types/GeneratedKeyType.java +++ b/paper-api-generator/src/main/java/io/papermc/generator/types/GeneratedKeyType.java @@ -23,14 +23,19 @@ import java.util.Map; import java.util.Set; import java.util.stream.Collectors; +import javax.lang.model.SourceVersion; import net.kyori.adventure.key.Key; import net.minecraft.core.Holder; import net.minecraft.core.HolderLookup; import net.minecraft.core.Registry; import net.minecraft.core.RegistrySetBuilder; +import net.minecraft.core.registries.Registries; import net.minecraft.data.registries.VanillaRegistries; +import net.minecraft.data.registries.WinterDropRegistries; import net.minecraft.resources.ResourceKey; import net.minecraft.world.flag.FeatureElement; +import net.minecraft.world.flag.FeatureFlags; +import org.bukkit.MinecraftExperimental; import org.checkerframework.checker.nullness.qual.NonNull; import org.checkerframework.checker.nullness.qual.Nullable; import org.checkerframework.framework.qual.DefaultQualifier; @@ -51,7 +56,8 @@ public class GeneratedKeyType extends SimpleGenerator { private static final Map>, RegistrySetBuilder.RegistryBootstrap> VANILLA_REGISTRY_ENTRIES = VanillaRegistries.BUILDER.entries.stream() .collect(Collectors.toMap(RegistrySetBuilder.RegistryStub::key, RegistrySetBuilder.RegistryStub::bootstrap)); - private static final Map>, RegistrySetBuilder.RegistryBootstrap> EXPERIMENTAL_REGISTRY_ENTRIES = Collections.emptyMap(); // Update for Experimental API + private static final Map>, RegistrySetBuilder.RegistryBootstrap> EXPERIMENTAL_REGISTRY_ENTRIES = WinterDropRegistries.BUILDER.entries.stream() + .collect(Collectors.toMap(RegistrySetBuilder.RegistryStub::key, RegistrySetBuilder.RegistryStub::bootstrap)); // Update for Experimental API private static final Map, String> REGISTRY_KEY_FIELD_NAMES; static { @@ -116,6 +122,13 @@ private TypeSpec.Builder keyHolderType() { ); } + @Deprecated + private static final Map JUKEBOX_SONG_NAMES = Map.of( + "5", "FIVE", + "11", "ELEVEN", + "13", "THIRTEEN" + ); + @Override protected TypeSpec getTypeSpec() { final TypeName typedKey = ParameterizedTypeName.get(TypedKey.class, this.apiType); @@ -123,19 +136,23 @@ protected TypeSpec getTypeSpec() { final TypeSpec.Builder typeBuilder = this.keyHolderType(); final MethodSpec.Builder createMethod = this.createMethod(typedKey); - final Registry registry = Main.REGISTRY_ACCESS.registryOrThrow(this.registryKey); + final Registry registry = Main.REGISTRY_ACCESS.lookupOrThrow(this.registryKey); final Set> experimental = this.collectExperimentalKeys(registry); boolean allExperimental = true; - for (final Holder.Reference reference : registry.holders().sorted(Formatting.alphabeticKeyOrder(reference -> reference.key().location().getPath())).toList()) { + for (final Holder.Reference reference : registry.listElements().sorted(Formatting.alphabeticKeyOrder(reference -> reference.key().location().getPath())).toList()) { final ResourceKey key = reference.key(); final String keyPath = key.location().getPath(); - final String fieldName = Formatting.formatKeyAsField(keyPath); + String fieldName = Formatting.formatKeyAsField(keyPath); + if (!SourceVersion.isIdentifier(fieldName) && this.registryKey.equals(Registries.JUKEBOX_SONG) && JUKEBOX_SONG_NAMES.containsKey(fieldName)) { + fieldName = JUKEBOX_SONG_NAMES.get(fieldName); + } + final FieldSpec.Builder fieldBuilder = FieldSpec.builder(typedKey, fieldName, PUBLIC, STATIC, FINAL) .initializer("$N(key($S))", createMethod.build(), keyPath) .addJavadoc(Javadocs.getVersionDependentField("{@code $L}"), key.location().toString()); if (experimental.contains(key)) { - fieldBuilder.addAnnotations(experimentalAnnotations(null)); // Update for Experimental API + fieldBuilder.addAnnotations(experimentalAnnotations(MinecraftExperimental.Requires.WINTER_DROP)); // Update for Experimental API } else { allExperimental = false; } @@ -150,6 +167,7 @@ protected TypeSpec getTypeSpec() { return typeBuilder.addMethod(createMethod.build()).build(); } + // todo at some point this should be per feature data pack not all merged private Set> collectExperimentalKeys(final Registry registry) { if (FeatureElement.FILTERED_REGISTRIES.contains(registry.key())) { return this.collectExperimentalKeysBuiltIn(registry); @@ -159,8 +177,8 @@ private Set> collectExperimentalKeys(final Registry registry) } private Set> collectExperimentalKeysBuiltIn(final Registry registry) { - final HolderLookup.RegistryLookup filteredLookup = registry.asLookup().filterElements(v -> { - return false; // Update for Experimental API + final HolderLookup.RegistryLookup filteredLookup = registry.filterElements(v -> { + return v instanceof final FeatureElement featureElement && FeatureFlags.isExperimental(featureElement.requiredFeatures()); // Update for Experimental API }); return filteredLookup.listElementIds().collect(Collectors.toUnmodifiableSet()); } diff --git a/paper-api-generator/src/main/java/io/papermc/generator/types/GeneratedTagKeyType.java b/paper-api-generator/src/main/java/io/papermc/generator/types/GeneratedTagKeyType.java index ff8d8b612c37..446611a45dd6 100644 --- a/paper-api-generator/src/main/java/io/papermc/generator/types/GeneratedTagKeyType.java +++ b/paper-api-generator/src/main/java/io/papermc/generator/types/GeneratedTagKeyType.java @@ -16,6 +16,7 @@ import java.lang.reflect.Field; import java.lang.reflect.Modifier; import java.util.HashMap; +import java.util.Locale; import java.util.Map; import java.util.concurrent.atomic.AtomicBoolean; import net.kyori.adventure.key.Key; @@ -105,16 +106,17 @@ protected TypeSpec getTypeSpec() { final TypeSpec.Builder typeBuilder = this.keyHolderType(); final MethodSpec.Builder createMethod = this.createMethod(tagKey); - final Registry registry = Main.REGISTRY_ACCESS.registryOrThrow(this.registryKey); + final Registry registry = Main.REGISTRY_ACCESS.lookupOrThrow(this.registryKey); final AtomicBoolean allExperimental = new AtomicBoolean(true); - registry.getTagNames().sorted(Formatting.alphabeticKeyOrder(nmsTagKey -> nmsTagKey.location().getPath())).forEach(nmsTagKey -> { + registry.listTagIds().sorted(Formatting.alphabeticKeyOrder(nmsTagKey -> nmsTagKey.location().getPath())).forEach(nmsTagKey -> { final String fieldName = Formatting.formatKeyAsField(nmsTagKey.location().getPath()); final FieldSpec.Builder fieldBuilder = FieldSpec.builder(tagKey, fieldName, PUBLIC, STATIC, FINAL) .initializer("$N(key($S))", createMethod.build(), nmsTagKey.location().getPath()) .addJavadoc(Javadocs.getVersionDependentField("{@code $L}"), "#" + nmsTagKey.location()); - if (Main.EXPERIMENTAL_TAGS.containsKey(nmsTagKey)) { - fieldBuilder.addAnnotations(experimentalAnnotations(MinecraftExperimental.Requires.TRADE_REBALANCE)); // Update for Experimental API + final String featureFlagName = Main.EXPERIMENTAL_TAGS.get(nmsTagKey); + if (featureFlagName != null) { + fieldBuilder.addAnnotations(experimentalAnnotations(MinecraftExperimental.Requires.valueOf(featureFlagName.toUpperCase(Locale.ENGLISH)))); // Update for Experimental API } else { allExperimental.set(false); } diff --git a/paper-api-generator/src/main/java/io/papermc/generator/types/SimpleGenerator.java b/paper-api-generator/src/main/java/io/papermc/generator/types/SimpleGenerator.java index b4c1095c84aa..3608b449f821 100644 --- a/paper-api-generator/src/main/java/io/papermc/generator/types/SimpleGenerator.java +++ b/paper-api-generator/src/main/java/io/papermc/generator/types/SimpleGenerator.java @@ -31,5 +31,4 @@ public void writeToFile(Path parent) throws IOException { builder.build().writeTo(parent, StandardCharsets.UTF_8); } - } diff --git a/paper-api-generator/src/main/java/io/papermc/generator/types/goal/MobGoalGenerator.java b/paper-api-generator/src/main/java/io/papermc/generator/types/goal/MobGoalGenerator.java index ffe3c485724a..86e2294ae7d0 100644 --- a/paper-api-generator/src/main/java/io/papermc/generator/types/goal/MobGoalGenerator.java +++ b/paper-api-generator/src/main/java/io/papermc/generator/types/goal/MobGoalGenerator.java @@ -1,6 +1,5 @@ package io.papermc.generator.types.goal; -import com.destroystokyo.paper.entity.RangedEntity; import com.destroystokyo.paper.entity.ai.GoalKey; import com.squareup.javapoet.ClassName; import com.squareup.javapoet.FieldSpec; @@ -19,44 +18,10 @@ import io.papermc.generator.utils.Javadocs; import java.util.Comparator; import java.util.List; -import javax.lang.model.element.Modifier; import net.minecraft.world.entity.ai.goal.Goal; import net.minecraft.world.entity.ai.goal.WrappedGoal; import org.bukkit.NamespacedKey; -import org.bukkit.entity.AbstractHorse; -import org.bukkit.entity.Blaze; -import org.bukkit.entity.Cat; -import org.bukkit.entity.Creature; -import org.bukkit.entity.Dolphin; -import org.bukkit.entity.Drowned; -import org.bukkit.entity.Enderman; -import org.bukkit.entity.Evoker; -import org.bukkit.entity.Fish; -import org.bukkit.entity.Fox; -import org.bukkit.entity.Ghast; -import org.bukkit.entity.Llama; import org.bukkit.entity.Mob; -import org.bukkit.entity.Monster; -import org.bukkit.entity.Panda; -import org.bukkit.entity.Parrot; -import org.bukkit.entity.PigZombie; -import org.bukkit.entity.PolarBear; -import org.bukkit.entity.Rabbit; -import org.bukkit.entity.Raider; -import org.bukkit.entity.Ravager; -import org.bukkit.entity.Shulker; -import org.bukkit.entity.Silverfish; -import org.bukkit.entity.SkeletonHorse; -import org.bukkit.entity.Slime; -import org.bukkit.entity.Spellcaster; -import org.bukkit.entity.Spider; -import org.bukkit.entity.Squid; -import org.bukkit.entity.Tameable; -import org.bukkit.entity.TraderLlama; -import org.bukkit.entity.Turtle; -import org.bukkit.entity.Vex; -import org.bukkit.entity.Vindicator; -import org.bukkit.entity.WanderingTrader; import org.checkerframework.checker.nullness.qual.NonNull; import org.checkerframework.framework.qual.DefaultQualifier; import org.jetbrains.annotations.Nullable; @@ -134,10 +99,4 @@ protected TypeSpec getTypeSpec() { protected JavaFile.Builder file(JavaFile.Builder builder) { return builder; } - - record DeprecatedEntry(Class entity, String entryName, @Nullable String removalVersion, - @Nullable String removedVersion) { - - } - } diff --git a/paper-api-generator/src/main/java/io/papermc/generator/types/goal/MobGoalNames.java b/paper-api-generator/src/main/java/io/papermc/generator/types/goal/MobGoalNames.java index 1bd6342f6191..07c6d5071972 100644 --- a/paper-api-generator/src/main/java/io/papermc/generator/types/goal/MobGoalNames.java +++ b/paper-api-generator/src/main/java/io/papermc/generator/types/goal/MobGoalNames.java @@ -236,6 +236,9 @@ public class MobGoalNames { bukkitMap.put(net.minecraft.world.entity.monster.breeze.Breeze.class, org.bukkit.entity.Breeze.class); bukkitMap.put(net.minecraft.world.entity.animal.armadillo.Armadillo.class, org.bukkit.entity.Armadillo.class); bukkitMap.put(net.minecraft.world.entity.monster.Bogged.class, org.bukkit.entity.Bogged.class); + bukkitMap.put(net.minecraft.world.entity.monster.creaking.Creaking.class, org.bukkit.entity.Creaking.class); + bukkitMap.put(net.minecraft.world.entity.monster.creaking.CreakingTransient.class, org.bukkit.entity.CreakingTransient.class); + bukkitMap.put(net.minecraft.world.entity.animal.AgeableWaterCreature.class, org.bukkit.entity.Squid.class); // close enough // } @@ -268,6 +271,7 @@ public static String getUsableName(String name) { name = cut; } } + name = name.replace("PathfinderGoal", ""); name = name.replace("TargetGoal", ""); name = name.replace("Goal", ""); diff --git a/paper-api-generator/src/main/java/io/papermc/generator/utils/CollectingContext.java b/paper-api-generator/src/main/java/io/papermc/generator/utils/CollectingContext.java index bb0687aa24dd..c2fbaa2a0518 100644 --- a/paper-api-generator/src/main/java/io/papermc/generator/utils/CollectingContext.java +++ b/paper-api-generator/src/main/java/io/papermc/generator/utils/CollectingContext.java @@ -18,11 +18,11 @@ public record CollectingContext(Set> registered, @Override public Holder.Reference register(final ResourceKey resourceKey, final @NonNull T t, final Lifecycle lifecycle) { this.registered.add(resourceKey); - return Holder.Reference.createStandAlone(this.registry.holderOwner(), resourceKey); + return Holder.Reference.createStandAlone(this.registry, resourceKey); } @Override public HolderGetter lookup(final ResourceKey> resourceKey) { - return Main.REGISTRY_ACCESS.registryOrThrow(resourceKey).asLookup(); + return Main.REGISTRY_ACCESS.lookupOrThrow(resourceKey); } } diff --git a/paper-api-generator/src/main/java/io/papermc/generator/utils/Formatting.java b/paper-api-generator/src/main/java/io/papermc/generator/utils/Formatting.java index 0006e07c53ab..b703a32455b3 100644 --- a/paper-api-generator/src/main/java/io/papermc/generator/utils/Formatting.java +++ b/paper-api-generator/src/main/java/io/papermc/generator/utils/Formatting.java @@ -1,6 +1,5 @@ package io.papermc.generator.utils; -import java.util.Map; import java.util.Optional; import org.apache.commons.lang3.math.NumberUtils; import java.util.Comparator; @@ -13,13 +12,8 @@ public final class Formatting { private static final Pattern ILLEGAL_FIELD_CHARACTERS = Pattern.compile("[.-/]"); - private static final Map MANUAL_OVERRIDES = Map.of( - "5", "five", - "11", "eleven", - "13", "thirteen" - ); public static String formatKeyAsField(String path) { - return ILLEGAL_FIELD_CHARACTERS.matcher(MANUAL_OVERRIDES.getOrDefault(path, path).toUpperCase(Locale.ROOT)).replaceAll("_"); + return ILLEGAL_FIELD_CHARACTERS.matcher(path.toUpperCase(Locale.ROOT)).replaceAll("_"); } public static Optional formatTagKey(String tagDir, String resourcePath) { diff --git a/paper-api-generator/src/main/java/io/papermc/generator/utils/TagCollector.java b/paper-api-generator/src/main/java/io/papermc/generator/utils/TagCollector.java index 2c537dba939c..546e136b4a74 100644 --- a/paper-api-generator/src/main/java/io/papermc/generator/utils/TagCollector.java +++ b/paper-api-generator/src/main/java/io/papermc/generator/utils/TagCollector.java @@ -47,7 +47,7 @@ public static Map, String> grabExperimental(final MultiPackResourceMan return; } - result.put(entry.value().getTagNames() + result.put(entry.value().listTagIds() .filter(tagKey -> tagKey.location().getPath().equals(path)) .findFirst() .orElseThrow(), packId); diff --git a/paper-api-generator/wideners.at b/paper-api-generator/wideners.at index 6928316973df..6c8ce1529e1f 100644 --- a/paper-api-generator/wideners.at +++ b/paper-api-generator/wideners.at @@ -1,7 +1,6 @@ -public net/minecraft/server/WorldLoader loadAndReplaceLayer(Lnet/minecraft/server/packs/resources/ResourceManager;Lnet/minecraft/core/LayeredRegistryAccess;Lnet/minecraft/server/RegistryLayer;Ljava/util/List;)Lnet/minecraft/core/LayeredRegistryAccess; - # for auto-marking experimental stuff public net/minecraft/core/RegistrySetBuilder entries public net/minecraft/core/RegistrySetBuilder$RegistryStub -public net/minecraft/data/registries/UpdateOneTwentyOneRegistries BUILDER public net/minecraft/data/registries/VanillaRegistries BUILDER +public net/minecraft/data/registries/WinterDropRegistries BUILDER +public net/minecraft/data/registries/TradeRebalanceRegistries BUILDER diff --git a/patches/api/0001-Convert-project-to-Gradle.patch b/patches/api/0001-Convert-project-to-Gradle.patch index 2f36dea1d538..6cec7f2fe380 100644 --- a/patches/api/0001-Convert-project-to-Gradle.patch +++ b/patches/api/0001-Convert-project-to-Gradle.patch @@ -124,7 +124,7 @@ index 0000000000000000000000000000000000000000..7ac6af074d76b782ef14fe4690bb5b63 +} diff --git a/pom.xml b/pom.xml deleted file mode 100644 -index 19c192e313d48ae35622a032e2b0911ea9fb8aa1..0000000000000000000000000000000000000000 +index bdde86ea96da233c802e98dc203cb4cb7f8616b0..0000000000000000000000000000000000000000 --- a/pom.xml +++ /dev/null @@ -1,267 +0,0 @@ @@ -135,7 +135,7 @@ index 19c192e313d48ae35622a032e2b0911ea9fb8aa1..00000000000000000000000000000000 - - org.spigotmc - spigot-api -- 1.21.1-R0.1-SNAPSHOT +- 1.21.3-R0.1-SNAPSHOT - jar - - Spigot-API diff --git a/patches/api/0002-Build-system-changes.patch b/patches/api/0002-Build-system-changes.patch index 60f39fcfceb3..944fcfdff7b2 100644 --- a/patches/api/0002-Build-system-changes.patch +++ b/patches/api/0002-Build-system-changes.patch @@ -5,7 +5,7 @@ Subject: [PATCH] Build system changes diff --git a/build.gradle.kts b/build.gradle.kts -index 6271e2bad0ed937c2c46a8c8fdf186c46b0b620e..78aadebda145fe83327ceb430c4b38f9a8e45a2b 100644 +index 7ac6af074d76b782ef14fe4690bb5b630ededa32..0b837b485bec96fa37ed65c18df97e55cecd0e9d 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -18,15 +18,27 @@ dependencies { diff --git a/patches/api/0003-Test-changes.patch b/patches/api/0003-Test-changes.patch index a94334f9673e..0355a16c86b8 100644 --- a/patches/api/0003-Test-changes.patch +++ b/patches/api/0003-Test-changes.patch @@ -12,7 +12,7 @@ Co-authored-by: Riley Park Co-authored-by: Jake Potrebic diff --git a/build.gradle.kts b/build.gradle.kts -index 665ad8b7d2728b836f26c2344111144728e00fb6..35480b9f7bb14217130b1b3a0638365a98c7a9d5 100644 +index 0b837b485bec96fa37ed65c18df97e55cecd0e9d..c8a8903d1b0c9822743549ecb8e4fdc7d0fd07c1 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -107,6 +107,12 @@ tasks.test { diff --git a/patches/api/0004-Code-Generation.patch b/patches/api/0004-Code-Generation.patch index 56d1a8f094d9..cc7000b9c682 100644 --- a/patches/api/0004-Code-Generation.patch +++ b/patches/api/0004-Code-Generation.patch @@ -7,7 +7,7 @@ Currently includes generated key holder classes for types used in the Registry Modification API diff --git a/build.gradle.kts b/build.gradle.kts -index 35480b9f7bb14217130b1b3a0638365a98c7a9d5..f7aa3f53e119b756b4645ca88cf9642d0f549d2c 100644 +index c8a8903d1b0c9822743549ecb8e4fdc7d0fd07c1..032f8b762b552e8cae20bbdd75c1e0844e64386a 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -1,6 +1,7 @@ @@ -366,13 +366,17 @@ index 0000000000000000000000000000000000000000..99375deaa6b90b33cd6a77e0df651236 +record TypedKeyImpl(Key key, RegistryKey registryKey) implements TypedKey { +} diff --git a/src/main/java/org/bukkit/MinecraftExperimental.java b/src/main/java/org/bukkit/MinecraftExperimental.java -index a86b87e4c3332202e40e484c3f9c6562b419c70f..305532968f9f7dd497c77259ed147ea2f081bc74 100644 +index b7845523e8587e13b86516c0012fe097d904846c..d92a75f610cb2a95203b3f22dc67bdbfb5c3405a 100644 --- a/src/main/java/org/bukkit/MinecraftExperimental.java +++ b/src/main/java/org/bukkit/MinecraftExperimental.java -@@ -47,5 +47,6 @@ public @interface MinecraftExperimental { - @ApiStatus.Internal +@@ -48,5 +48,10 @@ public @interface MinecraftExperimental { public enum Requires { -+ BUNDLE, TRADE_REBALANCE // Paper + WINTER_DROP, ++ // Paper start ++ TRADE_REBALANCE, ++ REDSTONE_EXPERIMENTS, ++ MINECART_IMPROVEMENTS ++ // Paper end } } diff --git a/patches/api/0005-Add-FastUtil-to-Bukkit.patch b/patches/api/0005-Add-FastUtil-to-Bukkit.patch index 68e47ea275f7..e4778e955969 100644 --- a/patches/api/0005-Add-FastUtil-to-Bukkit.patch +++ b/patches/api/0005-Add-FastUtil-to-Bukkit.patch @@ -6,7 +6,7 @@ Subject: [PATCH] Add FastUtil to Bukkit Doesn't expose to plugins, just allows Paper-API to use it for optimization diff --git a/build.gradle.kts b/build.gradle.kts -index f11a22ab01e97e51619c96f2d8a78a99297efc59..2f266350a787a4cfdfda1b0e760bfb7604cac43c 100644 +index 032f8b762b552e8cae20bbdd75c1e0844e64386a..4b6c5d1ae45d93d88adb7035eb19935361c06178 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -23,6 +23,7 @@ dependencies { diff --git a/patches/api/0006-Adventure.patch b/patches/api/0006-Adventure.patch index e464b2a6e636..da57b44ba1d6 100644 --- a/patches/api/0006-Adventure.patch +++ b/patches/api/0006-Adventure.patch @@ -8,7 +8,7 @@ Co-authored-by: Jake Potrebic Co-authored-by: Yannick Lamprecht diff --git a/build.gradle.kts b/build.gradle.kts -index b2e0bb305d015a19a4cb1e81e1b3b983c979d56d..562f8ae2d9cd2a1238bde1e5cfbf546c4f56bd6e 100644 +index 4b6c5d1ae45d93d88adb7035eb19935361c06178..cce9caa52c9a2208acccbd25fa88c0de066f23a4 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -11,12 +11,28 @@ java { @@ -1494,7 +1494,7 @@ index 18a53194483410c4d5ad35f901c90d44efaeef60..aff43d77f31d81b82e5fc5fea6272dda String getDisplayName(); diff --git a/src/main/java/org/bukkit/Sound.java b/src/main/java/org/bukkit/Sound.java -index f4d7efee38c3b1381fdbcd47ab1f59fb02728cf2..b2ff1da3386223a544ab5fc363a90c66c8869242 100644 +index 8824e83039195882fa9c2a854460b0aeedfb7d21..cf17af024b1953b6f21f18885411ea6a0baa1d4c 100644 --- a/src/main/java/org/bukkit/Sound.java +++ b/src/main/java/org/bukkit/Sound.java @@ -10,7 +10,7 @@ import org.jetbrains.annotations.NotNull; @@ -1506,7 +1506,7 @@ index f4d7efee38c3b1381fdbcd47ab1f59fb02728cf2..b2ff1da3386223a544ab5fc363a90c66 AMBIENT_BASALT_DELTAS_ADDITIONS("ambient.basalt_deltas.additions"), AMBIENT_BASALT_DELTAS_LOOP("ambient.basalt_deltas.loop"), -@@ -1635,4 +1635,11 @@ public enum Sound implements Keyed { +@@ -1660,4 +1660,11 @@ public enum Sound implements Keyed { public NamespacedKey getKey() { return key; } @@ -2320,10 +2320,10 @@ index 558fe6e23f562ee873fc84112f930c6ea19a09f4..c78fb359bd28b8dc1ba242642ec612e8 + // Paper end } diff --git a/src/main/java/org/bukkit/entity/Player.java b/src/main/java/org/bukkit/entity/Player.java -index fb976b3f5e92016373b83b1ab70032fb910f60d3..5bcec42a91859002409cab9756999e5adc4c867f 100644 +index 13bd964951a12a63f4ad243b8a1e1a830ce5abeb..84befa1e5123038b80e0734622a5174634f5a982 100644 --- a/src/main/java/org/bukkit/entity/Player.java +++ b/src/main/java/org/bukkit/entity/Player.java -@@ -57,7 +57,41 @@ import org.jetbrains.annotations.Nullable; +@@ -58,7 +58,41 @@ import org.jetbrains.annotations.Nullable; /** * Represents a player, connected or not */ @@ -2366,7 +2366,7 @@ index fb976b3f5e92016373b83b1ab70032fb910f60d3..5bcec42a91859002409cab9756999e5a /** * {@inheritDoc} -@@ -74,7 +108,9 @@ public interface Player extends HumanEntity, Conversable, OfflinePlayer, PluginM +@@ -75,7 +109,9 @@ public interface Player extends HumanEntity, Conversable, OfflinePlayer, PluginM * places defined by plugins. * * @return the friendly name @@ -2376,7 +2376,7 @@ index fb976b3f5e92016373b83b1ab70032fb910f60d3..5bcec42a91859002409cab9756999e5a @NotNull public String getDisplayName(); -@@ -86,15 +122,50 @@ public interface Player extends HumanEntity, Conversable, OfflinePlayer, PluginM +@@ -87,15 +123,50 @@ public interface Player extends HumanEntity, Conversable, OfflinePlayer, PluginM * places defined by plugins. * * @param name The new display name. @@ -2427,7 +2427,7 @@ index fb976b3f5e92016373b83b1ab70032fb910f60d3..5bcec42a91859002409cab9756999e5a public String getPlayerListName(); /** -@@ -103,14 +174,18 @@ public interface Player extends HumanEntity, Conversable, OfflinePlayer, PluginM +@@ -104,7 +175,9 @@ public interface Player extends HumanEntity, Conversable, OfflinePlayer, PluginM * If the value is null, the name will be identical to {@link #getName()}. * * @param name new player list name @@ -2437,6 +2437,7 @@ index fb976b3f5e92016373b83b1ab70032fb910f60d3..5bcec42a91859002409cab9756999e5a public void setPlayerListName(@Nullable String name); /** +@@ -126,7 +199,9 @@ public interface Player extends HumanEntity, Conversable, OfflinePlayer, PluginM * Gets the currently displayed player list header for this player. * * @return player list header or null @@ -2446,7 +2447,7 @@ index fb976b3f5e92016373b83b1ab70032fb910f60d3..5bcec42a91859002409cab9756999e5a @Nullable public String getPlayerListHeader(); -@@ -118,7 +193,9 @@ public interface Player extends HumanEntity, Conversable, OfflinePlayer, PluginM +@@ -134,7 +209,9 @@ public interface Player extends HumanEntity, Conversable, OfflinePlayer, PluginM * Gets the currently displayed player list footer for this player. * * @return player list header or null @@ -2456,7 +2457,7 @@ index fb976b3f5e92016373b83b1ab70032fb910f60d3..5bcec42a91859002409cab9756999e5a @Nullable public String getPlayerListFooter(); -@@ -126,14 +203,18 @@ public interface Player extends HumanEntity, Conversable, OfflinePlayer, PluginM +@@ -142,14 +219,18 @@ public interface Player extends HumanEntity, Conversable, OfflinePlayer, PluginM * Sets the currently displayed player list header for this player. * * @param header player list header, null for empty @@ -2475,7 +2476,7 @@ index fb976b3f5e92016373b83b1ab70032fb910f60d3..5bcec42a91859002409cab9756999e5a public void setPlayerListFooter(@Nullable String footer); /** -@@ -142,7 +223,9 @@ public interface Player extends HumanEntity, Conversable, OfflinePlayer, PluginM +@@ -158,7 +239,9 @@ public interface Player extends HumanEntity, Conversable, OfflinePlayer, PluginM * * @param header player list header, null for empty * @param footer player list footer, null for empty @@ -2485,7 +2486,7 @@ index fb976b3f5e92016373b83b1ab70032fb910f60d3..5bcec42a91859002409cab9756999e5a public void setPlayerListHeaderFooter(@Nullable String header, @Nullable String footer); /** -@@ -219,9 +302,25 @@ public interface Player extends HumanEntity, Conversable, OfflinePlayer, PluginM +@@ -235,9 +318,25 @@ public interface Player extends HumanEntity, Conversable, OfflinePlayer, PluginM * Kicks player with custom kick message. * * @param message kick message @@ -2511,7 +2512,7 @@ index fb976b3f5e92016373b83b1ab70032fb910f60d3..5bcec42a91859002409cab9756999e5a /** * Adds this user to the {@link ProfileBanList}. If a previous ban exists, this will * update the entry. -@@ -884,6 +983,106 @@ public interface Player extends HumanEntity, Conversable, OfflinePlayer, PluginM +@@ -924,6 +1023,106 @@ public interface Player extends HumanEntity, Conversable, OfflinePlayer, PluginM */ public void sendEquipmentChange(@NotNull LivingEntity entity, @NotNull Map items); @@ -2618,7 +2619,7 @@ index fb976b3f5e92016373b83b1ab70032fb910f60d3..5bcec42a91859002409cab9756999e5a /** * Send a sign change. This fakes a sign change packet for a user at * a certain location. This will not actually change the world in any way. -@@ -901,7 +1100,11 @@ public interface Player extends HumanEntity, Conversable, OfflinePlayer, PluginM +@@ -941,7 +1140,11 @@ public interface Player extends HumanEntity, Conversable, OfflinePlayer, PluginM * @param lines the new text on the sign or null to clear it * @throws IllegalArgumentException if location is null * @throws IllegalArgumentException if lines is non-null and has a length less than 4 @@ -2630,7 +2631,7 @@ index fb976b3f5e92016373b83b1ab70032fb910f60d3..5bcec42a91859002409cab9756999e5a public void sendSignChange(@NotNull Location loc, @Nullable String[] lines) throws IllegalArgumentException; /** -@@ -923,7 +1126,11 @@ public interface Player extends HumanEntity, Conversable, OfflinePlayer, PluginM +@@ -963,7 +1166,11 @@ public interface Player extends HumanEntity, Conversable, OfflinePlayer, PluginM * @throws IllegalArgumentException if location is null * @throws IllegalArgumentException if dyeColor is null * @throws IllegalArgumentException if lines is non-null and has a length less than 4 @@ -2642,7 +2643,7 @@ index fb976b3f5e92016373b83b1ab70032fb910f60d3..5bcec42a91859002409cab9756999e5a public void sendSignChange(@NotNull Location loc, @Nullable String[] lines, @NotNull DyeColor dyeColor) throws IllegalArgumentException; /** -@@ -946,7 +1153,11 @@ public interface Player extends HumanEntity, Conversable, OfflinePlayer, PluginM +@@ -986,7 +1193,11 @@ public interface Player extends HumanEntity, Conversable, OfflinePlayer, PluginM * @throws IllegalArgumentException if location is null * @throws IllegalArgumentException if dyeColor is null * @throws IllegalArgumentException if lines is non-null and has a length less than 4 @@ -2654,7 +2655,7 @@ index fb976b3f5e92016373b83b1ab70032fb910f60d3..5bcec42a91859002409cab9756999e5a public void sendSignChange(@NotNull Location loc, @Nullable String[] lines, @NotNull DyeColor dyeColor, boolean hasGlowingText) throws IllegalArgumentException; /** -@@ -1421,7 +1632,7 @@ public interface Player extends HumanEntity, Conversable, OfflinePlayer, PluginM +@@ -1461,7 +1672,7 @@ public interface Player extends HumanEntity, Conversable, OfflinePlayer, PluginM * @throws IllegalArgumentException Thrown if the URL is null. * @throws IllegalArgumentException Thrown if the URL is too long. * @deprecated Minecraft no longer uses textures packs. Instead you @@ -2663,7 +2664,7 @@ index fb976b3f5e92016373b83b1ab70032fb910f60d3..5bcec42a91859002409cab9756999e5a */ @Deprecated public void setTexturePack(@NotNull String url); -@@ -1457,7 +1668,9 @@ public interface Player extends HumanEntity, Conversable, OfflinePlayer, PluginM +@@ -1497,7 +1708,9 @@ public interface Player extends HumanEntity, Conversable, OfflinePlayer, PluginM * @throws IllegalArgumentException Thrown if the URL is null. * @throws IllegalArgumentException Thrown if the URL is too long. The * length restriction is an implementation specific arbitrary value. @@ -2673,7 +2674,7 @@ index fb976b3f5e92016373b83b1ab70032fb910f60d3..5bcec42a91859002409cab9756999e5a public void setResourcePack(@NotNull String url); /** -@@ -1489,6 +1702,7 @@ public interface Player extends HumanEntity, Conversable, OfflinePlayer, PluginM +@@ -1529,6 +1742,7 @@ public interface Player extends HumanEntity, Conversable, OfflinePlayer, PluginM * pack correctly. * * @@ -2681,7 +2682,7 @@ index fb976b3f5e92016373b83b1ab70032fb910f60d3..5bcec42a91859002409cab9756999e5a * @param url The URL from which the client will download the resource * pack. The string must contain only US-ASCII characters and should * be encoded as per RFC 1738. -@@ -1501,6 +1715,7 @@ public interface Player extends HumanEntity, Conversable, OfflinePlayer, PluginM +@@ -1541,6 +1755,7 @@ public interface Player extends HumanEntity, Conversable, OfflinePlayer, PluginM * @throws IllegalArgumentException Thrown if the hash is not 20 bytes * long. */ @@ -2689,7 +2690,7 @@ index fb976b3f5e92016373b83b1ab70032fb910f60d3..5bcec42a91859002409cab9756999e5a public void setResourcePack(@NotNull String url, @Nullable byte[] hash); /** -@@ -1525,12 +1740,13 @@ public interface Player extends HumanEntity, Conversable, OfflinePlayer, PluginM +@@ -1565,12 +1780,13 @@ public interface Player extends HumanEntity, Conversable, OfflinePlayer, PluginM * {@link PlayerResourcePackStatusEvent} to figure out whether or not * the player loaded the pack! *
  • To remove a resource pack you can use @@ -2704,7 +2705,7 @@ index fb976b3f5e92016373b83b1ab70032fb910f60d3..5bcec42a91859002409cab9756999e5a * @param url The URL from which the client will download the resource * pack. The string must contain only US-ASCII characters and should * be encoded as per RFC 1738. -@@ -1544,8 +1760,10 @@ public interface Player extends HumanEntity, Conversable, OfflinePlayer, PluginM +@@ -1584,8 +1800,10 @@ public interface Player extends HumanEntity, Conversable, OfflinePlayer, PluginM * @throws IllegalArgumentException Thrown if the hash is not 20 bytes * long. */ @@ -2715,7 +2716,7 @@ index fb976b3f5e92016373b83b1ab70032fb910f60d3..5bcec42a91859002409cab9756999e5a /** * Request that the player's client download and switch resource packs. *

    -@@ -1568,7 +1786,54 @@ public interface Player extends HumanEntity, Conversable, OfflinePlayer, PluginM +@@ -1608,7 +1826,54 @@ public interface Player extends HumanEntity, Conversable, OfflinePlayer, PluginM * {@link PlayerResourcePackStatusEvent} to figure out whether or not * the player loaded the pack! *

  • To remove a resource pack you can use @@ -2771,7 +2772,7 @@ index fb976b3f5e92016373b83b1ab70032fb910f60d3..5bcec42a91859002409cab9756999e5a *
  • The request is sent with empty string as the hash when the hash is * not provided. This might result in newer versions not loading the * pack correctly. -@@ -1587,7 +1852,9 @@ public interface Player extends HumanEntity, Conversable, OfflinePlayer, PluginM +@@ -1627,7 +1892,9 @@ public interface Player extends HumanEntity, Conversable, OfflinePlayer, PluginM * length restriction is an implementation specific arbitrary value. * @throws IllegalArgumentException Thrown if the hash is not 20 bytes * long. @@ -2781,7 +2782,7 @@ index fb976b3f5e92016373b83b1ab70032fb910f60d3..5bcec42a91859002409cab9756999e5a public void setResourcePack(@NotNull String url, @Nullable byte[] hash, boolean force); /** -@@ -1612,7 +1879,7 @@ public interface Player extends HumanEntity, Conversable, OfflinePlayer, PluginM +@@ -1652,7 +1919,7 @@ public interface Player extends HumanEntity, Conversable, OfflinePlayer, PluginM * {@link PlayerResourcePackStatusEvent} to figure out whether or not * the player loaded the pack! *
  • To remove a resource pack you can use @@ -2790,7 +2791,7 @@ index fb976b3f5e92016373b83b1ab70032fb910f60d3..5bcec42a91859002409cab9756999e5a *
  • The request is sent with empty string as the hash when the hash is * not provided. This might result in newer versions not loading the * pack correctly. -@@ -1632,9 +1899,61 @@ public interface Player extends HumanEntity, Conversable, OfflinePlayer, PluginM +@@ -1672,9 +1939,61 @@ public interface Player extends HumanEntity, Conversable, OfflinePlayer, PluginM * length restriction is an implementation specific arbitrary value. * @throws IllegalArgumentException Thrown if the hash is not 20 bytes * long. @@ -2852,7 +2853,7 @@ index fb976b3f5e92016373b83b1ab70032fb910f60d3..5bcec42a91859002409cab9756999e5a /** * Request that the player's client download and switch resource packs. *

    -@@ -1657,7 +1976,7 @@ public interface Player extends HumanEntity, Conversable, OfflinePlayer, PluginM +@@ -1697,7 +2016,7 @@ public interface Player extends HumanEntity, Conversable, OfflinePlayer, PluginM * {@link PlayerResourcePackStatusEvent} to figure out whether or not * the player loaded the pack! *

  • To remove a resource pack you can use @@ -2861,7 +2862,7 @@ index fb976b3f5e92016373b83b1ab70032fb910f60d3..5bcec42a91859002409cab9756999e5a *
  • The request is sent with empty string as the hash when the hash is * not provided. This might result in newer versions not loading the * pack correctly. -@@ -1678,9 +1997,60 @@ public interface Player extends HumanEntity, Conversable, OfflinePlayer, PluginM +@@ -1718,9 +2037,60 @@ public interface Player extends HumanEntity, Conversable, OfflinePlayer, PluginM * length restriction is an implementation specific arbitrary value. * @throws IllegalArgumentException Thrown if the hash is not 20 bytes * long. @@ -2922,7 +2923,7 @@ index fb976b3f5e92016373b83b1ab70032fb910f60d3..5bcec42a91859002409cab9756999e5a /** * Request that the player's client download and include another resource pack. *

    -@@ -1733,12 +2103,14 @@ public interface Player extends HumanEntity, Conversable, OfflinePlayer, PluginM +@@ -1773,12 +2143,14 @@ public interface Player extends HumanEntity, Conversable, OfflinePlayer, PluginM * * @param id the id of the resource pack. * @throws IllegalArgumentException If the ID is null. @@ -2937,7 +2938,7 @@ index fb976b3f5e92016373b83b1ab70032fb910f60d3..5bcec42a91859002409cab9756999e5a */ public void removeResourcePacks(); -@@ -1876,7 +2248,7 @@ public interface Player extends HumanEntity, Conversable, OfflinePlayer, PluginM +@@ -1916,7 +2288,7 @@ public interface Player extends HumanEntity, Conversable, OfflinePlayer, PluginM * * @param title Title text * @param subtitle Subtitle text @@ -2946,7 +2947,7 @@ index fb976b3f5e92016373b83b1ab70032fb910f60d3..5bcec42a91859002409cab9756999e5a */ @Deprecated public void sendTitle(@Nullable String title, @Nullable String subtitle); -@@ -1895,7 +2267,9 @@ public interface Player extends HumanEntity, Conversable, OfflinePlayer, PluginM +@@ -1935,7 +2307,9 @@ public interface Player extends HumanEntity, Conversable, OfflinePlayer, PluginM * @param fadeIn time in ticks for titles to fade in. Defaults to 10. * @param stay time in ticks for titles to stay. Defaults to 70. * @param fadeOut time in ticks for titles to fade out. Defaults to 20. @@ -2956,7 +2957,7 @@ index fb976b3f5e92016373b83b1ab70032fb910f60d3..5bcec42a91859002409cab9756999e5a public void sendTitle(@Nullable String title, @Nullable String subtitle, int fadeIn, int stay, int fadeOut); /** -@@ -2170,6 +2544,14 @@ public interface Player extends HumanEntity, Conversable, OfflinePlayer, PluginM +@@ -2210,6 +2584,14 @@ public interface Player extends HumanEntity, Conversable, OfflinePlayer, PluginM */ public int getClientViewDistance(); @@ -2971,7 +2972,7 @@ index fb976b3f5e92016373b83b1ab70032fb910f60d3..5bcec42a91859002409cab9756999e5a /** * Gets the player's estimated ping in milliseconds. * -@@ -2195,8 +2577,10 @@ public interface Player extends HumanEntity, Conversable, OfflinePlayer, PluginM +@@ -2235,8 +2617,10 @@ public interface Player extends HumanEntity, Conversable, OfflinePlayer, PluginM * they wish. * * @return the player's locale @@ -2982,7 +2983,7 @@ index fb976b3f5e92016373b83b1ab70032fb910f60d3..5bcec42a91859002409cab9756999e5a public String getLocale(); /** -@@ -2248,6 +2632,14 @@ public interface Player extends HumanEntity, Conversable, OfflinePlayer, PluginM +@@ -2288,6 +2672,14 @@ public interface Player extends HumanEntity, Conversable, OfflinePlayer, PluginM */ public boolean isAllowingServerListings(); @@ -2997,7 +2998,7 @@ index fb976b3f5e92016373b83b1ab70032fb910f60d3..5bcec42a91859002409cab9756999e5a // Spigot start public class Spigot extends Entity.Spigot { -@@ -2279,11 +2671,13 @@ public interface Player extends HumanEntity, Conversable, OfflinePlayer, PluginM +@@ -2319,11 +2711,13 @@ public interface Player extends HumanEntity, Conversable, OfflinePlayer, PluginM throw new UnsupportedOperationException("Not supported yet."); } @@ -3011,7 +3012,7 @@ index fb976b3f5e92016373b83b1ab70032fb910f60d3..5bcec42a91859002409cab9756999e5a @Override public void sendMessage(@NotNull net.md_5.bungee.api.chat.BaseComponent... components) { throw new UnsupportedOperationException("Not supported yet."); -@@ -2294,7 +2688,9 @@ public interface Player extends HumanEntity, Conversable, OfflinePlayer, PluginM +@@ -2334,7 +2728,9 @@ public interface Player extends HumanEntity, Conversable, OfflinePlayer, PluginM * * @param position the screen position * @param component the components to send @@ -3021,7 +3022,7 @@ index fb976b3f5e92016373b83b1ab70032fb910f60d3..5bcec42a91859002409cab9756999e5a public void sendMessage(@NotNull net.md_5.bungee.api.ChatMessageType position, @NotNull net.md_5.bungee.api.chat.BaseComponent component) { throw new UnsupportedOperationException("Not supported yet."); } -@@ -2304,7 +2700,9 @@ public interface Player extends HumanEntity, Conversable, OfflinePlayer, PluginM +@@ -2344,7 +2740,9 @@ public interface Player extends HumanEntity, Conversable, OfflinePlayer, PluginM * * @param position the screen position * @param components the components to send @@ -3031,7 +3032,7 @@ index fb976b3f5e92016373b83b1ab70032fb910f60d3..5bcec42a91859002409cab9756999e5a public void sendMessage(@NotNull net.md_5.bungee.api.ChatMessageType position, @NotNull net.md_5.bungee.api.chat.BaseComponent... components) { throw new UnsupportedOperationException("Not supported yet."); } -@@ -2315,7 +2713,9 @@ public interface Player extends HumanEntity, Conversable, OfflinePlayer, PluginM +@@ -2355,7 +2753,9 @@ public interface Player extends HumanEntity, Conversable, OfflinePlayer, PluginM * @param position the screen position * @param sender the sender of the message * @param component the components to send @@ -3041,7 +3042,7 @@ index fb976b3f5e92016373b83b1ab70032fb910f60d3..5bcec42a91859002409cab9756999e5a public void sendMessage(@NotNull net.md_5.bungee.api.ChatMessageType position, @Nullable java.util.UUID sender, @NotNull net.md_5.bungee.api.chat.BaseComponent component) { throw new UnsupportedOperationException("Not supported yet."); } -@@ -2326,7 +2726,9 @@ public interface Player extends HumanEntity, Conversable, OfflinePlayer, PluginM +@@ -2366,7 +2766,9 @@ public interface Player extends HumanEntity, Conversable, OfflinePlayer, PluginM * @param position the screen position * @param sender the sender of the message * @param components the components to send @@ -4710,10 +4711,10 @@ index 9bab73c3c2ca759b8e1c7d07d98cc593c961666a..f0c6943da3f783101ca647b75b3230fa throw new UnsupportedOperationException("Not supported yet."); } diff --git a/src/main/java/org/bukkit/inventory/meta/ItemMeta.java b/src/main/java/org/bukkit/inventory/meta/ItemMeta.java -index 9fb9f00f39d68777cde660b06beaa58b25cd4470..014c1a0379e532a5c924694a8e0715eb0ba50ec2 100644 +index 1d810042652dac9c35dd358f9d87e3dd2cebc48e..fabddfe3763e143b5a769764cb324f97876ccb1c 100644 --- a/src/main/java/org/bukkit/inventory/meta/ItemMeta.java +++ b/src/main/java/org/bukkit/inventory/meta/ItemMeta.java -@@ -37,6 +37,24 @@ public interface ItemMeta extends Cloneable, ConfigurationSerializable, Persiste +@@ -44,6 +44,24 @@ public interface ItemMeta extends Cloneable, ConfigurationSerializable, Persiste */ boolean hasDisplayName(); @@ -4738,7 +4739,7 @@ index 9fb9f00f39d68777cde660b06beaa58b25cd4470..014c1a0379e532a5c924694a8e0715eb /** * Gets the display name that is set. *

    -@@ -44,7 +62,9 @@ public interface ItemMeta extends Cloneable, ConfigurationSerializable, Persiste +@@ -51,7 +69,9 @@ public interface ItemMeta extends Cloneable, ConfigurationSerializable, Persiste * before calling this method. * * @return the display name that is set @@ -4748,7 +4749,7 @@ index 9fb9f00f39d68777cde660b06beaa58b25cd4470..014c1a0379e532a5c924694a8e0715eb @NotNull String getDisplayName(); -@@ -52,7 +72,9 @@ public interface ItemMeta extends Cloneable, ConfigurationSerializable, Persiste +@@ -59,7 +79,9 @@ public interface ItemMeta extends Cloneable, ConfigurationSerializable, Persiste * Sets the display name. * * @param name the name to set @@ -4758,7 +4759,7 @@ index 9fb9f00f39d68777cde660b06beaa58b25cd4470..014c1a0379e532a5c924694a8e0715eb void setDisplayName(@Nullable String name); /** -@@ -65,6 +87,32 @@ public interface ItemMeta extends Cloneable, ConfigurationSerializable, Persiste +@@ -72,6 +94,32 @@ public interface ItemMeta extends Cloneable, ConfigurationSerializable, Persiste */ boolean hasItemName(); @@ -4791,7 +4792,7 @@ index 9fb9f00f39d68777cde660b06beaa58b25cd4470..014c1a0379e532a5c924694a8e0715eb /** * Gets the item name that is set. *
    -@@ -75,7 +123,9 @@ public interface ItemMeta extends Cloneable, ConfigurationSerializable, Persiste +@@ -82,7 +130,9 @@ public interface ItemMeta extends Cloneable, ConfigurationSerializable, Persiste * calling this method. * * @return the item name that is set @@ -4801,7 +4802,7 @@ index 9fb9f00f39d68777cde660b06beaa58b25cd4470..014c1a0379e532a5c924694a8e0715eb @NotNull String getItemName(); -@@ -86,7 +136,9 @@ public interface ItemMeta extends Cloneable, ConfigurationSerializable, Persiste +@@ -93,7 +143,9 @@ public interface ItemMeta extends Cloneable, ConfigurationSerializable, Persiste * anvil, is not styled with italics, and does not show labels. * * @param name the name to set @@ -4811,7 +4812,7 @@ index 9fb9f00f39d68777cde660b06beaa58b25cd4470..014c1a0379e532a5c924694a8e0715eb void setItemName(@Nullable String name); /** -@@ -127,6 +179,24 @@ public interface ItemMeta extends Cloneable, ConfigurationSerializable, Persiste +@@ -134,6 +186,24 @@ public interface ItemMeta extends Cloneable, ConfigurationSerializable, Persiste */ boolean hasLore(); @@ -4836,7 +4837,7 @@ index 9fb9f00f39d68777cde660b06beaa58b25cd4470..014c1a0379e532a5c924694a8e0715eb /** * Gets the lore that is set. *

    -@@ -134,7 +204,9 @@ public interface ItemMeta extends Cloneable, ConfigurationSerializable, Persiste +@@ -141,7 +211,9 @@ public interface ItemMeta extends Cloneable, ConfigurationSerializable, Persiste * calling this method. * * @return a list of lore that is set @@ -4846,7 +4847,7 @@ index 9fb9f00f39d68777cde660b06beaa58b25cd4470..014c1a0379e532a5c924694a8e0715eb @Nullable List getLore(); -@@ -143,7 +215,9 @@ public interface ItemMeta extends Cloneable, ConfigurationSerializable, Persiste +@@ -150,7 +222,9 @@ public interface ItemMeta extends Cloneable, ConfigurationSerializable, Persiste * Removes lore when given null. * * @param lore the lore that will be set diff --git a/patches/api/0008-Use-ASM-for-event-executors.patch b/patches/api/0008-Use-ASM-for-event-executors.patch index b16393a72e84..fdb54d4a85ed 100644 --- a/patches/api/0008-Use-ASM-for-event-executors.patch +++ b/patches/api/0008-Use-ASM-for-event-executors.patch @@ -6,7 +6,7 @@ Subject: [PATCH] Use ASM for event executors. Uses method handles for private or static methods. diff --git a/build.gradle.kts b/build.gradle.kts -index 562f8ae2d9cd2a1238bde1e5cfbf546c4f56bd6e..65783c7a3711fa147a4e090a9715f7e218a312ac 100644 +index cce9caa52c9a2208acccbd25fa88c0de066f23a4..2b1d08d8e16037f6d17c74ea613eaa2ca36c664e 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -47,6 +47,9 @@ dependencies { diff --git a/patches/api/0009-Paper-Plugins.patch b/patches/api/0009-Paper-Plugins.patch index 175cbd40eb42..b9fcf1aeb9c9 100644 --- a/patches/api/0009-Paper-Plugins.patch +++ b/patches/api/0009-Paper-Plugins.patch @@ -5,7 +5,7 @@ Subject: [PATCH] Paper Plugins diff --git a/build.gradle.kts b/build.gradle.kts -index 65783c7a3711fa147a4e090a9715f7e218a312ac..c55223e0d0c30b7575bc5322e993d0205cbda85f 100644 +index 2b1d08d8e16037f6d17c74ea613eaa2ca36c664e..940fb51c1f3054465f305e98b13aac49c77e3a91 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -52,7 +52,7 @@ dependencies { diff --git a/patches/api/0010-Add-Position.patch b/patches/api/0010-Add-Position.patch index 2e4718c86b46..23e8152911b9 100644 --- a/patches/api/0010-Add-Position.patch +++ b/patches/api/0010-Add-Position.patch @@ -392,7 +392,7 @@ index 0000000000000000000000000000000000000000..0e6a6a6738353b118e0ed093994dda06 + } +} diff --git a/src/main/java/org/bukkit/Location.java b/src/main/java/org/bukkit/Location.java -index 7c4db051472fb6a6c6d24092dc6f75487356690a..85c342fc50f2fe0ce9a1b3980df9e088c3dea92d 100644 +index 734054f1e8dad74c13d7ae0b1c1af2d9f45b2636..bc8a64d54e001eae6ef4520a49e261b96c5ae9f3 100644 --- a/src/main/java/org/bukkit/Location.java +++ b/src/main/java/org/bukkit/Location.java @@ -20,7 +20,7 @@ import org.jetbrains.annotations.Nullable; diff --git a/patches/api/0011-Timings-v2.patch b/patches/api/0011-Timings-v2.patch index 928003f6213f..a484a8afd2be 100644 --- a/patches/api/0011-Timings-v2.patch +++ b/patches/api/0011-Timings-v2.patch @@ -8,7 +8,7 @@ expose isRunning diff --git a/src/main/java/co/aikar/timings/FullServerTickHandler.java b/src/main/java/co/aikar/timings/FullServerTickHandler.java new file mode 100644 -index 0000000000000000000000000000000000000000..3e747abde6fefae90f1c15cb00158bc5303cbe50 +index 0000000000000000000000000000000000000000..73b125979e2f2dfd13cbf689a90b29cc68a36e09 --- /dev/null +++ b/src/main/java/co/aikar/timings/FullServerTickHandler.java @@ -0,0 +1,89 @@ @@ -94,7 +94,7 @@ index 0000000000000000000000000000000000000000..3e747abde6fefae90f1c15cb00158bc5 + TimingsManager.HISTORY.add(new TimingHistory()); + TimingsManager.resetTimings(); + } -+ Bukkit.getUnsafe().reportTimings(); ++ //Bukkit.getUnsafe().reportTimings(); + } + + boolean isViolated() { @@ -1275,10 +1275,10 @@ index 0000000000000000000000000000000000000000..df142a89b8c43acb81eb383eac0ef048 +} diff --git a/src/main/java/co/aikar/timings/Timings.java b/src/main/java/co/aikar/timings/Timings.java new file mode 100644 -index 0000000000000000000000000000000000000000..e81d0bc309de877ed2b5da6122f55c162e9b5f10 +index 0000000000000000000000000000000000000000..95b7cdf0677ef71e6885fa78aa5c75bb500f5f53 --- /dev/null +++ b/src/main/java/co/aikar/timings/Timings.java -@@ -0,0 +1,331 @@ +@@ -0,0 +1,325 @@ +/* + * This file is licensed under the MIT License (MIT). + * @@ -1426,14 +1426,8 @@ index 0000000000000000000000000000000000000000..e81d0bc309de877ed2b5da6122f55c16 + * @param enabled Should timings be reported + */ + public static void setTimingsEnabled(boolean enabled) { -+ timingsEnabled = enabled; -+ warnAboutDeprecationOnEnable(); -+ reset(); -+ } -+ -+ private static void warnAboutDeprecationOnEnable() { -+ if (timingsEnabled && !warnedAboutDeprecationOnEnable) { -+ Bukkit.getLogger().warning(PlainTextComponentSerializer.plainText().serialize(deprecationMessage())); ++ if (enabled && !warnedAboutDeprecationOnEnable) { ++ Bukkit.getLogger().severe(PlainTextComponentSerializer.plainText().serialize(deprecationMessage())); + warnedAboutDeprecationOnEnable = true; + } + } @@ -1441,7 +1435,7 @@ index 0000000000000000000000000000000000000000..e81d0bc309de877ed2b5da6122f55c16 + public static Component deprecationMessage() { + return Component.text() + .color(TextColor.color(0xffc93a)) -+ .append(Component.text("[!] The timings profiler has been enabled but has been scheduled for removal from Paper in the future.")) ++ .append(Component.text("[!] The timings profiler is in no-op mode and will be fully removed in a later update.")) + .append(Component.newline()) + .append(Component.text(" We recommend migrating to the spark profiler.")) + .append(Component.newline()) @@ -1612,10 +1606,10 @@ index 0000000000000000000000000000000000000000..e81d0bc309de877ed2b5da6122f55c16 + diff --git a/src/main/java/co/aikar/timings/TimingsCommand.java b/src/main/java/co/aikar/timings/TimingsCommand.java new file mode 100644 -index 0000000000000000000000000000000000000000..95d87c9dbf2b237787294dfbe7fed87a36e6dedf +index 0000000000000000000000000000000000000000..b83e5ff7ada8771fdf27ba9807c77ba6a4ce12da --- /dev/null +++ b/src/main/java/co/aikar/timings/TimingsCommand.java -@@ -0,0 +1,126 @@ +@@ -0,0 +1,127 @@ +/* + * This file is licensed under the MIT License (MIT). + * @@ -1674,8 +1668,9 @@ index 0000000000000000000000000000000000000000..95d87c9dbf2b237787294dfbe7fed87a + if (!testPermission(sender)) { + return true; + } -+ if (false) { ++ if (true) { + sender.sendMessage(Timings.deprecationMessage()); ++ return true; + } + if (args.length < 1) { + sender.sendMessage(text("Usage: " + this.usageMessage, NamedTextColor.RED)); @@ -2906,35 +2901,6 @@ index fa6ad07214d5e38866bf6bee9139c6c938e9f51a..57c9b560c77a56588870598acb543469 /** * Sends the component to the player * -diff --git a/src/main/java/org/bukkit/UnsafeValues.java b/src/main/java/org/bukkit/UnsafeValues.java -index d8b346fe0f9634218954fe818d53272a0896af9c..12ef99f1c91b92a133611c5f5aeaaeebd02ce232 100644 ---- a/src/main/java/org/bukkit/UnsafeValues.java -+++ b/src/main/java/org/bukkit/UnsafeValues.java -@@ -40,6 +40,11 @@ public interface UnsafeValues { - net.kyori.adventure.text.Component resolveWithContext(net.kyori.adventure.text.Component component, org.bukkit.command.CommandSender context, org.bukkit.entity.Entity scoreboardSubject, boolean bypassPermissions) throws java.io.IOException; - // Paper end - -+ /** -+ * @deprecated Timings will be removed in the future -+ */ -+ @Deprecated(forRemoval = true) -+ void reportTimings(); // Paper - Material toLegacy(Material material); - - Material fromLegacy(Material material); -@@ -151,4 +156,12 @@ public interface UnsafeValues { - return !Bukkit.getUnsafe().isSupportedApiVersion(plugin.getDescription().getAPIVersion()); - } - // Paper end -+ -+ // Paper start -+ /** -+ * @deprecated Timings will be removed in the future -+ */ -+ @Deprecated(forRemoval = true) -+ String getTimingsServerName(); -+ // Paper end - } diff --git a/src/main/java/org/bukkit/command/BufferedCommandSender.java b/src/main/java/org/bukkit/command/BufferedCommandSender.java new file mode 100644 index 0000000000000000000000000000000000000000..45ed63797b13e114bf3795c80a6c3967d8eb2351 @@ -3484,10 +3450,10 @@ index 516d7fc7812aac343782861d0d567f54aa578c2a..00000000000000000000000000000000 - // Spigot end -} diff --git a/src/main/java/org/bukkit/entity/Player.java b/src/main/java/org/bukkit/entity/Player.java -index 5bcec42a91859002409cab9756999e5adc4c867f..3594b0eb4068c83c93efe948a8ef4ba217edce17 100644 +index 84befa1e5123038b80e0734622a5174634f5a982..22de066aef71ad2cf135d5b6f5d6f224de5fcd2d 100644 --- a/src/main/java/org/bukkit/entity/Player.java +++ b/src/main/java/org/bukkit/entity/Player.java -@@ -2731,7 +2731,19 @@ public interface Player extends HumanEntity, Conversable, OfflinePlayer, PluginM +@@ -2771,7 +2771,19 @@ public interface Player extends HumanEntity, Conversable, OfflinePlayer, PluginM @Deprecated // Paper public void sendMessage(@NotNull net.md_5.bungee.api.ChatMessageType position, @Nullable java.util.UUID sender, @NotNull net.md_5.bungee.api.chat.BaseComponent... components) { throw new UnsupportedOperationException("Not supported yet."); @@ -3508,7 +3474,7 @@ index 5bcec42a91859002409cab9756999e5adc4c867f..3594b0eb4068c83c93efe948a8ef4ba2 @NotNull diff --git a/src/main/java/org/bukkit/plugin/SimplePluginManager.java b/src/main/java/org/bukkit/plugin/SimplePluginManager.java -index 46c7be5fa69f13900860b9944523beea16f2409b..6018574cd15b802833613beefa88da15dc2730cb 100644 +index 46c7be5fa69f13900860b9944523beea16f2409b..f97669c8b58bc287fc289eeb098836ae314b053a 100644 --- a/src/main/java/org/bukkit/plugin/SimplePluginManager.java +++ b/src/main/java/org/bukkit/plugin/SimplePluginManager.java @@ -381,7 +381,6 @@ public final class SimplePluginManager implements PluginManager { @@ -3559,9 +3525,11 @@ index 46c7be5fa69f13900860b9944523beea16f2409b..6018574cd15b802833613beefa88da15 } /** -@@ -933,7 +933,7 @@ public final class SimplePluginManager implements PluginManager { +@@ -932,8 +932,9 @@ public final class SimplePluginManager implements PluginManager { + * * @param use True if per event timing code should be used */ ++ @Deprecated(forRemoval = true) public void useTimings(boolean use) { - useTimings = use; + co.aikar.timings.Timings.setTimingsEnabled(use); // Paper diff --git a/patches/api/0012-Add-command-line-option-to-load-extra-plugin-jars-no.patch b/patches/api/0012-Add-command-line-option-to-load-extra-plugin-jars-no.patch index 8dc512dfb3e8..1faeb9019097 100644 --- a/patches/api/0012-Add-command-line-option-to-load-extra-plugin-jars-no.patch +++ b/patches/api/0012-Add-command-line-option-to-load-extra-plugin-jars-no.patch @@ -7,7 +7,7 @@ Subject: [PATCH] Add command line option to load extra plugin jars not in the ex: java -jar paperclip.jar nogui -add-plugin=/path/to/plugin.jar -add-plugin=/path/to/another/plugin_jar.jar diff --git a/src/main/java/org/bukkit/Bukkit.java b/src/main/java/org/bukkit/Bukkit.java -index 6ecab28705afc0e3652677b516d8a5398e8b2666..db51751d2dc1ac419e8fac32466ad3a7727fa2fe 100644 +index 8d729fb196d83e01e4652fb1f77f5cab7b57cc31..d978c72cdbc10792f852a4ba372518073893d02b 100644 --- a/src/main/java/org/bukkit/Bukkit.java +++ b/src/main/java/org/bukkit/Bukkit.java @@ -83,6 +83,20 @@ public final class Bukkit { @@ -32,7 +32,7 @@ index 6ecab28705afc0e3652677b516d8a5398e8b2666..db51751d2dc1ac419e8fac32466ad3a7 * Attempts to set the {@link Server} singleton. *

    diff --git a/src/main/java/org/bukkit/Server.java b/src/main/java/org/bukkit/Server.java -index f4b2ad91c7a46af5fc16f31369d155e4e3ab3aae..638e98416fdf7ac065abe058d625b1c924be5abb 100644 +index 57c9b560c77a56588870598acb543469040ceec1..8949b8e29ae7f412481291630a5cb7b5b8809842 100644 --- a/src/main/java/org/bukkit/Server.java +++ b/src/main/java/org/bukkit/Server.java @@ -68,6 +68,18 @@ import org.jetbrains.annotations.Nullable; @@ -55,7 +55,7 @@ index f4b2ad91c7a46af5fc16f31369d155e4e3ab3aae..638e98416fdf7ac065abe058d625b1c9 * Used for all administrative messages, such as an operator using a * command. diff --git a/src/main/java/org/bukkit/plugin/SimplePluginManager.java b/src/main/java/org/bukkit/plugin/SimplePluginManager.java -index 6018574cd15b802833613beefa88da15dc2730cb..e7b1895d3918487d711afcbe41d76863d85c0a62 100644 +index f97669c8b58bc287fc289eeb098836ae314b053a..2c77b6ab388bd689acb8d84ec62bd5df1eb9373e 100644 --- a/src/main/java/org/bukkit/plugin/SimplePluginManager.java +++ b/src/main/java/org/bukkit/plugin/SimplePluginManager.java @@ -117,9 +117,22 @@ public final class SimplePluginManager implements PluginManager { diff --git a/patches/api/0013-Player-affects-spawning-API.patch b/patches/api/0013-Player-affects-spawning-API.patch index fff702d95822..ebafa9cc7090 100644 --- a/patches/api/0013-Player-affects-spawning-API.patch +++ b/patches/api/0013-Player-affects-spawning-API.patch @@ -5,10 +5,10 @@ Subject: [PATCH] Player affects spawning API diff --git a/src/main/java/org/bukkit/entity/Player.java b/src/main/java/org/bukkit/entity/Player.java -index 3594b0eb4068c83c93efe948a8ef4ba217edce17..1ba2f706a62ee6962451305b1895654453b485cd 100644 +index 22de066aef71ad2cf135d5b6f5d6f224de5fcd2d..1c25725a2abb36a81b1821102daee447c7170197 100644 --- a/src/main/java/org/bukkit/entity/Player.java +++ b/src/main/java/org/bukkit/entity/Player.java -@@ -2583,6 +2583,22 @@ public interface Player extends HumanEntity, Conversable, OfflinePlayer, PluginM +@@ -2623,6 +2623,22 @@ public interface Player extends HumanEntity, Conversable, OfflinePlayer, PluginM @Deprecated // Paper public String getLocale(); diff --git a/patches/api/0015-Expose-server-build-information.patch b/patches/api/0015-Expose-server-build-information.patch index f1bf77caf847..2853b4562644 100644 --- a/patches/api/0015-Expose-server-build-information.patch +++ b/patches/api/0015-Expose-server-build-information.patch @@ -316,21 +316,22 @@ index ba28d9f3213ca4b5f15178dc637bff37a8896edc..8a07f21eeb04fb54032ce377a1478f60 * Gets a view of all currently logged in players. This {@linkplain * Collections#unmodifiableCollection(Collection) view} is a reused diff --git a/src/main/java/org/bukkit/UnsafeValues.java b/src/main/java/org/bukkit/UnsafeValues.java -index 12ef99f1c91b92a133611c5f5aeaaeebd02ce232..6e67fdb091a006d2d13bc2d93db4d55348af4c8f 100644 +index d8b346fe0f9634218954fe818d53272a0896af9c..45ed0007d6de20b98794b3ccaef57aed213e72d4 100644 --- a/src/main/java/org/bukkit/UnsafeValues.java +++ b/src/main/java/org/bukkit/UnsafeValues.java -@@ -163,5 +163,12 @@ public interface UnsafeValues { - */ - @Deprecated(forRemoval = true) - String getTimingsServerName(); +@@ -151,4 +151,13 @@ public interface UnsafeValues { + return !Bukkit.getUnsafe().isSupportedApiVersion(plugin.getDescription().getAPIVersion()); + } + // Paper end + ++ // Paper start + /** + * Called once by the version command on first use, then cached. + */ + default com.destroystokyo.paper.util.VersionFetcher getVersionFetcher() { + return new com.destroystokyo.paper.util.VersionFetcher.DummyVersionFetcher(); + } - // Paper end ++ // Paper end } diff --git a/src/main/java/org/bukkit/command/defaults/VersionCommand.java b/src/main/java/org/bukkit/command/defaults/VersionCommand.java index 263208d3cba36cb80c9ee4e3022ef702ea113df2..e64bb57f74e6d6f78927be228825b3e0bdf41f48 100644 diff --git a/patches/api/0017-Add-view-distance-API.patch b/patches/api/0017-Add-view-distance-API.patch index aff8f375d85c..abf7dbde0afb 100644 --- a/patches/api/0017-Add-view-distance-API.patch +++ b/patches/api/0017-Add-view-distance-API.patch @@ -79,10 +79,10 @@ index 9732929b666b0a5e1a2a41c8e8794cc4f2535e41..0a3a66e04f8785874f10a76603bff464 * Gets all generated structures that intersect the chunk at the given * coordinates.
    diff --git a/src/main/java/org/bukkit/entity/Player.java b/src/main/java/org/bukkit/entity/Player.java -index 1ba2f706a62ee6962451305b1895654453b485cd..7d530bd0e4d833da760d1cf82aba966b7fb480b1 100644 +index 1c25725a2abb36a81b1821102daee447c7170197..28e821ca1ec8ec3eba160ef2ed06ab1bb7387cae 100644 --- a/src/main/java/org/bukkit/entity/Player.java +++ b/src/main/java/org/bukkit/entity/Player.java -@@ -2597,6 +2597,82 @@ public interface Player extends HumanEntity, Conversable, OfflinePlayer, PluginM +@@ -2637,6 +2637,82 @@ public interface Player extends HumanEntity, Conversable, OfflinePlayer, PluginM * @param affects Whether the player can affect mob spawning */ public void setAffectsSpawning(boolean affects); diff --git a/patches/api/0020-Graduate-bungeecord-chat-API-from-spigot-subclasses.patch b/patches/api/0020-Graduate-bungeecord-chat-API-from-spigot-subclasses.patch index d88bd2a23610..ab7a5818455e 100644 --- a/patches/api/0020-Graduate-bungeecord-chat-API-from-spigot-subclasses.patch +++ b/patches/api/0020-Graduate-bungeecord-chat-API-from-spigot-subclasses.patch @@ -6,7 +6,7 @@ Subject: [PATCH] Graduate bungeecord chat API from spigot subclasses Change Javadoc to be accurate diff --git a/src/main/java/org/bukkit/Bukkit.java b/src/main/java/org/bukkit/Bukkit.java -index fe074fe9553f61bdd72b64830532a78415348781..4c5327da1468cb1f9af00a99e7e79f578c47ee2a 100644 +index 26f3ac9c15ff554becfe8ea53a48f67b2de60ed6..bd3fa2bcee24ab7e8f740722f55ed6294fdb294a 100644 --- a/src/main/java/org/bukkit/Bukkit.java +++ b/src/main/java/org/bukkit/Bukkit.java @@ -472,6 +472,30 @@ public final class Bukkit { @@ -41,7 +41,7 @@ index fe074fe9553f61bdd72b64830532a78415348781..4c5327da1468cb1f9af00a99e7e79f57 * Gets the name of the update folder. The update folder is used to safely * update plugins at the right moment on a plugin load. diff --git a/src/main/java/org/bukkit/Server.java b/src/main/java/org/bukkit/Server.java -index 9545da2adacaf0bd719c2baef929588cd1042d25..19b75704ed9eee0c929df417e1e5d0ea3718e2f8 100644 +index d78481bf17818415524f14417caf86d5684b2235..067eb3a5f5676f3b1b3f49a65df9c4054c48a1e7 100644 --- a/src/main/java/org/bukkit/Server.java +++ b/src/main/java/org/bukkit/Server.java @@ -383,6 +383,30 @@ public interface Server extends PluginMessageRecipient, net.kyori.adventure.audi @@ -76,10 +76,10 @@ index 9545da2adacaf0bd719c2baef929588cd1042d25..19b75704ed9eee0c929df417e1e5d0ea * Gets the name of the update folder. The update folder is used to safely * update plugins at the right moment on a plugin load. diff --git a/src/main/java/org/bukkit/entity/Player.java b/src/main/java/org/bukkit/entity/Player.java -index 7d530bd0e4d833da760d1cf82aba966b7fb480b1..f9bacbfa223826b3b54525648080fda306a1ec36 100644 +index 28e821ca1ec8ec3eba160ef2ed06ab1bb7387cae..fefd6b8a4171c9ec3d2a09d1accb9f37d66aa5b9 100644 --- a/src/main/java/org/bukkit/entity/Player.java +++ b/src/main/java/org/bukkit/entity/Player.java -@@ -1214,6 +1214,42 @@ public interface Player extends HumanEntity, Conversable, OfflinePlayer, PluginM +@@ -1254,6 +1254,42 @@ public interface Player extends HumanEntity, Conversable, OfflinePlayer, PluginM */ public void sendMap(@NotNull MapView map); diff --git a/patches/api/0021-Add-exception-reporting-event.patch b/patches/api/0021-Add-exception-reporting-event.patch index adf50a8c29ac..c592c2b2ee3c 100644 --- a/patches/api/0021-Add-exception-reporting-event.patch +++ b/patches/api/0021-Add-exception-reporting-event.patch @@ -494,7 +494,7 @@ index 36fc2c35395c72f8b81a2a2f3265fd205384ce26..c7fa1d235cea78bda4656ed66b8d42b1 } diff --git a/src/main/java/org/bukkit/plugin/SimplePluginManager.java b/src/main/java/org/bukkit/plugin/SimplePluginManager.java -index e7b1895d3918487d711afcbe41d76863d85c0a62..003bece642b682985625db93cad93026352bfc66 100644 +index 2c77b6ab388bd689acb8d84ec62bd5df1eb9373e..b878e7167cfcdea0e224c182b40abeadd339d3b3 100644 --- a/src/main/java/org/bukkit/plugin/SimplePluginManager.java +++ b/src/main/java/org/bukkit/plugin/SimplePluginManager.java @@ -528,7 +528,8 @@ public final class SimplePluginManager implements PluginManager { diff --git a/patches/api/0024-Player-Tab-List-and-Title-APIs.patch b/patches/api/0024-Player-Tab-List-and-Title-APIs.patch index 33be050ecfac..888137a8b5c6 100644 --- a/patches/api/0024-Player-Tab-List-and-Title-APIs.patch +++ b/patches/api/0024-Player-Tab-List-and-Title-APIs.patch @@ -432,10 +432,10 @@ index 0000000000000000000000000000000000000000..20a028450667edf102b59b6b50ac6e89 + } +} diff --git a/src/main/java/org/bukkit/entity/Player.java b/src/main/java/org/bukkit/entity/Player.java -index f9bacbfa223826b3b54525648080fda306a1ec36..a0317801b5a41d523324c1482356f26935f6a330 100644 +index fefd6b8a4171c9ec3d2a09d1accb9f37d66aa5b9..2685a6fcda7afa23d3f5067af1d5d2fc89bdb0e6 100644 --- a/src/main/java/org/bukkit/entity/Player.java +++ b/src/main/java/org/bukkit/entity/Player.java -@@ -1248,6 +1248,131 @@ public interface Player extends HumanEntity, Conversable, OfflinePlayer, PluginM +@@ -1288,6 +1288,131 @@ public interface Player extends HumanEntity, Conversable, OfflinePlayer, PluginM public default void sendMessage(net.md_5.bungee.api.ChatMessageType position, net.md_5.bungee.api.chat.BaseComponent... components) { spigot().sendMessage(position, components); } diff --git a/patches/api/0026-Complete-resource-pack-API.patch b/patches/api/0026-Complete-resource-pack-API.patch index 22c24d7cc108..ab4274c827a9 100644 --- a/patches/api/0026-Complete-resource-pack-API.patch +++ b/patches/api/0026-Complete-resource-pack-API.patch @@ -5,10 +5,10 @@ Subject: [PATCH] Complete resource pack API diff --git a/src/main/java/org/bukkit/entity/Player.java b/src/main/java/org/bukkit/entity/Player.java -index a0317801b5a41d523324c1482356f26935f6a330..47c792202e8cc6d97fcb5e9bed98d327ecc5ab2b 100644 +index 2685a6fcda7afa23d3f5067af1d5d2fc89bdb0e6..47a0d09b433024cd464737eced8a31f995b1ffaf 100644 --- a/src/main/java/org/bukkit/entity/Player.java +++ b/src/main/java/org/bukkit/entity/Player.java -@@ -2212,6 +2212,180 @@ public interface Player extends HumanEntity, Conversable, OfflinePlayer, PluginM +@@ -2252,6 +2252,180 @@ public interface Player extends HumanEntity, Conversable, OfflinePlayer, PluginM void setResourcePack(@NotNull UUID uuid, @NotNull String url, byte @Nullable [] hash, net.kyori.adventure.text.@Nullable Component prompt, boolean force); // Paper end diff --git a/patches/api/0039-LootTable-API.patch b/patches/api/0039-LootTable-API.patch index 366e828564fe..75ed22dfb5ff 100644 --- a/patches/api/0039-LootTable-API.patch +++ b/patches/api/0039-LootTable-API.patch @@ -426,10 +426,10 @@ index 9ea403e6fd8e960d017660e0aec118abeda2c42b..238d118f7788b13cd86b7e9ea3a0fc38 +public interface StorageMinecart extends Minecart, InventoryHolder, LootableEntityInventory { // Paper } diff --git a/src/main/java/org/bukkit/loot/Lootable.java b/src/main/java/org/bukkit/loot/Lootable.java -index 24a3d989db3bc67e7afe8459a3d4bb132f448ea7..ad4b0fb7f55ed44dc74fb5a4bd36be6004231116 100644 +index b3e9347496fd60aa4f5d18ff256e8d4d73f2d9cd..649dd959035843604525a637dba639a4fbd34f97 100644 --- a/src/main/java/org/bukkit/loot/Lootable.java +++ b/src/main/java/org/bukkit/loot/Lootable.java -@@ -36,6 +36,31 @@ public interface Lootable { +@@ -35,6 +35,31 @@ public interface Lootable { @Nullable LootTable getLootTable(); diff --git a/patches/api/0045-Add-String-based-Action-Bar-API.patch b/patches/api/0045-Add-String-based-Action-Bar-API.patch index 8107305c07dd..c2b98dd46f46 100644 --- a/patches/api/0045-Add-String-based-Action-Bar-API.patch +++ b/patches/api/0045-Add-String-based-Action-Bar-API.patch @@ -5,10 +5,10 @@ Subject: [PATCH] Add String based Action Bar API diff --git a/src/main/java/org/bukkit/entity/Player.java b/src/main/java/org/bukkit/entity/Player.java -index 47c792202e8cc6d97fcb5e9bed98d327ecc5ab2b..add0826af957c773975f840c28cf77afbab85a09 100644 +index 47a0d09b433024cd464737eced8a31f995b1ffaf..9513ce004101bd1bef90ca4a558f6b7c28d97adb 100644 --- a/src/main/java/org/bukkit/entity/Player.java +++ b/src/main/java/org/bukkit/entity/Player.java -@@ -1215,6 +1215,39 @@ public interface Player extends HumanEntity, Conversable, OfflinePlayer, PluginM +@@ -1255,6 +1255,39 @@ public interface Player extends HumanEntity, Conversable, OfflinePlayer, PluginM public void sendMap(@NotNull MapView map); // Paper start @@ -48,7 +48,7 @@ index 47c792202e8cc6d97fcb5e9bed98d327ecc5ab2b..add0826af957c773975f840c28cf77af /** * Sends the component to the player * -@@ -1242,9 +1275,11 @@ public interface Player extends HumanEntity, Conversable, OfflinePlayer, PluginM +@@ -1282,9 +1315,11 @@ public interface Player extends HumanEntity, Conversable, OfflinePlayer, PluginM /** * Sends an array of components as a single message to the specified screen position of this player * diff --git a/patches/api/0053-Fix-upstream-javadocs.patch b/patches/api/0053-Fix-upstream-javadocs.patch index 135849142cbe..151552e5d6fc 100644 --- a/patches/api/0053-Fix-upstream-javadocs.patch +++ b/patches/api/0053-Fix-upstream-javadocs.patch @@ -89,10 +89,10 @@ index db6fcd635e295e561642d49941fd8e611247d38e..344b2b5d9207d2645bc5417d1ec00dd0 MOTION_BLOCKING_NO_LEAVES, /** diff --git a/src/main/java/org/bukkit/Particle.java b/src/main/java/org/bukkit/Particle.java -index 62a8bb18855be13ed1c466b4be7afcd3c91dc7aa..de9fd0fadd6d16ffe883a618bf499214878f443d 100644 +index 13557f78a6853fdf0619f0479cab7591ddadf666..a1896780f312a91ab2330d2c850641d66143f23e 100644 --- a/src/main/java/org/bukkit/Particle.java +++ b/src/main/java/org/bukkit/Particle.java -@@ -195,7 +195,7 @@ public enum Particle implements Keyed { +@@ -206,7 +206,7 @@ public enum Particle implements Keyed { } /** @@ -430,7 +430,7 @@ index 4e1fb0974d061d5bb64899cac576318d2e6f8bf6..539b3527d0c66611e21712f29b90fba9 public int getEntityId(); diff --git a/src/main/java/org/bukkit/entity/HumanEntity.java b/src/main/java/org/bukkit/entity/HumanEntity.java -index 40214a136894270695746ac83fb5f7bcc406f34a..2bb4d2da99d3f0f70e19381c13b43c147c34f944 100644 +index 29f9b380dda2f370e2a1159811167431796ec8a8..46a076bd8dc2f5d1094899638ab255a5a31c2568 100644 --- a/src/main/java/org/bukkit/entity/HumanEntity.java +++ b/src/main/java/org/bukkit/entity/HumanEntity.java @@ -22,6 +22,11 @@ import org.jetbrains.annotations.Nullable; @@ -500,10 +500,10 @@ index ae9eaaa8e38e1d9dfc459926c7fc51ddb89de84a..b2ec535bb1b0ce0c114ddd7638b90218 @Override public int getConversionTime(); diff --git a/src/main/java/org/bukkit/entity/Player.java b/src/main/java/org/bukkit/entity/Player.java -index add0826af957c773975f840c28cf77afbab85a09..fea44ddd358b65681d13215244836c21fc88e0fd 100644 +index 9513ce004101bd1bef90ca4a558f6b7c28d97adb..c8ba70f787a3460c1f9faaeaab086e9e43ecaf6f 100644 --- a/src/main/java/org/bukkit/entity/Player.java +++ b/src/main/java/org/bukkit/entity/Player.java -@@ -476,15 +476,15 @@ public interface Player extends HumanEntity, Conversable, OfflinePlayer, PluginM +@@ -492,15 +492,15 @@ public interface Player extends HumanEntity, Conversable, OfflinePlayer, PluginM /** * Saves the players current location, health, inventory, motion, and @@ -523,7 +523,7 @@ index add0826af957c773975f840c28cf77afbab85a09..fea44ddd358b65681d13215244836c21 *

    * Note: This will overwrite the players current inventory, health, * motion, etc, with the state from the saved dat file. -@@ -821,7 +821,7 @@ public interface Player extends HumanEntity, Conversable, OfflinePlayer, PluginM +@@ -861,7 +861,7 @@ public interface Player extends HumanEntity, Conversable, OfflinePlayer, PluginM /** * Plays an effect to just this player. * @@ -532,7 +532,7 @@ index add0826af957c773975f840c28cf77afbab85a09..fea44ddd358b65681d13215244836c21 * @param loc the location to play the effect at * @param effect the {@link Effect} * @param data a data bit needed for some effects -@@ -1232,7 +1232,7 @@ public interface Player extends HumanEntity, Conversable, OfflinePlayer, PluginM +@@ -1272,7 +1272,7 @@ public interface Player extends HumanEntity, Conversable, OfflinePlayer, PluginM * * Use supplied alternative character to the section symbol to represent legacy color codes. * @@ -541,7 +541,7 @@ index add0826af957c773975f840c28cf77afbab85a09..fea44ddd358b65681d13215244836c21 * @param message The message to send * @deprecated use {@link #sendActionBar(net.kyori.adventure.text.Component)} */ -@@ -1705,7 +1705,7 @@ public interface Player extends HumanEntity, Conversable, OfflinePlayer, PluginM +@@ -1745,7 +1745,7 @@ public interface Player extends HumanEntity, Conversable, OfflinePlayer, PluginM /** * Allows this player to see a player that was previously hidden. If @@ -550,7 +550,7 @@ index add0826af957c773975f840c28cf77afbab85a09..fea44ddd358b65681d13215244836c21 * remain hidden until the other plugin calls this method too. * * @param plugin Plugin that wants to show the player -@@ -1732,7 +1732,7 @@ public interface Player extends HumanEntity, Conversable, OfflinePlayer, PluginM +@@ -1772,7 +1772,7 @@ public interface Player extends HumanEntity, Conversable, OfflinePlayer, PluginM /** * Allows this player to see an entity that was previously hidden. If @@ -559,7 +559,7 @@ index add0826af957c773975f840c28cf77afbab85a09..fea44ddd358b65681d13215244836c21 * remain hidden until the other plugin calls this method too. * * @param plugin Plugin that wants to show the entity -@@ -1815,9 +1815,6 @@ public interface Player extends HumanEntity, Conversable, OfflinePlayer, PluginM +@@ -1855,9 +1855,6 @@ public interface Player extends HumanEntity, Conversable, OfflinePlayer, PluginM * case this method will have no affect on them. Use the * {@link PlayerResourcePackStatusEvent} to figure out whether or not * the player loaded the pack! @@ -569,7 +569,7 @@ index add0826af957c773975f840c28cf77afbab85a09..fea44ddd358b65681d13215244836c21 *

  • The request is send with "null" as the hash. This might result * in newer versions not loading the pack correctly. * -@@ -1851,9 +1848,6 @@ public interface Player extends HumanEntity, Conversable, OfflinePlayer, PluginM +@@ -1891,9 +1888,6 @@ public interface Player extends HumanEntity, Conversable, OfflinePlayer, PluginM * case this method will have no affect on them. Use the * {@link PlayerResourcePackStatusEvent} to figure out whether or not * the player loaded the pack! @@ -579,7 +579,7 @@ index add0826af957c773975f840c28cf77afbab85a09..fea44ddd358b65681d13215244836c21 *
  • The request is send with empty string as the hash. This might result * in newer versions not loading the pack correctly. * -@@ -1890,9 +1884,6 @@ public interface Player extends HumanEntity, Conversable, OfflinePlayer, PluginM +@@ -1930,9 +1924,6 @@ public interface Player extends HumanEntity, Conversable, OfflinePlayer, PluginM * case this method will have no affect on them. Use the * {@link PlayerResourcePackStatusEvent} to figure out whether or not * the player loaded the pack! @@ -1604,10 +1604,10 @@ index 35c6594fd1040a1af1029e7260e5e3a9307b107d..d58719ee75bef8bc265bfc81bc5d88a4 void addChargedProjectile(@NotNull ItemStack item); } diff --git a/src/main/java/org/bukkit/inventory/meta/ItemMeta.java b/src/main/java/org/bukkit/inventory/meta/ItemMeta.java -index 014c1a0379e532a5c924694a8e0715eb0ba50ec2..10ca843e57c74dfa32d539acd174c8867dfd56ec 100644 +index fabddfe3763e143b5a769764cb324f97876ccb1c..480dd9a5ff334e6f32b98aa0108ff02e6f7077ab 100644 --- a/src/main/java/org/bukkit/inventory/meta/ItemMeta.java +++ b/src/main/java/org/bukkit/inventory/meta/ItemMeta.java -@@ -540,7 +540,7 @@ public interface ItemMeta extends Cloneable, ConfigurationSerializable, Persiste +@@ -744,7 +744,7 @@ public interface ItemMeta extends Cloneable, ConfigurationSerializable, Persiste * The returned component is a snapshot of its current state and does not * reflect a live view of what is on an item. After changing any value on * this component, it must be set with @@ -1616,7 +1616,7 @@ index 014c1a0379e532a5c924694a8e0715eb0ba50ec2..10ca843e57c74dfa32d539acd174c886 * to apply the changes. * * @return component -@@ -549,7 +549,7 @@ public interface ItemMeta extends Cloneable, ConfigurationSerializable, Persiste +@@ -753,7 +753,7 @@ public interface ItemMeta extends Cloneable, ConfigurationSerializable, Persiste JukeboxPlayableComponent getJukeboxPlayable(); /** @@ -1625,7 +1625,7 @@ index 014c1a0379e532a5c924694a8e0715eb0ba50ec2..10ca843e57c74dfa32d539acd174c886 * * @param jukeboxPlayable new component */ -@@ -576,7 +576,7 @@ public interface ItemMeta extends Cloneable, ConfigurationSerializable, Persiste +@@ -780,7 +780,7 @@ public interface ItemMeta extends Cloneable, ConfigurationSerializable, Persiste /** * Return an immutable copy of all {@link Attribute}s and their * {@link AttributeModifier}s for a given {@link EquipmentSlot}.
    diff --git a/patches/api/0059-Shoulder-Entities-Release-API.patch b/patches/api/0059-Shoulder-Entities-Release-API.patch index 51832da222a0..183b51f4d71b 100644 --- a/patches/api/0059-Shoulder-Entities-Release-API.patch +++ b/patches/api/0059-Shoulder-Entities-Release-API.patch @@ -5,10 +5,10 @@ Subject: [PATCH] Shoulder Entities Release API diff --git a/src/main/java/org/bukkit/entity/HumanEntity.java b/src/main/java/org/bukkit/entity/HumanEntity.java -index 2bb4d2da99d3f0f70e19381c13b43c147c34f944..b8431d9722ef9fce0ac9e18367abef22717997ca 100644 +index 46a076bd8dc2f5d1094899638ab255a5a31c2568..08cef0d9fc27d0c09472cfe7091330d95956d9eb 100644 --- a/src/main/java/org/bukkit/entity/HumanEntity.java +++ b/src/main/java/org/bukkit/entity/HumanEntity.java -@@ -346,6 +346,26 @@ public interface HumanEntity extends LivingEntity, AnimalTamer, InventoryHolder +@@ -377,6 +377,26 @@ public interface HumanEntity extends LivingEntity, AnimalTamer, InventoryHolder */ public int getExpToLevel(); diff --git a/patches/api/0063-Add-getI18NDisplayName-API.patch b/patches/api/0063-Add-getI18NDisplayName-API.patch index dd788d0c271b..ecdf3c006c65 100644 --- a/patches/api/0063-Add-getI18NDisplayName-API.patch +++ b/patches/api/0063-Add-getI18NDisplayName-API.patch @@ -8,7 +8,7 @@ Currently the server only supports the English language. To override this, You must replace the language file embedded in the server jar. diff --git a/src/main/java/org/bukkit/inventory/ItemFactory.java b/src/main/java/org/bukkit/inventory/ItemFactory.java -index d5342258086066d3b9ef404916bad8440f0cf0cd..c92843d0adb438d7a64a5d00ce67b67efd65ca14 100644 +index d5342258086066d3b9ef404916bad8440f0cf0cd..333884bc8fe45c66d37a1bbcebc10ea655d2055f 100644 --- a/src/main/java/org/bukkit/inventory/ItemFactory.java +++ b/src/main/java/org/bukkit/inventory/ItemFactory.java @@ -220,4 +220,20 @@ public interface ItemFactory { @@ -28,7 +28,7 @@ index d5342258086066d3b9ef404916bad8440f0cf0cd..c92843d0adb438d7a64a5d00ce67b67e + * {@link net.kyori.adventure.text.Component#translatable(net.kyori.adventure.translation.Translatable)} instead. + */ + @Nullable -+ @Deprecated(since = "1.18.1") ++ @Deprecated(since = "1.18.1", forRemoval = true) + String getI18NDisplayName(@Nullable ItemStack item); + // Paper end - add getI18NDisplayName } diff --git a/patches/api/0064-ensureServerConversions-API.patch b/patches/api/0064-ensureServerConversions-API.patch index 8fa95dd9cbb9..9d1a4cb932f5 100644 --- a/patches/api/0064-ensureServerConversions-API.patch +++ b/patches/api/0064-ensureServerConversions-API.patch @@ -7,11 +7,11 @@ This will take a Bukkit ItemStack and run it through any conversions a server pr to ensure it meets latest minecraft expectations. diff --git a/src/main/java/org/bukkit/inventory/ItemFactory.java b/src/main/java/org/bukkit/inventory/ItemFactory.java -index c92843d0adb438d7a64a5d00ce67b67efd65ca14..3d08beee52f2247db6f6e679206ed6a965fbf9a8 100644 +index 333884bc8fe45c66d37a1bbcebc10ea655d2055f..c1ec8efffd5ff2a4dcb1d761be9a431a62284607 100644 --- a/src/main/java/org/bukkit/inventory/ItemFactory.java +++ b/src/main/java/org/bukkit/inventory/ItemFactory.java @@ -236,4 +236,18 @@ public interface ItemFactory { - @Deprecated(since = "1.18.1") + @Deprecated(since = "1.18.1", forRemoval = true) String getI18NDisplayName(@Nullable ItemStack item); // Paper end - add getI18NDisplayName + diff --git a/patches/api/0065-LivingEntity-setKiller.patch b/patches/api/0065-LivingEntity-setKiller.patch index fb3719a67986..0c89dae9a658 100644 --- a/patches/api/0065-LivingEntity-setKiller.patch +++ b/patches/api/0065-LivingEntity-setKiller.patch @@ -5,7 +5,7 @@ Subject: [PATCH] LivingEntity#setKiller diff --git a/src/main/java/org/bukkit/entity/LivingEntity.java b/src/main/java/org/bukkit/entity/LivingEntity.java -index 32e89741ffd895e31af0104a0126c2f72742a1bb..f154c5607b1dc3585052d9f02cf8b28cf8a3c886 100644 +index b0fbad5de65c33710ec46734ad6c69ec9b2769d5..e6bdfd14bffa394cd717de7118de951a997f50b3 100644 --- a/src/main/java/org/bukkit/entity/LivingEntity.java +++ b/src/main/java/org/bukkit/entity/LivingEntity.java @@ -365,6 +365,15 @@ public interface LivingEntity extends Attributable, Damageable, ProjectileSource diff --git a/patches/api/0067-Allow-plugins-to-use-SLF4J-for-logging.patch b/patches/api/0067-Allow-plugins-to-use-SLF4J-for-logging.patch index dae4f692516f..d94d18165b8c 100644 --- a/patches/api/0067-Allow-plugins-to-use-SLF4J-for-logging.patch +++ b/patches/api/0067-Allow-plugins-to-use-SLF4J-for-logging.patch @@ -14,7 +14,7 @@ it without having to shade it in the plugin and going through several layers of logging abstraction. diff --git a/build.gradle.kts b/build.gradle.kts -index c55223e0d0c30b7575bc5322e993d0205cbda85f..33e4635dbe6afabecfaaaeff57495e5b3ca5f891 100644 +index 940fb51c1f3054465f305e98b13aac49c77e3a91..6c8464d9e862b1b4dbf7a77e25446aa870803dae 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -12,6 +12,8 @@ java { diff --git a/patches/api/0068-Handle-plugin-prefixes-in-implementation-logging-con.patch b/patches/api/0068-Handle-plugin-prefixes-in-implementation-logging-con.patch index f80ceb6cb702..74e02036c1a7 100644 --- a/patches/api/0068-Handle-plugin-prefixes-in-implementation-logging-con.patch +++ b/patches/api/0068-Handle-plugin-prefixes-in-implementation-logging-con.patch @@ -17,7 +17,7 @@ The implementation should handle plugin prefixes by displaying logger names when appropriate. diff --git a/src/main/java/org/bukkit/plugin/java/JavaPlugin.java b/src/main/java/org/bukkit/plugin/java/JavaPlugin.java -index 2a14522c484febcd880d00197df4359a0020dddd..f81e335a4e533221529355bec2f5d588aa79e60c 100644 +index 7f17337b9f0fb60fa1c91c47af496c03290d1b1c..801578de8599d6b546cde63b3f2655fab48eee03 100644 --- a/src/main/java/org/bukkit/plugin/java/JavaPlugin.java +++ b/src/main/java/org/bukkit/plugin/java/JavaPlugin.java @@ -47,7 +47,7 @@ public abstract class JavaPlugin extends PluginBase { diff --git a/patches/api/0073-AsyncTabCompleteEvent.patch b/patches/api/0073-AsyncTabCompleteEvent.patch index e78867039c9c..a48195bf9fc6 100644 --- a/patches/api/0073-AsyncTabCompleteEvent.patch +++ b/patches/api/0073-AsyncTabCompleteEvent.patch @@ -596,7 +596,7 @@ index 270e6d8ad4358baa256cee5f16cff281f063ce3b..6465e290c090d82986352d5ab7ba5dc6 @Override diff --git a/src/test/java/org/bukkit/AnnotationTest.java b/src/test/java/org/bukkit/AnnotationTest.java -index 7ff939ea41417bad3a436a87c89d5efa7ecefe86..88d5db2995829cba919d78f988d5c735cf70cb1b 100644 +index f8b8969ee7a0b6f7b3224ff081e35c14a398c9d0..f9e4b16a21d6cc6c9cbbe06d20c8af25e72e3ddb 100644 --- a/src/test/java/org/bukkit/AnnotationTest.java +++ b/src/test/java/org/bukkit/AnnotationTest.java @@ -48,6 +48,8 @@ public class AnnotationTest { diff --git a/patches/api/0074-Expose-client-protocol-version-and-virtual-host.patch b/patches/api/0074-Expose-client-protocol-version-and-virtual-host.patch index 0a078f013df6..383d46bfff14 100644 --- a/patches/api/0074-Expose-client-protocol-version-and-virtual-host.patch +++ b/patches/api/0074-Expose-client-protocol-version-and-virtual-host.patch @@ -55,10 +55,10 @@ index 0000000000000000000000000000000000000000..c84ce3fc874eea3d8f0b1cf5273996d9 + +} diff --git a/src/main/java/org/bukkit/entity/Player.java b/src/main/java/org/bukkit/entity/Player.java -index fea44ddd358b65681d13215244836c21fc88e0fd..d470b26abbf54514e498d81d68af566e2af0a63f 100644 +index c8ba70f787a3460c1f9faaeaab086e9e43ecaf6f..1a6ddc4228ed4c26235cfe56b1e0a102c7aa320f 100644 --- a/src/main/java/org/bukkit/entity/Player.java +++ b/src/main/java/org/bukkit/entity/Player.java -@@ -57,7 +57,7 @@ import org.jetbrains.annotations.Nullable; +@@ -58,7 +58,7 @@ import org.jetbrains.annotations.Nullable; /** * Represents a player, connected or not */ diff --git a/patches/api/0078-Ability-to-apply-mending-to-XP-API.patch b/patches/api/0078-Ability-to-apply-mending-to-XP-API.patch index bb3d96e6f16f..b4e68511a402 100644 --- a/patches/api/0078-Ability-to-apply-mending-to-XP-API.patch +++ b/patches/api/0078-Ability-to-apply-mending-to-XP-API.patch @@ -10,10 +10,10 @@ of giving the player experience points. Both an API To standalone mend, and apply mending logic to .giveExp has been added. diff --git a/src/main/java/org/bukkit/entity/Player.java b/src/main/java/org/bukkit/entity/Player.java -index d470b26abbf54514e498d81d68af566e2af0a63f..77a740fb62a9c442c9b67943f8775a824cf2617f 100644 +index 1a6ddc4228ed4c26235cfe56b1e0a102c7aa320f..0d92eac00ba883d696d45340feb808988be05c16 100644 --- a/src/main/java/org/bukkit/entity/Player.java +++ b/src/main/java/org/bukkit/entity/Player.java -@@ -1548,6 +1548,15 @@ public interface Player extends HumanEntity, Conversable, OfflinePlayer, PluginM +@@ -1588,6 +1588,15 @@ public interface Player extends HumanEntity, Conversable, OfflinePlayer, PluginM */ public void resetPlayerWeather(); @@ -29,7 +29,7 @@ index d470b26abbf54514e498d81d68af566e2af0a63f..77a740fb62a9c442c9b67943f8775a82 /** * Gets the player's cooldown between picking up experience orbs. * -@@ -1573,8 +1582,20 @@ public interface Player extends HumanEntity, Conversable, OfflinePlayer, PluginM +@@ -1613,8 +1622,20 @@ public interface Player extends HumanEntity, Conversable, OfflinePlayer, PluginM * Gives the player the amount of experience specified. * * @param amount Exp amount to give diff --git a/patches/api/0084-Add-ArmorStand-Item-Meta.patch b/patches/api/0084-Add-ArmorStand-Item-Meta.patch index aed15d7522d5..36fe72fcef7c 100644 --- a/patches/api/0084-Add-ArmorStand-Item-Meta.patch +++ b/patches/api/0084-Add-ArmorStand-Item-Meta.patch @@ -95,10 +95,10 @@ index 0000000000000000000000000000000000000000..7e4acfff16db80a75e1ff2fee1972b16 + void setMarker(boolean marker); +} diff --git a/src/main/java/org/bukkit/inventory/ItemType.java b/src/main/java/org/bukkit/inventory/ItemType.java -index d4c29562aa3999afdac8b5bc457aa3153a0e9d77..270f85e99084ddf029bef076c335fe6b9bbddbb5 100644 +index 9a28c9dfe8dd5dd73f08ab4907db7f257719dc7e..88e1883dffb64974f5ec60acbf2828cfb9de9439 100644 --- a/src/main/java/org/bukkit/inventory/ItemType.java +++ b/src/main/java/org/bukkit/inventory/ItemType.java -@@ -1791,7 +1791,7 @@ public interface ItemType extends Keyed, Translatable { +@@ -1942,7 +1942,7 @@ public interface ItemType extends Keyed, Translatable { ItemType.Typed RABBIT_STEW = getItemType("rabbit_stew"); ItemType.Typed RABBIT_FOOT = getItemType("rabbit_foot"); ItemType.Typed RABBIT_HIDE = getItemType("rabbit_hide"); diff --git a/patches/api/0089-Player.setPlayerProfile-API.patch b/patches/api/0089-Player.setPlayerProfile-API.patch index ad5dc34a8985..fb9225d8d543 100644 --- a/patches/api/0089-Player.setPlayerProfile-API.patch +++ b/patches/api/0089-Player.setPlayerProfile-API.patch @@ -93,10 +93,10 @@ index 7a1b80e8d02f23c5d246c3032e5ced909f10bd41..01c052d90bbdad3fc374eb9c8e0a5133 /** diff --git a/src/main/java/org/bukkit/entity/Player.java b/src/main/java/org/bukkit/entity/Player.java -index 77a740fb62a9c442c9b67943f8775a824cf2617f..b427a37d1b382037e946e5a899e571c3aebe5ba9 100644 +index 0d92eac00ba883d696d45340feb808988be05c16..357e12c91a710751dc5bf3138362c10e5a26db1e 100644 --- a/src/main/java/org/bukkit/entity/Player.java +++ b/src/main/java/org/bukkit/entity/Player.java -@@ -3114,6 +3114,26 @@ public interface Player extends HumanEntity, Conversable, OfflinePlayer, PluginM +@@ -3154,6 +3154,26 @@ public interface Player extends HumanEntity, Conversable, OfflinePlayer, PluginM } // Paper end diff --git a/patches/api/0092-Add-openSign-method-to-HumanEntity.patch b/patches/api/0092-Add-openSign-method-to-HumanEntity.patch index 102ec8ae000c..90ee598a25f0 100644 --- a/patches/api/0092-Add-openSign-method-to-HumanEntity.patch +++ b/patches/api/0092-Add-openSign-method-to-HumanEntity.patch @@ -5,10 +5,10 @@ Subject: [PATCH] Add openSign method to HumanEntity diff --git a/src/main/java/org/bukkit/entity/HumanEntity.java b/src/main/java/org/bukkit/entity/HumanEntity.java -index b8431d9722ef9fce0ac9e18367abef22717997ca..0d01fe9c96a1b9076dbd6d031fa8cd41954ea8db 100644 +index 08cef0d9fc27d0c09472cfe7091330d95956d9eb..00b803cee96fef8830e5db8722c98ff14630fd2a 100644 --- a/src/main/java/org/bukkit/entity/HumanEntity.java +++ b/src/main/java/org/bukkit/entity/HumanEntity.java -@@ -507,6 +507,26 @@ public interface HumanEntity extends LivingEntity, AnimalTamer, InventoryHolder +@@ -538,6 +538,26 @@ public interface HumanEntity extends LivingEntity, AnimalTamer, InventoryHolder */ @Deprecated public void setShoulderEntityRight(@Nullable Entity entity); @@ -36,10 +36,10 @@ index b8431d9722ef9fce0ac9e18367abef22717997ca..0d01fe9c96a1b9076dbd6d031fa8cd41 /** * Make the entity drop the item in their hand. diff --git a/src/main/java/org/bukkit/entity/Player.java b/src/main/java/org/bukkit/entity/Player.java -index b427a37d1b382037e946e5a899e571c3aebe5ba9..d8f97d8626850ed833b2dd32fab682cdf61a9948 100644 +index 357e12c91a710751dc5bf3138362c10e5a26db1e..ecc0bbf986b065620910f3592115c0f45e863b98 100644 --- a/src/main/java/org/bukkit/entity/Player.java +++ b/src/main/java/org/bukkit/entity/Player.java -@@ -3075,10 +3075,12 @@ public interface Player extends HumanEntity, Conversable, OfflinePlayer, PluginM +@@ -3115,10 +3115,12 @@ public interface Player extends HumanEntity, Conversable, OfflinePlayer, PluginM /** * Open a Sign for editing by the Player. * diff --git a/patches/api/0093-Add-Ban-Methods-to-Player-Objects.patch b/patches/api/0093-Add-Ban-Methods-to-Player-Objects.patch index 8508d5aa31e9..62e9847bb6bd 100644 --- a/patches/api/0093-Add-Ban-Methods-to-Player-Objects.patch +++ b/patches/api/0093-Add-Ban-Methods-to-Player-Objects.patch @@ -86,10 +86,10 @@ index abbf3d6f11350ab2dd47a277771d9f46221036bd..a9d63b1630b05b86a0396355fcfee261 /** * Adds this user to the {@link ProfileBanList}. If a previous ban exists, this will diff --git a/src/main/java/org/bukkit/entity/Player.java b/src/main/java/org/bukkit/entity/Player.java -index d8f97d8626850ed833b2dd32fab682cdf61a9948..8a0e8447a7c5241a53ae933229f369bf93300c72 100644 +index ecc0bbf986b065620910f3592115c0f45e863b98..354beb974012fa83fdac0c9d28166016ace93070 100644 --- a/src/main/java/org/bukkit/entity/Player.java +++ b/src/main/java/org/bukkit/entity/Player.java -@@ -1215,6 +1215,186 @@ public interface Player extends HumanEntity, Conversable, OfflinePlayer, PluginM +@@ -1255,6 +1255,186 @@ public interface Player extends HumanEntity, Conversable, OfflinePlayer, PluginM public void sendMap(@NotNull MapView map); // Paper start diff --git a/patches/api/0097-Location.isChunkLoaded-API.patch b/patches/api/0097-Location.isChunkLoaded-API.patch index e1a043b2b47b..c865422539dd 100644 --- a/patches/api/0097-Location.isChunkLoaded-API.patch +++ b/patches/api/0097-Location.isChunkLoaded-API.patch @@ -5,7 +5,7 @@ Subject: [PATCH] Location.isChunkLoaded() API diff --git a/src/main/java/org/bukkit/Location.java b/src/main/java/org/bukkit/Location.java -index 85c342fc50f2fe0ce9a1b3980df9e088c3dea92d..251d26e6870490abd3e915c5e7c06ce1075a24ab 100644 +index bc8a64d54e001eae6ef4520a49e261b96c5ae9f3..7c7d2b2dc082fd3b5681a15a600422ae3937e2e1 100644 --- a/src/main/java/org/bukkit/Location.java +++ b/src/main/java/org/bukkit/Location.java @@ -533,6 +533,7 @@ public class Location implements Cloneable, ConfigurationSerializable, io.paperm diff --git a/patches/api/0098-Expand-World.spawnParticle-API-and-add-Builder.patch b/patches/api/0098-Expand-World.spawnParticle-API-and-add-Builder.patch index 884983b69f88..419d06621378 100644 --- a/patches/api/0098-Expand-World.spawnParticle-API-and-add-Builder.patch +++ b/patches/api/0098-Expand-World.spawnParticle-API-and-add-Builder.patch @@ -597,10 +597,10 @@ index 0000000000000000000000000000000000000000..6c405755f4507d6fbc6c3877c611a719 + } +} diff --git a/src/main/java/org/bukkit/Particle.java b/src/main/java/org/bukkit/Particle.java -index de9fd0fadd6d16ffe883a618bf499214878f443d..6f049e9044de4139971312f85ada19fb026fe75f 100644 +index a1896780f312a91ab2330d2c850641d66143f23e..37e7862be843da4f48ac061fb1625854fd671b2a 100644 --- a/src/main/java/org/bukkit/Particle.java +++ b/src/main/java/org/bukkit/Particle.java -@@ -194,6 +194,18 @@ public enum Particle implements Keyed { +@@ -205,6 +205,18 @@ public enum Particle implements Keyed { return key; } diff --git a/patches/api/0102-Location.toBlockLocation-toCenterLocation.patch b/patches/api/0102-Location.toBlockLocation-toCenterLocation.patch index c86b874f5f80..890edb723c04 100644 --- a/patches/api/0102-Location.toBlockLocation-toCenterLocation.patch +++ b/patches/api/0102-Location.toBlockLocation-toCenterLocation.patch @@ -6,7 +6,7 @@ Subject: [PATCH] Location.toBlockLocation/toCenterLocation() Convert location objects to their block coordinates, or the center of the block diff --git a/src/main/java/org/bukkit/Location.java b/src/main/java/org/bukkit/Location.java -index 251d26e6870490abd3e915c5e7c06ce1075a24ab..24a872dfc8cf1f4a567b6ebd5a5e742593616ede 100644 +index 7c7d2b2dc082fd3b5681a15a600422ae3937e2e1..3bcc8266d6b1de3c3f964ffad9820bf1b94bbd00 100644 --- a/src/main/java/org/bukkit/Location.java +++ b/src/main/java/org/bukkit/Location.java @@ -534,6 +534,32 @@ public class Location implements Cloneable, ConfigurationSerializable, io.paperm diff --git a/patches/api/0109-Add-getNearbyXXX-methods-to-Location.patch b/patches/api/0109-Add-getNearbyXXX-methods-to-Location.patch index 1898cc3ed689..63749fbc2588 100644 --- a/patches/api/0109-Add-getNearbyXXX-methods-to-Location.patch +++ b/patches/api/0109-Add-getNearbyXXX-methods-to-Location.patch @@ -5,7 +5,7 @@ Subject: [PATCH] Add "getNearbyXXX" methods to Location diff --git a/src/main/java/org/bukkit/Location.java b/src/main/java/org/bukkit/Location.java -index 24a872dfc8cf1f4a567b6ebd5a5e742593616ede..3161eae2fa5f03b7d3a5e9945ab659c15cf568c6 100644 +index 3bcc8266d6b1de3c3f964ffad9820bf1b94bbd00..df88bc77a3fa2506adf17eddc6300ac65774df6f 100644 --- a/src/main/java/org/bukkit/Location.java +++ b/src/main/java/org/bukkit/Location.java @@ -12,6 +12,15 @@ import org.bukkit.util.Vector; diff --git a/patches/api/0112-Expand-Explosions-API.patch b/patches/api/0112-Expand-Explosions-API.patch index 063dcb23797c..42f8422bee4f 100644 --- a/patches/api/0112-Expand-Explosions-API.patch +++ b/patches/api/0112-Expand-Explosions-API.patch @@ -9,7 +9,7 @@ Co-authored-by: Esoteric Enderman <90862990+EsotericEnderman@users.noreply.githu Co-authored-by: Bjarne Koll diff --git a/src/main/java/org/bukkit/Location.java b/src/main/java/org/bukkit/Location.java -index 3161eae2fa5f03b7d3a5e9945ab659c15cf568c6..af737017ee397f80c44ee02c6cc60cefa07f59c1 100644 +index df88bc77a3fa2506adf17eddc6300ac65774df6f..fe2e0939df61b1f59d12adf3f760f1d619bb3de3 100644 --- a/src/main/java/org/bukkit/Location.java +++ b/src/main/java/org/bukkit/Location.java @@ -7,6 +7,7 @@ import java.util.HashMap; diff --git a/patches/api/0114-LivingEntity-Active-Item-API.patch b/patches/api/0114-LivingEntity-Active-Item-API.patch index 89c2ebf21789..885fb3349a11 100644 --- a/patches/api/0114-LivingEntity-Active-Item-API.patch +++ b/patches/api/0114-LivingEntity-Active-Item-API.patch @@ -9,10 +9,10 @@ such as a bow or eating food. Co-authored-by: Jake Potrebic diff --git a/src/main/java/org/bukkit/entity/HumanEntity.java b/src/main/java/org/bukkit/entity/HumanEntity.java -index 0d01fe9c96a1b9076dbd6d031fa8cd41954ea8db..b0cb4377e14da5ef1e155513046c2340ab6e525e 100644 +index 00b803cee96fef8830e5db8722c98ff14630fd2a..5acc8740d14b53aadef1aa4d63d3355149acd0e2 100644 --- a/src/main/java/org/bukkit/entity/HumanEntity.java +++ b/src/main/java/org/bukkit/entity/HumanEntity.java -@@ -336,7 +336,9 @@ public interface HumanEntity extends LivingEntity, AnimalTamer, InventoryHolder +@@ -367,7 +367,9 @@ public interface HumanEntity extends LivingEntity, AnimalTamer, InventoryHolder * blocking). * * @return Whether their hand is raised diff --git a/patches/api/0116-Add-World.getEntity-UUID-API.patch b/patches/api/0116-Add-World.getEntity-UUID-API.patch index 93e1d998ed71..b0b2aa8c6fa3 100644 --- a/patches/api/0116-Add-World.getEntity-UUID-API.patch +++ b/patches/api/0116-Add-World.getEntity-UUID-API.patch @@ -5,7 +5,7 @@ Subject: [PATCH] Add World.getEntity(UUID) API diff --git a/src/main/java/org/bukkit/World.java b/src/main/java/org/bukkit/World.java -index 50c1e4957f66826feb0a2eb04293dbd6b5595700..fd61be5d75dadb91b5a4bb8dfe246c7ec7aa2f1f 100644 +index c2b5fdaace13c8bd46c073ac6d427fe411d96367..2053c92bc7e81b420ebf96cbd5f1275c514dd5f3 100644 --- a/src/main/java/org/bukkit/World.java +++ b/src/main/java/org/bukkit/World.java @@ -932,6 +932,17 @@ public interface World extends RegionAccessor, WorldInfo, PluginMessageRecipient diff --git a/patches/api/0117-InventoryCloseEvent-Reason-API.patch b/patches/api/0117-InventoryCloseEvent-Reason-API.patch index 1be4583340b2..572f11d4e603 100644 --- a/patches/api/0117-InventoryCloseEvent-Reason-API.patch +++ b/patches/api/0117-InventoryCloseEvent-Reason-API.patch @@ -7,7 +7,7 @@ Allows you to determine why an inventory was closed, enabling plugin developers to "confirm" things based on if it was player triggered close or not. diff --git a/src/main/java/org/bukkit/entity/HumanEntity.java b/src/main/java/org/bukkit/entity/HumanEntity.java -index 9cbb9093e7d8cd21eef6a23c265d68d7d0ee97b8..3985798654a3085c128144e46f7113b7744b8d14 100644 +index 5acc8740d14b53aadef1aa4d63d3355149acd0e2..6c2f01bf452d991faf40b995225bf7b7e49281df 100644 --- a/src/main/java/org/bukkit/entity/HumanEntity.java +++ b/src/main/java/org/bukkit/entity/HumanEntity.java @@ -187,6 +187,15 @@ public interface HumanEntity extends LivingEntity, AnimalTamer, InventoryHolder diff --git a/patches/api/0125-Expand-Location-Manipulation-API.patch b/patches/api/0125-Expand-Location-Manipulation-API.patch index e345f50d6207..ff5737313417 100644 --- a/patches/api/0125-Expand-Location-Manipulation-API.patch +++ b/patches/api/0125-Expand-Location-Manipulation-API.patch @@ -6,7 +6,7 @@ Subject: [PATCH] Expand Location Manipulation API Adds set(x, y, z), add(base, x, y, z), subtract(base, x, y, z); diff --git a/src/main/java/org/bukkit/Location.java b/src/main/java/org/bukkit/Location.java -index af737017ee397f80c44ee02c6cc60cefa07f59c1..41125de49db8eafce4be59cc110ce5be06836a47 100644 +index fe2e0939df61b1f59d12adf3f760f1d619bb3de3..56fd66a3fb5f6e33812d2981cd192d317453a0f5 100644 --- a/src/main/java/org/bukkit/Location.java +++ b/src/main/java/org/bukkit/Location.java @@ -545,6 +545,59 @@ public class Location implements Cloneable, ConfigurationSerializable, io.paperm diff --git a/patches/api/0129-Provide-Chunk-Coordinates-as-a-Long-API.patch b/patches/api/0129-Provide-Chunk-Coordinates-as-a-Long-API.patch index eb66322fb835..1d69849b56f1 100644 --- a/patches/api/0129-Provide-Chunk-Coordinates-as-a-Long-API.patch +++ b/patches/api/0129-Provide-Chunk-Coordinates-as-a-Long-API.patch @@ -44,7 +44,7 @@ index 20ed1c40437cbf8449dd4d7876086ccb6407b470..8764441ec1bae67a029b13c4c9824657 * Gets the world containing this chunk * diff --git a/src/main/java/org/bukkit/World.java b/src/main/java/org/bukkit/World.java -index fd61be5d75dadb91b5a4bb8dfe246c7ec7aa2f1f..4ecbfe4d28316527ff00e206941da9c0fc9235d0 100644 +index 2053c92bc7e81b420ebf96cbd5f1275c514dd5f3..11362777d834cad2265984c7aa493358105cbf68 100644 --- a/src/main/java/org/bukkit/World.java +++ b/src/main/java/org/bukkit/World.java @@ -182,6 +182,37 @@ public interface World extends RegionAccessor, WorldInfo, PluginMessageRecipient diff --git a/patches/api/0132-Allow-Blocks-to-be-accessed-via-a-long-key.patch b/patches/api/0132-Allow-Blocks-to-be-accessed-via-a-long-key.patch index 5204a8d4dbb9..5498e1910c58 100644 --- a/patches/api/0132-Allow-Blocks-to-be-accessed-via-a-long-key.patch +++ b/patches/api/0132-Allow-Blocks-to-be-accessed-via-a-long-key.patch @@ -18,7 +18,7 @@ Y range: [0, 1023] X, Z range: [-67 108 864, 67 108 863] diff --git a/src/main/java/org/bukkit/Location.java b/src/main/java/org/bukkit/Location.java -index 41125de49db8eafce4be59cc110ce5be06836a47..4df9a225e93aafb1e4af9591c482ac07e7f65422 100644 +index 56fd66a3fb5f6e33812d2981cd192d317453a0f5..12e1733d06471d0c2253ae846ee93a09140843cc 100644 --- a/src/main/java/org/bukkit/Location.java +++ b/src/main/java/org/bukkit/Location.java @@ -15,7 +15,6 @@ import org.jetbrains.annotations.Nullable; @@ -50,7 +50,7 @@ index 41125de49db8eafce4be59cc110ce5be06836a47..4df9a225e93aafb1e4af9591c482ac07 * @return A new location where X/Y/Z are the center of the block */ diff --git a/src/main/java/org/bukkit/World.java b/src/main/java/org/bukkit/World.java -index 4ecbfe4d28316527ff00e206941da9c0fc9235d0..190aba1ff06357dc0ef9341e584ab79b928d8f64 100644 +index 11362777d834cad2265984c7aa493358105cbf68..c474f6f0f1d758507f53c6f1ffbe3e26883e1425 100644 --- a/src/main/java/org/bukkit/World.java +++ b/src/main/java/org/bukkit/World.java @@ -99,6 +99,41 @@ public interface World extends RegionAccessor, WorldInfo, PluginMessageRecipient diff --git a/patches/api/0137-isChunkGenerated-API.patch b/patches/api/0137-isChunkGenerated-API.patch index a1d9b815f963..c379d6eb0343 100644 --- a/patches/api/0137-isChunkGenerated-API.patch +++ b/patches/api/0137-isChunkGenerated-API.patch @@ -5,7 +5,7 @@ Subject: [PATCH] isChunkGenerated API diff --git a/src/main/java/org/bukkit/Location.java b/src/main/java/org/bukkit/Location.java -index 4df9a225e93aafb1e4af9591c482ac07e7f65422..c30600666e7b32b8b4ba1e20ede04fd5ebd5a692 100644 +index 12e1733d06471d0c2253ae846ee93a09140843cc..b02efba048be00e42502111fcdd2297529926666 100644 --- a/src/main/java/org/bukkit/Location.java +++ b/src/main/java/org/bukkit/Location.java @@ -3,6 +3,7 @@ package org.bukkit; @@ -37,7 +37,7 @@ index 4df9a225e93aafb1e4af9591c482ac07e7f65422..c30600666e7b32b8b4ba1e20ede04fd5 /** diff --git a/src/main/java/org/bukkit/World.java b/src/main/java/org/bukkit/World.java -index 190aba1ff06357dc0ef9341e584ab79b928d8f64..1cc5bdd63a97a6bb62b1d29aca01658359bd15f1 100644 +index c474f6f0f1d758507f53c6f1ffbe3e26883e1425..ba9ab1d46effe1e6c08cebddb8b856e2b294d7cb 100644 --- a/src/main/java/org/bukkit/World.java +++ b/src/main/java/org/bukkit/World.java @@ -248,6 +248,19 @@ public interface World extends RegionAccessor, WorldInfo, PluginMessageRecipient diff --git a/patches/api/0139-Async-Chunks-API.patch b/patches/api/0139-Async-Chunks-API.patch index 037378415a38..caa55b4c1fae 100644 --- a/patches/api/0139-Async-Chunks-API.patch +++ b/patches/api/0139-Async-Chunks-API.patch @@ -8,7 +8,7 @@ Adds API's to load or generate chunks asynchronously. Also adds utility methods to Entity to teleport asynchronously. diff --git a/src/main/java/org/bukkit/World.java b/src/main/java/org/bukkit/World.java -index 1cc5bdd63a97a6bb62b1d29aca01658359bd15f1..6242b64416fdea1f3fd6378ba26ed7bb33ab4cc4 100644 +index ba9ab1d46effe1e6c08cebddb8b856e2b294d7cb..c77ca55c0686512e6d50b559139b6d6bbeb61062 100644 --- a/src/main/java/org/bukkit/World.java +++ b/src/main/java/org/bukkit/World.java @@ -977,6 +977,472 @@ public interface World extends RegionAccessor, WorldInfo, PluginMessageRecipient diff --git a/patches/api/0140-Add-ray-tracing-methods-to-LivingEntity.patch b/patches/api/0140-Add-ray-tracing-methods-to-LivingEntity.patch index 3001f15899a1..673555fc8b15 100644 --- a/patches/api/0140-Add-ray-tracing-methods-to-LivingEntity.patch +++ b/patches/api/0140-Add-ray-tracing-methods-to-LivingEntity.patch @@ -78,7 +78,7 @@ index 0000000000000000000000000000000000000000..bb12061985cdffbacfa2d113beaa35b2 + } +} diff --git a/src/main/java/org/bukkit/entity/LivingEntity.java b/src/main/java/org/bukkit/entity/LivingEntity.java -index 0ed64618b3f62ee984fe4f99dc6a52d5fad7b3cc..fab432fc00cf41d240ba172d5be43464ca2417b3 100644 +index 434ad8b07b6ee0b0919de8044d14fe3c789e203f..09b0dd1b14e2848253c1d3bfecb98951764737ee 100644 --- a/src/main/java/org/bukkit/entity/LivingEntity.java +++ b/src/main/java/org/bukkit/entity/LivingEntity.java @@ -85,6 +85,98 @@ public interface LivingEntity extends Attributable, Damageable, ProjectileSource diff --git a/patches/api/0141-Expose-attack-cooldown-methods-for-Player.patch b/patches/api/0141-Expose-attack-cooldown-methods-for-Player.patch index 53aad00d9378..d8e3d59112e0 100644 --- a/patches/api/0141-Expose-attack-cooldown-methods-for-Player.patch +++ b/patches/api/0141-Expose-attack-cooldown-methods-for-Player.patch @@ -5,10 +5,10 @@ Subject: [PATCH] Expose attack cooldown methods for Player diff --git a/src/main/java/org/bukkit/entity/Player.java b/src/main/java/org/bukkit/entity/Player.java -index 8a0e8447a7c5241a53ae933229f369bf93300c72..dc9829ab0f4efcf9534f1b2d4a2e48ea49e8d372 100644 +index 354beb974012fa83fdac0c9d28166016ace93070..76ffe308ae2e3bc36c0e7e1a98adc4b307f4211f 100644 --- a/src/main/java/org/bukkit/entity/Player.java +++ b/src/main/java/org/bukkit/entity/Player.java -@@ -3316,6 +3316,28 @@ public interface Player extends HumanEntity, Conversable, OfflinePlayer, PluginM +@@ -3356,6 +3356,28 @@ public interface Player extends HumanEntity, Conversable, OfflinePlayer, PluginM void setPlayerProfile(com.destroystokyo.paper.profile.@NotNull PlayerProfile profile); // Paper end - Player Profile API diff --git a/patches/api/0146-Material-API-additions.patch b/patches/api/0146-Material-API-additions.patch index bfae3f8d2b77..fa9a794ccad4 100644 --- a/patches/api/0146-Material-API-additions.patch +++ b/patches/api/0146-Material-API-additions.patch @@ -5,10 +5,10 @@ Subject: [PATCH] Material API additions diff --git a/src/main/java/org/bukkit/Material.java b/src/main/java/org/bukkit/Material.java -index 625fed5709d8dd814f4143d30ddb43bc57644fe6..28f2192f8a2748dfc631ce4b33599e23e6e68c62 100644 +index a53b79552a8a810ec0b1f1943e30c470217b26a9..6ebf024d5c2d5fc7253319b68ceed212de1cd73d 100644 --- a/src/main/java/org/bukkit/Material.java +++ b/src/main/java/org/bukkit/Material.java -@@ -131,6 +131,7 @@ import org.jetbrains.annotations.Nullable; +@@ -134,6 +134,7 @@ import org.jetbrains.annotations.Nullable; /** * An enum of all material IDs accepted by the official server and client */ @@ -16,7 +16,7 @@ index 625fed5709d8dd814f4143d30ddb43bc57644fe6..28f2192f8a2748dfc631ce4b33599e23 public enum Material implements Keyed, Translatable { // AIR(9648, 0), -@@ -4681,6 +4682,22 @@ public enum Material implements Keyed, Translatable { +@@ -4846,6 +4847,22 @@ public enum Material implements Keyed, Translatable { }); } diff --git a/patches/api/0147-Add-Material-Tags.patch b/patches/api/0147-Add-Material-Tags.patch index e5d1a2657dc4..69e9e51ae865 100644 --- a/patches/api/0147-Add-Material-Tags.patch +++ b/patches/api/0147-Add-Material-Tags.patch @@ -118,7 +118,7 @@ index 0000000000000000000000000000000000000000..28282090c8b3668c11faefa0523f9e4a +} diff --git a/src/main/java/com/destroystokyo/paper/MaterialTags.java b/src/main/java/com/destroystokyo/paper/MaterialTags.java new file mode 100644 -index 0000000000000000000000000000000000000000..be212b4fbeabab32a4dab6ae554768c368efaa88 +index 0000000000000000000000000000000000000000..41eaa8159f8c028faa118300e95f6a0fb9cfe989 --- /dev/null +++ b/src/main/java/com/destroystokyo/paper/MaterialTags.java @@ -0,0 +1,717 @@ @@ -227,7 +227,7 @@ index 0000000000000000000000000000000000000000..be212b4fbeabab32a4dab6ae554768c3 + */ + public static final MaterialSetTag DOORS = new MaterialSetTag(keyFor("doors")) + .endsWith("_DOOR") -+ .ensureSize("DOORS", 20).lock(); ++ .ensureSize("DOORS", 21).lock(); + + /** + * Covers all dyes. @@ -241,14 +241,14 @@ index 0000000000000000000000000000000000000000..be212b4fbeabab32a4dab6ae554768c3 + */ + public static final MaterialSetTag FENCE_GATES = new MaterialSetTag(keyFor("fence_gates")) + .endsWith("_GATE") -+ .ensureSize("FENCE_GATES", 11).lock(); ++ .ensureSize("FENCE_GATES", 12).lock(); + + /** + * Covers all variants of fences. + */ + public static final MaterialSetTag FENCES = new MaterialSetTag(keyFor("fences")) + .endsWith("_FENCE") -+ .ensureSize("FENCES", 12).lock(); ++ .ensureSize("FENCES", 13).lock(); + + /** + * Covers all variants of fish buckets. @@ -362,7 +362,7 @@ index 0000000000000000000000000000000000000000..be212b4fbeabab32a4dab6ae554768c3 + */ + public static final MaterialSetTag PRESSURE_PLATES = new MaterialSetTag(keyFor("pressure_plates")) + .endsWith("_PRESSURE_PLATE") -+ .ensureSize("PRESSURE_PLATES", 15).lock(); ++ .ensureSize("PRESSURE_PLATES", 16).lock(); + + /** + * Covers the variants of prismarine blocks. @@ -441,7 +441,7 @@ index 0000000000000000000000000000000000000000..be212b4fbeabab32a4dab6ae554768c3 + */ + public static final MaterialSetTag SPAWN_EGGS = new MaterialSetTag(keyFor("spawn_eggs")) + .endsWith("_SPAWN_EGG") -+ .ensureSize("SPAWN_EGGS", 80).lock(); ++ .ensureSize("SPAWN_EGGS", 81).lock(); + + /** + * Covers all colors of stained glass. @@ -462,7 +462,7 @@ index 0000000000000000000000000000000000000000..be212b4fbeabab32a4dab6ae554768c3 + */ + public static final MaterialSetTag TRAPDOORS = new MaterialSetTag(keyFor("trapdoors")) + .endsWith("_TRAPDOOR") -+ .ensureSize("TRAPDOORS", 20).lock(); ++ .ensureSize("TRAPDOORS", 21).lock(); + + /** + * Covers all wood variants of doors. @@ -471,7 +471,7 @@ index 0000000000000000000000000000000000000000..be212b4fbeabab32a4dab6ae554768c3 + .endsWith("_DOOR") + .not(Material.IRON_DOOR) + .notContains("COPPER") -+ .ensureSize("WOODEN_DOORS", 11).lock(); ++ .ensureSize("WOODEN_DOORS", 12).lock(); + + /** + * Covers all wood variants of fences. @@ -479,7 +479,7 @@ index 0000000000000000000000000000000000000000..be212b4fbeabab32a4dab6ae554768c3 + public static final MaterialSetTag WOODEN_FENCES = new MaterialSetTag(keyFor("wooden_fences")) + .endsWith("_FENCE") + .not(Material.NETHER_BRICK_FENCE) -+ .ensureSize("WOODEN_FENCES", 11).lock(); ++ .ensureSize("WOODEN_FENCES", 12).lock(); + + /** + * Covers all wood variants of trapdoors. @@ -488,14 +488,14 @@ index 0000000000000000000000000000000000000000..be212b4fbeabab32a4dab6ae554768c3 + .endsWith("_TRAPDOOR") + .not(Material.IRON_TRAPDOOR) + .notContains("COPPER") -+ .ensureSize("WOODEN_TRAPDOORS", 11).lock(); ++ .ensureSize("WOODEN_TRAPDOORS", 12).lock(); + + /** + * Covers the wood variants of gates. + */ + public static final MaterialSetTag WOODEN_GATES = new MaterialSetTag(keyFor("wooden_gates")) + .endsWith("_GATE") -+ .ensureSize("WOODEN_GATES", 11).lock(); ++ .ensureSize("WOODEN_GATES", 12).lock(); + + /** + * Covers the variants of purpur. @@ -509,7 +509,7 @@ index 0000000000000000000000000000000000000000..be212b4fbeabab32a4dab6ae554768c3 + */ + public static final MaterialSetTag SIGNS = new MaterialSetTag(keyFor("signs")) + .endsWith("_SIGN") -+ .ensureSize("SIGNS", 44).lock(); ++ .ensureSize("SIGNS", 48).lock(); + + /** + * Covers the variants of a regular torch. @@ -1140,7 +1140,7 @@ index 0000000000000000000000000000000000000000..bc07aaa5a001f8b58d0603d5db88f9c5 + .ensureSize("WATER_BASED", 12).lock(); +} diff --git a/src/main/java/org/bukkit/Tag.java b/src/main/java/org/bukkit/Tag.java -index e2a9b7db56e3048d1872f008a104bc33ddba98c3..b587897a26e9464b61a29e7482c60d2a66469571 100644 +index 2877eba0017ab8f3d94ca40a5e575e80adf0952e..0eca6dc6bcd9bbcad0a98a5956091bec362f2db6 100644 --- a/src/main/java/org/bukkit/Tag.java +++ b/src/main/java/org/bukkit/Tag.java @@ -11,6 +11,10 @@ import org.jetbrains.annotations.NotNull; diff --git a/patches/api/0149-Add-LivingEntity-getTargetEntity.patch b/patches/api/0149-Add-LivingEntity-getTargetEntity.patch index 27bc5319658b..40667d692a0b 100644 --- a/patches/api/0149-Add-LivingEntity-getTargetEntity.patch +++ b/patches/api/0149-Add-LivingEntity-getTargetEntity.patch @@ -51,7 +51,7 @@ index 0000000000000000000000000000000000000000..caa56541c435a3d9103cb0220ab88563 + } +} diff --git a/src/main/java/org/bukkit/entity/LivingEntity.java b/src/main/java/org/bukkit/entity/LivingEntity.java -index fab432fc00cf41d240ba172d5be43464ca2417b3..b5ea7b60b47f056553a1cec766c57e0f75735633 100644 +index 09b0dd1b14e2848253c1d3bfecb98951764737ee..9e0137ea412ec8c65b2903a76499ba8222446ea3 100644 --- a/src/main/java/org/bukkit/entity/LivingEntity.java +++ b/src/main/java/org/bukkit/entity/LivingEntity.java @@ -175,6 +175,77 @@ public interface LivingEntity extends Attributable, Damageable, ProjectileSource diff --git a/patches/api/0150-Add-sun-related-API.patch b/patches/api/0150-Add-sun-related-API.patch index 640a7df744ac..e0ca3a8475e6 100644 --- a/patches/api/0150-Add-sun-related-API.patch +++ b/patches/api/0150-Add-sun-related-API.patch @@ -5,7 +5,7 @@ Subject: [PATCH] Add sun related API diff --git a/src/main/java/org/bukkit/World.java b/src/main/java/org/bukkit/World.java -index 6242b64416fdea1f3fd6378ba26ed7bb33ab4cc4..fcdc5d83621acff5f9210585455be1ea50abb77c 100644 +index c77ca55c0686512e6d50b559139b6d6bbeb61062..7dbc2e4883feb5b0b1a20cf36cda01ef3795a262 100644 --- a/src/main/java/org/bukkit/World.java +++ b/src/main/java/org/bukkit/World.java @@ -1798,6 +1798,16 @@ public interface World extends RegionAccessor, WorldInfo, PluginMessageRecipient diff --git a/patches/api/0166-Fix-Spigot-annotation-mistakes.patch b/patches/api/0166-Fix-Spigot-annotation-mistakes.patch index 779749140432..bcff5eb5a0c4 100644 --- a/patches/api/0166-Fix-Spigot-annotation-mistakes.patch +++ b/patches/api/0166-Fix-Spigot-annotation-mistakes.patch @@ -300,10 +300,10 @@ index b02efba048be00e42502111fcdd2297529926666..fb4b6f0e908ffa50c3b2f8d04d9f3810 if (this.world == null) { return null; diff --git a/src/main/java/org/bukkit/Material.java b/src/main/java/org/bukkit/Material.java -index 28f2192f8a2748dfc631ce4b33599e23e6e68c62..bddfc295bb1247097cb5fdae6e13585a45c18e1c 100644 +index 6ebf024d5c2d5fc7253319b68ceed212de1cd73d..309a79cbe65498c90d9e135607bc246688ac6274 100644 --- a/src/main/java/org/bukkit/Material.java +++ b/src/main/java/org/bukkit/Material.java -@@ -4702,20 +4702,20 @@ public enum Material implements Keyed, Translatable { +@@ -4867,20 +4867,20 @@ public enum Material implements Keyed, Translatable { * Do not use for any reason. * * @return ID of this material @@ -328,7 +328,7 @@ index 28f2192f8a2748dfc631ce4b33599e23e6e68c62..bddfc295bb1247097cb5fdae6e13585a public boolean isLegacy() { return legacy; } -@@ -4791,8 +4791,10 @@ public enum Material implements Keyed, Translatable { +@@ -4956,8 +4956,10 @@ public enum Material implements Keyed, Translatable { * Gets the MaterialData class associated with this Material * * @return MaterialData associated with this Material @@ -339,7 +339,7 @@ index 28f2192f8a2748dfc631ce4b33599e23e6e68c62..bddfc295bb1247097cb5fdae6e13585a public Class getData() { Preconditions.checkArgument(legacy, "Cannot get data class of Modern Material"); return ctor.getDeclaringClass(); -@@ -5248,7 +5250,11 @@ public enum Material implements Keyed, Translatable { +@@ -5413,7 +5415,11 @@ public enum Material implements Keyed, Translatable { * material. * * @return true if this material can be interacted with. @@ -715,10 +715,10 @@ index 3e07fc1bc0e08d0cfd998711c7fd547b2b7b6b73..f4a739d8022d19a7ae0ee9bf93eb5c48 /** diff --git a/src/main/java/org/bukkit/block/BlockType.java b/src/main/java/org/bukkit/block/BlockType.java -index 6c745f4db620870d7e74ead2e846df34832a9e8e..eb6b330763931b55d73537153dbdb5cc96d3e94f 100644 +index cd575a28733a101b3b18ecdeb28ce474413045d6..5ba088456e2c647a719c4ee1e6f006f5c1cca651 100644 --- a/src/main/java/org/bukkit/block/BlockType.java +++ b/src/main/java/org/bukkit/block/BlockType.java -@@ -3437,9 +3437,14 @@ public interface BlockType extends Keyed, Translatable { +@@ -3576,9 +3576,14 @@ public interface BlockType extends Keyed, Translatable { * state as well. This method will return true if there is at least one * state in which additional interact handling is performed for the * block type. @@ -837,10 +837,10 @@ index 3afe2787de576f7190d87c796bea0ab34dc30248..875817b807c9f515eb07b03cc85d3689 /** diff --git a/src/main/java/org/bukkit/entity/EntityType.java b/src/main/java/org/bukkit/entity/EntityType.java -index 4047f026ab796eca7ad2d6718e1436f251c08e93..d248069adfc67eb840951f7ab4a1fa5d30214dec 100644 +index f5b0ff195b3f7cf1c5b8ebe2fb8cefcf6c1012f4..1c1cdfd6b5a98a378ff7bb7bb3201e84662b52f3 100644 --- a/src/main/java/org/bukkit/entity/EntityType.java +++ b/src/main/java/org/bukkit/entity/EntityType.java -@@ -381,9 +381,9 @@ public enum EntityType implements Keyed, Translatable { +@@ -424,9 +424,9 @@ public enum EntityType implements Keyed, Translatable { * * @param name the entity type's name * @return the matching entity type or null @@ -932,10 +932,10 @@ index 9e0137ea412ec8c65b2903a76499ba8222446ea3..db7dafba43b50146a32d749ec043c5d5 /** diff --git a/src/main/java/org/bukkit/entity/Minecart.java b/src/main/java/org/bukkit/entity/Minecart.java -index 95c79c5fa0c4e30201f887da6467ce5f81c8a255..a1e4328994a119de2966dce5470581b5a520d55e 100644 +index 4910075d0fb21b4dc4fab57894f9c7cca3093e3b..9125cc9f60258938946ee30932f0299edcd9573b 100644 --- a/src/main/java/org/bukkit/entity/Minecart.java +++ b/src/main/java/org/bukkit/entity/Minecart.java -@@ -101,7 +101,9 @@ public interface Minecart extends Vehicle { +@@ -106,7 +106,9 @@ public interface Minecart extends Vehicle { * Passing a null value will set the minecart to have no display block. * * @param material the material to set as display block. @@ -945,7 +945,7 @@ index 95c79c5fa0c4e30201f887da6467ce5f81c8a255..a1e4328994a119de2966dce5470581b5 public void setDisplayBlock(@Nullable MaterialData material); /** -@@ -109,8 +111,10 @@ public interface Minecart extends Vehicle { +@@ -114,8 +116,10 @@ public interface Minecart extends Vehicle { * This function will return the type AIR if none is set. * * @return the block displayed by this minecart. @@ -970,10 +970,10 @@ index 60522888bc320ba0a55655532e19185fac816bd1..4aa07d4edb2c81d0ae7999b30ad53ff8 /** diff --git a/src/main/java/org/bukkit/entity/Player.java b/src/main/java/org/bukkit/entity/Player.java -index dc9829ab0f4efcf9534f1b2d4a2e48ea49e8d372..e65f4f0df2e6832cf089572822c96ecc7a83dab3 100644 +index 76ffe308ae2e3bc36c0e7e1a98adc4b307f4211f..80e894e3d625cde14bfe881d2c367b43a4882cfd 100644 --- a/src/main/java/org/bukkit/entity/Player.java +++ b/src/main/java/org/bukkit/entity/Player.java -@@ -1640,11 +1640,8 @@ public interface Player extends HumanEntity, Conversable, OfflinePlayer, PluginM +@@ -1680,11 +1680,8 @@ public interface Player extends HumanEntity, Conversable, OfflinePlayer, PluginM /** * Forces an update of the player's entire inventory. @@ -1579,7 +1579,7 @@ index ebc14022c9ef9b0b3331ee53e96a32667e4762e0..5c258b6077277575daa5d96349837bdf public void setTitle(@NotNull String title); } diff --git a/src/main/java/org/bukkit/inventory/ItemFactory.java b/src/main/java/org/bukkit/inventory/ItemFactory.java -index 3d08beee52f2247db6f6e679206ed6a965fbf9a8..1b4f9b93860e58762ac28715adad5a67298b06d7 100644 +index c1ec8efffd5ff2a4dcb1d761be9a431a62284607..a1d8ef8eda3c0256e8c82b7a01c3e7b11454b250 100644 --- a/src/main/java/org/bukkit/inventory/ItemFactory.java +++ b/src/main/java/org/bukkit/inventory/ItemFactory.java @@ -29,7 +29,7 @@ public interface ItemFactory { @@ -1776,10 +1776,10 @@ index 597a18a767b68b47e81454b7d44613c7178c1366..bc3440eb72127824b3961fbdae583bb6 public ItemStack getInput() { return this.ingredient.getItemStack(); diff --git a/src/main/java/org/bukkit/inventory/meta/ItemMeta.java b/src/main/java/org/bukkit/inventory/meta/ItemMeta.java -index 10ca843e57c74dfa32d539acd174c8867dfd56ec..e7ee3c9ac835a6eaf7faae44e6b2a811e8f8a703 100644 +index 480dd9a5ff334e6f32b98aa0108ff02e6f7077ab..59e1987f0f0accef369cc29dbec464185eb7c99c 100644 --- a/src/main/java/org/bukkit/inventory/meta/ItemMeta.java +++ b/src/main/java/org/bukkit/inventory/meta/ItemMeta.java -@@ -144,6 +144,7 @@ public interface ItemMeta extends Cloneable, ConfigurationSerializable, Persiste +@@ -151,6 +151,7 @@ public interface ItemMeta extends Cloneable, ConfigurationSerializable, Persiste /** * Checks for existence of a localized name. * @@ -1787,7 +1787,7 @@ index 10ca843e57c74dfa32d539acd174c8867dfd56ec..e7ee3c9ac835a6eaf7faae44e6b2a811 * @return true if this has a localized name * @deprecated meta no longer exists */ -@@ -156,6 +157,7 @@ public interface ItemMeta extends Cloneable, ConfigurationSerializable, Persiste +@@ -163,6 +164,7 @@ public interface ItemMeta extends Cloneable, ConfigurationSerializable, Persiste * Plugins should check that hasLocalizedName() returns true * before calling this method. * @@ -1795,7 +1795,7 @@ index 10ca843e57c74dfa32d539acd174c8867dfd56ec..e7ee3c9ac835a6eaf7faae44e6b2a811 * @return the localized name that is set * @deprecated meta no longer exists */ -@@ -166,6 +168,7 @@ public interface ItemMeta extends Cloneable, ConfigurationSerializable, Persiste +@@ -173,6 +175,7 @@ public interface ItemMeta extends Cloneable, ConfigurationSerializable, Persiste /** * Sets the localized name. * @@ -1803,7 +1803,7 @@ index 10ca843e57c74dfa32d539acd174c8867dfd56ec..e7ee3c9ac835a6eaf7faae44e6b2a811 * @param name the name to set * @deprecated meta no longer exists */ -@@ -545,7 +548,7 @@ public interface ItemMeta extends Cloneable, ConfigurationSerializable, Persiste +@@ -749,7 +752,7 @@ public interface ItemMeta extends Cloneable, ConfigurationSerializable, Persiste * * @return component */ @@ -1845,6 +1845,19 @@ index 32055a8890425e0b819930f3059da5ea9dfca553..26a336dade83baee97d20eb39a058925 + //@Deprecated // Paper int getMapId(); + /** +diff --git a/src/main/java/org/bukkit/inventory/meta/components/EquippableComponent.java b/src/main/java/org/bukkit/inventory/meta/components/EquippableComponent.java +index c56e81eb71bccc03378aea71096fdf66b4bfa784..16713c9d4cfaed5ad509b4075121e44c55a8cc76 100644 +--- a/src/main/java/org/bukkit/inventory/meta/components/EquippableComponent.java ++++ b/src/main/java/org/bukkit/inventory/meta/components/EquippableComponent.java +@@ -37,7 +37,7 @@ public interface EquippableComponent extends ConfigurationSerializable { + * + * @return the sound + */ +- @Nullable ++ @NotNull // Paper + Sound getEquipSound(); + /** diff --git a/src/main/java/org/bukkit/map/MapCanvas.java b/src/main/java/org/bukkit/map/MapCanvas.java index edef478786bb7456af29ca960009873095830050..e8ac449e6280827beb6d2699df75b1d52a922c9b 100644 diff --git a/patches/api/0169-Add-Heightmap-API.patch b/patches/api/0169-Add-Heightmap-API.patch index b5d70f803f2e..a60fccf2adde 100644 --- a/patches/api/0169-Add-Heightmap-API.patch +++ b/patches/api/0169-Add-Heightmap-API.patch @@ -6,7 +6,7 @@ Subject: [PATCH] Add Heightmap API Changed to use upstream's heightmap API - Machine_Maker diff --git a/src/main/java/org/bukkit/Location.java b/src/main/java/org/bukkit/Location.java -index eec6c9cd7da6938351905129bb5a66f49a257d01..65618b6b3c950fb27707f243a766511d6cd3aab4 100644 +index fb4b6f0e908ffa50c3b2f8d04d9f3810898b8d5e..bdc065a486306236c7f0960718bea53bc0b0a9b6 100644 --- a/src/main/java/org/bukkit/Location.java +++ b/src/main/java/org/bukkit/Location.java @@ -649,6 +649,30 @@ public class Location implements Cloneable, ConfigurationSerializable, io.paperm diff --git a/patches/api/0173-Set-true-custom-payload-channel-size-limit.patch b/patches/api/0173-Set-true-custom-payload-channel-size-limit.patch index c524e2aaa0d2..e64cd78834e7 100644 --- a/patches/api/0173-Set-true-custom-payload-channel-size-limit.patch +++ b/patches/api/0173-Set-true-custom-payload-channel-size-limit.patch @@ -6,7 +6,7 @@ Subject: [PATCH] Set true custom payload channel size limit This fixes compatibility with some mods that are sending very long channel names. Also gives developers the ability to send longer channel names. diff --git a/src/main/java/org/bukkit/plugin/messaging/Messenger.java b/src/main/java/org/bukkit/plugin/messaging/Messenger.java -index 9d2c68c826f3b867d407e7f13c6394a899cc8ee8..aec70aa740152c34297c42ad6e06c8b54523e78b 100644 +index c748a94523c8bc2140e1842ed7d8d462b52507d5..754fac6b2a45399efa34b06c6aa61f88c19e3d2b 100644 --- a/src/main/java/org/bukkit/plugin/messaging/Messenger.java +++ b/src/main/java/org/bukkit/plugin/messaging/Messenger.java @@ -24,7 +24,7 @@ public interface Messenger { diff --git a/patches/api/0182-Add-Raw-Byte-ItemStack-Serialization.patch b/patches/api/0182-Add-Raw-Byte-ItemStack-Serialization.patch index bfdff2a3f4af..b852c64ded54 100644 --- a/patches/api/0182-Add-Raw-Byte-ItemStack-Serialization.patch +++ b/patches/api/0182-Add-Raw-Byte-ItemStack-Serialization.patch @@ -8,10 +8,10 @@ Serializes using NBT which is safer for server data migrations than bukkits form Co-authored-by: Nassim Jahnke diff --git a/src/main/java/org/bukkit/UnsafeValues.java b/src/main/java/org/bukkit/UnsafeValues.java -index 6e67fdb091a006d2d13bc2d93db4d55348af4c8f..e41d5d9b810c8816cd0d1eba5fd1ea56252fb0df 100644 +index 45ed0007d6de20b98794b3ccaef57aed213e72d4..dd81e309c584e37e4bc7644261ecc649e1237570 100644 --- a/src/main/java/org/bukkit/UnsafeValues.java +++ b/src/main/java/org/bukkit/UnsafeValues.java -@@ -170,5 +170,9 @@ public interface UnsafeValues { +@@ -159,5 +159,9 @@ public interface UnsafeValues { default com.destroystokyo.paper.util.VersionFetcher getVersionFetcher() { return new com.destroystokyo.paper.util.VersionFetcher.DummyVersionFetcher(); } diff --git a/patches/api/0183-Add-Player-Client-Options-API.patch b/patches/api/0183-Add-Player-Client-Options-API.patch index 56be6b483bb5..44f597e1444c 100644 --- a/patches/api/0183-Add-Player-Client-Options-API.patch +++ b/patches/api/0183-Add-Player-Client-Options-API.patch @@ -224,10 +224,10 @@ index 0000000000000000000000000000000000000000..6cf6aa876278d0d3e75148608951fc58 + } +} diff --git a/src/main/java/org/bukkit/entity/Player.java b/src/main/java/org/bukkit/entity/Player.java -index e65f4f0df2e6832cf089572822c96ecc7a83dab3..b5573a26486fdfb6eb3aa7f9c46a67c8cddba34d 100644 +index 80e894e3d625cde14bfe881d2c367b43a4882cfd..2d3c8febbbab959433101fb9dfc1e0ff9deca192 100644 --- a/src/main/java/org/bukkit/entity/Player.java +++ b/src/main/java/org/bukkit/entity/Player.java -@@ -3335,6 +3335,13 @@ public interface Player extends HumanEntity, Conversable, OfflinePlayer, PluginM +@@ -3375,6 +3375,13 @@ public interface Player extends HumanEntity, Conversable, OfflinePlayer, PluginM void resetCooldown(); // Paper end - attack cooldown API diff --git a/patches/api/0186-Villager-Restocks-API.patch b/patches/api/0186-Villager-Restocks-API.patch index 39c67cae0f00..66dc60c254fc 100644 --- a/patches/api/0186-Villager-Restocks-API.patch +++ b/patches/api/0186-Villager-Restocks-API.patch @@ -5,7 +5,7 @@ Subject: [PATCH] Villager Restocks API diff --git a/src/main/java/org/bukkit/entity/Villager.java b/src/main/java/org/bukkit/entity/Villager.java -index cfa0d4809f9bb4ac150251efa85ba4d1808ab1b2..ecb0f32a4449f8000248c4bebf89a56df186899f 100644 +index af4582f3e4687933dac6ccd43667a373f8daedb6..5a61175ccfe67c0a3c55cc2b84772fa8f6e6a6cb 100644 --- a/src/main/java/org/bukkit/entity/Villager.java +++ b/src/main/java/org/bukkit/entity/Villager.java @@ -82,6 +82,20 @@ public interface Villager extends AbstractVillager { diff --git a/patches/api/0190-Potential-bed-API.patch b/patches/api/0190-Potential-bed-API.patch index 1cbb7daffa24..a45ca6b83555 100644 --- a/patches/api/0190-Potential-bed-API.patch +++ b/patches/api/0190-Potential-bed-API.patch @@ -8,10 +8,10 @@ Adds a new method to fetch the location of a player's bed without generating any getPotentialBedLocation - Gets the last known location of a player's bed. This does not preform any check if the bed is still valid and does not load any chunks. diff --git a/src/main/java/org/bukkit/entity/HumanEntity.java b/src/main/java/org/bukkit/entity/HumanEntity.java -index 3985798654a3085c128144e46f7113b7744b8d14..11c5846848a6631a9376934622caeadd448b0391 100644 +index 6c2f01bf452d991faf40b995225bf7b7e49281df..92ceb765ccb80c3b09ac3ede9bcaad6219fabd3d 100644 --- a/src/main/java/org/bukkit/entity/HumanEntity.java +++ b/src/main/java/org/bukkit/entity/HumanEntity.java -@@ -277,6 +277,19 @@ public interface HumanEntity extends LivingEntity, AnimalTamer, InventoryHolder +@@ -308,6 +308,19 @@ public interface HumanEntity extends LivingEntity, AnimalTamer, InventoryHolder */ public int getSleepTicks(); diff --git a/patches/api/0193-Support-components-in-ItemMeta.patch b/patches/api/0193-Support-components-in-ItemMeta.patch index 5c407e184d66..d66067471bb3 100644 --- a/patches/api/0193-Support-components-in-ItemMeta.patch +++ b/patches/api/0193-Support-components-in-ItemMeta.patch @@ -5,7 +5,7 @@ Subject: [PATCH] Support components in ItemMeta diff --git a/src/main/java/org/bukkit/inventory/meta/ItemMeta.java b/src/main/java/org/bukkit/inventory/meta/ItemMeta.java -index e7ee3c9ac835a6eaf7faae44e6b2a811e8f8a703..1a4260b00b193b94ce4b1b2954644f4e41baff4c 100644 +index 59e1987f0f0accef369cc29dbec464185eb7c99c..afdcc2d67d55f2f07c913816e1f5b290d1415357 100644 --- a/src/main/java/org/bukkit/inventory/meta/ItemMeta.java +++ b/src/main/java/org/bukkit/inventory/meta/ItemMeta.java @@ -5,6 +5,7 @@ import java.util.Collection; @@ -13,10 +13,10 @@ index e7ee3c9ac835a6eaf7faae44e6b2a811e8f8a703..1a4260b00b193b94ce4b1b2954644f4e import java.util.Map; import java.util.Set; +import net.kyori.adventure.text.Component; + import org.bukkit.NamespacedKey; + import org.bukkit.Tag; import org.bukkit.attribute.Attribute; - import org.bukkit.attribute.AttributeModifier; - import org.bukkit.configuration.serialization.ConfigurationSerializable; -@@ -68,6 +69,20 @@ public interface ItemMeta extends Cloneable, ConfigurationSerializable, Persiste +@@ -75,6 +76,20 @@ public interface ItemMeta extends Cloneable, ConfigurationSerializable, Persiste @NotNull String getDisplayName(); @@ -37,7 +37,7 @@ index e7ee3c9ac835a6eaf7faae44e6b2a811e8f8a703..1a4260b00b193b94ce4b1b2954644f4e /** * Sets the display name. * -@@ -77,6 +92,16 @@ public interface ItemMeta extends Cloneable, ConfigurationSerializable, Persiste +@@ -84,6 +99,16 @@ public interface ItemMeta extends Cloneable, ConfigurationSerializable, Persiste @Deprecated // Paper void setDisplayName(@Nullable String name); @@ -54,7 +54,7 @@ index e7ee3c9ac835a6eaf7faae44e6b2a811e8f8a703..1a4260b00b193b94ce4b1b2954644f4e /** * Checks for existence of an item name. *
    -@@ -213,6 +238,19 @@ public interface ItemMeta extends Cloneable, ConfigurationSerializable, Persiste +@@ -220,6 +245,19 @@ public interface ItemMeta extends Cloneable, ConfigurationSerializable, Persiste @Nullable List getLore(); @@ -74,7 +74,7 @@ index e7ee3c9ac835a6eaf7faae44e6b2a811e8f8a703..1a4260b00b193b94ce4b1b2954644f4e /** * Sets the lore for this item. * Removes lore when given null. -@@ -223,6 +261,16 @@ public interface ItemMeta extends Cloneable, ConfigurationSerializable, Persiste +@@ -230,6 +268,16 @@ public interface ItemMeta extends Cloneable, ConfigurationSerializable, Persiste @Deprecated // Paper void setLore(@Nullable List lore); diff --git a/patches/api/0198-Brand-support.patch b/patches/api/0198-Brand-support.patch index 64e9c90fb4f7..42971e5ae7d0 100644 --- a/patches/api/0198-Brand-support.patch +++ b/patches/api/0198-Brand-support.patch @@ -5,10 +5,10 @@ Subject: [PATCH] Brand support diff --git a/src/main/java/org/bukkit/entity/Player.java b/src/main/java/org/bukkit/entity/Player.java -index b5573a26486fdfb6eb3aa7f9c46a67c8cddba34d..3f7ce423ef9f43c1ce9f8ef968eb2e3220906d77 100644 +index 2d3c8febbbab959433101fb9dfc1e0ff9deca192..54d33be1297a6a4646635ca09cae1f0a2d3fb7fa 100644 --- a/src/main/java/org/bukkit/entity/Player.java +++ b/src/main/java/org/bukkit/entity/Player.java -@@ -3448,6 +3448,16 @@ public interface Player extends HumanEntity, Conversable, OfflinePlayer, PluginM +@@ -3488,6 +3488,16 @@ public interface Player extends HumanEntity, Conversable, OfflinePlayer, PluginM // Paper end } diff --git a/patches/api/0202-Add-methods-to-get-translation-keys.patch b/patches/api/0202-Add-methods-to-get-translation-keys.patch index fe71b1056aa5..825d506e8a31 100644 --- a/patches/api/0202-Add-methods-to-get-translation-keys.patch +++ b/patches/api/0202-Add-methods-to-get-translation-keys.patch @@ -119,7 +119,7 @@ index 81e45984a88fc84acd0f76d825abf4ddaed0ac3b..fdc42a79c5af30fdade41ee99245e664 /** diff --git a/src/main/java/org/bukkit/GameRule.java b/src/main/java/org/bukkit/GameRule.java -index dc66bd69646ac949d1386ce8f6ff913e9475439d..4482e8f2c617c2f51b2b53762e775d118002363a 100644 +index b3211a705acc26449675727823aa42ae6bacac4f..8b6584fae0a9d5cccbe350d889fa8b4a14c78ca3 100644 --- a/src/main/java/org/bukkit/GameRule.java +++ b/src/main/java/org/bukkit/GameRule.java @@ -15,7 +15,7 @@ import org.jetbrains.annotations.Nullable; @@ -131,7 +131,7 @@ index dc66bd69646ac949d1386ce8f6ff913e9475439d..4482e8f2c617c2f51b2b53762e775d11 private static Map> gameRules = new HashMap<>(); // Boolean rules -@@ -355,4 +355,11 @@ public final class GameRule { +@@ -366,4 +366,11 @@ public final class GameRule { public static GameRule[] values() { return gameRules.values().toArray(new GameRule[gameRules.size()]); } @@ -144,10 +144,10 @@ index dc66bd69646ac949d1386ce8f6ff913e9475439d..4482e8f2c617c2f51b2b53762e775d11 + // Paper end } diff --git a/src/main/java/org/bukkit/Material.java b/src/main/java/org/bukkit/Material.java -index bddfc295bb1247097cb5fdae6e13585a45c18e1c..39bf5b2e93f45000d2e894b5c3d059b889068aae 100644 +index 309a79cbe65498c90d9e135607bc246688ac6274..8820dd330cee4f8463f2f39f84d4be0762558368 100644 --- a/src/main/java/org/bukkit/Material.java +++ b/src/main/java/org/bukkit/Material.java -@@ -132,7 +132,7 @@ import org.jetbrains.annotations.Nullable; +@@ -135,7 +135,7 @@ import org.jetbrains.annotations.Nullable; * An enum of all material IDs accepted by the official server and client */ @SuppressWarnings({"DeprecatedIsStillUsed", "deprecation"}) // Paper @@ -156,7 +156,7 @@ index bddfc295bb1247097cb5fdae6e13585a45c18e1c..39bf5b2e93f45000d2e894b5c3d059b8 // AIR(9648, 0), STONE(22948), -@@ -4698,6 +4698,17 @@ public enum Material implements Keyed, Translatable { +@@ -4863,6 +4863,17 @@ public enum Material implements Keyed, Translatable { } // Paper end @@ -174,7 +174,7 @@ index bddfc295bb1247097cb5fdae6e13585a45c18e1c..39bf5b2e93f45000d2e894b5c3d059b8 /** * Do not use for any reason. * -@@ -5447,9 +5458,11 @@ public enum Material implements Keyed, Translatable { +@@ -5612,9 +5623,11 @@ public enum Material implements Keyed, Translatable { * material * @see #getBlockTranslationKey() * @see #getItemTranslationKey() @@ -235,7 +235,7 @@ index e3faa2c675c85a9cbdbbb1debec0ff81c58a1bbd..fd1629c2d2028a88fb3d56b0aeb833d1 String getTranslationKey(); } diff --git a/src/main/java/org/bukkit/attribute/Attribute.java b/src/main/java/org/bukkit/attribute/Attribute.java -index ef9c998691d101c26b5247a4962628a7bc9e513f..947874c0172b690e7752e49b7bec64e0c0308515 100644 +index e5a9d1692f0f6fd8e9ac4903782e9330b4da6ef3..6075cd2a88394cd7f0ce2470e732a45094b033c0 100644 --- a/src/main/java/org/bukkit/attribute/Attribute.java +++ b/src/main/java/org/bukkit/attribute/Attribute.java @@ -9,7 +9,7 @@ import org.jetbrains.annotations.NotNull; @@ -247,7 +247,7 @@ index ef9c998691d101c26b5247a4962628a7bc9e513f..947874c0172b690e7752e49b7bec64e0 /** * Maximum health of an Entity. -@@ -153,4 +153,12 @@ public enum Attribute implements Keyed, Translatable { +@@ -157,4 +157,12 @@ public enum Attribute implements Keyed, Translatable { public String getTranslationKey() { return Bukkit.getUnsafe().getTranslationKey(this); } @@ -261,10 +261,10 @@ index ef9c998691d101c26b5247a4962628a7bc9e513f..947874c0172b690e7752e49b7bec64e0 + // Paper end } diff --git a/src/main/java/org/bukkit/block/Biome.java b/src/main/java/org/bukkit/block/Biome.java -index d3087d60378822cdd7cea25fd63d3f496e3cd2fb..5d8fa5b39a5d50cca48ba63af3a84b80f279b649 100644 +index 2201b63e7335b12622268a3ef40d1fcb06c1d705..b71975b904d48e22a0e2134bb0e8231679dd9700 100644 --- a/src/main/java/org/bukkit/block/Biome.java +++ b/src/main/java/org/bukkit/block/Biome.java -@@ -8,7 +8,7 @@ import org.jetbrains.annotations.NotNull; +@@ -10,7 +10,7 @@ import org.jetbrains.annotations.NotNull; /** * Holds all accepted Biomes in the default server */ @@ -273,7 +273,7 @@ index d3087d60378822cdd7cea25fd63d3f496e3cd2fb..5d8fa5b39a5d50cca48ba63af3a84b80 OCEAN, PLAINS, DESERT, -@@ -89,4 +89,11 @@ public enum Biome implements Keyed { +@@ -94,4 +94,11 @@ public enum Biome implements Keyed { public NamespacedKey getKey() { return key; } @@ -312,10 +312,10 @@ index 745413357506fa7399f8ba44dfe222d1f0c919f1..25db31b2e9a6d75f0c59f75237842f9a // Paper end } diff --git a/src/main/java/org/bukkit/block/BlockType.java b/src/main/java/org/bukkit/block/BlockType.java -index eb6b330763931b55d73537153dbdb5cc96d3e94f..5bfa98695265cdfd246411f93ab670d2c9e64ef1 100644 +index 5ba088456e2c647a719c4ee1e6f006f5c1cca651..aebd34785bb2070389ad2e2803fa9ff803b318c5 100644 --- a/src/main/java/org/bukkit/block/BlockType.java +++ b/src/main/java/org/bukkit/block/BlockType.java -@@ -125,7 +125,7 @@ import org.jetbrains.annotations.Nullable; +@@ -129,7 +129,7 @@ import org.jetbrains.annotations.Nullable; * changes may occur. Do not use this API in plugins. */ @ApiStatus.Internal @@ -324,7 +324,7 @@ index eb6b330763931b55d73537153dbdb5cc96d3e94f..5bfa98695265cdfd246411f93ab670d2 /** * Typed represents a subtype of {@link BlockType}s that have a known block -@@ -3502,4 +3502,13 @@ public interface BlockType extends Keyed, Translatable { +@@ -3641,4 +3641,13 @@ public interface BlockType extends Keyed, Translatable { @Nullable @Deprecated Material asMaterial(); @@ -384,10 +384,10 @@ index c4f86ba1037f3f0e5d697a0962d71d6f8c7c1fbe..ac0371285370594d4de1554871b19bbc // Paper end } diff --git a/src/main/java/org/bukkit/entity/EntityType.java b/src/main/java/org/bukkit/entity/EntityType.java -index d248069adfc67eb840951f7ab4a1fa5d30214dec..976f701ed9b9873945a5628173c580e2e6873864 100644 +index 1c1cdfd6b5a98a378ff7bb7bb3201e84662b52f3..be1c8c9b27ad792f2b0ff1cec0c575eb1fc3023a 100644 --- a/src/main/java/org/bukkit/entity/EntityType.java +++ b/src/main/java/org/bukkit/entity/EntityType.java -@@ -23,7 +23,7 @@ import org.jetbrains.annotations.Contract; +@@ -45,7 +45,7 @@ import org.jetbrains.annotations.Contract; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -396,7 +396,7 @@ index d248069adfc67eb840951f7ab4a1fa5d30214dec..976f701ed9b9873945a5628173c580e2 // These strings MUST match the strings in nms.EntityTypes and are case sensitive. /** -@@ -427,10 +427,22 @@ public enum EntityType implements Keyed, Translatable { +@@ -470,10 +470,22 @@ public enum EntityType implements Keyed, Translatable { @Override @NotNull @@ -564,10 +564,10 @@ index af09398e0864d338da530495bfd577db8adbe65a..60eec8a12f01562678732bcf38ac407e // Paper end } diff --git a/src/main/java/org/bukkit/inventory/ItemType.java b/src/main/java/org/bukkit/inventory/ItemType.java -index 1db9c6bd6e44ddd3d8550a2906422c97d45eb9ea..2b1a1ad04212651ad5a43edecca12b495ce61fd5 100644 +index 88e1883dffb64974f5ec60acbf2828cfb9de9439..49e5a9ea7cf5e4d7a4333d2cffa4e44b1a436403 100644 --- a/src/main/java/org/bukkit/inventory/ItemType.java +++ b/src/main/java/org/bukkit/inventory/ItemType.java -@@ -47,7 +47,7 @@ import org.jetbrains.annotations.Nullable; +@@ -48,7 +48,7 @@ import org.jetbrains.annotations.Nullable; * changes may occur. Do not use this API in plugins. */ @ApiStatus.Internal @@ -576,7 +576,7 @@ index 1db9c6bd6e44ddd3d8550a2906422c97d45eb9ea..2b1a1ad04212651ad5a43edecca12b49 /** * Typed represents a subtype of {@link ItemType}s that have a known item meta type -@@ -2297,4 +2297,13 @@ public interface ItemType extends Keyed, Translatable { +@@ -2448,4 +2448,13 @@ public interface ItemType extends Keyed, Translatable { @Nullable @Deprecated Material asMaterial(); diff --git a/patches/api/0203-Create-HoverEvent-from-ItemStack-Entity.patch b/patches/api/0203-Create-HoverEvent-from-ItemStack-Entity.patch index 8722000ff7dc..27625797e0fe 100644 --- a/patches/api/0203-Create-HoverEvent-from-ItemStack-Entity.patch +++ b/patches/api/0203-Create-HoverEvent-from-ItemStack-Entity.patch @@ -5,7 +5,7 @@ Subject: [PATCH] Create HoverEvent from ItemStack Entity diff --git a/src/main/java/org/bukkit/inventory/ItemFactory.java b/src/main/java/org/bukkit/inventory/ItemFactory.java -index 1b4f9b93860e58762ac28715adad5a67298b06d7..96546712f788e091749a1b4eebc6b1d6c3db7814 100644 +index a1d8ef8eda3c0256e8c82b7a01c3e7b11454b250..579a9037b656bef9fb65c6da03611e981492074a 100644 --- a/src/main/java/org/bukkit/inventory/ItemFactory.java +++ b/src/main/java/org/bukkit/inventory/ItemFactory.java @@ -250,4 +250,65 @@ public interface ItemFactory { diff --git a/patches/api/0204-Add-additional-open-container-api-to-HumanEntity.patch b/patches/api/0204-Add-additional-open-container-api-to-HumanEntity.patch index 3fe46b8c28ba..8fc602cf5596 100644 --- a/patches/api/0204-Add-additional-open-container-api-to-HumanEntity.patch +++ b/patches/api/0204-Add-additional-open-container-api-to-HumanEntity.patch @@ -5,7 +5,7 @@ Subject: [PATCH] Add additional open container api to HumanEntity diff --git a/src/main/java/org/bukkit/entity/HumanEntity.java b/src/main/java/org/bukkit/entity/HumanEntity.java -index 11c5846848a6631a9376934622caeadd448b0391..f20b0a439c4d5cd2c6caa70a46b1b49f8ab23425 100644 +index 92ceb765ccb80c3b09ac3ede9bcaad6219fabd3d..25f671863e23fdb674c55f3e1f50b1f195ca5469 100644 --- a/src/main/java/org/bukkit/entity/HumanEntity.java +++ b/src/main/java/org/bukkit/entity/HumanEntity.java @@ -182,6 +182,92 @@ public interface HumanEntity extends LivingEntity, AnimalTamer, InventoryHolder diff --git a/patches/api/0205-Expose-the-Entity-Counter-to-allow-plugins-to-use-va.patch b/patches/api/0205-Expose-the-Entity-Counter-to-allow-plugins-to-use-va.patch index c5e6d200ab88..241d581e0cd4 100644 --- a/patches/api/0205-Expose-the-Entity-Counter-to-allow-plugins-to-use-va.patch +++ b/patches/api/0205-Expose-the-Entity-Counter-to-allow-plugins-to-use-va.patch @@ -6,10 +6,10 @@ Subject: [PATCH] Expose the Entity Counter to allow plugins to use valid and diff --git a/src/main/java/org/bukkit/UnsafeValues.java b/src/main/java/org/bukkit/UnsafeValues.java -index e41d5d9b810c8816cd0d1eba5fd1ea56252fb0df..64be4e60a03cb7cdc21013837d241d288695b69d 100644 +index dd81e309c584e37e4bc7644261ecc649e1237570..db48f30704efa6928599a5cebf5ce577c8430198 100644 --- a/src/main/java/org/bukkit/UnsafeValues.java +++ b/src/main/java/org/bukkit/UnsafeValues.java -@@ -174,5 +174,12 @@ public interface UnsafeValues { +@@ -163,5 +163,12 @@ public interface UnsafeValues { byte[] serializeItem(ItemStack item); ItemStack deserializeItem(byte[] data); diff --git a/patches/api/0208-Player-elytra-boost-API.patch b/patches/api/0208-Player-elytra-boost-API.patch index 556783227408..c5f8ae83d794 100644 --- a/patches/api/0208-Player-elytra-boost-API.patch +++ b/patches/api/0208-Player-elytra-boost-API.patch @@ -5,10 +5,10 @@ Subject: [PATCH] Player elytra boost API diff --git a/src/main/java/org/bukkit/entity/Player.java b/src/main/java/org/bukkit/entity/Player.java -index 3f7ce423ef9f43c1ce9f8ef968eb2e3220906d77..77e14139e5cd22278e6decc7e5de31411fa2ae45 100644 +index 54d33be1297a6a4646635ca09cae1f0a2d3fb7fa..d2b74718998761a3f1a1a07547eb49661d5431df 100644 --- a/src/main/java/org/bukkit/entity/Player.java +++ b/src/main/java/org/bukkit/entity/Player.java -@@ -3342,6 +3342,25 @@ public interface Player extends HumanEntity, Conversable, OfflinePlayer, PluginM +@@ -3382,6 +3382,25 @@ public interface Player extends HumanEntity, Conversable, OfflinePlayer, PluginM @NotNull T getClientOption(com.destroystokyo.paper.@NotNull ClientOption option); // Paper end - client option API diff --git a/patches/api/0220-Expose-LivingEntity-hurt-direction.patch b/patches/api/0220-Expose-LivingEntity-hurt-direction.patch index cd51cbd838b7..1ee818c76f33 100644 --- a/patches/api/0220-Expose-LivingEntity-hurt-direction.patch +++ b/patches/api/0220-Expose-LivingEntity-hurt-direction.patch @@ -5,7 +5,7 @@ Subject: [PATCH] Expose LivingEntity hurt direction diff --git a/src/main/java/org/bukkit/entity/HumanEntity.java b/src/main/java/org/bukkit/entity/HumanEntity.java -index d06539841d973030c0cd5bb06a085ed2f0f73af6..7759062ca34506c56d2d1340cf1d9c2d36151d48 100644 +index 25f671863e23fdb674c55f3e1f50b1f195ca5469..b9c19abe26c100558c4a0388d65c2316261ee1d3 100644 --- a/src/main/java/org/bukkit/entity/HumanEntity.java +++ b/src/main/java/org/bukkit/entity/HumanEntity.java @@ -356,6 +356,16 @@ public interface HumanEntity extends LivingEntity, AnimalTamer, InventoryHolder @@ -23,7 +23,7 @@ index d06539841d973030c0cd5bb06a085ed2f0f73af6..7759062ca34506c56d2d1340cf1d9c2d + // Paper end + /** - * Get the sleep ticks of the player. This value may be capped. + * Check whether a cooldown is active on the specified item. * diff --git a/src/main/java/org/bukkit/entity/LivingEntity.java b/src/main/java/org/bukkit/entity/LivingEntity.java index 784da48ffc63bc932caafe58cf56ad30e7a86be6..49352ed3928163c6322634b8e6f1d3dd8caa5e74 100644 diff --git a/patches/api/0225-Add-API-to-get-Material-from-Boats-and-Minecarts.patch b/patches/api/0225-Add-API-to-get-Material-from-Boats-and-Minecarts.patch index 081fbd1bf88e..143ac8e614db 100644 --- a/patches/api/0225-Add-API-to-get-Material-from-Boats-and-Minecarts.patch +++ b/patches/api/0225-Add-API-to-get-Material-from-Boats-and-Minecarts.patch @@ -5,10 +5,10 @@ Subject: [PATCH] Add API to get Material from Boats and Minecarts diff --git a/src/main/java/org/bukkit/entity/Boat.java b/src/main/java/org/bukkit/entity/Boat.java -index 88852215d01f3fc4866449f7b826f6603b0ed9d8..f7548098bcdd033d9c530fdc584fc5538c635ca1 100644 +index dbdd2c1ad74a4d56e282736cd06d6937701f2e5c..a0fb3c44405f6362f8a1613661d507e448f7ba6b 100644 --- a/src/main/java/org/bukkit/entity/Boat.java +++ b/src/main/java/org/bukkit/entity/Boat.java -@@ -175,4 +175,14 @@ public interface Boat extends Vehicle { +@@ -181,4 +181,14 @@ public interface Boat extends Vehicle { ON_LAND, IN_AIR; } @@ -24,17 +24,18 @@ index 88852215d01f3fc4866449f7b826f6603b0ed9d8..f7548098bcdd033d9c530fdc584fc553 + // Paper end } diff --git a/src/main/java/org/bukkit/entity/Minecart.java b/src/main/java/org/bukkit/entity/Minecart.java -index a1e4328994a119de2966dce5470581b5a520d55e..d1f602faa34cc5cc4563e18b63a40078e406641d 100644 +index 9125cc9f60258938946ee30932f0299edcd9573b..148d8cddba48a886eddef72a3de63d5eaa15949f 100644 --- a/src/main/java/org/bukkit/entity/Minecart.java +++ b/src/main/java/org/bukkit/entity/Minecart.java -@@ -1,5 +1,6 @@ +@@ -1,6 +1,7 @@ package org.bukkit.entity; + import org.bukkit.GameRule; +import org.bukkit.Material; import org.bukkit.block.data.BlockData; import org.bukkit.material.MaterialData; import org.bukkit.util.Vector; -@@ -147,4 +148,14 @@ public interface Minecart extends Vehicle { +@@ -152,4 +153,14 @@ public interface Minecart extends Vehicle { * @return the current block offset for this minecart. */ public int getDisplayBlockOffset(); diff --git a/patches/api/0227-Zombie-API-breaking-doors.patch b/patches/api/0227-Zombie-API-breaking-doors.patch index 24118c13ee22..05fc193f7214 100644 --- a/patches/api/0227-Zombie-API-breaking-doors.patch +++ b/patches/api/0227-Zombie-API-breaking-doors.patch @@ -5,7 +5,7 @@ Subject: [PATCH] Zombie API - breaking doors diff --git a/src/main/java/org/bukkit/entity/Zombie.java b/src/main/java/org/bukkit/entity/Zombie.java -index c1a5b625ea602d751a8026d989882c60e80756c9..93de95f68af45dba6a1da350a46adc1d1d058899 100644 +index c1a5b625ea602d751a8026d989882c60e80756c9..0a7d4d3f776a72b345d746c583e4c32267929b54 100644 --- a/src/main/java/org/bukkit/entity/Zombie.java +++ b/src/main/java/org/bukkit/entity/Zombie.java @@ -100,8 +100,10 @@ public interface Zombie extends Monster, Ageable { @@ -21,7 +21,7 @@ index c1a5b625ea602d751a8026d989882c60e80756c9..93de95f68af45dba6a1da350a46adc1d * the entity is currently breaking a door. * * @param flag Whether this zombie can break doors -@@ -162,5 +164,15 @@ public interface Zombie extends Monster, Ageable { +@@ -162,5 +164,17 @@ public interface Zombie extends Monster, Ageable { * @param shouldBurnInDay True to burn in sunlight */ void setShouldBurnInDay(boolean shouldBurnInDay); @@ -33,7 +33,9 @@ index c1a5b625ea602d751a8026d989882c60e80756c9..93de95f68af45dba6a1da350a46adc1d + * no effect. + * + * @return true if entity supports breaking doors ++ * @deprecated Since 1.21.2 all zombie types can break doors if instructed as MC-137053 was fixed. + */ ++ @Deprecated(since = "1.21.2", forRemoval = true) + boolean supportsBreakingDoors(); // Paper end } diff --git a/patches/api/0235-Add-sendOpLevel-API.patch b/patches/api/0235-Add-sendOpLevel-API.patch index 360c197b188e..c64ed185a2b7 100644 --- a/patches/api/0235-Add-sendOpLevel-API.patch +++ b/patches/api/0235-Add-sendOpLevel-API.patch @@ -5,10 +5,10 @@ Subject: [PATCH] Add sendOpLevel API diff --git a/src/main/java/org/bukkit/entity/Player.java b/src/main/java/org/bukkit/entity/Player.java -index 77e14139e5cd22278e6decc7e5de31411fa2ae45..466218a3b7f749b3de67e619285ceeb6d85cc28e 100644 +index d2b74718998761a3f1a1a07547eb49661d5431df..a03ac5f230521cde2278222de8157f78a152f6a6 100644 --- a/src/main/java/org/bukkit/entity/Player.java +++ b/src/main/java/org/bukkit/entity/Player.java -@@ -3361,6 +3361,19 @@ public interface Player extends HumanEntity, Conversable, OfflinePlayer, PluginM +@@ -3401,6 +3401,19 @@ public interface Player extends HumanEntity, Conversable, OfflinePlayer, PluginM } // Paper end - elytra boost API diff --git a/patches/api/0253-Expand-world-key-API.patch b/patches/api/0253-Expand-world-key-API.patch index 2436c924af19..2716158a70bf 100644 --- a/patches/api/0253-Expand-world-key-API.patch +++ b/patches/api/0253-Expand-world-key-API.patch @@ -100,10 +100,10 @@ index 943f8881ea23481ea5d5125b6ec7c9c6f763f0b0..42930006b6425b5d82233e4ffe7025ce * Create a new virtual {@link WorldBorder}. *

    diff --git a/src/main/java/org/bukkit/UnsafeValues.java b/src/main/java/org/bukkit/UnsafeValues.java -index 64be4e60a03cb7cdc21013837d241d288695b69d..0231b79d96d535e0ae37296b3e806844740783ca 100644 +index db48f30704efa6928599a5cebf5ce577c8430198..4229db3c6abb693803a4bdd5a71e426c688f26cc 100644 --- a/src/main/java/org/bukkit/UnsafeValues.java +++ b/src/main/java/org/bukkit/UnsafeValues.java -@@ -181,5 +181,10 @@ public interface UnsafeValues { +@@ -170,5 +170,10 @@ public interface UnsafeValues { * Use this when sending custom packets, so that there are no collisions on the client or server. */ public int nextEntityId(); diff --git a/patches/api/0254-Improve-Item-Rarity-API.patch b/patches/api/0254-Improve-Item-Rarity-API.patch index 7a78a33aecb9..6914d28cf844 100644 --- a/patches/api/0254-Improve-Item-Rarity-API.patch +++ b/patches/api/0254-Improve-Item-Rarity-API.patch @@ -43,10 +43,10 @@ index 0000000000000000000000000000000000000000..f1cd5a4f37eee8975ac3d0421b524afc + } +} diff --git a/src/main/java/org/bukkit/Material.java b/src/main/java/org/bukkit/Material.java -index 39bf5b2e93f45000d2e894b5c3d059b889068aae..4ceb90598e4060678c2382568d4a691769efe126 100644 +index 8820dd330cee4f8463f2f39f84d4be0762558368..39277b3113a3bd0736330773e3c5c1f747773b55 100644 --- a/src/main/java/org/bukkit/Material.java +++ b/src/main/java/org/bukkit/Material.java -@@ -4709,6 +4709,21 @@ public enum Material implements Keyed, Translatable, net.kyori.adventure.transla +@@ -4874,6 +4874,21 @@ public enum Material implements Keyed, Translatable, net.kyori.adventure.transla } // Paper end - add Translatable @@ -132,10 +132,10 @@ index 60eec8a12f01562678732bcf38ac407e70d74965..45fc5fab3817a8d9e1c83bcfb0add9eb // Paper end } diff --git a/src/main/java/org/bukkit/inventory/ItemType.java b/src/main/java/org/bukkit/inventory/ItemType.java -index 2b1a1ad04212651ad5a43edecca12b495ce61fd5..9077257ed935a26af057b9d090f7d819956ebbce 100644 +index 49e5a9ea7cf5e4d7a4333d2cffa4e44b1a436403..d049aec3865c0eaa570ffe4234f02ff13d77e542 100644 --- a/src/main/java/org/bukkit/inventory/ItemType.java +++ b/src/main/java/org/bukkit/inventory/ItemType.java -@@ -2306,4 +2306,13 @@ public interface ItemType extends Keyed, Translatable, net.kyori.adventure.trans +@@ -2457,4 +2457,13 @@ public interface ItemType extends Keyed, Translatable, net.kyori.adventure.trans @Override @NotNull String getTranslationKey(); // Paper end - add Translatable diff --git a/patches/api/0255-Expose-protocol-version.patch b/patches/api/0255-Expose-protocol-version.patch index 9ba0c40a9613..f6cace865e17 100644 --- a/patches/api/0255-Expose-protocol-version.patch +++ b/patches/api/0255-Expose-protocol-version.patch @@ -5,10 +5,10 @@ Subject: [PATCH] Expose protocol version diff --git a/src/main/java/org/bukkit/UnsafeValues.java b/src/main/java/org/bukkit/UnsafeValues.java -index 0231b79d96d535e0ae37296b3e806844740783ca..51473ffbec65a2344449daa8ff5cf535b0b60520 100644 +index 4229db3c6abb693803a4bdd5a71e426c688f26cc..f33426207c403906c3c6fb99e848fd7ecbffd127 100644 --- a/src/main/java/org/bukkit/UnsafeValues.java +++ b/src/main/java/org/bukkit/UnsafeValues.java -@@ -186,5 +186,12 @@ public interface UnsafeValues { +@@ -175,5 +175,12 @@ public interface UnsafeValues { * Just don't use it. */ @org.jetbrains.annotations.NotNull String getMainLevelName(); diff --git a/patches/api/0256-add-isDeeplySleeping-to-HumanEntity.patch b/patches/api/0256-add-isDeeplySleeping-to-HumanEntity.patch index 14754cc21ce1..29f430175b35 100644 --- a/patches/api/0256-add-isDeeplySleeping-to-HumanEntity.patch +++ b/patches/api/0256-add-isDeeplySleeping-to-HumanEntity.patch @@ -5,7 +5,7 @@ Subject: [PATCH] add isDeeplySleeping to HumanEntity diff --git a/src/main/java/org/bukkit/entity/HumanEntity.java b/src/main/java/org/bukkit/entity/HumanEntity.java -index 937c136f2499bd1660989d14c0f50a7ef9a1a2b6..b1b18886fc63a4854c2858ff9869da70e92dae26 100644 +index b9c19abe26c100558c4a0388d65c2316261ee1d3..36b48bfff60ecc3d49f9f6575a91dd6b73ecf1ab 100644 --- a/src/main/java/org/bukkit/entity/HumanEntity.java +++ b/src/main/java/org/bukkit/entity/HumanEntity.java @@ -366,6 +366,15 @@ public interface HumanEntity extends LivingEntity, AnimalTamer, InventoryHolder @@ -22,5 +22,5 @@ index 937c136f2499bd1660989d14c0f50a7ef9a1a2b6..b1b18886fc63a4854c2858ff9869da70 + // Paper end + /** - * Get the sleep ticks of the player. This value may be capped. + * Check whether a cooldown is active on the specified item. * diff --git a/patches/api/0270-Add-basic-Datapack-API.patch b/patches/api/0270-Add-basic-Datapack-API.patch index 5535058f121b..796a519bd972 100644 --- a/patches/api/0270-Add-basic-Datapack-API.patch +++ b/patches/api/0270-Add-basic-Datapack-API.patch @@ -237,10 +237,10 @@ index b558fa73dbcf3747690933e6aadf7061a0de2630..be68351555bde59a4e55bf1bad261e9f @NotNull diff --git a/src/main/java/org/bukkit/Material.java b/src/main/java/org/bukkit/Material.java -index 4ceb90598e4060678c2382568d4a691769efe126..052d319e69f22277cb6e379e47380c7dc466d120 100644 +index 39277b3113a3bd0736330773e3c5c1f747773b55..c18fb0c9a6635a67041ba7499e8b2f97ce9a76d4 100644 --- a/src/main/java/org/bukkit/Material.java +++ b/src/main/java/org/bukkit/Material.java -@@ -5518,6 +5518,7 @@ public enum Material implements Keyed, Translatable, net.kyori.adventure.transla +@@ -5683,6 +5683,7 @@ public enum Material implements Keyed, Translatable, net.kyori.adventure.transla * @param world the world to check * @return true if this material can be used in this World. */ @@ -277,10 +277,10 @@ index 42930006b6425b5d82233e4ffe7025ce5397b277..45693e6c02eac37eb609cd3c59253a94 // Paper end } diff --git a/src/main/java/org/bukkit/entity/EntityType.java b/src/main/java/org/bukkit/entity/EntityType.java -index 976f701ed9b9873945a5628173c580e2e6873864..eea0351559a2835280713f5d5d1d430c7cf857a0 100644 +index be1c8c9b27ad792f2b0ff1cec0c575eb1fc3023a..f08d241d5350dfdb0d325e89190c90f79a5c791e 100644 --- a/src/main/java/org/bukkit/entity/EntityType.java +++ b/src/main/java/org/bukkit/entity/EntityType.java -@@ -449,6 +449,7 @@ public enum EntityType implements Keyed, Translatable, net.kyori.adventure.trans +@@ -492,6 +492,7 @@ public enum EntityType implements Keyed, Translatable, net.kyori.adventure.trans * @param world the world to check * @return true if this EntityType can be used to spawn an Entity for this World. */ diff --git a/patches/api/0272-ItemStack-repair-check-API.patch b/patches/api/0272-ItemStack-repair-check-API.patch index 783c40966a96..6385fdeb47ea 100644 --- a/patches/api/0272-ItemStack-repair-check-API.patch +++ b/patches/api/0272-ItemStack-repair-check-API.patch @@ -5,10 +5,10 @@ Subject: [PATCH] ItemStack repair check API diff --git a/src/main/java/org/bukkit/UnsafeValues.java b/src/main/java/org/bukkit/UnsafeValues.java -index 51473ffbec65a2344449daa8ff5cf535b0b60520..07669aad6d9910174fbc8fdf3cdd54211fbfcee3 100644 +index f33426207c403906c3c6fb99e848fd7ecbffd127..3ef6ffb506a7fdd05a08353f342e45de8066ca19 100644 --- a/src/main/java/org/bukkit/UnsafeValues.java +++ b/src/main/java/org/bukkit/UnsafeValues.java -@@ -193,5 +193,15 @@ public interface UnsafeValues { +@@ -182,5 +182,15 @@ public interface UnsafeValues { * @return the server's protocol version */ int getProtocolVersion(); @@ -25,7 +25,7 @@ index 51473ffbec65a2344449daa8ff5cf535b0b60520..07669aad6d9910174fbc8fdf3cdd5421 // Paper end } diff --git a/src/main/java/org/bukkit/inventory/ItemStack.java b/src/main/java/org/bukkit/inventory/ItemStack.java -index d2af613c56010f3b0dd0d3ff7b438193127353d0..5b84aec2897b35da3e1bee8ac73fba5c83717d5d 100644 +index 45fc5fab3817a8d9e1c83bcfb0add9eba023abfe..ea50697a8dcdf87be046569b75fcc53ec870ca3e 100644 --- a/src/main/java/org/bukkit/inventory/ItemStack.java +++ b/src/main/java/org/bukkit/inventory/ItemStack.java @@ -1006,5 +1006,27 @@ public class ItemStack implements Cloneable, ConfigurationSerializable, Translat diff --git a/patches/api/0275-ItemStack-editMeta.patch b/patches/api/0275-ItemStack-editMeta.patch index 83787f07f979..7361918ba909 100644 --- a/patches/api/0275-ItemStack-editMeta.patch +++ b/patches/api/0275-ItemStack-editMeta.patch @@ -5,7 +5,7 @@ Subject: [PATCH] ItemStack#editMeta diff --git a/src/main/java/org/bukkit/inventory/ItemStack.java b/src/main/java/org/bukkit/inventory/ItemStack.java -index b38154b45935ec45154e89277a8c2b1b9e46522d..7789c57ee27dc0e95764a6a5830de4cba210aa3b 100644 +index ea50697a8dcdf87be046569b75fcc53ec870ca3e..1d1731776af5f59cd9e6bd07cb3b9fab5073ef66 100644 --- a/src/main/java/org/bukkit/inventory/ItemStack.java +++ b/src/main/java/org/bukkit/inventory/ItemStack.java @@ -574,6 +574,50 @@ public class ItemStack implements Cloneable, ConfigurationSerializable, Translat diff --git a/patches/api/0277-Improve-item-default-attribute-API.patch b/patches/api/0277-Improve-item-default-attribute-API.patch index cba247deebfd..5fd65d72195c 100644 --- a/patches/api/0277-Improve-item-default-attribute-API.patch +++ b/patches/api/0277-Improve-item-default-attribute-API.patch @@ -5,10 +5,10 @@ Subject: [PATCH] Improve item default attribute API diff --git a/src/main/java/org/bukkit/Material.java b/src/main/java/org/bukkit/Material.java -index 052d319e69f22277cb6e379e47380c7dc466d120..eec1e53ce607d36a2e72f16a4a351869fd2f609f 100644 +index c18fb0c9a6635a67041ba7499e8b2f97ce9a76d4..3b344a49c26e9f4b3a7ae54ecb90da7c08d0ad49 100644 --- a/src/main/java/org/bukkit/Material.java +++ b/src/main/java/org/bukkit/Material.java -@@ -4724,6 +4724,23 @@ public enum Material implements Keyed, Translatable, net.kyori.adventure.transla +@@ -4889,6 +4889,23 @@ public enum Material implements Keyed, Translatable, net.kyori.adventure.transla } // Paper end - item rarity API @@ -32,7 +32,7 @@ index 052d319e69f22277cb6e379e47380c7dc466d120..eec1e53ce607d36a2e72f16a4a351869 /** * Do not use for any reason. * -@@ -5431,13 +5448,34 @@ public enum Material implements Keyed, Translatable, net.kyori.adventure.transla +@@ -5596,13 +5613,34 @@ public enum Material implements Keyed, Translatable, net.kyori.adventure.transla } } @@ -72,10 +72,10 @@ index 052d319e69f22277cb6e379e47380c7dc466d120..eec1e53ce607d36a2e72f16a4a351869 * * @param slot the {@link EquipmentSlot} to check diff --git a/src/main/java/org/bukkit/inventory/ItemType.java b/src/main/java/org/bukkit/inventory/ItemType.java -index 9077257ed935a26af057b9d090f7d819956ebbce..c42cfa76ff73a3ce8a164cb94a9c3f553b005ea5 100644 +index d049aec3865c0eaa570ffe4234f02ff13d77e542..7a1a0aebbfdaac6b6af41236d4a00512244b58fa 100644 --- a/src/main/java/org/bukkit/inventory/ItemType.java +++ b/src/main/java/org/bukkit/inventory/ItemType.java -@@ -2256,6 +2256,21 @@ public interface ItemType extends Keyed, Translatable, net.kyori.adventure.trans +@@ -2407,6 +2407,21 @@ public interface ItemType extends Keyed, Translatable, net.kyori.adventure.trans // @NotNull // EquipmentSlot getEquipmentSlot(); diff --git a/patches/api/0280-Add-PlayerKickEvent-causes.patch b/patches/api/0280-Add-PlayerKickEvent-causes.patch index aa55ace2fe76..5520442f0753 100644 --- a/patches/api/0280-Add-PlayerKickEvent-causes.patch +++ b/patches/api/0280-Add-PlayerKickEvent-causes.patch @@ -5,10 +5,10 @@ Subject: [PATCH] Add PlayerKickEvent causes diff --git a/src/main/java/org/bukkit/entity/Player.java b/src/main/java/org/bukkit/entity/Player.java -index 466218a3b7f749b3de67e619285ceeb6d85cc28e..7f854d545a76a6e4dd2439f9a6e193fa54d5874d 100644 +index a03ac5f230521cde2278222de8157f78a152f6a6..ff5a0d2b6a070c85545b00cf41c51b8d9cce8d0d 100644 --- a/src/main/java/org/bukkit/entity/Player.java +++ b/src/main/java/org/bukkit/entity/Player.java -@@ -319,6 +319,14 @@ public interface Player extends HumanEntity, Conversable, OfflinePlayer, PluginM +@@ -335,6 +335,14 @@ public interface Player extends HumanEntity, Conversable, OfflinePlayer, PluginM * @param message kick message */ void kick(final net.kyori.adventure.text.@Nullable Component message); diff --git a/patches/api/0284-Add-more-line-of-sight-methods.patch b/patches/api/0284-Add-more-line-of-sight-methods.patch index 151554550047..43d0fda6245c 100644 --- a/patches/api/0284-Add-more-line-of-sight-methods.patch +++ b/patches/api/0284-Add-more-line-of-sight-methods.patch @@ -23,7 +23,7 @@ index d8b1fa79dc24138dc71e32c14bda71c1d570ed88..b68367f123f029c3ff47eab6bfabd7a8 // Paper end } diff --git a/src/main/java/org/bukkit/entity/LivingEntity.java b/src/main/java/org/bukkit/entity/LivingEntity.java -index 4327045ec437c9c81bcd4c34c4959de6d5798132..4ba92fe8979ed127c18cb78c2b8204daa2425ed7 100644 +index 49352ed3928163c6322634b8e6f1d3dd8caa5e74..2eb6f650610ca1a9b9fca49e453f79e08944be75 100644 --- a/src/main/java/org/bukkit/entity/LivingEntity.java +++ b/src/main/java/org/bukkit/entity/LivingEntity.java @@ -622,6 +622,19 @@ public interface LivingEntity extends Attributable, Damageable, ProjectileSource diff --git a/patches/api/0287-Missing-Entity-API.patch b/patches/api/0287-Missing-Entity-API.patch index 94f2d56efecc..00f38bebc3a5 100644 --- a/patches/api/0287-Missing-Entity-API.patch +++ b/patches/api/0287-Missing-Entity-API.patch @@ -1095,15 +1095,18 @@ index 4374d5206d4d15a4d8d228c137ed9a96821a1f02..0eb7214472f3a43641a3526000af6bee +} +// Paper end diff --git a/src/main/java/org/bukkit/entity/Salmon.java b/src/main/java/org/bukkit/entity/Salmon.java -index a52a7af219633d575dcbe8ac4b219834bfd4d4d2..1e839b247182af6873a4d74b236d6412817c18bf 100644 +index 407aa5de170a3f0160c197a2b228f2e3b3269387..d8a2d44fe50a9ab24d8916aad270dfba0bd84e5e 100644 --- a/src/main/java/org/bukkit/entity/Salmon.java +++ b/src/main/java/org/bukkit/entity/Salmon.java -@@ -4,4 +4,4 @@ package org.bukkit.entity; +@@ -5,7 +5,7 @@ import org.jetbrains.annotations.NotNull; /** * Represents a salmon fish. */ --public interface Salmon extends Fish { } -+public interface Salmon extends io.papermc.paper.entity.SchoolableFish { } // Paper - Schooling Fish API +-public interface Salmon extends Fish { ++public interface Salmon extends io.papermc.paper.entity.SchoolableFish { // Paper - Schooling Fish API + + /** + * Get the variant of this salmon. diff --git a/src/main/java/org/bukkit/entity/TNTPrimed.java b/src/main/java/org/bukkit/entity/TNTPrimed.java index 0813bd913c8fdb2001963ce3e82c07c2af105418..87e717c9ea61b0cbf536bc62fa829ddcfae5ad8c 100644 --- a/src/main/java/org/bukkit/entity/TNTPrimed.java diff --git a/patches/api/0291-Stinger-API.patch b/patches/api/0291-Stinger-API.patch index 84fe8f4c2266..a12c525d768a 100644 --- a/patches/api/0291-Stinger-API.patch +++ b/patches/api/0291-Stinger-API.patch @@ -5,7 +5,7 @@ Subject: [PATCH] Stinger API diff --git a/src/main/java/org/bukkit/entity/LivingEntity.java b/src/main/java/org/bukkit/entity/LivingEntity.java -index 73247ab2cd2cf0035cf88c98250736f9bc9ee517..22428de50580f7b70d14484ba229aa271bfd7069 100644 +index c0772f72768846cffd065c53de7326f9fe6386a2..f7a3dd62ae5e492a7bccf8167cec0fc560499fa2 100644 --- a/src/main/java/org/bukkit/entity/LivingEntity.java +++ b/src/main/java/org/bukkit/entity/LivingEntity.java @@ -451,6 +451,52 @@ public interface LivingEntity extends Attributable, Damageable, ProjectileSource diff --git a/patches/api/0303-Add-methods-to-find-targets-for-lightning-strikes.patch b/patches/api/0303-Add-methods-to-find-targets-for-lightning-strikes.patch index b045b2afc976..dd357bfe8c44 100644 --- a/patches/api/0303-Add-methods-to-find-targets-for-lightning-strikes.patch +++ b/patches/api/0303-Add-methods-to-find-targets-for-lightning-strikes.patch @@ -5,7 +5,7 @@ Subject: [PATCH] Add methods to find targets for lightning strikes diff --git a/src/main/java/org/bukkit/World.java b/src/main/java/org/bukkit/World.java -index d3fc033aba36c5fd99846e9200ed0071fddd6045..637480c622bdb170456baabe71d84e446ebd7b13 100644 +index ce1f3ffbab6a8dc8395e3a5b74a7874bb6b38aa9..90270582b75705e42b4690cadb6d15de3188d98f 100644 --- a/src/main/java/org/bukkit/World.java +++ b/src/main/java/org/bukkit/World.java @@ -692,6 +692,37 @@ public interface World extends RegionAccessor, WorldInfo, PluginMessageRecipient diff --git a/patches/api/0304-Get-entity-default-attributes.patch b/patches/api/0304-Get-entity-default-attributes.patch index d77cd64f2c47..deca580fd38d 100644 --- a/patches/api/0304-Get-entity-default-attributes.patch +++ b/patches/api/0304-Get-entity-default-attributes.patch @@ -5,10 +5,10 @@ Subject: [PATCH] Get entity default attributes diff --git a/src/main/java/org/bukkit/UnsafeValues.java b/src/main/java/org/bukkit/UnsafeValues.java -index 07669aad6d9910174fbc8fdf3cdd54211fbfcee3..a25f7378e5cef3899c38dd34d369da0441951f24 100644 +index 3ef6ffb506a7fdd05a08353f342e45de8066ca19..b8627d845bbc8c845af364408d3b6abb57c7308b 100644 --- a/src/main/java/org/bukkit/UnsafeValues.java +++ b/src/main/java/org/bukkit/UnsafeValues.java -@@ -203,5 +203,22 @@ public interface UnsafeValues { +@@ -192,5 +192,22 @@ public interface UnsafeValues { * @return true if valid repair, false if not */ public boolean isValidRepairItemStack(@org.jetbrains.annotations.NotNull ItemStack itemToBeRepaired, @org.jetbrains.annotations.NotNull ItemStack repairMaterial); @@ -32,10 +32,10 @@ index 07669aad6d9910174fbc8fdf3cdd54211fbfcee3..a25f7378e5cef3899c38dd34d369da04 // Paper end } diff --git a/src/main/java/org/bukkit/entity/EntityType.java b/src/main/java/org/bukkit/entity/EntityType.java -index 976f701ed9b9873945a5628173c580e2e6873864..6905614c3d277a3a725554f02cc92d4b3430eecc 100644 +index f08d241d5350dfdb0d325e89190c90f79a5c791e..6521a20d69a4c8e75be7e9b3fdebbc25b843ec1b 100644 --- a/src/main/java/org/bukkit/entity/EntityType.java +++ b/src/main/java/org/bukkit/entity/EntityType.java -@@ -441,6 +441,25 @@ public enum EntityType implements Keyed, Translatable, net.kyori.adventure.trans +@@ -484,6 +484,25 @@ public enum EntityType implements Keyed, Translatable, net.kyori.adventure.trans Preconditions.checkArgument(this != UNKNOWN, "UNKNOWN entities do not have translation keys"); return org.bukkit.Bukkit.getUnsafe().getTranslationKey(this); } diff --git a/patches/api/0309-Add-hasCollision-methods-to-various-places.patch b/patches/api/0309-Add-hasCollision-methods-to-various-places.patch index 59e7c03a1f8d..d9455d45a633 100644 --- a/patches/api/0309-Add-hasCollision-methods-to-various-places.patch +++ b/patches/api/0309-Add-hasCollision-methods-to-various-places.patch @@ -5,10 +5,10 @@ Subject: [PATCH] Add hasCollision methods to various places diff --git a/src/main/java/org/bukkit/Material.java b/src/main/java/org/bukkit/Material.java -index eec1e53ce607d36a2e72f16a4a351869fd2f609f..23fff8ee71b99aab8e650f3916511b7f34b5eb4e 100644 +index 3b344a49c26e9f4b3a7ae54ecb90da7c08d0ad49..615eb24ffdd8f6d55ccd4f21760b809c1098bc68 100644 --- a/src/main/java/org/bukkit/Material.java +++ b/src/main/java/org/bukkit/Material.java -@@ -4741,6 +4741,21 @@ public enum Material implements Keyed, Translatable, net.kyori.adventure.transla +@@ -4906,6 +4906,21 @@ public enum Material implements Keyed, Translatable, net.kyori.adventure.transla } // Paper end - item default attributes API @@ -67,10 +67,10 @@ index f4a739d8022d19a7ae0ee9bf93eb5c4846b4bd40..94e1278340c0d9d2be9edc68f6454143 + // Paper end } diff --git a/src/main/java/org/bukkit/block/BlockType.java b/src/main/java/org/bukkit/block/BlockType.java -index 5bfa98695265cdfd246411f93ab670d2c9e64ef1..a58ef2238208fbb55341f4532eaa288577ed8c0e 100644 +index aebd34785bb2070389ad2e2803fa9ff803b318c5..c080c2a3323d19cb3d549aa0fe6c164666d7da75 100644 --- a/src/main/java/org/bukkit/block/BlockType.java +++ b/src/main/java/org/bukkit/block/BlockType.java -@@ -3511,4 +3511,13 @@ public interface BlockType extends Keyed, Translatable, net.kyori.adventure.tran +@@ -3650,4 +3650,13 @@ public interface BlockType extends Keyed, Translatable, net.kyori.adventure.tran @Override @NotNull String getTranslationKey(); // Paper end - add Translatable diff --git a/patches/api/0312-Add-Raw-Byte-Entity-Serialization.patch b/patches/api/0312-Add-Raw-Byte-Entity-Serialization.patch index 790704d4e93f..abe5d9f16364 100644 --- a/patches/api/0312-Add-Raw-Byte-Entity-Serialization.patch +++ b/patches/api/0312-Add-Raw-Byte-Entity-Serialization.patch @@ -5,10 +5,10 @@ Subject: [PATCH] Add Raw Byte Entity Serialization diff --git a/src/main/java/org/bukkit/UnsafeValues.java b/src/main/java/org/bukkit/UnsafeValues.java -index a25f7378e5cef3899c38dd34d369da0441951f24..3aeac7b102f7c6e6186d168294ea73ff022f9349 100644 +index b8627d845bbc8c845af364408d3b6abb57c7308b..ef22077e2bf9709bef21e259cfa6435f80305b5e 100644 --- a/src/main/java/org/bukkit/UnsafeValues.java +++ b/src/main/java/org/bukkit/UnsafeValues.java -@@ -175,6 +175,14 @@ public interface UnsafeValues { +@@ -164,6 +164,14 @@ public interface UnsafeValues { ItemStack deserializeItem(byte[] data); diff --git a/patches/api/0325-Multi-Block-Change-API.patch b/patches/api/0325-Multi-Block-Change-API.patch index 9d93df870696..f7cb304e6ec2 100644 --- a/patches/api/0325-Multi-Block-Change-API.patch +++ b/patches/api/0325-Multi-Block-Change-API.patch @@ -5,10 +5,10 @@ Subject: [PATCH] Multi Block Change API diff --git a/src/main/java/org/bukkit/entity/Player.java b/src/main/java/org/bukkit/entity/Player.java -index 7f854d545a76a6e4dd2439f9a6e193fa54d5874d..48b3154ee164b9de74433556d8727e8b818ffbe5 100644 +index ff5a0d2b6a070c85545b00cf41c51b8d9cce8d0d..99bad852128f499f8f71552399c49f373d51cf9c 100644 --- a/src/main/java/org/bukkit/entity/Player.java +++ b/src/main/java/org/bukkit/entity/Player.java -@@ -937,6 +937,29 @@ public interface Player extends HumanEntity, Conversable, OfflinePlayer, PluginM +@@ -977,6 +977,29 @@ public interface Player extends HumanEntity, Conversable, OfflinePlayer, PluginM */ public void sendBlockDamage(@NotNull Location loc, float progress); diff --git a/patches/api/0328-Dolphin-API.patch b/patches/api/0328-Dolphin-API.patch index 73beed437bbb..8c3706b8706c 100644 --- a/patches/api/0328-Dolphin-API.patch +++ b/patches/api/0328-Dolphin-API.patch @@ -5,16 +5,16 @@ Subject: [PATCH] Dolphin API diff --git a/src/main/java/org/bukkit/entity/Dolphin.java b/src/main/java/org/bukkit/entity/Dolphin.java -index f00eaadcdde7ceef95def2d8ec6eb63a76c177bd..8ab329946daaff25646f3dd4582feb9e4c0685ca 100644 +index fcf09afd38277180aa299703000e46dd2482c372..d3d3f8b20769eec65a81d9a59195625cb2be8579 100644 --- a/src/main/java/org/bukkit/entity/Dolphin.java +++ b/src/main/java/org/bukkit/entity/Dolphin.java @@ -1,3 +1,52 @@ package org.bukkit.entity; --public interface Dolphin extends WaterMob { } +-public interface Dolphin extends Ageable, WaterMob { } +import org.bukkit.Location; + -+public interface Dolphin extends WaterMob { // Paper start - Dolphin API ++public interface Dolphin extends Ageable, WaterMob { // Paper start - Dolphin API + + /** + * Gets the moistness level of this dolphin diff --git a/patches/api/0330-API-for-creating-command-sender-which-forwards-feedb.patch b/patches/api/0330-API-for-creating-command-sender-which-forwards-feedb.patch index 38c276ea9d13..2fdc723ece43 100644 --- a/patches/api/0330-API-for-creating-command-sender-which-forwards-feedb.patch +++ b/patches/api/0330-API-for-creating-command-sender-which-forwards-feedb.patch @@ -5,7 +5,7 @@ Subject: [PATCH] API for creating command sender which forwards feedback diff --git a/src/main/java/org/bukkit/Bukkit.java b/src/main/java/org/bukkit/Bukkit.java -index 8a22c242a93b0e16e0bca583d0918bab695248b1..aa1795b9640a5e39cc5063dd3c389f6d5815ed36 100644 +index be68351555bde59a4e55bf1bad261e9f6bc9f704..e2d3e42b403dce454988c3ae3e44bcd89337b1cf 100644 --- a/src/main/java/org/bukkit/Bukkit.java +++ b/src/main/java/org/bukkit/Bukkit.java @@ -1595,6 +1595,20 @@ public final class Bukkit { @@ -30,7 +30,7 @@ index 8a22c242a93b0e16e0bca583d0918bab695248b1..aa1795b9640a5e39cc5063dd3c389f6d * Gets the folder that contains all of the various {@link World}s. * diff --git a/src/main/java/org/bukkit/Server.java b/src/main/java/org/bukkit/Server.java -index 67b5cb7635c4251c259b1fb1ef50f99a0b2647e5..65060c06c1e5521656bd88547b8d0df5975c1d29 100644 +index 45693e6c02eac37eb609cd3c59253a949a6ca4c0..5dd7ce5c008c852dbeb0474a70e9357230406318 100644 --- a/src/main/java/org/bukkit/Server.java +++ b/src/main/java/org/bukkit/Server.java @@ -1349,6 +1349,18 @@ public interface Server extends PluginMessageRecipient, net.kyori.adventure.audi diff --git a/patches/api/0331-Implement-regenerateChunk.patch b/patches/api/0331-Implement-regenerateChunk.patch index 480af4b7470a..4146857008a0 100644 --- a/patches/api/0331-Implement-regenerateChunk.patch +++ b/patches/api/0331-Implement-regenerateChunk.patch @@ -5,7 +5,7 @@ Subject: [PATCH] Implement regenerateChunk diff --git a/src/main/java/org/bukkit/World.java b/src/main/java/org/bukkit/World.java -index 637480c622bdb170456baabe71d84e446ebd7b13..8e9ab00503167799c6c929d00e48c07cb328848c 100644 +index 90270582b75705e42b4690cadb6d15de3188d98f..2720f290a632dd32fd9e70a40e73db9d1d161e94 100644 --- a/src/main/java/org/bukkit/World.java +++ b/src/main/java/org/bukkit/World.java @@ -405,8 +405,8 @@ public interface World extends RegionAccessor, WorldInfo, PluginMessageRecipient diff --git a/patches/api/0332-Add-GameEvent-tags.patch b/patches/api/0332-Add-GameEvent-tags.patch index 8af580d84b28..d976b18139b9 100644 --- a/patches/api/0332-Add-GameEvent-tags.patch +++ b/patches/api/0332-Add-GameEvent-tags.patch @@ -5,10 +5,10 @@ Subject: [PATCH] Add GameEvent tags diff --git a/src/main/java/org/bukkit/Tag.java b/src/main/java/org/bukkit/Tag.java -index b587897a26e9464b61a29e7482c60d2a66469571..8bfec649f7c6dda956bc388a21b489f3565ff384 100644 +index 0eca6dc6bcd9bbcad0a98a5956091bec362f2db6..42f0501ae6a2d9297bdd9bb9ab2fbb02abb881f5 100644 --- a/src/main/java/org/bukkit/Tag.java +++ b/src/main/java/org/bukkit/Tag.java -@@ -1315,6 +1315,25 @@ public interface Tag extends Keyed { +@@ -1411,6 +1411,25 @@ public interface Tag extends Keyed { */ Tag ENTITY_TYPES_REDIRECTABLE_PROJECTILE = Bukkit.getTag(REGISTRY_ENTITY_TYPES, NamespacedKey.minecraft("redirectable_projectile"), EntityType.class); diff --git a/patches/api/0340-Add-enchantWithLevels-API.patch b/patches/api/0340-Add-enchantWithLevels-API.patch index 878b0c7df50b..744a7aa96386 100644 --- a/patches/api/0340-Add-enchantWithLevels-API.patch +++ b/patches/api/0340-Add-enchantWithLevels-API.patch @@ -7,7 +7,7 @@ Deprecate upstream's newer and poorly implemented similar API. diff --git a/src/main/java/org/bukkit/inventory/ItemFactory.java b/src/main/java/org/bukkit/inventory/ItemFactory.java -index 96546712f788e091749a1b4eebc6b1d6c3db7814..bd0e55562f1cabef3078573182e0cf9fbc844585 100644 +index 579a9037b656bef9fb65c6da03611e981492074a..e1986aea72bb1f1ba2ea76f3ba53f274b6aac899 100644 --- a/src/main/java/org/bukkit/inventory/ItemFactory.java +++ b/src/main/java/org/bukkit/inventory/ItemFactory.java @@ -170,8 +170,11 @@ public interface ItemFactory { @@ -70,7 +70,7 @@ index 96546712f788e091749a1b4eebc6b1d6c3db7814..bd0e55562f1cabef3078573182e0cf9f + // Paper end - enchantWithLevels API } diff --git a/src/main/java/org/bukkit/inventory/ItemStack.java b/src/main/java/org/bukkit/inventory/ItemStack.java -index 7789c57ee27dc0e95764a6a5830de4cba210aa3b..ab74890e9b6a13b76756f884d6d176bb45470191 100644 +index 1d1731776af5f59cd9e6bd07cb3b9fab5073ef66..9b2a62dc3da6718a3e8b39b4fb8bee3781e800cb 100644 --- a/src/main/java/org/bukkit/inventory/ItemStack.java +++ b/src/main/java/org/bukkit/inventory/ItemStack.java @@ -678,6 +678,24 @@ public class ItemStack implements Cloneable, ConfigurationSerializable, Translat diff --git a/patches/api/0349-Add-method-isTickingWorlds-to-Bukkit.patch b/patches/api/0349-Add-method-isTickingWorlds-to-Bukkit.patch index b0d1284d7c4a..e41abd2d6008 100644 --- a/patches/api/0349-Add-method-isTickingWorlds-to-Bukkit.patch +++ b/patches/api/0349-Add-method-isTickingWorlds-to-Bukkit.patch @@ -5,7 +5,7 @@ Subject: [PATCH] Add method isTickingWorlds() to Bukkit. diff --git a/src/main/java/org/bukkit/Bukkit.java b/src/main/java/org/bukkit/Bukkit.java -index 702e622a2ef2c3dcb0e582e38b3b10f9c939ed97..3a8be5316229f372b471549a00eae958543ac91d 100644 +index 22021582b2f490ea2db87f2d3fe8a99b44d4f457..adc95cd1486791787950533ef8e4baaf5d3827cc 100644 --- a/src/main/java/org/bukkit/Bukkit.java +++ b/src/main/java/org/bukkit/Bukkit.java @@ -809,12 +809,26 @@ public final class Bukkit { @@ -56,7 +56,7 @@ index 702e622a2ef2c3dcb0e582e38b3b10f9c939ed97..3a8be5316229f372b471549a00eae958 * @param world the world to unload * @param save whether to save the chunks before unloading diff --git a/src/main/java/org/bukkit/Server.java b/src/main/java/org/bukkit/Server.java -index 28e883ffb5c4db22613035899be4a627c230fbf4..547ba309beb6a4d1fdfadb234d671c1475abaae5 100644 +index 178e91f3ad918c1a5600d6e9a14a21d478f7e1df..0648130a6ce2e08d96b05fde1cfd58c2bb24ae07 100644 --- a/src/main/java/org/bukkit/Server.java +++ b/src/main/java/org/bukkit/Server.java @@ -679,34 +679,55 @@ public interface Server extends PluginMessageRecipient, net.kyori.adventure.audi diff --git a/patches/api/0352-Add-Player-getFishHook.patch b/patches/api/0352-Add-Player-getFishHook.patch index 0e0d38d513b4..340b5b119eaa 100644 --- a/patches/api/0352-Add-Player-getFishHook.patch +++ b/patches/api/0352-Add-Player-getFishHook.patch @@ -5,10 +5,10 @@ Subject: [PATCH] Add Player#getFishHook diff --git a/src/main/java/org/bukkit/entity/HumanEntity.java b/src/main/java/org/bukkit/entity/HumanEntity.java -index b1b18886fc63a4854c2858ff9869da70e92dae26..773651350c17cae9058346a590eda758071b7447 100644 +index 36b48bfff60ecc3d49f9f6575a91dd6b73ecf1ab..488604ba1a516b477693877c74712e4a45624a8b 100644 --- a/src/main/java/org/bukkit/entity/HumanEntity.java +++ b/src/main/java/org/bukkit/entity/HumanEntity.java -@@ -394,6 +394,13 @@ public interface HumanEntity extends LivingEntity, AnimalTamer, InventoryHolder +@@ -425,6 +425,13 @@ public interface HumanEntity extends LivingEntity, AnimalTamer, InventoryHolder @Nullable public Location getPotentialBedLocation(); // Paper end diff --git a/patches/api/0353-More-Teleport-API.patch b/patches/api/0353-More-Teleport-API.patch index a464ab538828..7357803c9787 100644 --- a/patches/api/0353-More-Teleport-API.patch +++ b/patches/api/0353-More-Teleport-API.patch @@ -158,10 +158,10 @@ index f51f3f04ba9efe15f68620c5531b502710078b6e..8bada7f7f0200103edc415ad003132d9 * Teleports this entity to the given location. If this entity is riding a * vehicle, it will be dismounted prior to teleportation. diff --git a/src/main/java/org/bukkit/entity/Player.java b/src/main/java/org/bukkit/entity/Player.java -index 48b3154ee164b9de74433556d8727e8b818ffbe5..fc6a7fcb7a3f1c92f725715eaea55500d6f553c1 100644 +index 99bad852128f499f8f71552399c49f373d51cf9c..4371ac4683604919782f6268756dff4f05695a40 100644 --- a/src/main/java/org/bukkit/entity/Player.java +++ b/src/main/java/org/bukkit/entity/Player.java -@@ -3521,6 +3521,45 @@ public interface Player extends HumanEntity, Conversable, OfflinePlayer, PluginM +@@ -3561,6 +3561,45 @@ public interface Player extends HumanEntity, Conversable, OfflinePlayer, PluginM String getClientBrandName(); // Paper end diff --git a/patches/api/0355-Custom-Chat-Completion-Suggestions-API.patch b/patches/api/0355-Custom-Chat-Completion-Suggestions-API.patch index 83811edce408..5c217eaf6bb0 100644 --- a/patches/api/0355-Custom-Chat-Completion-Suggestions-API.patch +++ b/patches/api/0355-Custom-Chat-Completion-Suggestions-API.patch @@ -5,10 +5,10 @@ Subject: [PATCH] Custom Chat Completion Suggestions API diff --git a/src/main/java/org/bukkit/entity/Player.java b/src/main/java/org/bukkit/entity/Player.java -index fc6a7fcb7a3f1c92f725715eaea55500d6f553c1..9e1785692bea39cda723094eba3985c6655ad267 100644 +index 4371ac4683604919782f6268756dff4f05695a40..761e7982d022eddd1adee6f2536fa93c4a9f67ea 100644 --- a/src/main/java/org/bukkit/entity/Player.java +++ b/src/main/java/org/bukkit/entity/Player.java -@@ -3405,6 +3405,31 @@ public interface Player extends HumanEntity, Conversable, OfflinePlayer, PluginM +@@ -3445,6 +3445,31 @@ public interface Player extends HumanEntity, Conversable, OfflinePlayer, PluginM void sendOpLevel(byte level); // Paper end - sendOpLevel API diff --git a/patches/api/0358-Add-NamespacedKey-biome-methods.patch b/patches/api/0358-Add-NamespacedKey-biome-methods.patch index adc8f6a1e21b..be2c0e7b2aa2 100644 --- a/patches/api/0358-Add-NamespacedKey-biome-methods.patch +++ b/patches/api/0358-Add-NamespacedKey-biome-methods.patch @@ -6,10 +6,10 @@ Subject: [PATCH] Add NamespacedKey biome methods Co-authored-by: Thonk <30448663+ExcessiveAmountsOfZombies@users.noreply.github.com> diff --git a/src/main/java/org/bukkit/UnsafeValues.java b/src/main/java/org/bukkit/UnsafeValues.java -index 3aeac7b102f7c6e6186d168294ea73ff022f9349..4f3e6e2698b28922e7b6448eddd5b166f4631759 100644 +index ef22077e2bf9709bef21e259cfa6435f80305b5e..14cf57a96f47ba666f05cedbc0005ff0fec6a33d 100644 --- a/src/main/java/org/bukkit/UnsafeValues.java +++ b/src/main/java/org/bukkit/UnsafeValues.java -@@ -229,4 +229,33 @@ public interface UnsafeValues { +@@ -218,4 +218,33 @@ public interface UnsafeValues { */ @org.jetbrains.annotations.NotNull org.bukkit.attribute.Attributable getDefaultEntityAttributes(@org.jetbrains.annotations.NotNull NamespacedKey entityKey); // Paper end diff --git a/patches/api/0364-Elder-Guardian-appearance-API.patch b/patches/api/0364-Elder-Guardian-appearance-API.patch index 4c5b51b7a5c9..5adec1def758 100644 --- a/patches/api/0364-Elder-Guardian-appearance-API.patch +++ b/patches/api/0364-Elder-Guardian-appearance-API.patch @@ -5,10 +5,10 @@ Subject: [PATCH] Elder Guardian appearance API diff --git a/src/main/java/org/bukkit/entity/Player.java b/src/main/java/org/bukkit/entity/Player.java -index 9e1785692bea39cda723094eba3985c6655ad267..6546bcbcced9468008212e9cf96a06c50c14a93f 100644 +index 761e7982d022eddd1adee6f2536fa93c4a9f67ea..404f448cf1488a9fa5aeebeeba5d51e9a5427bc7 100644 --- a/src/main/java/org/bukkit/entity/Player.java +++ b/src/main/java/org/bukkit/entity/Player.java -@@ -3585,6 +3585,24 @@ public interface Player extends HumanEntity, Conversable, OfflinePlayer, PluginM +@@ -3625,6 +3625,24 @@ public interface Player extends HumanEntity, Conversable, OfflinePlayer, PluginM void lookAt(@NotNull org.bukkit.entity.Entity entity, @NotNull io.papermc.paper.entity.LookAnchor playerAnchor, @NotNull io.papermc.paper.entity.LookAnchor entityAnchor); // Paper end - Teleport API diff --git a/patches/api/0371-Add-Player-Warden-Warning-API.patch b/patches/api/0371-Add-Player-Warden-Warning-API.patch index d1728e94925d..81dbe64546dd 100644 --- a/patches/api/0371-Add-Player-Warden-Warning-API.patch +++ b/patches/api/0371-Add-Player-Warden-Warning-API.patch @@ -5,10 +5,10 @@ Subject: [PATCH] Add Player Warden Warning API diff --git a/src/main/java/org/bukkit/entity/Player.java b/src/main/java/org/bukkit/entity/Player.java -index 6546bcbcced9468008212e9cf96a06c50c14a93f..edf4b93624e5c308be9bf0498187404c13525e09 100644 +index 404f448cf1488a9fa5aeebeeba5d51e9a5427bc7..17d69b6808e581dd69f93d7cb646ca43608b40df 100644 --- a/src/main/java/org/bukkit/entity/Player.java +++ b/src/main/java/org/bukkit/entity/Player.java -@@ -3601,6 +3601,59 @@ public interface Player extends HumanEntity, Conversable, OfflinePlayer, PluginM +@@ -3641,6 +3641,59 @@ public interface Player extends HumanEntity, Conversable, OfflinePlayer, PluginM * @param silent whether sound should be silenced */ void showElderGuardian(boolean silent); diff --git a/patches/api/0372-More-vanilla-friendly-methods-to-update-trades.patch b/patches/api/0372-More-vanilla-friendly-methods-to-update-trades.patch index dcad21ff699c..b56f34f9d14d 100644 --- a/patches/api/0372-More-vanilla-friendly-methods-to-update-trades.patch +++ b/patches/api/0372-More-vanilla-friendly-methods-to-update-trades.patch @@ -5,7 +5,7 @@ Subject: [PATCH] More vanilla friendly methods to update trades diff --git a/src/main/java/org/bukkit/entity/Villager.java b/src/main/java/org/bukkit/entity/Villager.java -index 0759f66986cec2c7e3f765aaa5b1654b5ed9f4b5..444744ea6f5921b0ae229995f8b15ea9d980c402 100644 +index 163f1afde2e04fdf4dddb894da62b301b52ed539..bc7137eb802d4613d042fba5fd97eca54a6eea29 100644 --- a/src/main/java/org/bukkit/entity/Villager.java +++ b/src/main/java/org/bukkit/entity/Villager.java @@ -64,8 +64,11 @@ public interface Villager extends AbstractVillager { diff --git a/patches/api/0374-ItemStack-damage-API.patch b/patches/api/0374-ItemStack-damage-API.patch index b3bc59b4df09..e48a903a6547 100644 --- a/patches/api/0374-ItemStack-damage-API.patch +++ b/patches/api/0374-ItemStack-damage-API.patch @@ -66,7 +66,7 @@ index 86c5ceddc722d28261f8a6d8368400fe2731aaf0..9f3e2903c955f2a5d1b25825c49188df + // Paper end - ItemStack damage API } diff --git a/src/main/java/org/bukkit/inventory/ItemStack.java b/src/main/java/org/bukkit/inventory/ItemStack.java -index a20f3a0894b700c235da1b8e1481062014054585..5c02a5fe37ea09502ca6c93d637a8ef5e4392ad4 100644 +index 9b2a62dc3da6718a3e8b39b4fb8bee3781e800cb..e39c9167bd66c528c09b256f15cc6c58666f0ca0 100644 --- a/src/main/java/org/bukkit/inventory/ItemStack.java +++ b/src/main/java/org/bukkit/inventory/ItemStack.java @@ -1090,5 +1090,19 @@ public class ItemStack implements Cloneable, ConfigurationSerializable, Translat diff --git a/patches/api/0376-Friction-API.patch b/patches/api/0376-Friction-API.patch index 7e07d5afbb91..8dc2a9591021 100644 --- a/patches/api/0376-Friction-API.patch +++ b/patches/api/0376-Friction-API.patch @@ -71,3 +71,16 @@ index 9f3e2903c955f2a5d1b25825c49188df62d20cef..016529563381a674db8050cb328f9e8f /** * Gets the height of the living entity's eyes above its Location. +diff --git a/src/main/java/org/bukkit/entity/Minecart.java b/src/main/java/org/bukkit/entity/Minecart.java +index 148d8cddba48a886eddef72a3de63d5eaa15949f..52cac73b7680806299a92013bbf959ecacac824f 100644 +--- a/src/main/java/org/bukkit/entity/Minecart.java ++++ b/src/main/java/org/bukkit/entity/Minecart.java +@@ -11,7 +11,7 @@ import org.jetbrains.annotations.Nullable; + /** + * Represents a minecart entity. + */ +-public interface Minecart extends Vehicle { ++public interface Minecart extends Vehicle, io.papermc.paper.entity.Frictional { // Paper + + /** + * Sets a minecart's damage. diff --git a/patches/api/0381-Add-Sneaking-API-for-Entities.patch b/patches/api/0381-Add-Sneaking-API-for-Entities.patch index 5419ce7d3c5a..ccee097cf06c 100644 --- a/patches/api/0381-Add-Sneaking-API-for-Entities.patch +++ b/patches/api/0381-Add-Sneaking-API-for-Entities.patch @@ -35,10 +35,10 @@ index 9f4498a955279b8b5c418609801fd09444a1efb5..6dcaf7e9bc9afb708ab569e82f27c878 * Get the category of spawn to which this entity belongs. * diff --git a/src/main/java/org/bukkit/entity/Player.java b/src/main/java/org/bukkit/entity/Player.java -index edf4b93624e5c308be9bf0498187404c13525e09..9dbcbefe989c6f4ab00a2ba90fa5cdb29dc74797 100644 +index 17d69b6808e581dd69f93d7cb646ca43608b40df..9faabe1dcfd69d8dfbefab98f23d8f0b6700e9b2 100644 --- a/src/main/java/org/bukkit/entity/Player.java +++ b/src/main/java/org/bukkit/entity/Player.java -@@ -459,6 +459,7 @@ public interface Player extends HumanEntity, Conversable, OfflinePlayer, PluginM +@@ -475,6 +475,7 @@ public interface Player extends HumanEntity, Conversable, OfflinePlayer, PluginM * * @return true if player is in sneak mode */ @@ -46,7 +46,7 @@ index edf4b93624e5c308be9bf0498187404c13525e09..9dbcbefe989c6f4ab00a2ba90fa5cdb2 public boolean isSneaking(); /** -@@ -466,6 +467,7 @@ public interface Player extends HumanEntity, Conversable, OfflinePlayer, PluginM +@@ -482,6 +483,7 @@ public interface Player extends HumanEntity, Conversable, OfflinePlayer, PluginM * * @param sneak true if player should appear sneaking */ diff --git a/patches/api/0383-Flying-Fall-Damage-API.patch b/patches/api/0383-Flying-Fall-Damage-API.patch index 13493c2aa922..f0e6b1e6caff 100644 --- a/patches/api/0383-Flying-Fall-Damage-API.patch +++ b/patches/api/0383-Flying-Fall-Damage-API.patch @@ -5,10 +5,10 @@ Subject: [PATCH] Flying Fall Damage API diff --git a/src/main/java/org/bukkit/entity/Player.java b/src/main/java/org/bukkit/entity/Player.java -index 9dbcbefe989c6f4ab00a2ba90fa5cdb29dc74797..32484ae22a7398bd4df94c185e7c1b4cb1d0f76c 100644 +index 9faabe1dcfd69d8dfbefab98f23d8f0b6700e9b2..1f9e9beb9c36d8885e0c4f19e324f26521634ed3 100644 --- a/src/main/java/org/bukkit/entity/Player.java +++ b/src/main/java/org/bukkit/entity/Player.java -@@ -1908,6 +1908,23 @@ public interface Player extends HumanEntity, Conversable, OfflinePlayer, PluginM +@@ -1948,6 +1948,23 @@ public interface Player extends HumanEntity, Conversable, OfflinePlayer, PluginM */ public void setAllowFlight(boolean flight); diff --git a/patches/api/0385-Win-Screen-API.patch b/patches/api/0385-Win-Screen-API.patch index cdeea2f4f9d4..8911de9fcb5b 100644 --- a/patches/api/0385-Win-Screen-API.patch +++ b/patches/api/0385-Win-Screen-API.patch @@ -5,10 +5,10 @@ Subject: [PATCH] Win Screen API diff --git a/src/main/java/org/bukkit/entity/Player.java b/src/main/java/org/bukkit/entity/Player.java -index 32484ae22a7398bd4df94c185e7c1b4cb1d0f76c..c86d8b88a1501bd8fd7562a21ee111607e523962 100644 +index 1f9e9beb9c36d8885e0c4f19e324f26521634ed3..dd4ce1d68962ec668db7c687c1e4764db52bc04f 100644 --- a/src/main/java/org/bukkit/entity/Player.java +++ b/src/main/java/org/bukkit/entity/Player.java -@@ -1247,6 +1247,47 @@ public interface Player extends HumanEntity, Conversable, OfflinePlayer, PluginM +@@ -1287,6 +1287,47 @@ public interface Player extends HumanEntity, Conversable, OfflinePlayer, PluginM */ public void sendMap(@NotNull MapView map); diff --git a/patches/api/0398-Fix-BanList-API.patch b/patches/api/0398-Fix-BanList-API.patch index a9640f44a249..0d0a781306dd 100644 --- a/patches/api/0398-Fix-BanList-API.patch +++ b/patches/api/0398-Fix-BanList-API.patch @@ -130,10 +130,10 @@ index e805e629cede1c4c0674282c930cb67852718c3e..5248cf08ef83c7304dd76c42a2f646bb + // Paper end } diff --git a/src/main/java/org/bukkit/entity/Player.java b/src/main/java/org/bukkit/entity/Player.java -index c86d8b88a1501bd8fd7562a21ee111607e523962..fbaea481feccfc71d744d9f93de3bf637fdcaaad 100644 +index dd4ce1d68962ec668db7c687c1e4764db52bc04f..baf7ff3b23be8dd80d2e59299a6ec8f9ce1053df 100644 --- a/src/main/java/org/bukkit/entity/Player.java +++ b/src/main/java/org/bukkit/entity/Player.java -@@ -343,7 +343,7 @@ public interface Player extends HumanEntity, Conversable, OfflinePlayer, PluginM +@@ -359,7 +359,7 @@ public interface Player extends HumanEntity, Conversable, OfflinePlayer, PluginM * (updated) previous ban */ @Nullable @@ -142,7 +142,7 @@ index c86d8b88a1501bd8fd7562a21ee111607e523962..fbaea481feccfc71d744d9f93de3bf63 /** * Adds this user to the {@link ProfileBanList}. If a previous ban exists, this will -@@ -359,7 +359,7 @@ public interface Player extends HumanEntity, Conversable, OfflinePlayer, PluginM +@@ -375,7 +375,7 @@ public interface Player extends HumanEntity, Conversable, OfflinePlayer, PluginM * (updated) previous ban */ @Nullable @@ -151,7 +151,7 @@ index c86d8b88a1501bd8fd7562a21ee111607e523962..fbaea481feccfc71d744d9f93de3bf63 /** * Adds this user to the {@link ProfileBanList}. If a previous ban exists, this will -@@ -375,7 +375,7 @@ public interface Player extends HumanEntity, Conversable, OfflinePlayer, PluginM +@@ -391,7 +391,7 @@ public interface Player extends HumanEntity, Conversable, OfflinePlayer, PluginM * (updated) previous ban */ @Nullable diff --git a/patches/api/0402-Fix-custom-statistic-criteria-creation.patch b/patches/api/0402-Fix-custom-statistic-criteria-creation.patch index 5e3f26f4f3fe..ff4bad9d3519 100644 --- a/patches/api/0402-Fix-custom-statistic-criteria-creation.patch +++ b/patches/api/0402-Fix-custom-statistic-criteria-creation.patch @@ -5,10 +5,10 @@ Subject: [PATCH] Fix custom statistic criteria creation diff --git a/src/main/java/org/bukkit/UnsafeValues.java b/src/main/java/org/bukkit/UnsafeValues.java -index 4f3e6e2698b28922e7b6448eddd5b166f4631759..334e392800803816cf502c2920c4a85774d6b0b2 100644 +index 14cf57a96f47ba666f05cedbc0005ff0fec6a33d..57b51acd566f6ccabeea0b3f4c76b19547d35b5a 100644 --- a/src/main/java/org/bukkit/UnsafeValues.java +++ b/src/main/java/org/bukkit/UnsafeValues.java -@@ -258,4 +258,6 @@ public interface UnsafeValues { +@@ -247,4 +247,6 @@ public interface UnsafeValues { */ void setBiomeKey(RegionAccessor accessor, int x, int y, int z, NamespacedKey biomeKey); // Paper end - namespaced key biome methods diff --git a/patches/api/0406-Add-Listing-API-for-Player.patch b/patches/api/0406-Add-Listing-API-for-Player.patch index 47a8946b80ba..3135969fe0dd 100644 --- a/patches/api/0406-Add-Listing-API-for-Player.patch +++ b/patches/api/0406-Add-Listing-API-for-Player.patch @@ -5,10 +5,10 @@ Subject: [PATCH] Add Listing API for Player diff --git a/src/main/java/org/bukkit/entity/Player.java b/src/main/java/org/bukkit/entity/Player.java -index fbaea481feccfc71d744d9f93de3bf637fdcaaad..24dc710f61d08253f66e7ecfd69873e7ebf68d1b 100644 +index baf7ff3b23be8dd80d2e59299a6ec8f9ce1053df..e128750b1175ab8bbe9b23fdd931665262c8d75e 100644 --- a/src/main/java/org/bukkit/entity/Player.java +++ b/src/main/java/org/bukkit/entity/Player.java -@@ -2038,6 +2038,32 @@ public interface Player extends HumanEntity, Conversable, OfflinePlayer, PluginM +@@ -2078,6 +2078,32 @@ public interface Player extends HumanEntity, Conversable, OfflinePlayer, PluginM */ public boolean canSee(@NotNull Entity entity); diff --git a/patches/api/0408-Fix-NPE-on-Boat-getStatus.patch b/patches/api/0408-Fix-NPE-on-Boat-getStatus.patch index 1cf6096a92f8..985aab9f2b60 100644 --- a/patches/api/0408-Fix-NPE-on-Boat-getStatus.patch +++ b/patches/api/0408-Fix-NPE-on-Boat-getStatus.patch @@ -5,10 +5,10 @@ Subject: [PATCH] Fix NPE on Boat getStatus diff --git a/src/main/java/org/bukkit/entity/Boat.java b/src/main/java/org/bukkit/entity/Boat.java -index f7548098bcdd033d9c530fdc584fc5538c635ca1..2ac685fb1817f3ce06ebe6391cc863712d68367c 100644 +index a0fb3c44405f6362f8a1613661d507e448f7ba6b..7076870c1abfa0edef33e00c39514aa413920f59 100644 --- a/src/main/java/org/bukkit/entity/Boat.java +++ b/src/main/java/org/bukkit/entity/Boat.java -@@ -169,6 +169,7 @@ public interface Boat extends Vehicle { +@@ -175,6 +175,7 @@ public interface Boat extends Vehicle { */ public enum Status { diff --git a/patches/api/0418-Allow-proper-checking-of-empty-item-stacks.patch b/patches/api/0418-Allow-proper-checking-of-empty-item-stacks.patch index ae89ab9bc017..0fe58e036986 100644 --- a/patches/api/0418-Allow-proper-checking-of-empty-item-stacks.patch +++ b/patches/api/0418-Allow-proper-checking-of-empty-item-stacks.patch @@ -6,7 +6,7 @@ Subject: [PATCH] Allow proper checking of empty item stacks This adds a method to check if an item stack is empty or not. This mirrors vanilla's implementation of the same method. diff --git a/src/main/java/org/bukkit/inventory/ItemStack.java b/src/main/java/org/bukkit/inventory/ItemStack.java -index 5c02a5fe37ea09502ca6c93d637a8ef5e4392ad4..8ded653f2b0549ebbe1a84d50ff0f3c85ddd07b7 100644 +index e39c9167bd66c528c09b256f15cc6c58666f0ca0..773780811a24aa1c1591257a993e30f2d99da436 100644 --- a/src/main/java/org/bukkit/inventory/ItemStack.java +++ b/src/main/java/org/bukkit/inventory/ItemStack.java @@ -1104,5 +1104,24 @@ public class ItemStack implements Cloneable, ConfigurationSerializable, Translat diff --git a/patches/api/0420-Add-player-idle-duration-API.patch b/patches/api/0420-Add-player-idle-duration-API.patch index 2fbfc5dc581c..ac250c0760d1 100644 --- a/patches/api/0420-Add-player-idle-duration-API.patch +++ b/patches/api/0420-Add-player-idle-duration-API.patch @@ -6,10 +6,10 @@ Subject: [PATCH] Add player idle duration API Implements API for getting and resetting a player's idle duration. diff --git a/src/main/java/org/bukkit/entity/Player.java b/src/main/java/org/bukkit/entity/Player.java -index 24dc710f61d08253f66e7ecfd69873e7ebf68d1b..09094f55509eaf66670c27409b4d5ec3d73412b0 100644 +index e128750b1175ab8bbe9b23fdd931665262c8d75e..4297a893799fe39b80029b97f6b5581d543afa8a 100644 --- a/src/main/java/org/bukkit/entity/Player.java +++ b/src/main/java/org/bukkit/entity/Player.java -@@ -3742,6 +3742,29 @@ public interface Player extends HumanEntity, Conversable, OfflinePlayer, PluginM +@@ -3782,6 +3782,29 @@ public interface Player extends HumanEntity, Conversable, OfflinePlayer, PluginM void increaseWardenWarningLevel(); // Paper end diff --git a/patches/api/0422-Add-predicate-for-blocks-when-raytracing.patch b/patches/api/0422-Add-predicate-for-blocks-when-raytracing.patch index 13d64df888b1..e24c9fcedf96 100644 --- a/patches/api/0422-Add-predicate-for-blocks-when-raytracing.patch +++ b/patches/api/0422-Add-predicate-for-blocks-when-raytracing.patch @@ -5,7 +5,7 @@ Subject: [PATCH] Add predicate for blocks when raytracing diff --git a/src/main/java/org/bukkit/World.java b/src/main/java/org/bukkit/World.java -index 907906e15c9250fea385e49f10d3c248236fd004..02184b68cc126b278985fd966e3c8e4ade18c464 100644 +index f037f46a9c6ce894f24af14c20fb514a58a8aee9..86fd5f3d322b6203f02ca7c427ccd56336b93fc0 100644 --- a/src/main/java/org/bukkit/World.java +++ b/src/main/java/org/bukkit/World.java @@ -1649,6 +1649,27 @@ public interface World extends RegionAccessor, WorldInfo, PluginMessageRecipient diff --git a/patches/api/0426-Remove-unnecessary-durability-check-in-ItemStack-isS.patch b/patches/api/0426-Remove-unnecessary-durability-check-in-ItemStack-isS.patch index bdb434071da5..88ecd35099b5 100644 --- a/patches/api/0426-Remove-unnecessary-durability-check-in-ItemStack-isS.patch +++ b/patches/api/0426-Remove-unnecessary-durability-check-in-ItemStack-isS.patch @@ -9,7 +9,7 @@ By removing this check we avoid unnecessarily allocating useless `ItemMeta` obje This is a leftover from when checking for the item's durability was "free" because the durability was stored in the `ItemStack` itself, this [was changed in Minecraft 1.13](https://hub.spigotmc.org/stash/projects/SPIGOT/repos/bukkit/commits/f8b2086d60942eb2cd7ac25a2a1408cb790c222c#src/main/java/org/bukkit/inventory/ItemStack.java). diff --git a/src/main/java/org/bukkit/inventory/ItemStack.java b/src/main/java/org/bukkit/inventory/ItemStack.java -index 5b918d510b9c8a6f8c6d146e90e1d0ef4a204b5a..13d035ace9fbe93c3754595ac6cadbfbe30062a5 100644 +index 773780811a24aa1c1591257a993e30f2d99da436..e6c69a54e0c1dc511fe5769f869dcecb13e04ed3 100644 --- a/src/main/java/org/bukkit/inventory/ItemStack.java +++ b/src/main/java/org/bukkit/inventory/ItemStack.java @@ -307,7 +307,7 @@ public class ItemStack implements Cloneable, ConfigurationSerializable, Translat diff --git a/patches/api/0427-Add-Structure-check-API.patch b/patches/api/0427-Add-Structure-check-API.patch index e2d19afb3258..43073fe8f413 100644 --- a/patches/api/0427-Add-Structure-check-API.patch +++ b/patches/api/0427-Add-Structure-check-API.patch @@ -5,7 +5,7 @@ Subject: [PATCH] Add Structure check API diff --git a/src/main/java/org/bukkit/World.java b/src/main/java/org/bukkit/World.java -index 02184b68cc126b278985fd966e3c8e4ade18c464..ecc2d486cfec79cce27a947dfeed4853575a594d 100644 +index 86fd5f3d322b6203f02ca7c427ccd56336b93fc0..16570c3c7ed5e7ad25f20c1034f7b966d6e694da 100644 --- a/src/main/java/org/bukkit/World.java +++ b/src/main/java/org/bukkit/World.java @@ -78,6 +78,30 @@ public interface World extends RegionAccessor, WorldInfo, PluginMessageRecipient diff --git a/patches/api/0428-Experimental-annotations-change.patch b/patches/api/0428-Experimental-annotations-change.patch index 010af408d2d1..5660d684cebf 100644 --- a/patches/api/0428-Experimental-annotations-change.patch +++ b/patches/api/0428-Experimental-annotations-change.patch @@ -5,57 +5,44 @@ Subject: [PATCH] Experimental annotations change diff --git a/src/main/java/org/bukkit/FeatureFlag.java b/src/main/java/org/bukkit/FeatureFlag.java -index 7522c611b5214dd09867c434d5f7cf161f5c04ca..026b1832bcd163ab89668c991bf002e608e36aef 100644 +index a96600443a0997c3a696a637422ab66ee1884fb0..20eb27ee041f77f295eb271f878c524ce5592251 100644 --- a/src/main/java/org/bukkit/FeatureFlag.java +++ b/src/main/java/org/bukkit/FeatureFlag.java -@@ -13,6 +13,7 @@ public interface FeatureFlag extends Keyed { - - public static final FeatureFlag VANILLA = Bukkit.getUnsafe().getFeatureFlag(NamespacedKey.minecraft("vanilla")); +@@ -29,6 +29,7 @@ public interface FeatureFlag extends Keyed { + @Deprecated + public static final FeatureFlag UPDATE_1_20 = Bukkit.getUnsafe().getFeatureFlag(NamespacedKey.minecraft("update_1_20")); + @ApiStatus.Experimental // Paper - add missing annotation - public static final FeatureFlag BUNDLE = Bukkit.getUnsafe().getFeatureFlag(NamespacedKey.minecraft("bundle")); + public static final FeatureFlag TRADE_REBALANCE = Bukkit.getUnsafe().getFeatureFlag(NamespacedKey.minecraft("trade_rebalance")); /** -@@ -23,6 +24,7 @@ public interface FeatureFlag extends Keyed { +@@ -39,10 +40,13 @@ public interface FeatureFlag extends Keyed { @Deprecated - public static final FeatureFlag UPDATE_1_20 = Bukkit.getUnsafe().getFeatureFlag(NamespacedKey.minecraft("update_1_20")); + public static final FeatureFlag UPDATE_121 = Bukkit.getUnsafe().getFeatureFlag(NamespacedKey.minecraft("update_1_21")); + @ApiStatus.Experimental // Paper - add missing annotation - public static final FeatureFlag TRADE_REBALANCE = Bukkit.getUnsafe().getFeatureFlag(NamespacedKey.minecraft("trade_rebalance")); + public static final FeatureFlag WINTER_DROP = Bukkit.getUnsafe().getFeatureFlag(NamespacedKey.minecraft("winter_drop")); - /** -diff --git a/src/main/java/org/bukkit/Material.java b/src/main/java/org/bukkit/Material.java -index 23fff8ee71b99aab8e650f3916511b7f34b5eb4e..77a15a99e441bd81650806142581bd5b24f30e10 100644 ---- a/src/main/java/org/bukkit/Material.java -+++ b/src/main/java/org/bukkit/Material.java -@@ -2501,6 +2501,8 @@ public enum Material implements Keyed, Translatable, net.kyori.adventure.transla - EGG(21603, 16), - COMPASS(24139), - RECOVERY_COMPASS(12710), -+ @MinecraftExperimental(org.bukkit.MinecraftExperimental.Requires.BUNDLE) // Paper - add missing annotation + @ApiStatus.Experimental // Paper - add missing annotation - BUNDLE(16835, 1), - FISHING_ROD(4167, 1, 64), - CLOCK(14980), -diff --git a/src/main/java/org/bukkit/Sound.java b/src/main/java/org/bukkit/Sound.java -index b2ff1da3386223a544ab5fc363a90c66c8869242..8c7b50906fc5b84c5570408f357410810bbfbded 100644 ---- a/src/main/java/org/bukkit/Sound.java -+++ b/src/main/java/org/bukkit/Sound.java -@@ -1506,8 +1506,14 @@ public enum Sound implements Keyed, net.kyori.adventure.sound.Sound.Type { // Pa - ITEM_BUCKET_FILL_LAVA("item.bucket.fill_lava"), - ITEM_BUCKET_FILL_POWDER_SNOW("item.bucket.fill_powder_snow"), - ITEM_BUCKET_FILL_TADPOLE("item.bucket.fill_tadpole"), -+ @MinecraftExperimental(org.bukkit.MinecraftExperimental.Requires.BUNDLE) // Paper - add missing annotation -+ @org.jetbrains.annotations.ApiStatus.Experimental // Paper - add missing annotation - ITEM_BUNDLE_DROP_CONTENTS("item.bundle.drop_contents"), -+ @MinecraftExperimental(org.bukkit.MinecraftExperimental.Requires.BUNDLE) // Paper - add missing annotation -+ @org.jetbrains.annotations.ApiStatus.Experimental // Paper - add missing annotation - ITEM_BUNDLE_INSERT("item.bundle.insert"), -+ @MinecraftExperimental(org.bukkit.MinecraftExperimental.Requires.BUNDLE) // Paper - add missing annotation + public static final FeatureFlag REDSTONE_EXPERIMENTS = Bukkit.getUnsafe().getFeatureFlag(NamespacedKey.minecraft("redstone_experiments")); + ++ @ApiStatus.Experimental // Paper - add missing annotation + public static final FeatureFlag MINECART_IMPROVEMENTS = Bukkit.getUnsafe().getFeatureFlag(NamespacedKey.minecraft("minecart_improvements")); + + } +diff --git a/src/main/java/org/bukkit/GameRule.java b/src/main/java/org/bukkit/GameRule.java +index 8b6584fae0a9d5cccbe350d889fa8b4a14c78ca3..89f1820ae94c48f51a44df750904bb285013720c 100644 +--- a/src/main/java/org/bukkit/GameRule.java ++++ b/src/main/java/org/bukkit/GameRule.java +@@ -287,6 +287,8 @@ public final class GameRule implements net.kyori.adventure.translation.Transl + * The maximum speed of minecarts (when the new movement algorithm is + * enabled). + */ ++ @MinecraftExperimental(MinecraftExperimental.Requires.MINECART_IMPROVEMENTS) // Paper - add missing annotation + @org.jetbrains.annotations.ApiStatus.Experimental // Paper - add missing annotation - ITEM_BUNDLE_REMOVE_ONE("item.bundle.remove_one"), - ITEM_CHORUS_FRUIT_TELEPORT("item.chorus_fruit.teleport"), - ITEM_CROP_PLANT("item.crop.plant"), + public static final GameRule MINECART_MAX_SPEED = new GameRule<>("minecartMaxSpeed", Integer.class); + + /** diff --git a/src/main/java/org/bukkit/block/Crafter.java b/src/main/java/org/bukkit/block/Crafter.java index 8d2dd78fc588a6817dfede8040b9909a7d5bde67..f737a2aae3f57a1bfe4cf68ea66f603da4eebd47 100644 --- a/src/main/java/org/bukkit/block/Crafter.java @@ -183,18 +170,6 @@ index bb1fb5e0518c6a62ef8b206733ee51d831f1f85b..49d0a37bbeb0b8fa9207164c74245ef0 */ -@ApiStatus.Experimental public interface CrafterInventory extends Inventory { } -diff --git a/src/main/java/org/bukkit/inventory/meta/BundleMeta.java b/src/main/java/org/bukkit/inventory/meta/BundleMeta.java -index e404cd1e2ba44e4c2d09524bc7cf730d8ffbdabd..cea0ebf50876dd32ab7fba6025b30f297d0a69c4 100644 ---- a/src/main/java/org/bukkit/inventory/meta/BundleMeta.java -+++ b/src/main/java/org/bukkit/inventory/meta/BundleMeta.java -@@ -6,6 +6,7 @@ import org.jetbrains.annotations.ApiStatus; - import org.jetbrains.annotations.NotNull; - import org.jetbrains.annotations.Nullable; - -+@org.bukkit.MinecraftExperimental(org.bukkit.MinecraftExperimental.Requires.BUNDLE) // Paper - add missing annotation - @ApiStatus.Experimental - public interface BundleMeta extends ItemMeta { - diff --git a/src/main/java/org/bukkit/map/MapCursor.java b/src/main/java/org/bukkit/map/MapCursor.java index 0899981b9ab1f98dacc617156d12779421e4c275..6c33fbf720a2e11655e254aeb516e08831c2adf4 100644 --- a/src/main/java/org/bukkit/map/MapCursor.java diff --git a/patches/api/0430-Improve-Registry.patch b/patches/api/0430-Improve-Registry.patch index 453d8c9488b4..607568bd87a0 100644 --- a/patches/api/0430-Improve-Registry.patch +++ b/patches/api/0430-Improve-Registry.patch @@ -146,10 +146,10 @@ index 6112db5d1153d045f2271038bada6b46d1a6a051..67cf3fcad21a8977d6fad172cc776b62 } } diff --git a/src/main/java/org/bukkit/Sound.java b/src/main/java/org/bukkit/Sound.java -index 8c7b50906fc5b84c5570408f357410810bbfbded..7a35120c82b88774de777d3c3176ef553a8e9244 100644 +index cf17af024b1953b6f21f18885411ea6a0baa1d4c..f32a035317f3f8e200bb8076e6a326cb1e87cfe1 100644 --- a/src/main/java/org/bukkit/Sound.java +++ b/src/main/java/org/bukkit/Sound.java -@@ -1636,6 +1636,13 @@ public enum Sound implements Keyed, net.kyori.adventure.sound.Sound.Type { // Pa +@@ -1655,6 +1655,13 @@ public enum Sound implements Keyed, net.kyori.adventure.sound.Sound.Type { // Pa this.key = NamespacedKey.minecraft(key); } diff --git a/patches/api/0431-Add-experience-points-API.patch b/patches/api/0431-Add-experience-points-API.patch index 9b489a9ded02..558188330155 100644 --- a/patches/api/0431-Add-experience-points-API.patch +++ b/patches/api/0431-Add-experience-points-API.patch @@ -5,10 +5,10 @@ Subject: [PATCH] Add experience points API diff --git a/src/main/java/org/bukkit/entity/Player.java b/src/main/java/org/bukkit/entity/Player.java -index 09094f55509eaf66670c27409b4d5ec3d73412b0..4bc4a1c0c6f6759f984843823f1bbec6ffed92bc 100644 +index 4297a893799fe39b80029b97f6b5581d543afa8a..b3b6cdf5491397e0e802ac91f5805d560ed5d88a 100644 --- a/src/main/java/org/bukkit/entity/Player.java +++ b/src/main/java/org/bukkit/entity/Player.java -@@ -1907,6 +1907,45 @@ public interface Player extends HumanEntity, Conversable, OfflinePlayer, PluginM +@@ -1947,6 +1947,45 @@ public interface Player extends HumanEntity, Conversable, OfflinePlayer, PluginM * @param exp New total experience points */ public void setTotalExperience(int exp); diff --git a/patches/api/0440-Add-api-for-spawn-egg-texture-colors.patch b/patches/api/0440-Add-api-for-spawn-egg-texture-colors.patch index 996340cf25a9..14a80bca1c12 100644 --- a/patches/api/0440-Add-api-for-spawn-egg-texture-colors.patch +++ b/patches/api/0440-Add-api-for-spawn-egg-texture-colors.patch @@ -5,10 +5,10 @@ Subject: [PATCH] Add api for spawn egg texture colors diff --git a/src/main/java/org/bukkit/UnsafeValues.java b/src/main/java/org/bukkit/UnsafeValues.java -index 334e392800803816cf502c2920c4a85774d6b0b2..27ac8e80192924eb38e5ceaee575ac418e92b410 100644 +index 57b51acd566f6ccabeea0b3f4c76b19547d35b5a..38e84d98670b45b1f855885cf07ce13f0433fa49 100644 --- a/src/main/java/org/bukkit/UnsafeValues.java +++ b/src/main/java/org/bukkit/UnsafeValues.java -@@ -260,4 +260,17 @@ public interface UnsafeValues { +@@ -249,4 +249,17 @@ public interface UnsafeValues { // Paper end - namespaced key biome methods String getStatisticCriteriaKey(@NotNull org.bukkit.Statistic statistic); // Paper - fix custom stats criteria creation diff --git a/patches/api/0441-Add-Lifecycle-Event-system.patch b/patches/api/0441-Add-Lifecycle-Event-system.patch index 31bfe9a70f5b..2d2d639018c9 100644 --- a/patches/api/0441-Add-Lifecycle-Event-system.patch +++ b/patches/api/0441-Add-Lifecycle-Event-system.patch @@ -546,10 +546,10 @@ index 0000000000000000000000000000000000000000..f70814de0d6c40b2c1c9921b8abdd116 + } +} diff --git a/src/main/java/org/bukkit/UnsafeValues.java b/src/main/java/org/bukkit/UnsafeValues.java -index 27ac8e80192924eb38e5ceaee575ac418e92b410..141d5a964cc299284aecd4d34d57008a32f94247 100644 +index 38e84d98670b45b1f855885cf07ce13f0433fa49..81b1c024e27a7021982336b94fc1e1ba33308f6c 100644 --- a/src/main/java/org/bukkit/UnsafeValues.java +++ b/src/main/java/org/bukkit/UnsafeValues.java -@@ -273,4 +273,12 @@ public interface UnsafeValues { +@@ -262,4 +262,12 @@ public interface UnsafeValues { */ @Nullable org.bukkit.Color getSpawnEggLayerColor(org.bukkit.entity.EntityType entityType, int layer); // Paper end - spawn egg color visibility diff --git a/patches/api/0442-ItemStack-Tooltip-API.patch b/patches/api/0442-ItemStack-Tooltip-API.patch index 4af91c3ade27..1d0644e6e126 100644 --- a/patches/api/0442-ItemStack-Tooltip-API.patch +++ b/patches/api/0442-ItemStack-Tooltip-API.patch @@ -110,10 +110,10 @@ index 0000000000000000000000000000000000000000..a649b90dfac6000c01579a48234a1138 + } +} diff --git a/src/main/java/org/bukkit/UnsafeValues.java b/src/main/java/org/bukkit/UnsafeValues.java -index 141d5a964cc299284aecd4d34d57008a32f94247..31217b38e769f97801fa1afefeb223d1c755cabd 100644 +index 81b1c024e27a7021982336b94fc1e1ba33308f6c..e5144471056e69586c1693a9264a3995387de3cc 100644 --- a/src/main/java/org/bukkit/UnsafeValues.java +++ b/src/main/java/org/bukkit/UnsafeValues.java -@@ -281,4 +281,6 @@ public interface UnsafeValues { +@@ -270,4 +270,6 @@ public interface UnsafeValues { @org.jetbrains.annotations.ApiStatus.Internal io.papermc.paper.plugin.lifecycle.event.LifecycleEventManager createPluginLifecycleEventManager(final org.bukkit.plugin.java.JavaPlugin plugin, final java.util.function.BooleanSupplier registrationCheck); // Paper end - lifecycle event API diff --git a/patches/api/0452-Deprecate-ItemStack-setType.patch b/patches/api/0452-Deprecate-ItemStack-setType.patch index cdb918bc110d..84fbcf3899ba 100644 --- a/patches/api/0452-Deprecate-ItemStack-setType.patch +++ b/patches/api/0452-Deprecate-ItemStack-setType.patch @@ -5,7 +5,7 @@ Subject: [PATCH] Deprecate ItemStack#setType diff --git a/src/main/java/org/bukkit/inventory/ItemStack.java b/src/main/java/org/bukkit/inventory/ItemStack.java -index 718070359c644de65c8fc2b34ad39913525d18c6..5b261bcffa7d04c9e7db57fee37d4a1471cbbd64 100644 +index 49390979cc0c68b8e719f2a2ce9e7d193c747959..82a66820311cfd918ea322f57df97e3a56e79c1d 100644 --- a/src/main/java/org/bukkit/inventory/ItemStack.java +++ b/src/main/java/org/bukkit/inventory/ItemStack.java @@ -143,8 +143,18 @@ public class ItemStack implements Cloneable, ConfigurationSerializable, Translat diff --git a/patches/api/0454-API-for-checking-sent-chunks.patch b/patches/api/0454-API-for-checking-sent-chunks.patch index a3844b54010e..0c353a8070e7 100644 --- a/patches/api/0454-API-for-checking-sent-chunks.patch +++ b/patches/api/0454-API-for-checking-sent-chunks.patch @@ -5,10 +5,10 @@ Subject: [PATCH] API for checking sent chunks diff --git a/src/main/java/org/bukkit/entity/Player.java b/src/main/java/org/bukkit/entity/Player.java -index 4bc4a1c0c6f6759f984843823f1bbec6ffed92bc..7b699c1e63cf5bc805754101f28066f836877ee2 100644 +index b3b6cdf5491397e0e802ac91f5805d560ed5d88a..20db14b3075d70b34eab16ca6332a2e674b34e75 100644 --- a/src/main/java/org/bukkit/entity/Player.java +++ b/src/main/java/org/bukkit/entity/Player.java -@@ -3804,6 +3804,47 @@ public interface Player extends HumanEntity, Conversable, OfflinePlayer, PluginM +@@ -3844,6 +3844,47 @@ public interface Player extends HumanEntity, Conversable, OfflinePlayer, PluginM void resetIdleDuration(); // Paper end diff --git a/patches/api/0457-Fix-SpawnerEntry-Equipment-API.patch b/patches/api/0457-Fix-SpawnerEntry-Equipment-API.patch index 0a16b34cd5e0..e170eabd170e 100644 --- a/patches/api/0457-Fix-SpawnerEntry-Equipment-API.patch +++ b/patches/api/0457-Fix-SpawnerEntry-Equipment-API.patch @@ -5,10 +5,10 @@ Subject: [PATCH] Fix SpawnerEntry$Equipment API diff --git a/src/main/java/org/bukkit/block/spawner/SpawnerEntry.java b/src/main/java/org/bukkit/block/spawner/SpawnerEntry.java -index 02b3471774ff1fd4ad15c2f04064fd485ef8f3e5..0fc5f04b8bb475e8afce61c6187a390cd36c3d9f 100644 +index fc1c0435dfea121923eb1fe0182880752f321143..bc8ccd139df6072f9744cfb85ad0070369600aa1 100644 --- a/src/main/java/org/bukkit/block/spawner/SpawnerEntry.java +++ b/src/main/java/org/bukkit/block/spawner/SpawnerEntry.java -@@ -121,28 +121,29 @@ public class SpawnerEntry { +@@ -120,27 +120,29 @@ public class SpawnerEntry { private final Map dropChances; public Equipment(@NotNull LootTable equipmentLootTable, @NotNull Map dropChances) { @@ -21,9 +21,8 @@ index 02b3471774ff1fd4ad15c2f04064fd485ef8f3e5..0fc5f04b8bb475e8afce61c6187a390c - * Set the loot table for the entity. + * Set the loot table for the spawned entity's equipment slots. *
    -- * To remove a loot table use null. Do not use {@link LootTables#EMPTY} -- * to clear a LootTable. -+ * To remove a loot table use {@link LootTables#EMPTY}. +- * To remove a loot table use null. ++ * To remove a loot table use the empty loot table. * * @param table this {@link org.bukkit.entity.Mob} will have. */ diff --git a/patches/api/0458-Fix-ItemFlags.patch b/patches/api/0458-Fix-ItemFlags.patch index a6301eff75c6..3ca141cfa509 100644 --- a/patches/api/0458-Fix-ItemFlags.patch +++ b/patches/api/0458-Fix-ItemFlags.patch @@ -47,7 +47,7 @@ index 5b8dac777bb1640dc00bbe98feb6460c36eebb98..1af15fd327e0613cd1a179bd7fef1e83 /** * Setting to show/hide item-specific information, including, but not limited to: diff --git a/src/main/java/org/bukkit/inventory/ItemStack.java b/src/main/java/org/bukkit/inventory/ItemStack.java -index 5b261bcffa7d04c9e7db57fee37d4a1471cbbd64..1d2ffdf88daa9186993c69c5ab2b96520b41920b 100644 +index 82a66820311cfd918ea322f57df97e3a56e79c1d..77edc2e1c5c865db7e101aaa186657ac85edfed9 100644 --- a/src/main/java/org/bukkit/inventory/ItemStack.java +++ b/src/main/java/org/bukkit/inventory/ItemStack.java @@ -588,6 +588,13 @@ public class ItemStack implements Cloneable, ConfigurationSerializable, Translat diff --git a/patches/api/0463-Added-API-to-get-player-ha-proxy-address.patch b/patches/api/0463-Added-API-to-get-player-ha-proxy-address.patch index b1702bee61c4..79b18047969a 100644 --- a/patches/api/0463-Added-API-to-get-player-ha-proxy-address.patch +++ b/patches/api/0463-Added-API-to-get-player-ha-proxy-address.patch @@ -5,10 +5,10 @@ Subject: [PATCH] Added API to get player ha proxy address diff --git a/src/main/java/org/bukkit/entity/Player.java b/src/main/java/org/bukkit/entity/Player.java -index 7b699c1e63cf5bc805754101f28066f836877ee2..7c56182acaf827f4b1a986a61cea8e9960604c98 100644 +index 20db14b3075d70b34eab16ca6332a2e674b34e75..82e92579f899ab3b86c748ba01860262b8ffa17f 100644 --- a/src/main/java/org/bukkit/entity/Player.java +++ b/src/main/java/org/bukkit/entity/Player.java -@@ -251,6 +251,16 @@ public interface Player extends HumanEntity, Conversable, OfflinePlayer, PluginM +@@ -267,6 +267,16 @@ public interface Player extends HumanEntity, Conversable, OfflinePlayer, PluginM @Nullable public InetSocketAddress getAddress(); diff --git a/patches/api/0465-Brigadier-based-command-API.patch b/patches/api/0465-Brigadier-based-command-API.patch index 88ce5b7aab3d..2d29373bc2b2 100644 --- a/patches/api/0465-Brigadier-based-command-API.patch +++ b/patches/api/0465-Brigadier-based-command-API.patch @@ -6,7 +6,7 @@ Subject: [PATCH] Brigadier based command API Co-authored-by: Jake Potrebic diff --git a/build.gradle.kts b/build.gradle.kts -index be8492b23d10332d046150a4ff18c67cc1b0f5d2..6c1cba3e87d2e3cb3eec65345ed7dcc56fd96363 100644 +index 6c8464d9e862b1b4dbf7a77e25446aa870803dae..254fd96d3950b4494c7e43547b00b5175ee53c93 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -27,6 +27,7 @@ configurations.api { @@ -17,18 +17,22 @@ index be8492b23d10332d046150a4ff18c67cc1b0f5d2..6c1cba3e87d2e3cb3eec65345ed7dcc5 // api dependencies are listed transitively to API consumers api("com.google.guava:guava:32.1.2-jre") api("com.google.code.gson:gson:2.10.1") -@@ -93,9 +94,29 @@ sourceSets { +@@ -93,9 +94,33 @@ sourceSets { } } // Paper end +// Paper start - brigadier API +val outgoingVariants = arrayOf("runtimeElements", "apiElements", "sourcesElements", "javadocElements") ++val mainCapability = "${project.group}:${project.name}:${project.version}" +configurations { + val outgoing = outgoingVariants.map { named(it) } + for (config in outgoing) { + config { ++ attributes { ++ attribute(io.papermc.paperweight.util.mainCapabilityAttribute, mainCapability) ++ } + outgoing { -+ capability("${project.group}:${project.name}:${project.version}") ++ capability(mainCapability) + capability("io.papermc.paper:paper-mojangapi:${project.version}") + capability("com.destroystokyo.paper:paper-mojangapi:${project.version}") + } diff --git a/patches/api/0469-General-ItemMeta-fixes.patch b/patches/api/0469-General-ItemMeta-fixes.patch index f74fb26a3576..953fd2e49c5d 100644 --- a/patches/api/0469-General-ItemMeta-fixes.patch +++ b/patches/api/0469-General-ItemMeta-fixes.patch @@ -112,10 +112,10 @@ index ff6818b6d9e0207eafdd749928f33aeac3f27191..992f39da07bafe9769effaa7dc6adc01 * Checks to see if this item has a maximum amount of damage. * diff --git a/src/main/java/org/bukkit/inventory/meta/ItemMeta.java b/src/main/java/org/bukkit/inventory/meta/ItemMeta.java -index 1a4260b00b193b94ce4b1b2954644f4e41baff4c..5d5fcb2720b62e47d47f441032c4de02574b051a 100644 +index afdcc2d67d55f2f07c913816e1f5b290d1415357..fc089b796f5a0f2e1ab081cc710e4bb5c3f5ee7b 100644 --- a/src/main/java/org/bukkit/inventory/meta/ItemMeta.java +++ b/src/main/java/org/bukkit/inventory/meta/ItemMeta.java -@@ -673,8 +673,9 @@ public interface ItemMeta extends Cloneable, ConfigurationSerializable, Persiste +@@ -877,8 +877,9 @@ public interface ItemMeta extends Cloneable, ConfigurationSerializable, Persiste /** * Set all {@link Attribute}s and their {@link AttributeModifier}s. diff --git a/patches/api/0472-Registry-Modification-API.patch b/patches/api/0472-Registry-Modification-API.patch index 6eca654483ab..b51badfa70a6 100644 --- a/patches/api/0472-Registry-Modification-API.patch +++ b/patches/api/0472-Registry-Modification-API.patch @@ -900,10 +900,10 @@ index 67cf3fcad21a8977d6fad172cc776b628ab68f25..b4ef3133fdd9d79a3381cf8f659ff561 } } diff --git a/src/main/java/org/bukkit/UnsafeValues.java b/src/main/java/org/bukkit/UnsafeValues.java -index 31217b38e769f97801fa1afefeb223d1c755cabd..9bdba60fa96edbc4be5dcf54a815579db887048b 100644 +index e5144471056e69586c1693a9264a3995387de3cc..2c365ecf3f5a5252e489bc1dc04359e766a2d739 100644 --- a/src/main/java/org/bukkit/UnsafeValues.java +++ b/src/main/java/org/bukkit/UnsafeValues.java -@@ -283,4 +283,6 @@ public interface UnsafeValues { +@@ -272,4 +272,6 @@ public interface UnsafeValues { // Paper end - lifecycle event API @NotNull java.util.List computeTooltipLines(@NotNull ItemStack itemStack, @NotNull io.papermc.paper.inventory.tooltip.TooltipContext tooltipContext, @Nullable org.bukkit.entity.Player player); // Paper - expose itemstack tooltip lines diff --git a/patches/api/0473-Introduce-registry-entry-and-builders.patch b/patches/api/0473-Introduce-registry-entry-and-builders.patch index 2731a371af9c..5d06fe497471 100644 --- a/patches/api/0473-Introduce-registry-entry-and-builders.patch +++ b/patches/api/0473-Introduce-registry-entry-and-builders.patch @@ -487,10 +487,10 @@ index cb5f7dfcdbbb548d93ad21c215ba35a9e142a7b2..e2c632afdf555418dd1dc6ad6c5d1976 + // Paper end } diff --git a/src/main/java/org/bukkit/inventory/ItemType.java b/src/main/java/org/bukkit/inventory/ItemType.java -index c42cfa76ff73a3ce8a164cb94a9c3f553b005ea5..15d68c4997f739c39675ef8ffa5ab7967dac59f2 100644 +index 7a1a0aebbfdaac6b6af41236d4a00512244b58fa..ef3a30d5cca29c7a7c546791be3c333e63e425f4 100644 --- a/src/main/java/org/bukkit/inventory/ItemType.java +++ b/src/main/java/org/bukkit/inventory/ItemType.java -@@ -46,7 +46,7 @@ import org.jetbrains.annotations.Nullable; +@@ -47,7 +47,7 @@ import org.jetbrains.annotations.Nullable; * official replacement for the aforementioned enum. Entirely incompatible * changes may occur. Do not use this API in plugins. */ diff --git a/patches/api/0474-Proxy-ItemStack-to-CraftItemStack.patch b/patches/api/0474-Proxy-ItemStack-to-CraftItemStack.patch index 246fa425d8f4..157b63eeb46c 100644 --- a/patches/api/0474-Proxy-ItemStack-to-CraftItemStack.patch +++ b/patches/api/0474-Proxy-ItemStack-to-CraftItemStack.patch @@ -5,10 +5,10 @@ Subject: [PATCH] Proxy ItemStack to CraftItemStack diff --git a/src/main/java/org/bukkit/UnsafeValues.java b/src/main/java/org/bukkit/UnsafeValues.java -index 9bdba60fa96edbc4be5dcf54a815579db887048b..330e3013eda204aa9b33d5e1c3104e0b595abdbc 100644 +index 2c365ecf3f5a5252e489bc1dc04359e766a2d739..06b7af5dbae3dd1c5cb024cc875162725a0b8c37 100644 --- a/src/main/java/org/bukkit/UnsafeValues.java +++ b/src/main/java/org/bukkit/UnsafeValues.java -@@ -285,4 +285,6 @@ public interface UnsafeValues { +@@ -274,4 +274,6 @@ public interface UnsafeValues { @NotNull java.util.List computeTooltipLines(@NotNull ItemStack itemStack, @NotNull io.papermc.paper.inventory.tooltip.TooltipContext tooltipContext, @Nullable org.bukkit.entity.Player player); // Paper - expose itemstack tooltip lines io.papermc.paper.registry.tag.@Nullable Tag getTag(io.papermc.paper.registry.tag.@NotNull TagKey tagKey); // Paper - hack to get tags for non-server backed registries diff --git a/patches/api/0479-Add-an-API-for-CanPlaceOn-and-CanDestroy-NBT-values.patch b/patches/api/0479-Add-an-API-for-CanPlaceOn-and-CanDestroy-NBT-values.patch index 3dd2df1411fa..9ee5f867c6fc 100644 --- a/patches/api/0479-Add-an-API-for-CanPlaceOn-and-CanDestroy-NBT-values.patch +++ b/patches/api/0479-Add-an-API-for-CanPlaceOn-and-CanDestroy-NBT-values.patch @@ -228,10 +228,10 @@ index 7ff6d60deb129e23b2a4d772aee123eb6c0b6433..52a2763773b234c581b2dcc6f0584f8d return key; } diff --git a/src/main/java/org/bukkit/inventory/meta/ItemMeta.java b/src/main/java/org/bukkit/inventory/meta/ItemMeta.java -index 5d5fcb2720b62e47d47f441032c4de02574b051a..f5541454ba5e508a72c83989c6feaef5406e2535 100644 +index fc089b796f5a0f2e1ab081cc710e4bb5c3f5ee7b..2a86e599175549a3021a63a837f8cc9d8da5697d 100644 --- a/src/main/java/org/bukkit/inventory/meta/ItemMeta.java +++ b/src/main/java/org/bukkit/inventory/meta/ItemMeta.java -@@ -806,4 +806,98 @@ public interface ItemMeta extends Cloneable, ConfigurationSerializable, Persiste +@@ -1010,4 +1010,98 @@ public interface ItemMeta extends Cloneable, ConfigurationSerializable, Persiste @SuppressWarnings("javadoc") @NotNull ItemMeta clone(); diff --git a/patches/api/0482-Leashable-API.patch b/patches/api/0482-Leashable-API.patch index e37174d49956..b84951d969d6 100644 --- a/patches/api/0482-Leashable-API.patch +++ b/patches/api/0482-Leashable-API.patch @@ -48,7 +48,7 @@ index 0000000000000000000000000000000000000000..7e687535d1a622ddf25e3ece387dbfd5 + boolean setLeashHolder(@Nullable Entity holder); +} diff --git a/src/main/java/org/bukkit/entity/Boat.java b/src/main/java/org/bukkit/entity/Boat.java -index 2ac685fb1817f3ce06ebe6391cc863712d68367c..d80524fe32672a8b8940d1028abf22026dace8d2 100644 +index 7076870c1abfa0edef33e00c39514aa413920f59..574574dc4f54a4ce32e7c97f2fbeb92a4991d353 100644 --- a/src/main/java/org/bukkit/entity/Boat.java +++ b/src/main/java/org/bukkit/entity/Boat.java @@ -7,7 +7,7 @@ import org.jetbrains.annotations.NotNull; diff --git a/patches/api/0485-Add-FeatureFlag-API.patch b/patches/api/0485-Add-FeatureFlag-API.patch index ae5e00b28300..26442d7b0125 100644 --- a/patches/api/0485-Add-FeatureFlag-API.patch +++ b/patches/api/0485-Add-FeatureFlag-API.patch @@ -104,10 +104,10 @@ index 0000000000000000000000000000000000000000..0955699df65ccbb8711cfa48f0b34d5a + @Unmodifiable Set getFeatureFlags(); +} diff --git a/src/main/java/org/bukkit/FeatureFlag.java b/src/main/java/org/bukkit/FeatureFlag.java -index 026b1832bcd163ab89668c991bf002e608e36aef..b5e87480b6a1d064547e4e608f3d402825931a00 100644 +index 20eb27ee041f77f295eb271f878c524ce5592251..0acea3e8d639c252b44cd5da5e3584aa6e6c9b5c 100644 --- a/src/main/java/org/bukkit/FeatureFlag.java +++ b/src/main/java/org/bukkit/FeatureFlag.java -@@ -1,37 +1,56 @@ +@@ -1,17 +1,24 @@ package org.bukkit; +// Paper start - overhaul FeatureFlag API @@ -136,12 +136,14 @@ index 026b1832bcd163ab89668c991bf002e608e36aef..b5e87480b6a1d064547e4e608f3d4028 + */ + FeatureFlag VANILLA = create("vanilla"); -+ /** -+ * The {@code bundle} feature flag. -+ */ - @ApiStatus.Experimental // Paper - add missing annotation + /** + * AVAILABLE BETWEEN VERSIONS: 1.19.3 - 1.21.1 +@@ -19,34 +26,43 @@ public interface FeatureFlag extends Keyed { + * @deprecated not available since 1.21.2 + */ + @Deprecated - public static final FeatureFlag BUNDLE = Bukkit.getUnsafe().getFeatureFlag(NamespacedKey.minecraft("bundle")); -+ FeatureFlag BUNDLE = create("bundle"); ++ FeatureFlag BUNDLE = deprecated("bundle"); /** - * AVAILABLE BETWEEN VERSIONS: 1.19 - 1.19.4 @@ -155,21 +157,35 @@ index 026b1832bcd163ab89668c991bf002e608e36aef..b5e87480b6a1d064547e4e608f3d4028 @ApiStatus.Experimental // Paper - add missing annotation - public static final FeatureFlag TRADE_REBALANCE = Bukkit.getUnsafe().getFeatureFlag(NamespacedKey.minecraft("trade_rebalance")); + FeatureFlag TRADE_REBALANCE = create("trade_rebalance"); -+ + +- /** +- * AVAILABLE BETWEEN VERSIONS: 1.20.5 - 1.20.6 +- * +- * @deprecated not available since 1.21 +- */ +- @Deprecated +- public static final FeatureFlag UPDATE_121 = Bukkit.getUnsafe().getFeatureFlag(NamespacedKey.minecraft("update_1_21")); + @Deprecated(since = "1.20") + FeatureFlag UPDATE_1_20 = deprecated("update_1_20"); + + @Deprecated(since = "1.21") + FeatureFlag UPDATE_121 = deprecated("update_1_21"); - /** -- * AVAILABLE BETWEEN VERSIONS: 1.20.5 - 1.20.6 -- * -- * @deprecated not available since 1.21 + @ApiStatus.Experimental // Paper - add missing annotation +- public static final FeatureFlag WINTER_DROP = Bukkit.getUnsafe().getFeatureFlag(NamespacedKey.minecraft("winter_drop")); ++ FeatureFlag WINTER_DROP = create("winter_drop"); + + @ApiStatus.Experimental // Paper - add missing annotation +- public static final FeatureFlag REDSTONE_EXPERIMENTS = Bukkit.getUnsafe().getFeatureFlag(NamespacedKey.minecraft("redstone_experiments")); ++ FeatureFlag REDSTONE_EXPERIMENTS = create("redstone_experiments"); + + @ApiStatus.Experimental // Paper - add missing annotation +- public static final FeatureFlag MINECART_IMPROVEMENTS = Bukkit.getUnsafe().getFeatureFlag(NamespacedKey.minecraft("minecart_improvements")); ++ FeatureFlag MINECART_IMPROVEMENTS = create("minecart_improvements"); ++ ++ /** + * An index of all feature flags. - */ -- @Deprecated -- public static final FeatureFlag UPDATE_121 = Bukkit.getUnsafe().getFeatureFlag(NamespacedKey.minecraft("update_1_21")); ++ */ + Index ALL_FLAGS = Index.create(FeatureFlag::key, List.copyOf(FeatureFlagImpl.ALL_FLAGS)); + + private static FeatureFlag create(@Subst("vanilla") final String name) { @@ -182,6 +198,7 @@ index 026b1832bcd163ab89668c991bf002e608e36aef..b5e87480b6a1d064547e4e608f3d4028 + return new FeatureFlagImpl.Deprecated(NamespacedKey.minecraft(name)); + } + // Paper end - overhaul FeatureFlag API + } diff --git a/src/main/java/org/bukkit/FeatureFlagImpl.java b/src/main/java/org/bukkit/FeatureFlagImpl.java new file mode 100644 @@ -230,10 +247,10 @@ index eb33e8e671972aa308ad75a7ce9aa9ac526f470f..05ecf3cb38ff42c8b52405d900197e6b /** * Gets the {@link Biome} at the given {@link Location}. diff --git a/src/main/java/org/bukkit/UnsafeValues.java b/src/main/java/org/bukkit/UnsafeValues.java -index 330e3013eda204aa9b33d5e1c3104e0b595abdbc..c80e0ef587a001ee6de3f5c182cc9696d58bafeb 100644 +index 06b7af5dbae3dd1c5cb024cc875162725a0b8c37..aa3916b0d8e40615a7ae142e254277744b4f024e 100644 --- a/src/main/java/org/bukkit/UnsafeValues.java +++ b/src/main/java/org/bukkit/UnsafeValues.java -@@ -114,8 +114,7 @@ public interface UnsafeValues { +@@ -109,8 +109,7 @@ public interface UnsafeValues { String getTranslationKey(Attribute attribute); @@ -244,10 +261,10 @@ index 330e3013eda204aa9b33d5e1c3104e0b595abdbc..c80e0ef587a001ee6de3f5c182cc9696 /** * Do not use, method will get removed, and the plugin won't run diff --git a/src/main/java/org/bukkit/block/BlockType.java b/src/main/java/org/bukkit/block/BlockType.java -index a58ef2238208fbb55341f4532eaa288577ed8c0e..fb2b373c1822e7248a30e610d11e2c2bd438c19a 100644 +index c080c2a3323d19cb3d549aa0fe6c164666d7da75..ed534fe4983873a2d5f623f0d9d5e3ce254615eb 100644 --- a/src/main/java/org/bukkit/block/BlockType.java +++ b/src/main/java/org/bukkit/block/BlockType.java -@@ -125,7 +125,7 @@ import org.jetbrains.annotations.Nullable; +@@ -129,7 +129,7 @@ import org.jetbrains.annotations.Nullable; * changes may occur. Do not use this API in plugins. */ @ApiStatus.Internal @@ -256,7 +273,7 @@ index a58ef2238208fbb55341f4532eaa288577ed8c0e..fb2b373c1822e7248a30e610d11e2c2b /** * Typed represents a subtype of {@link BlockType}s that have a known block -@@ -3490,7 +3490,9 @@ public interface BlockType extends Keyed, Translatable, net.kyori.adventure.tran +@@ -3629,7 +3629,9 @@ public interface BlockType extends Keyed, Translatable, net.kyori.adventure.tran * * @param world the world to check * @return true if this BlockType can be used in this World. @@ -267,10 +284,10 @@ index a58ef2238208fbb55341f4532eaa288577ed8c0e..fb2b373c1822e7248a30e610d11e2c2b /** diff --git a/src/main/java/org/bukkit/entity/EntityType.java b/src/main/java/org/bukkit/entity/EntityType.java -index ba605ad75d4ed920c0dc4527529998041a58676b..a78e1c431a6ea46ba7c44880e25a871f473bef41 100644 +index 6521a20d69a4c8e75be7e9b3fdebbc25b843ec1b..37dbd114f91a26bc09a1230d38afe7f6a99e5c28 100644 --- a/src/main/java/org/bukkit/entity/EntityType.java +++ b/src/main/java/org/bukkit/entity/EntityType.java -@@ -23,7 +23,7 @@ import org.jetbrains.annotations.Contract; +@@ -45,7 +45,7 @@ import org.jetbrains.annotations.Contract; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -293,10 +310,10 @@ index 5067f1371433cccd3287af7f03e152f2c3c1ece3..e0cb282541548ac3bd24cce86b3413f5 /** * Gets the unique name of this world diff --git a/src/main/java/org/bukkit/inventory/ItemType.java b/src/main/java/org/bukkit/inventory/ItemType.java -index 15d68c4997f739c39675ef8ffa5ab7967dac59f2..f96ced6ae8a969319728efb4fc4fe545923e32be 100644 +index ef3a30d5cca29c7a7c546791be3c333e63e425f4..72803c00e4af576f286d2af34bf300ee554a7f3c 100644 --- a/src/main/java/org/bukkit/inventory/ItemType.java +++ b/src/main/java/org/bukkit/inventory/ItemType.java -@@ -47,7 +47,7 @@ import org.jetbrains.annotations.Nullable; +@@ -48,7 +48,7 @@ import org.jetbrains.annotations.Nullable; * changes may occur. Do not use this API in plugins. */ @ApiStatus.Experimental // Paper - already required for registry builders @@ -305,7 +322,7 @@ index 15d68c4997f739c39675ef8ffa5ab7967dac59f2..f96ced6ae8a969319728efb4fc4fe545 /** * Typed represents a subtype of {@link ItemType}s that have a known item meta type -@@ -2300,7 +2300,9 @@ public interface ItemType extends Keyed, Translatable, net.kyori.adventure.trans +@@ -2451,7 +2451,9 @@ public interface ItemType extends Keyed, Translatable, net.kyori.adventure.trans * * @param world the world to check * @return true if this ItemType can be used in this World. diff --git a/patches/api/0487-Item-serialization-as-json.patch b/patches/api/0487-Item-serialization-as-json.patch index d2951870a15d..c2b00d8d3de7 100644 --- a/patches/api/0487-Item-serialization-as-json.patch +++ b/patches/api/0487-Item-serialization-as-json.patch @@ -5,10 +5,10 @@ Subject: [PATCH] Item serialization as json diff --git a/src/main/java/org/bukkit/UnsafeValues.java b/src/main/java/org/bukkit/UnsafeValues.java -index c80e0ef587a001ee6de3f5c182cc9696d58bafeb..10c87b7c19ed3eab28fdce5f225df3767292ee0a 100644 +index aa3916b0d8e40615a7ae142e254277744b4f024e..e4084369d12390bb5c92ab58ad34ff07afea1142 100644 --- a/src/main/java/org/bukkit/UnsafeValues.java +++ b/src/main/java/org/bukkit/UnsafeValues.java -@@ -174,6 +174,36 @@ public interface UnsafeValues { +@@ -163,6 +163,36 @@ public interface UnsafeValues { ItemStack deserializeItem(byte[] data); diff --git a/patches/api/0489-Add-enchantWithLevels-with-enchantment-registry-set.patch b/patches/api/0489-Add-enchantWithLevels-with-enchantment-registry-set.patch index 41f106e80f40..1978c0b0738f 100644 --- a/patches/api/0489-Add-enchantWithLevels-with-enchantment-registry-set.patch +++ b/patches/api/0489-Add-enchantWithLevels-with-enchantment-registry-set.patch @@ -5,7 +5,7 @@ Subject: [PATCH] Add enchantWithLevels with enchantment registry set diff --git a/src/main/java/org/bukkit/inventory/ItemFactory.java b/src/main/java/org/bukkit/inventory/ItemFactory.java -index bd0e55562f1cabef3078573182e0cf9fbc844585..2669d783088b9f63f0edd6d0384c3a307ddccac7 100644 +index e1986aea72bb1f1ba2ea76f3ba53f274b6aac899..0a814c1f65afc00034a454e3ff720d26e6c17ecc 100644 --- a/src/main/java/org/bukkit/inventory/ItemFactory.java +++ b/src/main/java/org/bukkit/inventory/ItemFactory.java @@ -338,4 +338,21 @@ public interface ItemFactory { diff --git a/patches/api/0490-Improve-entity-effect-API.patch b/patches/api/0490-Improve-entity-effect-API.patch index 912d051f49b5..6ec450bcea5e 100644 --- a/patches/api/0490-Improve-entity-effect-API.patch +++ b/patches/api/0490-Improve-entity-effect-API.patch @@ -5,7 +5,7 @@ Subject: [PATCH] Improve entity effect API diff --git a/src/main/java/org/bukkit/EntityEffect.java b/src/main/java/org/bukkit/EntityEffect.java -index 5341957b10cccd7bce5a7595699b1d90412a01d0..83d623cc824ddeeae7350c9f9a757fcf8e97c9ea 100644 +index 5341957b10cccd7bce5a7595699b1d90412a01d0..ef0acab253db878f1edee51b585fd1b20ef9161d 100644 --- a/src/main/java/org/bukkit/EntityEffect.java +++ b/src/main/java/org/bukkit/EntityEffect.java @@ -112,11 +112,25 @@ public enum EntityEffect { @@ -76,7 +76,7 @@ index 5341957b10cccd7bce5a7595699b1d90412a01d0..83d623cc824ddeeae7350c9f9a757fcf HURT_BERRY_BUSH(44, LivingEntity.class), /** * Fox chews the food in its mouth -@@ -331,7 +355,17 @@ public enum EntityEffect { +@@ -331,7 +355,25 @@ public enum EntityEffect { * Sniffer must have a target and be in {@link Sniffer.State#SEARCHING} or * {@link Sniffer.State#DIGGING} */ @@ -90,7 +90,15 @@ index 5341957b10cccd7bce5a7595699b1d90412a01d0..83d623cc824ddeeae7350c9f9a757fcf + /** + * {@link org.bukkit.inventory.EquipmentSlot#BODY} armor piece breaks + */ -+ BODY_BREAK(65, LivingEntity.class); ++ BODY_BREAK(65, LivingEntity.class), ++ /** ++ * A creaking transient shaking when being hit. ++ * Does not apply to plain creaking entities as they are not invulnerable like the transient ones spawned by the ++ * creaking heart. ++ */ ++ @MinecraftExperimental(MinecraftExperimental.Requires.WINTER_DROP) ++ @org.jetbrains.annotations.ApiStatus.Experimental ++ SHAKE(66, org.bukkit.entity.CreakingTransient.class); + // Paper end - add missing EntityEffect private final byte data; @@ -118,10 +126,10 @@ index 725ef320f929d5e3d141c1ed3246d73a7d741f31..d0ae8a94db20281d3664d74718c65234 + // Paper end - broadcast hurt animation } diff --git a/src/main/java/org/bukkit/entity/Player.java b/src/main/java/org/bukkit/entity/Player.java -index 7c56182acaf827f4b1a986a61cea8e9960604c98..8086acceacbceb2c5a7228fff005e41a86d37008 100644 +index 82e92579f899ab3b86c748ba01860262b8ffa17f..95f0b3186e313c7fbd5c8531d52b82a69e525f94 100644 --- a/src/main/java/org/bukkit/entity/Player.java +++ b/src/main/java/org/bukkit/entity/Player.java -@@ -3859,4 +3859,16 @@ public interface Player extends HumanEntity, Conversable, OfflinePlayer, PluginM +@@ -3899,4 +3899,16 @@ public interface Player extends HumanEntity, Conversable, OfflinePlayer, PluginM @Override Spigot spigot(); // Spigot end diff --git a/patches/api/0496-fix-DamageTypeTags-init.patch b/patches/api/0496-fix-DamageTypeTags-init.patch new file mode 100644 index 000000000000..2d775b1322ad --- /dev/null +++ b/patches/api/0496-fix-DamageTypeTags-init.patch @@ -0,0 +1,21 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Lulu13022002 <41980282+Lulu13022002@users.noreply.github.com> +Date: Sat, 26 Oct 2024 19:29:41 +0200 +Subject: [PATCH] fix DamageTypeTags init + + +diff --git a/src/main/java/org/bukkit/tag/DamageTypeTags.java b/src/main/java/org/bukkit/tag/DamageTypeTags.java +index 12d14ab2947517eedc563acf647857f13021f80c..c02d92b607dfc998e81b7722464cefd867c80072 100644 +--- a/src/main/java/org/bukkit/tag/DamageTypeTags.java ++++ b/src/main/java/org/bukkit/tag/DamageTypeTags.java +@@ -158,9 +158,8 @@ public final class DamageTypeTags { + @ApiStatus.Internal + public static final String REGISTRY_DAMAGE_TYPES = "damage_types"; + +- @NotNull + private static Tag getTag(String key) { +- return Objects.requireNonNull(Bukkit.getTag(REGISTRY_DAMAGE_TYPES, NamespacedKey.minecraft(key), DamageType.class)); ++ return Bukkit.getTag(REGISTRY_DAMAGE_TYPES, NamespacedKey.minecraft(key), DamageType.class); // Paper - bypasses_cooldown is not defined in vanilla + } + + private DamageTypeTags() { diff --git a/patches/removed/1.21/0393-Fix-SPIGOT-5989.patch b/patches/removed/1.21/0393-Fix-SPIGOT-5989.patch deleted file mode 100644 index 5b41e0742c55..000000000000 --- a/patches/removed/1.21/0393-Fix-SPIGOT-5989.patch +++ /dev/null @@ -1,63 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: JRoy -Date: Wed, 15 Jul 2020 21:42:52 -0400 -Subject: [PATCH] Fix SPIGOT-5989 - -Before this fix, if a player was respawning to a respawn anchor and -the respawn location was modified away from the anchor with the -PlayerRespawnEvent, the anchor would still lose some charge. -This fixes that by checking if the modified spawn location is -still at a respawn anchor. - -diff --git a/src/main/java/net/minecraft/server/players/PlayerList.java b/src/main/java/net/minecraft/server/players/PlayerList.java -index 935061ed5c22b48e43d27fe0840cc69fb60d5344..97a3f29414cd9f11e6a790cb30f7ef25a836b06c 100644 ---- a/src/main/java/net/minecraft/server/players/PlayerList.java -+++ b/src/main/java/net/minecraft/server/players/PlayerList.java -@@ -804,6 +804,7 @@ public abstract class PlayerList { - // Paper start - Add PlayerPostRespawnEvent - boolean isBedSpawn = false; - boolean isRespawn = false; -+ boolean isLocAltered = false; // Paper - Fix SPIGOT-5989 - // Paper end - Add PlayerPostRespawnEvent - - // CraftBukkit start - fire PlayerRespawnEvent -@@ -814,7 +815,7 @@ public abstract class PlayerList { - Optional optional; - - if (blockposition != null) { -- optional = net.minecraft.world.entity.player.Player.findRespawnPositionAndUseSpawnBlock(worldserver1, blockposition, f, flag1, flag); -+ optional = net.minecraft.world.entity.player.Player.findRespawnPositionAndUseSpawnBlock(worldserver1, blockposition, f, flag1, true); // Paper - Fix SPIGOT-5989 - } else { - optional = Optional.empty(); - } -@@ -858,7 +859,12 @@ public abstract class PlayerList { - } - // Spigot End - -- location = respawnEvent.getRespawnLocation(); -+ // Paper start - Fix SPIGOT-5989 -+ if (!location.equals(respawnEvent.getRespawnLocation()) ) { -+ location = respawnEvent.getRespawnLocation(); -+ isLocAltered = true; -+ } -+ // Paper end - Fix SPIGOT-5989 - if (!flag) entityplayer.reset(); // SPIGOT-4785 - isRespawn = true; // Paper - Add PlayerPostRespawnEvent - } else { -@@ -896,8 +902,14 @@ public abstract class PlayerList { - } - // entityplayer1.initInventoryMenu(); - entityplayer1.setHealth(entityplayer1.getHealth()); -- if (flag2) { -- entityplayer1.connection.send(new ClientboundSoundPacket(SoundEvents.RESPAWN_ANCHOR_DEPLETE, SoundSource.BLOCKS, (double) blockposition.getX(), (double) blockposition.getY(), (double) blockposition.getZ(), 1.0F, 1.0F, worldserver1.getRandom().nextLong())); -+ // Paper start - Fix SPIGOT-5989 -+ if (flag2 && !isLocAltered) { -+ if (!flag1) { -+ BlockState data = worldserver1.getBlockState(blockposition); -+ worldserver1.setBlock(blockposition, data.setValue(net.minecraft.world.level.block.RespawnAnchorBlock.CHARGE, data.getValue(net.minecraft.world.level.block.RespawnAnchorBlock.CHARGE) - 1), 3); -+ } -+ entityplayer1.connection.send(new ClientboundSoundPacket(SoundEvents.RESPAWN_ANCHOR_DEPLETE, SoundSource.BLOCKS, (double) location.getX(), (double) location.getY(), (double) location.getZ(), 1.0F, 1.0F, worldserver1.getRandom().nextLong())); -+ // Paper end - Fix SPIGOT-5989 - } - // Added from changeDimension - this.sendAllPlayerInfo(entityplayer); // Update health, etc... diff --git a/patches/removed/1.21/0562-Fix-dangerous-end-portal-logic.patch b/patches/removed/1.21/0562-Fix-dangerous-end-portal-logic.patch deleted file mode 100644 index 88c7e811e1bb..000000000000 --- a/patches/removed/1.21/0562-Fix-dangerous-end-portal-logic.patch +++ /dev/null @@ -1,88 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Spottedleaf -Date: Fri, 4 Jun 2021 17:06:52 -0400 -Subject: [PATCH] Fix dangerous end portal logic - -End portals could teleport entities during move calls. Stupid -logic given the caller will never expect that kind of thing, -and will result in all kinds of dupes. - -Move the tick logic into the post tick, where portaling was -designed to happen in the first place. - -diff --git a/src/main/java/net/minecraft/world/entity/Entity.java b/src/main/java/net/minecraft/world/entity/Entity.java -index 21150f44e4f71aba3801d5392e45243112d012c0..b4670fc653721283f95bb61ac57c306b224b7fb7 100644 ---- a/src/main/java/net/minecraft/world/entity/Entity.java -+++ b/src/main/java/net/minecraft/world/entity/Entity.java -@@ -422,6 +422,36 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess - return this.originWorld; - } - // Paper end - Entity origin API -+ // Paper start - make end portalling safe -+ public BlockPos portalBlock; -+ public ServerLevel portalWorld; -+ public void tickEndPortal() { -+ BlockPos pos = this.portalBlock; -+ ServerLevel world = this.portalWorld; -+ this.portalBlock = null; -+ this.portalWorld = null; -+ -+ if (pos == null || world == null || world != this.level) { -+ return; -+ } -+ -+ if (this.isPassenger() || this.isVehicle() || !this.canChangeDimensions() || this.isRemoved() || !this.valid || !this.isAlive()) { -+ return; -+ } -+ -+ ResourceKey resourcekey = world.getTypeKey() == LevelStem.END ? Level.OVERWORLD : Level.END; // CraftBukkit - SPIGOT-6152: send back to main overworld in custom ends -+ ServerLevel worldserver = world.getServer().getLevel(resourcekey); -+ -+ org.bukkit.event.entity.EntityPortalEnterEvent event = new org.bukkit.event.entity.EntityPortalEnterEvent(this.getBukkitEntity(), new org.bukkit.Location(world.getWorld(), pos.getX(), pos.getY(), pos.getZ())); -+ event.callEvent(); -+ -+ if (this instanceof ServerPlayer) { -+ ((ServerPlayer) this).changeDimension(worldserver, PlayerTeleportEvent.TeleportCause.END_PORTAL); -+ return; -+ } -+ this.teleportTo(worldserver, null); -+ } -+ // Paper end - make end portalling safe - public float getBukkitYaw() { - return this.yRot; - } -@@ -2833,6 +2863,7 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess - } - - this.processPortalCooldown(); -+ if (!io.papermc.paper.configuration.GlobalConfiguration.get().unsupportedSettings.allowUnsafeEndPortalTeleportation) this.tickEndPortal(); // Paper - make end portalling safe - } - } - -diff --git a/src/main/java/net/minecraft/world/level/block/EndPortalBlock.java b/src/main/java/net/minecraft/world/level/block/EndPortalBlock.java -index a2de13a366e4a462b746dab035372838127f4994..7272d70c672b54dcf595beafd7a2ed33c96e35cb 100644 ---- a/src/main/java/net/minecraft/world/level/block/EndPortalBlock.java -+++ b/src/main/java/net/minecraft/world/level/block/EndPortalBlock.java -@@ -61,16 +61,13 @@ public class EndPortalBlock extends BaseEntityBlock { - // return; // CraftBukkit - always fire event in case plugins wish to change it - } - -- // CraftBukkit start - Entity in portal -- EntityPortalEnterEvent event = new EntityPortalEnterEvent(entity.getBukkitEntity(), new org.bukkit.Location(world.getWorld(), pos.getX(), pos.getY(), pos.getZ())); -- world.getCraftServer().getPluginManager().callEvent(event); -- -- if (entity instanceof ServerPlayer) { -- ((ServerPlayer) entity).changeDimension(worldserver, PlayerTeleportEvent.TeleportCause.END_PORTAL); -- return; -+ // Paper start - move all of this logic into portal tick -+ entity.portalWorld = ((ServerLevel)world); -+ entity.portalBlock = pos.immutable(); -+ if (io.papermc.paper.configuration.GlobalConfiguration.get().unsupportedSettings.allowUnsafeEndPortalTeleportation) { -+ entity.tickEndPortal(); - } -- // CraftBukkit end -- entity.changeDimension(worldserver); -+ // Paper end - move all of this logic into portal tick - } - - } diff --git a/patches/removed/1.21/0993-Starlight.patch b/patches/removed/1.21/0993-Starlight.patch deleted file mode 100644 index bc604e348c66..000000000000 --- a/patches/removed/1.21/0993-Starlight.patch +++ /dev/null @@ -1,5429 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Spottedleaf -Date: Wed, 28 Oct 2020 16:51:55 -0700 -Subject: [PATCH] Starlight - -See https://github.com/PaperMC/Starlight - -== AT == -public net.minecraft.server.level.ChunkHolder broadcast(Lnet/minecraft/network/protocol/Packet;Z)V - -diff --git a/src/main/java/ca/spottedleaf/starlight/common/light/BlockStarLightEngine.java b/src/main/java/ca/spottedleaf/starlight/common/light/BlockStarLightEngine.java -new file mode 100644 -index 0000000000000000000000000000000000000000..3732a940d9603cf502983afbc4663113d1400be8 ---- /dev/null -+++ b/src/main/java/ca/spottedleaf/starlight/common/light/BlockStarLightEngine.java -@@ -0,0 +1,275 @@ -+package ca.spottedleaf.starlight.common.light; -+ -+import net.minecraft.core.BlockPos; -+import net.minecraft.world.level.Level; -+import net.minecraft.world.level.block.state.BlockState; -+import net.minecraft.world.level.chunk.ChunkAccess; -+import net.minecraft.world.level.chunk.status.ChunkStatus; -+import net.minecraft.world.level.chunk.LevelChunkSection; -+import net.minecraft.world.level.chunk.LightChunkGetter; -+import net.minecraft.world.level.chunk.PalettedContainer; -+import net.minecraft.world.phys.shapes.Shapes; -+import net.minecraft.world.phys.shapes.VoxelShape; -+import java.util.ArrayList; -+import java.util.List; -+import java.util.Set; -+ -+public final class BlockStarLightEngine extends StarLightEngine { -+ -+ public BlockStarLightEngine(final Level world) { -+ super(false, world); -+ } -+ -+ @Override -+ protected boolean[] getEmptinessMap(final ChunkAccess chunk) { -+ return chunk.getBlockEmptinessMap(); -+ } -+ -+ @Override -+ protected void setEmptinessMap(final ChunkAccess chunk, final boolean[] to) { -+ chunk.setBlockEmptinessMap(to); -+ } -+ -+ @Override -+ protected SWMRNibbleArray[] getNibblesOnChunk(final ChunkAccess chunk) { -+ return chunk.getBlockNibbles(); -+ } -+ -+ @Override -+ protected void setNibbles(final ChunkAccess chunk, final SWMRNibbleArray[] to) { -+ chunk.setBlockNibbles(to); -+ } -+ -+ @Override -+ protected boolean canUseChunk(final ChunkAccess chunk) { -+ return chunk.getStatus().isOrAfter(ChunkStatus.LIGHT) && (this.isClientSide || chunk.isLightCorrect()); -+ } -+ -+ @Override -+ protected void setNibbleNull(final int chunkX, final int chunkY, final int chunkZ) { -+ final SWMRNibbleArray nibble = this.getNibbleFromCache(chunkX, chunkY, chunkZ); -+ if (nibble != null) { -+ // de-initialisation is not as straightforward as with sky data, since deinit of block light is typically -+ // because a block was removed - which can decrease light. with sky data, block breaking can only result -+ // in increases, and thus the existing sky block check will actually correctly propagate light through -+ // a null section. so in order to propagate decreases correctly, we can do a couple of things: not remove -+ // the data section, or do edge checks on ALL axis (x, y, z). however I do not want edge checks running -+ // for clients at all, as they are expensive. so we don't remove the section, but to maintain the appearence -+ // of vanilla data management we "hide" them. -+ nibble.setHidden(); -+ } -+ } -+ -+ @Override -+ protected void initNibble(final int chunkX, final int chunkY, final int chunkZ, final boolean extrude, final boolean initRemovedNibbles) { -+ if (chunkY < this.minLightSection || chunkY > this.maxLightSection || this.getChunkInCache(chunkX, chunkZ) == null) { -+ return; -+ } -+ -+ final SWMRNibbleArray nibble = this.getNibbleFromCache(chunkX, chunkY, chunkZ); -+ if (nibble == null) { -+ if (!initRemovedNibbles) { -+ throw new IllegalStateException(); -+ } else { -+ this.setNibbleInCache(chunkX, chunkY, chunkZ, new SWMRNibbleArray()); -+ } -+ } else { -+ nibble.setNonNull(); -+ } -+ } -+ -+ @Override -+ protected final void checkBlock(final LightChunkGetter lightAccess, final int worldX, final int worldY, final int worldZ) { -+ // blocks can change opacity -+ // blocks can change emitted light -+ // blocks can change direction of propagation -+ -+ final int encodeOffset = this.coordinateOffset; -+ final int emittedMask = this.emittedLightMask; -+ -+ final int currentLevel = this.getLightLevel(worldX, worldY, worldZ); -+ final BlockState blockState = this.getBlockState(worldX, worldY, worldZ); -+ final int emittedLevel = blockState.getLightEmission() & emittedMask; -+ -+ this.setLightLevel(worldX, worldY, worldZ, emittedLevel); -+ // this accounts for change in emitted light that would cause an increase -+ if (emittedLevel != 0) { -+ this.appendToIncreaseQueue( -+ ((worldX + (worldZ << 6) + (worldY << (6 + 6)) + encodeOffset) & ((1L << (6 + 6 + 16)) - 1)) -+ | (emittedLevel & 0xFL) << (6 + 6 + 16) -+ | (((long)ALL_DIRECTIONS_BITSET) << (6 + 6 + 16 + 4)) -+ | (blockState.isConditionallyFullOpaque() ? FLAG_HAS_SIDED_TRANSPARENT_BLOCKS : 0) -+ ); -+ } -+ // this also accounts for a change in emitted light that would cause a decrease -+ // this also accounts for the change of direction of propagation (i.e old block was full transparent, new block is full opaque or vice versa) -+ // as it checks all neighbours (even if current level is 0) -+ this.appendToDecreaseQueue( -+ ((worldX + (worldZ << 6) + (worldY << (6 + 6)) + encodeOffset) & ((1L << (6 + 6 + 16)) - 1)) -+ | (currentLevel & 0xFL) << (6 + 6 + 16) -+ | (((long)ALL_DIRECTIONS_BITSET) << (6 + 6 + 16 + 4)) -+ // always keep sided transparent false here, new block might be conditionally transparent which would -+ // prevent us from decreasing sources in the directions where the new block is opaque -+ // if it turns out we were wrong to de-propagate the source, the re-propagate logic WILL always -+ // catch that and fix it. -+ ); -+ // re-propagating neighbours (done by the decrease queue) will also account for opacity changes in this block -+ } -+ -+ protected final BlockPos.MutableBlockPos recalcCenterPos = new BlockPos.MutableBlockPos(); -+ protected final BlockPos.MutableBlockPos recalcNeighbourPos = new BlockPos.MutableBlockPos(); -+ -+ @Override -+ protected int calculateLightValue(final LightChunkGetter lightAccess, final int worldX, final int worldY, final int worldZ, -+ final int expect) { -+ final BlockState centerState = this.getBlockState(worldX, worldY, worldZ); -+ int level = centerState.getLightEmission() & 0xF; -+ -+ if (level >= (15 - 1) || level > expect) { -+ return level; -+ } -+ -+ final int sectionOffset = this.chunkSectionIndexOffset; -+ final BlockState conditionallyOpaqueState; -+ int opacity = centerState.getOpacityIfCached(); -+ -+ if (opacity == -1) { -+ this.recalcCenterPos.set(worldX, worldY, worldZ); -+ opacity = centerState.getLightBlock(lightAccess.getLevel(), this.recalcCenterPos); -+ if (centerState.isConditionallyFullOpaque()) { -+ conditionallyOpaqueState = centerState; -+ } else { -+ conditionallyOpaqueState = null; -+ } -+ } else if (opacity >= 15) { -+ return level; -+ } else { -+ conditionallyOpaqueState = null; -+ } -+ opacity = Math.max(1, opacity); -+ -+ for (final AxisDirection direction : AXIS_DIRECTIONS) { -+ final int offX = worldX + direction.x; -+ final int offY = worldY + direction.y; -+ final int offZ = worldZ + direction.z; -+ -+ final int sectionIndex = (offX >> 4) + 5 * (offZ >> 4) + (5 * 5) * (offY >> 4) + sectionOffset; -+ -+ final int neighbourLevel = this.getLightLevel(sectionIndex, (offX & 15) | ((offZ & 15) << 4) | ((offY & 15) << 8)); -+ -+ if ((neighbourLevel - 1) <= level) { -+ // don't need to test transparency, we know it wont affect the result. -+ continue; -+ } -+ -+ final BlockState neighbourState = this.getBlockState(offX, offY, offZ); -+ if (neighbourState.isConditionallyFullOpaque()) { -+ // here the block can be conditionally opaque (i.e light cannot propagate from it), so we need to test that -+ // we don't read the blockstate because most of the time this is false, so using the faster -+ // known transparency lookup results in a net win -+ this.recalcNeighbourPos.set(offX, offY, offZ); -+ final VoxelShape neighbourFace = neighbourState.getFaceOcclusionShape(lightAccess.getLevel(), this.recalcNeighbourPos, direction.opposite.nms); -+ final VoxelShape thisFace = conditionallyOpaqueState == null ? Shapes.empty() : conditionallyOpaqueState.getFaceOcclusionShape(lightAccess.getLevel(), this.recalcCenterPos, direction.nms); -+ if (Shapes.faceShapeOccludes(thisFace, neighbourFace)) { -+ // not allowed to propagate -+ continue; -+ } -+ } -+ -+ // passed transparency, -+ -+ final int calculated = neighbourLevel - opacity; -+ level = Math.max(calculated, level); -+ if (level > expect) { -+ return level; -+ } -+ } -+ -+ return level; -+ } -+ -+ @Override -+ protected void propagateBlockChanges(final LightChunkGetter lightAccess, final ChunkAccess atChunk, final Set positions) { -+ for (final BlockPos pos : positions) { -+ this.checkBlock(lightAccess, pos.getX(), pos.getY(), pos.getZ()); -+ } -+ -+ this.performLightDecrease(lightAccess); -+ } -+ -+ protected List getSources(final LightChunkGetter lightAccess, final ChunkAccess chunk) { -+ final List sources = new ArrayList<>(); -+ -+ final int offX = chunk.getPos().x << 4; -+ final int offZ = chunk.getPos().z << 4; -+ -+ final LevelChunkSection[] sections = chunk.getSections(); -+ for (int sectionY = this.minSection; sectionY <= this.maxSection; ++sectionY) { -+ final LevelChunkSection section = sections[sectionY - this.minSection]; -+ if (section == null || section.hasOnlyAir()) { -+ // no sources in empty sections -+ continue; -+ } -+ if (!section.maybeHas((final BlockState state) -> { -+ return state.getLightEmission() > 0; -+ })) { -+ // no light sources in palette -+ continue; -+ } -+ final PalettedContainer states = section.states; -+ final int offY = sectionY << 4; -+ -+ for (int index = 0; index < (16 * 16 * 16); ++index) { -+ final BlockState state = states.get(index); -+ if (state.getLightEmission() <= 0) { -+ continue; -+ } -+ -+ // index = x | (z << 4) | (y << 8) -+ sources.add(new BlockPos(offX | (index & 15), offY | (index >>> 8), offZ | ((index >>> 4) & 15))); -+ } -+ } -+ -+ return sources; -+ } -+ -+ @Override -+ public void lightChunk(final LightChunkGetter lightAccess, final ChunkAccess chunk, final boolean needsEdgeChecks) { -+ // setup sources -+ final int emittedMask = this.emittedLightMask; -+ final List positions = this.getSources(lightAccess, chunk); -+ for (int i = 0, len = positions.size(); i < len; ++i) { -+ final BlockPos pos = positions.get(i); -+ final BlockState blockState = this.getBlockState(pos.getX(), pos.getY(), pos.getZ()); -+ final int emittedLight = blockState.getLightEmission() & emittedMask; -+ -+ if (emittedLight <= this.getLightLevel(pos.getX(), pos.getY(), pos.getZ())) { -+ // some other source is brighter -+ continue; -+ } -+ -+ this.appendToIncreaseQueue( -+ ((pos.getX() + (pos.getZ() << 6) + (pos.getY() << (6 + 6)) + this.coordinateOffset) & ((1L << (6 + 6 + 16)) - 1)) -+ | (emittedLight & 0xFL) << (6 + 6 + 16) -+ | (((long)ALL_DIRECTIONS_BITSET) << (6 + 6 + 16 + 4)) -+ | (blockState.isConditionallyFullOpaque() ? FLAG_HAS_SIDED_TRANSPARENT_BLOCKS : 0) -+ ); -+ -+ -+ // propagation wont set this for us -+ this.setLightLevel(pos.getX(), pos.getY(), pos.getZ(), emittedLight); -+ } -+ -+ if (needsEdgeChecks) { -+ // not required to propagate here, but this will reduce the hit of the edge checks -+ this.performLightIncrease(lightAccess); -+ -+ // verify neighbour edges -+ this.checkChunkEdges(lightAccess, chunk, this.minLightSection, this.maxLightSection); -+ } else { -+ this.propagateNeighbourLevels(lightAccess, chunk, this.minLightSection, this.maxLightSection); -+ -+ this.performLightIncrease(lightAccess); -+ } -+ } -+} -diff --git a/src/main/java/ca/spottedleaf/starlight/common/light/SWMRNibbleArray.java b/src/main/java/ca/spottedleaf/starlight/common/light/SWMRNibbleArray.java -new file mode 100644 -index 0000000000000000000000000000000000000000..4ffb4ffe01c4628d52742c5c0bbd35220eea6294 ---- /dev/null -+++ b/src/main/java/ca/spottedleaf/starlight/common/light/SWMRNibbleArray.java -@@ -0,0 +1,440 @@ -+package ca.spottedleaf.starlight.common.light; -+ -+import net.minecraft.world.level.chunk.DataLayer; -+import java.util.ArrayDeque; -+import java.util.Arrays; -+ -+// SWMR -> Single Writer Multi Reader Nibble Array -+public final class SWMRNibbleArray { -+ -+ /* -+ * Null nibble - nibble does not exist, and should not be written to. Just like vanilla - null -+ * nibbles are always 0 - and they are never written to directly. Only initialised/uninitialised -+ * nibbles can be written to. -+ * -+ * Uninitialised nibble - They are all 0, but the backing array isn't initialised. -+ * -+ * Initialised nibble - Has light data. -+ */ -+ -+ protected static final int INIT_STATE_NULL = 0; // null -+ protected static final int INIT_STATE_UNINIT = 1; // uninitialised -+ protected static final int INIT_STATE_INIT = 2; // initialised -+ protected static final int INIT_STATE_HIDDEN = 3; // initialised, but conversion to Vanilla data should be treated as if NULL -+ -+ public static final int ARRAY_SIZE = 16 * 16 * 16 / (8/4); // blocks / bytes per block -+ // this allows us to maintain only 1 byte array when we're not updating -+ static final ThreadLocal> WORKING_BYTES_POOL = ThreadLocal.withInitial(ArrayDeque::new); -+ -+ private static byte[] allocateBytes() { -+ final byte[] inPool = WORKING_BYTES_POOL.get().pollFirst(); -+ if (inPool != null) { -+ return inPool; -+ } -+ -+ return new byte[ARRAY_SIZE]; -+ } -+ -+ private static void freeBytes(final byte[] bytes) { -+ WORKING_BYTES_POOL.get().addFirst(bytes); -+ } -+ -+ public static SWMRNibbleArray fromVanilla(final DataLayer nibble) { -+ if (nibble == null) { -+ return new SWMRNibbleArray(null, true); -+ } else if (nibble.isEmpty()) { -+ return new SWMRNibbleArray(); -+ } else { -+ return new SWMRNibbleArray(nibble.getData().clone()); // make sure we don't write to the parameter later -+ } -+ } -+ -+ protected int stateUpdating; -+ protected volatile int stateVisible; -+ -+ protected byte[] storageUpdating; -+ protected boolean updatingDirty; // only returns whether storageUpdating is dirty -+ protected volatile byte[] storageVisible; -+ -+ public SWMRNibbleArray() { -+ this(null, false); // lazy init -+ } -+ -+ public SWMRNibbleArray(final byte[] bytes) { -+ this(bytes, false); -+ } -+ -+ public SWMRNibbleArray(final byte[] bytes, final boolean isNullNibble) { -+ if (bytes != null && bytes.length != ARRAY_SIZE) { -+ throw new IllegalArgumentException("Data of wrong length: " + bytes.length); -+ } -+ this.stateVisible = this.stateUpdating = bytes == null ? (isNullNibble ? INIT_STATE_NULL : INIT_STATE_UNINIT) : INIT_STATE_INIT; -+ this.storageUpdating = this.storageVisible = bytes; -+ } -+ -+ public SWMRNibbleArray(final byte[] bytes, final int state) { -+ if (bytes != null && bytes.length != ARRAY_SIZE) { -+ throw new IllegalArgumentException("Data of wrong length: " + bytes.length); -+ } -+ if (bytes == null && (state == INIT_STATE_INIT || state == INIT_STATE_HIDDEN)) { -+ throw new IllegalArgumentException("Data cannot be null and have state be initialised"); -+ } -+ this.stateUpdating = this.stateVisible = state; -+ this.storageUpdating = this.storageVisible = bytes; -+ } -+ -+ @Override -+ public String toString() { -+ StringBuilder stringBuilder = new StringBuilder(); -+ stringBuilder.append("State: "); -+ switch (this.stateVisible) { -+ case INIT_STATE_NULL: -+ stringBuilder.append("null"); -+ break; -+ case INIT_STATE_UNINIT: -+ stringBuilder.append("uninitialised"); -+ break; -+ case INIT_STATE_INIT: -+ stringBuilder.append("initialised"); -+ break; -+ case INIT_STATE_HIDDEN: -+ stringBuilder.append("hidden"); -+ break; -+ default: -+ stringBuilder.append("unknown"); -+ break; -+ } -+ stringBuilder.append("\nData:\n"); -+ -+ final byte[] data = this.storageVisible; -+ if (data != null) { -+ for (int i = 0; i < 4096; ++i) { -+ // Copied from NibbleArray#toString -+ final int level = ((data[i >>> 1] >>> ((i & 1) << 2)) & 0xF); -+ -+ stringBuilder.append(Integer.toHexString(level)); -+ if ((i & 15) == 15) { -+ stringBuilder.append("\n"); -+ } -+ -+ if ((i & 255) == 255) { -+ stringBuilder.append("\n"); -+ } -+ } -+ } else { -+ stringBuilder.append("null"); -+ } -+ -+ return stringBuilder.toString(); -+ } -+ -+ public SaveState getSaveState() { -+ synchronized (this) { -+ final int state = this.stateVisible; -+ final byte[] data = this.storageVisible; -+ if (state == INIT_STATE_NULL) { -+ return null; -+ } -+ if (state == INIT_STATE_UNINIT) { -+ return new SaveState(null, state); -+ } -+ final boolean zero = isAllZero(data); -+ if (zero) { -+ return state == INIT_STATE_INIT ? new SaveState(null, INIT_STATE_UNINIT) : null; -+ } else { -+ return new SaveState(data.clone(), state); -+ } -+ } -+ } -+ -+ protected static boolean isAllZero(final byte[] data) { -+ for (int i = 0; i < (ARRAY_SIZE >>> 4); ++i) { -+ byte whole = data[i << 4]; -+ -+ for (int k = 1; k < (1 << 4); ++k) { -+ whole |= data[(i << 4) | k]; -+ } -+ -+ if (whole != 0) { -+ return false; -+ } -+ } -+ -+ return true; -+ } -+ -+ // operation type: updating on src, updating on other -+ public void extrudeLower(final SWMRNibbleArray other) { -+ if (other.stateUpdating == INIT_STATE_NULL) { -+ throw new IllegalArgumentException(); -+ } -+ -+ if (other.storageUpdating == null) { -+ this.setUninitialised(); -+ return; -+ } -+ -+ final byte[] src = other.storageUpdating; -+ final byte[] into; -+ -+ if (!this.updatingDirty) { -+ if (this.storageUpdating != null) { -+ into = this.storageUpdating = allocateBytes(); -+ } else { -+ this.storageUpdating = into = allocateBytes(); -+ this.stateUpdating = INIT_STATE_INIT; -+ } -+ this.updatingDirty = true; -+ } else { -+ into = this.storageUpdating; -+ } -+ -+ final int start = 0; -+ final int end = (15 | (15 << 4)) >>> 1; -+ -+ /* x | (z << 4) | (y << 8) */ -+ for (int y = 0; y <= 15; ++y) { -+ System.arraycopy(src, start, into, y << (8 - 1), end - start + 1); -+ } -+ } -+ -+ // operation type: updating -+ public void setFull() { -+ if (this.stateUpdating != INIT_STATE_HIDDEN) { -+ this.stateUpdating = INIT_STATE_INIT; -+ } -+ Arrays.fill(this.storageUpdating == null || !this.updatingDirty ? this.storageUpdating = allocateBytes() : this.storageUpdating, (byte)-1); -+ this.updatingDirty = true; -+ } -+ -+ // operation type: updating -+ public void setZero() { -+ if (this.stateUpdating != INIT_STATE_HIDDEN) { -+ this.stateUpdating = INIT_STATE_INIT; -+ } -+ Arrays.fill(this.storageUpdating == null || !this.updatingDirty ? this.storageUpdating = allocateBytes() : this.storageUpdating, (byte)0); -+ this.updatingDirty = true; -+ } -+ -+ // operation type: updating -+ public void setNonNull() { -+ if (this.stateUpdating == INIT_STATE_HIDDEN) { -+ this.stateUpdating = INIT_STATE_INIT; -+ return; -+ } -+ if (this.stateUpdating != INIT_STATE_NULL) { -+ return; -+ } -+ this.stateUpdating = INIT_STATE_UNINIT; -+ } -+ -+ // operation type: updating -+ public void setNull() { -+ this.stateUpdating = INIT_STATE_NULL; -+ if (this.updatingDirty && this.storageUpdating != null) { -+ freeBytes(this.storageUpdating); -+ } -+ this.storageUpdating = null; -+ this.updatingDirty = false; -+ } -+ -+ // operation type: updating -+ public void setUninitialised() { -+ this.stateUpdating = INIT_STATE_UNINIT; -+ if (this.storageUpdating != null && this.updatingDirty) { -+ freeBytes(this.storageUpdating); -+ } -+ this.storageUpdating = null; -+ this.updatingDirty = false; -+ } -+ -+ // operation type: updating -+ public void setHidden() { -+ if (this.stateUpdating == INIT_STATE_HIDDEN) { -+ return; -+ } -+ if (this.stateUpdating != INIT_STATE_INIT) { -+ this.setNull(); -+ } else { -+ this.stateUpdating = INIT_STATE_HIDDEN; -+ } -+ } -+ -+ // operation type: updating -+ public boolean isDirty() { -+ return this.stateUpdating != this.stateVisible || this.updatingDirty; -+ } -+ -+ // operation type: updating -+ public boolean isNullNibbleUpdating() { -+ return this.stateUpdating == INIT_STATE_NULL; -+ } -+ -+ // operation type: visible -+ public boolean isNullNibbleVisible() { -+ return this.stateVisible == INIT_STATE_NULL; -+ } -+ -+ // opeartion type: updating -+ public boolean isUninitialisedUpdating() { -+ return this.stateUpdating == INIT_STATE_UNINIT; -+ } -+ -+ // operation type: visible -+ public boolean isUninitialisedVisible() { -+ return this.stateVisible == INIT_STATE_UNINIT; -+ } -+ -+ // operation type: updating -+ public boolean isInitialisedUpdating() { -+ return this.stateUpdating == INIT_STATE_INIT; -+ } -+ -+ // operation type: visible -+ public boolean isInitialisedVisible() { -+ return this.stateVisible == INIT_STATE_INIT; -+ } -+ -+ // operation type: updating -+ public boolean isHiddenUpdating() { -+ return this.stateUpdating == INIT_STATE_HIDDEN; -+ } -+ -+ // operation type: updating -+ public boolean isHiddenVisible() { -+ return this.stateVisible == INIT_STATE_HIDDEN; -+ } -+ -+ // operation type: updating -+ protected void swapUpdatingAndMarkDirty() { -+ if (this.updatingDirty) { -+ return; -+ } -+ -+ if (this.storageUpdating == null) { -+ this.storageUpdating = allocateBytes(); -+ Arrays.fill(this.storageUpdating, (byte)0); -+ } else { -+ System.arraycopy(this.storageUpdating, 0, this.storageUpdating = allocateBytes(), 0, ARRAY_SIZE); -+ } -+ -+ if (this.stateUpdating != INIT_STATE_HIDDEN) { -+ this.stateUpdating = INIT_STATE_INIT; -+ } -+ this.updatingDirty = true; -+ } -+ -+ // operation type: updating -+ public boolean updateVisible() { -+ if (!this.isDirty()) { -+ return false; -+ } -+ -+ synchronized (this) { -+ if (this.stateUpdating == INIT_STATE_NULL || this.stateUpdating == INIT_STATE_UNINIT) { -+ this.storageVisible = null; -+ } else { -+ if (this.storageVisible == null) { -+ this.storageVisible = this.storageUpdating.clone(); -+ } else { -+ if (this.storageUpdating != this.storageVisible) { -+ System.arraycopy(this.storageUpdating, 0, this.storageVisible, 0, ARRAY_SIZE); -+ } -+ } -+ -+ if (this.storageUpdating != this.storageVisible) { -+ freeBytes(this.storageUpdating); -+ } -+ this.storageUpdating = this.storageVisible; -+ } -+ this.updatingDirty = false; -+ this.stateVisible = this.stateUpdating; -+ } -+ -+ return true; -+ } -+ -+ // operation type: visible -+ public DataLayer toVanillaNibble() { -+ synchronized (this) { -+ switch (this.stateVisible) { -+ case INIT_STATE_HIDDEN: -+ case INIT_STATE_NULL: -+ return null; -+ case INIT_STATE_UNINIT: -+ return new DataLayer(); -+ case INIT_STATE_INIT: -+ return new DataLayer(this.storageVisible.clone()); -+ default: -+ throw new IllegalStateException(); -+ } -+ } -+ } -+ -+ /* x | (z << 4) | (y << 8) */ -+ -+ // operation type: updating -+ public int getUpdating(final int x, final int y, final int z) { -+ return this.getUpdating((x & 15) | ((z & 15) << 4) | ((y & 15) << 8)); -+ } -+ -+ // operation type: updating -+ public int getUpdating(final int index) { -+ // indices range from 0 -> 4096 -+ final byte[] bytes = this.storageUpdating; -+ if (bytes == null) { -+ return 0; -+ } -+ final byte value = bytes[index >>> 1]; -+ -+ // if we are an even index, we want lower 4 bits -+ // if we are an odd index, we want upper 4 bits -+ return ((value >>> ((index & 1) << 2)) & 0xF); -+ } -+ -+ // operation type: visible -+ public int getVisible(final int x, final int y, final int z) { -+ return this.getVisible((x & 15) | ((z & 15) << 4) | ((y & 15) << 8)); -+ } -+ -+ // operation type: visible -+ public int getVisible(final int index) { -+ // indices range from 0 -> 4096 -+ final byte[] visibleBytes = this.storageVisible; -+ if (visibleBytes == null) { -+ return 0; -+ } -+ final byte value = visibleBytes[index >>> 1]; -+ -+ // if we are an even index, we want lower 4 bits -+ // if we are an odd index, we want upper 4 bits -+ return ((value >>> ((index & 1) << 2)) & 0xF); -+ } -+ -+ // operation type: updating -+ public void set(final int x, final int y, final int z, final int value) { -+ this.set((x & 15) | ((z & 15) << 4) | ((y & 15) << 8), value); -+ } -+ -+ // operation type: updating -+ public void set(final int index, final int value) { -+ if (!this.updatingDirty) { -+ this.swapUpdatingAndMarkDirty(); -+ } -+ final int shift = (index & 1) << 2; -+ final int i = index >>> 1; -+ -+ this.storageUpdating[i] = (byte)((this.storageUpdating[i] & (0xF0 >>> shift)) | (value << shift)); -+ } -+ -+ public static final class SaveState { -+ -+ public final byte[] data; -+ public final int state; -+ -+ public SaveState(final byte[] data, final int state) { -+ this.data = data; -+ this.state = state; -+ } -+ } -+} -diff --git a/src/main/java/ca/spottedleaf/starlight/common/light/SkyStarLightEngine.java b/src/main/java/ca/spottedleaf/starlight/common/light/SkyStarLightEngine.java -new file mode 100644 -index 0000000000000000000000000000000000000000..43a2cce467d29f81ba57d77c03608e57857dd579 ---- /dev/null -+++ b/src/main/java/ca/spottedleaf/starlight/common/light/SkyStarLightEngine.java -@@ -0,0 +1,709 @@ -+package ca.spottedleaf.starlight.common.light; -+ -+import ca.spottedleaf.starlight.common.util.WorldUtil; -+import it.unimi.dsi.fastutil.shorts.ShortCollection; -+import it.unimi.dsi.fastutil.shorts.ShortIterator; -+import net.minecraft.core.BlockPos; -+import net.minecraft.world.level.BlockGetter; -+import net.minecraft.world.level.ChunkPos; -+import net.minecraft.world.level.Level; -+import net.minecraft.world.level.block.state.BlockState; -+import net.minecraft.world.level.chunk.ChunkAccess; -+import net.minecraft.world.level.chunk.status.ChunkStatus; -+import net.minecraft.world.level.chunk.LevelChunkSection; -+import net.minecraft.world.level.chunk.LightChunkGetter; -+import net.minecraft.world.phys.shapes.Shapes; -+import net.minecraft.world.phys.shapes.VoxelShape; -+import java.util.Arrays; -+import java.util.Set; -+ -+public final class SkyStarLightEngine extends StarLightEngine { -+ -+ /* -+ Specification for managing the initialisation and de-initialisation of skylight nibble arrays: -+ -+ Skylight nibble initialisation requires that non-empty chunk sections have 1 radius nibbles non-null. -+ -+ This presents some problems, as vanilla is only guaranteed to have 0 radius neighbours loaded when editing blocks. -+ However starlight fixes this so that it has 1 radius loaded. Still, we don't actually have guarantees -+ that we have the necessary chunks loaded to de-initialise neighbour sections (but we do have enough to de-initialise -+ our own) - we need a radius of 2 to de-initialise neighbour nibbles. -+ How do we solve this? -+ -+ Each chunk will store the last known "emptiness" of sections for each of their 1 radius neighbour chunk sections. -+ If the chunk does not have full data, then its nibbles are NOT de-initialised. This is because obviously the -+ chunk did not go through the light stage yet - or its neighbours are not lit. In either case, once the last -+ known "emptiness" of neighbouring sections is filled with data, the chunk will run a full check of the data -+ to see if any of its nibbles need to be de-initialised. -+ -+ The emptiness map allows us to de-initialise neighbour nibbles if the neighbour has it filled with data, -+ and if it doesn't have data then we know it will correctly de-initialise once it fills up. -+ -+ Unlike vanilla, we store whether nibbles are uninitialised on disk - so we don't need any dumb hacking -+ around those. -+ */ -+ -+ protected final int[] heightMapBlockChange = new int[16 * 16]; -+ { -+ Arrays.fill(this.heightMapBlockChange, Integer.MIN_VALUE); // clear heightmap -+ } -+ -+ protected final boolean[] nullPropagationCheckCache; -+ -+ public SkyStarLightEngine(final Level world) { -+ super(true, world); -+ this.nullPropagationCheckCache = new boolean[WorldUtil.getTotalLightSections(world)]; -+ } -+ -+ @Override -+ protected void initNibble(final int chunkX, final int chunkY, final int chunkZ, final boolean extrude, final boolean initRemovedNibbles) { -+ if (chunkY < this.minLightSection || chunkY > this.maxLightSection || this.getChunkInCache(chunkX, chunkZ) == null) { -+ return; -+ } -+ SWMRNibbleArray nibble = this.getNibbleFromCache(chunkX, chunkY, chunkZ); -+ if (nibble == null) { -+ if (!initRemovedNibbles) { -+ throw new IllegalStateException(); -+ } else { -+ this.setNibbleInCache(chunkX, chunkY, chunkZ, nibble = new SWMRNibbleArray(null, true)); -+ } -+ } -+ this.initNibble(nibble, chunkX, chunkY, chunkZ, extrude); -+ } -+ -+ @Override -+ protected void setNibbleNull(final int chunkX, final int chunkY, final int chunkZ) { -+ final SWMRNibbleArray nibble = this.getNibbleFromCache(chunkX, chunkY, chunkZ); -+ if (nibble != null) { -+ nibble.setNull(); -+ } -+ } -+ -+ protected final void initNibble(final SWMRNibbleArray currNibble, final int chunkX, final int chunkY, final int chunkZ, final boolean extrude) { -+ if (!currNibble.isNullNibbleUpdating()) { -+ // already initialised -+ return; -+ } -+ -+ final boolean[] emptinessMap = this.getEmptinessMap(chunkX, chunkZ); -+ -+ // are we above this chunk's lowest empty section? -+ int lowestY = this.minLightSection - 1; -+ for (int currY = this.maxSection; currY >= this.minSection; --currY) { -+ if (emptinessMap == null) { -+ // cannot delay nibble init for lit chunks, as we need to init to propagate into them. -+ final LevelChunkSection current = this.getChunkSection(chunkX, currY, chunkZ); -+ if (current == null || current.hasOnlyAir()) { -+ continue; -+ } -+ } else { -+ if (emptinessMap[currY - this.minSection]) { -+ continue; -+ } -+ } -+ -+ // should always be full lit here -+ lowestY = currY; -+ break; -+ } -+ -+ if (chunkY > lowestY) { -+ // we need to set this one to full -+ final SWMRNibbleArray nibble = this.getNibbleFromCache(chunkX, chunkY, chunkZ); -+ nibble.setNonNull(); -+ nibble.setFull(); -+ return; -+ } -+ -+ if (extrude) { -+ // this nibble is going to depend solely on the skylight data above it -+ // find first non-null data above (there does exist one, as we just found it above) -+ for (int currY = chunkY + 1; currY <= this.maxLightSection; ++currY) { -+ final SWMRNibbleArray nibble = this.getNibbleFromCache(chunkX, currY, chunkZ); -+ if (nibble != null && !nibble.isNullNibbleUpdating()) { -+ currNibble.setNonNull(); -+ currNibble.extrudeLower(nibble); -+ break; -+ } -+ } -+ } else { -+ currNibble.setNonNull(); -+ } -+ } -+ -+ protected final void rewriteNibbleCacheForSkylight(final ChunkAccess chunk) { -+ for (int index = 0, max = this.nibbleCache.length; index < max; ++index) { -+ final SWMRNibbleArray nibble = this.nibbleCache[index]; -+ if (nibble != null && nibble.isNullNibbleUpdating()) { -+ // stop propagation in these areas -+ this.nibbleCache[index] = null; -+ nibble.updateVisible(); -+ } -+ } -+ } -+ -+ // rets whether neighbours were init'd -+ -+ protected final boolean checkNullSection(final int chunkX, final int chunkY, final int chunkZ, -+ final boolean extrudeInitialised) { -+ // null chunk sections may have nibble neighbours in the horizontal 1 radius that are -+ // non-null. Propagation to these neighbours is necessary. -+ // What makes this easy is we know none of these neighbours are non-empty (otherwise -+ // this nibble would be initialised). So, we don't have to initialise -+ // the neighbours in the full 1 radius, because there's no worry that any "paths" -+ // to the neighbours on this horizontal plane are blocked. -+ if (chunkY < this.minLightSection || chunkY > this.maxLightSection || this.nullPropagationCheckCache[chunkY - this.minLightSection]) { -+ return false; -+ } -+ this.nullPropagationCheckCache[chunkY - this.minLightSection] = true; -+ -+ // check horizontal neighbours -+ boolean needInitNeighbours = false; -+ neighbour_search: -+ for (int dz = -1; dz <= 1; ++dz) { -+ for (int dx = -1; dx <= 1; ++dx) { -+ final SWMRNibbleArray nibble = this.getNibbleFromCache(dx + chunkX, chunkY, dz + chunkZ); -+ if (nibble != null && !nibble.isNullNibbleUpdating()) { -+ needInitNeighbours = true; -+ break neighbour_search; -+ } -+ } -+ } -+ -+ if (needInitNeighbours) { -+ for (int dz = -1; dz <= 1; ++dz) { -+ for (int dx = -1; dx <= 1; ++dx) { -+ this.initNibble(dx + chunkX, chunkY, dz + chunkZ, (dx | dz) == 0 ? extrudeInitialised : true, true); -+ } -+ } -+ } -+ -+ return needInitNeighbours; -+ } -+ -+ protected final int getLightLevelExtruded(final int worldX, final int worldY, final int worldZ) { -+ final int chunkX = worldX >> 4; -+ int chunkY = worldY >> 4; -+ final int chunkZ = worldZ >> 4; -+ -+ SWMRNibbleArray nibble = this.getNibbleFromCache(chunkX, chunkY, chunkZ); -+ if (nibble != null) { -+ return nibble.getUpdating(worldX, worldY, worldZ); -+ } -+ -+ for (;;) { -+ if (++chunkY > this.maxLightSection) { -+ return 15; -+ } -+ -+ nibble = this.getNibbleFromCache(chunkX, chunkY, chunkZ); -+ -+ if (nibble != null) { -+ return nibble.getUpdating(worldX, 0, worldZ); -+ } -+ } -+ } -+ -+ @Override -+ protected boolean[] getEmptinessMap(final ChunkAccess chunk) { -+ return chunk.getSkyEmptinessMap(); -+ } -+ -+ @Override -+ protected void setEmptinessMap(final ChunkAccess chunk, final boolean[] to) { -+ chunk.setSkyEmptinessMap(to); -+ } -+ -+ @Override -+ protected SWMRNibbleArray[] getNibblesOnChunk(final ChunkAccess chunk) { -+ return chunk.getSkyNibbles(); -+ } -+ -+ @Override -+ protected void setNibbles(final ChunkAccess chunk, final SWMRNibbleArray[] to) { -+ chunk.setSkyNibbles(to); -+ } -+ -+ @Override -+ protected boolean canUseChunk(final ChunkAccess chunk) { -+ // can only use chunks for sky stuff if their sections have been init'd -+ return chunk.getStatus().isOrAfter(ChunkStatus.LIGHT) && (this.isClientSide || chunk.isLightCorrect()); -+ } -+ -+ @Override -+ protected void checkChunkEdges(final LightChunkGetter lightAccess, final ChunkAccess chunk, final int fromSection, -+ final int toSection) { -+ Arrays.fill(this.nullPropagationCheckCache, false); -+ this.rewriteNibbleCacheForSkylight(chunk); -+ final int chunkX = chunk.getPos().x; -+ final int chunkZ = chunk.getPos().z; -+ for (int y = toSection; y >= fromSection; --y) { -+ this.checkNullSection(chunkX, y, chunkZ, true); -+ } -+ -+ super.checkChunkEdges(lightAccess, chunk, fromSection, toSection); -+ } -+ -+ @Override -+ protected void checkChunkEdges(final LightChunkGetter lightAccess, final ChunkAccess chunk, final ShortCollection sections) { -+ Arrays.fill(this.nullPropagationCheckCache, false); -+ this.rewriteNibbleCacheForSkylight(chunk); -+ final int chunkX = chunk.getPos().x; -+ final int chunkZ = chunk.getPos().z; -+ for (final ShortIterator iterator = sections.iterator(); iterator.hasNext();) { -+ final int y = (int)iterator.nextShort(); -+ this.checkNullSection(chunkX, y, chunkZ, true); -+ } -+ -+ super.checkChunkEdges(lightAccess, chunk, sections); -+ } -+ -+ @Override -+ protected void checkBlock(final LightChunkGetter lightAccess, final int worldX, final int worldY, final int worldZ) { -+ // blocks can change opacity -+ // blocks can change direction of propagation -+ -+ // same logic applies from BlockStarLightEngine#checkBlock -+ -+ final int encodeOffset = this.coordinateOffset; -+ -+ final int currentLevel = this.getLightLevel(worldX, worldY, worldZ); -+ -+ if (currentLevel == 15) { -+ // must re-propagate clobbered source -+ this.appendToIncreaseQueue( -+ ((worldX + (worldZ << 6) + (worldY << (6 + 6)) + encodeOffset) & ((1L << (6 + 6 + 16)) - 1)) -+ | (currentLevel & 0xFL) << (6 + 6 + 16) -+ | (((long)ALL_DIRECTIONS_BITSET) << (6 + 6 + 16 + 4)) -+ | FLAG_HAS_SIDED_TRANSPARENT_BLOCKS // don't know if the block is conditionally transparent -+ ); -+ } else { -+ this.setLightLevel(worldX, worldY, worldZ, 0); -+ } -+ -+ this.appendToDecreaseQueue( -+ ((worldX + (worldZ << 6) + (worldY << (6 + 6)) + encodeOffset) & ((1L << (6 + 6 + 16)) - 1)) -+ | (currentLevel & 0xFL) << (6 + 6 + 16) -+ | (((long)ALL_DIRECTIONS_BITSET) << (6 + 6 + 16 + 4)) -+ ); -+ } -+ -+ protected final BlockPos.MutableBlockPos recalcCenterPos = new BlockPos.MutableBlockPos(); -+ protected final BlockPos.MutableBlockPos recalcNeighbourPos = new BlockPos.MutableBlockPos(); -+ -+ @Override -+ protected int calculateLightValue(final LightChunkGetter lightAccess, final int worldX, final int worldY, final int worldZ, -+ final int expect) { -+ if (expect == 15) { -+ return expect; -+ } -+ -+ final int sectionOffset = this.chunkSectionIndexOffset; -+ final BlockState centerState = this.getBlockState(worldX, worldY, worldZ); -+ int opacity = centerState.getOpacityIfCached(); -+ -+ final BlockState conditionallyOpaqueState; -+ if (opacity < 0) { -+ this.recalcCenterPos.set(worldX, worldY, worldZ); -+ opacity = Math.max(1, centerState.getLightBlock(lightAccess.getLevel(), this.recalcCenterPos)); -+ if (centerState.isConditionallyFullOpaque()) { -+ conditionallyOpaqueState = centerState; -+ } else { -+ conditionallyOpaqueState = null; -+ } -+ } else { -+ conditionallyOpaqueState = null; -+ opacity = Math.max(1, opacity); -+ } -+ -+ int level = 0; -+ -+ for (final AxisDirection direction : AXIS_DIRECTIONS) { -+ final int offX = worldX + direction.x; -+ final int offY = worldY + direction.y; -+ final int offZ = worldZ + direction.z; -+ -+ final int sectionIndex = (offX >> 4) + 5 * (offZ >> 4) + (5 * 5) * (offY >> 4) + sectionOffset; -+ -+ final int neighbourLevel = this.getLightLevel(sectionIndex, (offX & 15) | ((offZ & 15) << 4) | ((offY & 15) << 8)); -+ -+ if ((neighbourLevel - 1) <= level) { -+ // don't need to test transparency, we know it wont affect the result. -+ continue; -+ } -+ -+ final BlockState neighbourState = this.getBlockState(offX, offY, offZ); -+ -+ if (neighbourState.isConditionallyFullOpaque()) { -+ // here the block can be conditionally opaque (i.e light cannot propagate from it), so we need to test that -+ // we don't read the blockstate because most of the time this is false, so using the faster -+ // known transparency lookup results in a net win -+ this.recalcNeighbourPos.set(offX, offY, offZ); -+ final VoxelShape neighbourFace = neighbourState.getFaceOcclusionShape(lightAccess.getLevel(), this.recalcNeighbourPos, direction.opposite.nms); -+ final VoxelShape thisFace = conditionallyOpaqueState == null ? Shapes.empty() : conditionallyOpaqueState.getFaceOcclusionShape(lightAccess.getLevel(), this.recalcCenterPos, direction.nms); -+ if (Shapes.faceShapeOccludes(thisFace, neighbourFace)) { -+ // not allowed to propagate -+ continue; -+ } -+ } -+ -+ final int calculated = neighbourLevel - opacity; -+ level = Math.max(calculated, level); -+ if (level > expect) { -+ return level; -+ } -+ } -+ -+ return level; -+ } -+ -+ @Override -+ protected void propagateBlockChanges(final LightChunkGetter lightAccess, final ChunkAccess atChunk, final Set positions) { -+ this.rewriteNibbleCacheForSkylight(atChunk); -+ Arrays.fill(this.nullPropagationCheckCache, false); -+ -+ final BlockGetter world = lightAccess.getLevel(); -+ final int chunkX = atChunk.getPos().x; -+ final int chunkZ = atChunk.getPos().z; -+ final int heightMapOffset = chunkX * -16 + (chunkZ * (-16 * 16)); -+ -+ // setup heightmap for changes -+ for (final BlockPos pos : positions) { -+ final int index = pos.getX() + (pos.getZ() << 4) + heightMapOffset; -+ final int curr = this.heightMapBlockChange[index]; -+ if (pos.getY() > curr) { -+ this.heightMapBlockChange[index] = pos.getY(); -+ } -+ } -+ -+ // note: light sets are delayed while processing skylight source changes due to how -+ // nibbles are initialised, as we want to avoid clobbering nibble values so what when -+ // below nibbles are initialised they aren't reading from partially modified nibbles -+ -+ // now we can recalculate the sources for the changed columns -+ for (int index = 0; index < (16 * 16); ++index) { -+ final int maxY = this.heightMapBlockChange[index]; -+ if (maxY == Integer.MIN_VALUE) { -+ // not changed -+ continue; -+ } -+ this.heightMapBlockChange[index] = Integer.MIN_VALUE; // restore default for next caller -+ -+ final int columnX = (index & 15) | (chunkX << 4); -+ final int columnZ = (index >>> 4) | (chunkZ << 4); -+ -+ // try and propagate from the above y -+ // delay light set until after processing all sources to setup -+ final int maxPropagationY = this.tryPropagateSkylight(world, columnX, maxY, columnZ, true, true); -+ -+ // maxPropagationY is now the highest block that could not be propagated to -+ -+ // remove all sources below that are 15 -+ final long propagateDirection = AxisDirection.POSITIVE_Y.everythingButThisDirection; -+ final int encodeOffset = this.coordinateOffset; -+ -+ if (this.getLightLevelExtruded(columnX, maxPropagationY, columnZ) == 15) { -+ // ensure section is checked -+ this.checkNullSection(columnX >> 4, maxPropagationY >> 4, columnZ >> 4, true); -+ -+ for (int currY = maxPropagationY; currY >= (this.minLightSection << 4); --currY) { -+ if ((currY & 15) == 15) { -+ // ensure section is checked -+ this.checkNullSection(columnX >> 4, (currY >> 4), columnZ >> 4, true); -+ } -+ -+ // ensure section below is always checked -+ final SWMRNibbleArray nibble = this.getNibbleFromCache(columnX >> 4, currY >> 4, columnZ >> 4); -+ if (nibble == null) { -+ // advance currY to the the top of the section below -+ currY = (currY) & (~15); -+ // note: this value ^ is actually 1 above the top, but the loop decrements by 1 so we actually -+ // end up there -+ continue; -+ } -+ -+ if (nibble.getUpdating(columnX, currY, columnZ) != 15) { -+ break; -+ } -+ -+ // delay light set until after processing all sources to setup -+ this.appendToDecreaseQueue( -+ ((columnX + (columnZ << 6) + (currY << (6 + 6)) + encodeOffset) & ((1L << (6 + 6 + 16)) - 1)) -+ | (15L << (6 + 6 + 16)) -+ | (propagateDirection << (6 + 6 + 16 + 4)) -+ // do not set transparent blocks for the same reason we don't in the checkBlock method -+ ); -+ } -+ } -+ } -+ -+ // delayed light sets are processed here, and must be processed before checkBlock as checkBlock reads -+ // immediate light value -+ this.processDelayedIncreases(); -+ this.processDelayedDecreases(); -+ -+ for (final BlockPos pos : positions) { -+ this.checkBlock(lightAccess, pos.getX(), pos.getY(), pos.getZ()); -+ } -+ -+ this.performLightDecrease(lightAccess); -+ } -+ -+ protected final int[] heightMapGen = new int[32 * 32]; -+ -+ @Override -+ protected void lightChunk(final LightChunkGetter lightAccess, final ChunkAccess chunk, final boolean needsEdgeChecks) { -+ this.rewriteNibbleCacheForSkylight(chunk); -+ Arrays.fill(this.nullPropagationCheckCache, false); -+ -+ final BlockGetter world = lightAccess.getLevel(); -+ final ChunkPos chunkPos = chunk.getPos(); -+ final int chunkX = chunkPos.x; -+ final int chunkZ = chunkPos.z; -+ -+ final LevelChunkSection[] sections = chunk.getSections(); -+ -+ int highestNonEmptySection = this.maxSection; -+ while (highestNonEmptySection == (this.minSection - 1) || -+ sections[highestNonEmptySection - this.minSection] == null || sections[highestNonEmptySection - this.minSection].hasOnlyAir()) { -+ this.checkNullSection(chunkX, highestNonEmptySection, chunkZ, false); -+ // try propagate FULL to neighbours -+ -+ // check neighbours to see if we need to propagate into them -+ for (final AxisDirection direction : ONLY_HORIZONTAL_DIRECTIONS) { -+ final int neighbourX = chunkX + direction.x; -+ final int neighbourZ = chunkZ + direction.z; -+ final SWMRNibbleArray neighbourNibble = this.getNibbleFromCache(neighbourX, highestNonEmptySection, neighbourZ); -+ if (neighbourNibble == null) { -+ // unloaded neighbour -+ // most of the time we fall here -+ continue; -+ } -+ -+ // it looks like we need to propagate into the neighbour -+ -+ final int incX; -+ final int incZ; -+ final int startX; -+ final int startZ; -+ -+ if (direction.x != 0) { -+ // x direction -+ incX = 0; -+ incZ = 1; -+ -+ if (direction.x < 0) { -+ // negative -+ startX = chunkX << 4; -+ } else { -+ startX = chunkX << 4 | 15; -+ } -+ startZ = chunkZ << 4; -+ } else { -+ // z direction -+ incX = 1; -+ incZ = 0; -+ -+ if (direction.z < 0) { -+ // negative -+ startZ = chunkZ << 4; -+ } else { -+ startZ = chunkZ << 4 | 15; -+ } -+ startX = chunkX << 4; -+ } -+ -+ final int encodeOffset = this.coordinateOffset; -+ final long propagateDirection = 1L << direction.ordinal(); // we only want to check in this direction -+ -+ for (int currY = highestNonEmptySection << 4, maxY = currY | 15; currY <= maxY; ++currY) { -+ for (int i = 0, currX = startX, currZ = startZ; i < 16; ++i, currX += incX, currZ += incZ) { -+ this.appendToIncreaseQueue( -+ ((currX + (currZ << 6) + (currY << (6 + 6)) + encodeOffset) & ((1L << (6 + 6 + 16)) - 1)) -+ | (15L << (6 + 6 + 16)) // we know we're at full lit here -+ | (propagateDirection << (6 + 6 + 16 + 4)) -+ // no transparent flag, we know for a fact there are no blocks here that could be directionally transparent (as the section is EMPTY) -+ ); -+ } -+ } -+ } -+ -+ if (highestNonEmptySection-- == (this.minSection - 1)) { -+ break; -+ } -+ } -+ -+ if (highestNonEmptySection >= this.minSection) { -+ // fill out our other sources -+ final int minX = chunkPos.x << 4; -+ final int maxX = chunkPos.x << 4 | 15; -+ final int minZ = chunkPos.z << 4; -+ final int maxZ = chunkPos.z << 4 | 15; -+ final int startY = highestNonEmptySection << 4 | 15; -+ for (int currZ = minZ; currZ <= maxZ; ++currZ) { -+ for (int currX = minX; currX <= maxX; ++currX) { -+ this.tryPropagateSkylight(world, currX, startY + 1, currZ, false, false); -+ } -+ } -+ } // else: apparently the chunk is empty -+ -+ if (needsEdgeChecks) { -+ // not required to propagate here, but this will reduce the hit of the edge checks -+ this.performLightIncrease(lightAccess); -+ -+ for (int y = highestNonEmptySection; y >= this.minLightSection; --y) { -+ this.checkNullSection(chunkX, y, chunkZ, false); -+ } -+ // no need to rewrite the nibble cache again -+ super.checkChunkEdges(lightAccess, chunk, this.minLightSection, highestNonEmptySection); -+ } else { -+ for (int y = highestNonEmptySection; y >= this.minLightSection; --y) { -+ this.checkNullSection(chunkX, y, chunkZ, false); -+ } -+ this.propagateNeighbourLevels(lightAccess, chunk, this.minLightSection, highestNonEmptySection); -+ -+ this.performLightIncrease(lightAccess); -+ } -+ } -+ -+ protected final void processDelayedIncreases() { -+ // copied from performLightIncrease -+ final long[] queue = this.increaseQueue; -+ final int decodeOffsetX = -this.encodeOffsetX; -+ final int decodeOffsetY = -this.encodeOffsetY; -+ final int decodeOffsetZ = -this.encodeOffsetZ; -+ -+ for (int i = 0, len = this.increaseQueueInitialLength; i < len; ++i) { -+ final long queueValue = queue[i]; -+ -+ final int posX = ((int)queueValue & 63) + decodeOffsetX; -+ final int posZ = (((int)queueValue >>> 6) & 63) + decodeOffsetZ; -+ final int posY = (((int)queueValue >>> 12) & ((1 << 16) - 1)) + decodeOffsetY; -+ final int propagatedLightLevel = (int)((queueValue >>> (6 + 6 + 16)) & 0xF); -+ -+ this.setLightLevel(posX, posY, posZ, propagatedLightLevel); -+ } -+ } -+ -+ protected final void processDelayedDecreases() { -+ // copied from performLightDecrease -+ final long[] queue = this.decreaseQueue; -+ final int decodeOffsetX = -this.encodeOffsetX; -+ final int decodeOffsetY = -this.encodeOffsetY; -+ final int decodeOffsetZ = -this.encodeOffsetZ; -+ -+ for (int i = 0, len = this.decreaseQueueInitialLength; i < len; ++i) { -+ final long queueValue = queue[i]; -+ -+ final int posX = ((int)queueValue & 63) + decodeOffsetX; -+ final int posZ = (((int)queueValue >>> 6) & 63) + decodeOffsetZ; -+ final int posY = (((int)queueValue >>> 12) & ((1 << 16) - 1)) + decodeOffsetY; -+ -+ this.setLightLevel(posX, posY, posZ, 0); -+ } -+ } -+ -+ // delaying the light set is useful for block changes since they need to worry about initialising nibblearrays -+ // while also queueing light at the same time (initialising nibblearrays might depend on nibbles above, so -+ // clobbering the light values will result in broken propagation) -+ protected final int tryPropagateSkylight(final BlockGetter world, final int worldX, int startY, final int worldZ, -+ final boolean extrudeInitialised, final boolean delayLightSet) { -+ final BlockPos.MutableBlockPos mutablePos = this.mutablePos3; -+ final int encodeOffset = this.coordinateOffset; -+ final long propagateDirection = AxisDirection.POSITIVE_Y.everythingButThisDirection; // just don't check upwards. -+ -+ if (this.getLightLevelExtruded(worldX, startY + 1, worldZ) != 15) { -+ return startY; -+ } -+ -+ // ensure this section is always checked -+ this.checkNullSection(worldX >> 4, startY >> 4, worldZ >> 4, extrudeInitialised); -+ -+ BlockState above = this.getBlockState(worldX, startY + 1, worldZ); -+ -+ for (;startY >= (this.minLightSection << 4); --startY) { -+ if ((startY & 15) == 15) { -+ // ensure this section is always checked -+ this.checkNullSection(worldX >> 4, startY >> 4, worldZ >> 4, extrudeInitialised); -+ } -+ final BlockState current = this.getBlockState(worldX, startY, worldZ); -+ -+ final VoxelShape fromShape; -+ if (above.isConditionallyFullOpaque()) { -+ this.mutablePos2.set(worldX, startY + 1, worldZ); -+ fromShape = above.getFaceOcclusionShape(world, this.mutablePos2, AxisDirection.NEGATIVE_Y.nms); -+ if (Shapes.faceShapeOccludes(Shapes.empty(), fromShape)) { -+ // above wont let us propagate -+ break; -+ } -+ } else { -+ fromShape = Shapes.empty(); -+ } -+ -+ final int opacityIfCached = current.getOpacityIfCached(); -+ // does light propagate from the top down? -+ if (opacityIfCached != -1) { -+ if (opacityIfCached != 0) { -+ // we cannot propagate 15 through this -+ break; -+ } -+ // most of the time it falls here. -+ // add to propagate -+ // light set delayed until we determine if this nibble section is null -+ this.appendToIncreaseQueue( -+ ((worldX + (worldZ << 6) + (startY << (6 + 6)) + encodeOffset) & ((1L << (6 + 6 + 16)) - 1)) -+ | (15L << (6 + 6 + 16)) // we know we're at full lit here -+ | (propagateDirection << (6 + 6 + 16 + 4)) -+ ); -+ } else { -+ mutablePos.set(worldX, startY, worldZ); -+ long flags = 0L; -+ if (current.isConditionallyFullOpaque()) { -+ final VoxelShape cullingFace = current.getFaceOcclusionShape(world, mutablePos, AxisDirection.POSITIVE_Y.nms); -+ -+ if (Shapes.faceShapeOccludes(fromShape, cullingFace)) { -+ // can't propagate here, we're done on this column. -+ break; -+ } -+ flags |= FLAG_HAS_SIDED_TRANSPARENT_BLOCKS; -+ } -+ -+ final int opacity = current.getLightBlock(world, mutablePos); -+ if (opacity > 0) { -+ // let the queued value (if any) handle it from here. -+ break; -+ } -+ -+ // light set delayed until we determine if this nibble section is null -+ this.appendToIncreaseQueue( -+ ((worldX + (worldZ << 6) + (startY << (6 + 6)) + encodeOffset) & ((1L << (6 + 6 + 16)) - 1)) -+ | (15L << (6 + 6 + 16)) // we know we're at full lit here -+ | (propagateDirection << (6 + 6 + 16 + 4)) -+ | flags -+ ); -+ } -+ -+ above = current; -+ -+ if (this.getNibbleFromCache(worldX >> 4, startY >> 4, worldZ >> 4) == null) { -+ // we skip empty sections here, as this is just an easy way of making sure the above block -+ // can propagate through air. -+ -+ // nothing can propagate in null sections, remove the queue entry for it -+ --this.increaseQueueInitialLength; -+ -+ // advance currY to the the top of the section below -+ startY = (startY) & (~15); -+ // note: this value ^ is actually 1 above the top, but the loop decrements by 1 so we actually -+ // end up there -+ -+ // make sure this is marked as AIR -+ above = AIR_BLOCK_STATE; -+ } else if (!delayLightSet) { -+ this.setLightLevel(worldX, startY, worldZ, 15); -+ } -+ } -+ -+ return startY; -+ } -+} -diff --git a/src/main/java/ca/spottedleaf/starlight/common/light/StarLightEngine.java b/src/main/java/ca/spottedleaf/starlight/common/light/StarLightEngine.java -new file mode 100644 -index 0000000000000000000000000000000000000000..ad1eeebe6de219143492b94da309cb54ae9e0a5b ---- /dev/null -+++ b/src/main/java/ca/spottedleaf/starlight/common/light/StarLightEngine.java -@@ -0,0 +1,1572 @@ -+package ca.spottedleaf.starlight.common.light; -+ -+import ca.spottedleaf.starlight.common.util.CoordinateUtils; -+import ca.spottedleaf.starlight.common.util.IntegerUtil; -+import ca.spottedleaf.starlight.common.util.WorldUtil; -+import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap; -+import it.unimi.dsi.fastutil.shorts.ShortCollection; -+import it.unimi.dsi.fastutil.shorts.ShortIterator; -+import net.minecraft.core.BlockPos; -+import net.minecraft.core.Direction; -+import net.minecraft.core.SectionPos; -+import net.minecraft.world.level.BlockGetter; -+import net.minecraft.world.level.ChunkPos; -+import net.minecraft.world.level.Level; -+import net.minecraft.world.level.LevelHeightAccessor; -+import net.minecraft.world.level.LightLayer; -+import net.minecraft.world.level.block.Blocks; -+import net.minecraft.world.level.block.state.BlockState; -+import net.minecraft.world.level.chunk.ChunkAccess; -+import net.minecraft.world.level.chunk.LevelChunkSection; -+import net.minecraft.world.level.chunk.LightChunkGetter; -+import net.minecraft.world.phys.shapes.Shapes; -+import net.minecraft.world.phys.shapes.VoxelShape; -+import java.util.ArrayList; -+import java.util.Arrays; -+import java.util.List; -+import java.util.Set; -+import java.util.function.Consumer; -+import java.util.function.IntConsumer; -+ -+public abstract class StarLightEngine { -+ -+ protected static final BlockState AIR_BLOCK_STATE = Blocks.AIR.defaultBlockState(); -+ -+ protected static final AxisDirection[] DIRECTIONS = AxisDirection.values(); -+ protected static final AxisDirection[] AXIS_DIRECTIONS = DIRECTIONS; -+ protected static final AxisDirection[] ONLY_HORIZONTAL_DIRECTIONS = new AxisDirection[] { -+ AxisDirection.POSITIVE_X, AxisDirection.NEGATIVE_X, -+ AxisDirection.POSITIVE_Z, AxisDirection.NEGATIVE_Z -+ }; -+ -+ protected static enum AxisDirection { -+ -+ // Declaration order is important and relied upon. Do not change without modifying propagation code. -+ POSITIVE_X(1, 0, 0), NEGATIVE_X(-1, 0, 0), -+ POSITIVE_Z(0, 0, 1), NEGATIVE_Z(0, 0, -1), -+ POSITIVE_Y(0, 1, 0), NEGATIVE_Y(0, -1, 0); -+ -+ static { -+ POSITIVE_X.opposite = NEGATIVE_X; NEGATIVE_X.opposite = POSITIVE_X; -+ POSITIVE_Z.opposite = NEGATIVE_Z; NEGATIVE_Z.opposite = POSITIVE_Z; -+ POSITIVE_Y.opposite = NEGATIVE_Y; NEGATIVE_Y.opposite = POSITIVE_Y; -+ } -+ -+ protected AxisDirection opposite; -+ -+ public final int x; -+ public final int y; -+ public final int z; -+ public final Direction nms; -+ public final long everythingButThisDirection; -+ public final long everythingButTheOppositeDirection; -+ -+ AxisDirection(final int x, final int y, final int z) { -+ this.x = x; -+ this.y = y; -+ this.z = z; -+ this.nms = Direction.fromDelta(x, y, z); -+ this.everythingButThisDirection = (long)(ALL_DIRECTIONS_BITSET ^ (1 << this.ordinal())); -+ // positive is always even, negative is always odd. Flip the 1 bit to get the negative direction. -+ this.everythingButTheOppositeDirection = (long)(ALL_DIRECTIONS_BITSET ^ (1 << (this.ordinal() ^ 1))); -+ } -+ -+ public AxisDirection getOpposite() { -+ return this.opposite; -+ } -+ } -+ -+ // I'd like to thank https://www.seedofandromeda.com/blogs/29-fast-flood-fill-lighting-in-a-blocky-voxel-game-pt-1 -+ // for explaining how light propagates via breadth-first search -+ -+ // While the above is a good start to understanding the general idea of what the general principles are, it's not -+ // exactly how the vanilla light engine should behave for minecraft. -+ -+ // similar to the above, except the chunk section indices vary from [-1, 1], or [0, 2] -+ // for the y chunk section it's from [minLightSection, maxLightSection] or [0, maxLightSection - minLightSection] -+ // index = x + (z * 5) + (y * 25) -+ // null index indicates the chunk section doesn't exist (empty or out of bounds) -+ protected final LevelChunkSection[] sectionCache; -+ -+ // the exact same as above, except for storing fast access to SWMRNibbleArray -+ // for the y chunk section it's from [minLightSection, maxLightSection] or [0, maxLightSection - minLightSection] -+ // index = x + (z * 5) + (y * 25) -+ protected final SWMRNibbleArray[] nibbleCache; -+ -+ // the exact same as above, except for storing fast access to nibbles to call change callbacks for -+ // for the y chunk section it's from [minLightSection, maxLightSection] or [0, maxLightSection - minLightSection] -+ // index = x + (z * 5) + (y * 25) -+ protected final boolean[] notifyUpdateCache; -+ -+ // always initialsed during start of lighting. -+ // index = x + (z * 5) -+ protected final ChunkAccess[] chunkCache = new ChunkAccess[5 * 5]; -+ -+ // index = x + (z * 5) -+ protected final boolean[][] emptinessMapCache = new boolean[5 * 5][]; -+ -+ protected final BlockPos.MutableBlockPos mutablePos1 = new BlockPos.MutableBlockPos(); -+ protected final BlockPos.MutableBlockPos mutablePos2 = new BlockPos.MutableBlockPos(); -+ protected final BlockPos.MutableBlockPos mutablePos3 = new BlockPos.MutableBlockPos(); -+ -+ protected int encodeOffsetX; -+ protected int encodeOffsetY; -+ protected int encodeOffsetZ; -+ -+ protected int coordinateOffset; -+ -+ protected int chunkOffsetX; -+ protected int chunkOffsetY; -+ protected int chunkOffsetZ; -+ -+ protected int chunkIndexOffset; -+ protected int chunkSectionIndexOffset; -+ -+ protected final boolean skylightPropagator; -+ protected final int emittedLightMask; -+ protected final boolean isClientSide; -+ -+ protected final Level world; -+ protected final int minLightSection; -+ protected final int maxLightSection; -+ protected final int minSection; -+ protected final int maxSection; -+ -+ protected StarLightEngine(final boolean skylightPropagator, final Level world) { -+ this.skylightPropagator = skylightPropagator; -+ this.emittedLightMask = skylightPropagator ? 0 : 0xF; -+ this.isClientSide = world.isClientSide; -+ this.world = world; -+ this.minLightSection = WorldUtil.getMinLightSection(world); -+ this.maxLightSection = WorldUtil.getMaxLightSection(world); -+ this.minSection = WorldUtil.getMinSection(world); -+ this.maxSection = WorldUtil.getMaxSection(world); -+ -+ this.sectionCache = new LevelChunkSection[5 * 5 * ((this.maxLightSection - this.minLightSection + 1) + 2)]; // add two extra sections for buffer -+ this.nibbleCache = new SWMRNibbleArray[5 * 5 * ((this.maxLightSection - this.minLightSection + 1) + 2)]; // add two extra sections for buffer -+ this.notifyUpdateCache = new boolean[5 * 5 * ((this.maxLightSection - this.minLightSection + 1) + 2)]; // add two extra sections for buffer -+ } -+ -+ protected final void setupEncodeOffset(final int centerX, final int centerY, final int centerZ) { -+ // 31 = center + encodeOffset -+ this.encodeOffsetX = 31 - centerX; -+ this.encodeOffsetY = (-(this.minLightSection - 1) << 4); // we want 0 to be the smallest encoded value -+ this.encodeOffsetZ = 31 - centerZ; -+ -+ // coordinateIndex = x | (z << 6) | (y << 12) -+ this.coordinateOffset = this.encodeOffsetX + (this.encodeOffsetZ << 6) + (this.encodeOffsetY << 12); -+ -+ // 2 = (centerX >> 4) + chunkOffset -+ this.chunkOffsetX = 2 - (centerX >> 4); -+ this.chunkOffsetY = -(this.minLightSection - 1); // lowest should be 0 -+ this.chunkOffsetZ = 2 - (centerZ >> 4); -+ -+ // chunk index = x + (5 * z) -+ this.chunkIndexOffset = this.chunkOffsetX + (5 * this.chunkOffsetZ); -+ -+ // chunk section index = x + (5 * z) + ((5*5) * y) -+ this.chunkSectionIndexOffset = this.chunkIndexOffset + ((5 * 5) * this.chunkOffsetY); -+ } -+ -+ protected final void setupCaches(final LightChunkGetter chunkProvider, final int centerX, final int centerY, final int centerZ, -+ final boolean relaxed, final boolean tryToLoadChunksFor2Radius) { -+ final int centerChunkX = centerX >> 4; -+ final int centerChunkY = centerY >> 4; -+ final int centerChunkZ = centerZ >> 4; -+ -+ this.setupEncodeOffset(centerChunkX * 16 + 7, centerChunkY * 16 + 7, centerChunkZ * 16 + 7); -+ -+ final int radius = tryToLoadChunksFor2Radius ? 2 : 1; -+ -+ for (int dz = -radius; dz <= radius; ++dz) { -+ for (int dx = -radius; dx <= radius; ++dx) { -+ final int cx = centerChunkX + dx; -+ final int cz = centerChunkZ + dz; -+ final boolean isTwoRadius = Math.max(IntegerUtil.branchlessAbs(dx), IntegerUtil.branchlessAbs(dz)) == 2; -+ final ChunkAccess chunk = (ChunkAccess)chunkProvider.getChunkForLighting(cx, cz); -+ -+ if (chunk == null) { -+ if (relaxed | isTwoRadius) { -+ continue; -+ } -+ throw new IllegalArgumentException("Trying to propagate light update before 1 radius neighbours ready"); -+ } -+ -+ if (!this.canUseChunk(chunk)) { -+ continue; -+ } -+ -+ this.setChunkInCache(cx, cz, chunk); -+ this.setEmptinessMapCache(cx, cz, this.getEmptinessMap(chunk)); -+ if (!isTwoRadius) { -+ this.setBlocksForChunkInCache(cx, cz, chunk.getSections()); -+ this.setNibblesForChunkInCache(cx, cz, this.getNibblesOnChunk(chunk)); -+ } -+ } -+ } -+ } -+ -+ protected final ChunkAccess getChunkInCache(final int chunkX, final int chunkZ) { -+ return this.chunkCache[chunkX + 5*chunkZ + this.chunkIndexOffset]; -+ } -+ -+ protected final void setChunkInCache(final int chunkX, final int chunkZ, final ChunkAccess chunk) { -+ this.chunkCache[chunkX + 5*chunkZ + this.chunkIndexOffset] = chunk; -+ } -+ -+ protected final LevelChunkSection getChunkSection(final int chunkX, final int chunkY, final int chunkZ) { -+ return this.sectionCache[chunkX + 5*chunkZ + (5 * 5) * chunkY + this.chunkSectionIndexOffset]; -+ } -+ -+ protected final void setChunkSectionInCache(final int chunkX, final int chunkY, final int chunkZ, final LevelChunkSection section) { -+ this.sectionCache[chunkX + 5*chunkZ + 5*5*chunkY + this.chunkSectionIndexOffset] = section; -+ } -+ -+ protected final void setBlocksForChunkInCache(final int chunkX, final int chunkZ, final LevelChunkSection[] sections) { -+ for (int cy = this.minLightSection; cy <= this.maxLightSection; ++cy) { -+ this.setChunkSectionInCache(chunkX, cy, chunkZ, -+ sections == null ? null : (cy >= this.minSection && cy <= this.maxSection ? sections[cy - this.minSection] : null)); -+ } -+ } -+ -+ protected final SWMRNibbleArray getNibbleFromCache(final int chunkX, final int chunkY, final int chunkZ) { -+ return this.nibbleCache[chunkX + 5*chunkZ + (5 * 5) * chunkY + this.chunkSectionIndexOffset]; -+ } -+ -+ protected final SWMRNibbleArray[] getNibblesForChunkFromCache(final int chunkX, final int chunkZ) { -+ final SWMRNibbleArray[] ret = new SWMRNibbleArray[this.maxLightSection - this.minLightSection + 1]; -+ -+ for (int cy = this.minLightSection; cy <= this.maxLightSection; ++cy) { -+ ret[cy - this.minLightSection] = this.nibbleCache[chunkX + 5*chunkZ + (cy * (5 * 5)) + this.chunkSectionIndexOffset]; -+ } -+ -+ return ret; -+ } -+ -+ protected final void setNibbleInCache(final int chunkX, final int chunkY, final int chunkZ, final SWMRNibbleArray nibble) { -+ this.nibbleCache[chunkX + 5*chunkZ + (5 * 5) * chunkY + this.chunkSectionIndexOffset] = nibble; -+ } -+ -+ protected final void setNibblesForChunkInCache(final int chunkX, final int chunkZ, final SWMRNibbleArray[] nibbles) { -+ for (int cy = this.minLightSection; cy <= this.maxLightSection; ++cy) { -+ this.setNibbleInCache(chunkX, cy, chunkZ, nibbles == null ? null : nibbles[cy - this.minLightSection]); -+ } -+ } -+ -+ protected final void updateVisible(final LightChunkGetter lightAccess) { -+ for (int index = 0, max = this.nibbleCache.length; index < max; ++index) { -+ final SWMRNibbleArray nibble = this.nibbleCache[index]; -+ if (!this.notifyUpdateCache[index] && (nibble == null || !nibble.isDirty())) { -+ continue; -+ } -+ -+ final int chunkX = (index % 5) - this.chunkOffsetX; -+ final int chunkZ = ((index / 5) % 5) - this.chunkOffsetZ; -+ final int ySections = (this.maxSection - this.minSection) + 1; -+ final int chunkY = ((index / (5*5)) % (ySections + 2 + 2)) - this.chunkOffsetY; -+ if ((nibble != null && nibble.updateVisible()) || this.notifyUpdateCache[index]) { -+ lightAccess.onLightUpdate(this.skylightPropagator ? LightLayer.SKY : LightLayer.BLOCK, SectionPos.of(chunkX, chunkY, chunkZ)); -+ } -+ } -+ } -+ -+ protected final void destroyCaches() { -+ Arrays.fill(this.sectionCache, null); -+ Arrays.fill(this.nibbleCache, null); -+ Arrays.fill(this.chunkCache, null); -+ Arrays.fill(this.emptinessMapCache, null); -+ if (this.isClientSide) { -+ Arrays.fill(this.notifyUpdateCache, false); -+ } -+ } -+ -+ protected final BlockState getBlockState(final int worldX, final int worldY, final int worldZ) { -+ final LevelChunkSection section = this.sectionCache[(worldX >> 4) + 5 * (worldZ >> 4) + (5 * 5) * (worldY >> 4) + this.chunkSectionIndexOffset]; -+ -+ if (section != null) { -+ return section.hasOnlyAir() ? AIR_BLOCK_STATE : section.getBlockState(worldX & 15, worldY & 15, worldZ & 15); -+ } -+ -+ return AIR_BLOCK_STATE; -+ } -+ -+ protected final BlockState getBlockState(final int sectionIndex, final int localIndex) { -+ final LevelChunkSection section = this.sectionCache[sectionIndex]; -+ -+ if (section != null) { -+ return section.hasOnlyAir() ? AIR_BLOCK_STATE : section.states.get(localIndex); -+ } -+ -+ return AIR_BLOCK_STATE; -+ } -+ -+ protected final int getLightLevel(final int worldX, final int worldY, final int worldZ) { -+ final SWMRNibbleArray nibble = this.nibbleCache[(worldX >> 4) + 5 * (worldZ >> 4) + (5 * 5) * (worldY >> 4) + this.chunkSectionIndexOffset]; -+ -+ return nibble == null ? 0 : nibble.getUpdating((worldX & 15) | ((worldZ & 15) << 4) | ((worldY & 15) << 8)); -+ } -+ -+ protected final int getLightLevel(final int sectionIndex, final int localIndex) { -+ final SWMRNibbleArray nibble = this.nibbleCache[sectionIndex]; -+ -+ return nibble == null ? 0 : nibble.getUpdating(localIndex); -+ } -+ -+ protected final void setLightLevel(final int worldX, final int worldY, final int worldZ, final int level) { -+ final int sectionIndex = (worldX >> 4) + 5 * (worldZ >> 4) + (5 * 5) * (worldY >> 4) + this.chunkSectionIndexOffset; -+ final SWMRNibbleArray nibble = this.nibbleCache[sectionIndex]; -+ -+ if (nibble != null) { -+ nibble.set((worldX & 15) | ((worldZ & 15) << 4) | ((worldY & 15) << 8), level); -+ if (this.isClientSide) { -+ int cx1 = (worldX - 1) >> 4; -+ int cx2 = (worldX + 1) >> 4; -+ int cy1 = (worldY - 1) >> 4; -+ int cy2 = (worldY + 1) >> 4; -+ int cz1 = (worldZ - 1) >> 4; -+ int cz2 = (worldZ + 1) >> 4; -+ for (int x = cx1; x <= cx2; ++x) { -+ for (int y = cy1; y <= cy2; ++y) { -+ for (int z = cz1; z <= cz2; ++z) { -+ this.notifyUpdateCache[x + 5 * z + (5 * 5) * y + this.chunkSectionIndexOffset] = true; -+ } -+ } -+ } -+ } -+ } -+ } -+ -+ protected final void postLightUpdate(final int worldX, final int worldY, final int worldZ) { -+ if (this.isClientSide) { -+ int cx1 = (worldX - 1) >> 4; -+ int cx2 = (worldX + 1) >> 4; -+ int cy1 = (worldY - 1) >> 4; -+ int cy2 = (worldY + 1) >> 4; -+ int cz1 = (worldZ - 1) >> 4; -+ int cz2 = (worldZ + 1) >> 4; -+ for (int x = cx1; x <= cx2; ++x) { -+ for (int y = cy1; y <= cy2; ++y) { -+ for (int z = cz1; z <= cz2; ++z) { -+ this.notifyUpdateCache[x + (5 * z) + (5 * 5 * y) + this.chunkSectionIndexOffset] = true; -+ } -+ } -+ } -+ } -+ } -+ -+ protected final void setLightLevel(final int sectionIndex, final int localIndex, final int worldX, final int worldY, final int worldZ, final int level) { -+ final SWMRNibbleArray nibble = this.nibbleCache[sectionIndex]; -+ -+ if (nibble != null) { -+ nibble.set(localIndex, level); -+ if (this.isClientSide) { -+ int cx1 = (worldX - 1) >> 4; -+ int cx2 = (worldX + 1) >> 4; -+ int cy1 = (worldY - 1) >> 4; -+ int cy2 = (worldY + 1) >> 4; -+ int cz1 = (worldZ - 1) >> 4; -+ int cz2 = (worldZ + 1) >> 4; -+ for (int x = cx1; x <= cx2; ++x) { -+ for (int y = cy1; y <= cy2; ++y) { -+ for (int z = cz1; z <= cz2; ++z) { -+ this.notifyUpdateCache[x + (5 * z) + (5 * 5 * y) + this.chunkSectionIndexOffset] = true; -+ } -+ } -+ } -+ } -+ } -+ } -+ -+ protected final boolean[] getEmptinessMap(final int chunkX, final int chunkZ) { -+ return this.emptinessMapCache[chunkX + 5*chunkZ + this.chunkIndexOffset]; -+ } -+ -+ protected final void setEmptinessMapCache(final int chunkX, final int chunkZ, final boolean[] emptinessMap) { -+ this.emptinessMapCache[chunkX + 5*chunkZ + this.chunkIndexOffset] = emptinessMap; -+ } -+ -+ public static SWMRNibbleArray[] getFilledEmptyLight(final LevelHeightAccessor world) { -+ return getFilledEmptyLight(WorldUtil.getTotalLightSections(world)); -+ } -+ -+ private static SWMRNibbleArray[] getFilledEmptyLight(final int totalLightSections) { -+ final SWMRNibbleArray[] ret = new SWMRNibbleArray[totalLightSections]; -+ -+ for (int i = 0, len = ret.length; i < len; ++i) { -+ ret[i] = new SWMRNibbleArray(null, true); -+ } -+ -+ return ret; -+ } -+ -+ protected abstract boolean[] getEmptinessMap(final ChunkAccess chunk); -+ -+ protected abstract void setEmptinessMap(final ChunkAccess chunk, final boolean[] to); -+ -+ protected abstract SWMRNibbleArray[] getNibblesOnChunk(final ChunkAccess chunk); -+ -+ protected abstract void setNibbles(final ChunkAccess chunk, final SWMRNibbleArray[] to); -+ -+ protected abstract boolean canUseChunk(final ChunkAccess chunk); -+ -+ public final void blocksChangedInChunk(final LightChunkGetter lightAccess, final int chunkX, final int chunkZ, -+ final Set positions, final Boolean[] changedSections) { -+ this.setupCaches(lightAccess, chunkX * 16 + 7, 128, chunkZ * 16 + 7, true, true); -+ try { -+ final ChunkAccess chunk = this.getChunkInCache(chunkX, chunkZ); -+ if (chunk == null) { -+ return; -+ } -+ if (changedSections != null) { -+ final boolean[] ret = this.handleEmptySectionChanges(lightAccess, chunk, changedSections, false); -+ if (ret != null) { -+ this.setEmptinessMap(chunk, ret); -+ } -+ } -+ if (!positions.isEmpty()) { -+ this.propagateBlockChanges(lightAccess, chunk, positions); -+ } -+ this.updateVisible(lightAccess); -+ } finally { -+ this.destroyCaches(); -+ } -+ } -+ -+ // subclasses should not initialise caches, as this will always be done by the super call -+ // subclasses should not invoke updateVisible, as this will always be done by the super call -+ protected abstract void propagateBlockChanges(final LightChunkGetter lightAccess, final ChunkAccess atChunk, final Set positions); -+ -+ protected abstract void checkBlock(final LightChunkGetter lightAccess, final int worldX, final int worldY, final int worldZ); -+ -+ // if ret > expect, then the real value is at least ret (early returns if ret > expect, rather than calculating actual) -+ // if ret == expect, then expect is the correct light value for pos -+ // if ret < expect, then ret is the real light value -+ protected abstract int calculateLightValue(final LightChunkGetter lightAccess, final int worldX, final int worldY, final int worldZ, -+ final int expect); -+ -+ protected final int[] chunkCheckDelayedUpdatesCenter = new int[16 * 16]; -+ protected final int[] chunkCheckDelayedUpdatesNeighbour = new int[16 * 16]; -+ -+ protected void checkChunkEdge(final LightChunkGetter lightAccess, final ChunkAccess chunk, -+ final int chunkX, final int chunkY, final int chunkZ) { -+ final SWMRNibbleArray currNibble = this.getNibbleFromCache(chunkX, chunkY, chunkZ); -+ if (currNibble == null) { -+ return; -+ } -+ -+ for (final AxisDirection direction : ONLY_HORIZONTAL_DIRECTIONS) { -+ final int neighbourOffX = direction.x; -+ final int neighbourOffZ = direction.z; -+ -+ final SWMRNibbleArray neighbourNibble = this.getNibbleFromCache(chunkX + neighbourOffX, -+ chunkY, chunkZ + neighbourOffZ); -+ -+ if (neighbourNibble == null) { -+ continue; -+ } -+ -+ if (!currNibble.isInitialisedUpdating() && !neighbourNibble.isInitialisedUpdating()) { -+ // both are zero, nothing to check. -+ continue; -+ } -+ -+ // this chunk -+ final int incX; -+ final int incZ; -+ final int startX; -+ final int startZ; -+ -+ if (neighbourOffX != 0) { -+ // x direction -+ incX = 0; -+ incZ = 1; -+ -+ if (direction.x < 0) { -+ // negative -+ startX = chunkX << 4; -+ } else { -+ startX = chunkX << 4 | 15; -+ } -+ startZ = chunkZ << 4; -+ } else { -+ // z direction -+ incX = 1; -+ incZ = 0; -+ -+ if (neighbourOffZ < 0) { -+ // negative -+ startZ = chunkZ << 4; -+ } else { -+ startZ = chunkZ << 4 | 15; -+ } -+ startX = chunkX << 4; -+ } -+ -+ int centerDelayedChecks = 0; -+ int neighbourDelayedChecks = 0; -+ for (int currY = chunkY << 4, maxY = currY | 15; currY <= maxY; ++currY) { -+ for (int i = 0, currX = startX, currZ = startZ; i < 16; ++i, currX += incX, currZ += incZ) { -+ final int neighbourX = currX + neighbourOffX; -+ final int neighbourZ = currZ + neighbourOffZ; -+ -+ final int currentIndex = (currX & 15) | -+ ((currZ & 15)) << 4 | -+ ((currY & 15) << 8); -+ final int currentLevel = currNibble.getUpdating(currentIndex); -+ -+ final int neighbourIndex = -+ (neighbourX & 15) | -+ ((neighbourZ & 15)) << 4 | -+ ((currY & 15) << 8); -+ final int neighbourLevel = neighbourNibble.getUpdating(neighbourIndex); -+ -+ // the checks are delayed because the checkBlock method clobbers light values - which then -+ // affect later calculate light value operations. While they don't affect it in a behaviourly significant -+ // way, they do have a negative performance impact due to simply queueing more values -+ -+ if (this.calculateLightValue(lightAccess, currX, currY, currZ, currentLevel) != currentLevel) { -+ this.chunkCheckDelayedUpdatesCenter[centerDelayedChecks++] = currentIndex; -+ } -+ -+ if (this.calculateLightValue(lightAccess, neighbourX, currY, neighbourZ, neighbourLevel) != neighbourLevel) { -+ this.chunkCheckDelayedUpdatesNeighbour[neighbourDelayedChecks++] = neighbourIndex; -+ } -+ } -+ } -+ -+ final int currentChunkOffX = chunkX << 4; -+ final int currentChunkOffZ = chunkZ << 4; -+ final int neighbourChunkOffX = (chunkX + direction.x) << 4; -+ final int neighbourChunkOffZ = (chunkZ + direction.z) << 4; -+ final int chunkOffY = chunkY << 4; -+ for (int i = 0, len = Math.max(centerDelayedChecks, neighbourDelayedChecks); i < len; ++i) { -+ // try to queue neighbouring data together -+ // index = x | (z << 4) | (y << 8) -+ if (i < centerDelayedChecks) { -+ final int value = this.chunkCheckDelayedUpdatesCenter[i]; -+ this.checkBlock(lightAccess, currentChunkOffX | (value & 15), -+ chunkOffY | (value >>> 8), -+ currentChunkOffZ | ((value >>> 4) & 0xF)); -+ } -+ if (i < neighbourDelayedChecks) { -+ final int value = this.chunkCheckDelayedUpdatesNeighbour[i]; -+ this.checkBlock(lightAccess, neighbourChunkOffX | (value & 15), -+ chunkOffY | (value >>> 8), -+ neighbourChunkOffZ | ((value >>> 4) & 0xF)); -+ } -+ } -+ } -+ } -+ -+ protected void checkChunkEdges(final LightChunkGetter lightAccess, final ChunkAccess chunk, final ShortCollection sections) { -+ final ChunkPos chunkPos = chunk.getPos(); -+ final int chunkX = chunkPos.x; -+ final int chunkZ = chunkPos.z; -+ -+ for (final ShortIterator iterator = sections.iterator(); iterator.hasNext();) { -+ this.checkChunkEdge(lightAccess, chunk, chunkX, iterator.nextShort(), chunkZ); -+ } -+ -+ this.performLightDecrease(lightAccess); -+ } -+ -+ // subclasses should not initialise caches, as this will always be done by the super call -+ // subclasses should not invoke updateVisible, as this will always be done by the super call -+ // verifies that light levels on this chunks edges are consistent with this chunk's neighbours -+ // edges. if they are not, they are decreased (effectively performing the logic in checkBlock). -+ // This does not resolve skylight source problems. -+ protected void checkChunkEdges(final LightChunkGetter lightAccess, final ChunkAccess chunk, final int fromSection, final int toSection) { -+ final ChunkPos chunkPos = chunk.getPos(); -+ final int chunkX = chunkPos.x; -+ final int chunkZ = chunkPos.z; -+ -+ for (int currSectionY = toSection; currSectionY >= fromSection; --currSectionY) { -+ this.checkChunkEdge(lightAccess, chunk, chunkX, currSectionY, chunkZ); -+ } -+ -+ this.performLightDecrease(lightAccess); -+ } -+ -+ // pulls light from neighbours, and adds them into the increase queue. does not actually propagate. -+ protected final void propagateNeighbourLevels(final LightChunkGetter lightAccess, final ChunkAccess chunk, final int fromSection, final int toSection) { -+ final ChunkPos chunkPos = chunk.getPos(); -+ final int chunkX = chunkPos.x; -+ final int chunkZ = chunkPos.z; -+ -+ for (int currSectionY = toSection; currSectionY >= fromSection; --currSectionY) { -+ final SWMRNibbleArray currNibble = this.getNibbleFromCache(chunkX, currSectionY, chunkZ); -+ if (currNibble == null) { -+ continue; -+ } -+ for (final AxisDirection direction : ONLY_HORIZONTAL_DIRECTIONS) { -+ final int neighbourOffX = direction.x; -+ final int neighbourOffZ = direction.z; -+ -+ final SWMRNibbleArray neighbourNibble = this.getNibbleFromCache(chunkX + neighbourOffX, -+ currSectionY, chunkZ + neighbourOffZ); -+ -+ if (neighbourNibble == null || !neighbourNibble.isInitialisedUpdating()) { -+ // can't pull from 0 -+ continue; -+ } -+ -+ // neighbour chunk -+ final int incX; -+ final int incZ; -+ final int startX; -+ final int startZ; -+ -+ if (neighbourOffX != 0) { -+ // x direction -+ incX = 0; -+ incZ = 1; -+ -+ if (direction.x < 0) { -+ // negative -+ startX = (chunkX << 4) - 1; -+ } else { -+ startX = (chunkX << 4) + 16; -+ } -+ startZ = chunkZ << 4; -+ } else { -+ // z direction -+ incX = 1; -+ incZ = 0; -+ -+ if (neighbourOffZ < 0) { -+ // negative -+ startZ = (chunkZ << 4) - 1; -+ } else { -+ startZ = (chunkZ << 4) + 16; -+ } -+ startX = chunkX << 4; -+ } -+ -+ final long propagateDirection = 1L << direction.getOpposite().ordinal(); // we only want to check in this direction towards this chunk -+ final int encodeOffset = this.coordinateOffset; -+ -+ for (int currY = currSectionY << 4, maxY = currY | 15; currY <= maxY; ++currY) { -+ for (int i = 0, currX = startX, currZ = startZ; i < 16; ++i, currX += incX, currZ += incZ) { -+ final int level = neighbourNibble.getUpdating( -+ (currX & 15) -+ | ((currZ & 15) << 4) -+ | ((currY & 15) << 8) -+ ); -+ -+ if (level <= 1) { -+ // nothing to propagate -+ continue; -+ } -+ -+ this.appendToIncreaseQueue( -+ ((currX + (currZ << 6) + (currY << (6 + 6)) + encodeOffset) & ((1L << (6 + 6 + 16)) - 1)) -+ | ((level & 0xFL) << (6 + 6 + 16)) -+ | (propagateDirection << (6 + 6 + 16 + 4)) -+ | FLAG_HAS_SIDED_TRANSPARENT_BLOCKS // don't know if the current block is transparent, must check. -+ ); -+ } -+ } -+ } -+ } -+ } -+ -+ public static Boolean[] getEmptySectionsForChunk(final ChunkAccess chunk) { -+ final LevelChunkSection[] sections = chunk.getSections(); -+ final Boolean[] ret = new Boolean[sections.length]; -+ -+ for (int i = 0; i < sections.length; ++i) { -+ if (sections[i] == null || sections[i].hasOnlyAir()) { -+ ret[i] = Boolean.TRUE; -+ } else { -+ ret[i] = Boolean.FALSE; -+ } -+ } -+ -+ return ret; -+ } -+ -+ public final void forceHandleEmptySectionChanges(final LightChunkGetter lightAccess, final ChunkAccess chunk, final Boolean[] emptinessChanges) { -+ final int chunkX = chunk.getPos().x; -+ final int chunkZ = chunk.getPos().z; -+ this.setupCaches(lightAccess, chunkX * 16 + 7, 128, chunkZ * 16 + 7, true, true); -+ try { -+ // force current chunk into cache -+ this.setChunkInCache(chunkX, chunkZ, chunk); -+ this.setBlocksForChunkInCache(chunkX, chunkZ, chunk.getSections()); -+ this.setNibblesForChunkInCache(chunkX, chunkZ, this.getNibblesOnChunk(chunk)); -+ this.setEmptinessMapCache(chunkX, chunkZ, this.getEmptinessMap(chunk)); -+ -+ final boolean[] ret = this.handleEmptySectionChanges(lightAccess, chunk, emptinessChanges, false); -+ if (ret != null) { -+ this.setEmptinessMap(chunk, ret); -+ } -+ this.updateVisible(lightAccess); -+ } finally { -+ this.destroyCaches(); -+ } -+ } -+ -+ public final void handleEmptySectionChanges(final LightChunkGetter lightAccess, final int chunkX, final int chunkZ, -+ final Boolean[] emptinessChanges) { -+ this.setupCaches(lightAccess, chunkX * 16 + 7, 128, chunkZ * 16 + 7, true, true); -+ try { -+ final ChunkAccess chunk = this.getChunkInCache(chunkX, chunkZ); -+ if (chunk == null) { -+ return; -+ } -+ final boolean[] ret = this.handleEmptySectionChanges(lightAccess, chunk, emptinessChanges, false); -+ if (ret != null) { -+ this.setEmptinessMap(chunk, ret); -+ } -+ this.updateVisible(lightAccess); -+ } finally { -+ this.destroyCaches(); -+ } -+ } -+ -+ protected abstract void initNibble(final int chunkX, final int chunkY, final int chunkZ, final boolean extrude, final boolean initRemovedNibbles); -+ -+ protected abstract void setNibbleNull(final int chunkX, final int chunkY, final int chunkZ); -+ -+ // subclasses should not initialise caches, as this will always be done by the super call -+ // subclasses should not invoke updateVisible, as this will always be done by the super call -+ // subclasses are guaranteed that this is always called before a changed block set -+ // newChunk specifies whether the changes describe a "first load" of a chunk or changes to existing, already loaded chunks -+ // rets non-null when the emptiness map changed and needs to be updated -+ protected final boolean[] handleEmptySectionChanges(final LightChunkGetter lightAccess, final ChunkAccess chunk, -+ final Boolean[] emptinessChanges, final boolean unlit) { -+ final Level world = (Level)lightAccess.getLevel(); -+ final int chunkX = chunk.getPos().x; -+ final int chunkZ = chunk.getPos().z; -+ -+ boolean[] chunkEmptinessMap = this.getEmptinessMap(chunkX, chunkZ); -+ boolean[] ret = null; -+ final boolean needsInit = unlit || chunkEmptinessMap == null; -+ if (needsInit) { -+ this.setEmptinessMapCache(chunkX, chunkZ, ret = chunkEmptinessMap = new boolean[WorldUtil.getTotalSections(world)]); -+ } -+ -+ // update emptiness map -+ for (int sectionIndex = (emptinessChanges.length - 1); sectionIndex >= 0; --sectionIndex) { -+ Boolean valueBoxed = emptinessChanges[sectionIndex]; -+ if (valueBoxed == null) { -+ if (!needsInit) { -+ continue; -+ } -+ final LevelChunkSection section = this.getChunkSection(chunkX, sectionIndex + this.minSection, chunkZ); -+ emptinessChanges[sectionIndex] = valueBoxed = section == null || section.hasOnlyAir() ? Boolean.TRUE : Boolean.FALSE; -+ } -+ chunkEmptinessMap[sectionIndex] = valueBoxed.booleanValue(); -+ } -+ -+ // now init neighbour nibbles -+ for (int sectionIndex = (emptinessChanges.length - 1); sectionIndex >= 0; --sectionIndex) { -+ final Boolean valueBoxed = emptinessChanges[sectionIndex]; -+ final int sectionY = sectionIndex + this.minSection; -+ if (valueBoxed == null) { -+ continue; -+ } -+ -+ final boolean empty = valueBoxed.booleanValue(); -+ -+ if (empty) { -+ continue; -+ } -+ -+ for (int dz = -1; dz <= 1; ++dz) { -+ for (int dx = -1; dx <= 1; ++dx) { -+ // if we're not empty, we also need to initialise nibbles -+ // note: if we're unlit, we absolutely do not want to extrude, as light data isn't set up -+ final boolean extrude = (dx | dz) != 0 || !unlit; -+ for (int dy = 1; dy >= -1; --dy) { -+ this.initNibble(dx + chunkX, dy + sectionY, dz + chunkZ, extrude, false); -+ } -+ } -+ } -+ } -+ -+ // check for de-init and lazy-init -+ // lazy init is when chunks are being lit, so at the time they weren't loaded when their neighbours were running -+ // init checks. -+ for (int dz = -1; dz <= 1; ++dz) { -+ for (int dx = -1; dx <= 1; ++dx) { -+ // does this neighbour have 1 radius loaded? -+ boolean neighboursLoaded = true; -+ neighbour_loaded_search: -+ for (int dz2 = -1; dz2 <= 1; ++dz2) { -+ for (int dx2 = -1; dx2 <= 1; ++dx2) { -+ if (this.getEmptinessMap(dx + dx2 + chunkX, dz + dz2 + chunkZ) == null) { -+ neighboursLoaded = false; -+ break neighbour_loaded_search; -+ } -+ } -+ } -+ -+ for (int sectionY = this.maxLightSection; sectionY >= this.minLightSection; --sectionY) { -+ // check neighbours to see if we need to de-init this one -+ boolean allEmpty = true; -+ neighbour_search: -+ for (int dy2 = -1; dy2 <= 1; ++dy2) { -+ for (int dz2 = -1; dz2 <= 1; ++dz2) { -+ for (int dx2 = -1; dx2 <= 1; ++dx2) { -+ final int y = sectionY + dy2; -+ if (y < this.minSection || y > this.maxSection) { -+ // empty -+ continue; -+ } -+ final boolean[] emptinessMap = this.getEmptinessMap(dx + dx2 + chunkX, dz + dz2 + chunkZ); -+ if (emptinessMap != null) { -+ if (!emptinessMap[y - this.minSection]) { -+ allEmpty = false; -+ break neighbour_search; -+ } -+ } else { -+ final LevelChunkSection section = this.getChunkSection(dx + dx2 + chunkX, y, dz + dz2 + chunkZ); -+ if (section != null && !section.hasOnlyAir()) { -+ allEmpty = false; -+ break neighbour_search; -+ } -+ } -+ } -+ } -+ } -+ -+ if (allEmpty & neighboursLoaded) { -+ // can only de-init when neighbours are loaded -+ // de-init is fine to delay, as de-init is just an optimisation - it's not required for lighting -+ // to be correct -+ -+ // all were empty, so de-init -+ this.setNibbleNull(dx + chunkX, sectionY, dz + chunkZ); -+ } else if (!allEmpty) { -+ // must init -+ final boolean extrude = (dx | dz) != 0 || !unlit; -+ this.initNibble(dx + chunkX, sectionY, dz + chunkZ, extrude, false); -+ } -+ } -+ } -+ } -+ -+ return ret; -+ } -+ -+ public final void checkChunkEdges(final LightChunkGetter lightAccess, final int chunkX, final int chunkZ) { -+ this.setupCaches(lightAccess, chunkX * 16 + 7, 128, chunkZ * 16 + 7, true, false); -+ try { -+ final ChunkAccess chunk = this.getChunkInCache(chunkX, chunkZ); -+ if (chunk == null) { -+ return; -+ } -+ this.checkChunkEdges(lightAccess, chunk, this.minLightSection, this.maxLightSection); -+ this.updateVisible(lightAccess); -+ } finally { -+ this.destroyCaches(); -+ } -+ } -+ -+ public final void checkChunkEdges(final LightChunkGetter lightAccess, final int chunkX, final int chunkZ, final ShortCollection sections) { -+ this.setupCaches(lightAccess, chunkX * 16 + 7, 128, chunkZ * 16 + 7, true, false); -+ try { -+ final ChunkAccess chunk = this.getChunkInCache(chunkX, chunkZ); -+ if (chunk == null) { -+ return; -+ } -+ this.checkChunkEdges(lightAccess, chunk, sections); -+ this.updateVisible(lightAccess); -+ } finally { -+ this.destroyCaches(); -+ } -+ } -+ -+ // subclasses should not initialise caches, as this will always be done by the super call -+ // subclasses should not invoke updateVisible, as this will always be done by the super call -+ // needsEdgeChecks applies when possibly loading vanilla data, which means we need to validate the current -+ // chunks light values with respect to neighbours -+ // subclasses should note that the emptiness changes are propagated BEFORE this is called, so this function -+ // does not need to detect empty chunks itself (and it should do no handling for them either!) -+ protected abstract void lightChunk(final LightChunkGetter lightAccess, final ChunkAccess chunk, final boolean needsEdgeChecks); -+ -+ public final void light(final LightChunkGetter lightAccess, final ChunkAccess chunk, final Boolean[] emptySections) { -+ final int chunkX = chunk.getPos().x; -+ final int chunkZ = chunk.getPos().z; -+ this.setupCaches(lightAccess, chunkX * 16 + 7, 128, chunkZ * 16 + 7, true, true); -+ -+ try { -+ final SWMRNibbleArray[] nibbles = getFilledEmptyLight(this.maxLightSection - this.minLightSection + 1); -+ // force current chunk into cache -+ this.setChunkInCache(chunkX, chunkZ, chunk); -+ this.setBlocksForChunkInCache(chunkX, chunkZ, chunk.getSections()); -+ this.setNibblesForChunkInCache(chunkX, chunkZ, nibbles); -+ this.setEmptinessMapCache(chunkX, chunkZ, this.getEmptinessMap(chunk)); -+ -+ final boolean[] ret = this.handleEmptySectionChanges(lightAccess, chunk, emptySections, true); -+ if (ret != null) { -+ this.setEmptinessMap(chunk, ret); -+ } -+ this.lightChunk(lightAccess, chunk, true); -+ this.setNibbles(chunk, nibbles); -+ this.updateVisible(lightAccess); -+ } finally { -+ this.destroyCaches(); -+ } -+ } -+ -+ public final void relightChunks(final LightChunkGetter lightAccess, final Set chunks, -+ final Consumer chunkLightCallback, final IntConsumer onComplete) { -+ // it's recommended for maximum performance that the set is ordered according to a BFS from the center of -+ // the region of chunks to relight -+ // it's required that tickets are added for each chunk to keep them loaded -+ final Long2ObjectOpenHashMap nibblesByChunk = new Long2ObjectOpenHashMap<>(); -+ final Long2ObjectOpenHashMap emptinessMapByChunk = new Long2ObjectOpenHashMap<>(); -+ -+ final int[] neighbourLightOrder = new int[] { -+ // d = 0 -+ 0, 0, -+ // d = 1 -+ -1, 0, -+ 0, -1, -+ 1, 0, -+ 0, 1, -+ // d = 2 -+ -1, 1, -+ 1, 1, -+ -1, -1, -+ 1, -1, -+ }; -+ -+ int lightCalls = 0; -+ -+ for (final ChunkPos chunkPos : chunks) { -+ final int chunkX = chunkPos.x; -+ final int chunkZ = chunkPos.z; -+ final ChunkAccess chunk = (ChunkAccess)lightAccess.getChunkForLighting(chunkX, chunkZ); -+ if (chunk == null || !this.canUseChunk(chunk)) { -+ throw new IllegalStateException(); -+ } -+ -+ for (int i = 0, len = neighbourLightOrder.length; i < len; i += 2) { -+ final int dx = neighbourLightOrder[i]; -+ final int dz = neighbourLightOrder[i + 1]; -+ final int neighbourX = dx + chunkX; -+ final int neighbourZ = dz + chunkZ; -+ -+ final ChunkAccess neighbour = (ChunkAccess)lightAccess.getChunkForLighting(neighbourX, neighbourZ); -+ if (neighbour == null || !this.canUseChunk(neighbour)) { -+ continue; -+ } -+ -+ if (nibblesByChunk.get(CoordinateUtils.getChunkKey(neighbourX, neighbourZ)) != null) { -+ // lit already called for neighbour, no need to light it now -+ continue; -+ } -+ -+ // light neighbour chunk -+ this.setupEncodeOffset(neighbourX * 16 + 7, 128, neighbourZ * 16 + 7); -+ try { -+ // insert all neighbouring chunks for this neighbour that we have data for -+ for (int dz2 = -1; dz2 <= 1; ++dz2) { -+ for (int dx2 = -1; dx2 <= 1; ++dx2) { -+ final int neighbourX2 = neighbourX + dx2; -+ final int neighbourZ2 = neighbourZ + dz2; -+ final long key = CoordinateUtils.getChunkKey(neighbourX2, neighbourZ2); -+ final ChunkAccess neighbour2 = (ChunkAccess)lightAccess.getChunkForLighting(neighbourX2, neighbourZ2); -+ if (neighbour2 == null || !this.canUseChunk(neighbour2)) { -+ continue; -+ } -+ -+ final SWMRNibbleArray[] nibbles = nibblesByChunk.get(key); -+ if (nibbles == null) { -+ // we haven't lit this chunk -+ continue; -+ } -+ -+ this.setChunkInCache(neighbourX2, neighbourZ2, neighbour2); -+ this.setBlocksForChunkInCache(neighbourX2, neighbourZ2, neighbour2.getSections()); -+ this.setNibblesForChunkInCache(neighbourX2, neighbourZ2, nibbles); -+ this.setEmptinessMapCache(neighbourX2, neighbourZ2, emptinessMapByChunk.get(key)); -+ } -+ } -+ -+ final long key = CoordinateUtils.getChunkKey(neighbourX, neighbourZ); -+ -+ // now insert the neighbour chunk and light it -+ final SWMRNibbleArray[] nibbles = getFilledEmptyLight(this.world); -+ nibblesByChunk.put(key, nibbles); -+ -+ this.setChunkInCache(neighbourX, neighbourZ, neighbour); -+ this.setBlocksForChunkInCache(neighbourX, neighbourZ, neighbour.getSections()); -+ this.setNibblesForChunkInCache(neighbourX, neighbourZ, nibbles); -+ -+ final boolean[] neighbourEmptiness = this.handleEmptySectionChanges(lightAccess, neighbour, getEmptySectionsForChunk(neighbour), true); -+ emptinessMapByChunk.put(key, neighbourEmptiness); -+ if (chunks.contains(new ChunkPos(neighbourX, neighbourZ))) { -+ this.setEmptinessMap(neighbour, neighbourEmptiness); -+ } -+ -+ this.lightChunk(lightAccess, neighbour, false); -+ } finally { -+ this.destroyCaches(); -+ } -+ } -+ -+ // done lighting all neighbours, so the chunk is now fully lit -+ -+ // make sure nibbles are fully updated before calling back -+ final SWMRNibbleArray[] nibbles = nibblesByChunk.get(CoordinateUtils.getChunkKey(chunkX, chunkZ)); -+ for (final SWMRNibbleArray nibble : nibbles) { -+ nibble.updateVisible(); -+ } -+ -+ this.setNibbles(chunk, nibbles); -+ -+ for (int y = this.minLightSection; y <= this.maxLightSection; ++y) { -+ lightAccess.onLightUpdate(this.skylightPropagator ? LightLayer.SKY : LightLayer.BLOCK, SectionPos.of(chunkX, y, chunkX)); -+ } -+ -+ // now do callback -+ if (chunkLightCallback != null) { -+ chunkLightCallback.accept(chunkPos); -+ } -+ ++lightCalls; -+ } -+ -+ if (onComplete != null) { -+ onComplete.accept(lightCalls); -+ } -+ } -+ -+ // contains: -+ // lower (6 + 6 + 16) = 28 bits: encoded coordinate position (x | (z << 6) | (y << (6 + 6)))) -+ // next 4 bits: propagated light level (0, 15] -+ // next 6 bits: propagation direction bitset -+ // next 24 bits: unused -+ // last 3 bits: state flags -+ // state flags: -+ // whether the increase propagator needs to write the propagated level to the position, used to avoid cascading light -+ // updates for block sources -+ protected static final long FLAG_WRITE_LEVEL = Long.MIN_VALUE >>> 2; -+ // whether the propagation needs to check if its current level is equal to the expected level -+ // used only in increase propagation -+ protected static final long FLAG_RECHECK_LEVEL = Long.MIN_VALUE >>> 1; -+ // whether the propagation needs to consider if its block is conditionally transparent -+ protected static final long FLAG_HAS_SIDED_TRANSPARENT_BLOCKS = Long.MIN_VALUE; -+ -+ protected long[] increaseQueue = new long[16 * 16 * 16]; -+ protected int increaseQueueInitialLength; -+ protected long[] decreaseQueue = new long[16 * 16 * 16]; -+ protected int decreaseQueueInitialLength; -+ -+ protected final long[] resizeIncreaseQueue() { -+ return this.increaseQueue = Arrays.copyOf(this.increaseQueue, this.increaseQueue.length * 2); -+ } -+ -+ protected final long[] resizeDecreaseQueue() { -+ return this.decreaseQueue = Arrays.copyOf(this.decreaseQueue, this.decreaseQueue.length * 2); -+ } -+ -+ protected final void appendToIncreaseQueue(final long value) { -+ final int idx = this.increaseQueueInitialLength++; -+ long[] queue = this.increaseQueue; -+ if (idx >= queue.length) { -+ queue = this.resizeIncreaseQueue(); -+ queue[idx] = value; -+ } else { -+ queue[idx] = value; -+ } -+ } -+ -+ protected final void appendToDecreaseQueue(final long value) { -+ final int idx = this.decreaseQueueInitialLength++; -+ long[] queue = this.decreaseQueue; -+ if (idx >= queue.length) { -+ queue = this.resizeDecreaseQueue(); -+ queue[idx] = value; -+ } else { -+ queue[idx] = value; -+ } -+ } -+ -+ protected static final AxisDirection[][] OLD_CHECK_DIRECTIONS = new AxisDirection[1 << 6][]; -+ protected static final int ALL_DIRECTIONS_BITSET = (1 << 6) - 1; -+ static { -+ for (int i = 0; i < OLD_CHECK_DIRECTIONS.length; ++i) { -+ final List directions = new ArrayList<>(); -+ for (int bitset = i, len = Integer.bitCount(i), index = 0; index < len; ++index, bitset ^= IntegerUtil.getTrailingBit(bitset)) { -+ directions.add(AXIS_DIRECTIONS[IntegerUtil.trailingZeros(bitset)]); -+ } -+ OLD_CHECK_DIRECTIONS[i] = directions.toArray(new AxisDirection[0]); -+ } -+ } -+ -+ protected final void performLightIncrease(final LightChunkGetter lightAccess) { -+ final BlockGetter world = lightAccess.getLevel(); -+ long[] queue = this.increaseQueue; -+ int queueReadIndex = 0; -+ int queueLength = this.increaseQueueInitialLength; -+ this.increaseQueueInitialLength = 0; -+ final int decodeOffsetX = -this.encodeOffsetX; -+ final int decodeOffsetY = -this.encodeOffsetY; -+ final int decodeOffsetZ = -this.encodeOffsetZ; -+ final int encodeOffset = this.coordinateOffset; -+ final int sectionOffset = this.chunkSectionIndexOffset; -+ -+ while (queueReadIndex < queueLength) { -+ final long queueValue = queue[queueReadIndex++]; -+ -+ final int posX = ((int)queueValue & 63) + decodeOffsetX; -+ final int posZ = (((int)queueValue >>> 6) & 63) + decodeOffsetZ; -+ final int posY = (((int)queueValue >>> 12) & ((1 << 16) - 1)) + decodeOffsetY; -+ final int propagatedLightLevel = (int)((queueValue >>> (6 + 6 + 16)) & 0xFL); -+ final AxisDirection[] checkDirections = OLD_CHECK_DIRECTIONS[(int)((queueValue >>> (6 + 6 + 16 + 4)) & 63L)]; -+ -+ if ((queueValue & FLAG_RECHECK_LEVEL) != 0L) { -+ if (this.getLightLevel(posX, posY, posZ) != propagatedLightLevel) { -+ // not at the level we expect, so something changed. -+ continue; -+ } -+ } else if ((queueValue & FLAG_WRITE_LEVEL) != 0L) { -+ // these are used to restore block sources after a propagation decrease -+ this.setLightLevel(posX, posY, posZ, propagatedLightLevel); -+ } -+ -+ if ((queueValue & FLAG_HAS_SIDED_TRANSPARENT_BLOCKS) == 0L) { -+ // we don't need to worry about our state here. -+ for (final AxisDirection propagate : checkDirections) { -+ final int offX = posX + propagate.x; -+ final int offY = posY + propagate.y; -+ final int offZ = posZ + propagate.z; -+ -+ final int sectionIndex = (offX >> 4) + 5 * (offZ >> 4) + (5 * 5) * (offY >> 4) + sectionOffset; -+ final int localIndex = (offX & 15) | ((offZ & 15) << 4) | ((offY & 15) << 8); -+ -+ final SWMRNibbleArray currentNibble = this.nibbleCache[sectionIndex]; -+ final int currentLevel; -+ if (currentNibble == null || (currentLevel = currentNibble.getUpdating(localIndex)) >= (propagatedLightLevel - 1)) { -+ continue; // already at the level we want or unloaded -+ } -+ -+ final BlockState blockState = this.getBlockState(sectionIndex, localIndex); -+ if (blockState == null) { -+ continue; -+ } -+ final int opacityCached = blockState.getOpacityIfCached(); -+ if (opacityCached != -1) { -+ final int targetLevel = propagatedLightLevel - Math.max(1, opacityCached); -+ if (targetLevel > currentLevel) { -+ currentNibble.set(localIndex, targetLevel); -+ this.postLightUpdate(offX, offY, offZ); -+ -+ if (targetLevel > 1) { -+ if (queueLength >= queue.length) { -+ queue = this.resizeIncreaseQueue(); -+ } -+ queue[queueLength++] = -+ ((offX + (offZ << 6) + (offY << 12) + encodeOffset) & ((1L << (6 + 6 + 16)) - 1)) -+ | ((targetLevel & 0xFL) << (6 + 6 + 16)) -+ | (propagate.everythingButTheOppositeDirection << (6 + 6 + 16 + 4)); -+ continue; -+ } -+ } -+ continue; -+ } else { -+ this.mutablePos1.set(offX, offY, offZ); -+ long flags = 0; -+ if (blockState.isConditionallyFullOpaque()) { -+ final VoxelShape cullingFace = blockState.getFaceOcclusionShape(world, this.mutablePos1, propagate.getOpposite().nms); -+ -+ if (Shapes.faceShapeOccludes(Shapes.empty(), cullingFace)) { -+ continue; -+ } -+ flags |= FLAG_HAS_SIDED_TRANSPARENT_BLOCKS; -+ } -+ -+ final int opacity = blockState.getLightBlock(world, this.mutablePos1); -+ final int targetLevel = propagatedLightLevel - Math.max(1, opacity); -+ if (targetLevel <= currentLevel) { -+ continue; -+ } -+ -+ currentNibble.set(localIndex, targetLevel); -+ this.postLightUpdate(offX, offY, offZ); -+ -+ if (targetLevel > 1) { -+ if (queueLength >= queue.length) { -+ queue = this.resizeIncreaseQueue(); -+ } -+ queue[queueLength++] = -+ ((offX + (offZ << 6) + (offY << 12) + encodeOffset) & ((1L << (6 + 6 + 16)) - 1)) -+ | ((targetLevel & 0xFL) << (6 + 6 + 16)) -+ | (propagate.everythingButTheOppositeDirection << (6 + 6 + 16 + 4)) -+ | (flags); -+ } -+ continue; -+ } -+ } -+ } else { -+ // we actually need to worry about our state here -+ final BlockState fromBlock = this.getBlockState(posX, posY, posZ); -+ this.mutablePos2.set(posX, posY, posZ); -+ for (final AxisDirection propagate : checkDirections) { -+ final int offX = posX + propagate.x; -+ final int offY = posY + propagate.y; -+ final int offZ = posZ + propagate.z; -+ -+ final VoxelShape fromShape = fromBlock.isConditionallyFullOpaque() ? fromBlock.getFaceOcclusionShape(world, this.mutablePos2, propagate.nms) : Shapes.empty(); -+ -+ if (fromShape != Shapes.empty() && Shapes.faceShapeOccludes(Shapes.empty(), fromShape)) { -+ continue; -+ } -+ -+ final int sectionIndex = (offX >> 4) + 5 * (offZ >> 4) + (5 * 5) * (offY >> 4) + sectionOffset; -+ final int localIndex = (offX & 15) | ((offZ & 15) << 4) | ((offY & 15) << 8); -+ -+ final SWMRNibbleArray currentNibble = this.nibbleCache[sectionIndex]; -+ final int currentLevel; -+ -+ if (currentNibble == null || (currentLevel = currentNibble.getUpdating(localIndex)) >= (propagatedLightLevel - 1)) { -+ continue; // already at the level we want -+ } -+ -+ final BlockState blockState = this.getBlockState(sectionIndex, localIndex); -+ if (blockState == null) { -+ continue; -+ } -+ final int opacityCached = blockState.getOpacityIfCached(); -+ if (opacityCached != -1) { -+ final int targetLevel = propagatedLightLevel - Math.max(1, opacityCached); -+ if (targetLevel > currentLevel) { -+ currentNibble.set(localIndex, targetLevel); -+ this.postLightUpdate(offX, offY, offZ); -+ -+ if (targetLevel > 1) { -+ if (queueLength >= queue.length) { -+ queue = this.resizeIncreaseQueue(); -+ } -+ queue[queueLength++] = -+ ((offX + (offZ << 6) + (offY << 12) + encodeOffset) & ((1L << (6 + 6 + 16)) - 1)) -+ | ((targetLevel & 0xFL) << (6 + 6 + 16)) -+ | (propagate.everythingButTheOppositeDirection << (6 + 6 + 16 + 4)); -+ continue; -+ } -+ } -+ continue; -+ } else { -+ this.mutablePos1.set(offX, offY, offZ); -+ long flags = 0; -+ if (blockState.isConditionallyFullOpaque()) { -+ final VoxelShape cullingFace = blockState.getFaceOcclusionShape(world, this.mutablePos1, propagate.getOpposite().nms); -+ -+ if (Shapes.faceShapeOccludes(fromShape, cullingFace)) { -+ continue; -+ } -+ flags |= FLAG_HAS_SIDED_TRANSPARENT_BLOCKS; -+ } -+ -+ final int opacity = blockState.getLightBlock(world, this.mutablePos1); -+ final int targetLevel = propagatedLightLevel - Math.max(1, opacity); -+ if (targetLevel <= currentLevel) { -+ continue; -+ } -+ -+ currentNibble.set(localIndex, targetLevel); -+ this.postLightUpdate(offX, offY, offZ); -+ -+ if (targetLevel > 1) { -+ if (queueLength >= queue.length) { -+ queue = this.resizeIncreaseQueue(); -+ } -+ queue[queueLength++] = -+ ((offX + (offZ << 6) + (offY << 12) + encodeOffset) & ((1L << (6 + 6 + 16)) - 1)) -+ | ((targetLevel & 0xFL) << (6 + 6 + 16)) -+ | (propagate.everythingButTheOppositeDirection << (6 + 6 + 16 + 4)) -+ | (flags); -+ } -+ continue; -+ } -+ } -+ } -+ } -+ } -+ -+ protected final void performLightDecrease(final LightChunkGetter lightAccess) { -+ final BlockGetter world = lightAccess.getLevel(); -+ long[] queue = this.decreaseQueue; -+ long[] increaseQueue = this.increaseQueue; -+ int queueReadIndex = 0; -+ int queueLength = this.decreaseQueueInitialLength; -+ this.decreaseQueueInitialLength = 0; -+ int increaseQueueLength = this.increaseQueueInitialLength; -+ final int decodeOffsetX = -this.encodeOffsetX; -+ final int decodeOffsetY = -this.encodeOffsetY; -+ final int decodeOffsetZ = -this.encodeOffsetZ; -+ final int encodeOffset = this.coordinateOffset; -+ final int sectionOffset = this.chunkSectionIndexOffset; -+ final int emittedMask = this.emittedLightMask; -+ -+ while (queueReadIndex < queueLength) { -+ final long queueValue = queue[queueReadIndex++]; -+ -+ final int posX = ((int)queueValue & 63) + decodeOffsetX; -+ final int posZ = (((int)queueValue >>> 6) & 63) + decodeOffsetZ; -+ final int posY = (((int)queueValue >>> 12) & ((1 << 16) - 1)) + decodeOffsetY; -+ final int propagatedLightLevel = (int)((queueValue >>> (6 + 6 + 16)) & 0xF); -+ final AxisDirection[] checkDirections = OLD_CHECK_DIRECTIONS[(int)((queueValue >>> (6 + 6 + 16 + 4)) & 63)]; -+ -+ if ((queueValue & FLAG_HAS_SIDED_TRANSPARENT_BLOCKS) == 0L) { -+ // we don't need to worry about our state here. -+ for (final AxisDirection propagate : checkDirections) { -+ final int offX = posX + propagate.x; -+ final int offY = posY + propagate.y; -+ final int offZ = posZ + propagate.z; -+ -+ final int sectionIndex = (offX >> 4) + 5 * (offZ >> 4) + (5 * 5) * (offY >> 4) + sectionOffset; -+ final int localIndex = (offX & 15) | ((offZ & 15) << 4) | ((offY & 15) << 8); -+ -+ final SWMRNibbleArray currentNibble = this.nibbleCache[sectionIndex]; -+ final int lightLevel; -+ -+ if (currentNibble == null || (lightLevel = currentNibble.getUpdating(localIndex)) == 0) { -+ // already at lowest (or unloaded), nothing we can do -+ continue; -+ } -+ -+ final BlockState blockState = this.getBlockState(sectionIndex, localIndex); -+ if (blockState == null) { -+ continue; -+ } -+ final int opacityCached = blockState.getOpacityIfCached(); -+ if (opacityCached != -1) { -+ final int targetLevel = Math.max(0, propagatedLightLevel - Math.max(1, opacityCached)); -+ if (lightLevel > targetLevel) { -+ // it looks like another source propagated here, so re-propagate it -+ if (increaseQueueLength >= increaseQueue.length) { -+ increaseQueue = this.resizeIncreaseQueue(); -+ } -+ increaseQueue[increaseQueueLength++] = -+ ((offX + (offZ << 6) + (offY << 12) + encodeOffset) & ((1L << (6 + 6 + 16)) - 1)) -+ | ((lightLevel & 0xFL) << (6 + 6 + 16)) -+ | (((long)ALL_DIRECTIONS_BITSET) << (6 + 6 + 16 + 4)) -+ | FLAG_RECHECK_LEVEL; -+ continue; -+ } -+ final int emittedLight = blockState.getLightEmission() & emittedMask; -+ if (emittedLight != 0) { -+ // re-propagate source -+ // note: do not set recheck level, or else the propagation will fail -+ if (increaseQueueLength >= increaseQueue.length) { -+ increaseQueue = this.resizeIncreaseQueue(); -+ } -+ increaseQueue[increaseQueueLength++] = -+ ((offX + (offZ << 6) + (offY << 12) + encodeOffset) & ((1L << (6 + 6 + 16)) - 1)) -+ | ((emittedLight & 0xFL) << (6 + 6 + 16)) -+ | (((long)ALL_DIRECTIONS_BITSET) << (6 + 6 + 16 + 4)) -+ | (blockState.isConditionallyFullOpaque() ? (FLAG_WRITE_LEVEL | FLAG_HAS_SIDED_TRANSPARENT_BLOCKS) : FLAG_WRITE_LEVEL); -+ } -+ -+ currentNibble.set(localIndex, 0); -+ this.postLightUpdate(offX, offY, offZ); -+ -+ if (targetLevel > 0) { // we actually need to propagate 0 just in case we find a neighbour... -+ if (queueLength >= queue.length) { -+ queue = this.resizeDecreaseQueue(); -+ } -+ queue[queueLength++] = -+ ((offX + (offZ << 6) + (offY << 12) + encodeOffset) & ((1L << (6 + 6 + 16)) - 1)) -+ | ((targetLevel & 0xFL) << (6 + 6 + 16)) -+ | ((propagate.everythingButTheOppositeDirection) << (6 + 6 + 16 + 4)); -+ continue; -+ } -+ continue; -+ } else { -+ this.mutablePos1.set(offX, offY, offZ); -+ long flags = 0; -+ if (blockState.isConditionallyFullOpaque()) { -+ final VoxelShape cullingFace = blockState.getFaceOcclusionShape(world, this.mutablePos1, propagate.getOpposite().nms); -+ -+ if (Shapes.faceShapeOccludes(Shapes.empty(), cullingFace)) { -+ continue; -+ } -+ flags |= FLAG_HAS_SIDED_TRANSPARENT_BLOCKS; -+ } -+ -+ final int opacity = blockState.getLightBlock(world, this.mutablePos1); -+ final int targetLevel = Math.max(0, propagatedLightLevel - Math.max(1, opacity)); -+ if (lightLevel > targetLevel) { -+ // it looks like another source propagated here, so re-propagate it -+ if (increaseQueueLength >= increaseQueue.length) { -+ increaseQueue = this.resizeIncreaseQueue(); -+ } -+ increaseQueue[increaseQueueLength++] = -+ ((offX + (offZ << 6) + (offY << 12) + encodeOffset) & ((1L << (6 + 6 + 16)) - 1)) -+ | ((lightLevel & 0xFL) << (6 + 6 + 16)) -+ | (((long)ALL_DIRECTIONS_BITSET) << (6 + 6 + 16 + 4)) -+ | (FLAG_RECHECK_LEVEL | flags); -+ continue; -+ } -+ final int emittedLight = blockState.getLightEmission() & emittedMask; -+ if (emittedLight != 0) { -+ // re-propagate source -+ // note: do not set recheck level, or else the propagation will fail -+ if (increaseQueueLength >= increaseQueue.length) { -+ increaseQueue = this.resizeIncreaseQueue(); -+ } -+ increaseQueue[increaseQueueLength++] = -+ ((offX + (offZ << 6) + (offY << 12) + encodeOffset) & ((1L << (6 + 6 + 16)) - 1)) -+ | ((emittedLight & 0xFL) << (6 + 6 + 16)) -+ | (((long)ALL_DIRECTIONS_BITSET) << (6 + 6 + 16 + 4)) -+ | (flags | FLAG_WRITE_LEVEL); -+ } -+ -+ currentNibble.set(localIndex, 0); -+ this.postLightUpdate(offX, offY, offZ); -+ -+ if (targetLevel > 0) { -+ if (queueLength >= queue.length) { -+ queue = this.resizeDecreaseQueue(); -+ } -+ queue[queueLength++] = -+ ((offX + (offZ << 6) + (offY << 12) + encodeOffset) & ((1L << (6 + 6 + 16)) - 1)) -+ | ((targetLevel & 0xFL) << (6 + 6 + 16)) -+ | ((propagate.everythingButTheOppositeDirection) << (6 + 6 + 16 + 4)) -+ | flags; -+ } -+ continue; -+ } -+ } -+ } else { -+ // we actually need to worry about our state here -+ final BlockState fromBlock = this.getBlockState(posX, posY, posZ); -+ this.mutablePos2.set(posX, posY, posZ); -+ for (final AxisDirection propagate : checkDirections) { -+ final int offX = posX + propagate.x; -+ final int offY = posY + propagate.y; -+ final int offZ = posZ + propagate.z; -+ -+ final int sectionIndex = (offX >> 4) + 5 * (offZ >> 4) + (5 * 5) * (offY >> 4) + sectionOffset; -+ final int localIndex = (offX & 15) | ((offZ & 15) << 4) | ((offY & 15) << 8); -+ -+ final VoxelShape fromShape = (fromBlock.isConditionallyFullOpaque()) ? fromBlock.getFaceOcclusionShape(world, this.mutablePos2, propagate.nms) : Shapes.empty(); -+ -+ if (fromShape != Shapes.empty() && Shapes.faceShapeOccludes(Shapes.empty(), fromShape)) { -+ continue; -+ } -+ -+ final SWMRNibbleArray currentNibble = this.nibbleCache[sectionIndex]; -+ final int lightLevel; -+ -+ if (currentNibble == null || (lightLevel = currentNibble.getUpdating(localIndex)) == 0) { -+ // already at lowest (or unloaded), nothing we can do -+ continue; -+ } -+ -+ final BlockState blockState = this.getBlockState(sectionIndex, localIndex); -+ if (blockState == null) { -+ continue; -+ } -+ final int opacityCached = blockState.getOpacityIfCached(); -+ if (opacityCached != -1) { -+ final int targetLevel = Math.max(0, propagatedLightLevel - Math.max(1, opacityCached)); -+ if (lightLevel > targetLevel) { -+ // it looks like another source propagated here, so re-propagate it -+ if (increaseQueueLength >= increaseQueue.length) { -+ increaseQueue = this.resizeIncreaseQueue(); -+ } -+ increaseQueue[increaseQueueLength++] = -+ ((offX + (offZ << 6) + (offY << 12) + encodeOffset) & ((1L << (6 + 6 + 16)) - 1)) -+ | ((lightLevel & 0xFL) << (6 + 6 + 16)) -+ | (((long)ALL_DIRECTIONS_BITSET) << (6 + 6 + 16 + 4)) -+ | FLAG_RECHECK_LEVEL; -+ continue; -+ } -+ final int emittedLight = blockState.getLightEmission() & emittedMask; -+ if (emittedLight != 0) { -+ // re-propagate source -+ // note: do not set recheck level, or else the propagation will fail -+ if (increaseQueueLength >= increaseQueue.length) { -+ increaseQueue = this.resizeIncreaseQueue(); -+ } -+ increaseQueue[increaseQueueLength++] = -+ ((offX + (offZ << 6) + (offY << 12) + encodeOffset) & ((1L << (6 + 6 + 16)) - 1)) -+ | ((emittedLight & 0xFL) << (6 + 6 + 16)) -+ | (((long)ALL_DIRECTIONS_BITSET) << (6 + 6 + 16 + 4)) -+ | (blockState.isConditionallyFullOpaque() ? (FLAG_WRITE_LEVEL | FLAG_HAS_SIDED_TRANSPARENT_BLOCKS) : FLAG_WRITE_LEVEL); -+ } -+ -+ currentNibble.set(localIndex, 0); -+ this.postLightUpdate(offX, offY, offZ); -+ -+ if (targetLevel > 0) { // we actually need to propagate 0 just in case we find a neighbour... -+ if (queueLength >= queue.length) { -+ queue = this.resizeDecreaseQueue(); -+ } -+ queue[queueLength++] = -+ ((offX + (offZ << 6) + (offY << 12) + encodeOffset) & ((1L << (6 + 6 + 16)) - 1)) -+ | ((targetLevel & 0xFL) << (6 + 6 + 16)) -+ | ((propagate.everythingButTheOppositeDirection) << (6 + 6 + 16 + 4)); -+ continue; -+ } -+ continue; -+ } else { -+ this.mutablePos1.set(offX, offY, offZ); -+ long flags = 0; -+ if (blockState.isConditionallyFullOpaque()) { -+ final VoxelShape cullingFace = blockState.getFaceOcclusionShape(world, this.mutablePos1, propagate.getOpposite().nms); -+ -+ if (Shapes.faceShapeOccludes(fromShape, cullingFace)) { -+ continue; -+ } -+ flags |= FLAG_HAS_SIDED_TRANSPARENT_BLOCKS; -+ } -+ -+ final int opacity = blockState.getLightBlock(world, this.mutablePos1); -+ final int targetLevel = Math.max(0, propagatedLightLevel - Math.max(1, opacity)); -+ if (lightLevel > targetLevel) { -+ // it looks like another source propagated here, so re-propagate it -+ if (increaseQueueLength >= increaseQueue.length) { -+ increaseQueue = this.resizeIncreaseQueue(); -+ } -+ increaseQueue[increaseQueueLength++] = -+ ((offX + (offZ << 6) + (offY << 12) + encodeOffset) & ((1L << (6 + 6 + 16)) - 1)) -+ | ((lightLevel & 0xFL) << (6 + 6 + 16)) -+ | (((long)ALL_DIRECTIONS_BITSET) << (6 + 6 + 16 + 4)) -+ | (FLAG_RECHECK_LEVEL | flags); -+ continue; -+ } -+ final int emittedLight = blockState.getLightEmission() & emittedMask; -+ if (emittedLight != 0) { -+ // re-propagate source -+ // note: do not set recheck level, or else the propagation will fail -+ if (increaseQueueLength >= increaseQueue.length) { -+ increaseQueue = this.resizeIncreaseQueue(); -+ } -+ increaseQueue[increaseQueueLength++] = -+ ((offX + (offZ << 6) + (offY << 12) + encodeOffset) & ((1L << (6 + 6 + 16)) - 1)) -+ | ((emittedLight & 0xFL) << (6 + 6 + 16)) -+ | (((long)ALL_DIRECTIONS_BITSET) << (6 + 6 + 16 + 4)) -+ | (flags | FLAG_WRITE_LEVEL); -+ } -+ -+ currentNibble.set(localIndex, 0); -+ this.postLightUpdate(offX, offY, offZ); -+ -+ if (targetLevel > 0) { // we actually need to propagate 0 just in case we find a neighbour... -+ if (queueLength >= queue.length) { -+ queue = this.resizeDecreaseQueue(); -+ } -+ queue[queueLength++] = -+ ((offX + (offZ << 6) + (offY << 12) + encodeOffset) & ((1L << (6 + 6 + 16)) - 1)) -+ | ((targetLevel & 0xFL) << (6 + 6 + 16)) -+ | ((propagate.everythingButTheOppositeDirection) << (6 + 6 + 16 + 4)) -+ | flags; -+ } -+ continue; -+ } -+ } -+ } -+ } -+ -+ // propagate sources we clobbered -+ this.increaseQueueInitialLength = increaseQueueLength; -+ this.performLightIncrease(lightAccess); -+ } -+} -diff --git a/src/main/java/ca/spottedleaf/starlight/common/light/StarLightInterface.java b/src/main/java/ca/spottedleaf/starlight/common/light/StarLightInterface.java -new file mode 100644 -index 0000000000000000000000000000000000000000..e0338db4d6fa359029ed5edeacc3646aa98701f5 ---- /dev/null -+++ b/src/main/java/ca/spottedleaf/starlight/common/light/StarLightInterface.java -@@ -0,0 +1,674 @@ -+package ca.spottedleaf.starlight.common.light; -+ -+import ca.spottedleaf.starlight.common.util.CoordinateUtils; -+import ca.spottedleaf.starlight.common.util.WorldUtil; -+import it.unimi.dsi.fastutil.longs.Long2ObjectLinkedOpenHashMap; -+import it.unimi.dsi.fastutil.shorts.ShortCollection; -+import it.unimi.dsi.fastutil.shorts.ShortOpenHashSet; -+import it.unimi.dsi.fastutil.objects.ObjectOpenHashSet; -+import net.minecraft.core.BlockPos; -+import net.minecraft.core.SectionPos; -+import net.minecraft.server.level.ServerChunkCache; -+import net.minecraft.server.level.ServerLevel; -+import net.minecraft.server.level.TicketType; -+import net.minecraft.world.level.ChunkPos; -+import net.minecraft.world.level.Level; -+import net.minecraft.world.level.chunk.ChunkAccess; -+import net.minecraft.world.level.chunk.status.ChunkStatus; -+import net.minecraft.world.level.chunk.DataLayer; -+import net.minecraft.world.level.chunk.LevelChunk; -+import net.minecraft.world.level.chunk.LightChunkGetter; -+import net.minecraft.world.level.lighting.LayerLightEventListener; -+import net.minecraft.world.level.lighting.LevelLightEngine; -+import java.util.ArrayDeque; -+import java.util.ArrayList; -+import java.util.List; -+import java.util.Set; -+import java.util.concurrent.CompletableFuture; -+import java.util.function.Consumer; -+import java.util.function.IntConsumer; -+ -+public final class StarLightInterface { -+ -+ public static final TicketType CHUNK_WORK_TICKET = TicketType.create("starlight_chunk_work_ticket", (p1, p2) -> Long.compare(p1.toLong(), p2.toLong())); -+ -+ /** -+ * Can be {@code null}, indicating the light is all empty. -+ */ -+ protected final Level world; -+ protected final LightChunkGetter lightAccess; -+ -+ protected final ArrayDeque cachedSkyPropagators; -+ protected final ArrayDeque cachedBlockPropagators; -+ -+ protected final LightQueue lightQueue = new LightQueue(this); -+ -+ protected final LayerLightEventListener skyReader; -+ protected final LayerLightEventListener blockReader; -+ protected final boolean isClientSide; -+ -+ protected final int minSection; -+ protected final int maxSection; -+ protected final int minLightSection; -+ protected final int maxLightSection; -+ -+ public final LevelLightEngine lightEngine; -+ -+ private final boolean hasBlockLight; -+ private final boolean hasSkyLight; -+ -+ public StarLightInterface(final LightChunkGetter lightAccess, final boolean hasSkyLight, final boolean hasBlockLight, final LevelLightEngine lightEngine) { -+ this.lightAccess = lightAccess; -+ this.world = lightAccess == null ? null : (Level)lightAccess.getLevel(); -+ this.cachedSkyPropagators = hasSkyLight && lightAccess != null ? new ArrayDeque<>() : null; -+ this.cachedBlockPropagators = hasBlockLight && lightAccess != null ? new ArrayDeque<>() : null; -+ this.isClientSide = !(this.world instanceof ServerLevel); -+ if (this.world == null) { -+ this.minSection = -4; -+ this.maxSection = 19; -+ this.minLightSection = -5; -+ this.maxLightSection = 20; -+ } else { -+ this.minSection = WorldUtil.getMinSection(this.world); -+ this.maxSection = WorldUtil.getMaxSection(this.world); -+ this.minLightSection = WorldUtil.getMinLightSection(this.world); -+ this.maxLightSection = WorldUtil.getMaxLightSection(this.world); -+ } -+ this.lightEngine = lightEngine; -+ this.hasBlockLight = hasBlockLight; -+ this.hasSkyLight = hasSkyLight; -+ this.skyReader = !hasSkyLight ? LayerLightEventListener.DummyLightLayerEventListener.INSTANCE : new LayerLightEventListener() { -+ @Override -+ public void checkBlock(final BlockPos blockPos) { -+ StarLightInterface.this.lightEngine.checkBlock(blockPos.immutable()); -+ } -+ -+ @Override -+ public void propagateLightSources(final ChunkPos chunkPos) { -+ throw new UnsupportedOperationException(); -+ } -+ -+ @Override -+ public boolean hasLightWork() { -+ // not really correct... -+ return StarLightInterface.this.hasUpdates(); -+ } -+ -+ @Override -+ public int runLightUpdates() { -+ throw new UnsupportedOperationException(); -+ } -+ -+ @Override -+ public void setLightEnabled(final ChunkPos chunkPos, final boolean bl) { -+ throw new UnsupportedOperationException(); -+ } -+ -+ @Override -+ public DataLayer getDataLayerData(final SectionPos pos) { -+ final ChunkAccess chunk = StarLightInterface.this.getAnyChunkNow(pos.getX(), pos.getZ()); -+ if (chunk == null || (!StarLightInterface.this.isClientSide && !chunk.isLightCorrect()) || !chunk.getStatus().isOrAfter(ChunkStatus.LIGHT)) { -+ return null; -+ } -+ -+ final int sectionY = pos.getY(); -+ -+ if (sectionY > StarLightInterface.this.maxLightSection || sectionY < StarLightInterface.this.minLightSection) { -+ return null; -+ } -+ -+ if (chunk.getSkyEmptinessMap() == null) { -+ return null; -+ } -+ -+ return chunk.getSkyNibbles()[sectionY - StarLightInterface.this.minLightSection].toVanillaNibble(); -+ } -+ -+ @Override -+ public int getLightValue(final BlockPos blockPos) { -+ return StarLightInterface.this.getSkyLightValue(blockPos, StarLightInterface.this.getAnyChunkNow(blockPos.getX() >> 4, blockPos.getZ() >> 4)); -+ } -+ -+ @Override -+ public void updateSectionStatus(final SectionPos pos, final boolean notReady) { -+ StarLightInterface.this.sectionChange(pos, notReady); -+ } -+ }; -+ this.blockReader = !hasBlockLight ? LayerLightEventListener.DummyLightLayerEventListener.INSTANCE : new LayerLightEventListener() { -+ @Override -+ public void checkBlock(final BlockPos blockPos) { -+ StarLightInterface.this.lightEngine.checkBlock(blockPos.immutable()); -+ } -+ -+ @Override -+ public void propagateLightSources(final ChunkPos chunkPos) { -+ throw new UnsupportedOperationException(); -+ } -+ -+ @Override -+ public boolean hasLightWork() { -+ // not really correct... -+ return StarLightInterface.this.hasUpdates(); -+ } -+ -+ @Override -+ public int runLightUpdates() { -+ throw new UnsupportedOperationException(); -+ } -+ -+ @Override -+ public void setLightEnabled(final ChunkPos chunkPos, final boolean bl) { -+ throw new UnsupportedOperationException(); -+ } -+ -+ @Override -+ public DataLayer getDataLayerData(final SectionPos pos) { -+ final ChunkAccess chunk = StarLightInterface.this.getAnyChunkNow(pos.getX(), pos.getZ()); -+ -+ if (chunk == null || pos.getY() < StarLightInterface.this.minLightSection || pos.getY() > StarLightInterface.this.maxLightSection) { -+ return null; -+ } -+ -+ return chunk.getBlockNibbles()[pos.getY() - StarLightInterface.this.minLightSection].toVanillaNibble(); -+ } -+ -+ @Override -+ public int getLightValue(final BlockPos blockPos) { -+ return StarLightInterface.this.getBlockLightValue(blockPos, StarLightInterface.this.getAnyChunkNow(blockPos.getX() >> 4, blockPos.getZ() >> 4)); -+ } -+ -+ @Override -+ public void updateSectionStatus(final SectionPos pos, final boolean notReady) { -+ StarLightInterface.this.sectionChange(pos, notReady); -+ } -+ }; -+ } -+ -+ public boolean hasSkyLight() { -+ return this.hasSkyLight; -+ } -+ -+ public boolean hasBlockLight() { -+ return this.hasBlockLight; -+ } -+ -+ public int getSkyLightValue(final BlockPos blockPos, final ChunkAccess chunk) { -+ if (!this.hasSkyLight) { -+ return 0; -+ } -+ final int x = blockPos.getX(); -+ int y = blockPos.getY(); -+ final int z = blockPos.getZ(); -+ -+ final int minSection = this.minSection; -+ final int maxSection = this.maxSection; -+ final int minLightSection = this.minLightSection; -+ final int maxLightSection = this.maxLightSection; -+ -+ if (chunk == null || (!this.isClientSide && !chunk.isLightCorrect()) || !chunk.getStatus().isOrAfter(ChunkStatus.LIGHT)) { -+ return 15; -+ } -+ -+ int sectionY = y >> 4; -+ -+ if (sectionY > maxLightSection) { -+ return 15; -+ } -+ -+ if (sectionY < minLightSection) { -+ sectionY = minLightSection; -+ y = sectionY << 4; -+ } -+ -+ final SWMRNibbleArray[] nibbles = chunk.getSkyNibbles(); -+ final SWMRNibbleArray immediate = nibbles[sectionY - minLightSection]; -+ -+ if (!immediate.isNullNibbleVisible()) { -+ return immediate.getVisible(x, y, z); -+ } -+ -+ final boolean[] emptinessMap = chunk.getSkyEmptinessMap(); -+ -+ if (emptinessMap == null) { -+ return 15; -+ } -+ -+ // are we above this chunk's lowest empty section? -+ int lowestY = minLightSection - 1; -+ for (int currY = maxSection; currY >= minSection; --currY) { -+ if (emptinessMap[currY - minSection]) { -+ continue; -+ } -+ -+ // should always be full lit here -+ lowestY = currY; -+ break; -+ } -+ -+ if (sectionY > lowestY) { -+ return 15; -+ } -+ -+ // this nibble is going to depend solely on the skylight data above it -+ // find first non-null data above (there does exist one, as we just found it above) -+ for (int currY = sectionY + 1; currY <= maxLightSection; ++currY) { -+ final SWMRNibbleArray nibble = nibbles[currY - minLightSection]; -+ if (!nibble.isNullNibbleVisible()) { -+ return nibble.getVisible(x, 0, z); -+ } -+ } -+ -+ // should never reach here -+ return 15; -+ } -+ -+ public int getBlockLightValue(final BlockPos blockPos, final ChunkAccess chunk) { -+ if (!this.hasBlockLight) { -+ return 0; -+ } -+ final int y = blockPos.getY(); -+ final int cy = y >> 4; -+ -+ final int minLightSection = this.minLightSection; -+ final int maxLightSection = this.maxLightSection; -+ -+ if (cy < minLightSection || cy > maxLightSection) { -+ return 0; -+ } -+ -+ if (chunk == null) { -+ return 0; -+ } -+ -+ final SWMRNibbleArray nibble = chunk.getBlockNibbles()[cy - minLightSection]; -+ return nibble.getVisible(blockPos.getX(), y, blockPos.getZ()); -+ } -+ -+ public int getRawBrightness(final BlockPos pos, final int ambientDarkness) { -+ final ChunkAccess chunk = this.getAnyChunkNow(pos.getX() >> 4, pos.getZ() >> 4); -+ -+ final int sky = this.getSkyLightValue(pos, chunk) - ambientDarkness; -+ // Don't fetch the block light level if the skylight level is 15, since the value will never be higher. -+ if (sky == 15) return 15; -+ final int block = this.getBlockLightValue(pos, chunk); -+ return Math.max(sky, block); -+ } -+ -+ public LayerLightEventListener getSkyReader() { -+ return this.skyReader; -+ } -+ -+ public LayerLightEventListener getBlockReader() { -+ return this.blockReader; -+ } -+ -+ public boolean isClientSide() { -+ return this.isClientSide; -+ } -+ -+ public ChunkAccess getAnyChunkNow(final int chunkX, final int chunkZ) { -+ if (this.world == null) { -+ // empty world -+ return null; -+ } -+ -+ final ServerChunkCache chunkProvider = ((ServerLevel)this.world).getChunkSource(); -+ final LevelChunk fullLoaded = chunkProvider.getChunkAtIfLoadedImmediately(chunkX, chunkZ); -+ if (fullLoaded != null) { -+ return fullLoaded; -+ } -+ -+ return chunkProvider.getChunkAtImmediately(chunkX, chunkZ); -+ } -+ -+ public boolean hasUpdates() { -+ return !this.lightQueue.isEmpty(); -+ } -+ -+ public Level getWorld() { -+ return this.world; -+ } -+ -+ public LightChunkGetter getLightAccess() { -+ return this.lightAccess; -+ } -+ -+ protected final SkyStarLightEngine getSkyLightEngine() { -+ if (this.cachedSkyPropagators == null) { -+ return null; -+ } -+ final SkyStarLightEngine ret; -+ synchronized (this.cachedSkyPropagators) { -+ ret = this.cachedSkyPropagators.pollFirst(); -+ } -+ -+ if (ret == null) { -+ return new SkyStarLightEngine(this.world); -+ } -+ return ret; -+ } -+ -+ protected final void releaseSkyLightEngine(final SkyStarLightEngine engine) { -+ if (this.cachedSkyPropagators == null) { -+ return; -+ } -+ synchronized (this.cachedSkyPropagators) { -+ this.cachedSkyPropagators.addFirst(engine); -+ } -+ } -+ -+ protected final BlockStarLightEngine getBlockLightEngine() { -+ if (this.cachedBlockPropagators == null) { -+ return null; -+ } -+ final BlockStarLightEngine ret; -+ synchronized (this.cachedBlockPropagators) { -+ ret = this.cachedBlockPropagators.pollFirst(); -+ } -+ -+ if (ret == null) { -+ return new BlockStarLightEngine(this.world); -+ } -+ return ret; -+ } -+ -+ protected final void releaseBlockLightEngine(final BlockStarLightEngine engine) { -+ if (this.cachedBlockPropagators == null) { -+ return; -+ } -+ synchronized (this.cachedBlockPropagators) { -+ this.cachedBlockPropagators.addFirst(engine); -+ } -+ } -+ -+ public LightQueue.ChunkTasks blockChange(final BlockPos pos) { -+ if (this.world == null || pos.getY() < WorldUtil.getMinBlockY(this.world) || pos.getY() > WorldUtil.getMaxBlockY(this.world)) { // empty world -+ return null; -+ } -+ -+ return this.lightQueue.queueBlockChange(pos); -+ } -+ -+ public LightQueue.ChunkTasks sectionChange(final SectionPos pos, final boolean newEmptyValue) { -+ if (this.world == null) { // empty world -+ return null; -+ } -+ -+ return this.lightQueue.queueSectionChange(pos, newEmptyValue); -+ } -+ -+ public void forceLoadInChunk(final ChunkAccess chunk, final Boolean[] emptySections) { -+ final SkyStarLightEngine skyEngine = this.getSkyLightEngine(); -+ final BlockStarLightEngine blockEngine = this.getBlockLightEngine(); -+ -+ try { -+ if (skyEngine != null) { -+ skyEngine.forceHandleEmptySectionChanges(this.lightAccess, chunk, emptySections); -+ } -+ if (blockEngine != null) { -+ blockEngine.forceHandleEmptySectionChanges(this.lightAccess, chunk, emptySections); -+ } -+ } finally { -+ this.releaseSkyLightEngine(skyEngine); -+ this.releaseBlockLightEngine(blockEngine); -+ } -+ } -+ -+ public void loadInChunk(final int chunkX, final int chunkZ, final Boolean[] emptySections) { -+ final SkyStarLightEngine skyEngine = this.getSkyLightEngine(); -+ final BlockStarLightEngine blockEngine = this.getBlockLightEngine(); -+ -+ try { -+ if (skyEngine != null) { -+ skyEngine.handleEmptySectionChanges(this.lightAccess, chunkX, chunkZ, emptySections); -+ } -+ if (blockEngine != null) { -+ blockEngine.handleEmptySectionChanges(this.lightAccess, chunkX, chunkZ, emptySections); -+ } -+ } finally { -+ this.releaseSkyLightEngine(skyEngine); -+ this.releaseBlockLightEngine(blockEngine); -+ } -+ } -+ -+ public void lightChunk(final ChunkAccess chunk, final Boolean[] emptySections) { -+ final SkyStarLightEngine skyEngine = this.getSkyLightEngine(); -+ final BlockStarLightEngine blockEngine = this.getBlockLightEngine(); -+ -+ try { -+ if (skyEngine != null) { -+ skyEngine.light(this.lightAccess, chunk, emptySections); -+ } -+ if (blockEngine != null) { -+ blockEngine.light(this.lightAccess, chunk, emptySections); -+ } -+ } finally { -+ this.releaseSkyLightEngine(skyEngine); -+ this.releaseBlockLightEngine(blockEngine); -+ } -+ } -+ -+ public void relightChunks(final Set chunks, final Consumer chunkLightCallback, -+ final IntConsumer onComplete) { -+ final SkyStarLightEngine skyEngine = this.getSkyLightEngine(); -+ final BlockStarLightEngine blockEngine = this.getBlockLightEngine(); -+ -+ try { -+ if (skyEngine != null) { -+ skyEngine.relightChunks(this.lightAccess, chunks, blockEngine == null ? chunkLightCallback : null, -+ blockEngine == null ? onComplete : null); -+ } -+ if (blockEngine != null) { -+ blockEngine.relightChunks(this.lightAccess, chunks, chunkLightCallback, onComplete); -+ } -+ } finally { -+ this.releaseSkyLightEngine(skyEngine); -+ this.releaseBlockLightEngine(blockEngine); -+ } -+ } -+ -+ public void checkChunkEdges(final int chunkX, final int chunkZ) { -+ this.checkSkyEdges(chunkX, chunkZ); -+ this.checkBlockEdges(chunkX, chunkZ); -+ } -+ -+ public void checkSkyEdges(final int chunkX, final int chunkZ) { -+ final SkyStarLightEngine skyEngine = this.getSkyLightEngine(); -+ -+ try { -+ if (skyEngine != null) { -+ skyEngine.checkChunkEdges(this.lightAccess, chunkX, chunkZ); -+ } -+ } finally { -+ this.releaseSkyLightEngine(skyEngine); -+ } -+ } -+ -+ public void checkBlockEdges(final int chunkX, final int chunkZ) { -+ final BlockStarLightEngine blockEngine = this.getBlockLightEngine(); -+ try { -+ if (blockEngine != null) { -+ blockEngine.checkChunkEdges(this.lightAccess, chunkX, chunkZ); -+ } -+ } finally { -+ this.releaseBlockLightEngine(blockEngine); -+ } -+ } -+ -+ public void checkSkyEdges(final int chunkX, final int chunkZ, final ShortCollection sections) { -+ final SkyStarLightEngine skyEngine = this.getSkyLightEngine(); -+ -+ try { -+ if (skyEngine != null) { -+ skyEngine.checkChunkEdges(this.lightAccess, chunkX, chunkZ, sections); -+ } -+ } finally { -+ this.releaseSkyLightEngine(skyEngine); -+ } -+ } -+ -+ public void checkBlockEdges(final int chunkX, final int chunkZ, final ShortCollection sections) { -+ final BlockStarLightEngine blockEngine = this.getBlockLightEngine(); -+ try { -+ if (blockEngine != null) { -+ blockEngine.checkChunkEdges(this.lightAccess, chunkX, chunkZ, sections); -+ } -+ } finally { -+ this.releaseBlockLightEngine(blockEngine); -+ } -+ } -+ -+ public void scheduleChunkLight(final ChunkPos pos, final Runnable run) { -+ this.lightQueue.queueChunkLighting(pos, run); -+ } -+ -+ public void removeChunkTasks(final ChunkPos pos) { -+ this.lightQueue.removeChunk(pos); -+ } -+ -+ public void propagateChanges() { -+ if (this.lightQueue.isEmpty()) { -+ return; -+ } -+ -+ final SkyStarLightEngine skyEngine = this.getSkyLightEngine(); -+ final BlockStarLightEngine blockEngine = this.getBlockLightEngine(); -+ -+ try { -+ LightQueue.ChunkTasks task; -+ while ((task = this.lightQueue.removeFirstTask()) != null) { -+ if (task.lightTasks != null) { -+ for (final Runnable run : task.lightTasks) { -+ run.run(); -+ } -+ } -+ -+ final long coordinate = task.chunkCoordinate; -+ final int chunkX = CoordinateUtils.getChunkX(coordinate); -+ final int chunkZ = CoordinateUtils.getChunkZ(coordinate); -+ -+ final Set positions = task.changedPositions; -+ final Boolean[] sectionChanges = task.changedSectionSet; -+ -+ if (skyEngine != null && (!positions.isEmpty() || sectionChanges != null)) { -+ skyEngine.blocksChangedInChunk(this.lightAccess, chunkX, chunkZ, positions, sectionChanges); -+ } -+ if (blockEngine != null && (!positions.isEmpty() || sectionChanges != null)) { -+ blockEngine.blocksChangedInChunk(this.lightAccess, chunkX, chunkZ, positions, sectionChanges); -+ } -+ -+ if (skyEngine != null && task.queuedEdgeChecksSky != null) { -+ skyEngine.checkChunkEdges(this.lightAccess, chunkX, chunkZ, task.queuedEdgeChecksSky); -+ } -+ if (blockEngine != null && task.queuedEdgeChecksBlock != null) { -+ blockEngine.checkChunkEdges(this.lightAccess, chunkX, chunkZ, task.queuedEdgeChecksBlock); -+ } -+ -+ task.onComplete.complete(null); -+ } -+ } finally { -+ this.releaseSkyLightEngine(skyEngine); -+ this.releaseBlockLightEngine(blockEngine); -+ } -+ } -+ -+ public static final class LightQueue { -+ -+ protected final Long2ObjectLinkedOpenHashMap chunkTasks = new Long2ObjectLinkedOpenHashMap<>(); -+ protected final StarLightInterface manager; -+ -+ public LightQueue(final StarLightInterface manager) { -+ this.manager = manager; -+ } -+ -+ public synchronized boolean isEmpty() { -+ return this.chunkTasks.isEmpty(); -+ } -+ -+ public synchronized LightQueue.ChunkTasks queueBlockChange(final BlockPos pos) { -+ final ChunkTasks tasks = this.chunkTasks.computeIfAbsent(CoordinateUtils.getChunkKey(pos), ChunkTasks::new); -+ tasks.changedPositions.add(pos.immutable()); -+ return tasks; -+ } -+ -+ public synchronized LightQueue.ChunkTasks queueSectionChange(final SectionPos pos, final boolean newEmptyValue) { -+ final ChunkTasks tasks = this.chunkTasks.computeIfAbsent(CoordinateUtils.getChunkKey(pos), ChunkTasks::new); -+ -+ if (tasks.changedSectionSet == null) { -+ tasks.changedSectionSet = new Boolean[this.manager.maxSection - this.manager.minSection + 1]; -+ } -+ tasks.changedSectionSet[pos.getY() - this.manager.minSection] = Boolean.valueOf(newEmptyValue); -+ -+ return tasks; -+ } -+ -+ public synchronized LightQueue.ChunkTasks queueChunkLighting(final ChunkPos pos, final Runnable lightTask) { -+ final ChunkTasks tasks = this.chunkTasks.computeIfAbsent(CoordinateUtils.getChunkKey(pos), ChunkTasks::new); -+ if (tasks.lightTasks == null) { -+ tasks.lightTasks = new ArrayList<>(); -+ } -+ tasks.lightTasks.add(lightTask); -+ -+ return tasks; -+ } -+ -+ public synchronized LightQueue.ChunkTasks queueChunkSkylightEdgeCheck(final SectionPos pos, final ShortCollection sections) { -+ final ChunkTasks tasks = this.chunkTasks.computeIfAbsent(CoordinateUtils.getChunkKey(pos), ChunkTasks::new); -+ -+ ShortOpenHashSet queuedEdges = tasks.queuedEdgeChecksSky; -+ if (queuedEdges == null) { -+ queuedEdges = tasks.queuedEdgeChecksSky = new ShortOpenHashSet(); -+ } -+ queuedEdges.addAll(sections); -+ -+ return tasks; -+ } -+ -+ public synchronized LightQueue.ChunkTasks queueChunkBlocklightEdgeCheck(final SectionPos pos, final ShortCollection sections) { -+ final ChunkTasks tasks = this.chunkTasks.computeIfAbsent(CoordinateUtils.getChunkKey(pos), ChunkTasks::new); -+ -+ ShortOpenHashSet queuedEdges = tasks.queuedEdgeChecksBlock; -+ if (queuedEdges == null) { -+ queuedEdges = tasks.queuedEdgeChecksBlock = new ShortOpenHashSet(); -+ } -+ queuedEdges.addAll(sections); -+ -+ return tasks; -+ } -+ -+ public void removeChunk(final ChunkPos pos) { -+ final ChunkTasks tasks; -+ synchronized (this) { -+ tasks = this.chunkTasks.remove(CoordinateUtils.getChunkKey(pos)); -+ } -+ if (tasks != null) { -+ tasks.onComplete.complete(null); -+ } -+ } -+ -+ public synchronized ChunkTasks removeFirstTask() { -+ if (this.chunkTasks.isEmpty()) { -+ return null; -+ } -+ return this.chunkTasks.removeFirst(); -+ } -+ -+ public static final class ChunkTasks { -+ -+ public final Set changedPositions = new ObjectOpenHashSet<>(); -+ public Boolean[] changedSectionSet; -+ public ShortOpenHashSet queuedEdgeChecksSky; -+ public ShortOpenHashSet queuedEdgeChecksBlock; -+ public List lightTasks; -+ -+ public boolean isTicketAdded = false; -+ public final CompletableFuture onComplete = new CompletableFuture<>(); -+ -+ public final long chunkCoordinate; -+ -+ public ChunkTasks(final long chunkCoordinate) { -+ this.chunkCoordinate = chunkCoordinate; -+ } -+ } -+ } -+} -diff --git a/src/main/java/ca/spottedleaf/starlight/common/util/CoordinateUtils.java b/src/main/java/ca/spottedleaf/starlight/common/util/CoordinateUtils.java -new file mode 100644 -index 0000000000000000000000000000000000000000..16a4a14e7ccf9e4d7fdf1166674fe8f529c06d39 ---- /dev/null -+++ b/src/main/java/ca/spottedleaf/starlight/common/util/CoordinateUtils.java -@@ -0,0 +1,128 @@ -+package ca.spottedleaf.starlight.common.util; -+ -+import net.minecraft.core.BlockPos; -+import net.minecraft.core.SectionPos; -+import net.minecraft.util.Mth; -+import net.minecraft.world.entity.Entity; -+import net.minecraft.world.level.ChunkPos; -+ -+public final class CoordinateUtils { -+ -+ // dx, dz are relative to the target chunk -+ // dx, dz in [-radius, radius] -+ public static int getNeighbourMappedIndex(final int dx, final int dz, final int radius) { -+ return (dx + radius) + (2 * radius + 1)*(dz + radius); -+ } -+ -+ // the chunk keys are compatible with vanilla -+ -+ public static long getChunkKey(final BlockPos pos) { -+ return ((long)(pos.getZ() >> 4) << 32) | ((pos.getX() >> 4) & 0xFFFFFFFFL); -+ } -+ -+ public static long getChunkKey(final Entity entity) { -+ return ((long)(Mth.floor(entity.getZ()) >> 4) << 32) | ((Mth.floor(entity.getX()) >> 4) & 0xFFFFFFFFL); -+ } -+ -+ public static long getChunkKey(final ChunkPos pos) { -+ return ((long)pos.z << 32) | (pos.x & 0xFFFFFFFFL); -+ } -+ -+ public static long getChunkKey(final SectionPos pos) { -+ return ((long)pos.getZ() << 32) | (pos.getX() & 0xFFFFFFFFL); -+ } -+ -+ public static long getChunkKey(final int x, final int z) { -+ return ((long)z << 32) | (x & 0xFFFFFFFFL); -+ } -+ -+ public static int getChunkX(final long chunkKey) { -+ return (int)chunkKey; -+ } -+ -+ public static int getChunkZ(final long chunkKey) { -+ return (int)(chunkKey >>> 32); -+ } -+ -+ public static int getChunkCoordinate(final double blockCoordinate) { -+ return Mth.floor(blockCoordinate) >> 4; -+ } -+ -+ // the section keys are compatible with vanilla's -+ -+ static final int SECTION_X_BITS = 22; -+ static final long SECTION_X_MASK = (1L << SECTION_X_BITS) - 1; -+ static final int SECTION_Y_BITS = 20; -+ static final long SECTION_Y_MASK = (1L << SECTION_Y_BITS) - 1; -+ static final int SECTION_Z_BITS = 22; -+ static final long SECTION_Z_MASK = (1L << SECTION_Z_BITS) - 1; -+ // format is y,z,x (in order of LSB to MSB) -+ static final int SECTION_Y_SHIFT = 0; -+ static final int SECTION_Z_SHIFT = SECTION_Y_SHIFT + SECTION_Y_BITS; -+ static final int SECTION_X_SHIFT = SECTION_Z_SHIFT + SECTION_X_BITS; -+ static final int SECTION_TO_BLOCK_SHIFT = 4; -+ -+ public static long getChunkSectionKey(final int x, final int y, final int z) { -+ return ((x & SECTION_X_MASK) << SECTION_X_SHIFT) -+ | ((y & SECTION_Y_MASK) << SECTION_Y_SHIFT) -+ | ((z & SECTION_Z_MASK) << SECTION_Z_SHIFT); -+ } -+ -+ public static long getChunkSectionKey(final SectionPos pos) { -+ return ((pos.getX() & SECTION_X_MASK) << SECTION_X_SHIFT) -+ | ((pos.getY() & SECTION_Y_MASK) << SECTION_Y_SHIFT) -+ | ((pos.getZ() & SECTION_Z_MASK) << SECTION_Z_SHIFT); -+ } -+ -+ public static long getChunkSectionKey(final ChunkPos pos, final int y) { -+ return ((pos.x & SECTION_X_MASK) << SECTION_X_SHIFT) -+ | ((y & SECTION_Y_MASK) << SECTION_Y_SHIFT) -+ | ((pos.z & SECTION_Z_MASK) << SECTION_Z_SHIFT); -+ } -+ -+ public static long getChunkSectionKey(final BlockPos pos) { -+ return (((long)pos.getX() << (SECTION_X_SHIFT - SECTION_TO_BLOCK_SHIFT)) & (SECTION_X_MASK << SECTION_X_SHIFT)) | -+ ((pos.getY() >> SECTION_TO_BLOCK_SHIFT) & (SECTION_Y_MASK << SECTION_Y_SHIFT)) | -+ (((long)pos.getZ() << (SECTION_Z_SHIFT - SECTION_TO_BLOCK_SHIFT)) & (SECTION_Z_MASK << SECTION_Z_SHIFT)); -+ } -+ -+ public static long getChunkSectionKey(final Entity entity) { -+ return ((Mth.lfloor(entity.getX()) << (SECTION_X_SHIFT - SECTION_TO_BLOCK_SHIFT)) & (SECTION_X_MASK << SECTION_X_SHIFT)) | -+ ((Mth.lfloor(entity.getY()) >> SECTION_TO_BLOCK_SHIFT) & (SECTION_Y_MASK << SECTION_Y_SHIFT)) | -+ ((Mth.lfloor(entity.getZ()) << (SECTION_Z_SHIFT - SECTION_TO_BLOCK_SHIFT)) & (SECTION_Z_MASK << SECTION_Z_SHIFT)); -+ } -+ -+ public static int getChunkSectionX(final long key) { -+ return (int)(key << (Long.SIZE - (SECTION_X_SHIFT + SECTION_X_BITS)) >> (Long.SIZE - SECTION_X_BITS)); -+ } -+ -+ public static int getChunkSectionY(final long key) { -+ return (int)(key << (Long.SIZE - (SECTION_Y_SHIFT + SECTION_Y_BITS)) >> (Long.SIZE - SECTION_Y_BITS)); -+ } -+ -+ public static int getChunkSectionZ(final long key) { -+ return (int)(key << (Long.SIZE - (SECTION_Z_SHIFT + SECTION_Z_BITS)) >> (Long.SIZE - SECTION_Z_BITS)); -+ } -+ -+ // the block coordinates are not necessarily compatible with vanilla's -+ -+ public static int getBlockCoordinate(final double blockCoordinate) { -+ return Mth.floor(blockCoordinate); -+ } -+ -+ public static long getBlockKey(final int x, final int y, final int z) { -+ return ((long)x & 0x7FFFFFF) | (((long)z & 0x7FFFFFF) << 27) | ((long)y << 54); -+ } -+ -+ public static long getBlockKey(final BlockPos pos) { -+ return ((long)pos.getX() & 0x7FFFFFF) | (((long)pos.getZ() & 0x7FFFFFF) << 27) | ((long)pos.getY() << 54); -+ } -+ -+ public static long getBlockKey(final Entity entity) { -+ return ((long)entity.getX() & 0x7FFFFFF) | (((long)entity.getZ() & 0x7FFFFFF) << 27) | ((long)entity.getY() << 54); -+ } -+ -+ private CoordinateUtils() { -+ throw new RuntimeException(); -+ } -+} -diff --git a/src/main/java/ca/spottedleaf/starlight/common/util/IntegerUtil.java b/src/main/java/ca/spottedleaf/starlight/common/util/IntegerUtil.java -new file mode 100644 -index 0000000000000000000000000000000000000000..fabf1e97c019c7365212f40018dcd08d3b828113 ---- /dev/null -+++ b/src/main/java/ca/spottedleaf/starlight/common/util/IntegerUtil.java -@@ -0,0 +1,242 @@ -+package ca.spottedleaf.starlight.common.util; -+ -+public final class IntegerUtil { -+ -+ public static final int HIGH_BIT_U32 = Integer.MIN_VALUE; -+ public static final long HIGH_BIT_U64 = Long.MIN_VALUE; -+ -+ public static int ceilLog2(final int value) { -+ return Integer.SIZE - Integer.numberOfLeadingZeros(value - 1); // see doc of numberOfLeadingZeros -+ } -+ -+ public static long ceilLog2(final long value) { -+ return Long.SIZE - Long.numberOfLeadingZeros(value - 1); // see doc of numberOfLeadingZeros -+ } -+ -+ public static int floorLog2(final int value) { -+ // xor is optimized subtract for 2^n -1 -+ // note that (2^n -1) - k = (2^n -1) ^ k for k <= (2^n - 1) -+ return (Integer.SIZE - 1) ^ Integer.numberOfLeadingZeros(value); // see doc of numberOfLeadingZeros -+ } -+ -+ public static int floorLog2(final long value) { -+ // xor is optimized subtract for 2^n -1 -+ // note that (2^n -1) - k = (2^n -1) ^ k for k <= (2^n - 1) -+ return (Long.SIZE - 1) ^ Long.numberOfLeadingZeros(value); // see doc of numberOfLeadingZeros -+ } -+ -+ public static int roundCeilLog2(final int value) { -+ // optimized variant of 1 << (32 - leading(val - 1)) -+ // given -+ // 1 << n = HIGH_BIT_32 >>> (31 - n) for n [0, 32) -+ // 1 << (32 - leading(val - 1)) = HIGH_BIT_32 >>> (31 - (32 - leading(val - 1))) -+ // HIGH_BIT_32 >>> (31 - (32 - leading(val - 1))) -+ // HIGH_BIT_32 >>> (31 - 32 + leading(val - 1)) -+ // HIGH_BIT_32 >>> (-1 + leading(val - 1)) -+ return HIGH_BIT_U32 >>> (Integer.numberOfLeadingZeros(value - 1) - 1); -+ } -+ -+ public static long roundCeilLog2(final long value) { -+ // see logic documented above -+ return HIGH_BIT_U64 >>> (Long.numberOfLeadingZeros(value - 1) - 1); -+ } -+ -+ public static int roundFloorLog2(final int value) { -+ // optimized variant of 1 << (31 - leading(val)) -+ // given -+ // 1 << n = HIGH_BIT_32 >>> (31 - n) for n [0, 32) -+ // 1 << (31 - leading(val)) = HIGH_BIT_32 >> (31 - (31 - leading(val))) -+ // HIGH_BIT_32 >> (31 - (31 - leading(val))) -+ // HIGH_BIT_32 >> (31 - 31 + leading(val)) -+ return HIGH_BIT_U32 >>> Integer.numberOfLeadingZeros(value); -+ } -+ -+ public static long roundFloorLog2(final long value) { -+ // see logic documented above -+ return HIGH_BIT_U64 >>> Long.numberOfLeadingZeros(value); -+ } -+ -+ public static boolean isPowerOfTwo(final int n) { -+ // 2^n has one bit -+ // note: this rets true for 0 still -+ return IntegerUtil.getTrailingBit(n) == n; -+ } -+ -+ public static boolean isPowerOfTwo(final long n) { -+ // 2^n has one bit -+ // note: this rets true for 0 still -+ return IntegerUtil.getTrailingBit(n) == n; -+ } -+ -+ public static int getTrailingBit(final int n) { -+ return -n & n; -+ } -+ -+ public static long getTrailingBit(final long n) { -+ return -n & n; -+ } -+ -+ public static int trailingZeros(final int n) { -+ return Integer.numberOfTrailingZeros(n); -+ } -+ -+ public static int trailingZeros(final long n) { -+ return Long.numberOfTrailingZeros(n); -+ } -+ -+ // from hacker's delight (signed division magic value) -+ public static int getDivisorMultiple(final long numbers) { -+ return (int)(numbers >>> 32); -+ } -+ -+ // from hacker's delight (signed division magic value) -+ public static int getDivisorShift(final long numbers) { -+ return (int)numbers; -+ } -+ -+ // copied from hacker's delight (signed division magic value) -+ // http://www.hackersdelight.org/hdcodetxt/magic.c.txt -+ public static long getDivisorNumbers(final int d) { -+ final int ad = branchlessAbs(d); -+ -+ if (ad < 2) { -+ throw new IllegalArgumentException("|number| must be in [2, 2^31 -1], not: " + d); -+ } -+ -+ final int two31 = 0x80000000; -+ final long mask = 0xFFFFFFFFL; // mask for enforcing unsigned behaviour -+ -+ /* -+ Signed usage: -+ int number; -+ long magic = getDivisorNumbers(div); -+ long mul = magic >>> 32; -+ int sign = number >> 31; -+ int result = (int)(((long)number * mul) >>> magic) - sign; -+ */ -+ /* -+ Unsigned usage: -+ int number; -+ long magic = getDivisorNumbers(div); -+ long mul = magic >>> 32; -+ int result = (int)(((long)number * mul) >>> magic); -+ */ -+ -+ int p = 31; -+ -+ // all these variables are UNSIGNED! -+ int t = two31 + (d >>> 31); -+ int anc = t - 1 - (int)((t & mask)%ad); -+ int q1 = (int)((two31 & mask)/(anc & mask)); -+ int r1 = two31 - q1*anc; -+ int q2 = (int)((two31 & mask)/(ad & mask)); -+ int r2 = two31 - q2*ad; -+ int delta; -+ -+ do { -+ p = p + 1; -+ q1 = 2*q1; // Update q1 = 2**p/|nc|. -+ r1 = 2*r1; // Update r1 = rem(2**p, |nc|). -+ if ((r1 & mask) >= (anc & mask)) {// (Must be an unsigned comparison here) -+ q1 = q1 + 1; -+ r1 = r1 - anc; -+ } -+ q2 = 2*q2; // Update q2 = 2**p/|d|. -+ r2 = 2*r2; // Update r2 = rem(2**p, |d|). -+ if ((r2 & mask) >= (ad & mask)) {// (Must be an unsigned comparison here) -+ q2 = q2 + 1; -+ r2 = r2 - ad; -+ } -+ delta = ad - r2; -+ } while ((q1 & mask) < (delta & mask) || (q1 == delta && r1 == 0)); -+ -+ int magicNum = q2 + 1; -+ if (d < 0) { -+ magicNum = -magicNum; -+ } -+ int shift = p; -+ return ((long)magicNum << 32) | shift; -+ } -+ -+ public static int branchlessAbs(final int val) { -+ // -n = -1 ^ n + 1 -+ final int mask = val >> (Integer.SIZE - 1); // -1 if < 0, 0 if >= 0 -+ return (mask ^ val) - mask; // if val < 0, then (0 ^ val) - 0 else (-1 ^ val) + 1 -+ } -+ -+ public static long branchlessAbs(final long val) { -+ // -n = -1 ^ n + 1 -+ final long mask = val >> (Long.SIZE - 1); // -1 if < 0, 0 if >= 0 -+ return (mask ^ val) - mask; // if val < 0, then (0 ^ val) - 0 else (-1 ^ val) + 1 -+ } -+ -+ //https://github.com/skeeto/hash-prospector for hash functions -+ -+ //score = ~590.47984224483832 -+ public static int hash0(int x) { -+ x *= 0x36935555; -+ x ^= x >>> 16; -+ return x; -+ } -+ -+ //score = ~310.01596637036749 -+ public static int hash1(int x) { -+ x ^= x >>> 15; -+ x *= 0x356aaaad; -+ x ^= x >>> 17; -+ return x; -+ } -+ -+ public static int hash2(int x) { -+ x ^= x >>> 16; -+ x *= 0x7feb352d; -+ x ^= x >>> 15; -+ x *= 0x846ca68b; -+ x ^= x >>> 16; -+ return x; -+ } -+ -+ public static int hash3(int x) { -+ x ^= x >>> 17; -+ x *= 0xed5ad4bb; -+ x ^= x >>> 11; -+ x *= 0xac4c1b51; -+ x ^= x >>> 15; -+ x *= 0x31848bab; -+ x ^= x >>> 14; -+ return x; -+ } -+ -+ //score = ~365.79959673201887 -+ public static long hash1(long x) { -+ x ^= x >>> 27; -+ x *= 0xb24924b71d2d354bL; -+ x ^= x >>> 28; -+ return x; -+ } -+ -+ //h2 hash -+ public static long hash2(long x) { -+ x ^= x >>> 32; -+ x *= 0xd6e8feb86659fd93L; -+ x ^= x >>> 32; -+ x *= 0xd6e8feb86659fd93L; -+ x ^= x >>> 32; -+ return x; -+ } -+ -+ public static long hash3(long x) { -+ x ^= x >>> 45; -+ x *= 0xc161abe5704b6c79L; -+ x ^= x >>> 41; -+ x *= 0xe3e5389aedbc90f7L; -+ x ^= x >>> 56; -+ x *= 0x1f9aba75a52db073L; -+ x ^= x >>> 53; -+ return x; -+ } -+ -+ private IntegerUtil() { -+ throw new RuntimeException(); -+ } -+} -diff --git a/src/main/java/ca/spottedleaf/starlight/common/util/SaveUtil.java b/src/main/java/ca/spottedleaf/starlight/common/util/SaveUtil.java -new file mode 100644 -index 0000000000000000000000000000000000000000..c2903150c8fc6955f4f4f71acc932b6c2ac83484 ---- /dev/null -+++ b/src/main/java/ca/spottedleaf/starlight/common/util/SaveUtil.java -@@ -0,0 +1,192 @@ -+package ca.spottedleaf.starlight.common.util; -+ -+import ca.spottedleaf.starlight.common.light.SWMRNibbleArray; -+import ca.spottedleaf.starlight.common.light.StarLightEngine; -+import com.mojang.logging.LogUtils; -+import net.minecraft.nbt.CompoundTag; -+import net.minecraft.nbt.ListTag; -+import net.minecraft.server.level.ServerLevel; -+import net.minecraft.world.level.ChunkPos; -+import net.minecraft.world.level.Level; -+import net.minecraft.world.level.chunk.ChunkAccess; -+import net.minecraft.world.level.chunk.status.ChunkStatus; -+import org.slf4j.Logger; -+ -+public final class SaveUtil { -+ -+ private static final Logger LOGGER = LogUtils.getLogger(); -+ -+ private static final int STARLIGHT_LIGHT_VERSION = 9; -+ -+ public static int getLightVersion() { -+ return STARLIGHT_LIGHT_VERSION; -+ } -+ -+ private static final String BLOCKLIGHT_STATE_TAG = "starlight.blocklight_state"; -+ private static final String SKYLIGHT_STATE_TAG = "starlight.skylight_state"; -+ private static final String STARLIGHT_VERSION_TAG = "starlight.light_version"; -+ -+ public static void saveLightHook(final Level world, final ChunkAccess chunk, final CompoundTag nbt) { -+ try { -+ saveLightHookReal(world, chunk, nbt); -+ } catch (final Throwable ex) { -+ // failing to inject is not fatal so we catch anything here. if it fails, it will have correctly set lit to false -+ // for Vanilla to relight on load and it will not set our lit tag so we will relight on load -+ if (ex instanceof ThreadDeath) { -+ throw (ThreadDeath)ex; -+ } -+ LOGGER.warn("Failed to inject light data into save data for chunk " + chunk.getPos() + ", chunk light will be recalculated on its next load", ex); -+ } -+ } -+ -+ private static void saveLightHookReal(final Level world, final ChunkAccess chunk, final CompoundTag tag) { -+ if (tag == null) { -+ return; -+ } -+ -+ final int minSection = WorldUtil.getMinLightSection(world); -+ final int maxSection = WorldUtil.getMaxLightSection(world); -+ -+ SWMRNibbleArray[] blockNibbles = chunk.getBlockNibbles(); -+ SWMRNibbleArray[] skyNibbles = chunk.getSkyNibbles(); -+ -+ boolean lit = chunk.isLightCorrect() || !(world instanceof ServerLevel); -+ // diff start - store our tag for whether light data is init'd -+ if (lit) { -+ tag.putBoolean("isLightOn", false); -+ } -+ // diff end - store our tag for whether light data is init'd -+ ChunkStatus status = ChunkStatus.byName(tag.getString("Status")); -+ -+ CompoundTag[] sections = new CompoundTag[maxSection - minSection + 1]; -+ -+ ListTag sectionsStored = tag.getList("sections", 10); -+ -+ for (int i = 0; i < sectionsStored.size(); ++i) { -+ CompoundTag sectionStored = sectionsStored.getCompound(i); -+ int k = sectionStored.getByte("Y"); -+ -+ // strip light data -+ sectionStored.remove("BlockLight"); -+ sectionStored.remove("SkyLight"); -+ -+ if (!sectionStored.isEmpty()) { -+ sections[k - minSection] = sectionStored; -+ } -+ } -+ -+ if (lit && status.isOrAfter(ChunkStatus.LIGHT)) { -+ for (int i = minSection; i <= maxSection; ++i) { -+ SWMRNibbleArray.SaveState blockNibble = blockNibbles[i - minSection].getSaveState(); -+ SWMRNibbleArray.SaveState skyNibble = skyNibbles[i - minSection].getSaveState(); -+ if (blockNibble != null || skyNibble != null) { -+ CompoundTag section = sections[i - minSection]; -+ if (section == null) { -+ section = new CompoundTag(); -+ section.putByte("Y", (byte)i); -+ sections[i - minSection] = section; -+ } -+ -+ // we store under the same key so mod programs editing nbt -+ // can still read the data, hopefully. -+ // however, for compatibility we store chunks as unlit so vanilla -+ // is forced to re-light them if it encounters our data. It's too much of a burden -+ // to try and maintain compatibility with a broken and inferior skylight management system. -+ -+ if (blockNibble != null) { -+ if (blockNibble.data != null) { -+ section.putByteArray("BlockLight", blockNibble.data); -+ } -+ section.putInt(BLOCKLIGHT_STATE_TAG, blockNibble.state); -+ } -+ -+ if (skyNibble != null) { -+ if (skyNibble.data != null) { -+ section.putByteArray("SkyLight", skyNibble.data); -+ } -+ section.putInt(SKYLIGHT_STATE_TAG, skyNibble.state); -+ } -+ } -+ } -+ } -+ -+ // rewrite section list -+ sectionsStored.clear(); -+ for (CompoundTag section : sections) { -+ if (section != null) { -+ sectionsStored.add(section); -+ } -+ } -+ tag.put("sections", sectionsStored); -+ if (lit) { -+ tag.putInt(STARLIGHT_VERSION_TAG, STARLIGHT_LIGHT_VERSION); // only mark as fully lit after we have successfully injected our data -+ } -+ } -+ -+ public static void loadLightHook(final Level world, final ChunkPos pos, final CompoundTag tag, final ChunkAccess into) { -+ try { -+ loadLightHookReal(world, pos, tag, into); -+ } catch (final Throwable ex) { -+ // failing to inject is not fatal so we catch anything here. if it fails, then we simply relight. Not a problem, we get correct -+ // lighting in both cases. -+ if (ex instanceof ThreadDeath) { -+ throw (ThreadDeath)ex; -+ } -+ LOGGER.warn("Failed to load light for chunk " + pos + ", light will be recalculated", ex); -+ } -+ } -+ -+ private static void loadLightHookReal(final Level world, final ChunkPos pos, final CompoundTag tag, final ChunkAccess into) { -+ if (into == null) { -+ return; -+ } -+ final int minSection = WorldUtil.getMinLightSection(world); -+ final int maxSection = WorldUtil.getMaxLightSection(world); -+ -+ into.setLightCorrect(false); // mark as unlit in case we fail parsing -+ -+ SWMRNibbleArray[] blockNibbles = StarLightEngine.getFilledEmptyLight(world); -+ SWMRNibbleArray[] skyNibbles = StarLightEngine.getFilledEmptyLight(world); -+ -+ -+ // start copy from the original method -+ boolean lit = tag.get("isLightOn") != null && tag.getInt(STARLIGHT_VERSION_TAG) == STARLIGHT_LIGHT_VERSION; -+ boolean canReadSky = world.dimensionType().hasSkyLight(); -+ ChunkStatus status = ChunkStatus.byName(tag.getString("Status")); -+ if (lit && status.isOrAfter(ChunkStatus.LIGHT)) { // diff - we add the status check here -+ ListTag sections = tag.getList("sections", 10); -+ -+ for (int i = 0; i < sections.size(); ++i) { -+ CompoundTag sectionData = sections.getCompound(i); -+ int y = sectionData.getByte("Y"); -+ -+ if (sectionData.contains("BlockLight", 7)) { -+ // this is where our diff is -+ blockNibbles[y - minSection] = new SWMRNibbleArray(sectionData.getByteArray("BlockLight").clone(), sectionData.getInt(BLOCKLIGHT_STATE_TAG)); // clone for data safety -+ } else { -+ blockNibbles[y - minSection] = new SWMRNibbleArray(null, sectionData.getInt(BLOCKLIGHT_STATE_TAG)); -+ } -+ -+ if (canReadSky) { -+ if (sectionData.contains("SkyLight", 7)) { -+ // we store under the same key so mod programs editing nbt -+ // can still read the data, hopefully. -+ // however, for compatibility we store chunks as unlit so vanilla -+ // is forced to re-light them if it encounters our data. It's too much of a burden -+ // to try and maintain compatibility with a broken and inferior skylight management system. -+ skyNibbles[y - minSection] = new SWMRNibbleArray(sectionData.getByteArray("SkyLight").clone(), sectionData.getInt(SKYLIGHT_STATE_TAG)); // clone for data safety -+ } else { -+ skyNibbles[y - minSection] = new SWMRNibbleArray(null, sectionData.getInt(SKYLIGHT_STATE_TAG)); -+ } -+ } -+ } -+ } -+ // end copy from vanilla -+ -+ into.setBlockNibbles(blockNibbles); -+ into.setSkyNibbles(skyNibbles); -+ into.setLightCorrect(lit); // now we set lit here, only after we've correctly parsed data -+ } -+ -+ private SaveUtil() {} -+} -diff --git a/src/main/java/ca/spottedleaf/starlight/common/util/WorldUtil.java b/src/main/java/ca/spottedleaf/starlight/common/util/WorldUtil.java -new file mode 100644 -index 0000000000000000000000000000000000000000..dd995e25ae620ae36cd5eecb2fe10ad034ba50d2 ---- /dev/null -+++ b/src/main/java/ca/spottedleaf/starlight/common/util/WorldUtil.java -@@ -0,0 +1,47 @@ -+package ca.spottedleaf.starlight.common.util; -+ -+import net.minecraft.world.level.LevelHeightAccessor; -+ -+public final class WorldUtil { -+ -+ // min, max are inclusive -+ -+ public static int getMaxSection(final LevelHeightAccessor world) { -+ return world.getMaxSection() - 1; // getMaxSection() is exclusive -+ } -+ -+ public static int getMinSection(final LevelHeightAccessor world) { -+ return world.getMinSection(); -+ } -+ -+ public static int getMaxLightSection(final LevelHeightAccessor world) { -+ return getMaxSection(world) + 1; -+ } -+ -+ public static int getMinLightSection(final LevelHeightAccessor world) { -+ return getMinSection(world) - 1; -+ } -+ -+ -+ -+ public static int getTotalSections(final LevelHeightAccessor world) { -+ return getMaxSection(world) - getMinSection(world) + 1; -+ } -+ -+ public static int getTotalLightSections(final LevelHeightAccessor world) { -+ return getMaxLightSection(world) - getMinLightSection(world) + 1; -+ } -+ -+ public static int getMinBlockY(final LevelHeightAccessor world) { -+ return getMinSection(world) << 4; -+ } -+ -+ public static int getMaxBlockY(final LevelHeightAccessor world) { -+ return (getMaxSection(world) << 4) | 15; -+ } -+ -+ private WorldUtil() { -+ throw new RuntimeException(); -+ } -+ -+} -diff --git a/src/main/java/io/papermc/paper/command/PaperCommand.java b/src/main/java/io/papermc/paper/command/PaperCommand.java -index 46bf42d5ea9e7b046f962531c5962d287cf44a41..a3f43dccb796f30f6e9389e1ae182f06e9024e96 100644 ---- a/src/main/java/io/papermc/paper/command/PaperCommand.java -+++ b/src/main/java/io/papermc/paper/command/PaperCommand.java -@@ -42,6 +42,7 @@ public final class PaperCommand extends Command { - commands.put(Set.of("dumpitem"), new DumpItemCommand()); - commands.put(Set.of("mobcaps", "playermobcaps"), new MobcapsCommand()); - commands.put(Set.of("dumplisteners"), new DumpListenersCommand()); -+ commands.put(Set.of("fixlight"), new FixLightCommand()); - - return commands.entrySet().stream() - .flatMap(entry -> entry.getKey().stream().map(s -> Map.entry(s, entry.getValue()))) -diff --git a/src/main/java/io/papermc/paper/command/subcommands/FixLightCommand.java b/src/main/java/io/papermc/paper/command/subcommands/FixLightCommand.java -new file mode 100644 -index 0000000000000000000000000000000000000000..56524cbe4303901007e1e7fb3703a19efbf79ae7 ---- /dev/null -+++ b/src/main/java/io/papermc/paper/command/subcommands/FixLightCommand.java -@@ -0,0 +1,109 @@ -+package io.papermc.paper.command.subcommands; -+ -+import io.papermc.paper.command.PaperSubcommand; -+import io.papermc.paper.util.MCUtil; -+import net.minecraft.server.level.ServerLevel; -+import net.minecraft.server.level.ServerPlayer; -+import net.minecraft.server.level.ThreadedLevelLightEngine; -+import net.minecraft.world.level.ChunkPos; -+import net.minecraft.world.level.chunk.ChunkAccess; -+import org.bukkit.command.CommandSender; -+import org.bukkit.craftbukkit.entity.CraftPlayer; -+import org.bukkit.entity.Player; -+import org.checkerframework.checker.nullness.qual.NonNull; -+import org.checkerframework.checker.nullness.qual.Nullable; -+import org.checkerframework.framework.qual.DefaultQualifier; -+ -+import static net.kyori.adventure.text.Component.text; -+import static net.kyori.adventure.text.format.NamedTextColor.BLUE; -+import static net.kyori.adventure.text.format.NamedTextColor.DARK_AQUA; -+import static net.kyori.adventure.text.format.NamedTextColor.RED; -+ -+@DefaultQualifier(NonNull.class) -+public final class FixLightCommand implements PaperSubcommand { -+ @Override -+ public boolean execute(final CommandSender sender, final String subCommand, final String[] args) { -+ this.doFixLight(sender, args); -+ return true; -+ } -+ -+ private void doFixLight(final CommandSender sender, final String[] args) { -+ if (!(sender instanceof Player)) { -+ sender.sendMessage(text("Only players can use this command", RED)); -+ return; -+ } -+ @Nullable Runnable post = null; -+ int radius = 2; -+ if (args.length > 0) { -+ try { -+ final int parsed = Integer.parseInt(args[0]); -+ if (parsed < 0) { -+ sender.sendMessage(text("Radius cannot be negative!", RED)); -+ return; -+ } -+ final int maxRadius = 32; -+ radius = Math.min(maxRadius, parsed); -+ if (radius != parsed) { -+ post = () -> sender.sendMessage(text("Radius '" + parsed + "' was not in the required range [0, " + maxRadius + "], it was lowered to the maximum (" + maxRadius + " chunks).", RED)); -+ } -+ } catch (final Exception e) { -+ sender.sendMessage(text("'" + args[0] + "' is not a valid number.", RED)); -+ return; -+ } -+ } -+ -+ CraftPlayer player = (CraftPlayer) sender; -+ ServerPlayer handle = player.getHandle(); -+ ServerLevel world = (ServerLevel) handle.level(); -+ ThreadedLevelLightEngine lightengine = world.getChunkSource().getLightEngine(); -+ this.starlightFixLight(handle, world, lightengine, radius, post); -+ } -+ -+ private void starlightFixLight( -+ final ServerPlayer sender, -+ final ServerLevel world, -+ final ThreadedLevelLightEngine lightengine, -+ final int radius, -+ final @Nullable Runnable done -+ ) { -+ final long start = System.nanoTime(); -+ final java.util.LinkedHashSet chunks = new java.util.LinkedHashSet<>(MCUtil.getSpiralOutChunks(sender.blockPosition(), radius)); // getChunkCoordinates is actually just bad mappings, this function rets position as blockpos -+ -+ final int[] pending = new int[1]; -+ for (java.util.Iterator iterator = chunks.iterator(); iterator.hasNext(); ) { -+ final ChunkPos chunkPos = iterator.next(); -+ -+ final @Nullable ChunkAccess chunk = (ChunkAccess) world.getChunkSource().getChunkForLighting(chunkPos.x, chunkPos.z); -+ if (chunk == null || !chunk.isLightCorrect() || !chunk.getStatus().isOrAfter(net.minecraft.world.level.chunk.status.ChunkStatus.LIGHT)) { -+ // cannot relight this chunk -+ iterator.remove(); -+ continue; -+ } -+ -+ ++pending[0]; -+ } -+ -+ final int[] relitChunks = new int[1]; -+ lightengine.relight(chunks, -+ (final ChunkPos chunkPos) -> { -+ ++relitChunks[0]; -+ sender.getBukkitEntity().sendMessage(text().color(DARK_AQUA).append( -+ text("Relit chunk ", BLUE), text(chunkPos.toString()), -+ text(", progress: ", BLUE), text((int) (Math.round(100.0 * (double) (relitChunks[0]) / (double) pending[0])) + "%") -+ )); -+ }, -+ (final int totalRelit) -> { -+ final long end = System.nanoTime(); -+ final long diff = Math.round(1.0e-6 * (end - start)); -+ sender.getBukkitEntity().sendMessage(text().color(DARK_AQUA).append( -+ text("Relit ", BLUE), text(totalRelit), -+ text(" chunks. Took ", BLUE), text(diff + "ms") -+ )); -+ if (done != null) { -+ done.run(); -+ } -+ } -+ ); -+ sender.getBukkitEntity().sendMessage(text().color(BLUE).append(text("Relighting "), text(pending[0], DARK_AQUA), text(" chunks"))); -+ } -+} -diff --git a/src/main/java/net/minecraft/server/level/ChunkHolder.java b/src/main/java/net/minecraft/server/level/ChunkHolder.java -index b12921579cb9ab3cbf5607841cc84f2f843624ea..88729d92878f98729eb5669cce5ae5b1418865a1 100644 ---- a/src/main/java/net/minecraft/server/level/ChunkHolder.java -+++ b/src/main/java/net/minecraft/server/level/ChunkHolder.java -@@ -51,7 +51,7 @@ public class ChunkHolder { - private volatile CompletableFuture> fullChunkFuture; private int fullChunkCreateCount; private volatile boolean isFullChunkReady; // Paper - cache chunk ticking stage - private volatile CompletableFuture> tickingChunkFuture; private volatile boolean isTickingReady; // Paper - cache chunk ticking stage - private volatile CompletableFuture> entityTickingChunkFuture; private volatile boolean isEntityTickingReady; // Paper - cache chunk ticking stage -- private CompletableFuture chunkToSave; -+ public CompletableFuture chunkToSave; // Paper - public - @Nullable - private final DebugBuffer chunkToSaveHistory; - public int oldTicketLevel; -@@ -261,6 +261,12 @@ public class ChunkHolder { - } - } - -+ // Paper start - starlight -+ public void broadcast(Packet packet, boolean onChunkViewEdge) { -+ this.broadcast(this.playerProvider.getPlayers(this.pos, onChunkViewEdge), packet); -+ } -+ // Paper end - starlight -+ - public void broadcastChanges(LevelChunk chunk) { - if (this.hasChangedSections || !this.skyChangedLightSectionFilter.isEmpty() || !this.blockChangedLightSectionFilter.isEmpty()) { - Level world = chunk.getLevel(); -diff --git a/src/main/java/net/minecraft/server/level/ChunkMap.java b/src/main/java/net/minecraft/server/level/ChunkMap.java -index 35f627c58e93c03ee58b44877398432bba57dc2d..d3f63185edd1db9fab3887ea3f08982435b3a23c 100644 ---- a/src/main/java/net/minecraft/server/level/ChunkMap.java -+++ b/src/main/java/net/minecraft/server/level/ChunkMap.java -@@ -128,7 +128,7 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider - private final LongSet entitiesInLevel; - public final ServerLevel level; - private final ThreadedLevelLightEngine lightEngine; -- private final BlockableEventLoop mainThreadExecutor; -+ public final BlockableEventLoop mainThreadExecutor; // Paper - public - public ChunkGenerator generator; - private final RandomState randomState; - private final ChunkGeneratorStructureState chunkGeneratorState; -diff --git a/src/main/java/net/minecraft/server/level/DistanceManager.java b/src/main/java/net/minecraft/server/level/DistanceManager.java -index c473cb1888e9ab0e91ba44f1439b81742758304e..7a48ae2ba962ff56d0abff581b51f28b48bd9aae 100644 ---- a/src/main/java/net/minecraft/server/level/DistanceManager.java -+++ b/src/main/java/net/minecraft/server/level/DistanceManager.java -@@ -379,7 +379,7 @@ public abstract class DistanceManager { - } - - public void removeTicketsOnClosing() { -- ImmutableSet> immutableset = ImmutableSet.of(TicketType.UNKNOWN, TicketType.POST_TELEPORT, TicketType.LIGHT, TicketType.FUTURE_AWAIT); // Paper - add additional tickets to preserve -+ ImmutableSet> immutableset = ImmutableSet.of(TicketType.UNKNOWN, TicketType.POST_TELEPORT, TicketType.LIGHT, TicketType.FUTURE_AWAIT, TicketType.CHUNK_RELIGHT, ca.spottedleaf.starlight.common.light.StarLightInterface.CHUNK_WORK_TICKET); // Paper - add additional tickets to preserve - ObjectIterator>>> objectiterator = this.tickets.long2ObjectEntrySet().fastIterator(); - - while (objectiterator.hasNext()) { -diff --git a/src/main/java/net/minecraft/server/level/ThreadedLevelLightEngine.java b/src/main/java/net/minecraft/server/level/ThreadedLevelLightEngine.java -index 1dfae40ec19c4df0a97359941cf2c948cd1c9cb2..f206df06a7d8895175db31d4a840d7467ffe826f 100644 ---- a/src/main/java/net/minecraft/server/level/ThreadedLevelLightEngine.java -+++ b/src/main/java/net/minecraft/server/level/ThreadedLevelLightEngine.java -@@ -23,6 +23,17 @@ import net.minecraft.world.level.chunk.LightChunkGetter; - import net.minecraft.world.level.lighting.LevelLightEngine; - import org.slf4j.Logger; - -+// Paper start -+import ca.spottedleaf.starlight.common.light.StarLightEngine; -+import io.papermc.paper.util.CoordinateUtils; -+import java.util.function.Supplier; -+import net.minecraft.world.level.lighting.LayerLightEventListener; -+import it.unimi.dsi.fastutil.longs.Long2IntOpenHashMap; -+import it.unimi.dsi.fastutil.longs.LongArrayList; -+import it.unimi.dsi.fastutil.longs.LongIterator; -+import net.minecraft.world.level.chunk.status.ChunkStatus; -+// Paper end -+ - public class ThreadedLevelLightEngine extends LevelLightEngine implements AutoCloseable { - public static final int DEFAULT_BATCH_SIZE = 1000; - private static final Logger LOGGER = LogUtils.getLogger(); -@@ -33,6 +44,12 @@ public class ThreadedLevelLightEngine extends LevelLightEngine implements AutoCl - private final int taskPerBatch = 1000; - private final AtomicBoolean scheduled = new AtomicBoolean(); - -+ // Paper start - replace light engine impl -+ protected final ca.spottedleaf.starlight.common.light.StarLightInterface theLightEngine; -+ public final boolean hasBlockLight; -+ public final boolean hasSkyLight; -+ // Paper end - replace light engine impl -+ - public ThreadedLevelLightEngine( - LightChunkGetter chunkProvider, - ChunkMap chunkStorage, -@@ -40,11 +57,153 @@ public class ThreadedLevelLightEngine extends LevelLightEngine implements AutoCl - ProcessorMailbox processor, - ProcessorHandle> executor - ) { -- super(chunkProvider, true, hasBlockLight); -+ super(chunkProvider, false, false); // Paper - destroy vanilla light engine state - this.chunkMap = chunkStorage; - this.sorterMailbox = executor; - this.taskMailbox = processor; -+ // Paper start - replace light engine impl -+ this.hasBlockLight = true; -+ this.hasSkyLight = hasBlockLight; // Nice variable name. -+ this.theLightEngine = new ca.spottedleaf.starlight.common.light.StarLightInterface(chunkProvider, this.hasSkyLight, this.hasBlockLight, this); -+ // Paper end - replace light engine impl -+ } -+ -+ // Paper start - replace light engine impl -+ protected final ChunkAccess getChunk(final int chunkX, final int chunkZ) { -+ return ((ServerLevel)this.theLightEngine.getWorld()).getChunkSource().getChunkAtImmediately(chunkX, chunkZ); -+ } -+ -+ protected long relightCounter; -+ -+ public int relight(java.util.Set chunks_param, -+ java.util.function.Consumer chunkLightCallback, -+ java.util.function.IntConsumer onComplete) { -+ if (!org.bukkit.Bukkit.isPrimaryThread()) { -+ throw new IllegalStateException("Must only be called on the main thread"); -+ } -+ -+ java.util.Set chunks = new java.util.LinkedHashSet<>(chunks_param); -+ // add tickets -+ java.util.Map ticketIds = new java.util.HashMap<>(); -+ int totalChunks = 0; -+ for (java.util.Iterator iterator = chunks.iterator(); iterator.hasNext();) { -+ final ChunkPos chunkPos = iterator.next(); -+ -+ final ChunkAccess chunk = (ChunkAccess)((ServerLevel)this.theLightEngine.getWorld()).getChunkSource().getChunkForLighting(chunkPos.x, chunkPos.z); -+ if (chunk == null || !chunk.isLightCorrect() || !chunk.getStatus().isOrAfter(ChunkStatus.LIGHT)) { -+ // cannot relight this chunk -+ iterator.remove(); -+ continue; -+ } -+ -+ final Long id = Long.valueOf(this.relightCounter++); -+ -+ ((ServerLevel)this.theLightEngine.getWorld()).getChunkSource().addTicketAtLevel(TicketType.CHUNK_RELIGHT, chunkPos, io.papermc.paper.util.MCUtil.getTicketLevelFor(ChunkStatus.LIGHT), id); -+ ticketIds.put(chunkPos, id); -+ -+ ++totalChunks; -+ } -+ -+ this.taskMailbox.tell(() -> { -+ this.theLightEngine.relightChunks(chunks, (ChunkPos chunkPos) -> { -+ chunkLightCallback.accept(chunkPos); -+ ((java.util.concurrent.Executor)((ServerLevel)this.theLightEngine.getWorld()).getChunkSource().mainThreadProcessor).execute(() -> { -+ ((ServerLevel)this.theLightEngine.getWorld()).getChunkSource().chunkMap.getUpdatingChunkIfPresent(chunkPos.toLong()).broadcast(new net.minecraft.network.protocol.game.ClientboundLightUpdatePacket(chunkPos, ThreadedLevelLightEngine.this, null, null), false); -+ ((ServerLevel)this.theLightEngine.getWorld()).getChunkSource().removeTicketAtLevel(TicketType.CHUNK_RELIGHT, chunkPos, io.papermc.paper.util.MCUtil.getTicketLevelFor(ChunkStatus.LIGHT), ticketIds.get(chunkPos)); -+ }); -+ }, onComplete); -+ }); -+ this.tryScheduleUpdate(); -+ -+ return totalChunks; -+ } -+ -+ private final Long2IntOpenHashMap chunksBeingWorkedOn = new Long2IntOpenHashMap(); -+ -+ private void queueTaskForSection(final int chunkX, final int chunkY, final int chunkZ, -+ final Supplier runnable) { -+ final ServerLevel world = (ServerLevel)this.theLightEngine.getWorld(); -+ -+ final ChunkAccess center = this.theLightEngine.getAnyChunkNow(chunkX, chunkZ); -+ if (center == null || !center.getStatus().isOrAfter(ChunkStatus.LIGHT)) { -+ // do not accept updates in unlit chunks, unless we might be generating a chunk. thanks to the amazing -+ // chunk scheduling, we could be lighting and generating a chunk at the same time -+ return; -+ } -+ -+ if (center.getStatus() != ChunkStatus.FULL) { -+ // do not keep chunk loaded, we are probably in a gen thread -+ // if we proceed to add a ticket the chunk will be loaded, which is not what we want (avoid cascading gen) -+ runnable.get(); -+ return; -+ } -+ -+ if (!world.getChunkSource().chunkMap.mainThreadExecutor.isSameThread()) { -+ // ticket logic is not safe to run off-main, re-schedule -+ world.getChunkSource().chunkMap.mainThreadExecutor.execute(() -> { -+ this.queueTaskForSection(chunkX, chunkY, chunkZ, runnable); -+ }); -+ return; -+ } -+ -+ final long key = CoordinateUtils.getChunkKey(chunkX, chunkZ); -+ -+ final ca.spottedleaf.starlight.common.light.StarLightInterface.LightQueue.ChunkTasks updateFuture = runnable.get(); -+ -+ if (updateFuture == null) { -+ // not scheduled -+ return; -+ } -+ -+ if (updateFuture.isTicketAdded) { -+ // ticket already added -+ return; -+ } -+ updateFuture.isTicketAdded = true; -+ -+ final int references = this.chunksBeingWorkedOn.addTo(key, 1); -+ if (references == 0) { -+ final ChunkPos pos = new ChunkPos(chunkX, chunkZ); -+ world.getChunkSource().addRegionTicket(ca.spottedleaf.starlight.common.light.StarLightInterface.CHUNK_WORK_TICKET, pos, 0, pos); -+ } -+ -+ updateFuture.onComplete.thenAcceptAsync((final Void ignore) -> { -+ final int newReferences = this.chunksBeingWorkedOn.get(key); -+ if (newReferences == 1) { -+ this.chunksBeingWorkedOn.remove(key); -+ final ChunkPos pos = new ChunkPos(chunkX, chunkZ); -+ world.getChunkSource().removeRegionTicket(ca.spottedleaf.starlight.common.light.StarLightInterface.CHUNK_WORK_TICKET, pos, 0, pos); -+ } else { -+ this.chunksBeingWorkedOn.put(key, newReferences - 1); -+ } -+ }, world.getChunkSource().chunkMap.mainThreadExecutor).whenComplete((final Void ignore, final Throwable thr) -> { -+ if (thr != null) { -+ LOGGER.error("Failed to remove ticket level for post chunk task " + new ChunkPos(chunkX, chunkZ), thr); -+ } -+ }); -+ } -+ -+ @Override -+ public boolean hasLightWork() { -+ // route to new light engine -+ return this.theLightEngine.hasUpdates(); -+ } -+ -+ @Override -+ public LayerLightEventListener getLayerListener(final LightLayer lightType) { -+ return lightType == LightLayer.BLOCK ? this.theLightEngine.getBlockReader() : this.theLightEngine.getSkyReader(); -+ } -+ -+ @Override -+ public int getRawBrightness(final BlockPos pos, final int ambientDarkness) { -+ // need to use new light hooks for this -+ final int sky = this.theLightEngine.getSkyReader().getLightValue(pos) - ambientDarkness; -+ // Don't fetch the block light level if the skylight level is 15, since the value will never be higher. -+ if (sky == 15) return 15; -+ final int block = this.theLightEngine.getBlockReader().getLightValue(pos); -+ return Math.max(sky, block); - } -+ // Paper end - replace light engine imp - - @Override - public void close() { -@@ -57,16 +216,16 @@ public class ThreadedLevelLightEngine extends LevelLightEngine implements AutoCl - - @Override - public void checkBlock(BlockPos pos) { -- BlockPos blockPos = pos.immutable(); -- this.addTask( -- SectionPos.blockToSectionCoord(pos.getX()), -- SectionPos.blockToSectionCoord(pos.getZ()), -- ThreadedLevelLightEngine.TaskType.PRE_UPDATE, -- Util.name(() -> super.checkBlock(blockPos), () -> "checkBlock " + blockPos) -- ); -+ // Paper start - replace light engine impl -+ final BlockPos posCopy = pos.immutable(); -+ this.queueTaskForSection(posCopy.getX() >> 4, posCopy.getY() >> 4, posCopy.getZ() >> 4, () -> { -+ return this.theLightEngine.blockChange(posCopy); -+ }); -+ // Paper end - replace light engine impl - } - - protected void updateChunkStatus(ChunkPos pos) { -+ if (true) return; // Paper - replace light engine impl - this.addTask(pos.x, pos.z, () -> 0, ThreadedLevelLightEngine.TaskType.PRE_UPDATE, Util.name(() -> { - super.retainData(pos, false); - super.setLightEnabled(pos, false); -@@ -84,17 +243,16 @@ public class ThreadedLevelLightEngine extends LevelLightEngine implements AutoCl - - @Override - public void updateSectionStatus(SectionPos pos, boolean notReady) { -- this.addTask( -- pos.x(), -- pos.z(), -- () -> 0, -- ThreadedLevelLightEngine.TaskType.PRE_UPDATE, -- Util.name(() -> super.updateSectionStatus(pos, notReady), () -> "updateSectionStatus " + pos + " " + notReady) -- ); -+ // Paper start - replace light engine impl -+ this.queueTaskForSection(pos.getX(), pos.getY(), pos.getZ(), () -> { -+ return this.theLightEngine.sectionChange(pos, notReady); -+ }); -+ // Paper end - replace light engine impl - } - - @Override - public void propagateLightSources(ChunkPos chunkPos) { -+ if (true) return; // Paper - replace light engine impl - this.addTask( - chunkPos.x, - chunkPos.z, -@@ -105,6 +263,7 @@ public class ThreadedLevelLightEngine extends LevelLightEngine implements AutoCl - - @Override - public void setLightEnabled(ChunkPos pos, boolean retainData) { -+ if (true) return; // Paper - replace light engine impl - this.addTask( - pos.x, - pos.z, -@@ -115,6 +274,7 @@ public class ThreadedLevelLightEngine extends LevelLightEngine implements AutoCl - - @Override - public void queueSectionData(LightLayer lightType, SectionPos pos, @Nullable DataLayer nibbles) { -+ if (true) return; // Paper - replace light engine impl - this.addTask( - pos.x(), - pos.z(), -@@ -139,12 +299,14 @@ public class ThreadedLevelLightEngine extends LevelLightEngine implements AutoCl - - @Override - public void retainData(ChunkPos pos, boolean retainData) { -+ if (true) return; // Paper - replace light engine impl - this.addTask( - pos.x, pos.z, () -> 0, ThreadedLevelLightEngine.TaskType.PRE_UPDATE, Util.name(() -> super.retainData(pos, retainData), () -> "retainData " + pos) - ); - } - - public CompletableFuture initializeLight(ChunkAccess chunk, boolean bl) { -+ if (true) return CompletableFuture.completedFuture(chunk); // Paper - replace light engine impl - ChunkPos chunkPos = chunk.getPos(); - this.addTask(chunkPos.x, chunkPos.z, ThreadedLevelLightEngine.TaskType.PRE_UPDATE, Util.name(() -> { - LevelChunkSection[] levelChunkSections = chunk.getSections(); -@@ -165,6 +327,37 @@ public class ThreadedLevelLightEngine extends LevelLightEngine implements AutoCl - } - - public CompletableFuture lightChunk(ChunkAccess chunk, boolean excludeBlocks) { -+ // Paper start - replace light engine impl -+ if (true) { -+ boolean lit = excludeBlocks; -+ final ChunkPos chunkPos = chunk.getPos(); -+ -+ return CompletableFuture.supplyAsync(() -> { -+ final Boolean[] emptySections = StarLightEngine.getEmptySectionsForChunk(chunk); -+ if (!lit) { -+ chunk.setLightCorrect(false); -+ this.theLightEngine.lightChunk(chunk, emptySections); -+ chunk.setLightCorrect(true); -+ } else { -+ this.theLightEngine.forceLoadInChunk(chunk, emptySections); -+ // can't really force the chunk to be edged checked, as we need neighbouring chunks - but we don't have -+ // them, so if it's not loaded then i guess we can't do edge checks. later loads of the chunk should -+ // catch what we miss here. -+ this.theLightEngine.checkChunkEdges(chunkPos.x, chunkPos.z); -+ } -+ -+ this.chunkMap.releaseLightTicket(chunkPos); -+ return chunk; -+ }, (runnable) -> { -+ this.theLightEngine.scheduleChunkLight(chunkPos, runnable); -+ this.tryScheduleUpdate(); -+ }).whenComplete((final ChunkAccess c, final Throwable throwable) -> { -+ if (throwable != null) { -+ LOGGER.error("Failed to light chunk " + chunkPos, throwable); -+ } -+ }); -+ } -+ // Paper end - replace light engine impl - ChunkPos chunkPos = chunk.getPos(); - chunk.setLightCorrect(false); - this.addTask(chunkPos.x, chunkPos.z, ThreadedLevelLightEngine.TaskType.PRE_UPDATE, Util.name(() -> { -@@ -180,7 +373,7 @@ public class ThreadedLevelLightEngine extends LevelLightEngine implements AutoCl - } - - public void tryScheduleUpdate() { -- if ((!this.lightTasks.isEmpty() || super.hasLightWork()) && this.scheduled.compareAndSet(false, true)) { -+ if (this.hasLightWork() && this.scheduled.compareAndSet(false, true)) { // Paper // Paper - rewrite light engine - this.taskMailbox.tell(() -> { - this.runUpdate(); - this.scheduled.set(false); -@@ -201,7 +394,7 @@ public class ThreadedLevelLightEngine extends LevelLightEngine implements AutoCl - } - - objectListIterator.back(j); -- super.runLightUpdates(); -+ this.theLightEngine.propagateChanges(); // Paper - rewrite light engine - - for (int var5 = 0; objectListIterator.hasNext() && var5 < i; var5++) { - Pair pair2 = objectListIterator.next(); -diff --git a/src/main/java/net/minecraft/server/level/TicketType.java b/src/main/java/net/minecraft/server/level/TicketType.java -index 0d536d72ac918fbd403397ff369d10143ee9c204..6051e5f272838ef23276a90e21c2fc821ca155d1 100644 ---- a/src/main/java/net/minecraft/server/level/TicketType.java -+++ b/src/main/java/net/minecraft/server/level/TicketType.java -@@ -26,6 +26,7 @@ public class TicketType { - public static final TicketType UNKNOWN = TicketType.create("unknown", Comparator.comparingLong(ChunkPos::toLong), 1); - public static final TicketType PLUGIN = TicketType.create("plugin", (a, b) -> 0); // CraftBukkit - public static final TicketType PLUGIN_TICKET = TicketType.create("plugin_ticket", (plugin1, plugin2) -> plugin1.getClass().getName().compareTo(plugin2.getClass().getName())); // CraftBukkit -+ public static final TicketType CHUNK_RELIGHT = create("light_update", Long::compareTo); // Paper - ensure chunks stay loaded for lighting - - public static TicketType create(String name, Comparator argumentComparator) { - return new TicketType<>(name, argumentComparator, 0L); -diff --git a/src/main/java/net/minecraft/server/level/WorldGenRegion.java b/src/main/java/net/minecraft/server/level/WorldGenRegion.java -index 386fbf79afe91af445f54aeab7d1296d1407a4d8..abbd4140cb4478a34a5185d8555f83d96c04d468 100644 ---- a/src/main/java/net/minecraft/server/level/WorldGenRegion.java -+++ b/src/main/java/net/minecraft/server/level/WorldGenRegion.java -@@ -109,6 +109,27 @@ public class WorldGenRegion implements WorldGenLevel { - } - } - -+ // Paper start - starlight -+ @Override -+ public int getBrightness(final net.minecraft.world.level.LightLayer lightLayer, final BlockPos blockPos) { -+ final ChunkAccess chunk = this.getChunk(blockPos.getX() >> 4, blockPos.getZ() >> 4); -+ if (!chunk.isLightCorrect()) { -+ return 0; -+ } -+ return this.getLightEngine().getLayerListener(lightLayer).getLightValue(blockPos); -+ } -+ -+ -+ @Override -+ public int getRawBrightness(final BlockPos blockPos, final int subtract) { -+ final ChunkAccess chunk = this.getChunk(blockPos.getX() >> 4, blockPos.getZ() >> 4); -+ if (!chunk.isLightCorrect()) { -+ return 0; -+ } -+ return this.getLightEngine().getRawBrightness(blockPos, subtract); -+ } -+ // Paper end - starlight -+ - public boolean isOldChunkAround(ChunkPos chunkPos, int checkRadius) { - return this.level.getChunkSource().chunkMap.isOldChunkAround(chunkPos, checkRadius); - } -diff --git a/src/main/java/net/minecraft/world/level/block/state/BlockBehaviour.java b/src/main/java/net/minecraft/world/level/block/state/BlockBehaviour.java -index f87d9cb38caf3bf92fd32f2118f76799ede418db..c7da359c525522b55763e594a1db0c26a026b73f 100644 ---- a/src/main/java/net/minecraft/world/level/block/state/BlockBehaviour.java -+++ b/src/main/java/net/minecraft/world/level/block/state/BlockBehaviour.java -@@ -812,6 +812,7 @@ public abstract class BlockBehaviour implements FeatureElement { - this.spawnTerrainParticles = blockbase_info.spawnTerrainParticles; - this.instrument = blockbase_info.instrument; - this.replaceable = blockbase_info.replaceable; -+ this.conditionallyFullOpaque = this.canOcclude & this.useShapeForLightOcclusion; // Paper - } - // Paper start - Perf: impl cached craft block data, lazy load to fix issue with loading at the wrong time - private org.bukkit.craftbukkit.block.data.CraftBlockData cachedCraftBlockData; -@@ -848,6 +849,18 @@ public abstract class BlockBehaviour implements FeatureElement { - return this.shapeExceedsCube; - } - // Paper end -+ // Paper start - starlight -+ protected int opacityIfCached = -1; -+ // ret -1 if opacity is dynamic, or -1 if the block is conditionally full opaque, else return opacity in [0, 15] -+ public final int getOpacityIfCached() { -+ return this.opacityIfCached; -+ } -+ -+ protected final boolean conditionallyFullOpaque; -+ public final boolean isConditionallyFullOpaque() { -+ return this.conditionallyFullOpaque; -+ } -+ // Paper end - starlight - - public void initCache() { - this.fluidState = ((Block) this.owner).getFluidState(this.asState()); -@@ -856,6 +869,7 @@ public abstract class BlockBehaviour implements FeatureElement { - this.cache = new BlockBehaviour.BlockStateBase.Cache(this.asState()); - } - this.shapeExceedsCube = this.cache == null || this.cache.largeCollisionShape; // Paper - moved from actual method to here -+ this.opacityIfCached = this.cache == null || this.isConditionallyFullOpaque() ? -1 : this.cache.lightBlock; // Paper - starlight - cache opacity for light - - this.legacySolid = this.calculateSolid(); - } -diff --git a/src/main/java/net/minecraft/world/level/chunk/ChunkAccess.java b/src/main/java/net/minecraft/world/level/chunk/ChunkAccess.java -index f4e3bd2ae4f63e6d3d25463a3635b8f89fecc068..1f8c72b6c7d8683d67880fa175843c73b3d39b78 100644 ---- a/src/main/java/net/minecraft/world/level/chunk/ChunkAccess.java -+++ b/src/main/java/net/minecraft/world/level/chunk/ChunkAccess.java -@@ -77,7 +77,7 @@ public abstract class ChunkAccess implements BlockGetter, BiomeManager.NoiseBiom - @Nullable - protected BlendingData blendingData; - public final Map heightmaps = Maps.newEnumMap(Heightmap.Types.class); -- protected ChunkSkyLightSources skyLightSources; -+ // Paper - starlight - remove skyLightSources - private final Map structureStarts = Maps.newHashMap(); - private final Map structuresRefences = Maps.newHashMap(); - protected final Map pendingBlockEntities = Maps.newHashMap(); -@@ -89,8 +89,55 @@ public abstract class ChunkAccess implements BlockGetter, BiomeManager.NoiseBiom - private static final org.bukkit.craftbukkit.persistence.CraftPersistentDataTypeRegistry DATA_TYPE_REGISTRY = new org.bukkit.craftbukkit.persistence.CraftPersistentDataTypeRegistry(); - public org.bukkit.craftbukkit.persistence.DirtyCraftPersistentDataContainer persistentDataContainer = new org.bukkit.craftbukkit.persistence.DirtyCraftPersistentDataContainer(ChunkAccess.DATA_TYPE_REGISTRY); - // CraftBukkit end -+ // Paper start - rewrite light engine -+ private volatile ca.spottedleaf.starlight.common.light.SWMRNibbleArray[] blockNibbles; -+ -+ private volatile ca.spottedleaf.starlight.common.light.SWMRNibbleArray[] skyNibbles; -+ -+ private volatile boolean[] skyEmptinessMap; -+ -+ private volatile boolean[] blockEmptinessMap; -+ -+ public ca.spottedleaf.starlight.common.light.SWMRNibbleArray[] getBlockNibbles() { -+ return this.blockNibbles; -+ } -+ -+ public void setBlockNibbles(final ca.spottedleaf.starlight.common.light.SWMRNibbleArray[] nibbles) { -+ this.blockNibbles = nibbles; -+ } -+ -+ public ca.spottedleaf.starlight.common.light.SWMRNibbleArray[] getSkyNibbles() { -+ return this.skyNibbles; -+ } -+ -+ public void setSkyNibbles(final ca.spottedleaf.starlight.common.light.SWMRNibbleArray[] nibbles) { -+ this.skyNibbles = nibbles; -+ } -+ -+ public boolean[] getSkyEmptinessMap() { -+ return this.skyEmptinessMap; -+ } -+ -+ public void setSkyEmptinessMap(final boolean[] emptinessMap) { -+ this.skyEmptinessMap = emptinessMap; -+ } -+ -+ public boolean[] getBlockEmptinessMap() { -+ return this.blockEmptinessMap; -+ } -+ -+ public void setBlockEmptinessMap(final boolean[] emptinessMap) { -+ this.blockEmptinessMap = emptinessMap; -+ } -+ // Paper end - rewrite light engine - - public ChunkAccess(ChunkPos pos, UpgradeData upgradeData, LevelHeightAccessor heightLimitView, Registry biomeRegistry, long inhabitedTime, @Nullable LevelChunkSection[] sectionArray, @Nullable BlendingData blendingData) { -+ // Paper start - rewrite light engine -+ if (!(this instanceof ImposterProtoChunk)) { -+ this.setBlockNibbles(ca.spottedleaf.starlight.common.light.StarLightEngine.getFilledEmptyLight(heightLimitView)); -+ this.setSkyNibbles(ca.spottedleaf.starlight.common.light.StarLightEngine.getFilledEmptyLight(heightLimitView)); -+ } -+ // Paper end - rewrite light engine - this.locX = pos.x; this.locZ = pos.z; // Paper - reduce need for field lookups - this.chunkPos = pos; this.coordinateKey = ChunkPos.asLong(locX, locZ); // Paper - cache long key - this.upgradeData = upgradeData; -@@ -99,7 +146,7 @@ public abstract class ChunkAccess implements BlockGetter, BiomeManager.NoiseBiom - this.inhabitedTime = inhabitedTime; - this.postProcessing = new ShortList[heightLimitView.getSectionsCount()]; - this.blendingData = blendingData; -- this.skyLightSources = new ChunkSkyLightSources(heightLimitView); -+ // Paper - starlight - remove skyLightSources - if (sectionArray != null) { - if (this.sections.length == sectionArray.length) { - System.arraycopy(sectionArray, 0, this.sections, 0, this.sections.length); -@@ -510,12 +557,12 @@ public abstract class ChunkAccess implements BlockGetter, BiomeManager.NoiseBiom - } - - public void initializeLightSources() { -- this.skyLightSources.fillFrom(this); -+ // Paper - starlight - remove skyLightSources - } - - @Override - public ChunkSkyLightSources getSkyLightSources() { -- return this.skyLightSources; -+ return null; // Paper - starlight - remove skyLightSources - } - - public static record TicksToSave(SerializableTickContainer blocks, SerializableTickContainer fluids) { -diff --git a/src/main/java/net/minecraft/world/level/chunk/EmptyLevelChunk.java b/src/main/java/net/minecraft/world/level/chunk/EmptyLevelChunk.java -index 2ee1658532cb00d7bcd1d11e03f19d21ca7f2a9e..ac754827172a4de600d0a57a7d11853481a2dbf2 100644 ---- a/src/main/java/net/minecraft/world/level/chunk/EmptyLevelChunk.java -+++ b/src/main/java/net/minecraft/world/level/chunk/EmptyLevelChunk.java -@@ -21,6 +21,40 @@ public class EmptyLevelChunk extends LevelChunk { - this.biome = biomeEntry; - } - -+ // Paper start - starlight -+ @Override -+ public ca.spottedleaf.starlight.common.light.SWMRNibbleArray[] getBlockNibbles() { -+ return ca.spottedleaf.starlight.common.light.StarLightEngine.getFilledEmptyLight(this.getLevel()); -+ } -+ -+ @Override -+ public void setBlockNibbles(final ca.spottedleaf.starlight.common.light.SWMRNibbleArray[] nibbles) {} -+ -+ @Override -+ public ca.spottedleaf.starlight.common.light.SWMRNibbleArray[] getSkyNibbles() { -+ return ca.spottedleaf.starlight.common.light.StarLightEngine.getFilledEmptyLight(this.getLevel()); -+ } -+ -+ @Override -+ public void setSkyNibbles(final ca.spottedleaf.starlight.common.light.SWMRNibbleArray[] nibbles) {} -+ -+ @Override -+ public boolean[] getSkyEmptinessMap() { -+ return null; -+ } -+ -+ @Override -+ public void setSkyEmptinessMap(final boolean[] emptinessMap) {} -+ -+ @Override -+ public boolean[] getBlockEmptinessMap() { -+ return null; -+ } -+ -+ @Override -+ public void setBlockEmptinessMap(final boolean[] emptinessMap) {} -+ // Paper end - starlight -+ - @Override - public BlockState getBlockState(BlockPos pos) { - return Blocks.VOID_AIR.defaultBlockState(); -diff --git a/src/main/java/net/minecraft/world/level/chunk/ImposterProtoChunk.java b/src/main/java/net/minecraft/world/level/chunk/ImposterProtoChunk.java -index 2953e93965aa688be8fc1620580701ba0c9d907e..aa5dee839d4c0dbc3c2abee9b501ec250c575cb3 100644 ---- a/src/main/java/net/minecraft/world/level/chunk/ImposterProtoChunk.java -+++ b/src/main/java/net/minecraft/world/level/chunk/ImposterProtoChunk.java -@@ -47,6 +47,48 @@ public class ImposterProtoChunk extends ProtoChunk { - this.allowWrites = propagateToWrapped; - } - -+ // Paper start - rewrite light engine -+ @Override -+ public ca.spottedleaf.starlight.common.light.SWMRNibbleArray[] getBlockNibbles() { -+ return this.wrapped.getBlockNibbles(); -+ } -+ -+ @Override -+ public void setBlockNibbles(final ca.spottedleaf.starlight.common.light.SWMRNibbleArray[] nibbles) { -+ this.wrapped.setBlockNibbles(nibbles); -+ } -+ -+ @Override -+ public ca.spottedleaf.starlight.common.light.SWMRNibbleArray[] getSkyNibbles() { -+ return this.wrapped.getSkyNibbles(); -+ } -+ -+ @Override -+ public void setSkyNibbles(final ca.spottedleaf.starlight.common.light.SWMRNibbleArray[] nibbles) { -+ this.wrapped.setSkyNibbles(nibbles); -+ } -+ -+ @Override -+ public boolean[] getSkyEmptinessMap() { -+ return this.wrapped.getSkyEmptinessMap(); -+ } -+ -+ @Override -+ public void setSkyEmptinessMap(final boolean[] emptinessMap) { -+ this.wrapped.setSkyEmptinessMap(emptinessMap); -+ } -+ -+ @Override -+ public boolean[] getBlockEmptinessMap() { -+ return this.wrapped.getBlockEmptinessMap(); -+ } -+ -+ @Override -+ public void setBlockEmptinessMap(final boolean[] emptinessMap) { -+ this.wrapped.setBlockEmptinessMap(emptinessMap); -+ } -+ // Paper end - rewrite light engine -+ - @Nullable - @Override - public BlockEntity getBlockEntity(BlockPos pos) { -diff --git a/src/main/java/net/minecraft/world/level/chunk/LevelChunk.java b/src/main/java/net/minecraft/world/level/chunk/LevelChunk.java -index 8de6ad8b131061b2dae440dff71e2e6e7af2de39..bac191f92ea3735df19c68d5568c2c7962c8680f 100644 ---- a/src/main/java/net/minecraft/world/level/chunk/LevelChunk.java -+++ b/src/main/java/net/minecraft/world/level/chunk/LevelChunk.java -@@ -222,6 +222,12 @@ public class LevelChunk extends ChunkAccess { - - public LevelChunk(ServerLevel world, ProtoChunk protoChunk, @Nullable LevelChunk.PostLoadProcessor entityLoader) { - this(world, protoChunk.getPos(), protoChunk.getUpgradeData(), protoChunk.unpackBlockTicks(), protoChunk.unpackFluidTicks(), protoChunk.getInhabitedTime(), protoChunk.getSections(), entityLoader, protoChunk.getBlendingData()); -+ // Paper start - rewrite light engine -+ this.setBlockNibbles(protoChunk.getBlockNibbles()); -+ this.setSkyNibbles(protoChunk.getSkyNibbles()); -+ this.setSkyEmptinessMap(protoChunk.getSkyEmptinessMap()); -+ this.setBlockEmptinessMap(protoChunk.getBlockEmptinessMap()); -+ // Paper end - rewrite light engine - Iterator iterator = protoChunk.getBlockEntities().values().iterator(); - - while (iterator.hasNext()) { -@@ -248,7 +254,7 @@ public class LevelChunk extends ChunkAccess { - } - } - -- this.skyLightSources = protoChunk.skyLightSources; -+ // Paper - starlight - remove skyLightSources - this.setLightCorrect(protoChunk.isLightCorrect()); - this.unsaved = true; - this.needsDecoration = true; // CraftBukkit -@@ -437,7 +443,7 @@ public class LevelChunk extends ChunkAccess { - ProfilerFiller gameprofilerfiller = this.level.getProfiler(); - - gameprofilerfiller.push("updateSkyLightSources"); -- this.skyLightSources.update(this, j, i, l); -+ // Paper - starlight - remove skyLightSources - gameprofilerfiller.popPush("queueCheckLight"); - this.level.getChunkSource().getLightEngine().checkBlock(blockposition); - gameprofilerfiller.pop(); -diff --git a/src/main/java/net/minecraft/world/level/chunk/PalettedContainer.java b/src/main/java/net/minecraft/world/level/chunk/PalettedContainer.java -index 2fa0097a9374a89177e4f1068d1bfed30b8ff122..fa9df6ebcd90d4e9e5836a37212b1f60665783b1 100644 ---- a/src/main/java/net/minecraft/world/level/chunk/PalettedContainer.java -+++ b/src/main/java/net/minecraft/world/level/chunk/PalettedContainer.java -@@ -155,7 +155,7 @@ public class PalettedContainer implements PaletteResize, PalettedContainer - return this.get(this.strategy.getIndex(x, y, z)); - } - -- protected T get(int index) { -+ public T get(int index) { // Paper - public - PalettedContainer.Data data = this.data; - return data.palette.valueFor(data.storage.get(index)); - } -diff --git a/src/main/java/net/minecraft/world/level/chunk/ProtoChunk.java b/src/main/java/net/minecraft/world/level/chunk/ProtoChunk.java -index bcc70883d23d38c408130ffe778205e371ff4e8a..576ae0cb138b265c8a3995de7b5ebc827d50949d 100644 ---- a/src/main/java/net/minecraft/world/level/chunk/ProtoChunk.java -+++ b/src/main/java/net/minecraft/world/level/chunk/ProtoChunk.java -@@ -143,7 +143,7 @@ public class ProtoChunk extends ChunkAccess { - } - - if (LightEngine.hasDifferentLightProperties(this, pos, blockState, state)) { -- this.skyLightSources.update(this, m, j, o); -+ // Paper - starlight - remove skyLightSources - this.lightEngine.checkBlock(pos); - } - } -diff --git a/src/main/java/net/minecraft/world/level/chunk/status/ChunkStatus.java b/src/main/java/net/minecraft/world/level/chunk/status/ChunkStatus.java -index ae992aeb8b836e8c2e5bab338ae46cc31c317245..95318092f8281d98132d1d3ceb4a5c36cf32eb05 100644 ---- a/src/main/java/net/minecraft/world/level/chunk/status/ChunkStatus.java -+++ b/src/main/java/net/minecraft/world/level/chunk/status/ChunkStatus.java -@@ -117,6 +117,18 @@ public class ChunkStatus { - private final ChunkType chunkType; - private final EnumSet heightmapsAfter; - -+ // Paper start - starlight -+ public static ChunkStatus getStatus(String name) { -+ try { -+ // We need this otherwise we return EMPTY for invalid names -+ ResourceLocation key = new ResourceLocation(name); -+ return BuiltInRegistries.CHUNK_STATUS.getOptional(key).orElse(null); -+ } catch (Exception ex) { -+ return null; // invalid name -+ } -+ } -+ // Paper end - starlight -+ - private static ChunkStatus register( - String id, - @Nullable ChunkStatus previous, -diff --git a/src/main/java/net/minecraft/world/level/chunk/storage/ChunkSerializer.java b/src/main/java/net/minecraft/world/level/chunk/storage/ChunkSerializer.java -index 88f0aca2da0e14ed5ec0513944fa0ba28b73b5d1..01d6b8683a9fa30d05b03ebfef8ee2dca4e83a56 100644 ---- a/src/main/java/net/minecraft/world/level/chunk/storage/ChunkSerializer.java -+++ b/src/main/java/net/minecraft/world/level/chunk/storage/ChunkSerializer.java -@@ -90,6 +90,14 @@ public class ChunkSerializer { - private static final int CURRENT_DATA_VERSION = net.minecraft.SharedConstants.getCurrentVersion().getDataVersion().getVersion(); - private static final boolean JUST_CORRUPT_IT = Boolean.getBoolean("Paper.ignoreWorldDataVersion"); - // Paper end - Do not let the server load chunks from newer versions -+ // Paper start - replace light engine impl -+ private static final int STARLIGHT_LIGHT_VERSION = 9; -+ -+ private static final String BLOCKLIGHT_STATE_TAG = "starlight.blocklight_state"; -+ private static final String SKYLIGHT_STATE_TAG = "starlight.skylight_state"; -+ private static final String STARLIGHT_VERSION_TAG = "starlight.light_version"; -+ // Paper end - replace light engine impl -+ - public ChunkSerializer() {} - - // Paper start - guard against serializing mismatching coordinates -@@ -121,19 +129,26 @@ public class ChunkSerializer { - } - - UpgradeData chunkconverter = nbt.contains("UpgradeData", 10) ? new UpgradeData(nbt.getCompound("UpgradeData"), world) : UpgradeData.EMPTY; -- boolean flag = nbt.getBoolean("isLightOn"); -+ boolean flag = getStatus(nbt) != null && getStatus(nbt).isOrAfter(ChunkStatus.LIGHT) && nbt.get("isLightOn") != null && nbt.getInt(STARLIGHT_VERSION_TAG) == STARLIGHT_LIGHT_VERSION; // Paper - ListTag nbttaglist = nbt.getList("sections", 10); - int i = world.getSectionsCount(); - LevelChunkSection[] achunksection = new LevelChunkSection[i]; - boolean flag1 = world.dimensionType().hasSkyLight(); - ServerChunkCache chunkproviderserver = world.getChunkSource(); - LevelLightEngine levellightengine = chunkproviderserver.getLightEngine(); -+ // Paper start -+ ca.spottedleaf.starlight.common.light.SWMRNibbleArray[] blockNibbles = ca.spottedleaf.starlight.common.light.StarLightEngine.getFilledEmptyLight(world); -+ ca.spottedleaf.starlight.common.light.SWMRNibbleArray[] skyNibbles = ca.spottedleaf.starlight.common.light.StarLightEngine.getFilledEmptyLight(world); -+ final int minSection = io.papermc.paper.util.WorldUtil.getMinLightSection(world); -+ final int maxSection = io.papermc.paper.util.WorldUtil.getMaxLightSection(world); -+ boolean canReadSky = world.dimensionType().hasSkyLight(); -+ // Paper end - Registry iregistry = world.registryAccess().registryOrThrow(Registries.BIOME); - Codec>> codec = ChunkSerializer.makeBiomeCodecRW(iregistry); // CraftBukkit - read/write - boolean flag2 = false; - - for (int j = 0; j < nbttaglist.size(); ++j) { -- CompoundTag nbttagcompound1 = nbttaglist.getCompound(j); -+ CompoundTag nbttagcompound1 = nbttaglist.getCompound(j); CompoundTag sectionData = nbttagcompound1; // Paper - byte b0 = nbttagcompound1.getByte("Y"); - int k = world.getSectionIndexFromSectionY(b0); - -@@ -169,19 +184,39 @@ public class ChunkSerializer { - boolean flag3 = nbttagcompound1.contains("BlockLight", 7); - boolean flag4 = flag1 && nbttagcompound1.contains("SkyLight", 7); - -- if (flag3 || flag4) { -- if (!flag2) { -- levellightengine.retainData(chunkPos, true); -- flag2 = true; -- } -- -+ // Paper start - rewrite the light engine -+ if (flag) { -+ try { -+ int y = sectionData.getByte("Y"); -+ // Paper end - rewrite the light engine - if (flag3) { -- levellightengine.queueSectionData(LightLayer.BLOCK, SectionPos.of(chunkPos, b0), new DataLayer(nbttagcompound1.getByteArray("BlockLight"))); -+ // Paper start - rewrite the light engine -+ // this is where our diff is -+ blockNibbles[y - minSection] = new ca.spottedleaf.starlight.common.light.SWMRNibbleArray(sectionData.getByteArray("BlockLight").clone(), sectionData.getInt(BLOCKLIGHT_STATE_TAG)); // clone for data safety -+ } else { -+ blockNibbles[y - minSection] = new ca.spottedleaf.starlight.common.light.SWMRNibbleArray(null, sectionData.getInt(BLOCKLIGHT_STATE_TAG)); -+ // Paper end - rewrite the light engine - } - - if (flag4) { -- levellightengine.queueSectionData(LightLayer.SKY, SectionPos.of(chunkPos, b0), new DataLayer(nbttagcompound1.getByteArray("SkyLight"))); -+ // Paper start - rewrite the light engine -+ // we store under the same key so mod programs editing nbt -+ // can still read the data, hopefully. -+ // however, for compatibility we store chunks as unlit so vanilla -+ // is forced to re-light them if it encounters our data. It's too much of a burden -+ // to try and maintain compatibility with a broken and inferior skylight management system. -+ skyNibbles[y - minSection] = new ca.spottedleaf.starlight.common.light.SWMRNibbleArray(sectionData.getByteArray("SkyLight").clone(), sectionData.getInt(SKYLIGHT_STATE_TAG)); // clone for data safety -+ } else if (flag1) { -+ skyNibbles[y - minSection] = new ca.spottedleaf.starlight.common.light.SWMRNibbleArray(null, sectionData.getInt(SKYLIGHT_STATE_TAG)); -+ // Paper end - rewrite the light engine -+ } -+ -+ // Paper start - rewrite the light engine -+ } catch (Exception ex) { -+ LOGGER.warn("Failed to load light data for chunk " + chunkPos + " in world '" + world.getWorld().getName() + "', light will be regenerated", ex); -+ flag = false; - } -+ // Paper end - rewrite light engine - } - } - -@@ -211,6 +246,8 @@ public class ChunkSerializer { - }, chunkPos); - - object1 = new LevelChunk(world.getLevel(), chunkPos, chunkconverter, levelchunkticks, levelchunkticks1, l, achunksection, ChunkSerializer.postLoadChunk(world, nbt), blendingdata); -+ ((LevelChunk)object1).setBlockNibbles(blockNibbles); // Paper - replace light impl -+ ((LevelChunk)object1).setSkyNibbles(skyNibbles); // Paper - replace light impl - } else { - ProtoChunkTicks protochunkticklist = ProtoChunkTicks.load(nbt.getList("block_ticks", 10), (s) -> { - return BuiltInRegistries.BLOCK.getOptional(ResourceLocation.tryParse(s)); -@@ -219,6 +256,8 @@ public class ChunkSerializer { - return BuiltInRegistries.FLUID.getOptional(ResourceLocation.tryParse(s)); - }, chunkPos); - ProtoChunk protochunk = new ProtoChunk(chunkPos, chunkconverter, achunksection, protochunkticklist, protochunkticklist1, world, iregistry, blendingdata); -+ protochunk.setBlockNibbles(blockNibbles); // Paper - replace light impl -+ protochunk.setSkyNibbles(skyNibbles); // Paper - replace light impl - - object1 = protochunk; - protochunk.setInhabitedTime(l); -@@ -340,6 +379,12 @@ public class ChunkSerializer { - // CraftBukkit end - - public static CompoundTag write(ServerLevel world, ChunkAccess chunk) { -+ // Paper start - rewrite light impl -+ final int minSection = io.papermc.paper.util.WorldUtil.getMinLightSection(world); -+ final int maxSection = io.papermc.paper.util.WorldUtil.getMaxLightSection(world); -+ ca.spottedleaf.starlight.common.light.SWMRNibbleArray[] blockNibbles = chunk.getBlockNibbles(); -+ ca.spottedleaf.starlight.common.light.SWMRNibbleArray[] skyNibbles = chunk.getSkyNibbles(); -+ // Paper end - rewrite light impl - ChunkPos chunkcoordintpair = chunk.getPos(); - CompoundTag nbttagcompound = NbtUtils.addCurrentDataVersion(new CompoundTag()); - -@@ -389,11 +434,14 @@ public class ChunkSerializer { - for (int i = lightenginethreaded.getMinLightSection(); i < lightenginethreaded.getMaxLightSection(); ++i) { - int j = chunk.getSectionIndexFromSectionY(i); - boolean flag1 = j >= 0 && j < achunksection.length; -- DataLayer nibblearray = lightenginethreaded.getLayerListener(LightLayer.BLOCK).getDataLayerData(SectionPos.of(chunkcoordintpair, i)); -- DataLayer nibblearray1 = lightenginethreaded.getLayerListener(LightLayer.SKY).getDataLayerData(SectionPos.of(chunkcoordintpair, i)); -+ // Paper - replace light engine - -- if (flag1 || nibblearray != null || nibblearray1 != null) { -- CompoundTag nbttagcompound1 = new CompoundTag(); -+ // Paper start - replace light engine -+ ca.spottedleaf.starlight.common.light.SWMRNibbleArray.SaveState blockNibble = blockNibbles[i - minSection].getSaveState(); -+ ca.spottedleaf.starlight.common.light.SWMRNibbleArray.SaveState skyNibble = skyNibbles[i - minSection].getSaveState(); -+ if (flag1 || blockNibble != null || skyNibble != null) { -+ // Paper end - replace light engine -+ CompoundTag nbttagcompound1 = new CompoundTag(); CompoundTag section = nbttagcompound1; // Paper - - if (flag1) { - LevelChunkSection chunksection = achunksection[j]; -@@ -402,13 +450,27 @@ public class ChunkSerializer { - nbttagcompound1.put("biomes", (Tag) codec.encodeStart(NbtOps.INSTANCE, chunksection.getBiomes()).getOrThrow()); - } - -- if (nibblearray != null && !nibblearray.isEmpty()) { -- nbttagcompound1.putByteArray("BlockLight", nibblearray.getData()); -+ // Paper start -+ // we store under the same key so mod programs editing nbt -+ // can still read the data, hopefully. -+ // however, for compatibility we store chunks as unlit so vanilla -+ // is forced to re-light them if it encounters our data. It's too much of a burden -+ // to try and maintain compatibility with a broken and inferior skylight management system. -+ -+ if (blockNibble != null) { -+ if (blockNibble.data != null) { -+ section.putByteArray("BlockLight", blockNibble.data); -+ } -+ section.putInt(BLOCKLIGHT_STATE_TAG, blockNibble.state); - } - -- if (nibblearray1 != null && !nibblearray1.isEmpty()) { -- nbttagcompound1.putByteArray("SkyLight", nibblearray1.getData()); -+ if (skyNibble != null) { -+ if (skyNibble.data != null) { -+ section.putByteArray("SkyLight", skyNibble.data); -+ } -+ section.putInt(SKYLIGHT_STATE_TAG, skyNibble.state); - } -+ // Paper end - - if (!nbttagcompound1.isEmpty()) { - nbttagcompound1.putByte("Y", (byte) i); -@@ -419,7 +481,8 @@ public class ChunkSerializer { - - nbttagcompound.put("sections", nbttaglist); - if (flag) { -- nbttagcompound.putBoolean("isLightOn", true); -+ nbttagcompound.putInt(STARLIGHT_VERSION_TAG, STARLIGHT_LIGHT_VERSION); // Paper -+ nbttagcompound.putBoolean("isLightOn", false); // Paper - set to false but still store, this allows us to detect --eraseCache (as eraseCache _removes_) - } - - ListTag nbttaglist1 = new ListTag(); -@@ -493,6 +556,17 @@ public class ChunkSerializer { - })); - } - -+ // Paper start -+ public static @Nullable ChunkStatus getStatus(@Nullable CompoundTag compound) { -+ if (compound == null) { -+ return null; -+ } -+ -+ // Note: Copied from below -+ return ChunkStatus.getStatus(compound.getString("Status")); -+ } -+ // Paper end -+ - public static ChunkType getChunkTypeFromTag(@Nullable CompoundTag nbt) { - return nbt != null ? ChunkStatus.byName(nbt.getString("Status")).getChunkType() : ChunkType.PROTOCHUNK; - } -diff --git a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java -index 44ed6bd76fb9e81f6c0d99fe46173685dbbfe2a7..7aee9f6b143c89cf8d65ca55eeda808152b4dd26 100644 ---- a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java -+++ b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java -@@ -507,12 +507,7 @@ public class CraftWorld extends CraftRegionAccessor implements World { - } - } - -- for (final ChunkPos pos : chunksToRelight) { -- final ChunkAccess chunk = serverChunkCache.getChunk(pos.x, pos.z, false); -- if (chunk != null) { -- serverChunkCache.getLightEngine().lightChunk(chunk, false); -- } -- } -+ serverChunkCache.getLightEngine().relight(chunksToRelight, pos -> {}, relit -> {}); // Paper - Starlight - - return true; - // Paper end - implement regenerate chunk method diff --git a/patches/removed/1.21/0994-Rewrite-chunk-system.patch b/patches/removed/1.21/0994-Rewrite-chunk-system.patch deleted file mode 100644 index 4df84d014cfd..000000000000 --- a/patches/removed/1.21/0994-Rewrite-chunk-system.patch +++ /dev/null @@ -1,21846 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Spottedleaf -Date: Thu, 11 Mar 2021 02:32:30 -0800 -Subject: [PATCH] Rewrite chunk system - -Rebased patches: - -New player chunk loader system - -Make ChunkStatus.EMPTY not rely on the main thread for completion - -In order to do this, we need to push the POI consistency checks -to a later status. Since FULL is the only other status that -uses the main thread, it can go there. - -The consistency checks are only really for when a desync occurs, -and so that delaying the check only matters when the chunk data -has desync'd. As long as the desync is sorted before the -chunk is full loaded (i.e before setBlock can occur on -a chunk), it should not matter. - -This change is primarily due to behavioural changes -in the chunk task queue brought by region threading - -which is to split the queue into separate regions. As such, -it is required that in order for the sync load to complete -that the region owning the chunk drain and execute the task -while ticking. However, that is not always possible in -region threading. Thus, removing the main thread reliance allows -the chunk to progress without requiring a tick thread. -Specifically, this allows far sync loads (outside of a specific -regions bounds) to occur without issue - namely with structure -searching. - -Increase parallelism for neighbour writing chunk statuses - -Namely, everything after FEATURES. By creating a dependency -chain indicating what chunks are in use, we can safely -schedule completely independent tasks in parallel. This -will allow the chunk system to scale beyond 10 threads -per world. - -Properly cancel chunk load tasks that were not scheduled - -Since the chunk load task was not scheduled, the entity/poi load -task fields will not be set, but the task complete counter -will not be adjusted. Thus, the chunk load task will not complete. - -To resolve this, detect when the entity/poi tasks were not scheduled -and decrement the task complete counter in such cases. - -Mark POI/Entity load tasks as completed before releasing scheduling lock - -It must be marked as completed during that lock hold since the -waiters field is set to null. Thus, any other thread attempting -a cancellation will fail to remove from waiters. Also, any -other thread attempting to cancel may set the completed field -to true which would cause accept() to fail as well. - -Completion was always designed to happen while holding the -scheduling lock to prevent these race conditions. The code -was originally set up to complete while not holding the -scheduling lock to avoid invoking callbacks while holding the -lock, however the access to the completion field was not -considered. - -Resolve this by marking the callback as completed during the -lock, but invoking the accept() function after releasing -the lock. This will prevent any cancellation attempts to be -blocked, and allow the current thread to complete the callback -without any issues. - -Cache whether region files do not exist - -The repeated I/O of creating the directory for the regionfile -or for checking if the file exists can be heavy in -when pushing chunk generation extremely hard - as each chunk gen -request may effectively go through to the I/O thread. - -Use coordinate-based locking to increase chunk system parallelism - -A significant overhead in Folia comes from the chunk system's -locks, the ticket lock and the scheduling lock. The public -test server, which had ~330 players, had signficant performance -problems with these locks: ~80% of the time spent ticking -was _waiting_ for the locks to free. Given that it used -around 15 cores total at peak, this is a complete and utter loss -of potential. - -To address this issue, I have replaced the ticket lock and scheduling -lock with two ReentrantAreaLocks. The ReentrantAreaLock takes a -shift, which is used internally to group positions into sections. -This grouping is neccessary, as the possible radius of area that -needs to be acquired for any given lock usage is up to 64. As such, -the shift is critical to reduce the number of areas required to lock -for any lock operation. Currently, it is set to a shift of 6, which -is identical to the ticket level propagation shift (and, it must be -at least the ticket level propagation shift AND the region shift). - -The chunk system locking changes required a complete rewrite of the -chunk system tick, chunk system unload, and chunk system ticket level -propagation - as all of the previous logic only works with a single -global lock. - -This does introduce two other section shifts: the lock shift, and the -ticket shift. The lock shift is simply what shift the area locks use, -and the ticket shift represents the size of the ticket sections. -Currently, these values are just set to the region shift for simplicity. -However, they are not arbitrary: the lock shift must be at least the size -of the ticket shift and must be at least the size of the region shift. -The ticket shift must also be >= the ceil(log2(max ticket level source)). - -The chunk system's ticket propagator is now global state, instead of -region state. This cleans up the logic for ticket levels significantly, -and removes usage of the region lock in this area, but it also means -that the addition of a ticket no longer creates a region. To alleviate -the side effects of this change, the global tick thread now processes -ticket level updates for each world every tick to guarantee eventual -ticket level processing. The chunk system also provides a hook to -process ticket level changes in a given _section_, so that the -region queue can guarantee that after adding its reference counter -that the region section is created/exists/wont be destroyed. - -The ticket propagator operates by updating the sources in a single ticket -section, and propagating the updates to its 1 radius neighbours. This -allows the ticket updates to occur in parallel or selectively (see above). -Currently, the process ticket level update function operates by -polling from a concurrent queue of sections to update and simply -invoking the single section update logic. This allows the function -to operate completely in parallel, provided the queue is ordered right. -Additionally, this limits the area used in the ticket/scheduling lock -when processing updates, which should massively increase parallelism compared -to before. - -The chunk system ticket addition for expirable ticket types has been modified -to no longer track exact tick deadlines, as this relies on what region the -ticket is in. Instead, the chunk system tracks a map of -lock section -> (chunk coordinate -> expire ticket count) and every ticket -has been changed to have a removeDelay count that is decremented each tick. -Each region searches its own sections to find tickets to try to expire. - -Chunk system unloading has been modified to track unloads by lock section. -The ordering is determined by which section a chunk resides in. -The unload process now removes from unload sections and processes -the full unload stages (1, 2, 3) before moving to the next section, if possible. -This allows the unload logic to only hold one lock section at a time for -each lock, which is a massive parallelism increase. - -In stress testing, these changes lowered the locking overhead to only 5% -from ~70%, which completely fix the original problem as described. - -== AT == -public net.minecraft.server.level.ChunkHolder pos -public net.minecraft.server.level.ChunkMap overworldDataStorage -public-f net.minecraft.world.level.chunk.storage.RegionFileStorage -public net.minecraft.server.level.ChunkMap getPoiManager()Lnet/minecraft/world/entity/ai/village/poi/PoiManager; - -diff --git a/src/main/java/ca/spottedleaf/concurrentutil/lock/ReentrantAreaLock.java b/src/main/java/ca/spottedleaf/concurrentutil/lock/ReentrantAreaLock.java -new file mode 100644 -index 0000000000000000000000000000000000000000..4fd9a0cd8f1e6ae1a97e963dc7731a80bc6fac5b ---- /dev/null -+++ b/src/main/java/ca/spottedleaf/concurrentutil/lock/ReentrantAreaLock.java -@@ -0,0 +1,395 @@ -+package ca.spottedleaf.concurrentutil.lock; -+ -+import ca.spottedleaf.concurrentutil.collection.MultiThreadedQueue; -+import it.unimi.dsi.fastutil.HashCommon; -+import java.util.ArrayList; -+import java.util.List; -+import java.util.concurrent.ConcurrentHashMap; -+import java.util.concurrent.locks.LockSupport; -+ -+public final class ReentrantAreaLock { -+ -+ public final int coordinateShift; -+ -+ // aggressive load factor to reduce contention -+ private final ConcurrentHashMap nodes = new ConcurrentHashMap<>(128, 0.2f); -+ -+ public ReentrantAreaLock(final int coordinateShift) { -+ this.coordinateShift = coordinateShift; -+ } -+ -+ public boolean isHeldByCurrentThread(final int x, final int z) { -+ final Thread currThread = Thread.currentThread(); -+ final int shift = this.coordinateShift; -+ final int sectionX = x >> shift; -+ final int sectionZ = z >> shift; -+ -+ final Coordinate coordinate = new Coordinate(Coordinate.key(sectionX, sectionZ)); -+ final Node node = this.nodes.get(coordinate); -+ -+ return node != null && node.thread == currThread; -+ } -+ -+ public boolean isHeldByCurrentThread(final int centerX, final int centerZ, final int radius) { -+ return this.isHeldByCurrentThread(centerX - radius, centerZ - radius, centerX + radius, centerZ + radius); -+ } -+ -+ public boolean isHeldByCurrentThread(final int fromX, final int fromZ, final int toX, final int toZ) { -+ if (fromX > toX || fromZ > toZ) { -+ throw new IllegalArgumentException(); -+ } -+ -+ final Thread currThread = Thread.currentThread(); -+ final int shift = this.coordinateShift; -+ final int fromSectionX = fromX >> shift; -+ final int fromSectionZ = fromZ >> shift; -+ final int toSectionX = toX >> shift; -+ final int toSectionZ = toZ >> shift; -+ -+ for (int currZ = fromSectionZ; currZ <= toSectionZ; ++currZ) { -+ for (int currX = fromSectionX; currX <= toSectionX; ++currX) { -+ final Coordinate coordinate = new Coordinate(Coordinate.key(currX, currZ)); -+ -+ final Node node = this.nodes.get(coordinate); -+ -+ if (node == null || node.thread != currThread) { -+ return false; -+ } -+ } -+ } -+ -+ return true; -+ } -+ -+ public Node tryLock(final int x, final int z) { -+ return this.tryLock(x, z, x, z); -+ } -+ -+ public Node tryLock(final int centerX, final int centerZ, final int radius) { -+ return this.tryLock(centerX - radius, centerZ - radius, centerX + radius, centerZ + radius); -+ } -+ -+ public Node tryLock(final int fromX, final int fromZ, final int toX, final int toZ) { -+ if (fromX > toX || fromZ > toZ) { -+ throw new IllegalArgumentException(); -+ } -+ -+ final Thread currThread = Thread.currentThread(); -+ final int shift = this.coordinateShift; -+ final int fromSectionX = fromX >> shift; -+ final int fromSectionZ = fromZ >> shift; -+ final int toSectionX = toX >> shift; -+ final int toSectionZ = toZ >> shift; -+ -+ final List areaAffected = new ArrayList<>(); -+ -+ final Node ret = new Node(this, areaAffected, currThread); -+ -+ boolean failed = false; -+ -+ // try to fast acquire area -+ for (int currZ = fromSectionZ; currZ <= toSectionZ; ++currZ) { -+ for (int currX = fromSectionX; currX <= toSectionX; ++currX) { -+ final Coordinate coordinate = new Coordinate(Coordinate.key(currX, currZ)); -+ -+ final Node prev = this.nodes.putIfAbsent(coordinate, ret); -+ -+ if (prev == null) { -+ areaAffected.add(coordinate); -+ continue; -+ } -+ -+ if (prev.thread != currThread) { -+ failed = true; -+ break; -+ } -+ } -+ } -+ -+ if (!failed) { -+ return ret; -+ } -+ -+ // failed, undo logic -+ if (!areaAffected.isEmpty()) { -+ for (int i = 0, len = areaAffected.size(); i < len; ++i) { -+ final Coordinate key = areaAffected.get(i); -+ -+ if (this.nodes.remove(key) != ret) { -+ throw new IllegalStateException(); -+ } -+ } -+ -+ areaAffected.clear(); -+ -+ // since we inserted, we need to drain waiters -+ Thread unpark; -+ while ((unpark = ret.pollOrBlockAdds()) != null) { -+ LockSupport.unpark(unpark); -+ } -+ } -+ -+ return null; -+ } -+ -+ public Node lock(final int x, final int z) { -+ final Thread currThread = Thread.currentThread(); -+ final int shift = this.coordinateShift; -+ final int sectionX = x >> shift; -+ final int sectionZ = z >> shift; -+ -+ final List areaAffected = new ArrayList<>(1); -+ -+ final Node ret = new Node(this, areaAffected, currThread); -+ final Coordinate coordinate = new Coordinate(Coordinate.key(sectionX, sectionZ)); -+ -+ for (long failures = 0L;;) { -+ final Node park; -+ -+ // try to fast acquire area -+ { -+ final Node prev = this.nodes.putIfAbsent(coordinate, ret); -+ -+ if (prev == null) { -+ areaAffected.add(coordinate); -+ return ret; -+ } else if (prev.thread != currThread) { -+ park = prev; -+ } else { -+ // only one node we would want to acquire, and it's owned by this thread already -+ return ret; -+ } -+ } -+ -+ ++failures; -+ -+ if (failures > 128L && park.add(currThread)) { -+ LockSupport.park(); -+ } else { -+ // high contention, spin wait -+ if (failures < 128L) { -+ for (long i = 0; i < failures; ++i) { -+ Thread.onSpinWait(); -+ } -+ failures = failures << 1; -+ } else if (failures < 1_200L) { -+ LockSupport.parkNanos(1_000L); -+ failures = failures + 1L; -+ } else { // scale 0.1ms (100us) per failure -+ Thread.yield(); -+ LockSupport.parkNanos(100_000L * failures); -+ failures = failures + 1L; -+ } -+ } -+ } -+ } -+ -+ public Node lock(final int centerX, final int centerZ, final int radius) { -+ return this.lock(centerX - radius, centerZ - radius, centerX + radius, centerZ + radius); -+ } -+ -+ public Node lock(final int fromX, final int fromZ, final int toX, final int toZ) { -+ if (fromX > toX || fromZ > toZ) { -+ throw new IllegalArgumentException(); -+ } -+ -+ final Thread currThread = Thread.currentThread(); -+ final int shift = this.coordinateShift; -+ final int fromSectionX = fromX >> shift; -+ final int fromSectionZ = fromZ >> shift; -+ final int toSectionX = toX >> shift; -+ final int toSectionZ = toZ >> shift; -+ -+ if (((fromSectionX ^ toSectionX) | (fromSectionZ ^ toSectionZ)) == 0) { -+ return this.lock(fromX, fromZ); -+ } -+ -+ final List areaAffected = new ArrayList<>(); -+ -+ final Node ret = new Node(this, areaAffected, currThread); -+ -+ for (long failures = 0L;;) { -+ Node park = null; -+ boolean addedToArea = false; -+ boolean alreadyOwned = false; -+ boolean allOwned = true; -+ -+ // try to fast acquire area -+ for (int currZ = fromSectionZ; currZ <= toSectionZ; ++currZ) { -+ for (int currX = fromSectionX; currX <= toSectionX; ++currX) { -+ final Coordinate coordinate = new Coordinate(Coordinate.key(currX, currZ)); -+ -+ final Node prev = this.nodes.putIfAbsent(coordinate, ret); -+ -+ if (prev == null) { -+ addedToArea = true; -+ allOwned = false; -+ areaAffected.add(coordinate); -+ continue; -+ } -+ -+ if (prev.thread != currThread) { -+ park = prev; -+ alreadyOwned = true; -+ break; -+ } -+ } -+ } -+ -+ if (park == null) { -+ if (alreadyOwned && !allOwned) { -+ throw new IllegalStateException("Improper lock usage: Should never acquire intersecting areas"); -+ } -+ return ret; -+ } -+ -+ // failed, undo logic -+ if (addedToArea) { -+ for (int i = 0, len = areaAffected.size(); i < len; ++i) { -+ final Coordinate key = areaAffected.get(i); -+ -+ if (this.nodes.remove(key) != ret) { -+ throw new IllegalStateException(); -+ } -+ } -+ -+ areaAffected.clear(); -+ -+ // since we inserted, we need to drain waiters -+ Thread unpark; -+ while ((unpark = ret.pollOrBlockAdds()) != null) { -+ LockSupport.unpark(unpark); -+ } -+ } -+ -+ ++failures; -+ -+ if (failures > 128L && park.add(currThread)) { -+ LockSupport.park(park); -+ } else { -+ // high contention, spin wait -+ if (failures < 128L) { -+ for (long i = 0; i < failures; ++i) { -+ Thread.onSpinWait(); -+ } -+ failures = failures << 1; -+ } else if (failures < 1_200L) { -+ LockSupport.parkNanos(1_000L); -+ failures = failures + 1L; -+ } else { // scale 0.1ms (100us) per failure -+ Thread.yield(); -+ LockSupport.parkNanos(100_000L * failures); -+ failures = failures + 1L; -+ } -+ } -+ -+ if (addedToArea) { -+ // try again, so we need to allow adds so that other threads can properly block on us -+ ret.allowAdds(); -+ } -+ } -+ } -+ -+ public void unlock(final Node node) { -+ if (node.lock != this) { -+ throw new IllegalStateException("Unlock target lock mismatch"); -+ } -+ -+ final List areaAffected = node.areaAffected; -+ -+ if (areaAffected.isEmpty()) { -+ // here we are not in the node map, and so do not need to remove from the node map or unblock any waiters -+ return; -+ } -+ -+ // remove from node map; allowing other threads to lock -+ for (int i = 0, len = areaAffected.size(); i < len; ++i) { -+ final Coordinate coordinate = areaAffected.get(i); -+ if (this.nodes.remove(coordinate) != node) { -+ throw new IllegalStateException(); -+ } -+ } -+ -+ Thread unpark; -+ while ((unpark = node.pollOrBlockAdds()) != null) { -+ LockSupport.unpark(unpark); -+ } -+ } -+ -+ public static final class Node extends MultiThreadedQueue { -+ -+ private final ReentrantAreaLock lock; -+ private final List areaAffected; -+ private final Thread thread; -+ //private final Throwable WHO_CREATED_MY_ASS = new Throwable(); -+ -+ private Node(final ReentrantAreaLock lock, final List areaAffected, final Thread thread) { -+ this.lock = lock; -+ this.areaAffected = areaAffected; -+ this.thread = thread; -+ } -+ -+ @Override -+ public String toString() { -+ return "Node{" + -+ "areaAffected=" + this.areaAffected + -+ ", thread=" + this.thread + -+ '}'; -+ } -+ } -+ -+ private static final class Coordinate implements Comparable { -+ -+ public final long key; -+ -+ public Coordinate(final long key) { -+ this.key = key; -+ } -+ -+ public Coordinate(final int x, final int z) { -+ this.key = key(x, z); -+ } -+ -+ public static long key(final int x, final int z) { -+ return ((long)z << 32) | (x & 0xFFFFFFFFL); -+ } -+ -+ public static int x(final long key) { -+ return (int)key; -+ } -+ -+ public static int z(final long key) { -+ return (int)(key >>> 32); -+ } -+ -+ @Override -+ public int hashCode() { -+ return (int)HashCommon.mix(this.key); -+ } -+ -+ @Override -+ public boolean equals(final Object obj) { -+ if (this == obj) { -+ return true; -+ } -+ -+ if (!(obj instanceof Coordinate other)) { -+ return false; -+ } -+ -+ return this.key == other.key; -+ } -+ -+ // This class is intended for HashMap/ConcurrentHashMap usage, which do treeify bin nodes if the chain -+ // is too large. So we should implement compareTo to help. -+ @Override -+ public int compareTo(final Coordinate other) { -+ return Long.compare(this.key, other.key); -+ } -+ -+ @Override -+ public String toString() { -+ return "[" + x(this.key) + "," + z(this.key) + "]"; -+ } -+ } -+} -diff --git a/src/main/java/ca/spottedleaf/concurrentutil/lock/SyncReentrantAreaLock.java b/src/main/java/ca/spottedleaf/concurrentutil/lock/SyncReentrantAreaLock.java -new file mode 100644 -index 0000000000000000000000000000000000000000..64b5803d002b2968841a5ddee987f98b72964e87 ---- /dev/null -+++ b/src/main/java/ca/spottedleaf/concurrentutil/lock/SyncReentrantAreaLock.java -@@ -0,0 +1,217 @@ -+package ca.spottedleaf.concurrentutil.lock; -+ -+import ca.spottedleaf.concurrentutil.collection.MultiThreadedQueue; -+import it.unimi.dsi.fastutil.longs.Long2ReferenceOpenHashMap; -+import it.unimi.dsi.fastutil.longs.LongArrayList; -+import java.util.concurrent.locks.LockSupport; -+ -+// not concurrent, unlike ReentrantAreaLock -+// no incorrect lock usage detection (acquiring intersecting areas) -+// this class is nothing more than a performance reference for ReentrantAreaLock -+public final class SyncReentrantAreaLock { -+ -+ private final int coordinateShift; -+ -+ // aggressive load factor to reduce contention -+ private final Long2ReferenceOpenHashMap nodes = new Long2ReferenceOpenHashMap<>(128, 0.2f); -+ -+ public SyncReentrantAreaLock(final int coordinateShift) { -+ this.coordinateShift = coordinateShift; -+ } -+ -+ private static long key(final int x, final int z) { -+ return ((long)z << 32) | (x & 0xFFFFFFFFL); -+ } -+ -+ public Node lock(final int x, final int z) { -+ final Thread currThread = Thread.currentThread(); -+ final int shift = this.coordinateShift; -+ final int sectionX = x >> shift; -+ final int sectionZ = z >> shift; -+ -+ final LongArrayList areaAffected = new LongArrayList(); -+ -+ final Node ret = new Node(this, areaAffected, currThread); -+ -+ final long coordinate = key(sectionX, sectionZ); -+ -+ for (long failures = 0L;;) { -+ final Node park; -+ -+ synchronized (this) { -+ // try to fast acquire area -+ final Node prev = this.nodes.putIfAbsent(coordinate, ret); -+ -+ if (prev == null) { -+ areaAffected.add(coordinate); -+ return ret; -+ } else if (prev.thread != currThread) { -+ park = prev; -+ } else { -+ // only one node we would want to acquire, and it's owned by this thread already -+ return ret; -+ } -+ } -+ -+ ++failures; -+ -+ if (failures > 128L && park.add(currThread)) { -+ LockSupport.park(); -+ } else { -+ // high contention, spin wait -+ if (failures < 128L) { -+ for (long i = 0; i < failures; ++i) { -+ Thread.onSpinWait(); -+ } -+ failures = failures << 1; -+ } else if (failures < 1_200L) { -+ LockSupport.parkNanos(1_000L); -+ failures = failures + 1L; -+ } else { // scale 0.1ms (100us) per failure -+ Thread.yield(); -+ LockSupport.parkNanos(100_000L * failures); -+ failures = failures + 1L; -+ } -+ } -+ } -+ } -+ -+ public Node lock(final int centerX, final int centerZ, final int radius) { -+ return this.lock(centerX - radius, centerZ - radius, centerX + radius, centerZ + radius); -+ } -+ -+ public Node lock(final int fromX, final int fromZ, final int toX, final int toZ) { -+ if (fromX > toX || fromZ > toZ) { -+ throw new IllegalArgumentException(); -+ } -+ -+ final Thread currThread = Thread.currentThread(); -+ final int shift = this.coordinateShift; -+ final int fromSectionX = fromX >> shift; -+ final int fromSectionZ = fromZ >> shift; -+ final int toSectionX = toX >> shift; -+ final int toSectionZ = toZ >> shift; -+ -+ final LongArrayList areaAffected = new LongArrayList(); -+ -+ final Node ret = new Node(this, areaAffected, currThread); -+ -+ for (long failures = 0L;;) { -+ Node park = null; -+ boolean addedToArea = false; -+ -+ synchronized (this) { -+ // try to fast acquire area -+ for (int currZ = fromSectionZ; currZ <= toSectionZ; ++currZ) { -+ for (int currX = fromSectionX; currX <= toSectionX; ++currX) { -+ final long coordinate = key(currX, currZ); -+ -+ final Node prev = this.nodes.putIfAbsent(coordinate, ret); -+ -+ if (prev == null) { -+ addedToArea = true; -+ areaAffected.add(coordinate); -+ continue; -+ } -+ -+ if (prev.thread != currThread) { -+ park = prev; -+ break; -+ } -+ } -+ } -+ -+ if (park == null) { -+ return ret; -+ } -+ -+ // failed, undo logic -+ if (!areaAffected.isEmpty()) { -+ for (int i = 0, len = areaAffected.size(); i < len; ++i) { -+ final long key = areaAffected.getLong(i); -+ -+ if (!this.nodes.remove(key, ret)) { -+ throw new IllegalStateException(); -+ } -+ } -+ } -+ } -+ -+ if (addedToArea) { -+ areaAffected.clear(); -+ // since we inserted, we need to drain waiters -+ Thread unpark; -+ while ((unpark = ret.pollOrBlockAdds()) != null) { -+ LockSupport.unpark(unpark); -+ } -+ } -+ -+ ++failures; -+ -+ if (failures > 128L && park.add(currThread)) { -+ LockSupport.park(); -+ } else { -+ // high contention, spin wait -+ if (failures < 128L) { -+ for (long i = 0; i < failures; ++i) { -+ Thread.onSpinWait(); -+ } -+ failures = failures << 1; -+ } else if (failures < 1_200L) { -+ LockSupport.parkNanos(1_000L); -+ failures = failures + 1L; -+ } else { // scale 0.1ms (100us) per failure -+ Thread.yield(); -+ LockSupport.parkNanos(100_000L * failures); -+ failures = failures + 1L; -+ } -+ } -+ -+ if (addedToArea) { -+ // try again, so we need to allow adds so that other threads can properly block on us -+ ret.allowAdds(); -+ } -+ } -+ } -+ -+ public void unlock(final Node node) { -+ if (node.lock != this) { -+ throw new IllegalStateException("Unlock target lock mismatch"); -+ } -+ -+ final LongArrayList areaAffected = node.areaAffected; -+ -+ if (areaAffected.isEmpty()) { -+ // here we are not in the node map, and so do not need to remove from the node map or unblock any waiters -+ return; -+ } -+ -+ // remove from node map; allowing other threads to lock -+ synchronized (this) { -+ for (int i = 0, len = areaAffected.size(); i < len; ++i) { -+ final long coordinate = areaAffected.getLong(i); -+ if (!this.nodes.remove(coordinate, node)) { -+ throw new IllegalStateException(); -+ } -+ } -+ } -+ -+ Thread unpark; -+ while ((unpark = node.pollOrBlockAdds()) != null) { -+ LockSupport.unpark(unpark); -+ } -+ } -+ -+ public static final class Node extends MultiThreadedQueue { -+ -+ private final SyncReentrantAreaLock lock; -+ private final LongArrayList areaAffected; -+ private final Thread thread; -+ -+ private Node(final SyncReentrantAreaLock lock, final LongArrayList areaAffected, final Thread thread) { -+ this.lock = lock; -+ this.areaAffected = areaAffected; -+ this.thread = thread; -+ } -+ } -+} -diff --git a/src/main/java/ca/spottedleaf/starlight/common/light/StarLightInterface.java b/src/main/java/ca/spottedleaf/starlight/common/light/StarLightInterface.java -index e0338db4d6fa359029ed5edeacc3646aa98701f5..c03dbb4a74d00d794be4139f0f7c4b5ff1b01d38 100644 ---- a/src/main/java/ca/spottedleaf/starlight/common/light/StarLightInterface.java -+++ b/src/main/java/ca/spottedleaf/starlight/common/light/StarLightInterface.java -@@ -41,14 +41,14 @@ public final class StarLightInterface { - protected final ArrayDeque cachedSkyPropagators; - protected final ArrayDeque cachedBlockPropagators; - -- protected final LightQueue lightQueue = new LightQueue(this); -+ public final io.papermc.paper.chunk.system.light.LightQueue lightQueue; // Paper - replace light queue - - protected final LayerLightEventListener skyReader; - protected final LayerLightEventListener blockReader; - protected final boolean isClientSide; - -- protected final int minSection; -- protected final int maxSection; -+ public final int minSection; // Paper - public -+ public final int maxSection; // Paper - public - protected final int minLightSection; - protected final int maxLightSection; - -@@ -182,6 +182,7 @@ public final class StarLightInterface { - StarLightInterface.this.sectionChange(pos, notReady); - } - }; -+ this.lightQueue = new io.papermc.paper.chunk.system.light.LightQueue(this); // Paper - replace light queue - } - - public boolean hasSkyLight() { -@@ -333,7 +334,7 @@ public final class StarLightInterface { - return this.lightAccess; - } - -- protected final SkyStarLightEngine getSkyLightEngine() { -+ public final SkyStarLightEngine getSkyLightEngine() { // Paper - public - if (this.cachedSkyPropagators == null) { - return null; - } -@@ -348,7 +349,7 @@ public final class StarLightInterface { - return ret; - } - -- protected final void releaseSkyLightEngine(final SkyStarLightEngine engine) { -+ public final void releaseSkyLightEngine(final SkyStarLightEngine engine) { // Paper - public - if (this.cachedSkyPropagators == null) { - return; - } -@@ -357,7 +358,7 @@ public final class StarLightInterface { - } - } - -- protected final BlockStarLightEngine getBlockLightEngine() { -+ public final BlockStarLightEngine getBlockLightEngine() { // Paper - public - if (this.cachedBlockPropagators == null) { - return null; - } -@@ -372,7 +373,7 @@ public final class StarLightInterface { - return ret; - } - -- protected final void releaseBlockLightEngine(final BlockStarLightEngine engine) { -+ public final void releaseBlockLightEngine(final BlockStarLightEngine engine) { // Paper - public - if (this.cachedBlockPropagators == null) { - return; - } -@@ -381,7 +382,7 @@ public final class StarLightInterface { - } - } - -- public LightQueue.ChunkTasks blockChange(final BlockPos pos) { -+ public io.papermc.paper.chunk.system.light.LightQueue.ChunkTasks blockChange(final BlockPos pos) { // Paper - rewrite chunk system - if (this.world == null || pos.getY() < WorldUtil.getMinBlockY(this.world) || pos.getY() > WorldUtil.getMaxBlockY(this.world)) { // empty world - return null; - } -@@ -389,7 +390,7 @@ public final class StarLightInterface { - return this.lightQueue.queueBlockChange(pos); - } - -- public LightQueue.ChunkTasks sectionChange(final SectionPos pos, final boolean newEmptyValue) { -+ public io.papermc.paper.chunk.system.light.LightQueue.ChunkTasks sectionChange(final SectionPos pos, final boolean newEmptyValue) { // Paper - rewrite chunk system - if (this.world == null) { // empty world - return null; - } -@@ -519,57 +520,15 @@ public final class StarLightInterface { - } - - public void scheduleChunkLight(final ChunkPos pos, final Runnable run) { -- this.lightQueue.queueChunkLighting(pos, run); -+ throw new UnsupportedOperationException("No longer implemented, use the new lightQueue field to queue tasks"); // Paper - replace light queue - } - - public void removeChunkTasks(final ChunkPos pos) { -- this.lightQueue.removeChunk(pos); -+ throw new UnsupportedOperationException("No longer implemented, use the new lightQueue field to queue tasks"); // Paper - replace light queue - } - - public void propagateChanges() { -- if (this.lightQueue.isEmpty()) { -- return; -- } -- -- final SkyStarLightEngine skyEngine = this.getSkyLightEngine(); -- final BlockStarLightEngine blockEngine = this.getBlockLightEngine(); -- -- try { -- LightQueue.ChunkTasks task; -- while ((task = this.lightQueue.removeFirstTask()) != null) { -- if (task.lightTasks != null) { -- for (final Runnable run : task.lightTasks) { -- run.run(); -- } -- } -- -- final long coordinate = task.chunkCoordinate; -- final int chunkX = CoordinateUtils.getChunkX(coordinate); -- final int chunkZ = CoordinateUtils.getChunkZ(coordinate); -- -- final Set positions = task.changedPositions; -- final Boolean[] sectionChanges = task.changedSectionSet; -- -- if (skyEngine != null && (!positions.isEmpty() || sectionChanges != null)) { -- skyEngine.blocksChangedInChunk(this.lightAccess, chunkX, chunkZ, positions, sectionChanges); -- } -- if (blockEngine != null && (!positions.isEmpty() || sectionChanges != null)) { -- blockEngine.blocksChangedInChunk(this.lightAccess, chunkX, chunkZ, positions, sectionChanges); -- } -- -- if (skyEngine != null && task.queuedEdgeChecksSky != null) { -- skyEngine.checkChunkEdges(this.lightAccess, chunkX, chunkZ, task.queuedEdgeChecksSky); -- } -- if (blockEngine != null && task.queuedEdgeChecksBlock != null) { -- blockEngine.checkChunkEdges(this.lightAccess, chunkX, chunkZ, task.queuedEdgeChecksBlock); -- } -- -- task.onComplete.complete(null); -- } -- } finally { -- this.releaseSkyLightEngine(skyEngine); -- this.releaseBlockLightEngine(blockEngine); -- } -+ throw new UnsupportedOperationException("No longer implemented, task draining is now performed by the light thread"); // Paper - replace light queue - } - - public static final class LightQueue { -diff --git a/src/main/java/co/aikar/timings/WorldTimingsHandler.java b/src/main/java/co/aikar/timings/WorldTimingsHandler.java -index 2f0d9b953802dee821cfde82d22b0567cce8ee91..22687667ec69a954261e55e59261286ac1b8b8cd 100644 ---- a/src/main/java/co/aikar/timings/WorldTimingsHandler.java -+++ b/src/main/java/co/aikar/timings/WorldTimingsHandler.java -@@ -59,6 +59,16 @@ public class WorldTimingsHandler { - - public final Timing miscMobSpawning; - -+ public final Timing poiUnload; -+ public final Timing chunkUnload; -+ public final Timing poiSaveDataSerialization; -+ public final Timing chunkSave; -+ public final Timing chunkSaveDataSerialization; -+ public final Timing chunkSaveIOWait; -+ public final Timing chunkUnloadPrepareSave; -+ public final Timing chunkUnloadPOISerialization; -+ public final Timing chunkUnloadDataSave; -+ - public WorldTimingsHandler(Level server) { - String name = ((PrimaryLevelData) server.getLevelData()).getLevelName() + " - "; - -@@ -112,6 +122,16 @@ public class WorldTimingsHandler { - - - miscMobSpawning = Timings.ofSafe(name + "Mob spawning - Misc"); -+ -+ poiUnload = Timings.ofSafe(name + "Chunk unload - POI"); -+ chunkUnload = Timings.ofSafe(name + "Chunk unload - Chunk"); -+ poiSaveDataSerialization = Timings.ofSafe(name + "Chunk save - POI Data serialization"); -+ chunkSave = Timings.ofSafe(name + "Chunk save - Chunk"); -+ chunkSaveDataSerialization = Timings.ofSafe(name + "Chunk save - Chunk Data serialization"); -+ chunkSaveIOWait = Timings.ofSafe(name + "Chunk save - Chunk IO Wait"); -+ chunkUnloadPrepareSave = Timings.ofSafe(name + "Chunk unload - Async Save Prepare"); -+ chunkUnloadPOISerialization = Timings.ofSafe(name + "Chunk unload - POI Data Serialization"); -+ chunkUnloadDataSave = Timings.ofSafe(name + "Chunk unload - Data Serialization"); - } - - public static Timing getTickList(ServerLevel worldserver, String timingsType) { -diff --git a/src/main/java/io/papermc/paper/chunk/system/ChunkSystem.java b/src/main/java/io/papermc/paper/chunk/system/ChunkSystem.java -index cff2f04409fab9abca87ceec85a551e1d59f9e7d..e3f56908cc8a9c3f4580def50fcfdc61bd495a71 100644 ---- a/src/main/java/io/papermc/paper/chunk/system/ChunkSystem.java -+++ b/src/main/java/io/papermc/paper/chunk/system/ChunkSystem.java -@@ -32,192 +32,41 @@ public final class ChunkSystem { - } - - public static void scheduleChunkTask(final ServerLevel level, final int chunkX, final int chunkZ, final Runnable run, final PrioritisedExecutor.Priority priority) { -- level.chunkSource.mainThreadProcessor.execute(run); -+ level.chunkTaskScheduler.scheduleChunkTask(chunkX, chunkZ, run, priority); // Paper - rewrite chunk system - } - - public static void scheduleChunkLoad(final ServerLevel level, final int chunkX, final int chunkZ, final boolean gen, - final ChunkStatus toStatus, final boolean addTicket, final PrioritisedExecutor.Priority priority, - final Consumer onComplete) { -- if (gen) { -- scheduleChunkLoad(level, chunkX, chunkZ, toStatus, addTicket, priority, onComplete); -- return; -- } -- scheduleChunkLoad(level, chunkX, chunkZ, ChunkStatus.EMPTY, addTicket, priority, (final ChunkAccess chunk) -> { -- if (chunk == null) { -- onComplete.accept(null); -- } else { -- if (chunk.getStatus().isOrAfter(toStatus)) { -- scheduleChunkLoad(level, chunkX, chunkZ, toStatus, addTicket, priority, onComplete); -- } else { -- onComplete.accept(null); -- } -- } -- }); -+ level.chunkTaskScheduler.scheduleChunkLoad(chunkX, chunkZ, gen, toStatus, addTicket, priority, onComplete); // Paper - rewrite chunk system - } - -- static final TicketType CHUNK_LOAD = TicketType.create("chunk_load", Long::compareTo); -- -- private static long chunkLoadCounter = 0L; -+ // Paper - rewrite chunk system - public static void scheduleChunkLoad(final ServerLevel level, final int chunkX, final int chunkZ, final ChunkStatus toStatus, - final boolean addTicket, final PrioritisedExecutor.Priority priority, final Consumer onComplete) { -- if (!Bukkit.isPrimaryThread()) { -- scheduleChunkTask(level, chunkX, chunkZ, () -> { -- scheduleChunkLoad(level, chunkX, chunkZ, toStatus, addTicket, priority, onComplete); -- }, priority); -- return; -- } -- -- final int minLevel = 33 + ChunkStatus.getDistance(toStatus); -- final Long chunkReference = addTicket ? Long.valueOf(++chunkLoadCounter) : null; -- final ChunkPos chunkPos = new ChunkPos(chunkX, chunkZ); -- -- if (addTicket) { -- level.chunkSource.addTicketAtLevel(CHUNK_LOAD, chunkPos, minLevel, chunkReference); -- } -- level.chunkSource.runDistanceManagerUpdates(); -- -- final Consumer loadCallback = (final ChunkAccess chunk) -> { -- try { -- if (onComplete != null) { -- onComplete.accept(chunk); -- } -- } catch (final ThreadDeath death) { -- throw death; -- } catch (final Throwable thr) { -- LOGGER.error("Exception handling chunk load callback", thr); -- SneakyThrow.sneaky(thr); -- } finally { -- if (addTicket) { -- level.chunkSource.addTicketAtLevel(TicketType.UNKNOWN, chunkPos, minLevel, chunkPos); -- level.chunkSource.removeTicketAtLevel(CHUNK_LOAD, chunkPos, minLevel, chunkReference); -- } -- } -- }; -- -- final ChunkHolder holder = level.chunkSource.chunkMap.updatingChunkMap.get(CoordinateUtils.getChunkKey(chunkX, chunkZ)); -- -- if (holder == null || holder.getTicketLevel() > minLevel) { -- loadCallback.accept(null); -- return; -- } -- -- final CompletableFuture> loadFuture = holder.getOrScheduleFuture(toStatus, level.chunkSource.chunkMap); -- -- if (loadFuture.isDone()) { -- loadCallback.accept(loadFuture.join().left().orElse(null)); -- return; -- } -- -- loadFuture.whenCompleteAsync((final Either either, final Throwable thr) -> { -- if (thr != null) { -- loadCallback.accept(null); -- return; -- } -- loadCallback.accept(either.left().orElse(null)); -- }, (final Runnable r) -> { -- scheduleChunkTask(level, chunkX, chunkZ, r, PrioritisedExecutor.Priority.HIGHEST); -- }); -+ level.chunkTaskScheduler.scheduleChunkLoad(chunkX, chunkZ, toStatus, addTicket, priority, onComplete); // Paper - rewrite chunk system - } - - public static void scheduleTickingState(final ServerLevel level, final int chunkX, final int chunkZ, - final FullChunkStatus toStatus, final boolean addTicket, - final PrioritisedExecutor.Priority priority, final Consumer onComplete) { -- // This method goes unused until the chunk system rewrite -- if (toStatus == FullChunkStatus.INACCESSIBLE) { -- throw new IllegalArgumentException("Cannot wait for INACCESSIBLE status"); -- } -- -- if (!Bukkit.isPrimaryThread()) { -- scheduleChunkTask(level, chunkX, chunkZ, () -> { -- scheduleTickingState(level, chunkX, chunkZ, toStatus, addTicket, priority, onComplete); -- }, priority); -- return; -- } -- -- final int minLevel = 33 - (toStatus.ordinal() - 1); -- final int radius = toStatus.ordinal() - 1; -- final Long chunkReference = addTicket ? Long.valueOf(++chunkLoadCounter) : null; -- final ChunkPos chunkPos = new ChunkPos(chunkX, chunkZ); -- -- if (addTicket) { -- level.chunkSource.addTicketAtLevel(CHUNK_LOAD, chunkPos, minLevel, chunkReference); -- } -- level.chunkSource.runDistanceManagerUpdates(); -- -- final Consumer loadCallback = (final LevelChunk chunk) -> { -- try { -- if (onComplete != null) { -- onComplete.accept(chunk); -- } -- } catch (final ThreadDeath death) { -- throw death; -- } catch (final Throwable thr) { -- LOGGER.error("Exception handling chunk load callback", thr); -- SneakyThrow.sneaky(thr); -- } finally { -- if (addTicket) { -- level.chunkSource.addTicketAtLevel(TicketType.UNKNOWN, chunkPos, minLevel, chunkPos); -- level.chunkSource.removeTicketAtLevel(CHUNK_LOAD, chunkPos, minLevel, chunkReference); -- } -- } -- }; -- -- final ChunkHolder holder = level.chunkSource.chunkMap.updatingChunkMap.get(CoordinateUtils.getChunkKey(chunkX, chunkZ)); -- -- if (holder == null || holder.getTicketLevel() > minLevel) { -- loadCallback.accept(null); -- return; -- } -- -- final CompletableFuture> tickingState; -- switch (toStatus) { -- case FULL: { -- tickingState = holder.getFullChunkFuture(); -- break; -- } -- case BLOCK_TICKING: { -- tickingState = holder.getTickingChunkFuture(); -- break; -- } -- case ENTITY_TICKING: { -- tickingState = holder.getEntityTickingChunkFuture(); -- break; -- } -- default: { -- throw new IllegalStateException("Cannot reach here"); -- } -- } -- -- if (tickingState.isDone()) { -- loadCallback.accept(tickingState.join().left().orElse(null)); -- return; -- } -- -- tickingState.whenCompleteAsync((final Either either, final Throwable thr) -> { -- if (thr != null) { -- loadCallback.accept(null); -- return; -- } -- loadCallback.accept(either.left().orElse(null)); -- }, (final Runnable r) -> { -- scheduleChunkTask(level, chunkX, chunkZ, r, PrioritisedExecutor.Priority.HIGHEST); -- }); -+ level.chunkTaskScheduler.scheduleTickingState(chunkX, chunkZ, toStatus, addTicket, priority, onComplete); // Paper - rewrite chunk system - } - - public static List getVisibleChunkHolders(final ServerLevel level) { -- return new ArrayList<>(level.chunkSource.chunkMap.visibleChunkMap.values()); -+ return level.chunkTaskScheduler.chunkHolderManager.getOldChunkHolders(); // Paper - rewrite chunk system - } - - public static List getUpdatingChunkHolders(final ServerLevel level) { -- return new ArrayList<>(level.chunkSource.chunkMap.updatingChunkMap.values()); -+ return level.chunkTaskScheduler.chunkHolderManager.getOldChunkHolders(); // Paper - rewrite chunk system - } - - public static int getVisibleChunkHolderCount(final ServerLevel level) { -- return level.chunkSource.chunkMap.visibleChunkMap.size(); -+ return level.chunkTaskScheduler.chunkHolderManager.size(); // Paper - rewrite chunk system - } - - public static int getUpdatingChunkHolderCount(final ServerLevel level) { -- return level.chunkSource.chunkMap.updatingChunkMap.size(); -+ return level.chunkTaskScheduler.chunkHolderManager.size(); // Paper - rewrite chunk system - } - - public static boolean hasAnyChunkHolders(final ServerLevel level) { -@@ -244,26 +93,32 @@ public final class ChunkSystem { - - public static void onChunkBorder(final LevelChunk chunk, final ChunkHolder holder) { - chunk.playerChunk = holder; -+ chunk.chunkStatus = net.minecraft.server.level.FullChunkStatus.FULL; - } - - public static void onChunkNotBorder(final LevelChunk chunk, final ChunkHolder holder) { -- -+ chunk.chunkStatus = net.minecraft.server.level.FullChunkStatus.INACCESSIBLE; - } - - public static void onChunkTicking(final LevelChunk chunk, final ChunkHolder holder) { - chunk.level.getChunkSource().tickingChunks.add(chunk); -+ chunk.chunkStatus = net.minecraft.server.level.FullChunkStatus.BLOCK_TICKING; -+ chunk.level.chunkSource.chunkMap.tickingGenerated.incrementAndGet(); - } - - public static void onChunkNotTicking(final LevelChunk chunk, final ChunkHolder holder) { - chunk.level.getChunkSource().tickingChunks.remove(chunk); -+ chunk.chunkStatus = net.minecraft.server.level.FullChunkStatus.FULL; - } - - public static void onChunkEntityTicking(final LevelChunk chunk, final ChunkHolder holder) { - chunk.level.getChunkSource().entityTickingChunks.add(chunk); -+ chunk.chunkStatus = net.minecraft.server.level.FullChunkStatus.ENTITY_TICKING; - } - - public static void onChunkNotEntityTicking(final LevelChunk chunk, final ChunkHolder holder) { - chunk.level.getChunkSource().entityTickingChunks.remove(chunk); -+ chunk.chunkStatus = net.minecraft.server.level.FullChunkStatus.BLOCK_TICKING; - } - - public static ChunkHolder getUnloadingChunkHolder(final ServerLevel level, final int chunkX, final int chunkZ) { -@@ -271,23 +126,15 @@ public final class ChunkSystem { - } - - public static int getSendViewDistance(final ServerPlayer player) { -- return getLoadViewDistance(player); -+ return io.papermc.paper.chunk.system.RegionizedPlayerChunkLoader.getAPISendViewDistance(player); - } - - public static int getLoadViewDistance(final ServerPlayer player) { -- final ServerLevel level = player.serverLevel(); -- if (level == null) { -- return Bukkit.getViewDistance(); -- } -- return level.chunkSource.chunkMap.getPlayerViewDistance(player); -+ return io.papermc.paper.chunk.system.RegionizedPlayerChunkLoader.getLoadViewDistance(player); - } - - public static int getTickViewDistance(final ServerPlayer player) { -- final ServerLevel level = player.serverLevel(); -- if (level == null) { -- return Bukkit.getSimulationDistance(); -- } -- return level.chunkSource.chunkMap.distanceManager.simulationDistance; -+ return io.papermc.paper.chunk.system.RegionizedPlayerChunkLoader.getAPITickViewDistance(player); - } - - private ChunkSystem() { -diff --git a/src/main/java/io/papermc/paper/chunk/system/RegionizedPlayerChunkLoader.java b/src/main/java/io/papermc/paper/chunk/system/RegionizedPlayerChunkLoader.java -new file mode 100644 -index 0000000000000000000000000000000000000000..ee58c67cb2bd78159cce19ec75f13dc6168a0e7a ---- /dev/null -+++ b/src/main/java/io/papermc/paper/chunk/system/RegionizedPlayerChunkLoader.java -@@ -0,0 +1,1375 @@ -+package io.papermc.paper.chunk.system; -+ -+import ca.spottedleaf.concurrentutil.collection.SRSWLinkedQueue; -+import ca.spottedleaf.concurrentutil.executor.standard.PrioritisedExecutor; -+import ca.spottedleaf.concurrentutil.util.ConcurrentUtil; -+import io.papermc.paper.chunk.system.scheduling.ChunkHolderManager; -+import io.papermc.paper.configuration.GlobalConfiguration; -+import io.papermc.paper.util.CoordinateUtils; -+import io.papermc.paper.util.TickThread; -+import io.papermc.paper.util.player.SingleUserAreaMap; -+import it.unimi.dsi.fastutil.longs.Long2ByteOpenHashMap; -+import it.unimi.dsi.fastutil.longs.LongArrayList; -+import it.unimi.dsi.fastutil.longs.LongComparator; -+import it.unimi.dsi.fastutil.longs.LongHeapPriorityQueue; -+import it.unimi.dsi.fastutil.longs.LongIterator; -+import it.unimi.dsi.fastutil.longs.LongLinkedOpenHashSet; -+import it.unimi.dsi.fastutil.longs.LongOpenHashSet; -+import net.minecraft.network.protocol.Packet; -+import net.minecraft.network.protocol.game.ClientboundSetChunkCacheCenterPacket; -+import net.minecraft.network.protocol.game.ClientboundSetChunkCacheRadiusPacket; -+import net.minecraft.network.protocol.game.ClientboundSetSimulationDistancePacket; -+import net.minecraft.server.level.ChunkTrackingView; -+import net.minecraft.server.level.ServerLevel; -+import net.minecraft.server.level.ServerPlayer; -+import net.minecraft.server.level.TicketType; -+import net.minecraft.server.network.PlayerChunkSender; -+import net.minecraft.world.level.ChunkPos; -+import net.minecraft.world.level.GameRules; -+import net.minecraft.world.level.chunk.ChunkAccess; -+import net.minecraft.world.level.chunk.status.ChunkStatus; -+import net.minecraft.world.level.chunk.LevelChunk; -+import net.minecraft.world.level.levelgen.BelowZeroRetrogen; -+import org.bukkit.craftbukkit.entity.CraftPlayer; -+import org.bukkit.entity.Player; -+import java.lang.invoke.VarHandle; -+import java.util.ArrayDeque; -+import java.util.Arrays; -+import java.util.Objects; -+import java.util.concurrent.TimeUnit; -+import java.util.concurrent.atomic.AtomicLong; -+ -+public class RegionizedPlayerChunkLoader { -+ -+ // expected that this list returns for a given radius, the set of chunks ordered -+ // by manhattan distance -+ private static final long[][] SEARCH_RADIUS_ITERATION_LIST = new long[64+2+1][]; -+ static { -+ for (int i = 0; i < SEARCH_RADIUS_ITERATION_LIST.length; ++i) { -+ // a BFS around -x, -z, +x, +z will give increasing manhatten distance -+ SEARCH_RADIUS_ITERATION_LIST[i] = generateBFSOrder(i); -+ } -+ } -+ -+ private static void expandQuadrants(final CustomLongArray input, final int size) { -+ final int len = input.size(); -+ final long[] array = input.elements(); -+ -+ int writeIndex = size - 1; -+ for (int i = len - 1; i >= 0; --i) { -+ final long key = array[i]; -+ final int chunkX = CoordinateUtils.getChunkX(key); -+ final int chunkZ = CoordinateUtils.getChunkZ(key); -+ -+ if ((chunkX | chunkZ) < 0 || (i != 0 && chunkX == 0 && chunkZ == 0)) { -+ throw new IllegalStateException(); -+ } -+ -+ // Q4 -+ if (chunkZ != 0) { -+ array[writeIndex--] = CoordinateUtils.getChunkKey(chunkX, -chunkZ); -+ } -+ // Q3 -+ if (chunkX != 0 && chunkZ != 0) { -+ array[writeIndex--] = CoordinateUtils.getChunkKey(-chunkX, -chunkZ); -+ } -+ // Q2 -+ if (chunkX != 0) { -+ array[writeIndex--] = CoordinateUtils.getChunkKey(-chunkX, chunkZ); -+ } -+ -+ array[writeIndex--] = key; -+ } -+ -+ input.forceSize(size); -+ -+ if (writeIndex != -1) { -+ throw new IllegalStateException(); -+ } -+ } -+ -+ private static long[] generateBFSOrder(final int radius) { -+ // by using only the first quadrant, we can reduce the total element size by 4 when spreading -+ final CustomLongArray[] byDistance = makeQ1BFS(radius); -+ -+ // to increase generation parallelism, we want to space the chunks out so that they are not nearby when generating -+ // this also means we are minimising locality -+ // but, we need to maintain sorted order by manhatten distance -+ -+ // per manhatten distance we transform the chunk list so that each element is maximally spaced out from each other -+ for (int i = 0, len = byDistance.length; i < len; ++i) { -+ final CustomLongArray points = byDistance[i]; -+ final int expectedSize = getDistanceSize(i, radius); -+ -+ final CustomLongArray spread = spread(points, expectedSize); -+ // add in Q2, Q3, Q4 -+ expandQuadrants(spread, expectedSize); -+ -+ byDistance[i] = spread; -+ } -+ -+ // now, rebuild the list so that it still maintains manhatten distance order -+ final CustomLongArray ret = new CustomLongArray((2 * radius + 1) * (2 * radius + 1)); -+ -+ for (final CustomLongArray dist : byDistance) { -+ ret.addAll(dist); -+ } -+ -+ return ret.elements(); -+ } -+ -+ public static final TicketType REGION_PLAYER_TICKET = TicketType.create("region_player_ticket", Long::compareTo); -+ -+ public static final int MIN_VIEW_DISTANCE = 2; -+ public static final int MAX_VIEW_DISTANCE = 32; -+ -+ public static final int TICK_TICKET_LEVEL = 31; -+ public static final int GENERATED_TICKET_LEVEL = 33 + ChunkStatus.getDistance(ChunkStatus.FULL); -+ public static final int LOADED_TICKET_LEVEL = 33 + ChunkStatus.getDistance(ChunkStatus.EMPTY); -+ -+ public static final record ViewDistances( -+ int tickViewDistance, -+ int loadViewDistance, -+ int sendViewDistance -+ ) { -+ public ViewDistances setTickViewDistance(final int distance) { -+ return new ViewDistances(distance, this.loadViewDistance, this.sendViewDistance); -+ } -+ -+ public ViewDistances setLoadViewDistance(final int distance) { -+ return new ViewDistances(this.tickViewDistance, distance, this.sendViewDistance); -+ } -+ -+ -+ public ViewDistances setSendViewDistance(final int distance) { -+ return new ViewDistances(this.tickViewDistance, this.loadViewDistance, distance); -+ } -+ } -+ -+ public static int getAPITickViewDistance(final Player player) { -+ return getAPITickViewDistance(((CraftPlayer)player).getHandle()); -+ } -+ -+ public static int getAPITickViewDistance(final ServerPlayer player) { -+ final ServerLevel level = (ServerLevel)player.level(); -+ final PlayerChunkLoaderData data = player.chunkLoader; -+ if (data == null) { -+ return level.playerChunkLoader.getAPITickDistance(); -+ } -+ return data.lastTickDistance; -+ } -+ -+ public static int getAPIViewDistance(final Player player) { -+ return getAPIViewDistance(((CraftPlayer)player).getHandle()); -+ } -+ -+ public static int getAPIViewDistance(final ServerPlayer player) { -+ final ServerLevel level = (ServerLevel)player.level(); -+ final PlayerChunkLoaderData data = player.chunkLoader; -+ if (data == null) { -+ return level.playerChunkLoader.getAPIViewDistance(); -+ } -+ // view distance = load distance + 1 -+ return data.lastLoadDistance - 1; -+ } -+ -+ public static int getLoadViewDistance(final ServerPlayer player) { -+ final ServerLevel level = (ServerLevel)player.level(); -+ final PlayerChunkLoaderData data = player.chunkLoader; -+ if (data == null) { -+ return level.playerChunkLoader.getAPIViewDistance(); -+ } -+ // view distance = load distance + 1 -+ return data.lastLoadDistance - 1; -+ } -+ -+ public static int getAPISendViewDistance(final Player player) { -+ return getAPISendViewDistance(((CraftPlayer)player).getHandle()); -+ } -+ -+ public static int getAPISendViewDistance(final ServerPlayer player) { -+ final ServerLevel level = (ServerLevel)player.level(); -+ final PlayerChunkLoaderData data = player.chunkLoader; -+ if (data == null) { -+ return level.playerChunkLoader.getAPISendViewDistance(); -+ } -+ return data.lastSendDistance; -+ } -+ -+ private final ServerLevel world; -+ -+ public RegionizedPlayerChunkLoader(final ServerLevel world) { -+ this.world = world; -+ } -+ -+ public void addPlayer(final ServerPlayer player) { -+ TickThread.ensureTickThread(player, "Cannot add player to player chunk loader async"); -+ if (!player.isRealPlayer) { -+ return; -+ } -+ -+ if (player.chunkLoader != null) { -+ throw new IllegalStateException("Player is already added to player chunk loader"); -+ } -+ -+ final PlayerChunkLoaderData loader = new PlayerChunkLoaderData(this.world, player); -+ -+ player.chunkLoader = loader; -+ loader.add(); -+ } -+ -+ public void updatePlayer(final ServerPlayer player) { -+ final PlayerChunkLoaderData loader = player.chunkLoader; -+ if (loader != null) { -+ loader.update(); -+ } -+ } -+ -+ public void removePlayer(final ServerPlayer player) { -+ TickThread.ensureTickThread(player, "Cannot remove player from player chunk loader async"); -+ if (!player.isRealPlayer) { -+ return; -+ } -+ -+ final PlayerChunkLoaderData loader = player.chunkLoader; -+ -+ if (loader == null) { -+ return; -+ } -+ -+ loader.remove(); -+ player.chunkLoader = null; -+ } -+ -+ public void setSendDistance(final int distance) { -+ this.world.setSendViewDistance(distance); -+ } -+ -+ public void setLoadDistance(final int distance) { -+ this.world.setLoadViewDistance(distance); -+ } -+ -+ public void setTickDistance(final int distance) { -+ this.world.setTickViewDistance(distance); -+ } -+ -+ // Note: follow the player chunk loader so everything stays consistent... -+ public int getAPITickDistance() { -+ final ViewDistances distances = this.world.getViewDistances(); -+ final int tickViewDistance = PlayerChunkLoaderData.getTickDistance(-1, distances.tickViewDistance); -+ return tickViewDistance; -+ } -+ -+ public int getAPIViewDistance() { -+ final ViewDistances distances = this.world.getViewDistances(); -+ final int tickViewDistance = PlayerChunkLoaderData.getTickDistance(-1, distances.tickViewDistance); -+ final int loadDistance = PlayerChunkLoaderData.getLoadViewDistance(tickViewDistance, -1, distances.loadViewDistance); -+ -+ // loadDistance = api view distance + 1 -+ return loadDistance - 1; -+ } -+ -+ public int getAPISendViewDistance() { -+ final ViewDistances distances = this.world.getViewDistances(); -+ final int tickViewDistance = PlayerChunkLoaderData.getTickDistance(-1, distances.tickViewDistance); -+ final int loadDistance = PlayerChunkLoaderData.getLoadViewDistance(tickViewDistance, -1, distances.loadViewDistance); -+ final int sendViewDistance = PlayerChunkLoaderData.getSendViewDistance( -+ loadDistance, -1, -1, distances.sendViewDistance -+ ); -+ -+ return sendViewDistance; -+ } -+ -+ public boolean isChunkSent(final ServerPlayer player, final int chunkX, final int chunkZ, final boolean borderOnly) { -+ return borderOnly ? this.isChunkSentBorderOnly(player, chunkX, chunkZ) : this.isChunkSent(player, chunkX, chunkZ); -+ } -+ -+ public boolean isChunkSent(final ServerPlayer player, final int chunkX, final int chunkZ) { -+ final PlayerChunkLoaderData loader = player.chunkLoader; -+ if (loader == null) { -+ return false; -+ } -+ -+ return loader.sentChunks.contains(CoordinateUtils.getChunkKey(chunkX, chunkZ)); -+ } -+ -+ public boolean isChunkSentBorderOnly(final ServerPlayer player, final int chunkX, final int chunkZ) { -+ final PlayerChunkLoaderData loader = player.chunkLoader; -+ if (loader == null) { -+ return false; -+ } -+ -+ for (int dz = -1; dz <= 1; ++dz) { -+ for (int dx = -1; dx <= 1; ++dx) { -+ if (!loader.sentChunks.contains(CoordinateUtils.getChunkKey(dx + chunkX, dz + chunkZ))) { -+ return true; -+ } -+ } -+ } -+ -+ return false; -+ } -+ -+ public void tick() { -+ TickThread.ensureTickThread("Cannot tick player chunk loader async"); -+ long currTime = System.nanoTime(); -+ for (final ServerPlayer player : new java.util.ArrayList<>(this.world.players())) { -+ final PlayerChunkLoaderData loader = player.chunkLoader; -+ if (loader == null || loader.world != this.world) { -+ // not our problem anymore -+ continue; -+ } -+ loader.update(); // can't invoke plugin logic -+ loader.updateQueues(currTime); -+ } -+ } -+ -+ public static final class PlayerChunkLoaderData { -+ -+ private static final AtomicLong ID_GENERATOR = new AtomicLong(); -+ private final long id = ID_GENERATOR.incrementAndGet(); -+ private final Long idBoxed = Long.valueOf(this.id); -+ -+ private static final long MAX_RATE = 10_000L; -+ -+ private final ServerPlayer player; -+ private final ServerLevel world; -+ -+ private int lastChunkX = Integer.MIN_VALUE; -+ private int lastChunkZ = Integer.MIN_VALUE; -+ -+ private int lastSendDistance = Integer.MIN_VALUE; -+ private int lastLoadDistance = Integer.MIN_VALUE; -+ private int lastTickDistance = Integer.MIN_VALUE; -+ -+ private int lastSentChunkCenterX = Integer.MIN_VALUE; -+ private int lastSentChunkCenterZ = Integer.MIN_VALUE; -+ -+ private int lastSentChunkRadius = Integer.MIN_VALUE; -+ private int lastSentSimulationDistance = Integer.MIN_VALUE; -+ -+ private boolean canGenerateChunks = true; -+ -+ private final ArrayDeque> delayedTicketOps = new ArrayDeque<>(); -+ private final LongOpenHashSet sentChunks = new LongOpenHashSet(); -+ -+ private static final byte CHUNK_TICKET_STAGE_NONE = 0; -+ private static final byte CHUNK_TICKET_STAGE_LOADING = 1; -+ private static final byte CHUNK_TICKET_STAGE_LOADED = 2; -+ private static final byte CHUNK_TICKET_STAGE_GENERATING = 3; -+ private static final byte CHUNK_TICKET_STAGE_GENERATED = 4; -+ private static final byte CHUNK_TICKET_STAGE_TICK = 5; -+ private static final int[] TICKET_STAGE_TO_LEVEL = new int[] { -+ ChunkHolderManager.MAX_TICKET_LEVEL + 1, -+ LOADED_TICKET_LEVEL, -+ LOADED_TICKET_LEVEL, -+ GENERATED_TICKET_LEVEL, -+ GENERATED_TICKET_LEVEL, -+ TICK_TICKET_LEVEL -+ }; -+ private final Long2ByteOpenHashMap chunkTicketStage = new Long2ByteOpenHashMap(); -+ { -+ this.chunkTicketStage.defaultReturnValue(CHUNK_TICKET_STAGE_NONE); -+ } -+ -+ // rate limiting -+ private final AllocatingRateLimiter chunkSendLimiter = new AllocatingRateLimiter(); -+ private final AllocatingRateLimiter chunkLoadTicketLimiter = new AllocatingRateLimiter(); -+ private final AllocatingRateLimiter chunkGenerateTicketLimiter = new AllocatingRateLimiter(); -+ -+ // queues -+ private final LongComparator CLOSEST_MANHATTAN_DIST = (final long c1, final long c2) -> { -+ final int c1x = CoordinateUtils.getChunkX(c1); -+ final int c1z = CoordinateUtils.getChunkZ(c1); -+ -+ final int c2x = CoordinateUtils.getChunkX(c2); -+ final int c2z = CoordinateUtils.getChunkZ(c2); -+ -+ final int centerX = PlayerChunkLoaderData.this.lastChunkX; -+ final int centerZ = PlayerChunkLoaderData.this.lastChunkZ; -+ -+ return Integer.compare( -+ Math.abs(c1x - centerX) + Math.abs(c1z - centerZ), -+ Math.abs(c2x - centerX) + Math.abs(c2z - centerZ) -+ ); -+ }; -+ private final LongHeapPriorityQueue sendQueue = new LongHeapPriorityQueue(CLOSEST_MANHATTAN_DIST); -+ private final LongHeapPriorityQueue tickingQueue = new LongHeapPriorityQueue(CLOSEST_MANHATTAN_DIST); -+ private final LongHeapPriorityQueue generatingQueue = new LongHeapPriorityQueue(CLOSEST_MANHATTAN_DIST); -+ private final LongHeapPriorityQueue genQueue = new LongHeapPriorityQueue(CLOSEST_MANHATTAN_DIST); -+ private final LongHeapPriorityQueue loadingQueue = new LongHeapPriorityQueue(CLOSEST_MANHATTAN_DIST); -+ private final LongHeapPriorityQueue loadQueue = new LongHeapPriorityQueue(CLOSEST_MANHATTAN_DIST); -+ -+ private volatile boolean removed; -+ -+ public PlayerChunkLoaderData(final ServerLevel world, final ServerPlayer player) { -+ this.world = world; -+ this.player = player; -+ } -+ -+ private void flushDelayedTicketOps() { -+ if (this.delayedTicketOps.isEmpty()) { -+ return; -+ } -+ this.world.chunkTaskScheduler.chunkHolderManager.performTicketUpdates(this.delayedTicketOps); -+ this.delayedTicketOps.clear(); -+ } -+ -+ private void pushDelayedTicketOp(final ChunkHolderManager.TicketOperation op) { -+ this.delayedTicketOps.addLast(op); -+ } -+ -+ private void sendChunk(final int chunkX, final int chunkZ) { -+ if (this.sentChunks.add(CoordinateUtils.getChunkKey(chunkX, chunkZ))) { -+ PlayerChunkSender.sendChunk(this.player.connection, this.world, this.world.getChunkIfLoaded(chunkX, chunkZ)); -+ return; -+ } -+ throw new IllegalStateException(); -+ } -+ -+ private void sendUnloadChunk(final int chunkX, final int chunkZ) { -+ if (!this.sentChunks.remove(CoordinateUtils.getChunkKey(chunkX, chunkZ))) { -+ return; -+ } -+ this.sendUnloadChunkRaw(chunkX, chunkZ); -+ } -+ -+ private void sendUnloadChunkRaw(final int chunkX, final int chunkZ) { -+ PlayerChunkSender.dropChunkStatic(this.player, new ChunkPos(chunkX, chunkZ)); -+ } -+ -+ private final SingleUserAreaMap broadcastMap = new SingleUserAreaMap<>(this) { -+ @Override -+ protected void addCallback(final PlayerChunkLoaderData parameter, final int chunkX, final int chunkZ) { -+ // do nothing, we only care about remove -+ } -+ -+ @Override -+ protected void removeCallback(final PlayerChunkLoaderData parameter, final int chunkX, final int chunkZ) { -+ parameter.sendUnloadChunk(chunkX, chunkZ); -+ } -+ }; -+ private final SingleUserAreaMap loadTicketCleanup = new SingleUserAreaMap<>(this) { -+ @Override -+ protected void addCallback(final PlayerChunkLoaderData parameter, final int chunkX, final int chunkZ) { -+ // do nothing, we only care about remove -+ } -+ -+ @Override -+ protected void removeCallback(final PlayerChunkLoaderData parameter, final int chunkX, final int chunkZ) { -+ final long chunk = CoordinateUtils.getChunkKey(chunkX, chunkZ); -+ final byte ticketStage = parameter.chunkTicketStage.remove(chunk); -+ final int level = TICKET_STAGE_TO_LEVEL[ticketStage]; -+ if (level > ChunkHolderManager.MAX_TICKET_LEVEL) { -+ return; -+ } -+ -+ parameter.pushDelayedTicketOp(ChunkHolderManager.TicketOperation.addAndRemove( -+ chunk, -+ TicketType.UNKNOWN, level, new ChunkPos(chunkX, chunkZ), -+ REGION_PLAYER_TICKET, level, parameter.idBoxed -+ )); -+ } -+ }; -+ private final SingleUserAreaMap tickMap = new SingleUserAreaMap<>(this) { -+ @Override -+ protected void addCallback(final PlayerChunkLoaderData parameter, final int chunkX, final int chunkZ) { -+ // do nothing, we will detect ticking chunks when we try to load them -+ } -+ -+ @Override -+ protected void removeCallback(final PlayerChunkLoaderData parameter, final int chunkX, final int chunkZ) { -+ final long chunk = CoordinateUtils.getChunkKey(chunkX, chunkZ); -+ // note: by the time this is called, the tick cleanup should have ran - so, if the chunk is at -+ // the tick stage it was deemed in range for loading. Thus, we need to move it to generated -+ if (!parameter.chunkTicketStage.replace(chunk, CHUNK_TICKET_STAGE_TICK, CHUNK_TICKET_STAGE_GENERATED)) { -+ return; -+ } -+ -+ // Since we are possibly downgrading the ticket level, we add an unknown ticket so that -+ // the level is kept until tick(). -+ parameter.pushDelayedTicketOp(ChunkHolderManager.TicketOperation.addAndRemove( -+ chunk, -+ TicketType.UNKNOWN, TICK_TICKET_LEVEL, new ChunkPos(chunkX, chunkZ), -+ REGION_PLAYER_TICKET, TICK_TICKET_LEVEL, parameter.idBoxed -+ )); -+ // keep chunk at new generated level -+ parameter.pushDelayedTicketOp(ChunkHolderManager.TicketOperation.addOp( -+ chunk, -+ REGION_PLAYER_TICKET, GENERATED_TICKET_LEVEL, parameter.idBoxed -+ )); -+ } -+ }; -+ -+ private static boolean wantChunkLoaded(final int centerX, final int centerZ, final int chunkX, final int chunkZ, -+ final int sendRadius) { -+ // expect sendRadius to be = 1 + target viewable radius -+ return ChunkTrackingView.isWithinDistance(centerX, centerZ, sendRadius, chunkX, chunkZ, true); -+ } -+ -+ private static int getClientViewDistance(final ServerPlayer player) { -+ final Integer vd = player.requestedViewDistance(); -+ return vd == null ? -1 : Math.max(0, vd.intValue()); -+ } -+ -+ private static int getTickDistance(final int playerTickViewDistance, final int worldTickViewDistance) { -+ return playerTickViewDistance < 0 ? worldTickViewDistance : playerTickViewDistance; -+ } -+ -+ private static int getLoadViewDistance(final int tickViewDistance, final int playerLoadViewDistance, -+ final int worldLoadViewDistance) { -+ return Math.max(tickViewDistance + 1, playerLoadViewDistance < 0 ? worldLoadViewDistance : playerLoadViewDistance); -+ } -+ -+ private static int getSendViewDistance(final int loadViewDistance, final int clientViewDistance, -+ final int playerSendViewDistance, final int worldSendViewDistance) { -+ return Math.min( -+ loadViewDistance - 1, -+ playerSendViewDistance < 0 ? (!GlobalConfiguration.get().chunkLoadingAdvanced.autoConfigSendDistance || clientViewDistance < 0 ? (worldSendViewDistance < 0 ? (loadViewDistance - 1) : worldSendViewDistance) : clientViewDistance + 1) : playerSendViewDistance -+ ); -+ } -+ -+ private Packet updateClientChunkRadius(final int radius) { -+ this.lastSentChunkRadius = radius; -+ return new ClientboundSetChunkCacheRadiusPacket(radius); -+ } -+ -+ private Packet updateClientSimulationDistance(final int distance) { -+ this.lastSentSimulationDistance = distance; -+ return new ClientboundSetSimulationDistancePacket(distance); -+ } -+ -+ private Packet updateClientChunkCenter(final int chunkX, final int chunkZ) { -+ this.lastSentChunkCenterX = chunkX; -+ this.lastSentChunkCenterZ = chunkZ; -+ return new ClientboundSetChunkCacheCenterPacket(chunkX, chunkZ); -+ } -+ -+ private boolean canPlayerGenerateChunks() { -+ return !this.player.isSpectator() || this.world.getGameRules().getBoolean(GameRules.RULE_SPECTATORSGENERATECHUNKS); -+ } -+ -+ private double getMaxChunkLoadRate() { -+ final double configRate = GlobalConfiguration.get().chunkLoadingBasic.playerMaxChunkLoadRate; -+ -+ return configRate < 0.0 || configRate > (double)MAX_RATE ? (double)MAX_RATE : Math.max(1.0, configRate); -+ } -+ -+ private double getMaxChunkGenRate() { -+ final double configRate = GlobalConfiguration.get().chunkLoadingBasic.playerMaxChunkGenerateRate; -+ -+ return configRate < 0.0 || configRate > (double)MAX_RATE ? (double)MAX_RATE : Math.max(1.0, configRate); -+ } -+ -+ private double getMaxChunkSendRate() { -+ final double configRate = GlobalConfiguration.get().chunkLoadingBasic.playerMaxChunkSendRate; -+ -+ return configRate < 0.0 || configRate > (double)MAX_RATE ? (double)MAX_RATE : Math.max(1.0, configRate); -+ } -+ -+ private long getMaxChunkLoads() { -+ final long radiusChunks = (2L * this.lastLoadDistance + 1L) * (2L * this.lastLoadDistance + 1L); -+ long configLimit = GlobalConfiguration.get().chunkLoadingAdvanced.playerMaxConcurrentChunkLoads; -+ if (configLimit == 0L) { -+ // by default, only allow 1/5th of the chunks in the view distance to be concurrently active -+ configLimit = Math.max(5L, radiusChunks / 5L); -+ } else if (configLimit < 0L) { -+ configLimit = Integer.MAX_VALUE; -+ } // else: use the value configured -+ configLimit = configLimit - this.loadingQueue.size(); -+ -+ return configLimit; -+ } -+ -+ private long getMaxChunkGenerates() { -+ final long radiusChunks = (2L * this.lastLoadDistance + 1L) * (2L * this.lastLoadDistance + 1L); -+ long configLimit = GlobalConfiguration.get().chunkLoadingAdvanced.playerMaxConcurrentChunkGenerates; -+ if (configLimit == 0L) { -+ // by default, only allow 1/5th of the chunks in the view distance to be concurrently active -+ configLimit = Math.max(5L, radiusChunks / 5L); -+ } else if (configLimit < 0L) { -+ configLimit = Integer.MAX_VALUE; -+ } // else: use the value configured -+ configLimit = configLimit - this.generatingQueue.size(); -+ -+ return configLimit; -+ } -+ -+ private boolean wantChunkSent(final int chunkX, final int chunkZ) { -+ final int dx = this.lastChunkX - chunkX; -+ final int dz = this.lastChunkZ - chunkZ; -+ return (Math.max(Math.abs(dx), Math.abs(dz)) <= (this.lastSendDistance + 1)) && wantChunkLoaded( -+ this.lastChunkX, this.lastChunkZ, chunkX, chunkZ, this.lastSendDistance -+ ); -+ } -+ -+ private boolean wantChunkTicked(final int chunkX, final int chunkZ) { -+ final int dx = this.lastChunkX - chunkX; -+ final int dz = this.lastChunkZ - chunkZ; -+ return Math.max(Math.abs(dx), Math.abs(dz)) <= this.lastTickDistance; -+ } -+ -+ void updateQueues(final long time) { -+ TickThread.ensureTickThread(this.player, "Cannot tick player chunk loader async"); -+ if (this.removed) { -+ throw new IllegalStateException("Ticking removed player chunk loader"); -+ } -+ // update rate limits -+ final double loadRate = this.getMaxChunkLoadRate(); -+ final double genRate = this.getMaxChunkGenRate(); -+ final double sendRate = this.getMaxChunkSendRate(); -+ -+ this.chunkLoadTicketLimiter.tickAllocation(time, loadRate, loadRate); -+ this.chunkGenerateTicketLimiter.tickAllocation(time, genRate, genRate); -+ this.chunkSendLimiter.tickAllocation(time, sendRate, sendRate); -+ -+ // try to progress chunk loads -+ while (!this.loadingQueue.isEmpty()) { -+ final long pendingLoadChunk = this.loadingQueue.firstLong(); -+ final int pendingChunkX = CoordinateUtils.getChunkX(pendingLoadChunk); -+ final int pendingChunkZ = CoordinateUtils.getChunkZ(pendingLoadChunk); -+ final ChunkAccess pending = this.world.chunkSource.getChunkAtImmediately(pendingChunkX, pendingChunkZ); -+ if (pending == null) { -+ // nothing to do here -+ break; -+ } -+ // chunk has loaded, so we can take it out of the queue -+ this.loadingQueue.dequeueLong(); -+ -+ // try to move to generate queue -+ final byte prev = this.chunkTicketStage.put(pendingLoadChunk, CHUNK_TICKET_STAGE_LOADED); -+ if (prev != CHUNK_TICKET_STAGE_LOADING) { -+ throw new IllegalStateException("Previous state should be " + CHUNK_TICKET_STAGE_LOADING + ", not " + prev); -+ } -+ -+ if (this.canGenerateChunks || this.isLoadedChunkGeneratable(pending)) { -+ this.genQueue.enqueue(pendingLoadChunk); -+ } // else: don't want to generate, so just leave it loaded -+ } -+ -+ // try to push more chunk loads -+ final long maxLoads = Math.max(0L, Math.min(MAX_RATE, Math.min(this.loadQueue.size(), this.getMaxChunkLoads()))); -+ final int maxLoadsThisTick = (int)this.chunkLoadTicketLimiter.takeAllocation(time, loadRate, maxLoads); -+ if (maxLoadsThisTick > 0) { -+ final LongArrayList chunks = new LongArrayList(maxLoadsThisTick); -+ for (int i = 0; i < maxLoadsThisTick; ++i) { -+ final long chunk = this.loadQueue.dequeueLong(); -+ final byte prev = this.chunkTicketStage.put(chunk, CHUNK_TICKET_STAGE_LOADING); -+ if (prev != CHUNK_TICKET_STAGE_NONE) { -+ throw new IllegalStateException("Previous state should be " + CHUNK_TICKET_STAGE_NONE + ", not " + prev); -+ } -+ this.pushDelayedTicketOp( -+ ChunkHolderManager.TicketOperation.addOp( -+ chunk, -+ REGION_PLAYER_TICKET, LOADED_TICKET_LEVEL, this.idBoxed -+ ) -+ ); -+ chunks.add(chunk); -+ this.loadingQueue.enqueue(chunk); -+ } -+ -+ // here we need to flush tickets, as scheduleChunkLoad requires tickets to be propagated with addTicket = false -+ this.flushDelayedTicketOps(); -+ // we only need to call scheduleChunkLoad because the loaded ticket level is not enough to start the chunk -+ // load - only generate ticket levels start anything, but they start generation... -+ // propagate levels -+ // Note: this CAN call plugin logic, so it is VITAL that our bookkeeping logic is completely done by the time this is invoked -+ this.world.chunkTaskScheduler.chunkHolderManager.processTicketUpdates(); -+ -+ if (this.removed) { -+ // process ticket updates may invoke plugin logic, which may remove this player -+ return; -+ } -+ -+ for (int i = 0; i < maxLoadsThisTick; ++i) { -+ final long queuedLoadChunk = chunks.getLong(i); -+ final int queuedChunkX = CoordinateUtils.getChunkX(queuedLoadChunk); -+ final int queuedChunkZ = CoordinateUtils.getChunkZ(queuedLoadChunk); -+ this.world.chunkTaskScheduler.scheduleChunkLoad( -+ queuedChunkX, queuedChunkZ, ChunkStatus.EMPTY, false, PrioritisedExecutor.Priority.NORMAL, null -+ ); -+ if (this.removed) { -+ return; -+ } -+ } -+ } -+ -+ // try to progress chunk generations -+ while (!this.generatingQueue.isEmpty()) { -+ final long pendingGenChunk = this.generatingQueue.firstLong(); -+ final int pendingChunkX = CoordinateUtils.getChunkX(pendingGenChunk); -+ final int pendingChunkZ = CoordinateUtils.getChunkZ(pendingGenChunk); -+ final LevelChunk pending = this.world.chunkSource.getChunkAtIfLoadedMainThreadNoCache(pendingChunkX, pendingChunkZ); -+ if (pending == null) { -+ // nothing to do here -+ break; -+ } -+ -+ // chunk has generated, so we can take it out of queue -+ this.generatingQueue.dequeueLong(); -+ -+ final byte prev = this.chunkTicketStage.put(pendingGenChunk, CHUNK_TICKET_STAGE_GENERATED); -+ if (prev != CHUNK_TICKET_STAGE_GENERATING) { -+ throw new IllegalStateException("Previous state should be " + CHUNK_TICKET_STAGE_GENERATING + ", not " + prev); -+ } -+ -+ // try to move to send queue -+ if (this.wantChunkSent(pendingChunkX, pendingChunkZ)) { -+ this.sendQueue.enqueue(pendingGenChunk); -+ } -+ // try to move to tick queue -+ if (this.wantChunkTicked(pendingChunkX, pendingChunkZ)) { -+ this.tickingQueue.enqueue(pendingGenChunk); -+ } -+ } -+ -+ // try to push more chunk generations -+ final long maxGens = Math.max(0L, Math.min(MAX_RATE, Math.min(this.genQueue.size(), this.getMaxChunkGenerates()))); -+ final int maxGensThisTick = (int)this.chunkGenerateTicketLimiter.takeAllocation(time, genRate, maxGens); -+ int ratedGensThisTick = 0; -+ while (!this.genQueue.isEmpty()) { -+ final long chunkKey = this.genQueue.firstLong(); -+ final int chunkX = CoordinateUtils.getChunkX(chunkKey); -+ final int chunkZ = CoordinateUtils.getChunkZ(chunkKey); -+ final ChunkAccess chunk = this.world.chunkSource.getChunkAtImmediately(chunkX, chunkZ); -+ if (chunk.getStatus() != ChunkStatus.FULL) { -+ // only rate limit actual generations -+ if ((ratedGensThisTick + 1) > maxGensThisTick) { -+ break; -+ } -+ ++ratedGensThisTick; -+ } -+ -+ this.genQueue.dequeueLong(); -+ -+ final byte prev = this.chunkTicketStage.put(chunkKey, CHUNK_TICKET_STAGE_GENERATING); -+ if (prev != CHUNK_TICKET_STAGE_LOADED) { -+ throw new IllegalStateException("Previous state should be " + CHUNK_TICKET_STAGE_LOADED + ", not " + prev); -+ } -+ this.pushDelayedTicketOp( -+ ChunkHolderManager.TicketOperation.addAndRemove( -+ chunkKey, -+ REGION_PLAYER_TICKET, GENERATED_TICKET_LEVEL, this.idBoxed, -+ REGION_PLAYER_TICKET, LOADED_TICKET_LEVEL, this.idBoxed -+ ) -+ ); -+ this.generatingQueue.enqueue(chunkKey); -+ } -+ -+ // try to pull ticking chunks -+ tick_check_outer: -+ while (!this.tickingQueue.isEmpty()) { -+ final long pendingTicking = this.tickingQueue.firstLong(); -+ final int pendingChunkX = CoordinateUtils.getChunkX(pendingTicking); -+ final int pendingChunkZ = CoordinateUtils.getChunkZ(pendingTicking); -+ -+ final int tickingReq = 2; -+ for (int dz = -tickingReq; dz <= tickingReq; ++dz) { -+ for (int dx = -tickingReq; dx <= tickingReq; ++dx) { -+ if ((dx | dz) == 0) { -+ continue; -+ } -+ final long neighbour = CoordinateUtils.getChunkKey(dx + pendingChunkX, dz + pendingChunkZ); -+ final byte stage = this.chunkTicketStage.get(neighbour); -+ if (stage != CHUNK_TICKET_STAGE_GENERATED && stage != CHUNK_TICKET_STAGE_TICK) { -+ break tick_check_outer; -+ } -+ } -+ } -+ // only gets here if all neighbours were marked as generated or ticking themselves -+ this.tickingQueue.dequeueLong(); -+ this.pushDelayedTicketOp( -+ ChunkHolderManager.TicketOperation.addAndRemove( -+ pendingTicking, -+ REGION_PLAYER_TICKET, TICK_TICKET_LEVEL, this.idBoxed, -+ REGION_PLAYER_TICKET, GENERATED_TICKET_LEVEL, this.idBoxed -+ ) -+ ); -+ // there is no queue to add after ticking -+ final byte prev = this.chunkTicketStage.put(pendingTicking, CHUNK_TICKET_STAGE_TICK); -+ if (prev != CHUNK_TICKET_STAGE_GENERATED) { -+ throw new IllegalStateException("Previous state should be " + CHUNK_TICKET_STAGE_GENERATED + ", not " + prev); -+ } -+ } -+ -+ // try to pull sending chunks -+ final long maxSends = Math.max(0L, Math.min(MAX_RATE, Integer.MAX_VALUE)); // no logic to track concurrent sends -+ final int maxSendsThisTick = Math.min((int)this.chunkSendLimiter.takeAllocation(time, sendRate, maxSends), this.sendQueue.size()); -+ // we do not return sends that we took from the allocation back because we want to limit the max send rate, not target it -+ for (int i = 0; i < maxSendsThisTick; ++i) { -+ final long pendingSend = this.sendQueue.firstLong(); -+ final int pendingSendX = CoordinateUtils.getChunkX(pendingSend); -+ final int pendingSendZ = CoordinateUtils.getChunkZ(pendingSend); -+ final LevelChunk chunk = this.world.chunkSource.getChunkAtIfLoadedMainThreadNoCache(pendingSendX, pendingSendZ); -+ if (!chunk.areNeighboursLoaded(1) || !TickThread.isTickThreadFor(this.world, pendingSendX, pendingSendZ)) { -+ // nothing to do -+ // the target chunk may not be owned by this region, but this should be resolved in the future -+ break; -+ } -+ if (!chunk.isPostProcessingDone) { -+ // not yet post-processed, need to do this so that tile entities can properly be sent to clients -+ chunk.postProcessGeneration(); -+ // check if there was any recursive action -+ if (this.removed || this.sendQueue.isEmpty() || this.sendQueue.firstLong() != pendingSend) { -+ return; -+ } // else: good to dequeue and send, fall through -+ } -+ this.sendQueue.dequeueLong(); -+ -+ this.sendChunk(pendingSendX, pendingSendZ); -+ if (this.removed) { -+ // sendChunk may invoke plugin logic -+ return; -+ } -+ } -+ -+ this.flushDelayedTicketOps(); -+ // we assume propagate ticket levels happens after this call -+ } -+ -+ void add() { -+ TickThread.ensureTickThread(this.player, "Cannot add player asynchronously"); -+ if (this.removed) { -+ throw new IllegalStateException("Adding removed player chunk loader"); -+ } -+ final ViewDistances playerDistances = this.player.getViewDistances(); -+ final ViewDistances worldDistances = this.world.getViewDistances(); -+ final int chunkX = this.player.chunkPosition().x; -+ final int chunkZ = this.player.chunkPosition().z; -+ -+ final int tickViewDistance = getTickDistance(playerDistances.tickViewDistance, worldDistances.tickViewDistance); -+ // load view cannot be less-than tick view + 1 -+ final int loadViewDistance = getLoadViewDistance(tickViewDistance, playerDistances.loadViewDistance, worldDistances.loadViewDistance); -+ // send view cannot be greater-than load view -+ final int clientViewDistance = getClientViewDistance(this.player); -+ final int sendViewDistance = getSendViewDistance(loadViewDistance, clientViewDistance, playerDistances.sendViewDistance, worldDistances.sendViewDistance); -+ -+ // send view distances -+ this.player.connection.send(this.updateClientChunkRadius(sendViewDistance)); -+ this.player.connection.send(this.updateClientSimulationDistance(tickViewDistance)); -+ -+ // add to distance maps -+ this.broadcastMap.add(chunkX, chunkZ, sendViewDistance + 1); -+ this.loadTicketCleanup.add(chunkX, chunkZ, loadViewDistance + 1); -+ this.tickMap.add(chunkX, chunkZ, tickViewDistance); -+ -+ // update chunk center -+ this.player.connection.send(this.updateClientChunkCenter(chunkX, chunkZ)); -+ -+ // now we can update -+ this.update(); -+ } -+ -+ private boolean isLoadedChunkGeneratable(final int chunkX, final int chunkZ) { -+ return this.isLoadedChunkGeneratable(this.world.chunkSource.getChunkAtImmediately(chunkX, chunkZ)); -+ } -+ -+ private boolean isLoadedChunkGeneratable(final ChunkAccess chunkAccess) { -+ final BelowZeroRetrogen belowZeroRetrogen; -+ // see PortalForcer#findPortalAround -+ return chunkAccess != null && ( -+ chunkAccess.getStatus() == ChunkStatus.FULL || -+ ((belowZeroRetrogen = chunkAccess.getBelowZeroRetrogen()) != null && belowZeroRetrogen.targetStatus().isOrAfter(ChunkStatus.SPAWN)) -+ ); -+ } -+ -+ void update() { -+ TickThread.ensureTickThread(this.player, "Cannot update player asynchronously"); -+ if (this.removed) { -+ throw new IllegalStateException("Updating removed player chunk loader"); -+ } -+ final ViewDistances playerDistances = this.player.getViewDistances(); -+ final ViewDistances worldDistances = this.world.getViewDistances(); -+ -+ final int tickViewDistance = getTickDistance(playerDistances.tickViewDistance, worldDistances.tickViewDistance); -+ // load view cannot be less-than tick view + 1 -+ final int loadViewDistance = getLoadViewDistance(tickViewDistance, playerDistances.loadViewDistance, worldDistances.loadViewDistance); -+ // send view cannot be greater-than load view -+ final int clientViewDistance = getClientViewDistance(this.player); -+ final int sendViewDistance = getSendViewDistance(loadViewDistance, clientViewDistance, playerDistances.sendViewDistance, worldDistances.sendViewDistance); -+ -+ final ChunkPos playerPos = this.player.chunkPosition(); -+ final boolean canGenerateChunks = this.canPlayerGenerateChunks(); -+ final int currentChunkX = playerPos.x; -+ final int currentChunkZ = playerPos.z; -+ -+ final int prevChunkX = this.lastChunkX; -+ final int prevChunkZ = this.lastChunkZ; -+ -+ if ( -+ // has view distance stayed the same? -+ sendViewDistance == this.lastSendDistance -+ && loadViewDistance == this.lastLoadDistance -+ && tickViewDistance == this.lastTickDistance -+ -+ // has our chunk stayed the same? -+ && prevChunkX == currentChunkX -+ && prevChunkZ == currentChunkZ -+ -+ // can we still generate chunks? -+ && this.canGenerateChunks == canGenerateChunks -+ ) { -+ // nothing we care about changed, so we're not re-calculating -+ return; -+ } -+ -+ // update distance maps -+ this.broadcastMap.update(currentChunkX, currentChunkZ, sendViewDistance + 1); -+ this.loadTicketCleanup.update(currentChunkX, currentChunkZ, loadViewDistance + 1); -+ this.tickMap.update(currentChunkX, currentChunkZ, tickViewDistance); -+ if (sendViewDistance > loadViewDistance || tickViewDistance > loadViewDistance) { -+ throw new IllegalStateException(); -+ } -+ -+ // update VDs for client -+ // this should be after the distance map updates, as they will send unload packets -+ if (this.lastSentChunkRadius != sendViewDistance) { -+ this.player.connection.send(this.updateClientChunkRadius(sendViewDistance)); -+ } -+ if (this.lastSentSimulationDistance != tickViewDistance) { -+ this.player.connection.send(this.updateClientSimulationDistance(tickViewDistance)); -+ } -+ -+ this.sendQueue.clear(); -+ this.tickingQueue.clear(); -+ this.generatingQueue.clear(); -+ this.genQueue.clear(); -+ this.loadingQueue.clear(); -+ this.loadQueue.clear(); -+ -+ this.lastChunkX = currentChunkX; -+ this.lastChunkZ = currentChunkZ; -+ this.lastSendDistance = sendViewDistance; -+ this.lastLoadDistance = loadViewDistance; -+ this.lastTickDistance = tickViewDistance; -+ this.canGenerateChunks = canGenerateChunks; -+ -+ // +1 since we need to load chunks +1 around the load view distance... -+ final long[] toIterate = SEARCH_RADIUS_ITERATION_LIST[loadViewDistance + 1]; -+ // the iteration order is by increasing manhattan distance - so, we do NOT need to -+ // sort anything in the queue! -+ for (final long deltaChunk : toIterate) { -+ final int dx = CoordinateUtils.getChunkX(deltaChunk); -+ final int dz = CoordinateUtils.getChunkZ(deltaChunk); -+ final int chunkX = dx + currentChunkX; -+ final int chunkZ = dz + currentChunkZ; -+ final long chunk = CoordinateUtils.getChunkKey(chunkX, chunkZ); -+ final int squareDistance = Math.max(Math.abs(dx), Math.abs(dz)); -+ final int manhattanDistance = Math.abs(dx) + Math.abs(dz); -+ -+ // since chunk sending is not by radius alone, we need an extra check here to account for -+ // everything <= sendDistance -+ // Note: Vanilla may want to send chunks outside the send view distance, so we do need -+ // the dist <= view check -+ final boolean sendChunk = (squareDistance <= (sendViewDistance + 1)) -+ && wantChunkLoaded(currentChunkX, currentChunkZ, chunkX, chunkZ, sendViewDistance); -+ final boolean sentChunk = sendChunk ? this.sentChunks.contains(chunk) : this.sentChunks.remove(chunk); -+ -+ if (!sendChunk && sentChunk) { -+ // have sent the chunk, but don't want it anymore -+ // unload it now -+ this.sendUnloadChunkRaw(chunkX, chunkZ); -+ } -+ -+ final byte stage = this.chunkTicketStage.get(chunk); -+ switch (stage) { -+ case CHUNK_TICKET_STAGE_NONE: { -+ // we want the chunk to be at least loaded -+ this.loadQueue.enqueue(chunk); -+ break; -+ } -+ case CHUNK_TICKET_STAGE_LOADING: { -+ this.loadingQueue.enqueue(chunk); -+ break; -+ } -+ case CHUNK_TICKET_STAGE_LOADED: { -+ if (canGenerateChunks || this.isLoadedChunkGeneratable(chunkX, chunkZ)) { -+ this.genQueue.enqueue(chunk); -+ } -+ break; -+ } -+ case CHUNK_TICKET_STAGE_GENERATING: { -+ this.generatingQueue.enqueue(chunk); -+ break; -+ } -+ case CHUNK_TICKET_STAGE_GENERATED: { -+ if (sendChunk && !sentChunk) { -+ this.sendQueue.enqueue(chunk); -+ } -+ if (squareDistance <= tickViewDistance) { -+ this.tickingQueue.enqueue(chunk); -+ } -+ break; -+ } -+ case CHUNK_TICKET_STAGE_TICK: { -+ if (sendChunk && !sentChunk) { -+ this.sendQueue.enqueue(chunk); -+ } -+ break; -+ } -+ default: { -+ throw new IllegalStateException("Unknown stage: " + stage); -+ } -+ } -+ } -+ -+ // update the chunk center -+ // this must be done last so that the client does not ignore any of our unload chunk packets above -+ if (this.lastSentChunkCenterX != currentChunkX || this.lastSentChunkCenterZ != currentChunkZ) { -+ this.player.connection.send(this.updateClientChunkCenter(currentChunkX, currentChunkZ)); -+ } -+ -+ this.flushDelayedTicketOps(); -+ } -+ -+ void remove() { -+ TickThread.ensureTickThread(this.player, "Cannot add player asynchronously"); -+ if (this.removed) { -+ throw new IllegalStateException("Removing removed player chunk loader"); -+ } -+ this.removed = true; -+ // sends the chunk unload packets -+ this.broadcastMap.remove(); -+ // cleans up loading/generating tickets -+ this.loadTicketCleanup.remove(); -+ // cleans up ticking tickets -+ this.tickMap.remove(); -+ -+ // purge queues -+ this.sendQueue.clear(); -+ this.tickingQueue.clear(); -+ this.generatingQueue.clear(); -+ this.genQueue.clear(); -+ this.loadingQueue.clear(); -+ this.loadQueue.clear(); -+ -+ // flush ticket changes -+ this.flushDelayedTicketOps(); -+ -+ // now all tickets should be removed, which is all of our external state -+ } -+ } -+ -+ // TODO rebase into util patch -+ private static final class AllocatingRateLimiter { -+ -+ // max difference granularity in ns -+ private static final long MAX_GRANULARITY = TimeUnit.SECONDS.toNanos(1L); -+ -+ private double allocation; -+ private long lastAllocationUpdate; -+ private double takeCarry; -+ private long lastTakeUpdate; -+ -+ // rate in units/s, and time in ns -+ public void tickAllocation(final long time, final double rate, final double maxAllocation) { -+ final long diff = Math.min(MAX_GRANULARITY, time - this.lastAllocationUpdate); -+ this.lastAllocationUpdate = time; -+ -+ this.allocation = Math.min(maxAllocation - this.takeCarry, this.allocation + rate * (diff*1.0E-9D)); -+ } -+ -+ // rate in units/s, and time in ns -+ public long takeAllocation(final long time, final double rate, final long maxTake) { -+ if (maxTake < 1L) { -+ return 0L; -+ } -+ -+ double ret = this.takeCarry; -+ final long diff = Math.min(MAX_GRANULARITY, time - this.lastTakeUpdate); -+ this.lastTakeUpdate = time; -+ -+ // note: abs(takeCarry) <= 1.0 -+ final double take = Math.min(Math.min((double)maxTake - this.takeCarry, this.allocation), rate * (diff*1.0E-9)); -+ -+ ret += take; -+ this.allocation -= take; -+ -+ final long retInteger = (long)Math.floor(ret); -+ this.takeCarry = ret - (double)retInteger; -+ -+ return retInteger; -+ } -+ } -+ -+ static final class CountedSRSWLinkedQueue { -+ -+ private final SRSWLinkedQueue queue = new SRSWLinkedQueue<>(); -+ private volatile long countAdded; -+ private volatile long countRemoved; -+ -+ private static final VarHandle COUNT_ADDED_HANDLE = ConcurrentUtil.getVarHandle(CountedSRSWLinkedQueue.class, "countAdded", long.class); -+ private static final VarHandle COUNT_REMOVED_HANDLE = ConcurrentUtil.getVarHandle(CountedSRSWLinkedQueue.class, "countRemoved", long.class); -+ -+ private long getCountAddedPlain() { -+ return (long)COUNT_ADDED_HANDLE.get(this); -+ } -+ -+ private long getCountAddedAcquire() { -+ return (long)COUNT_ADDED_HANDLE.getAcquire(this); -+ } -+ -+ private void setCountAddedRelease(final long to) { -+ COUNT_ADDED_HANDLE.setRelease(this, to); -+ } -+ -+ private long getCountRemovedPlain() { -+ return (long)COUNT_REMOVED_HANDLE.get(this); -+ } -+ -+ private long getCountRemovedAcquire() { -+ return (long)COUNT_REMOVED_HANDLE.getAcquire(this); -+ } -+ -+ private void setCountRemovedRelease(final long to) { -+ COUNT_REMOVED_HANDLE.setRelease(this, to); -+ } -+ -+ public void add(final E element) { -+ this.setCountAddedRelease(this.getCountAddedPlain() + 1L); -+ this.queue.addLast(element); -+ } -+ -+ public E poll() { -+ final E ret = this.queue.poll(); -+ if (ret != null) { -+ this.setCountRemovedRelease(this.getCountRemovedPlain() + 1L); -+ } -+ -+ return ret; -+ } -+ -+ public long size() { -+ final long removed = this.getCountRemovedAcquire(); -+ final long added = this.getCountAddedAcquire(); -+ -+ return added - removed; -+ } -+ } -+ -+ private static class CustomLongArray extends LongArrayList { -+ -+ public CustomLongArray() { -+ super(); -+ } -+ -+ public CustomLongArray(final int expected) { -+ super(expected); -+ } -+ -+ public boolean addAll(final CustomLongArray list) { -+ this.addElements(this.size, list.a, 0, list.size); -+ return list.size != 0; -+ } -+ -+ public void addUnchecked(final long value) { -+ this.a[this.size++] = value; -+ } -+ -+ public void forceSize(final int to) { -+ this.size = to; -+ } -+ -+ @Override -+ public int hashCode() { -+ long h = 1L; -+ -+ Objects.checkFromToIndex(0, this.size, this.a.length); -+ -+ for (int i = 0; i < this.size; ++i) { -+ h = it.unimi.dsi.fastutil.HashCommon.mix(h + this.a[i]); -+ } -+ -+ return (int)h; -+ } -+ -+ @Override -+ public boolean equals(final Object o) { -+ if (o == this) { -+ return true; -+ } -+ -+ if (!(o instanceof CustomLongArray other)) { -+ return false; -+ } -+ -+ return this.size == other.size && Arrays.equals(this.a, 0, this.size, other.a, 0, this.size); -+ } -+ } -+ -+ private static int getDistanceSize(final int radius, final int max) { -+ if (radius == 0) { -+ return 1; -+ } -+ final int diff = radius - max; -+ if (diff <= 0) { -+ return 4*radius; -+ } -+ return 4*(max - Math.max(0, diff - 1)); -+ } -+ -+ private static int getQ1DistanceSize(final int radius, final int max) { -+ if (radius == 0) { -+ return 1; -+ } -+ final int diff = radius - max; -+ if (diff <= 0) { -+ return radius+1; -+ } -+ return max - diff + 1; -+ } -+ -+ private static final class BasicFIFOLQueue { -+ -+ private final long[] values; -+ private int head, tail; -+ -+ public BasicFIFOLQueue(final int cap) { -+ if (cap <= 1) { -+ throw new IllegalArgumentException(); -+ } -+ this.values = new long[cap]; -+ } -+ -+ public boolean isEmpty() { -+ return this.head == this.tail; -+ } -+ -+ public long removeFirst() { -+ final long ret = this.values[this.head]; -+ -+ if (this.head == this.tail) { -+ throw new IllegalStateException(); -+ } -+ -+ ++this.head; -+ if (this.head == this.values.length) { -+ this.head = 0; -+ } -+ -+ return ret; -+ } -+ -+ public void addLast(final long value) { -+ this.values[this.tail++] = value; -+ -+ if (this.tail == this.head) { -+ throw new IllegalStateException(); -+ } -+ -+ if (this.tail == this.values.length) { -+ this.tail = 0; -+ } -+ } -+ } -+ -+ private static CustomLongArray[] makeQ1BFS(final int radius) { -+ final CustomLongArray[] ret = new CustomLongArray[2 * radius + 1]; -+ final BasicFIFOLQueue queue = new BasicFIFOLQueue(Math.max(1, 4 * radius) + 1); -+ final LongOpenHashSet seen = new LongOpenHashSet((radius + 1) * (radius + 1)); -+ -+ seen.add(CoordinateUtils.getChunkKey(0, 0)); -+ queue.addLast(CoordinateUtils.getChunkKey(0, 0)); -+ while (!queue.isEmpty()) { -+ final long chunk = queue.removeFirst(); -+ final int chunkX = CoordinateUtils.getChunkX(chunk); -+ final int chunkZ = CoordinateUtils.getChunkZ(chunk); -+ -+ final int index = Math.abs(chunkX) + Math.abs(chunkZ); -+ final CustomLongArray list = ret[index]; -+ if (list != null) { -+ list.addUnchecked(chunk); -+ } else { -+ (ret[index] = new CustomLongArray(getQ1DistanceSize(index, radius))).addUnchecked(chunk); -+ } -+ -+ for (int i = 0; i < 4; ++i) { -+ // 0 -> -1, 0 -+ // 1 -> 0, -1 -+ // 2 -> 1, 0 -+ // 3 -> 0, 1 -+ -+ final int signInv = -(i >>> 1); // 2/3 -> -(1), 0/1 -> -(0) -+ // note: -n = (~n) + 1 -+ // (n ^ signInv) - signInv = signInv == 0 ? ((n ^ 0) - 0 = n) : ((n ^ -1) - (-1) = ~n + 1) -+ -+ final int axis = i & 1; // 0/2 -> 0, 1/3 -> 1 -+ final int dx = ((axis - 1) ^ signInv) - signInv; // 0 -> -1, 1 -> 0 -+ final int dz = (-axis ^ signInv) - signInv; // 0 -> 0, 1 -> -1 -+ -+ final int neighbourX = chunkX + dx; -+ final int neighbourZ = chunkZ + dz; -+ final long neighbour = CoordinateUtils.getChunkKey(neighbourX, neighbourZ); -+ -+ if ((neighbourX | neighbourZ) < 0 || Math.max(Math.abs(neighbourX), Math.abs(neighbourZ)) > radius) { -+ // don't enqueue out of range -+ continue; -+ } -+ -+ if (!seen.add(neighbour)) { -+ continue; -+ } -+ -+ queue.addLast(neighbour); -+ } -+ } -+ -+ return ret; -+ } -+ -+ // doesn't appear worth optimising this function now, even though it's 70% of the call -+ private static CustomLongArray spread(final CustomLongArray input, final int size) { -+ final LongLinkedOpenHashSet notAdded = new LongLinkedOpenHashSet(input); -+ final CustomLongArray added = new CustomLongArray(size); -+ -+ while (!notAdded.isEmpty()) { -+ if (added.isEmpty()) { -+ added.addUnchecked(notAdded.removeLastLong()); -+ continue; -+ } -+ -+ long maxChunk = -1L; -+ int maxDist = 0; -+ -+ // select the chunk from the not yet added set that has the largest minimum distance from -+ // the current set of added chunks -+ -+ for (final LongIterator iterator = notAdded.iterator(); iterator.hasNext();) { -+ final long chunkKey = iterator.nextLong(); -+ final int chunkX = CoordinateUtils.getChunkX(chunkKey); -+ final int chunkZ = CoordinateUtils.getChunkZ(chunkKey); -+ -+ int minDist = Integer.MAX_VALUE; -+ -+ final int len = added.size(); -+ final long[] addedArr = added.elements(); -+ Objects.checkFromToIndex(0, len, addedArr.length); -+ for (int i = 0; i < len; ++i) { -+ final long addedKey = addedArr[i]; -+ final int addedX = CoordinateUtils.getChunkX(addedKey); -+ final int addedZ = CoordinateUtils.getChunkZ(addedKey); -+ -+ // here we use square distance because chunk generation uses neighbours in a square radius -+ final int dist = Math.max(Math.abs(addedX - chunkX), Math.abs(addedZ - chunkZ)); -+ -+ minDist = Math.min(dist, minDist); -+ } -+ -+ if (minDist > maxDist) { -+ maxDist = minDist; -+ maxChunk = chunkKey; -+ } -+ } -+ -+ // move the selected chunk from the not added set to the added set -+ -+ if (!notAdded.remove(maxChunk)) { -+ throw new IllegalStateException(); -+ } -+ -+ added.addUnchecked(maxChunk); -+ } -+ -+ return added; -+ } -+} -diff --git a/src/main/java/io/papermc/paper/chunk/system/entity/EntityLookup.java b/src/main/java/io/papermc/paper/chunk/system/entity/EntityLookup.java -new file mode 100644 -index 0000000000000000000000000000000000000000..15ee41452992714108efe53b708b5a4e1da7c1ff ---- /dev/null -+++ b/src/main/java/io/papermc/paper/chunk/system/entity/EntityLookup.java -@@ -0,0 +1,902 @@ -+package io.papermc.paper.chunk.system.entity; -+ -+import com.destroystokyo.paper.util.maplist.EntityList; -+import com.mojang.logging.LogUtils; -+import io.papermc.paper.util.CoordinateUtils; -+import io.papermc.paper.util.TickThread; -+import io.papermc.paper.util.WorldUtil; -+import io.papermc.paper.world.ChunkEntitySlices; -+import it.unimi.dsi.fastutil.ints.Int2ReferenceOpenHashMap; -+import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap; -+import it.unimi.dsi.fastutil.objects.Object2ReferenceOpenHashMap; -+import net.minecraft.core.BlockPos; -+import io.papermc.paper.chunk.system.ChunkSystem; -+import net.minecraft.server.level.ChunkHolder; -+import net.minecraft.server.level.ServerLevel; -+import net.minecraft.util.AbortableIterationConsumer; -+import net.minecraft.util.Mth; -+import net.minecraft.world.entity.Entity; -+import net.minecraft.world.entity.EntityType; -+import net.minecraft.world.level.ChunkPos; -+import net.minecraft.world.level.entity.EntityInLevelCallback; -+import net.minecraft.world.level.entity.EntityTypeTest; -+import net.minecraft.world.level.entity.LevelCallback; -+import net.minecraft.server.level.FullChunkStatus; -+import net.minecraft.world.level.entity.LevelEntityGetter; -+import net.minecraft.world.level.entity.Visibility; -+import net.minecraft.world.phys.AABB; -+import net.minecraft.world.phys.Vec3; -+import org.jetbrains.annotations.NotNull; -+import org.jetbrains.annotations.Nullable; -+import org.slf4j.Logger; -+import java.util.ArrayList; -+import java.util.Arrays; -+import java.util.Iterator; -+import java.util.List; -+import java.util.NoSuchElementException; -+import java.util.UUID; -+import java.util.concurrent.locks.StampedLock; -+import java.util.function.Consumer; -+import java.util.function.Predicate; -+ -+public final class EntityLookup implements LevelEntityGetter { -+ -+ private static final Logger LOGGER = LogUtils.getClassLogger(); -+ -+ protected static final int REGION_SHIFT = 5; -+ protected static final int REGION_MASK = (1 << REGION_SHIFT) - 1; -+ protected static final int REGION_SIZE = 1 << REGION_SHIFT; -+ -+ public final ServerLevel world; -+ -+ private final StampedLock stateLock = new StampedLock(); -+ protected final Long2ObjectOpenHashMap regions = new Long2ObjectOpenHashMap<>(128, 0.5f); -+ -+ private final int minSection; // inclusive -+ private final int maxSection; // inclusive -+ private final LevelCallback worldCallback; -+ -+ private final StampedLock entityByLock = new StampedLock(); -+ private final Int2ReferenceOpenHashMap entityById = new Int2ReferenceOpenHashMap<>(); -+ private final Object2ReferenceOpenHashMap entityByUUID = new Object2ReferenceOpenHashMap<>(); -+ private final EntityList accessibleEntities = new EntityList(); -+ -+ public EntityLookup(final ServerLevel world, final LevelCallback worldCallback) { -+ this.world = world; -+ this.minSection = WorldUtil.getMinSection(world); -+ this.maxSection = WorldUtil.getMaxSection(world); -+ this.worldCallback = worldCallback; -+ } -+ -+ private static Entity maskNonAccessible(final Entity entity) { -+ if (entity == null) { -+ return null; -+ } -+ final Visibility visibility = EntityLookup.getEntityStatus(entity); -+ return visibility.isAccessible() ? entity : null; -+ } -+ -+ @Nullable -+ @Override -+ public Entity get(final int id) { -+ final long attempt = this.entityByLock.tryOptimisticRead(); -+ if (attempt != 0L) { -+ try { -+ final Entity ret = this.entityById.get(id); -+ -+ if (this.entityByLock.validate(attempt)) { -+ return maskNonAccessible(ret); -+ } -+ } catch (final Error error) { -+ throw error; -+ } catch (final Throwable thr) { -+ // ignore -+ } -+ } -+ -+ this.entityByLock.readLock(); -+ try { -+ return maskNonAccessible(this.entityById.get(id)); -+ } finally { -+ this.entityByLock.tryUnlockRead(); -+ } -+ } -+ -+ @Nullable -+ @Override -+ public Entity get(final UUID id) { -+ final long attempt = this.entityByLock.tryOptimisticRead(); -+ if (attempt != 0L) { -+ try { -+ final Entity ret = this.entityByUUID.get(id); -+ -+ if (this.entityByLock.validate(attempt)) { -+ return maskNonAccessible(ret); -+ } -+ } catch (final Error error) { -+ throw error; -+ } catch (final Throwable thr) { -+ // ignore -+ } -+ } -+ -+ this.entityByLock.readLock(); -+ try { -+ return maskNonAccessible(this.entityByUUID.get(id)); -+ } finally { -+ this.entityByLock.tryUnlockRead(); -+ } -+ } -+ -+ public boolean hasEntity(final UUID uuid) { -+ return this.get(uuid) != null; -+ } -+ -+ public String getDebugInfo() { -+ return "count_id:" + this.entityById.size() + ",count_uuid:" + this.entityByUUID.size() + ",region_count:" + this.regions.size(); -+ } -+ -+ static final class ArrayIterable implements Iterable { -+ -+ private final T[] array; -+ private final int off; -+ private final int length; -+ -+ public ArrayIterable(final T[] array, final int off, final int length) { -+ this.array = array; -+ this.off = off; -+ this.length = length; -+ if (length > array.length) { -+ throw new IllegalArgumentException("Length must be no greater-than the array length"); -+ } -+ } -+ -+ @NotNull -+ @Override -+ public Iterator iterator() { -+ return new ArrayIterator<>(this.array, this.off, this.length); -+ } -+ -+ static final class ArrayIterator implements Iterator { -+ -+ private final T[] array; -+ private int off; -+ private final int length; -+ -+ public ArrayIterator(final T[] array, final int off, final int length) { -+ this.array = array; -+ this.off = off; -+ this.length = length; -+ } -+ -+ @Override -+ public boolean hasNext() { -+ return this.off < this.length; -+ } -+ -+ @Override -+ public T next() { -+ if (this.off >= this.length) { -+ throw new NoSuchElementException(); -+ } -+ return this.array[this.off++]; -+ } -+ -+ @Override -+ public void remove() { -+ throw new UnsupportedOperationException(); -+ } -+ } -+ } -+ -+ @Override -+ public Iterable getAll() { -+ return new ArrayIterable<>(this.accessibleEntities.getRawData(), 0, this.accessibleEntities.size()); -+ } -+ -+ public Entity[] getAllCopy() { -+ return Arrays.copyOf(this.accessibleEntities.getRawData(), this.accessibleEntities.size(), Entity[].class); -+ } -+ -+ @Override -+ public void get(final EntityTypeTest filter, final AbortableIterationConsumer action) { -+ final Int2ReferenceOpenHashMap entityCopy; -+ -+ this.entityByLock.readLock(); -+ try { -+ entityCopy = this.entityById.clone(); -+ } finally { -+ this.entityByLock.tryUnlockRead(); -+ } -+ for (final Entity entity : entityCopy.values()) { -+ final Visibility visibility = EntityLookup.getEntityStatus(entity); -+ if (!visibility.isAccessible()) { -+ continue; -+ } -+ final U casted = filter.tryCast(entity); -+ if (casted != null && action.accept(casted).shouldAbort()) { -+ break; -+ } -+ } -+ } -+ -+ @Override -+ public void get(final AABB box, final Consumer action) { -+ List entities = new ArrayList<>(); -+ this.getEntitiesWithoutDragonParts(null, box, entities, null); -+ for (int i = 0, len = entities.size(); i < len; ++i) { -+ action.accept(entities.get(i)); -+ } -+ } -+ -+ @Override -+ public void get(final EntityTypeTest filter, final AABB box, final AbortableIterationConsumer action) { -+ List entities = new ArrayList<>(); -+ this.getEntitiesWithoutDragonParts(null, box, entities, null); -+ for (int i = 0, len = entities.size(); i < len; ++i) { -+ final U casted = filter.tryCast(entities.get(i)); -+ if (casted != null && action.accept(casted).shouldAbort()) { -+ break; -+ } -+ } -+ } -+ -+ public void entityStatusChange(final Entity entity, final ChunkEntitySlices slices, final Visibility oldVisibility, final Visibility newVisibility, final boolean moved, -+ final boolean created, final boolean destroyed) { -+ TickThread.ensureTickThread(entity, "Entity status change must only happen on the main thread"); -+ -+ if (entity.updatingSectionStatus) { -+ // recursive status update -+ LOGGER.error("Cannot recursively update entity chunk status for entity " + entity, new Throwable()); -+ return; -+ } -+ -+ final boolean entityStatusUpdateBefore = slices == null ? false : slices.startPreventingStatusUpdates(); -+ -+ if (entityStatusUpdateBefore) { -+ LOGGER.error("Cannot update chunk status for entity " + entity + " since entity chunk (" + slices.chunkX + "," + slices.chunkZ + ") is receiving update", new Throwable()); -+ return; -+ } -+ -+ try { -+ final Boolean ticketBlockBefore = this.world.chunkTaskScheduler.chunkHolderManager.blockTicketUpdates(); -+ try { -+ entity.updatingSectionStatus = true; -+ try { -+ if (created) { -+ EntityLookup.this.worldCallback.onCreated(entity); -+ } -+ -+ if (oldVisibility == newVisibility) { -+ if (moved && newVisibility.isAccessible()) { -+ EntityLookup.this.worldCallback.onSectionChange(entity); -+ } -+ return; -+ } -+ -+ if (newVisibility.ordinal() > oldVisibility.ordinal()) { -+ // status upgrade -+ if (!oldVisibility.isAccessible() && newVisibility.isAccessible()) { -+ this.accessibleEntities.add(entity); -+ EntityLookup.this.worldCallback.onTrackingStart(entity); -+ } -+ -+ if (!oldVisibility.isTicking() && newVisibility.isTicking()) { -+ EntityLookup.this.worldCallback.onTickingStart(entity); -+ } -+ } else { -+ // status downgrade -+ if (oldVisibility.isTicking() && !newVisibility.isTicking()) { -+ EntityLookup.this.worldCallback.onTickingEnd(entity); -+ } -+ -+ if (oldVisibility.isAccessible() && !newVisibility.isAccessible()) { -+ this.accessibleEntities.remove(entity); -+ EntityLookup.this.worldCallback.onTrackingEnd(entity); -+ } -+ } -+ -+ if (moved && newVisibility.isAccessible()) { -+ EntityLookup.this.worldCallback.onSectionChange(entity); -+ } -+ -+ if (destroyed) { -+ EntityLookup.this.worldCallback.onDestroyed(entity); -+ } -+ } finally { -+ entity.updatingSectionStatus = false; -+ } -+ } finally { -+ this.world.chunkTaskScheduler.chunkHolderManager.unblockTicketUpdates(ticketBlockBefore); -+ } -+ } finally { -+ if (slices != null) { -+ slices.stopPreventingStatusUpdates(false); -+ } -+ } -+ } -+ -+ public void chunkStatusChange(final int x, final int z, final FullChunkStatus newStatus) { -+ this.getChunk(x, z).updateStatus(newStatus, this); -+ } -+ -+ public void addLegacyChunkEntities(final List entities, final ChunkPos forChunk) { -+ this.addEntityChunk(entities, forChunk, true); -+ } -+ -+ public void addEntityChunkEntities(final List entities, final ChunkPos forChunk) { -+ this.addEntityChunk(entities, forChunk, true); -+ } -+ -+ public void addWorldGenChunkEntities(final List entities, final ChunkPos forChunk) { -+ this.addEntityChunk(entities, forChunk, false); -+ } -+ -+ private void addRecursivelySafe(final Entity root, final boolean fromDisk) { -+ if (!this.addEntity(root, fromDisk)) { -+ // possible we are a passenger, and so should dismount from any valid entity in the world -+ root.stopRiding(true); -+ return; -+ } -+ for (final Entity passenger : root.getPassengers()) { -+ this.addRecursivelySafe(passenger, fromDisk); -+ } -+ } -+ -+ private void addEntityChunk(final List entities, final ChunkPos forChunk, final boolean fromDisk) { -+ for (int i = 0, len = entities.size(); i < len; ++i) { -+ final Entity entity = entities.get(i); -+ if (entity.isPassenger()) { -+ continue; -+ } -+ -+ if (!entity.chunkPosition().equals(forChunk)) { -+ LOGGER.warn("Root entity " + entity + " is outside of serialized chunk " + forChunk); -+ // can't set removed here, as we may not own the chunk position -+ // skip the entity -+ continue; -+ } -+ -+ final Vec3 rootPosition = entity.position(); -+ -+ // always adjust positions before adding passengers in case plugins access the entity, and so that -+ // they are added to the right entity chunk -+ for (final Entity passenger : entity.getIndirectPassengers()) { -+ if (!passenger.chunkPosition().equals(forChunk)) { -+ passenger.setPosRaw(rootPosition.x, rootPosition.y, rootPosition.z, true); -+ } -+ } -+ -+ this.addRecursivelySafe(entity, fromDisk); -+ } -+ } -+ -+ public boolean addNewEntity(final Entity entity) { -+ return this.addEntity(entity, false); -+ } -+ -+ public static Visibility getEntityStatus(final Entity entity) { -+ if (entity.isAlwaysTicking()) { -+ return Visibility.TICKING; -+ } -+ final FullChunkStatus entityStatus = entity.chunkStatus; -+ return Visibility.fromFullChunkStatus(entityStatus == null ? FullChunkStatus.INACCESSIBLE : entityStatus); -+ } -+ -+ private boolean addEntity(final Entity entity, final boolean fromDisk) { -+ final BlockPos pos = entity.blockPosition(); -+ final int sectionX = pos.getX() >> 4; -+ final int sectionY = Mth.clamp(pos.getY() >> 4, this.minSection, this.maxSection); -+ final int sectionZ = pos.getZ() >> 4; -+ TickThread.ensureTickThread(this.world, sectionX, sectionZ, "Cannot add entity off-main thread"); -+ -+ if (entity.isRemoved()) { -+ LOGGER.warn("Refusing to add removed entity: " + entity); -+ return false; -+ } -+ -+ if (entity.updatingSectionStatus) { -+ LOGGER.warn("Entity " + entity + " is currently prevented from being added/removed to world since it is processing section status updates", new Throwable()); -+ return false; -+ } -+ -+ if (fromDisk) { -+ ChunkSystem.onEntityPreAdd(this.world, entity); -+ if (entity.isRemoved()) { -+ // removed from checkDupeUUID call -+ return false; -+ } -+ } -+ -+ this.entityByLock.writeLock(); -+ try { -+ if (this.entityById.containsKey(entity.getId())) { -+ LOGGER.warn("Entity id already exists: " + entity.getId() + ", mapped to " + this.entityById.get(entity.getId()) + ", can't add " + entity); -+ return false; -+ } -+ if (this.entityByUUID.containsKey(entity.getUUID())) { -+ LOGGER.warn("Entity uuid already exists: " + entity.getUUID() + ", mapped to " + this.entityByUUID.get(entity.getUUID()) + ", can't add " + entity); -+ return false; -+ } -+ this.entityById.put(entity.getId(), entity); -+ this.entityByUUID.put(entity.getUUID(), entity); -+ } finally { -+ this.entityByLock.tryUnlockWrite(); -+ } -+ -+ entity.sectionX = sectionX; -+ entity.sectionY = sectionY; -+ entity.sectionZ = sectionZ; -+ final ChunkEntitySlices slices = this.getOrCreateChunk(sectionX, sectionZ); -+ if (!slices.addEntity(entity, sectionY)) { -+ LOGGER.warn("Entity " + entity + " added to world '" + this.world.getWorld().getName() + "', but was already contained in entity chunk (" + sectionX + "," + sectionZ + ")"); -+ } -+ -+ entity.setLevelCallback(new EntityCallback(entity)); -+ -+ this.entityStatusChange(entity, slices, Visibility.HIDDEN, getEntityStatus(entity), false, !fromDisk, false); -+ -+ return true; -+ } -+ -+ public boolean canRemoveEntity(final Entity entity) { -+ if (entity.updatingSectionStatus) { -+ return false; -+ } -+ -+ final int sectionX = entity.sectionX; -+ final int sectionZ = entity.sectionZ; -+ final ChunkEntitySlices slices = this.getChunk(sectionX, sectionZ); -+ return slices == null || !slices.isPreventingStatusUpdates(); -+ } -+ -+ private void removeEntity(final Entity entity) { -+ final int sectionX = entity.sectionX; -+ final int sectionY = entity.sectionY; -+ final int sectionZ = entity.sectionZ; -+ TickThread.ensureTickThread(this.world, sectionX, sectionZ, "Cannot remove entity off-main"); -+ if (!entity.isRemoved()) { -+ throw new IllegalStateException("Only call Entity#setRemoved to remove an entity"); -+ } -+ final ChunkEntitySlices slices = this.getChunk(sectionX, sectionZ); -+ // all entities should be in a chunk -+ if (slices == null) { -+ LOGGER.warn("Cannot remove entity " + entity + " from null entity slices (" + sectionX + "," + sectionZ + ")"); -+ } else { -+ if (slices.isPreventingStatusUpdates()) { -+ throw new IllegalStateException("Attempting to remove entity " + entity + " from entity slices (" + sectionX + "," + sectionZ + ") that is receiving status updates"); -+ } -+ if (!slices.removeEntity(entity, sectionY)) { -+ LOGGER.warn("Failed to remove entity " + entity + " from entity slices (" + sectionX + "," + sectionZ + ")"); -+ } -+ } -+ entity.sectionX = entity.sectionY = entity.sectionZ = Integer.MIN_VALUE; -+ -+ this.entityByLock.writeLock(); -+ try { -+ if (!this.entityById.remove(entity.getId(), entity)) { -+ LOGGER.warn("Failed to remove entity " + entity + " by id, current entity mapped: " + this.entityById.get(entity.getId())); -+ } -+ if (!this.entityByUUID.remove(entity.getUUID(), entity)) { -+ LOGGER.warn("Failed to remove entity " + entity + " by uuid, current entity mapped: " + this.entityByUUID.get(entity.getUUID())); -+ } -+ } finally { -+ this.entityByLock.tryUnlockWrite(); -+ } -+ } -+ -+ private ChunkEntitySlices moveEntity(final Entity entity) { -+ // ensure we own the entity -+ TickThread.ensureTickThread(entity, "Cannot move entity off-main"); -+ -+ final BlockPos newPos = entity.blockPosition(); -+ final int newSectionX = newPos.getX() >> 4; -+ final int newSectionY = Mth.clamp(newPos.getY() >> 4, this.minSection, this.maxSection); -+ final int newSectionZ = newPos.getZ() >> 4; -+ -+ if (newSectionX == entity.sectionX && newSectionY == entity.sectionY && newSectionZ == entity.sectionZ) { -+ return null; -+ } -+ -+ // ensure the new section is owned by this tick thread -+ TickThread.ensureTickThread(this.world, newSectionX, newSectionZ, "Cannot move entity off-main"); -+ -+ // ensure the old section is owned by this tick thread -+ TickThread.ensureTickThread(this.world, entity.sectionX, entity.sectionZ, "Cannot move entity off-main"); -+ -+ final ChunkEntitySlices old = this.getChunk(entity.sectionX, entity.sectionZ); -+ final ChunkEntitySlices slices = this.getOrCreateChunk(newSectionX, newSectionZ); -+ -+ if (!old.removeEntity(entity, entity.sectionY)) { -+ LOGGER.warn("Could not remove entity " + entity + " from its old chunk section (" + entity.sectionX + "," + entity.sectionY + "," + entity.sectionZ + ") since it was not contained in the section"); -+ } -+ -+ if (!slices.addEntity(entity, newSectionY)) { -+ LOGGER.warn("Could not add entity " + entity + " to its new chunk section (" + newSectionX + "," + newSectionY + "," + newSectionZ + ") as it is already contained in the section"); -+ } -+ -+ entity.sectionX = newSectionX; -+ entity.sectionY = newSectionY; -+ entity.sectionZ = newSectionZ; -+ -+ return slices; -+ } -+ -+ public void getEntitiesWithoutDragonParts(final Entity except, final AABB box, final List into, final Predicate predicate) { -+ final int minChunkX = (Mth.floor(box.minX) - 2) >> 4; -+ final int minChunkZ = (Mth.floor(box.minZ) - 2) >> 4; -+ final int maxChunkX = (Mth.floor(box.maxX) + 2) >> 4; -+ final int maxChunkZ = (Mth.floor(box.maxZ) + 2) >> 4; -+ -+ final int minRegionX = minChunkX >> REGION_SHIFT; -+ final int minRegionZ = minChunkZ >> REGION_SHIFT; -+ final int maxRegionX = maxChunkX >> REGION_SHIFT; -+ final int maxRegionZ = maxChunkZ >> REGION_SHIFT; -+ -+ for (int currRegionZ = minRegionZ; currRegionZ <= maxRegionZ; ++currRegionZ) { -+ final int minZ = currRegionZ == minRegionZ ? minChunkZ & REGION_MASK : 0; -+ final int maxZ = currRegionZ == maxRegionZ ? maxChunkZ & REGION_MASK : REGION_MASK; -+ -+ for (int currRegionX = minRegionX; currRegionX <= maxRegionX; ++currRegionX) { -+ final ChunkSlicesRegion region = this.getRegion(currRegionX, currRegionZ); -+ -+ if (region == null) { -+ continue; -+ } -+ -+ final int minX = currRegionX == minRegionX ? minChunkX & REGION_MASK : 0; -+ final int maxX = currRegionX == maxRegionX ? maxChunkX & REGION_MASK : REGION_MASK; -+ -+ for (int currZ = minZ; currZ <= maxZ; ++currZ) { -+ for (int currX = minX; currX <= maxX; ++currX) { -+ final ChunkEntitySlices chunk = region.get(currX | (currZ << REGION_SHIFT)); -+ if (chunk == null || !chunk.status.isOrAfter(FullChunkStatus.FULL)) { -+ continue; -+ } -+ -+ chunk.getEntitiesWithoutDragonParts(except, box, into, predicate); -+ } -+ } -+ } -+ } -+ } -+ -+ public void getEntities(final Entity except, final AABB box, final List into, final Predicate predicate) { -+ final int minChunkX = (Mth.floor(box.minX) - 2) >> 4; -+ final int minChunkZ = (Mth.floor(box.minZ) - 2) >> 4; -+ final int maxChunkX = (Mth.floor(box.maxX) + 2) >> 4; -+ final int maxChunkZ = (Mth.floor(box.maxZ) + 2) >> 4; -+ -+ final int minRegionX = minChunkX >> REGION_SHIFT; -+ final int minRegionZ = minChunkZ >> REGION_SHIFT; -+ final int maxRegionX = maxChunkX >> REGION_SHIFT; -+ final int maxRegionZ = maxChunkZ >> REGION_SHIFT; -+ -+ for (int currRegionZ = minRegionZ; currRegionZ <= maxRegionZ; ++currRegionZ) { -+ final int minZ = currRegionZ == minRegionZ ? minChunkZ & REGION_MASK : 0; -+ final int maxZ = currRegionZ == maxRegionZ ? maxChunkZ & REGION_MASK : REGION_MASK; -+ -+ for (int currRegionX = minRegionX; currRegionX <= maxRegionX; ++currRegionX) { -+ final ChunkSlicesRegion region = this.getRegion(currRegionX, currRegionZ); -+ -+ if (region == null) { -+ continue; -+ } -+ -+ final int minX = currRegionX == minRegionX ? minChunkX & REGION_MASK : 0; -+ final int maxX = currRegionX == maxRegionX ? maxChunkX & REGION_MASK : REGION_MASK; -+ -+ for (int currZ = minZ; currZ <= maxZ; ++currZ) { -+ for (int currX = minX; currX <= maxX; ++currX) { -+ final ChunkEntitySlices chunk = region.get(currX | (currZ << REGION_SHIFT)); -+ if (chunk == null || !chunk.status.isOrAfter(FullChunkStatus.FULL)) { -+ continue; -+ } -+ -+ chunk.getEntities(except, box, into, predicate); -+ } -+ } -+ } -+ } -+ } -+ -+ public void getHardCollidingEntities(final Entity except, final AABB box, final List into, final Predicate predicate) { -+ final int minChunkX = (Mth.floor(box.minX) - 2) >> 4; -+ final int minChunkZ = (Mth.floor(box.minZ) - 2) >> 4; -+ final int maxChunkX = (Mth.floor(box.maxX) + 2) >> 4; -+ final int maxChunkZ = (Mth.floor(box.maxZ) + 2) >> 4; -+ -+ final int minRegionX = minChunkX >> REGION_SHIFT; -+ final int minRegionZ = minChunkZ >> REGION_SHIFT; -+ final int maxRegionX = maxChunkX >> REGION_SHIFT; -+ final int maxRegionZ = maxChunkZ >> REGION_SHIFT; -+ -+ for (int currRegionZ = minRegionZ; currRegionZ <= maxRegionZ; ++currRegionZ) { -+ final int minZ = currRegionZ == minRegionZ ? minChunkZ & REGION_MASK : 0; -+ final int maxZ = currRegionZ == maxRegionZ ? maxChunkZ & REGION_MASK : REGION_MASK; -+ -+ for (int currRegionX = minRegionX; currRegionX <= maxRegionX; ++currRegionX) { -+ final ChunkSlicesRegion region = this.getRegion(currRegionX, currRegionZ); -+ -+ if (region == null) { -+ continue; -+ } -+ -+ final int minX = currRegionX == minRegionX ? minChunkX & REGION_MASK : 0; -+ final int maxX = currRegionX == maxRegionX ? maxChunkX & REGION_MASK : REGION_MASK; -+ -+ for (int currZ = minZ; currZ <= maxZ; ++currZ) { -+ for (int currX = minX; currX <= maxX; ++currX) { -+ final ChunkEntitySlices chunk = region.get(currX | (currZ << REGION_SHIFT)); -+ if (chunk == null || !chunk.status.isOrAfter(FullChunkStatus.FULL)) { -+ continue; -+ } -+ -+ chunk.getHardCollidingEntities(except, box, into, predicate); -+ } -+ } -+ } -+ } -+ } -+ -+ public void getEntities(final EntityType type, final AABB box, final List into, -+ final Predicate predicate) { -+ final int minChunkX = (Mth.floor(box.minX) - 2) >> 4; -+ final int minChunkZ = (Mth.floor(box.minZ) - 2) >> 4; -+ final int maxChunkX = (Mth.floor(box.maxX) + 2) >> 4; -+ final int maxChunkZ = (Mth.floor(box.maxZ) + 2) >> 4; -+ -+ final int minRegionX = minChunkX >> REGION_SHIFT; -+ final int minRegionZ = minChunkZ >> REGION_SHIFT; -+ final int maxRegionX = maxChunkX >> REGION_SHIFT; -+ final int maxRegionZ = maxChunkZ >> REGION_SHIFT; -+ -+ for (int currRegionZ = minRegionZ; currRegionZ <= maxRegionZ; ++currRegionZ) { -+ final int minZ = currRegionZ == minRegionZ ? minChunkZ & REGION_MASK : 0; -+ final int maxZ = currRegionZ == maxRegionZ ? maxChunkZ & REGION_MASK : REGION_MASK; -+ -+ for (int currRegionX = minRegionX; currRegionX <= maxRegionX; ++currRegionX) { -+ final ChunkSlicesRegion region = this.getRegion(currRegionX, currRegionZ); -+ -+ if (region == null) { -+ continue; -+ } -+ -+ final int minX = currRegionX == minRegionX ? minChunkX & REGION_MASK : 0; -+ final int maxX = currRegionX == maxRegionX ? maxChunkX & REGION_MASK : REGION_MASK; -+ -+ for (int currZ = minZ; currZ <= maxZ; ++currZ) { -+ for (int currX = minX; currX <= maxX; ++currX) { -+ final ChunkEntitySlices chunk = region.get(currX | (currZ << REGION_SHIFT)); -+ if (chunk == null || !chunk.status.isOrAfter(FullChunkStatus.FULL)) { -+ continue; -+ } -+ -+ chunk.getEntities(type, box, (List)into, (Predicate)predicate); -+ } -+ } -+ } -+ } -+ } -+ -+ public void getEntities(final Class clazz, final Entity except, final AABB box, final List into, -+ final Predicate predicate) { -+ final int minChunkX = (Mth.floor(box.minX) - 2) >> 4; -+ final int minChunkZ = (Mth.floor(box.minZ) - 2) >> 4; -+ final int maxChunkX = (Mth.floor(box.maxX) + 2) >> 4; -+ final int maxChunkZ = (Mth.floor(box.maxZ) + 2) >> 4; -+ -+ final int minRegionX = minChunkX >> REGION_SHIFT; -+ final int minRegionZ = minChunkZ >> REGION_SHIFT; -+ final int maxRegionX = maxChunkX >> REGION_SHIFT; -+ final int maxRegionZ = maxChunkZ >> REGION_SHIFT; -+ -+ for (int currRegionZ = minRegionZ; currRegionZ <= maxRegionZ; ++currRegionZ) { -+ final int minZ = currRegionZ == minRegionZ ? minChunkZ & REGION_MASK : 0; -+ final int maxZ = currRegionZ == maxRegionZ ? maxChunkZ & REGION_MASK : REGION_MASK; -+ -+ for (int currRegionX = minRegionX; currRegionX <= maxRegionX; ++currRegionX) { -+ final ChunkSlicesRegion region = this.getRegion(currRegionX, currRegionZ); -+ -+ if (region == null) { -+ continue; -+ } -+ -+ final int minX = currRegionX == minRegionX ? minChunkX & REGION_MASK : 0; -+ final int maxX = currRegionX == maxRegionX ? maxChunkX & REGION_MASK : REGION_MASK; -+ -+ for (int currZ = minZ; currZ <= maxZ; ++currZ) { -+ for (int currX = minX; currX <= maxX; ++currX) { -+ final ChunkEntitySlices chunk = region.get(currX | (currZ << REGION_SHIFT)); -+ if (chunk == null || !chunk.status.isOrAfter(FullChunkStatus.FULL)) { -+ continue; -+ } -+ -+ chunk.getEntities(clazz, except, box, into, predicate); -+ } -+ } -+ } -+ } -+ } -+ -+ public void entitySectionLoad(final int chunkX, final int chunkZ, final ChunkEntitySlices slices) { -+ TickThread.ensureTickThread(this.world, chunkX, chunkZ, "Cannot load in entity section off-main"); -+ synchronized (this) { -+ final ChunkEntitySlices curr = this.getChunk(chunkX, chunkZ); -+ if (curr != null) { -+ this.removeChunk(chunkX, chunkZ); -+ -+ curr.mergeInto(slices); -+ -+ this.addChunk(chunkX, chunkZ, slices); -+ } else { -+ this.addChunk(chunkX, chunkZ, slices); -+ } -+ } -+ } -+ -+ public void entitySectionUnload(final int chunkX, final int chunkZ) { -+ TickThread.ensureTickThread(this.world, chunkX, chunkZ, "Cannot unload entity section off-main"); -+ this.removeChunk(chunkX, chunkZ); -+ } -+ -+ public ChunkEntitySlices getChunk(final int chunkX, final int chunkZ) { -+ final ChunkSlicesRegion region = this.getRegion(chunkX >> REGION_SHIFT, chunkZ >> REGION_SHIFT); -+ if (region == null) { -+ return null; -+ } -+ -+ return region.get((chunkX & REGION_MASK) | ((chunkZ & REGION_MASK) << REGION_SHIFT)); -+ } -+ -+ public ChunkEntitySlices getOrCreateChunk(final int chunkX, final int chunkZ) { -+ final ChunkSlicesRegion region = this.getRegion(chunkX >> REGION_SHIFT, chunkZ >> REGION_SHIFT); -+ ChunkEntitySlices ret; -+ if (region == null || (ret = region.get((chunkX & REGION_MASK) | ((chunkZ & REGION_MASK) << REGION_SHIFT))) == null) { -+ // loadInEntityChunk will call addChunk for us -+ return this.world.chunkTaskScheduler.chunkHolderManager.getOrCreateEntityChunk(chunkX, chunkZ, true); -+ } -+ -+ return ret; -+ } -+ -+ public ChunkSlicesRegion getRegion(final int regionX, final int regionZ) { -+ final long key = CoordinateUtils.getChunkKey(regionX, regionZ); -+ final long attempt = this.stateLock.tryOptimisticRead(); -+ if (attempt != 0L) { -+ try { -+ final ChunkSlicesRegion ret = this.regions.get(key); -+ -+ if (this.stateLock.validate(attempt)) { -+ return ret; -+ } -+ } catch (final Error error) { -+ throw error; -+ } catch (final Throwable thr) { -+ // ignore -+ } -+ } -+ -+ this.stateLock.readLock(); -+ try { -+ return this.regions.get(key); -+ } finally { -+ this.stateLock.tryUnlockRead(); -+ } -+ } -+ -+ private synchronized void removeChunk(final int chunkX, final int chunkZ) { -+ final long key = CoordinateUtils.getChunkKey(chunkX >> REGION_SHIFT, chunkZ >> REGION_SHIFT); -+ final int relIndex = (chunkX & REGION_MASK) | ((chunkZ & REGION_MASK) << REGION_SHIFT); -+ -+ final ChunkSlicesRegion region = this.regions.get(key); -+ final int remaining = region.remove(relIndex); -+ -+ if (remaining == 0) { -+ this.stateLock.writeLock(); -+ try { -+ this.regions.remove(key); -+ } finally { -+ this.stateLock.tryUnlockWrite(); -+ } -+ } -+ } -+ -+ public synchronized void addChunk(final int chunkX, final int chunkZ, final ChunkEntitySlices slices) { -+ final long key = CoordinateUtils.getChunkKey(chunkX >> REGION_SHIFT, chunkZ >> REGION_SHIFT); -+ final int relIndex = (chunkX & REGION_MASK) | ((chunkZ & REGION_MASK) << REGION_SHIFT); -+ -+ ChunkSlicesRegion region = this.regions.get(key); -+ if (region != null) { -+ region.add(relIndex, slices); -+ } else { -+ region = new ChunkSlicesRegion(); -+ region.add(relIndex, slices); -+ this.stateLock.writeLock(); -+ try { -+ this.regions.put(key, region); -+ } finally { -+ this.stateLock.tryUnlockWrite(); -+ } -+ } -+ } -+ -+ public static final class ChunkSlicesRegion { -+ -+ protected final ChunkEntitySlices[] slices = new ChunkEntitySlices[REGION_SIZE * REGION_SIZE]; -+ protected int sliceCount; -+ -+ public ChunkEntitySlices get(final int index) { -+ return this.slices[index]; -+ } -+ -+ public int remove(final int index) { -+ final ChunkEntitySlices slices = this.slices[index]; -+ if (slices == null) { -+ throw new IllegalStateException(); -+ } -+ -+ this.slices[index] = null; -+ -+ return --this.sliceCount; -+ } -+ -+ public void add(final int index, final ChunkEntitySlices slices) { -+ final ChunkEntitySlices curr = this.slices[index]; -+ if (curr != null) { -+ throw new IllegalStateException(); -+ } -+ -+ this.slices[index] = slices; -+ -+ ++this.sliceCount; -+ } -+ } -+ -+ private final class EntityCallback implements EntityInLevelCallback { -+ -+ public final Entity entity; -+ -+ public EntityCallback(final Entity entity) { -+ this.entity = entity; -+ } -+ -+ @Override -+ public void onMove() { -+ final Entity entity = this.entity; -+ final Visibility oldVisibility = getEntityStatus(entity); -+ final ChunkEntitySlices newSlices = EntityLookup.this.moveEntity(this.entity); -+ if (newSlices == null) { -+ // no new section, so didn't change sections -+ return; -+ } -+ final Visibility newVisibility = getEntityStatus(entity); -+ -+ EntityLookup.this.entityStatusChange(entity, newSlices, oldVisibility, newVisibility, true, false, false); -+ } -+ -+ @Override -+ public void onRemove(final Entity.RemovalReason reason) { -+ final Entity entity = this.entity; -+ TickThread.ensureTickThread(entity, "Cannot remove entity off-main"); // Paper - rewrite chunk system -+ final Visibility tickingState = EntityLookup.getEntityStatus(entity); -+ -+ EntityLookup.this.removeEntity(entity); -+ -+ EntityLookup.this.entityStatusChange(entity, null, tickingState, Visibility.HIDDEN, false, false, reason.shouldDestroy()); -+ -+ this.entity.setLevelCallback(NoOpCallback.INSTANCE); -+ } -+ } -+ -+ private static final class NoOpCallback implements EntityInLevelCallback { -+ -+ public static final NoOpCallback INSTANCE = new NoOpCallback(); -+ -+ @Override -+ public void onMove() {} -+ -+ @Override -+ public void onRemove(final Entity.RemovalReason reason) {} -+ } -+} -diff --git a/src/main/java/io/papermc/paper/chunk/system/io/RegionFileIOThread.java b/src/main/java/io/papermc/paper/chunk/system/io/RegionFileIOThread.java -new file mode 100644 -index 0000000000000000000000000000000000000000..2096e57c025858519e7c46788993b9aac1ec60e8 ---- /dev/null -+++ b/src/main/java/io/papermc/paper/chunk/system/io/RegionFileIOThread.java -@@ -0,0 +1,1289 @@ -+package io.papermc.paper.chunk.system.io; -+ -+import ca.spottedleaf.concurrentutil.collection.MultiThreadedQueue; -+import ca.spottedleaf.concurrentutil.executor.Cancellable; -+import ca.spottedleaf.concurrentutil.executor.standard.PrioritisedExecutor; -+import ca.spottedleaf.concurrentutil.executor.standard.PrioritisedQueueExecutorThread; -+import ca.spottedleaf.concurrentutil.executor.standard.PrioritisedThreadedTaskQueue; -+import ca.spottedleaf.concurrentutil.util.ConcurrentUtil; -+import com.mojang.logging.LogUtils; -+import io.papermc.paper.util.CoordinateUtils; -+import io.papermc.paper.util.TickThread; -+import it.unimi.dsi.fastutil.HashCommon; -+import net.minecraft.nbt.CompoundTag; -+import net.minecraft.server.level.ServerLevel; -+import net.minecraft.world.level.ChunkPos; -+import net.minecraft.world.level.chunk.storage.RegionFile; -+import net.minecraft.world.level.chunk.storage.RegionFileStorage; -+import org.slf4j.Logger; -+import java.io.IOException; -+import java.lang.invoke.VarHandle; -+import java.util.concurrent.CompletableFuture; -+import java.util.concurrent.CompletionException; -+import java.util.concurrent.ConcurrentHashMap; -+import java.util.concurrent.atomic.AtomicInteger; -+import java.util.function.BiConsumer; -+import java.util.function.BiFunction; -+import java.util.function.Consumer; -+import java.util.function.Function; -+ -+/** -+ * Prioritised RegionFile I/O executor, responsible for all RegionFile access. -+ *

    -+ * All functions provided are MT-Safe, however certain ordering constraints are recommended: -+ *

    -+ * By following these constraints, no chunk data loss should occur with the exception of underlying I/O problems. -+ */ -+public final class RegionFileIOThread extends PrioritisedQueueExecutorThread { -+ -+ private static final Logger LOGGER = LogUtils.getClassLogger(); -+ -+ /** -+ * The kinds of region files controlled by the region file thread. Add more when needed, and ensure -+ * getControllerFor is updated. -+ */ -+ public static enum RegionFileType { -+ CHUNK_DATA, -+ POI_DATA, -+ ENTITY_DATA; -+ } -+ -+ protected static final RegionFileType[] CACHED_REGIONFILE_TYPES = RegionFileType.values(); -+ -+ private ChunkDataController getControllerFor(final ServerLevel world, final RegionFileType type) { -+ return switch (type) { -+ case CHUNK_DATA -> world.chunkDataControllerNew; -+ case POI_DATA -> world.poiDataControllerNew; -+ case ENTITY_DATA -> world.entityDataControllerNew; -+ default -> throw new IllegalStateException("Unknown controller type " + type); -+ }; -+ } -+ -+ /** -+ * Collects regionfile data for a certain chunk. -+ */ -+ public static final class RegionFileData { -+ -+ private final boolean[] hasResult = new boolean[CACHED_REGIONFILE_TYPES.length]; -+ private final CompoundTag[] data = new CompoundTag[CACHED_REGIONFILE_TYPES.length]; -+ private final Throwable[] throwables = new Throwable[CACHED_REGIONFILE_TYPES.length]; -+ -+ /** -+ * Sets the result associated with the specified regionfile type. Note that -+ * results can only be set once per regionfile type. -+ * -+ * @param type The regionfile type. -+ * @param data The result to set. -+ */ -+ public void setData(final RegionFileType type, final CompoundTag data) { -+ final int index = type.ordinal(); -+ -+ if (this.hasResult[index]) { -+ throw new IllegalArgumentException("Result already exists for type " + type); -+ } -+ this.hasResult[index] = true; -+ this.data[index] = data; -+ } -+ -+ /** -+ * Sets the result associated with the specified regionfile type. Note that -+ * results can only be set once per regionfile type. -+ * -+ * @param type The regionfile type. -+ * @param throwable The result to set. -+ */ -+ public void setThrowable(final RegionFileType type, final Throwable throwable) { -+ final int index = type.ordinal(); -+ -+ if (this.hasResult[index]) { -+ throw new IllegalArgumentException("Result already exists for type " + type); -+ } -+ this.hasResult[index] = true; -+ this.throwables[index] = throwable; -+ } -+ -+ /** -+ * Returns whether there is a result for the specified regionfile type. -+ * -+ * @param type Specified regionfile type. -+ * -+ * @return Whether a result exists for {@code type}. -+ */ -+ public boolean hasResult(final RegionFileType type) { -+ return this.hasResult[type.ordinal()]; -+ } -+ -+ /** -+ * Returns the data result for the regionfile type. -+ * -+ * @param type Specified regionfile type. -+ * -+ * @throws IllegalArgumentException If the result has not been set for {@code type}. -+ * @return The data result for the specified type. If the result is a {@code Throwable}, -+ * then returns {@code null}. -+ */ -+ public CompoundTag getData(final RegionFileType type) { -+ final int index = type.ordinal(); -+ -+ if (!this.hasResult[index]) { -+ throw new IllegalArgumentException("Result does not exist for type " + type); -+ } -+ -+ return this.data[index]; -+ } -+ -+ /** -+ * Returns the throwable result for the regionfile type. -+ * -+ * @param type Specified regionfile type. -+ * -+ * @throws IllegalArgumentException If the result has not been set for {@code type}. -+ * @return The throwable result for the specified type. If the result is an {@code CompoundTag}, -+ * then returns {@code null}. -+ */ -+ public Throwable getThrowable(final RegionFileType type) { -+ final int index = type.ordinal(); -+ -+ if (!this.hasResult[index]) { -+ throw new IllegalArgumentException("Result does not exist for type " + type); -+ } -+ -+ return this.throwables[index]; -+ } -+ } -+ -+ private static final Object INIT_LOCK = new Object(); -+ -+ static RegionFileIOThread[] threads; -+ -+ /* needs to be consistent given a set of parameters */ -+ static RegionFileIOThread selectThread(final ServerLevel world, final int chunkX, final int chunkZ, final RegionFileType type) { -+ if (threads == null) { -+ throw new IllegalStateException("Threads not initialised"); -+ } -+ -+ final int regionX = chunkX >> 5; -+ final int regionZ = chunkZ >> 5; -+ final int typeOffset = type.ordinal(); -+ -+ return threads[(System.identityHashCode(world) + regionX + regionZ + typeOffset) % threads.length]; -+ } -+ -+ /** -+ * Shuts down the I/O executor(s). Watis for all tasks to complete if specified. -+ * Tasks queued during this call might not be accepted, and tasks queued after will not be accepted. -+ * -+ * @param wait Whether to wait until all tasks have completed. -+ */ -+ public static void close(final boolean wait) { -+ for (int i = 0, len = threads.length; i < len; ++i) { -+ threads[i].close(false, true); -+ } -+ if (wait) { -+ RegionFileIOThread.flush(); -+ } -+ } -+ -+ public static long[] getExecutedTasks() { -+ final long[] ret = new long[threads.length]; -+ for (int i = 0, len = threads.length; i < len; ++i) { -+ ret[i] = threads[i].getTotalTasksExecuted(); -+ } -+ -+ return ret; -+ } -+ -+ public static long[] getTasksScheduled() { -+ final long[] ret = new long[threads.length]; -+ for (int i = 0, len = threads.length; i < len; ++i) { -+ ret[i] = threads[i].getTotalTasksScheduled(); -+ } -+ return ret; -+ } -+ -+ public static void flush() { -+ for (int i = 0, len = threads.length; i < len; ++i) { -+ threads[i].waitUntilAllExecuted(); -+ } -+ } -+ -+ public static void partialFlush(final int totalTasksRemaining) { -+ long failures = 1L; // start out at 0.25ms -+ -+ for (;;) { -+ final long[] executed = getExecutedTasks(); -+ final long[] scheduled = getTasksScheduled(); -+ -+ long sum = 0; -+ for (int i = 0; i < executed.length; ++i) { -+ sum += scheduled[i] - executed[i]; -+ } -+ -+ if (sum <= totalTasksRemaining) { -+ break; -+ } -+ -+ failures = ConcurrentUtil.linearLongBackoff(failures, 250_000L, 5_000_000L); // 500us, 5ms -+ } -+ } -+ -+ /** -+ * Inits the executor with the specified number of threads. -+ * -+ * @param threads Specified number of threads. -+ */ -+ public static void init(final int threads) { -+ synchronized (INIT_LOCK) { -+ if (RegionFileIOThread.threads != null) { -+ throw new IllegalStateException("Already initialised threads"); -+ } -+ -+ RegionFileIOThread.threads = new RegionFileIOThread[threads]; -+ -+ for (int i = 0; i < threads; ++i) { -+ RegionFileIOThread.threads[i] = new RegionFileIOThread(i); -+ RegionFileIOThread.threads[i].start(); -+ } -+ } -+ } -+ -+ private RegionFileIOThread(final int threadNumber) { -+ super(new PrioritisedThreadedTaskQueue(), (int)(1.0e6)); // 1.0ms spinwait time -+ this.setName("RegionFile I/O Thread #" + threadNumber); -+ this.setPriority(Thread.NORM_PRIORITY - 2); // we keep priority close to normal because threads can wait on us -+ this.setUncaughtExceptionHandler((final Thread thread, final Throwable thr) -> { -+ LOGGER.error("Uncaught exception thrown from I/O thread, report this! Thread: " + thread.getName(), thr); -+ }); -+ } -+ -+ /** -+ * Returns whether the current thread is a regionfile I/O executor. -+ * @return Whether the current thread is a regionfile I/O executor. -+ */ -+ public static boolean isRegionFileThread() { -+ return Thread.currentThread() instanceof RegionFileIOThread; -+ } -+ -+ /** -+ * Returns the priority associated with blocking I/O based on the current thread. The goal is to avoid -+ * dumb plugins from taking away priority from threads we consider crucial. -+ * @return The priroity to use with blocking I/O on the current thread. -+ */ -+ public static PrioritisedExecutor.Priority getIOBlockingPriorityForCurrentThread() { -+ if (TickThread.isTickThread()) { -+ return PrioritisedExecutor.Priority.BLOCKING; -+ } -+ return PrioritisedExecutor.Priority.HIGHEST; -+ } -+ -+ /** -+ * Returns the current {@code CompoundTag} pending for write for the specified chunk and regionfile type. -+ * Note that this does not copy the result, so do not modify the result returned. -+ * -+ * @param world Specified world. -+ * @param chunkX Specified chunk x. -+ * @param chunkZ Specified chunk z. -+ * @param type Specified regionfile type. -+ * -+ * @return The compound tag associated for the specified chunk. {@code null} if no write was pending, or if {@code null} is the write pending. -+ */ -+ public static CompoundTag getPendingWrite(final ServerLevel world, final int chunkX, final int chunkZ, final RegionFileType type) { -+ final RegionFileIOThread thread = RegionFileIOThread.selectThread(world, chunkX, chunkZ, type); -+ return thread.getPendingWriteInternal(world, chunkX, chunkZ, type); -+ } -+ -+ CompoundTag getPendingWriteInternal(final ServerLevel world, final int chunkX, final int chunkZ, final RegionFileType type) { -+ final ChunkDataController taskController = this.getControllerFor(world, type); -+ final ChunkDataTask task = taskController.tasks.get(Long.valueOf(CoordinateUtils.getChunkKey(chunkX, chunkZ))); -+ -+ if (task == null) { -+ return null; -+ } -+ -+ final CompoundTag ret = task.inProgressWrite; -+ -+ return ret == ChunkDataTask.NOTHING_TO_WRITE ? null : ret; -+ } -+ -+ /** -+ * Returns the priority for the specified regionfile type for the specified chunk. -+ * @param world Specified world. -+ * @param chunkX Specified chunk x. -+ * @param chunkZ Specified chunk z. -+ * @param type Specified regionfile type. -+ * @return The priority for the chunk -+ */ -+ public static PrioritisedExecutor.Priority getPriority(final ServerLevel world, final int chunkX, final int chunkZ, final RegionFileType type) { -+ final RegionFileIOThread thread = RegionFileIOThread.selectThread(world, chunkX, chunkZ, type); -+ return thread.getPriorityInternal(world, chunkX, chunkZ, type); -+ } -+ -+ PrioritisedExecutor.Priority getPriorityInternal(final ServerLevel world, final int chunkX, final int chunkZ, final RegionFileType type) { -+ final ChunkDataController taskController = this.getControllerFor(world, type); -+ final ChunkDataTask task = taskController.tasks.get(Long.valueOf(CoordinateUtils.getChunkKey(chunkX, chunkZ))); -+ -+ if (task == null) { -+ return PrioritisedExecutor.Priority.COMPLETING; -+ } -+ -+ return task.prioritisedTask.getPriority(); -+ } -+ -+ /** -+ * Sets the priority for all regionfile types for the specified chunk. Note that great care should -+ * be taken using this method, as there can be multiple tasks tied to the same chunk that want different -+ * priorities. -+ * -+ * @param world Specified world. -+ * @param chunkX Specified chunk x. -+ * @param chunkZ Specified chunk z. -+ * @param priority New priority. -+ * -+ * @see #raisePriority(ServerLevel, int, int, Priority) -+ * @see #raisePriority(ServerLevel, int, int, RegionFileType, Priority) -+ * @see #lowerPriority(ServerLevel, int, int, Priority) -+ * @see #lowerPriority(ServerLevel, int, int, RegionFileType, Priority) -+ */ -+ public static void setPriority(final ServerLevel world, final int chunkX, final int chunkZ, -+ final PrioritisedExecutor.Priority priority) { -+ for (final RegionFileType type : CACHED_REGIONFILE_TYPES) { -+ RegionFileIOThread.setPriority(world, chunkX, chunkZ, type, priority); -+ } -+ } -+ -+ /** -+ * Sets the priority for the specified regionfile type for the specified chunk. Note that great care should -+ * be taken using this method, as there can be multiple tasks tied to the same chunk that want different -+ * priorities. -+ * -+ * @param world Specified world. -+ * @param chunkX Specified chunk x. -+ * @param chunkZ Specified chunk z. -+ * @param type Specified regionfile type. -+ * @param priority New priority. -+ * -+ * @see #raisePriority(ServerLevel, int, int, Priority) -+ * @see #raisePriority(ServerLevel, int, int, RegionFileType, Priority) -+ * @see #lowerPriority(ServerLevel, int, int, Priority) -+ * @see #lowerPriority(ServerLevel, int, int, RegionFileType, Priority) -+ */ -+ public static void setPriority(final ServerLevel world, final int chunkX, final int chunkZ, final RegionFileType type, -+ final PrioritisedExecutor.Priority priority) { -+ final RegionFileIOThread thread = RegionFileIOThread.selectThread(world, chunkX, chunkZ, type); -+ thread.setPriorityInternal(world, chunkX, chunkZ, type, priority); -+ } -+ -+ void setPriorityInternal(final ServerLevel world, final int chunkX, final int chunkZ, final RegionFileType type, -+ final PrioritisedExecutor.Priority priority) { -+ final ChunkDataController taskController = this.getControllerFor(world, type); -+ final ChunkDataTask task = taskController.tasks.get(Long.valueOf(CoordinateUtils.getChunkKey(chunkX, chunkZ))); -+ -+ if (task != null) { -+ task.prioritisedTask.setPriority(priority); -+ } -+ } -+ -+ /** -+ * Raises the priority for all regionfile types for the specified chunk. -+ * -+ * @param world Specified world. -+ * @param chunkX Specified chunk x. -+ * @param chunkZ Specified chunk z. -+ * @param priority New priority. -+ * -+ * @see #setPriority(ServerLevel, int, int, Priority) -+ * @see #setPriority(ServerLevel, int, int, RegionFileType, Priority) -+ * @see #lowerPriority(ServerLevel, int, int, Priority) -+ * @see #lowerPriority(ServerLevel, int, int, RegionFileType, Priority) -+ */ -+ public static void raisePriority(final ServerLevel world, final int chunkX, final int chunkZ, -+ final PrioritisedExecutor.Priority priority) { -+ for (final RegionFileType type : CACHED_REGIONFILE_TYPES) { -+ RegionFileIOThread.raisePriority(world, chunkX, chunkZ, type, priority); -+ } -+ } -+ -+ /** -+ * Raises the priority for the specified regionfile type for the specified chunk. -+ * -+ * @param world Specified world. -+ * @param chunkX Specified chunk x. -+ * @param chunkZ Specified chunk z. -+ * @param type Specified regionfile type. -+ * @param priority New priority. -+ * -+ * @see #setPriority(ServerLevel, int, int, Priority) -+ * @see #setPriority(ServerLevel, int, int, RegionFileType, Priority) -+ * @see #lowerPriority(ServerLevel, int, int, Priority) -+ * @see #lowerPriority(ServerLevel, int, int, RegionFileType, Priority) -+ */ -+ public static void raisePriority(final ServerLevel world, final int chunkX, final int chunkZ, final RegionFileType type, -+ final PrioritisedExecutor.Priority priority) { -+ final RegionFileIOThread thread = RegionFileIOThread.selectThread(world, chunkX, chunkZ, type); -+ thread.raisePriorityInternal(world, chunkX, chunkZ, type, priority); -+ } -+ -+ void raisePriorityInternal(final ServerLevel world, final int chunkX, final int chunkZ, final RegionFileType type, -+ final PrioritisedExecutor.Priority priority) { -+ final ChunkDataController taskController = this.getControllerFor(world, type); -+ final ChunkDataTask task = taskController.tasks.get(Long.valueOf(CoordinateUtils.getChunkKey(chunkX, chunkZ))); -+ -+ if (task != null) { -+ task.prioritisedTask.raisePriority(priority); -+ } -+ } -+ -+ /** -+ * Lowers the priority for all regionfile types for the specified chunk. -+ * -+ * @param world Specified world. -+ * @param chunkX Specified chunk x. -+ * @param chunkZ Specified chunk z. -+ * @param priority New priority. -+ * -+ * @see #raisePriority(ServerLevel, int, int, Priority) -+ * @see #raisePriority(ServerLevel, int, int, RegionFileType, Priority) -+ * @see #setPriority(ServerLevel, int, int, Priority) -+ * @see #setPriority(ServerLevel, int, int, RegionFileType, Priority) -+ */ -+ public static void lowerPriority(final ServerLevel world, final int chunkX, final int chunkZ, -+ final PrioritisedExecutor.Priority priority) { -+ for (final RegionFileType type : CACHED_REGIONFILE_TYPES) { -+ RegionFileIOThread.lowerPriority(world, chunkX, chunkZ, type, priority); -+ } -+ } -+ -+ /** -+ * Lowers the priority for the specified regionfile type for the specified chunk. -+ * -+ * @param world Specified world. -+ * @param chunkX Specified chunk x. -+ * @param chunkZ Specified chunk z. -+ * @param type Specified regionfile type. -+ * @param priority New priority. -+ * -+ * @see #raisePriority(ServerLevel, int, int, Priority) -+ * @see #raisePriority(ServerLevel, int, int, RegionFileType, Priority) -+ * @see #setPriority(ServerLevel, int, int, Priority) -+ * @see #setPriority(ServerLevel, int, int, RegionFileType, Priority) -+ */ -+ public static void lowerPriority(final ServerLevel world, final int chunkX, final int chunkZ, final RegionFileType type, -+ final PrioritisedExecutor.Priority priority) { -+ final RegionFileIOThread thread = RegionFileIOThread.selectThread(world, chunkX, chunkZ, type); -+ thread.lowerPriorityInternal(world, chunkX, chunkZ, type, priority); -+ } -+ -+ void lowerPriorityInternal(final ServerLevel world, final int chunkX, final int chunkZ, final RegionFileType type, -+ final PrioritisedExecutor.Priority priority) { -+ final ChunkDataController taskController = this.getControllerFor(world, type); -+ final ChunkDataTask task = taskController.tasks.get(Long.valueOf(CoordinateUtils.getChunkKey(chunkX, chunkZ))); -+ -+ if (task != null) { -+ task.prioritisedTask.lowerPriority(priority); -+ } -+ } -+ -+ /** -+ * Schedules the chunk data to be written asynchronously. -+ *

    -+ * Impl notes: -+ *

      -+ *
    • -+ * This function presumes a chunk load for the coordinates is not called during this function (anytime after is OK). This means -+ * saves must be scheduled before a chunk is unloaded. -+ *
    • -+ *
    • -+ * Writes may be called concurrently, although only the "later" write will go through. -+ *
    • -+ *
    -+ * -+ * @param world Chunk's world -+ * @param chunkX Chunk's x coordinate -+ * @param chunkZ Chunk's z coordinate -+ * @param data Chunk's data -+ * @param type The regionfile type to write to. -+ * -+ * @throws IllegalStateException If the file io thread has shutdown. -+ */ -+ public static void scheduleSave(final ServerLevel world, final int chunkX, final int chunkZ, final CompoundTag data, -+ final RegionFileType type) { -+ RegionFileIOThread.scheduleSave(world, chunkX, chunkZ, data, type, PrioritisedExecutor.Priority.NORMAL); -+ } -+ -+ /** -+ * Schedules the chunk data to be written asynchronously. -+ *

    -+ * Impl notes: -+ *

      -+ *
    • -+ * This function presumes a chunk load for the coordinates is not called during this function (anytime after is OK). This means -+ * saves must be scheduled before a chunk is unloaded. -+ *
    • -+ *
    • -+ * Writes may be called concurrently, although only the "later" write will go through. -+ *
    • -+ *
    -+ * -+ * @param world Chunk's world -+ * @param chunkX Chunk's x coordinate -+ * @param chunkZ Chunk's z coordinate -+ * @param data Chunk's data -+ * @param type The regionfile type to write to. -+ * @param priority The minimum priority to schedule at. -+ * -+ * @throws IllegalStateException If the file io thread has shutdown. -+ */ -+ public static void scheduleSave(final ServerLevel world, final int chunkX, final int chunkZ, final CompoundTag data, -+ final RegionFileType type, final PrioritisedExecutor.Priority priority) { -+ final RegionFileIOThread thread = RegionFileIOThread.selectThread(world, chunkX, chunkZ, type); -+ thread.scheduleSaveInternal(world, chunkX, chunkZ, data, type, priority); -+ } -+ -+ void scheduleSaveInternal(final ServerLevel world, final int chunkX, final int chunkZ, final CompoundTag data, -+ final RegionFileType type, final PrioritisedExecutor.Priority priority) { -+ final ChunkDataController taskController = this.getControllerFor(world, type); -+ -+ final boolean[] created = new boolean[1]; -+ final ChunkCoordinate key = new ChunkCoordinate(CoordinateUtils.getChunkKey(chunkX, chunkZ)); -+ final ChunkDataTask task = taskController.tasks.compute(key, (final ChunkCoordinate keyInMap, final ChunkDataTask taskRunning) -> { -+ if (taskRunning == null || taskRunning.failedWrite) { -+ // no task is scheduled or the previous write failed - meaning we need to overwrite it -+ -+ // create task -+ final ChunkDataTask newTask = new ChunkDataTask(world, chunkX, chunkZ, taskController, RegionFileIOThread.this, priority); -+ newTask.inProgressWrite = data; -+ created[0] = true; -+ -+ return newTask; -+ } -+ -+ taskRunning.inProgressWrite = data; -+ -+ return taskRunning; -+ }); -+ -+ if (created[0]) { -+ task.prioritisedTask.queue(); -+ } else { -+ task.prioritisedTask.raisePriority(priority); -+ } -+ } -+ -+ /** -+ * Schedules a load to be executed asynchronously. This task will load all regionfile types, and then call -+ * {@code onComplete}. This is a bulk load operation, see {@link #loadDataAsync(ServerLevel, int, int, RegionFileType, BiConsumer, boolean)} -+ * for single load. -+ *

    -+ * Impl notes: -+ *

      -+ *
    • -+ * The {@code onComplete} parameter may be completed during the execution of this function synchronously or it may -+ * be completed asynchronously on this file io thread. Interacting with the file IO thread in the completion of -+ * data is undefined behaviour, and can cause deadlock. -+ *
    • -+ *
    -+ * -+ * @param world Chunk's world -+ * @param chunkX Chunk's x coordinate -+ * @param chunkZ Chunk's z coordinate -+ * @param onComplete Consumer to execute once this task has completed -+ * @param intendingToBlock Whether the caller is intending to block on completion. This only affects the cost -+ * of this call. -+ * -+ * @return The {@link Cancellable} for this chunk load. Cancelling it will not affect other loads for the same chunk data. -+ * -+ * @see #loadDataAsync(ServerLevel, int, int, RegionFileType, BiConsumer, boolean) -+ * @see #loadDataAsync(ServerLevel, int, int, RegionFileType, BiConsumer, boolean, Priority) -+ * @see #loadChunkData(ServerLevel, int, int, Consumer, boolean, RegionFileType...) -+ * @see #loadChunkData(ServerLevel, int, int, Consumer, boolean, Priority, RegionFileType...) -+ */ -+ public static Cancellable loadAllChunkData(final ServerLevel world, final int chunkX, final int chunkZ, -+ final Consumer onComplete, final boolean intendingToBlock) { -+ return RegionFileIOThread.loadAllChunkData(world, chunkX, chunkZ, onComplete, intendingToBlock, PrioritisedExecutor.Priority.NORMAL); -+ } -+ -+ /** -+ * Schedules a load to be executed asynchronously. This task will load all regionfile types, and then call -+ * {@code onComplete}. This is a bulk load operation, see {@link #loadDataAsync(ServerLevel, int, int, RegionFileType, BiConsumer, boolean, Priority)} -+ * for single load. -+ *

    -+ * Impl notes: -+ *

      -+ *
    • -+ * The {@code onComplete} parameter may be completed during the execution of this function synchronously or it may -+ * be completed asynchronously on this file io thread. Interacting with the file IO thread in the completion of -+ * data is undefined behaviour, and can cause deadlock. -+ *
    • -+ *
    -+ * -+ * @param world Chunk's world -+ * @param chunkX Chunk's x coordinate -+ * @param chunkZ Chunk's z coordinate -+ * @param onComplete Consumer to execute once this task has completed -+ * @param intendingToBlock Whether the caller is intending to block on completion. This only affects the cost -+ * of this call. -+ * @param priority The minimum priority to load the data at. -+ * -+ * @return The {@link Cancellable} for this chunk load. Cancelling it will not affect other loads for the same chunk data. -+ * -+ * @see #loadDataAsync(ServerLevel, int, int, RegionFileType, BiConsumer, boolean) -+ * @see #loadDataAsync(ServerLevel, int, int, RegionFileType, BiConsumer, boolean, Priority) -+ * @see #loadChunkData(ServerLevel, int, int, Consumer, boolean, RegionFileType...) -+ * @see #loadChunkData(ServerLevel, int, int, Consumer, boolean, Priority, RegionFileType...) -+ */ -+ public static Cancellable loadAllChunkData(final ServerLevel world, final int chunkX, final int chunkZ, -+ final Consumer onComplete, final boolean intendingToBlock, -+ final PrioritisedExecutor.Priority priority) { -+ return RegionFileIOThread.loadChunkData(world, chunkX, chunkZ, onComplete, intendingToBlock, priority, CACHED_REGIONFILE_TYPES); -+ } -+ -+ /** -+ * Schedules a load to be executed asynchronously. This task will load data for the specified regionfile type(s), and -+ * then call {@code onComplete}. This is a bulk load operation, see {@link #loadDataAsync(ServerLevel, int, int, RegionFileType, BiConsumer, boolean)} -+ * for single load. -+ *

    -+ * Impl notes: -+ *

      -+ *
    • -+ * The {@code onComplete} parameter may be completed during the execution of this function synchronously or it may -+ * be completed asynchronously on this file io thread. Interacting with the file IO thread in the completion of -+ * data is undefined behaviour, and can cause deadlock. -+ *
    • -+ *
    -+ * -+ * @param world Chunk's world -+ * @param chunkX Chunk's x coordinate -+ * @param chunkZ Chunk's z coordinate -+ * @param onComplete Consumer to execute once this task has completed -+ * @param intendingToBlock Whether the caller is intending to block on completion. This only affects the cost -+ * of this call. -+ * @param types The regionfile type(s) to load. -+ * -+ * @return The {@link Cancellable} for this chunk load. Cancelling it will not affect other loads for the same chunk data. -+ * -+ * @see #loadDataAsync(ServerLevel, int, int, RegionFileType, BiConsumer, boolean) -+ * @see #loadDataAsync(ServerLevel, int, int, RegionFileType, BiConsumer, boolean, Priority) -+ * @see #loadAllChunkData(ServerLevel, int, int, Consumer, boolean) -+ * @see #loadAllChunkData(ServerLevel, int, int, Consumer, boolean, Priority) -+ */ -+ public static Cancellable loadChunkData(final ServerLevel world, final int chunkX, final int chunkZ, -+ final Consumer onComplete, final boolean intendingToBlock, -+ final RegionFileType... types) { -+ return RegionFileIOThread.loadChunkData(world, chunkX, chunkZ, onComplete, intendingToBlock, PrioritisedExecutor.Priority.NORMAL, types); -+ } -+ -+ /** -+ * Schedules a load to be executed asynchronously. This task will load data for the specified regionfile type(s), and -+ * then call {@code onComplete}. This is a bulk load operation, see {@link #loadDataAsync(ServerLevel, int, int, RegionFileType, BiConsumer, boolean, Priority)} -+ * for single load. -+ *

    -+ * Impl notes: -+ *

      -+ *
    • -+ * The {@code onComplete} parameter may be completed during the execution of this function synchronously or it may -+ * be completed asynchronously on this file io thread. Interacting with the file IO thread in the completion of -+ * data is undefined behaviour, and can cause deadlock. -+ *
    • -+ *
    -+ * -+ * @param world Chunk's world -+ * @param chunkX Chunk's x coordinate -+ * @param chunkZ Chunk's z coordinate -+ * @param onComplete Consumer to execute once this task has completed -+ * @param intendingToBlock Whether the caller is intending to block on completion. This only affects the cost -+ * of this call. -+ * @param types The regionfile type(s) to load. -+ * @param priority The minimum priority to load the data at. -+ * -+ * @return The {@link Cancellable} for this chunk load. Cancelling it will not affect other loads for the same chunk data. -+ * -+ * @see #loadDataAsync(ServerLevel, int, int, RegionFileType, BiConsumer, boolean) -+ * @see #loadDataAsync(ServerLevel, int, int, RegionFileType, BiConsumer, boolean, Priority) -+ * @see #loadAllChunkData(ServerLevel, int, int, Consumer, boolean) -+ * @see #loadAllChunkData(ServerLevel, int, int, Consumer, boolean, Priority) -+ */ -+ public static Cancellable loadChunkData(final ServerLevel world, final int chunkX, final int chunkZ, -+ final Consumer onComplete, final boolean intendingToBlock, -+ final PrioritisedExecutor.Priority priority, final RegionFileType... types) { -+ if (types == null) { -+ throw new NullPointerException("Types cannot be null"); -+ } -+ if (types.length == 0) { -+ throw new IllegalArgumentException("Types cannot be empty"); -+ } -+ -+ final RegionFileData ret = new RegionFileData(); -+ -+ final Cancellable[] reads = new CancellableRead[types.length]; -+ final AtomicInteger completions = new AtomicInteger(); -+ final int expectedCompletions = types.length; -+ -+ for (int i = 0; i < expectedCompletions; ++i) { -+ final RegionFileType type = types[i]; -+ reads[i] = RegionFileIOThread.loadDataAsync(world, chunkX, chunkZ, type, -+ (final CompoundTag data, final Throwable throwable) -> { -+ if (throwable != null) { -+ ret.setThrowable(type, throwable); -+ } else { -+ ret.setData(type, data); -+ } -+ -+ if (completions.incrementAndGet() == expectedCompletions) { -+ onComplete.accept(ret); -+ } -+ }, intendingToBlock, priority); -+ } -+ -+ return new CancellableReads(reads); -+ } -+ -+ /** -+ * Schedules a load to be executed asynchronously. This task will load the specified regionfile type, and then call -+ * {@code onComplete}. -+ *

    -+ * Impl notes: -+ *

      -+ *
    • -+ * The {@code onComplete} parameter may be completed during the execution of this function synchronously or it may -+ * be completed asynchronously on this file io thread. Interacting with the file IO thread in the completion of -+ * data is undefined behaviour, and can cause deadlock. -+ *
    • -+ *
    -+ * -+ * @param world Chunk's world -+ * @param chunkX Chunk's x coordinate -+ * @param chunkZ Chunk's z coordinate -+ * @param onComplete Consumer to execute once this task has completed -+ * @param intendingToBlock Whether the caller is intending to block on completion. This only affects the cost -+ * of this call. -+ * -+ * @return The {@link Cancellable} for this chunk load. Cancelling it will not affect other loads for the same chunk data. -+ * -+ * @see #loadChunkData(ServerLevel, int, int, Consumer, boolean, RegionFileType...) -+ * @see #loadChunkData(ServerLevel, int, int, Consumer, boolean, Priority, RegionFileType...) -+ * @see #loadAllChunkData(ServerLevel, int, int, Consumer, boolean) -+ * @see #loadAllChunkData(ServerLevel, int, int, Consumer, boolean, Priority) -+ */ -+ public static Cancellable loadDataAsync(final ServerLevel world, final int chunkX, final int chunkZ, -+ final RegionFileType type, final BiConsumer onComplete, -+ final boolean intendingToBlock) { -+ return RegionFileIOThread.loadDataAsync(world, chunkX, chunkZ, type, onComplete, intendingToBlock, PrioritisedExecutor.Priority.NORMAL); -+ } -+ -+ /** -+ * Schedules a load to be executed asynchronously. This task will load the specified regionfile type, and then call -+ * {@code onComplete}. -+ *

    -+ * Impl notes: -+ *

      -+ *
    • -+ * The {@code onComplete} parameter may be completed during the execution of this function synchronously or it may -+ * be completed asynchronously on this file io thread. Interacting with the file IO thread in the completion of -+ * data is undefined behaviour, and can cause deadlock. -+ *
    • -+ *
    -+ * -+ * @param world Chunk's world -+ * @param chunkX Chunk's x coordinate -+ * @param chunkZ Chunk's z coordinate -+ * @param onComplete Consumer to execute once this task has completed -+ * @param intendingToBlock Whether the caller is intending to block on completion. This only affects the cost -+ * of this call. -+ * @param priority Minimum priority to load the data at. -+ * -+ * @return The {@link Cancellable} for this chunk load. Cancelling it will not affect other loads for the same chunk data. -+ * -+ * @see #loadChunkData(ServerLevel, int, int, Consumer, boolean, RegionFileType...) -+ * @see #loadChunkData(ServerLevel, int, int, Consumer, boolean, Priority, RegionFileType...) -+ * @see #loadAllChunkData(ServerLevel, int, int, Consumer, boolean) -+ * @see #loadAllChunkData(ServerLevel, int, int, Consumer, boolean, Priority) -+ */ -+ public static Cancellable loadDataAsync(final ServerLevel world, final int chunkX, final int chunkZ, -+ final RegionFileType type, final BiConsumer onComplete, -+ final boolean intendingToBlock, final PrioritisedExecutor.Priority priority) { -+ final RegionFileIOThread thread = RegionFileIOThread.selectThread(world, chunkX, chunkZ, type); -+ return thread.loadDataAsyncInternal(world, chunkX, chunkZ, type, onComplete, intendingToBlock, priority); -+ } -+ -+ Cancellable loadDataAsyncInternal(final ServerLevel world, final int chunkX, final int chunkZ, -+ final RegionFileType type, final BiConsumer onComplete, -+ final boolean intendingToBlock, final PrioritisedExecutor.Priority priority) { -+ final ChunkDataController taskController = this.getControllerFor(world, type); -+ -+ final ImmediateCallbackCompletion callbackInfo = new ImmediateCallbackCompletion(); -+ -+ final ChunkCoordinate key = new ChunkCoordinate(CoordinateUtils.getChunkKey(chunkX, chunkZ)); -+ final BiFunction compute = (final ChunkCoordinate keyInMap, final ChunkDataTask running) -> { -+ if (running == null) { -+ // not scheduled -+ -+ // set up task -+ final ChunkDataTask newTask = new ChunkDataTask( -+ world, chunkX, chunkZ, taskController, RegionFileIOThread.this, priority -+ ); -+ newTask.inProgressRead = new RegionFileIOThread.InProgressRead(); -+ newTask.inProgressRead.waiters.add(onComplete); -+ -+ callbackInfo.tasksNeedsScheduling = true; -+ return newTask; -+ } -+ -+ final CompoundTag pendingWrite = running.inProgressWrite; -+ -+ if (pendingWrite == ChunkDataTask.NOTHING_TO_WRITE) { -+ // need to add to waiters here, because the regionfile thread will use compute() to lock and check for cancellations -+ if (!running.inProgressRead.addToWaiters(onComplete)) { -+ callbackInfo.data = running.inProgressRead.value; -+ callbackInfo.throwable = running.inProgressRead.throwable; -+ callbackInfo.completeNow = true; -+ } -+ return running; -+ } -+ // using the result sync here - don't bump priority -+ -+ // at this stage we have to use the in progress write's data to avoid an order issue -+ callbackInfo.data = pendingWrite; -+ callbackInfo.throwable = null; -+ callbackInfo.completeNow = true; -+ return running; -+ }; -+ -+ final ChunkDataTask ret = taskController.tasks.compute(key, compute); -+ -+ // needs to be scheduled -+ if (callbackInfo.tasksNeedsScheduling) { -+ ret.prioritisedTask.queue(); -+ } else if (callbackInfo.completeNow) { -+ try { -+ onComplete.accept(callbackInfo.data, callbackInfo.throwable); -+ } catch (final ThreadDeath thr) { -+ throw thr; -+ } catch (final Throwable thr) { -+ LOGGER.error("Callback " + ConcurrentUtil.genericToString(onComplete) + " synchronously failed to handle chunk data for task " + ret.toString(), thr); -+ } -+ } else { -+ // we're waiting on a task we didn't schedule, so raise its priority to what we want -+ ret.prioritisedTask.raisePriority(priority); -+ } -+ -+ return new CancellableRead(onComplete, ret); -+ } -+ -+ /** -+ * Schedules a load task to be executed asynchronously, and blocks on that task. -+ * -+ * @param world Chunk's world -+ * @param chunkX Chunk's x coordinate -+ * @param chunkZ Chunk's z coordinate -+ * @param type Regionfile type -+ * @param priority Minimum priority to load the data at. -+ * -+ * @return The chunk data for the chunk. Note that a {@code null} result means the chunk or regionfile does not exist on disk. -+ * -+ * @throws IOException If the load fails for any reason -+ */ -+ public static CompoundTag loadData(final ServerLevel world, final int chunkX, final int chunkZ, final RegionFileType type, -+ final PrioritisedExecutor.Priority priority) throws IOException { -+ final CompletableFuture ret = new CompletableFuture<>(); -+ -+ RegionFileIOThread.loadDataAsync(world, chunkX, chunkZ, type, (final CompoundTag compound, final Throwable thr) -> { -+ if (thr != null) { -+ ret.completeExceptionally(thr); -+ } else { -+ ret.complete(compound); -+ } -+ }, true, priority); -+ -+ try { -+ return ret.join(); -+ } catch (final CompletionException ex) { -+ throw new IOException(ex); -+ } -+ } -+ -+ private static final class ImmediateCallbackCompletion { -+ -+ public CompoundTag data; -+ public Throwable throwable; -+ public boolean completeNow; -+ public boolean tasksNeedsScheduling; -+ -+ } -+ -+ static final class CancellableRead implements Cancellable { -+ -+ private BiConsumer callback; -+ private RegionFileIOThread.ChunkDataTask task; -+ -+ CancellableRead(final BiConsumer callback, final RegionFileIOThread.ChunkDataTask task) { -+ this.callback = callback; -+ this.task = task; -+ } -+ -+ @Override -+ public boolean cancel() { -+ final BiConsumer callback = this.callback; -+ final RegionFileIOThread.ChunkDataTask task = this.task; -+ -+ if (callback == null || task == null) { -+ return false; -+ } -+ -+ this.callback = null; -+ this.task = null; -+ -+ final RegionFileIOThread.InProgressRead read = task.inProgressRead; -+ -+ // read can be null if no read was scheduled (i.e no regionfile existed or chunk in regionfile didn't) -+ return (read != null && read.waiters.remove(callback)); -+ } -+ } -+ -+ static final class CancellableReads implements Cancellable { -+ -+ private Cancellable[] reads; -+ -+ protected static final VarHandle READS_HANDLE = ConcurrentUtil.getVarHandle(CancellableReads.class, "reads", Cancellable[].class); -+ -+ CancellableReads(final Cancellable[] reads) { -+ this.reads = reads; -+ } -+ -+ @Override -+ public boolean cancel() { -+ final Cancellable[] reads = (Cancellable[])READS_HANDLE.getAndSet((CancellableReads)this, (Cancellable[])null); -+ -+ if (reads == null) { -+ return false; -+ } -+ -+ boolean ret = false; -+ -+ for (final Cancellable read : reads) { -+ ret |= read.cancel(); -+ } -+ -+ return ret; -+ } -+ } -+ -+ static final class InProgressRead { -+ -+ private static final Logger LOGGER = LogUtils.getClassLogger(); -+ -+ CompoundTag value; -+ Throwable throwable; -+ final MultiThreadedQueue> waiters = new MultiThreadedQueue<>(); -+ -+ // rets false if already completed (callback not invoked), true if callback was added -+ boolean addToWaiters(final BiConsumer callback) { -+ return this.waiters.add(callback); -+ } -+ -+ void complete(final RegionFileIOThread.ChunkDataTask task, final CompoundTag value, final Throwable throwable) { -+ this.value = value; -+ this.throwable = throwable; -+ -+ BiConsumer consumer; -+ while ((consumer = this.waiters.pollOrBlockAdds()) != null) { -+ try { -+ consumer.accept(value, throwable); -+ } catch (final ThreadDeath thr) { -+ throw thr; -+ } catch (final Throwable thr) { -+ LOGGER.error("Callback " + ConcurrentUtil.genericToString(consumer) + " failed to handle chunk data for task " + task.toString(), thr); -+ } -+ } -+ } -+ } -+ -+ /** -+ * Class exists to replace {@link Long} usages as keys inside non-fastutil hashtables. The hash for some Long {@code x} -+ * is defined as {@code (x >>> 32) ^ x}. Chunk keys as long values are defined as {@code ((chunkX & 0xFFFFFFFFL) | (chunkZ << 32))}, -+ * which means the hashcode as a Long value will be {@code chunkX ^ chunkZ}. Given that most chunks are created within a radius arounds players, -+ * this will lead to many hash collisions. So, this class uses a better hashing algorithm so that usage of -+ * non-fastutil collections is not degraded. -+ */ -+ public static final class ChunkCoordinate implements Comparable { -+ -+ public final long key; -+ -+ public ChunkCoordinate(final long key) { -+ this.key = key; -+ } -+ -+ @Override -+ public int hashCode() { -+ return (int)HashCommon.mix(this.key); -+ } -+ -+ @Override -+ public boolean equals(final Object obj) { -+ if (this == obj) { -+ return true; -+ } -+ -+ if (!(obj instanceof ChunkCoordinate)) { -+ return false; -+ } -+ -+ final ChunkCoordinate other = (ChunkCoordinate)obj; -+ -+ return this.key == other.key; -+ } -+ -+ // This class is intended for HashMap/ConcurrentHashMap usage, which do treeify bin nodes if the chain -+ // is too large. So we should implement compareTo to help. -+ @Override -+ public int compareTo(final RegionFileIOThread.ChunkCoordinate other) { -+ return Long.compare(this.key, other.key); -+ } -+ -+ @Override -+ public String toString() { -+ return new ChunkPos(this.key).toString(); -+ } -+ } -+ -+ public static abstract class ChunkDataController { -+ -+ // ConcurrentHashMap synchronizes per chain, so reduce the chance of task's hashes colliding. -+ protected final ConcurrentHashMap tasks = new ConcurrentHashMap<>(8192, 0.10f); -+ -+ public final RegionFileType type; -+ -+ public ChunkDataController(final RegionFileType type) { -+ this.type = type; -+ } -+ -+ public abstract RegionFileStorage getCache(); -+ -+ public abstract void writeData(final int chunkX, final int chunkZ, final CompoundTag compound) throws IOException; -+ -+ public abstract CompoundTag readData(final int chunkX, final int chunkZ) throws IOException; -+ -+ public boolean hasTasks() { -+ return !this.tasks.isEmpty(); -+ } -+ -+ public boolean doesRegionFileNotExist(final int chunkX, final int chunkZ) { -+ return this.getCache().doesRegionFileNotExistNoIO(new ChunkPos(chunkX, chunkZ)); -+ } -+ -+ public T computeForRegionFile(final int chunkX, final int chunkZ, final boolean existingOnly, final Function function) { -+ final RegionFileStorage cache = this.getCache(); -+ final RegionFile regionFile; -+ synchronized (cache) { -+ try { -+ regionFile = cache.getRegionFile(new ChunkPos(chunkX, chunkZ), existingOnly, true); -+ } catch (final IOException ex) { -+ throw new RuntimeException(ex); -+ } -+ } -+ -+ try { -+ return function.apply(regionFile); -+ } finally { -+ if (regionFile != null) { -+ regionFile.fileLock.unlock(); -+ } -+ } -+ } -+ -+ public T computeForRegionFileIfLoaded(final int chunkX, final int chunkZ, final Function function) { -+ final RegionFileStorage cache = this.getCache(); -+ final RegionFile regionFile; -+ -+ synchronized (cache) { -+ regionFile = cache.getRegionFileIfLoaded(new ChunkPos(chunkX, chunkZ)); -+ if (regionFile != null) { -+ regionFile.fileLock.lock(); -+ } -+ } -+ -+ try { -+ return function.apply(regionFile); -+ } finally { -+ if (regionFile != null) { -+ regionFile.fileLock.unlock(); -+ } -+ } -+ } -+ } -+ -+ static final class ChunkDataTask implements Runnable { -+ -+ protected static final CompoundTag NOTHING_TO_WRITE = new CompoundTag(); -+ -+ private static final Logger LOGGER = LogUtils.getClassLogger(); -+ -+ RegionFileIOThread.InProgressRead inProgressRead; -+ volatile CompoundTag inProgressWrite = NOTHING_TO_WRITE; // only needs to be acquire/release -+ -+ boolean failedWrite; -+ -+ final ServerLevel world; -+ final int chunkX; -+ final int chunkZ; -+ final RegionFileIOThread.ChunkDataController taskController; -+ -+ final PrioritisedExecutor.PrioritisedTask prioritisedTask; -+ -+ /* -+ * IO thread will perform reads before writes for a given chunk x and z -+ * -+ * How reads/writes are scheduled: -+ * -+ * If read is scheduled while scheduling write, take no special action and just schedule write -+ * If read is scheduled while scheduling read and no write is scheduled, chain the read task -+ * -+ * -+ * If write is scheduled while scheduling read, use the pending write data and ret immediately (so no read is scheduled) -+ * If write is scheduled while scheduling write (ignore read in progress), overwrite the write in progress data -+ * -+ * This allows the reads and writes to act as if they occur synchronously to the thread scheduling them, however -+ * it fails to properly propagate write failures thanks to writes overwriting each other -+ */ -+ -+ public ChunkDataTask(final ServerLevel world, final int chunkX, final int chunkZ, final RegionFileIOThread.ChunkDataController taskController, -+ final PrioritisedExecutor executor, final PrioritisedExecutor.Priority priority) { -+ this.world = world; -+ this.chunkX = chunkX; -+ this.chunkZ = chunkZ; -+ this.taskController = taskController; -+ this.prioritisedTask = executor.createTask(this, priority); -+ } -+ -+ @Override -+ public String toString() { -+ return "Task for world: '" + this.world.getWorld().getName() + "' at (" + this.chunkX + "," + this.chunkZ + -+ ") type: " + this.taskController.type.name() + ", hash: " + this.hashCode(); -+ } -+ -+ @Override -+ public void run() { -+ final RegionFileIOThread.InProgressRead read = this.inProgressRead; -+ final ChunkCoordinate chunkKey = new ChunkCoordinate(CoordinateUtils.getChunkKey(this.chunkX, this.chunkZ)); -+ -+ if (read != null) { -+ final boolean[] canRead = new boolean[] { true }; -+ -+ if (read.waiters.isEmpty()) { -+ // cancelled read? go to task controller to confirm -+ final ChunkDataTask inMap = this.taskController.tasks.compute(chunkKey, (final ChunkCoordinate keyInMap, final ChunkDataTask valueInMap) -> { -+ if (valueInMap == null) { -+ throw new IllegalStateException("Write completed concurrently, expected this task: " + ChunkDataTask.this.toString() + ", report this!"); -+ } -+ if (valueInMap != ChunkDataTask.this) { -+ throw new IllegalStateException("Chunk task mismatch, expected this task: " + ChunkDataTask.this.toString() + ", got: " + valueInMap.toString() + ", report this!"); -+ } -+ -+ if (!read.waiters.isEmpty()) { // as per usual IntelliJ is unable to figure out that there are concurrent accesses. -+ return valueInMap; -+ } else { -+ canRead[0] = false; -+ } -+ -+ return valueInMap.inProgressWrite == NOTHING_TO_WRITE ? null : valueInMap; -+ }); -+ -+ if (inMap == null) { -+ // read is cancelled - and no write pending, so we're done -+ return; -+ } -+ // if there is a write in progress, we don't actually have to worry about waiters gaining new entries - -+ // the readers will just use the in progress write, so the value in canRead is good to use without -+ // further synchronisation. -+ } -+ -+ if (canRead[0]) { -+ CompoundTag compound = null; -+ Throwable throwable = null; -+ -+ try { -+ compound = this.taskController.readData(this.chunkX, this.chunkZ); -+ } catch (final ThreadDeath thr) { -+ throw thr; -+ } catch (final Throwable thr) { -+ throwable = thr; -+ LOGGER.error("Failed to read chunk data for task: " + this.toString(), thr); -+ } -+ read.complete(this, compound, throwable); -+ } -+ } -+ -+ CompoundTag write = this.inProgressWrite; -+ -+ if (write == NOTHING_TO_WRITE) { -+ final ChunkDataTask inMap = this.taskController.tasks.compute(chunkKey, (final ChunkCoordinate keyInMap, final ChunkDataTask valueInMap) -> { -+ if (valueInMap == null) { -+ throw new IllegalStateException("Write completed concurrently, expected this task: " + ChunkDataTask.this.toString() + ", report this!"); -+ } -+ if (valueInMap != ChunkDataTask.this) { -+ throw new IllegalStateException("Chunk task mismatch, expected this task: " + ChunkDataTask.this.toString() + ", got: " + valueInMap.toString() + ", report this!"); -+ } -+ return valueInMap.inProgressWrite == NOTHING_TO_WRITE ? null : valueInMap; -+ }); -+ -+ if (inMap == null) { -+ return; // set the task value to null, indicating we're done -+ } // else: inProgressWrite changed, so now we have something to write -+ } -+ -+ for (;;) { -+ write = this.inProgressWrite; -+ final CompoundTag dataWritten = write; -+ -+ boolean failedWrite = false; -+ -+ try { -+ this.taskController.writeData(this.chunkX, this.chunkZ, write); -+ } catch (final ThreadDeath thr) { -+ throw thr; -+ } catch (final Throwable thr) { -+ if (thr instanceof RegionFileStorage.RegionFileSizeException) { -+ final int maxSize = RegionFile.MAX_CHUNK_SIZE / (1024 * 1024); -+ LOGGER.error("Chunk at (" + this.chunkX + "," + this.chunkZ + ") in '" + this.world.getWorld().getName() + "' exceeds max size of " + maxSize + "MiB, it has been deleted from disk."); -+ } else { -+ failedWrite = thr instanceof IOException; -+ LOGGER.error("Failed to write chunk data for task: " + this.toString(), thr); -+ } -+ } -+ -+ final boolean finalFailWrite = failedWrite; -+ final boolean[] done = new boolean[] { false }; -+ -+ this.taskController.tasks.compute(chunkKey, (final ChunkCoordinate keyInMap, final ChunkDataTask valueInMap) -> { -+ if (valueInMap == null) { -+ throw new IllegalStateException("Write completed concurrently, expected this task: " + ChunkDataTask.this.toString() + ", report this!"); -+ } -+ if (valueInMap != ChunkDataTask.this) { -+ throw new IllegalStateException("Chunk task mismatch, expected this task: " + ChunkDataTask.this.toString() + ", got: " + valueInMap.toString() + ", report this!"); -+ } -+ if (valueInMap.inProgressWrite == dataWritten) { -+ valueInMap.failedWrite = finalFailWrite; -+ done[0] = true; -+ // keep the data in map if we failed the write so we can try to prevent data loss -+ return finalFailWrite ? valueInMap : null; -+ } -+ // different data than expected, means we need to retry write -+ return valueInMap; -+ }); -+ -+ if (done[0]) { -+ return; -+ } -+ -+ // fetch & write new data -+ continue; -+ } -+ } -+ } -+} -diff --git a/src/main/java/io/papermc/paper/chunk/system/light/LightQueue.java b/src/main/java/io/papermc/paper/chunk/system/light/LightQueue.java -new file mode 100644 -index 0000000000000000000000000000000000000000..69e9944358951bd69ff5e8b3482da1a5e4476209 ---- /dev/null -+++ b/src/main/java/io/papermc/paper/chunk/system/light/LightQueue.java -@@ -0,0 +1,283 @@ -+package io.papermc.paper.chunk.system.light; -+ -+import ca.spottedleaf.concurrentutil.executor.standard.PrioritisedExecutor; -+import ca.spottedleaf.starlight.common.light.BlockStarLightEngine; -+import ca.spottedleaf.starlight.common.light.SkyStarLightEngine; -+import ca.spottedleaf.starlight.common.light.StarLightInterface; -+import io.papermc.paper.util.CoordinateUtils; -+import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap; -+import it.unimi.dsi.fastutil.shorts.ShortCollection; -+import it.unimi.dsi.fastutil.shorts.ShortOpenHashSet; -+import net.minecraft.core.BlockPos; -+import net.minecraft.core.SectionPos; -+import net.minecraft.server.level.ServerLevel; -+import net.minecraft.world.level.ChunkPos; -+import net.minecraft.world.level.chunk.status.ChunkStatus; -+import java.util.ArrayList; -+import java.util.HashSet; -+import java.util.List; -+import java.util.Set; -+import java.util.concurrent.CompletableFuture; -+import java.util.function.BooleanSupplier; -+ -+public final class LightQueue { -+ -+ protected final Long2ObjectOpenHashMap chunkTasks = new Long2ObjectOpenHashMap<>(); -+ protected final StarLightInterface manager; -+ protected final ServerLevel world; -+ -+ public LightQueue(final StarLightInterface manager) { -+ this.manager = manager; -+ this.world = ((ServerLevel)manager.getWorld()); -+ } -+ -+ public void lowerPriority(final int chunkX, final int chunkZ, final PrioritisedExecutor.Priority priority) { -+ final ChunkTasks task; -+ synchronized (this) { -+ task = this.chunkTasks.get(CoordinateUtils.getChunkKey(chunkX, chunkZ)); -+ } -+ if (task != null) { -+ task.lowerPriority(priority); -+ } -+ } -+ -+ public void setPriority(final int chunkX, final int chunkZ, final PrioritisedExecutor.Priority priority) { -+ final ChunkTasks task; -+ synchronized (this) { -+ task = this.chunkTasks.get(CoordinateUtils.getChunkKey(chunkX, chunkZ)); -+ } -+ if (task != null) { -+ task.setPriority(priority); -+ } -+ } -+ -+ public void raisePriority(final int chunkX, final int chunkZ, final PrioritisedExecutor.Priority priority) { -+ final ChunkTasks task; -+ synchronized (this) { -+ task = this.chunkTasks.get(CoordinateUtils.getChunkKey(chunkX, chunkZ)); -+ } -+ if (task != null) { -+ task.raisePriority(priority); -+ } -+ } -+ -+ public PrioritisedExecutor.Priority getPriority(final int chunkX, final int chunkZ) { -+ final ChunkTasks task; -+ synchronized (this) { -+ task = this.chunkTasks.get(CoordinateUtils.getChunkKey(chunkX, chunkZ)); -+ } -+ if (task != null) { -+ return task.getPriority(); -+ } -+ -+ return PrioritisedExecutor.Priority.COMPLETING; -+ } -+ -+ public boolean isEmpty() { -+ synchronized (this) { -+ return this.chunkTasks.isEmpty(); -+ } -+ } -+ -+ public ChunkTasks queueBlockChange(final BlockPos pos) { -+ final ChunkTasks tasks; -+ synchronized (this) { -+ tasks = this.chunkTasks.computeIfAbsent(CoordinateUtils.getChunkKey(pos), (final long keyInMap) -> { -+ return new ChunkTasks(keyInMap, LightQueue.this.manager, LightQueue.this); -+ }); -+ tasks.changedPositions.add(pos.immutable()); -+ } -+ -+ tasks.schedule(); -+ -+ return tasks; -+ } -+ -+ public ChunkTasks queueSectionChange(final SectionPos pos, final boolean newEmptyValue) { -+ final ChunkTasks tasks; -+ synchronized (this) { -+ tasks = this.chunkTasks.computeIfAbsent(CoordinateUtils.getChunkKey(pos), (final long keyInMap) -> { -+ return new ChunkTasks(keyInMap, LightQueue.this.manager, LightQueue.this); -+ }); -+ -+ if (tasks.changedSectionSet == null) { -+ tasks.changedSectionSet = new Boolean[this.manager.maxSection - this.manager.minSection + 1]; -+ } -+ tasks.changedSectionSet[pos.getY() - this.manager.minSection] = Boolean.valueOf(newEmptyValue); -+ } -+ -+ tasks.schedule(); -+ -+ return tasks; -+ } -+ -+ public ChunkTasks queueChunkLightTask(final ChunkPos pos, final BooleanSupplier lightTask, final PrioritisedExecutor.Priority priority) { -+ final ChunkTasks tasks; -+ synchronized (this) { -+ tasks = this.chunkTasks.computeIfAbsent(CoordinateUtils.getChunkKey(pos), (final long keyInMap) -> { -+ return new ChunkTasks(keyInMap, LightQueue.this.manager, LightQueue.this, priority); -+ }); -+ if (tasks.lightTasks == null) { -+ tasks.lightTasks = new ArrayList<>(); -+ } -+ tasks.lightTasks.add(lightTask); -+ } -+ -+ tasks.schedule(); -+ -+ return tasks; -+ } -+ -+ public ChunkTasks queueChunkSkylightEdgeCheck(final SectionPos pos, final ShortCollection sections) { -+ final ChunkTasks tasks; -+ synchronized (this) { -+ tasks = this.chunkTasks.computeIfAbsent(CoordinateUtils.getChunkKey(pos), (final long keyInMap) -> { -+ return new ChunkTasks(keyInMap, LightQueue.this.manager, LightQueue.this); -+ }); -+ -+ ShortOpenHashSet queuedEdges = tasks.queuedEdgeChecksSky; -+ if (queuedEdges == null) { -+ queuedEdges = tasks.queuedEdgeChecksSky = new ShortOpenHashSet(); -+ } -+ queuedEdges.addAll(sections); -+ } -+ -+ tasks.schedule(); -+ -+ return tasks; -+ } -+ -+ public ChunkTasks queueChunkBlocklightEdgeCheck(final SectionPos pos, final ShortCollection sections) { -+ final ChunkTasks tasks; -+ -+ synchronized (this) { -+ tasks = this.chunkTasks.computeIfAbsent(CoordinateUtils.getChunkKey(pos), (final long keyInMap) -> { -+ return new ChunkTasks(keyInMap, LightQueue.this.manager, LightQueue.this); -+ }); -+ -+ ShortOpenHashSet queuedEdges = tasks.queuedEdgeChecksBlock; -+ if (queuedEdges == null) { -+ queuedEdges = tasks.queuedEdgeChecksBlock = new ShortOpenHashSet(); -+ } -+ queuedEdges.addAll(sections); -+ } -+ -+ tasks.schedule(); -+ -+ return tasks; -+ } -+ -+ public void removeChunk(final ChunkPos pos) { -+ final ChunkTasks tasks; -+ synchronized (this) { -+ tasks = this.chunkTasks.remove(CoordinateUtils.getChunkKey(pos)); -+ } -+ if (tasks != null && tasks.cancel()) { -+ tasks.onComplete.complete(null); -+ } -+ } -+ -+ public static final class ChunkTasks implements Runnable { -+ -+ public final CompletableFuture onComplete = new CompletableFuture<>(); -+ public boolean isTicketAdded; -+ public final long chunkCoordinate; -+ -+ private final StarLightInterface lightEngine; -+ private final LightQueue queue; -+ private final PrioritisedExecutor.PrioritisedTask task; -+ private final Set changedPositions = new HashSet<>(); -+ private Boolean[] changedSectionSet; -+ private ShortOpenHashSet queuedEdgeChecksSky; -+ private ShortOpenHashSet queuedEdgeChecksBlock; -+ private List lightTasks; -+ -+ public ChunkTasks(final long chunkCoordinate, final StarLightInterface lightEngine, final LightQueue queue) { -+ this(chunkCoordinate, lightEngine, queue, PrioritisedExecutor.Priority.NORMAL); -+ } -+ -+ public ChunkTasks(final long chunkCoordinate, final StarLightInterface lightEngine, final LightQueue queue, -+ final PrioritisedExecutor.Priority priority) { -+ this.chunkCoordinate = chunkCoordinate; -+ this.lightEngine = lightEngine; -+ this.queue = queue; -+ this.task = queue.world.chunkTaskScheduler.radiusAwareScheduler.createTask( -+ CoordinateUtils.getChunkX(chunkCoordinate), CoordinateUtils.getChunkZ(chunkCoordinate), -+ ChunkStatus.LIGHT.writeRadius, this, priority -+ ); -+ } -+ -+ public void schedule() { -+ this.task.queue(); -+ } -+ -+ public boolean cancel() { -+ return this.task.cancel(); -+ } -+ -+ public PrioritisedExecutor.Priority getPriority() { -+ return this.task.getPriority(); -+ } -+ -+ public void lowerPriority(final PrioritisedExecutor.Priority priority) { -+ this.task.lowerPriority(priority); -+ } -+ -+ public void setPriority(final PrioritisedExecutor.Priority priority) { -+ this.task.setPriority(priority); -+ } -+ -+ public void raisePriority(final PrioritisedExecutor.Priority priority) { -+ this.task.raisePriority(priority); -+ } -+ -+ @Override -+ public void run() { -+ synchronized (this.queue) { -+ this.queue.chunkTasks.remove(this.chunkCoordinate); -+ } -+ -+ boolean litChunk = false; -+ if (this.lightTasks != null) { -+ for (final BooleanSupplier run : this.lightTasks) { -+ if (run.getAsBoolean()) { -+ litChunk = true; -+ break; -+ } -+ } -+ } -+ -+ final SkyStarLightEngine skyEngine = this.lightEngine.getSkyLightEngine(); -+ final BlockStarLightEngine blockEngine = this.lightEngine.getBlockLightEngine(); -+ try { -+ final long coordinate = this.chunkCoordinate; -+ final int chunkX = CoordinateUtils.getChunkX(coordinate); -+ final int chunkZ = CoordinateUtils.getChunkZ(coordinate); -+ -+ final Set positions = this.changedPositions; -+ final Boolean[] sectionChanges = this.changedSectionSet; -+ -+ if (!litChunk) { -+ if (skyEngine != null && (!positions.isEmpty() || sectionChanges != null)) { -+ skyEngine.blocksChangedInChunk(this.lightEngine.getLightAccess(), chunkX, chunkZ, positions, sectionChanges); -+ } -+ if (blockEngine != null && (!positions.isEmpty() || sectionChanges != null)) { -+ blockEngine.blocksChangedInChunk(this.lightEngine.getLightAccess(), chunkX, chunkZ, positions, sectionChanges); -+ } -+ -+ if (skyEngine != null && this.queuedEdgeChecksSky != null) { -+ skyEngine.checkChunkEdges(this.lightEngine.getLightAccess(), chunkX, chunkZ, this.queuedEdgeChecksSky); -+ } -+ if (blockEngine != null && this.queuedEdgeChecksBlock != null) { -+ blockEngine.checkChunkEdges(this.lightEngine.getLightAccess(), chunkX, chunkZ, this.queuedEdgeChecksBlock); -+ } -+ } -+ -+ this.onComplete.complete(null); -+ } finally { -+ this.lightEngine.releaseSkyLightEngine(skyEngine); -+ this.lightEngine.releaseBlockLightEngine(blockEngine); -+ } -+ } -+ } -+} -diff --git a/src/main/java/io/papermc/paper/chunk/system/poi/PoiChunk.java b/src/main/java/io/papermc/paper/chunk/system/poi/PoiChunk.java -new file mode 100644 -index 0000000000000000000000000000000000000000..d72041aa814ff179e6e29a45dcd359a91d426d47 ---- /dev/null -+++ b/src/main/java/io/papermc/paper/chunk/system/poi/PoiChunk.java -@@ -0,0 +1,213 @@ -+package io.papermc.paper.chunk.system.poi; -+ -+import com.mojang.logging.LogUtils; -+import com.mojang.serialization.Codec; -+import com.mojang.serialization.DataResult; -+import io.papermc.paper.util.CoordinateUtils; -+import io.papermc.paper.util.TickThread; -+import io.papermc.paper.util.WorldUtil; -+import net.minecraft.SharedConstants; -+import net.minecraft.nbt.CompoundTag; -+import net.minecraft.nbt.NbtOps; -+import net.minecraft.nbt.Tag; -+import net.minecraft.resources.RegistryOps; -+import net.minecraft.server.level.ServerLevel; -+import net.minecraft.world.entity.ai.village.poi.PoiManager; -+import net.minecraft.world.entity.ai.village.poi.PoiSection; -+import org.slf4j.Logger; -+ -+import java.util.Optional; -+ -+public final class PoiChunk { -+ -+ private static final Logger LOGGER = LogUtils.getClassLogger(); -+ -+ public final ServerLevel world; -+ public final int chunkX; -+ public final int chunkZ; -+ public final int minSection; -+ public final int maxSection; -+ -+ protected final PoiSection[] sections; -+ -+ private boolean isDirty; -+ private boolean loaded; -+ -+ public PoiChunk(final ServerLevel world, final int chunkX, final int chunkZ, final int minSection, final int maxSection) { -+ this(world, chunkX, chunkZ, minSection, maxSection, new PoiSection[maxSection - minSection + 1]); -+ } -+ -+ public PoiChunk(final ServerLevel world, final int chunkX, final int chunkZ, final int minSection, final int maxSection, final PoiSection[] sections) { -+ this.world = world; -+ this.chunkX = chunkX; -+ this.chunkZ = chunkZ; -+ this.minSection = minSection; -+ this.maxSection = maxSection; -+ this.sections = sections; -+ if (this.sections.length != (maxSection - minSection + 1)) { -+ throw new IllegalStateException("Incorrect length used, expected " + (maxSection - minSection + 1) + ", got " + this.sections.length); -+ } -+ } -+ -+ public void load() { -+ TickThread.ensureTickThread(this.world, this.chunkX, this.chunkZ, "Loading in poi chunk off-main"); -+ if (this.loaded) { -+ return; -+ } -+ this.loaded = true; -+ this.world.chunkSource.getPoiManager().loadInPoiChunk(this); -+ } -+ -+ public boolean isLoaded() { -+ return this.loaded; -+ } -+ -+ public boolean isEmpty() { -+ for (final PoiSection section : this.sections) { -+ if (section != null && !section.isEmpty()) { -+ return false; -+ } -+ } -+ -+ return true; -+ } -+ -+ public PoiSection getOrCreateSection(final int chunkY) { -+ if (chunkY >= this.minSection && chunkY <= this.maxSection) { -+ final int idx = chunkY - this.minSection; -+ final PoiSection ret = this.sections[idx]; -+ if (ret != null) { -+ return ret; -+ } -+ -+ final PoiManager poiManager = this.world.getPoiManager(); -+ final long key = CoordinateUtils.getChunkSectionKey(this.chunkX, chunkY, this.chunkZ); -+ -+ return this.sections[idx] = new PoiSection(() -> { -+ poiManager.setDirty(key); -+ }); -+ } -+ throw new IllegalArgumentException("chunkY is out of bounds, chunkY: " + chunkY + " outside [" + this.minSection + "," + this.maxSection + "]"); -+ } -+ -+ public PoiSection getSection(final int chunkY) { -+ if (chunkY >= this.minSection && chunkY <= this.maxSection) { -+ return this.sections[chunkY - this.minSection]; -+ } -+ return null; -+ } -+ -+ public Optional getSectionForVanilla(final int chunkY) { -+ if (chunkY >= this.minSection && chunkY <= this.maxSection) { -+ final PoiSection ret = this.sections[chunkY - this.minSection]; -+ return ret == null ? Optional.empty() : ret.noAllocateOptional; -+ } -+ return Optional.empty(); -+ } -+ -+ public boolean isDirty() { -+ return this.isDirty; -+ } -+ -+ public void setDirty(final boolean dirty) { -+ this.isDirty = dirty; -+ } -+ -+ // returns null if empty -+ public CompoundTag save() { -+ final RegistryOps registryOps = RegistryOps.create(NbtOps.INSTANCE, world.getPoiManager().registryAccess); -+ -+ final CompoundTag ret = new CompoundTag(); -+ final CompoundTag sections = new CompoundTag(); -+ ret.put("Sections", sections); -+ -+ ret.putInt("DataVersion", SharedConstants.getCurrentVersion().getDataVersion().getVersion()); -+ -+ final ServerLevel world = this.world; -+ final PoiManager poiManager = world.getPoiManager(); -+ final int chunkX = this.chunkX; -+ final int chunkZ = this.chunkZ; -+ -+ for (int sectionY = this.minSection; sectionY <= this.maxSection; ++sectionY) { -+ final PoiSection chunk = this.sections[sectionY - this.minSection]; -+ if (chunk == null || chunk.isEmpty()) { -+ continue; -+ } -+ -+ final long key = CoordinateUtils.getChunkSectionKey(chunkX, sectionY, chunkZ); -+ // codecs are honestly such a fucking disaster. What the fuck is this trash? -+ final Codec codec = PoiSection.codec(() -> { -+ poiManager.setDirty(key); -+ }); -+ -+ final DataResult serializedResult = codec.encodeStart(registryOps, chunk); -+ final int finalSectionY = sectionY; -+ final Tag serialized = serializedResult.resultOrPartial((final String description) -> { -+ LOGGER.error("Failed to serialize poi chunk for world: " + world.getWorld().getName() + ", chunk: (" + chunkX + "," + finalSectionY + "," + chunkZ + "); description: " + description); -+ }).orElse(null); -+ if (serialized == null) { -+ // failed, should be logged from the resultOrPartial -+ continue; -+ } -+ -+ sections.put(Integer.toString(sectionY), serialized); -+ } -+ -+ return sections.isEmpty() ? null : ret; -+ } -+ -+ public static PoiChunk empty(final ServerLevel world, final int chunkX, final int chunkZ) { -+ final PoiChunk ret = new PoiChunk(world, chunkX, chunkZ, WorldUtil.getMinSection(world), WorldUtil.getMaxSection(world)); -+ ret.loaded = true; -+ return ret; -+ } -+ -+ public static PoiChunk parse(final ServerLevel world, final int chunkX, final int chunkZ, final CompoundTag data) { -+ final PoiChunk ret = empty(world, chunkX, chunkZ); -+ -+ final RegistryOps registryOps = RegistryOps.create(NbtOps.INSTANCE, world.getPoiManager().registryAccess); -+ -+ final CompoundTag sections = data.getCompound("Sections"); -+ -+ if (sections.isEmpty()) { -+ // nothing to parse -+ return ret; -+ } -+ -+ final PoiManager poiManager = world.getPoiManager(); -+ -+ boolean readAnything = false; -+ -+ for (int sectionY = ret.minSection; sectionY <= ret.maxSection; ++sectionY) { -+ final String key = Integer.toString(sectionY); -+ if (!sections.contains(key)) { -+ continue; -+ } -+ -+ final long coordinateKey = CoordinateUtils.getChunkSectionKey(chunkX, sectionY, chunkZ); -+ // codecs are honestly such a fucking disaster. What the fuck is this trash? -+ final Codec codec = PoiSection.codec(() -> { -+ poiManager.setDirty(coordinateKey); -+ }); -+ -+ final CompoundTag section = sections.getCompound(key); -+ final DataResult deserializeResult = codec.parse(registryOps, section); -+ final int finalSectionY = sectionY; -+ final PoiSection deserialized = deserializeResult.resultOrPartial((final String description) -> { -+ LOGGER.error("Failed to deserialize poi chunk for world: " + world.getWorld().getName() + ", chunk: (" + chunkX + "," + finalSectionY + "," + chunkZ + "); description: " + description); -+ }).orElse(null); -+ -+ if (deserialized == null || deserialized.isEmpty()) { -+ // completely empty, no point in storing this -+ continue; -+ } -+ -+ readAnything = true; -+ ret.sections[sectionY - ret.minSection] = deserialized; -+ } -+ -+ ret.loaded = !readAnything; // Set loaded to false if we read anything to ensure proper callbacks to PoiManager are made on #load -+ -+ return ret; -+ } -+} -diff --git a/src/main/java/io/papermc/paper/chunk/system/scheduling/ChunkFullTask.java b/src/main/java/io/papermc/paper/chunk/system/scheduling/ChunkFullTask.java -new file mode 100644 -index 0000000000000000000000000000000000000000..c307b084f59f7bb94dc02f25bbcd3e01e01d2306 ---- /dev/null -+++ b/src/main/java/io/papermc/paper/chunk/system/scheduling/ChunkFullTask.java -@@ -0,0 +1,131 @@ -+package io.papermc.paper.chunk.system.scheduling; -+ -+import ca.spottedleaf.concurrentutil.executor.standard.PrioritisedExecutor; -+import ca.spottedleaf.concurrentutil.util.ConcurrentUtil; -+import com.mojang.logging.LogUtils; -+import io.papermc.paper.chunk.system.poi.PoiChunk; -+import net.minecraft.server.level.ChunkMap; -+import net.minecraft.server.level.ServerLevel; -+import net.minecraft.world.level.chunk.ChunkAccess; -+import net.minecraft.world.level.chunk.status.ChunkStatus; -+import net.minecraft.world.level.chunk.ImposterProtoChunk; -+import net.minecraft.world.level.chunk.LevelChunk; -+import net.minecraft.world.level.chunk.ProtoChunk; -+import org.slf4j.Logger; -+import java.lang.invoke.VarHandle; -+ -+public final class ChunkFullTask extends ChunkProgressionTask implements Runnable { -+ -+ private static final Logger LOGGER = LogUtils.getClassLogger(); -+ -+ protected final NewChunkHolder chunkHolder; -+ protected final ChunkAccess fromChunk; -+ protected final PrioritisedExecutor.PrioritisedTask convertToFullTask; -+ -+ public ChunkFullTask(final ChunkTaskScheduler scheduler, final ServerLevel world, final int chunkX, final int chunkZ, -+ final NewChunkHolder chunkHolder, final ChunkAccess fromChunk, final PrioritisedExecutor.Priority priority) { -+ super(scheduler, world, chunkX, chunkZ); -+ this.chunkHolder = chunkHolder; -+ this.fromChunk = fromChunk; -+ this.convertToFullTask = scheduler.createChunkTask(chunkX, chunkZ, this, priority); -+ } -+ -+ @Override -+ public ChunkStatus getTargetStatus() { -+ return ChunkStatus.FULL; -+ } -+ -+ @Override -+ public void run() { -+ // See Vanilla protoChunkToFullChunk for what this function should be doing -+ final LevelChunk chunk; -+ try { -+ // moved from the load from nbt stage into here -+ final PoiChunk poiChunk = this.chunkHolder.getPoiChunk(); -+ if (poiChunk == null) { -+ LOGGER.error("Expected poi chunk to be loaded with chunk for task " + this.toString()); -+ } else { -+ poiChunk.load(); -+ this.world.getPoiManager().checkConsistency(this.fromChunk); -+ } -+ -+ if (this.fromChunk instanceof ImposterProtoChunk wrappedFull) { -+ chunk = wrappedFull.getWrapped(); -+ } else { -+ final ServerLevel world = this.world; -+ final ProtoChunk protoChunk = (ProtoChunk)this.fromChunk; -+ chunk = new LevelChunk(this.world, protoChunk, (final LevelChunk unused) -> { -+ ChunkMap.postLoadProtoChunk(world, protoChunk.getEntities(), protoChunk.getPos()); // Paper - rewrite chunk system -+ }); -+ } -+ -+ chunk.setChunkHolder(this.scheduler.chunkHolderManager.getChunkHolder(this.chunkX, this.chunkZ)); // replaces setFullStatus -+ chunk.runPostLoad(); -+ // Unlike Vanilla, we load the entity chunk here, as we load the NBT in empty status (unlike Vanilla) -+ // This brings entity addition back in line with older versions of the game -+ // Since we load the NBT in the empty status, this will never block for I/O -+ this.world.chunkTaskScheduler.chunkHolderManager.getOrCreateEntityChunk(this.chunkX, this.chunkZ, false); -+ -+ // we don't need the entitiesInLevel trash, this system doesn't double run callbacks -+ chunk.setLoaded(true); -+ chunk.registerAllBlockEntitiesAfterLevelLoad(); -+ chunk.registerTickContainerInLevel(this.world); -+ } catch (final Throwable throwable) { -+ this.complete(null, throwable); -+ return; -+ } -+ this.complete(chunk, null); -+ } -+ -+ protected volatile boolean scheduled; -+ protected static final VarHandle SCHEDULED_HANDLE = ConcurrentUtil.getVarHandle(ChunkFullTask.class, "scheduled", boolean.class); -+ -+ @Override -+ public boolean isScheduled() { -+ return this.scheduled; -+ } -+ -+ @Override -+ public void schedule() { -+ if ((boolean)SCHEDULED_HANDLE.getAndSet((ChunkFullTask)this, true)) { -+ throw new IllegalStateException("Cannot double call schedule()"); -+ } -+ this.convertToFullTask.queue(); -+ } -+ -+ @Override -+ public void cancel() { -+ if (this.convertToFullTask.cancel()) { -+ this.complete(null, null); -+ } -+ } -+ -+ @Override -+ public PrioritisedExecutor.Priority getPriority() { -+ return this.convertToFullTask.getPriority(); -+ } -+ -+ @Override -+ public void lowerPriority(final PrioritisedExecutor.Priority priority) { -+ if (!PrioritisedExecutor.Priority.isValidPriority(priority)) { -+ throw new IllegalArgumentException("Invalid priority " + priority); -+ } -+ this.convertToFullTask.lowerPriority(priority); -+ } -+ -+ @Override -+ public void setPriority(final PrioritisedExecutor.Priority priority) { -+ if (!PrioritisedExecutor.Priority.isValidPriority(priority)) { -+ throw new IllegalArgumentException("Invalid priority " + priority); -+ } -+ this.convertToFullTask.setPriority(priority); -+ } -+ -+ @Override -+ public void raisePriority(final PrioritisedExecutor.Priority priority) { -+ if (!PrioritisedExecutor.Priority.isValidPriority(priority)) { -+ throw new IllegalArgumentException("Invalid priority " + priority); -+ } -+ this.convertToFullTask.raisePriority(priority); -+ } -+} -diff --git a/src/main/java/io/papermc/paper/chunk/system/scheduling/ChunkHolderManager.java b/src/main/java/io/papermc/paper/chunk/system/scheduling/ChunkHolderManager.java -new file mode 100644 -index 0000000000000000000000000000000000000000..5b446e6ac151f99f64f0c442d0b40b5e251bc4c4 ---- /dev/null -+++ b/src/main/java/io/papermc/paper/chunk/system/scheduling/ChunkHolderManager.java -@@ -0,0 +1,1500 @@ -+package io.papermc.paper.chunk.system.scheduling; -+import ca.spottedleaf.concurrentutil.executor.standard.PrioritisedExecutor; -+import ca.spottedleaf.concurrentutil.lock.ReentrantAreaLock; -+import ca.spottedleaf.concurrentutil.map.SWMRLong2ObjectHashTable; -+import com.google.common.collect.ImmutableList; -+import com.google.gson.JsonArray; -+import com.google.gson.JsonObject; -+import com.mojang.logging.LogUtils; -+import io.papermc.paper.chunk.system.RegionizedPlayerChunkLoader; -+import io.papermc.paper.chunk.system.io.RegionFileIOThread; -+import io.papermc.paper.chunk.system.poi.PoiChunk; -+import io.papermc.paper.threadedregions.TickRegions; -+import io.papermc.paper.util.CoordinateUtils; -+import io.papermc.paper.util.TickThread; -+import io.papermc.paper.world.ChunkEntitySlices; -+import it.unimi.dsi.fastutil.longs.Long2ByteLinkedOpenHashMap; -+import it.unimi.dsi.fastutil.longs.Long2ByteMap; -+import it.unimi.dsi.fastutil.longs.Long2IntLinkedOpenHashMap; -+import it.unimi.dsi.fastutil.longs.Long2IntMap; -+import it.unimi.dsi.fastutil.longs.Long2IntOpenHashMap; -+import it.unimi.dsi.fastutil.longs.Long2ObjectMap; -+import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap; -+import it.unimi.dsi.fastutil.longs.LongArrayList; -+import it.unimi.dsi.fastutil.longs.LongIterator; -+import it.unimi.dsi.fastutil.objects.ObjectRBTreeSet; -+import net.minecraft.nbt.CompoundTag; -+import io.papermc.paper.chunk.system.ChunkSystem; -+import net.minecraft.server.MinecraftServer; -+import net.minecraft.server.level.ChunkHolder; -+import net.minecraft.server.level.ChunkLevel; -+import net.minecraft.server.level.FullChunkStatus; -+import net.minecraft.server.level.ServerLevel; -+import net.minecraft.server.level.Ticket; -+import net.minecraft.server.level.TicketType; -+import net.minecraft.util.SortedArraySet; -+import net.minecraft.util.Unit; -+import net.minecraft.world.level.ChunkPos; -+import org.bukkit.plugin.Plugin; -+import org.slf4j.Logger; -+import java.io.IOException; -+import java.text.DecimalFormat; -+import java.util.ArrayDeque; -+import java.util.ArrayList; -+import java.util.Collection; -+import java.util.Collections; -+import java.util.Iterator; -+import java.util.List; -+import java.util.concurrent.ConcurrentHashMap; -+import java.util.concurrent.TimeUnit; -+import java.util.concurrent.atomic.AtomicBoolean; -+import java.util.concurrent.atomic.AtomicLong; -+import java.util.concurrent.atomic.AtomicReference; -+import java.util.concurrent.locks.LockSupport; -+import java.util.function.Predicate; -+ -+public final class ChunkHolderManager { -+ -+ private static final Logger LOGGER = LogUtils.getClassLogger(); -+ -+ public static final int FULL_LOADED_TICKET_LEVEL = 33; -+ public static final int BLOCK_TICKING_TICKET_LEVEL = 32; -+ public static final int ENTITY_TICKING_TICKET_LEVEL = 31; -+ public static final int MAX_TICKET_LEVEL = ChunkLevel.MAX_LEVEL; // inclusive -+ -+ private static final long NO_TIMEOUT_MARKER = Long.MIN_VALUE; -+ private static final long PROBE_MARKER = Long.MIN_VALUE + 1; -+ public final ReentrantAreaLock ticketLockArea; -+ -+ private final ConcurrentHashMap>> tickets = new java.util.concurrent.ConcurrentHashMap<>(); -+ private final ConcurrentHashMap sectionToChunkToExpireCount = new java.util.concurrent.ConcurrentHashMap<>(); -+ final ChunkQueue unloadQueue; -+ -+ public boolean processTicketUpdates(final int posX, final int posZ) { -+ final int ticketShift = ThreadedTicketLevelPropagator.SECTION_SHIFT; -+ final int ticketMask = (1 << ticketShift) - 1; -+ final List scheduledTasks = new ArrayList<>(); -+ final List changedFullStatus = new ArrayList<>(); -+ final boolean ret; -+ final ReentrantAreaLock.Node ticketLock = this.ticketLockArea.lock( -+ ((posX >> ticketShift) - 1) << ticketShift, -+ ((posZ >> ticketShift) - 1) << ticketShift, -+ (((posX >> ticketShift) + 1) << ticketShift) | ticketMask, -+ (((posZ >> ticketShift) + 1) << ticketShift) | ticketMask -+ ); -+ try { -+ ret = this.processTicketUpdatesNoLock(posX >> ticketShift, posZ >> ticketShift, scheduledTasks, changedFullStatus); -+ } finally { -+ this.ticketLockArea.unlock(ticketLock); -+ } -+ -+ this.addChangedStatuses(changedFullStatus); -+ -+ for (int i = 0, len = scheduledTasks.size(); i < len; ++i) { -+ scheduledTasks.get(i).schedule(); -+ } -+ -+ return ret; -+ } -+ -+ private boolean processTicketUpdatesNoLock(final int sectionX, final int sectionZ, final List scheduledTasks, -+ final List changedFullStatus) { -+ return this.ticketLevelPropagator.performUpdate( -+ sectionX, sectionZ, this.taskScheduler.schedulingLockArea, scheduledTasks, changedFullStatus -+ ); -+ } -+ -+ private final SWMRLong2ObjectHashTable chunkHolders = new SWMRLong2ObjectHashTable<>(16384, 0.25f); -+ // what a disaster of a name -+ // this is a map of removal tick to a map of chunks and the number of tickets a chunk has that are to expire that tick -+ private final Long2ObjectOpenHashMap removeTickToChunkExpireTicketCount = new Long2ObjectOpenHashMap<>(); -+ private final ServerLevel world; -+ private final ChunkTaskScheduler taskScheduler; -+ private long currentTick; -+ -+ private final ArrayDeque pendingFullLoadUpdate = new ArrayDeque<>(); -+ private final ObjectRBTreeSet autoSaveQueue = new ObjectRBTreeSet<>((final NewChunkHolder c1, final NewChunkHolder c2) -> { -+ if (c1 == c2) { -+ return 0; -+ } -+ -+ final int saveTickCompare = Long.compare(c1.lastAutoSave, c2.lastAutoSave); -+ -+ if (saveTickCompare != 0) { -+ return saveTickCompare; -+ } -+ -+ final long coord1 = CoordinateUtils.getChunkKey(c1.chunkX, c1.chunkZ); -+ final long coord2 = CoordinateUtils.getChunkKey(c2.chunkX, c2.chunkZ); -+ -+ if (coord1 == coord2) { -+ throw new IllegalStateException("Duplicate chunkholder in auto save queue"); -+ } -+ -+ return Long.compare(coord1, coord2); -+ }); -+ -+ public ChunkHolderManager(final ServerLevel world, final ChunkTaskScheduler taskScheduler) { -+ this.world = world; -+ this.taskScheduler = taskScheduler; -+ this.ticketLockArea = new ReentrantAreaLock(taskScheduler.getChunkSystemLockShift()); -+ this.unloadQueue = new ChunkQueue(world.getRegionChunkShift()); -+ } -+ -+ private final AtomicLong statusUpgradeId = new AtomicLong(); -+ -+ long getNextStatusUpgradeId() { -+ return this.statusUpgradeId.incrementAndGet(); -+ } -+ -+ public List getOldChunkHolders() { -+ final List holders = this.getChunkHolders(); -+ final List ret = new ArrayList<>(holders.size()); -+ for (final NewChunkHolder holder : holders) { -+ ret.add(holder.vanillaChunkHolder); -+ } -+ return ret; -+ } -+ -+ public List getChunkHolders() { -+ final List ret = new ArrayList<>(this.chunkHolders.size()); -+ this.chunkHolders.forEachValue(ret::add); -+ return ret; -+ } -+ -+ public int size() { -+ return this.chunkHolders.size(); -+ } -+ -+ public void close(final boolean save, final boolean halt) { -+ TickThread.ensureTickThread("Closing world off-main"); -+ if (halt) { -+ LOGGER.info("Waiting 60s for chunk system to halt for world '" + this.world.getWorld().getName() + "'"); -+ if (!this.taskScheduler.halt(true, TimeUnit.SECONDS.toNanos(60L))) { -+ LOGGER.warn("Failed to halt world generation/loading tasks for world '" + this.world.getWorld().getName() + "'"); -+ } else { -+ LOGGER.info("Halted chunk system for world '" + this.world.getWorld().getName() + "'"); -+ } -+ } -+ -+ if (save) { -+ this.saveAllChunks(true, true, true); -+ } -+ -+ if (this.world.chunkDataControllerNew.hasTasks() || this.world.entityDataControllerNew.hasTasks() || this.world.poiDataControllerNew.hasTasks()) { -+ RegionFileIOThread.flush(); -+ } -+ -+ // kill regionfile cache -+ try { -+ this.world.chunkDataControllerNew.getCache().close(); -+ } catch (final IOException ex) { -+ LOGGER.error("Failed to close chunk regionfile cache for world '" + this.world.getWorld().getName() + "'", ex); -+ } -+ try { -+ this.world.entityDataControllerNew.getCache().close(); -+ } catch (final IOException ex) { -+ LOGGER.error("Failed to close entity regionfile cache for world '" + this.world.getWorld().getName() + "'", ex); -+ } -+ try { -+ this.world.poiDataControllerNew.getCache().close(); -+ } catch (final IOException ex) { -+ LOGGER.error("Failed to close poi regionfile cache for world '" + this.world.getWorld().getName() + "'", ex); -+ } -+ } -+ -+ void ensureInAutosave(final NewChunkHolder holder) { -+ if (!this.autoSaveQueue.contains(holder)) { -+ holder.lastAutoSave = MinecraftServer.currentTick; -+ this.autoSaveQueue.add(holder); -+ } -+ } -+ -+ public void autoSave() { -+ final List reschedule = new ArrayList<>(); -+ final long currentTick = MinecraftServer.currentTickLong; -+ final long maxSaveTime = currentTick - this.world.paperConfig().chunks.autoSaveInterval.value(); -+ for (int autoSaved = 0; autoSaved < this.world.paperConfig().chunks.maxAutoSaveChunksPerTick && !this.autoSaveQueue.isEmpty();) { -+ final NewChunkHolder holder = this.autoSaveQueue.first(); -+ -+ if (holder.lastAutoSave > maxSaveTime) { -+ break; -+ } -+ -+ this.autoSaveQueue.remove(holder); -+ -+ holder.lastAutoSave = currentTick; -+ if (holder.save(false, false) != null) { -+ ++autoSaved; -+ } -+ -+ if (holder.getChunkStatus().isOrAfter(FullChunkStatus.FULL)) { -+ reschedule.add(holder); -+ } -+ } -+ -+ for (final NewChunkHolder holder : reschedule) { -+ if (holder.getChunkStatus().isOrAfter(FullChunkStatus.FULL)) { -+ this.autoSaveQueue.add(holder); -+ } -+ } -+ } -+ -+ public void saveAllChunks(final boolean flush, final boolean shutdown, final boolean logProgress) { -+ final List holders = this.getChunkHolders(); -+ -+ if (logProgress) { -+ LOGGER.info("Saving all chunkholders for world '" + this.world.getWorld().getName() + "'"); -+ } -+ -+ final DecimalFormat format = new DecimalFormat("#0.00"); -+ -+ int saved = 0; -+ -+ long start = System.nanoTime(); -+ long lastLog = start; -+ boolean needsFlush = false; -+ final int flushInterval = 50; -+ -+ int savedChunk = 0; -+ int savedEntity = 0; -+ int savedPoi = 0; -+ -+ for (int i = 0, len = holders.size(); i < len; ++i) { -+ final NewChunkHolder holder = holders.get(i); -+ try { -+ final NewChunkHolder.SaveStat saveStat = holder.save(shutdown, false); -+ if (saveStat != null) { -+ ++saved; -+ needsFlush = flush; -+ if (saveStat.savedChunk()) { -+ ++savedChunk; -+ } -+ if (saveStat.savedEntityChunk()) { -+ ++savedEntity; -+ } -+ if (saveStat.savedPoiChunk()) { -+ ++savedPoi; -+ } -+ } -+ } catch (final ThreadDeath thr) { -+ throw thr; -+ } catch (final Throwable thr) { -+ LOGGER.error("Failed to save chunk (" + holder.chunkX + "," + holder.chunkZ + ") in world '" + this.world.getWorld().getName() + "'", thr); -+ } -+ if (needsFlush && (saved % flushInterval) == 0) { -+ needsFlush = false; -+ RegionFileIOThread.partialFlush(flushInterval / 2); -+ } -+ if (logProgress) { -+ final long currTime = System.nanoTime(); -+ if ((currTime - lastLog) > TimeUnit.SECONDS.toNanos(10L)) { -+ lastLog = currTime; -+ LOGGER.info("Saved " + saved + " chunks (" + format.format((double)(i+1)/(double)len * 100.0) + "%) in world '" + this.world.getWorld().getName() + "'"); -+ } -+ } -+ } -+ if (flush) { -+ RegionFileIOThread.flush(); -+ if (this.world.paperConfig().chunks.flushRegionsOnSave) { -+ try { -+ this.world.chunkSource.chunkMap.regionFileCache.flush(); -+ } catch (IOException ex) { -+ LOGGER.error("Exception when flushing regions in world {}", this.world.getWorld().getName(), ex); -+ } -+ } -+ } -+ if (logProgress) { -+ LOGGER.info("Saved " + savedChunk + " block chunks, " + savedEntity + " entity chunks, " + savedPoi + " poi chunks in world '" + this.world.getWorld().getName() + "' in " + format.format(1.0E-9 * (System.nanoTime() - start)) + "s"); -+ } -+ } -+ -+ protected final ThreadedTicketLevelPropagator ticketLevelPropagator = new ThreadedTicketLevelPropagator() { -+ @Override -+ protected void processLevelUpdates(final Long2ByteLinkedOpenHashMap updates) { -+ // first the necessary chunkholders must be created, so just update the ticket levels -+ for (final Iterator iterator = updates.long2ByteEntrySet().fastIterator(); iterator.hasNext();) { -+ final Long2ByteMap.Entry entry = iterator.next(); -+ final long key = entry.getLongKey(); -+ final int newLevel = convertBetweenTicketLevels((int)entry.getByteValue()); -+ -+ NewChunkHolder current = ChunkHolderManager.this.chunkHolders.get(key); -+ if (current == null && newLevel > MAX_TICKET_LEVEL) { -+ // not loaded and it shouldn't be loaded! -+ iterator.remove(); -+ continue; -+ } -+ -+ final int currentLevel = current == null ? MAX_TICKET_LEVEL + 1 : current.getCurrentTicketLevel(); -+ if (currentLevel == newLevel) { -+ // nothing to do -+ iterator.remove(); -+ continue; -+ } -+ -+ if (current == null) { -+ // must create -+ current = ChunkHolderManager.this.createChunkHolder(key); -+ synchronized (ChunkHolderManager.this.chunkHolders) { -+ ChunkHolderManager.this.chunkHolders.put(key, current); -+ } -+ current.updateTicketLevel(newLevel); -+ } else { -+ current.updateTicketLevel(newLevel); -+ } -+ } -+ } -+ -+ @Override -+ protected void processSchedulingUpdates(final Long2ByteLinkedOpenHashMap updates, final List scheduledTasks, -+ final List changedFullStatus) { -+ final List prev = CURRENT_TICKET_UPDATE_SCHEDULING.get(); -+ CURRENT_TICKET_UPDATE_SCHEDULING.set(scheduledTasks); -+ try { -+ for (final LongIterator iterator = updates.keySet().iterator(); iterator.hasNext();) { -+ final long key = iterator.nextLong(); -+ final NewChunkHolder current = ChunkHolderManager.this.chunkHolders.get(key); -+ -+ if (current == null) { -+ throw new IllegalStateException("Expected chunk holder to be created"); -+ } -+ -+ current.processTicketLevelUpdate(scheduledTasks, changedFullStatus); -+ } -+ } finally { -+ CURRENT_TICKET_UPDATE_SCHEDULING.set(prev); -+ } -+ } -+ }; -+ // function for converting between ticket levels and propagator levels and vice versa -+ // the problem is the ticket level propagator will propagate from a set source down to zero, whereas mojang expects -+ // levels to propagate from a set value up to a maximum value. so we need to convert the levels we put into the propagator -+ // and the levels we get out of the propagator -+ -+ public static int convertBetweenTicketLevels(final int level) { -+ return ChunkLevel.MAX_LEVEL - level + 1; -+ } -+ -+ public String getTicketDebugString(final long coordinate) { -+ final ReentrantAreaLock.Node ticketLock = this.ticketLockArea.lock(CoordinateUtils.getChunkX(coordinate), CoordinateUtils.getChunkZ(coordinate)); -+ try { -+ final SortedArraySet> tickets = this.tickets.get(new RegionFileIOThread.ChunkCoordinate(coordinate)); -+ -+ return tickets != null ? tickets.first().toString() : "no_ticket"; -+ } finally { -+ if (ticketLock != null) { -+ this.ticketLockArea.unlock(ticketLock); -+ } -+ } -+ } -+ -+ public Long2ObjectOpenHashMap>> getTicketsCopy() { -+ final Long2ObjectOpenHashMap>> ret = new Long2ObjectOpenHashMap<>(); -+ final Long2ObjectOpenHashMap> sections = new Long2ObjectOpenHashMap(); -+ final int sectionShift = this.taskScheduler.getChunkSystemLockShift(); -+ for (final RegionFileIOThread.ChunkCoordinate coord : this.tickets.keySet()) { -+ sections.computeIfAbsent( -+ CoordinateUtils.getChunkKey( -+ CoordinateUtils.getChunkX(coord.key) >> sectionShift, -+ CoordinateUtils.getChunkZ(coord.key) >> sectionShift -+ ), -+ (final long keyInMap) -> { -+ return new ArrayList<>(); -+ } -+ ).add(coord); -+ } -+ -+ for (final Iterator>> iterator = sections.long2ObjectEntrySet().fastIterator(); -+ iterator.hasNext();) { -+ final Long2ObjectMap.Entry> entry = iterator.next(); -+ final long sectionKey = entry.getLongKey(); -+ final List coordinates = entry.getValue(); -+ -+ final ReentrantAreaLock.Node ticketLock = this.ticketLockArea.lock( -+ CoordinateUtils.getChunkX(sectionKey) << sectionShift, -+ CoordinateUtils.getChunkZ(sectionKey) << sectionShift -+ ); -+ try { -+ for (final RegionFileIOThread.ChunkCoordinate coord : coordinates) { -+ final SortedArraySet> tickets = this.tickets.get(coord); -+ if (tickets == null) { -+ // removed before we acquired lock -+ continue; -+ } -+ ret.put(coord.key, new SortedArraySet<>(tickets)); -+ } -+ } finally { -+ this.ticketLockArea.unlock(ticketLock); -+ } -+ } -+ -+ return ret; -+ } -+ -+ public Collection getPluginChunkTickets(int x, int z) { -+ ImmutableList.Builder ret; -+ final ReentrantAreaLock.Node ticketLock = this.ticketLockArea.lock(x, z); -+ try { -+ final long coordinate = CoordinateUtils.getChunkKey(x, z); -+ final SortedArraySet> tickets = this.tickets.get(new RegionFileIOThread.ChunkCoordinate(coordinate)); -+ -+ if (tickets == null) { -+ return Collections.emptyList(); -+ } -+ -+ ret = ImmutableList.builder(); -+ for (Ticket ticket : tickets) { -+ if (ticket.getType() == TicketType.PLUGIN_TICKET) { -+ ret.add((Plugin)ticket.key); -+ } -+ } -+ } finally { -+ this.ticketLockArea.unlock(ticketLock); -+ } -+ -+ return ret.build(); -+ } -+ -+ protected final void updateTicketLevel(final long coordinate, final int ticketLevel) { -+ if (ticketLevel > ChunkLevel.MAX_LEVEL) { -+ this.ticketLevelPropagator.removeSource(CoordinateUtils.getChunkX(coordinate), CoordinateUtils.getChunkZ(coordinate)); -+ } else { -+ this.ticketLevelPropagator.setSource(CoordinateUtils.getChunkX(coordinate), CoordinateUtils.getChunkZ(coordinate), convertBetweenTicketLevels(ticketLevel)); -+ } -+ } -+ -+ private static int getTicketLevelAt(SortedArraySet> tickets) { -+ return !tickets.isEmpty() ? tickets.first().getTicketLevel() : MAX_TICKET_LEVEL + 1; -+ } -+ -+ public boolean addTicketAtLevel(final TicketType type, final ChunkPos chunkPos, final int level, -+ final T identifier) { -+ return this.addTicketAtLevel(type, CoordinateUtils.getChunkKey(chunkPos), level, identifier); -+ } -+ -+ public boolean addTicketAtLevel(final TicketType type, final int chunkX, final int chunkZ, final int level, -+ final T identifier) { -+ return this.addTicketAtLevel(type, CoordinateUtils.getChunkKey(chunkX, chunkZ), level, identifier); -+ } -+ -+ private void addExpireCount(final int chunkX, final int chunkZ) { -+ final long chunkKey = CoordinateUtils.getChunkKey(chunkX, chunkZ); -+ -+ final int sectionShift = this.world.getRegionChunkShift(); -+ final RegionFileIOThread.ChunkCoordinate sectionKey = new RegionFileIOThread.ChunkCoordinate(CoordinateUtils.getChunkKey( -+ chunkX >> sectionShift, -+ chunkZ >> sectionShift -+ )); -+ -+ this.sectionToChunkToExpireCount.computeIfAbsent(sectionKey, (final RegionFileIOThread.ChunkCoordinate keyInMap) -> { -+ return new Long2IntOpenHashMap(); -+ }).addTo(chunkKey, 1); -+ } -+ -+ private void removeExpireCount(final int chunkX, final int chunkZ) { -+ final long chunkKey = CoordinateUtils.getChunkKey(chunkX, chunkZ); -+ -+ final int sectionShift = this.world.getRegionChunkShift(); -+ final RegionFileIOThread.ChunkCoordinate sectionKey = new RegionFileIOThread.ChunkCoordinate(CoordinateUtils.getChunkKey( -+ chunkX >> sectionShift, -+ chunkZ >> sectionShift -+ )); -+ -+ final Long2IntOpenHashMap removeCounts = this.sectionToChunkToExpireCount.get(sectionKey); -+ final int prevCount = removeCounts.addTo(chunkKey, -1); -+ -+ if (prevCount == 1) { -+ removeCounts.remove(chunkKey); -+ if (removeCounts.isEmpty()) { -+ this.sectionToChunkToExpireCount.remove(sectionKey); -+ } -+ } -+ } -+ -+ // supposed to return true if the ticket was added and did not replace another -+ // but, we always return false if the ticket cannot be added -+ public boolean addTicketAtLevel(final TicketType type, final long chunk, final int level, final T identifier) { -+ return this.addTicketAtLevel(type, chunk, level, identifier, true); -+ } -+ -+ boolean addTicketAtLevel(final TicketType type, final long chunk, final int level, final T identifier, final boolean lock) { -+ final long removeDelay = type.timeout <= 0 ? NO_TIMEOUT_MARKER : type.timeout; -+ if (level > MAX_TICKET_LEVEL) { -+ return false; -+ } -+ -+ final int chunkX = CoordinateUtils.getChunkX(chunk); -+ final int chunkZ = CoordinateUtils.getChunkZ(chunk); -+ final RegionFileIOThread.ChunkCoordinate chunkCoord = new RegionFileIOThread.ChunkCoordinate(chunk); -+ final Ticket ticket = new Ticket<>(type, level, identifier, removeDelay); -+ -+ final ReentrantAreaLock.Node ticketLock = lock ? this.ticketLockArea.lock(chunkX, chunkZ) : null; -+ try { -+ final SortedArraySet> ticketsAtChunk = this.tickets.computeIfAbsent(chunkCoord, (final RegionFileIOThread.ChunkCoordinate keyInMap) -> { -+ return SortedArraySet.create(4); -+ }); -+ -+ final int levelBefore = getTicketLevelAt(ticketsAtChunk); -+ final Ticket current = (Ticket)ticketsAtChunk.replace(ticket); -+ final int levelAfter = getTicketLevelAt(ticketsAtChunk); -+ -+ if (current != ticket) { -+ final long oldRemoveDelay = current.removeDelay; -+ if (removeDelay != oldRemoveDelay) { -+ if (oldRemoveDelay != NO_TIMEOUT_MARKER && removeDelay == NO_TIMEOUT_MARKER) { -+ this.removeExpireCount(chunkX, chunkZ); -+ } else if (oldRemoveDelay == NO_TIMEOUT_MARKER) { -+ // since old != new, we have that NO_TIMEOUT_MARKER != new -+ this.addExpireCount(chunkX, chunkZ); -+ } -+ } -+ } else { -+ if (removeDelay != NO_TIMEOUT_MARKER) { -+ this.addExpireCount(chunkX, chunkZ); -+ } -+ } -+ -+ if (levelBefore != levelAfter) { -+ this.updateTicketLevel(chunk, levelAfter); -+ } -+ -+ return current == ticket; -+ } finally { -+ if (ticketLock != null) { -+ this.ticketLockArea.unlock(ticketLock); -+ } -+ } -+ } -+ -+ public boolean removeTicketAtLevel(final TicketType type, final ChunkPos chunkPos, final int level, final T identifier) { -+ return this.removeTicketAtLevel(type, CoordinateUtils.getChunkKey(chunkPos), level, identifier); -+ } -+ -+ public boolean removeTicketAtLevel(final TicketType type, final int chunkX, final int chunkZ, final int level, final T identifier) { -+ return this.removeTicketAtLevel(type, CoordinateUtils.getChunkKey(chunkX, chunkZ), level, identifier); -+ } -+ -+ public boolean removeTicketAtLevel(final TicketType type, final long chunk, final int level, final T identifier) { -+ return this.removeTicketAtLevel(type, chunk, level, identifier, true); -+ } -+ -+ boolean removeTicketAtLevel(final TicketType type, final long chunk, final int level, final T identifier, final boolean lock) { -+ if (level > MAX_TICKET_LEVEL) { -+ return false; -+ } -+ -+ final int chunkX = CoordinateUtils.getChunkX(chunk); -+ final int chunkZ = CoordinateUtils.getChunkZ(chunk); -+ final RegionFileIOThread.ChunkCoordinate chunkCoord = new RegionFileIOThread.ChunkCoordinate(chunk); -+ final Ticket probe = new Ticket<>(type, level, identifier, PROBE_MARKER); -+ -+ final ReentrantAreaLock.Node ticketLock = lock ? this.ticketLockArea.lock(chunkX, chunkZ) : null; -+ try { -+ final SortedArraySet> ticketsAtChunk = this.tickets.get(chunkCoord); -+ if (ticketsAtChunk == null) { -+ return false; -+ } -+ -+ final int oldLevel = getTicketLevelAt(ticketsAtChunk); -+ final Ticket ticket = (Ticket)ticketsAtChunk.removeAndGet(probe); -+ -+ if (ticket == null) { -+ return false; -+ } -+ -+ final int newLevel = getTicketLevelAt(ticketsAtChunk); -+ // we should not change the ticket levels while the target region may be ticking -+ if (oldLevel != newLevel) { -+ // Delay unload chunk patch originally by Aikar, updated to 1.20 by jpenilla -+ // these days, the patch is mostly useful to keep chunks ticking when players teleport -+ // so that their pets can teleport with them as well. -+ final long delayTimeout = this.world.paperConfig().chunks.delayChunkUnloadsBy.ticks(); -+ final TicketType toAdd; -+ final long timeout; -+ if (type == RegionizedPlayerChunkLoader.REGION_PLAYER_TICKET && delayTimeout > 0) { -+ toAdd = TicketType.DELAY_UNLOAD; -+ timeout = delayTimeout; -+ } else { -+ toAdd = TicketType.UNKNOWN; -+ // always expect UNKNOWN to be > 1, but just in case -+ timeout = Math.max(1, toAdd.timeout); -+ } -+ final Ticket unknownTicket = new Ticket<>(toAdd, level, new ChunkPos(chunk), timeout); -+ if (ticketsAtChunk.add(unknownTicket)) { -+ this.addExpireCount(chunkX, chunkZ); -+ } else { -+ throw new IllegalStateException("Should have been able to add " + unknownTicket + " to " + ticketsAtChunk); -+ } -+ } -+ -+ final long removeDelay = ticket.removeDelay; -+ if (removeDelay != NO_TIMEOUT_MARKER) { -+ this.removeExpireCount(chunkX, chunkZ); -+ } -+ -+ return true; -+ } finally { -+ if (ticketLock != null) { -+ this.ticketLockArea.unlock(ticketLock); -+ } -+ } -+ } -+ -+ // atomic with respect to all add/remove/addandremove ticket calls for the given chunk -+ public void addAndRemoveTickets(final long chunk, final TicketType addType, final int addLevel, final T addIdentifier, -+ final TicketType removeType, final int removeLevel, final V removeIdentifier) { -+ final ReentrantAreaLock.Node ticketLock = this.ticketLockArea.lock(CoordinateUtils.getChunkX(chunk), CoordinateUtils.getChunkZ(chunk)); -+ try { -+ this.addTicketAtLevel(addType, chunk, addLevel, addIdentifier, false); -+ this.removeTicketAtLevel(removeType, chunk, removeLevel, removeIdentifier, false); -+ } finally { -+ this.ticketLockArea.unlock(ticketLock); -+ } -+ } -+ -+ // atomic with respect to all add/remove/addandremove ticket calls for the given chunk -+ public boolean addIfRemovedTicket(final long chunk, final TicketType addType, final int addLevel, final T addIdentifier, -+ final TicketType removeType, final int removeLevel, final V removeIdentifier) { -+ final ReentrantAreaLock.Node ticketLock = this.ticketLockArea.lock(CoordinateUtils.getChunkX(chunk), CoordinateUtils.getChunkZ(chunk)); -+ try { -+ if (this.removeTicketAtLevel(removeType, chunk, removeLevel, removeIdentifier, false)) { -+ this.addTicketAtLevel(addType, chunk, addLevel, addIdentifier, false); -+ return true; -+ } -+ return false; -+ } finally { -+ this.ticketLockArea.unlock(ticketLock); -+ } -+ } -+ -+ public void removeAllTicketsFor(final TicketType ticketType, final int ticketLevel, final T ticketIdentifier) { -+ if (ticketLevel > MAX_TICKET_LEVEL) { -+ return; -+ } -+ -+ final Long2ObjectOpenHashMap> sections = new Long2ObjectOpenHashMap(); -+ final int sectionShift = this.taskScheduler.getChunkSystemLockShift(); -+ for (final RegionFileIOThread.ChunkCoordinate coord : this.tickets.keySet()) { -+ sections.computeIfAbsent( -+ CoordinateUtils.getChunkKey( -+ CoordinateUtils.getChunkX(coord.key) >> sectionShift, -+ CoordinateUtils.getChunkZ(coord.key) >> sectionShift -+ ), -+ (final long keyInMap) -> { -+ return new ArrayList<>(); -+ } -+ ).add(coord); -+ } -+ -+ for (final Iterator>> iterator = sections.long2ObjectEntrySet().fastIterator(); -+ iterator.hasNext();) { -+ final Long2ObjectMap.Entry> entry = iterator.next(); -+ final long sectionKey = entry.getLongKey(); -+ final List coordinates = entry.getValue(); -+ -+ final ReentrantAreaLock.Node ticketLock = this.ticketLockArea.lock( -+ CoordinateUtils.getChunkX(sectionKey) << sectionShift, -+ CoordinateUtils.getChunkZ(sectionKey) << sectionShift -+ ); -+ try { -+ for (final RegionFileIOThread.ChunkCoordinate coord : coordinates) { -+ this.removeTicketAtLevel(ticketType, coord.key, ticketLevel, ticketIdentifier, false); -+ } -+ } finally { -+ this.ticketLockArea.unlock(ticketLock); -+ } -+ } -+ } -+ -+ public void tick() { -+ final int sectionShift = this.world.getRegionChunkShift(); -+ -+ final Predicate> expireNow = (final Ticket ticket) -> { -+ if (ticket.removeDelay == NO_TIMEOUT_MARKER) { -+ return false; -+ } -+ return --ticket.removeDelay <= 0L; -+ }; -+ -+ for (final Iterator iterator = this.sectionToChunkToExpireCount.keySet().iterator(); iterator.hasNext();) { -+ final RegionFileIOThread.ChunkCoordinate section = iterator.next(); -+ final long sectionKey = section.key; -+ -+ if (!this.sectionToChunkToExpireCount.containsKey(section)) { -+ // removed concurrently -+ continue; -+ } -+ -+ final ReentrantAreaLock.Node ticketLock = this.ticketLockArea.lock( -+ CoordinateUtils.getChunkX(sectionKey) << sectionShift, -+ CoordinateUtils.getChunkZ(sectionKey) << sectionShift -+ ); -+ -+ try { -+ final Long2IntOpenHashMap chunkToExpireCount = this.sectionToChunkToExpireCount.get(section); -+ if (chunkToExpireCount == null) { -+ // lost to some race -+ continue; -+ } -+ -+ for (final Iterator iterator1 = chunkToExpireCount.long2IntEntrySet().fastIterator(); iterator1.hasNext();) { -+ final Long2IntMap.Entry entry = iterator1.next(); -+ -+ final long chunkKey = entry.getLongKey(); -+ final int expireCount = entry.getIntValue(); -+ -+ final RegionFileIOThread.ChunkCoordinate chunk = new RegionFileIOThread.ChunkCoordinate(chunkKey); -+ -+ final SortedArraySet> tickets = this.tickets.get(chunk); -+ final int levelBefore = getTicketLevelAt(tickets); -+ -+ final int sizeBefore = tickets.size(); -+ tickets.removeIf(expireNow); -+ final int sizeAfter = tickets.size(); -+ final int levelAfter = getTicketLevelAt(tickets); -+ -+ if (tickets.isEmpty()) { -+ this.tickets.remove(chunk); -+ } -+ if (levelBefore != levelAfter) { -+ this.updateTicketLevel(chunkKey, levelAfter); -+ } -+ -+ final int newExpireCount = expireCount - (sizeBefore - sizeAfter); -+ -+ if (newExpireCount == expireCount) { -+ continue; -+ } -+ -+ if (newExpireCount != 0) { -+ entry.setValue(newExpireCount); -+ } else { -+ iterator1.remove(); -+ } -+ } -+ -+ if (chunkToExpireCount.isEmpty()) { -+ this.sectionToChunkToExpireCount.remove(section); -+ } -+ } finally { -+ this.ticketLockArea.unlock(ticketLock); -+ } -+ } -+ -+ this.processTicketUpdates(); -+ } -+ -+ public NewChunkHolder getChunkHolder(final int chunkX, final int chunkZ) { -+ return this.chunkHolders.get(CoordinateUtils.getChunkKey(chunkX, chunkZ)); -+ } -+ -+ public NewChunkHolder getChunkHolder(final long position) { -+ return this.chunkHolders.get(position); -+ } -+ -+ public void raisePriority(final int x, final int z, final PrioritisedExecutor.Priority priority) { -+ final NewChunkHolder chunkHolder = this.getChunkHolder(x, z); -+ if (chunkHolder != null) { -+ chunkHolder.raisePriority(priority); -+ } -+ } -+ -+ public void setPriority(final int x, final int z, final PrioritisedExecutor.Priority priority) { -+ final NewChunkHolder chunkHolder = this.getChunkHolder(x, z); -+ if (chunkHolder != null) { -+ chunkHolder.setPriority(priority); -+ } -+ } -+ -+ public void lowerPriority(final int x, final int z, final PrioritisedExecutor.Priority priority) { -+ final NewChunkHolder chunkHolder = this.getChunkHolder(x, z); -+ if (chunkHolder != null) { -+ chunkHolder.lowerPriority(priority); -+ } -+ } -+ -+ private NewChunkHolder createChunkHolder(final long position) { -+ final NewChunkHolder ret = new NewChunkHolder(this.world, CoordinateUtils.getChunkX(position), CoordinateUtils.getChunkZ(position), this.taskScheduler); -+ -+ ChunkSystem.onChunkHolderCreate(this.world, ret.vanillaChunkHolder); -+ ret.vanillaChunkHolder.onChunkAdd(); -+ -+ return ret; -+ } -+ -+ // because this function creates the chunk holder without a ticket, it is the caller's responsibility to ensure -+ // the chunk holder eventually unloads. this should only be used to avoid using processTicketUpdates to create chunkholders, -+ // as processTicketUpdates may call plugin logic; in every other case a ticket is appropriate -+ private NewChunkHolder getOrCreateChunkHolder(final int chunkX, final int chunkZ) { -+ return this.getOrCreateChunkHolder(CoordinateUtils.getChunkKey(chunkX, chunkZ)); -+ } -+ -+ private NewChunkHolder getOrCreateChunkHolder(final long position) { -+ final int chunkX = CoordinateUtils.getChunkX(position); -+ final int chunkZ = CoordinateUtils.getChunkZ(position); -+ -+ if (!this.ticketLockArea.isHeldByCurrentThread(chunkX, chunkZ)) { -+ throw new IllegalStateException("Must hold ticket level update lock!"); -+ } -+ if (!this.taskScheduler.schedulingLockArea.isHeldByCurrentThread(chunkX, chunkZ)) { -+ throw new IllegalStateException("Must hold scheduler lock!!"); -+ } -+ -+ // we could just acquire these locks, but... -+ // must own the locks because the caller needs to ensure that no unload can occur AFTER this function returns -+ -+ NewChunkHolder current = this.chunkHolders.get(position); -+ if (current != null) { -+ return current; -+ } -+ -+ current = this.createChunkHolder(position); -+ synchronized (this.chunkHolders) { -+ this.chunkHolders.put(position, current); -+ } -+ -+ return current; -+ } -+ -+ private final AtomicLong entityLoadCounter = new AtomicLong(); -+ -+ public ChunkEntitySlices getOrCreateEntityChunk(final int chunkX, final int chunkZ, final boolean transientChunk) { -+ TickThread.ensureTickThread(this.world, chunkX, chunkZ, "Cannot create entity chunk off-main"); -+ ChunkEntitySlices ret; -+ -+ NewChunkHolder current = this.getChunkHolder(chunkX, chunkZ); -+ if (current != null && (ret = current.getEntityChunk()) != null && (transientChunk || !ret.isTransient())) { -+ return ret; -+ } -+ -+ final AtomicBoolean isCompleted = new AtomicBoolean(); -+ final Thread waiter = Thread.currentThread(); -+ final Long entityLoadId = Long.valueOf(this.entityLoadCounter.getAndIncrement()); -+ NewChunkHolder.GenericDataLoadTaskCallback loadTask = null; -+ final ReentrantAreaLock.Node ticketLock = this.ticketLockArea.lock(chunkX, chunkZ); -+ try { -+ this.addTicketAtLevel(TicketType.ENTITY_LOAD, chunkX, chunkZ, MAX_TICKET_LEVEL, entityLoadId); -+ final ReentrantAreaLock.Node schedulingLock = this.taskScheduler.schedulingLockArea.lock(chunkX, chunkZ); -+ try { -+ current = this.getOrCreateChunkHolder(chunkX, chunkZ); -+ if ((ret = current.getEntityChunk()) != null && (transientChunk || !ret.isTransient())) { -+ this.removeTicketAtLevel(TicketType.ENTITY_LOAD, chunkX, chunkZ, MAX_TICKET_LEVEL, entityLoadId); -+ return ret; -+ } -+ -+ if (current.isEntityChunkNBTLoaded()) { -+ isCompleted.setPlain(true); -+ } else { -+ loadTask = current.getOrLoadEntityData((final GenericDataLoadTask.TaskResult result) -> { -+ if (!transientChunk) { -+ isCompleted.set(true); -+ LockSupport.unpark(waiter); -+ } -+ }); -+ final ChunkLoadTask.EntityDataLoadTask entityLoad = current.getEntityDataLoadTask(); -+ -+ if (entityLoad != null && !transientChunk) { -+ entityLoad.raisePriority(PrioritisedExecutor.Priority.BLOCKING); -+ } -+ } -+ } finally { -+ this.taskScheduler.schedulingLockArea.unlock(schedulingLock); -+ } -+ } finally { -+ this.ticketLockArea.unlock(ticketLock); -+ } -+ -+ if (loadTask != null) { -+ loadTask.schedule(); -+ } -+ -+ if (!transientChunk) { -+ // Note: no need to busy wait on the chunk queue, entity load will complete off-main -+ boolean interrupted = false; -+ while (!isCompleted.get()) { -+ interrupted |= Thread.interrupted(); -+ LockSupport.park(); -+ } -+ -+ if (interrupted) { -+ Thread.currentThread().interrupt(); -+ } -+ } -+ -+ // now that the entity data is loaded, we can load it into the world -+ -+ ret = current.loadInEntityChunk(transientChunk); -+ -+ final long chunkKey = CoordinateUtils.getChunkKey(chunkX, chunkZ); -+ this.addAndRemoveTickets(chunkKey, -+ TicketType.UNKNOWN, MAX_TICKET_LEVEL, new ChunkPos(chunkX, chunkZ), -+ TicketType.ENTITY_LOAD, MAX_TICKET_LEVEL, entityLoadId -+ ); -+ -+ return ret; -+ } -+ -+ public PoiChunk getPoiChunkIfLoaded(final int chunkX, final int chunkZ, final boolean checkLoadInCallback) { -+ final NewChunkHolder holder = this.getChunkHolder(chunkX, chunkZ); -+ if (holder != null) { -+ final PoiChunk ret = holder.getPoiChunk(); -+ return ret == null || (checkLoadInCallback && !ret.isLoaded()) ? null : ret; -+ } -+ return null; -+ } -+ -+ private final AtomicLong poiLoadCounter = new AtomicLong(); -+ -+ public PoiChunk loadPoiChunk(final int chunkX, final int chunkZ) { -+ TickThread.ensureTickThread(this.world, chunkX, chunkZ, "Cannot create poi chunk off-main"); -+ PoiChunk ret; -+ -+ NewChunkHolder current = this.getChunkHolder(chunkX, chunkZ); -+ if (current != null && (ret = current.getPoiChunk()) != null) { -+ if (!ret.isLoaded()) { -+ ret.load(); -+ } -+ return ret; -+ } -+ -+ final AtomicReference completed = new AtomicReference<>(); -+ final AtomicBoolean isCompleted = new AtomicBoolean(); -+ final Thread waiter = Thread.currentThread(); -+ final Long poiLoadId = Long.valueOf(this.poiLoadCounter.getAndIncrement()); -+ NewChunkHolder.GenericDataLoadTaskCallback loadTask = null; -+ final ca.spottedleaf.concurrentutil.lock.ReentrantAreaLock.Node ticketLock = this.ticketLockArea.lock(chunkX, chunkZ); // Folia - use area based lock to reduce contention -+ try { -+ // Folia - use area based lock to reduce contention -+ this.addTicketAtLevel(TicketType.POI_LOAD, chunkX, chunkZ, MAX_TICKET_LEVEL, poiLoadId); -+ final ca.spottedleaf.concurrentutil.lock.ReentrantAreaLock.Node schedulingLock = this.taskScheduler.schedulingLockArea.lock(chunkX, chunkZ); // Folia - use area based lock to reduce contention -+ try { -+ current = this.getOrCreateChunkHolder(chunkX, chunkZ); -+ if (current.isPoiChunkLoaded()) { -+ this.removeTicketAtLevel(TicketType.POI_LOAD, chunkX, chunkZ, MAX_TICKET_LEVEL, poiLoadId); -+ return current.getPoiChunk(); -+ } -+ -+ loadTask = current.getOrLoadPoiData((final GenericDataLoadTask.TaskResult result) -> { -+ completed.setPlain(result.left()); -+ isCompleted.set(true); -+ LockSupport.unpark(waiter); -+ }); -+ final ChunkLoadTask.PoiDataLoadTask poiLoad = current.getPoiDataLoadTask(); -+ -+ if (poiLoad != null) { -+ poiLoad.raisePriority(PrioritisedExecutor.Priority.BLOCKING); -+ } -+ } finally { -+ this.taskScheduler.schedulingLockArea.unlock(schedulingLock); // Folia - use area based lock to reduce contention -+ } -+ } finally { -+ this.ticketLockArea.unlock(ticketLock); // Folia - use area based lock to reduce contention -+ } -+ -+ if (loadTask != null) { -+ loadTask.schedule(); -+ } -+ -+ // Note: no need to busy wait on the chunk queue, poi load will complete off-main -+ -+ boolean interrupted = false; -+ while (!isCompleted.get()) { -+ interrupted |= Thread.interrupted(); -+ LockSupport.park(); -+ } -+ -+ if (interrupted) { -+ Thread.currentThread().interrupt(); -+ } -+ -+ ret = completed.getPlain(); -+ -+ ret.load(); -+ -+ final long chunkKey = CoordinateUtils.getChunkKey(chunkX, chunkZ); -+ this.addAndRemoveTickets(chunkKey, -+ TicketType.UNKNOWN, MAX_TICKET_LEVEL, new ChunkPos(chunkX, chunkZ), -+ TicketType.POI_LOAD, MAX_TICKET_LEVEL, poiLoadId -+ ); -+ -+ return ret; -+ } -+ -+ void addChangedStatuses(final List changedFullStatus) { -+ if (changedFullStatus.isEmpty()) { -+ return; -+ } -+ if (!TickThread.isTickThread()) { -+ this.taskScheduler.scheduleChunkTask(() -> { -+ final ArrayDeque pendingFullLoadUpdate = ChunkHolderManager.this.pendingFullLoadUpdate; -+ for (int i = 0, len = changedFullStatus.size(); i < len; ++i) { -+ pendingFullLoadUpdate.add(changedFullStatus.get(i)); -+ } -+ -+ ChunkHolderManager.this.processPendingFullUpdate(); -+ }, PrioritisedExecutor.Priority.HIGHEST); -+ } else { -+ final ArrayDeque pendingFullLoadUpdate = this.pendingFullLoadUpdate; -+ for (int i = 0, len = changedFullStatus.size(); i < len; ++i) { -+ pendingFullLoadUpdate.add(changedFullStatus.get(i)); -+ } -+ } -+ } -+ -+ private void removeChunkHolder(final NewChunkHolder holder) { -+ holder.killed = true; -+ holder.vanillaChunkHolder.onChunkRemove(); -+ this.autoSaveQueue.remove(holder); -+ ChunkSystem.onChunkHolderDelete(this.world, holder.vanillaChunkHolder); -+ synchronized (this.chunkHolders) { -+ this.chunkHolders.remove(CoordinateUtils.getChunkKey(holder.chunkX, holder.chunkZ)); -+ } -+ } -+ -+ // note: never call while inside the chunk system, this will absolutely break everything -+ public void processUnloads() { -+ TickThread.ensureTickThread("Cannot unload chunks off-main"); -+ -+ if (BLOCK_TICKET_UPDATES.get() == Boolean.TRUE) { -+ throw new IllegalStateException("Cannot unload chunks recursively"); -+ } -+ final int sectionShift = this.unloadQueue.coordinateShift; // sectionShift <= lock shift -+ final List unloadSectionsForRegion = this.unloadQueue.retrieveForAllRegions(); -+ int unloadCountTentative = 0; -+ for (final ChunkQueue.SectionToUnload sectionRef : unloadSectionsForRegion) { -+ final ChunkQueue.UnloadSection section -+ = this.unloadQueue.getSectionUnsynchronized(sectionRef.sectionX(), sectionRef.sectionZ()); -+ -+ if (section == null) { -+ // removed concurrently -+ continue; -+ } -+ -+ // technically reading the size field is unsafe, and it may be incorrect. -+ // We assume that the error here cumulatively goes away over many ticks. If it did not, then it is possible -+ // for chunks to never unload or not unload fast enough. -+ unloadCountTentative += section.chunks.size(); -+ } -+ -+ if (unloadCountTentative <= 0) { -+ // no work to do -+ return; -+ } -+ -+ // Note: The behaviour that we process ticket updates while holding the lock has been dropped here, as it is racey behavior. -+ // But, we do need to process updates here so that any add ticket that is synchronised before this call does not go missed. -+ this.processTicketUpdates(); -+ -+ final int toUnloadCount = Math.max(50, (int)(unloadCountTentative * 0.05)); -+ int processedCount = 0; -+ -+ for (final ChunkQueue.SectionToUnload sectionRef : unloadSectionsForRegion) { -+ final List stage1 = new ArrayList<>(); -+ final List stage2 = new ArrayList<>(); -+ -+ final int sectionLowerX = sectionRef.sectionX() << sectionShift; -+ final int sectionLowerZ = sectionRef.sectionZ() << sectionShift; -+ -+ // stage 1: set up for stage 2 while holding critical locks -+ ReentrantAreaLock.Node ticketLock = this.ticketLockArea.lock(sectionLowerX, sectionLowerZ); -+ try { -+ final ReentrantAreaLock.Node scheduleLock = this.taskScheduler.schedulingLockArea.lock(sectionLowerX, sectionLowerZ); -+ try { -+ final ChunkQueue.UnloadSection section -+ = this.unloadQueue.getSectionUnsynchronized(sectionRef.sectionX(), sectionRef.sectionZ()); -+ -+ if (section == null) { -+ // removed concurrently -+ continue; -+ } -+ -+ // collect the holders to run stage 1 on -+ final int sectionCount = section.chunks.size(); -+ -+ if ((sectionCount + processedCount) <= toUnloadCount) { -+ // we can just drain the entire section -+ -+ for (final LongIterator iterator = section.chunks.iterator(); iterator.hasNext();) { -+ final NewChunkHolder holder = this.chunkHolders.get(iterator.nextLong()); -+ if (holder == null) { -+ throw new IllegalStateException(); -+ } -+ stage1.add(holder); -+ } -+ -+ // remove section -+ this.unloadQueue.removeSection(sectionRef.sectionX(), sectionRef.sectionZ()); -+ } else { -+ // processedCount + len = toUnloadCount -+ // we cannot drain the entire section -+ for (int i = 0, len = toUnloadCount - processedCount; i < len; ++i) { -+ final NewChunkHolder holder = this.chunkHolders.get(section.chunks.removeFirstLong()); -+ if (holder == null) { -+ throw new IllegalStateException(); -+ } -+ stage1.add(holder); -+ } -+ } -+ -+ // run stage 1 -+ for (int i = 0, len = stage1.size(); i < len; ++i) { -+ final NewChunkHolder chunkHolder = stage1.get(i); -+ if (chunkHolder.isSafeToUnload() != null) { -+ LOGGER.error("Chunkholder " + chunkHolder + " is not safe to unload but is inside the unload queue?"); -+ continue; -+ } -+ final NewChunkHolder.UnloadState state = chunkHolder.unloadStage1(); -+ if (state == null) { -+ // can unload immediately -+ this.removeChunkHolder(chunkHolder); -+ continue; -+ } -+ stage2.add(state); -+ } -+ } finally { -+ this.taskScheduler.schedulingLockArea.unlock(scheduleLock); -+ } -+ } finally { -+ this.ticketLockArea.unlock(ticketLock); -+ } -+ -+ // stage 2: invoke expensive unload logic, designed to run without locks thanks to stage 1 -+ final List stage3 = new ArrayList<>(stage2.size()); -+ -+ final Boolean before = this.blockTicketUpdates(); -+ try { -+ for (int i = 0, len = stage2.size(); i < len; ++i) { -+ final NewChunkHolder.UnloadState state = stage2.get(i); -+ final NewChunkHolder holder = state.holder(); -+ -+ holder.unloadStage2(state); -+ stage3.add(holder); -+ } -+ } finally { -+ this.unblockTicketUpdates(before); -+ } -+ -+ // stage 3: actually attempt to remove the chunk holders -+ ticketLock = this.ticketLockArea.lock(sectionLowerX, sectionLowerZ); -+ try { -+ final ReentrantAreaLock.Node scheduleLock = this.taskScheduler.schedulingLockArea.lock(sectionLowerX, sectionLowerZ); -+ try { -+ for (int i = 0, len = stage3.size(); i < len; ++i) { -+ final NewChunkHolder holder = stage3.get(i); -+ -+ if (holder.unloadStage3()) { -+ this.removeChunkHolder(holder); -+ } else { -+ // add cooldown so the next unload check is not immediately next tick -+ this.addTicketAtLevel(TicketType.UNLOAD_COOLDOWN, CoordinateUtils.getChunkKey(holder.chunkX, holder.chunkZ), MAX_TICKET_LEVEL, Unit.INSTANCE, false); -+ } -+ } -+ } finally { -+ this.taskScheduler.schedulingLockArea.unlock(scheduleLock); -+ } -+ } finally { -+ this.ticketLockArea.unlock(ticketLock); -+ } -+ -+ processedCount += stage1.size(); -+ -+ if (processedCount >= toUnloadCount) { -+ break; -+ } -+ } -+ } -+ -+ public enum TicketOperationType { -+ ADD, REMOVE, ADD_IF_REMOVED, ADD_AND_REMOVE -+ } -+ -+ public static record TicketOperation ( -+ TicketOperationType op, long chunkCoord, -+ TicketType ticketType, int ticketLevel, T identifier, -+ TicketType ticketType2, int ticketLevel2, V identifier2 -+ ) { -+ -+ private TicketOperation(TicketOperationType op, long chunkCoord, -+ TicketType ticketType, int ticketLevel, T identifier) { -+ this(op, chunkCoord, ticketType, ticketLevel, identifier, null, 0, null); -+ } -+ -+ public static TicketOperation addOp(final ChunkPos chunk, final TicketType type, final int ticketLevel, final T identifier) { -+ return addOp(CoordinateUtils.getChunkKey(chunk), type, ticketLevel, identifier); -+ } -+ -+ public static TicketOperation addOp(final int chunkX, final int chunkZ, final TicketType type, final int ticketLevel, final T identifier) { -+ return addOp(CoordinateUtils.getChunkKey(chunkX, chunkZ), type, ticketLevel, identifier); -+ } -+ -+ public static TicketOperation addOp(final long chunk, final TicketType type, final int ticketLevel, final T identifier) { -+ return new TicketOperation<>(TicketOperationType.ADD, chunk, type, ticketLevel, identifier); -+ } -+ -+ public static TicketOperation removeOp(final ChunkPos chunk, final TicketType type, final int ticketLevel, final T identifier) { -+ return removeOp(CoordinateUtils.getChunkKey(chunk), type, ticketLevel, identifier); -+ } -+ -+ public static TicketOperation removeOp(final int chunkX, final int chunkZ, final TicketType type, final int ticketLevel, final T identifier) { -+ return removeOp(CoordinateUtils.getChunkKey(chunkX, chunkZ), type, ticketLevel, identifier); -+ } -+ -+ public static TicketOperation removeOp(final long chunk, final TicketType type, final int ticketLevel, final T identifier) { -+ return new TicketOperation<>(TicketOperationType.REMOVE, chunk, type, ticketLevel, identifier); -+ } -+ -+ public static TicketOperation addIfRemovedOp(final long chunk, -+ final TicketType addType, final int addLevel, final T addIdentifier, -+ final TicketType removeType, final int removeLevel, final V removeIdentifier) { -+ return new TicketOperation<>( -+ TicketOperationType.ADD_IF_REMOVED, chunk, addType, addLevel, addIdentifier, -+ removeType, removeLevel, removeIdentifier -+ ); -+ } -+ -+ public static TicketOperation addAndRemove(final long chunk, -+ final TicketType addType, final int addLevel, final T addIdentifier, -+ final TicketType removeType, final int removeLevel, final V removeIdentifier) { -+ return new TicketOperation<>( -+ TicketOperationType.ADD_AND_REMOVE, chunk, addType, addLevel, addIdentifier, -+ removeType, removeLevel, removeIdentifier -+ ); -+ } -+ } -+ -+ private boolean processTicketOp(TicketOperation operation) { -+ boolean ret = false; -+ switch (operation.op) { -+ case ADD: { -+ ret |= this.addTicketAtLevel(operation.ticketType, operation.chunkCoord, operation.ticketLevel, operation.identifier); -+ break; -+ } -+ case REMOVE: { -+ ret |= this.removeTicketAtLevel(operation.ticketType, operation.chunkCoord, operation.ticketLevel, operation.identifier); -+ break; -+ } -+ case ADD_IF_REMOVED: { -+ ret |= this.addIfRemovedTicket( -+ operation.chunkCoord, -+ operation.ticketType, operation.ticketLevel, operation.identifier, -+ operation.ticketType2, operation.ticketLevel2, operation.identifier2 -+ ); -+ break; -+ } -+ case ADD_AND_REMOVE: { -+ ret = true; -+ this.addAndRemoveTickets( -+ operation.chunkCoord, -+ operation.ticketType, operation.ticketLevel, operation.identifier, -+ operation.ticketType2, operation.ticketLevel2, operation.identifier2 -+ ); -+ break; -+ } -+ } -+ -+ return ret; -+ } -+ -+ public void performTicketUpdates(final Collection> operations) { -+ for (final TicketOperation operation : operations) { -+ this.processTicketOp(operation); -+ } -+ } -+ -+ private final ThreadLocal BLOCK_TICKET_UPDATES = ThreadLocal.withInitial(() -> { -+ return Boolean.FALSE; -+ }); -+ -+ public Boolean blockTicketUpdates() { -+ final Boolean ret = BLOCK_TICKET_UPDATES.get(); -+ BLOCK_TICKET_UPDATES.set(Boolean.TRUE); -+ return ret; -+ } -+ -+ public void unblockTicketUpdates(final Boolean before) { -+ BLOCK_TICKET_UPDATES.set(before); -+ } -+ -+ public boolean processTicketUpdates() { -+ return this.processTicketUpdates(true, true, null); -+ } -+ -+ private static final ThreadLocal> CURRENT_TICKET_UPDATE_SCHEDULING = new ThreadLocal<>(); -+ -+ static List getCurrentTicketUpdateScheduling() { -+ return CURRENT_TICKET_UPDATE_SCHEDULING.get(); -+ } -+ -+ private boolean processTicketUpdates(final boolean checkLocks, final boolean processFullUpdates, List scheduledTasks) { -+ TickThread.ensureTickThread("Cannot process ticket levels off-main"); -+ if (BLOCK_TICKET_UPDATES.get() == Boolean.TRUE) { -+ throw new IllegalStateException("Cannot update ticket level while unloading chunks or updating entity manager"); -+ } -+ -+ List changedFullStatus = null; -+ -+ final boolean isTickThread = TickThread.isTickThread(); -+ -+ boolean ret = false; -+ final boolean canProcessFullUpdates = processFullUpdates & isTickThread; -+ final boolean canProcessScheduling = scheduledTasks == null; -+ -+ if (this.ticketLevelPropagator.hasPendingUpdates()) { -+ if (scheduledTasks == null) { -+ scheduledTasks = new ArrayList<>(); -+ } -+ changedFullStatus = new ArrayList<>(); -+ -+ ret |= this.ticketLevelPropagator.performUpdates( -+ this.ticketLockArea, this.taskScheduler.schedulingLockArea, -+ scheduledTasks, changedFullStatus -+ ); -+ } -+ -+ if (changedFullStatus != null) { -+ this.addChangedStatuses(changedFullStatus); -+ } -+ -+ if (canProcessScheduling && scheduledTasks != null) { -+ for (int i = 0, len = scheduledTasks.size(); i < len; ++i) { -+ scheduledTasks.get(i).schedule(); -+ } -+ } -+ -+ if (canProcessFullUpdates) { -+ ret |= this.processPendingFullUpdate(); -+ } -+ -+ return ret; -+ } -+ -+ // only call on tick thread -+ protected final boolean processPendingFullUpdate() { -+ final ArrayDeque pendingFullLoadUpdate = this.pendingFullLoadUpdate; -+ -+ boolean ret = false; -+ -+ List changedFullStatus = new ArrayList<>(); -+ -+ NewChunkHolder holder; -+ while ((holder = pendingFullLoadUpdate.poll()) != null) { -+ ret |= holder.handleFullStatusChange(changedFullStatus); -+ -+ if (!changedFullStatus.isEmpty()) { -+ for (int i = 0, len = changedFullStatus.size(); i < len; ++i) { -+ pendingFullLoadUpdate.add(changedFullStatus.get(i)); -+ } -+ changedFullStatus.clear(); -+ } -+ } -+ -+ return ret; -+ } -+ -+ public JsonObject getDebugJsonForWatchdog() { -+ return this.getDebugJsonNoLock(); -+ } -+ -+ private JsonObject getDebugJsonNoLock() { -+ final JsonObject ret = new JsonObject(); -+ ret.addProperty("current_tick", Long.valueOf(this.currentTick)); -+ -+ final JsonArray unloadQueue = new JsonArray(); -+ ret.add("unload_queue", unloadQueue); -+ ret.addProperty("lock_shift", Integer.valueOf(this.taskScheduler.getChunkSystemLockShift())); -+ ret.addProperty("ticket_shift", Integer.valueOf(ThreadedTicketLevelPropagator.SECTION_SHIFT)); -+ ret.addProperty("region_shift", Integer.valueOf(this.world.getRegionChunkShift())); -+ for (final ChunkQueue.SectionToUnload section : this.unloadQueue.retrieveForAllRegions()) { -+ final JsonObject sectionJson = new JsonObject(); -+ unloadQueue.add(sectionJson); -+ sectionJson.addProperty("sectionX", section.sectionX()); -+ sectionJson.addProperty("sectionZ", section.sectionX()); -+ sectionJson.addProperty("order", section.order()); -+ -+ final JsonArray coordinates = new JsonArray(); -+ sectionJson.add("coordinates", coordinates); -+ -+ final ChunkQueue.UnloadSection actualSection = this.unloadQueue.getSectionUnsynchronized(section.sectionX(), section.sectionZ()); -+ for (final LongIterator iterator = actualSection.chunks.iterator(); iterator.hasNext();) { -+ final long coordinate = iterator.nextLong(); -+ -+ final JsonObject coordinateJson = new JsonObject(); -+ coordinates.add(coordinateJson); -+ -+ coordinateJson.addProperty("chunkX", Integer.valueOf(CoordinateUtils.getChunkX(coordinate))); -+ coordinateJson.addProperty("chunkZ", Integer.valueOf(CoordinateUtils.getChunkZ(coordinate))); -+ } -+ } -+ -+ final JsonArray holders = new JsonArray(); -+ ret.add("chunkholders", holders); -+ -+ for (final NewChunkHolder holder : this.getChunkHolders()) { -+ holders.add(holder.getDebugJson()); -+ } -+ -+ // TODO -+ /* -+ final JsonArray removeTickToChunkExpireTicketCount = new JsonArray(); -+ ret.add("remove_tick_to_chunk_expire_ticket_count", removeTickToChunkExpireTicketCount); -+ -+ for (final Long2ObjectMap.Entry tickEntry : this.removeTickToChunkExpireTicketCount.long2ObjectEntrySet()) { -+ final long tick = tickEntry.getLongKey(); -+ final Long2IntOpenHashMap coordinateToCount = tickEntry.getValue(); -+ -+ final JsonObject tickJson = new JsonObject(); -+ removeTickToChunkExpireTicketCount.add(tickJson); -+ -+ tickJson.addProperty("tick", Long.valueOf(tick)); -+ -+ final JsonArray tickEntries = new JsonArray(); -+ tickJson.add("entries", tickEntries); -+ -+ for (final Long2IntMap.Entry entry : coordinateToCount.long2IntEntrySet()) { -+ final long coordinate = entry.getLongKey(); -+ final int count = entry.getIntValue(); -+ -+ final JsonObject entryJson = new JsonObject(); -+ tickEntries.add(entryJson); -+ -+ entryJson.addProperty("chunkX", Long.valueOf(CoordinateUtils.getChunkX(coordinate))); -+ entryJson.addProperty("chunkZ", Long.valueOf(CoordinateUtils.getChunkZ(coordinate))); -+ entryJson.addProperty("count", Integer.valueOf(count)); -+ } -+ } -+ -+ final JsonArray allTicketsJson = new JsonArray(); -+ ret.add("tickets", allTicketsJson); -+ -+ for (final Long2ObjectMap.Entry>> coordinateTickets : this.tickets.long2ObjectEntrySet()) { -+ final long coordinate = coordinateTickets.getLongKey(); -+ final SortedArraySet> tickets = coordinateTickets.getValue(); -+ -+ final JsonObject coordinateJson = new JsonObject(); -+ allTicketsJson.add(coordinateJson); -+ -+ coordinateJson.addProperty("chunkX", Long.valueOf(CoordinateUtils.getChunkX(coordinate))); -+ coordinateJson.addProperty("chunkZ", Long.valueOf(CoordinateUtils.getChunkZ(coordinate))); -+ -+ final JsonArray ticketsSerialized = new JsonArray(); -+ coordinateJson.add("tickets", ticketsSerialized); -+ -+ for (final Ticket ticket : tickets) { -+ final JsonObject ticketSerialized = new JsonObject(); -+ ticketsSerialized.add(ticketSerialized); -+ -+ ticketSerialized.addProperty("type", ticket.getType().toString()); -+ ticketSerialized.addProperty("level", Integer.valueOf(ticket.getTicketLevel())); -+ ticketSerialized.addProperty("identifier", Objects.toString(ticket.key)); -+ ticketSerialized.addProperty("remove_tick", Long.valueOf(ticket.removalTick)); -+ } -+ } -+ */ -+ -+ return ret; -+ } -+ -+ public JsonObject getDebugJson() { -+ return this.getDebugJsonNoLock(); // Folia - use area based lock to reduce contention -+ } -+} -diff --git a/src/main/java/io/papermc/paper/chunk/system/scheduling/ChunkLightTask.java b/src/main/java/io/papermc/paper/chunk/system/scheduling/ChunkLightTask.java -new file mode 100644 -index 0000000000000000000000000000000000000000..86e618586d2ad9d843ad761b7336bb3073ed4c23 ---- /dev/null -+++ b/src/main/java/io/papermc/paper/chunk/system/scheduling/ChunkLightTask.java -@@ -0,0 +1,181 @@ -+package io.papermc.paper.chunk.system.scheduling; -+ -+import ca.spottedleaf.concurrentutil.executor.standard.PrioritisedExecutor; -+import ca.spottedleaf.starlight.common.light.StarLightEngine; -+import ca.spottedleaf.starlight.common.light.StarLightInterface; -+import io.papermc.paper.chunk.system.light.LightQueue; -+import net.minecraft.server.level.ServerLevel; -+import net.minecraft.world.level.ChunkPos; -+import net.minecraft.world.level.chunk.ChunkAccess; -+import net.minecraft.world.level.chunk.status.ChunkStatus; -+import net.minecraft.world.level.chunk.ProtoChunk; -+import org.apache.logging.log4j.LogManager; -+import org.apache.logging.log4j.Logger; -+import java.util.function.BooleanSupplier; -+ -+public final class ChunkLightTask extends ChunkProgressionTask { -+ -+ private static final Logger LOGGER = LogManager.getLogger(); -+ -+ protected final ChunkAccess fromChunk; -+ -+ private final LightTaskPriorityHolder priorityHolder; -+ -+ public ChunkLightTask(final ChunkTaskScheduler scheduler, final ServerLevel world, final int chunkX, final int chunkZ, -+ final ChunkAccess chunk, final PrioritisedExecutor.Priority priority) { -+ super(scheduler, world, chunkX, chunkZ); -+ if (!PrioritisedExecutor.Priority.isValidPriority(priority)) { -+ throw new IllegalArgumentException("Invalid priority " + priority); -+ } -+ this.priorityHolder = new LightTaskPriorityHolder(priority, this); -+ this.fromChunk = chunk; -+ } -+ -+ @Override -+ public boolean isScheduled() { -+ return this.priorityHolder.isScheduled(); -+ } -+ -+ @Override -+ public ChunkStatus getTargetStatus() { -+ return ChunkStatus.LIGHT; -+ } -+ -+ @Override -+ public void schedule() { -+ this.priorityHolder.schedule(); -+ } -+ -+ @Override -+ public void cancel() { -+ this.priorityHolder.cancel(); -+ } -+ -+ @Override -+ public PrioritisedExecutor.Priority getPriority() { -+ return this.priorityHolder.getPriority(); -+ } -+ -+ @Override -+ public void lowerPriority(final PrioritisedExecutor.Priority priority) { -+ this.priorityHolder.raisePriority(priority); -+ } -+ -+ @Override -+ public void setPriority(final PrioritisedExecutor.Priority priority) { -+ this.priorityHolder.setPriority(priority); -+ } -+ -+ @Override -+ public void raisePriority(final PrioritisedExecutor.Priority priority) { -+ this.priorityHolder.raisePriority(priority); -+ } -+ -+ private static final class LightTaskPriorityHolder extends PriorityHolder { -+ -+ protected final ChunkLightTask task; -+ -+ protected LightTaskPriorityHolder(final PrioritisedExecutor.Priority priority, final ChunkLightTask task) { -+ super(priority); -+ this.task = task; -+ } -+ -+ @Override -+ protected void cancelScheduled() { -+ final ChunkLightTask task = this.task; -+ task.complete(null, null); -+ } -+ -+ @Override -+ protected PrioritisedExecutor.Priority getScheduledPriority() { -+ final ChunkLightTask task = this.task; -+ return task.world.getChunkSource().getLightEngine().theLightEngine.lightQueue.getPriority(task.chunkX, task.chunkZ); -+ } -+ -+ @Override -+ protected void scheduleTask(final PrioritisedExecutor.Priority priority) { -+ final ChunkLightTask task = this.task; -+ final StarLightInterface starLightInterface = task.world.getChunkSource().getLightEngine().theLightEngine; -+ final LightQueue lightQueue = starLightInterface.lightQueue; -+ lightQueue.queueChunkLightTask(new ChunkPos(task.chunkX, task.chunkZ), new LightTask(starLightInterface, task), priority); -+ lightQueue.setPriority(task.chunkX, task.chunkZ, priority); -+ } -+ -+ @Override -+ protected void lowerPriorityScheduled(final PrioritisedExecutor.Priority priority) { -+ final ChunkLightTask task = this.task; -+ final StarLightInterface starLightInterface = task.world.getChunkSource().getLightEngine().theLightEngine; -+ final LightQueue lightQueue = starLightInterface.lightQueue; -+ lightQueue.lowerPriority(task.chunkX, task.chunkZ, priority); -+ } -+ -+ @Override -+ protected void setPriorityScheduled(final PrioritisedExecutor.Priority priority) { -+ final ChunkLightTask task = this.task; -+ final StarLightInterface starLightInterface = task.world.getChunkSource().getLightEngine().theLightEngine; -+ final LightQueue lightQueue = starLightInterface.lightQueue; -+ lightQueue.setPriority(task.chunkX, task.chunkZ, priority); -+ } -+ -+ @Override -+ protected void raisePriorityScheduled(final PrioritisedExecutor.Priority priority) { -+ final ChunkLightTask task = this.task; -+ final StarLightInterface starLightInterface = task.world.getChunkSource().getLightEngine().theLightEngine; -+ final LightQueue lightQueue = starLightInterface.lightQueue; -+ lightQueue.raisePriority(task.chunkX, task.chunkZ, priority); -+ } -+ } -+ -+ private static final class LightTask implements BooleanSupplier { -+ -+ protected final StarLightInterface lightEngine; -+ protected final ChunkLightTask task; -+ -+ public LightTask(final StarLightInterface lightEngine, final ChunkLightTask task) { -+ this.lightEngine = lightEngine; -+ this.task = task; -+ } -+ -+ @Override -+ public boolean getAsBoolean() { -+ final ChunkLightTask task = this.task; -+ // executed on light thread -+ if (!task.priorityHolder.markExecuting()) { -+ // cancelled -+ return false; -+ } -+ -+ try { -+ final Boolean[] emptySections = StarLightEngine.getEmptySectionsForChunk(task.fromChunk); -+ -+ if (task.fromChunk.isLightCorrect() && task.fromChunk.getStatus().isOrAfter(ChunkStatus.LIGHT)) { -+ this.lightEngine.forceLoadInChunk(task.fromChunk, emptySections); -+ this.lightEngine.checkChunkEdges(task.chunkX, task.chunkZ); -+ } else { -+ task.fromChunk.setLightCorrect(false); -+ this.lightEngine.lightChunk(task.fromChunk, emptySections); -+ task.fromChunk.setLightCorrect(true); -+ } -+ // we need to advance status -+ if (task.fromChunk instanceof ProtoChunk chunk && chunk.getStatus() == ChunkStatus.LIGHT.getParent()) { -+ chunk.setStatus(ChunkStatus.LIGHT); -+ } -+ } catch (final Throwable thr) { -+ if (!(thr instanceof ThreadDeath)) { -+ LOGGER.fatal("Failed to light chunk " + task.fromChunk.getPos().toString() + " in world '" + this.lightEngine.getWorld().getWorld().getName() + "'", thr); -+ } -+ -+ task.complete(null, thr); -+ -+ if (thr instanceof ThreadDeath) { -+ throw (ThreadDeath)thr; -+ } -+ -+ return true; -+ } -+ -+ task.complete(task.fromChunk, null); -+ return true; -+ } -+ } -+} -diff --git a/src/main/java/io/papermc/paper/chunk/system/scheduling/ChunkLoadTask.java b/src/main/java/io/papermc/paper/chunk/system/scheduling/ChunkLoadTask.java -new file mode 100644 -index 0000000000000000000000000000000000000000..5ff994d5af24b0bdd7b3a16e245b2c4100bef3f0 ---- /dev/null -+++ b/src/main/java/io/papermc/paper/chunk/system/scheduling/ChunkLoadTask.java -@@ -0,0 +1,484 @@ -+package io.papermc.paper.chunk.system.scheduling; -+ -+import ca.spottedleaf.concurrentutil.collection.MultiThreadedQueue; -+import ca.spottedleaf.concurrentutil.executor.standard.PrioritisedExecutor; -+import ca.spottedleaf.concurrentutil.lock.ReentrantAreaLock; -+import ca.spottedleaf.concurrentutil.util.ConcurrentUtil; -+import ca.spottedleaf.dataconverter.minecraft.MCDataConverter; -+import ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry; -+import com.mojang.logging.LogUtils; -+import io.papermc.paper.chunk.system.io.RegionFileIOThread; -+import io.papermc.paper.chunk.system.poi.PoiChunk; -+import net.minecraft.SharedConstants; -+import net.minecraft.core.registries.Registries; -+import net.minecraft.nbt.CompoundTag; -+import net.minecraft.server.level.ChunkMap; -+import net.minecraft.server.level.ServerLevel; -+import net.minecraft.world.level.ChunkPos; -+import net.minecraft.world.level.chunk.ChunkAccess; -+import net.minecraft.world.level.chunk.status.ChunkStatus; -+import net.minecraft.world.level.chunk.ProtoChunk; -+import net.minecraft.world.level.chunk.UpgradeData; -+import net.minecraft.world.level.chunk.storage.ChunkSerializer; -+import net.minecraft.world.level.chunk.storage.EntityStorage; -+import net.minecraft.world.level.levelgen.blending.BlendingData; -+import org.slf4j.Logger; -+import java.lang.invoke.VarHandle; -+import java.util.Map; -+import java.util.concurrent.atomic.AtomicInteger; -+import java.util.function.Consumer; -+ -+public final class ChunkLoadTask extends ChunkProgressionTask { -+ -+ private static final Logger LOGGER = LogUtils.getClassLogger(); -+ -+ private final NewChunkHolder chunkHolder; -+ private final ChunkDataLoadTask loadTask; -+ -+ private volatile boolean cancelled; -+ private NewChunkHolder.GenericDataLoadTaskCallback entityLoadTask; -+ private NewChunkHolder.GenericDataLoadTaskCallback poiLoadTask; -+ private GenericDataLoadTask.TaskResult loadResult; -+ private final AtomicInteger taskCountToComplete = new AtomicInteger(3); // one for poi, one for entity, and one for chunk data -+ -+ protected ChunkLoadTask(final ChunkTaskScheduler scheduler, final ServerLevel world, final int chunkX, final int chunkZ, -+ final NewChunkHolder chunkHolder, final PrioritisedExecutor.Priority priority) { -+ super(scheduler, world, chunkX, chunkZ); -+ this.chunkHolder = chunkHolder; -+ this.loadTask = new ChunkDataLoadTask(scheduler, world, chunkX, chunkZ, priority); -+ this.loadTask.addCallback((final GenericDataLoadTask.TaskResult result) -> { -+ ChunkLoadTask.this.loadResult = result; // must be before getAndDecrement -+ ChunkLoadTask.this.tryCompleteLoad(); -+ }); -+ } -+ -+ private void tryCompleteLoad() { -+ if (this.taskCountToComplete.decrementAndGet() == 0) { -+ final GenericDataLoadTask.TaskResult result = this.cancelled ? null : this.loadResult; // only after the getAndDecrement -+ ChunkLoadTask.this.complete(result == null ? null : result.left(), result == null ? null : result.right()); -+ } -+ } -+ -+ @Override -+ public ChunkStatus getTargetStatus() { -+ return ChunkStatus.EMPTY; -+ } -+ -+ private boolean scheduled; -+ -+ @Override -+ public boolean isScheduled() { -+ return this.scheduled; -+ } -+ -+ @Override -+ public void schedule() { -+ final NewChunkHolder.GenericDataLoadTaskCallback entityLoadTask; -+ final NewChunkHolder.GenericDataLoadTaskCallback poiLoadTask; -+ -+ final Consumer> scheduleLoadTask = (final GenericDataLoadTask.TaskResult result) -> { -+ ChunkLoadTask.this.tryCompleteLoad(); -+ }; -+ -+ // NOTE: it is IMPOSSIBLE for getOrLoadEntityData/getOrLoadPoiData to complete synchronously, because -+ // they must schedule a task to off main or to on main to complete -+ final ReentrantAreaLock.Node schedulingLock = this.scheduler.schedulingLockArea.lock(this.chunkX, this.chunkZ); -+ try { -+ if (this.scheduled) { -+ throw new IllegalStateException("schedule() called twice"); -+ } -+ this.scheduled = true; -+ if (this.cancelled) { -+ return; -+ } -+ if (!this.chunkHolder.isEntityChunkNBTLoaded()) { -+ entityLoadTask = this.chunkHolder.getOrLoadEntityData((Consumer)scheduleLoadTask); -+ } else { -+ entityLoadTask = null; -+ this.taskCountToComplete.getAndDecrement(); // we know the chunk load is not done here, as it is not scheduled -+ } -+ -+ if (!this.chunkHolder.isPoiChunkLoaded()) { -+ poiLoadTask = this.chunkHolder.getOrLoadPoiData((Consumer)scheduleLoadTask); -+ } else { -+ poiLoadTask = null; -+ this.taskCountToComplete.getAndDecrement(); // we know the chunk load is not done here, as it is not scheduled -+ } -+ -+ this.entityLoadTask = entityLoadTask; -+ this.poiLoadTask = poiLoadTask; -+ } finally { -+ this.scheduler.schedulingLockArea.unlock(schedulingLock); -+ } -+ -+ if (entityLoadTask != null) { -+ entityLoadTask.schedule(); -+ } -+ -+ if (poiLoadTask != null) { -+ poiLoadTask.schedule(); -+ } -+ -+ this.loadTask.schedule(false); -+ } -+ -+ @Override -+ public void cancel() { -+ // must be before load task access, so we can synchronise with the writes to the fields -+ final boolean scheduled; -+ final ReentrantAreaLock.Node schedulingLock = this.scheduler.schedulingLockArea.lock(this.chunkX, this.chunkZ); -+ try { -+ // must read field here, as it may be written later conucrrently - -+ // we need to know if we scheduled _before_ cancellation -+ scheduled = this.scheduled; -+ this.cancelled = true; -+ } finally { -+ this.scheduler.schedulingLockArea.unlock(schedulingLock); -+ } -+ -+ /* -+ Note: The entityLoadTask/poiLoadTask do not complete when cancelled, -+ so we need to manually try to complete in those cases -+ It is also important to note that we set the cancelled field first, just in case -+ the chunk load task attempts to complete with a non-null value -+ */ -+ -+ if (scheduled) { -+ // since we scheduled, we need to cancel the tasks -+ if (this.entityLoadTask != null) { -+ if (this.entityLoadTask.cancel()) { -+ this.tryCompleteLoad(); -+ } -+ } -+ if (this.poiLoadTask != null) { -+ if (this.poiLoadTask.cancel()) { -+ this.tryCompleteLoad(); -+ } -+ } -+ } else { -+ // since nothing was scheduled, we need to decrement the task count here ourselves -+ -+ // for entity load task -+ this.tryCompleteLoad(); -+ -+ // for poi load task -+ this.tryCompleteLoad(); -+ } -+ this.loadTask.cancel(); -+ } -+ -+ @Override -+ public PrioritisedExecutor.Priority getPriority() { -+ return this.loadTask.getPriority(); -+ } -+ -+ @Override -+ public void lowerPriority(final PrioritisedExecutor.Priority priority) { -+ final EntityDataLoadTask entityLoad = this.chunkHolder.getEntityDataLoadTask(); -+ if (entityLoad != null) { -+ entityLoad.lowerPriority(priority); -+ } -+ -+ final PoiDataLoadTask poiLoad = this.chunkHolder.getPoiDataLoadTask(); -+ -+ if (poiLoad != null) { -+ poiLoad.lowerPriority(priority); -+ } -+ -+ this.loadTask.lowerPriority(priority); -+ } -+ -+ @Override -+ public void setPriority(final PrioritisedExecutor.Priority priority) { -+ final EntityDataLoadTask entityLoad = this.chunkHolder.getEntityDataLoadTask(); -+ if (entityLoad != null) { -+ entityLoad.setPriority(priority); -+ } -+ -+ final PoiDataLoadTask poiLoad = this.chunkHolder.getPoiDataLoadTask(); -+ -+ if (poiLoad != null) { -+ poiLoad.setPriority(priority); -+ } -+ -+ this.loadTask.setPriority(priority); -+ } -+ -+ @Override -+ public void raisePriority(final PrioritisedExecutor.Priority priority) { -+ final EntityDataLoadTask entityLoad = this.chunkHolder.getEntityDataLoadTask(); -+ if (entityLoad != null) { -+ entityLoad.raisePriority(priority); -+ } -+ -+ final PoiDataLoadTask poiLoad = this.chunkHolder.getPoiDataLoadTask(); -+ -+ if (poiLoad != null) { -+ poiLoad.raisePriority(priority); -+ } -+ -+ this.loadTask.raisePriority(priority); -+ } -+ -+ protected static abstract class CallbackDataLoadTask extends GenericDataLoadTask { -+ -+ private TaskResult result; -+ private final MultiThreadedQueue>> waiters = new MultiThreadedQueue<>(); -+ -+ protected volatile boolean completed; -+ protected static final VarHandle COMPLETED_HANDLE = ConcurrentUtil.getVarHandle(CallbackDataLoadTask.class, "completed", boolean.class); -+ -+ protected CallbackDataLoadTask(final ChunkTaskScheduler scheduler, final ServerLevel world, final int chunkX, -+ final int chunkZ, final RegionFileIOThread.RegionFileType type, -+ final PrioritisedExecutor.Priority priority) { -+ super(scheduler, world, chunkX, chunkZ, type, priority); -+ } -+ -+ public void addCallback(final Consumer> consumer) { -+ if (!this.waiters.add(consumer)) { -+ try { -+ consumer.accept(this.result); -+ } catch (final Throwable throwable) { -+ this.scheduler.unrecoverableChunkSystemFailure(this.chunkX, this.chunkZ, Map.of( -+ "Consumer", ChunkTaskScheduler.stringIfNull(consumer), -+ "Completed throwable", ChunkTaskScheduler.stringIfNull(this.result.right()) -+ ), throwable); -+ if (throwable instanceof ThreadDeath) { -+ throw (ThreadDeath)throwable; -+ } -+ } -+ } -+ } -+ -+ @Override -+ protected void onComplete(final TaskResult result) { -+ if ((boolean)COMPLETED_HANDLE.getAndSet((CallbackDataLoadTask)this, (boolean)true)) { -+ throw new IllegalStateException("Already completed"); -+ } -+ this.result = result; -+ Consumer> consumer; -+ while ((consumer = this.waiters.pollOrBlockAdds()) != null) { -+ try { -+ consumer.accept(result); -+ } catch (final Throwable throwable) { -+ this.scheduler.unrecoverableChunkSystemFailure(this.chunkX, this.chunkZ, Map.of( -+ "Consumer", ChunkTaskScheduler.stringIfNull(consumer), -+ "Completed throwable", ChunkTaskScheduler.stringIfNull(result.right()) -+ ), throwable); -+ if (throwable instanceof ThreadDeath) { -+ throw (ThreadDeath)throwable; -+ } -+ return; -+ } -+ } -+ } -+ } -+ -+ public static final class ChunkDataLoadTask extends CallbackDataLoadTask { -+ protected ChunkDataLoadTask(final ChunkTaskScheduler scheduler, final ServerLevel world, final int chunkX, -+ final int chunkZ, final PrioritisedExecutor.Priority priority) { -+ super(scheduler, world, chunkX, chunkZ, RegionFileIOThread.RegionFileType.CHUNK_DATA, priority); -+ } -+ -+ @Override -+ protected boolean hasOffMain() { -+ return true; -+ } -+ -+ @Override -+ protected boolean hasOnMain() { -+ return false; -+ } -+ -+ @Override -+ protected PrioritisedExecutor.PrioritisedTask createOffMain(final Runnable run, final PrioritisedExecutor.Priority priority) { -+ return this.scheduler.loadExecutor.createTask(run, priority); -+ } -+ -+ @Override -+ protected PrioritisedExecutor.PrioritisedTask createOnMain(final Runnable run, final PrioritisedExecutor.Priority priority) { -+ throw new UnsupportedOperationException(); -+ } -+ -+ @Override -+ protected TaskResult completeOnMainOffMain(final ChunkAccess data, final Throwable throwable) { -+ throw new UnsupportedOperationException(); -+ } -+ -+ private ProtoChunk getEmptyChunk() { -+ return new ProtoChunk( -+ new ChunkPos(this.chunkX, this.chunkZ), UpgradeData.EMPTY, this.world, -+ this.world.registryAccess().registryOrThrow(Registries.BIOME), (BlendingData)null -+ ); -+ } -+ -+ @Override -+ protected TaskResult runOffMain(final CompoundTag data, final Throwable throwable) { -+ if (throwable != null) { -+ LOGGER.error("Failed to load chunk data for task: " + this.toString() + ", chunk data will be lost", throwable); -+ return new TaskResult<>(this.getEmptyChunk(), null); -+ } -+ -+ if (data == null) { -+ return new TaskResult<>(this.getEmptyChunk(), null); -+ } -+ -+ // need to convert data, and then deserialize it -+ -+ try { -+ final ChunkPos chunkPos = new ChunkPos(this.chunkX, this.chunkZ); -+ final ChunkMap chunkMap = this.world.getChunkSource().chunkMap; -+ // run converters -+ // note: upgradeChunkTag copies the data already -+ final CompoundTag converted = chunkMap.upgradeChunkTag( -+ this.world.getTypeKey(), chunkMap.overworldDataStorage, data, chunkMap.generator.getTypeNameForDataFixer(), -+ chunkPos, this.world -+ ); -+ // deserialize -+ final ChunkSerializer.InProgressChunkHolder chunkHolder = ChunkSerializer.readInProgressChunkHolder( -+ this.world, chunkMap.getPoiManager(), chunkPos, converted -+ ); -+ -+ return new TaskResult<>(chunkHolder.protoChunk, null); -+ } catch (final ThreadDeath death) { -+ throw death; -+ } catch (final Throwable thr2) { -+ LOGGER.error("Failed to parse chunk data for task: " + this.toString() + ", chunk data will be lost", thr2); -+ return new TaskResult<>(this.getEmptyChunk(), null); -+ } -+ } -+ -+ @Override -+ protected TaskResult runOnMain(final ChunkAccess data, final Throwable throwable) { -+ throw new UnsupportedOperationException(); -+ } -+ } -+ -+ public static final class PoiDataLoadTask extends CallbackDataLoadTask { -+ public PoiDataLoadTask(final ChunkTaskScheduler scheduler, final ServerLevel world, final int chunkX, -+ final int chunkZ, final PrioritisedExecutor.Priority priority) { -+ super(scheduler, world, chunkX, chunkZ, RegionFileIOThread.RegionFileType.POI_DATA, priority); -+ } -+ -+ @Override -+ protected boolean hasOffMain() { -+ return true; -+ } -+ -+ @Override -+ protected boolean hasOnMain() { -+ return false; -+ } -+ -+ @Override -+ protected PrioritisedExecutor.PrioritisedTask createOffMain(final Runnable run, final PrioritisedExecutor.Priority priority) { -+ return this.scheduler.loadExecutor.createTask(run, priority); -+ } -+ -+ @Override -+ protected PrioritisedExecutor.PrioritisedTask createOnMain(final Runnable run, final PrioritisedExecutor.Priority priority) { -+ throw new UnsupportedOperationException(); -+ } -+ -+ @Override -+ protected TaskResult completeOnMainOffMain(final PoiChunk data, final Throwable throwable) { -+ throw new UnsupportedOperationException(); -+ } -+ -+ @Override -+ protected TaskResult runOffMain(CompoundTag data, final Throwable throwable) { -+ if (throwable != null) { -+ LOGGER.error("Failed to load poi data for task: " + this.toString() + ", poi data will be lost", throwable); -+ return new TaskResult<>(PoiChunk.empty(this.world, this.chunkX, this.chunkZ), null); -+ } -+ -+ if (data == null || data.isEmpty()) { -+ // nothing to do -+ return new TaskResult<>(PoiChunk.empty(this.world, this.chunkX, this.chunkZ), null); -+ } -+ -+ try { -+ data = data.copy(); // coming from the I/O thread, so we need to copy -+ // run converters -+ final int dataVersion = !data.contains(SharedConstants.DATA_VERSION_TAG, net.minecraft.nbt.Tag.TAG_ANY_NUMERIC) ? 1945 : data.getInt(SharedConstants.DATA_VERSION_TAG); -+ final CompoundTag converted = MCDataConverter.convertTag( -+ MCTypeRegistry.POI_CHUNK, data, dataVersion, SharedConstants.getCurrentVersion().getDataVersion().getVersion() -+ ); -+ -+ // now we need to parse it -+ return new TaskResult<>(PoiChunk.parse(this.world, this.chunkX, this.chunkZ, converted), null); -+ } catch (final ThreadDeath death) { -+ throw death; -+ } catch (final Throwable thr2) { -+ LOGGER.error("Failed to run parse poi data for task: " + this.toString() + ", poi data will be lost", thr2); -+ return new TaskResult<>(PoiChunk.empty(this.world, this.chunkX, this.chunkZ), null); -+ } -+ } -+ -+ @Override -+ protected TaskResult runOnMain(final PoiChunk data, final Throwable throwable) { -+ throw new UnsupportedOperationException(); -+ } -+ } -+ -+ public static final class EntityDataLoadTask extends CallbackDataLoadTask { -+ -+ public EntityDataLoadTask(final ChunkTaskScheduler scheduler, final ServerLevel world, final int chunkX, -+ final int chunkZ, final PrioritisedExecutor.Priority priority) { -+ super(scheduler, world, chunkX, chunkZ, RegionFileIOThread.RegionFileType.ENTITY_DATA, priority); -+ } -+ -+ @Override -+ protected boolean hasOffMain() { -+ return true; -+ } -+ -+ @Override -+ protected boolean hasOnMain() { -+ return false; -+ } -+ -+ @Override -+ protected PrioritisedExecutor.PrioritisedTask createOffMain(final Runnable run, final PrioritisedExecutor.Priority priority) { -+ return this.scheduler.loadExecutor.createTask(run, priority); -+ } -+ -+ @Override -+ protected PrioritisedExecutor.PrioritisedTask createOnMain(final Runnable run, final PrioritisedExecutor.Priority priority) { -+ throw new UnsupportedOperationException(); -+ } -+ -+ @Override -+ protected TaskResult completeOnMainOffMain(final CompoundTag data, final Throwable throwable) { -+ throw new UnsupportedOperationException(); -+ } -+ -+ @Override -+ protected TaskResult runOffMain(final CompoundTag data, final Throwable throwable) { -+ if (throwable != null) { -+ LOGGER.error("Failed to load entity data for task: " + this.toString() + ", entity data will be lost", throwable); -+ return new TaskResult<>(null, null); -+ } -+ -+ if (data == null || data.isEmpty()) { -+ // nothing to do -+ return new TaskResult<>(null, null); -+ } -+ -+ try { -+ // note: data comes from the I/O thread, so we need to copy it -+ return new TaskResult<>(EntityStorage.upgradeChunkTag(data.copy()), null); -+ } catch (final ThreadDeath death) { -+ throw death; -+ } catch (final Throwable thr2) { -+ LOGGER.error("Failed to run converters for entity data for task: " + this.toString() + ", entity data will be lost", thr2); -+ return new TaskResult<>(null, thr2); -+ } -+ } -+ -+ @Override -+ protected TaskResult runOnMain(final CompoundTag data, final Throwable throwable) { -+ throw new UnsupportedOperationException(); -+ } -+ } -+} -diff --git a/src/main/java/io/papermc/paper/chunk/system/scheduling/ChunkProgressionTask.java b/src/main/java/io/papermc/paper/chunk/system/scheduling/ChunkProgressionTask.java -new file mode 100644 -index 0000000000000000000000000000000000000000..b2341328bb22f08836ef18785dc27393a36ce8d6 ---- /dev/null -+++ b/src/main/java/io/papermc/paper/chunk/system/scheduling/ChunkProgressionTask.java -@@ -0,0 +1,105 @@ -+package io.papermc.paper.chunk.system.scheduling; -+ -+import ca.spottedleaf.concurrentutil.collection.MultiThreadedQueue; -+import ca.spottedleaf.concurrentutil.executor.standard.PrioritisedExecutor; -+import ca.spottedleaf.concurrentutil.util.ConcurrentUtil; -+import net.minecraft.server.level.ServerLevel; -+import net.minecraft.world.level.chunk.ChunkAccess; -+import net.minecraft.world.level.chunk.status.ChunkStatus; -+import java.lang.invoke.VarHandle; -+import java.util.Map; -+import java.util.function.BiConsumer; -+ -+public abstract class ChunkProgressionTask { -+ -+ private final MultiThreadedQueue> waiters = new MultiThreadedQueue<>(); -+ private ChunkAccess completedChunk; -+ private Throwable completedThrowable; -+ -+ protected final ChunkTaskScheduler scheduler; -+ protected final ServerLevel world; -+ protected final int chunkX; -+ protected final int chunkZ; -+ -+ protected volatile boolean completed; -+ protected static final VarHandle COMPLETED_HANDLE = ConcurrentUtil.getVarHandle(ChunkProgressionTask.class, "completed", boolean.class); -+ -+ protected ChunkProgressionTask(final ChunkTaskScheduler scheduler, final ServerLevel world, final int chunkX, final int chunkZ) { -+ this.scheduler = scheduler; -+ this.world = world; -+ this.chunkX = chunkX; -+ this.chunkZ = chunkZ; -+ } -+ -+ // Used only for debug json -+ public abstract boolean isScheduled(); -+ -+ // Note: It is the responsibility of the task to set the chunk's status once it has completed -+ public abstract ChunkStatus getTargetStatus(); -+ -+ /* Only executed once */ -+ /* Implementations must be prepared to handle cases where cancel() is called before schedule() */ -+ public abstract void schedule(); -+ -+ /* May be called multiple times */ -+ public abstract void cancel(); -+ -+ public abstract PrioritisedExecutor.Priority getPriority(); -+ -+ /* Schedule lock is always held for the priority update calls */ -+ -+ public abstract void lowerPriority(final PrioritisedExecutor.Priority priority); -+ -+ public abstract void setPriority(final PrioritisedExecutor.Priority priority); -+ -+ public abstract void raisePriority(final PrioritisedExecutor.Priority priority); -+ -+ public final void onComplete(final BiConsumer onComplete) { -+ if (!this.waiters.add(onComplete)) { -+ try { -+ onComplete.accept(this.completedChunk, this.completedThrowable); -+ } catch (final Throwable throwable) { -+ this.scheduler.unrecoverableChunkSystemFailure(this.chunkX, this.chunkZ, Map.of( -+ "Consumer", ChunkTaskScheduler.stringIfNull(onComplete), -+ "Completed throwable", ChunkTaskScheduler.stringIfNull(this.completedThrowable) -+ ), throwable); -+ if (throwable instanceof ThreadDeath) { -+ throw (ThreadDeath)throwable; -+ } -+ } -+ } -+ } -+ -+ protected final void complete(final ChunkAccess chunk, final Throwable throwable) { -+ try { -+ this.complete0(chunk, throwable); -+ } catch (final Throwable thr2) { -+ this.scheduler.unrecoverableChunkSystemFailure(this.chunkX, this.chunkZ, Map.of( -+ "Completed throwable", ChunkTaskScheduler.stringIfNull(throwable) -+ ), thr2); -+ if (thr2 instanceof ThreadDeath) { -+ throw (ThreadDeath)thr2; -+ } -+ } -+ } -+ -+ private void complete0(final ChunkAccess chunk, final Throwable throwable) { -+ if ((boolean)COMPLETED_HANDLE.getAndSet((ChunkProgressionTask)this, (boolean)true)) { -+ throw new IllegalStateException("Already completed"); -+ } -+ this.completedChunk = chunk; -+ this.completedThrowable = throwable; -+ -+ BiConsumer consumer; -+ while ((consumer = this.waiters.pollOrBlockAdds()) != null) { -+ consumer.accept(chunk, throwable); -+ } -+ } -+ -+ @Override -+ public String toString() { -+ return "ChunkProgressionTask{class: " + this.getClass().getName() + ", for world: " + this.world.getWorld().getName() + -+ ", chunk: (" + this.chunkX + "," + this.chunkZ + "), hashcode: " + System.identityHashCode(this) + ", priority: " + this.getPriority() + -+ ", status: " + this.getTargetStatus().toString() + ", scheduled: " + this.isScheduled() + "}"; -+ } -+} -diff --git a/src/main/java/io/papermc/paper/chunk/system/scheduling/ChunkQueue.java b/src/main/java/io/papermc/paper/chunk/system/scheduling/ChunkQueue.java -new file mode 100644 -index 0000000000000000000000000000000000000000..4cc1b3ba6d093a9683dbd8b7fe76106ae391e019 ---- /dev/null -+++ b/src/main/java/io/papermc/paper/chunk/system/scheduling/ChunkQueue.java -@@ -0,0 +1,160 @@ -+package io.papermc.paper.chunk.system.scheduling; -+ -+import it.unimi.dsi.fastutil.HashCommon; -+import it.unimi.dsi.fastutil.longs.LongLinkedOpenHashSet; -+import java.util.ArrayList; -+import java.util.List; -+import java.util.Map; -+import java.util.concurrent.ConcurrentHashMap; -+import java.util.concurrent.atomic.AtomicLong; -+ -+public final class ChunkQueue { -+ -+ public final int coordinateShift; -+ private final AtomicLong orderGenerator = new AtomicLong(); -+ private final ConcurrentHashMap unloadSections = new ConcurrentHashMap<>(); -+ -+ /* -+ * Note: write operations do not occur in parallel for any given section. -+ * Note: coordinateShift <= region shift in order for retrieveForCurrentRegion() to function correctly -+ */ -+ -+ public ChunkQueue(final int coordinateShift) { -+ this.coordinateShift = coordinateShift; -+ } -+ -+ public static record SectionToUnload(int sectionX, int sectionZ, Coordinate coord, long order, int count) {} -+ -+ public List retrieveForAllRegions() { -+ final List ret = new ArrayList<>(); -+ -+ for (final Map.Entry entry : this.unloadSections.entrySet()) { -+ final Coordinate coord = entry.getKey(); -+ final long key = coord.key; -+ final UnloadSection section = entry.getValue(); -+ final int sectionX = Coordinate.x(key); -+ final int sectionZ = Coordinate.z(key); -+ -+ ret.add(new SectionToUnload(sectionX, sectionZ, coord, section.order, section.chunks.size())); -+ } -+ -+ ret.sort((final SectionToUnload s1, final SectionToUnload s2) -> { -+ return Long.compare(s1.order, s2.order); -+ }); -+ -+ return ret; -+ } -+ -+ public UnloadSection getSectionUnsynchronized(final int sectionX, final int sectionZ) { -+ final Coordinate coordinate = new Coordinate(Coordinate.key(sectionX, sectionZ)); -+ return this.unloadSections.get(coordinate); -+ } -+ -+ public UnloadSection removeSection(final int sectionX, final int sectionZ) { -+ final Coordinate coordinate = new Coordinate(Coordinate.key(sectionX, sectionZ)); -+ return this.unloadSections.remove(coordinate); -+ } -+ -+ // write operation -+ public boolean addChunk(final int chunkX, final int chunkZ) { -+ final int shift = this.coordinateShift; -+ final int sectionX = chunkX >> shift; -+ final int sectionZ = chunkZ >> shift; -+ final Coordinate coordinate = new Coordinate(Coordinate.key(sectionX, sectionZ)); -+ final long chunkKey = Coordinate.key(chunkX, chunkZ); -+ -+ UnloadSection section = this.unloadSections.get(coordinate); -+ if (section == null) { -+ section = new UnloadSection(this.orderGenerator.getAndIncrement()); -+ // write operations do not occur in parallel for a given section -+ this.unloadSections.put(coordinate, section); -+ } -+ -+ return section.chunks.add(chunkKey); -+ } -+ -+ // write operation -+ public boolean removeChunk(final int chunkX, final int chunkZ) { -+ final int shift = this.coordinateShift; -+ final int sectionX = chunkX >> shift; -+ final int sectionZ = chunkZ >> shift; -+ final Coordinate coordinate = new Coordinate(Coordinate.key(sectionX, sectionZ)); -+ final long chunkKey = Coordinate.key(chunkX, chunkZ); -+ -+ final UnloadSection section = this.unloadSections.get(coordinate); -+ -+ if (section == null) { -+ return false; -+ } -+ -+ if (!section.chunks.remove(chunkKey)) { -+ return false; -+ } -+ -+ if (section.chunks.isEmpty()) { -+ this.unloadSections.remove(coordinate); -+ } -+ -+ return true; -+ } -+ -+ public static final class UnloadSection { -+ -+ public final long order; -+ public final LongLinkedOpenHashSet chunks = new LongLinkedOpenHashSet(); -+ -+ public UnloadSection(final long order) { -+ this.order = order; -+ } -+ } -+ -+ private static final class Coordinate implements Comparable { -+ -+ public final long key; -+ -+ public Coordinate(final long key) { -+ this.key = key; -+ } -+ -+ public Coordinate(final int x, final int z) { -+ this.key = key(x, z); -+ } -+ -+ public static long key(final int x, final int z) { -+ return ((long)z << 32) | (x & 0xFFFFFFFFL); -+ } -+ -+ public static int x(final long key) { -+ return (int)key; -+ } -+ -+ public static int z(final long key) { -+ return (int)(key >>> 32); -+ } -+ -+ @Override -+ public int hashCode() { -+ return (int)HashCommon.mix(this.key); -+ } -+ -+ @Override -+ public boolean equals(final Object obj) { -+ if (this == obj) { -+ return true; -+ } -+ -+ if (!(obj instanceof Coordinate other)) { -+ return false; -+ } -+ -+ return this.key == other.key; -+ } -+ -+ // This class is intended for HashMap/ConcurrentHashMap usage, which do treeify bin nodes if the chain -+ // is too large. So we should implement compareTo to help. -+ @Override -+ public int compareTo(final Coordinate other) { -+ return Long.compare(this.key, other.key); -+ } -+ } -+} -diff --git a/src/main/java/io/papermc/paper/chunk/system/scheduling/ChunkTaskScheduler.java b/src/main/java/io/papermc/paper/chunk/system/scheduling/ChunkTaskScheduler.java -new file mode 100644 -index 0000000000000000000000000000000000000000..049e20407033073b06fcdeb46c38485f4926d778 ---- /dev/null -+++ b/src/main/java/io/papermc/paper/chunk/system/scheduling/ChunkTaskScheduler.java -@@ -0,0 +1,883 @@ -+package io.papermc.paper.chunk.system.scheduling; -+ -+import ca.spottedleaf.concurrentutil.executor.standard.PrioritisedExecutor; -+import ca.spottedleaf.concurrentutil.executor.standard.PrioritisedThreadPool; -+import ca.spottedleaf.concurrentutil.executor.standard.PrioritisedThreadedTaskQueue; -+import ca.spottedleaf.concurrentutil.lock.ReentrantAreaLock; -+import ca.spottedleaf.concurrentutil.util.ConcurrentUtil; -+import com.mojang.logging.LogUtils; -+import io.papermc.paper.chunk.system.io.RegionFileIOThread; -+import io.papermc.paper.chunk.system.scheduling.queue.RadiusAwarePrioritisedExecutor; -+import io.papermc.paper.configuration.GlobalConfiguration; -+import io.papermc.paper.util.CoordinateUtils; -+import io.papermc.paper.util.TickThread; -+import java.util.function.BooleanSupplier; -+import net.minecraft.CrashReport; -+import net.minecraft.CrashReportCategory; -+import net.minecraft.ReportedException; -+import io.papermc.paper.util.MCUtil; -+import net.minecraft.server.MinecraftServer; -+import net.minecraft.server.level.ChunkMap; -+import net.minecraft.server.level.FullChunkStatus; -+import net.minecraft.server.level.ServerLevel; -+import net.minecraft.server.level.TicketType; -+import net.minecraft.world.level.ChunkPos; -+import net.minecraft.world.level.chunk.ChunkAccess; -+import net.minecraft.world.level.chunk.status.ChunkStatus; -+import net.minecraft.world.level.chunk.LevelChunk; -+import org.bukkit.Bukkit; -+import org.slf4j.Logger; -+import java.io.File; -+import java.util.ArrayDeque; -+import java.util.ArrayList; -+import java.util.Arrays; -+import java.util.Collections; -+import java.util.List; -+import java.util.Map; -+import java.util.Objects; -+import java.util.concurrent.atomic.AtomicBoolean; -+import java.util.concurrent.atomic.AtomicLong; -+import java.util.function.Consumer; -+ -+public final class ChunkTaskScheduler { -+ -+ private static final Logger LOGGER = LogUtils.getClassLogger(); -+ -+ static int newChunkSystemIOThreads; -+ static int newChunkSystemWorkerThreads; -+ static int newChunkSystemGenParallelism; -+ static int newChunkSystemLoadParallelism; -+ -+ public static ca.spottedleaf.concurrentutil.executor.standard.PrioritisedThreadPool workerThreads; -+ -+ private static boolean initialised = false; -+ -+ public static void init(final GlobalConfiguration.ChunkSystem config) { -+ if (initialised) { -+ return; -+ } -+ initialised = true; -+ newChunkSystemIOThreads = config.ioThreads; -+ newChunkSystemWorkerThreads = config.workerThreads; -+ if (newChunkSystemIOThreads < 0) { -+ newChunkSystemIOThreads = 1; -+ } else { -+ newChunkSystemIOThreads = Math.max(1, newChunkSystemIOThreads); -+ } -+ int defaultWorkerThreads = Runtime.getRuntime().availableProcessors() / 2; -+ if (defaultWorkerThreads <= 4) { -+ defaultWorkerThreads = defaultWorkerThreads <= 3 ? 1 : 2; -+ } else { -+ defaultWorkerThreads = defaultWorkerThreads / 2; -+ } -+ defaultWorkerThreads = Integer.getInteger("Paper.WorkerThreadCount", Integer.valueOf(defaultWorkerThreads)); -+ -+ if (newChunkSystemWorkerThreads < 0) { -+ newChunkSystemWorkerThreads = defaultWorkerThreads; -+ } else { -+ newChunkSystemWorkerThreads = Math.max(1, newChunkSystemWorkerThreads); -+ } -+ -+ String newChunkSystemGenParallelism = config.genParallelism; -+ if (newChunkSystemGenParallelism.equalsIgnoreCase("default")) { -+ newChunkSystemGenParallelism = "true"; -+ } -+ boolean useParallelGen; -+ if (newChunkSystemGenParallelism.equalsIgnoreCase("on") || newChunkSystemGenParallelism.equalsIgnoreCase("enabled") -+ || newChunkSystemGenParallelism.equalsIgnoreCase("true")) { -+ useParallelGen = true; -+ } else if (newChunkSystemGenParallelism.equalsIgnoreCase("off") || newChunkSystemGenParallelism.equalsIgnoreCase("disabled") -+ || newChunkSystemGenParallelism.equalsIgnoreCase("false")) { -+ useParallelGen = false; -+ } else { -+ throw new IllegalStateException("Invalid option for gen-parallelism: must be one of [on, off, enabled, disabled, true, false, default]"); -+ } -+ -+ ChunkTaskScheduler.newChunkSystemGenParallelism = useParallelGen ? newChunkSystemWorkerThreads : 1; -+ ChunkTaskScheduler.newChunkSystemLoadParallelism = newChunkSystemWorkerThreads; -+ -+ RegionFileIOThread.init(newChunkSystemIOThreads); -+ workerThreads = new ca.spottedleaf.concurrentutil.executor.standard.PrioritisedThreadPool( -+ "Paper Chunk System Worker Pool", newChunkSystemWorkerThreads, -+ (final Thread thread, final Integer id) -> { -+ thread.setPriority(Thread.NORM_PRIORITY - 2); -+ thread.setName("Tuinity Chunk System Worker #" + id.intValue()); -+ thread.setUncaughtExceptionHandler(io.papermc.paper.chunk.system.scheduling.NewChunkHolder.CHUNKSYSTEM_UNCAUGHT_EXCEPTION_HANDLER); -+ }, (long)(20.0e6)); // 20ms -+ -+ LOGGER.info("Chunk system is using " + newChunkSystemIOThreads + " I/O threads, " + newChunkSystemWorkerThreads + " worker threads, and gen parallelism of " + ChunkTaskScheduler.newChunkSystemGenParallelism + " threads"); -+ } -+ -+ public final ServerLevel world; -+ public final PrioritisedThreadPool workers; -+ public final RadiusAwarePrioritisedExecutor radiusAwareScheduler; -+ public final PrioritisedThreadPool.PrioritisedPoolExecutor parallelGenExecutor; -+ private final PrioritisedThreadPool.PrioritisedPoolExecutor radiusAwareGenExecutor; -+ public final PrioritisedThreadPool.PrioritisedPoolExecutor loadExecutor; -+ -+ private final PrioritisedThreadedTaskQueue mainThreadExecutor = new PrioritisedThreadedTaskQueue(); -+ -+ public final ChunkHolderManager chunkHolderManager; -+ -+ static { -+ ChunkStatus.EMPTY.writeRadius = 0; -+ ChunkStatus.STRUCTURE_STARTS.writeRadius = 0; -+ ChunkStatus.STRUCTURE_REFERENCES.writeRadius = 0; -+ ChunkStatus.BIOMES.writeRadius = 0; -+ ChunkStatus.NOISE.writeRadius = 0; -+ ChunkStatus.SURFACE.writeRadius = 0; -+ ChunkStatus.CARVERS.writeRadius = 0; -+ ChunkStatus.FEATURES.writeRadius = 1; -+ ChunkStatus.INITIALIZE_LIGHT.writeRadius = 0; -+ ChunkStatus.LIGHT.writeRadius = 2; -+ ChunkStatus.SPAWN.writeRadius = 0; -+ ChunkStatus.FULL.writeRadius = 0; -+ -+ /* -+ It's important that the neighbour read radius is taken into account. If _any_ later status is using some chunk as -+ a neighbour, it must be also safe if that neighbour is being generated. i.e for any status later than FEATURES, -+ for a status to be parallel safe it must not read the block data from its neighbours. -+ */ -+ final List parallelCapableStatus = Arrays.asList( -+ // No-op executor. -+ ChunkStatus.EMPTY, -+ -+ // This is parallel capable, as CB has fixed the concurrency issue with stronghold generations. -+ // Does not touch neighbour chunks. -+ ChunkStatus.STRUCTURE_STARTS, -+ -+ // Surprisingly this is parallel capable. It is simply reading the already-created structure starts -+ // into the structure references for the chunk. So while it reads from it neighbours, its neighbours -+ // will not change, even if executed in parallel. -+ ChunkStatus.STRUCTURE_REFERENCES, -+ -+ // Safe. Mojang runs it in parallel as well. -+ ChunkStatus.BIOMES, -+ -+ // Safe. Mojang runs it in parallel as well. -+ ChunkStatus.NOISE, -+ -+ // Parallel safe. Only touches the target chunk. Biome retrieval is now noise based, which is -+ // completely thread-safe. -+ ChunkStatus.SURFACE, -+ -+ // No global state is modified in the carvers. It only touches the specified chunk. So it is parallel safe. -+ ChunkStatus.CARVERS, -+ -+ // FEATURES is not parallel safe. It writes to neighbours. -+ -+ // no-op executor -+ ChunkStatus.INITIALIZE_LIGHT -+ -+ // LIGHT is not parallel safe. It also doesn't run on the generation executor, so no point. -+ -+ // Only writes to the specified chunk. State is not read by later statuses. Parallel safe. -+ // Note: it may look unsafe because it writes to a worldgenregion, but the region size is always 0 - -+ // see the task margin. -+ // However, if the neighbouring FEATURES chunk is unloaded, but then fails to load in again (for whatever -+ // reason), then it would write to this chunk - and since this status reads blocks from itself, it's not -+ // safe to execute this in parallel. -+ // SPAWN -+ -+ // FULL is executed on main. -+ ); -+ -+ for (final ChunkStatus status : parallelCapableStatus) { -+ status.isParallelCapable = true; -+ } -+ } -+ -+ private static final int[] ACCESS_RADIUS_TABLE = new int[ChunkStatus.getStatusList().size()]; -+ private static final int[] MAX_ACCESS_RADIUS_TABLE = new int[ACCESS_RADIUS_TABLE.length]; -+ static { -+ Arrays.fill(ACCESS_RADIUS_TABLE, -1); -+ } -+ -+ private static int getAccessRadius0(final ChunkStatus genStatus) { -+ if (genStatus == ChunkStatus.EMPTY) { -+ return 0; -+ } -+ -+ final int radius = Math.max(genStatus.loadRange, genStatus.getRange()); -+ int maxRange = radius; -+ -+ for (int dist = 1; dist <= radius; ++dist) { -+ final ChunkStatus requiredNeighbourStatus = ChunkMap.getDependencyStatus(genStatus, radius); -+ final int rad = ACCESS_RADIUS_TABLE[requiredNeighbourStatus.getIndex()]; -+ if (rad == -1) { -+ throw new IllegalStateException(); -+ } -+ -+ maxRange = Math.max(maxRange, dist + rad); -+ } -+ -+ return maxRange; -+ } -+ -+ private static int maxAccessRadius; -+ -+ static { -+ final List statuses = ChunkStatus.getStatusList(); -+ for (int i = 0, len = statuses.size(); i < len; ++i) { -+ ACCESS_RADIUS_TABLE[i] = getAccessRadius0(statuses.get(i)); -+ } -+ int max = 0; -+ for (int i = 0, len = statuses.size(); i < len; ++i) { -+ MAX_ACCESS_RADIUS_TABLE[i] = max = Math.max(ACCESS_RADIUS_TABLE[i], max); -+ } -+ maxAccessRadius = max; -+ } -+ -+ public static int getMaxAccessRadius() { -+ return maxAccessRadius; -+ } -+ -+ public static int getAccessRadius(final ChunkStatus genStatus) { -+ return ACCESS_RADIUS_TABLE[genStatus.getIndex()]; -+ } -+ -+ public static int getAccessRadius(final FullChunkStatus status) { -+ return (status.ordinal() - 1) + getAccessRadius(ChunkStatus.FULL); -+ } -+ -+ final ReentrantAreaLock schedulingLockArea; -+ private final int lockShift; -+ -+ public final int getChunkSystemLockShift() { -+ return this.lockShift; -+ } -+ // Folia end - use area based lock to reduce contention -+ -+ public ChunkTaskScheduler(final ServerLevel world, final PrioritisedThreadPool workers) { -+ this.world = world; -+ this.workers = workers; -+ // must be >= region shift (in paper, doesn't exist) and must be >= ticket propagator section shift -+ // it must be >= region shift since the regioniser assumes ticket updates do not occur in parallel for the region sections -+ // it must be >= ticket propagator section shift so that the ticket propagator can assume that owning a position implies owning -+ // the entire section -+ // we just take the max, as we want the smallest shift that satisfies these properties -+ this.lockShift = Math.max(world.getRegionChunkShift(), ThreadedTicketLevelPropagator.SECTION_SHIFT); -+ this.schedulingLockArea = new ReentrantAreaLock(this.getChunkSystemLockShift()); -+ -+ final String worldName = world.getWorld().getName(); -+ this.parallelGenExecutor = workers.createExecutor("Chunk parallel generation executor for world '" + worldName + "'", Math.max(1, newChunkSystemGenParallelism)); -+ this.radiusAwareGenExecutor = -+ newChunkSystemGenParallelism <= 1 ? this.parallelGenExecutor : workers.createExecutor("Chunk radius aware generator for world '" + worldName + "'", newChunkSystemGenParallelism); -+ this.loadExecutor = workers.createExecutor("Chunk load executor for world '" + worldName + "'", newChunkSystemLoadParallelism); -+ this.radiusAwareScheduler = new RadiusAwarePrioritisedExecutor(this.radiusAwareGenExecutor, Math.max(1, newChunkSystemGenParallelism)); -+ this.chunkHolderManager = new ChunkHolderManager(world, this); -+ } -+ -+ private final AtomicBoolean failedChunkSystem = new AtomicBoolean(); -+ -+ public static Object stringIfNull(final Object obj) { -+ return obj == null ? "null" : obj; -+ } -+ -+ public void unrecoverableChunkSystemFailure(final int chunkX, final int chunkZ, final Map objectsOfInterest, final Throwable thr) { -+ final NewChunkHolder holder = this.chunkHolderManager.getChunkHolder(chunkX, chunkZ); -+ LOGGER.error("Chunk system error at chunk (" + chunkX + "," + chunkZ + "), holder: " + holder + ", exception:", new Throwable(thr)); -+ -+ if (this.failedChunkSystem.getAndSet(true)) { -+ return; -+ } -+ -+ final ReportedException reportedException = thr instanceof ReportedException ? (ReportedException)thr : new ReportedException(new CrashReport("Chunk system error", thr)); -+ -+ CrashReportCategory crashReportCategory = reportedException.getReport().addCategory("Chunk system details"); -+ crashReportCategory.setDetail("Chunk coordinate", new ChunkPos(chunkX, chunkZ).toString()); -+ crashReportCategory.setDetail("ChunkHolder", Objects.toString(holder)); -+ crashReportCategory.setDetail("unrecoverableChunkSystemFailure caller thread", Thread.currentThread().getName()); -+ -+ crashReportCategory = reportedException.getReport().addCategory("Chunk System Objects of Interest"); -+ for (final Map.Entry entry : objectsOfInterest.entrySet()) { -+ if (entry.getValue() instanceof Throwable thrObject) { -+ crashReportCategory.setDetailError(Objects.toString(entry.getKey()), thrObject); -+ } else { -+ crashReportCategory.setDetail(Objects.toString(entry.getKey()), Objects.toString(entry.getValue())); -+ } -+ } -+ -+ final Runnable crash = () -> { -+ throw new RuntimeException("Chunk system crash propagated from unrecoverableChunkSystemFailure", reportedException); -+ }; -+ -+ // this may not be good enough, specifically thanks to stupid ass plugins swallowing exceptions -+ this.scheduleChunkTask(chunkX, chunkZ, crash, PrioritisedExecutor.Priority.BLOCKING); -+ // so, make the main thread pick it up -+ MinecraftServer.chunkSystemCrash = new RuntimeException("Chunk system crash propagated from unrecoverableChunkSystemFailure", reportedException); -+ } -+ -+ public boolean executeMainThreadTask() { -+ TickThread.ensureTickThread("Cannot execute main thread task off-main"); -+ return this.mainThreadExecutor.executeTask(); -+ } -+ -+ public void raisePriority(final int x, final int z, final PrioritisedExecutor.Priority priority) { -+ this.chunkHolderManager.raisePriority(x, z, priority); -+ } -+ -+ public void setPriority(final int x, final int z, final PrioritisedExecutor.Priority priority) { -+ this.chunkHolderManager.setPriority(x, z, priority); -+ } -+ -+ public void lowerPriority(final int x, final int z, final PrioritisedExecutor.Priority priority) { -+ this.chunkHolderManager.lowerPriority(x, z, priority); -+ } -+ -+ private final AtomicLong chunkLoadCounter = new AtomicLong(); -+ -+ public void scheduleTickingState(final int chunkX, final int chunkZ, final FullChunkStatus toStatus, -+ final boolean addTicket, final PrioritisedExecutor.Priority priority, -+ final Consumer onComplete) { -+ if (!TickThread.isTickThread()) { -+ this.scheduleChunkTask(chunkX, chunkZ, () -> { -+ ChunkTaskScheduler.this.scheduleTickingState(chunkX, chunkZ, toStatus, addTicket, priority, onComplete); -+ }, priority); -+ return; -+ } -+ final int accessRadius = getAccessRadius(toStatus); -+ if (this.chunkHolderManager.ticketLockArea.isHeldByCurrentThread(chunkX, chunkZ, accessRadius)) { -+ throw new IllegalStateException("Cannot schedule chunk load during ticket level update"); -+ } -+ if (this.schedulingLockArea.isHeldByCurrentThread(chunkX, chunkZ, accessRadius)) { -+ throw new IllegalStateException("Cannot schedule chunk loading recursively"); -+ } -+ -+ if (toStatus == FullChunkStatus.INACCESSIBLE) { -+ throw new IllegalArgumentException("Cannot wait for INACCESSIBLE status"); -+ } -+ -+ final int minLevel = 33 - (toStatus.ordinal() - 1); -+ final Long chunkReference = addTicket ? Long.valueOf(this.chunkLoadCounter.getAndIncrement()) : null; -+ final long chunkKey = CoordinateUtils.getChunkKey(chunkX, chunkZ); -+ -+ if (addTicket) { -+ this.chunkHolderManager.addTicketAtLevel(TicketType.CHUNK_LOAD, chunkKey, minLevel, chunkReference); -+ this.chunkHolderManager.processTicketUpdates(); -+ } -+ -+ final Consumer loadCallback = (final LevelChunk chunk) -> { -+ try { -+ if (onComplete != null) { -+ onComplete.accept(chunk); -+ } -+ } finally { -+ if (addTicket) { -+ ChunkTaskScheduler.this.chunkHolderManager.addAndRemoveTickets(chunkKey, -+ TicketType.UNKNOWN, minLevel, new ChunkPos(chunkKey), -+ TicketType.CHUNK_LOAD, minLevel, chunkReference -+ ); -+ } -+ } -+ }; -+ -+ final boolean scheduled; -+ final LevelChunk chunk; -+ final ReentrantAreaLock.Node ticketLock = this.chunkHolderManager.ticketLockArea.lock(chunkX, chunkZ, accessRadius); -+ try { -+ final ReentrantAreaLock.Node schedulingLock = this.schedulingLockArea.lock(chunkX, chunkZ, accessRadius); -+ try { -+ final NewChunkHolder chunkHolder = this.chunkHolderManager.getChunkHolder(chunkKey); -+ if (chunkHolder == null || chunkHolder.getTicketLevel() > minLevel) { -+ scheduled = false; -+ chunk = null; -+ } else { -+ final FullChunkStatus currStatus = chunkHolder.getChunkStatus(); -+ if (currStatus.isOrAfter(toStatus)) { -+ scheduled = false; -+ chunk = (LevelChunk)chunkHolder.getCurrentChunk(); -+ } else { -+ scheduled = true; -+ chunk = null; -+ -+ final int radius = toStatus.ordinal() - 1; // 0 -> BORDER, 1 -> TICKING, 2 -> ENTITY_TICKING -+ for (int dz = -radius; dz <= radius; ++dz) { -+ for (int dx = -radius; dx <= radius; ++dx) { -+ final NewChunkHolder neighbour = -+ (dx | dz) == 0 ? chunkHolder : this.chunkHolderManager.getChunkHolder(dx + chunkX, dz + chunkZ); -+ if (neighbour != null) { -+ neighbour.raisePriority(priority); -+ } -+ } -+ } -+ -+ // ticket level should schedule for us -+ chunkHolder.addFullStatusConsumer(toStatus, loadCallback); -+ } -+ } -+ } finally { -+ this.schedulingLockArea.unlock(schedulingLock); -+ } -+ } finally { -+ this.chunkHolderManager.ticketLockArea.unlock(ticketLock); -+ } -+ -+ if (!scheduled) { -+ // couldn't schedule -+ try { -+ loadCallback.accept(chunk); -+ } catch (final ThreadDeath thr) { -+ throw thr; -+ } catch (final Throwable thr) { -+ LOGGER.error("Failed to process chunk full status callback", thr); -+ } -+ } -+ } -+ -+ public void scheduleChunkLoad(final int chunkX, final int chunkZ, final boolean gen, final ChunkStatus toStatus, final boolean addTicket, -+ final PrioritisedExecutor.Priority priority, final Consumer onComplete) { -+ if (gen) { -+ this.scheduleChunkLoad(chunkX, chunkZ, toStatus, addTicket, priority, onComplete); -+ return; -+ } -+ this.scheduleChunkLoad(chunkX, chunkZ, ChunkStatus.EMPTY, addTicket, priority, (final ChunkAccess chunk) -> { -+ if (chunk == null) { -+ onComplete.accept(null); -+ } else { -+ if (chunk.getStatus().isOrAfter(toStatus)) { -+ this.scheduleChunkLoad(chunkX, chunkZ, toStatus, addTicket, priority, onComplete); -+ } else { -+ onComplete.accept(null); -+ } -+ } -+ }); -+ } -+ -+ // only appropriate to use with ServerLevel#syncLoadNonFull -+ public boolean beginChunkLoadForNonFullSync(final int chunkX, final int chunkZ, final ChunkStatus toStatus, -+ final PrioritisedExecutor.Priority priority) { -+ final int accessRadius = getAccessRadius(toStatus); -+ final long chunkKey = CoordinateUtils.getChunkKey(chunkX, chunkZ); -+ final int minLevel = 33 + ChunkStatus.getDistance(toStatus); -+ final List tasks = new ArrayList<>(); -+ final ca.spottedleaf.concurrentutil.lock.ReentrantAreaLock.Node ticketLock = this.chunkHolderManager.ticketLockArea.lock(chunkX, chunkZ, accessRadius); // Folia - use area based lock to reduce contention -+ try { -+ final ca.spottedleaf.concurrentutil.lock.ReentrantAreaLock.Node schedulingLock = this.schedulingLockArea.lock(chunkX, chunkZ, accessRadius); // Folia - use area based lock to reduce contention -+ try { -+ final NewChunkHolder chunkHolder = this.chunkHolderManager.getChunkHolder(chunkKey); -+ if (chunkHolder == null || chunkHolder.getTicketLevel() > minLevel) { -+ return false; -+ } else { -+ final ChunkStatus genStatus = chunkHolder.getCurrentGenStatus(); -+ if (genStatus != null && genStatus.isOrAfter(toStatus)) { -+ return true; -+ } else { -+ chunkHolder.raisePriority(priority); -+ -+ if (!chunkHolder.upgradeGenTarget(toStatus)) { -+ this.schedule(chunkX, chunkZ, toStatus, chunkHolder, tasks); -+ } -+ } -+ } -+ } finally { -+ this.schedulingLockArea.unlock(schedulingLock); -+ } -+ } finally { -+ this.chunkHolderManager.ticketLockArea.unlock(ticketLock); -+ } -+ -+ for (int i = 0, len = tasks.size(); i < len; ++i) { -+ tasks.get(i).schedule(); -+ } -+ -+ return true; -+ } -+ -+ public void scheduleChunkLoad(final int chunkX, final int chunkZ, final ChunkStatus toStatus, final boolean addTicket, -+ final PrioritisedExecutor.Priority priority, final Consumer onComplete) { -+ if (!TickThread.isTickThread()) { -+ this.scheduleChunkTask(chunkX, chunkZ, () -> { -+ ChunkTaskScheduler.this.scheduleChunkLoad(chunkX, chunkZ, toStatus, addTicket, priority, onComplete); -+ }, priority); -+ return; -+ } -+ final int accessRadius = getAccessRadius(toStatus); -+ if (this.chunkHolderManager.ticketLockArea.isHeldByCurrentThread(chunkX, chunkZ, accessRadius)) { -+ throw new IllegalStateException("Cannot schedule chunk load during ticket level update"); -+ } -+ if (this.schedulingLockArea.isHeldByCurrentThread(chunkX, chunkZ, accessRadius)) { -+ throw new IllegalStateException("Cannot schedule chunk loading recursively"); -+ } -+ -+ if (toStatus == ChunkStatus.FULL) { -+ this.scheduleTickingState(chunkX, chunkZ, FullChunkStatus.FULL, addTicket, priority, (Consumer)onComplete); -+ return; -+ } -+ -+ final int minLevel = 33 + ChunkStatus.getDistance(toStatus); -+ final Long chunkReference = addTicket ? Long.valueOf(this.chunkLoadCounter.getAndIncrement()) : null; -+ final long chunkKey = CoordinateUtils.getChunkKey(chunkX, chunkZ); -+ -+ if (addTicket) { -+ this.chunkHolderManager.addTicketAtLevel(TicketType.CHUNK_LOAD, chunkKey, minLevel, chunkReference); -+ this.chunkHolderManager.processTicketUpdates(); -+ } -+ -+ final Consumer loadCallback = (final ChunkAccess chunk) -> { -+ try { -+ if (onComplete != null) { -+ onComplete.accept(chunk); -+ } -+ } finally { -+ if (addTicket) { -+ ChunkTaskScheduler.this.chunkHolderManager.addAndRemoveTickets(chunkKey, -+ TicketType.UNKNOWN, minLevel, new ChunkPos(chunkKey), -+ TicketType.CHUNK_LOAD, minLevel, chunkReference -+ ); -+ } -+ } -+ }; -+ -+ final List tasks = new ArrayList<>(); -+ -+ final boolean scheduled; -+ final ChunkAccess chunk; -+ final ReentrantAreaLock.Node ticketLock = this.chunkHolderManager.ticketLockArea.lock(chunkX, chunkZ, accessRadius); -+ try { -+ final ReentrantAreaLock.Node schedulingLock = this.schedulingLockArea.lock(chunkX, chunkZ, accessRadius); -+ try { -+ final NewChunkHolder chunkHolder = this.chunkHolderManager.getChunkHolder(chunkKey); -+ if (chunkHolder == null || chunkHolder.getTicketLevel() > minLevel) { -+ scheduled = false; -+ chunk = null; -+ } else { -+ final ChunkStatus genStatus = chunkHolder.getCurrentGenStatus(); -+ if (genStatus != null && genStatus.isOrAfter(toStatus)) { -+ scheduled = false; -+ chunk = chunkHolder.getCurrentChunk(); -+ } else { -+ scheduled = true; -+ chunk = null; -+ chunkHolder.raisePriority(priority); -+ -+ if (!chunkHolder.upgradeGenTarget(toStatus)) { -+ this.schedule(chunkX, chunkZ, toStatus, chunkHolder, tasks); -+ } -+ chunkHolder.addStatusConsumer(toStatus, loadCallback); -+ } -+ } -+ } finally { -+ this.schedulingLockArea.unlock(schedulingLock); -+ } -+ } finally { -+ this.chunkHolderManager.ticketLockArea.unlock(ticketLock); -+ } -+ -+ for (int i = 0, len = tasks.size(); i < len; ++i) { -+ tasks.get(i).schedule(); -+ } -+ -+ if (!scheduled) { -+ // couldn't schedule -+ try { -+ loadCallback.accept(chunk); -+ } catch (final ThreadDeath thr) { -+ throw thr; -+ } catch (final Throwable thr) { -+ LOGGER.error("Failed to process chunk status callback", thr); -+ } -+ } -+ } -+ -+ private ChunkProgressionTask createTask(final int chunkX, final int chunkZ, final ChunkAccess chunk, -+ final NewChunkHolder chunkHolder, final List neighbours, -+ final ChunkStatus toStatus, final PrioritisedExecutor.Priority initialPriority) { -+ if (toStatus == ChunkStatus.EMPTY) { -+ return new ChunkLoadTask(this, this.world, chunkX, chunkZ, chunkHolder, initialPriority); -+ } -+ if (toStatus == ChunkStatus.LIGHT) { -+ return new ChunkLightTask(this, this.world, chunkX, chunkZ, chunk, initialPriority); -+ } -+ if (toStatus == ChunkStatus.FULL) { -+ return new ChunkFullTask(this, this.world, chunkX, chunkZ, chunkHolder, chunk, initialPriority); -+ } -+ -+ return new ChunkUpgradeGenericStatusTask(this, this.world, chunkX, chunkZ, chunk, neighbours, toStatus, initialPriority); -+ } -+ -+ ChunkProgressionTask schedule(final int chunkX, final int chunkZ, final ChunkStatus targetStatus, final NewChunkHolder chunkHolder, -+ final List allTasks) { -+ return this.schedule(chunkX, chunkZ, targetStatus, chunkHolder, allTasks, chunkHolder.getEffectivePriority()); -+ } -+ -+ // rets new task scheduled for the _specified_ chunk -+ // note: this must hold the scheduling lock -+ // minPriority is only used to pass the priority through to neighbours, as priority calculation has not yet been done -+ // schedule will ignore the generation target, so it should be checked by the caller to ensure the target is not regressed! -+ private ChunkProgressionTask schedule(final int chunkX, final int chunkZ, final ChunkStatus targetStatus, -+ final NewChunkHolder chunkHolder, final List allTasks, -+ final PrioritisedExecutor.Priority minPriority) { -+ if (!this.schedulingLockArea.isHeldByCurrentThread(chunkX, chunkZ, getAccessRadius(targetStatus))) { -+ throw new IllegalStateException("Not holding scheduling lock"); -+ } -+ -+ if (chunkHolder.hasGenerationTask()) { -+ chunkHolder.upgradeGenTarget(targetStatus); -+ return null; -+ } -+ -+ final PrioritisedExecutor.Priority requestedPriority = PrioritisedExecutor.Priority.max(minPriority, chunkHolder.getEffectivePriority()); -+ final ChunkStatus currentGenStatus = chunkHolder.getCurrentGenStatus(); -+ final ChunkAccess chunk = chunkHolder.getCurrentChunk(); -+ -+ if (currentGenStatus == null) { -+ // not yet loaded -+ final ChunkProgressionTask task = this.createTask( -+ chunkX, chunkZ, chunk, chunkHolder, Collections.emptyList(), ChunkStatus.EMPTY, requestedPriority -+ ); -+ -+ allTasks.add(task); -+ -+ final List chunkHolderNeighbours = new ArrayList<>(1); -+ chunkHolderNeighbours.add(chunkHolder); -+ -+ chunkHolder.setGenerationTarget(targetStatus); -+ chunkHolder.setGenerationTask(task, ChunkStatus.EMPTY, chunkHolderNeighbours); -+ -+ return task; -+ } -+ -+ if (currentGenStatus.isOrAfter(targetStatus)) { -+ // nothing to do -+ return null; -+ } -+ -+ // we know for sure now that we want to schedule _something_, so set the target -+ chunkHolder.setGenerationTarget(targetStatus); -+ -+ final ChunkStatus chunkRealStatus = chunk.getStatus(); -+ final ChunkStatus toStatus = currentGenStatus.getNextStatus(); -+ -+ // if this chunk has already generated up to or past the specified status, then we don't -+ // need the neighbours AT ALL. -+ final int neighbourReadRadius = chunkRealStatus.isOrAfter(toStatus) ? toStatus.loadRange : toStatus.getRange(); -+ -+ boolean unGeneratedNeighbours = false; -+ -+ // copied from MCUtil.getSpiralOutChunks -+ for (int r = 1; r <= neighbourReadRadius; r++) { -+ int x = -r; -+ int z = r; -+ -+ // Iterates the edge of half of the box; then negates for other half. -+ while (x <= r && z > -r) { -+ final int radius = Math.max(Math.abs(x), Math.abs(z)); -+ final ChunkStatus requiredNeighbourStatus = ChunkMap.getDependencyStatus(toStatus, radius); -+ -+ unGeneratedNeighbours |= this.checkNeighbour( -+ chunkX + x, chunkZ + z, requiredNeighbourStatus, chunkHolder, allTasks, requestedPriority -+ ); -+ unGeneratedNeighbours |= this.checkNeighbour( -+ chunkX - x, chunkZ - z, requiredNeighbourStatus, chunkHolder, allTasks, requestedPriority -+ ); -+ -+ if (x < r) { -+ x++; -+ } else { -+ z--; -+ } -+ } -+ } -+ -+ if (unGeneratedNeighbours) { -+ // can't schedule, but neighbour completion will schedule for us when they're ALL done -+ -+ // propagate our priority to neighbours -+ chunkHolder.recalculateNeighbourPriorities(); -+ return null; -+ } -+ -+ // need to gather neighbours -+ -+ final List neighbours; -+ final List chunkHolderNeighbours; -+ if (neighbourReadRadius <= 0) { -+ neighbours = new ArrayList<>(1); -+ chunkHolderNeighbours = new ArrayList<>(1); -+ neighbours.add(chunk); -+ chunkHolderNeighbours.add(chunkHolder); -+ } else { -+ // the iteration order is _very_ important, as all generation statuses expect a certain order such that: -+ // chunkAtRelative = neighbours.get(relX + relZ * (2 * radius + 1)) -+ neighbours = new ArrayList<>((2 * neighbourReadRadius + 1) * (2 * neighbourReadRadius + 1)); -+ chunkHolderNeighbours = new ArrayList<>((2 * neighbourReadRadius + 1) * (2 * neighbourReadRadius + 1)); -+ for (int dz = -neighbourReadRadius; dz <= neighbourReadRadius; ++dz) { -+ for (int dx = -neighbourReadRadius; dx <= neighbourReadRadius; ++dx) { -+ final NewChunkHolder holder = (dx | dz) == 0 ? chunkHolder : this.chunkHolderManager.getChunkHolder(dx + chunkX, dz + chunkZ); -+ neighbours.add(holder.getChunkForNeighbourAccess()); -+ chunkHolderNeighbours.add(holder); -+ } -+ } -+ } -+ -+ final ChunkProgressionTask task = this.createTask(chunkX, chunkZ, chunk, chunkHolder, neighbours, toStatus, chunkHolder.getEffectivePriority()); -+ allTasks.add(task); -+ -+ chunkHolder.setGenerationTask(task, toStatus, chunkHolderNeighbours); -+ -+ return task; -+ } -+ -+ // rets true if the neighbour is not at the required status, false otherwise -+ private boolean checkNeighbour(final int chunkX, final int chunkZ, final ChunkStatus requiredStatus, final NewChunkHolder center, -+ final List tasks, final PrioritisedExecutor.Priority minPriority) { -+ final NewChunkHolder chunkHolder = this.chunkHolderManager.getChunkHolder(chunkX, chunkZ); -+ -+ if (chunkHolder == null) { -+ throw new IllegalStateException("Missing chunkholder when required"); -+ } -+ -+ final ChunkStatus holderStatus = chunkHolder.getCurrentGenStatus(); -+ if (holderStatus != null && holderStatus.isOrAfter(requiredStatus)) { -+ return false; -+ } -+ -+ if (chunkHolder.hasFailedGeneration()) { -+ return true; -+ } -+ -+ center.addGenerationBlockingNeighbour(chunkHolder); -+ chunkHolder.addWaitingNeighbour(center, requiredStatus); -+ -+ if (chunkHolder.upgradeGenTarget(requiredStatus)) { -+ return true; -+ } -+ -+ // not at status required, so we need to schedule its generation -+ this.schedule( -+ chunkX, chunkZ, requiredStatus, chunkHolder, tasks, minPriority -+ ); -+ -+ return true; -+ } -+ -+ /** -+ * @deprecated Chunk tasks must be tied to coordinates in the future -+ */ -+ @Deprecated -+ public PrioritisedExecutor.PrioritisedTask scheduleChunkTask(final Runnable run) { -+ return this.scheduleChunkTask(run, PrioritisedExecutor.Priority.NORMAL); -+ } -+ -+ /** -+ * @deprecated Chunk tasks must be tied to coordinates in the future -+ */ -+ @Deprecated -+ public PrioritisedExecutor.PrioritisedTask scheduleChunkTask(final Runnable run, final PrioritisedExecutor.Priority priority) { -+ return this.mainThreadExecutor.queueRunnable(run, priority); -+ } -+ -+ public PrioritisedExecutor.PrioritisedTask createChunkTask(final int chunkX, final int chunkZ, final Runnable run) { -+ return this.createChunkTask(chunkX, chunkZ, run, PrioritisedExecutor.Priority.NORMAL); -+ } -+ -+ public PrioritisedExecutor.PrioritisedTask createChunkTask(final int chunkX, final int chunkZ, final Runnable run, -+ final PrioritisedExecutor.Priority priority) { -+ return this.mainThreadExecutor.createTask(run, priority); -+ } -+ -+ public PrioritisedExecutor.PrioritisedTask scheduleChunkTask(final int chunkX, final int chunkZ, final Runnable run) { -+ return this.mainThreadExecutor.queueRunnable(run); -+ } -+ -+ public PrioritisedExecutor.PrioritisedTask scheduleChunkTask(final int chunkX, final int chunkZ, final Runnable run, -+ final PrioritisedExecutor.Priority priority) { -+ return this.mainThreadExecutor.queueRunnable(run, priority); -+ } -+ -+ public void executeTasksUntil(final BooleanSupplier exit) { -+ if (Bukkit.isPrimaryThread()) { -+ this.mainThreadExecutor.executeConditionally(exit); -+ } else { -+ long counter = 1L; -+ while (!exit.getAsBoolean()) { -+ counter = ConcurrentUtil.linearLongBackoff(counter, 100_000L, 5_000_000L); // 100us, 5ms -+ } -+ } -+ } -+ -+ public boolean halt(final boolean sync, final long maxWaitNS) { -+ this.radiusAwareGenExecutor.halt(); -+ this.parallelGenExecutor.halt(); -+ this.loadExecutor.halt(); -+ final long time = System.nanoTime(); -+ if (sync) { -+ for (long failures = 9L;; failures = ConcurrentUtil.linearLongBackoff(failures, 500_000L, 50_000_000L)) { -+ if ( -+ !this.radiusAwareGenExecutor.isActive() && -+ !this.parallelGenExecutor.isActive() && -+ !this.loadExecutor.isActive() -+ ) { -+ return true; -+ } -+ if ((System.nanoTime() - time) >= maxWaitNS) { -+ return false; -+ } -+ } -+ } -+ -+ return true; -+ } -+ -+ public static final ArrayDeque WAITING_CHUNKS = new ArrayDeque<>(); // stack -+ -+ public static final class ChunkInfo { -+ -+ public final int chunkX; -+ public final int chunkZ; -+ public final ServerLevel world; -+ -+ public ChunkInfo(final int chunkX, final int chunkZ, final ServerLevel world) { -+ this.chunkX = chunkX; -+ this.chunkZ = chunkZ; -+ this.world = world; -+ } -+ -+ @Override -+ public String toString() { -+ return "[( " + this.chunkX + "," + this.chunkZ + ") in '" + this.world.getWorld().getName() + "']"; -+ } -+ } -+ -+ public static void pushChunkWait(final ServerLevel world, final int chunkX, final int chunkZ) { -+ synchronized (WAITING_CHUNKS) { -+ WAITING_CHUNKS.push(new ChunkInfo(chunkX, chunkZ, world)); -+ } -+ } -+ -+ public static void popChunkWait() { -+ synchronized (WAITING_CHUNKS) { -+ WAITING_CHUNKS.pop(); -+ } -+ } -+ -+ public static ChunkInfo[] getChunkInfos() { -+ synchronized (WAITING_CHUNKS) { -+ return WAITING_CHUNKS.toArray(new ChunkInfo[0]); -+ } -+ } -+ -+ public static void dumpAllChunkLoadInfo(final boolean longPrint) { -+ final ChunkInfo[] chunkInfos = getChunkInfos(); -+ if (chunkInfos.length > 0) { -+ LOGGER.error("Chunk wait task info below: "); -+ for (final ChunkInfo chunkInfo : chunkInfos) { -+ final NewChunkHolder holder = chunkInfo.world.chunkTaskScheduler.chunkHolderManager.getChunkHolder(chunkInfo.chunkX, chunkInfo.chunkZ); -+ LOGGER.error("Chunk wait: " + chunkInfo); -+ LOGGER.error("Chunk holder: " + holder); -+ } -+ -+ if (longPrint) { -+ final File file = new File(new File(new File("."), "debug"), "chunks-watchdog.txt"); -+ LOGGER.error("Writing chunk information dump to " + file); -+ try { -+ MCUtil.dumpChunks(file, true); -+ LOGGER.error("Successfully written chunk information!"); -+ } catch (final Throwable thr) { -+ MinecraftServer.LOGGER.warn("Failed to dump chunk information to file " + file.toString(), thr); -+ } -+ } -+ } -+ } -+} -diff --git a/src/main/java/io/papermc/paper/chunk/system/scheduling/ChunkUpgradeGenericStatusTask.java b/src/main/java/io/papermc/paper/chunk/system/scheduling/ChunkUpgradeGenericStatusTask.java -new file mode 100644 -index 0000000000000000000000000000000000000000..bd0d0c4436f357392e13d9efd4412886385a6924 ---- /dev/null -+++ b/src/main/java/io/papermc/paper/chunk/system/scheduling/ChunkUpgradeGenericStatusTask.java -@@ -0,0 +1,214 @@ -+package io.papermc.paper.chunk.system.scheduling; -+ -+import ca.spottedleaf.concurrentutil.executor.standard.PrioritisedExecutor; -+import ca.spottedleaf.concurrentutil.util.ConcurrentUtil; -+import com.mojang.logging.LogUtils; -+import net.minecraft.server.level.ChunkMap; -+import net.minecraft.server.level.ChunkResult; -+import net.minecraft.server.level.ServerChunkCache; -+import net.minecraft.server.level.ServerLevel; -+import net.minecraft.world.level.chunk.ChunkAccess; -+import net.minecraft.world.level.chunk.status.ChunkStatus; -+import net.minecraft.world.level.chunk.ProtoChunk; -+import net.minecraft.world.level.chunk.status.WorldGenContext; -+import org.slf4j.Logger; -+import java.lang.invoke.VarHandle; -+import java.util.List; -+import java.util.Map; -+import java.util.concurrent.CompletableFuture; -+ -+public final class ChunkUpgradeGenericStatusTask extends ChunkProgressionTask implements Runnable { -+ -+ private static final Logger LOGGER = LogUtils.getClassLogger(); -+ -+ protected final ChunkAccess fromChunk; -+ protected final ChunkStatus fromStatus; -+ protected final ChunkStatus toStatus; -+ protected final List neighbours; -+ -+ protected final PrioritisedExecutor.PrioritisedTask generateTask; -+ -+ public ChunkUpgradeGenericStatusTask(final ChunkTaskScheduler scheduler, final ServerLevel world, final int chunkX, -+ final int chunkZ, final ChunkAccess chunk, final List neighbours, -+ final ChunkStatus toStatus, final PrioritisedExecutor.Priority priority) { -+ super(scheduler, world, chunkX, chunkZ); -+ if (!PrioritisedExecutor.Priority.isValidPriority(priority)) { -+ throw new IllegalArgumentException("Invalid priority " + priority); -+ } -+ this.fromChunk = chunk; -+ this.fromStatus = chunk.getStatus(); -+ this.toStatus = toStatus; -+ this.neighbours = neighbours; -+ if (this.toStatus.isParallelCapable) { -+ this.generateTask = this.scheduler.parallelGenExecutor.createTask(this, priority); -+ } else { -+ this.generateTask = this.scheduler.radiusAwareScheduler.createTask(chunkX, chunkZ, this.toStatus.writeRadius, this, priority); -+ } -+ } -+ -+ @Override -+ public ChunkStatus getTargetStatus() { -+ return this.toStatus; -+ } -+ -+ private boolean isEmptyTask() { -+ // must use fromStatus here to avoid any race condition with run() overwriting the status -+ final boolean generation = !this.fromStatus.isOrAfter(this.toStatus); -+ return (generation && this.toStatus.isEmptyGenStatus()) || (!generation && this.toStatus.isEmptyLoadStatus()); -+ } -+ -+ @Override -+ public void run() { -+ final ChunkAccess chunk = this.fromChunk; -+ -+ final ServerChunkCache serverChunkCache = this.world.chunkSource; -+ final ChunkMap chunkMap = serverChunkCache.chunkMap; -+ -+ final CompletableFuture completeFuture; -+ -+ final boolean generation; -+ boolean completing = false; -+ -+ // note: should optimise the case where the chunk does not need to execute the status, because -+ // schedule() calls this synchronously if it will run through that path -+ -+ final WorldGenContext ctx = new WorldGenContext( -+ this.world, -+ chunkMap.generator, -+ chunkMap.getWorldGenContext().structureManager(), -+ serverChunkCache.getLightEngine() -+ ); -+ try { -+ generation = !chunk.getStatus().isOrAfter(this.toStatus); -+ if (generation) { -+ if (this.toStatus.isEmptyGenStatus()) { -+ if (chunk instanceof ProtoChunk) { -+ ((ProtoChunk)chunk).setStatus(this.toStatus); -+ } -+ completing = true; -+ this.complete(chunk, null); -+ return; -+ } -+ completeFuture = this.toStatus.generate(ctx, Runnable::run, null, this.neighbours) -+ .whenComplete((final ChunkAccess either, final Throwable throwable) -> { -+ if (either instanceof ProtoChunk proto) { -+ proto.setStatus(ChunkUpgradeGenericStatusTask.this.toStatus); -+ } -+ } -+ ); -+ } else { -+ if (this.toStatus.isEmptyLoadStatus()) { -+ completing = true; -+ this.complete(chunk, null); -+ return; -+ } -+ completeFuture = this.toStatus.load(ctx, null, chunk); -+ } -+ } catch (final Throwable throwable) { -+ if (!completing) { -+ this.complete(null, throwable); -+ -+ if (throwable instanceof ThreadDeath) { -+ throw (ThreadDeath)throwable; -+ } -+ return; -+ } -+ -+ this.scheduler.unrecoverableChunkSystemFailure(this.chunkX, this.chunkZ, Map.of( -+ "Target status", ChunkTaskScheduler.stringIfNull(this.toStatus), -+ "From status", ChunkTaskScheduler.stringIfNull(this.fromStatus), -+ "Generation task", this -+ ), throwable); -+ -+ if (!(throwable instanceof ThreadDeath)) { -+ LOGGER.error("Failed to complete status for chunk: status:" + this.toStatus + ", chunk: (" + this.chunkX + "," + this.chunkZ + "), world: " + this.world.getWorld().getName(), throwable); -+ } else { -+ // ensure the chunk system can respond, then die -+ throw (ThreadDeath)throwable; -+ } -+ return; -+ } -+ -+ if (!completeFuture.isDone() && !this.toStatus.warnedAboutNoImmediateComplete.getAndSet(true)) { -+ LOGGER.warn("Future status not complete after scheduling: " + this.toStatus.toString() + ", generate: " + generation); -+ } -+ -+ final ChunkAccess newChunk; -+ -+ try { -+ newChunk = completeFuture.join(); -+ } catch (final Throwable throwable) { -+ this.complete(null, throwable); -+ // ensure the chunk system can respond, then die -+ if (throwable instanceof ThreadDeath) { -+ throw (ThreadDeath)throwable; -+ } -+ return; -+ } -+ -+ if (newChunk == null) { -+ this.complete(null, new IllegalStateException("Chunk for status: " + ChunkUpgradeGenericStatusTask.this.toStatus.toString() + ", generation: " + generation + " should not be null! Future: " + completeFuture).fillInStackTrace()); -+ return; -+ } -+ -+ this.complete(newChunk, null); -+ } -+ -+ protected volatile boolean scheduled; -+ protected static final VarHandle SCHEDULED_HANDLE = ConcurrentUtil.getVarHandle(ChunkUpgradeGenericStatusTask.class, "scheduled", boolean.class); -+ -+ @Override -+ public boolean isScheduled() { -+ return this.scheduled; -+ } -+ -+ @Override -+ public void schedule() { -+ if ((boolean)SCHEDULED_HANDLE.getAndSet((ChunkUpgradeGenericStatusTask)this, true)) { -+ throw new IllegalStateException("Cannot double call schedule()"); -+ } -+ if (this.isEmptyTask()) { -+ if (this.generateTask.cancel()) { -+ this.run(); -+ } -+ } else { -+ this.generateTask.queue(); -+ } -+ } -+ -+ @Override -+ public void cancel() { -+ if (this.generateTask.cancel()) { -+ this.complete(null, null); -+ } -+ } -+ -+ @Override -+ public PrioritisedExecutor.Priority getPriority() { -+ return this.generateTask.getPriority(); -+ } -+ -+ @Override -+ public void lowerPriority(final PrioritisedExecutor.Priority priority) { -+ if (!PrioritisedExecutor.Priority.isValidPriority(priority)) { -+ throw new IllegalArgumentException("Invalid priority " + priority); -+ } -+ this.generateTask.lowerPriority(priority); -+ } -+ -+ @Override -+ public void setPriority(final PrioritisedExecutor.Priority priority) { -+ if (!PrioritisedExecutor.Priority.isValidPriority(priority)) { -+ throw new IllegalArgumentException("Invalid priority " + priority); -+ } -+ this.generateTask.setPriority(priority); -+ } -+ -+ @Override -+ public void raisePriority(final PrioritisedExecutor.Priority priority) { -+ if (!PrioritisedExecutor.Priority.isValidPriority(priority)) { -+ throw new IllegalArgumentException("Invalid priority " + priority); -+ } -+ this.generateTask.raisePriority(priority); -+ } -+} -diff --git a/src/main/java/io/papermc/paper/chunk/system/scheduling/GenericDataLoadTask.java b/src/main/java/io/papermc/paper/chunk/system/scheduling/GenericDataLoadTask.java -new file mode 100644 -index 0000000000000000000000000000000000000000..396d72c00e47cf1669ae20dc839c1c961b1f262a ---- /dev/null -+++ b/src/main/java/io/papermc/paper/chunk/system/scheduling/GenericDataLoadTask.java -@@ -0,0 +1,746 @@ -+package io.papermc.paper.chunk.system.scheduling; -+ -+import ca.spottedleaf.concurrentutil.completable.Completable; -+import ca.spottedleaf.concurrentutil.executor.Cancellable; -+import ca.spottedleaf.concurrentutil.executor.standard.DelayedPrioritisedTask; -+import ca.spottedleaf.concurrentutil.executor.standard.PrioritisedExecutor; -+import ca.spottedleaf.concurrentutil.util.ConcurrentUtil; -+import com.mojang.logging.LogUtils; -+import io.papermc.paper.chunk.system.io.RegionFileIOThread; -+import net.minecraft.nbt.CompoundTag; -+import net.minecraft.server.level.ServerLevel; -+import org.slf4j.Logger; -+import java.lang.invoke.VarHandle; -+import java.util.Map; -+import java.util.concurrent.atomic.AtomicBoolean; -+import java.util.concurrent.atomic.AtomicLong; -+import java.util.function.BiConsumer; -+ -+public abstract class GenericDataLoadTask { -+ -+ private static final Logger LOGGER = LogUtils.getClassLogger(); -+ -+ protected static final CompoundTag CANCELLED_DATA = new CompoundTag(); -+ -+ // reference count is the upper 32 bits -+ protected final AtomicLong stageAndReferenceCount = new AtomicLong(STAGE_NOT_STARTED); -+ -+ protected static final long STAGE_MASK = 0xFFFFFFFFL; -+ protected static final long STAGE_CANCELLED = 0xFFFFFFFFL; -+ protected static final long STAGE_NOT_STARTED = 0L; -+ protected static final long STAGE_LOADING = 1L; -+ protected static final long STAGE_PROCESSING = 2L; -+ protected static final long STAGE_COMPLETED = 3L; -+ -+ // for loading data off disk -+ protected final LoadDataFromDiskTask loadDataFromDiskTask; -+ // processing off-main -+ protected final PrioritisedExecutor.PrioritisedTask processOffMain; -+ // processing on-main -+ protected final PrioritisedExecutor.PrioritisedTask processOnMain; -+ -+ protected final ChunkTaskScheduler scheduler; -+ protected final ServerLevel world; -+ protected final int chunkX; -+ protected final int chunkZ; -+ protected final RegionFileIOThread.RegionFileType type; -+ -+ public GenericDataLoadTask(final ChunkTaskScheduler scheduler, final ServerLevel world, final int chunkX, -+ final int chunkZ, final RegionFileIOThread.RegionFileType type, -+ final PrioritisedExecutor.Priority priority) { -+ this.scheduler = scheduler; -+ this.world = world; -+ this.chunkX = chunkX; -+ this.chunkZ = chunkZ; -+ this.type = type; -+ -+ final ProcessOnMainTask mainTask; -+ if (this.hasOnMain()) { -+ mainTask = new ProcessOnMainTask(); -+ this.processOnMain = this.createOnMain(mainTask, priority); -+ } else { -+ mainTask = null; -+ this.processOnMain = null; -+ } -+ -+ final ProcessOffMainTask offMainTask; -+ if (this.hasOffMain()) { -+ offMainTask = new ProcessOffMainTask(mainTask); -+ this.processOffMain = this.createOffMain(offMainTask, priority); -+ } else { -+ offMainTask = null; -+ this.processOffMain = null; -+ } -+ -+ if (this.processOffMain == null && this.processOnMain == null) { -+ throw new IllegalStateException("Illegal class implementation: " + this.getClass().getName() + ", should be able to schedule at least one task!"); -+ } -+ -+ this.loadDataFromDiskTask = new LoadDataFromDiskTask(world, chunkX, chunkZ, type, new DataLoadCallback(offMainTask, mainTask), priority); -+ } -+ -+ public static final record TaskResult(L left, R right) {} -+ -+ protected abstract boolean hasOffMain(); -+ -+ protected abstract boolean hasOnMain(); -+ -+ protected abstract PrioritisedExecutor.PrioritisedTask createOffMain(final Runnable run, final PrioritisedExecutor.Priority priority); -+ -+ protected abstract PrioritisedExecutor.PrioritisedTask createOnMain(final Runnable run, final PrioritisedExecutor.Priority priority); -+ -+ protected abstract TaskResult runOffMain(final CompoundTag data, final Throwable throwable); -+ -+ protected abstract TaskResult runOnMain(final OnMain data, final Throwable throwable); -+ -+ protected abstract void onComplete(final TaskResult result); -+ -+ protected abstract TaskResult completeOnMainOffMain(final OnMain data, final Throwable throwable); -+ -+ @Override -+ public String toString() { -+ return "GenericDataLoadTask{class: " + this.getClass().getName() + ", world: " + this.world.getWorld().getName() + -+ ", chunk: (" + this.chunkX + "," + this.chunkZ + "), hashcode: " + System.identityHashCode(this) + ", priority: " + this.getPriority() + -+ ", type: " + this.type.toString() + "}"; -+ } -+ -+ public PrioritisedExecutor.Priority getPriority() { -+ if (this.processOnMain != null) { -+ return this.processOnMain.getPriority(); -+ } else { -+ return this.processOffMain.getPriority(); -+ } -+ } -+ -+ public void lowerPriority(final PrioritisedExecutor.Priority priority) { -+ // can't lower I/O tasks, we don't know what they affect -+ if (this.processOffMain != null) { -+ this.processOffMain.lowerPriority(priority); -+ } -+ if (this.processOnMain != null) { -+ this.processOnMain.lowerPriority(priority); -+ } -+ } -+ -+ public void setPriority(final PrioritisedExecutor.Priority priority) { -+ // can't lower I/O tasks, we don't know what they affect -+ this.loadDataFromDiskTask.raisePriority(priority); -+ if (this.processOffMain != null) { -+ this.processOffMain.setPriority(priority); -+ } -+ if (this.processOnMain != null) { -+ this.processOnMain.setPriority(priority); -+ } -+ } -+ -+ public void raisePriority(final PrioritisedExecutor.Priority priority) { -+ // can't lower I/O tasks, we don't know what they affect -+ this.loadDataFromDiskTask.raisePriority(priority); -+ if (this.processOffMain != null) { -+ this.processOffMain.raisePriority(priority); -+ } -+ if (this.processOnMain != null) { -+ this.processOnMain.raisePriority(priority); -+ } -+ } -+ -+ // returns whether scheduleNow() needs to be called -+ public boolean schedule(final boolean delay) { -+ if (this.stageAndReferenceCount.get() != STAGE_NOT_STARTED || -+ !this.stageAndReferenceCount.compareAndSet(STAGE_NOT_STARTED, (1L << 32) | STAGE_LOADING)) { -+ // try and increment reference count -+ int failures = 0; -+ for (long curr = this.stageAndReferenceCount.get();;) { -+ if ((curr & STAGE_MASK) == STAGE_CANCELLED || (curr & STAGE_MASK) == STAGE_COMPLETED) { -+ // cancelled or completed, nothing to do here -+ return false; -+ } -+ -+ if (curr == (curr = this.stageAndReferenceCount.compareAndExchange(curr, curr + (1L << 32)))) { -+ // successful -+ return false; -+ } -+ -+ ++failures; -+ for (int i = 0; i < failures; ++i) { -+ ConcurrentUtil.backoff(); -+ } -+ } -+ } -+ -+ if (!delay) { -+ this.scheduleNow(); -+ return false; -+ } -+ return true; -+ } -+ -+ public void scheduleNow() { -+ this.loadDataFromDiskTask.schedule(); // will schedule the rest -+ } -+ -+ // assumes the current stage cannot be completed -+ // returns false if cancelled, returns true if can proceed -+ private boolean advanceStage(final long expect, final long to) { -+ int failures = 0; -+ for (long curr = this.stageAndReferenceCount.get();;) { -+ if ((curr & STAGE_MASK) != expect) { -+ // must be cancelled -+ return false; -+ } -+ -+ final long newVal = (curr & ~STAGE_MASK) | to; -+ if (curr == (curr = this.stageAndReferenceCount.compareAndExchange(curr, newVal))) { -+ return true; -+ } -+ -+ ++failures; -+ for (int i = 0; i < failures; ++i) { -+ ConcurrentUtil.backoff(); -+ } -+ } -+ } -+ -+ public boolean cancel() { -+ int failures = 0; -+ for (long curr = this.stageAndReferenceCount.get();;) { -+ if ((curr & STAGE_MASK) == STAGE_COMPLETED || (curr & STAGE_MASK) == STAGE_CANCELLED) { -+ return false; -+ } -+ -+ if ((curr & STAGE_MASK) == STAGE_NOT_STARTED || (curr & ~STAGE_MASK) == (1L << 32)) { -+ // no other references, so we can cancel -+ final long newVal = STAGE_CANCELLED; -+ if (curr == (curr = this.stageAndReferenceCount.compareAndExchange(curr, newVal))) { -+ this.loadDataFromDiskTask.cancel(); -+ if (this.processOffMain != null) { -+ this.processOffMain.cancel(); -+ } -+ if (this.processOnMain != null) { -+ this.processOnMain.cancel(); -+ } -+ this.onComplete(null); -+ return true; -+ } -+ } else { -+ if ((curr & ~STAGE_MASK) == (0L << 32)) { -+ throw new IllegalStateException("Reference count cannot be zero here"); -+ } -+ // just decrease the reference count -+ final long newVal = curr - (1L << 32); -+ if (curr == (curr = this.stageAndReferenceCount.compareAndExchange(curr, newVal))) { -+ return false; -+ } -+ } -+ -+ ++failures; -+ for (int i = 0; i < failures; ++i) { -+ ConcurrentUtil.backoff(); -+ } -+ } -+ } -+ -+ protected final class DataLoadCallback implements BiConsumer { -+ -+ protected final ProcessOffMainTask offMainTask; -+ protected final ProcessOnMainTask onMainTask; -+ -+ public DataLoadCallback(final ProcessOffMainTask offMainTask, final ProcessOnMainTask onMainTask) { -+ this.offMainTask = offMainTask; -+ this.onMainTask = onMainTask; -+ } -+ -+ @Override -+ public void accept(final CompoundTag compoundTag, final Throwable throwable) { -+ if (GenericDataLoadTask.this.stageAndReferenceCount.get() == STAGE_CANCELLED) { -+ // don't try to schedule further -+ return; -+ } -+ -+ try { -+ if (compoundTag == CANCELLED_DATA) { -+ // cancelled, except this isn't possible -+ LOGGER.error("Data callback says cancelled, but stage does not?"); -+ return; -+ } -+ -+ // get off of the regionfile callback ASAP, no clue what locks are held right now... -+ if (GenericDataLoadTask.this.processOffMain != null) { -+ this.offMainTask.data = compoundTag; -+ this.offMainTask.throwable = throwable; -+ GenericDataLoadTask.this.processOffMain.queue(); -+ return; -+ } else { -+ // no off-main task, so go straight to main -+ this.onMainTask.data = (OnMain)compoundTag; -+ this.onMainTask.throwable = throwable; -+ GenericDataLoadTask.this.processOnMain.queue(); -+ } -+ } catch (final ThreadDeath death) { -+ throw death; -+ } catch (final Throwable thr2) { -+ LOGGER.error("Failed I/O callback for task: " + GenericDataLoadTask.this.toString(), thr2); -+ GenericDataLoadTask.this.scheduler.unrecoverableChunkSystemFailure( -+ GenericDataLoadTask.this.chunkX, GenericDataLoadTask.this.chunkZ, Map.of( -+ "Callback throwable", ChunkTaskScheduler.stringIfNull(throwable) -+ ), thr2); -+ } -+ } -+ } -+ -+ protected final class ProcessOffMainTask implements Runnable { -+ -+ protected CompoundTag data; -+ protected Throwable throwable; -+ protected final ProcessOnMainTask schedule; -+ -+ public ProcessOffMainTask(final ProcessOnMainTask schedule) { -+ this.schedule = schedule; -+ } -+ -+ @Override -+ public void run() { -+ if (!GenericDataLoadTask.this.advanceStage(STAGE_LOADING, this.schedule == null ? STAGE_COMPLETED : STAGE_PROCESSING)) { -+ // cancelled -+ return; -+ } -+ final TaskResult newData = GenericDataLoadTask.this.runOffMain(this.data, this.throwable); -+ -+ if (GenericDataLoadTask.this.stageAndReferenceCount.get() == STAGE_CANCELLED) { -+ // don't try to schedule further -+ return; -+ } -+ -+ if (this.schedule != null) { -+ final TaskResult syncComplete = GenericDataLoadTask.this.completeOnMainOffMain(newData.left, newData.right); -+ -+ if (syncComplete != null) { -+ if (GenericDataLoadTask.this.advanceStage(STAGE_PROCESSING, STAGE_COMPLETED)) { -+ GenericDataLoadTask.this.onComplete(syncComplete); -+ } // else: cancelled -+ return; -+ } -+ -+ this.schedule.data = newData.left; -+ this.schedule.throwable = newData.right; -+ -+ GenericDataLoadTask.this.processOnMain.queue(); -+ } else { -+ GenericDataLoadTask.this.onComplete((TaskResult)newData); -+ } -+ } -+ } -+ -+ protected final class ProcessOnMainTask implements Runnable { -+ -+ protected OnMain data; -+ protected Throwable throwable; -+ -+ @Override -+ public void run() { -+ if (!GenericDataLoadTask.this.advanceStage(STAGE_PROCESSING, STAGE_COMPLETED)) { -+ // cancelled -+ return; -+ } -+ final TaskResult result = GenericDataLoadTask.this.runOnMain(this.data, this.throwable); -+ -+ GenericDataLoadTask.this.onComplete(result); -+ } -+ } -+ -+ public static final class LoadDataFromDiskTask { -+ -+ protected volatile int priority; -+ protected static final VarHandle PRIORITY_HANDLE = ConcurrentUtil.getVarHandle(LoadDataFromDiskTask.class, "priority", int.class); -+ -+ protected static final int PRIORITY_EXECUTED = Integer.MIN_VALUE >>> 0; -+ protected static final int PRIORITY_LOAD_SCHEDULED = Integer.MIN_VALUE >>> 1; -+ protected static final int PRIORITY_UNLOAD_SCHEDULED = Integer.MIN_VALUE >>> 2; -+ -+ protected static final int PRIORITY_FLAGS = ~Character.MAX_VALUE; -+ -+ protected final int getPriorityVolatile() { -+ return (int)PRIORITY_HANDLE.getVolatile((LoadDataFromDiskTask)this); -+ } -+ -+ protected final int compareAndExchangePriorityVolatile(final int expect, final int update) { -+ return (int)PRIORITY_HANDLE.compareAndExchange((LoadDataFromDiskTask)this, (int)expect, (int)update); -+ } -+ -+ protected final int getAndOrPriorityVolatile(final int val) { -+ return (int)PRIORITY_HANDLE.getAndBitwiseOr((LoadDataFromDiskTask)this, (int)val); -+ } -+ -+ protected final void setPriorityPlain(final int val) { -+ PRIORITY_HANDLE.set((LoadDataFromDiskTask)this, (int)val); -+ } -+ -+ private final ServerLevel world; -+ private final int chunkX; -+ private final int chunkZ; -+ -+ private final RegionFileIOThread.RegionFileType type; -+ private Cancellable dataLoadTask; -+ private Cancellable dataUnloadCancellable; -+ private DelayedPrioritisedTask dataUnloadTask; -+ -+ private final BiConsumer onComplete; -+ -+ // onComplete should be caller sensitive, it may complete synchronously with schedule() - which does -+ // hold a priority lock. -+ public LoadDataFromDiskTask(final ServerLevel world, final int chunkX, final int chunkZ, -+ final RegionFileIOThread.RegionFileType type, -+ final BiConsumer onComplete, -+ final PrioritisedExecutor.Priority priority) { -+ if (!PrioritisedExecutor.Priority.isValidPriority(priority)) { -+ throw new IllegalArgumentException("Invalid priority " + priority); -+ } -+ this.world = world; -+ this.chunkX = chunkX; -+ this.chunkZ = chunkZ; -+ this.type = type; -+ this.onComplete = onComplete; -+ this.setPriorityPlain(priority.priority); -+ } -+ -+ private void complete(final CompoundTag data, final Throwable throwable) { -+ try { -+ this.onComplete.accept(data, throwable); -+ } catch (final Throwable thr2) { -+ this.world.chunkTaskScheduler.unrecoverableChunkSystemFailure(this.chunkX, this.chunkZ, Map.of( -+ "Completed throwable", ChunkTaskScheduler.stringIfNull(throwable), -+ "Regionfile type", ChunkTaskScheduler.stringIfNull(this.type) -+ ), thr2); -+ if (thr2 instanceof ThreadDeath) { -+ throw (ThreadDeath)thr2; -+ } -+ } -+ } -+ -+ protected boolean markExecuting() { -+ return (this.getAndOrPriorityVolatile(PRIORITY_EXECUTED) & PRIORITY_EXECUTED) == 0; -+ } -+ -+ protected boolean isMarkedExecuted() { -+ return (this.getPriorityVolatile() & PRIORITY_EXECUTED) != 0; -+ } -+ -+ public void lowerPriority(final PrioritisedExecutor.Priority priority) { -+ if (!PrioritisedExecutor.Priority.isValidPriority(priority)) { -+ throw new IllegalArgumentException("Invalid priority " + priority); -+ } -+ -+ int failures = 0; -+ for (int curr = this.getPriorityVolatile();;) { -+ if ((curr & PRIORITY_EXECUTED) != 0) { -+ // cancelled or executed -+ return; -+ } -+ -+ if ((curr & PRIORITY_LOAD_SCHEDULED) != 0) { -+ RegionFileIOThread.lowerPriority(this.world, this.chunkX, this.chunkZ, this.type, priority); -+ return; -+ } -+ -+ if ((curr & PRIORITY_UNLOAD_SCHEDULED) != 0) { -+ if (this.dataUnloadTask != null) { -+ this.dataUnloadTask.lowerPriority(priority); -+ } -+ // no return - we need to propagate priority -+ } -+ -+ if (!priority.isHigherPriority(curr & ~PRIORITY_FLAGS)) { -+ return; -+ } -+ -+ if (curr == (curr = this.compareAndExchangePriorityVolatile(curr, priority.priority | (curr & PRIORITY_FLAGS)))) { -+ return; -+ } -+ -+ // failed, retry -+ -+ ++failures; -+ for (int i = 0; i < failures; ++i) { -+ ConcurrentUtil.backoff(); -+ } -+ } -+ } -+ -+ public void setPriority(final PrioritisedExecutor.Priority priority) { -+ if (!PrioritisedExecutor.Priority.isValidPriority(priority)) { -+ throw new IllegalArgumentException("Invalid priority " + priority); -+ } -+ -+ int failures = 0; -+ for (int curr = this.getPriorityVolatile();;) { -+ if ((curr & PRIORITY_EXECUTED) != 0) { -+ // cancelled or executed -+ return; -+ } -+ -+ if ((curr & PRIORITY_LOAD_SCHEDULED) != 0) { -+ RegionFileIOThread.setPriority(this.world, this.chunkX, this.chunkZ, this.type, priority); -+ return; -+ } -+ -+ if ((curr & PRIORITY_UNLOAD_SCHEDULED) != 0) { -+ if (this.dataUnloadTask != null) { -+ this.dataUnloadTask.setPriority(priority); -+ } -+ // no return - we need to propagate priority -+ } -+ -+ if (curr == (curr = this.compareAndExchangePriorityVolatile(curr, priority.priority | (curr & PRIORITY_FLAGS)))) { -+ return; -+ } -+ -+ // failed, retry -+ -+ ++failures; -+ for (int i = 0; i < failures; ++i) { -+ ConcurrentUtil.backoff(); -+ } -+ } -+ } -+ -+ public void raisePriority(final PrioritisedExecutor.Priority priority) { -+ if (!PrioritisedExecutor.Priority.isValidPriority(priority)) { -+ throw new IllegalArgumentException("Invalid priority " + priority); -+ } -+ -+ int failures = 0; -+ for (int curr = this.getPriorityVolatile();;) { -+ if ((curr & PRIORITY_EXECUTED) != 0) { -+ // cancelled or executed -+ return; -+ } -+ -+ if ((curr & PRIORITY_LOAD_SCHEDULED) != 0) { -+ RegionFileIOThread.raisePriority(this.world, this.chunkX, this.chunkZ, this.type, priority); -+ return; -+ } -+ -+ if ((curr & PRIORITY_UNLOAD_SCHEDULED) != 0) { -+ if (this.dataUnloadTask != null) { -+ this.dataUnloadTask.raisePriority(priority); -+ } -+ // no return - we need to propagate priority -+ } -+ -+ if (!priority.isLowerPriority(curr & ~PRIORITY_FLAGS)) { -+ return; -+ } -+ -+ if (curr == (curr = this.compareAndExchangePriorityVolatile(curr, priority.priority | (curr & PRIORITY_FLAGS)))) { -+ return; -+ } -+ -+ // failed, retry -+ -+ ++failures; -+ for (int i = 0; i < failures; ++i) { -+ ConcurrentUtil.backoff(); -+ } -+ } -+ } -+ -+ public void cancel() { -+ if ((this.getAndOrPriorityVolatile(PRIORITY_EXECUTED) & PRIORITY_EXECUTED) != 0) { -+ // cancelled or executed already -+ return; -+ } -+ -+ // OK if we miss the field read, the task cannot complete if the cancelled bit is set and -+ // the write to dataLoadTask will check for the cancelled bit -+ if (this.dataUnloadCancellable != null) { -+ this.dataUnloadCancellable.cancel(); -+ } -+ -+ if (this.dataLoadTask != null) { -+ this.dataLoadTask.cancel(); -+ } -+ -+ this.complete(CANCELLED_DATA, null); -+ } -+ -+ private final AtomicBoolean scheduled = new AtomicBoolean(); -+ -+ public void schedule() { -+ if (this.scheduled.getAndSet(true)) { -+ throw new IllegalStateException("schedule() called twice"); -+ } -+ int priority = this.getPriorityVolatile(); -+ -+ if ((priority & PRIORITY_EXECUTED) != 0) { -+ // cancelled -+ return; -+ } -+ -+ final BiConsumer consumer = (final CompoundTag data, final Throwable thr) -> { -+ // because cancelScheduled() cannot actually stop this task from executing in every case, we need -+ // to mark complete here to ensure we do not double complete -+ if (LoadDataFromDiskTask.this.markExecuting()) { -+ LoadDataFromDiskTask.this.complete(data, thr); -+ } // else: cancelled -+ }; -+ -+ final PrioritisedExecutor.Priority initialPriority = PrioritisedExecutor.Priority.getPriority(priority); -+ boolean scheduledUnload = false; -+ -+ final NewChunkHolder holder = this.world.chunkTaskScheduler.chunkHolderManager.getChunkHolder(this.chunkX, this.chunkZ); -+ if (holder != null) { -+ final BiConsumer unloadConsumer = (final CompoundTag data, final Throwable thr) -> { -+ if (data != null) { -+ consumer.accept(data, null); -+ } else { -+ // need to schedule task -+ LoadDataFromDiskTask.this.schedule(false, consumer, PrioritisedExecutor.Priority.getPriority(LoadDataFromDiskTask.this.getPriorityVolatile() & ~PRIORITY_FLAGS)); -+ } -+ }; -+ Cancellable unloadCancellable = null; -+ CompoundTag syncComplete = null; -+ final NewChunkHolder.UnloadTask unloadTask = holder.getUnloadTask(this.type); // can be null if no task exists -+ final Completable unloadCompletable = unloadTask == null ? null : unloadTask.completable(); -+ if (unloadCompletable != null) { -+ unloadCancellable = unloadCompletable.addAsynchronousWaiter(unloadConsumer); -+ if (unloadCancellable == null) { -+ syncComplete = unloadCompletable.getResult(); -+ } -+ } -+ -+ if (syncComplete != null) { -+ consumer.accept(syncComplete, null); -+ return; -+ } -+ -+ if (unloadCancellable != null) { -+ scheduledUnload = true; -+ this.dataUnloadCancellable = unloadCancellable; -+ this.dataUnloadTask = unloadTask.task(); -+ } -+ } -+ -+ this.schedule(scheduledUnload, consumer, initialPriority); -+ } -+ -+ private void schedule(final boolean scheduledUnload, final BiConsumer consumer, final PrioritisedExecutor.Priority initialPriority) { -+ int priority = this.getPriorityVolatile(); -+ -+ if ((priority & PRIORITY_EXECUTED) != 0) { -+ // cancelled -+ return; -+ } -+ -+ if (!scheduledUnload) { -+ this.dataLoadTask = RegionFileIOThread.loadDataAsync( -+ this.world, this.chunkX, this.chunkZ, this.type, consumer, -+ initialPriority.isHigherPriority(PrioritisedExecutor.Priority.NORMAL), initialPriority -+ ); -+ } -+ -+ int failures = 0; -+ for (;;) { -+ if (priority == (priority = this.compareAndExchangePriorityVolatile(priority, priority | (scheduledUnload ? PRIORITY_UNLOAD_SCHEDULED : PRIORITY_LOAD_SCHEDULED)))) { -+ return; -+ } -+ -+ if ((priority & PRIORITY_EXECUTED) != 0) { -+ // cancelled or executed -+ if (this.dataUnloadCancellable != null) { -+ this.dataUnloadCancellable.cancel(); -+ } -+ -+ if (this.dataLoadTask != null) { -+ this.dataLoadTask.cancel(); -+ } -+ return; -+ } -+ -+ if (scheduledUnload) { -+ if (this.dataUnloadTask != null) { -+ this.dataUnloadTask.setPriority(PrioritisedExecutor.Priority.getPriority(priority & ~PRIORITY_FLAGS)); -+ } -+ } else { -+ RegionFileIOThread.setPriority(this.world, this.chunkX, this.chunkZ, this.type, PrioritisedExecutor.Priority.getPriority(priority & ~PRIORITY_FLAGS)); -+ } -+ -+ ++failures; -+ for (int i = 0; i < failures; ++i) { -+ ConcurrentUtil.backoff(); -+ } -+ } -+ } -+ -+ /* -+ private static final class LoadDataPriorityHolder extends PriorityHolder { -+ -+ protected final LoadDataFromDiskTask task; -+ -+ protected LoadDataPriorityHolder(final PrioritisedExecutor.Priority priority, final LoadDataFromDiskTask task) { -+ super(priority); -+ this.task = task; -+ } -+ -+ @Override -+ protected void cancelScheduled() { -+ final Cancellable dataLoadTask = this.task.dataLoadTask; -+ if (dataLoadTask != null) { -+ // OK if we miss the field read, the task cannot complete if the cancelled bit is set and -+ // the write to dataLoadTask will check for the cancelled bit -+ this.task.dataLoadTask.cancel(); -+ } -+ this.task.complete(CANCELLED_DATA, null); -+ } -+ -+ @Override -+ protected PrioritisedExecutor.Priority getScheduledPriority() { -+ final LoadDataFromDiskTask task = this.task; -+ return RegionFileIOThread.getPriority(task.world, task.chunkX, task.chunkZ, task.type); -+ } -+ -+ @Override -+ protected void scheduleTask(final PrioritisedExecutor.Priority priority) { -+ final LoadDataFromDiskTask task = this.task; -+ final BiConsumer consumer = (final CompoundTag data, final Throwable thr) -> { -+ // because cancelScheduled() cannot actually stop this task from executing in every case, we need -+ // to mark complete here to ensure we do not double complete -+ if (LoadDataPriorityHolder.this.markExecuting()) { -+ LoadDataPriorityHolder.this.task.complete(data, thr); -+ } // else: cancelled -+ }; -+ task.dataLoadTask = RegionFileIOThread.loadDataAsync( -+ task.world, task.chunkX, task.chunkZ, task.type, consumer, -+ priority.isHigherPriority(PrioritisedExecutor.Priority.NORMAL), priority -+ ); -+ if (this.isMarkedExecuted()) { -+ // if we are marked as completed, it could be: -+ // 1. we were cancelled -+ // 2. the consumer was completed -+ // in the 2nd case, cancel() does nothing -+ // in the 1st case, we ensure cancel() is called as it is possible for the cancelling thread -+ // to miss the field write here -+ task.dataLoadTask.cancel(); -+ } -+ } -+ -+ @Override -+ protected void lowerPriorityScheduled(final PrioritisedExecutor.Priority priority) { -+ final LoadDataFromDiskTask task = this.task; -+ RegionFileIOThread.lowerPriority(task.world, task.chunkX, task.chunkZ, task.type, priority); -+ } -+ -+ @Override -+ protected void setPriorityScheduled(final PrioritisedExecutor.Priority priority) { -+ final LoadDataFromDiskTask task = this.task; -+ RegionFileIOThread.setPriority(task.world, task.chunkX, task.chunkZ, task.type, priority); -+ } -+ -+ @Override -+ protected void raisePriorityScheduled(final PrioritisedExecutor.Priority priority) { -+ final LoadDataFromDiskTask task = this.task; -+ RegionFileIOThread.raisePriority(task.world, task.chunkX, task.chunkZ, task.type, priority); -+ } -+ } -+ */ -+ } -+} -diff --git a/src/main/java/io/papermc/paper/chunk/system/scheduling/NewChunkHolder.java b/src/main/java/io/papermc/paper/chunk/system/scheduling/NewChunkHolder.java -new file mode 100644 -index 0000000000000000000000000000000000000000..56b07a3306e5735816c8d89601b519cb0db6379a ---- /dev/null -+++ b/src/main/java/io/papermc/paper/chunk/system/scheduling/NewChunkHolder.java -@@ -0,0 +1,2106 @@ -+package io.papermc.paper.chunk.system.scheduling; -+ -+import ca.spottedleaf.concurrentutil.completable.Completable; -+import ca.spottedleaf.concurrentutil.executor.Cancellable; -+import ca.spottedleaf.concurrentutil.executor.standard.DelayedPrioritisedTask; -+import ca.spottedleaf.concurrentutil.executor.standard.PrioritisedExecutor; -+import ca.spottedleaf.concurrentutil.lock.ReentrantAreaLock; -+import ca.spottedleaf.concurrentutil.util.ConcurrentUtil; -+import com.google.gson.JsonArray; -+import com.google.gson.JsonElement; -+import com.google.gson.JsonObject; -+import com.google.gson.JsonPrimitive; -+import com.mojang.logging.LogUtils; -+import io.papermc.paper.chunk.system.io.RegionFileIOThread; -+import io.papermc.paper.chunk.system.poi.PoiChunk; -+import io.papermc.paper.util.CoordinateUtils; -+import io.papermc.paper.util.TickThread; -+import io.papermc.paper.util.WorldUtil; -+import io.papermc.paper.world.ChunkEntitySlices; -+import it.unimi.dsi.fastutil.objects.Reference2ObjectLinkedOpenHashMap; -+import it.unimi.dsi.fastutil.objects.Reference2ObjectMap; -+import it.unimi.dsi.fastutil.objects.Reference2ObjectOpenHashMap; -+import it.unimi.dsi.fastutil.objects.ReferenceLinkedOpenHashSet; -+import net.minecraft.nbt.CompoundTag; -+import net.minecraft.server.level.ChunkHolder; -+import net.minecraft.server.level.ChunkLevel; -+import net.minecraft.server.level.FullChunkStatus; -+import net.minecraft.server.level.ServerLevel; -+import net.minecraft.server.level.TicketType; -+import net.minecraft.world.entity.Entity; -+import net.minecraft.world.level.ChunkPos; -+import net.minecraft.world.level.chunk.ChunkAccess; -+import net.minecraft.world.level.chunk.status.ChunkStatus; -+import net.minecraft.world.level.chunk.ImposterProtoChunk; -+import net.minecraft.world.level.chunk.LevelChunk; -+import net.minecraft.world.level.chunk.storage.ChunkSerializer; -+import net.minecraft.world.level.chunk.storage.EntityStorage; -+import org.slf4j.Logger; -+import java.lang.invoke.VarHandle; -+import java.util.ArrayList; -+import java.util.Iterator; -+import java.util.List; -+import java.util.Map; -+import java.util.Objects; -+import java.util.concurrent.atomic.AtomicBoolean; -+import java.util.function.Consumer; -+ -+public final class NewChunkHolder { -+ -+ private static final Logger LOGGER = LogUtils.getClassLogger(); -+ -+ public static final Thread.UncaughtExceptionHandler CHUNKSYSTEM_UNCAUGHT_EXCEPTION_HANDLER = new Thread.UncaughtExceptionHandler() { -+ @Override -+ public void uncaughtException(final Thread thread, final Throwable throwable) { -+ if (!(throwable instanceof ThreadDeath)) { -+ LOGGER.error("Uncaught exception in thread " + thread.getName(), throwable); -+ } -+ } -+ }; -+ -+ public final ServerLevel world; -+ public final int chunkX; -+ public final int chunkZ; -+ -+ public final ChunkTaskScheduler scheduler; -+ -+ // load/unload state -+ -+ // chunk data state -+ -+ private ChunkEntitySlices entityChunk; -+ // entity chunk that is loaded, but not yet deserialized -+ private CompoundTag pendingEntityChunk; -+ -+ ChunkEntitySlices loadInEntityChunk(final boolean transientChunk) { -+ TickThread.ensureTickThread(this.world, this.chunkX, this.chunkZ, "Cannot sync load entity data off-main"); -+ final CompoundTag entityChunk; -+ final ChunkEntitySlices ret; -+ final ReentrantAreaLock.Node schedulingLock = this.scheduler.schedulingLockArea.lock(this.chunkX, this.chunkZ); -+ try { -+ if (this.entityChunk != null && (transientChunk || !this.entityChunk.isTransient())) { -+ return this.entityChunk; -+ } -+ final CompoundTag pendingEntityChunk = this.pendingEntityChunk; -+ if (!transientChunk && pendingEntityChunk == null) { -+ throw new IllegalStateException("Must load entity data from disk before loading in the entity chunk!"); -+ } -+ -+ if (this.entityChunk == null) { -+ ret = this.entityChunk = new ChunkEntitySlices( -+ this.world, this.chunkX, this.chunkZ, this.getChunkStatus(), -+ WorldUtil.getMinSection(this.world), WorldUtil.getMaxSection(this.world) -+ ); -+ -+ ret.setTransient(transientChunk); -+ -+ this.world.getEntityLookup().entitySectionLoad(this.chunkX, this.chunkZ, ret); -+ } else { -+ // transientChunk = false here -+ ret = this.entityChunk; -+ this.entityChunk.setTransient(false); -+ } -+ -+ if (!transientChunk) { -+ this.pendingEntityChunk = null; -+ entityChunk = pendingEntityChunk == EMPTY_ENTITY_CHUNK ? null : pendingEntityChunk; -+ } else { -+ entityChunk = null; -+ } -+ } finally { -+ this.scheduler.schedulingLockArea.unlock(schedulingLock); -+ } -+ -+ if (!transientChunk) { -+ if (entityChunk != null) { -+ final List entities = EntityStorage.readEntities(this.world, entityChunk); -+ -+ this.world.getEntityLookup().addEntityChunkEntities(entities, new ChunkPos(this.chunkX, this.chunkZ)); -+ } -+ } -+ -+ return ret; -+ } -+ -+ // needed to distinguish whether the entity chunk has been read from disk but is empty or whether it has _not_ -+ // been read from disk -+ private static final CompoundTag EMPTY_ENTITY_CHUNK = new CompoundTag(); -+ -+ private ChunkLoadTask.EntityDataLoadTask entityDataLoadTask; -+ // note: if entityDataLoadTask is cancelled, but on its completion entityDataLoadTaskWaiters.size() != 0, -+ // then the task is rescheduled -+ private List entityDataLoadTaskWaiters; -+ -+ public ChunkLoadTask.EntityDataLoadTask getEntityDataLoadTask() { -+ return this.entityDataLoadTask; -+ } -+ -+ // must hold schedule lock for the two below functions -+ -+ // returns only if the data has been loaded from disk, DOES NOT relate to whether it has been deserialized -+ // or added into the world (or even into entityChunk) -+ public boolean isEntityChunkNBTLoaded() { -+ return (this.entityChunk != null && !this.entityChunk.isTransient()) || this.pendingEntityChunk != null; -+ } -+ -+ private void completeEntityLoad(final GenericDataLoadTask.TaskResult result) { -+ final List completeWaiters; -+ ChunkLoadTask.EntityDataLoadTask entityDataLoadTask = null; -+ boolean scheduleEntityTask = false; -+ ReentrantAreaLock.Node schedulingLock = this.scheduler.schedulingLockArea.lock(this.chunkX, this.chunkZ); -+ try { -+ final List waiters = this.entityDataLoadTaskWaiters; -+ this.entityDataLoadTask = null; -+ if (result != null) { -+ this.entityDataLoadTaskWaiters = null; -+ this.pendingEntityChunk = result.left() == null ? EMPTY_ENTITY_CHUNK : result.left(); -+ if (result.right() != null) { -+ LOGGER.error("Unhandled entity data load exception, data data will be lost: ", result.right()); -+ } -+ -+ for (final GenericDataLoadTaskCallback callback : waiters) { -+ callback.markCompleted(); -+ } -+ -+ completeWaiters = waiters; -+ } else { -+ // cancelled -+ completeWaiters = null; -+ -+ // need to re-schedule? -+ if (waiters.isEmpty()) { -+ this.entityDataLoadTaskWaiters = null; -+ // no tasks to schedule _for_ -+ } else { -+ entityDataLoadTask = this.entityDataLoadTask = new ChunkLoadTask.EntityDataLoadTask( -+ this.scheduler, this.world, this.chunkX, this.chunkZ, this.getEffectivePriority() -+ ); -+ entityDataLoadTask.addCallback(this::completeEntityLoad); -+ // need one schedule() per waiter -+ for (final GenericDataLoadTaskCallback callback : waiters) { -+ scheduleEntityTask |= entityDataLoadTask.schedule(true); -+ } -+ } -+ } -+ } finally { -+ this.scheduler.schedulingLockArea.unlock(schedulingLock); -+ } -+ -+ if (scheduleEntityTask) { -+ entityDataLoadTask.scheduleNow(); -+ } -+ -+ // avoid holding the scheduling lock while completing -+ if (completeWaiters != null) { -+ for (final GenericDataLoadTaskCallback callback : completeWaiters) { -+ callback.acceptCompleted(result); -+ } -+ } -+ -+ schedulingLock = this.scheduler.schedulingLockArea.lock(this.chunkX, this.chunkZ); -+ try { -+ this.checkUnload(); -+ } finally { -+ this.scheduler.schedulingLockArea.unlock(schedulingLock); -+ } -+ } -+ -+ // note: it is guaranteed that the consumer cannot be called for the entirety that the schedule lock is held -+ // however, when the consumer is invoked, it will hold the schedule lock -+ public GenericDataLoadTaskCallback getOrLoadEntityData(final Consumer> consumer) { -+ if (this.isEntityChunkNBTLoaded()) { -+ throw new IllegalStateException("Cannot load entity data, it is already loaded"); -+ } -+ // why not just acquire the lock? because the caller NEEDS to call isEntityChunkNBTLoaded before this! -+ if (!this.scheduler.schedulingLockArea.isHeldByCurrentThread(this.chunkX, this.chunkZ)) { -+ throw new IllegalStateException("Must hold scheduling lock"); -+ } -+ -+ final GenericDataLoadTaskCallback ret = new EntityDataLoadTaskCallback((Consumer)consumer, this); -+ -+ if (this.entityDataLoadTask == null) { -+ this.entityDataLoadTask = new ChunkLoadTask.EntityDataLoadTask( -+ this.scheduler, this.world, this.chunkX, this.chunkZ, this.getEffectivePriority() -+ ); -+ this.entityDataLoadTask.addCallback(this::completeEntityLoad); -+ this.entityDataLoadTaskWaiters = new ArrayList<>(); -+ } -+ this.entityDataLoadTaskWaiters.add(ret); -+ if (this.entityDataLoadTask.schedule(true)) { -+ ret.schedule = this.entityDataLoadTask; -+ } -+ this.checkUnload(); -+ -+ return ret; -+ } -+ -+ private static final class EntityDataLoadTaskCallback extends GenericDataLoadTaskCallback { -+ -+ public EntityDataLoadTaskCallback(final Consumer> consumer, final NewChunkHolder chunkHolder) { -+ super(consumer, chunkHolder); -+ } -+ -+ @Override -+ void internalCancel() { -+ this.chunkHolder.entityDataLoadTaskWaiters.remove(this); -+ this.chunkHolder.entityDataLoadTask.cancel(); -+ } -+ } -+ -+ private PoiChunk poiChunk; -+ -+ private ChunkLoadTask.PoiDataLoadTask poiDataLoadTask; -+ // note: if entityDataLoadTask is cancelled, but on its completion entityDataLoadTaskWaiters.size() != 0, -+ // then the task is rescheduled -+ private List poiDataLoadTaskWaiters; -+ -+ public ChunkLoadTask.PoiDataLoadTask getPoiDataLoadTask() { -+ return this.poiDataLoadTask; -+ } -+ -+ // must hold schedule lock for the two below functions -+ -+ public boolean isPoiChunkLoaded() { -+ return this.poiChunk != null; -+ } -+ -+ private void completePoiLoad(final GenericDataLoadTask.TaskResult result) { -+ final List completeWaiters; -+ ChunkLoadTask.PoiDataLoadTask poiDataLoadTask = null; -+ boolean schedulePoiTask = false; -+ ReentrantAreaLock.Node schedulingLock = this.scheduler.schedulingLockArea.lock(this.chunkX, this.chunkZ); -+ try { -+ final List waiters = this.poiDataLoadTaskWaiters; -+ this.poiDataLoadTask = null; -+ if (result != null) { -+ this.poiDataLoadTaskWaiters = null; -+ this.poiChunk = result.left(); -+ if (result.right() != null) { -+ LOGGER.error("Unhandled poi load exception, poi data will be lost: ", result.right()); -+ } -+ -+ for (final GenericDataLoadTaskCallback callback : waiters) { -+ callback.markCompleted(); -+ } -+ -+ completeWaiters = waiters; -+ } else { -+ // cancelled -+ completeWaiters = null; -+ -+ // need to re-schedule? -+ if (waiters.isEmpty()) { -+ this.poiDataLoadTaskWaiters = null; -+ // no tasks to schedule _for_ -+ } else { -+ poiDataLoadTask = this.poiDataLoadTask = new ChunkLoadTask.PoiDataLoadTask( -+ this.scheduler, this.world, this.chunkX, this.chunkZ, this.getEffectivePriority() -+ ); -+ poiDataLoadTask.addCallback(this::completePoiLoad); -+ // need one schedule() per waiter -+ for (final GenericDataLoadTaskCallback callback : waiters) { -+ schedulePoiTask |= poiDataLoadTask.schedule(true); -+ } -+ } -+ } -+ } finally { -+ this.scheduler.schedulingLockArea.unlock(schedulingLock); -+ } -+ -+ if (schedulePoiTask) { -+ poiDataLoadTask.scheduleNow(); -+ } -+ -+ // avoid holding the scheduling lock while completing -+ if (completeWaiters != null) { -+ for (final GenericDataLoadTaskCallback callback : completeWaiters) { -+ callback.acceptCompleted(result); -+ } -+ } -+ schedulingLock = this.scheduler.schedulingLockArea.lock(this.chunkX, this.chunkZ); -+ try { -+ this.checkUnload(); -+ } finally { -+ this.scheduler.schedulingLockArea.unlock(schedulingLock); -+ } -+ } -+ -+ // note: it is guaranteed that the consumer cannot be called for the entirety that the schedule lock is held -+ // however, when the consumer is invoked, it will hold the schedule lock -+ public GenericDataLoadTaskCallback getOrLoadPoiData(final Consumer> consumer) { -+ if (this.isPoiChunkLoaded()) { -+ throw new IllegalStateException("Cannot load poi data, it is already loaded"); -+ } -+ // why not just acquire the lock? because the caller NEEDS to call isPoiChunkLoaded before this! -+ if (!this.scheduler.schedulingLockArea.isHeldByCurrentThread(this.chunkX, this.chunkZ)) { -+ throw new IllegalStateException("Must hold scheduling lock"); -+ } -+ -+ final GenericDataLoadTaskCallback ret = new PoiDataLoadTaskCallback((Consumer)consumer, this); -+ -+ if (this.poiDataLoadTask == null) { -+ this.poiDataLoadTask = new ChunkLoadTask.PoiDataLoadTask( -+ this.scheduler, this.world, this.chunkX, this.chunkZ, this.getEffectivePriority() -+ ); -+ this.poiDataLoadTask.addCallback(this::completePoiLoad); -+ this.poiDataLoadTaskWaiters = new ArrayList<>(); -+ } -+ this.poiDataLoadTaskWaiters.add(ret); -+ if (this.poiDataLoadTask.schedule(true)) { -+ ret.schedule = this.poiDataLoadTask; -+ } -+ this.checkUnload(); -+ -+ return ret; -+ } -+ -+ private static final class PoiDataLoadTaskCallback extends GenericDataLoadTaskCallback { -+ -+ public PoiDataLoadTaskCallback(final Consumer> consumer, final NewChunkHolder chunkHolder) { -+ super(consumer, chunkHolder); -+ } -+ -+ @Override -+ void internalCancel() { -+ this.chunkHolder.poiDataLoadTaskWaiters.remove(this); -+ this.chunkHolder.poiDataLoadTask.cancel(); -+ } -+ } -+ -+ public static abstract class GenericDataLoadTaskCallback implements Cancellable { -+ -+ protected final Consumer> consumer; -+ protected final NewChunkHolder chunkHolder; -+ protected boolean completed; -+ protected GenericDataLoadTask schedule; -+ protected final AtomicBoolean scheduled = new AtomicBoolean(); -+ -+ public GenericDataLoadTaskCallback(final Consumer> consumer, -+ final NewChunkHolder chunkHolder) { -+ this.consumer = consumer; -+ this.chunkHolder = chunkHolder; -+ } -+ -+ public void schedule() { -+ if (this.scheduled.getAndSet(true)) { -+ throw new IllegalStateException("Double calling schedule()"); -+ } -+ if (this.schedule != null) { -+ this.schedule.scheduleNow(); -+ this.schedule = null; -+ } -+ } -+ -+ boolean isCompleted() { -+ return this.completed; -+ } -+ -+ // must hold scheduling lock -+ private boolean setCompleted() { -+ if (this.completed) { -+ return false; -+ } -+ return this.completed = true; -+ } -+ -+ // must hold scheduling lock -+ void markCompleted() { -+ if (this.completed) { -+ throw new IllegalStateException("May not be completed here"); -+ } -+ this.completed = true; -+ } -+ -+ void acceptCompleted(final GenericDataLoadTask.TaskResult result) { -+ if (result != null) { -+ if (this.completed) { -+ this.consumer.accept(result); -+ } else { -+ throw new IllegalStateException("Cannot be uncompleted at this point"); -+ } -+ } else { -+ throw new NullPointerException("Result cannot be null (cancelled)"); -+ } -+ } -+ -+ // holds scheduling lock -+ abstract void internalCancel(); -+ -+ @Override -+ public boolean cancel() { -+ final NewChunkHolder holder = this.chunkHolder; // Folia - use area based lock to reduce contention -+ final ReentrantAreaLock.Node schedulingLock = holder.scheduler.schedulingLockArea.lock(holder.chunkX, holder.chunkZ); -+ try { -+ if (!this.completed) { -+ this.completed = true; -+ this.internalCancel(); -+ return true; -+ } -+ return false; -+ } finally { -+ holder.scheduler.schedulingLockArea.unlock(schedulingLock); -+ } -+ } -+ } -+ -+ private ChunkAccess currentChunk; -+ -+ // generation status state -+ -+ /** -+ * Current status the chunk has been brought up to by the chunk system. null indicates no work at all -+ */ -+ private ChunkStatus currentGenStatus; -+ -+ // This allows unsynchronised access to the chunk and last gen status -+ private volatile ChunkCompletion lastChunkCompletion; -+ -+ public ChunkCompletion getLastChunkCompletion() { -+ return this.lastChunkCompletion; -+ } -+ -+ public static final record ChunkCompletion(ChunkAccess chunk, ChunkStatus genStatus) {}; -+ -+ /** -+ * The target final chunk status the chunk system will bring the chunk to. -+ */ -+ private ChunkStatus requestedGenStatus; -+ -+ private ChunkProgressionTask generationTask; -+ private ChunkStatus generationTaskStatus; -+ -+ /** -+ * contains the neighbours that this chunk generation is blocking on -+ */ -+ protected final ReferenceLinkedOpenHashSet neighboursBlockingGenTask = new ReferenceLinkedOpenHashSet<>(4); -+ -+ /** -+ * map of ChunkHolder -> Required Status for this chunk -+ */ -+ protected final Reference2ObjectLinkedOpenHashMap neighboursWaitingForUs = new Reference2ObjectLinkedOpenHashMap<>(); -+ -+ public void addGenerationBlockingNeighbour(final NewChunkHolder neighbour) { -+ this.neighboursBlockingGenTask.add(neighbour); -+ } -+ -+ public void addWaitingNeighbour(final NewChunkHolder neighbour, final ChunkStatus requiredStatus) { -+ final boolean wasEmpty = this.neighboursWaitingForUs.isEmpty(); -+ this.neighboursWaitingForUs.put(neighbour, requiredStatus); -+ if (wasEmpty) { -+ this.checkUnload(); -+ } -+ } -+ -+ // priority state -+ -+ // the target priority for this chunk to generate at -+ // TODO this will screw over scheduling at lower priorities to neighbours, fix -+ private PrioritisedExecutor.Priority priority = PrioritisedExecutor.Priority.NORMAL; -+ private boolean priorityLocked; -+ -+ // the priority neighbouring chunks have requested this chunk generate at -+ private PrioritisedExecutor.Priority neighbourRequestedPriority = PrioritisedExecutor.Priority.IDLE; -+ -+ public PrioritisedExecutor.Priority getEffectivePriority() { -+ return PrioritisedExecutor.Priority.max(this.priority, this.neighbourRequestedPriority); -+ } -+ -+ protected void recalculateNeighbourRequestedPriority() { -+ if (this.neighboursWaitingForUs.isEmpty()) { -+ this.neighbourRequestedPriority = PrioritisedExecutor.Priority.IDLE; -+ return; -+ } -+ -+ PrioritisedExecutor.Priority max = PrioritisedExecutor.Priority.IDLE; -+ -+ for (final NewChunkHolder holder : this.neighboursWaitingForUs.keySet()) { -+ final PrioritisedExecutor.Priority neighbourPriority = holder.getEffectivePriority(); -+ if (neighbourPriority.isHigherPriority(max)) { -+ max = neighbourPriority; -+ } -+ } -+ -+ final PrioritisedExecutor.Priority current = this.getEffectivePriority(); -+ this.neighbourRequestedPriority = max; -+ final PrioritisedExecutor.Priority next = this.getEffectivePriority(); -+ -+ if (current == next) { -+ return; -+ } -+ -+ // our effective priority has changed, so change our task -+ if (this.generationTask != null) { -+ this.generationTask.setPriority(next); -+ } -+ -+ // now propagate this to our neighbours -+ this.recalculateNeighbourPriorities(); -+ } -+ -+ public void recalculateNeighbourPriorities() { -+ for (final NewChunkHolder holder : this.neighboursBlockingGenTask) { -+ holder.recalculateNeighbourRequestedPriority(); -+ } -+ } -+ -+ // must hold scheduling lock -+ public void raisePriority(final PrioritisedExecutor.Priority priority) { -+ if (this.priority != null && this.priority.isHigherOrEqualPriority(priority)) { -+ return; -+ } -+ this.setPriority(priority); -+ } -+ -+ private void lockPriority() { -+ this.priority = PrioritisedExecutor.Priority.NORMAL; -+ this.priorityLocked = true; -+ } -+ -+ // must hold scheduling lock -+ public void setPriority(final PrioritisedExecutor.Priority priority) { -+ if (this.priorityLocked) { -+ return; -+ } -+ final PrioritisedExecutor.Priority old = this.getEffectivePriority(); -+ this.priority = priority; -+ final PrioritisedExecutor.Priority newPriority = this.getEffectivePriority(); -+ -+ if (old != newPriority) { -+ if (this.generationTask != null) { -+ this.generationTask.setPriority(newPriority); -+ } -+ } -+ -+ this.recalculateNeighbourPriorities(); -+ } -+ -+ // must hold scheduling lock -+ public void lowerPriority(final PrioritisedExecutor.Priority priority) { -+ if (this.priority != null && this.priority.isLowerOrEqualPriority(priority)) { -+ return; -+ } -+ this.setPriority(priority); -+ } -+ -+ // error handling state -+ private ChunkStatus failedGenStatus; -+ private Throwable genTaskException; -+ private Thread genTaskFailedThread; -+ -+ private boolean failedLightUpdate; -+ -+ public void failedLightUpdate() { -+ this.failedLightUpdate = true; -+ } -+ -+ public boolean hasFailedGeneration() { -+ return this.genTaskException != null; -+ } -+ -+ // ticket level state -+ private int oldTicketLevel = ChunkLevel.MAX_LEVEL + 1; -+ private int currentTicketLevel = ChunkLevel.MAX_LEVEL + 1; -+ -+ public int getTicketLevel() { -+ return this.currentTicketLevel; -+ } -+ -+ public final ChunkHolder vanillaChunkHolder; -+ -+ public NewChunkHolder(final ServerLevel world, final int chunkX, final int chunkZ, final ChunkTaskScheduler scheduler) { -+ this.world = world; -+ this.chunkX = chunkX; -+ this.chunkZ = chunkZ; -+ this.scheduler = scheduler; -+ this.vanillaChunkHolder = new ChunkHolder(new ChunkPos(chunkX, chunkZ), world, world.getLightEngine(), world.chunkSource.chunkMap, this); -+ } -+ -+ protected ImposterProtoChunk wrappedChunkForNeighbour; -+ -+ // holds scheduling lock -+ public ChunkAccess getChunkForNeighbourAccess() { -+ // Vanilla overrides the status futures with an imposter chunk to prevent writes to full chunks -+ // But we don't store per-status futures, so we need this hack -+ if (this.wrappedChunkForNeighbour != null) { -+ return this.wrappedChunkForNeighbour; -+ } -+ final ChunkAccess ret = this.currentChunk; -+ return ret instanceof LevelChunk fullChunk ? this.wrappedChunkForNeighbour = new ImposterProtoChunk(fullChunk, false) : ret; -+ } -+ -+ public ChunkAccess getCurrentChunk() { -+ return this.currentChunk; -+ } -+ -+ int getCurrentTicketLevel() { -+ return this.currentTicketLevel; -+ } -+ -+ void updateTicketLevel(final int toLevel) { -+ this.currentTicketLevel = toLevel; -+ } -+ -+ private int totalNeighboursUsingThisChunk = 0; -+ -+ // holds schedule lock -+ public void addNeighbourUsingChunk() { -+ final int now = ++this.totalNeighboursUsingThisChunk; -+ -+ if (now == 1) { -+ this.checkUnload(); -+ } -+ } -+ -+ // holds schedule lock -+ public void removeNeighbourUsingChunk() { -+ final int now = --this.totalNeighboursUsingThisChunk; -+ -+ if (now == 0) { -+ this.checkUnload(); -+ } -+ -+ if (now < 0) { -+ throw new IllegalStateException("Neighbours using this chunk cannot be negative"); -+ } -+ } -+ -+ // must hold scheduling lock -+ // returns string reason for why chunk should remain loaded, null otherwise -+ public final String isSafeToUnload() { -+ // is ticket level below threshold? -+ if (this.oldTicketLevel <= ChunkHolderManager.MAX_TICKET_LEVEL) { -+ return "ticket_level"; -+ } -+ -+ // are we being used by another chunk for generation? -+ if (this.totalNeighboursUsingThisChunk != 0) { -+ return "neighbours_generating"; -+ } -+ -+ // are we going to be used by another chunk for generation? -+ if (!this.neighboursWaitingForUs.isEmpty()) { -+ return "neighbours_waiting"; -+ } -+ -+ // chunk must be marked inaccessible (i.e unloaded to plugins) -+ if (this.getChunkStatus() != FullChunkStatus.INACCESSIBLE) { -+ return "fullchunkstatus"; -+ } -+ -+ // are we currently generating anything, or have requested generation? -+ if (this.generationTask != null) { -+ return "generating"; -+ } -+ if (this.requestedGenStatus != null) { -+ return "requested_generation"; -+ } -+ -+ // entity data requested? -+ if (this.entityDataLoadTask != null) { -+ return "entity_data_requested"; -+ } -+ -+ // poi data requested? -+ if (this.poiDataLoadTask != null) { -+ return "poi_data_requested"; -+ } -+ -+ // are we pending serialization? -+ if (this.entityDataUnload != null) { -+ return "entity_serialization"; -+ } -+ if (this.poiDataUnload != null) { -+ return "poi_serialization"; -+ } -+ if (this.chunkDataUnload != null) { -+ return "chunk_serialization"; -+ } -+ -+ // Note: light tasks do not need a check, as they add a ticket. -+ -+ // nothing is using this chunk, so it should be unloaded -+ return null; -+ } -+ -+ /** Unloaded from chunk map */ -+ boolean killed; -+ -+ // must hold scheduling lock -+ private void checkUnload() { -+ if (this.killed) { -+ return; -+ } -+ if (this.isSafeToUnload() == null) { -+ // ensure in unload queue -+ this.scheduler.chunkHolderManager.unloadQueue.addChunk(this.chunkX, this.chunkZ); -+ } else { -+ // ensure not in unload queue -+ this.scheduler.chunkHolderManager.unloadQueue.removeChunk(this.chunkX, this.chunkZ); -+ } -+ } -+ -+ static final record UnloadState(NewChunkHolder holder, ChunkAccess chunk, ChunkEntitySlices entityChunk, PoiChunk poiChunk) {}; -+ -+ // note: these are completed with null to indicate that no write occurred -+ // they are also completed with null to indicate a null write occurred -+ private UnloadTask chunkDataUnload; -+ private UnloadTask entityDataUnload; -+ private UnloadTask poiDataUnload; -+ -+ public static final record UnloadTask(Completable completable, DelayedPrioritisedTask task) {} -+ -+ public UnloadTask getUnloadTask(final RegionFileIOThread.RegionFileType type) { -+ switch (type) { -+ case CHUNK_DATA: -+ return this.chunkDataUnload; -+ case ENTITY_DATA: -+ return this.entityDataUnload; -+ case POI_DATA: -+ return this.poiDataUnload; -+ default: -+ throw new IllegalStateException("Unknown regionfile type " + type); -+ } -+ } -+ -+ private UnloadState unloadState; -+ -+ // holds schedule lock -+ UnloadState unloadStage1() { -+ // because we hold the scheduling lock, we cannot actually unload anything -+ // so we need to null this chunk's state -+ ChunkAccess chunk = this.currentChunk; -+ ChunkEntitySlices entityChunk = this.entityChunk; -+ PoiChunk poiChunk = this.poiChunk; -+ // chunk state -+ this.currentChunk = null; -+ this.currentGenStatus = null; -+ this.wrappedChunkForNeighbour = null; -+ this.lastChunkCompletion = null; -+ // entity chunk state -+ this.entityChunk = null; -+ this.pendingEntityChunk = null; -+ -+ // poi chunk state -+ this.poiChunk = null; -+ -+ // priority state -+ this.priorityLocked = false; -+ -+ if (chunk != null) { -+ this.chunkDataUnload = new UnloadTask(new Completable<>(), new DelayedPrioritisedTask(PrioritisedExecutor.Priority.NORMAL)); -+ } -+ if (poiChunk != null) { -+ this.poiDataUnload = new UnloadTask(new Completable<>(), null); -+ } -+ if (entityChunk != null) { -+ this.entityDataUnload = new UnloadTask(new Completable<>(), null); -+ } -+ -+ return this.unloadState = (chunk != null || entityChunk != null || poiChunk != null) ? new UnloadState(this, chunk, entityChunk, poiChunk) : null; -+ } -+ -+ // data is null if failed or does not need to be saved -+ void completeAsyncChunkDataSave(final CompoundTag data) { -+ if (data != null) { -+ RegionFileIOThread.scheduleSave(this.world, this.chunkX, this.chunkZ, data, RegionFileIOThread.RegionFileType.CHUNK_DATA); -+ } -+ this.chunkDataUnload.completable().complete(data); -+ final ReentrantAreaLock.Node schedulingLock = this.scheduler.schedulingLockArea.lock(this.chunkX, this.chunkZ); -+ try { -+ // can only write to these fields while holding the schedule lock -+ this.chunkDataUnload = null; -+ this.checkUnload(); -+ } finally { -+ this.scheduler.schedulingLockArea.unlock(schedulingLock); -+ } -+ } -+ -+ void unloadStage2(final UnloadState state) { -+ this.unloadState = null; -+ final ChunkAccess chunk = state.chunk(); -+ final ChunkEntitySlices entityChunk = state.entityChunk(); -+ final PoiChunk poiChunk = state.poiChunk(); -+ -+ final boolean shouldLevelChunkNotSave = (chunk instanceof LevelChunk levelChunk && levelChunk.mustNotSave); -+ -+ // unload chunk data -+ if (chunk != null) { -+ if (chunk instanceof LevelChunk levelChunk) { -+ levelChunk.setLoaded(false); -+ } -+ -+ if (!shouldLevelChunkNotSave) { -+ this.saveChunk(chunk, true); -+ } else { -+ this.completeAsyncChunkDataSave(null); -+ } -+ -+ if (chunk instanceof LevelChunk levelChunk) { -+ this.world.unload(levelChunk); -+ } -+ } -+ -+ // unload entity data -+ if (entityChunk != null) { -+ this.saveEntities(entityChunk, true); -+ // yes this is a hack to pass the compound tag through... -+ final CompoundTag lastEntityUnload = this.lastEntityUnload; -+ this.lastEntityUnload = null; -+ -+ if (entityChunk.unload()) { -+ final ReentrantAreaLock.Node schedulingLock = this.scheduler.schedulingLockArea.lock(this.chunkX, this.chunkZ); -+ try { -+ entityChunk.setTransient(true); -+ this.entityChunk = entityChunk; -+ } finally { -+ this.scheduler.schedulingLockArea.unlock(schedulingLock); -+ } -+ } else { -+ this.world.getEntityLookup().entitySectionUnload(this.chunkX, this.chunkZ); -+ } -+ // we need to delay the callback until after determining transience, otherwise a potential loader could -+ // set entityChunk before we do -+ this.entityDataUnload.completable().complete(lastEntityUnload); -+ } -+ -+ // unload poi data -+ if (poiChunk != null) { -+ if (poiChunk.isDirty() && !shouldLevelChunkNotSave) { -+ this.savePOI(poiChunk, true); -+ } else { -+ this.poiDataUnload.completable().complete(null); -+ } -+ -+ if (poiChunk.isLoaded()) { -+ this.world.getPoiManager().onUnload(CoordinateUtils.getChunkKey(this.chunkX, this.chunkZ)); -+ } -+ } -+ } -+ -+ boolean unloadStage3() { -+ // can only write to these while holding the schedule lock, and we instantly complete them in stage2 -+ this.poiDataUnload = null; -+ this.entityDataUnload = null; -+ -+ // we need to check if anything has been loaded in the meantime (or if we have transient entities) -+ if (this.entityChunk != null || this.poiChunk != null || this.currentChunk != null) { -+ return false; -+ } -+ -+ return this.isSafeToUnload() == null; -+ } -+ -+ private void cancelGenTask() { -+ if (this.generationTask != null) { -+ this.generationTask.cancel(); -+ } else { -+ // otherwise, we are blocking on neighbours, so remove them -+ if (!this.neighboursBlockingGenTask.isEmpty()) { -+ for (final NewChunkHolder neighbour : this.neighboursBlockingGenTask) { -+ if (neighbour.neighboursWaitingForUs.remove(this) == null) { -+ throw new IllegalStateException("Corrupt state"); -+ } -+ if (neighbour.neighboursWaitingForUs.isEmpty()) { -+ neighbour.checkUnload(); -+ } -+ } -+ this.neighboursBlockingGenTask.clear(); -+ this.checkUnload(); -+ } -+ } -+ } -+ -+ // holds: ticket level update lock -+ // holds: schedule lock -+ public void processTicketLevelUpdate(final List scheduledTasks, final List changedLoadStatus) { -+ final int oldLevel = this.oldTicketLevel; -+ final int newLevel = this.currentTicketLevel; -+ -+ if (oldLevel == newLevel) { -+ return; -+ } -+ -+ this.oldTicketLevel = newLevel; -+ -+ final FullChunkStatus oldState = ChunkLevel.fullStatus(oldLevel); -+ final FullChunkStatus newState = ChunkLevel.fullStatus(newLevel); -+ final boolean oldUnloaded = oldLevel > ChunkHolderManager.MAX_TICKET_LEVEL; -+ final boolean newUnloaded = newLevel > ChunkHolderManager.MAX_TICKET_LEVEL; -+ -+ final ChunkStatus maxGenerationStatusOld = ChunkLevel.generationStatus(oldLevel); -+ final ChunkStatus maxGenerationStatusNew = ChunkLevel.generationStatus(newLevel); -+ -+ // check for cancellations from downgrading ticket level -+ if (this.requestedGenStatus != null && !newState.isOrAfter(FullChunkStatus.FULL) && newLevel > oldLevel) { -+ // note: cancel() may invoke onChunkGenComplete synchronously here -+ if (newUnloaded) { -+ // need to cancel all tasks -+ // note: requested status must be set to null here before cancellation, to indicate to the -+ // completion logic that we do not want rescheduling to occur -+ this.requestedGenStatus = null; -+ this.cancelGenTask(); -+ } else { -+ final ChunkStatus toCancel = maxGenerationStatusNew.getNextStatus(); -+ final ChunkStatus currentRequestedStatus = this.requestedGenStatus; -+ -+ if (currentRequestedStatus.isOrAfter(toCancel)) { -+ // we do have to cancel something here -+ // clamp requested status to the maximum -+ if (this.currentGenStatus != null && this.currentGenStatus.isOrAfter(maxGenerationStatusNew)) { -+ // already generated to status, so we must cancel -+ this.requestedGenStatus = null; -+ this.cancelGenTask(); -+ } else { -+ // not generated to status, so we may have to cancel -+ // note: gen task is always 1 status above current gen status if not null -+ this.requestedGenStatus = maxGenerationStatusNew; -+ if (this.generationTaskStatus != null && this.generationTaskStatus.isOrAfter(toCancel)) { -+ // TOOD is this even possible? i don't think so -+ throw new IllegalStateException("?????"); -+ } -+ } -+ } -+ } -+ } -+ -+ if (newState != oldState) { -+ if (newState.isOrAfter(oldState)) { -+ // status upgrade -+ if (!oldState.isOrAfter(FullChunkStatus.FULL) && newState.isOrAfter(FullChunkStatus.FULL)) { -+ // may need to schedule full load -+ if (this.currentGenStatus != ChunkStatus.FULL) { -+ if (this.requestedGenStatus != null) { -+ this.requestedGenStatus = ChunkStatus.FULL; -+ } else { -+ this.scheduler.schedule( -+ this.chunkX, this.chunkZ, ChunkStatus.FULL, this, scheduledTasks -+ ); -+ } -+ } else { -+ // now we are fully loaded -+ this.queueBorderFullStatus(true, changedLoadStatus); -+ } -+ } -+ } else { -+ // status downgrade -+ if (!newState.isOrAfter(FullChunkStatus.ENTITY_TICKING) && oldState.isOrAfter(FullChunkStatus.ENTITY_TICKING)) { -+ this.completeFullStatusConsumers(FullChunkStatus.ENTITY_TICKING, null); -+ } -+ -+ if (!newState.isOrAfter(FullChunkStatus.BLOCK_TICKING) && oldState.isOrAfter(FullChunkStatus.BLOCK_TICKING)) { -+ this.completeFullStatusConsumers(FullChunkStatus.BLOCK_TICKING, null); -+ } -+ -+ if (!newState.isOrAfter(FullChunkStatus.FULL) && oldState.isOrAfter(FullChunkStatus.FULL)) { -+ this.completeFullStatusConsumers(FullChunkStatus.FULL, null); -+ } -+ } -+ } -+ -+ if (oldState != newState) { -+ if (this.onTicketUpdate(oldState, newState)) { -+ changedLoadStatus.add(this); -+ } -+ } -+ -+ if (oldUnloaded != newUnloaded) { -+ this.checkUnload(); -+ } -+ } -+ -+ /* -+ For full chunks, vanilla just loads chunks around it up to FEATURES, 1 radius -+ -+ For ticking chunks, it updates the persistent entity manager (soon to be completely nuked by EntitySliceManager, which -+ will also need to be updated but with far less implications) -+ It also shoves the scheduled block ticks into the tick scheduler -+ -+ For entity ticking chunks, updates the entity manager (see above) -+ */ -+ -+ static final int NEIGHBOUR_RADIUS = 2; -+ private long fullNeighbourChunksLoadedBitset; -+ -+ private static int getFullNeighbourIndex(final int relativeX, final int relativeZ) { -+ // index = (relativeX + NEIGHBOUR_CACHE_RADIUS) + (relativeZ + NEIGHBOUR_CACHE_RADIUS) * (NEIGHBOUR_CACHE_RADIUS * 2 + 1) -+ // optimised variant of the above by moving some of the ops to compile time -+ return relativeX + (relativeZ * (NEIGHBOUR_RADIUS * 2 + 1)) + (NEIGHBOUR_RADIUS + NEIGHBOUR_RADIUS * ((NEIGHBOUR_RADIUS * 2 + 1))); -+ } -+ public final boolean isNeighbourFullLoaded(final int relativeX, final int relativeZ) { -+ return (this.fullNeighbourChunksLoadedBitset & (1L << getFullNeighbourIndex(relativeX, relativeZ))) != 0; -+ } -+ -+ // returns true if this chunk changed full status -+ public final boolean setNeighbourFullLoaded(final int relativeX, final int relativeZ) { -+ final long before = this.fullNeighbourChunksLoadedBitset; -+ final int index = getFullNeighbourIndex(relativeX, relativeZ); -+ this.fullNeighbourChunksLoadedBitset |= (1L << index); -+ return this.onNeighbourChange(before, this.fullNeighbourChunksLoadedBitset); -+ } -+ -+ // returns true if this chunk changed full status -+ public final boolean setNeighbourFullUnloaded(final int relativeX, final int relativeZ) { -+ final long before = this.fullNeighbourChunksLoadedBitset; -+ final int index = getFullNeighbourIndex(relativeX, relativeZ); -+ this.fullNeighbourChunksLoadedBitset &= ~(1L << index); -+ return this.onNeighbourChange(before, this.fullNeighbourChunksLoadedBitset); -+ } -+ -+ public static boolean areNeighboursFullLoaded(final long bitset, final int radius) { -+ // index = relativeX + (relativeZ * (NEIGHBOUR_CACHE_RADIUS * 2 + 1)) + (NEIGHBOUR_CACHE_RADIUS + NEIGHBOUR_CACHE_RADIUS * ((NEIGHBOUR_CACHE_RADIUS * 2 + 1))) -+ switch (radius) { -+ case 0: { -+ return (bitset & (1L << getFullNeighbourIndex(0, 0))) != 0L; -+ } -+ case 1: { -+ long mask = 0L; -+ for (int dx = -1; dx <= 1; ++dx) { -+ for (int dz = -1; dz <= 1; ++dz) { -+ mask |= (1L << getFullNeighbourIndex(dx, dz)); -+ } -+ } -+ return (bitset & mask) == mask; -+ } -+ case 2: { -+ long mask = 0L; -+ for (int dx = -2; dx <= 2; ++dx) { -+ for (int dz = -2; dz <= 2; ++dz) { -+ mask |= (1L << getFullNeighbourIndex(dx, dz)); -+ } -+ } -+ return (bitset & mask) == mask; -+ } -+ -+ default: { -+ throw new IllegalArgumentException("Radius not recognized: " + radius); -+ } -+ } -+ } -+ -+ // upper 16 bits are pending status, lower 16 bits are current status -+ private volatile long chunkStatus; -+ private static final long PENDING_STATUS_MASK = Long.MIN_VALUE >> 31; -+ private static final FullChunkStatus[] CHUNK_STATUS_BY_ID = FullChunkStatus.values(); -+ private static final VarHandle CHUNK_STATUS_HANDLE = ConcurrentUtil.getVarHandle(NewChunkHolder.class, "chunkStatus", long.class); -+ -+ public static FullChunkStatus getCurrentChunkStatus(final long encoded) { -+ return CHUNK_STATUS_BY_ID[(int)encoded]; -+ } -+ -+ public static FullChunkStatus getPendingChunkStatus(final long encoded) { -+ return CHUNK_STATUS_BY_ID[(int)(encoded >>> 32)]; -+ } -+ -+ public FullChunkStatus getChunkStatus() { -+ return getCurrentChunkStatus(((long)CHUNK_STATUS_HANDLE.getVolatile((NewChunkHolder)this))); -+ } -+ -+ public boolean isEntityTickingReady() { -+ return this.getChunkStatus().isOrAfter(FullChunkStatus.ENTITY_TICKING); -+ } -+ -+ public boolean isTickingReady() { -+ return this.getChunkStatus().isOrAfter(FullChunkStatus.BLOCK_TICKING); -+ } -+ -+ public boolean isFullChunkReady() { -+ return this.getChunkStatus().isOrAfter(FullChunkStatus.FULL); -+ } -+ -+ private static FullChunkStatus getStatusForBitset(final long bitset) { -+ if (areNeighboursFullLoaded(bitset, 2)) { -+ return FullChunkStatus.ENTITY_TICKING; -+ } else if (areNeighboursFullLoaded(bitset, 1)) { -+ return FullChunkStatus.BLOCK_TICKING; -+ } else if (areNeighboursFullLoaded(bitset, 0)) { -+ return FullChunkStatus.FULL; -+ } else { -+ return FullChunkStatus.INACCESSIBLE; -+ } -+ } -+ -+ // note: only while updating ticket level, so holds ticket update lock + scheduling lock -+ protected final boolean onTicketUpdate(final FullChunkStatus oldState, final FullChunkStatus newState) { -+ if (oldState == newState) { -+ return false; -+ } -+ -+ // preserve border request after full status complete, as it does not set anything in the bitset -+ FullChunkStatus byNeighbours = getStatusForBitset(this.fullNeighbourChunksLoadedBitset); -+ if (byNeighbours == FullChunkStatus.INACCESSIBLE && newState.isOrAfter(FullChunkStatus.FULL) && this.currentGenStatus == ChunkStatus.FULL) { -+ byNeighbours = FullChunkStatus.FULL; -+ } -+ -+ final FullChunkStatus toSet; -+ -+ if (newState.isOrAfter(byNeighbours)) { -+ // must clamp to neighbours level, even though we have the ticket level -+ toSet = byNeighbours; -+ } else { -+ // must clamp to ticket level, even though we have the neighbours -+ toSet = newState; -+ } -+ -+ long curr = (long)CHUNK_STATUS_HANDLE.getVolatile((NewChunkHolder)this); -+ -+ if (curr == ((long)toSet.ordinal() | ((long)toSet.ordinal() << 32))) { -+ // nothing to do -+ return false; -+ } -+ -+ int failures = 0; -+ for (;;) { -+ final long update = (curr & ~PENDING_STATUS_MASK) | ((long)toSet.ordinal() << 32); -+ if (curr == (curr = (long)CHUNK_STATUS_HANDLE.compareAndExchange((NewChunkHolder)this, curr, update))) { -+ return true; -+ } -+ -+ ++failures; -+ for (int i = 0; i < failures; ++i) { -+ ConcurrentUtil.backoff(); -+ } -+ } -+ } -+ -+ protected final boolean onNeighbourChange(final long bitsetBefore, final long bitsetAfter) { -+ FullChunkStatus oldState = getStatusForBitset(bitsetBefore); -+ FullChunkStatus newState = getStatusForBitset(bitsetAfter); -+ final FullChunkStatus currStateTicketLevel = ChunkLevel.fullStatus(this.oldTicketLevel); -+ if (oldState.isOrAfter(currStateTicketLevel)) { -+ oldState = currStateTicketLevel; -+ } -+ if (newState.isOrAfter(currStateTicketLevel)) { -+ newState = currStateTicketLevel; -+ } -+ // preserve border request after full status complete, as it does not set anything in the bitset -+ if (newState == FullChunkStatus.INACCESSIBLE && currStateTicketLevel.isOrAfter(FullChunkStatus.FULL) && this.currentGenStatus == ChunkStatus.FULL) { -+ newState = FullChunkStatus.FULL; -+ } -+ -+ if (oldState == newState) { -+ return false; -+ } -+ -+ int failures = 0; -+ for (long curr = (long)CHUNK_STATUS_HANDLE.getVolatile((NewChunkHolder)this);;) { -+ final long update = (curr & ~PENDING_STATUS_MASK) | ((long)newState.ordinal() << 32); -+ if (curr == (curr = (long)CHUNK_STATUS_HANDLE.compareAndExchange((NewChunkHolder)this, curr, update))) { -+ return true; -+ } -+ -+ ++failures; -+ for (int i = 0; i < failures; ++i) { -+ ConcurrentUtil.backoff(); -+ } -+ } -+ } -+ -+ private boolean queueBorderFullStatus(final boolean loaded, final List changedFullStatus) { -+ final FullChunkStatus toStatus = loaded ? FullChunkStatus.FULL : FullChunkStatus.INACCESSIBLE; -+ -+ int failures = 0; -+ for (long curr = (long)CHUNK_STATUS_HANDLE.getVolatile((NewChunkHolder)this);;) { -+ final FullChunkStatus currPending = getPendingChunkStatus(curr); -+ if (loaded && currPending != FullChunkStatus.INACCESSIBLE) { -+ throw new IllegalStateException("Expected " + FullChunkStatus.INACCESSIBLE + " for pending, but got " + currPending); -+ } -+ -+ final long update = (curr & ~PENDING_STATUS_MASK) | ((long)toStatus.ordinal() << 32); -+ if (curr == (curr = (long)CHUNK_STATUS_HANDLE.compareAndExchange((NewChunkHolder)this, curr, update))) { -+ if ((int)(update) != (int)(update >>> 32)) { -+ changedFullStatus.add(this); -+ return true; -+ } -+ return false; -+ } -+ -+ ++failures; -+ for (int i = 0; i < failures; ++i) { -+ ConcurrentUtil.backoff(); -+ } -+ } -+ } -+ -+ // only call on main thread, must hold ticket level and scheduling lock -+ private void onFullChunkLoadChange(final boolean loaded, final List changedFullStatus) { -+ final ReentrantAreaLock.Node schedulingLock = this.scheduler.schedulingLockArea.lock(this.chunkX, this.chunkZ, NEIGHBOUR_RADIUS); -+ try { -+ for (int dz = -NEIGHBOUR_RADIUS; dz <= NEIGHBOUR_RADIUS; ++dz) { -+ for (int dx = -NEIGHBOUR_RADIUS; dx <= NEIGHBOUR_RADIUS; ++dx) { -+ final NewChunkHolder holder = (dx | dz) == 0 ? this : this.scheduler.chunkHolderManager.getChunkHolder(dx + this.chunkX, dz + this.chunkZ); -+ if (loaded) { -+ if (holder.setNeighbourFullLoaded(-dx, -dz)) { -+ changedFullStatus.add(holder); -+ } -+ } else { -+ if (holder != null && holder.setNeighbourFullUnloaded(-dx, -dz)) { -+ changedFullStatus.add(holder); -+ } -+ } -+ } -+ } -+ } finally { -+ this.scheduler.schedulingLockArea.unlock(schedulingLock); -+ } -+ } -+ -+ private FullChunkStatus updateCurrentState(final FullChunkStatus to) { -+ int failures = 0; -+ for (long curr = (long)CHUNK_STATUS_HANDLE.getVolatile((NewChunkHolder)this);;) { -+ final long update = (curr & PENDING_STATUS_MASK) | (long)to.ordinal(); -+ if (curr == (curr = (long)CHUNK_STATUS_HANDLE.compareAndExchange((NewChunkHolder)this, curr, update))) { -+ return getPendingChunkStatus(curr); -+ } -+ -+ ++failures; -+ for (int i = 0; i < failures; ++i) { -+ ConcurrentUtil.backoff(); -+ } -+ } -+ } -+ -+ private void changeEntityChunkStatus(final FullChunkStatus toStatus) { -+ this.world.getEntityLookup().chunkStatusChange(this.chunkX, this.chunkZ, toStatus); -+ } -+ -+ private boolean processingFullStatus = false; -+ -+ // only to be called on the main thread, no locks need to be held -+ public boolean handleFullStatusChange(final List changedFullStatus) { -+ TickThread.ensureTickThread(this.world, this.chunkX, this.chunkZ, "Cannot update full status thread off-main"); -+ -+ boolean ret = false; -+ -+ if (this.processingFullStatus) { -+ // we cannot process updates recursively -+ return ret; -+ } -+ -+ // note: use opaque reads for chunk status read since we need it to be atomic -+ -+ // test if anything changed -+ long statusCheck = (long)CHUNK_STATUS_HANDLE.getOpaque((NewChunkHolder)this); -+ if ((int)statusCheck == (int)(statusCheck >>> 32)) { -+ // nothing changed -+ return ret; -+ } -+ -+ final ChunkTaskScheduler scheduler = this.scheduler; -+ final ChunkHolderManager holderManager = scheduler.chunkHolderManager; -+ final int ticketKeep; -+ final Long ticketId = Long.valueOf(holderManager.getNextStatusUpgradeId()); -+ final ReentrantAreaLock.Node ticketLock = holderManager.ticketLockArea.lock(this.chunkX, this.chunkZ); -+ try { -+ ticketKeep = this.currentTicketLevel; -+ statusCheck = (long)CHUNK_STATUS_HANDLE.getOpaque((NewChunkHolder)this); -+ // handle race condition where ticket level and target status is updated concurrently -+ if ((int)statusCheck == (int)(statusCheck >>> 32)) { -+ // nothing changed -+ return ret; -+ } -+ holderManager.addTicketAtLevel(TicketType.STATUS_UPGRADE, CoordinateUtils.getChunkKey(this.chunkX, this.chunkZ), ticketKeep, ticketId, false); -+ } finally { -+ holderManager.ticketLockArea.unlock(ticketLock); -+ } -+ -+ this.processingFullStatus = true; -+ try { -+ for (;;) { -+ final long currStateEncoded = (long)CHUNK_STATUS_HANDLE.getOpaque((NewChunkHolder)this); -+ final FullChunkStatus currState = getCurrentChunkStatus(currStateEncoded); -+ FullChunkStatus nextState = getPendingChunkStatus(currStateEncoded); -+ if (currState == nextState) { -+ if (nextState == FullChunkStatus.INACCESSIBLE) { -+ final ReentrantAreaLock.Node schedulingLock = this.scheduler.schedulingLockArea.lock(this.chunkX, this.chunkZ); -+ try { -+ this.checkUnload(); -+ } finally { -+ this.scheduler.schedulingLockArea.unlock(schedulingLock); -+ } -+ } -+ break; -+ } -+ -+ // chunks cannot downgrade state while status is pending a change -+ final LevelChunk chunk = (LevelChunk)this.currentChunk; -+ -+ // Note: we assume that only load/unload contain plugin logic -+ // plugin logic is anything stupid enough to possibly change the chunk status while it is already -+ // being changed (i.e during load it is possible it will try to set to full ticking) -+ // in order to allow this change, we also need this plugin logic to be contained strictly after all -+ // of the chunk system load callbacks are invoked -+ if (nextState.isOrAfter(currState)) { -+ // state upgrade -+ if (!currState.isOrAfter(FullChunkStatus.FULL) && nextState.isOrAfter(FullChunkStatus.FULL)) { -+ nextState = this.updateCurrentState(FullChunkStatus.FULL); -+ holderManager.ensureInAutosave(this); -+ chunk.pushChunkIntoLoadedMap(); -+ this.changeEntityChunkStatus(FullChunkStatus.FULL); -+ chunk.onChunkLoad(this); -+ this.onFullChunkLoadChange(true, changedFullStatus); -+ this.completeFullStatusConsumers(FullChunkStatus.FULL, chunk); -+ } -+ -+ if (!currState.isOrAfter(FullChunkStatus.BLOCK_TICKING) && nextState.isOrAfter(FullChunkStatus.BLOCK_TICKING)) { -+ nextState = this.updateCurrentState(FullChunkStatus.BLOCK_TICKING); -+ this.changeEntityChunkStatus(FullChunkStatus.BLOCK_TICKING); -+ chunk.onChunkTicking(this); -+ this.completeFullStatusConsumers(FullChunkStatus.BLOCK_TICKING, chunk); -+ } -+ -+ if (!currState.isOrAfter(FullChunkStatus.ENTITY_TICKING) && nextState.isOrAfter(FullChunkStatus.ENTITY_TICKING)) { -+ nextState = this.updateCurrentState(FullChunkStatus.ENTITY_TICKING); -+ this.changeEntityChunkStatus(FullChunkStatus.ENTITY_TICKING); -+ chunk.onChunkEntityTicking(this); -+ this.completeFullStatusConsumers(FullChunkStatus.ENTITY_TICKING, chunk); -+ } -+ } else { -+ if (currState.isOrAfter(FullChunkStatus.ENTITY_TICKING) && !nextState.isOrAfter(FullChunkStatus.ENTITY_TICKING)) { -+ this.changeEntityChunkStatus(FullChunkStatus.BLOCK_TICKING); -+ chunk.onChunkNotEntityTicking(this); -+ nextState = this.updateCurrentState(FullChunkStatus.BLOCK_TICKING); -+ } -+ -+ if (currState.isOrAfter(FullChunkStatus.BLOCK_TICKING) && !nextState.isOrAfter(FullChunkStatus.BLOCK_TICKING)) { -+ this.changeEntityChunkStatus(FullChunkStatus.FULL); -+ chunk.onChunkNotTicking(this); -+ nextState = this.updateCurrentState(FullChunkStatus.FULL); -+ } -+ -+ if (currState.isOrAfter(FullChunkStatus.FULL) && !nextState.isOrAfter(FullChunkStatus.FULL)) { -+ this.onFullChunkLoadChange(false, changedFullStatus); -+ this.changeEntityChunkStatus(FullChunkStatus.INACCESSIBLE); -+ chunk.onChunkUnload(this); -+ nextState = this.updateCurrentState(FullChunkStatus.INACCESSIBLE); -+ } -+ } -+ -+ ret = true; -+ } -+ } finally { -+ this.processingFullStatus = false; -+ holderManager.removeTicketAtLevel(TicketType.STATUS_UPGRADE, this.chunkX, this.chunkZ, ticketKeep, ticketId); -+ } -+ -+ return ret; -+ } -+ -+ // note: must hold scheduling lock -+ // rets true if the current requested gen status is not null (effectively, whether further scheduling is not needed) -+ boolean upgradeGenTarget(final ChunkStatus toStatus) { -+ if (toStatus == null) { -+ throw new NullPointerException("toStatus cannot be null"); -+ } -+ if (this.requestedGenStatus == null && this.generationTask == null) { -+ return false; -+ } -+ if (this.requestedGenStatus == null || !this.requestedGenStatus.isOrAfter(toStatus)) { -+ this.requestedGenStatus = toStatus; -+ } -+ return true; -+ } -+ -+ public void setGenerationTarget(final ChunkStatus toStatus) { -+ this.requestedGenStatus = toStatus; -+ } -+ -+ public boolean hasGenerationTask() { -+ return this.generationTask != null; -+ } -+ -+ public ChunkStatus getCurrentGenStatus() { -+ return this.currentGenStatus; -+ } -+ -+ public ChunkStatus getRequestedGenStatus() { -+ return this.requestedGenStatus; -+ } -+ -+ private final Reference2ObjectOpenHashMap>> statusWaiters = new Reference2ObjectOpenHashMap<>(); -+ -+ void addStatusConsumer(final ChunkStatus status, final Consumer consumer) { -+ this.statusWaiters.computeIfAbsent(status, (final ChunkStatus keyInMap) -> { -+ return new ArrayList<>(4); -+ }).add(consumer); -+ } -+ -+ private void completeStatusConsumers(ChunkStatus status, final ChunkAccess chunk) { -+ // need to tell future statuses to complete if cancelled -+ do { -+ this.completeStatusConsumers0(status, chunk); -+ } while (chunk == null && status != (status = status.getNextStatus())); -+ } -+ -+ private void completeStatusConsumers0(final ChunkStatus status, final ChunkAccess chunk) { -+ final List> consumers; -+ consumers = this.statusWaiters.remove(status); -+ -+ if (consumers == null) { -+ return; -+ } -+ -+ // must be scheduled to main, we do not trust the callback to not do anything stupid -+ this.scheduler.scheduleChunkTask(this.chunkX, this.chunkZ, () -> { -+ for (final Consumer consumer : consumers) { -+ try { -+ consumer.accept(chunk); -+ } catch (final ThreadDeath thr) { -+ throw thr; -+ } catch (final Throwable thr) { -+ LOGGER.error("Failed to process chunk status callback", thr); -+ } -+ } -+ }, PrioritisedExecutor.Priority.HIGHEST); -+ } -+ -+ private final Reference2ObjectOpenHashMap>> fullStatusWaiters = new Reference2ObjectOpenHashMap<>(); -+ -+ void addFullStatusConsumer(final FullChunkStatus status, final Consumer consumer) { -+ this.fullStatusWaiters.computeIfAbsent(status, (final FullChunkStatus keyInMap) -> { -+ return new ArrayList<>(4); -+ }).add(consumer); -+ } -+ -+ private void completeFullStatusConsumers(FullChunkStatus status, final LevelChunk chunk) { -+ // need to tell future statuses to complete if cancelled -+ final FullChunkStatus max = CHUNK_STATUS_BY_ID[CHUNK_STATUS_BY_ID.length - 1]; -+ -+ for (;;) { -+ this.completeFullStatusConsumers0(status, chunk); -+ if (chunk != null || status == max) { -+ break; -+ } -+ status = CHUNK_STATUS_BY_ID[status.ordinal() + 1]; -+ } -+ } -+ -+ private void completeFullStatusConsumers0(final FullChunkStatus status, final LevelChunk chunk) { -+ final List> consumers; -+ consumers = this.fullStatusWaiters.remove(status); -+ -+ if (consumers == null) { -+ return; -+ } -+ -+ // must be scheduled to main, we do not trust the callback to not do anything stupid -+ this.scheduler.scheduleChunkTask(this.chunkX, this.chunkZ, () -> { -+ for (final Consumer consumer : consumers) { -+ try { -+ consumer.accept(chunk); -+ } catch (final ThreadDeath thr) { -+ throw thr; -+ } catch (final Throwable thr) { -+ LOGGER.error("Failed to process chunk status callback", thr); -+ } -+ } -+ }, PrioritisedExecutor.Priority.HIGHEST); -+ } -+ -+ // note: must hold scheduling lock -+ private void onChunkGenComplete(final ChunkAccess newChunk, final ChunkStatus newStatus, -+ final List scheduleList, final List changedLoadStatus) { -+ if (!this.neighboursBlockingGenTask.isEmpty()) { -+ throw new IllegalStateException("Cannot have neighbours blocking this gen task"); -+ } -+ if (newChunk != null || (this.requestedGenStatus == null || !this.requestedGenStatus.isOrAfter(newStatus))) { -+ this.completeStatusConsumers(newStatus, newChunk); -+ } -+ // done now, clear state (must be done before scheduling new tasks) -+ this.generationTask = null; -+ this.generationTaskStatus = null; -+ if (newChunk == null) { -+ // task was cancelled -+ // should be careful as this could be called while holding the schedule lock and/or inside the -+ // ticket level update -+ // while a task may be cancelled, it is possible for it to be later re-scheduled -+ // however, because generationTask is only set to null on _completion_, the scheduler leaves -+ // the rescheduling logic to us here -+ final ChunkStatus requestedGenStatus = this.requestedGenStatus; -+ this.requestedGenStatus = null; -+ if (requestedGenStatus != null) { -+ // it looks like it has been requested, so we must reschedule -+ if (!this.neighboursWaitingForUs.isEmpty()) { -+ for (final Iterator> iterator = this.neighboursWaitingForUs.reference2ObjectEntrySet().fastIterator(); iterator.hasNext();) { -+ final Reference2ObjectMap.Entry entry = iterator.next(); -+ -+ final NewChunkHolder chunkHolder = entry.getKey(); -+ final ChunkStatus toStatus = entry.getValue(); -+ -+ if (!requestedGenStatus.isOrAfter(toStatus)) { -+ // if we were cancelled, we are responsible for removing the waiter -+ if (!chunkHolder.neighboursBlockingGenTask.remove(this)) { -+ throw new IllegalStateException("Corrupt state"); -+ } -+ if (chunkHolder.neighboursBlockingGenTask.isEmpty()) { -+ chunkHolder.checkUnload(); -+ } -+ iterator.remove(); -+ continue; -+ } -+ } -+ } -+ -+ // note: only after generationTask -> null, generationTaskStatus -> null, and requestedGenStatus -> null -+ this.scheduler.schedule( -+ this.chunkX, this.chunkZ, requestedGenStatus, this, scheduleList -+ ); -+ -+ // return, can't do anything further -+ return; -+ } -+ -+ if (!this.neighboursWaitingForUs.isEmpty()) { -+ for (final NewChunkHolder chunkHolder : this.neighboursWaitingForUs.keySet()) { -+ if (!chunkHolder.neighboursBlockingGenTask.remove(this)) { -+ throw new IllegalStateException("Corrupt state"); -+ } -+ if (chunkHolder.neighboursBlockingGenTask.isEmpty()) { -+ chunkHolder.checkUnload(); -+ } -+ } -+ this.neighboursWaitingForUs.clear(); -+ } -+ // reset priority, we have nothing left to generate to -+ this.setPriority(PrioritisedExecutor.Priority.NORMAL); -+ this.checkUnload(); -+ return; -+ } -+ -+ this.currentChunk = newChunk; -+ this.currentGenStatus = newStatus; -+ this.lastChunkCompletion = new ChunkCompletion(newChunk, newStatus); -+ -+ final ChunkStatus requestedGenStatus = this.requestedGenStatus; -+ -+ List needsScheduling = null; -+ boolean recalculatePriority = false; -+ for (final Iterator> iterator -+ = this.neighboursWaitingForUs.reference2ObjectEntrySet().fastIterator(); iterator.hasNext();) { -+ final Reference2ObjectMap.Entry entry = iterator.next(); -+ final NewChunkHolder neighbour = entry.getKey(); -+ final ChunkStatus requiredStatus = entry.getValue(); -+ -+ if (!newStatus.isOrAfter(requiredStatus)) { -+ if (requestedGenStatus == null || !requestedGenStatus.isOrAfter(requiredStatus)) { -+ // if we're cancelled, still need to clear this map -+ if (!neighbour.neighboursBlockingGenTask.remove(this)) { -+ throw new IllegalStateException("Neighbour is not waiting for us?"); -+ } -+ if (neighbour.neighboursBlockingGenTask.isEmpty()) { -+ neighbour.checkUnload(); -+ } -+ -+ iterator.remove(); -+ } -+ continue; -+ } -+ -+ // doesn't matter what isCancelled is here, we need to schedule if we can -+ -+ recalculatePriority = true; -+ if (!neighbour.neighboursBlockingGenTask.remove(this)) { -+ throw new IllegalStateException("Neighbour is not waiting for us?"); -+ } -+ -+ if (neighbour.neighboursBlockingGenTask.isEmpty()) { -+ if (neighbour.requestedGenStatus != null) { -+ if (needsScheduling == null) { -+ needsScheduling = new ArrayList<>(); -+ } -+ needsScheduling.add(neighbour); -+ } else { -+ neighbour.checkUnload(); -+ } -+ } -+ -+ // remove last; access to entry will throw if removed -+ iterator.remove(); -+ } -+ -+ if (newStatus == ChunkStatus.FULL) { -+ this.lockPriority(); -+ // must use oldTicketLevel, we hold the schedule lock but not the ticket level lock -+ // however, schedule lock needs to be held for ticket level callback, so we're fine here -+ if (ChunkLevel.fullStatus(this.oldTicketLevel).isOrAfter(FullChunkStatus.FULL)) { -+ this.queueBorderFullStatus(true, changedLoadStatus); -+ } -+ } -+ -+ if (recalculatePriority) { -+ this.recalculateNeighbourRequestedPriority(); -+ } -+ -+ if (requestedGenStatus != null && !newStatus.isOrAfter(requestedGenStatus)) { -+ this.scheduleNeighbours(needsScheduling, scheduleList); -+ -+ // we need to schedule more tasks now -+ this.scheduler.schedule( -+ this.chunkX, this.chunkZ, requestedGenStatus, this, scheduleList -+ ); -+ } else { -+ // we're done now -+ if (requestedGenStatus != null) { -+ this.requestedGenStatus = null; -+ } -+ // reached final stage, so stop scheduling now -+ this.setPriority(PrioritisedExecutor.Priority.NORMAL); -+ this.checkUnload(); -+ -+ this.scheduleNeighbours(needsScheduling, scheduleList); -+ } -+ } -+ -+ private void scheduleNeighbours(final List needsScheduling, final List scheduleList) { -+ if (needsScheduling != null) { -+ for (int i = 0, len = needsScheduling.size(); i < len; ++i) { -+ final NewChunkHolder neighbour = needsScheduling.get(i); -+ -+ this.scheduler.schedule( -+ neighbour.chunkX, neighbour.chunkZ, neighbour.requestedGenStatus, neighbour, scheduleList -+ ); -+ } -+ } -+ } -+ -+ public void setGenerationTask(final ChunkProgressionTask generationTask, final ChunkStatus taskStatus, -+ final List neighbours) { -+ if (this.generationTask != null || (this.currentGenStatus != null && this.currentGenStatus.isOrAfter(taskStatus))) { -+ throw new IllegalStateException("Currently generating or provided task is trying to generate to a level we are already at!"); -+ } -+ if (this.requestedGenStatus == null || !this.requestedGenStatus.isOrAfter(taskStatus)) { -+ throw new IllegalStateException("Cannot schedule generation task when not requested"); -+ } -+ this.generationTask = generationTask; -+ this.generationTaskStatus = taskStatus; -+ -+ for (int i = 0, len = neighbours.size(); i < len; ++i) { -+ neighbours.get(i).addNeighbourUsingChunk(); -+ } -+ -+ this.checkUnload(); -+ -+ generationTask.onComplete((final ChunkAccess access, final Throwable thr) -> { -+ if (generationTask != this.generationTask) { -+ throw new IllegalStateException( -+ "Cannot complete generation task '" + generationTask + "' because we are waiting on '" + this.generationTask + "' instead!" -+ ); -+ } -+ if (thr != null) { -+ if (this.genTaskException != null) { -+ // first one is probably the TRUE problem -+ return; -+ } -+ // don't set generation task to null, so that scheduling will not attempt to create another task and it -+ // will automatically block any further scheduling usage of this chunk as it will wait forever for a failed -+ // task to complete -+ this.genTaskException = thr; -+ this.failedGenStatus = taskStatus; -+ this.genTaskFailedThread = Thread.currentThread(); -+ -+ this.scheduler.unrecoverableChunkSystemFailure(this.chunkX, this.chunkZ, Map.of( -+ "Generation task", ChunkTaskScheduler.stringIfNull(generationTask), -+ "Task to status", ChunkTaskScheduler.stringIfNull(taskStatus) -+ ), thr); -+ return; -+ } -+ -+ final boolean scheduleTasks; -+ List tasks = ChunkHolderManager.getCurrentTicketUpdateScheduling(); -+ if (tasks == null) { -+ scheduleTasks = true; -+ tasks = new ArrayList<>(); -+ } else { -+ scheduleTasks = false; -+ // we are currently updating ticket levels, so we already hold the schedule lock -+ // this means we have to leave the ticket level update to handle the scheduling -+ } -+ final List changedLoadStatus = new ArrayList<>(); -+ // theoretically, we could schedule a chunk at the max radius which performs another max radius access. So we need to double the radius. -+ final ReentrantAreaLock.Node schedulingLock = this.scheduler.schedulingLockArea.lock(this.chunkX, this.chunkZ, 2 * ChunkTaskScheduler.getMaxAccessRadius()); -+ try { -+ for (int i = 0, len = neighbours.size(); i < len; ++i) { -+ neighbours.get(i).removeNeighbourUsingChunk(); -+ } -+ this.onChunkGenComplete(access, taskStatus, tasks, changedLoadStatus); -+ } finally { -+ this.scheduler.schedulingLockArea.unlock(schedulingLock); -+ } -+ this.scheduler.chunkHolderManager.addChangedStatuses(changedLoadStatus); -+ -+ if (scheduleTasks) { -+ // can't hold the lock while scheduling, so we have to build the tasks and then schedule after -+ for (int i = 0, len = tasks.size(); i < len; ++i) { -+ tasks.get(i).schedule(); -+ } -+ } -+ }); -+ } -+ -+ public PoiChunk getPoiChunk() { -+ return this.poiChunk; -+ } -+ -+ public ChunkEntitySlices getEntityChunk() { -+ return this.entityChunk; -+ } -+ -+ public long lastAutoSave; -+ -+ public static final record SaveStat(boolean savedChunk, boolean savedEntityChunk, boolean savedPoiChunk) {} -+ -+ public SaveStat save(final boolean shutdown, final boolean unloading) { -+ TickThread.ensureTickThread(this.world, this.chunkX, this.chunkZ, "Cannot save data off-main"); -+ -+ ChunkAccess chunk = this.getCurrentChunk(); -+ PoiChunk poi = this.getPoiChunk(); -+ ChunkEntitySlices entities = this.getEntityChunk(); -+ boolean executedUnloadTask = false; -+ -+ if (shutdown) { -+ // make sure that the async unloads complete -+ if (this.unloadState != null) { -+ // must have errored during unload -+ chunk = this.unloadState.chunk(); -+ poi = this.unloadState.poiChunk(); -+ entities = this.unloadState.entityChunk(); -+ } -+ final UnloadTask chunkUnloadTask = this.chunkDataUnload; -+ final DelayedPrioritisedTask chunkDataUnloadTask = chunkUnloadTask == null ? null : chunkUnloadTask.task(); -+ if (chunkDataUnloadTask != null) { -+ final PrioritisedExecutor.PrioritisedTask unloadTask = chunkDataUnloadTask.getTask(); -+ if (unloadTask != null) { -+ executedUnloadTask = unloadTask.execute(); -+ } -+ } -+ } -+ -+ boolean canSaveChunk = !(chunk instanceof LevelChunk levelChunk && levelChunk.mustNotSave) && -+ (chunk != null && ((shutdown || chunk instanceof LevelChunk) && chunk.isUnsaved())); -+ boolean canSavePOI = !(chunk instanceof LevelChunk levelChunk && levelChunk.mustNotSave) && (poi != null && poi.isDirty()); -+ boolean canSaveEntities = entities != null; -+ -+ try (co.aikar.timings.Timing ignored = this.world.timings.chunkSave.startTiming()) { // Paper -+ if (canSaveChunk) { -+ canSaveChunk = this.saveChunk(chunk, unloading); -+ } -+ if (canSavePOI) { -+ canSavePOI = this.savePOI(poi, unloading); -+ } -+ if (canSaveEntities) { -+ // on shutdown, we need to force transient entity chunks to save -+ canSaveEntities = this.saveEntities(entities, unloading || shutdown); -+ if (unloading || shutdown) { -+ this.lastEntityUnload = null; -+ } -+ } -+ } -+ -+ return executedUnloadTask | canSaveChunk | canSaveEntities | canSavePOI ? new SaveStat(executedUnloadTask || canSaveChunk, canSaveEntities, canSavePOI): null; -+ } -+ -+ static final class AsyncChunkSerializeTask implements Runnable { -+ -+ private final ServerLevel world; -+ private final ChunkAccess chunk; -+ private final ChunkSerializer.AsyncSaveData asyncSaveData; -+ private final NewChunkHolder toComplete; -+ -+ public AsyncChunkSerializeTask(final ServerLevel world, final ChunkAccess chunk, final ChunkSerializer.AsyncSaveData asyncSaveData, -+ final NewChunkHolder toComplete) { -+ this.world = world; -+ this.chunk = chunk; -+ this.asyncSaveData = asyncSaveData; -+ this.toComplete = toComplete; -+ } -+ -+ @Override -+ public void run() { -+ final CompoundTag toSerialize; -+ try { -+ toSerialize = ChunkSerializer.saveChunk(this.world, this.chunk, this.asyncSaveData); -+ } catch (final ThreadDeath death) { -+ throw death; -+ } catch (final Throwable throwable) { -+ LOGGER.error("Failed to asynchronously save chunk " + this.chunk.getPos() + " for world '" + this.world.getWorld().getName() + "', falling back to synchronous save", throwable); -+ this.world.chunkTaskScheduler.scheduleChunkTask(this.chunk.locX, this.chunk.locZ, () -> { -+ final CompoundTag synchronousSave; -+ try { -+ synchronousSave = ChunkSerializer.saveChunk(AsyncChunkSerializeTask.this.world, AsyncChunkSerializeTask.this.chunk, AsyncChunkSerializeTask.this.asyncSaveData); -+ } catch (final ThreadDeath death) { -+ throw death; -+ } catch (final Throwable throwable2) { -+ LOGGER.error("Failed to synchronously save chunk " + AsyncChunkSerializeTask.this.chunk.getPos() + " for world '" + AsyncChunkSerializeTask.this.world.getWorld().getName() + "', chunk data will be lost", throwable2); -+ AsyncChunkSerializeTask.this.toComplete.completeAsyncChunkDataSave(null); -+ return; -+ } -+ -+ AsyncChunkSerializeTask.this.toComplete.completeAsyncChunkDataSave(synchronousSave); -+ LOGGER.info("Successfully serialized chunk " + AsyncChunkSerializeTask.this.chunk.getPos() + " for world '" + AsyncChunkSerializeTask.this.world.getWorld().getName() + "' synchronously"); -+ -+ }, PrioritisedExecutor.Priority.HIGHEST); -+ return; -+ } -+ this.toComplete.completeAsyncChunkDataSave(toSerialize); -+ } -+ -+ @Override -+ public String toString() { -+ return "AsyncChunkSerializeTask{" + -+ "chunk={pos=" + this.chunk.getPos() + ",world=\"" + this.world.getWorld().getName() + "\"}" + -+ "}"; -+ } -+ } -+ -+ private boolean saveChunk(final ChunkAccess chunk, final boolean unloading) { -+ if (!chunk.isUnsaved()) { -+ if (unloading) { -+ this.completeAsyncChunkDataSave(null); -+ } -+ return false; -+ } -+ boolean completing = false; -+ try { -+ if (unloading) { -+ try { -+ final ChunkSerializer.AsyncSaveData asyncSaveData = ChunkSerializer.getAsyncSaveData(this.world, chunk); -+ -+ final PrioritisedExecutor.PrioritisedTask task = this.scheduler.loadExecutor.createTask(new AsyncChunkSerializeTask(this.world, chunk, asyncSaveData, this)); -+ -+ this.chunkDataUnload.task().setTask(task); -+ -+ task.queue(); -+ -+ chunk.setUnsaved(false); -+ -+ return true; -+ } catch (final ThreadDeath death) { -+ throw death; -+ } catch (final Throwable thr) { -+ LOGGER.error("Failed to prepare async chunk data (" + this.chunkX + "," + this.chunkZ + ") in world '" + this.world.getWorld().getName() + "', falling back to synchronous save", thr); -+ // fall through to synchronous save -+ } -+ } -+ -+ final CompoundTag save = ChunkSerializer.saveChunk(this.world, chunk, null); -+ -+ if (unloading) { -+ completing = true; -+ this.completeAsyncChunkDataSave(save); -+ LOGGER.info("Successfully serialized chunk data (" + this.chunkX + "," + this.chunkZ + ") in world '" + this.world.getWorld().getName() + "' synchronously"); -+ } else { -+ RegionFileIOThread.scheduleSave(this.world, this.chunkX, this.chunkZ, save, RegionFileIOThread.RegionFileType.CHUNK_DATA); -+ } -+ chunk.setUnsaved(false); -+ } catch (final ThreadDeath death) { -+ throw death; -+ } catch (final Throwable thr) { -+ LOGGER.error("Failed to save chunk data (" + this.chunkX + "," + this.chunkZ + ") in world '" + this.world.getWorld().getName() + "'"); -+ if (unloading && !completing) { -+ this.completeAsyncChunkDataSave(null); -+ } -+ } -+ -+ return true; -+ } -+ -+ private boolean lastEntitySaveNull; -+ private CompoundTag lastEntityUnload; -+ private boolean saveEntities(final ChunkEntitySlices entities, final boolean unloading) { -+ try { -+ CompoundTag mergeFrom = null; -+ if (entities.isTransient()) { -+ if (!unloading) { -+ // if we're a transient chunk, we cannot save until unloading because otherwise a double save will -+ // result in double adding the entities -+ return false; -+ } -+ try { -+ mergeFrom = RegionFileIOThread.loadData(this.world, this.chunkX, this.chunkZ, RegionFileIOThread.RegionFileType.ENTITY_DATA, PrioritisedExecutor.Priority.BLOCKING); -+ } catch (final Exception ex) { -+ LOGGER.error("Cannot merge transient entities for chunk (" + this.chunkX + "," + this.chunkZ + ") in world '" + this.world.getWorld().getName() + "', data on disk will be replaced", ex); -+ } -+ } -+ -+ final CompoundTag save = entities.save(); -+ if (mergeFrom != null) { -+ if (save == null) { -+ // don't override the data on disk with nothing -+ return false; -+ } else { -+ EntityStorage.copyEntities(mergeFrom, save); -+ } -+ } -+ if (save == null && this.lastEntitySaveNull) { -+ return false; -+ } -+ -+ RegionFileIOThread.scheduleSave(this.world, this.chunkX, this.chunkZ, save, RegionFileIOThread.RegionFileType.ENTITY_DATA); -+ this.lastEntitySaveNull = save == null; -+ if (unloading) { -+ this.lastEntityUnload = save; -+ } -+ } catch (final ThreadDeath death) { -+ throw death; -+ } catch (final Throwable thr) { -+ LOGGER.error("Failed to save entity data (" + this.chunkX + "," + this.chunkZ + ") in world '" + this.world.getWorld().getName() + "'"); -+ } -+ -+ return true; -+ } -+ -+ private boolean lastPoiSaveNull; -+ private boolean savePOI(final PoiChunk poi, final boolean unloading) { -+ try { -+ final CompoundTag save = poi.save(); -+ poi.setDirty(false); -+ if (save == null && this.lastPoiSaveNull) { -+ if (unloading) { -+ this.poiDataUnload.completable().complete(null); -+ } -+ return false; -+ } -+ -+ RegionFileIOThread.scheduleSave(this.world, this.chunkX, this.chunkZ, save, RegionFileIOThread.RegionFileType.POI_DATA); -+ this.lastPoiSaveNull = save == null; -+ if (unloading) { -+ this.poiDataUnload.completable().complete(save); -+ } -+ } catch (final ThreadDeath death) { -+ throw death; -+ } catch (final Throwable thr) { -+ LOGGER.error("Failed to save poi data (" + this.chunkX + "," + this.chunkZ + ") in world '" + this.world.getWorld().getName() + "'"); -+ } -+ -+ return true; -+ } -+ -+ @Override -+ public String toString() { -+ final ChunkCompletion lastCompletion = this.lastChunkCompletion; -+ final ChunkEntitySlices entityChunk = this.entityChunk; -+ final long chunkStatus = this.chunkStatus; -+ final int fullChunkStatus = (int)chunkStatus; -+ final int pendingChunkStatus = (int)(chunkStatus >>> 32); -+ final FullChunkStatus currentFullStatus = fullChunkStatus < 0 || fullChunkStatus >= CHUNK_STATUS_BY_ID.length ? null : CHUNK_STATUS_BY_ID[fullChunkStatus]; -+ final FullChunkStatus pendingFullStatus = pendingChunkStatus < 0 || pendingChunkStatus >= CHUNK_STATUS_BY_ID.length ? null : CHUNK_STATUS_BY_ID[pendingChunkStatus]; -+ return "NewChunkHolder{" + -+ "world=" + this.world.getWorld().getName() + -+ ", chunkX=" + this.chunkX + -+ ", chunkZ=" + this.chunkZ + -+ ", entityChunkFromDisk=" + (entityChunk != null && !entityChunk.isTransient()) + -+ ", lastChunkCompletion={chunk_class=" + (lastCompletion == null || lastCompletion.chunk() == null ? "null" : lastCompletion.chunk().getClass().getName()) + ",status=" + (lastCompletion == null ? "null" : lastCompletion.genStatus()) + "}" + -+ ", currentGenStatus=" + this.currentGenStatus + -+ ", requestedGenStatus=" + this.requestedGenStatus + -+ ", generationTask=" + this.generationTask + -+ ", generationTaskStatus=" + this.generationTaskStatus + -+ ", priority=" + this.priority + -+ ", priorityLocked=" + this.priorityLocked + -+ ", neighbourRequestedPriority=" + this.neighbourRequestedPriority + -+ ", effective_priority=" + this.getEffectivePriority() + -+ ", oldTicketLevel=" + this.oldTicketLevel + -+ ", currentTicketLevel=" + this.currentTicketLevel + -+ ", totalNeighboursUsingThisChunk=" + this.totalNeighboursUsingThisChunk + -+ ", fullNeighbourChunksLoadedBitset=" + this.fullNeighbourChunksLoadedBitset + -+ ", chunkStatusRaw=" + chunkStatus + -+ ", currentChunkStatus=" + currentFullStatus + -+ ", pendingChunkStatus=" + pendingFullStatus + -+ ", is_unload_safe=" + this.isSafeToUnload() + -+ ", killed=" + this.killed + -+ '}'; -+ } -+ -+ private static JsonElement serializeCompletable(final Completable completable) { -+ if (completable == null) { -+ return new JsonPrimitive("null"); -+ } -+ -+ final JsonObject ret = new JsonObject(); -+ final boolean isCompleted = completable.isCompleted(); -+ ret.addProperty("completed", Boolean.valueOf(isCompleted)); -+ -+ if (isCompleted) { -+ ret.addProperty("completed_exceptionally", Boolean.valueOf(completable.getThrowable() != null)); -+ } -+ -+ return ret; -+ } -+ -+ // holds ticket and scheduling lock -+ public JsonObject getDebugJson() { -+ final JsonObject ret = new JsonObject(); -+ -+ final ChunkCompletion lastCompletion = this.lastChunkCompletion; -+ final ChunkEntitySlices slices = this.entityChunk; -+ final PoiChunk poiChunk = this.poiChunk; -+ -+ ret.addProperty("chunkX", Integer.valueOf(this.chunkX)); -+ ret.addProperty("chunkZ", Integer.valueOf(this.chunkZ)); -+ ret.addProperty("entity_chunk", slices == null ? "null" : "transient=" + slices.isTransient()); -+ ret.addProperty("poi_chunk", "null=" + (poiChunk == null)); -+ ret.addProperty("completed_chunk_class", lastCompletion == null ? "null" : lastCompletion.chunk().getClass().getName()); -+ ret.addProperty("completed_gen_status", lastCompletion == null ? "null" : lastCompletion.genStatus().toString()); -+ ret.addProperty("priority", Objects.toString(this.priority)); -+ ret.addProperty("neighbour_requested_priority", Objects.toString(this.neighbourRequestedPriority)); -+ ret.addProperty("generation_task", Objects.toString(this.generationTask)); -+ ret.addProperty("is_safe_unload", Objects.toString(this.isSafeToUnload())); -+ ret.addProperty("old_ticket_level", Integer.valueOf(this.oldTicketLevel)); -+ ret.addProperty("current_ticket_level", Integer.valueOf(this.currentTicketLevel)); -+ ret.addProperty("neighbours_using_chunk", Integer.valueOf(this.totalNeighboursUsingThisChunk)); -+ -+ final JsonObject neighbourWaitState = new JsonObject(); -+ ret.add("neighbour_state", neighbourWaitState); -+ -+ final JsonArray blockingGenNeighbours = new JsonArray(); -+ neighbourWaitState.add("blocking_gen_task", blockingGenNeighbours); -+ for (final NewChunkHolder blockingGenNeighbour : this.neighboursBlockingGenTask) { -+ final JsonObject neighbour = new JsonObject(); -+ blockingGenNeighbours.add(neighbour); -+ -+ neighbour.addProperty("chunkX", Integer.valueOf(blockingGenNeighbour.chunkX)); -+ neighbour.addProperty("chunkZ", Integer.valueOf(blockingGenNeighbour.chunkZ)); -+ } -+ -+ final JsonArray neighboursWaitingForUs = new JsonArray(); -+ neighbourWaitState.add("neighbours_waiting_on_us", neighboursWaitingForUs); -+ for (final Reference2ObjectMap.Entry entry : this.neighboursWaitingForUs.reference2ObjectEntrySet()) { -+ final NewChunkHolder holder = entry.getKey(); -+ final ChunkStatus status = entry.getValue(); -+ -+ final JsonObject neighbour = new JsonObject(); -+ neighboursWaitingForUs.add(neighbour); -+ -+ -+ neighbour.addProperty("chunkX", Integer.valueOf(holder.chunkX)); -+ neighbour.addProperty("chunkZ", Integer.valueOf(holder.chunkZ)); -+ neighbour.addProperty("waiting_for", Objects.toString(status)); -+ } -+ -+ ret.addProperty("fullchunkstatus", Objects.toString(this.getChunkStatus())); -+ ret.addProperty("fullchunkstatus_raw", Long.valueOf(this.chunkStatus)); -+ ret.addProperty("generation_task", Objects.toString(this.generationTask)); -+ ret.addProperty("requested_generation", Objects.toString(this.requestedGenStatus)); -+ ret.addProperty("has_entity_load_task", Boolean.valueOf(this.entityDataLoadTask != null)); -+ ret.addProperty("has_poi_load_task", Boolean.valueOf(this.poiDataLoadTask != null)); -+ -+ final UnloadTask entityDataUnload = this.entityDataUnload; -+ final UnloadTask poiDataUnload = this.poiDataUnload; -+ final UnloadTask chunkDataUnload = this.chunkDataUnload; -+ -+ ret.add("entity_unload_completable", serializeCompletable(entityDataUnload == null ? null : entityDataUnload.completable())); -+ ret.add("poi_unload_completable", serializeCompletable(poiDataUnload == null ? null : poiDataUnload.completable())); -+ ret.add("chunk_unload_completable", serializeCompletable(chunkDataUnload == null ? null : chunkDataUnload.completable())); -+ -+ final DelayedPrioritisedTask unloadTask = chunkDataUnload == null ? null : chunkDataUnload.task(); -+ if (unloadTask == null) { -+ ret.addProperty("unload_task_priority", "null"); -+ ret.addProperty("unload_task_priority_raw", "null"); -+ } else { -+ ret.addProperty("unload_task_priority", Objects.toString(unloadTask.getPriority())); -+ ret.addProperty("unload_task_priority_raw", Integer.valueOf(unloadTask.getPriorityInternal())); -+ } -+ -+ ret.addProperty("killed", Boolean.valueOf(this.killed)); -+ -+ return ret; -+ } -+} -diff --git a/src/main/java/io/papermc/paper/chunk/system/scheduling/PriorityHolder.java b/src/main/java/io/papermc/paper/chunk/system/scheduling/PriorityHolder.java -new file mode 100644 -index 0000000000000000000000000000000000000000..b4c56bf12dc8dd17452210ece4fd67411cc6b2fd ---- /dev/null -+++ b/src/main/java/io/papermc/paper/chunk/system/scheduling/PriorityHolder.java -@@ -0,0 +1,215 @@ -+package io.papermc.paper.chunk.system.scheduling; -+ -+import ca.spottedleaf.concurrentutil.executor.standard.PrioritisedExecutor; -+import ca.spottedleaf.concurrentutil.util.ConcurrentUtil; -+import java.lang.invoke.VarHandle; -+ -+public abstract class PriorityHolder { -+ -+ protected volatile int priority; -+ protected static final VarHandle PRIORITY_HANDLE = ConcurrentUtil.getVarHandle(PriorityHolder.class, "priority", int.class); -+ -+ protected static final int PRIORITY_SCHEDULED = Integer.MIN_VALUE >>> 0; -+ protected static final int PRIORITY_EXECUTED = Integer.MIN_VALUE >>> 1; -+ -+ protected final int getPriorityVolatile() { -+ return (int)PRIORITY_HANDLE.getVolatile((PriorityHolder)this); -+ } -+ -+ protected final int compareAndExchangePriorityVolatile(final int expect, final int update) { -+ return (int)PRIORITY_HANDLE.compareAndExchange((PriorityHolder)this, (int)expect, (int)update); -+ } -+ -+ protected final int getAndOrPriorityVolatile(final int val) { -+ return (int)PRIORITY_HANDLE.getAndBitwiseOr((PriorityHolder)this, (int)val); -+ } -+ -+ protected final void setPriorityPlain(final int val) { -+ PRIORITY_HANDLE.set((PriorityHolder)this, (int)val); -+ } -+ -+ protected PriorityHolder(final PrioritisedExecutor.Priority priority) { -+ if (!PrioritisedExecutor.Priority.isValidPriority(priority)) { -+ throw new IllegalArgumentException("Invalid priority " + priority); -+ } -+ this.setPriorityPlain(priority.priority); -+ } -+ -+ // used only for debug json -+ public boolean isScheduled() { -+ return (this.getPriorityVolatile() & PRIORITY_SCHEDULED) != 0; -+ } -+ -+ // returns false if cancelled -+ protected boolean markExecuting() { -+ return (this.getAndOrPriorityVolatile(PRIORITY_EXECUTED) & PRIORITY_EXECUTED) == 0; -+ } -+ -+ protected boolean isMarkedExecuted() { -+ return (this.getPriorityVolatile() & PRIORITY_EXECUTED) != 0; -+ } -+ -+ public void cancel() { -+ if ((this.getAndOrPriorityVolatile(PRIORITY_EXECUTED) & PRIORITY_EXECUTED) != 0) { -+ // cancelled already -+ return; -+ } -+ this.cancelScheduled(); -+ } -+ -+ public void schedule() { -+ int priority = this.getPriorityVolatile(); -+ -+ if ((priority & PRIORITY_SCHEDULED) != 0) { -+ throw new IllegalStateException("schedule() called twice"); -+ } -+ -+ if ((priority & PRIORITY_EXECUTED) != 0) { -+ // cancelled -+ return; -+ } -+ -+ this.scheduleTask(PrioritisedExecutor.Priority.getPriority(priority)); -+ -+ int failures = 0; -+ for (;;) { -+ if (priority == (priority = this.compareAndExchangePriorityVolatile(priority, priority | PRIORITY_SCHEDULED))) { -+ return; -+ } -+ -+ if ((priority & PRIORITY_SCHEDULED) != 0) { -+ throw new IllegalStateException("schedule() called twice"); -+ } -+ -+ if ((priority & PRIORITY_EXECUTED) != 0) { -+ // cancelled or executed -+ return; -+ } -+ -+ this.setPriorityScheduled(PrioritisedExecutor.Priority.getPriority(priority)); -+ -+ ++failures; -+ for (int i = 0; i < failures; ++i) { -+ ConcurrentUtil.backoff(); -+ } -+ } -+ } -+ -+ public final PrioritisedExecutor.Priority getPriority() { -+ final int ret = this.getPriorityVolatile(); -+ if ((ret & PRIORITY_EXECUTED) != 0) { -+ return PrioritisedExecutor.Priority.COMPLETING; -+ } -+ if ((ret & PRIORITY_SCHEDULED) != 0) { -+ return this.getScheduledPriority(); -+ } -+ return PrioritisedExecutor.Priority.getPriority(ret); -+ } -+ -+ public final void lowerPriority(final PrioritisedExecutor.Priority priority) { -+ if (!PrioritisedExecutor.Priority.isValidPriority(priority)) { -+ throw new IllegalArgumentException("Invalid priority " + priority); -+ } -+ -+ int failures = 0; -+ for (int curr = this.getPriorityVolatile();;) { -+ if ((curr & PRIORITY_EXECUTED) != 0) { -+ return; -+ } -+ -+ if ((curr & PRIORITY_SCHEDULED) != 0) { -+ this.lowerPriorityScheduled(priority); -+ return; -+ } -+ -+ if (!priority.isLowerPriority(curr)) { -+ return; -+ } -+ -+ if (curr == (curr = this.compareAndExchangePriorityVolatile(curr, priority.priority))) { -+ return; -+ } -+ -+ // failed, retry -+ -+ ++failures; -+ for (int i = 0; i < failures; ++i) { -+ ConcurrentUtil.backoff(); -+ } -+ } -+ } -+ -+ public final void setPriority(final PrioritisedExecutor.Priority priority) { -+ if (!PrioritisedExecutor.Priority.isValidPriority(priority)) { -+ throw new IllegalArgumentException("Invalid priority " + priority); -+ } -+ -+ int failures = 0; -+ for (int curr = this.getPriorityVolatile();;) { -+ if ((curr & PRIORITY_EXECUTED) != 0) { -+ return; -+ } -+ -+ if ((curr & PRIORITY_SCHEDULED) != 0) { -+ this.setPriorityScheduled(priority); -+ return; -+ } -+ -+ if (curr == (curr = this.compareAndExchangePriorityVolatile(curr, priority.priority))) { -+ return; -+ } -+ -+ // failed, retry -+ -+ ++failures; -+ for (int i = 0; i < failures; ++i) { -+ ConcurrentUtil.backoff(); -+ } -+ } -+ } -+ -+ public final void raisePriority(final PrioritisedExecutor.Priority priority) { -+ if (!PrioritisedExecutor.Priority.isValidPriority(priority)) { -+ throw new IllegalArgumentException("Invalid priority " + priority); -+ } -+ -+ int failures = 0; -+ for (int curr = this.getPriorityVolatile();;) { -+ if ((curr & PRIORITY_EXECUTED) != 0) { -+ return; -+ } -+ -+ if ((curr & PRIORITY_SCHEDULED) != 0) { -+ this.raisePriorityScheduled(priority); -+ return; -+ } -+ -+ if (!priority.isHigherPriority(curr)) { -+ return; -+ } -+ -+ if (curr == (curr = this.compareAndExchangePriorityVolatile(curr, priority.priority))) { -+ return; -+ } -+ -+ // failed, retry -+ -+ ++failures; -+ for (int i = 0; i < failures; ++i) { -+ ConcurrentUtil.backoff(); -+ } -+ } -+ } -+ -+ protected abstract void cancelScheduled(); -+ -+ protected abstract PrioritisedExecutor.Priority getScheduledPriority(); -+ -+ protected abstract void scheduleTask(final PrioritisedExecutor.Priority priority); -+ -+ protected abstract void lowerPriorityScheduled(final PrioritisedExecutor.Priority priority); -+ -+ protected abstract void setPriorityScheduled(final PrioritisedExecutor.Priority priority); -+ -+ protected abstract void raisePriorityScheduled(final PrioritisedExecutor.Priority priority); -+} -diff --git a/src/main/java/io/papermc/paper/chunk/system/scheduling/ThreadedTicketLevelPropagator.java b/src/main/java/io/papermc/paper/chunk/system/scheduling/ThreadedTicketLevelPropagator.java -new file mode 100644 -index 0000000000000000000000000000000000000000..287240ed3b440f2f5733c368416e4276f626405d ---- /dev/null -+++ b/src/main/java/io/papermc/paper/chunk/system/scheduling/ThreadedTicketLevelPropagator.java -@@ -0,0 +1,1477 @@ -+package io.papermc.paper.chunk.system.scheduling; -+ -+import ca.spottedleaf.concurrentutil.collection.MultiThreadedQueue; -+import ca.spottedleaf.concurrentutil.lock.ReentrantAreaLock; -+import ca.spottedleaf.concurrentutil.util.ConcurrentUtil; -+import it.unimi.dsi.fastutil.HashCommon; -+import it.unimi.dsi.fastutil.longs.Long2ByteLinkedOpenHashMap; -+import it.unimi.dsi.fastutil.shorts.Short2ByteLinkedOpenHashMap; -+import it.unimi.dsi.fastutil.shorts.Short2ByteMap; -+import it.unimi.dsi.fastutil.shorts.ShortOpenHashSet; -+import java.lang.invoke.VarHandle; -+import java.util.ArrayDeque; -+import java.util.Arrays; -+import java.util.Iterator; -+import java.util.List; -+import java.util.concurrent.ConcurrentHashMap; -+import java.util.concurrent.locks.LockSupport; -+ -+public abstract class ThreadedTicketLevelPropagator { -+ -+ // sections are 64 in length -+ public static final int SECTION_SHIFT = 6; -+ public static final int SECTION_SIZE = 1 << SECTION_SHIFT; -+ private static final int LEVEL_BITS = SECTION_SHIFT; -+ private static final int LEVEL_COUNT = 1 << LEVEL_BITS; -+ private static final int MIN_SOURCE_LEVEL = 1; -+ // we limit the max source to 62 because the depropagation code _must_ attempt to depropagate -+ // a 1 level to 0; and if a source was 63 then it may cross more than 2 sections in depropagation -+ private static final int MAX_SOURCE_LEVEL = 62; -+ -+ private final UpdateQueue updateQueue; -+ private final ConcurrentHashMap sections = new ConcurrentHashMap<>(); -+ -+ public ThreadedTicketLevelPropagator() { -+ this.updateQueue = new UpdateQueue(); -+ } -+ -+ // must hold ticket lock for: -+ // (posX & ~(SECTION_SIZE - 1), posZ & ~(SECTION_SIZE - 1)) to (posX | (SECTION_SIZE - 1), posZ | (SECTION_SIZE - 1)) -+ public void setSource(final int posX, final int posZ, final int to) { -+ if (to < 1 || to > MAX_SOURCE_LEVEL) { -+ throw new IllegalArgumentException("Source: " + to); -+ } -+ -+ final int sectionX = posX >> SECTION_SHIFT; -+ final int sectionZ = posZ >> SECTION_SHIFT; -+ -+ final Coordinate coordinate = new Coordinate(sectionX, sectionZ); -+ Section section = this.sections.get(coordinate); -+ if (section == null) { -+ if (null != this.sections.putIfAbsent(coordinate, section = new Section(sectionX, sectionZ))) { -+ throw new IllegalStateException("Race condition while creating new section"); -+ } -+ } -+ -+ final int localIdx = (posX & (SECTION_SIZE - 1)) | ((posZ & (SECTION_SIZE - 1)) << SECTION_SHIFT); -+ final short sLocalIdx = (short)localIdx; -+ -+ final short sourceAndLevel = section.levels[localIdx]; -+ final int currentSource = (sourceAndLevel >>> 8) & 0xFF; -+ -+ if (currentSource == to) { -+ // nothing to do -+ // make sure to kill the current update, if any -+ section.queuedSources.replace(sLocalIdx, (byte)to); -+ return; -+ } -+ -+ if (section.queuedSources.put(sLocalIdx, (byte)to) == Section.NO_QUEUED_UPDATE && section.queuedSources.size() == 1) { -+ this.queueSectionUpdate(section); -+ } -+ } -+ -+ // must hold ticket lock for: -+ // (posX & ~(SECTION_SIZE - 1), posZ & ~(SECTION_SIZE - 1)) to (posX | (SECTION_SIZE - 1), posZ | (SECTION_SIZE - 1)) -+ public void removeSource(final int posX, final int posZ) { -+ final int sectionX = posX >> SECTION_SHIFT; -+ final int sectionZ = posZ >> SECTION_SHIFT; -+ -+ final Coordinate coordinate = new Coordinate(sectionX, sectionZ); -+ final Section section = this.sections.get(coordinate); -+ -+ if (section == null) { -+ return; -+ } -+ -+ final int localIdx = (posX & (SECTION_SIZE - 1)) | ((posZ & (SECTION_SIZE - 1)) << SECTION_SHIFT); -+ final short sLocalIdx = (short)localIdx; -+ -+ final int currentSource = (section.levels[localIdx] >>> 8) & 0xFF; -+ -+ if (currentSource == 0) { -+ // we use replace here so that we do not possibly multi-queue a section for an update -+ section.queuedSources.replace(sLocalIdx, (byte)0); -+ return; -+ } -+ -+ if (section.queuedSources.put(sLocalIdx, (byte)0) == Section.NO_QUEUED_UPDATE && section.queuedSources.size() == 1) { -+ this.queueSectionUpdate(section); -+ } -+ } -+ -+ private void queueSectionUpdate(final Section section) { -+ this.updateQueue.append(new UpdateQueue.UpdateQueueNode(section, null)); -+ } -+ -+ public boolean hasPendingUpdates() { -+ return !this.updateQueue.isEmpty(); -+ } -+ -+ // holds ticket lock for every chunk section represented by any position in the key set -+ // updates is modifiable and passed to processSchedulingUpdates after this call -+ protected abstract void processLevelUpdates(final Long2ByteLinkedOpenHashMap updates); -+ -+ // holds ticket lock for every chunk section represented by any position in the key set -+ // holds scheduling lock in max access radius for every position held by the ticket lock -+ // updates is cleared after this call -+ protected abstract void processSchedulingUpdates(final Long2ByteLinkedOpenHashMap updates, final List scheduledTasks, -+ final List changedFullStatus); -+ -+ // must hold ticket lock for every position in the sections in one radius around sectionX,sectionZ -+ public boolean performUpdate(final int sectionX, final int sectionZ, final ReentrantAreaLock schedulingLock, -+ final List scheduledTasks, final List changedFullStatus) { -+ if (!this.hasPendingUpdates()) { -+ return false; -+ } -+ -+ final Coordinate coordinate = new Coordinate(Coordinate.key(sectionX, sectionZ)); -+ final Section section = this.sections.get(coordinate); -+ -+ if (section == null || section.queuedSources.isEmpty()) { -+ // no section or no updates -+ return false; -+ } -+ -+ final Propagator propagator = Propagator.acquirePropagator(); -+ final boolean ret = this.performUpdate(section, null, propagator, -+ null, schedulingLock, scheduledTasks, changedFullStatus -+ ); -+ Propagator.returnPropagator(propagator); -+ return ret; -+ } -+ -+ private boolean performUpdate(final Section section, final UpdateQueue.UpdateQueueNode node, final Propagator propagator, -+ final ReentrantAreaLock ticketLock, final ReentrantAreaLock schedulingLock, -+ final List scheduledTasks, final List changedFullStatus) { -+ final int sectionX = section.sectionX; -+ final int sectionZ = section.sectionZ; -+ -+ final int rad1MinX = (sectionX - 1) << SECTION_SHIFT; -+ final int rad1MinZ = (sectionZ - 1) << SECTION_SHIFT; -+ final int rad1MaxX = ((sectionX + 1) << SECTION_SHIFT) | (SECTION_SIZE - 1); -+ final int rad1MaxZ = ((sectionZ + 1) << SECTION_SHIFT) | (SECTION_SIZE - 1); -+ -+ // set up encode offset first as we need to queue level changes _before_ -+ propagator.setupEncodeOffset(sectionX, sectionZ); -+ -+ final int coordinateOffset = propagator.coordinateOffset; -+ -+ final ReentrantAreaLock.Node ticketNode = ticketLock == null ? null : ticketLock.lock(rad1MinX, rad1MinZ, rad1MaxX, rad1MaxZ); -+ final boolean ret; -+ try { -+ // first, check if this update was stolen -+ if (section != this.sections.get(new Coordinate(sectionX, sectionZ))) { -+ // occurs when a stolen update deletes this section -+ // it is possible that another update is scheduled, but that one will have the correct section -+ if (node != null) { -+ this.updateQueue.remove(node); -+ } -+ return false; -+ } -+ -+ final int oldSourceSize = section.sources.size(); -+ -+ // process pending sources -+ for (final Iterator iterator = section.queuedSources.short2ByteEntrySet().fastIterator(); iterator.hasNext();) { -+ final Short2ByteMap.Entry entry = iterator.next(); -+ final int pos = (int)entry.getShortKey(); -+ final int posX = (pos & (SECTION_SIZE - 1)) | (sectionX << SECTION_SHIFT); -+ final int posZ = ((pos >> SECTION_SHIFT) & (SECTION_SIZE - 1)) | (sectionZ << SECTION_SHIFT); -+ final int newSource = (int)entry.getByteValue(); -+ -+ final short currentEncoded = section.levels[pos]; -+ final int currLevel = currentEncoded & 0xFF; -+ final int prevSource = (currentEncoded >>> 8) & 0xFF; -+ -+ if (prevSource == newSource) { -+ // nothing changed -+ continue; -+ } -+ -+ if ((prevSource < currLevel && newSource <= currLevel) || newSource == currLevel) { -+ // just update the source, don't need to propagate change -+ section.levels[pos] = (short)(currLevel | (newSource << 8)); -+ // level is unchanged, don't add to changed positions -+ } else { -+ // set current level and current source to new source -+ section.levels[pos] = (short)(newSource | (newSource << 8)); -+ // must add to updated positions in case this is final -+ propagator.updatedPositions.put(Coordinate.key(posX, posZ), (byte)newSource); -+ if (newSource != 0) { -+ // queue increase with new source level -+ propagator.appendToIncreaseQueue( -+ ((long)(posX + (posZ << Propagator.COORDINATE_BITS) + coordinateOffset) & ((1L << (Propagator.COORDINATE_BITS + Propagator.COORDINATE_BITS)) - 1)) | -+ ((newSource & (LEVEL_COUNT - 1L)) << (Propagator.COORDINATE_BITS + Propagator.COORDINATE_BITS)) | -+ (Propagator.ALL_DIRECTIONS_BITSET << (Propagator.COORDINATE_BITS + Propagator.COORDINATE_BITS + LEVEL_BITS)) -+ ); -+ } -+ // queue decrease with previous level -+ if (newSource < currLevel) { -+ propagator.appendToDecreaseQueue( -+ ((long)(posX + (posZ << Propagator.COORDINATE_BITS) + coordinateOffset) & ((1L << (Propagator.COORDINATE_BITS + Propagator.COORDINATE_BITS)) - 1)) | -+ ((currLevel & (LEVEL_COUNT - 1L)) << (Propagator.COORDINATE_BITS + Propagator.COORDINATE_BITS)) | -+ (Propagator.ALL_DIRECTIONS_BITSET << (Propagator.COORDINATE_BITS + Propagator.COORDINATE_BITS + LEVEL_BITS)) -+ ); -+ } -+ } -+ -+ if (newSource == 0) { -+ // prevSource != newSource, so we are removing this source -+ section.sources.remove((short)pos); -+ } else if (prevSource == 0) { -+ // prevSource != newSource, so we are adding this source -+ section.sources.add((short)pos); -+ } -+ } -+ -+ section.queuedSources.clear(); -+ -+ final int newSourceSize = section.sources.size(); -+ -+ if (oldSourceSize == 0 && newSourceSize != 0) { -+ // need to make sure the sections in 1 radius are initialised -+ for (int dz = -1; dz <= 1; ++dz) { -+ for (int dx = -1; dx <= 1; ++dx) { -+ if ((dx | dz) == 0) { -+ continue; -+ } -+ final int offX = dx + sectionX; -+ final int offZ = dz + sectionZ; -+ final Coordinate coordinate = new Coordinate(offX, offZ); -+ final Section neighbour = this.sections.computeIfAbsent(coordinate, (final Coordinate keyInMap) -> { -+ return new Section(Coordinate.x(keyInMap.key), Coordinate.z(keyInMap.key)); -+ }); -+ -+ // increase ref count -+ ++neighbour.oneRadNeighboursWithSources; -+ if (neighbour.oneRadNeighboursWithSources <= 0 || neighbour.oneRadNeighboursWithSources > 8) { -+ throw new IllegalStateException(Integer.toString(neighbour.oneRadNeighboursWithSources)); -+ } -+ } -+ } -+ } -+ -+ if (propagator.hasUpdates()) { -+ propagator.setupCaches(this, sectionX, sectionZ, 1); -+ propagator.performDecrease(); -+ // don't need try-finally, as any exception will cause the propagator to not be returned -+ propagator.destroyCaches(); -+ } -+ -+ if (newSourceSize == 0) { -+ final boolean decrementRef = oldSourceSize != 0; -+ // check for section de-init -+ for (int dz = -1; dz <= 1; ++dz) { -+ for (int dx = -1; dx <= 1; ++dx) { -+ final int offX = dx + sectionX; -+ final int offZ = dz + sectionZ; -+ final Coordinate coordinate = new Coordinate(offX, offZ); -+ final Section neighbour = this.sections.get(coordinate); -+ -+ if (neighbour == null) { -+ if (oldSourceSize == 0 && (dx | dz) != 0) { -+ // since we don't have sources, this section is allowed to null -+ continue; -+ } -+ throw new IllegalStateException("??"); -+ } -+ -+ if (decrementRef && (dx | dz) != 0) { -+ // decrease ref count, but only for neighbours -+ --neighbour.oneRadNeighboursWithSources; -+ } -+ -+ // we need to check the current section for de-init as well -+ if (neighbour.oneRadNeighboursWithSources == 0) { -+ if (neighbour.queuedSources.isEmpty() && neighbour.sources.isEmpty()) { -+ // need to de-init -+ this.sections.remove(coordinate); -+ } // else: neighbour is queued for an update, and it will de-init itself -+ } else if (neighbour.oneRadNeighboursWithSources < 0 || neighbour.oneRadNeighboursWithSources > 8) { -+ throw new IllegalStateException(Integer.toString(neighbour.oneRadNeighboursWithSources)); -+ } -+ } -+ } -+ } -+ -+ -+ ret = !propagator.updatedPositions.isEmpty(); -+ -+ if (ret) { -+ this.processLevelUpdates(propagator.updatedPositions); -+ -+ if (!propagator.updatedPositions.isEmpty()) { -+ // now we can actually update the ticket levels in the chunk holders -+ final int maxScheduleRadius = 2 * ChunkTaskScheduler.getMaxAccessRadius(); -+ -+ // allow the chunkholders to process ticket level updates without needing to acquire the schedule lock every time -+ final ReentrantAreaLock.Node schedulingNode = schedulingLock.lock( -+ rad1MinX - maxScheduleRadius, rad1MinZ - maxScheduleRadius, -+ rad1MaxX + maxScheduleRadius, rad1MaxZ + maxScheduleRadius -+ ); -+ try { -+ this.processSchedulingUpdates(propagator.updatedPositions, scheduledTasks, changedFullStatus); -+ } finally { -+ schedulingLock.unlock(schedulingNode); -+ } -+ } -+ -+ propagator.updatedPositions.clear(); -+ } -+ } finally { -+ if (ticketLock != null) { -+ ticketLock.unlock(ticketNode); -+ } -+ } -+ -+ // finished -+ if (node != null) { -+ this.updateQueue.remove(node); -+ } -+ -+ return ret; -+ } -+ -+ public boolean performUpdates(final ReentrantAreaLock ticketLock, final ReentrantAreaLock schedulingLock, -+ final List scheduledTasks, final List changedFullStatus) { -+ if (this.updateQueue.isEmpty()) { -+ return false; -+ } -+ -+ final long maxOrder = this.updateQueue.getLastOrder(); -+ -+ boolean updated = false; -+ Propagator propagator = null; -+ -+ for (;;) { -+ final UpdateQueue.UpdateQueueNode toUpdate = this.updateQueue.acquireNextToUpdate(maxOrder); -+ if (toUpdate == null) { -+ this.updateQueue.awaitFirst(maxOrder); -+ -+ if (!this.updateQueue.hasRemainingUpdates(maxOrder)) { -+ if (propagator != null) { -+ Propagator.returnPropagator(propagator); -+ } -+ return updated; -+ } -+ -+ continue; -+ } -+ -+ if (propagator == null) { -+ propagator = Propagator.acquirePropagator(); -+ } -+ -+ updated |= this.performUpdate(toUpdate.section, toUpdate, propagator, ticketLock, schedulingLock, scheduledTasks, changedFullStatus); -+ } -+ } -+ -+ private static final class UpdateQueue { -+ -+ private volatile UpdateQueueNode head; -+ private volatile UpdateQueueNode tail; -+ private volatile UpdateQueueNode lastUpdating; -+ -+ protected static final VarHandle HEAD_HANDLE = ConcurrentUtil.getVarHandle(UpdateQueue.class, "head", UpdateQueueNode.class); -+ protected static final VarHandle TAIL_HANDLE = ConcurrentUtil.getVarHandle(UpdateQueue.class, "tail", UpdateQueueNode.class); -+ protected static final VarHandle LAST_UPDATING = ConcurrentUtil.getVarHandle(UpdateQueue.class, "lastUpdating", UpdateQueueNode.class); -+ -+ /* head */ -+ -+ protected final void setHeadPlain(final UpdateQueueNode newHead) { -+ HEAD_HANDLE.set(this, newHead); -+ } -+ -+ protected final void setHeadOpaque(final UpdateQueueNode newHead) { -+ HEAD_HANDLE.setOpaque(this, newHead); -+ } -+ -+ protected final UpdateQueueNode getHeadPlain() { -+ return (UpdateQueueNode)HEAD_HANDLE.get(this); -+ } -+ -+ protected final UpdateQueueNode getHeadOpaque() { -+ return (UpdateQueueNode)HEAD_HANDLE.getOpaque(this); -+ } -+ -+ protected final UpdateQueueNode getHeadAcquire() { -+ return (UpdateQueueNode)HEAD_HANDLE.getAcquire(this); -+ } -+ -+ /* tail */ -+ -+ protected final void setTailPlain(final UpdateQueueNode newTail) { -+ TAIL_HANDLE.set(this, newTail); -+ } -+ -+ protected final void setTailOpaque(final UpdateQueueNode newTail) { -+ TAIL_HANDLE.setOpaque(this, newTail); -+ } -+ -+ protected final UpdateQueueNode getTailPlain() { -+ return (UpdateQueueNode)TAIL_HANDLE.get(this); -+ } -+ -+ protected final UpdateQueueNode getTailOpaque() { -+ return (UpdateQueueNode)TAIL_HANDLE.getOpaque(this); -+ } -+ -+ /* lastUpdating */ -+ -+ protected final UpdateQueueNode getLastUpdatingVolatile() { -+ return (UpdateQueueNode)LAST_UPDATING.getVolatile(this); -+ } -+ -+ protected final UpdateQueueNode compareAndExchangeLastUpdatingVolatile(final UpdateQueueNode expect, final UpdateQueueNode update) { -+ return (UpdateQueueNode)LAST_UPDATING.compareAndExchange(this, expect, update); -+ } -+ -+ public UpdateQueue() { -+ final UpdateQueueNode dummy = new UpdateQueueNode(null, null); -+ dummy.order = -1L; -+ dummy.preventAdds(); -+ -+ this.setHeadPlain(dummy); -+ this.setTailPlain(dummy); -+ } -+ -+ public boolean isEmpty() { -+ return this.peek() == null; -+ } -+ -+ public boolean hasRemainingUpdates(final long maxUpdate) { -+ final UpdateQueueNode node = this.peek(); -+ return node != null && node.order <= maxUpdate; -+ } -+ -+ public long getLastOrder() { -+ for (UpdateQueueNode tail = this.getTailOpaque(), curr = tail;;) { -+ final UpdateQueueNode next = curr.getNextVolatile(); -+ if (next == null) { -+ // try to update stale tail -+ if (this.getTailOpaque() == tail && curr != tail) { -+ this.setTailOpaque(curr); -+ } -+ return curr.order; -+ } -+ curr = next; -+ } -+ } -+ -+ public UpdateQueueNode acquireNextToUpdate(final long maxOrder) { -+ int failures = 0; -+ for (UpdateQueueNode prev = this.getLastUpdatingVolatile();;) { -+ UpdateQueueNode next = prev == null ? this.peek() : prev.next; -+ -+ if (next == null || next.order > maxOrder) { -+ return null; -+ } -+ -+ for (int i = 0; i < failures; ++i) { -+ ConcurrentUtil.backoff(); -+ } -+ -+ if (prev == (prev = this.compareAndExchangeLastUpdatingVolatile(prev, next))) { -+ return next; -+ } -+ -+ ++failures; -+ } -+ } -+ -+ public void awaitFirst(final long maxOrder) { -+ final UpdateQueueNode earliest = this.peek(); -+ if (earliest == null || earliest.order > maxOrder) { -+ return; -+ } -+ -+ final Thread currThread = Thread.currentThread(); -+ // we do not use add-blocking because we use the nullability of the section to block -+ // remove() does not begin to poll from the wait queue until the section is null'd, -+ // and so provided we check the nullability before parking there is no ordering of these operations -+ // such that remove() finishes polling from the wait queue while section is not null -+ earliest.add(currThread); -+ -+ // wait until completed -+ while (earliest.getSectionVolatile() != null) { -+ LockSupport.park(); -+ } -+ } -+ -+ public UpdateQueueNode peek() { -+ for (UpdateQueueNode head = this.getHeadOpaque(), curr = head;;) { -+ final UpdateQueueNode next = curr.getNextVolatile(); -+ final Section element = curr.getSectionVolatile(); /* Likely in sync */ -+ -+ if (element != null) { -+ if (this.getHeadOpaque() == head && curr != head) { -+ this.setHeadOpaque(curr); -+ } -+ return curr; -+ } -+ -+ if (next == null) { -+ if (this.getHeadOpaque() == head && curr != head) { -+ this.setHeadOpaque(curr); -+ } -+ return null; -+ } -+ curr = next; -+ } -+ } -+ -+ public void remove(final UpdateQueueNode node) { -+ // mark as removed -+ node.setSectionVolatile(null); -+ -+ // use peek to advance head -+ this.peek(); -+ -+ // unpark any waiters / block the wait queue -+ Thread unpark; -+ while ((unpark = node.poll()) != null) { -+ LockSupport.unpark(unpark); -+ } -+ } -+ -+ public void append(final UpdateQueueNode node) { -+ int failures = 0; -+ -+ for (UpdateQueueNode currTail = this.getTailOpaque(), curr = currTail;;) { -+ /* It has been experimentally shown that placing the read before the backoff results in significantly greater performance */ -+ /* It is likely due to a cache miss caused by another write to the next field */ -+ final UpdateQueueNode next = curr.getNextVolatile(); -+ -+ for (int i = 0; i < failures; ++i) { -+ ConcurrentUtil.backoff(); -+ } -+ -+ if (next == null) { -+ node.order = curr.order + 1L; -+ final UpdateQueueNode compared = curr.compareExchangeNextVolatile(null, node); -+ -+ if (compared == null) { -+ /* Added */ -+ /* Avoid CASing on tail more than we need to */ -+ /* CAS to avoid setting an out-of-date tail */ -+ if (this.getTailOpaque() == currTail) { -+ this.setTailOpaque(node); -+ } -+ return; -+ } -+ -+ ++failures; -+ curr = compared; -+ continue; -+ } -+ -+ if (curr == currTail) { -+ /* Tail is likely not up-to-date */ -+ curr = next; -+ } else { -+ /* Try to update to tail */ -+ if (currTail == (currTail = this.getTailOpaque())) { -+ curr = next; -+ } else { -+ curr = currTail; -+ } -+ } -+ } -+ } -+ -+ // each node also represents a set of waiters, represented by the MTQ -+ // if the queue is add-blocked, then the update is complete -+ private static final class UpdateQueueNode extends MultiThreadedQueue { -+ private long order; -+ private Section section; -+ private volatile UpdateQueueNode next; -+ -+ protected static final VarHandle SECTION_HANDLE = ConcurrentUtil.getVarHandle(UpdateQueueNode.class, "section", Section.class); -+ protected static final VarHandle NEXT_HANDLE = ConcurrentUtil.getVarHandle(UpdateQueueNode.class, "next", UpdateQueueNode.class); -+ -+ public UpdateQueueNode(final Section section, final UpdateQueueNode next) { -+ SECTION_HANDLE.set(this, section); -+ NEXT_HANDLE.set(this, next); -+ } -+ -+ /* section */ -+ -+ protected final Section getSectionPlain() { -+ return (Section)SECTION_HANDLE.get(this); -+ } -+ -+ protected final Section getSectionVolatile() { -+ return (Section)SECTION_HANDLE.getVolatile(this); -+ } -+ -+ protected final void setSectionPlain(final Section update) { -+ SECTION_HANDLE.set(this, update); -+ } -+ -+ protected final void setSectionOpaque(final Section update) { -+ SECTION_HANDLE.setOpaque(this, update); -+ } -+ -+ protected final void setSectionVolatile(final Section update) { -+ SECTION_HANDLE.setVolatile(this, update); -+ } -+ -+ protected final Section getAndSetSectionVolatile(final Section update) { -+ return (Section)SECTION_HANDLE.getAndSet(this, update); -+ } -+ -+ protected final Section compareExchangeSectionVolatile(final Section expect, final Section update) { -+ return (Section)SECTION_HANDLE.compareAndExchange(this, expect, update); -+ } -+ -+ /* next */ -+ -+ protected final UpdateQueueNode getNextPlain() { -+ return (UpdateQueueNode)NEXT_HANDLE.get(this); -+ } -+ -+ protected final UpdateQueueNode getNextOpaque() { -+ return (UpdateQueueNode)NEXT_HANDLE.getOpaque(this); -+ } -+ -+ protected final UpdateQueueNode getNextAcquire() { -+ return (UpdateQueueNode)NEXT_HANDLE.getAcquire(this); -+ } -+ -+ protected final UpdateQueueNode getNextVolatile() { -+ return (UpdateQueueNode)NEXT_HANDLE.getVolatile(this); -+ } -+ -+ protected final void setNextPlain(final UpdateQueueNode next) { -+ NEXT_HANDLE.set(this, next); -+ } -+ -+ protected final void setNextVolatile(final UpdateQueueNode next) { -+ NEXT_HANDLE.setVolatile(this, next); -+ } -+ -+ protected final UpdateQueueNode compareExchangeNextVolatile(final UpdateQueueNode expect, final UpdateQueueNode set) { -+ return (UpdateQueueNode)NEXT_HANDLE.compareAndExchange(this, expect, set); -+ } -+ } -+ } -+ -+ private static final class Section { -+ -+ // upper 8 bits: sources, lower 8 bits: level -+ // if we REALLY wanted to get crazy, we could make the increase propagator use MethodHandles#byteArrayViewVarHandle -+ // to read and write the lower 8 bits of this array directly rather than reading, updating the bits, then writing back. -+ private final short[] levels = new short[SECTION_SIZE * SECTION_SIZE]; -+ // set of local positions that represent sources -+ private final ShortOpenHashSet sources = new ShortOpenHashSet(); -+ // map of local index to new source level -+ // the source level _cannot_ be updated in the backing storage immediately since the update -+ private static final byte NO_QUEUED_UPDATE = (byte)-1; -+ private final Short2ByteLinkedOpenHashMap queuedSources = new Short2ByteLinkedOpenHashMap(); -+ { -+ this.queuedSources.defaultReturnValue(NO_QUEUED_UPDATE); -+ } -+ private int oneRadNeighboursWithSources = 0; -+ -+ public final int sectionX; -+ public final int sectionZ; -+ -+ public Section(final int sectionX, final int sectionZ) { -+ this.sectionX = sectionX; -+ this.sectionZ = sectionZ; -+ } -+ -+ public boolean isZero() { -+ for (final short val : this.levels) { -+ if (val != 0) { -+ return false; -+ } -+ } -+ return true; -+ } -+ -+ @Override -+ public String toString() { -+ final StringBuilder ret = new StringBuilder(); -+ -+ for (int x = 0; x < SECTION_SIZE; ++x) { -+ ret.append("levels x=").append(x).append("\n"); -+ for (int z = 0; z < SECTION_SIZE; ++z) { -+ final short v = this.levels[x | (z << SECTION_SHIFT)]; -+ ret.append(v & 0xFF).append("."); -+ } -+ ret.append("\n"); -+ ret.append("sources x=").append(x).append("\n"); -+ for (int z = 0; z < SECTION_SIZE; ++z) { -+ final short v = this.levels[x | (z << SECTION_SHIFT)]; -+ ret.append((v >>> 8) & 0xFF).append("."); -+ } -+ ret.append("\n\n"); -+ } -+ -+ return ret.toString(); -+ } -+ } -+ -+ -+ private static final class Propagator { -+ -+ private static final ArrayDeque CACHED_PROPAGATORS = new ArrayDeque<>(); -+ private static final int MAX_PROPAGATORS = Runtime.getRuntime().availableProcessors() * 2; -+ -+ private static Propagator acquirePropagator() { -+ synchronized (CACHED_PROPAGATORS) { -+ final Propagator ret = CACHED_PROPAGATORS.pollFirst(); -+ if (ret != null) { -+ return ret; -+ } -+ } -+ return new Propagator(); -+ } -+ -+ private static void returnPropagator(final Propagator propagator) { -+ synchronized (CACHED_PROPAGATORS) { -+ if (CACHED_PROPAGATORS.size() < MAX_PROPAGATORS) { -+ CACHED_PROPAGATORS.add(propagator); -+ } -+ } -+ } -+ -+ private static final int SECTION_RADIUS = 2; -+ private static final int SECTION_CACHE_WIDTH = 2 * SECTION_RADIUS + 1; -+ // minimum number of bits to represent [0, SECTION_SIZE * SECTION_CACHE_WIDTH) -+ private static final int COORDINATE_BITS = 9; -+ private static final int COORDINATE_SIZE = 1 << COORDINATE_BITS; -+ static { -+ if ((SECTION_SIZE * SECTION_CACHE_WIDTH) > (1 << COORDINATE_BITS)) { -+ throw new IllegalStateException("Adjust COORDINATE_BITS"); -+ } -+ } -+ // index = x + (z * SECTION_CACHE_WIDTH) -+ // (this requires x >= 0 and z >= 0) -+ private final Section[] sections = new Section[SECTION_CACHE_WIDTH * SECTION_CACHE_WIDTH]; -+ -+ private int encodeOffsetX; -+ private int encodeOffsetZ; -+ -+ private int coordinateOffset; -+ -+ private int encodeSectionOffsetX; -+ private int encodeSectionOffsetZ; -+ -+ private int sectionIndexOffset; -+ -+ public final boolean hasUpdates() { -+ return this.decreaseQueueInitialLength != 0 || this.increaseQueueInitialLength != 0; -+ } -+ -+ protected final void setupEncodeOffset(final int centerSectionX, final int centerSectionZ) { -+ final int maxCoordinate = (SECTION_RADIUS * SECTION_SIZE - 1); -+ // must have that encoded >= 0 -+ // coordinates can range from [-maxCoordinate + centerSection*SECTION_SIZE, maxCoordinate + centerSection*SECTION_SIZE] -+ // we want a range of [0, maxCoordinate*2] -+ // so, 0 = -maxCoordinate + centerSection*SECTION_SIZE + offset -+ this.encodeOffsetX = maxCoordinate - (centerSectionX << SECTION_SHIFT); -+ this.encodeOffsetZ = maxCoordinate - (centerSectionZ << SECTION_SHIFT); -+ -+ // encoded coordinates range from [0, SECTION_SIZE * SECTION_CACHE_WIDTH) -+ // coordinate index = (x + encodeOffsetX) + ((z + encodeOffsetZ) << COORDINATE_BITS) -+ this.coordinateOffset = this.encodeOffsetX + (this.encodeOffsetZ << COORDINATE_BITS); -+ -+ // need encoded values to be >= 0 -+ // so, 0 = (-SECTION_RADIUS + centerSectionX) + encodeOffset -+ this.encodeSectionOffsetX = SECTION_RADIUS - centerSectionX; -+ this.encodeSectionOffsetZ = SECTION_RADIUS - centerSectionZ; -+ -+ // section index = (secX + encodeSectionOffsetX) + ((secZ + encodeSectionOffsetZ) * SECTION_CACHE_WIDTH) -+ this.sectionIndexOffset = this.encodeSectionOffsetX + (this.encodeSectionOffsetZ * SECTION_CACHE_WIDTH); -+ } -+ -+ // must hold ticket lock for (centerSectionX,centerSectionZ) in radius rad -+ // must call setupEncodeOffset -+ protected final void setupCaches(final ThreadedTicketLevelPropagator propagator, -+ final int centerSectionX, final int centerSectionZ, -+ final int rad) { -+ for (int dz = -rad; dz <= rad; ++dz) { -+ for (int dx = -rad; dx <= rad; ++dx) { -+ final int sectionX = centerSectionX + dx; -+ final int sectionZ = centerSectionZ + dz; -+ final Coordinate coordinate = new Coordinate(sectionX, sectionZ); -+ final Section section = propagator.sections.get(coordinate); -+ -+ if (section == null) { -+ throw new IllegalStateException("Section at " + coordinate + " should not be null"); -+ } -+ -+ this.setSectionInCache(sectionX, sectionZ, section); -+ } -+ } -+ } -+ -+ protected final void setSectionInCache(final int sectionX, final int sectionZ, final Section section) { -+ this.sections[sectionX + SECTION_CACHE_WIDTH*sectionZ + this.sectionIndexOffset] = section; -+ } -+ -+ protected final Section getSection(final int sectionX, final int sectionZ) { -+ return this.sections[sectionX + SECTION_CACHE_WIDTH*sectionZ + this.sectionIndexOffset]; -+ } -+ -+ protected final int getLevel(final int posX, final int posZ) { -+ final Section section = this.sections[(posX >> SECTION_SHIFT) + SECTION_CACHE_WIDTH*(posZ >> SECTION_SHIFT) + this.sectionIndexOffset]; -+ if (section != null) { -+ return (int)section.levels[(posX & (SECTION_SIZE - 1)) | ((posZ & (SECTION_SIZE - 1)) << SECTION_SHIFT)] & 0xFF; -+ } -+ -+ return 0; -+ } -+ -+ protected final void setLevel(final int posX, final int posZ, final int to) { -+ final Section section = this.sections[(posX >> SECTION_SHIFT) + SECTION_CACHE_WIDTH*(posZ >> SECTION_SHIFT) + this.sectionIndexOffset]; -+ if (section != null) { -+ final int index = (posX & (SECTION_SIZE - 1)) | ((posZ & (SECTION_SIZE - 1)) << SECTION_SHIFT); -+ final short level = section.levels[index]; -+ section.levels[index] = (short)((level & ~0xFF) | (to & 0xFF)); -+ this.updatedPositions.put(Coordinate.key(posX, posZ), (byte)to); -+ } -+ } -+ -+ protected final void destroyCaches() { -+ Arrays.fill(this.sections, null); -+ } -+ -+ // contains: -+ // lower (COORDINATE_BITS(9) + COORDINATE_BITS(9) = 18) bits encoded position: (x | (z << COORDINATE_BITS)) -+ // next LEVEL_BITS (6) bits: propagated level [0, 63] -+ // propagation directions bitset (16 bits): -+ protected static final long ALL_DIRECTIONS_BITSET = ( -+ // z = -1 -+ (1L << ((1 - 1) | ((1 - 1) << 2))) | -+ (1L << ((1 + 0) | ((1 - 1) << 2))) | -+ (1L << ((1 + 1) | ((1 - 1) << 2))) | -+ -+ // z = 0 -+ (1L << ((1 - 1) | ((1 + 0) << 2))) | -+ //(1L << ((1 + 0) | ((1 + 0) << 2))) | // exclude (0,0) -+ (1L << ((1 + 1) | ((1 + 0) << 2))) | -+ -+ // z = 1 -+ (1L << ((1 - 1) | ((1 + 1) << 2))) | -+ (1L << ((1 + 0) | ((1 + 1) << 2))) | -+ (1L << ((1 + 1) | ((1 + 1) << 2))) -+ ); -+ -+ private void ex(int bitset) { -+ for (int i = 0, len = Integer.bitCount(bitset); i < len; ++i) { -+ final int set = Integer.numberOfTrailingZeros(bitset); -+ final int tailingBit = (-bitset) & bitset; -+ // XOR to remove the trailing bit -+ bitset ^= tailingBit; -+ -+ // the encoded value set is (x_val) | (z_val << 2), totaling 4 bits -+ // thus, the bitset is 16 bits wide where each one represents a direction to propagate and the -+ // index of the set bit is the encoded value -+ // the encoded coordinate has 3 valid states: -+ // 0b00 (0) -> -1 -+ // 0b01 (1) -> 0 -+ // 0b10 (2) -> 1 -+ // the decode operation then is val - 1, and the encode operation is val + 1 -+ final int xOff = (set & 3) - 1; -+ final int zOff = ((set >>> 2) & 3) - 1; -+ System.out.println("Encoded: (" + xOff + "," + zOff + ")"); -+ } -+ } -+ -+ private void ch(long bs, int shift) { -+ int bitset = (int)(bs >>> shift); -+ for (int i = 0, len = Integer.bitCount(bitset); i < len; ++i) { -+ final int set = Integer.numberOfTrailingZeros(bitset); -+ final int tailingBit = (-bitset) & bitset; -+ // XOR to remove the trailing bit -+ bitset ^= tailingBit; -+ -+ // the encoded value set is (x_val) | (z_val << 2), totaling 4 bits -+ // thus, the bitset is 16 bits wide where each one represents a direction to propagate and the -+ // index of the set bit is the encoded value -+ // the encoded coordinate has 3 valid states: -+ // 0b00 (0) -> -1 -+ // 0b01 (1) -> 0 -+ // 0b10 (2) -> 1 -+ // the decode operation then is val - 1, and the encode operation is val + 1 -+ final int xOff = (set & 3) - 1; -+ final int zOff = ((set >>> 2) & 3) - 1; -+ if (Math.abs(xOff) > 1 || Math.abs(zOff) > 1 || (xOff | zOff) == 0) { -+ throw new IllegalStateException(); -+ } -+ } -+ } -+ -+ // whether the increase propagator needs to write the propagated level to the position, used to avoid cascading -+ // updates for sources -+ protected static final long FLAG_WRITE_LEVEL = Long.MIN_VALUE >>> 1; -+ // whether the propagation needs to check if its current level is equal to the expected level -+ // used only in increase propagation -+ protected static final long FLAG_RECHECK_LEVEL = Long.MIN_VALUE >>> 0; -+ -+ protected long[] increaseQueue = new long[SECTION_SIZE * SECTION_SIZE * 2]; -+ protected int increaseQueueInitialLength; -+ protected long[] decreaseQueue = new long[SECTION_SIZE * SECTION_SIZE * 2]; -+ protected int decreaseQueueInitialLength; -+ -+ protected final Long2ByteLinkedOpenHashMap updatedPositions = new Long2ByteLinkedOpenHashMap(); -+ -+ protected final long[] resizeIncreaseQueue() { -+ return this.increaseQueue = Arrays.copyOf(this.increaseQueue, this.increaseQueue.length * 2); -+ } -+ -+ protected final long[] resizeDecreaseQueue() { -+ return this.decreaseQueue = Arrays.copyOf(this.decreaseQueue, this.decreaseQueue.length * 2); -+ } -+ -+ protected final void appendToIncreaseQueue(final long value) { -+ final int idx = this.increaseQueueInitialLength++; -+ long[] queue = this.increaseQueue; -+ if (idx >= queue.length) { -+ queue = this.resizeIncreaseQueue(); -+ queue[idx] = value; -+ return; -+ } else { -+ queue[idx] = value; -+ return; -+ } -+ } -+ -+ protected final void appendToDecreaseQueue(final long value) { -+ final int idx = this.decreaseQueueInitialLength++; -+ long[] queue = this.decreaseQueue; -+ if (idx >= queue.length) { -+ queue = this.resizeDecreaseQueue(); -+ queue[idx] = value; -+ return; -+ } else { -+ queue[idx] = value; -+ return; -+ } -+ } -+ -+ protected final void performIncrease() { -+ long[] queue = this.increaseQueue; -+ int queueReadIndex = 0; -+ int queueLength = this.increaseQueueInitialLength; -+ this.increaseQueueInitialLength = 0; -+ final int decodeOffsetX = -this.encodeOffsetX; -+ final int decodeOffsetZ = -this.encodeOffsetZ; -+ final int encodeOffset = this.coordinateOffset; -+ final int sectionOffset = this.sectionIndexOffset; -+ -+ final Long2ByteLinkedOpenHashMap updatedPositions = this.updatedPositions; -+ -+ while (queueReadIndex < queueLength) { -+ final long queueValue = queue[queueReadIndex++]; -+ -+ final int posX = ((int)queueValue & (COORDINATE_SIZE - 1)) + decodeOffsetX; -+ final int posZ = (((int)queueValue >>> COORDINATE_BITS) & (COORDINATE_SIZE - 1)) + decodeOffsetZ; -+ final int propagatedLevel = ((int)queueValue >>> (COORDINATE_BITS + COORDINATE_BITS)) & (LEVEL_COUNT - 1); -+ // note: the above code requires coordinate bits * 2 < 32 -+ // bitset is 16 bits -+ int propagateDirectionBitset = (int)(queueValue >>> (COORDINATE_BITS + COORDINATE_BITS + LEVEL_BITS)) & ((1 << 16) - 1); -+ -+ if ((queueValue & FLAG_RECHECK_LEVEL) != 0L) { -+ if (this.getLevel(posX, posZ) != propagatedLevel) { -+ // not at the level we expect, so something changed. -+ continue; -+ } -+ } else if ((queueValue & FLAG_WRITE_LEVEL) != 0L) { -+ // these are used to restore sources after a propagation decrease -+ this.setLevel(posX, posZ, propagatedLevel); -+ } -+ -+ // this bitset represents the values that we have not propagated to -+ // this bitset lets us determine what directions the neighbours we set should propagate to, in most cases -+ // significantly reducing the total number of ops -+ // since we propagate in a 1 radius, we need a 2 radius bitset to hold all possible values we would possibly need -+ // but if we use only 5x5 bits, then we need to use div/mod to retrieve coordinates from the bitset, so instead -+ // we use an 8x8 bitset and luckily that can be fit into only one long value (64 bits) -+ // to make things easy, we use positions [0, 4] in the bitset, with current position being 2 -+ // index = x | (z << 3) -+ -+ // to start, we eliminate everything 1 radius from the current position as the previous propagator -+ // must guarantee that either we propagate everything in 1 radius or we partially propagate for 1 radius -+ // but the rest not propagated are already handled -+ long currentPropagation = ~( -+ // z = -1 -+ (1L << ((2 - 1) | ((2 - 1) << 3))) | -+ (1L << ((2 + 0) | ((2 - 1) << 3))) | -+ (1L << ((2 + 1) | ((2 - 1) << 3))) | -+ -+ // z = 0 -+ (1L << ((2 - 1) | ((2 + 0) << 3))) | -+ (1L << ((2 + 0) | ((2 + 0) << 3))) | -+ (1L << ((2 + 1) | ((2 + 0) << 3))) | -+ -+ // z = 1 -+ (1L << ((2 - 1) | ((2 + 1) << 3))) | -+ (1L << ((2 + 0) | ((2 + 1) << 3))) | -+ (1L << ((2 + 1) | ((2 + 1) << 3))) -+ ); -+ -+ final int toPropagate = propagatedLevel - 1; -+ -+ // we could use while (propagateDirectionBitset != 0), but it's not a predictable branch. By counting -+ // the bits, the cpu loop predictor should perfectly predict the loop. -+ for (int l = 0, len = Integer.bitCount(propagateDirectionBitset); l < len; ++l) { -+ final int set = Integer.numberOfTrailingZeros(propagateDirectionBitset); -+ final int tailingBit = (-propagateDirectionBitset) & propagateDirectionBitset; -+ propagateDirectionBitset ^= tailingBit; -+ -+ // pDecode is from [0, 2], and 1 must be subtracted to fully decode the offset -+ // it has been split to save some cycles via parallelism -+ final int pDecodeX = (set & 3); -+ final int pDecodeZ = ((set >>> 2) & 3); -+ -+ // re-ordered -1 on the position decode into pos - 1 to occur in parallel with determining pDecodeX -+ final int offX = (posX - 1) + pDecodeX; -+ final int offZ = (posZ - 1) + pDecodeZ; -+ -+ final int sectionIndex = (offX >> SECTION_SHIFT) + ((offZ >> SECTION_SHIFT) * SECTION_CACHE_WIDTH) + sectionOffset; -+ final int localIndex = (offX & (SECTION_SIZE - 1)) | ((offZ & (SECTION_SIZE - 1)) << SECTION_SHIFT); -+ -+ // to retrieve a set of bits from a long value: (n_bitmask << (nstartidx)) & bitset -+ // bitset idx = x | (z << 3) -+ -+ // read three bits, so we need 7L -+ // note that generally: off - pos = (pos - 1) + pDecode - pos = pDecode - 1 -+ // nstartidx1 = x rel -1 for z rel -1 -+ // = (offX - posX - 1 + 2) | ((offZ - posZ - 1 + 2) << 3) -+ // = (pDecodeX - 1 - 1 + 2) | ((pDecodeZ - 1 - 1 + 2) << 3) -+ // = pDecodeX | (pDecodeZ << 3) = start -+ final int start = pDecodeX | (pDecodeZ << 3); -+ final long bitsetLine1 = currentPropagation & (7L << (start)); -+ -+ // nstartidx2 = x rel -1 for z rel 0 = line after line1, so we can just add 8 (row length of bitset) -+ final long bitsetLine2 = currentPropagation & (7L << (start + 8)); -+ -+ // nstartidx2 = x rel -1 for z rel 0 = line after line2, so we can just add 8 (row length of bitset) -+ final long bitsetLine3 = currentPropagation & (7L << (start + (8 + 8))); -+ -+ // remove ("take") lines from bitset -+ currentPropagation ^= (bitsetLine1 | bitsetLine2 | bitsetLine3); -+ -+ // now try to propagate -+ final Section section = this.sections[sectionIndex]; -+ -+ // lower 8 bits are current level, next upper 7 bits are source level, next 1 bit is updated source flag -+ final short currentStoredLevel = section.levels[localIndex]; -+ final int currentLevel = currentStoredLevel & 0xFF; -+ -+ if (currentLevel >= toPropagate) { -+ continue; // already at the level we want -+ } -+ -+ // update level -+ section.levels[localIndex] = (short)((currentStoredLevel & ~0xFF) | (toPropagate & 0xFF)); -+ updatedPositions.putAndMoveToLast(Coordinate.key(offX, offZ), (byte)toPropagate); -+ -+ // queue next -+ if (toPropagate > 1) { -+ // now combine into one bitset to pass to child -+ // the child bitset is 4x4, so we just shift each line by 4 -+ // add the propagation bitset offset to each line to make it easy to OR it into the propagation queue value -+ final long childPropagation = -+ ((bitsetLine1 >>> (start)) << (COORDINATE_BITS + COORDINATE_BITS + LEVEL_BITS)) | // z = -1 -+ ((bitsetLine2 >>> (start + 8)) << (4 + COORDINATE_BITS + COORDINATE_BITS + LEVEL_BITS)) | // z = 0 -+ ((bitsetLine3 >>> (start + (8 + 8))) << (4 + 4 + COORDINATE_BITS + COORDINATE_BITS + LEVEL_BITS)); // z = 1 -+ -+ // don't queue update if toPropagate cannot propagate anything to neighbours -+ // (for increase, propagating 0 to neighbours is useless) -+ if (queueLength >= queue.length) { -+ queue = this.resizeIncreaseQueue(); -+ } -+ queue[queueLength++] = -+ ((long)(offX + (offZ << COORDINATE_BITS) + encodeOffset) & ((1L << (COORDINATE_BITS + COORDINATE_BITS)) - 1)) | -+ ((toPropagate & (LEVEL_COUNT - 1L)) << (COORDINATE_BITS + COORDINATE_BITS)) | -+ childPropagation; //(ALL_DIRECTIONS_BITSET << (COORDINATE_BITS + COORDINATE_BITS + LEVEL_BITS)); -+ continue; -+ } -+ continue; -+ } -+ } -+ } -+ -+ protected final void performDecrease() { -+ long[] queue = this.decreaseQueue; -+ long[] increaseQueue = this.increaseQueue; -+ int queueReadIndex = 0; -+ int queueLength = this.decreaseQueueInitialLength; -+ this.decreaseQueueInitialLength = 0; -+ int increaseQueueLength = this.increaseQueueInitialLength; -+ final int decodeOffsetX = -this.encodeOffsetX; -+ final int decodeOffsetZ = -this.encodeOffsetZ; -+ final int encodeOffset = this.coordinateOffset; -+ final int sectionOffset = this.sectionIndexOffset; -+ -+ final Long2ByteLinkedOpenHashMap updatedPositions = this.updatedPositions; -+ -+ while (queueReadIndex < queueLength) { -+ final long queueValue = queue[queueReadIndex++]; -+ -+ final int posX = ((int)queueValue & (COORDINATE_SIZE - 1)) + decodeOffsetX; -+ final int posZ = (((int)queueValue >>> COORDINATE_BITS) & (COORDINATE_SIZE - 1)) + decodeOffsetZ; -+ final int propagatedLevel = ((int)queueValue >>> (COORDINATE_BITS + COORDINATE_BITS)) & (LEVEL_COUNT - 1); -+ // note: the above code requires coordinate bits * 2 < 32 -+ // bitset is 16 bits -+ int propagateDirectionBitset = (int)(queueValue >>> (COORDINATE_BITS + COORDINATE_BITS + LEVEL_BITS)) & ((1 << 16) - 1); -+ -+ // this bitset represents the values that we have not propagated to -+ // this bitset lets us determine what directions the neighbours we set should propagate to, in most cases -+ // significantly reducing the total number of ops -+ // since we propagate in a 1 radius, we need a 2 radius bitset to hold all possible values we would possibly need -+ // but if we use only 5x5 bits, then we need to use div/mod to retrieve coordinates from the bitset, so instead -+ // we use an 8x8 bitset and luckily that can be fit into only one long value (64 bits) -+ // to make things easy, we use positions [0, 4] in the bitset, with current position being 2 -+ // index = x | (z << 3) -+ -+ // to start, we eliminate everything 1 radius from the current position as the previous propagator -+ // must guarantee that either we propagate everything in 1 radius or we partially propagate for 1 radius -+ // but the rest not propagated are already handled -+ long currentPropagation = ~( -+ // z = -1 -+ (1L << ((2 - 1) | ((2 - 1) << 3))) | -+ (1L << ((2 + 0) | ((2 - 1) << 3))) | -+ (1L << ((2 + 1) | ((2 - 1) << 3))) | -+ -+ // z = 0 -+ (1L << ((2 - 1) | ((2 + 0) << 3))) | -+ (1L << ((2 + 0) | ((2 + 0) << 3))) | -+ (1L << ((2 + 1) | ((2 + 0) << 3))) | -+ -+ // z = 1 -+ (1L << ((2 - 1) | ((2 + 1) << 3))) | -+ (1L << ((2 + 0) | ((2 + 1) << 3))) | -+ (1L << ((2 + 1) | ((2 + 1) << 3))) -+ ); -+ -+ final int toPropagate = propagatedLevel - 1; -+ -+ // we could use while (propagateDirectionBitset != 0), but it's not a predictable branch. By counting -+ // the bits, the cpu loop predictor should perfectly predict the loop. -+ for (int l = 0, len = Integer.bitCount(propagateDirectionBitset); l < len; ++l) { -+ final int set = Integer.numberOfTrailingZeros(propagateDirectionBitset); -+ final int tailingBit = (-propagateDirectionBitset) & propagateDirectionBitset; -+ propagateDirectionBitset ^= tailingBit; -+ -+ -+ // pDecode is from [0, 2], and 1 must be subtracted to fully decode the offset -+ // it has been split to save some cycles via parallelism -+ final int pDecodeX = (set & 3); -+ final int pDecodeZ = ((set >>> 2) & 3); -+ -+ // re-ordered -1 on the position decode into pos - 1 to occur in parallel with determining pDecodeX -+ final int offX = (posX - 1) + pDecodeX; -+ final int offZ = (posZ - 1) + pDecodeZ; -+ -+ final int sectionIndex = (offX >> SECTION_SHIFT) + ((offZ >> SECTION_SHIFT) * SECTION_CACHE_WIDTH) + sectionOffset; -+ final int localIndex = (offX & (SECTION_SIZE - 1)) | ((offZ & (SECTION_SIZE - 1)) << SECTION_SHIFT); -+ -+ // to retrieve a set of bits from a long value: (n_bitmask << (nstartidx)) & bitset -+ // bitset idx = x | (z << 3) -+ -+ // read three bits, so we need 7L -+ // note that generally: off - pos = (pos - 1) + pDecode - pos = pDecode - 1 -+ // nstartidx1 = x rel -1 for z rel -1 -+ // = (offX - posX - 1 + 2) | ((offZ - posZ - 1 + 2) << 3) -+ // = (pDecodeX - 1 - 1 + 2) | ((pDecodeZ - 1 - 1 + 2) << 3) -+ // = pDecodeX | (pDecodeZ << 3) = start -+ final int start = pDecodeX | (pDecodeZ << 3); -+ final long bitsetLine1 = currentPropagation & (7L << (start)); -+ -+ // nstartidx2 = x rel -1 for z rel 0 = line after line1, so we can just add 8 (row length of bitset) -+ final long bitsetLine2 = currentPropagation & (7L << (start + 8)); -+ -+ // nstartidx2 = x rel -1 for z rel 0 = line after line2, so we can just add 8 (row length of bitset) -+ final long bitsetLine3 = currentPropagation & (7L << (start + (8 + 8))); -+ -+ // now try to propagate -+ final Section section = this.sections[sectionIndex]; -+ -+ // lower 8 bits are current level, next upper 7 bits are source level, next 1 bit is updated source flag -+ final short currentStoredLevel = section.levels[localIndex]; -+ final int currentLevel = currentStoredLevel & 0xFF; -+ final int sourceLevel = (currentStoredLevel >>> 8) & 0xFF; -+ -+ if (currentLevel == 0) { -+ continue; // already at the level we want -+ } -+ -+ if (currentLevel > toPropagate) { -+ // it looks like another source propagated here, so re-propagate it -+ if (increaseQueueLength >= increaseQueue.length) { -+ increaseQueue = this.resizeIncreaseQueue(); -+ } -+ increaseQueue[increaseQueueLength++] = -+ ((long)(offX + (offZ << COORDINATE_BITS) + encodeOffset) & ((1L << (COORDINATE_BITS + COORDINATE_BITS)) - 1)) | -+ ((currentLevel & (LEVEL_COUNT - 1L)) << (COORDINATE_BITS + COORDINATE_BITS)) | -+ (FLAG_RECHECK_LEVEL | (ALL_DIRECTIONS_BITSET << (COORDINATE_BITS + COORDINATE_BITS + LEVEL_BITS))); -+ continue; -+ } -+ -+ // remove ("take") lines from bitset -+ // can't do this during decrease, TODO WHY? -+ //currentPropagation ^= (bitsetLine1 | bitsetLine2 | bitsetLine3); -+ -+ // update level -+ section.levels[localIndex] = (short)((currentStoredLevel & ~0xFF)); -+ updatedPositions.putAndMoveToLast(Coordinate.key(offX, offZ), (byte)0); -+ -+ if (sourceLevel != 0) { -+ // re-propagate source -+ // note: do not set recheck level, or else the propagation will fail -+ if (increaseQueueLength >= increaseQueue.length) { -+ increaseQueue = this.resizeIncreaseQueue(); -+ } -+ increaseQueue[increaseQueueLength++] = -+ ((long)(offX + (offZ << COORDINATE_BITS) + encodeOffset) & ((1L << (COORDINATE_BITS + COORDINATE_BITS)) - 1)) | -+ ((sourceLevel & (LEVEL_COUNT - 1L)) << (COORDINATE_BITS + COORDINATE_BITS)) | -+ (FLAG_WRITE_LEVEL | (ALL_DIRECTIONS_BITSET << (COORDINATE_BITS + COORDINATE_BITS + LEVEL_BITS))); -+ } -+ -+ // queue next -+ // note: targetLevel > 0 here, since toPropagate >= currentLevel and currentLevel > 0 -+ // now combine into one bitset to pass to child -+ // the child bitset is 4x4, so we just shift each line by 4 -+ // add the propagation bitset offset to each line to make it easy to OR it into the propagation queue value -+ final long childPropagation = -+ ((bitsetLine1 >>> (start)) << (COORDINATE_BITS + COORDINATE_BITS + LEVEL_BITS)) | // z = -1 -+ ((bitsetLine2 >>> (start + 8)) << (4 + COORDINATE_BITS + COORDINATE_BITS + LEVEL_BITS)) | // z = 0 -+ ((bitsetLine3 >>> (start + (8 + 8))) << (4 + 4 + COORDINATE_BITS + COORDINATE_BITS + LEVEL_BITS)); // z = 1 -+ -+ // don't queue update if toPropagate cannot propagate anything to neighbours -+ // (for increase, propagating 0 to neighbours is useless) -+ if (queueLength >= queue.length) { -+ queue = this.resizeDecreaseQueue(); -+ } -+ queue[queueLength++] = -+ ((long)(offX + (offZ << COORDINATE_BITS) + encodeOffset) & ((1L << (COORDINATE_BITS + COORDINATE_BITS)) - 1)) | -+ ((toPropagate & (LEVEL_COUNT - 1L)) << (COORDINATE_BITS + COORDINATE_BITS)) | -+ (ALL_DIRECTIONS_BITSET << (COORDINATE_BITS + COORDINATE_BITS + LEVEL_BITS)); //childPropagation; -+ continue; -+ } -+ } -+ -+ // propagate sources we clobbered -+ this.increaseQueueInitialLength = increaseQueueLength; -+ this.performIncrease(); -+ } -+ } -+ -+ private static final class Coordinate implements Comparable { -+ -+ public final long key; -+ -+ public Coordinate(final long key) { -+ this.key = key; -+ } -+ -+ public Coordinate(final int x, final int z) { -+ this.key = key(x, z); -+ } -+ -+ public static long key(final int x, final int z) { -+ return ((long)z << 32) | (x & 0xFFFFFFFFL); -+ } -+ -+ public static int x(final long key) { -+ return (int)key; -+ } -+ -+ public static int z(final long key) { -+ return (int)(key >>> 32); -+ } -+ -+ @Override -+ public int hashCode() { -+ return (int)HashCommon.mix(this.key); -+ } -+ -+ @Override -+ public boolean equals(final Object obj) { -+ if (this == obj) { -+ return true; -+ } -+ -+ if (!(obj instanceof Coordinate other)) { -+ return false; -+ } -+ -+ return this.key == other.key; -+ } -+ -+ // This class is intended for HashMap/ConcurrentHashMap usage, which do treeify bin nodes if the chain -+ // is too large. So we should implement compareTo to help. -+ @Override -+ public int compareTo(final Coordinate other) { -+ return Long.compare(this.key, other.key); -+ } -+ -+ @Override -+ public String toString() { -+ return "[" + x(this.key) + "," + z(this.key) + "]"; -+ } -+ } -+ -+ /* -+ private static final java.util.Random random = new java.util.Random(4L); -+ private static final List> walkers = -+ new java.util.ArrayList<>(); -+ static final int PLAYERS = 0; -+ static final int RAD_BLOCKS = 10000; -+ static final int RAD = RAD_BLOCKS >> 4; -+ static final int RAD_BIG_BLOCKS = 100_000; -+ static final int RAD_BIG = RAD_BIG_BLOCKS >> 4; -+ static final int VD = 4; -+ static final int BIG_PLAYERS = 50; -+ static final double WALK_CHANCE = 0.10; -+ static final double TP_CHANCE = 0.01; -+ static final int TP_BACK_PLAYERS = 200; -+ static final double TP_BACK_CHANCE = 0.25; -+ static final double TP_STEAL_CHANCE = 0.25; -+ private static final List> tpBack = -+ new java.util.ArrayList<>(); -+ -+ public static void main(final String[] args) { -+ final ReentrantAreaLock ticketLock = new ReentrantAreaLock(SECTION_SHIFT); -+ final ReentrantAreaLock schedulingLock = new ReentrantAreaLock(SECTION_SHIFT); -+ final Long2ByteLinkedOpenHashMap levelMap = new Long2ByteLinkedOpenHashMap(); -+ final Long2ByteLinkedOpenHashMap refMap = new Long2ByteLinkedOpenHashMap(); -+ final io.papermc.paper.util.misc.Delayed8WayDistancePropagator2D ref = new io.papermc.paper.util.misc.Delayed8WayDistancePropagator2D((final long coordinate, final byte oldLevel, final byte newLevel) -> { -+ if (newLevel == 0) { -+ refMap.remove(coordinate); -+ } else { -+ refMap.put(coordinate, newLevel); -+ } -+ }); -+ final ThreadedTicketLevelPropagator propagator = new ThreadedTicketLevelPropagator() { -+ @Override -+ protected void processLevelUpdates(Long2ByteLinkedOpenHashMap updates) { -+ for (final long key : updates.keySet()) { -+ final byte val = updates.get(key); -+ if (val == 0) { -+ levelMap.remove(key); -+ } else { -+ levelMap.put(key, val); -+ } -+ } -+ } -+ -+ @Override -+ protected void processSchedulingUpdates(Long2ByteLinkedOpenHashMap updates, List scheduledTasks, List changedFullStatus) {} -+ }; -+ -+ for (;;) { -+ if (walkers.isEmpty() && tpBack.isEmpty()) { -+ for (int i = 0; i < PLAYERS; ++i) { -+ int rad = i < BIG_PLAYERS ? RAD_BIG : RAD; -+ int posX = random.nextInt(-rad, rad + 1); -+ int posZ = random.nextInt(-rad, rad + 1); -+ -+ io.papermc.paper.chunk.system.RegionizedPlayerChunkLoader.SingleUserAreaMap map = new io.papermc.paper.chunk.system.RegionizedPlayerChunkLoader.SingleUserAreaMap<>(null) { -+ @Override -+ protected void addCallback(Void parameter, int chunkX, int chunkZ) { -+ int src = 45 - 31 + 1; -+ ref.setSource(chunkX, chunkZ, src); -+ propagator.setSource(chunkX, chunkZ, src); -+ } -+ -+ @Override -+ protected void removeCallback(Void parameter, int chunkX, int chunkZ) { -+ ref.removeSource(chunkX, chunkZ); -+ propagator.removeSource(chunkX, chunkZ); -+ } -+ }; -+ -+ map.add(posX, posZ, VD); -+ -+ walkers.add(map); -+ } -+ for (int i = 0; i < TP_BACK_PLAYERS; ++i) { -+ int rad = RAD_BIG; -+ int posX = random.nextInt(-rad, rad + 1); -+ int posZ = random.nextInt(-rad, rad + 1); -+ -+ io.papermc.paper.chunk.system.RegionizedPlayerChunkLoader.SingleUserAreaMap map = new io.papermc.paper.chunk.system.RegionizedPlayerChunkLoader.SingleUserAreaMap<>(null) { -+ @Override -+ protected void addCallback(Void parameter, int chunkX, int chunkZ) { -+ int src = 45 - 31 + 1; -+ ref.setSource(chunkX, chunkZ, src); -+ propagator.setSource(chunkX, chunkZ, src); -+ } -+ -+ @Override -+ protected void removeCallback(Void parameter, int chunkX, int chunkZ) { -+ ref.removeSource(chunkX, chunkZ); -+ propagator.removeSource(chunkX, chunkZ); -+ } -+ }; -+ -+ map.add(posX, posZ, random.nextInt(1, 63)); -+ -+ tpBack.add(map); -+ } -+ } else { -+ for (int i = 0; i < PLAYERS; ++i) { -+ if (random.nextDouble() > WALK_CHANCE) { -+ continue; -+ } -+ -+ io.papermc.paper.chunk.system.RegionizedPlayerChunkLoader.SingleUserAreaMap map = walkers.get(i); -+ -+ int updateX = random.nextInt(-1, 2); -+ int updateZ = random.nextInt(-1, 2); -+ -+ map.update(map.lastChunkX + updateX, map.lastChunkZ + updateZ, VD); -+ } -+ -+ for (int i = 0; i < PLAYERS; ++i) { -+ if (random.nextDouble() > TP_CHANCE) { -+ continue; -+ } -+ -+ int rad = i < BIG_PLAYERS ? RAD_BIG : RAD; -+ int posX = random.nextInt(-rad, rad + 1); -+ int posZ = random.nextInt(-rad, rad + 1); -+ -+ io.papermc.paper.chunk.system.RegionizedPlayerChunkLoader.SingleUserAreaMap map = walkers.get(i); -+ -+ map.update(posX, posZ, VD); -+ } -+ -+ for (int i = 0; i < TP_BACK_PLAYERS; ++i) { -+ if (random.nextDouble() > TP_BACK_CHANCE) { -+ continue; -+ } -+ -+ io.papermc.paper.chunk.system.RegionizedPlayerChunkLoader.SingleUserAreaMap map = tpBack.get(i); -+ -+ map.update(-map.lastChunkX, -map.lastChunkZ, random.nextInt(1, 63)); -+ -+ if (random.nextDouble() > TP_STEAL_CHANCE) { -+ propagator.performUpdate( -+ map.lastChunkX >> SECTION_SHIFT, map.lastChunkZ >> SECTION_SHIFT, schedulingLock, null, null -+ ); -+ propagator.performUpdate( -+ (-map.lastChunkX >> SECTION_SHIFT), (-map.lastChunkZ >> SECTION_SHIFT), schedulingLock, null, null -+ ); -+ } -+ } -+ } -+ -+ ref.propagateUpdates(); -+ propagator.performUpdates(ticketLock, schedulingLock, null, null); -+ -+ if (!refMap.equals(levelMap)) { -+ throw new IllegalStateException("Error!"); -+ } -+ } -+ } -+ */ -+} -diff --git a/src/main/java/io/papermc/paper/chunk/system/scheduling/queue/RadiusAwarePrioritisedExecutor.java b/src/main/java/io/papermc/paper/chunk/system/scheduling/queue/RadiusAwarePrioritisedExecutor.java -new file mode 100644 -index 0000000000000000000000000000000000000000..f7b0e2564ac4bd2db1d2b2bdc230c9f52f8a21b7 ---- /dev/null -+++ b/src/main/java/io/papermc/paper/chunk/system/scheduling/queue/RadiusAwarePrioritisedExecutor.java -@@ -0,0 +1,667 @@ -+package io.papermc.paper.chunk.system.scheduling.queue; -+ -+import ca.spottedleaf.concurrentutil.executor.standard.PrioritisedExecutor; -+import io.papermc.paper.util.CoordinateUtils; -+import it.unimi.dsi.fastutil.longs.Long2ReferenceOpenHashMap; -+import it.unimi.dsi.fastutil.objects.ReferenceOpenHashSet; -+import java.util.ArrayList; -+import java.util.Comparator; -+import java.util.List; -+import java.util.PriorityQueue; -+ -+public class RadiusAwarePrioritisedExecutor { -+ -+ private static final Comparator DEPENDENCY_NODE_COMPARATOR = (final DependencyNode t1, final DependencyNode t2) -> { -+ return Long.compare(t1.id, t2.id); -+ }; -+ -+ private final DependencyTree[] queues = new DependencyTree[PrioritisedExecutor.Priority.TOTAL_SCHEDULABLE_PRIORITIES]; -+ private static final int NO_TASKS_QUEUED = -1; -+ private int selectedQueue = NO_TASKS_QUEUED; -+ private boolean canQueueTasks = true; -+ -+ public RadiusAwarePrioritisedExecutor(final PrioritisedExecutor executor, final int maxToSchedule) { -+ for (int i = 0; i < this.queues.length; ++i) { -+ this.queues[i] = new DependencyTree(this, executor, maxToSchedule, i); -+ } -+ } -+ -+ private boolean canQueueTasks() { -+ return this.canQueueTasks; -+ } -+ -+ private List treeFinished() { -+ this.canQueueTasks = true; -+ for (int priority = 0; priority < this.queues.length; ++priority) { -+ final DependencyTree queue = this.queues[priority]; -+ if (queue.hasWaitingTasks()) { -+ final List ret = queue.tryPushTasks(); -+ -+ if (ret == null || ret.isEmpty()) { -+ // this happens when the tasks in the wait queue were purged -+ // in this case, the queue was actually empty, we just had to purge it -+ // if we set the selected queue without scheduling any tasks, the queue will never be unselected -+ // as that requires a scheduled task completing... -+ continue; -+ } -+ -+ this.selectedQueue = priority; -+ return ret; -+ } -+ } -+ -+ this.selectedQueue = NO_TASKS_QUEUED; -+ -+ return null; -+ } -+ -+ private List queue(final Task task, final PrioritisedExecutor.Priority priority) { -+ final int priorityId = priority.priority; -+ final DependencyTree queue = this.queues[priorityId]; -+ -+ final DependencyNode node = new DependencyNode(task, queue); -+ -+ if (task.dependencyNode != null) { -+ throw new IllegalStateException(); -+ } -+ task.dependencyNode = node; -+ -+ queue.pushNode(node); -+ -+ if (this.selectedQueue == NO_TASKS_QUEUED) { -+ this.canQueueTasks = true; -+ this.selectedQueue = priorityId; -+ return queue.tryPushTasks(); -+ } -+ -+ if (!this.canQueueTasks) { -+ return null; -+ } -+ -+ if (PrioritisedExecutor.Priority.isHigherPriority(priorityId, this.selectedQueue)) { -+ // prevent the lower priority tree from queueing more tasks -+ this.canQueueTasks = false; -+ return null; -+ } -+ -+ // priorityId != selectedQueue: lower priority, don't care - treeFinished will pick it up -+ return priorityId == this.selectedQueue ? queue.tryPushTasks() : null; -+ } -+ -+ public PrioritisedExecutor.PrioritisedTask createTask(final int chunkX, final int chunkZ, final int radius, -+ final Runnable run, final PrioritisedExecutor.Priority priority) { -+ if (radius < 0) { -+ throw new IllegalArgumentException("Radius must be > 0: " + radius); -+ } -+ return new Task(this, chunkX, chunkZ, radius, run, priority); -+ } -+ -+ public PrioritisedExecutor.PrioritisedTask createTask(final int chunkX, final int chunkZ, final int radius, -+ final Runnable run) { -+ return this.createTask(chunkX, chunkZ, radius, run, PrioritisedExecutor.Priority.NORMAL); -+ } -+ -+ public PrioritisedExecutor.PrioritisedTask queueTask(final int chunkX, final int chunkZ, final int radius, -+ final Runnable run, final PrioritisedExecutor.Priority priority) { -+ final PrioritisedExecutor.PrioritisedTask ret = this.createTask(chunkX, chunkZ, radius, run, priority); -+ -+ ret.queue(); -+ -+ return ret; -+ } -+ -+ public PrioritisedExecutor.PrioritisedTask queueTask(final int chunkX, final int chunkZ, final int radius, -+ final Runnable run) { -+ final PrioritisedExecutor.PrioritisedTask ret = this.createTask(chunkX, chunkZ, radius, run); -+ -+ ret.queue(); -+ -+ return ret; -+ } -+ -+ public PrioritisedExecutor.PrioritisedTask createInfiniteRadiusTask(final Runnable run, final PrioritisedExecutor.Priority priority) { -+ return new Task(this, 0, 0, -1, run, priority); -+ } -+ -+ public PrioritisedExecutor.PrioritisedTask createInfiniteRadiusTask(final Runnable run) { -+ return this.createInfiniteRadiusTask(run, PrioritisedExecutor.Priority.NORMAL); -+ } -+ -+ public PrioritisedExecutor.PrioritisedTask queueInfiniteRadiusTask(final Runnable run, final PrioritisedExecutor.Priority priority) { -+ final PrioritisedExecutor.PrioritisedTask ret = this.createInfiniteRadiusTask(run, priority); -+ -+ ret.queue(); -+ -+ return ret; -+ } -+ -+ public PrioritisedExecutor.PrioritisedTask queueInfiniteRadiusTask(final Runnable run) { -+ final PrioritisedExecutor.PrioritisedTask ret = this.createInfiniteRadiusTask(run, PrioritisedExecutor.Priority.NORMAL); -+ -+ ret.queue(); -+ -+ return ret; -+ } -+ -+ // all accesses must be synchronised by the radius aware object -+ private static final class DependencyTree { -+ -+ private final RadiusAwarePrioritisedExecutor scheduler; -+ private final PrioritisedExecutor executor; -+ private final int maxToSchedule; -+ private final int treeIndex; -+ -+ private int currentlyExecuting; -+ private long idGenerator; -+ -+ private final PriorityQueue awaiting = new PriorityQueue<>(DEPENDENCY_NODE_COMPARATOR); -+ -+ private final PriorityQueue infiniteRadius = new PriorityQueue<>(DEPENDENCY_NODE_COMPARATOR); -+ private boolean isInfiniteRadiusScheduled; -+ -+ private final Long2ReferenceOpenHashMap nodeByPosition = new Long2ReferenceOpenHashMap<>(); -+ -+ public DependencyTree(final RadiusAwarePrioritisedExecutor scheduler, final PrioritisedExecutor executor, -+ final int maxToSchedule, final int treeIndex) { -+ this.scheduler = scheduler; -+ this.executor = executor; -+ this.maxToSchedule = maxToSchedule; -+ this.treeIndex = treeIndex; -+ } -+ -+ public boolean hasWaitingTasks() { -+ return !this.awaiting.isEmpty() || !this.infiniteRadius.isEmpty(); -+ } -+ -+ private long nextId() { -+ return this.idGenerator++; -+ } -+ -+ private boolean isExecutingAnyTasks() { -+ return this.currentlyExecuting != 0; -+ } -+ -+ private void pushNode(final DependencyNode node) { -+ if (!node.task.isFiniteRadius()) { -+ this.infiniteRadius.add(node); -+ return; -+ } -+ -+ // set up dependency for node -+ final Task task = node.task; -+ -+ final int centerX = task.chunkX; -+ final int centerZ = task.chunkZ; -+ final int radius = task.radius; -+ -+ final int minX = centerX - radius; -+ final int maxX = centerX + radius; -+ -+ final int minZ = centerZ - radius; -+ final int maxZ = centerZ + radius; -+ -+ ReferenceOpenHashSet parents = null; -+ for (int currZ = minZ; currZ <= maxZ; ++currZ) { -+ for (int currX = minX; currX <= maxX; ++currX) { -+ final DependencyNode dependency = this.nodeByPosition.put(CoordinateUtils.getChunkKey(currX, currZ), node); -+ if (dependency != null) { -+ if (parents == null) { -+ parents = new ReferenceOpenHashSet<>(); -+ } -+ if (parents.add(dependency)) { -+ // added a dependency, so we need to add as a child to the dependency -+ if (dependency.children == null) { -+ dependency.children = new ArrayList<>(); -+ } -+ dependency.children.add(node); -+ } -+ } -+ } -+ } -+ -+ if (parents == null) { -+ // no dependencies, add straight to awaiting -+ this.awaiting.add(node); -+ } else { -+ node.parents = parents.size(); -+ // we will be added to awaiting once we have no parents -+ } -+ } -+ -+ // called only when a node is returned after being executed -+ private List returnNode(final DependencyNode node) { -+ final Task task = node.task; -+ -+ // now that the task is completed, we can push its children to the awaiting queue -+ this.pushChildren(node); -+ -+ if (task.isFiniteRadius()) { -+ // remove from dependency map -+ this.removeNodeFromMap(node); -+ } else { -+ // mark as no longer executing infinite radius -+ if (!this.isInfiniteRadiusScheduled) { -+ throw new IllegalStateException(); -+ } -+ this.isInfiniteRadiusScheduled = false; -+ } -+ -+ // decrement executing count, we are done executing this task -+ --this.currentlyExecuting; -+ -+ if (this.currentlyExecuting == 0) { -+ return this.scheduler.treeFinished(); -+ } -+ -+ return this.scheduler.canQueueTasks() ? this.tryPushTasks() : null; -+ } -+ -+ private List tryPushTasks() { -+ // tasks are not queued, but only created here - we do hold the lock for the map -+ List ret = null; -+ PrioritisedExecutor.PrioritisedTask pushedTask; -+ while ((pushedTask = this.tryPushTask()) != null) { -+ if (ret == null) { -+ ret = new ArrayList<>(); -+ } -+ ret.add(pushedTask); -+ } -+ -+ return ret; -+ } -+ -+ private void removeNodeFromMap(final DependencyNode node) { -+ final Task task = node.task; -+ -+ final int centerX = task.chunkX; -+ final int centerZ = task.chunkZ; -+ final int radius = task.radius; -+ -+ final int minX = centerX - radius; -+ final int maxX = centerX + radius; -+ -+ final int minZ = centerZ - radius; -+ final int maxZ = centerZ + radius; -+ -+ for (int currZ = minZ; currZ <= maxZ; ++currZ) { -+ for (int currX = minX; currX <= maxX; ++currX) { -+ this.nodeByPosition.remove(CoordinateUtils.getChunkKey(currX, currZ), node); -+ } -+ } -+ } -+ -+ private void pushChildren(final DependencyNode node) { -+ // add all the children that we can into awaiting -+ final List children = node.children; -+ if (children != null) { -+ for (int i = 0, len = children.size(); i < len; ++i) { -+ final DependencyNode child = children.get(i); -+ int newParents = --child.parents; -+ if (newParents == 0) { -+ // no more dependents, we can push to awaiting -+ // even if the child is purged, we need to push it so that its children will be pushed -+ this.awaiting.add(child); -+ } else if (newParents < 0) { -+ throw new IllegalStateException(); -+ } -+ } -+ } -+ } -+ -+ private DependencyNode pollAwaiting() { -+ final DependencyNode ret = this.awaiting.poll(); -+ if (ret == null) { -+ return ret; -+ } -+ -+ if (ret.parents != 0) { -+ throw new IllegalStateException(); -+ } -+ -+ if (ret.purged) { -+ // need to manually remove from state here -+ this.pushChildren(ret); -+ this.removeNodeFromMap(ret); -+ } // else: delay children push until the task has finished -+ -+ return ret; -+ } -+ -+ private DependencyNode pollInfinite() { -+ return this.infiniteRadius.poll(); -+ } -+ -+ public PrioritisedExecutor.PrioritisedTask tryPushTask() { -+ if (this.currentlyExecuting >= this.maxToSchedule || this.isInfiniteRadiusScheduled) { -+ return null; -+ } -+ -+ DependencyNode firstInfinite; -+ while ((firstInfinite = this.infiniteRadius.peek()) != null && firstInfinite.purged) { -+ this.pollInfinite(); -+ } -+ -+ DependencyNode firstAwaiting; -+ while ((firstAwaiting = this.awaiting.peek()) != null && firstAwaiting.purged) { -+ this.pollAwaiting(); -+ } -+ -+ if (firstInfinite == null && firstAwaiting == null) { -+ return null; -+ } -+ -+ // firstAwaiting compared to firstInfinite -+ final int compare; -+ -+ if (firstAwaiting == null) { -+ // we choose first infinite, or infinite < awaiting -+ compare = 1; -+ } else if (firstInfinite == null) { -+ // we choose first awaiting, or awaiting < infinite -+ compare = -1; -+ } else { -+ compare = DEPENDENCY_NODE_COMPARATOR.compare(firstAwaiting, firstInfinite); -+ } -+ -+ if (compare >= 0) { -+ if (this.currentlyExecuting != 0) { -+ // don't queue infinite task while other tasks are executing in parallel -+ return null; -+ } -+ ++this.currentlyExecuting; -+ this.pollInfinite(); -+ this.isInfiniteRadiusScheduled = true; -+ return firstInfinite.task.pushTask(this.executor); -+ } else { -+ ++this.currentlyExecuting; -+ this.pollAwaiting(); -+ return firstAwaiting.task.pushTask(this.executor); -+ } -+ } -+ } -+ -+ private static final class DependencyNode { -+ -+ private final Task task; -+ private final DependencyTree tree; -+ -+ // dependency tree fields -+ // (must hold lock on the scheduler to use) -+ // null is the same as empty, we just use it so that we don't allocate the set unless we need to -+ private List children; -+ // 0 indicates that this task is considered "awaiting" -+ private int parents; -+ // false -> scheduled and not cancelled -+ // true -> scheduled but cancelled -+ private boolean purged; -+ private final long id; -+ -+ public DependencyNode(final Task task, final DependencyTree tree) { -+ this.task = task; -+ this.id = tree.nextId(); -+ this.tree = tree; -+ } -+ } -+ -+ private static final class Task implements PrioritisedExecutor.PrioritisedTask, Runnable { -+ -+ // task specific fields -+ private final RadiusAwarePrioritisedExecutor scheduler; -+ private final int chunkX; -+ private final int chunkZ; -+ private final int radius; -+ private Runnable run; -+ private PrioritisedExecutor.Priority priority; -+ -+ private DependencyNode dependencyNode; -+ private PrioritisedExecutor.PrioritisedTask queuedTask; -+ -+ private Task(final RadiusAwarePrioritisedExecutor scheduler, final int chunkX, final int chunkZ, final int radius, -+ final Runnable run, final PrioritisedExecutor.Priority priority) { -+ this.scheduler = scheduler; -+ this.chunkX = chunkX; -+ this.chunkZ = chunkZ; -+ this.radius = radius; -+ this.run = run; -+ this.priority = priority; -+ } -+ -+ private boolean isFiniteRadius() { -+ return this.radius >= 0; -+ } -+ -+ private PrioritisedExecutor.PrioritisedTask pushTask(final PrioritisedExecutor executor) { -+ return this.queuedTask = executor.createTask(this, this.priority); -+ } -+ -+ private void executeTask() { -+ final Runnable run = this.run; -+ this.run = null; -+ run.run(); -+ } -+ -+ private static void scheduleTasks(final List toSchedule) { -+ if (toSchedule != null) { -+ for (int i = 0, len = toSchedule.size(); i < len; ++i) { -+ toSchedule.get(i).queue(); -+ } -+ } -+ } -+ -+ private void returnNode() { -+ final List toSchedule; -+ synchronized (this.scheduler) { -+ final DependencyNode node = this.dependencyNode; -+ this.dependencyNode = null; -+ toSchedule = node.tree.returnNode(node); -+ } -+ -+ scheduleTasks(toSchedule); -+ } -+ -+ @Override -+ public void run() { -+ final Runnable run = this.run; -+ this.run = null; -+ try { -+ run.run(); -+ } finally { -+ this.returnNode(); -+ } -+ } -+ -+ @Override -+ public boolean queue() { -+ final List toSchedule; -+ synchronized (this.scheduler) { -+ if (this.queuedTask != null || this.dependencyNode != null || this.priority == PrioritisedExecutor.Priority.COMPLETING) { -+ return false; -+ } -+ -+ toSchedule = this.scheduler.queue(this, this.priority); -+ } -+ -+ scheduleTasks(toSchedule); -+ return true; -+ } -+ -+ @Override -+ public boolean cancel() { -+ final PrioritisedExecutor.PrioritisedTask task; -+ synchronized (this.scheduler) { -+ if ((task = this.queuedTask) == null) { -+ if (this.priority == PrioritisedExecutor.Priority.COMPLETING) { -+ return false; -+ } -+ -+ this.priority = PrioritisedExecutor.Priority.COMPLETING; -+ if (this.dependencyNode != null) { -+ this.dependencyNode.purged = true; -+ this.dependencyNode = null; -+ } -+ -+ return true; -+ } -+ } -+ -+ if (task.cancel()) { -+ // must manually return the node -+ this.run = null; -+ this.returnNode(); -+ return true; -+ } -+ return false; -+ } -+ -+ @Override -+ public boolean execute() { -+ final PrioritisedExecutor.PrioritisedTask task; -+ synchronized (this.scheduler) { -+ if ((task = this.queuedTask) == null) { -+ if (this.priority == PrioritisedExecutor.Priority.COMPLETING) { -+ return false; -+ } -+ -+ this.priority = PrioritisedExecutor.Priority.COMPLETING; -+ if (this.dependencyNode != null) { -+ this.dependencyNode.purged = true; -+ this.dependencyNode = null; -+ } -+ // fall through to execution logic -+ } -+ } -+ -+ if (task != null) { -+ // will run the return node logic automatically -+ return task.execute(); -+ } else { -+ // don't run node removal/insertion logic, we aren't actually removed from the dependency tree -+ this.executeTask(); -+ return true; -+ } -+ } -+ -+ @Override -+ public PrioritisedExecutor.Priority getPriority() { -+ final PrioritisedExecutor.PrioritisedTask task; -+ synchronized (this.scheduler) { -+ if ((task = this.queuedTask) == null) { -+ return this.priority; -+ } -+ } -+ -+ return task.getPriority(); -+ } -+ -+ @Override -+ public boolean setPriority(final PrioritisedExecutor.Priority priority) { -+ if (!PrioritisedExecutor.Priority.isValidPriority(priority)) { -+ throw new IllegalArgumentException("Invalid priority " + priority); -+ } -+ -+ final PrioritisedExecutor.PrioritisedTask task; -+ List toSchedule = null; -+ synchronized (this.scheduler) { -+ if ((task = this.queuedTask) == null) { -+ if (this.priority == PrioritisedExecutor.Priority.COMPLETING) { -+ return false; -+ } -+ -+ if (this.priority == priority) { -+ return true; -+ } -+ -+ this.priority = priority; -+ if (this.dependencyNode != null) { -+ // need to re-insert node -+ this.dependencyNode.purged = true; -+ this.dependencyNode = null; -+ toSchedule = this.scheduler.queue(this, priority); -+ } -+ } -+ } -+ -+ if (task != null) { -+ return task.setPriority(priority); -+ } -+ -+ scheduleTasks(toSchedule); -+ -+ return true; -+ } -+ -+ @Override -+ public boolean raisePriority(final PrioritisedExecutor.Priority priority) { -+ if (!PrioritisedExecutor.Priority.isValidPriority(priority)) { -+ throw new IllegalArgumentException("Invalid priority " + priority); -+ } -+ -+ final PrioritisedExecutor.PrioritisedTask task; -+ List toSchedule = null; -+ synchronized (this.scheduler) { -+ if ((task = this.queuedTask) == null) { -+ if (this.priority == PrioritisedExecutor.Priority.COMPLETING) { -+ return false; -+ } -+ -+ if (this.priority.isHigherOrEqualPriority(priority)) { -+ return true; -+ } -+ -+ this.priority = priority; -+ if (this.dependencyNode != null) { -+ // need to re-insert node -+ this.dependencyNode.purged = true; -+ this.dependencyNode = null; -+ toSchedule = this.scheduler.queue(this, priority); -+ } -+ } -+ } -+ -+ if (task != null) { -+ return task.raisePriority(priority); -+ } -+ -+ scheduleTasks(toSchedule); -+ -+ return true; -+ } -+ -+ @Override -+ public boolean lowerPriority(final PrioritisedExecutor.Priority priority) { -+ if (!PrioritisedExecutor.Priority.isValidPriority(priority)) { -+ throw new IllegalArgumentException("Invalid priority " + priority); -+ } -+ -+ final PrioritisedExecutor.PrioritisedTask task; -+ List toSchedule = null; -+ synchronized (this.scheduler) { -+ if ((task = this.queuedTask) == null) { -+ if (this.priority == PrioritisedExecutor.Priority.COMPLETING) { -+ return false; -+ } -+ -+ if (this.priority.isLowerOrEqualPriority(priority)) { -+ return true; -+ } -+ -+ this.priority = priority; -+ if (this.dependencyNode != null) { -+ // need to re-insert node -+ this.dependencyNode.purged = true; -+ this.dependencyNode = null; -+ toSchedule = this.scheduler.queue(this, priority); -+ } -+ } -+ } -+ -+ if (task != null) { -+ return task.lowerPriority(priority); -+ } -+ -+ scheduleTasks(toSchedule); -+ -+ return true; -+ } -+ } -+} -diff --git a/src/main/java/io/papermc/paper/command/PaperCommand.java b/src/main/java/io/papermc/paper/command/PaperCommand.java -index a3f43dccb796f30f6e9389e1ae182f06e9024e96..92bf78112c1cc75173e4e735bdeec9695fe10df6 100644 ---- a/src/main/java/io/papermc/paper/command/PaperCommand.java -+++ b/src/main/java/io/papermc/paper/command/PaperCommand.java -@@ -43,6 +43,7 @@ public final class PaperCommand extends Command { - commands.put(Set.of("mobcaps", "playermobcaps"), new MobcapsCommand()); - commands.put(Set.of("dumplisteners"), new DumpListenersCommand()); - commands.put(Set.of("fixlight"), new FixLightCommand()); -+ commands.put(Set.of("debug", "chunkinfo", "holderinfo"), new ChunkDebugCommand()); - - return commands.entrySet().stream() - .flatMap(entry -> entry.getKey().stream().map(s -> Map.entry(s, entry.getValue()))) -diff --git a/src/main/java/io/papermc/paper/command/subcommands/ChunkDebugCommand.java b/src/main/java/io/papermc/paper/command/subcommands/ChunkDebugCommand.java -new file mode 100644 -index 0000000000000000000000000000000000000000..61fa1ab0f6550fec2b9d035ea45c72627eb990d4 ---- /dev/null -+++ b/src/main/java/io/papermc/paper/command/subcommands/ChunkDebugCommand.java -@@ -0,0 +1,265 @@ -+package io.papermc.paper.command.subcommands; -+ -+import io.papermc.paper.command.CommandUtil; -+import io.papermc.paper.command.PaperSubcommand; -+import java.io.File; -+import java.time.LocalDateTime; -+import java.time.format.DateTimeFormatter; -+import java.util.ArrayList; -+import java.util.Collections; -+import java.util.List; -+import java.util.Locale; -+import io.papermc.paper.util.MCUtil; -+import net.minecraft.server.MinecraftServer; -+import net.minecraft.server.level.ChunkHolder; -+import net.minecraft.server.level.FullChunkStatus; -+import net.minecraft.server.level.ServerLevel; -+import net.minecraft.world.level.chunk.ChunkAccess; -+import net.minecraft.world.level.chunk.ImposterProtoChunk; -+import net.minecraft.world.level.chunk.LevelChunk; -+import net.minecraft.world.level.chunk.ProtoChunk; -+import org.bukkit.Bukkit; -+import org.bukkit.command.CommandSender; -+import org.bukkit.craftbukkit.CraftWorld; -+import org.checkerframework.checker.nullness.qual.NonNull; -+import org.checkerframework.checker.nullness.qual.Nullable; -+import org.checkerframework.framework.qual.DefaultQualifier; -+ -+import static net.kyori.adventure.text.Component.text; -+import static net.kyori.adventure.text.format.NamedTextColor.BLUE; -+import static net.kyori.adventure.text.format.NamedTextColor.DARK_AQUA; -+import static net.kyori.adventure.text.format.NamedTextColor.GREEN; -+import static net.kyori.adventure.text.format.NamedTextColor.RED; -+ -+@DefaultQualifier(NonNull.class) -+public final class ChunkDebugCommand implements PaperSubcommand { -+ @Override -+ public boolean execute(final CommandSender sender, final String subCommand, final String[] args) { -+ switch (subCommand) { -+ case "debug" -> this.doDebug(sender, args); -+ case "chunkinfo" -> this.doChunkInfo(sender, args); -+ case "holderinfo" -> this.doHolderInfo(sender, args); -+ } -+ return true; -+ } -+ -+ @Override -+ public List tabComplete(final CommandSender sender, final String subCommand, final String[] args) { -+ switch (subCommand) { -+ case "debug" -> { -+ if (args.length == 1) { -+ return CommandUtil.getListMatchingLast(sender, args, "help", "chunks"); -+ } -+ } -+ case "holderinfo" -> { -+ List worldNames = new ArrayList<>(); -+ worldNames.add("*"); -+ for (org.bukkit.World world : Bukkit.getWorlds()) { -+ worldNames.add(world.getName()); -+ } -+ if (args.length == 1) { -+ return CommandUtil.getListMatchingLast(sender, args, worldNames); -+ } -+ } -+ case "chunkinfo" -> { -+ List worldNames = new ArrayList<>(); -+ worldNames.add("*"); -+ for (org.bukkit.World world : Bukkit.getWorlds()) { -+ worldNames.add(world.getName()); -+ } -+ if (args.length == 1) { -+ return CommandUtil.getListMatchingLast(sender, args, worldNames); -+ } -+ } -+ } -+ return Collections.emptyList(); -+ } -+ -+ private void doChunkInfo(final CommandSender sender, final String[] args) { -+ List worlds; -+ if (args.length < 1 || args[0].equals("*")) { -+ worlds = Bukkit.getWorlds(); -+ } else { -+ worlds = new ArrayList<>(args.length); -+ for (final String arg : args) { -+ org.bukkit.@Nullable World world = Bukkit.getWorld(arg); -+ if (world == null) { -+ sender.sendMessage(text("World '" + arg + "' is invalid", RED)); -+ return; -+ } -+ worlds.add(world); -+ } -+ } -+ -+ int accumulatedTotal = 0; -+ int accumulatedInactive = 0; -+ int accumulatedBorder = 0; -+ int accumulatedTicking = 0; -+ int accumulatedEntityTicking = 0; -+ -+ for (final org.bukkit.World bukkitWorld : worlds) { -+ final ServerLevel world = ((CraftWorld) bukkitWorld).getHandle(); -+ -+ int total = 0; -+ int inactive = 0; -+ int full = 0; -+ int blockTicking = 0; -+ int entityTicking = 0; -+ -+ for (final ChunkHolder chunk : io.papermc.paper.chunk.system.ChunkSystem.getVisibleChunkHolders(world)) { -+ if (chunk.getFullChunkNowUnchecked() == null) { -+ continue; -+ } -+ -+ ++total; -+ -+ FullChunkStatus state = chunk.getFullStatus(); -+ -+ switch (state) { -+ case INACCESSIBLE -> ++inactive; -+ case FULL -> ++full; -+ case BLOCK_TICKING -> ++blockTicking; -+ case ENTITY_TICKING -> ++entityTicking; -+ } -+ } -+ -+ accumulatedTotal += total; -+ accumulatedInactive += inactive; -+ accumulatedBorder += full; -+ accumulatedTicking += blockTicking; -+ accumulatedEntityTicking += entityTicking; -+ -+ sender.sendMessage(text().append(text("Chunks in ", BLUE), text(bukkitWorld.getName(), GREEN), text(":"))); -+ sender.sendMessage(text().color(DARK_AQUA).append( -+ text("Total: ", BLUE), text(total), -+ text(" Inactive: ", BLUE), text(inactive), -+ text(" Full: ", BLUE), text(full), -+ text(" Block Ticking: ", BLUE), text(blockTicking), -+ text(" Entity Ticking: ", BLUE), text(entityTicking) -+ )); -+ } -+ if (worlds.size() > 1) { -+ sender.sendMessage(text().append(text("Chunks in ", BLUE), text("all listed worlds", GREEN), text(":", DARK_AQUA))); -+ sender.sendMessage(text().color(DARK_AQUA).append( -+ text("Total: ", BLUE), text(accumulatedTotal), -+ text(" Inactive: ", BLUE), text(accumulatedInactive), -+ text(" Full: ", BLUE), text(accumulatedBorder), -+ text(" Block Ticking: ", BLUE), text(accumulatedTicking), -+ text(" Entity Ticking: ", BLUE), text(accumulatedEntityTicking) -+ )); -+ } -+ } -+ -+ private void doHolderInfo(final CommandSender sender, final String[] args) { -+ List worlds; -+ if (args.length < 1 || args[0].equals("*")) { -+ worlds = Bukkit.getWorlds(); -+ } else { -+ worlds = new ArrayList<>(args.length); -+ for (final String arg : args) { -+ org.bukkit.@Nullable World world = Bukkit.getWorld(arg); -+ if (world == null) { -+ sender.sendMessage(text("World '" + arg + "' is invalid", RED)); -+ return; -+ } -+ worlds.add(world); -+ } -+ } -+ -+ int accumulatedTotal = 0; -+ int accumulatedCanUnload = 0; -+ int accumulatedNull = 0; -+ int accumulatedReadOnly = 0; -+ int accumulatedProtoChunk = 0; -+ int accumulatedFullChunk = 0; -+ -+ for (final org.bukkit.World bukkitWorld : worlds) { -+ final ServerLevel world = ((CraftWorld) bukkitWorld).getHandle(); -+ -+ int total = 0; -+ int canUnload = 0; -+ int nullChunks = 0; -+ int readOnly = 0; -+ int protoChunk = 0; -+ int fullChunk = 0; -+ -+ for (final ChunkHolder chunk : world.chunkTaskScheduler.chunkHolderManager.getOldChunkHolders()) { // Paper - change updating chunks map -+ final ChunkAccess lastChunk = chunk.getAvailableChunkNow(); -+ -+ ++total; -+ -+ if (lastChunk == null) { -+ ++nullChunks; -+ } else if (lastChunk instanceof ImposterProtoChunk) { -+ ++readOnly; -+ } else if (lastChunk instanceof ProtoChunk) { -+ ++protoChunk; -+ } else if (lastChunk instanceof LevelChunk) { -+ ++fullChunk; -+ } -+ -+ if (chunk.newChunkHolder.isSafeToUnload() == null) { -+ ++canUnload; -+ } -+ } -+ -+ accumulatedTotal += total; -+ accumulatedCanUnload += canUnload; -+ accumulatedNull += nullChunks; -+ accumulatedReadOnly += readOnly; -+ accumulatedProtoChunk += protoChunk; -+ accumulatedFullChunk += fullChunk; -+ -+ sender.sendMessage(text().append(text("Chunks in ", BLUE), text(bukkitWorld.getName(), GREEN), text(":"))); -+ sender.sendMessage(text().color(DARK_AQUA).append( -+ text("Total: ", BLUE), text(total), -+ text(" Unloadable: ", BLUE), text(canUnload), -+ text(" Null: ", BLUE), text(nullChunks), -+ text(" ReadOnly: ", BLUE), text(readOnly), -+ text(" Proto: ", BLUE), text(protoChunk), -+ text(" Full: ", BLUE), text(fullChunk) -+ )); -+ } -+ if (worlds.size() > 1) { -+ sender.sendMessage(text().append(text("Chunks in ", BLUE), text("all listed worlds", GREEN), text(":", DARK_AQUA))); -+ sender.sendMessage(text().color(DARK_AQUA).append( -+ text("Total: ", BLUE), text(accumulatedTotal), -+ text(" Unloadable: ", BLUE), text(accumulatedCanUnload), -+ text(" Null: ", BLUE), text(accumulatedNull), -+ text(" ReadOnly: ", BLUE), text(accumulatedReadOnly), -+ text(" Proto: ", BLUE), text(accumulatedProtoChunk), -+ text(" Full: ", BLUE), text(accumulatedFullChunk) -+ )); -+ } -+ } -+ -+ private void doDebug(final CommandSender sender, final String[] args) { -+ if (args.length < 1) { -+ sender.sendMessage(text("Use /paper debug [chunks] help for more information on a specific command", RED)); -+ return; -+ } -+ -+ final String debugType = args[0].toLowerCase(Locale.ROOT); -+ switch (debugType) { -+ case "chunks" -> { -+ if (args.length >= 2 && args[1].toLowerCase(Locale.ROOT).equals("help")) { -+ sender.sendMessage(text("Use /paper debug chunks [world] to dump loaded chunk information to a file", RED)); -+ break; -+ } -+ File file = new File(new File(new File("."), "debug"), -+ "chunks-" + DateTimeFormatter.ofPattern("yyyy-MM-dd_HH.mm.ss").format(LocalDateTime.now()) + ".txt"); -+ sender.sendMessage(text("Writing chunk information dump to " + file, GREEN)); -+ try { -+ MCUtil.dumpChunks(file, false); -+ sender.sendMessage(text("Successfully written chunk information!", GREEN)); -+ } catch (Throwable thr) { -+ MinecraftServer.LOGGER.warn("Failed to dump chunk information to file " + file.toString(), thr); -+ sender.sendMessage(text("Failed to dump chunk information, see console", RED)); -+ } -+ } -+ // "help" & default -+ default -> sender.sendMessage(text("Use /paper debug [chunks] help for more information on a specific command", RED)); -+ } -+ } -+ -+} -diff --git a/src/main/java/io/papermc/paper/configuration/GlobalConfiguration.java b/src/main/java/io/papermc/paper/configuration/GlobalConfiguration.java -index 4de88f74182bb596c6b5ad0351cc0dacefd0ce96..2874bc3001c4e7d9191e47ba512c5a68369c21f1 100644 ---- a/src/main/java/io/papermc/paper/configuration/GlobalConfiguration.java -+++ b/src/main/java/io/papermc/paper/configuration/GlobalConfiguration.java -@@ -29,6 +29,45 @@ public class GlobalConfiguration extends ConfigurationPart { - public static GlobalConfiguration get() { - return instance; - } -+ -+ public ChunkLoadingBasic chunkLoadingBasic; -+ -+ public class ChunkLoadingBasic extends ConfigurationPart { -+ @Comment("The maximum rate in chunks per second that the server will send to any individual player. Set to -1 to disable this limit.") -+ public double playerMaxChunkSendRate = 75.0; -+ -+ @Comment( -+ "The maximum rate at which chunks will load for any individual player. " + -+ "Note that this setting also affects chunk generations, since a chunk load is always first issued to test if a" + -+ "chunk is already generated. Set to -1 to disable this limit." -+ ) -+ public double playerMaxChunkLoadRate = 100.0; -+ -+ @Comment("The maximum rate at which chunks will generate for any individual player. Set to -1 to disable this limit.") -+ public double playerMaxChunkGenerateRate = -1.0; -+ } -+ -+ public ChunkLoadingAdvanced chunkLoadingAdvanced; -+ -+ public class ChunkLoadingAdvanced extends ConfigurationPart { -+ @Comment( -+ "Set to true if the server will match the chunk send radius that clients have configured" + -+ "in their view distance settings if the client is less-than the server's send distance." -+ ) -+ public boolean autoConfigSendDistance = true; -+ -+ @Comment( -+ "Specifies the maximum amount of concurrent chunk loads that an individual player can have." + -+ "Set to 0 to let the server configure it automatically per player, or set it to -1 to disable the limit." -+ ) -+ public int playerMaxConcurrentChunkLoads = 0; -+ -+ @Comment( -+ "Specifies the maximum amount of concurrent chunk generations that an individual player can have." + -+ "Set to 0 to let the server configure it automatically per player, or set it to -1 to disable the limit." -+ ) -+ public int playerMaxConcurrentChunkGenerates = 0; -+ } - static void set(GlobalConfiguration instance) { - GlobalConfiguration.instance = instance; - } -@@ -130,21 +169,6 @@ public class GlobalConfiguration extends ConfigurationPart { - public int incomingPacketThreshold = 300; - } - -- public ChunkLoading chunkLoading; -- -- public class ChunkLoading extends ConfigurationPart { -- public int minLoadRadius = 2; -- public int maxConcurrentSends = 2; -- public boolean autoconfigSendDistance = true; -- public double targetPlayerChunkSendRate = 100.0; -- public double globalMaxChunkSendRate = -1.0; -- public boolean enableFrustumPriority = false; -- public double globalMaxChunkLoadRate = -1.0; -- public double playerMaxConcurrentLoads = 20.0; -- public double globalMaxConcurrentLoads = 500.0; -- public double playerMaxChunkLoadRate = -1.0; -- } -- - public UnsupportedSettings unsupportedSettings; - - public class UnsupportedSettings extends ConfigurationPart { -@@ -201,7 +225,7 @@ public class GlobalConfiguration extends ConfigurationPart { - - @PostProcess - private void postProcess() { -- //io.papermc.paper.chunk.system.scheduling.ChunkTaskScheduler.init(this); -+ io.papermc.paper.chunk.system.scheduling.ChunkTaskScheduler.init(this); - } - } - -diff --git a/src/main/java/io/papermc/paper/threadedregions/TickRegions.java b/src/main/java/io/papermc/paper/threadedregions/TickRegions.java -new file mode 100644 -index 0000000000000000000000000000000000000000..d5d39e9c1f326e91010237b0db80d527ac52f4d6 ---- /dev/null -+++ b/src/main/java/io/papermc/paper/threadedregions/TickRegions.java -@@ -0,0 +1,9 @@ -+package io.papermc.paper.threadedregions; -+ -+// placeholder class for Folia -+public class TickRegions { -+ -+ public static int getRegionChunkShift() { -+ return 4; -+ } -+} -diff --git a/src/main/java/io/papermc/paper/util/IntervalledCounter.java b/src/main/java/io/papermc/paper/util/IntervalledCounter.java -index cea9c098ade00ee87b8efc8164ab72f5279758f0..197224e31175252d8438a8df585bbb65f2288d7f 100644 ---- a/src/main/java/io/papermc/paper/util/IntervalledCounter.java -+++ b/src/main/java/io/papermc/paper/util/IntervalledCounter.java -@@ -2,6 +2,8 @@ package io.papermc.paper.util; - - public final class IntervalledCounter { - -+ private static final int INITIAL_SIZE = 8; -+ - protected long[] times; - protected long[] counts; - protected final long interval; -@@ -11,8 +13,8 @@ public final class IntervalledCounter { - protected int tail; // exclusive - - public IntervalledCounter(final long interval) { -- this.times = new long[8]; -- this.counts = new long[8]; -+ this.times = new long[INITIAL_SIZE]; -+ this.counts = new long[INITIAL_SIZE]; - this.interval = interval; - } - -@@ -67,13 +69,13 @@ public final class IntervalledCounter { - this.tail = nextTail; - } - -- public void updateAndAdd(final int count) { -+ public void updateAndAdd(final long count) { - final long currTime = System.nanoTime(); - this.updateCurrentTime(currTime); - this.addTime(currTime, count); - } - -- public void updateAndAdd(final int count, final long currTime) { -+ public void updateAndAdd(final long count, final long currTime) { - this.updateCurrentTime(currTime); - this.addTime(currTime, count); - } -@@ -93,9 +95,13 @@ public final class IntervalledCounter { - this.tail = size; - - if (tail >= head) { -+ // sequentially ordered from [head, tail) - System.arraycopy(oldElements, head, newElements, 0, size); - System.arraycopy(oldCounts, head, newCounts, 0, size); - } else { -+ // ordered from [head, length) -+ // then followed by [0, tail) -+ - System.arraycopy(oldElements, head, newElements, 0, oldElements.length - head); - System.arraycopy(oldElements, 0, newElements, oldElements.length - head, tail); - -@@ -106,10 +112,18 @@ public final class IntervalledCounter { - - // returns in units per second - public double getRate() { -- return this.size() / (this.interval * 1.0e-9); -+ return (double)this.sum / ((double)this.interval * 1.0E-9); -+ } -+ -+ public long getInterval() { -+ return this.interval; - } - -- public long size() { -+ public long getSum() { - return this.sum; - } -+ -+ public int totalDataPoints() { -+ return this.tail >= this.head ? (this.tail - this.head) : (this.tail + (this.counts.length - this.head)); -+ } - } -diff --git a/src/main/java/io/papermc/paper/util/MCUtil.java b/src/main/java/io/papermc/paper/util/MCUtil.java -index c95a0af32178fe24448a5ae7a229c86ec883e8de..1d6b3fe2ce240af4ede61588795456b046eee6c9 100644 ---- a/src/main/java/io/papermc/paper/util/MCUtil.java -+++ b/src/main/java/io/papermc/paper/util/MCUtil.java -@@ -7,17 +7,30 @@ import com.google.common.util.concurrent.ThreadFactoryBuilder; - import io.papermc.paper.math.BlockPosition; - import io.papermc.paper.math.FinePosition; - import io.papermc.paper.math.Position; -+import com.google.gson.JsonArray; -+import com.google.gson.JsonObject; -+import com.google.gson.internal.Streams; -+import com.google.gson.stream.JsonWriter; -+import com.mojang.datafixers.util.Either; - import it.unimi.dsi.fastutil.objects.ObjectRBTreeSet; - import java.lang.ref.Cleaner; -+import it.unimi.dsi.fastutil.objects.ReferenceArrayList; - import net.minecraft.core.BlockPos; - import net.minecraft.core.Direction; - import net.minecraft.core.Vec3i; - import net.minecraft.server.MinecraftServer; -+import net.minecraft.server.level.ChunkHolder; -+import net.minecraft.server.level.ChunkMap; -+import net.minecraft.server.level.DistanceManager; - import net.minecraft.server.level.ServerLevel; -+import net.minecraft.server.level.ServerPlayer; -+import net.minecraft.server.level.Ticket; - import net.minecraft.world.entity.Entity; - import net.minecraft.world.level.ChunkPos; - import net.minecraft.world.level.ClipContext; - import net.minecraft.world.level.Level; -+import net.minecraft.world.level.chunk.ChunkAccess; -+import net.minecraft.world.level.chunk.status.ChunkStatus; - import net.minecraft.world.phys.Vec3; - import org.apache.commons.lang.exception.ExceptionUtils; - import com.mojang.authlib.GameProfile; -@@ -30,8 +43,11 @@ import org.spigotmc.AsyncCatcher; - - import javax.annotation.Nonnull; - import javax.annotation.Nullable; -+import java.io.*; -+import java.nio.charset.StandardCharsets; - import java.util.List; - import java.util.Queue; -+import java.util.Set; - import java.util.concurrent.CompletableFuture; - import java.util.concurrent.ExecutionException; - import java.util.concurrent.LinkedBlockingQueue; -@@ -532,6 +548,98 @@ public final class MCUtil { - } - } - -+ public static ChunkStatus getChunkStatus(ChunkHolder chunk) { -+ return chunk.getChunkHolderStatus(); -+ } -+ -+ public static void dumpChunks(File file, boolean watchdog) throws IOException { -+ file.getParentFile().mkdirs(); -+ file.createNewFile(); -+ ReferenceArrayList worlds = new ReferenceArrayList<>(org.bukkit.Bukkit.getWorlds()); -+ ReferenceArrayList loadedWorlds = new ReferenceArrayList<>(worlds); -+ JsonObject data = new JsonObject(); -+ -+ data.addProperty("server-version", org.bukkit.Bukkit.getVersion()); -+ data.addProperty("data-version", 1); -+ -+ { -+ JsonArray players = new JsonArray(); -+ data.add("all-players", players); -+ List playerList = MinecraftServer.getServer().getPlayerList().players; -+ for (ServerPlayer player : playerList) { -+ JsonObject playerData = new JsonObject(); -+ players.add(playerData); -+ -+ Level playerWorld = player.level(); -+ org.bukkit.World craftWorld = playerWorld.getWorld(); -+ Entity.RemovalReason removalReason = player.getRemovalReason(); -+ -+ playerData.addProperty("name", player.getScoreboardName()); -+ playerData.addProperty("x", player.getX()); -+ playerData.addProperty("y", player.getY()); -+ playerData.addProperty("z", player.getZ()); -+ playerData.addProperty("world", playerWorld == null ? "null world" : craftWorld.getName()); -+ playerData.addProperty("removalReason", removalReason == null ? "null" : removalReason.name()); -+ -+ if (!worlds.contains(craftWorld)) { -+ worlds.add(craftWorld); -+ } -+ } -+ } -+ -+ JsonArray chunkWaitInformation = new JsonArray(); -+ data.add("chunk-wait-infos", chunkWaitInformation); -+ -+ for (io.papermc.paper.chunk.system.scheduling.ChunkTaskScheduler.ChunkInfo chunkInfo : io.papermc.paper.chunk.system.scheduling.ChunkTaskScheduler.getChunkInfos()) { -+ chunkWaitInformation.add(chunkInfo.toString()); -+ } -+ -+ JsonArray worldsData = new JsonArray(); -+ -+ for (org.bukkit.World bukkitWorld : worlds) { -+ JsonObject worldData = new JsonObject(); -+ -+ ServerLevel world = ((org.bukkit.craftbukkit.CraftWorld)bukkitWorld).getHandle(); -+ List players = world.players(); -+ -+ worldData.addProperty("is-loaded", loadedWorlds.contains(bukkitWorld)); -+ worldData.addProperty("name", world.getWorld().getName()); -+ worldData.addProperty("view-distance", world.getWorld().getViewDistance()); // Paper - replace chunk loader system -+ worldData.addProperty("tick-view-distance", world.getWorld().getSimulationDistance()); // Paper - replace chunk loader system -+ -+ JsonArray playersData = new JsonArray(); -+ -+ for (ServerPlayer player : players) { -+ JsonObject playerData = new JsonObject(); -+ -+ playerData.addProperty("name", player.getScoreboardName()); -+ playerData.addProperty("x", player.getX()); -+ playerData.addProperty("y", player.getY()); -+ playerData.addProperty("z", player.getZ()); -+ -+ playersData.add(playerData); -+ } -+ -+ worldData.add("players", playersData); -+ worldData.add("chunk-data", watchdog ? world.chunkTaskScheduler.chunkHolderManager.getDebugJsonForWatchdog() : world.chunkTaskScheduler.chunkHolderManager.getDebugJson()); -+ worldsData.add(worldData); -+ } -+ -+ data.add("worlds", worldsData); -+ -+ StringWriter stringWriter = new StringWriter(); -+ JsonWriter jsonWriter = new JsonWriter(stringWriter); -+ jsonWriter.setIndent(" "); -+ jsonWriter.setLenient(false); -+ Streams.write(data, jsonWriter); -+ -+ String fileData = stringWriter.toString(); -+ -+ try (PrintStream out = new PrintStream(new FileOutputStream(file), false, StandardCharsets.UTF_8)) { -+ out.print(fileData); -+ } -+ } -+ - public static int getTicketLevelFor(net.minecraft.world.level.chunk.status.ChunkStatus status) { - return net.minecraft.server.level.ChunkMap.MAX_VIEW_DISTANCE + net.minecraft.world.level.chunk.status.ChunkStatus.getDistance(status); - } -diff --git a/src/main/java/io/papermc/paper/util/TickThread.java b/src/main/java/io/papermc/paper/util/TickThread.java -index 73e83d56a340f0c7dcb8ff737d621003e72c6de4..bdaf062f9b66ceab303a0807eca301342886a8ea 100644 ---- a/src/main/java/io/papermc/paper/util/TickThread.java -+++ b/src/main/java/io/papermc/paper/util/TickThread.java -@@ -1,12 +1,20 @@ - package io.papermc.paper.util; - -+import net.minecraft.core.BlockPos; - import net.minecraft.server.MinecraftServer; - import net.minecraft.server.level.ServerLevel; -+import net.minecraft.server.level.ServerPlayer; -+import net.minecraft.server.network.ServerGamePacketListenerImpl; -+import net.minecraft.util.Mth; - import net.minecraft.world.entity.Entity; -+import net.minecraft.world.level.ChunkPos; -+import net.minecraft.world.level.Level; -+import net.minecraft.world.phys.AABB; -+import net.minecraft.world.phys.Vec3; - import org.bukkit.Bukkit; - import java.util.concurrent.atomic.AtomicInteger; - --public final class TickThread extends Thread { -+public class TickThread extends Thread { - - public static final boolean STRICT_THREAD_CHECKS = Boolean.getBoolean("paper.strict-thread-checks"); - -@@ -16,6 +24,10 @@ public final class TickThread extends Thread { - } - } - -+ /** -+ * @deprecated -+ */ -+ @Deprecated - public static void softEnsureTickThread(final String reason) { - if (!STRICT_THREAD_CHECKS) { - return; -@@ -23,6 +35,10 @@ public final class TickThread extends Thread { - ensureTickThread(reason); - } - -+ /** -+ * @deprecated -+ */ -+ @Deprecated - public static void ensureTickThread(final String reason) { - if (!isTickThread()) { - MinecraftServer.LOGGER.error("Thread " + Thread.currentThread().getName() + " failed main thread check: " + reason, new Throwable()); -@@ -30,6 +46,20 @@ public final class TickThread extends Thread { - } - } - -+ public static void ensureTickThread(final ServerLevel world, final BlockPos pos, final String reason) { -+ if (!isTickThreadFor(world, pos)) { -+ MinecraftServer.LOGGER.error("Thread " + Thread.currentThread().getName() + " failed main thread check: " + reason, new Throwable()); -+ throw new IllegalStateException(reason); -+ } -+ } -+ -+ public static void ensureTickThread(final ServerLevel world, final ChunkPos pos, final String reason) { -+ if (!isTickThreadFor(world, pos)) { -+ MinecraftServer.LOGGER.error("Thread " + Thread.currentThread().getName() + " failed main thread check: " + reason, new Throwable()); -+ throw new IllegalStateException(reason); -+ } -+ } -+ - public static void ensureTickThread(final ServerLevel world, final int chunkX, final int chunkZ, final String reason) { - if (!isTickThreadFor(world, chunkX, chunkZ)) { - MinecraftServer.LOGGER.error("Thread " + Thread.currentThread().getName() + " failed main thread check: " + reason, new Throwable()); -@@ -44,6 +74,20 @@ public final class TickThread extends Thread { - } - } - -+ public static void ensureTickThread(final ServerLevel world, final AABB aabb, final String reason) { -+ if (!isTickThreadFor(world, aabb)) { -+ MinecraftServer.LOGGER.error("Thread " + Thread.currentThread().getName() + " failed main thread check: " + reason, new Throwable()); -+ throw new IllegalStateException(reason); -+ } -+ } -+ -+ public static void ensureTickThread(final ServerLevel world, final double blockX, final double blockZ, final String reason) { -+ if (!isTickThreadFor(world, blockX, blockZ)) { -+ MinecraftServer.LOGGER.error("Thread " + Thread.currentThread().getName() + " failed main thread check: " + reason, new Throwable()); -+ throw new IllegalStateException(reason); -+ } -+ } -+ - public final int id; /* We don't override getId as the spec requires that it be unique (with respect to all other threads) */ - - private static final AtomicInteger ID_GENERATOR = new AtomicInteger(); -@@ -66,13 +110,45 @@ public final class TickThread extends Thread { - } - - public static boolean isTickThread() { -- return Bukkit.isPrimaryThread(); -+ return Thread.currentThread() instanceof TickThread; -+ } -+ -+ public static boolean isShutdownThread() { -+ return false; -+ } -+ -+ public static boolean isTickThreadFor(final ServerLevel world, final BlockPos pos) { -+ return isTickThread(); -+ } -+ -+ public static boolean isTickThreadFor(final ServerLevel world, final ChunkPos pos) { -+ return isTickThread(); -+ } -+ -+ public static boolean isTickThreadFor(final ServerLevel world, final Vec3 pos) { -+ return isTickThread(); - } - - public static boolean isTickThreadFor(final ServerLevel world, final int chunkX, final int chunkZ) { - return isTickThread(); - } - -+ public static boolean isTickThreadFor(final ServerLevel world, final AABB aabb) { -+ return isTickThread(); -+ } -+ -+ public static boolean isTickThreadFor(final ServerLevel world, final double blockX, final double blockZ) { -+ return isTickThread(); -+ } -+ -+ public static boolean isTickThreadFor(final ServerLevel world, final Vec3 position, final Vec3 deltaMovement, final int buffer) { -+ return isTickThread(); -+ } -+ -+ public static boolean isTickThreadFor(final ServerLevel world, final int fromChunkX, final int fromChunkZ, final int toChunkX, final int toChunkZ) { -+ return isTickThread(); -+ } -+ - public static boolean isTickThreadFor(final ServerLevel world, final int chunkX, final int chunkZ, final int radius) { - return isTickThread(); - } -diff --git a/src/main/java/io/papermc/paper/world/ChunkEntitySlices.java b/src/main/java/io/papermc/paper/world/ChunkEntitySlices.java -new file mode 100644 -index 0000000000000000000000000000000000000000..c78cbec447032de9fe69748591bef6be300160ed ---- /dev/null -+++ b/src/main/java/io/papermc/paper/world/ChunkEntitySlices.java -@@ -0,0 +1,607 @@ -+package io.papermc.paper.world; -+ -+import com.destroystokyo.paper.util.maplist.EntityList; -+import io.papermc.paper.chunk.system.entity.EntityLookup; -+import io.papermc.paper.util.TickThread; -+import it.unimi.dsi.fastutil.objects.Reference2ObjectMap; -+import it.unimi.dsi.fastutil.objects.Reference2ObjectOpenHashMap; -+import net.minecraft.nbt.CompoundTag; -+import net.minecraft.server.level.ChunkHolder; -+import net.minecraft.server.level.FullChunkStatus; -+import net.minecraft.server.level.ServerLevel; -+import net.minecraft.util.Mth; -+import net.minecraft.world.entity.Entity; -+import net.minecraft.world.entity.EntityType; -+import net.minecraft.world.entity.boss.EnderDragonPart; -+import net.minecraft.world.entity.boss.enderdragon.EnderDragon; -+import net.minecraft.world.level.ChunkPos; -+import net.minecraft.world.level.chunk.storage.EntityStorage; -+import net.minecraft.world.level.entity.Visibility; -+import net.minecraft.world.phys.AABB; -+import org.bukkit.craftbukkit.event.CraftEventFactory; -+import java.util.ArrayList; -+import java.util.Arrays; -+import java.util.Iterator; -+import java.util.List; -+import java.util.function.Predicate; -+import org.bukkit.event.entity.EntityRemoveEvent; -+ -+public final class ChunkEntitySlices { -+ -+ protected final int minSection; -+ protected final int maxSection; -+ public final int chunkX; -+ public final int chunkZ; -+ protected final ServerLevel world; -+ -+ protected final EntityCollectionBySection allEntities; -+ protected final EntityCollectionBySection hardCollidingEntities; -+ protected final Reference2ObjectOpenHashMap, EntityCollectionBySection> entitiesByClass; -+ protected final EntityList entities = new EntityList(); -+ -+ public FullChunkStatus status; -+ -+ protected boolean isTransient; -+ -+ public boolean isTransient() { -+ return this.isTransient; -+ } -+ -+ public void setTransient(final boolean value) { -+ this.isTransient = value; -+ } -+ -+ // TODO implement container search optimisations -+ -+ public ChunkEntitySlices(final ServerLevel world, final int chunkX, final int chunkZ, final FullChunkStatus status, -+ final int minSection, final int maxSection) { // inclusive, inclusive -+ this.minSection = minSection; -+ this.maxSection = maxSection; -+ this.chunkX = chunkX; -+ this.chunkZ = chunkZ; -+ this.world = world; -+ -+ this.allEntities = new EntityCollectionBySection(this); -+ this.hardCollidingEntities = new EntityCollectionBySection(this); -+ this.entitiesByClass = new Reference2ObjectOpenHashMap<>(); -+ -+ this.status = status; -+ } -+ -+ // Paper start - optimise CraftChunk#getEntities -+ public org.bukkit.entity.Entity[] getChunkEntities() { -+ List ret = new java.util.ArrayList<>(); -+ final Entity[] entities = this.entities.getRawData(); -+ for (int i = 0, size = Math.min(entities.length, this.entities.size()); i < size; ++i) { -+ final Entity entity = entities[i]; -+ if (entity == null) { -+ continue; -+ } -+ final org.bukkit.entity.Entity bukkit = entity.getBukkitEntity(); -+ if (bukkit != null && bukkit.isValid()) { -+ ret.add(bukkit); -+ } -+ } -+ -+ return ret.toArray(new org.bukkit.entity.Entity[0]); -+ } -+ -+ public CompoundTag save() { -+ final int len = this.entities.size(); -+ if (len == 0) { -+ return null; -+ } -+ -+ final Entity[] rawData = this.entities.getRawData(); -+ final List collectedEntities = new ArrayList<>(len); -+ for (int i = 0; i < len; ++i) { -+ final Entity entity = rawData[i]; -+ if (entity.shouldBeSaved()) { -+ collectedEntities.add(entity); -+ } -+ } -+ -+ if (collectedEntities.isEmpty()) { -+ return null; -+ } -+ -+ return EntityStorage.saveEntityChunk(collectedEntities, new ChunkPos(this.chunkX, this.chunkZ), this.world); -+ } -+ -+ // returns true if this chunk has transient entities remaining -+ public boolean unload() { -+ final int len = this.entities.size(); -+ final Entity[] collectedEntities = Arrays.copyOf(this.entities.getRawData(), len); -+ -+ for (int i = 0; i < len; ++i) { -+ final Entity entity = collectedEntities[i]; -+ if (entity.isRemoved()) { -+ // removed by us below -+ continue; -+ } -+ if (entity.shouldBeSaved()) { -+ entity.setRemoved(Entity.RemovalReason.UNLOADED_TO_CHUNK, EntityRemoveEvent.Cause.UNLOAD); -+ if (entity.isVehicle()) { -+ // we cannot assume that these entities are contained within this chunk, because entities can -+ // desync - so we need to remove them all -+ for (final Entity passenger : entity.getIndirectPassengers()) { -+ passenger.setRemoved(Entity.RemovalReason.UNLOADED_TO_CHUNK, EntityRemoveEvent.Cause.UNLOAD); -+ } -+ } -+ } -+ } -+ -+ return this.entities.size() != 0; -+ } -+ -+ private List getAllEntities() { -+ final int len = this.entities.size(); -+ if (len == 0) { -+ return new ArrayList<>(); -+ } -+ -+ final Entity[] rawData = this.entities.getRawData(); -+ final List collectedEntities = new ArrayList<>(len); -+ for (int i = 0; i < len; ++i) { -+ collectedEntities.add(rawData[i]); -+ } -+ -+ return collectedEntities; -+ } -+ -+ public void callEntitiesLoadEvent() { -+ CraftEventFactory.callEntitiesLoadEvent(this.world, new ChunkPos(this.chunkX, this.chunkZ), this.getAllEntities()); -+ } -+ -+ public void callEntitiesUnloadEvent() { -+ CraftEventFactory.callEntitiesUnloadEvent(this.world, new ChunkPos(this.chunkX, this.chunkZ), this.getAllEntities()); -+ } -+ // Paper end - optimise CraftChunk#getEntities -+ -+ public boolean isEmpty() { -+ return this.entities.size() == 0; -+ } -+ -+ public void mergeInto(final ChunkEntitySlices slices) { -+ final Entity[] entities = this.entities.getRawData(); -+ for (int i = 0, size = Math.min(entities.length, this.entities.size()); i < size; ++i) { -+ final Entity entity = entities[i]; -+ slices.addEntity(entity, entity.sectionY); -+ } -+ } -+ -+ private boolean preventStatusUpdates; -+ public boolean startPreventingStatusUpdates() { -+ final boolean ret = this.preventStatusUpdates; -+ this.preventStatusUpdates = true; -+ return ret; -+ } -+ -+ public boolean isPreventingStatusUpdates() { -+ return this.preventStatusUpdates; -+ } -+ -+ public void stopPreventingStatusUpdates(final boolean prev) { -+ this.preventStatusUpdates = prev; -+ } -+ -+ public void updateStatus(final FullChunkStatus status, final EntityLookup lookup) { -+ this.status = status; -+ -+ final Entity[] entities = this.entities.getRawData(); -+ -+ for (int i = 0, size = this.entities.size(); i < size; ++i) { -+ final Entity entity = entities[i]; -+ -+ final Visibility oldVisibility = EntityLookup.getEntityStatus(entity); -+ entity.chunkStatus = status; -+ final Visibility newVisibility = EntityLookup.getEntityStatus(entity); -+ -+ lookup.entityStatusChange(entity, this, oldVisibility, newVisibility, false, false, false); -+ } -+ } -+ -+ public boolean addEntity(final Entity entity, final int chunkSection) { -+ if (!this.entities.add(entity)) { -+ return false; -+ } -+ entity.chunkStatus = this.status; -+ final int sectionIndex = chunkSection - this.minSection; -+ -+ this.allEntities.addEntity(entity, sectionIndex); -+ -+ if (entity.hardCollides()) { -+ this.hardCollidingEntities.addEntity(entity, sectionIndex); -+ } -+ -+ for (final Iterator, EntityCollectionBySection>> iterator = -+ this.entitiesByClass.reference2ObjectEntrySet().fastIterator(); iterator.hasNext();) { -+ final Reference2ObjectMap.Entry, EntityCollectionBySection> entry = iterator.next(); -+ -+ if (entry.getKey().isInstance(entity)) { -+ entry.getValue().addEntity(entity, sectionIndex); -+ } -+ } -+ -+ return true; -+ } -+ -+ public boolean removeEntity(final Entity entity, final int chunkSection) { -+ if (!this.entities.remove(entity)) { -+ return false; -+ } -+ entity.chunkStatus = null; -+ final int sectionIndex = chunkSection - this.minSection; -+ -+ this.allEntities.removeEntity(entity, sectionIndex); -+ -+ if (entity.hardCollides()) { -+ this.hardCollidingEntities.removeEntity(entity, sectionIndex); -+ } -+ -+ for (final Iterator, EntityCollectionBySection>> iterator = -+ this.entitiesByClass.reference2ObjectEntrySet().fastIterator(); iterator.hasNext();) { -+ final Reference2ObjectMap.Entry, EntityCollectionBySection> entry = iterator.next(); -+ -+ if (entry.getKey().isInstance(entity)) { -+ entry.getValue().removeEntity(entity, sectionIndex); -+ } -+ } -+ -+ return true; -+ } -+ -+ public void getHardCollidingEntities(final Entity except, final AABB box, final List into, final Predicate predicate) { -+ this.hardCollidingEntities.getEntities(except, box, into, predicate); -+ } -+ -+ public void getEntities(final Entity except, final AABB box, final List into, final Predicate predicate) { -+ this.allEntities.getEntitiesWithEnderDragonParts(except, box, into, predicate); -+ } -+ -+ public void getEntitiesWithoutDragonParts(final Entity except, final AABB box, final List into, final Predicate predicate) { -+ this.allEntities.getEntities(except, box, into, predicate); -+ } -+ -+ public void getEntities(final EntityType type, final AABB box, final List into, -+ final Predicate predicate) { -+ this.allEntities.getEntities(type, box, (List)into, (Predicate)predicate); -+ } -+ -+ protected EntityCollectionBySection initClass(final Class clazz) { -+ final EntityCollectionBySection ret = new EntityCollectionBySection(this); -+ -+ for (int sectionIndex = 0; sectionIndex < this.allEntities.entitiesBySection.length; ++sectionIndex) { -+ final BasicEntityList sectionEntities = this.allEntities.entitiesBySection[sectionIndex]; -+ if (sectionEntities == null) { -+ continue; -+ } -+ -+ final Entity[] storage = sectionEntities.storage; -+ -+ for (int i = 0, len = Math.min(storage.length, sectionEntities.size()); i < len; ++i) { -+ final Entity entity = storage[i]; -+ -+ if (clazz.isInstance(entity)) { -+ ret.addEntity(entity, sectionIndex); -+ } -+ } -+ } -+ -+ return ret; -+ } -+ -+ public void getEntities(final Class clazz, final Entity except, final AABB box, final List into, -+ final Predicate predicate) { -+ EntityCollectionBySection collection = this.entitiesByClass.get(clazz); -+ if (collection != null) { -+ collection.getEntitiesWithEnderDragonParts(except, clazz, box, (List)into, (Predicate)predicate); -+ } else { -+ this.entitiesByClass.putIfAbsent(clazz, collection = this.initClass(clazz)); -+ collection.getEntitiesWithEnderDragonParts(except, clazz, box, (List)into, (Predicate)predicate); -+ } -+ } -+ -+ protected static final class BasicEntityList { -+ -+ protected static final Entity[] EMPTY = new Entity[0]; -+ protected static final int DEFAULT_CAPACITY = 4; -+ -+ protected E[] storage; -+ protected int size; -+ -+ public BasicEntityList() { -+ this(0); -+ } -+ -+ public BasicEntityList(final int cap) { -+ this.storage = (E[])(cap <= 0 ? EMPTY : new Entity[cap]); -+ } -+ -+ public boolean isEmpty() { -+ return this.size == 0; -+ } -+ -+ public int size() { -+ return this.size; -+ } -+ -+ private void resize() { -+ if (this.storage == EMPTY) { -+ this.storage = (E[])new Entity[DEFAULT_CAPACITY]; -+ } else { -+ this.storage = Arrays.copyOf(this.storage, this.storage.length * 2); -+ } -+ } -+ -+ public void add(final E entity) { -+ final int idx = this.size++; -+ if (idx >= this.storage.length) { -+ this.resize(); -+ this.storage[idx] = entity; -+ } else { -+ this.storage[idx] = entity; -+ } -+ } -+ -+ public int indexOf(final E entity) { -+ final E[] storage = this.storage; -+ -+ for (int i = 0, len = Math.min(this.storage.length, this.size); i < len; ++i) { -+ if (storage[i] == entity) { -+ return i; -+ } -+ } -+ -+ return -1; -+ } -+ -+ public boolean remove(final E entity) { -+ final int idx = this.indexOf(entity); -+ if (idx == -1) { -+ return false; -+ } -+ -+ final int size = --this.size; -+ final E[] storage = this.storage; -+ if (idx != size) { -+ System.arraycopy(storage, idx + 1, storage, idx, size - idx); -+ } -+ -+ storage[size] = null; -+ -+ return true; -+ } -+ -+ public boolean has(final E entity) { -+ return this.indexOf(entity) != -1; -+ } -+ } -+ -+ protected static final class EntityCollectionBySection { -+ -+ protected final ChunkEntitySlices manager; -+ protected final long[] nonEmptyBitset; -+ protected final BasicEntityList[] entitiesBySection; -+ protected int count; -+ -+ public EntityCollectionBySection(final ChunkEntitySlices manager) { -+ this.manager = manager; -+ -+ final int sectionCount = manager.maxSection - manager.minSection + 1; -+ -+ this.nonEmptyBitset = new long[(sectionCount + (Long.SIZE - 1)) >>> 6]; // (sectionCount + (Long.SIZE - 1)) / Long.SIZE -+ this.entitiesBySection = new BasicEntityList[sectionCount]; -+ } -+ -+ public void addEntity(final Entity entity, final int sectionIndex) { -+ BasicEntityList list = this.entitiesBySection[sectionIndex]; -+ -+ if (list != null && list.has(entity)) { -+ return; -+ } -+ -+ if (list == null) { -+ this.entitiesBySection[sectionIndex] = list = new BasicEntityList<>(); -+ this.nonEmptyBitset[sectionIndex >>> 6] |= (1L << (sectionIndex & (Long.SIZE - 1))); -+ } -+ -+ list.add(entity); -+ ++this.count; -+ } -+ -+ public void removeEntity(final Entity entity, final int sectionIndex) { -+ final BasicEntityList list = this.entitiesBySection[sectionIndex]; -+ -+ if (list == null || !list.remove(entity)) { -+ return; -+ } -+ -+ --this.count; -+ -+ if (list.isEmpty()) { -+ this.entitiesBySection[sectionIndex] = null; -+ this.nonEmptyBitset[sectionIndex >>> 6] ^= (1L << (sectionIndex & (Long.SIZE - 1))); -+ } -+ } -+ -+ public void getEntities(final Entity except, final AABB box, final List into, final Predicate predicate) { -+ if (this.count == 0) { -+ return; -+ } -+ -+ final int minSection = this.manager.minSection; -+ final int maxSection = this.manager.maxSection; -+ -+ final int min = Mth.clamp(Mth.floor(box.minY - 2.0) >> 4, minSection, maxSection); -+ final int max = Mth.clamp(Mth.floor(box.maxY + 2.0) >> 4, minSection, maxSection); -+ -+ final BasicEntityList[] entitiesBySection = this.entitiesBySection; -+ -+ for (int section = min; section <= max; ++section) { -+ final BasicEntityList list = entitiesBySection[section - minSection]; -+ -+ if (list == null) { -+ continue; -+ } -+ -+ final Entity[] storage = list.storage; -+ -+ for (int i = 0, len = Math.min(storage.length, list.size()); i < len; ++i) { -+ final Entity entity = storage[i]; -+ -+ if (entity == null || entity == except || !entity.getBoundingBox().intersects(box)) { -+ continue; -+ } -+ -+ if (predicate != null && !predicate.test(entity)) { -+ continue; -+ } -+ -+ into.add(entity); -+ } -+ } -+ } -+ -+ public void getEntitiesWithEnderDragonParts(final Entity except, final AABB box, final List into, -+ final Predicate predicate) { -+ if (this.count == 0) { -+ return; -+ } -+ -+ final int minSection = this.manager.minSection; -+ final int maxSection = this.manager.maxSection; -+ -+ final int min = Mth.clamp(Mth.floor(box.minY - 2.0) >> 4, minSection, maxSection); -+ final int max = Mth.clamp(Mth.floor(box.maxY + 2.0) >> 4, minSection, maxSection); -+ -+ final BasicEntityList[] entitiesBySection = this.entitiesBySection; -+ -+ for (int section = min; section <= max; ++section) { -+ final BasicEntityList list = entitiesBySection[section - minSection]; -+ -+ if (list == null) { -+ continue; -+ } -+ -+ final Entity[] storage = list.storage; -+ -+ for (int i = 0, len = Math.min(storage.length, list.size()); i < len; ++i) { -+ final Entity entity = storage[i]; -+ -+ if (entity == null || entity == except || !entity.getBoundingBox().intersects(box)) { -+ continue; -+ } -+ -+ if (predicate == null || predicate.test(entity)) { -+ into.add(entity); -+ } // else: continue to test the ender dragon parts -+ -+ if (entity instanceof EnderDragon) { -+ for (final EnderDragonPart part : ((EnderDragon)entity).subEntities) { -+ if (part == except || !part.getBoundingBox().intersects(box)) { -+ continue; -+ } -+ -+ if (predicate != null && !predicate.test(part)) { -+ continue; -+ } -+ -+ into.add(part); -+ } -+ } -+ } -+ } -+ } -+ -+ public void getEntitiesWithEnderDragonParts(final Entity except, final Class clazz, final AABB box, final List into, -+ final Predicate predicate) { -+ if (this.count == 0) { -+ return; -+ } -+ -+ final int minSection = this.manager.minSection; -+ final int maxSection = this.manager.maxSection; -+ -+ final int min = Mth.clamp(Mth.floor(box.minY - 2.0) >> 4, minSection, maxSection); -+ final int max = Mth.clamp(Mth.floor(box.maxY + 2.0) >> 4, minSection, maxSection); -+ -+ final BasicEntityList[] entitiesBySection = this.entitiesBySection; -+ -+ for (int section = min; section <= max; ++section) { -+ final BasicEntityList list = entitiesBySection[section - minSection]; -+ -+ if (list == null) { -+ continue; -+ } -+ -+ final Entity[] storage = list.storage; -+ -+ for (int i = 0, len = Math.min(storage.length, list.size()); i < len; ++i) { -+ final Entity entity = storage[i]; -+ -+ if (entity == null || entity == except || !entity.getBoundingBox().intersects(box)) { -+ continue; -+ } -+ -+ if (predicate == null || predicate.test(entity)) { -+ into.add(entity); -+ } // else: continue to test the ender dragon parts -+ -+ if (entity instanceof EnderDragon) { -+ for (final EnderDragonPart part : ((EnderDragon)entity).subEntities) { -+ if (part == except || !part.getBoundingBox().intersects(box) || !clazz.isInstance(part)) { -+ continue; -+ } -+ -+ if (predicate != null && !predicate.test(part)) { -+ continue; -+ } -+ -+ into.add(part); -+ } -+ } -+ } -+ } -+ } -+ -+ public void getEntities(final EntityType type, final AABB box, final List into, -+ final Predicate predicate) { -+ if (this.count == 0) { -+ return; -+ } -+ -+ final int minSection = this.manager.minSection; -+ final int maxSection = this.manager.maxSection; -+ -+ final int min = Mth.clamp(Mth.floor(box.minY - 2.0) >> 4, minSection, maxSection); -+ final int max = Mth.clamp(Mth.floor(box.maxY + 2.0) >> 4, minSection, maxSection); -+ -+ final BasicEntityList[] entitiesBySection = this.entitiesBySection; -+ -+ for (int section = min; section <= max; ++section) { -+ final BasicEntityList list = entitiesBySection[section - minSection]; -+ -+ if (list == null) { -+ continue; -+ } -+ -+ final Entity[] storage = list.storage; -+ -+ for (int i = 0, len = Math.min(storage.length, list.size()); i < len; ++i) { -+ final Entity entity = storage[i]; -+ -+ if (entity == null || (type != null && entity.getType() != type) || !entity.getBoundingBox().intersects(box)) { -+ continue; -+ } -+ -+ if (predicate != null && !predicate.test((T)entity)) { -+ continue; -+ } -+ -+ into.add((T)entity); -+ } -+ } -+ } -+ } -+} -diff --git a/src/main/java/net/minecraft/server/Main.java b/src/main/java/net/minecraft/server/Main.java -index c33f85b570f159ab465b5a10a8044a81f2797f43..244a19ecd0234fa1d7a6ecfea20751595688605d 100644 ---- a/src/main/java/net/minecraft/server/Main.java -+++ b/src/main/java/net/minecraft/server/Main.java -@@ -320,6 +320,7 @@ public class Main { - - convertable_conversionsession.saveDataTag(iregistrycustom_dimension, savedata); - */ -+ Class.forName(net.minecraft.world.entity.npc.VillagerTrades.class.getName()); // Paper - load this sync so it won't fail later async - final DedicatedServer dedicatedserver = (DedicatedServer) MinecraftServer.spin((thread) -> { - DedicatedServer dedicatedserver1 = new DedicatedServer(optionset, worldLoader.get(), thread, convertable_conversionsession, resourcepackrepository, worldstem, dedicatedserversettings, DataFixers.getDataFixer(), services, LoggerChunkProgressListener::createFromGameruleRadius); - -diff --git a/src/main/java/net/minecraft/server/MinecraftServer.java b/src/main/java/net/minecraft/server/MinecraftServer.java -index 1a9e323659dcff12ce53919eb3d6d6f66f835292..2e4f20ba5f6f61b797f1eef267302fa3314f94a5 100644 ---- a/src/main/java/net/minecraft/server/MinecraftServer.java -+++ b/src/main/java/net/minecraft/server/MinecraftServer.java -@@ -315,7 +315,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop S spin(Function serverFactory) { - AtomicReference atomicreference = new AtomicReference(); -- Thread thread = new Thread(() -> { -+ Thread thread = new io.papermc.paper.util.TickThread(() -> { // Paper - rewrite chunk system - ((MinecraftServer) atomicreference.get()).runServer(); - }, "Server thread"); - -@@ -651,7 +651,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop { -- return worldserver1.getChunkSource().chunkMap.hasWork(); -- })) { -- this.nextTickTimeNanos = Util.getNanos() + TimeUtil.NANOSECONDS_PER_MILLISECOND; -- iterator = this.getAllLevels().iterator(); -- -- while (iterator.hasNext()) { -- worldserver = (ServerLevel) iterator.next(); -- worldserver.getChunkSource().removeTicketsOnClosing(); -- worldserver.getChunkSource().tick(() -> { -- return true; -- }, false); -- } -- -- this.waitUntilNextTick(); -- } -- -- this.saveAllChunks(false, true, false); -- iterator = this.getAllLevels().iterator(); -- -- while (iterator.hasNext()) { -- worldserver = (ServerLevel) iterator.next(); -- if (worldserver != null) { -- try { -- worldserver.close(); -- } catch (IOException ioexception) { -- MinecraftServer.LOGGER.error("Exception closing the level", ioexception); -- } -- } -- } -+ this.saveAllChunks(false, true, false, true); // Paper - rewrite chunk system - move closing into here - - this.isSaving = false; - this.resources.close(); -@@ -1020,6 +1001,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop { -- for (final Entity entity : level.getEntities().getAll()) { -+ for (final Entity entity : level.getEntityLookup().getAllCopy()) { // Paper - rewrite chunk system - if (entity.isRemoved()) { - continue; - } -@@ -2622,6 +2617,13 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop { -+ LOGGER.info("Async debug chunks executing"); -+ io.papermc.paper.chunk.system.scheduling.ChunkTaskScheduler.dumpAllChunkLoadInfo(false); -+ CommandSender sender = MinecraftServer.getServer().console; -+ java.io.File file = new java.io.File(new java.io.File(new java.io.File("."), "debug"), -+ "chunks-" + java.time.format.DateTimeFormatter.ofPattern("yyyy-MM-dd_HH.mm.ss").format(java.time.LocalDateTime.now()) + ".txt"); -+ sender.sendMessage(net.kyori.adventure.text.Component.text("Writing chunk information dump to " + file, net.kyori.adventure.text.format.NamedTextColor.GREEN)); -+ try { -+ io.papermc.paper.util.MCUtil.dumpChunks(file, true); -+ sender.sendMessage(net.kyori.adventure.text.Component.text("Successfully written chunk information!", net.kyori.adventure.text.format.NamedTextColor.GREEN)); -+ } catch (Throwable thr) { -+ MinecraftServer.LOGGER.warn("Failed to dump chunk information to file " + file.toString(), thr); -+ sender.sendMessage(net.kyori.adventure.text.Component.text("Failed to dump chunk information, see console", net.kyori.adventure.text.format.NamedTextColor.RED)); -+ } -+ }; -+ Thread t = new Thread(run); -+ t.setName("Async debug thread #" + ASYNC_DEBUG_CHUNKS_COUNT.getAndIncrement()); -+ t.setDaemon(true); -+ t.start(); -+ return; -+ } -+ // Paper end - rewrite chunk system - this.serverCommandQueue.add(new ConsoleInput(command, commandSource)); // Paper - Perf: use proper queue - } - -diff --git a/src/main/java/net/minecraft/server/level/ChunkHolder.java b/src/main/java/net/minecraft/server/level/ChunkHolder.java -index 88729d92878f98729eb5669cce5ae5b1418865a1..13d15a135dd0373bef4a5ac9ffb56dbbf53353a0 100644 ---- a/src/main/java/net/minecraft/server/level/ChunkHolder.java -+++ b/src/main/java/net/minecraft/server/level/ChunkHolder.java -@@ -46,17 +46,12 @@ public class ChunkHolder { - public static final ChunkResult NOT_DONE_YET = ChunkResult.error("Not done yet"); - private static final CompletableFuture> UNLOADED_LEVEL_CHUNK_FUTURE = CompletableFuture.completedFuture(ChunkHolder.UNLOADED_LEVEL_CHUNK); - private static final List CHUNK_STATUSES = ChunkStatus.getStatusList(); -- private final AtomicReferenceArray>> futures; -+ // Paper - rewrite chunk system - private final LevelHeightAccessor levelHeightAccessor; -- private volatile CompletableFuture> fullChunkFuture; private int fullChunkCreateCount; private volatile boolean isFullChunkReady; // Paper - cache chunk ticking stage -- private volatile CompletableFuture> tickingChunkFuture; private volatile boolean isTickingReady; // Paper - cache chunk ticking stage -- private volatile CompletableFuture> entityTickingChunkFuture; private volatile boolean isEntityTickingReady; // Paper - cache chunk ticking stage -- public CompletableFuture chunkToSave; // Paper - public -+ // Paper - rewrite chunk system - @Nullable - private final DebugBuffer chunkToSaveHistory; -- public int oldTicketLevel; -- private int ticketLevel; -- private int queueLevel; -+ // Paper - rewrite chunk system - public final ChunkPos pos; - private boolean hasChangedSections; - private final ShortSet[] changedBlocksPerSection; -@@ -65,11 +60,20 @@ public class ChunkHolder { - private final LevelLightEngine lightEngine; - private final ChunkHolder.LevelChangeListener onLevelChange; - public final ChunkHolder.PlayerProvider playerProvider; -- private boolean wasAccessibleSinceLastSave; -- private CompletableFuture pendingFullStateConfirmation; -- private CompletableFuture sendSync; -+ // Paper - rewrite chunk system - - private final ChunkMap chunkMap; // Paper -+ // Paper start - no-tick view distance -+ public final LevelChunk getSendingChunk() { -+ // it's important that we use getChunkAtIfLoadedImmediately to mirror the chunk sending logic used -+ // in Chunk's neighbour callback -+ LevelChunk ret = this.chunkMap.level.getChunkSource().getChunkAtIfLoadedImmediately(this.pos.x, this.pos.z); -+ if (ret != null && ret.areNeighboursLoaded(1)) { -+ return ret; -+ } -+ return null; -+ } -+ // Paper end - no-tick view distance - - // Paper start - public void onChunkAdd() { -@@ -81,147 +85,131 @@ public class ChunkHolder { - } - // Paper end - -- public ChunkHolder(ChunkPos pos, int level, LevelHeightAccessor world, LevelLightEngine lightingProvider, ChunkHolder.LevelChangeListener levelUpdateListener, ChunkHolder.PlayerProvider playersWatchingChunkProvider) { -- this.futures = new AtomicReferenceArray(ChunkHolder.CHUNK_STATUSES.size()); -- this.fullChunkFuture = ChunkHolder.UNLOADED_LEVEL_CHUNK_FUTURE; -- this.tickingChunkFuture = ChunkHolder.UNLOADED_LEVEL_CHUNK_FUTURE; -- this.entityTickingChunkFuture = ChunkHolder.UNLOADED_LEVEL_CHUNK_FUTURE; -- this.chunkToSave = CompletableFuture.completedFuture(null); // CraftBukkit - decompile error -+ public final io.papermc.paper.chunk.system.scheduling.NewChunkHolder newChunkHolder; // Paper - rewrite chunk system -+ -+ // Paper start - replace player chunk loader -+ private final com.destroystokyo.paper.util.maplist.ReferenceList playersSentChunkTo = new com.destroystokyo.paper.util.maplist.ReferenceList<>(); -+ -+ public void addPlayer(ServerPlayer player) { -+ if (!this.playersSentChunkTo.add(player)) { -+ throw new IllegalStateException("Already sent chunk " + this.pos + " in world '" + this.chunkMap.level.getWorld().getName() + "' to player " + player); -+ } -+ } -+ -+ public void removePlayer(ServerPlayer player) { -+ if (!this.playersSentChunkTo.remove(player)) { -+ throw new IllegalStateException("Have not sent chunk " + this.pos + " in world '" + this.chunkMap.level.getWorld().getName() + "' to player " + player); -+ } -+ } -+ -+ public boolean hasChunkBeenSent() { -+ return this.playersSentChunkTo.size() != 0; -+ } -+ -+ public boolean hasBeenSent(ServerPlayer to) { -+ return this.playersSentChunkTo.contains(to); -+ } -+ // Paper end - replace player chunk loader -+ public ChunkHolder(ChunkPos pos, LevelHeightAccessor world, LevelLightEngine lightingProvider, ChunkHolder.PlayerProvider playersWatchingChunkProvider, io.papermc.paper.chunk.system.scheduling.NewChunkHolder newChunkHolder) { // Paper - rewrite chunk system -+ this.newChunkHolder = newChunkHolder; // Paper - rewrite chunk system - this.chunkToSaveHistory = null; - this.blockChangedLightSectionFilter = new BitSet(); - this.skyChangedLightSectionFilter = new BitSet(); -- this.pendingFullStateConfirmation = CompletableFuture.completedFuture(null); // CraftBukkit - decompile error -- this.sendSync = CompletableFuture.completedFuture(null); // CraftBukkit - decompile error -+ // Paper - rewrite chunk system - this.pos = pos; - this.levelHeightAccessor = world; - this.lightEngine = lightingProvider; -- this.onLevelChange = levelUpdateListener; -+ this.onLevelChange = null; // Paper - rewrite chunk system - this.playerProvider = playersWatchingChunkProvider; -- this.oldTicketLevel = ChunkLevel.MAX_LEVEL + 1; -- this.ticketLevel = this.oldTicketLevel; -- this.queueLevel = this.oldTicketLevel; -- this.setTicketLevel(level); -+ // Paper - rewrite chunk system - this.changedBlocksPerSection = new ShortSet[world.getSectionsCount()]; - this.chunkMap = (ChunkMap)playersWatchingChunkProvider; // Paper - } - - // Paper start - public @Nullable ChunkAccess getAvailableChunkNow() { -- // TODO can we just getStatusFuture(EMPTY)? -- for (ChunkStatus curr = ChunkStatus.FULL, next = curr.getParent(); curr != next; curr = next, next = next.getParent()) { -- CompletableFuture> future = this.getFutureIfPresentUnchecked(curr); -- ChunkResult either = future.getNow(null); -- if (either == null || either.isSuccess()) { -- continue; -- } -- return either.orElseThrow(IllegalStateException::new); -- } -- return null; -+ return this.newChunkHolder.getCurrentChunk(); // Paper - rewrite chunk system - } - // Paper end - // CraftBukkit start - public LevelChunk getFullChunkNow() { -- // Note: We use the oldTicketLevel for isLoaded checks. -- if (!ChunkLevel.fullStatus(this.oldTicketLevel).isOrAfter(FullChunkStatus.FULL)) return null; -- return this.getFullChunkNowUnchecked(); -+ // Paper start - rewrite chunk system -+ if (!this.isFullChunkReady() || !(this.getAvailableChunkNow() instanceof LevelChunk chunk)) return null; // instanceof to avoid a race condition on off-main threads -+ return chunk; -+ // Paper end - rewrite chunk system - } - - public LevelChunk getFullChunkNowUnchecked() { -- CompletableFuture> statusFuture = this.getFutureIfPresentUnchecked(ChunkStatus.FULL); -- ChunkResult either = statusFuture.getNow(null); -- return (either == null) ? null : (LevelChunk) either.orElse(null); -+ // Paper start - rewrite chunk system -+ return this.getAvailableChunkNow() instanceof LevelChunk chunk ? chunk : null; -+ // Paper end - rewrite chunk system - } - // CraftBukkit end - - public CompletableFuture> getFutureIfPresentUnchecked(ChunkStatus leastStatus) { -- CompletableFuture> completablefuture = (CompletableFuture) this.futures.get(leastStatus.getIndex()); -- -- return completablefuture == null ? ChunkHolder.UNLOADED_CHUNK_FUTURE : completablefuture; -+ throw new UnsupportedOperationException(); // Paper - rewrite chunk system - } - - public CompletableFuture> getFutureIfPresent(ChunkStatus leastStatus) { -- return ChunkLevel.generationStatus(this.ticketLevel).isOrAfter(leastStatus) ? this.getFutureIfPresentUnchecked(leastStatus) : ChunkHolder.UNLOADED_CHUNK_FUTURE; -+ throw new UnsupportedOperationException(); // Paper - rewrite chunk system - } - - public final CompletableFuture> getTickingChunkFuture() { // Paper - final for inline -- return this.tickingChunkFuture; -+ throw new UnsupportedOperationException(); // Paper - rewrite chunk system - } - - public final CompletableFuture> getEntityTickingChunkFuture() { // Paper - final for inline -- return this.entityTickingChunkFuture; -+ throw new UnsupportedOperationException(); // Paper - rewrite chunk systemv - } - - public final CompletableFuture> getFullChunkFuture() { // Paper - final for inline -- return this.fullChunkFuture; -+ throw new UnsupportedOperationException(); // Paper - rewrite chunk systemv - } - - @Nullable - public final LevelChunk getTickingChunk() { // Paper - final for inline -- return (LevelChunk) ((ChunkResult) this.getTickingChunkFuture().getNow(ChunkHolder.UNLOADED_LEVEL_CHUNK)).orElse(null); // CraftBukkit - decompile error -+ // Paper start - rewrite chunk system -+ if (!this.isTickingReady()) { -+ return null; -+ } -+ return (LevelChunk)this.getAvailableChunkNow(); -+ // Paper end - rewrite chunk system - } - - public CompletableFuture getChunkSendSyncFuture() { -- return this.sendSync; -+ throw new UnsupportedOperationException(); // Paper - rewrite chunk system - } - - @Nullable - public LevelChunk getChunkToSend() { -- return !this.sendSync.isDone() ? null : this.getTickingChunk(); -+ return this.getSendingChunk(); // Paper - rewrite chunk system - } - - @Nullable - public ChunkStatus getLastAvailableStatus() { -- for (int i = ChunkHolder.CHUNK_STATUSES.size() - 1; i >= 0; --i) { -- ChunkStatus chunkstatus = (ChunkStatus) ChunkHolder.CHUNK_STATUSES.get(i); -- CompletableFuture> completablefuture = this.getFutureIfPresentUnchecked(chunkstatus); -- -- if (((ChunkResult) completablefuture.getNow(ChunkHolder.UNLOADED_CHUNK)).isSuccess()) { -- return chunkstatus; -- } -- } -- -- return null; -+ return this.newChunkHolder.getCurrentGenStatus(); // Paper - rewrite chunk system - } - - // Paper start - public @Nullable ChunkStatus getChunkHolderStatus() { -- for (ChunkStatus curr = ChunkStatus.FULL, next = curr.getParent(); curr != next; curr = next, next = next.getParent()) { -- CompletableFuture> future = this.getFutureIfPresentUnchecked(curr); -- ChunkResult either = future.getNow(null); -- if (either == null || !either.isSuccess()) { -- continue; -- } -- return curr; -- } -- -- return null; -+ return this.newChunkHolder.getCurrentGenStatus(); // Paper - rewrite chunk system - } - // Paper end - - @Nullable - public ChunkAccess getLastAvailable() { -- for (int i = ChunkHolder.CHUNK_STATUSES.size() - 1; i >= 0; --i) { -- ChunkStatus chunkstatus = (ChunkStatus) ChunkHolder.CHUNK_STATUSES.get(i); -- CompletableFuture> completablefuture = this.getFutureIfPresentUnchecked(chunkstatus); -- -- if (!completablefuture.isCompletedExceptionally()) { -- ChunkAccess ichunkaccess = (ChunkAccess) ((ChunkResult) completablefuture.getNow(ChunkHolder.UNLOADED_CHUNK)).orElse((Object) null); -- -- if (ichunkaccess != null) { -- return ichunkaccess; -- } -- } -- } -- -- return null; -+ return this.newChunkHolder.getCurrentChunk(); // Paper - rewrite chunk system - } - -- public final CompletableFuture getChunkToSave() { // Paper - final for inline -- return this.chunkToSave; -- } -+ // Paper - rewrite chunk system - - public void blockChanged(BlockPos pos) { -- LevelChunk chunk = this.getTickingChunk(); -+ // Paper start - replace player chunk loader -+ if (this.playersSentChunkTo.size() == 0) { -+ return; -+ } -+ // Paper end - replace player chunk loader -+ LevelChunk chunk = this.getSendingChunk(); // Paper - no-tick view distance - - if (chunk != null) { - int i = this.levelHeightAccessor.getSectionIndex(pos.getY()); -@@ -237,13 +225,13 @@ public class ChunkHolder { - } - - public void sectionLightChanged(LightLayer lightType, int y) { -- ChunkAccess ichunkaccess = (ChunkAccess) ((ChunkResult) this.getFutureIfPresent(ChunkStatus.INITIALIZE_LIGHT).getNow(ChunkHolder.UNLOADED_CHUNK)).orElse(null); // CraftBukkit - decompile error -+ ChunkAccess ichunkaccess = this.getAvailableChunkNow(); // Paper - rewrite chunk system - - if (ichunkaccess != null) { - ichunkaccess.setUnsaved(true); -- LevelChunk chunk = this.getTickingChunk(); -+ LevelChunk chunk = this.getSendingChunk(); // Paper - rewrite chunk system - -- if (chunk != null) { -+ if (this.playersSentChunkTo.size() != 0 && chunk != null) { // Paper - replace player chunk loader - int j = this.lightEngine.getMinLightSection(); - int k = this.lightEngine.getMaxLightSection(); - -@@ -263,7 +251,7 @@ public class ChunkHolder { - - // Paper start - starlight - public void broadcast(Packet packet, boolean onChunkViewEdge) { -- this.broadcast(this.playerProvider.getPlayers(this.pos, onChunkViewEdge), packet); -+ this.broadcast(this.getPlayers(onChunkViewEdge), packet); // Paper - rewrite chunk system - } - // Paper end - starlight - -@@ -273,7 +261,7 @@ public class ChunkHolder { - List list; - - if (!this.skyChangedLightSectionFilter.isEmpty() || !this.blockChangedLightSectionFilter.isEmpty()) { -- list = this.playerProvider.getPlayers(this.pos, true); -+ list = this.getPlayers(true); // Paper - rewrite chunk system - if (!list.isEmpty()) { - ClientboundLightUpdatePacket packetplayoutlightupdate = new ClientboundLightUpdatePacket(chunk.getPos(), this.lightEngine, this.skyChangedLightSectionFilter, this.blockChangedLightSectionFilter); - -@@ -285,7 +273,7 @@ public class ChunkHolder { - } - - if (this.hasChangedSections) { -- list = this.playerProvider.getPlayers(this.pos, false); -+ list = this.getPlayers(false); // Paper - rewrite chunk system - - for (int i = 0; i < this.changedBlocksPerSection.length; ++i) { - ShortSet shortset = this.changedBlocksPerSection[i]; -@@ -343,75 +331,33 @@ public class ChunkHolder { - - } - -- private void broadcast(List players, Packet packet) { -- players.forEach((entityplayer) -> { -- entityplayer.connection.send(packet); -- }); -- } -- -- public CompletableFuture> getOrScheduleFuture(ChunkStatus targetStatus, ChunkMap chunkStorage) { -- int i = targetStatus.getIndex(); -- CompletableFuture> completablefuture = (CompletableFuture) this.futures.get(i); -+ // Paper start - rewrite chunk system -+ public List getPlayers(boolean onlyOnWatchDistanceEdge) { -+ List ret = new java.util.ArrayList<>(); - -- if (completablefuture != null) { -- ChunkResult chunkresult = (ChunkResult) completablefuture.getNow(ChunkHolder.NOT_DONE_YET); -- -- if (chunkresult == null) { -- String s = String.valueOf(targetStatus); -- String s1 = "value in future for status: " + s + " was incorrectly set to null at chunk: " + String.valueOf(this.pos); -- -- throw chunkStorage.debugFuturesAndCreateReportedException(new IllegalStateException("null value previously set for chunk status"), s1); -- } -- -- if (chunkresult == ChunkHolder.NOT_DONE_YET || chunkresult.isSuccess()) { -- return completablefuture; -+ for (int i = 0, len = this.playersSentChunkTo.size(); i < len; ++i) { -+ ServerPlayer player = this.playersSentChunkTo.getUnchecked(i); -+ if (onlyOnWatchDistanceEdge && !this.chunkMap.level.playerChunkLoader.isChunkSent(player, this.pos.x, this.pos.z, onlyOnWatchDistanceEdge)) { -+ continue; - } -+ ret.add(player); - } - -- if (ChunkLevel.generationStatus(this.ticketLevel).isOrAfter(targetStatus)) { -- CompletableFuture> completablefuture1 = chunkStorage.schedule(this, targetStatus); -- -- this.updateChunkToSave(completablefuture1, "schedule " + String.valueOf(targetStatus)); -- this.futures.set(i, completablefuture1); -- return completablefuture1; -- } else { -- return completablefuture == null ? ChunkHolder.UNLOADED_CHUNK_FUTURE : completablefuture; -- } -+ return ret; - } -+ // Paper end - rewrite chunk system - -- protected void addSaveDependency(String thenDesc, CompletableFuture then) { -- if (this.chunkToSaveHistory != null) { -- this.chunkToSaveHistory.push(new ChunkHolder.ChunkSaveDebug(Thread.currentThread(), then, thenDesc)); -- } - -- this.chunkToSave = this.chunkToSave.thenCombine(then, (ichunkaccess, object) -> { -- return ichunkaccess; -- }); -- } -- -- private void updateChunkToSave(CompletableFuture> then, String thenDesc) { -- if (this.chunkToSaveHistory != null) { -- this.chunkToSaveHistory.push(new ChunkHolder.ChunkSaveDebug(Thread.currentThread(), then, thenDesc)); -- } -- -- this.chunkToSave = this.chunkToSave.thenCombine(then, (ichunkaccess, chunkresult) -> { -- return (ChunkAccess) ChunkResult.orElse(chunkresult, ichunkaccess); -+ private void broadcast(List players, Packet packet) { -+ players.forEach((entityplayer) -> { -+ entityplayer.connection.send(packet); - }); - } - -- public void addSendDependency(CompletableFuture postProcessingFuture) { -- if (this.sendSync.isDone()) { -- this.sendSync = postProcessingFuture; -- } else { -- this.sendSync = this.sendSync.thenCombine(postProcessingFuture, (object, object1) -> { -- return null; -- }); -- } -- -- } -+ // Paper - rewrite chunk system - - public FullChunkStatus getFullStatus() { -- return ChunkLevel.fullStatus(this.ticketLevel); -+ return this.newChunkHolder.getChunkStatus(); // Paper - rewrite chunk system - } - - public final ChunkPos getPos() { // Paper - final for inline -@@ -419,238 +365,17 @@ public class ChunkHolder { - } - - public final int getTicketLevel() { // Paper - final for inline -- return this.ticketLevel; -- } -- -- public int getQueueLevel() { -- return this.queueLevel; -- } -- -- private void setQueueLevel(int level) { -- this.queueLevel = level; -- } -- -- public void setTicketLevel(int level) { -- this.ticketLevel = level; -- } -- -- private void scheduleFullChunkPromotion(ChunkMap playerchunkmap, CompletableFuture> completablefuture, Executor executor, FullChunkStatus fullchunkstatus) { -- this.pendingFullStateConfirmation.cancel(false); -- CompletableFuture completablefuture1 = new CompletableFuture(); -- -- completablefuture1.thenRunAsync(() -> { -- playerchunkmap.onFullChunkStatusChange(this.pos, fullchunkstatus); -- }, executor); -- this.pendingFullStateConfirmation = completablefuture1; -- completablefuture.thenAccept((chunkresult) -> { -- chunkresult.ifSuccess((chunk) -> { -- completablefuture1.complete(null); // CraftBukkit - decompile error -- }); -- }); -- } -- -- private void demoteFullChunk(ChunkMap playerchunkmap, FullChunkStatus fullchunkstatus) { -- this.pendingFullStateConfirmation.cancel(false); -- playerchunkmap.onFullChunkStatusChange(this.pos, fullchunkstatus); -- } -- -- protected void updateFutures(ChunkMap chunkStorage, Executor executor) { -- ChunkStatus chunkstatus = ChunkLevel.generationStatus(this.oldTicketLevel); -- ChunkStatus chunkstatus1 = ChunkLevel.generationStatus(this.ticketLevel); -- boolean flag = ChunkLevel.isLoaded(this.oldTicketLevel); -- boolean flag1 = ChunkLevel.isLoaded(this.ticketLevel); -- FullChunkStatus fullchunkstatus = ChunkLevel.fullStatus(this.oldTicketLevel); -- FullChunkStatus fullchunkstatus1 = ChunkLevel.fullStatus(this.ticketLevel); -- // CraftBukkit start -- // ChunkUnloadEvent: Called before the chunk is unloaded: isChunkLoaded is still true and chunk can still be modified by plugins. -- if (fullchunkstatus.isOrAfter(FullChunkStatus.FULL) && !fullchunkstatus1.isOrAfter(FullChunkStatus.FULL)) { -- this.getFutureIfPresentUnchecked(ChunkStatus.FULL).thenAccept((either) -> { -- LevelChunk chunk = (LevelChunk) either.orElse(null); -- if (chunk != null) { -- chunkStorage.callbackExecutor.execute(() -> { -- // Minecraft will apply the chunks tick lists to the world once the chunk got loaded, and then store the tick -- // lists again inside the chunk once the chunk becomes inaccessible and set the chunk's needsSaving flag. -- // These actions may however happen deferred, so we manually set the needsSaving flag already here. -- chunk.setUnsaved(true); -- chunk.unloadCallback(); -- }); -- } -- }).exceptionally((throwable) -> { -- // ensure exceptions are printed, by default this is not the case -- MinecraftServer.LOGGER.error("Failed to schedule unload callback for chunk " + ChunkHolder.this.pos, throwable); -- return null; -- }); -- -- // Run callback right away if the future was already done -- chunkStorage.callbackExecutor.run(); -- } -- // CraftBukkit end -- -- if (flag) { -- ChunkResult chunkresult = ChunkResult.error(() -> { -- return "Unloaded ticket level " + String.valueOf(this.pos); -- }); -- -- for (int i = flag1 ? chunkstatus1.getIndex() + 1 : 0; i <= chunkstatus.getIndex(); ++i) { -- CompletableFuture> completablefuture = (CompletableFuture) this.futures.get(i); -- -- if (completablefuture == null) { -- this.futures.set(i, CompletableFuture.completedFuture(chunkresult)); -- } -- } -- } -- -- boolean flag2 = fullchunkstatus.isOrAfter(FullChunkStatus.FULL); -- boolean flag3 = fullchunkstatus1.isOrAfter(FullChunkStatus.FULL); -- -- this.wasAccessibleSinceLastSave |= flag3; -- if (!flag2 && flag3) { -- int expectCreateCount = ++this.fullChunkCreateCount; // Paper -- this.fullChunkFuture = chunkStorage.prepareAccessibleChunk(this); -- this.scheduleFullChunkPromotion(chunkStorage, this.fullChunkFuture, executor, FullChunkStatus.FULL); -- // Paper start - cache ticking ready status -- this.fullChunkFuture.thenAccept(chunkResult -> { -- chunkResult.ifSuccess(chunk -> { -- if (ChunkHolder.this.fullChunkCreateCount == expectCreateCount) { -- ChunkHolder.this.isFullChunkReady = true; -- io.papermc.paper.chunk.system.ChunkSystem.onChunkBorder(chunk, this); -- } -- }); -- }); -- this.updateChunkToSave(this.fullChunkFuture, "full"); -- } -- -- if (flag2 && !flag3) { -- // Paper start -- if (this.isFullChunkReady) { -- io.papermc.paper.chunk.system.ChunkSystem.onChunkNotBorder(this.fullChunkFuture.join().orElseThrow(IllegalStateException::new), this); // Paper -- } -- // Paper end -- this.fullChunkFuture.complete(ChunkHolder.UNLOADED_LEVEL_CHUNK); -- this.fullChunkFuture = ChunkHolder.UNLOADED_LEVEL_CHUNK_FUTURE; -- ++this.fullChunkCreateCount; // Paper - cache ticking ready status -- this.isFullChunkReady = false; // Paper - cache ticking ready status -- } -- -- boolean flag4 = fullchunkstatus.isOrAfter(FullChunkStatus.BLOCK_TICKING); -- boolean flag5 = fullchunkstatus1.isOrAfter(FullChunkStatus.BLOCK_TICKING); -- -- if (!flag4 && flag5) { -- this.tickingChunkFuture = chunkStorage.prepareTickingChunk(this); -- this.scheduleFullChunkPromotion(chunkStorage, this.tickingChunkFuture, executor, FullChunkStatus.BLOCK_TICKING); -- // Paper start - cache ticking ready status -- this.tickingChunkFuture.thenAccept(chunkResult -> { -- chunkResult.ifSuccess(chunk -> { -- // note: Here is a very good place to add callbacks to logic waiting on this. -- ChunkHolder.this.isTickingReady = true; -- io.papermc.paper.chunk.system.ChunkSystem.onChunkTicking(chunk, this); -- }); -- }); -- // Paper end -- this.updateChunkToSave(this.tickingChunkFuture, "ticking"); -- } -- -- if (flag4 && !flag5) { -- // Paper start -- if (this.isTickingReady) { -- io.papermc.paper.chunk.system.ChunkSystem.onChunkNotTicking(this.tickingChunkFuture.join().orElseThrow(IllegalStateException::new), this); // Paper -- } -- // Paper end -- this.tickingChunkFuture.complete(ChunkHolder.UNLOADED_LEVEL_CHUNK); this.isTickingReady = false; // Paper - cache chunk ticking stage -- this.tickingChunkFuture = ChunkHolder.UNLOADED_LEVEL_CHUNK_FUTURE; -- } -- -- boolean flag6 = fullchunkstatus.isOrAfter(FullChunkStatus.ENTITY_TICKING); -- boolean flag7 = fullchunkstatus1.isOrAfter(FullChunkStatus.ENTITY_TICKING); -- -- if (!flag6 && flag7) { -- if (this.entityTickingChunkFuture != ChunkHolder.UNLOADED_LEVEL_CHUNK_FUTURE) { -- throw (IllegalStateException) Util.pauseInIde(new IllegalStateException()); -- } -- -- this.entityTickingChunkFuture = chunkStorage.prepareEntityTickingChunk(this); -- this.scheduleFullChunkPromotion(chunkStorage, this.entityTickingChunkFuture, executor, FullChunkStatus.ENTITY_TICKING); -- // Paper start - cache ticking ready status -- this.entityTickingChunkFuture.thenAccept(chunkResult -> { -- chunkResult.ifSuccess(chunk -> { -- ChunkHolder.this.isEntityTickingReady = true; -- io.papermc.paper.chunk.system.ChunkSystem.onChunkEntityTicking(chunk, this); -- }); -- }); -- // Paper end -- this.updateChunkToSave(this.entityTickingChunkFuture, "entity ticking"); -- } -- -- if (flag6 && !flag7) { -- // Paper start -- if (this.isEntityTickingReady) { -- io.papermc.paper.chunk.system.ChunkSystem.onChunkNotEntityTicking(this.entityTickingChunkFuture.join().orElseThrow(IllegalStateException::new), this); -- } -- // Paper end -- this.entityTickingChunkFuture.complete(ChunkHolder.UNLOADED_LEVEL_CHUNK); this.isEntityTickingReady = false; // Paper - cache chunk ticking stage -- this.entityTickingChunkFuture = ChunkHolder.UNLOADED_LEVEL_CHUNK_FUTURE; -- } -- -- if (!fullchunkstatus1.isOrAfter(fullchunkstatus)) { -- this.demoteFullChunk(chunkStorage, fullchunkstatus1); -- } -- -- this.onLevelChange.onLevelChange(this.pos, this::getQueueLevel, this.ticketLevel, this::setQueueLevel); -- this.oldTicketLevel = this.ticketLevel; -- // CraftBukkit start -- // ChunkLoadEvent: Called after the chunk is loaded: isChunkLoaded returns true and chunk is ready to be modified by plugins. -- if (!fullchunkstatus.isOrAfter(FullChunkStatus.FULL) && fullchunkstatus1.isOrAfter(FullChunkStatus.FULL)) { -- this.getFutureIfPresentUnchecked(ChunkStatus.FULL).thenAccept((either) -> { -- LevelChunk chunk = (LevelChunk) either.orElse(null); -- if (chunk != null) { -- chunkStorage.callbackExecutor.execute(() -> { -- chunk.loadCallback(); -- }); -- } -- }).exceptionally((throwable) -> { -- // ensure exceptions are printed, by default this is not the case -- MinecraftServer.LOGGER.error("Failed to schedule load callback for chunk " + ChunkHolder.this.pos, throwable); -- return null; -- }); -- -- // Run callback right away if the future was already done -- chunkStorage.callbackExecutor.run(); -- } -- // CraftBukkit end -- } -- -- public boolean wasAccessibleSinceLastSave() { -- return this.wasAccessibleSinceLastSave; -+ return this.newChunkHolder.getTicketLevel(); // Paper - rewrite chunk system - } - -- public void refreshAccessibility() { -- this.wasAccessibleSinceLastSave = ChunkLevel.fullStatus(this.ticketLevel).isOrAfter(FullChunkStatus.FULL); -- } -+ // Paper - rewrite chunk system - - public void replaceProtoChunk(ImposterProtoChunk chunk) { -- for (int i = 0; i < this.futures.length(); ++i) { -- CompletableFuture> completablefuture = (CompletableFuture) this.futures.get(i); -- -- if (completablefuture != null) { -- ChunkAccess ichunkaccess = (ChunkAccess) ((ChunkResult) completablefuture.getNow(ChunkHolder.UNLOADED_CHUNK)).orElse((Object) null); -- -- if (ichunkaccess instanceof ProtoChunk) { -- this.futures.set(i, CompletableFuture.completedFuture(ChunkResult.of(chunk))); -- } -- } -- } -- -- this.updateChunkToSave(CompletableFuture.completedFuture(ChunkResult.of(chunk.getWrapped())), "replaceProto"); -+ throw new UnsupportedOperationException(); // Paper - rewrite chunk system - } - - public List>>> getAllFutures() { -- List>>> list = new ArrayList(); -- -- for (int i = 0; i < ChunkHolder.CHUNK_STATUSES.size(); ++i) { -- list.add(Pair.of((ChunkStatus) ChunkHolder.CHUNK_STATUSES.get(i), (CompletableFuture) this.futures.get(i))); -- } -- -- return list; -+ throw new UnsupportedOperationException(); // Paper - rewrite chunk system - } - - @FunctionalInterface -@@ -670,15 +395,15 @@ public class ChunkHolder { - - // Paper start - public final boolean isEntityTickingReady() { -- return this.isEntityTickingReady; -+ return this.newChunkHolder.isEntityTickingReady(); // Paper - rewrite chunk system - } - - public final boolean isTickingReady() { -- return this.isTickingReady; -+ return this.newChunkHolder.isTickingReady(); // Paper - rewrite chunk system - } - - public final boolean isFullChunkReady() { -- return this.isFullChunkReady; -+ return this.newChunkHolder.isFullChunkReady(); // Paper - rewrite chunk system - } - // Paper end - } -diff --git a/src/main/java/net/minecraft/server/level/ChunkMap.java b/src/main/java/net/minecraft/server/level/ChunkMap.java -index d3f63185edd1db9fab3887ea3f08982435b3a23c..d6ecee1db17cb9eaeffa94b3d8dd150238fdefe5 100644 ---- a/src/main/java/net/minecraft/server/level/ChunkMap.java -+++ b/src/main/java/net/minecraft/server/level/ChunkMap.java -@@ -122,10 +122,7 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider - public static final int MIN_VIEW_DISTANCE = 2; - public static final int MAX_VIEW_DISTANCE = 32; - public static final int FORCED_TICKET_LEVEL = ChunkLevel.byStatus(FullChunkStatus.ENTITY_TICKING); -- public final Long2ObjectLinkedOpenHashMap updatingChunkMap = new Long2ObjectLinkedOpenHashMap(); -- public volatile Long2ObjectLinkedOpenHashMap visibleChunkMap; -- private final Long2ObjectLinkedOpenHashMap pendingUnloads; -- private final LongSet entitiesInLevel; -+ // Paper - rewrite chunk system - public final ServerLevel level; - private final ThreadedLevelLightEngine lightEngine; - public final BlockableEventLoop mainThreadExecutor; // Paper - public -@@ -134,15 +131,13 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider - private final ChunkGeneratorStructureState chunkGeneratorState; - public final Supplier overworldDataStorage; - private final PoiManager poiManager; -- public final LongSet toDrop; -+ // Paper - rewrite chunk system - private boolean modified; -- private final ChunkTaskPriorityQueueSorter queueSorter; -- private final ProcessorHandle> worldgenMailbox; -- private final ProcessorHandle> mainThreadMailbox; -+ // Paper - rewrite chunk system - public final ChunkProgressListener progressListener; - private final ChunkStatusUpdateListener chunkStatusListener; - public final ChunkMap.ChunkDistanceManager distanceManager; -- private final AtomicInteger tickingGenerated; -+ public final AtomicInteger tickingGenerated; // Paper - public - private final String storageName; - private final PlayerMap playerMap; - public final Int2ObjectMap entityMap; -@@ -150,28 +145,7 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider - private final Long2LongMap chunkSaveCooldowns; - private final Queue unloadQueue; - public int serverViewDistance; -- private WorldGenContext worldGenContext; -- -- // CraftBukkit start - recursion-safe executor for Chunk loadCallback() and unloadCallback() -- public final CallbackExecutor callbackExecutor = new CallbackExecutor(); -- public static final class CallbackExecutor implements java.util.concurrent.Executor, Runnable { -- -- private final java.util.Queue queue = new java.util.ArrayDeque<>(); -- -- @Override -- public void execute(Runnable runnable) { -- this.queue.add(runnable); -- } -- -- @Override -- public void run() { -- Runnable task; -- while ((task = this.queue.poll()) != null) { -- task.run(); -- } -- } -- }; -- // CraftBukkit end -+ private WorldGenContext worldGenContext; public final WorldGenContext getWorldGenContext() { return this.worldGenContext; } // Paper - rewrite chunk system - - // Paper start - distance maps - private final com.destroystokyo.paper.util.misc.PooledLinkedHashSets pooledLinkedPlayerHashSets = new com.destroystokyo.paper.util.misc.PooledLinkedHashSets<>(); -@@ -181,6 +155,7 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider - int chunkZ = io.papermc.paper.util.MCUtil.getChunkCoordinate(player.getZ()); - // Note: players need to be explicitly added to distance maps before they can be updated - this.nearbyPlayers.addPlayer(player); -+ this.level.playerChunkLoader.addPlayer(player); // Paper - replace chunk loader - } - - void removePlayerFromDistanceMaps(ServerPlayer player) { -@@ -188,6 +163,7 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider - int chunkZ = io.papermc.paper.util.MCUtil.getChunkCoordinate(player.getZ()); - // Note: players need to be explicitly added to distance maps before they can be updated - this.nearbyPlayers.removePlayer(player); -+ this.level.playerChunkLoader.removePlayer(player); // Paper - replace chunk loader - } - - void updateMaps(ServerPlayer player) { -@@ -195,6 +171,7 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider - int chunkZ = io.papermc.paper.util.MCUtil.getChunkCoordinate(player.getZ()); - // Note: players need to be explicitly added to distance maps before they can be updated - this.nearbyPlayers.tickPlayer(player); -+ this.level.playerChunkLoader.updatePlayer(player); // Paper - replace chunk loader - } - // Paper end - // Paper start -@@ -224,17 +201,14 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider - } - - public final ChunkHolder getUnloadingChunkHolder(int chunkX, int chunkZ) { -- return this.pendingUnloads.get(io.papermc.paper.util.CoordinateUtils.getChunkKey(chunkX, chunkZ)); -+ return null; // Paper - rewrite chunk system - } - public final io.papermc.paper.util.player.NearbyPlayers nearbyPlayers; - // Paper end - - public ChunkMap(ServerLevel world, LevelStorageSource.LevelStorageAccess session, DataFixer dataFixer, StructureTemplateManager structureTemplateManager, Executor executor, BlockableEventLoop mainThreadExecutor, LightChunkGetter chunkProvider, ChunkGenerator chunkGenerator, ChunkProgressListener worldGenerationProgressListener, ChunkStatusUpdateListener chunkStatusChangeListener, Supplier persistentStateManagerFactory, int viewDistance, boolean dsync) { - super(new RegionStorageInfo(session.getLevelId(), world.dimension(), "chunk"), session.getDimensionPath(world.dimension()).resolve("region"), dataFixer, dsync); -- this.visibleChunkMap = this.updatingChunkMap.clone(); -- this.pendingUnloads = new Long2ObjectLinkedOpenHashMap(); -- this.entitiesInLevel = new LongOpenHashSet(); -- this.toDrop = new LongOpenHashSet(); -+ // Paper - rewrite chunk system - this.tickingGenerated = new AtomicInteger(); - this.playerMap = new PlayerMap(); - this.entityMap = new Int2ObjectOpenHashMap(); -@@ -263,19 +237,17 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider - - this.chunkGeneratorState = chunkGenerator.createState(iregistrycustom.lookupOrThrow(Registries.STRUCTURE_SET), this.randomState, j, world.spigotConfig); // Spigot - this.mainThreadExecutor = mainThreadExecutor; -- ProcessorMailbox threadedmailbox = ProcessorMailbox.create(executor, "worldgen"); -+ // Paper - rewrite chunk system - - Objects.requireNonNull(mainThreadExecutor); -- ProcessorHandle mailbox = ProcessorHandle.of("main", mainThreadExecutor::tell); -+ // Paper - rewrite chunk system - - this.progressListener = worldGenerationProgressListener; - this.chunkStatusListener = chunkStatusChangeListener; -- ProcessorMailbox threadedmailbox1 = ProcessorMailbox.create(executor, "light"); -+ // Paper - rewrite chunk system - -- this.queueSorter = new ChunkTaskPriorityQueueSorter(ImmutableList.of(threadedmailbox, mailbox, threadedmailbox1), executor, Integer.MAX_VALUE); -- this.worldgenMailbox = this.queueSorter.getProcessor(threadedmailbox, false); -- this.mainThreadMailbox = this.queueSorter.getProcessor(mailbox, false); -- this.lightEngine = new ThreadedLevelLightEngine(chunkProvider, this, this.level.dimensionType().hasSkyLight(), threadedmailbox1, this.queueSorter.getProcessor(threadedmailbox1, false)); -+ // Paper - rewrite chunk system -+ this.lightEngine = new ThreadedLevelLightEngine(chunkProvider, this, this.level.dimensionType().hasSkyLight(), null, null); // Paper - rewrite chunk system - this.distanceManager = new ChunkMap.ChunkDistanceManager(executor, mainThreadExecutor); - this.overworldDataStorage = persistentStateManagerFactory; - this.poiManager = new PoiManager(new RegionStorageInfo(session.getLevelId(), world.dimension(), "poi"), path.resolve("poi"), dataFixer, dsync, iregistrycustom, world); -@@ -333,23 +305,15 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider - } - - boolean isChunkTracked(ServerPlayer player, int chunkX, int chunkZ) { -- return player.getChunkTrackingView().contains(chunkX, chunkZ) && !player.connection.chunkSender.isPending(ChunkPos.asLong(chunkX, chunkZ)); -+ // Paper start - rewrite player chunk loader -+ return this.level.playerChunkLoader.isChunkSent(player, chunkX, chunkZ); -+ // Paper end - rewrite player chunk loader - } - - private boolean isChunkOnTrackedBorder(ServerPlayer player, int chunkX, int chunkZ) { -- if (!this.isChunkTracked(player, chunkX, chunkZ)) { -- return false; -- } else { -- for (int k = -1; k <= 1; ++k) { -- for (int l = -1; l <= 1; ++l) { -- if ((k != 0 || l != 0) && !this.isChunkTracked(player, chunkX + k, chunkZ + l)) { -- return true; -- } -- } -- } -- -- return false; -- } -+ // Paper start - rewrite player chunk loader -+ return this.level.playerChunkLoader.isChunkSent(player, chunkX, chunkZ, true); -+ // Paper end - rewrite player chunk loader - } - - protected ThreadedLevelLightEngine getLightEngine() { -@@ -358,20 +322,22 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider - - @Nullable - protected ChunkHolder getUpdatingChunkIfPresent(long pos) { -- return (ChunkHolder) this.updatingChunkMap.get(pos); -+ // Paper start - rewrite chunk system -+ io.papermc.paper.chunk.system.scheduling.NewChunkHolder holder = this.level.chunkTaskScheduler.chunkHolderManager.getChunkHolder(pos); -+ return holder == null ? null : holder.vanillaChunkHolder; -+ // Paper end - rewrite chunk system - } - - @Nullable - public ChunkHolder getVisibleChunkIfPresent(long pos) { -- return (ChunkHolder) this.visibleChunkMap.get(pos); -+ // Paper start - rewrite chunk system -+ io.papermc.paper.chunk.system.scheduling.NewChunkHolder holder = this.level.chunkTaskScheduler.chunkHolderManager.getChunkHolder(pos); -+ return holder == null ? null : holder.vanillaChunkHolder; -+ // Paper end - rewrite chunk system - } - - protected IntSupplier getChunkQueueLevel(long pos) { -- return () -> { -- ChunkHolder playerchunk = this.getVisibleChunkIfPresent(pos); -- -- return playerchunk == null ? ChunkTaskPriorityQueue.PRIORITY_LEVEL_COUNT - 1 : Math.min(playerchunk.getQueueLevel(), ChunkTaskPriorityQueue.PRIORITY_LEVEL_COUNT - 1); -- }; -+ throw new UnsupportedOperationException(); // Paper - rewrite chunk system - } - - public String getChunkDebugData(ChunkPos chunkPos) { -@@ -400,80 +366,7 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider - } - - private CompletableFuture>> getChunkRangeFuture(ChunkHolder centerChunk, int margin, IntFunction distanceToStatus) { -- if (margin == 0) { -- ChunkStatus chunkstatus = (ChunkStatus) distanceToStatus.apply(0); -- -- return centerChunk.getOrScheduleFuture(chunkstatus, this).thenApply((chunkresult) -> { -- return chunkresult.map(List::of); -- }); -- } else { -- List>> list = new ArrayList(); -- List list1 = new ArrayList(); -- ChunkPos chunkcoordintpair = centerChunk.getPos(); -- int j = chunkcoordintpair.x; -- int k = chunkcoordintpair.z; -- -- for (int l = -margin; l <= margin; ++l) { -- for (int i1 = -margin; i1 <= margin; ++i1) { -- int j1 = Math.max(Math.abs(i1), Math.abs(l)); -- ChunkPos chunkcoordintpair1 = new ChunkPos(j + i1, k + l); -- long k1 = chunkcoordintpair1.toLong(); -- ChunkHolder playerchunk1 = this.getUpdatingChunkIfPresent(k1); -- -- if (playerchunk1 == null) { -- return CompletableFuture.completedFuture(ChunkResult.error(() -> { -- return "Unloaded " + String.valueOf(chunkcoordintpair1); -- })); -- } -- -- ChunkStatus chunkstatus1 = (ChunkStatus) distanceToStatus.apply(j1); -- CompletableFuture> completablefuture = playerchunk1.getOrScheduleFuture(chunkstatus1, this); -- -- list1.add(playerchunk1); -- list.add(completablefuture); -- } -- } -- -- CompletableFuture>> completablefuture1 = Util.sequence(list); -- CompletableFuture>> completablefuture2 = completablefuture1.thenApply((list2) -> { -- List list3 = Lists.newArrayList(); -- // CraftBukkit start - decompile error -- int cnt = 0; -- -- for (Iterator iterator = list2.iterator(); iterator.hasNext(); ++cnt) { -- final int l1 = cnt; -- // CraftBukkit end -- ChunkResult chunkresult = (ChunkResult) iterator.next(); -- -- if (chunkresult == null) { -- throw this.debugFuturesAndCreateReportedException(new IllegalStateException("At least one of the chunk futures were null"), "n/a"); -- } -- -- ChunkAccess ichunkaccess = (ChunkAccess) chunkresult.orElse(null); // CraftBukkit - decompile error -- -- if (ichunkaccess == null) { -- return ChunkResult.error(() -> { -- String s = String.valueOf(new ChunkPos(j + l1 % (margin * 2 + 1), k + l1 / (margin * 2 + 1))); -- -- return "Unloaded " + s + " " + chunkresult.getError(); -- }); -- } -- -- list3.add(ichunkaccess); -- } -- -- return ChunkResult.of(list3); -- }); -- Iterator iterator = list1.iterator(); -- -- while (iterator.hasNext()) { -- ChunkHolder playerchunk2 = (ChunkHolder) iterator.next(); -- -- playerchunk2.addSaveDependency("getChunkRangeFuture " + String.valueOf(chunkcoordintpair) + " " + margin, completablefuture2); -- } -- -- return completablefuture2; -- } -+ throw new UnsupportedOperationException(); // Paper - rewrite chunk system - } - - public ReportedException debugFuturesAndCreateReportedException(IllegalStateException exception, String details) { -@@ -503,263 +396,72 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider - } - - public CompletableFuture> prepareEntityTickingChunk(ChunkHolder chunk) { -- return this.getChunkRangeFuture(chunk, 2, (i) -> { -- return ChunkStatus.FULL; -- }).thenApplyAsync((chunkresult) -> { -- return chunkresult.map((list) -> { -- return (LevelChunk) list.get(list.size() / 2); -- }); -- }, this.mainThreadExecutor); -+ throw new UnsupportedOperationException(); // Paper - rewrite chunk system - } - - @Nullable - ChunkHolder updateChunkScheduling(long pos, int level, @Nullable ChunkHolder holder, int k) { -- if (!ChunkLevel.isLoaded(k) && !ChunkLevel.isLoaded(level)) { -- return holder; -- } else { -- if (holder != null) { -- holder.setTicketLevel(level); -- } -- -- if (holder != null) { -- if (!ChunkLevel.isLoaded(level)) { -- this.toDrop.add(pos); -- } else { -- this.toDrop.remove(pos); -- } -- } -- -- if (ChunkLevel.isLoaded(level) && holder == null) { -- holder = (ChunkHolder) this.pendingUnloads.remove(pos); -- if (holder != null) { -- holder.setTicketLevel(level); -- } else { -- holder = new ChunkHolder(new ChunkPos(pos), level, this.level, this.lightEngine, this.queueSorter, this); -- // Paper start -- io.papermc.paper.chunk.system.ChunkSystem.onChunkHolderCreate(this.level, holder); -- // Paper end -- } -- -- // Paper start -- holder.onChunkAdd(); -- // Paper end -- this.updatingChunkMap.put(pos, holder); -- this.modified = true; -- } -- -- return holder; -- } -+ throw new UnsupportedOperationException(); // Paper - rewrite chunk system - } - - @Override - public void close() throws IOException { -- try { -- this.queueSorter.close(); -- this.poiManager.close(); -- } finally { -- super.close(); -- } -+ throw new UnsupportedOperationException("Use ServerChunkCache#close"); // Paper - rewrite chunk system -+ } - -+ // Paper start - rewrite chunk system -+ protected void saveIncrementally() { -+ this.level.chunkTaskScheduler.chunkHolderManager.autoSave(); // Paper - rewrite chunk system - } -+ // Paper end - - rewrite chunk system - - protected void saveAllChunks(boolean flush) { -- if (flush) { -- List list = io.papermc.paper.chunk.system.ChunkSystem.getVisibleChunkHolders(this.level).stream().filter(ChunkHolder::wasAccessibleSinceLastSave).peek(ChunkHolder::refreshAccessibility).toList(); // Paper -- MutableBoolean mutableboolean = new MutableBoolean(); -- -- do { -- mutableboolean.setFalse(); -- list.stream().map((playerchunk) -> { -- CompletableFuture completablefuture; -- -- do { -- completablefuture = playerchunk.getChunkToSave(); -- BlockableEventLoop iasynctaskhandler = this.mainThreadExecutor; -- -- Objects.requireNonNull(completablefuture); -- iasynctaskhandler.managedBlock(completablefuture::isDone); -- } while (completablefuture != playerchunk.getChunkToSave()); -- -- return (ChunkAccess) completablefuture.join(); -- }).filter((ichunkaccess) -> { -- return ichunkaccess instanceof ImposterProtoChunk || ichunkaccess instanceof LevelChunk; -- }).filter(this::save).forEach((ichunkaccess) -> { -- mutableboolean.setTrue(); -- }); -- } while (mutableboolean.isTrue()); -- -- this.processUnloads(() -> { -- return true; -- }); -- this.flushWorker(); -- } else { -- io.papermc.paper.chunk.system.ChunkSystem.getVisibleChunkHolders(this.level).forEach(this::saveChunkIfNeeded); -- } -- -+ this.level.chunkTaskScheduler.chunkHolderManager.saveAllChunks(flush, false, false); // Paper - rewrite chunk system - } - - protected void tick(BooleanSupplier shouldKeepTicking) { - ProfilerFiller gameprofilerfiller = this.level.getProfiler(); - -+ try (Timing ignored = this.level.timings.poiUnload.startTiming()) { // Paper - gameprofilerfiller.push("poi"); - this.poiManager.tick(shouldKeepTicking); -+ } // Paper - gameprofilerfiller.popPush("chunk_unload"); - if (!this.level.noSave()) { -+ try (Timing ignored = this.level.timings.chunkUnload.startTiming()) { // Paper - this.processUnloads(shouldKeepTicking); -+ } // Paper - } - - gameprofilerfiller.pop(); - } - - public boolean hasWork() { -- return this.lightEngine.hasLightWork() || !this.pendingUnloads.isEmpty() || io.papermc.paper.chunk.system.ChunkSystem.hasAnyChunkHolders(this.level) || this.poiManager.hasWork() || !this.toDrop.isEmpty() || !this.unloadQueue.isEmpty() || this.queueSorter.hasWork() || this.distanceManager.hasTickets(); // Paper -+ throw new UnsupportedOperationException(); // Paper - rewrite chunk system - } - - private void processUnloads(BooleanSupplier shouldKeepTicking) { -- LongIterator longiterator = this.toDrop.iterator(); -- -- for (int i = 0; longiterator.hasNext() && (shouldKeepTicking.getAsBoolean() || i < 200 || this.toDrop.size() > 2000); longiterator.remove()) { -- long j = longiterator.nextLong(); -- ChunkHolder playerchunk = (ChunkHolder) this.updatingChunkMap.remove(j); -- -- if (playerchunk != null) { -- playerchunk.onChunkRemove(); // Paper -- this.pendingUnloads.put(j, playerchunk); -- this.modified = true; -- ++i; -- this.scheduleUnload(j, playerchunk); -- } -- } -- -- int k = Math.max(0, this.unloadQueue.size() - 2000); -- -- Runnable runnable; -- -- while ((shouldKeepTicking.getAsBoolean() || k > 0) && (runnable = (Runnable) this.unloadQueue.poll()) != null) { -- --k; -- runnable.run(); -- } -- -- int l = 0; -- Iterator objectiterator = io.papermc.paper.chunk.system.ChunkSystem.getVisibleChunkHolders(this.level).iterator(); // Paper -- -- while (l < 20 && shouldKeepTicking.getAsBoolean() && objectiterator.hasNext()) { -- if (this.saveChunkIfNeeded((ChunkHolder) objectiterator.next())) { -- ++l; -- } -- } -+ this.level.chunkTaskScheduler.chunkHolderManager.processUnloads(); // Paper - rewrite chunk system - - } - - private void scheduleUnload(long pos, ChunkHolder holder) { -- CompletableFuture completablefuture = holder.getChunkToSave(); -- Consumer consumer = (ichunkaccess) -> { // CraftBukkit - decompile error -- CompletableFuture completablefuture1 = holder.getChunkToSave(); -- -- if (completablefuture1 != completablefuture) { -- this.scheduleUnload(pos, holder); -- } else { -- // Paper start -- boolean removed; -- if ((removed = this.pendingUnloads.remove(pos, holder)) && ichunkaccess != null) { -- io.papermc.paper.chunk.system.ChunkSystem.onChunkHolderDelete(this.level, holder); -- // Paper end -- if (ichunkaccess instanceof LevelChunk) { -- ((LevelChunk) ichunkaccess).setLoaded(false); -- } -- -- this.save(ichunkaccess); -- if (this.entitiesInLevel.remove(pos) && ichunkaccess instanceof LevelChunk) { -- LevelChunk chunk = (LevelChunk) ichunkaccess; -- -- this.level.unload(chunk); -- } -- -- this.lightEngine.updateChunkStatus(ichunkaccess.getPos()); -- this.lightEngine.tryScheduleUpdate(); -- this.progressListener.onStatusChange(ichunkaccess.getPos(), (ChunkStatus) null); -- this.chunkSaveCooldowns.remove(ichunkaccess.getPos().toLong()); -- } else if (removed) { // Paper start -- io.papermc.paper.chunk.system.ChunkSystem.onChunkHolderDelete(this.level, holder); -- } // Paper end -- -- } -- }; -- Queue queue = this.unloadQueue; -- -- Objects.requireNonNull(this.unloadQueue); -- completablefuture.thenAcceptAsync(consumer, queue::add).whenComplete((ovoid, throwable) -> { -- if (throwable != null) { -- ChunkMap.LOGGER.error("Failed to save chunk {}", holder.getPos(), throwable); -- } -- -- }); -+ throw new UnsupportedOperationException(); // Paper - rewrite chunk system - } - - protected boolean promoteChunkMap() { -- if (!this.modified) { -- return false; -- } else { -- this.visibleChunkMap = this.updatingChunkMap.clone(); -- this.modified = false; -- return true; -- } -+ throw new UnsupportedOperationException(); // Paper - rewrite chunk system - } - - public CompletableFuture> schedule(ChunkHolder holder, ChunkStatus requiredStatus) { -- ChunkPos chunkcoordintpair = holder.getPos(); -- -- if (requiredStatus == ChunkStatus.EMPTY) { -- return this.scheduleChunkLoad(chunkcoordintpair).thenApply(ChunkResult::of); -- } else { -- if (requiredStatus == ChunkStatus.LIGHT) { -- this.distanceManager.addTicket(TicketType.LIGHT, chunkcoordintpair, ChunkLevel.byStatus(ChunkStatus.LIGHT), chunkcoordintpair); -- } -- -- if (!requiredStatus.hasLoadDependencies()) { -- ChunkAccess ichunkaccess = (ChunkAccess) ((ChunkResult) holder.getOrScheduleFuture(requiredStatus.getParent(), this).getNow(ChunkHolder.UNLOADED_CHUNK)).orElse((Object) null); -- -- if (ichunkaccess != null && ichunkaccess.getStatus().isOrAfter(requiredStatus)) { -- CompletableFuture completablefuture = requiredStatus.load(this.worldGenContext, (ichunkaccess1) -> { -- return this.protoChunkToFullChunk(holder, ichunkaccess1); -- }, ichunkaccess); -- -- this.progressListener.onStatusChange(chunkcoordintpair, requiredStatus); -- return completablefuture.thenApply(ChunkResult::of); -- } -- } -- -- return this.scheduleChunkGeneration(holder, requiredStatus); -- } -+ throw new UnsupportedOperationException(); // Paper - rewrite chunk system - } - - private CompletableFuture scheduleChunkLoad(ChunkPos pos) { -- return this.readChunk(pos).thenApply((optional) -> { -- return optional.filter((nbttagcompound) -> { -- boolean flag = ChunkMap.isChunkDataValid(nbttagcompound); -- -- if (!flag) { -- ChunkMap.LOGGER.error("Chunk file at {} is missing level data, skipping", pos); -- } -- -- return flag; -- }); -- }).thenApplyAsync((optional) -> { -- this.level.getProfiler().incrementCounter("chunkLoad"); -- if (optional.isPresent()) { -- ProtoChunk protochunk = ChunkSerializer.read(this.level, this.poiManager, pos, (CompoundTag) optional.get()); -- -- this.markPosition(pos, protochunk.getStatus().getChunkType()); -- return protochunk; -- } else { -- return this.createEmptyChunk(pos); -- } -- }, this.mainThreadExecutor).exceptionallyAsync((throwable) -> { -- return this.handleChunkLoadFailure(throwable, pos); -- }, this.mainThreadExecutor); -+ throw new UnsupportedOperationException(); // Paper - rewrite chunk system - } - -- private static boolean isChunkDataValid(CompoundTag nbt) { -+ public static boolean isChunkDataValid(CompoundTag nbt) { // Paper - async chunk loading - return nbt.contains("Status", 8); - } - -@@ -816,60 +518,7 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider - } - - private CompletableFuture> scheduleChunkGeneration(ChunkHolder holder, ChunkStatus requiredStatus) { -- ChunkPos chunkcoordintpair = holder.getPos(); -- CompletableFuture>> completablefuture = this.getChunkRangeFuture(holder, requiredStatus.getRange(), (i) -> { -- return this.getDependencyStatus(requiredStatus, i); -- }); -- -- this.level.getProfiler().incrementCounter(() -> { -- return "chunkGenerate " + String.valueOf(requiredStatus); -- }); -- Executor executor = (runnable) -> { -- this.worldgenMailbox.tell(ChunkTaskPriorityQueueSorter.message(holder, runnable)); -- }; -- -- return completablefuture.thenComposeAsync((chunkresult) -> { -- List list = (List) chunkresult.orElse(null); // CraftBukkit - decompile error -- -- if (list == null) { -- this.releaseLightTicket(chunkcoordintpair); -- Objects.requireNonNull(chunkresult); -- return CompletableFuture.completedFuture(ChunkResult.error(chunkresult::getError)); -- } else { -- try { -- ChunkAccess ichunkaccess = (ChunkAccess) list.get(list.size() / 2); -- CompletableFuture completablefuture1; -- -- if (ichunkaccess.getStatus().isOrAfter(requiredStatus)) { -- completablefuture1 = requiredStatus.load(this.worldGenContext, (ichunkaccess1) -> { -- return this.protoChunkToFullChunk(holder, ichunkaccess1); -- }, ichunkaccess); -- } else { -- completablefuture1 = requiredStatus.generate(this.worldGenContext, executor, (ichunkaccess1) -> { -- return this.protoChunkToFullChunk(holder, ichunkaccess1); -- }, list); -- } -- -- this.progressListener.onStatusChange(chunkcoordintpair, requiredStatus); -- return completablefuture1.thenApply(ChunkResult::of); -- } catch (Exception exception) { -- exception.getStackTrace(); -- CrashReport crashreport = CrashReport.forThrowable(exception, "Exception generating new chunk"); -- CrashReportCategory crashreportsystemdetails = crashreport.addCategory("Chunk to be generated"); -- -- crashreportsystemdetails.setDetail("Status being generated", () -> { -- return BuiltInRegistries.CHUNK_STATUS.getKey(requiredStatus).toString(); -- }); -- crashreportsystemdetails.setDetail("Location", (Object) String.format(Locale.ROOT, "%d,%d", chunkcoordintpair.x, chunkcoordintpair.z)); -- crashreportsystemdetails.setDetail("Position hash", (Object) ChunkPos.asLong(chunkcoordintpair.x, chunkcoordintpair.z)); -- crashreportsystemdetails.setDetail("Generator", (Object) this.generator); -- this.mainThreadExecutor.execute(() -> { -- throw new ReportedException(crashreport); -- }); -- throw new ReportedException(crashreport); -- } -- } -- }, executor); -+ throw new UnsupportedOperationException(); // Paper - rewrite chunk system - } - - protected void releaseLightTicket(ChunkPos pos) { -@@ -880,7 +529,7 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider - })); - } - -- private ChunkStatus getDependencyStatus(ChunkStatus centerChunkTargetStatus, int distance) { -+ public static ChunkStatus getDependencyStatus(ChunkStatus centerChunkTargetStatus, int distance) { // Paper -> public, static - ChunkStatus chunkstatus1; - - if (distance == 0) { -@@ -892,7 +541,7 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider - return chunkstatus1; - } - -- private static void postLoadProtoChunk(ServerLevel world, List nbt) { -+ public static void postLoadProtoChunk(ServerLevel world, List nbt, ChunkPos position) { // Paper - public and add chunk position parameter - if (!nbt.isEmpty()) { - // CraftBukkit start - these are spawned serialized (DefinedStructure) and we don't call an add event below at the moment due to ordering complexities - world.addWorldGenChunkEntities(EntityType.loadEntitiesRecursive(nbt, world).filter((entity) -> { -@@ -908,45 +557,14 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider - } - checkDupeUUID(world, entity); // Paper - duplicate uuid resolving - return !needsRemoval; -- })); -+ }), position); // Paper - rewrite chunk system - // CraftBukkit end - } - - } - - private CompletableFuture protoChunkToFullChunk(ChunkHolder playerchunk, ChunkAccess ichunkaccess) { -- return CompletableFuture.supplyAsync(() -> { -- ChunkPos chunkcoordintpair = playerchunk.getPos(); -- ProtoChunk protochunk = (ProtoChunk) ichunkaccess; -- LevelChunk chunk; -- -- if (protochunk instanceof ImposterProtoChunk) { -- chunk = ((ImposterProtoChunk) protochunk).getWrapped(); -- } else { -- chunk = new LevelChunk(this.level, protochunk, (chunk1) -> { -- ChunkMap.postLoadProtoChunk(this.level, protochunk.getEntities()); -- }); -- playerchunk.replaceProtoChunk(new ImposterProtoChunk(chunk, false)); -- } -- -- chunk.setFullStatus(() -> { -- return ChunkLevel.fullStatus(playerchunk.getTicketLevel()); -- }); -- chunk.runPostLoad(); -- if (this.entitiesInLevel.add(chunkcoordintpair.toLong())) { -- chunk.setLoaded(true); -- chunk.registerAllBlockEntitiesAfterLevelLoad(); -- chunk.registerTickContainerInLevel(this.level); -- } -- -- return chunk; -- }, (runnable) -> { -- ProcessorHandle mailbox = this.mainThreadMailbox; -- long i = playerchunk.getPos().toLong(); -- -- Objects.requireNonNull(playerchunk); -- mailbox.tell(ChunkTaskPriorityQueueSorter.message(runnable, i, playerchunk::getTicketLevel)); -- }); -+ throw new UnsupportedOperationException(); // Paper - rewrite chunk system - } - - // Paper start - duplicate uuid resolving -@@ -990,61 +608,16 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider - // Paper end - duplicate uuid resolving - - public CompletableFuture> prepareTickingChunk(ChunkHolder holder) { -- CompletableFuture>> completablefuture = this.getChunkRangeFuture(holder, 1, (i) -> { -- return ChunkStatus.FULL; -- }); -- CompletableFuture> completablefuture1 = completablefuture.thenApplyAsync((chunkresult) -> { -- return chunkresult.map((list) -> { -- return (LevelChunk) list.get(list.size() / 2); -- }); -- }, (runnable) -> { -- this.mainThreadMailbox.tell(ChunkTaskPriorityQueueSorter.message(holder, runnable)); -- }).thenApplyAsync((chunkresult) -> { -- return chunkresult.ifSuccess((chunk) -> { -- chunk.postProcessGeneration(); -- this.level.startTickingChunk(chunk); -- CompletableFuture completablefuture2 = holder.getChunkSendSyncFuture(); -- -- if (completablefuture2.isDone()) { -- this.onChunkReadyToSend(chunk); -- } else { -- completablefuture2.thenAcceptAsync((object) -> { -- this.onChunkReadyToSend(chunk); -- }, this.mainThreadExecutor); -- } -- -- }); -- }, this.mainThreadExecutor); -- -- completablefuture1.handle((chunkresult, throwable) -> { -- this.tickingGenerated.getAndIncrement(); -- return null; -- }); -- return completablefuture1; -+ throw new UnsupportedOperationException(); // Paper - rewrite chunk system - } - - private void onChunkReadyToSend(LevelChunk chunk) { -- ChunkPos chunkcoordintpair = chunk.getPos(); -- Iterator iterator = this.playerMap.getAllPlayers().iterator(); -- -- while (iterator.hasNext()) { -- ServerPlayer entityplayer = (ServerPlayer) iterator.next(); -- -- if (entityplayer.getChunkTrackingView().contains(chunkcoordintpair)) { -- ChunkMap.markChunkPendingToSend(entityplayer, chunk); -- } -- } -+ throw new UnsupportedOperationException(); // Paper - rewrite player chunk loader - - } - - public CompletableFuture> prepareAccessibleChunk(ChunkHolder holder) { -- return this.getChunkRangeFuture(holder, 1, ChunkStatus::getStatusAroundFullChunk).thenApplyAsync((chunkresult) -> { -- return chunkresult.map((list) -> { -- return (LevelChunk) list.get(list.size() / 2); -- }); -- }, (runnable) -> { -- this.mainThreadMailbox.tell(ChunkTaskPriorityQueueSorter.message(holder, runnable)); -- }); -+ throw new UnsupportedOperationException(); // Paper - rewrite chunk system - } - - public int getTickingGenerated() { -@@ -1052,96 +625,15 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider - } - - private boolean saveChunkIfNeeded(ChunkHolder chunkHolder) { -- if (!chunkHolder.wasAccessibleSinceLastSave()) { -- return false; -- } else { -- ChunkAccess ichunkaccess = (ChunkAccess) chunkHolder.getChunkToSave().getNow(null); // CraftBukkit - decompile error -- -- if (!(ichunkaccess instanceof ImposterProtoChunk) && !(ichunkaccess instanceof LevelChunk)) { -- return false; -- } else { -- long i = ichunkaccess.getPos().toLong(); -- long j = this.chunkSaveCooldowns.getOrDefault(i, -1L); -- long k = System.currentTimeMillis(); -- -- if (k < j) { -- return false; -- } else { -- boolean flag = this.save(ichunkaccess); -- -- chunkHolder.refreshAccessibility(); -- if (flag) { -- this.chunkSaveCooldowns.put(i, k + 10000L); -- } -- -- return flag; -- } -- } -- } -+ throw new UnsupportedOperationException(); // Paper - rewrite chunk system - } - - public boolean save(ChunkAccess chunk) { -- this.poiManager.flush(chunk.getPos()); -- if (!chunk.isUnsaved()) { -- return false; -- } else { -- chunk.setUnsaved(false); -- ChunkPos chunkcoordintpair = chunk.getPos(); -- -- try { -- ChunkStatus chunkstatus = chunk.getStatus(); -- -- if (chunkstatus.getChunkType() != ChunkType.LEVELCHUNK) { -- if (this.isExistingChunkFull(chunkcoordintpair)) { -- return false; -- } -- -- if (chunkstatus == ChunkStatus.EMPTY && chunk.getAllStarts().values().stream().noneMatch(StructureStart::isValid)) { -- return false; -- } -- } -- -- this.level.getProfiler().incrementCounter("chunkSave"); -- CompoundTag nbttagcompound = ChunkSerializer.write(this.level, chunk); -- -- this.write(chunkcoordintpair, nbttagcompound).exceptionallyAsync((throwable) -> { -- this.level.getServer().reportChunkSaveFailure(chunkcoordintpair); -- return null; -- }, this.mainThreadExecutor); -- this.markPosition(chunkcoordintpair, chunkstatus.getChunkType()); -- return true; -- } catch (Exception exception) { -- ChunkMap.LOGGER.error("Failed to save chunk {},{}", new Object[]{chunkcoordintpair.x, chunkcoordintpair.z, exception}); -- this.level.getServer().reportChunkSaveFailure(chunkcoordintpair); -- return false; -- } -- } -+ throw new UnsupportedOperationException(); // Paper - rewrite chunk system - } - - private boolean isExistingChunkFull(ChunkPos pos) { -- byte b0 = this.chunkTypeCache.get(pos.toLong()); -- -- if (b0 != 0) { -- return b0 == 1; -- } else { -- CompoundTag nbttagcompound; -- -- try { -- nbttagcompound = (CompoundTag) ((Optional) this.readChunk(pos).join()).orElse((Object) null); -- if (nbttagcompound == null) { -- this.markPositionReplaceable(pos); -- return false; -- } -- } catch (Exception exception) { -- ChunkMap.LOGGER.error("Failed to read chunk {}", pos, exception); -- this.markPositionReplaceable(pos); -- return false; -- } -- -- ChunkType chunktype = ChunkSerializer.getChunkTypeFromTag(nbttagcompound); -- -- return this.markPosition(pos, chunktype) == 1; -- } -+ throw new UnsupportedOperationException(); // Paper - rewrite chunk system - } - - public void setServerViewDistance(int watchDistance) { // Paper - public -@@ -1149,37 +641,36 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider - - if (j != this.serverViewDistance) { - this.serverViewDistance = j; -- this.distanceManager.updatePlayerTickets(this.serverViewDistance); -- Iterator iterator = this.playerMap.getAllPlayers().iterator(); -+ this.level.playerChunkLoader.setLoadDistance(this.serverViewDistance + 1); // Paper - replace player loader system -+ } - -- while (iterator.hasNext()) { -- ServerPlayer entityplayer = (ServerPlayer) iterator.next(); -+ } - -- this.updateChunkTracking(entityplayer); -- } -- } -+ // Paper start - replace player loader system -+ public void setTickViewDistance(int distance) { -+ this.level.playerChunkLoader.setTickDistance(distance); -+ } - -+ public void setSendViewDistance(int distance) { -+ this.level.playerChunkLoader.setSendDistance(distance); - } -+ // Paper end - replace player loader system - - public int getPlayerViewDistance(ServerPlayer player) { // Paper - public -- return Mth.clamp(player.requestedViewDistance(), 2, this.serverViewDistance); -+ return io.papermc.paper.chunk.system.ChunkSystem.getSendViewDistance(player); // Paper - per player view distance - } - - private void markChunkPendingToSend(ServerPlayer player, ChunkPos pos) { -- LevelChunk chunk = this.getChunkToSend(pos.toLong()); -- -- if (chunk != null) { -- ChunkMap.markChunkPendingToSend(player, chunk); -- } -+ throw new UnsupportedOperationException(); // Paper - per player view distance - - } - - private static void markChunkPendingToSend(ServerPlayer player, LevelChunk chunk) { -- player.connection.chunkSender.markChunkPendingToSend(chunk); -+ throw new UnsupportedOperationException(); // Paper - rewrite player chunk loader - } - - private static void dropChunk(ServerPlayer player, ChunkPos pos) { -- player.connection.chunkSender.dropChunk(player, pos); -+ // Paper - rewrite player chunk loader - } - - @Nullable -@@ -1202,30 +693,7 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider - } - - void dumpChunks(Writer writer) throws IOException { -- CsvOutput csvwriter = CsvOutput.builder().addColumn("x").addColumn("z").addColumn("level").addColumn("in_memory").addColumn("status").addColumn("full_status").addColumn("accessible_ready").addColumn("ticking_ready").addColumn("entity_ticking_ready").addColumn("ticket").addColumn("spawning").addColumn("block_entity_count").addColumn("ticking_ticket").addColumn("ticking_level").addColumn("block_ticks").addColumn("fluid_ticks").build(writer); -- TickingTracker tickingtracker = this.distanceManager.tickingTracker(); -- Iterator objectbidirectionaliterator = io.papermc.paper.chunk.system.ChunkSystem.getVisibleChunkHolders(this.level).iterator(); // Paper -- -- while (objectbidirectionaliterator.hasNext()) { -- ChunkHolder playerchunk = objectbidirectionaliterator.next(); // Paper -- long i = playerchunk.pos.toLong(); // Paper -- ChunkPos chunkcoordintpair = new ChunkPos(i); -- // Paper -- Optional optional = Optional.ofNullable(playerchunk.getLastAvailable()); -- Optional optional1 = optional.flatMap((ichunkaccess) -> { -- return ichunkaccess instanceof LevelChunk ? Optional.of((LevelChunk) ichunkaccess) : Optional.empty(); -- }); -- -- // CraftBukkit - decompile error -- csvwriter.writeRow(chunkcoordintpair.x, chunkcoordintpair.z, playerchunk.getTicketLevel(), optional.isPresent(), optional.map(ChunkAccess::getStatus).orElse(null), optional1.map(LevelChunk::getFullStatus).orElse(null), ChunkMap.printFuture(playerchunk.getFullChunkFuture()), ChunkMap.printFuture(playerchunk.getTickingChunkFuture()), ChunkMap.printFuture(playerchunk.getEntityTickingChunkFuture()), this.distanceManager.getTicketDebugString(i), this.anyPlayerCloseEnoughForSpawning(chunkcoordintpair), optional1.map((chunk) -> { -- return chunk.getBlockEntities().size(); -- }).orElse(0), tickingtracker.getTicketDebugString(i), tickingtracker.getLevel(i), optional1.map((chunk) -> { -- return chunk.getBlockTicks().count(); -- }).orElse(0), optional1.map((chunk) -> { -- return chunk.getFluidTicks().count(); -- }).orElse(0)); -- } -- -+ throw new UnsupportedOperationException(); // Paper - rewrite chunk system - } - - private static String printFuture(CompletableFuture> future) { -@@ -1240,6 +708,32 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider - } - } - -+ // Paper start - Asynchronous chunk io -+ @Nullable -+ @Override -+ public CompoundTag readSync(ChunkPos chunkcoordintpair) throws IOException { -+ if (!io.papermc.paper.chunk.system.io.RegionFileIOThread.isRegionFileThread()) { -+ return io.papermc.paper.chunk.system.io.RegionFileIOThread.loadData( -+ this.level, chunkcoordintpair.x, chunkcoordintpair.z, io.papermc.paper.chunk.system.io.RegionFileIOThread.RegionFileType.CHUNK_DATA, -+ io.papermc.paper.chunk.system.io.RegionFileIOThread.getIOBlockingPriorityForCurrentThread() -+ ); -+ } -+ return super.readSync(chunkcoordintpair); -+ } -+ -+ @Override -+ public CompletableFuture write(ChunkPos chunkcoordintpair, CompoundTag nbttagcompound) throws IOException { -+ if (!io.papermc.paper.chunk.system.io.RegionFileIOThread.isRegionFileThread()) { -+ io.papermc.paper.chunk.system.io.RegionFileIOThread.scheduleSave( -+ this.level, chunkcoordintpair.x, chunkcoordintpair.z, nbttagcompound, -+ io.papermc.paper.chunk.system.io.RegionFileIOThread.RegionFileType.CHUNK_DATA); -+ return null; -+ } -+ super.write(chunkcoordintpair, nbttagcompound); -+ return null; -+ } -+ // Paper end -+ - private CompletableFuture> readChunk(ChunkPos chunkPos) { - return this.read(chunkPos).thenApplyAsync((optional) -> { - return optional.map((nbttagcompound) -> this.upgradeChunkTag(nbttagcompound, chunkPos)); // CraftBukkit -@@ -1340,8 +834,7 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider - this.distanceManager.addPlayer(SectionPos.of((EntityAccess) player), player); - } - -- player.setChunkTrackingView(ChunkTrackingView.EMPTY); -- this.updateChunkTracking(player); -+ // Paper - handled by player chunk loader - this.addPlayerToDistanceMaps(player); // Paper - distance maps - } else { - SectionPos sectionposition = player.getLastSectionPos(); -@@ -1352,7 +845,7 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider - } - - this.removePlayerFromDistanceMaps(player); // Paper - distance maps -- this.applyChunkTrackingView(player, ChunkTrackingView.EMPTY); -+ // Paper - handled by player chunk loader - } - - } -@@ -1400,71 +893,30 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider - this.playerMap.unIgnorePlayer(player); - } - -- this.updateChunkTracking(player); -+ // Paper - replaced by PlayerChunkLoader - } - - this.updateMaps(player); // Paper - distance maps - } - - private void updateChunkTracking(ServerPlayer player) { -- ChunkPos chunkcoordintpair = player.chunkPosition(); -- int i = this.getPlayerViewDistance(player); -- ChunkTrackingView chunktrackingview = player.getChunkTrackingView(); -- -- if (chunktrackingview instanceof ChunkTrackingView.Positioned chunktrackingview_a) { -- if (chunktrackingview_a.center().equals(chunkcoordintpair) && chunktrackingview_a.viewDistance() == i) { -- return; -- } -- } -- -- this.applyChunkTrackingView(player, ChunkTrackingView.of(chunkcoordintpair, i)); -+ throw new UnsupportedOperationException(); // Paper - replaced by PlayerChunkLoader - } - - private void applyChunkTrackingView(ServerPlayer player, ChunkTrackingView chunkFilter) { -- if (player.level() == this.level) { -- ChunkTrackingView chunktrackingview1 = player.getChunkTrackingView(); -- -- if (chunkFilter instanceof ChunkTrackingView.Positioned) { -- label15: -- { -- ChunkTrackingView.Positioned chunktrackingview_a = (ChunkTrackingView.Positioned) chunkFilter; -- -- if (chunktrackingview1 instanceof ChunkTrackingView.Positioned) { -- ChunkTrackingView.Positioned chunktrackingview_a1 = (ChunkTrackingView.Positioned) chunktrackingview1; -- -- if (chunktrackingview_a1.center().equals(chunktrackingview_a.center())) { -- break label15; -- } -- } -- -- player.connection.send(new ClientboundSetChunkCacheCenterPacket(chunktrackingview_a.center().x, chunktrackingview_a.center().z)); -- } -- } -- -- ChunkTrackingView.difference(chunktrackingview1, chunkFilter, (chunkcoordintpair) -> { -- this.markChunkPendingToSend(player, chunkcoordintpair); -- }, (chunkcoordintpair) -> { -- ChunkMap.dropChunk(player, chunkcoordintpair); -- }); -- player.setChunkTrackingView(chunkFilter); -- } -+ throw new UnsupportedOperationException(); // Paper - replaced by PlayerChunkLoader - } - - @Override - public List getPlayers(ChunkPos chunkPos, boolean onlyOnWatchDistanceEdge) { -- Set set = this.playerMap.getAllPlayers(); -- Builder builder = ImmutableList.builder(); -- Iterator iterator = set.iterator(); -- -- while (iterator.hasNext()) { -- ServerPlayer entityplayer = (ServerPlayer) iterator.next(); -- -- if (onlyOnWatchDistanceEdge && this.isChunkOnTrackedBorder(entityplayer, chunkPos.x, chunkPos.z) || !onlyOnWatchDistanceEdge && this.isChunkTracked(entityplayer, chunkPos.x, chunkPos.z)) { -- builder.add(entityplayer); -- } -+ // Paper start - per player view distance -+ ChunkHolder holder = this.getVisibleChunkIfPresent(chunkPos.toLong()); -+ if (holder == null) { -+ return new java.util.ArrayList<>(); -+ } else { -+ return holder.getPlayers(onlyOnWatchDistanceEdge); - } -- -- return builder.build(); -+ // Paper end - per player view distance - } - - public void addEntity(Entity entity) { -@@ -1535,13 +987,7 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider - } - - protected void tick() { -- Iterator iterator = this.playerMap.getAllPlayers().iterator(); -- -- while (iterator.hasNext()) { -- ServerPlayer entityplayer = (ServerPlayer) iterator.next(); -- -- this.updateChunkTracking(entityplayer); -- } -+ // Paper - replaced by PlayerChunkLoader - - List list = Lists.newArrayList(); - List list1 = this.level.players(); -@@ -1648,16 +1094,7 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider - } - - public void waitForLightBeforeSending(ChunkPos centerPos, int radius) { -- int j = radius + 1; -- -- ChunkPos.rangeClosed(centerPos, j).forEach((chunkcoordintpair1) -> { -- ChunkHolder playerchunk = this.getVisibleChunkIfPresent(chunkcoordintpair1.toLong()); -- -- if (playerchunk != null) { -- playerchunk.addSendDependency(this.lightEngine.waitForPendingTasks(chunkcoordintpair1.x, chunkcoordintpair1.z)); -- } -- -- }); -+ // Paper - rewrite player chunk loader - } - - public class ChunkDistanceManager extends DistanceManager { // Paper - public -@@ -1668,7 +1105,7 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider - - @Override - protected boolean isChunkToRemove(long pos) { -- return ChunkMap.this.toDrop.contains(pos); -+ throw new UnsupportedOperationException(); // Paper - rewrite chunk system - } - - @Nullable -diff --git a/src/main/java/net/minecraft/server/level/DistanceManager.java b/src/main/java/net/minecraft/server/level/DistanceManager.java -index 7a48ae2ba962ff56d0abff581b51f28b48bd9aae..ed5154e41ca858f4d6b4d1c276c66831c038d2a6 100644 ---- a/src/main/java/net/minecraft/server/level/DistanceManager.java -+++ b/src/main/java/net/minecraft/server/level/DistanceManager.java -@@ -38,65 +38,28 @@ import org.slf4j.Logger; - - public abstract class DistanceManager { - -+ // Paper start - rewrite chunk system -+ public io.papermc.paper.chunk.system.scheduling.ChunkHolderManager getChunkHolderManager() { -+ return this.chunkMap.level.chunkTaskScheduler.chunkHolderManager; -+ } -+ // Paper end - rewrite chunk system -+ - static final Logger LOGGER = LogUtils.getLogger(); - static final int PLAYER_TICKET_LEVEL = ChunkLevel.byStatus(FullChunkStatus.ENTITY_TICKING); - private static final int INITIAL_TICKET_LIST_CAPACITY = 4; - final Long2ObjectMap> playersPerChunk = new Long2ObjectOpenHashMap(); -- public final Long2ObjectOpenHashMap>> tickets = new Long2ObjectOpenHashMap(); -- private final DistanceManager.ChunkTicketTracker ticketTracker = new DistanceManager.ChunkTicketTracker(); -+ // Paper - rewrite chunk system - private final DistanceManager.FixedPlayerDistanceChunkTracker naturalSpawnChunkCounter = new DistanceManager.FixedPlayerDistanceChunkTracker(8); -- private final TickingTracker tickingTicketsTracker = new TickingTracker(); -- private final DistanceManager.PlayerTicketTracker playerTicketManager = new DistanceManager.PlayerTicketTracker(32); -- final Set chunksToUpdateFutures = Sets.newHashSet(); -- final ChunkTaskPriorityQueueSorter ticketThrottler; -- final ProcessorHandle> ticketThrottlerInput; -- final ProcessorHandle ticketThrottlerReleaser; -- final LongSet ticketsToRelease = new LongOpenHashSet(); -- final Executor mainThreadExecutor; -- private long ticketTickCounter; -- public int simulationDistance = 10; -+ // Paper - rewrite chunk system - private final ChunkMap chunkMap; // Paper - - protected DistanceManager(Executor workerExecutor, Executor mainThreadExecutor, ChunkMap chunkMap) { -- Objects.requireNonNull(mainThreadExecutor); -- ProcessorHandle mailbox = ProcessorHandle.of("player ticket throttler", mainThreadExecutor::execute); -- ChunkTaskPriorityQueueSorter chunktaskqueuesorter = new ChunkTaskPriorityQueueSorter(ImmutableList.of(mailbox), workerExecutor, 4); -- -- this.ticketThrottler = chunktaskqueuesorter; -- this.ticketThrottlerInput = chunktaskqueuesorter.getProcessor(mailbox, true); -- this.ticketThrottlerReleaser = chunktaskqueuesorter.getReleaseProcessor(mailbox); -- this.mainThreadExecutor = mainThreadExecutor; -+ // Paper - rewrite chunk system - this.chunkMap = chunkMap; // Paper - } - - protected void purgeStaleTickets() { -- ++this.ticketTickCounter; -- ObjectIterator>>> objectiterator = this.tickets.long2ObjectEntrySet().fastIterator(); -- -- while (objectiterator.hasNext()) { -- Entry>> entry = (Entry) objectiterator.next(); -- Iterator> iterator = ((SortedArraySet) entry.getValue()).iterator(); -- boolean flag = false; -- -- while (iterator.hasNext()) { -- Ticket ticket = (Ticket) iterator.next(); -- -- if (ticket.timedOut(this.ticketTickCounter)) { -- iterator.remove(); -- flag = true; -- this.tickingTicketsTracker.removeTicket(entry.getLongKey(), ticket); -- } -- } -- -- if (flag) { -- this.ticketTracker.update(entry.getLongKey(), DistanceManager.getTicketLevelAt((SortedArraySet) entry.getValue()), false); -- } -- -- if (((SortedArraySet) entry.getValue()).isEmpty()) { -- objectiterator.remove(); -- } -- } -- -+ this.getChunkHolderManager().tick(); // Paper - rewrite chunk system - } - - private static int getTicketLevelAt(SortedArraySet> tickets) { -@@ -112,108 +75,25 @@ public abstract class DistanceManager { - protected abstract ChunkHolder updateChunkScheduling(long pos, int level, @Nullable ChunkHolder holder, int k); - - public boolean runAllUpdates(ChunkMap chunkStorage) { -- this.naturalSpawnChunkCounter.runAllUpdates(); -- this.tickingTicketsTracker.runAllUpdates(); -- this.playerTicketManager.runAllUpdates(); -- int i = Integer.MAX_VALUE - this.ticketTracker.runDistanceUpdates(Integer.MAX_VALUE); -- boolean flag = i != 0; -- -- if (flag) { -- ; -- } -- -- if (!this.chunksToUpdateFutures.isEmpty()) { -- // CraftBukkit start -- // Iterate pending chunk updates with protection against concurrent modification exceptions -- java.util.Iterator iter = this.chunksToUpdateFutures.iterator(); -- int expectedSize = this.chunksToUpdateFutures.size(); -- do { -- ChunkHolder playerchunk = iter.next(); -- iter.remove(); -- expectedSize--; -- -- playerchunk.updateFutures(chunkStorage, this.mainThreadExecutor); -- -- // Reset iterator if set was modified using add() -- if (this.chunksToUpdateFutures.size() != expectedSize) { -- expectedSize = this.chunksToUpdateFutures.size(); -- iter = this.chunksToUpdateFutures.iterator(); -- } -- } while (iter.hasNext()); -- // CraftBukkit end -- -- return true; -- } else { -- if (!this.ticketsToRelease.isEmpty()) { -- LongIterator longiterator = this.ticketsToRelease.iterator(); -- -- while (longiterator.hasNext()) { -- long j = longiterator.nextLong(); -- -- if (this.getTickets(j).stream().anyMatch((ticket) -> { -- return ticket.getType() == TicketType.PLAYER; -- })) { -- ChunkHolder playerchunk = chunkStorage.getUpdatingChunkIfPresent(j); -- -- if (playerchunk == null) { -- throw new IllegalStateException(); -- } -- -- CompletableFuture> completablefuture = playerchunk.getEntityTickingChunkFuture(); -- -- completablefuture.thenAccept((chunkresult) -> { -- this.mainThreadExecutor.execute(() -> { -- this.ticketThrottlerReleaser.tell(ChunkTaskPriorityQueueSorter.release(() -> { -- }, j, false)); -- }); -- }); -- } -- } -- -- this.ticketsToRelease.clear(); -- } -- -- return flag; -- } -+ return this.getChunkHolderManager().processTicketUpdates(); // Paper - rewrite chunk system - } - - boolean addTicket(long i, Ticket ticket) { // CraftBukkit - void -> boolean -- SortedArraySet> arraysetsorted = this.getTickets(i); -- int j = DistanceManager.getTicketLevelAt(arraysetsorted); -- Ticket ticket1 = (Ticket) arraysetsorted.addOrGet(ticket); -- -- ticket1.setCreatedTick(this.ticketTickCounter); -- if (ticket.getTicketLevel() < j) { -- this.ticketTracker.update(i, ticket.getTicketLevel(), true); -- } -- -- return ticket == ticket1; // CraftBukkit -+ org.spigotmc.AsyncCatcher.catchOp("ChunkMapDistance::addTicket"); // Paper -+ return this.getChunkHolderManager().addTicketAtLevel((TicketType)ticket.getType(), i, ticket.getTicketLevel(), ticket.key); // Paper - rewrite chunk system - } - - boolean removeTicket(long i, Ticket ticket) { // CraftBukkit - void -> boolean -- SortedArraySet> arraysetsorted = this.getTickets(i); -- -- boolean removed = false; // CraftBukkit -- if (arraysetsorted.remove(ticket)) { -- removed = true; // CraftBukkit -- } -- -- if (arraysetsorted.isEmpty()) { -- this.tickets.remove(i); -- } -- -- this.ticketTracker.update(i, DistanceManager.getTicketLevelAt(arraysetsorted), false); -- return removed; // CraftBukkit -+ org.spigotmc.AsyncCatcher.catchOp("ChunkMapDistance::removeTicket"); // Paper -+ return this.getChunkHolderManager().removeTicketAtLevel((TicketType)ticket.getType(), i, ticket.getTicketLevel(), ticket.key); // Paper - rewrite chunk system - } - - public void addTicket(TicketType type, ChunkPos pos, int level, T argument) { -- this.addTicket(pos.toLong(), new Ticket<>(type, level, argument)); -+ this.getChunkHolderManager().addTicketAtLevel(type, pos, level, argument); // Paper - rewrite chunk system - } - - public void removeTicket(TicketType type, ChunkPos pos, int level, T argument) { -- Ticket ticket = new Ticket<>(type, level, argument); -- -- this.removeTicket(pos.toLong(), ticket); -+ this.getChunkHolderManager().removeTicketAtLevel(type, pos, level, argument); // Paper - rewrite chunk system - } - - public void addRegionTicket(TicketType type, ChunkPos pos, int radius, T argument) { -@@ -222,13 +102,7 @@ public abstract class DistanceManager { - } - - public boolean addRegionTicketAtDistance(TicketType tickettype, ChunkPos chunkcoordintpair, int i, T t0) { -- // CraftBukkit end -- Ticket ticket = new Ticket<>(tickettype, ChunkLevel.byStatus(FullChunkStatus.FULL) - i, t0); -- long j = chunkcoordintpair.toLong(); -- -- boolean added = this.addTicket(j, ticket); // CraftBukkit -- this.tickingTicketsTracker.addTicket(j, ticket); -- return added; // CraftBukkit -+ return this.getChunkHolderManager().addTicketAtLevel(tickettype, chunkcoordintpair, ChunkLevel.byStatus(FullChunkStatus.FULL) - i, t0); // Paper - rewrite chunk system - } - - public void removeRegionTicket(TicketType type, ChunkPos pos, int radius, T argument) { -@@ -237,31 +111,21 @@ public abstract class DistanceManager { - } - - public boolean removeRegionTicketAtDistance(TicketType tickettype, ChunkPos chunkcoordintpair, int i, T t0) { -- // CraftBukkit end -- Ticket ticket = new Ticket<>(tickettype, ChunkLevel.byStatus(FullChunkStatus.FULL) - i, t0); -- long j = chunkcoordintpair.toLong(); -- -- boolean removed = this.removeTicket(j, ticket); // CraftBukkit -- this.tickingTicketsTracker.removeTicket(j, ticket); -- return removed; // CraftBukkit -+ return this.getChunkHolderManager().removeTicketAtLevel(tickettype, chunkcoordintpair, ChunkLevel.byStatus(FullChunkStatus.FULL) - i, t0); // Paper - rewrite chunk system - } - -- private SortedArraySet> getTickets(long position) { -- return (SortedArraySet) this.tickets.computeIfAbsent(position, (j) -> { -- return SortedArraySet.create(4); -- }); -- } -+ // Paper - rewrite chunk system - - protected void updateChunkForced(ChunkPos pos, boolean forced) { -- Ticket ticket = new Ticket<>(TicketType.FORCED, ChunkMap.FORCED_TICKET_LEVEL, pos); -+ Ticket ticket = new Ticket<>(TicketType.FORCED, ChunkMap.FORCED_TICKET_LEVEL, pos, 0L); // Paper - rewrite chunk system - long i = pos.toLong(); - - if (forced) { - this.addTicket(i, ticket); -- this.tickingTicketsTracker.addTicket(i, ticket); -+ //this.tickingTicketsTracker.addTicket(i, ticket); // Paper - no longer used - } else { - this.removeTicket(i, ticket); -- this.tickingTicketsTracker.removeTicket(i, ticket); -+ //this.tickingTicketsTracker.removeTicket(i, ticket); // Paper - no longer used - } - - } -@@ -270,12 +134,10 @@ public abstract class DistanceManager { - ChunkPos chunkcoordintpair = pos.chunk(); - long i = chunkcoordintpair.toLong(); - -- ((ObjectSet) this.playersPerChunk.computeIfAbsent(i, (j) -> { -- return new ObjectOpenHashSet(); -- })).add(player); -+ // Paper - no longer used - this.naturalSpawnChunkCounter.update(i, 0, true); -- this.playerTicketManager.update(i, 0, true); -- this.tickingTicketsTracker.addTicket(TicketType.PLAYER, chunkcoordintpair, this.getPlayerTicketLevel(), chunkcoordintpair); -+ //this.playerTicketManager.update(i, 0, true); // Paper - no longer used -+ //this.tickingTicketsTracker.addTicket(TicketType.PLAYER, chunkcoordintpair, this.getPlayerTicketLevel(), chunkcoordintpair); // Paper - no longer used - } - - public void removePlayer(SectionPos pos, ServerPlayer player) { -@@ -288,40 +150,44 @@ public abstract class DistanceManager { - if (objectset == null || objectset.isEmpty()) { // Paper - this.playersPerChunk.remove(i); - this.naturalSpawnChunkCounter.update(i, Integer.MAX_VALUE, false); -- this.playerTicketManager.update(i, Integer.MAX_VALUE, false); -- this.tickingTicketsTracker.removeTicket(TicketType.PLAYER, chunkcoordintpair, this.getPlayerTicketLevel(), chunkcoordintpair); -+ //this.playerTicketManager.update(i, Integer.MAX_VALUE, false); // Paper - no longer used -+ //this.tickingTicketsTracker.removeTicket(TicketType.PLAYER, chunkcoordintpair, this.getPlayerTicketLevel(), chunkcoordintpair); // Paper - no longer used - } - - } - -- private int getPlayerTicketLevel() { -- return Math.max(0, ChunkLevel.byStatus(FullChunkStatus.ENTITY_TICKING) - this.simulationDistance); -- } -+ // Paper - rewrite chunk system - - public boolean inEntityTickingRange(long chunkPos) { -- return ChunkLevel.isEntityTicking(this.tickingTicketsTracker.getLevel(chunkPos)); -+ // Paper start - replace player chunk loader system -+ ChunkHolder holder = this.chunkMap.getVisibleChunkIfPresent(chunkPos); -+ return holder != null && holder.isEntityTickingReady(); -+ // Paper end - replace player chunk loader system - } - - public boolean inBlockTickingRange(long chunkPos) { -- return ChunkLevel.isBlockTicking(this.tickingTicketsTracker.getLevel(chunkPos)); -+ // Paper start - replace player chunk loader system -+ ChunkHolder holder = this.chunkMap.getVisibleChunkIfPresent(chunkPos); -+ return holder != null && holder.isTickingReady(); -+ // Paper end - replace player chunk loader system - } - - protected String getTicketDebugString(long pos) { -- SortedArraySet> arraysetsorted = (SortedArraySet) this.tickets.get(pos); -- -- return arraysetsorted != null && !arraysetsorted.isEmpty() ? ((Ticket) arraysetsorted.first()).toString() : "no_ticket"; -+ return this.getChunkHolderManager().getTicketDebugString(pos); // Paper - rewrite chunk system - } - - protected void updatePlayerTickets(int viewDistance) { -- this.playerTicketManager.updateViewDistance(viewDistance); -+ this.chunkMap.setServerViewDistance(viewDistance); // Paper - route to player chunk manager - } - -- public void updateSimulationDistance(int simulationDistance) { -- if (simulationDistance != this.simulationDistance) { -- this.simulationDistance = simulationDistance; -- this.tickingTicketsTracker.replacePlayerTicketsLevel(this.getPlayerTicketLevel()); -- } -+ // Paper start -+ public int getSimulationDistance() { -+ return this.chunkMap.level.playerChunkLoader.getAPITickDistance(); -+ } -+ // Paper end - -+ public void updateSimulationDistance(int simulationDistance) { -+ this.chunkMap.level.playerChunkLoader.setTickDistance(simulationDistance); // Paper - route to player chunk manager - } - - public int getNaturalSpawnChunkCount() { -@@ -335,103 +201,26 @@ public abstract class DistanceManager { - } - - public String getDebugStatus() { -- return this.ticketThrottler.getDebugStatus(); -+ return "No DistanceManager stats available"; // Paper - rewrite chunk system - } - -- private void dumpTickets(String path) { -- try { -- FileOutputStream fileoutputstream = new FileOutputStream(new File(path)); -- -- try { -- ObjectIterator objectiterator = this.tickets.long2ObjectEntrySet().iterator(); -- -- while (objectiterator.hasNext()) { -- Entry>> entry = (Entry) objectiterator.next(); -- ChunkPos chunkcoordintpair = new ChunkPos(entry.getLongKey()); -- Iterator iterator = ((SortedArraySet) entry.getValue()).iterator(); -- -- while (iterator.hasNext()) { -- Ticket ticket = (Ticket) iterator.next(); -- -- fileoutputstream.write((chunkcoordintpair.x + "\t" + chunkcoordintpair.z + "\t" + String.valueOf(ticket.getType()) + "\t" + ticket.getTicketLevel() + "\t\n").getBytes(StandardCharsets.UTF_8)); -- } -- } -- } catch (Throwable throwable) { -- try { -- fileoutputstream.close(); -- } catch (Throwable throwable1) { -- throwable.addSuppressed(throwable1); -- } -- -- throw throwable; -- } -- -- fileoutputstream.close(); -- } catch (IOException ioexception) { -- DistanceManager.LOGGER.error("Failed to dump tickets to {}", path, ioexception); -- } -- -- } -- -- @VisibleForTesting -- TickingTracker tickingTracker() { -- return this.tickingTicketsTracker; -- } -+ // Paper - rewrite chunk system - - public void removeTicketsOnClosing() { -- ImmutableSet> immutableset = ImmutableSet.of(TicketType.UNKNOWN, TicketType.POST_TELEPORT, TicketType.LIGHT, TicketType.FUTURE_AWAIT, TicketType.CHUNK_RELIGHT, ca.spottedleaf.starlight.common.light.StarLightInterface.CHUNK_WORK_TICKET); // Paper - add additional tickets to preserve -- ObjectIterator>>> objectiterator = this.tickets.long2ObjectEntrySet().fastIterator(); -- -- while (objectiterator.hasNext()) { -- Entry>> entry = (Entry) objectiterator.next(); -- Iterator> iterator = ((SortedArraySet) entry.getValue()).iterator(); -- boolean flag = false; -- -- while (iterator.hasNext()) { -- Ticket ticket = (Ticket) iterator.next(); -- -- if (!immutableset.contains(ticket.getType())) { -- iterator.remove(); -- flag = true; -- this.tickingTicketsTracker.removeTicket(entry.getLongKey(), ticket); -- } -- } -- -- if (flag) { -- this.ticketTracker.update(entry.getLongKey(), DistanceManager.getTicketLevelAt((SortedArraySet) entry.getValue()), false); -- } -- -- if (((SortedArraySet) entry.getValue()).isEmpty()) { -- objectiterator.remove(); -- } -- } -- -+ // Paper - rewrite chunk system - this stupid hack ain't needed anymore - } - - public boolean hasTickets() { -- return !this.tickets.isEmpty(); -+ throw new UnsupportedOperationException(); // Paper - rewrite chunk system - } - - // CraftBukkit start - public void removeAllTicketsFor(TicketType ticketType, int ticketLevel, T ticketIdentifier) { -- Ticket target = new Ticket<>(ticketType, ticketLevel, ticketIdentifier); -- -- for (java.util.Iterator>>> iterator = this.tickets.long2ObjectEntrySet().fastIterator(); iterator.hasNext();) { -- Entry>> entry = iterator.next(); -- SortedArraySet> tickets = entry.getValue(); -- if (tickets.remove(target)) { -- // copied from removeTicket -- this.ticketTracker.update(entry.getLongKey(), DistanceManager.getTicketLevelAt(tickets), false); -- -- // can't use entry after it's removed -- if (tickets.isEmpty()) { -- iterator.remove(); -- } -- } -- } -+ this.getChunkHolderManager().removeAllTicketsFor(ticketType, ticketLevel, ticketIdentifier); // Paper - rewrite chunk system - } - // CraftBukkit end - -+ /* Paper - rewrite chunk system - private class ChunkTicketTracker extends ChunkTracker { - - private static final int MAX_LEVEL = ChunkLevel.MAX_LEVEL + 1; -@@ -478,6 +267,7 @@ public abstract class DistanceManager { - return this.runUpdates(distance); - } - } -+ */ // Paper - rewrite chunk system - - private class FixedPlayerDistanceChunkTracker extends ChunkTracker { - -@@ -557,6 +347,7 @@ public abstract class DistanceManager { - } - } - -+ /* Paper - rewrite chunk system - private class PlayerTicketTracker extends DistanceManager.FixedPlayerDistanceChunkTracker { - - private int viewDistance = 0; -@@ -652,4 +443,5 @@ public abstract class DistanceManager { - return distance <= this.viewDistance; - } - } -+ */ // Paper - rewrite chunk system - } -diff --git a/src/main/java/net/minecraft/server/level/ServerChunkCache.java b/src/main/java/net/minecraft/server/level/ServerChunkCache.java -index 0e89cf0742b9443f5256081987242554de24d893..802e9d266c01eaf8a83e78fe8dbe881e22e8b4d6 100644 ---- a/src/main/java/net/minecraft/server/level/ServerChunkCache.java -+++ b/src/main/java/net/minecraft/server/level/ServerChunkCache.java -@@ -71,7 +71,7 @@ public class ServerChunkCache extends ChunkSource { - public final io.papermc.paper.util.maplist.IteratorSafeOrderedReferenceSet entityTickingChunks = new io.papermc.paper.util.maplist.IteratorSafeOrderedReferenceSet<>(4096, 0.75f, 4096, 0.15, true); - final com.destroystokyo.paper.util.concurrent.WeakSeqLock loadedChunkMapSeqLock = new com.destroystokyo.paper.util.concurrent.WeakSeqLock(); - final it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap loadedChunkMap = new it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap<>(8192, 0.5f); -- long chunkFutureAwaitCounter; -+ final java.util.concurrent.atomic.AtomicLong chunkFutureAwaitCounter = new java.util.concurrent.atomic.AtomicLong(); // Paper - chunk system rewrite - private final LevelChunk[] lastLoadedChunks = new LevelChunk[4 * 4]; - // Paper end - -@@ -195,7 +195,7 @@ public class ServerChunkCache extends ChunkSource { - public LevelChunk getChunkAtIfLoadedImmediately(int x, int z) { - long k = ChunkPos.asLong(x, z); - -- if (Thread.currentThread() == this.mainThread) { -+ if (io.papermc.paper.util.TickThread.isTickThread()) { // Paper - rewrite chunk system - return this.getChunkAtIfLoadedMainThread(x, z); - } - -@@ -247,7 +247,8 @@ public class ServerChunkCache extends ChunkSource { - @Nullable - @Override - public ChunkAccess getChunk(int x, int z, ChunkStatus leastStatus, boolean create) { -- if (Thread.currentThread() != this.mainThread) { -+ final int x1 = x; final int z1 = z; // Paper - conflict on variable change -+ if (!io.papermc.paper.util.TickThread.isTickThread()) { // Paper - rewrite chunk system - return (ChunkAccess) CompletableFuture.supplyAsync(() -> { - return this.getChunk(x, z, leastStatus, create); - }, this.mainThreadProcessor).join(); -@@ -263,15 +264,7 @@ public class ServerChunkCache extends ChunkSource { - gameprofilerfiller.incrementCounter("getChunk"); - long k = ChunkPos.asLong(x, z); - -- for (int l = 0; l < 4; ++l) { -- if (k == this.lastChunkPos[l] && leastStatus == this.lastChunkStatus[l]) { -- ChunkAccess ichunkaccess = this.lastChunk[l]; -- -- if (ichunkaccess != null) { // CraftBukkit - the chunk can become accessible in the meantime TODO for non-null chunks it might also make sense to check that the chunk's state hasn't changed in the meantime -- return ichunkaccess; -- } -- } -- } -+ // Paper - rewrite chunk system - there are no correct callbacks to remove items from cache in the new chunk system - - gameprofilerfiller.incrementCounter("getChunkCacheMiss"); - CompletableFuture> completablefuture = this.getChunkFutureMainThread(x, z, leastStatus, create); -@@ -279,9 +272,11 @@ public class ServerChunkCache extends ChunkSource { - - Objects.requireNonNull(completablefuture); - if (!completablefuture.isDone()) { // Paper -+ io.papermc.paper.chunk.system.scheduling.ChunkTaskScheduler.pushChunkWait(this.level, x1, z1); // Paper - rewrite chunk system - com.destroystokyo.paper.io.SyncLoadFinder.logSyncLoad(this.level, x, z); // Paper - Add debug for sync chunk loads - this.level.timings.syncChunkLoad.startTiming(); // Paper - chunkproviderserver_b.managedBlock(completablefuture::isDone); -+ io.papermc.paper.chunk.system.scheduling.ChunkTaskScheduler.popChunkWait(); // Paper - rewrite chunk system - this.level.timings.syncChunkLoad.stopTiming(); // Paper - } // Paper - ChunkResult chunkresult = (ChunkResult) completablefuture.join(); -@@ -299,7 +294,7 @@ public class ServerChunkCache extends ChunkSource { - @Nullable - @Override - public LevelChunk getChunkNow(int chunkX, int chunkZ) { -- if (Thread.currentThread() != this.mainThread) { -+ if (!io.papermc.paper.util.TickThread.isTickThread()) { // Paper - rewrite chunk system - return null; - } else { - return this.getChunkAtIfLoadedMainThread(chunkX, chunkZ); // Paper - Perf: Optimise getChunkAt calls for loaded chunks -@@ -313,7 +308,7 @@ public class ServerChunkCache extends ChunkSource { - } - - public CompletableFuture> getChunkFuture(int chunkX, int chunkZ, ChunkStatus leastStatus, boolean create) { -- boolean flag1 = Thread.currentThread() == this.mainThread; -+ boolean flag1 = io.papermc.paper.util.TickThread.isTickThread(); // Paper - rewrite chunk system - CompletableFuture completablefuture; - - if (flag1) { -@@ -333,48 +328,54 @@ public class ServerChunkCache extends ChunkSource { - return completablefuture; - } - -+ - private CompletableFuture> getChunkFutureMainThread(int chunkX, int chunkZ, ChunkStatus leastStatus, boolean create) { -- ChunkPos chunkcoordintpair = new ChunkPos(chunkX, chunkZ); -- long k = chunkcoordintpair.toLong(); -- int l = ChunkLevel.byStatus(leastStatus); -- ChunkHolder playerchunk = this.getVisibleChunkIfPresent(k); -+ // Paper start - add isUrgent - old sig left in place for dirty nms plugins -+ return getChunkFutureMainThread(chunkX, chunkZ, leastStatus, create, false); -+ } -+ private CompletableFuture> getChunkFutureMainThread(int chunkX, int chunkZ, ChunkStatus leastStatus, boolean create, boolean isUrgent) { -+ // Paper start - rewrite chunk system -+ io.papermc.paper.util.TickThread.ensureTickThread(this.level, chunkX, chunkZ, "Scheduling chunk load off-main"); -+ int minLevel = ChunkLevel.byStatus(leastStatus); -+ io.papermc.paper.chunk.system.scheduling.NewChunkHolder chunkHolder = this.level.chunkTaskScheduler.chunkHolderManager.getChunkHolder(chunkX, chunkZ); - -- // CraftBukkit start - don't add new ticket for currently unloading chunk -- boolean currentlyUnloading = false; -- if (playerchunk != null) { -- FullChunkStatus oldChunkState = ChunkLevel.fullStatus(playerchunk.oldTicketLevel); -- FullChunkStatus currentChunkState = ChunkLevel.fullStatus(playerchunk.getTicketLevel()); -- currentlyUnloading = (oldChunkState.isOrAfter(FullChunkStatus.FULL) && !currentChunkState.isOrAfter(FullChunkStatus.FULL)); -+ boolean needsFullScheduling = leastStatus == ChunkStatus.FULL && (chunkHolder == null || !chunkHolder.getChunkStatus().isOrAfter(FullChunkStatus.FULL)); -+ -+ if ((chunkHolder == null || chunkHolder.getTicketLevel() > minLevel || needsFullScheduling) && !create) { -+ return ChunkHolder.UNLOADED_CHUNK_FUTURE; - } -- if (create && !currentlyUnloading) { -- // CraftBukkit end -- this.distanceManager.addTicket(TicketType.UNKNOWN, chunkcoordintpair, l, chunkcoordintpair); -- if (this.chunkAbsent(playerchunk, l)) { -- ProfilerFiller gameprofilerfiller = this.level.getProfiler(); -- -- gameprofilerfiller.push("chunkLoad"); -- this.runDistanceManagerUpdates(); -- playerchunk = this.getVisibleChunkIfPresent(k); -- gameprofilerfiller.pop(); -- if (this.chunkAbsent(playerchunk, l)) { -- throw (IllegalStateException) Util.pauseInIde(new IllegalStateException("No chunk holder after ticket has been added")); -+ -+ io.papermc.paper.chunk.system.scheduling.NewChunkHolder.ChunkCompletion chunkCompletion = chunkHolder == null ? null : chunkHolder.getLastChunkCompletion(); -+ if (needsFullScheduling || chunkCompletion == null || !chunkCompletion.genStatus().isOrAfter(leastStatus)) { -+ // schedule -+ CompletableFuture> ret = new CompletableFuture<>(); -+ Consumer complete = (ChunkAccess chunk) -> { -+ if (chunk == null) { -+ ret.complete(ChunkResult.error("Unexpected chunk unload")); -+ } else { -+ ret.complete(ChunkResult.of(chunk)); - } -- } -- } -+ }; - -- return this.chunkAbsent(playerchunk, l) ? ChunkHolder.UNLOADED_CHUNK_FUTURE : playerchunk.getOrScheduleFuture(leastStatus, this.chunkMap); -- } -+ this.level.chunkTaskScheduler.scheduleChunkLoad( -+ chunkX, chunkZ, leastStatus, true, -+ isUrgent ? ca.spottedleaf.concurrentutil.executor.standard.PrioritisedExecutor.Priority.BLOCKING : ca.spottedleaf.concurrentutil.executor.standard.PrioritisedExecutor.Priority.NORMAL, -+ complete -+ ); - -- private boolean chunkAbsent(@Nullable ChunkHolder holder, int maxLevel) { -- return holder == null || holder.oldTicketLevel > maxLevel; // CraftBukkit using oldTicketLevel for isLoaded checks -+ return ret; -+ } else { -+ // can return now -+ return CompletableFuture.completedFuture(ChunkResult.of(chunkCompletion.chunk())); -+ } -+ // Paper end - rewrite chunk system - } - -+ // Paper - rewrite chunk system -+ - @Override - public boolean hasChunk(int x, int z) { -- ChunkHolder playerchunk = this.getVisibleChunkIfPresent((new ChunkPos(x, z)).toLong()); -- int k = ChunkLevel.byStatus(ChunkStatus.FULL); -- -- return !this.chunkAbsent(playerchunk, k); -+ return this.getChunkAtIfLoadedImmediately(x, z) != null; // Paper - rewrite chunk system - } - - @Nullable -@@ -386,22 +387,13 @@ public class ServerChunkCache extends ChunkSource { - if (playerchunk == null) { - return null; - } else { -- int l = ServerChunkCache.CHUNK_STATUSES.size() - 1; -- -- while (true) { -- ChunkStatus chunkstatus = (ChunkStatus) ServerChunkCache.CHUNK_STATUSES.get(l); -- ChunkAccess ichunkaccess = (ChunkAccess) ((ChunkResult) playerchunk.getFutureIfPresentUnchecked(chunkstatus).getNow(ChunkHolder.UNLOADED_CHUNK)).orElse((Object) null); -- -- if (ichunkaccess != null) { -- return ichunkaccess; -- } -- -- if (chunkstatus == ChunkStatus.INITIALIZE_LIGHT.getParent()) { -- return null; -- } -- -- --l; -+ // Paper start - rewrite chunk system -+ ChunkStatus status = playerchunk.getChunkHolderStatus(); -+ if (status != null && !status.isOrAfter(ChunkStatus.LIGHT.getParent())) { -+ return null; - } -+ return playerchunk.getAvailableChunkNow(); -+ // Paper end - rewrite chunk system - } - } - -@@ -415,15 +407,7 @@ public class ServerChunkCache extends ChunkSource { - } - - public boolean runDistanceManagerUpdates() { // Paper - public -- boolean flag = this.distanceManager.runAllUpdates(this.chunkMap); -- boolean flag1 = this.chunkMap.promoteChunkMap(); -- -- if (!flag && !flag1) { -- return false; -- } else { -- this.clearCache(); -- return true; -- } -+ return this.level.chunkTaskScheduler.chunkHolderManager.processTicketUpdates(); // Paper - rewrite chunk system - } - - // Paper start -@@ -433,9 +417,10 @@ public class ServerChunkCache extends ChunkSource { - // Paper end - - public boolean isPositionTicking(long pos) { -- ChunkHolder playerchunk = this.getVisibleChunkIfPresent(pos); -- -- return playerchunk == null ? false : (!this.level.shouldTickBlocksAt(pos) ? false : ((ChunkResult) playerchunk.getTickingChunkFuture().getNow(ChunkHolder.UNLOADED_LEVEL_CHUNK)).isSuccess()); -+ // Paper start - replace player chunk loader system -+ ChunkHolder holder = this.chunkMap.getVisibleChunkIfPresent(pos); -+ return holder != null && holder.isTickingReady(); -+ // Paper end - replace player chunk loader system - } - - public void save(boolean flush) { -@@ -451,17 +436,13 @@ public class ServerChunkCache extends ChunkSource { - this.close(true); - } - -- public void close(boolean save) throws IOException { -- if (save) { -- this.save(true); -- } -- // CraftBukkit end -- this.lightEngine.close(); -- this.chunkMap.close(); -+ public void close(boolean save) { // Paper - rewrite chunk system -+ this.level.chunkTaskScheduler.chunkHolderManager.close(save, true); // Paper - rewrite chunk system - } - - // CraftBukkit start - modelled on below - public void purgeUnload() { -+ if (true) return; // Paper - tickets will be removed later, this behavior isn't really well accounted for by the chunk system - this.level.getProfiler().push("purge"); - this.distanceManager.purgeStaleTickets(); - this.runDistanceManagerUpdates(); -@@ -485,6 +466,7 @@ public class ServerChunkCache extends ChunkSource { - this.level.getProfiler().popPush("chunks"); - if (tickChunks) { - this.level.timings.chunks.startTiming(); // Paper - timings -+ this.chunkMap.level.playerChunkLoader.tick(); // Paper - replace player chunk loader - this is mostly required to account for view distance changes - this.tickChunks(); - this.level.timings.chunks.stopTiming(); // Paper - timings - this.chunkMap.tick(); -@@ -587,7 +569,12 @@ public class ServerChunkCache extends ChunkSource { - ChunkHolder playerchunk = this.getVisibleChunkIfPresent(pos); - - if (playerchunk != null) { -- ((ChunkResult) playerchunk.getFullChunkFuture().getNow(ChunkHolder.UNLOADED_LEVEL_CHUNK)).ifSuccess(chunkConsumer); -+ // Paper start - rewrite chunk system -+ LevelChunk chunk = playerchunk.getFullChunkNow(); -+ if (chunk != null) { -+ chunkConsumer.accept(chunk); -+ } -+ // Paper end - rewrite chunk system - } - - } -@@ -753,17 +740,10 @@ public class ServerChunkCache extends ChunkSource { - @Override - // CraftBukkit start - process pending Chunk loadCallback() and unloadCallback() after each run task - public boolean pollTask() { -- try { - if (ServerChunkCache.this.runDistanceManagerUpdates()) { - return true; -- } else { -- ServerChunkCache.this.lightEngine.tryScheduleUpdate(); -- return super.pollTask(); - } -- } finally { -- ServerChunkCache.this.chunkMap.callbackExecutor.run(); -- } -- // CraftBukkit end -+ return super.pollTask() | ServerChunkCache.this.level.chunkTaskScheduler.executeMainThreadTask(); // Paper - rewrite chunk system - } - } - -diff --git a/src/main/java/net/minecraft/server/level/ServerLevel.java b/src/main/java/net/minecraft/server/level/ServerLevel.java -index b33bf957b1541756e3b983b87b1c83629757739a..0ccdc8d135dd3edb410fbc1d248c20a4a45b37fa 100644 ---- a/src/main/java/net/minecraft/server/level/ServerLevel.java -+++ b/src/main/java/net/minecraft/server/level/ServerLevel.java -@@ -199,7 +199,7 @@ public class ServerLevel extends Level implements WorldGenLevel { - public final PrimaryLevelData serverLevelData; // CraftBukkit - type - private int lastSpawnChunkRadius; - final EntityTickList entityTickList; -- public final PersistentEntitySectionManager entityManager; -+ //public final PersistentEntitySectionManager entityManager; // Paper - rewrite chunk system - private final GameEventDispatcher gameEventDispatcher; - public boolean noSave; - private final SleepStatus sleepStatus; -@@ -268,50 +268,65 @@ public class ServerLevel extends Level implements WorldGenLevel { - return true; - } - -- public final void loadChunksForMoveAsync(AABB axisalignedbb, ca.spottedleaf.concurrentutil.executor.standard.PrioritisedExecutor.Priority priority, -- java.util.function.Consumer> onLoad) { -- if (Thread.currentThread() != this.thread) { -- this.getChunkSource().mainThreadProcessor.execute(() -> { -- this.loadChunksForMoveAsync(axisalignedbb, priority, onLoad); -- }); -- return; -- } -+ public final void loadChunksAsync(BlockPos pos, int radiusBlocks, -+ ca.spottedleaf.concurrentutil.executor.standard.PrioritisedExecutor.Priority priority, -+ java.util.function.Consumer> onLoad) { -+ loadChunksAsync( -+ (pos.getX() - radiusBlocks) >> 4, -+ (pos.getX() + radiusBlocks) >> 4, -+ (pos.getZ() - radiusBlocks) >> 4, -+ (pos.getZ() + radiusBlocks) >> 4, -+ priority, onLoad -+ ); -+ } -+ -+ public final void loadChunksAsync(BlockPos pos, int radiusBlocks, -+ net.minecraft.world.level.chunk.status.ChunkStatus chunkStatus, -+ ca.spottedleaf.concurrentutil.executor.standard.PrioritisedExecutor.Priority priority, -+ java.util.function.Consumer> onLoad) { -+ loadChunksAsync( -+ (pos.getX() - radiusBlocks) >> 4, -+ (pos.getX() + radiusBlocks) >> 4, -+ (pos.getZ() - radiusBlocks) >> 4, -+ (pos.getZ() + radiusBlocks) >> 4, -+ chunkStatus, priority, onLoad -+ ); -+ } -+ -+ public final void loadChunksAsync(int minChunkX, int maxChunkX, int minChunkZ, int maxChunkZ, -+ ca.spottedleaf.concurrentutil.executor.standard.PrioritisedExecutor.Priority priority, -+ java.util.function.Consumer> onLoad) { -+ this.loadChunksAsync(minChunkX, maxChunkX, minChunkZ, maxChunkZ, net.minecraft.world.level.chunk.status.ChunkStatus.FULL, priority, onLoad); -+ } -+ -+ public final void loadChunksAsync(int minChunkX, int maxChunkX, int minChunkZ, int maxChunkZ, -+ net.minecraft.world.level.chunk.status.ChunkStatus chunkStatus, -+ ca.spottedleaf.concurrentutil.executor.standard.PrioritisedExecutor.Priority priority, -+ java.util.function.Consumer> onLoad) { - List ret = new java.util.ArrayList<>(); -- it.unimi.dsi.fastutil.ints.IntArrayList ticketLevels = new it.unimi.dsi.fastutil.ints.IntArrayList(); -- -- int minBlockX = Mth.floor(axisalignedbb.minX - 1.0E-7D) - 3; -- int maxBlockX = Mth.floor(axisalignedbb.maxX + 1.0E-7D) + 3; -- -- int minBlockZ = Mth.floor(axisalignedbb.minZ - 1.0E-7D) - 3; -- int maxBlockZ = Mth.floor(axisalignedbb.maxZ + 1.0E-7D) + 3; -- -- int minChunkX = minBlockX >> 4; -- int maxChunkX = maxBlockX >> 4; -- -- int minChunkZ = minBlockZ >> 4; -- int maxChunkZ = maxBlockZ >> 4; - - ServerChunkCache chunkProvider = this.getChunkSource(); - - int requiredChunks = (maxChunkX - minChunkX + 1) * (maxChunkZ - minChunkZ + 1); -- int[] loadedChunks = new int[1]; -+ java.util.concurrent.atomic.AtomicInteger loadedChunks = new java.util.concurrent.atomic.AtomicInteger(); - -- Long holderIdentifier = Long.valueOf(chunkProvider.chunkFutureAwaitCounter++); -+ Long holderIdentifier = Long.valueOf(chunkProvider.chunkFutureAwaitCounter.getAndIncrement()); -+ -+ int ticketLevel = 33 + net.minecraft.world.level.chunk.status.ChunkStatus.getDistance(chunkStatus); - - java.util.function.Consumer consumer = (net.minecraft.world.level.chunk.ChunkAccess chunk) -> { - if (chunk != null) { -- int ticketLevel = Math.max(33, chunkProvider.chunkMap.getUpdatingChunkIfPresent(chunk.getPos().toLong()).getTicketLevel()); -+ synchronized (ret) { - ret.add(chunk); -- ticketLevels.add(ticketLevel); -+ } - chunkProvider.addTicketAtLevel(TicketType.FUTURE_AWAIT, chunk.getPos(), ticketLevel, holderIdentifier); - } -- if (++loadedChunks[0] == requiredChunks) { -+ if (loadedChunks.incrementAndGet() == requiredChunks) { - try { - onLoad.accept(java.util.Collections.unmodifiableList(ret)); - } finally { - for (int i = 0, len = ret.size(); i < len; ++i) { - ChunkPos chunkPos = ret.get(i).getPos(); -- int ticketLevel = ticketLevels.getInt(i); - - chunkProvider.addTicketAtLevel(TicketType.UNKNOWN, chunkPos, ticketLevel, chunkPos); - chunkProvider.removeTicketAtLevel(TicketType.FUTURE_AWAIT, chunkPos, ticketLevel, holderIdentifier); -@@ -323,12 +338,228 @@ public class ServerLevel extends Level implements WorldGenLevel { - for (int cx = minChunkX; cx <= maxChunkX; ++cx) { - for (int cz = minChunkZ; cz <= maxChunkZ; ++cz) { - io.papermc.paper.chunk.system.ChunkSystem.scheduleChunkLoad( -- this, cx, cz, net.minecraft.world.level.chunk.status.ChunkStatus.FULL, true, priority, consumer -+ this, cx, cz, chunkStatus, true, priority, consumer - ); - } - } - } -- // Paper end -+ -+ public final void loadChunksForMoveAsync(AABB axisalignedbb, ca.spottedleaf.concurrentutil.executor.standard.PrioritisedExecutor.Priority priority, -+ java.util.function.Consumer> onLoad) { -+ -+ int minBlockX = Mth.floor(axisalignedbb.minX - 1.0E-7D) - 3; -+ int maxBlockX = Mth.floor(axisalignedbb.maxX + 1.0E-7D) + 3; -+ -+ int minBlockZ = Mth.floor(axisalignedbb.minZ - 1.0E-7D) - 3; -+ int maxBlockZ = Mth.floor(axisalignedbb.maxZ + 1.0E-7D) + 3; -+ -+ int minChunkX = minBlockX >> 4; -+ int maxChunkX = maxBlockX >> 4; -+ -+ int minChunkZ = minBlockZ >> 4; -+ int maxChunkZ = maxBlockZ >> 4; -+ -+ this.loadChunksAsync(minChunkX, maxChunkX, minChunkZ, maxChunkZ, priority, onLoad); -+ } -+ -+ // Paper start - rewrite chunk system -+ public final io.papermc.paper.chunk.system.scheduling.ChunkTaskScheduler chunkTaskScheduler; -+ public final io.papermc.paper.chunk.system.io.RegionFileIOThread.ChunkDataController chunkDataControllerNew -+ = new io.papermc.paper.chunk.system.io.RegionFileIOThread.ChunkDataController(io.papermc.paper.chunk.system.io.RegionFileIOThread.RegionFileType.CHUNK_DATA) { -+ -+ @Override -+ public net.minecraft.world.level.chunk.storage.RegionFileStorage getCache() { -+ return ServerLevel.this.getChunkSource().chunkMap.regionFileCache; -+ } -+ -+ @Override -+ public void writeData(int chunkX, int chunkZ, net.minecraft.nbt.CompoundTag compound) throws IOException { -+ ServerLevel.this.getChunkSource().chunkMap.write(new ChunkPos(chunkX, chunkZ), compound); -+ } -+ -+ @Override -+ public net.minecraft.nbt.CompoundTag readData(int chunkX, int chunkZ) throws IOException { -+ return ServerLevel.this.getChunkSource().chunkMap.readSync(new ChunkPos(chunkX, chunkZ)); -+ } -+ }; -+ public final io.papermc.paper.chunk.system.io.RegionFileIOThread.ChunkDataController poiDataControllerNew -+ = new io.papermc.paper.chunk.system.io.RegionFileIOThread.ChunkDataController(io.papermc.paper.chunk.system.io.RegionFileIOThread.RegionFileType.POI_DATA) { -+ -+ @Override -+ public net.minecraft.world.level.chunk.storage.RegionFileStorage getCache() { -+ return ServerLevel.this.getChunkSource().chunkMap.getPoiManager(); -+ } -+ -+ @Override -+ public void writeData(int chunkX, int chunkZ, net.minecraft.nbt.CompoundTag compound) throws IOException { -+ ServerLevel.this.getChunkSource().chunkMap.getPoiManager().write(new ChunkPos(chunkX, chunkZ), compound); -+ } -+ -+ @Override -+ public net.minecraft.nbt.CompoundTag readData(int chunkX, int chunkZ) throws IOException { -+ return ServerLevel.this.getChunkSource().chunkMap.getPoiManager().read(new ChunkPos(chunkX, chunkZ)); -+ } -+ }; -+ public final io.papermc.paper.chunk.system.io.RegionFileIOThread.ChunkDataController entityDataControllerNew -+ = new io.papermc.paper.chunk.system.io.RegionFileIOThread.ChunkDataController(io.papermc.paper.chunk.system.io.RegionFileIOThread.RegionFileType.ENTITY_DATA) { -+ -+ @Override -+ public net.minecraft.world.level.chunk.storage.RegionFileStorage getCache() { -+ return ServerLevel.this.entityStorage; -+ } -+ -+ @Override -+ public void writeData(int chunkX, int chunkZ, net.minecraft.nbt.CompoundTag compound) throws IOException { -+ ServerLevel.this.writeEntityChunk(chunkX, chunkZ, compound); -+ } -+ -+ @Override -+ public net.minecraft.nbt.CompoundTag readData(int chunkX, int chunkZ) throws IOException { -+ return ServerLevel.this.readEntityChunk(chunkX, chunkZ); -+ } -+ }; -+ private final EntityRegionFileStorage entityStorage; -+ -+ private static final class EntityRegionFileStorage extends net.minecraft.world.level.chunk.storage.RegionFileStorage { -+ -+ public EntityRegionFileStorage(RegionStorageInfo storageKey, Path directory, boolean dsync) { -+ super(storageKey, directory, dsync); -+ } -+ -+ protected void write(ChunkPos pos, net.minecraft.nbt.CompoundTag nbt) throws IOException { -+ ChunkPos nbtPos = nbt == null ? null : EntityStorage.readChunkPos(nbt); -+ if (nbtPos != null && !pos.equals(nbtPos)) { -+ throw new IllegalArgumentException( -+ "Entity chunk coordinate and serialized data do not have matching coordinates, trying to serialize coordinate " + pos.toString() -+ + " but compound says coordinate is " + nbtPos + " for world: " + this -+ ); -+ } -+ super.write(pos, nbt); -+ } -+ } -+ -+ private void writeEntityChunk(int chunkX, int chunkZ, net.minecraft.nbt.CompoundTag compound) throws IOException { -+ if (!io.papermc.paper.chunk.system.io.RegionFileIOThread.isRegionFileThread()) { -+ io.papermc.paper.chunk.system.io.RegionFileIOThread.scheduleSave( -+ this, chunkX, chunkZ, compound, -+ io.papermc.paper.chunk.system.io.RegionFileIOThread.RegionFileType.ENTITY_DATA); -+ return; -+ } -+ this.entityStorage.write(new ChunkPos(chunkX, chunkZ), compound); -+ } -+ -+ private net.minecraft.nbt.CompoundTag readEntityChunk(int chunkX, int chunkZ) throws IOException { -+ if (!io.papermc.paper.chunk.system.io.RegionFileIOThread.isRegionFileThread()) { -+ return io.papermc.paper.chunk.system.io.RegionFileIOThread.loadData( -+ this, chunkX, chunkZ, io.papermc.paper.chunk.system.io.RegionFileIOThread.RegionFileType.ENTITY_DATA, -+ io.papermc.paper.chunk.system.io.RegionFileIOThread.getIOBlockingPriorityForCurrentThread() -+ ); -+ } -+ return this.entityStorage.read(new ChunkPos(chunkX, chunkZ)); -+ } -+ -+ private final io.papermc.paper.chunk.system.entity.EntityLookup entityLookup; -+ public final io.papermc.paper.chunk.system.entity.EntityLookup getEntityLookup() { -+ return this.entityLookup; -+ } -+ -+ private final java.util.concurrent.atomic.AtomicLong nonFullSyncLoadIdGenerator = new java.util.concurrent.atomic.AtomicLong(); -+ -+ private ChunkAccess getIfAboveStatus(int chunkX, int chunkZ, net.minecraft.world.level.chunk.status.ChunkStatus status) { -+ io.papermc.paper.chunk.system.scheduling.NewChunkHolder loaded = -+ this.chunkTaskScheduler.chunkHolderManager.getChunkHolder(chunkX, chunkZ); -+ io.papermc.paper.chunk.system.scheduling.NewChunkHolder.ChunkCompletion loadedCompletion; -+ if (loaded != null && (loadedCompletion = loaded.getLastChunkCompletion()) != null && loadedCompletion.genStatus().isOrAfter(status)) { -+ return loadedCompletion.chunk(); -+ } -+ -+ return null; -+ } -+ -+ @Override -+ public ChunkAccess syncLoadNonFull(int chunkX, int chunkZ, net.minecraft.world.level.chunk.status.ChunkStatus status) { -+ if (status == null || status.isOrAfter(net.minecraft.world.level.chunk.status.ChunkStatus.FULL)) { -+ throw new IllegalArgumentException("Status: " + status); -+ } -+ ChunkAccess loaded = this.getIfAboveStatus(chunkX, chunkZ, status); -+ if (loaded != null) { -+ return loaded; -+ } -+ -+ Long ticketId = Long.valueOf(this.nonFullSyncLoadIdGenerator.getAndIncrement()); -+ int ticketLevel = 33 + net.minecraft.world.level.chunk.status.ChunkStatus.getDistance(status); -+ this.chunkTaskScheduler.chunkHolderManager.addTicketAtLevel( -+ TicketType.NON_FULL_SYNC_LOAD, chunkX, chunkZ, ticketLevel, ticketId -+ ); -+ this.chunkTaskScheduler.chunkHolderManager.processTicketUpdates(); -+ -+ this.chunkTaskScheduler.beginChunkLoadForNonFullSync(chunkX, chunkZ, status, ca.spottedleaf.concurrentutil.executor.standard.PrioritisedExecutor.Priority.BLOCKING); -+ -+ // we could do a simple spinwait here, since we do not need to process tasks while performing this load -+ // but we process tasks only because it's a better use of the time spent -+ this.chunkSource.mainThreadProcessor.managedBlock(() -> { -+ return ServerLevel.this.getIfAboveStatus(chunkX, chunkZ, status) != null; -+ }); -+ -+ loaded = ServerLevel.this.getIfAboveStatus(chunkX, chunkZ, status); -+ if (loaded == null) { -+ throw new IllegalStateException("Expected chunk to be loaded for status " + status); -+ } -+ -+ this.chunkTaskScheduler.chunkHolderManager.removeTicketAtLevel( -+ TicketType.NON_FULL_SYNC_LOAD, chunkX, chunkZ, ticketLevel, ticketId -+ ); -+ -+ return loaded; -+ } -+ -+ public final int getRegionChunkShift() { -+ // placeholder for folia -+ return io.papermc.paper.threadedregions.TickRegions.getRegionChunkShift(); -+ } -+ // Paper end - rewrite chunk system -+ -+ public final io.papermc.paper.chunk.system.RegionizedPlayerChunkLoader playerChunkLoader = new io.papermc.paper.chunk.system.RegionizedPlayerChunkLoader(this); -+ private final java.util.concurrent.atomic.AtomicReference viewDistances = new java.util.concurrent.atomic.AtomicReference<>(new io.papermc.paper.chunk.system.RegionizedPlayerChunkLoader.ViewDistances(-1, -1, -1)); -+ -+ public io.papermc.paper.chunk.system.RegionizedPlayerChunkLoader.ViewDistances getViewDistances() { -+ return this.viewDistances.get(); -+ } -+ -+ private void updateViewDistance(final java.util.function.Function update) { -+ for (io.papermc.paper.chunk.system.RegionizedPlayerChunkLoader.ViewDistances curr = this.viewDistances.get();;) { -+ if (this.viewDistances.compareAndSet(curr, update.apply(curr))) { -+ return; -+ } -+ } -+ } -+ -+ public void setTickViewDistance(final int distance) { -+ if ((distance < io.papermc.paper.chunk.system.RegionizedPlayerChunkLoader.MIN_VIEW_DISTANCE || distance > io.papermc.paper.chunk.system.RegionizedPlayerChunkLoader.MAX_VIEW_DISTANCE)) { -+ throw new IllegalArgumentException("Tick view distance must be a number between " + io.papermc.paper.chunk.system.RegionizedPlayerChunkLoader.MIN_VIEW_DISTANCE + " and " + (io.papermc.paper.chunk.system.RegionizedPlayerChunkLoader.MAX_VIEW_DISTANCE) + ", got: " + distance); -+ } -+ this.updateViewDistance((input) -> { -+ return input.setTickViewDistance(distance); -+ }); -+ } -+ -+ public void setLoadViewDistance(final int distance) { -+ if (distance != -1 && (distance < io.papermc.paper.chunk.system.RegionizedPlayerChunkLoader.MIN_VIEW_DISTANCE || distance > io.papermc.paper.chunk.system.RegionizedPlayerChunkLoader.MAX_VIEW_DISTANCE + 1)) { -+ throw new IllegalArgumentException("Load view distance must be a number between " + io.papermc.paper.chunk.system.RegionizedPlayerChunkLoader.MIN_VIEW_DISTANCE + " and " + (io.papermc.paper.chunk.system.RegionizedPlayerChunkLoader.MAX_VIEW_DISTANCE + 1) + " or -1, got: " + distance); -+ } -+ this.updateViewDistance((input) -> { -+ return input.setLoadViewDistance(distance); -+ }); -+ } -+ -+ public void setSendViewDistance(final int distance) { -+ if (distance != -1 && (distance < io.papermc.paper.chunk.system.RegionizedPlayerChunkLoader.MIN_VIEW_DISTANCE || distance > io.papermc.paper.chunk.system.RegionizedPlayerChunkLoader.MAX_VIEW_DISTANCE + 1)) { -+ throw new IllegalArgumentException("Send view distance must be a number between " + io.papermc.paper.chunk.system.RegionizedPlayerChunkLoader.MIN_VIEW_DISTANCE + " and " + (io.papermc.paper.chunk.system.RegionizedPlayerChunkLoader.MAX_VIEW_DISTANCE + 1) + " or -1, got: " + distance); -+ } -+ this.updateViewDistance((input) -> { -+ return input.setSendViewDistance(distance); -+ }); -+ } - - // Paper start - optimise getPlayerByUUID - @Nullable -@@ -382,16 +613,16 @@ public class ServerLevel extends Level implements WorldGenLevel { - // CraftBukkit end - boolean flag2 = minecraftserver.forceSynchronousWrites(); - DataFixer datafixer = minecraftserver.getFixerUpper(); -- EntityPersistentStorage entitypersistentstorage = new EntityStorage(new SimpleRegionStorage(new RegionStorageInfo(convertable_conversionsession.getLevelId(), resourcekey, "entities"), convertable_conversionsession.getDimensionPath(resourcekey).resolve("entities"), datafixer, flag2, DataFixTypes.ENTITY_CHUNK), this, minecraftserver); -+ this.entityStorage = new EntityRegionFileStorage(new RegionStorageInfo(convertable_conversionsession.getLevelId(), resourcekey, "entities"), convertable_conversionsession.getDimensionPath(resourcekey).resolve("entities"), flag2); // Paper - rewrite chunk system - -- this.entityManager = new PersistentEntitySectionManager<>(Entity.class, new ServerLevel.EntityCallbacks(), entitypersistentstorage); -+ // this.entityManager = new PersistentEntitySectionManager<>(Entity.class, new ServerLevel.EntityCallbacks(), entitypersistentstorage, this.entitySliceManager); // Paper // Paper - rewrite chunk system - StructureTemplateManager structuretemplatemanager = minecraftserver.getStructureManager(); - int j = this.spigotConfig.viewDistance; // Spigot - int k = this.spigotConfig.simulationDistance; // Spigot -- PersistentEntitySectionManager persistententitysectionmanager = this.entityManager; -+ //PersistentEntitySectionManager persistententitysectionmanager = this.entityManager; // Paper - rewrite chunk system - -- Objects.requireNonNull(this.entityManager); -- this.chunkSource = new ServerChunkCache(this, convertable_conversionsession, datafixer, structuretemplatemanager, executor, chunkgenerator, j, k, flag2, worldloadlistener, persistententitysectionmanager::updateChunkStatus, () -> { -+ //Objects.requireNonNull(this.entityManager); // Paper - rewrite chunk system -+ this.chunkSource = new ServerChunkCache(this, convertable_conversionsession, datafixer, structuretemplatemanager, executor, chunkgenerator, j, k, flag2, worldloadlistener, null, () -> { // Paper - rewrite chunk system - return minecraftserver.overworld().getDataStorage(); - }); - this.chunkSource.getGeneratorState().ensureStructuresGenerated(); -@@ -420,6 +651,9 @@ public class ServerLevel extends Level implements WorldGenLevel { - return (RandomSequences) this.getDataStorage().computeIfAbsent(RandomSequences.factory(l), "random_sequences"); - }); - this.getCraftServer().addWorld(this.getWorld()); // CraftBukkit -+ -+ this.chunkTaskScheduler = new io.papermc.paper.chunk.system.scheduling.ChunkTaskScheduler(this, io.papermc.paper.chunk.system.scheduling.ChunkTaskScheduler.workerThreads); // Paper - rewrite chunk system -+ this.entityLookup = new io.papermc.paper.chunk.system.entity.EntityLookup(this, new EntityCallbacks()); // Paper - rewrite chunk system - } - - // Paper start -@@ -552,7 +786,7 @@ public class ServerLevel extends Level implements WorldGenLevel { - gameprofilerfiller.push("checkDespawn"); - entity.checkDespawn(); - gameprofilerfiller.pop(); -- if (this.chunkSource.chunkMap.getDistanceManager().inEntityTickingRange(entity.chunkPosition().toLong())) { -+ if (true || this.chunkSource.chunkMap.getDistanceManager().inEntityTickingRange(entity.chunkPosition().toLong())) { // Paper - now always true if in the ticking list - Entity entity1 = entity.getVehicle(); - - if (entity1 != null) { -@@ -577,13 +811,16 @@ public class ServerLevel extends Level implements WorldGenLevel { - } - - gameprofilerfiller.push("entityManagement"); -- this.entityManager.tick(); -+ //this.entityManager.tick(); // Paper - rewrite chunk system - gameprofilerfiller.pop(); - } - - @Override - public boolean shouldTickBlocksAt(long chunkPos) { -- return this.chunkSource.chunkMap.getDistanceManager().inBlockTickingRange(chunkPos); -+ // Paper start - replace player chunk loader system -+ ChunkHolder holder = this.chunkSource.chunkMap.getVisibleChunkIfPresent(chunkPos); -+ return holder != null && holder.isTickingReady(); -+ // Paper end - replace player chunk loader system - } - - protected void tickTime() { -@@ -1060,6 +1297,11 @@ public class ServerLevel extends Level implements WorldGenLevel { - } - - public void save(@Nullable ProgressListener progressListener, boolean flush, boolean savingDisabled) { -+ // Paper start - rewrite chunk system - add close param -+ this.save(progressListener, flush, savingDisabled, false); -+ } -+ public void save(@Nullable ProgressListener progressListener, boolean flush, boolean savingDisabled, boolean close) { -+ // Paper end - rewrite chunk system - add close param - ServerChunkCache chunkproviderserver = this.getChunkSource(); - - if (!savingDisabled) { -@@ -1075,16 +1317,13 @@ public class ServerLevel extends Level implements WorldGenLevel { - } - - timings.worldSaveChunks.startTiming(); // Paper -- chunkproviderserver.save(flush); -+ if (!close) chunkproviderserver.save(flush); // Paper - rewrite chunk system -+ if (close) chunkproviderserver.close(true); // Paper - rewrite chunk system - timings.worldSaveChunks.stopTiming(); // Paper - }// Paper -- if (flush) { -- this.entityManager.saveAll(); -- } else { -- this.entityManager.autoSave(); -- } -+ // Paper - rewrite chunk system - entity saving moved into ChunkHolder - -- } -+ } else if (close) { chunkproviderserver.close(false); } // Paper - rewrite chunk system - - // CraftBukkit start - moved from MinecraftServer.saveChunks - ServerLevel worldserver1 = this; -@@ -1220,7 +1459,7 @@ public class ServerLevel extends Level implements WorldGenLevel { - this.removePlayerImmediately((ServerPlayer) entity, Entity.RemovalReason.DISCARDED); - } - -- this.entityManager.addNewEntity(player); -+ this.entityLookup.addNewEntity(player); // Paper - rewite chunk system - } - - // CraftBukkit start -@@ -1251,7 +1490,7 @@ public class ServerLevel extends Level implements WorldGenLevel { - } - // CraftBukkit end - -- return this.entityManager.addNewEntity(entity); -+ return this.entityLookup.addNewEntity(entity); // Paper - rewrite chunk system - } - } - -@@ -1263,10 +1502,10 @@ public class ServerLevel extends Level implements WorldGenLevel { - public boolean tryAddFreshEntityWithPassengers(Entity entity, org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason reason) { - // CraftBukkit end - Stream stream = entity.getSelfAndPassengers().map(Entity::getUUID); // CraftBukkit - decompile error -- PersistentEntitySectionManager persistententitysectionmanager = this.entityManager; -+ //PersistentEntitySectionManager persistententitysectionmanager = this.entityManager; // Paper - rewrite chunk system - -- Objects.requireNonNull(this.entityManager); -- if (stream.anyMatch(persistententitysectionmanager::isLoaded)) { -+ //Objects.requireNonNull(this.entityManager); // Paper - rewrite chunk system -+ if (stream.anyMatch(this.entityLookup::hasEntity)) { // Paper - rewrite chunk system - return false; - } else { - this.addFreshEntityWithPassengers(entity, reason); // CraftBukkit -@@ -1852,7 +2091,7 @@ public class ServerLevel extends Level implements WorldGenLevel { - } - } - -- bufferedwriter.write(String.format(Locale.ROOT, "entities: %s\n", this.entityManager.gatherStats())); -+ bufferedwriter.write(String.format(Locale.ROOT, "entities: %s\n", this.entityLookup.getDebugInfo())); // Paper - rewrite chunk system - bufferedwriter.write(String.format(Locale.ROOT, "block_entity_tickers: %d\n", this.blockEntityTickers.size())); - bufferedwriter.write(String.format(Locale.ROOT, "block_ticks: %d\n", this.getBlockTicks().count())); - bufferedwriter.write(String.format(Locale.ROOT, "fluid_ticks: %d\n", this.getFluidTicks().count())); -@@ -1901,7 +2140,7 @@ public class ServerLevel extends Level implements WorldGenLevel { - BufferedWriter bufferedwriter2 = Files.newBufferedWriter(path1); - - try { -- playerchunkmap.dumpChunks(bufferedwriter2); -+ //playerchunkmap.dumpChunks(bufferedwriter2); // Paper - rewrite chunk system - } catch (Throwable throwable4) { - if (bufferedwriter2 != null) { - try { -@@ -1922,7 +2161,7 @@ public class ServerLevel extends Level implements WorldGenLevel { - BufferedWriter bufferedwriter3 = Files.newBufferedWriter(path2); - - try { -- this.entityManager.dumpSections(bufferedwriter3); -+ //this.entityManager.dumpSections(bufferedwriter3); // Paper - rewrite chunk system - } catch (Throwable throwable6) { - if (bufferedwriter3 != null) { - try { -@@ -2064,7 +2303,7 @@ public class ServerLevel extends Level implements WorldGenLevel { - - @VisibleForTesting - public String getWatchdogStats() { -- return String.format(Locale.ROOT, "players: %s, entities: %s [%s], block_entities: %d [%s], block_ticks: %d, fluid_ticks: %d, chunk_source: %s", this.players.size(), this.entityManager.gatherStats(), ServerLevel.getTypeCount(this.entityManager.getEntityGetter().getAll(), (entity) -> { -+ return String.format(Locale.ROOT, "players: %s, entities: %s [%s], block_entities: %d [%s], block_ticks: %d, fluid_ticks: %d, chunk_source: %s", this.players.size(), this.entityLookup.getDebugInfo(), ServerLevel.getTypeCount(this.entityLookup.getAll(), (entity) -> { // Paper - rewrite chunk system - return BuiltInRegistries.ENTITY_TYPE.getKey(entity.getType()).toString(); - }), this.blockEntityTickers.size(), ServerLevel.getTypeCount(this.blockEntityTickers, TickingBlockEntity::getType), this.getBlockTicks().count(), this.getFluidTicks().count(), this.gatherChunkSourceStats()); - } -@@ -2124,15 +2363,15 @@ public class ServerLevel extends Level implements WorldGenLevel { - @Override - public LevelEntityGetter getEntities() { - org.spigotmc.AsyncCatcher.catchOp("Chunk getEntities call"); // Spigot -- return this.entityManager.getEntityGetter(); -+ return this.entityLookup; // Paper - rewrite chunk system - } - -- public void addLegacyChunkEntities(Stream entities) { -- this.entityManager.addLegacyChunkEntities(entities); -+ public void addLegacyChunkEntities(Stream entities, ChunkPos forChunk) { // Paper - rewrite chunk system -+ this.entityLookup.addLegacyChunkEntities(entities.toList(), forChunk); // Paper - rewrite chunk system - } - -- public void addWorldGenChunkEntities(Stream entities) { -- this.entityManager.addWorldGenChunkEntities(entities); -+ public void addWorldGenChunkEntities(Stream entities, ChunkPos forChunk) { // Paper - rewrite chunk system -+ this.entityLookup.addWorldGenChunkEntities(entities.toList(), forChunk); // Paper - rewrite chunk system - } - - public void startTickingChunk(LevelChunk chunk) { -@@ -2152,34 +2391,49 @@ public class ServerLevel extends Level implements WorldGenLevel { - @Override - public void close() throws IOException { - super.close(); -- this.entityManager.close(); -+ //this.entityManager.close(); // Paper - rewrite chunk system - } - - @Override - public String gatherChunkSourceStats() { - String s = this.chunkSource.gatherStats(); - -- return "Chunks[S] W: " + s + " E: " + this.entityManager.gatherStats(); -+ return "Chunks[S] W: " + s + " E: " + this.entityLookup.getDebugInfo(); // Paper - rewrite chunk system - } - - public boolean areEntitiesLoaded(long chunkPos) { -- return this.entityManager.areEntitiesLoaded(chunkPos); -+ // Paper start - rewrite chunk system -+ return this.getChunkIfLoadedImmediately(ChunkPos.getX(chunkPos), ChunkPos.getZ(chunkPos)) != null; -+ // Paper end - rewrite chunk system - } - - private boolean isPositionTickingWithEntitiesLoaded(long chunkPos) { -- return this.areEntitiesLoaded(chunkPos) && this.chunkSource.isPositionTicking(chunkPos); -+ // Paper start - optimize is ticking ready type functions -+ io.papermc.paper.chunk.system.scheduling.NewChunkHolder chunkHolder = this.chunkTaskScheduler.chunkHolderManager.getChunkHolder(chunkPos); -+ // isTicking implies the chunk is loaded, and the chunk is loaded now implies the entities are loaded -+ return chunkHolder != null && chunkHolder.isTickingReady(); -+ // Paper end - } - - public boolean isPositionEntityTicking(BlockPos pos) { -- return this.entityManager.canPositionTick(pos) && this.chunkSource.chunkMap.getDistanceManager().inEntityTickingRange(ChunkPos.asLong(pos)); -+ // Paper start - rewrite chunk system -+ io.papermc.paper.chunk.system.scheduling.NewChunkHolder chunkHolder = this.chunkTaskScheduler.chunkHolderManager.getChunkHolder(io.papermc.paper.util.CoordinateUtils.getChunkKey(pos)); -+ return chunkHolder != null && chunkHolder.isEntityTickingReady(); -+ // Paper end - rewrite chunk system - } - - public boolean isNaturalSpawningAllowed(BlockPos pos) { -- return this.entityManager.canPositionTick(pos); -+ // Paper start - rewrite chunk system -+ io.papermc.paper.chunk.system.scheduling.NewChunkHolder chunkHolder = this.chunkTaskScheduler.chunkHolderManager.getChunkHolder(io.papermc.paper.util.CoordinateUtils.getChunkKey(pos)); -+ return chunkHolder != null && chunkHolder.isEntityTickingReady(); -+ // Paper end - rewrite chunk system - } - - public boolean isNaturalSpawningAllowed(ChunkPos pos) { -- return this.entityManager.canPositionTick(pos); -+ // Paper start - rewrite chunk system -+ io.papermc.paper.chunk.system.scheduling.NewChunkHolder chunkHolder = this.chunkTaskScheduler.chunkHolderManager.getChunkHolder(io.papermc.paper.util.CoordinateUtils.getChunkKey(pos)); -+ return chunkHolder != null && chunkHolder.isEntityTickingReady(); -+ // Paper end - rewrite chunk system - } - - @Override -@@ -2205,7 +2459,7 @@ public class ServerLevel extends Level implements WorldGenLevel { - CrashReportCategory crashreportsystemdetails = super.fillReportDetails(report); - - crashreportsystemdetails.setDetail("Loaded entity count", () -> { -- return String.valueOf(this.entityManager.count()); -+ return String.valueOf(this.entityLookup.getAllCopy().length); // Paper - }); - return crashreportsystemdetails; - } -diff --git a/src/main/java/net/minecraft/server/level/ServerPlayer.java b/src/main/java/net/minecraft/server/level/ServerPlayer.java -index b032ce115b98af0e0384fb88ca88075eb4ffac11..e2b72b07888e84fb4472920932b3feedbd4829b9 100644 ---- a/src/main/java/net/minecraft/server/level/ServerPlayer.java -+++ b/src/main/java/net/minecraft/server/level/ServerPlayer.java -@@ -293,6 +293,50 @@ public class ServerPlayer extends Player { - public @Nullable String clientBrandName = null; // Paper - Brand support - public org.bukkit.event.player.PlayerQuitEvent.QuitReason quitReason = null; // Paper - Add API for quit reason; there are a lot of changes to do if we change all methods leading to the event - -+ // Paper start - replace player chunk loader -+ private final java.util.concurrent.atomic.AtomicReference viewDistances = new java.util.concurrent.atomic.AtomicReference<>(new io.papermc.paper.chunk.system.RegionizedPlayerChunkLoader.ViewDistances(-1, -1, -1)); -+ public io.papermc.paper.chunk.system.RegionizedPlayerChunkLoader.PlayerChunkLoaderData chunkLoader; -+ -+ public io.papermc.paper.chunk.system.RegionizedPlayerChunkLoader.ViewDistances getViewDistances() { -+ return this.viewDistances.get(); -+ } -+ -+ private void updateViewDistance(final java.util.function.Function update) { -+ for (io.papermc.paper.chunk.system.RegionizedPlayerChunkLoader.ViewDistances curr = this.viewDistances.get();;) { -+ if (this.viewDistances.compareAndSet(curr, update.apply(curr))) { -+ return; -+ } -+ } -+ } -+ -+ public void setTickViewDistance(final int distance) { -+ if ((distance < io.papermc.paper.chunk.system.RegionizedPlayerChunkLoader.MIN_VIEW_DISTANCE || distance > io.papermc.paper.chunk.system.RegionizedPlayerChunkLoader.MAX_VIEW_DISTANCE)) { -+ throw new IllegalArgumentException("Tick view distance must be a number between " + io.papermc.paper.chunk.system.RegionizedPlayerChunkLoader.MIN_VIEW_DISTANCE + " and " + (io.papermc.paper.chunk.system.RegionizedPlayerChunkLoader.MAX_VIEW_DISTANCE) + ", got: " + distance); -+ } -+ this.updateViewDistance((input) -> { -+ return input.setTickViewDistance(distance); -+ }); -+ } -+ -+ public void setLoadViewDistance(final int distance) { -+ if (distance != -1 && (distance < io.papermc.paper.chunk.system.RegionizedPlayerChunkLoader.MIN_VIEW_DISTANCE || distance > io.papermc.paper.chunk.system.RegionizedPlayerChunkLoader.MAX_VIEW_DISTANCE + 1)) { -+ throw new IllegalArgumentException("Load view distance must be a number between " + io.papermc.paper.chunk.system.RegionizedPlayerChunkLoader.MIN_VIEW_DISTANCE + " and " + (io.papermc.paper.chunk.system.RegionizedPlayerChunkLoader.MAX_VIEW_DISTANCE + 1) + " or -1, got: " + distance); -+ } -+ this.updateViewDistance((input) -> { -+ return input.setLoadViewDistance(distance); -+ }); -+ } -+ -+ public void setSendViewDistance(final int distance) { -+ if (distance != -1 && (distance < io.papermc.paper.chunk.system.RegionizedPlayerChunkLoader.MIN_VIEW_DISTANCE || distance > io.papermc.paper.chunk.system.RegionizedPlayerChunkLoader.MAX_VIEW_DISTANCE + 1)) { -+ throw new IllegalArgumentException("Send view distance must be a number between " + io.papermc.paper.chunk.system.RegionizedPlayerChunkLoader.MIN_VIEW_DISTANCE + " and " + (io.papermc.paper.chunk.system.RegionizedPlayerChunkLoader.MAX_VIEW_DISTANCE + 1) + " or -1, got: " + distance); -+ } -+ this.updateViewDistance((input) -> { -+ return input.setSendViewDistance(distance); -+ }); -+ } -+ // Paper end - replace player chunk loader -+ - public ServerPlayer(MinecraftServer server, ServerLevel world, GameProfile profile, ClientInformation clientOptions) { - super(world, world.getSharedSpawnPos(), world.getSharedSpawnAngle(), profile); - this.chatVisibility = ChatVisiblity.FULL; -diff --git a/src/main/java/net/minecraft/server/level/ThreadedLevelLightEngine.java b/src/main/java/net/minecraft/server/level/ThreadedLevelLightEngine.java -index f206df06a7d8895175db31d4a840d7467ffe826f..8ef22f8f0d6da49247a90152e5cfa9ffc7f596a4 100644 ---- a/src/main/java/net/minecraft/server/level/ThreadedLevelLightEngine.java -+++ b/src/main/java/net/minecraft/server/level/ThreadedLevelLightEngine.java -@@ -37,15 +37,12 @@ import net.minecraft.world.level.chunk.status.ChunkStatus; - public class ThreadedLevelLightEngine extends LevelLightEngine implements AutoCloseable { - public static final int DEFAULT_BATCH_SIZE = 1000; - private static final Logger LOGGER = LogUtils.getLogger(); -- private final ProcessorMailbox taskMailbox; -- private final ObjectList> lightTasks = new ObjectArrayList<>(); -+ // Paper - rewrite chunk system - private final ChunkMap chunkMap; -- private final ProcessorHandle> sorterMailbox; -- private final int taskPerBatch = 1000; -- private final AtomicBoolean scheduled = new AtomicBoolean(); -+ // Paper - rewrite chunk system - - // Paper start - replace light engine impl -- protected final ca.spottedleaf.starlight.common.light.StarLightInterface theLightEngine; -+ public final ca.spottedleaf.starlight.common.light.StarLightInterface theLightEngine; - public final boolean hasBlockLight; - public final boolean hasSkyLight; - // Paper end - replace light engine impl -@@ -59,8 +56,7 @@ public class ThreadedLevelLightEngine extends LevelLightEngine implements AutoCl - ) { - super(chunkProvider, false, false); // Paper - destroy vanilla light engine state - this.chunkMap = chunkStorage; -- this.sorterMailbox = executor; -- this.taskMailbox = processor; -+ // Paper - rewrite chunk system - // Paper start - replace light engine impl - this.hasBlockLight = true; - this.hasSkyLight = hasBlockLight; // Nice variable name. -@@ -104,7 +100,7 @@ public class ThreadedLevelLightEngine extends LevelLightEngine implements AutoCl - ++totalChunks; - } - -- this.taskMailbox.tell(() -> { -+ this.chunkMap.level.chunkTaskScheduler.radiusAwareScheduler.queueInfiniteRadiusTask(() -> { // Paper - rewrite chunk system - this.theLightEngine.relightChunks(chunks, (ChunkPos chunkPos) -> { - chunkLightCallback.accept(chunkPos); - ((java.util.concurrent.Executor)((ServerLevel)this.theLightEngine.getWorld()).getChunkSource().mainThreadProcessor).execute(() -> { -@@ -121,7 +117,7 @@ public class ThreadedLevelLightEngine extends LevelLightEngine implements AutoCl - private final Long2IntOpenHashMap chunksBeingWorkedOn = new Long2IntOpenHashMap(); - - private void queueTaskForSection(final int chunkX, final int chunkY, final int chunkZ, -- final Supplier runnable) { -+ final Supplier runnable) { // Paper - rewrite chunk system - final ServerLevel world = (ServerLevel)this.theLightEngine.getWorld(); - - final ChunkAccess center = this.theLightEngine.getAnyChunkNow(chunkX, chunkZ); -@@ -148,7 +144,7 @@ public class ThreadedLevelLightEngine extends LevelLightEngine implements AutoCl - - final long key = CoordinateUtils.getChunkKey(chunkX, chunkZ); - -- final ca.spottedleaf.starlight.common.light.StarLightInterface.LightQueue.ChunkTasks updateFuture = runnable.get(); -+ final io.papermc.paper.chunk.system.light.LightQueue.ChunkTasks updateFuture = runnable.get(); // Paper - rewrite chunk system - - if (updateFuture == null) { - // not scheduled -@@ -285,16 +281,11 @@ public class ThreadedLevelLightEngine extends LevelLightEngine implements AutoCl - } - - private void addTask(int x, int z, ThreadedLevelLightEngine.TaskType stage, Runnable task) { -- this.addTask(x, z, this.chunkMap.getChunkQueueLevel(ChunkPos.asLong(x, z)), stage, task); -+ throw new UnsupportedOperationException(); // Paper - rewrite chunk system - } - - private void addTask(int x, int z, IntSupplier completedLevelSupplier, ThreadedLevelLightEngine.TaskType stage, Runnable task) { -- this.sorterMailbox.tell(ChunkTaskPriorityQueueSorter.message(() -> { -- this.lightTasks.add(Pair.of(stage, task)); -- if (this.lightTasks.size() >= 1000) { -- this.runUpdate(); -- } -- }, ChunkPos.asLong(x, z), completedLevelSupplier)); -+ throw new UnsupportedOperationException(); // Paper - rewrite chunk system - } - - @Override -@@ -327,83 +318,15 @@ public class ThreadedLevelLightEngine extends LevelLightEngine implements AutoCl - } - - public CompletableFuture lightChunk(ChunkAccess chunk, boolean excludeBlocks) { -- // Paper start - replace light engine impl -- if (true) { -- boolean lit = excludeBlocks; -- final ChunkPos chunkPos = chunk.getPos(); -- -- return CompletableFuture.supplyAsync(() -> { -- final Boolean[] emptySections = StarLightEngine.getEmptySectionsForChunk(chunk); -- if (!lit) { -- chunk.setLightCorrect(false); -- this.theLightEngine.lightChunk(chunk, emptySections); -- chunk.setLightCorrect(true); -- } else { -- this.theLightEngine.forceLoadInChunk(chunk, emptySections); -- // can't really force the chunk to be edged checked, as we need neighbouring chunks - but we don't have -- // them, so if it's not loaded then i guess we can't do edge checks. later loads of the chunk should -- // catch what we miss here. -- this.theLightEngine.checkChunkEdges(chunkPos.x, chunkPos.z); -- } -- -- this.chunkMap.releaseLightTicket(chunkPos); -- return chunk; -- }, (runnable) -> { -- this.theLightEngine.scheduleChunkLight(chunkPos, runnable); -- this.tryScheduleUpdate(); -- }).whenComplete((final ChunkAccess c, final Throwable throwable) -> { -- if (throwable != null) { -- LOGGER.error("Failed to light chunk " + chunkPos, throwable); -- } -- }); -- } -- // Paper end - replace light engine impl -- ChunkPos chunkPos = chunk.getPos(); -- chunk.setLightCorrect(false); -- this.addTask(chunkPos.x, chunkPos.z, ThreadedLevelLightEngine.TaskType.PRE_UPDATE, Util.name(() -> { -- if (!excludeBlocks) { -- super.propagateLightSources(chunkPos); -- } -- }, () -> "lightChunk " + chunkPos + " " + excludeBlocks)); -- return CompletableFuture.supplyAsync(() -> { -- chunk.setLightCorrect(true); -- this.chunkMap.releaseLightTicket(chunkPos); -- return chunk; -- }, task -> this.addTask(chunkPos.x, chunkPos.z, ThreadedLevelLightEngine.TaskType.POST_UPDATE, task)); -+ throw new UnsupportedOperationException(); // Paper - rewrite chunk system - } - - public void tryScheduleUpdate() { -- if (this.hasLightWork() && this.scheduled.compareAndSet(false, true)) { // Paper // Paper - rewrite light engine -- this.taskMailbox.tell(() -> { -- this.runUpdate(); -- this.scheduled.set(false); -- }); -- } -+ // Paper - rewrite chunk system - } - - private void runUpdate() { -- int i = Math.min(this.lightTasks.size(), 1000); -- ObjectListIterator> objectListIterator = this.lightTasks.iterator(); -- -- int j; -- for (j = 0; objectListIterator.hasNext() && j < i; j++) { -- Pair pair = objectListIterator.next(); -- if (pair.getFirst() == ThreadedLevelLightEngine.TaskType.PRE_UPDATE) { -- pair.getSecond().run(); -- } -- } -- -- objectListIterator.back(j); -- this.theLightEngine.propagateChanges(); // Paper - rewrite light engine -- -- for (int var5 = 0; objectListIterator.hasNext() && var5 < i; var5++) { -- Pair pair2 = objectListIterator.next(); -- if (pair2.getFirst() == ThreadedLevelLightEngine.TaskType.POST_UPDATE) { -- pair2.getSecond().run(); -- } -- -- objectListIterator.remove(); -- } -+ throw new UnsupportedOperationException(); // Paper - rewrite chunk system - } - - public CompletableFuture waitForPendingTasks(int x, int z) { -diff --git a/src/main/java/net/minecraft/server/level/Ticket.java b/src/main/java/net/minecraft/server/level/Ticket.java -index eba83b085435150e5954fd5d41dda9ce1d0601ad..e97329f867de2acbdd666925ba5d2aafa7a90574 100644 ---- a/src/main/java/net/minecraft/server/level/Ticket.java -+++ b/src/main/java/net/minecraft/server/level/Ticket.java -@@ -6,9 +6,12 @@ public final class Ticket implements Comparable> { - private final TicketType type; - private final int ticketLevel; - public final T key; -- private long createdTick; -+ // Paper start - rewrite chunk system -+ public long removeDelay; - -- protected Ticket(TicketType type, int level, T argument) { -+ public Ticket(TicketType type, int level, T argument, long removeDelay) { -+ this.removeDelay = removeDelay; -+ // Paper end - rewrite chunk system - this.type = type; - this.ticketLevel = level; - this.key = argument; -@@ -41,7 +44,7 @@ public final class Ticket implements Comparable> { - - @Override - public String toString() { -- return "Ticket[" + this.type + " " + this.ticketLevel + " (" + this.key + ")] at " + this.createdTick; -+ return "Ticket[" + this.type + " " + this.ticketLevel + " (" + this.key + ")] to die in " + this.removeDelay; // Paper - rewrite chunk system - } - - public TicketType getType() { -@@ -53,11 +56,10 @@ public final class Ticket implements Comparable> { - } - - protected void setCreatedTick(long tickCreated) { -- this.createdTick = tickCreated; -+ throw new UnsupportedOperationException(); // Paper - rewrite chunk system - } - - protected boolean timedOut(long currentTick) { -- long l = this.type.timeout(); -- return l != 0L && currentTick - this.createdTick > l; -+ throw new UnsupportedOperationException(); // Paper - rewrite chunk system - } - } -diff --git a/src/main/java/net/minecraft/server/level/TicketType.java b/src/main/java/net/minecraft/server/level/TicketType.java -index 6051e5f272838ef23276a90e21c2fc821ca155d1..658e63ebde81dc14c8ab5850fb246dc0aab25dea 100644 ---- a/src/main/java/net/minecraft/server/level/TicketType.java -+++ b/src/main/java/net/minecraft/server/level/TicketType.java -@@ -8,6 +8,7 @@ import net.minecraft.world.level.ChunkPos; - - public class TicketType { - public static final TicketType FUTURE_AWAIT = create("future_await", Long::compareTo); // Paper -+ public static final TicketType ASYNC_LOAD = create("async_load", Long::compareTo); // Paper - - private final String name; - private final Comparator comparator; -@@ -27,6 +28,15 @@ public class TicketType { - public static final TicketType PLUGIN = TicketType.create("plugin", (a, b) -> 0); // CraftBukkit - public static final TicketType PLUGIN_TICKET = TicketType.create("plugin_ticket", (plugin1, plugin2) -> plugin1.getClass().getName().compareTo(plugin2.getClass().getName())); // CraftBukkit - public static final TicketType CHUNK_RELIGHT = create("light_update", Long::compareTo); // Paper - ensure chunks stay loaded for lighting -+ // Paper start - rewrite chunk system -+ public static final TicketType CHUNK_LOAD = create("chunk_load", Long::compareTo); -+ public static final TicketType STATUS_UPGRADE = create("status_upgrade", Long::compareTo); -+ public static final TicketType ENTITY_LOAD = create("entity_load", Long::compareTo); -+ public static final TicketType POI_LOAD = create("poi_load", Long::compareTo); -+ public static final TicketType UNLOAD_COOLDOWN = create("unload_cooldown", (u1, u2) -> 0, 5 * 20); -+ public static final TicketType NON_FULL_SYNC_LOAD = create("non_full_sync_load", Long::compareTo); -+ public static final TicketType DELAY_UNLOAD = create("delay_unload", Comparator.comparingLong(ChunkPos::toLong), 1); -+ // Paper end - rewrite chunk system - - public static TicketType create(String name, Comparator argumentComparator) { - return new TicketType<>(name, argumentComparator, 0L); -diff --git a/src/main/java/net/minecraft/server/level/WorldGenRegion.java b/src/main/java/net/minecraft/server/level/WorldGenRegion.java -index abbd4140cb4478a34a5185d8555f83d96c04d468..a6c31a558794a6e626e83176a1cbe78b6bd90f6e 100644 ---- a/src/main/java/net/minecraft/server/level/WorldGenRegion.java -+++ b/src/main/java/net/minecraft/server/level/WorldGenRegion.java -@@ -554,4 +554,21 @@ public class WorldGenRegion implements WorldGenLevel { - public long nextSubTickCount() { - return this.subTickCount.getAndIncrement(); - } -+ -+ // Paper start -+ // No-op, this class doesn't provide entity access -+ @Override -+ public List getHardCollidingEntities(Entity except, AABB box, Predicate predicate) { -+ return Collections.emptyList(); -+ } -+ -+ @Override -+ public void getEntities(Entity except, AABB box, Predicate predicate, List into) {} -+ -+ @Override -+ public void getHardCollidingEntities(Entity except, AABB box, Predicate predicate, List into) {} -+ -+ @Override -+ public void getEntitiesByClass(Class clazz, Entity except, AABB box, List into, Predicate predicate) {} -+ // Paper end - } -diff --git a/src/main/java/net/minecraft/server/network/PlayerChunkSender.java b/src/main/java/net/minecraft/server/network/PlayerChunkSender.java -index c502d9b85eb68b277ae17dfea34e0475f0156647..27d0f1ed58948039004f8f1eba2f7f9609fdeec0 100644 ---- a/src/main/java/net/minecraft/server/network/PlayerChunkSender.java -+++ b/src/main/java/net/minecraft/server/network/PlayerChunkSender.java -@@ -43,16 +43,23 @@ public class PlayerChunkSender { - - public void dropChunk(ServerPlayer player, ChunkPos pos) { - if (!this.pendingChunks.remove(pos.toLong()) && player.isAlive()) { -+ // Paper start - rewrite player chunk loader -+ dropChunkStatic(player, pos); -+ } -+ } -+ public static void dropChunkStatic(ServerPlayer player, ChunkPos pos) { -+ player.serverLevel().chunkSource.chunkMap.getVisibleChunkIfPresent(pos.toLong()).removePlayer(player); - player.connection.send(new ClientboundForgetLevelChunkPacket(pos)); - // Paper start - PlayerChunkUnloadEvent - if (io.papermc.paper.event.packet.PlayerChunkUnloadEvent.getHandlerList().getRegisteredListeners().length > 0) { - new io.papermc.paper.event.packet.PlayerChunkUnloadEvent(player.getBukkitEntity().getWorld().getChunkAt(pos.longKey), player.getBukkitEntity()).callEvent(); - } - // Paper end - PlayerChunkUnloadEvent -- } - } -+ // Paper end - rewrite player chunk loader - - public void sendNextChunks(ServerPlayer player) { -+ if (true) return; // Paper - rewrite player chunk loader - if (this.unacknowledgedBatches < this.maxUnacknowledgedBatches) { - float f = Math.max(1.0F, this.desiredChunksPerTick); - this.batchQuota = Math.min(this.batchQuota + this.desiredChunksPerTick, f); -@@ -78,7 +85,8 @@ public class PlayerChunkSender { - } - } - -- private static void sendChunk(ServerGamePacketListenerImpl handler, ServerLevel world, LevelChunk chunk) { -+ public static void sendChunk(ServerGamePacketListenerImpl handler, ServerLevel world, LevelChunk chunk) { // Paper - rewrite chunk loader - public -+ handler.player.serverLevel().chunkSource.chunkMap.getVisibleChunkIfPresent(chunk.getPos().toLong()).addPlayer(handler.player); - handler.send(new ClientboundLevelChunkWithLightPacket(chunk, world.getLightEngine(), null, null)); - // Paper start - PlayerChunkLoadEvent - if (io.papermc.paper.event.packet.PlayerChunkLoadEvent.getHandlerList().getRegisteredListeners().length > 0) { -@@ -118,6 +126,7 @@ public class PlayerChunkSender { - } - - public void onChunkBatchReceivedByClient(float desiredBatchSize) { -+ if (true) return; // Paper - rewrite player chunk loader - this.unacknowledgedBatches--; - this.desiredChunksPerTick = Double.isNaN((double)desiredBatchSize) ? 0.01F : Mth.clamp(desiredBatchSize, 0.01F, 64.0F); - if (this.unacknowledgedBatches == 0) { -diff --git a/src/main/java/net/minecraft/server/players/PlayerList.java b/src/main/java/net/minecraft/server/players/PlayerList.java -index 0aa28caa1254137c0bae8e213bd08c9a654f5335..c4b4e5f5c9366b241686e881cda34568a57b4877 100644 ---- a/src/main/java/net/minecraft/server/players/PlayerList.java -+++ b/src/main/java/net/minecraft/server/players/PlayerList.java -@@ -296,7 +296,7 @@ public abstract class PlayerList { - boolean flag2 = gamerules.getBoolean(GameRules.RULE_LIMITED_CRAFTING); - - // Spigot - view distance -- playerconnection.send(new ClientboundLoginPacket(player.getId(), worlddata.isHardcore(), this.server.levelKeys(), this.getMaxPlayers(), worldserver1.spigotConfig.viewDistance, worldserver1.spigotConfig.simulationDistance, flag1, !flag, flag2, player.createCommonSpawnInfo(worldserver1), this.server.enforceSecureProfile())); -+ playerconnection.send(new ClientboundLoginPacket(player.getId(), worlddata.isHardcore(), this.server.levelKeys(), this.getMaxPlayers(), worldserver1.getWorld().getSendViewDistance(), worldserver1.getWorld().getSimulationDistance(), flag1, !flag, flag2, player.createCommonSpawnInfo(worldserver1), this.server.enforceSecureProfile())); // Paper - replace old player chunk management - player.getBukkitEntity().sendSupportedChannels(); // CraftBukkit - playerconnection.send(new ClientboundChangeDifficultyPacket(worlddata.getDifficulty(), worlddata.isDifficultyLocked())); - playerconnection.send(new ClientboundPlayerAbilitiesPacket(player.getAbilities())); -@@ -943,8 +943,8 @@ public abstract class PlayerList { - LevelData worlddata = worldserver2.getLevelData(); - - entityplayer1.connection.send(new ClientboundRespawnPacket(entityplayer1.createCommonSpawnInfo(worldserver2), (byte) i)); -- entityplayer1.connection.send(new ClientboundSetChunkCacheRadiusPacket(worldserver1.spigotConfig.viewDistance)); // Spigot -- entityplayer1.connection.send(new ClientboundSetSimulationDistancePacket(worldserver1.spigotConfig.simulationDistance)); // Spigot -+ entityplayer1.connection.send(new ClientboundSetChunkCacheRadiusPacket(worldserver1.getWorld().getSendViewDistance())); // Spigot // Paper - replace old player chunk management -+ entityplayer1.connection.send(new ClientboundSetSimulationDistancePacket(worldserver1.getWorld().getSimulationDistance())); // Spigot // Paper - replace old player chunk management - entityplayer1.connection.teleport(CraftLocation.toBukkit(entityplayer1.position(), worldserver2.getWorld(), entityplayer1.getYRot(), entityplayer1.getXRot())); // CraftBukkit - entityplayer1.connection.send(new ClientboundSetDefaultSpawnPositionPacket(worldserver1.getSharedSpawnPos(), worldserver1.getSharedSpawnAngle())); - entityplayer1.connection.send(new ClientboundChangeDifficultyPacket(worlddata.getDifficulty(), worlddata.isDifficultyLocked())); -@@ -1496,7 +1496,7 @@ public abstract class PlayerList { - - public void setViewDistance(int viewDistance) { - this.viewDistance = viewDistance; -- this.broadcastAll(new ClientboundSetChunkCacheRadiusPacket(viewDistance)); -+ //this.broadcastAll(new ClientboundSetChunkCacheRadiusPacket(viewDistance)); // Paper - move into setViewDistance - Iterator iterator = this.server.getAllLevels().iterator(); - - while (iterator.hasNext()) { -@@ -1511,7 +1511,7 @@ public abstract class PlayerList { - - public void setSimulationDistance(int simulationDistance) { - this.simulationDistance = simulationDistance; -- this.broadcastAll(new ClientboundSetSimulationDistancePacket(simulationDistance)); -+ //this.broadcastAll(new ClientboundSetSimulationDistancePacket(simulationDistance)); // Paper - handled by playerchunkloader - Iterator iterator = this.server.getAllLevels().iterator(); - - while (iterator.hasNext()) { -diff --git a/src/main/java/net/minecraft/util/SortedArraySet.java b/src/main/java/net/minecraft/util/SortedArraySet.java -index ea72dcb064a35bc6245bc5c94d592efedd8faf41..0793dfe47e68a2b48b010aad5b12dcfa1701293a 100644 ---- a/src/main/java/net/minecraft/util/SortedArraySet.java -+++ b/src/main/java/net/minecraft/util/SortedArraySet.java -@@ -14,6 +14,14 @@ public class SortedArraySet extends AbstractSet { - T[] contents; - int size; - -+ // Paper start - rewrite chunk system -+ public SortedArraySet(final SortedArraySet other) { -+ this.comparator = other.comparator; -+ this.size = other.size; -+ this.contents = Arrays.copyOf(other.contents, this.size); -+ } -+ // Paper end - rewrite chunk system -+ - private SortedArraySet(int initialCapacity, Comparator comparator) { - this.comparator = comparator; - if (initialCapacity < 0) { -@@ -22,6 +30,41 @@ public class SortedArraySet extends AbstractSet { - this.contents = (T[])castRawArray(new Object[initialCapacity]); - } - } -+ // Paper start - optimise removeIf -+ @Override -+ public boolean removeIf(java.util.function.Predicate filter) { -+ // prev. impl used an iterator, which could be n^2 and creates garbage -+ int i = 0, len = this.size; -+ T[] backingArray = this.contents; -+ -+ for (;;) { -+ if (i >= len) { -+ return false; -+ } -+ if (!filter.test(backingArray[i])) { -+ ++i; -+ continue; -+ } -+ break; -+ } -+ -+ // we only want to write back to backingArray if we really need to -+ -+ int lastIndex = i; // this is where new elements are shifted to -+ -+ for (; i < len; ++i) { -+ T curr = backingArray[i]; -+ if (!filter.test(curr)) { // if test throws we're screwed -+ backingArray[lastIndex++] = curr; -+ } -+ } -+ -+ // cleanup end -+ Arrays.fill(backingArray, lastIndex, len, null); -+ this.size = lastIndex; -+ return true; -+ } -+ // Paper end - optimise removeIf - - public static > SortedArraySet create() { - return create(10); -@@ -110,6 +153,31 @@ public class SortedArraySet extends AbstractSet { - } - } - -+ // Paper start - rewrite chunk system -+ public T replace(T object) { -+ int i = this.findIndex(object); -+ if (i >= 0) { -+ T old = this.contents[i]; -+ this.contents[i] = object; -+ return old; -+ } else { -+ this.addInternal(object, getInsertionPosition(i)); -+ return object; -+ } -+ } -+ -+ public T removeAndGet(T object) { -+ int i = this.findIndex(object); -+ if (i >= 0) { -+ final T ret = this.contents[i]; -+ this.removeInternal(i); -+ return ret; -+ } else { -+ return null; -+ } -+ } -+ // Paper end - rewrite chunk system -+ - @Override - public boolean remove(Object object) { - int i = this.findIndex((T)object); -diff --git a/src/main/java/net/minecraft/util/worldupdate/WorldUpgrader.java b/src/main/java/net/minecraft/util/worldupdate/WorldUpgrader.java -index 0382b6597a130d746f8954a93a756a9d1ac81d50..ffbb3bf9ff3fc968ef69d4f889b0baf7e8ab691b 100644 ---- a/src/main/java/net/minecraft/util/worldupdate/WorldUpgrader.java -+++ b/src/main/java/net/minecraft/util/worldupdate/WorldUpgrader.java -@@ -227,7 +227,13 @@ public class WorldUpgrader { - this.previousWriteFuture.join(); - } - -+ // Paper start - async chunk io -+ try { - this.previousWriteFuture = storage.write(chunkPos, nbttagcompound1); -+ } catch (final IOException e) { -+ com.destroystokyo.paper.util.SneakyThrow.sneaky(e); -+ } -+ // Paper end - async chunk io - return true; - } - } -diff --git a/src/main/java/net/minecraft/world/entity/Entity.java b/src/main/java/net/minecraft/world/entity/Entity.java -index 62363c09111aaa31220fb260940744c097af7b3c..ff497f0e80889508dd8c183b48cd33bc7831ba6c 100644 ---- a/src/main/java/net/minecraft/world/entity/Entity.java -+++ b/src/main/java/net/minecraft/world/entity/Entity.java -@@ -481,6 +481,58 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess - } - // Paper end - -+ // Paper start -+ /** -+ * Overriding this field will cause memory leaks. -+ */ -+ private final boolean hardCollides; -+ -+ private static final java.util.Map, Boolean> cachedOverrides = java.util.Collections.synchronizedMap(new java.util.WeakHashMap<>()); -+ { -+ /* // Goodbye, broken on reobf... -+ Boolean hardCollides = cachedOverrides.get(this.getClass()); -+ if (hardCollides == null) { -+ try { -+ java.lang.reflect.Method getHardCollisionBoxEntityMethod = Entity.class.getMethod("canCollideWith", Entity.class); -+ java.lang.reflect.Method hasHardCollisionBoxMethod = Entity.class.getMethod("canBeCollidedWith"); -+ if (!this.getClass().getMethod(hasHardCollisionBoxMethod.getName(), hasHardCollisionBoxMethod.getParameterTypes()).equals(hasHardCollisionBoxMethod) -+ || !this.getClass().getMethod(getHardCollisionBoxEntityMethod.getName(), getHardCollisionBoxEntityMethod.getParameterTypes()).equals(getHardCollisionBoxEntityMethod)) { -+ hardCollides = Boolean.TRUE; -+ } else { -+ hardCollides = Boolean.FALSE; -+ } -+ cachedOverrides.put(this.getClass(), hardCollides); -+ } -+ catch (ThreadDeath thr) { throw thr; } -+ catch (Throwable thr) { -+ // shouldn't happen, just explode -+ throw new RuntimeException(thr); -+ } -+ } */ -+ this.hardCollides = this instanceof Boat -+ || this instanceof net.minecraft.world.entity.monster.Shulker -+ || this instanceof net.minecraft.world.entity.vehicle.AbstractMinecart -+ || this.shouldHardCollide(); -+ } -+ -+ // plugins can override -+ protected boolean shouldHardCollide() { -+ return false; -+ } -+ -+ public final boolean hardCollides() { -+ return this.hardCollides; -+ } -+ -+ public net.minecraft.server.level.FullChunkStatus chunkStatus; -+ -+ public int sectionX = Integer.MIN_VALUE; -+ public int sectionY = Integer.MIN_VALUE; -+ public int sectionZ = Integer.MIN_VALUE; -+ -+ public boolean updatingSectionStatus = false; -+ // Paper end -+ - public Entity(EntityType type, Level world) { - this.id = Entity.ENTITY_COUNTER.incrementAndGet(); - this.passengers = ImmutableList.of(); -@@ -2607,11 +2659,11 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess - return InteractionResult.PASS; - } - -- public boolean canCollideWith(Entity other) { -+ public boolean canCollideWith(Entity other) { // Paper - diff on change, hard colliding entities override this - TODO CHECK ON UPDATE - AbstractMinecart/Boat override - return other.canBeCollidedWith() && !this.isPassengerOfSameVehicle(other); - } - -- public boolean canBeCollidedWith() { -+ public boolean canBeCollidedWith() { // Paper - diff on change, hard colliding entities override this TODO CHECK ON UPDATE - Boat/Shulker override - return false; - } - -@@ -4042,6 +4094,13 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess - }).count(); - } - -+ // Paper start - rewrite chunk system -+ public boolean hasAnyPlayerPassengers() { -+ // copied from below -+ if (this.passengers.isEmpty()) { return false; } -+ return this.getIndirectPassengersStream().anyMatch((entity) -> entity instanceof Player); -+ } -+ // Paper end - rewrite chunk system - public boolean hasExactlyOnePlayerPassenger() { - if (this.passengers.isEmpty()) { return false; } // Paper - Optimize indirect passenger iteration - return this.countPlayerPassengers() == 1; -@@ -4392,6 +4451,12 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess - return; - } - // Paper end - Block invalid positions and bounding box -+ // Paper start - rewrite chunk system -+ if (this.updatingSectionStatus) { -+ LOGGER.error("Refusing to update position for entity {} to position {} since it is processing a section status update", this, new Vec3(x, y, z), new Throwable()); -+ return; -+ } -+ // Paper end - rewrite chunk system - // Paper start - Fix MC-4 - if (this instanceof ItemEntity) { - if (io.papermc.paper.configuration.GlobalConfiguration.get().misc.fixEntityPositionDesync) { -@@ -4519,6 +4584,13 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess - - @Override - public final void setRemoved(Entity.RemovalReason entity_removalreason, EntityRemoveEvent.Cause cause) { -+ // Paper start - rewrite chunk system -+ io.papermc.paper.util.TickThread.ensureTickThread(this, "Cannot remove entity off-main"); -+ if (!((ServerLevel)this.level).getEntityLookup().canRemoveEntity(this)) { -+ LOGGER.warn("Entity " + this + " is currently prevented from being removed from the world since it is processing section status updates", new Throwable()); -+ return; -+ } -+ // Paper end - rewrite chunk system - CraftEventFactory.callEntityRemoveEvent(this, cause); - // CraftBukkit end - final boolean alreadyRemoved = this.removalReason != null; // Paper - Folia schedulers -@@ -4530,7 +4602,7 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess - this.stopRiding(); - } - -- this.getPassengers().forEach(Entity::stopRiding); -+ if (entity_removalreason != RemovalReason.UNLOADED_TO_CHUNK) this.getPassengers().forEach(Entity::stopRiding); // Paper - chunk system - don't adjust passenger state when unloading, it's just not safe (and messes with our logic in entity chunk unload) - this.levelCallback.onRemove(entity_removalreason); - // Paper start - Folia schedulers - if (!(this instanceof ServerPlayer) && entity_removalreason != RemovalReason.CHANGED_DIMENSION && !alreadyRemoved) { -@@ -4561,7 +4633,7 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess - - @Override - public boolean shouldBeSaved() { -- return this.removalReason != null && !this.removalReason.shouldSave() ? false : (this.isPassenger() ? false : !this.isVehicle() || !this.hasExactlyOnePlayerPassenger()); -+ return this.removalReason != null && !this.removalReason.shouldSave() ? false : (this.isPassenger() ? false : !this.isVehicle() || !this.hasAnyPlayerPassengers()); // Paper - rewrite chunk system - it should check if the entity has ANY player passengers - } - - @Override -diff --git a/src/main/java/net/minecraft/world/entity/ai/village/poi/PoiManager.java b/src/main/java/net/minecraft/world/entity/ai/village/poi/PoiManager.java -index 71d8909f35a22256406a2232d21adfd7d94dc3a5..7b52b0507cbda76aee1db954641f397bef51f94d 100644 ---- a/src/main/java/net/minecraft/world/entity/ai/village/poi/PoiManager.java -+++ b/src/main/java/net/minecraft/world/entity/ai/village/poi/PoiManager.java -@@ -40,20 +40,40 @@ import net.minecraft.world.level.chunk.storage.SimpleRegionStorage; - public class PoiManager extends SectionStorage { - public static final int MAX_VILLAGE_DISTANCE = 6; - public static final int VILLAGE_SECTION_SIZE = 1; -- private final PoiManager.DistanceTracker distanceTracker; -- private final LongSet loadedChunks = new LongOpenHashSet(); -+ // Paper start - rewrite chunk system -+ // the vanilla tracker needs to be replaced because it does not support level removes -+ public final net.minecraft.server.level.ServerLevel world; -+ private final io.papermc.paper.util.misc.Delayed26WayDistancePropagator3D villageDistanceTracker = new io.papermc.paper.util.misc.Delayed26WayDistancePropagator3D(); -+ static final int POI_DATA_SOURCE = 7; -+ public static int convertBetweenLevels(final int level) { -+ return POI_DATA_SOURCE - level; -+ } -+ -+ protected void updateDistanceTracking(long section) { -+ if (this.isVillageCenter(section)) { -+ this.villageDistanceTracker.setSource(section, POI_DATA_SOURCE); -+ } else { -+ this.villageDistanceTracker.removeSource(section); -+ } -+ } -+ // Paper end - rewrite chunk system - - public PoiManager( - RegionStorageInfo storageKey, Path directory, DataFixer dataFixer, boolean dsync, RegistryAccess registryManager, LevelHeightAccessor world - ) { - super( -+ // Paper start -+ storageKey, -+ directory, -+ dsync, -+ // Paper end - new SimpleRegionStorage(storageKey, directory, dataFixer, dsync, DataFixTypes.POI_CHUNK), - PoiSection::codec, - PoiSection::new, - registryManager, - world - ); -- this.distanceTracker = new PoiManager.DistanceTracker(); -+ this.world = (net.minecraft.server.level.ServerLevel)world; // Paper - rewrite chunk system - } - - public void add(BlockPos pos, Holder type) { -@@ -187,8 +207,8 @@ public class PoiManager extends SectionStorage { - } - - public int sectionsToVillage(SectionPos pos) { -- this.distanceTracker.runAllUpdates(); -- return this.distanceTracker.getLevel(pos.asLong()); -+ this.villageDistanceTracker.propagateUpdates(); // Paper - replace distance tracking util -+ return convertBetweenLevels(this.villageDistanceTracker.getLevel(io.papermc.paper.util.CoordinateUtils.getChunkSectionKey(pos))); // Paper - replace distance tracking util - } - - boolean isVillageCenter(long pos) { -@@ -202,20 +222,117 @@ public class PoiManager extends SectionStorage { - - @Override - public void tick(BooleanSupplier shouldKeepTicking) { -- super.tick(shouldKeepTicking); -- this.distanceTracker.runAllUpdates(); -+ this.villageDistanceTracker.propagateUpdates(); // Paper - rewrite chunk system - } - - @Override -- protected void setDirty(long pos) { -- super.setDirty(pos); -- this.distanceTracker.update(pos, this.distanceTracker.getLevelFromSource(pos), false); -+ public void setDirty(long pos) { -+ // Paper start - rewrite chunk system -+ int chunkX = io.papermc.paper.util.CoordinateUtils.getChunkSectionX(pos); -+ int chunkZ = io.papermc.paper.util.CoordinateUtils.getChunkSectionZ(pos); -+ io.papermc.paper.chunk.system.scheduling.ChunkHolderManager manager = this.world.chunkTaskScheduler.chunkHolderManager; -+ io.papermc.paper.chunk.system.poi.PoiChunk chunk = manager.getPoiChunkIfLoaded(chunkX, chunkZ, false); -+ if (chunk != null) { -+ chunk.setDirty(true); -+ } -+ this.updateDistanceTracking(pos); -+ // Paper end - rewrite chunk system - } - - @Override - protected void onSectionLoad(long pos) { -- this.distanceTracker.update(pos, this.distanceTracker.getLevelFromSource(pos), false); -+ this.updateDistanceTracking(pos); // Paper - move to new distance tracking util -+ } -+ -+ // Paper start - rewrite chunk system -+ @Override -+ public Optional get(long pos) { -+ int chunkX = io.papermc.paper.util.CoordinateUtils.getChunkSectionX(pos); -+ int chunkY = io.papermc.paper.util.CoordinateUtils.getChunkSectionY(pos); -+ int chunkZ = io.papermc.paper.util.CoordinateUtils.getChunkSectionZ(pos); -+ -+ io.papermc.paper.util.TickThread.ensureTickThread(this.world, chunkX, chunkZ, "Accessing poi chunk off-main"); -+ -+ io.papermc.paper.chunk.system.scheduling.ChunkHolderManager manager = this.world.chunkTaskScheduler.chunkHolderManager; -+ io.papermc.paper.chunk.system.poi.PoiChunk ret = manager.getPoiChunkIfLoaded(chunkX, chunkZ, true); -+ -+ return ret == null ? Optional.empty() : ret.getSectionForVanilla(chunkY); -+ } -+ -+ @Override -+ public Optional getOrLoad(long pos) { -+ int chunkX = io.papermc.paper.util.CoordinateUtils.getChunkSectionX(pos); -+ int chunkY = io.papermc.paper.util.CoordinateUtils.getChunkSectionY(pos); -+ int chunkZ = io.papermc.paper.util.CoordinateUtils.getChunkSectionZ(pos); -+ -+ io.papermc.paper.util.TickThread.ensureTickThread(this.world, chunkX, chunkZ, "Accessing poi chunk off-main"); -+ -+ io.papermc.paper.chunk.system.scheduling.ChunkHolderManager manager = this.world.chunkTaskScheduler.chunkHolderManager; -+ -+ if (chunkY >= io.papermc.paper.util.WorldUtil.getMinSection(this.world) && -+ chunkY <= io.papermc.paper.util.WorldUtil.getMaxSection(this.world)) { -+ io.papermc.paper.chunk.system.poi.PoiChunk ret = manager.getPoiChunkIfLoaded(chunkX, chunkZ, true); -+ if (ret != null) { -+ return ret.getSectionForVanilla(chunkY); -+ } else { -+ return manager.loadPoiChunk(chunkX, chunkZ).getSectionForVanilla(chunkY); -+ } -+ } -+ // retain vanilla behavior: do not load section if out of bounds! -+ return Optional.empty(); -+ } -+ -+ @Override -+ protected PoiSection getOrCreate(long pos) { -+ int chunkX = io.papermc.paper.util.CoordinateUtils.getChunkSectionX(pos); -+ int chunkY = io.papermc.paper.util.CoordinateUtils.getChunkSectionY(pos); -+ int chunkZ = io.papermc.paper.util.CoordinateUtils.getChunkSectionZ(pos); -+ -+ io.papermc.paper.util.TickThread.ensureTickThread(this.world, chunkX, chunkZ, "Accessing poi chunk off-main"); -+ -+ io.papermc.paper.chunk.system.scheduling.ChunkHolderManager manager = this.world.chunkTaskScheduler.chunkHolderManager; -+ -+ io.papermc.paper.chunk.system.poi.PoiChunk ret = manager.getPoiChunkIfLoaded(chunkX, chunkZ, true); -+ if (ret != null) { -+ return ret.getOrCreateSection(chunkY); -+ } else { -+ return manager.loadPoiChunk(chunkX, chunkZ).getOrCreateSection(chunkY); -+ } -+ } -+ -+ public void onUnload(long coordinate) { // Paper - rewrite chunk system -+ int chunkX = io.papermc.paper.util.MCUtil.getCoordinateX(coordinate); -+ int chunkZ = io.papermc.paper.util.MCUtil.getCoordinateZ(coordinate); -+ io.papermc.paper.util.TickThread.ensureTickThread(this.world, chunkX, chunkZ, "Unloading poi chunk off-main"); -+ for (int section = this.levelHeightAccessor.getMinSection(); section < this.levelHeightAccessor.getMaxSection(); ++section) { -+ long sectionPos = SectionPos.asLong(chunkX, section, chunkZ); -+ this.updateDistanceTracking(sectionPos); -+ } -+ } -+ -+ public void loadInPoiChunk(io.papermc.paper.chunk.system.poi.PoiChunk poiChunk) { -+ int chunkX = poiChunk.chunkX; -+ int chunkZ = poiChunk.chunkZ; -+ io.papermc.paper.util.TickThread.ensureTickThread(this.world, chunkX, chunkZ, "Loading poi chunk off-main"); -+ for (int sectionY = this.levelHeightAccessor.getMinSection(); sectionY < this.levelHeightAccessor.getMaxSection(); ++sectionY) { -+ PoiSection section = poiChunk.getSection(sectionY); -+ if (section != null && !section.isEmpty()) { -+ this.onSectionLoad(SectionPos.asLong(chunkX, sectionY, chunkZ)); -+ } -+ } -+ } -+ -+ public void checkConsistency(net.minecraft.world.level.chunk.ChunkAccess chunk) { -+ int chunkX = chunk.getPos().x; -+ int chunkZ = chunk.getPos().z; -+ int minY = io.papermc.paper.util.WorldUtil.getMinSection(chunk); -+ int maxY = io.papermc.paper.util.WorldUtil.getMaxSection(chunk); -+ LevelChunkSection[] sections = chunk.getSections(); -+ for (int section = minY; section <= maxY; ++section) { -+ this.checkConsistencyWithBlocks(SectionPos.of(chunkX, section, chunkZ), sections[section - minY]); -+ } - } -+ // Paper end - rewrite chunk system - - public void checkConsistencyWithBlocks(SectionPos sectionPos, LevelChunkSection chunkSection) { - Util.ifElse(this.getOrLoad(sectionPos.asLong()), poiSet -> poiSet.refresh(populator -> { -@@ -251,7 +368,7 @@ public class PoiManager extends SectionStorage { - .map(sectionPos -> Pair.of(sectionPos, this.getOrLoad(sectionPos.asLong()))) - .filter(pair -> !pair.getSecond().map(PoiSection::isValid).orElse(false)) - .map(pair -> pair.getFirst().chunk()) -- .filter(chunkPos -> this.loadedChunks.add(chunkPos.toLong())) -+ // Paper - rewrite chunk system - .forEach(chunkPos -> world.getChunk(chunkPos.x, chunkPos.z, ChunkStatus.EMPTY)); - } - -@@ -265,7 +382,7 @@ public class PoiManager extends SectionStorage { - - @Override - protected int getLevelFromSource(long id) { -- return PoiManager.this.isVillageCenter(id) ? 0 : 7; -+ return PoiManager.this.isVillageCenter(id) ? 0 : 7; // Paper - rewrite chunk system - diff on change, this specifies the source level to use for distance tracking - } - - @Override -@@ -287,6 +404,35 @@ public class PoiManager extends SectionStorage { - } - } - -+ // Paper start - Asynchronous chunk io -+ @javax.annotation.Nullable -+ @Override -+ public net.minecraft.nbt.CompoundTag read(ChunkPos chunkcoordintpair) throws java.io.IOException { -+ // Paper start - rewrite chunk system -+ if (!io.papermc.paper.chunk.system.io.RegionFileIOThread.isRegionFileThread()) { -+ return io.papermc.paper.chunk.system.io.RegionFileIOThread.loadData( -+ this.world, chunkcoordintpair.x, chunkcoordintpair.z, io.papermc.paper.chunk.system.io.RegionFileIOThread.RegionFileType.POI_DATA, -+ io.papermc.paper.chunk.system.io.RegionFileIOThread.getIOBlockingPriorityForCurrentThread() -+ ); -+ } -+ // Paper end - rewrite chunk system -+ return super.read(chunkcoordintpair); -+ } -+ -+ @Override -+ public void write(ChunkPos chunkcoordintpair, net.minecraft.nbt.CompoundTag nbttagcompound) throws java.io.IOException { -+ // Paper start - rewrite chunk system -+ if (!io.papermc.paper.chunk.system.io.RegionFileIOThread.isRegionFileThread()) { -+ io.papermc.paper.chunk.system.io.RegionFileIOThread.scheduleSave( -+ this.world, chunkcoordintpair.x, chunkcoordintpair.z, nbttagcompound, -+ io.papermc.paper.chunk.system.io.RegionFileIOThread.RegionFileType.POI_DATA); -+ return; -+ } -+ // Paper end - rewrite chunk system -+ super.write(chunkcoordintpair, nbttagcompound); -+ } -+ // Paper end -+ - public static enum Occupancy { - HAS_SPACE(PoiRecord::hasSpace), - IS_OCCUPIED(PoiRecord::isOccupied), -diff --git a/src/main/java/net/minecraft/world/entity/ai/village/poi/PoiSection.java b/src/main/java/net/minecraft/world/entity/ai/village/poi/PoiSection.java -index 971fb29a2c3dc713cb8ab1d2eed054cc16f9c93c..5b7deae326228e482b218aeebd857a59b7434eaf 100644 ---- a/src/main/java/net/minecraft/world/entity/ai/village/poi/PoiSection.java -+++ b/src/main/java/net/minecraft/world/entity/ai/village/poi/PoiSection.java -@@ -29,6 +29,7 @@ public class PoiSection { - private final Map, Set> byType = Maps.newHashMap(); - private final Runnable setDirty; - private boolean isValid; -+ public final Optional noAllocateOptional = Optional.of(this); // Paper - rewrite chunk system - - public static Codec codec(Runnable updateListener) { - return RecordCodecBuilder.create( -@@ -46,6 +47,12 @@ public class PoiSection { - this(updateListener, true, ImmutableList.of()); - } - -+ // Paper start - isEmpty -+ public boolean isEmpty() { -+ return this.isValid && this.records.isEmpty() && this.byType.isEmpty(); -+ } -+ // Paper end -+ - private PoiSection(Runnable updateListener, boolean valid, List pois) { - this.setDirty = updateListener; - this.isValid = valid; -diff --git a/src/main/java/net/minecraft/world/level/EntityGetter.java b/src/main/java/net/minecraft/world/level/EntityGetter.java -index bd20bea7f76a7307f1698fb2dfef37125032d166..9a28912f52824acdc80a62243b136e6f365bf567 100644 ---- a/src/main/java/net/minecraft/world/level/EntityGetter.java -+++ b/src/main/java/net/minecraft/world/level/EntityGetter.java -@@ -19,6 +19,18 @@ import net.minecraft.world.phys.shapes.Shapes; - import net.minecraft.world.phys.shapes.VoxelShape; - - public interface EntityGetter { -+ -+ // Paper start -+ List getHardCollidingEntities(Entity except, AABB box, Predicate predicate); -+ -+ void getEntities(Entity except, AABB box, Predicate predicate, List into); -+ -+ void getHardCollidingEntities(Entity except, AABB box, Predicate predicate, List into); -+ -+ void getEntitiesByClass(Class clazz, Entity except, final AABB box, List into, -+ Predicate predicate); -+ // Paper end -+ - List getEntities(@Nullable Entity except, AABB box, Predicate predicate); - - List getEntities(EntityTypeTest filter, AABB box, Predicate predicate); -diff --git a/src/main/java/net/minecraft/world/level/Level.java b/src/main/java/net/minecraft/world/level/Level.java -index 975fcd4b8f93cb8c602ddeb165c485214eac10a4..d3137c9e5cc42ef191ea233b0d37eafeffc6f82c 100644 ---- a/src/main/java/net/minecraft/world/level/Level.java -+++ b/src/main/java/net/minecraft/world/level/Level.java -@@ -547,6 +547,11 @@ public abstract class Level implements LevelAccessor, AutoCloseable { - - if ((i & 2) != 0 && (!this.isClientSide || (i & 4) == 0) && (this.isClientSide || chunk == null || (chunk.getFullStatus() != null && chunk.getFullStatus().isOrAfter(FullChunkStatus.BLOCK_TICKING)))) { // allow chunk to be null here as chunk.isReady() is false when we send our notification during block placement - this.sendBlockUpdated(blockposition, iblockdata1, iblockdata, i); -+ // Paper start - per player view distance - allow block updates for non-ticking chunks in player view distance -+ // if copied from above -+ } else if ((i & 2) != 0 && (!this.isClientSide || (i & 4) == 0)) { // Paper - replace old player chunk management -+ ((ServerLevel)this).getChunkSource().blockChanged(blockposition); -+ // Paper end - per player view distance - } - - if ((i & 1) != 0) { -@@ -941,7 +946,7 @@ public abstract class Level implements LevelAccessor, AutoCloseable { - } - // Paper end - Perf: Optimize capturedTileEntities lookup - // CraftBukkit end -- return this.isOutsideBuildHeight(blockposition) ? null : (!this.isClientSide && Thread.currentThread() != this.thread ? null : this.getChunkAt(blockposition).getBlockEntity(blockposition, LevelChunk.EntityCreationType.IMMEDIATE)); -+ return this.isOutsideBuildHeight(blockposition) ? null : (!this.isClientSide && !io.papermc.paper.util.TickThread.isTickThread() ? null : this.getChunkAt(blockposition).getBlockEntity(blockposition, LevelChunk.EntityCreationType.IMMEDIATE)); // Paper - rewrite chunk system - } - - public void setBlockEntity(BlockEntity blockEntity) { -@@ -1032,26 +1037,7 @@ public abstract class Level implements LevelAccessor, AutoCloseable { - public List getEntities(@Nullable Entity except, AABB box, Predicate predicate) { - this.getProfiler().incrementCounter("getEntities"); - List list = Lists.newArrayList(); -- -- this.getEntities().get(box, (entity1) -> { -- if (entity1 != except && predicate.test(entity1)) { -- list.add(entity1); -- } -- -- if (entity1 instanceof EnderDragon) { -- EnderDragonPart[] aentitycomplexpart = ((EnderDragon) entity1).getSubEntities(); -- int i = aentitycomplexpart.length; -- -- for (int j = 0; j < i; ++j) { -- EnderDragonPart entitycomplexpart = aentitycomplexpart[j]; -- -- if (entity1 != except && predicate.test(entitycomplexpart)) { -- list.add(entitycomplexpart); -- } -- } -- } -- -- }); -+ ((ServerLevel)this).getEntityLookup().getEntities(except, box, list, predicate); // Paper - optimise this call - return list; - } - -@@ -1069,33 +1055,23 @@ public abstract class Level implements LevelAccessor, AutoCloseable { - - public void getEntities(EntityTypeTest filter, AABB box, Predicate predicate, List result, int limit) { - this.getProfiler().incrementCounter("getEntities"); -- this.getEntities().get(filter, box, (entity) -> { -- if (predicate.test(entity)) { -- result.add(entity); -- if (result.size() >= limit) { -- return AbortableIterationConsumer.Continuation.ABORT; -- } -- } -- -- if (entity instanceof EnderDragon entityenderdragon) { -- EnderDragonPart[] aentitycomplexpart = entityenderdragon.getSubEntities(); -- int j = aentitycomplexpart.length; -- -- for (int k = 0; k < j; ++k) { -- EnderDragonPart entitycomplexpart = aentitycomplexpart[k]; -- T t0 = filter.tryCast(entitycomplexpart); // CraftBukkit - decompile error -- -- if (t0 != null && predicate.test(t0)) { -- result.add(t0); -- if (result.size() >= limit) { -- return AbortableIterationConsumer.Continuation.ABORT; -- } -- } -- } -+ // Paper start - optimise this call -+ //TODO use limit -+ if (filter instanceof net.minecraft.world.entity.EntityType entityTypeTest) { -+ ((ServerLevel) this).getEntityLookup().getEntities(entityTypeTest, box, result, predicate); -+ } else { -+ Predicate test = (obj) -> { -+ return filter.tryCast(obj) != null; -+ }; -+ predicate = predicate == null ? test : test.and((Predicate) predicate); -+ Class base; -+ if (filter == null || (base = filter.getBaseClass()) == null || base == Entity.class) { -+ ((ServerLevel) this).getEntityLookup().getEntities((Entity) null, box, (List) result, (Predicate)predicate); -+ } else { -+ ((ServerLevel) this).getEntityLookup().getEntities(base, null, box, (List) result, (Predicate)predicate); // Paper - optimise this call - } -- -- return AbortableIterationConsumer.Continuation.CONTINUE; -- }); -+ } -+ // Paper end - optimise this call - } - - @Nullable -@@ -1385,4 +1361,45 @@ public abstract class Level implements LevelAccessor, AutoCloseable { - } - } - // Paper end - notify observers even if grow failed -+ // Paper start -+ //protected final io.papermc.paper.world.EntitySliceManager entitySliceManager; // Paper - rewrite chunk system -+ -+ public org.bukkit.entity.Entity[] getChunkEntities(int chunkX, int chunkZ) { -+ io.papermc.paper.world.ChunkEntitySlices slices = ((ServerLevel)this).getEntityLookup().getChunk(chunkX, chunkZ); -+ if (slices == null) { -+ return new org.bukkit.entity.Entity[0]; -+ } -+ return slices.getChunkEntities(); -+ } -+ -+ @Override -+ public List getHardCollidingEntities(Entity except, AABB box, Predicate predicate) { -+ List ret = new java.util.ArrayList<>(); -+ ((ServerLevel)this).getEntityLookup().getHardCollidingEntities(except, box, ret, predicate); -+ return ret; -+ } -+ -+ @Override -+ public void getEntities(Entity except, AABB box, Predicate predicate, List into) { -+ ((ServerLevel)this).getEntityLookup().getEntities(except, box, into, predicate); -+ } -+ -+ @Override -+ public void getHardCollidingEntities(Entity except, AABB box, Predicate predicate, List into) { -+ ((ServerLevel)this).getEntityLookup().getHardCollidingEntities(except, box, into, predicate); -+ } -+ -+ @Override -+ public void getEntitiesByClass(Class clazz, Entity except, final AABB box, List into, -+ Predicate predicate) { -+ ((ServerLevel)this).getEntityLookup().getEntities((Class)clazz, except, box, (List)into, (Predicate)predicate); -+ } -+ -+ @Override -+ public List getEntitiesOfClass(Class entityClass, AABB box, Predicate predicate) { -+ List ret = new java.util.ArrayList<>(); -+ ((ServerLevel)this).getEntityLookup().getEntities(entityClass, null, box, ret, predicate); -+ return ret; -+ } -+ // Paper end - } -diff --git a/src/main/java/net/minecraft/world/level/LevelReader.java b/src/main/java/net/minecraft/world/level/LevelReader.java -index a0ae26d6197e1069ca09982b4f8b706c55ae8491..32bfeb9aa87b43a9d2ce46dcc99dbd0ff355b412 100644 ---- a/src/main/java/net/minecraft/world/level/LevelReader.java -+++ b/src/main/java/net/minecraft/world/level/LevelReader.java -@@ -26,6 +26,15 @@ public interface LevelReader extends BlockAndTintGetter, CollisionGetter, Signal - @Nullable - ChunkAccess getChunk(int chunkX, int chunkZ, ChunkStatus leastStatus, boolean create); - -+ // Paper start - rewrite chunk system -+ default ChunkAccess syncLoadNonFull(int chunkX, int chunkZ, ChunkStatus status) { -+ if (status == null || status.isOrAfter(ChunkStatus.FULL)) { -+ throw new IllegalArgumentException("Status: " + status.toString()); -+ } -+ return this.getChunk(chunkX, chunkZ, status, true); -+ } -+ // Paper end - rewrite chunk system -+ - @Nullable ChunkAccess getChunkIfLoadedImmediately(int x, int z); // Paper - ifLoaded api (we need this since current impl blocks if the chunk is loading) - @Nullable default ChunkAccess getChunkIfLoadedImmediately(BlockPos pos) { return this.getChunkIfLoadedImmediately(pos.getX() >> 4, pos.getZ() >> 4);} - -diff --git a/src/main/java/net/minecraft/world/level/chunk/ChunkGenerator.java b/src/main/java/net/minecraft/world/level/chunk/ChunkGenerator.java -index c9cd18ce79a6ee7297a8fd14f4dbe712570b3ced..927bdebdb8ae01613f0cea074b3367bd7ffe9ab1 100644 ---- a/src/main/java/net/minecraft/world/level/chunk/ChunkGenerator.java -+++ b/src/main/java/net/minecraft/world/level/chunk/ChunkGenerator.java -@@ -120,7 +120,7 @@ public abstract class ChunkGenerator { - return CompletableFuture.supplyAsync(Util.wrapThreadWithTaskName("init_biomes", () -> { - chunk.fillBiomesFromNoise(this.biomeSource, noiseConfig.sampler()); - return chunk; -- }), Util.backgroundExecutor()); -+ }), executor); // Paper - run with supplied executor - } - - public abstract void applyCarvers(WorldGenRegion chunkRegion, long seed, RandomState noiseConfig, BiomeManager biomeAccess, StructureManager structureAccessor, ChunkAccess chunk, GenerationStep.Carving carverStep); -@@ -315,7 +315,7 @@ public abstract class ChunkGenerator { - return Pair.of(placement.getLocatePos(pos), holder); - } - -- ChunkAccess ichunkaccess = world.getChunk(pos.x, pos.z, ChunkStatus.STRUCTURE_STARTS); -+ ChunkAccess ichunkaccess = world.syncLoadNonFull(pos.x, pos.z, ChunkStatus.STRUCTURE_STARTS); // Paper - rewrite chunk system - - structurestart = structureAccessor.getStartForStructure(SectionPos.bottomOf(ichunkaccess), (Structure) holder.value(), ichunkaccess); - } while (structurestart == null); -diff --git a/src/main/java/net/minecraft/world/level/chunk/LevelChunk.java b/src/main/java/net/minecraft/world/level/chunk/LevelChunk.java -index bac191f92ea3735df19c68d5568c2c7962c8680f..5d94aee1303d9eca5f1fa9a2e033ad0d12909635 100644 ---- a/src/main/java/net/minecraft/world/level/chunk/LevelChunk.java -+++ b/src/main/java/net/minecraft/world/level/chunk/LevelChunk.java -@@ -86,6 +86,7 @@ public class LevelChunk extends ChunkAccess { - private final Int2ObjectMap gameEventListenerRegistrySections; - private final LevelChunkTicks blockTicks; - private final LevelChunkTicks fluidTicks; -+ public volatile FullChunkStatus chunkStatus = FullChunkStatus.INACCESSIBLE; // Paper - rewrite chunk system - - public LevelChunk(Level world, ChunkPos pos) { - this(world, pos, UpgradeData.EMPTY, new LevelChunkTicks<>(), new LevelChunkTicks<>(), 0L, (LevelChunkSection[]) null, (LevelChunk.PostLoadProcessor) null, (BlendingData) null); -@@ -690,9 +691,26 @@ public class LevelChunk extends ChunkAccess { - - } - -- // CraftBukkit start -- public void loadCallback() { -- // Paper start - neighbour cache -+ // Paper start - new load callbacks -+ private io.papermc.paper.chunk.system.scheduling.NewChunkHolder chunkHolder; -+ public io.papermc.paper.chunk.system.scheduling.NewChunkHolder getChunkHolder() { -+ return this.chunkHolder; -+ } -+ -+ public void setChunkHolder(io.papermc.paper.chunk.system.scheduling.NewChunkHolder chunkHolder) { -+ if (chunkHolder == null) { -+ throw new NullPointerException("Chunkholder cannot be null"); -+ } -+ if (this.chunkHolder != null) { -+ throw new IllegalStateException("Already have chunkholder: " + this.chunkHolder + ", cannot replace with " + chunkHolder); -+ } -+ this.chunkHolder = chunkHolder; -+ this.playerChunk = chunkHolder.vanillaChunkHolder; -+ } -+ -+ /* Note: We skip the light neighbour chunk loading done for the vanilla full chunk */ -+ /* Starlight does not need these chunks for lighting purposes because of edge checks */ -+ public void pushChunkIntoLoadedMap() { - int chunkX = this.chunkPos.x; - int chunkZ = this.chunkPos.z; - net.minecraft.server.level.ServerChunkCache chunkProvider = this.level.getChunkSource(); -@@ -707,10 +725,55 @@ public class LevelChunk extends ChunkAccess { - } - } - this.setNeighbourLoaded(0, 0, this); -+ this.level.getChunkSource().addLoadedChunk(this); -+ } -+ -+ public void onChunkLoad(io.papermc.paper.chunk.system.scheduling.NewChunkHolder chunkHolder) { -+ // figure out how this should interface with: -+ // the entity chunk load event // -> moved to the FULL status -+ // the chunk load event // -> stays here -+ // any entity add to world events // -> in FULL status -+ this.loadCallback(); -+ io.papermc.paper.chunk.system.ChunkSystem.onChunkBorder(this, chunkHolder.vanillaChunkHolder); -+ } -+ -+ public void onChunkUnload(io.papermc.paper.chunk.system.scheduling.NewChunkHolder chunkHolder) { -+ // figure out how this should interface with: -+ // the entity chunk load event // -> moved to chunk unload to disk (not written yet) -+ // the chunk load event // -> stays here -+ // any entity add to world events // -> goes into the unload logic, it will completely explode -+ // etc later -+ this.unloadCallback(); -+ io.papermc.paper.chunk.system.ChunkSystem.onChunkNotBorder(this, chunkHolder.vanillaChunkHolder); -+ } -+ -+ public void onChunkTicking(io.papermc.paper.chunk.system.scheduling.NewChunkHolder chunkHolder) { -+ this.postProcessGeneration(); -+ this.level.startTickingChunk(this); -+ io.papermc.paper.chunk.system.ChunkSystem.onChunkTicking(this, chunkHolder.vanillaChunkHolder); -+ } -+ -+ public void onChunkNotTicking(io.papermc.paper.chunk.system.scheduling.NewChunkHolder chunkHolder) { -+ io.papermc.paper.chunk.system.ChunkSystem.onChunkNotTicking(this, chunkHolder.vanillaChunkHolder); -+ } -+ -+ public void onChunkEntityTicking(io.papermc.paper.chunk.system.scheduling.NewChunkHolder chunkHolder) { -+ io.papermc.paper.chunk.system.ChunkSystem.onChunkEntityTicking(this, chunkHolder.vanillaChunkHolder); -+ } -+ -+ public void onChunkNotEntityTicking(io.papermc.paper.chunk.system.scheduling.NewChunkHolder chunkHolder) { -+ io.papermc.paper.chunk.system.ChunkSystem.onChunkNotEntityTicking(this, chunkHolder.vanillaChunkHolder); -+ } -+ // Paper end - new load callbacks -+ -+ // CraftBukkit start -+ public void loadCallback() { -+ if (this.loadedTicketLevel) { LOGGER.error("Double calling chunk load!", new Throwable()); } // Paper -+ // Paper - rewrite chunk system - move into separate callback - this.loadedTicketLevel = true; -- // Paper end - neighbour cache -+ // Paper - rewrite chunk system - move into separate callback - org.bukkit.Server server = this.level.getCraftServer(); -- this.level.getChunkSource().addLoadedChunk(this); // Paper -+ // Paper - rewrite chunk system - move into separate callback - if (server != null) { - /* - * If it's a new world, the first few chunks are generated inside -@@ -719,6 +782,7 @@ public class LevelChunk extends ChunkAccess { - */ - org.bukkit.Chunk bukkitChunk = new org.bukkit.craftbukkit.CraftChunk(this); - server.getPluginManager().callEvent(new org.bukkit.event.world.ChunkLoadEvent(bukkitChunk, this.needsDecoration)); -+ this.chunkHolder.getEntityChunk().callEntitiesLoadEvent(); // Paper - rewrite chunk system - - if (this.needsDecoration) { - try (co.aikar.timings.Timing ignored = this.level.timings.chunkLoadPopulate.startTiming()) { // Paper -@@ -747,9 +811,11 @@ public class LevelChunk extends ChunkAccess { - } - - public void unloadCallback() { -+ if (!this.loadedTicketLevel) { LOGGER.error("Double calling chunk unload!", new Throwable()); } // Paper - org.bukkit.Server server = this.level.getCraftServer(); -+ this.chunkHolder.getEntityChunk().callEntitiesUnloadEvent(); // Paper - rewrite chunk system - org.bukkit.Chunk bukkitChunk = new org.bukkit.craftbukkit.CraftChunk(this); -- org.bukkit.event.world.ChunkUnloadEvent unloadEvent = new org.bukkit.event.world.ChunkUnloadEvent(bukkitChunk, this.isUnsaved()); -+ org.bukkit.event.world.ChunkUnloadEvent unloadEvent = new org.bukkit.event.world.ChunkUnloadEvent(bukkitChunk, true); // Paper - rewrite chunk system - force save to true so that mustNotSave is correctly set below - server.getPluginManager().callEvent(unloadEvent); - // note: saving can be prevented, but not forced if no saving is actually required - this.mustNotSave = !unloadEvent.isSaveChunk(); -@@ -771,9 +837,26 @@ public class LevelChunk extends ChunkAccess { - // Paper end - } - -+ // Paper start - add dirty system to tick lists -+ @Override -+ public void setUnsaved(boolean needsSaving) { -+ if (!needsSaving) { -+ this.blockTicks.clearDirty(); -+ this.fluidTicks.clearDirty(); -+ } -+ super.setUnsaved(needsSaving); -+ } -+ // Paper end - add dirty system to tick lists -+ - @Override - public boolean isUnsaved() { -- return super.isUnsaved() && !this.mustNotSave; -+ // Paper start - add dirty system to tick lists -+ long gameTime = this.level.getLevelData().getGameTime(); -+ if (this.blockTicks.isDirty(gameTime) || this.fluidTicks.isDirty(gameTime)) { -+ return true; -+ } -+ // Paper end - add dirty system to tick lists -+ return super.isUnsaved(); // Paper - rewrite chunk system - do NOT clobber the dirty flag - } - // CraftBukkit end - -@@ -842,7 +925,9 @@ public class LevelChunk extends ChunkAccess { - return this.blockEntities; - } - -+ public boolean isPostProcessingDone; // Paper - replace chunk loader system - public void postProcessGeneration() { -+ try { // Paper - replace chunk loader system - ChunkPos chunkcoordintpair = this.getPos(); - - for (int i = 0; i < this.postProcessing.length; ++i) { -@@ -863,6 +948,7 @@ public class LevelChunk extends ChunkAccess { - BlockState iblockdata1 = Block.updateFromNeighbourShapes(iblockdata, this.level, blockposition); - - this.level.setBlock(blockposition, iblockdata1, 20); -+ if (iblockdata1 != iblockdata) this.level.chunkSource.blockChanged(blockposition); // Paper - replace player chunk loader - notify since we send before processing full updates - } - } - -@@ -880,6 +966,10 @@ public class LevelChunk extends ChunkAccess { - - this.pendingBlockEntities.clear(); - this.upgradeData.upgrade(this); -+ } finally { // Paper start - replace chunk loader system -+ this.isPostProcessingDone = true; -+ } -+ // Paper end - replace chunk loader system - } - - @Nullable -@@ -929,7 +1019,7 @@ public class LevelChunk extends ChunkAccess { - } - - public FullChunkStatus getFullStatus() { -- return this.fullStatus == null ? FullChunkStatus.FULL : (FullChunkStatus) this.fullStatus.get(); -+ return this.chunkHolder == null ? FullChunkStatus.INACCESSIBLE : this.chunkHolder.getChunkStatus(); // Paper - rewrite chunk system - } - - public void setFullStatus(Supplier levelTypeProvider) { -diff --git a/src/main/java/net/minecraft/world/level/chunk/status/ChunkStatus.java b/src/main/java/net/minecraft/world/level/chunk/status/ChunkStatus.java -index 95318092f8281d98132d1d3ceb4a5c36cf32eb05..b81c548c0e1ac53784e9c94b34b65db5f123309c 100644 ---- a/src/main/java/net/minecraft/world/level/chunk/status/ChunkStatus.java -+++ b/src/main/java/net/minecraft/world/level/chunk/status/ChunkStatus.java -@@ -21,13 +21,15 @@ import net.minecraft.world.level.chunk.ProtoChunk; - import net.minecraft.world.level.levelgen.Heightmap; - - public class ChunkStatus { -+ static final ChunkStatus.LoadingTask PASSTHROUGH_LOAD_TASK = (WorldGenContext context, ChunkStatus status, ToFullChunk fullChunkConverter, ChunkAccess chunk) -> CompletableFuture.completedFuture(chunk); // Paper - rewrite chunk system -+ protected static final java.util.List statuses = new java.util.ArrayList<>(); // Paper - rewrite chunk system - public static final int MAX_STRUCTURE_DISTANCE = 8; - private static final EnumSet PRE_FEATURES = EnumSet.of(Heightmap.Types.OCEAN_FLOOR_WG, Heightmap.Types.WORLD_SURFACE_WG); - public static final EnumSet POST_FEATURES = EnumSet.of( - Heightmap.Types.OCEAN_FLOOR, Heightmap.Types.WORLD_SURFACE, Heightmap.Types.MOTION_BLOCKING, Heightmap.Types.MOTION_BLOCKING_NO_LEAVES - ); - public static final ChunkStatus EMPTY = register( -- "empty", null, -1, false, PRE_FEATURES, ChunkType.PROTOCHUNK, ChunkStatusTasks::generateEmpty, ChunkStatusTasks::loadPassThrough -+ "empty", null, -1, false, PRE_FEATURES, ChunkType.PROTOCHUNK, ChunkStatusTasks::generateEmpty, PASSTHROUGH_LOAD_TASK // Paper - rewrite chunk system - ); - public static final ChunkStatus STRUCTURE_STARTS = register( - "structure_starts", -@@ -47,22 +49,22 @@ public class ChunkStatus { - PRE_FEATURES, - ChunkType.PROTOCHUNK, - ChunkStatusTasks::generateStructureReferences, -- ChunkStatusTasks::loadPassThrough -+ PASSTHROUGH_LOAD_TASK // Paper - rewrite chunk system - ); - public static final ChunkStatus BIOMES = register( -- "biomes", STRUCTURE_REFERENCES, 8, false, PRE_FEATURES, ChunkType.PROTOCHUNK, ChunkStatusTasks::generateBiomes, ChunkStatusTasks::loadPassThrough -+ "biomes", STRUCTURE_REFERENCES, 8, false, PRE_FEATURES, ChunkType.PROTOCHUNK, ChunkStatusTasks::generateBiomes, PASSTHROUGH_LOAD_TASK // Paper - rewrite chunk system - ); - public static final ChunkStatus NOISE = register( -- "noise", BIOMES, 8, false, PRE_FEATURES, ChunkType.PROTOCHUNK, ChunkStatusTasks::generateNoise, ChunkStatusTasks::loadPassThrough -+ "noise", BIOMES, 8, false, PRE_FEATURES, ChunkType.PROTOCHUNK, ChunkStatusTasks::generateNoise, PASSTHROUGH_LOAD_TASK // Paper - rewrite chunk system - ); - public static final ChunkStatus SURFACE = register( -- "surface", NOISE, 8, false, PRE_FEATURES, ChunkType.PROTOCHUNK, ChunkStatusTasks::generateSurface, ChunkStatusTasks::loadPassThrough -+ "surface", NOISE, 8, false, PRE_FEATURES, ChunkType.PROTOCHUNK, ChunkStatusTasks::generateSurface, PASSTHROUGH_LOAD_TASK // Paper - rewrite chunk system - ); - public static final ChunkStatus CARVERS = register( -- "carvers", SURFACE, 8, false, POST_FEATURES, ChunkType.PROTOCHUNK, ChunkStatusTasks::generateCarvers, ChunkStatusTasks::loadPassThrough -+ "carvers", SURFACE, 8, false, POST_FEATURES, ChunkType.PROTOCHUNK, ChunkStatusTasks::generateCarvers, PASSTHROUGH_LOAD_TASK // Paper - rewrite chunk system - ); - public static final ChunkStatus FEATURES = register( -- "features", CARVERS, 8, false, POST_FEATURES, ChunkType.PROTOCHUNK, ChunkStatusTasks::generateFeatures, ChunkStatusTasks::loadPassThrough -+ "features", CARVERS, 8, false, POST_FEATURES, ChunkType.PROTOCHUNK, ChunkStatusTasks::generateFeatures, PASSTHROUGH_LOAD_TASK // Paper - rewrite chunk system - ); - public static final ChunkStatus INITIALIZE_LIGHT = register( - "initialize_light", -@@ -78,7 +80,7 @@ public class ChunkStatus { - "light", INITIALIZE_LIGHT, 1, true, POST_FEATURES, ChunkType.PROTOCHUNK, ChunkStatusTasks::generateLight, ChunkStatusTasks::loadLight - ); - public static final ChunkStatus SPAWN = register( -- "spawn", LIGHT, 1, false, POST_FEATURES, ChunkType.PROTOCHUNK, ChunkStatusTasks::generateSpawn, ChunkStatusTasks::loadPassThrough -+ "spawn", LIGHT, 1, false, POST_FEATURES, ChunkType.PROTOCHUNK, ChunkStatusTasks::generateSpawn, PASSTHROUGH_LOAD_TASK // Paper - rewrite chunk system - ); - public static final ChunkStatus FULL = register( - "full", SPAWN, 0, false, POST_FEATURES, ChunkType.LEVELCHUNK, ChunkStatusTasks::generateFull, ChunkStatusTasks::loadFull -@@ -128,6 +130,27 @@ public class ChunkStatus { - } - } - // Paper end - starlight -+ // Paper start - rewrite chunk system -+ public boolean isParallelCapable; // Paper -+ public int writeRadius = -1; -+ public int loadRange = 0; -+ -+ private ChunkStatus nextStatus; -+ -+ public final ChunkStatus getNextStatus() { -+ return this.nextStatus; -+ } -+ -+ public final boolean isEmptyLoadStatus() { -+ return this.loadingTask == PASSTHROUGH_LOAD_TASK; -+ } -+ -+ public final boolean isEmptyGenStatus() { -+ return this == ChunkStatus.EMPTY; -+ } -+ -+ public final java.util.concurrent.atomic.AtomicBoolean warnedAboutNoImmediateComplete = new java.util.concurrent.atomic.AtomicBoolean(); -+ // Paper end - rewrite chunk system - - private static ChunkStatus register( - String id, -@@ -190,6 +213,13 @@ public class ChunkStatus { - this.chunkType = chunkType; - this.heightmapsAfter = heightMapTypes; - this.index = previous == null ? 0 : previous.getIndex() + 1; -+ // Paper start -+ this.nextStatus = this; -+ if (statuses.size() > 0) { -+ statuses.get(statuses.size() - 1).nextStatus = this; -+ } -+ statuses.add(this); -+ // Paper end - } - - public int getIndex() { -diff --git a/src/main/java/net/minecraft/world/level/chunk/status/ChunkStatusTasks.java b/src/main/java/net/minecraft/world/level/chunk/status/ChunkStatusTasks.java -index ce7f154b9dad4e78ee0189405cf57dcb3d5301b8..a5e8078b99161272b0f826b8c39e56d17588c264 100644 ---- a/src/main/java/net/minecraft/world/level/chunk/status/ChunkStatusTasks.java -+++ b/src/main/java/net/minecraft/world/level/chunk/status/ChunkStatusTasks.java -@@ -26,8 +26,9 @@ public class ChunkStatusTasks { - return CompletableFuture.completedFuture(chunk); - } - -- static CompletableFuture loadPassThrough(WorldGenContext context, ChunkStatus status, ToFullChunk fullChunkConverter, ChunkAccess chunk) { -- return CompletableFuture.completedFuture(chunk); -+ @io.papermc.paper.annotation.DoNotUse @Deprecated(forRemoval = true) // Paper - rewrite chunk system - use ChunkStatus.PASSTHROUGH_LOAD_TASK instead -+ static CompletableFuture loadPassThrough(WorldGenContext context, ChunkStatus status, ToFullChunk fullChunkConverter, ChunkAccess chunk) { // Paper - rewrite chunk system - diff on change -+ return CompletableFuture.completedFuture(chunk); // Paper - rewrite chunk system - diff on change - } - - static CompletableFuture generateStructureStarts(WorldGenContext context, ChunkStatus status, Executor executor, ToFullChunk fullChunkConverter, List chunks, ChunkAccess chunk) { -@@ -125,7 +126,7 @@ public class ChunkStatusTasks { - ((ProtoChunk) chunk).setLightEngine(lightingProvider); - boolean flag = ChunkStatusTasks.isLighted(chunk); - -- return lightingProvider.initializeLight(chunk, flag); -+ return CompletableFuture.completedFuture(chunk); // Paper - rewrite chunk system - } - - static CompletableFuture generateLight(WorldGenContext context, ChunkStatus status, Executor executor, ToFullChunk fullChunkConverter, List chunks, ChunkAccess chunk) { -@@ -139,7 +140,7 @@ public class ChunkStatusTasks { - private static CompletableFuture lightChunk(ThreadedLevelLightEngine lightingProvider, ChunkAccess chunk) { - boolean flag = ChunkStatusTasks.isLighted(chunk); - -- return lightingProvider.lightChunk(chunk, flag); -+ return CompletableFuture.completedFuture(chunk); // Paper - rewrite chunk system - } - - static CompletableFuture generateSpawn(WorldGenContext context, ChunkStatus status, Executor executor, ToFullChunk fullChunkConverter, List chunks, ChunkAccess chunk) { -diff --git a/src/main/java/net/minecraft/world/level/chunk/storage/ChunkSerializer.java b/src/main/java/net/minecraft/world/level/chunk/storage/ChunkSerializer.java -index 01d6b8683a9fa30d05b03ebfef8ee2dca4e83a56..5f85d8d82212f9a8133304dc05bf2cd39da1f9e7 100644 ---- a/src/main/java/net/minecraft/world/level/chunk/storage/ChunkSerializer.java -+++ b/src/main/java/net/minecraft/world/level/chunk/storage/ChunkSerializer.java -@@ -112,7 +112,25 @@ public class ChunkSerializer { - } - } - // Paper end - guard against serializing mismatching coordinates -+ // Paper start - rewrite chunk system -+ public static final class InProgressChunkHolder { -+ -+ public final ProtoChunk protoChunk; -+ -+ public CompoundTag poiData; -+ -+ public InProgressChunkHolder(final ProtoChunk protoChunk) { -+ this.protoChunk = protoChunk; -+ } -+ } - public static ProtoChunk read(ServerLevel world, PoiManager poiStorage, ChunkPos chunkPos, CompoundTag nbt) { -+ // Paper start - rewrite chunk system -+ InProgressChunkHolder holder = readInProgressChunkHolder(world, poiStorage, chunkPos, nbt); -+ return holder.protoChunk; -+ } -+ -+ public static InProgressChunkHolder readInProgressChunkHolder(ServerLevel world, PoiManager poiStorage, ChunkPos chunkPos, CompoundTag nbt) { -+ // Paper end - rewrite chunk system - // Paper start - Do not let the server load chunks from newer versions - if (nbt.contains("DataVersion", net.minecraft.nbt.Tag.TAG_ANY_NUMERIC)) { - final int dataVersion = nbt.getInt("DataVersion"); -@@ -178,7 +196,7 @@ public class ChunkSerializer { - achunksection[k] = chunksection; - SectionPos sectionposition = SectionPos.of(chunkPos, b0); - -- poiStorage.checkConsistencyWithBlocks(sectionposition, chunksection); -+ // Paper - rewrite chunk system - moved to final load stage - } - - boolean flag3 = nbttagcompound1.contains("BlockLight", 7); -@@ -325,7 +343,7 @@ public class ChunkSerializer { - } - - if (chunktype == ChunkType.LEVELCHUNK) { -- return new ImposterProtoChunk((LevelChunk) object1, false); -+ return new InProgressChunkHolder(new ImposterProtoChunk((LevelChunk) object1, false)); // Paper - Async chunk loading - } else { - ProtoChunk protochunk1 = (ProtoChunk) object1; - -@@ -360,9 +378,41 @@ public class ChunkSerializer { - protochunk1.setCarvingMask(worldgenstage_features, new CarvingMask(nbttagcompound5.getLongArray(s1), ((ChunkAccess) object1).getMinBuildHeight())); - } - -- return protochunk1; -+ return new InProgressChunkHolder(protochunk1); // Paper - Async chunk loading -+ } -+ } -+ -+ // Paper start - async chunk save for unload -+ public record AsyncSaveData( -+ Tag blockTickList, // non-null if we had to go to the server's tick list -+ Tag fluidTickList, // non-null if we had to go to the server's tick list -+ ListTag blockEntities, -+ long worldTime -+ ) {} -+ -+ // must be called sync -+ public static AsyncSaveData getAsyncSaveData(ServerLevel world, ChunkAccess chunk) { -+ org.spigotmc.AsyncCatcher.catchOp("preparation of chunk data for async save"); -+ -+ final CompoundTag tickLists = new CompoundTag(); -+ ChunkSerializer.saveTicks(world, tickLists, chunk.getTicksForSerialization()); -+ -+ ListTag blockEntitiesSerialized = new ListTag(); -+ for (final BlockPos blockPos : chunk.getBlockEntitiesPos()) { -+ final CompoundTag blockEntityNbt = chunk.getBlockEntityNbtForSaving(blockPos, world.registryAccess()); -+ if (blockEntityNbt != null) { -+ blockEntitiesSerialized.add(blockEntityNbt); -+ } - } -+ -+ return new AsyncSaveData( -+ tickLists.get(BLOCK_TICKS_TAG), -+ tickLists.get(FLUID_TICKS_TAG), -+ blockEntitiesSerialized, -+ world.getGameTime() -+ ); - } -+ // Paper end - - private static void logErrors(ChunkPos chunkPos, int y, String message) { - ChunkSerializer.LOGGER.error("Recoverable errors when loading section [" + chunkPos.x + ", " + y + ", " + chunkPos.z + "]: " + message); -@@ -379,6 +429,11 @@ public class ChunkSerializer { - // CraftBukkit end - - public static CompoundTag write(ServerLevel world, ChunkAccess chunk) { -+ // Paper start -+ return saveChunk(world, chunk, null); -+ } -+ public static CompoundTag saveChunk(ServerLevel world, ChunkAccess chunk, @org.checkerframework.checker.nullness.qual.Nullable AsyncSaveData asyncsavedata) { -+ // Paper end - // Paper start - rewrite light impl - final int minSection = io.papermc.paper.util.WorldUtil.getMinLightSection(world); - final int maxSection = io.papermc.paper.util.WorldUtil.getMaxLightSection(world); -@@ -391,7 +446,7 @@ public class ChunkSerializer { - nbttagcompound.putInt("xPos", chunkcoordintpair.x); - nbttagcompound.putInt("yPos", chunk.getMinSection()); - nbttagcompound.putInt("zPos", chunkcoordintpair.z); -- nbttagcompound.putLong("LastUpdate", world.getGameTime()); -+ nbttagcompound.putLong("LastUpdate", asyncsavedata != null ? asyncsavedata.worldTime : world.getGameTime()); // Paper - async chunk unloading - nbttagcompound.putLong("InhabitedTime", chunk.getInhabitedTime()); - nbttagcompound.putString("Status", BuiltInRegistries.CHUNK_STATUS.getKey(chunk.getStatus()).toString()); - BlendingData blendingdata = chunk.getBlendingData(); -@@ -485,8 +540,17 @@ public class ChunkSerializer { - nbttagcompound.putBoolean("isLightOn", false); // Paper - set to false but still store, this allows us to detect --eraseCache (as eraseCache _removes_) - } - -- ListTag nbttaglist1 = new ListTag(); -- Iterator iterator = chunk.getBlockEntitiesPos().iterator(); -+ // Paper start -+ ListTag nbttaglist1; -+ Iterator iterator; -+ if (asyncsavedata != null) { -+ nbttaglist1 = asyncsavedata.blockEntities; -+ iterator = java.util.Collections.emptyIterator(); -+ } else { -+ nbttaglist1 = new ListTag(); -+ iterator = chunk.getBlockEntitiesPos().iterator(); -+ } -+ // Paper end - - CompoundTag nbttagcompound2; - -@@ -522,7 +586,14 @@ public class ChunkSerializer { - nbttagcompound.put("CarvingMasks", nbttagcompound2); - } - -+ // Paper start -+ if (asyncsavedata != null) { -+ nbttagcompound.put(BLOCK_TICKS_TAG, asyncsavedata.blockTickList); -+ nbttagcompound.put(FLUID_TICKS_TAG, asyncsavedata.fluidTickList); -+ } else { - ChunkSerializer.saveTicks(world, nbttagcompound, chunk.getTicksForSerialization()); -+ } -+ // Paper end - nbttagcompound.put("PostProcessing", ChunkSerializer.packOffsets(chunk.getPostProcessing())); - CompoundTag nbttagcompound3 = new CompoundTag(); - Iterator iterator1 = chunk.getHeightmaps().iterator(); -@@ -578,7 +649,7 @@ public class ChunkSerializer { - - return nbttaglist == null && nbttaglist1 == null ? null : (chunk) -> { - if (nbttaglist != null) { -- world.addLegacyChunkEntities(EntityType.loadEntitiesRecursive(nbttaglist, world)); -+ world.addLegacyChunkEntities(EntityType.loadEntitiesRecursive(nbttaglist, world), chunk.getPos()); // Paper - rewrite chunk system - } - - if (nbttaglist1 != null) { -diff --git a/src/main/java/net/minecraft/world/level/chunk/storage/ChunkStorage.java b/src/main/java/net/minecraft/world/level/chunk/storage/ChunkStorage.java -index a62c90e10c0dfa4c6211a05c4071932756d7b218..554dede2ad0e45d3ee4ccc5510b7644f2e9e4250 100644 ---- a/src/main/java/net/minecraft/world/level/chunk/storage/ChunkStorage.java -+++ b/src/main/java/net/minecraft/world/level/chunk/storage/ChunkStorage.java -@@ -31,18 +31,21 @@ import net.minecraft.world.level.storage.DimensionDataStorage; - public class ChunkStorage implements AutoCloseable { - - public static final int LAST_MONOLYTH_STRUCTURE_DATA_VERSION = 1493; -- private final IOWorker worker; -+ // Paper start - rewrite chunk system; async chunk IO -+ private final Object persistentDataLock = new Object(); -+ public final RegionFileStorage regionFileCache; -+ // Paper end - rewrite chunk system - protected final DataFixer fixerUpper; - @Nullable - private volatile LegacyStructureDataHandler legacyStructureHandler; - - public ChunkStorage(RegionStorageInfo storageKey, Path directory, DataFixer dataFixer, boolean dsync) { - this.fixerUpper = dataFixer; -- this.worker = new IOWorker(storageKey, directory, dsync); -+ this.regionFileCache = new RegionFileStorage(storageKey, directory, dsync); // Paper - rewrite chunk system; async chunk IO - } - - public boolean isOldChunkAround(ChunkPos chunkPos, int checkRadius) { -- return this.worker.isOldChunkAround(chunkPos, checkRadius); -+ return true; // Paper - rewrite chunk system - } - - // CraftBukkit start -@@ -50,8 +53,9 @@ public class ChunkStorage implements AutoCloseable { - if (true) return true; // Paper - Perf: this isn't even needed anymore, light is purged updating to 1.14+, why are we holding up the conversion process reading chunk data off disk - return true, we need to set light populated to true so the converter recognizes the chunk as being "full" - ChunkPos pos = new ChunkPos(x, z); - if (cps != null) { -- com.google.common.base.Preconditions.checkState(org.bukkit.Bukkit.isPrimaryThread(), "primary thread"); -- if (cps.hasChunk(x, z)) { -+ // Paper start - rewrite chunk system; async chunk IO -+ if (cps.getChunkAtIfCachedImmediately(x, z) != null) { // isLoaded is a ticket level check, not a chunk loaded check! -+ // Paper end - rewrite chunk system - return true; - } - } -@@ -79,6 +83,7 @@ public class ChunkStorage implements AutoCloseable { - - public CompoundTag upgradeChunkTag(ResourceKey resourcekey, Supplier supplier, CompoundTag nbttagcompound, Optional>> optional, ChunkPos pos, @Nullable LevelAccessor generatoraccess) { - // CraftBukkit end -+ nbttagcompound = nbttagcompound.copy(); // Paper - defensive copy, another thread might modify this - int i = ChunkStorage.getVersion(nbttagcompound); - - try { -@@ -97,9 +102,11 @@ public class ChunkStorage implements AutoCloseable { - if (i < 1493) { - ca.spottedleaf.dataconverter.minecraft.MCDataConverter.convertTag(ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry.CHUNK, nbttagcompound, i, 1493); // Paper - replace chunk converter - if (nbttagcompound.getCompound("Level").getBoolean("hasLegacyStructureData")) { -+ synchronized (this.persistentDataLock) { // Paper - Async chunk loading - LegacyStructureDataHandler persistentstructurelegacy = this.getLegacyStructureHandler(resourcekey, supplier); - - nbttagcompound = persistentstructurelegacy.updateFromLegacy(nbttagcompound); -+ } // Paper - Async chunk loading - } - } - -@@ -139,7 +146,7 @@ public class ChunkStorage implements AutoCloseable { - LegacyStructureDataHandler persistentstructurelegacy = this.legacyStructureHandler; - - if (persistentstructurelegacy == null) { -- synchronized (this) { -+ synchronized (this.persistentDataLock) { // Paper - async chunk loading - persistentstructurelegacy = this.legacyStructureHandler; - if (persistentstructurelegacy == null) { - this.legacyStructureHandler = persistentstructurelegacy = LegacyStructureDataHandler.getLegacyStructureHandler(worldKey, (DimensionDataStorage) stateManagerGetter.get()); -@@ -165,10 +172,20 @@ public class ChunkStorage implements AutoCloseable { - } - - public CompletableFuture> read(ChunkPos chunkPos) { -- return this.worker.loadAsync(chunkPos); -+ // Paper start - async chunk io -+ try { -+ return CompletableFuture.completedFuture(Optional.ofNullable(this.readSync(chunkPos))); -+ } catch (Throwable thr) { -+ return CompletableFuture.failedFuture(thr); -+ } -+ } -+ @Nullable -+ public CompoundTag readSync(ChunkPos chunkPos) throws IOException { -+ return this.regionFileCache.read(chunkPos); - } -+ // Paper end - async chunk io - -- public CompletableFuture write(ChunkPos chunkPos, CompoundTag nbt) { -+ public CompletableFuture write(ChunkPos chunkPos, CompoundTag nbt) throws IOException { // Paper - rewrite chunk system; async chunk io - // Paper start - guard against serializing mismatching coordinates - if (nbt != null && !chunkPos.equals(ChunkSerializer.getChunkCoordinate(nbt))) { - final String world = (this instanceof net.minecraft.server.level.ChunkMap) ? ((net.minecraft.server.level.ChunkMap) this).level.getWorld().getName() : null; -@@ -176,26 +193,39 @@ public class ChunkStorage implements AutoCloseable { - + " but compound says coordinate is " + ChunkSerializer.getChunkCoordinate(nbt) + (world == null ? " for an unknown world" : (" for world: " + world))); - } - // Paper end - guard against serializing mismatching coordinates -+ this.regionFileCache.write(chunkPos, nbt); // Paper - rewrite chunk system; async chunk io, move above legacy structure index - this.handleLegacyStructureIndex(chunkPos); -- return this.worker.store(chunkPos, nbt); -+ // Paper - rewrite chunk system; async chunk io, move above legacy structure index -+ return null; - } - - protected void handleLegacyStructureIndex(ChunkPos chunkPos) { - if (this.legacyStructureHandler != null) { -+ synchronized (this.persistentDataLock) { // Paper - rewrite chunk system; async chunk io - this.legacyStructureHandler.removeIndex(chunkPos.toLong()); -+ } // Paper - rewrite chunk system; async chunk io - } - - } - - public void flushWorker() { -- this.worker.synchronize(true).join(); -+ io.papermc.paper.chunk.system.io.RegionFileIOThread.flush(); // Paper - rewrite chunk system - } - - public void close() throws IOException { -- this.worker.close(); -+ this.regionFileCache.close(); // Paper - nuke IO worker - } - - public ChunkScanAccess chunkScanner() { -- return this.worker; -+ // Paper start - nuke IO worker -+ return ((chunkPos, streamTagVisitor) -> { -+ try { -+ this.regionFileCache.scanChunk(chunkPos, streamTagVisitor); -+ return java.util.concurrent.CompletableFuture.completedFuture(null); -+ } catch (IOException e) { -+ throw new RuntimeException(e); -+ } -+ }); -+ // Paper end - } - } -diff --git a/src/main/java/net/minecraft/world/level/chunk/storage/EntityStorage.java b/src/main/java/net/minecraft/world/level/chunk/storage/EntityStorage.java -index 49d8a62d2b6ca6da4e02b3cec7e42c38b7781b57..9fdf8f857a5f9b231c6d0633eaba498244214f74 100644 ---- a/src/main/java/net/minecraft/world/level/chunk/storage/EntityStorage.java -+++ b/src/main/java/net/minecraft/world/level/chunk/storage/EntityStorage.java -@@ -27,43 +27,30 @@ public class EntityStorage implements EntityPersistentStorage { - private static final String ENTITIES_TAG = "Entities"; - private static final String POSITION_TAG = "Position"; - public final ServerLevel level; -- private final SimpleRegionStorage simpleRegionStorage; -+ // Paper - rewrite chunk system - private final LongSet emptyChunks = new LongOpenHashSet(); -- public final ProcessorMailbox entityDeserializerQueue; -+ // Paper - rewrite chunk system - - public EntityStorage(SimpleRegionStorage storage, ServerLevel world, Executor executor) { -- this.simpleRegionStorage = storage; -+ // Paper - rewrite chunk system - this.level = world; -- this.entityDeserializerQueue = ProcessorMailbox.create(executor, "entity-deserializer"); -+ // Paper - rewrite chunk system - } - - @Override - public CompletableFuture> loadEntities(ChunkPos pos) { -- return this.emptyChunks.contains(pos.toLong()) -- ? CompletableFuture.completedFuture(emptyChunk(pos)) -- : this.simpleRegionStorage.read(pos).thenApplyAsync(nbt -> { -- if (nbt.isEmpty()) { -- this.emptyChunks.add(pos.toLong()); -- return emptyChunk(pos); -- } else { -- try { -- ChunkPos chunkPos2 = readChunkPos(nbt.get()); -- if (!Objects.equals(pos, chunkPos2)) { -- LOGGER.error("Chunk file at {} is in the wrong location. (Expected {}, got {})", pos, pos, chunkPos2); -- } -- } catch (Exception var6) { -- LOGGER.warn("Failed to parse chunk {} position info", pos, var6); -- } -- -- CompoundTag compoundTag = this.simpleRegionStorage.upgradeChunkTag(nbt.get(), -1); -- ListTag listTag = compoundTag.getList("Entities", 10); -- List list = EntityType.loadEntitiesRecursive(listTag, this.level).collect(ImmutableList.toImmutableList()); -- return new ChunkEntities<>(pos, list); -- } -- }, this.entityDeserializerQueue::tell); -+ throw new UnsupportedOperationException(); // Paper - rewrite chunk system - copy out read logic into readEntities - } - -- private static ChunkPos readChunkPos(CompoundTag chunkNbt) { -+ // Paper start - rewrite chunk system -+ public static List readEntities(ServerLevel level, CompoundTag compoundTag) { -+ ListTag listTag = compoundTag.getList("Entities", 10); -+ List list = EntityType.loadEntitiesRecursive(listTag, level).collect(ImmutableList.toImmutableList()); -+ return list; -+ } -+ // Paper end - rewrite chunk system -+ -+ public static ChunkPos readChunkPos(CompoundTag chunkNbt) { // Paper - public - int[] is = chunkNbt.getIntArray("Position"); - return new ChunkPos(is[0], is[1]); - } -@@ -78,38 +65,74 @@ public class EntityStorage implements EntityPersistentStorage { - - @Override - public void storeEntities(ChunkEntities dataList) { -+ // Paper start - rewrite chunk system -+ if (true) { -+ throw new UnsupportedOperationException(); -+ } -+ // Paper end - rewrite chunk system - ChunkPos chunkPos = dataList.getPos(); - if (dataList.isEmpty()) { - if (this.emptyChunks.add(chunkPos.toLong())) { -- this.simpleRegionStorage.write(chunkPos, null); -+ // Paper - rewrite chunk system - fix compile for unused field in dead code - } - } else { -- ListTag listTag = new ListTag(); -- dataList.getEntities().forEach(entity -> { -- CompoundTag compoundTagx = new CompoundTag(); -- if (entity.save(compoundTagx)) { -- listTag.add(compoundTagx); -- } -- }); -- CompoundTag compoundTag = NbtUtils.addCurrentDataVersion(new CompoundTag()); -- compoundTag.put("Entities", listTag); -- writeChunkPos(compoundTag, chunkPos); -- this.simpleRegionStorage.write(chunkPos, compoundTag).exceptionally(ex -> { -- LOGGER.error("Failed to store chunk {}", chunkPos, ex); -- return null; -- }); -+ // Paper - move into saveEntityChunk0 - this.emptyChunks.remove(chunkPos.toLong()); - } - } - -+ // Paper start - rewrite chunk system -+ public static void copyEntities(final CompoundTag from, final CompoundTag into) { -+ if (from == null) { -+ return; -+ } -+ final ListTag entitiesFrom = from.getList("Entities", net.minecraft.nbt.Tag.TAG_COMPOUND); -+ if (entitiesFrom == null || entitiesFrom.isEmpty()) { -+ return; -+ } -+ -+ final ListTag entitiesInto = into.getList("Entities", net.minecraft.nbt.Tag.TAG_COMPOUND); -+ into.put("Entities", entitiesInto); // this is in case into doesn't have any entities -+ entitiesInto.addAll(0, entitiesFrom.copy()); // need to copy, this is coming from the save thread -+ } -+ -+ public static CompoundTag saveEntityChunk(List entities, ChunkPos chunkPos, ServerLevel level) { -+ return saveEntityChunk0(entities, chunkPos, level, false); -+ } -+ private static CompoundTag saveEntityChunk0(List entities, ChunkPos chunkPos, ServerLevel level, boolean force) { -+ if (!force && entities.isEmpty()) { -+ return null; -+ } -+ -+ ListTag listTag = new ListTag(); -+ entities.forEach((entity) -> { // diff here: use entities parameter -+ CompoundTag compoundTag = new CompoundTag(); -+ if (entity.save(compoundTag)) { -+ listTag.add(compoundTag); -+ } -+ -+ }); -+ CompoundTag compoundTag = NbtUtils.addCurrentDataVersion(new CompoundTag()); -+ compoundTag.put("Entities", listTag); -+ writeChunkPos(compoundTag, chunkPos); -+ // Paper - remove worker usage -+ -+ return !force && listTag.isEmpty() ? null : compoundTag; -+ } -+ -+ public static CompoundTag upgradeChunkTag(CompoundTag chunkNbt) { -+ int i = NbtUtils.getDataVersion(chunkNbt, -1); -+ return ca.spottedleaf.dataconverter.minecraft.MCDataConverter.convertTag(ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry.ENTITY_CHUNK, chunkNbt, i, net.minecraft.SharedConstants.getCurrentVersion().getDataVersion().getVersion()); -+ } -+ // Paper end - rewrite chunk system -+ - @Override - public void flush(boolean sync) { -- this.simpleRegionStorage.synchronize(sync).join(); -- this.entityDeserializerQueue.runAll(); -+ throw new UnsupportedOperationException(); // Paper - rewrite chunk system - } - - @Override - public void close() throws IOException { -- this.simpleRegionStorage.close(); -+ throw new UnsupportedOperationException(); // Paper - rewrite chunk system - } - } -diff --git a/src/main/java/net/minecraft/world/level/chunk/storage/RegionFile.java b/src/main/java/net/minecraft/world/level/chunk/storage/RegionFile.java -index e858436bcf1b234d4bc6e6a117f5224d5c2d9f90..307196b2a58d4f8db3e6e3c3517a8004d4908b13 100644 ---- a/src/main/java/net/minecraft/world/level/chunk/storage/RegionFile.java -+++ b/src/main/java/net/minecraft/world/level/chunk/storage/RegionFile.java -@@ -48,6 +48,7 @@ public class RegionFile implements AutoCloseable { - private final IntBuffer timestamps; - @VisibleForTesting - protected final RegionBitmap usedSectors; -+ public final java.util.concurrent.locks.ReentrantLock fileLock = new java.util.concurrent.locks.ReentrantLock(); // Paper - - public RegionFile(RegionStorageInfo storageKey, Path directory, Path path, boolean dsync) throws IOException { - this(storageKey, directory, path, RegionFileVersion.getCompressionFormat(), dsync); // Paper - Configurable region compression format -@@ -250,7 +251,7 @@ public class RegionFile implements AutoCloseable { - return (byteCount + 4096 - 1) / 4096; - } - -- public boolean doesChunkExist(ChunkPos pos) { -+ public synchronized boolean doesChunkExist(ChunkPos pos) { // Paper - synchronized - int i = this.getOffset(pos); - - if (i == 0) { -@@ -417,6 +418,11 @@ public class RegionFile implements AutoCloseable { - } - - public void close() throws IOException { -+ // Paper start - Prevent regionfiles from being closed during use -+ this.fileLock.lock(); -+ synchronized (this) { -+ try { -+ // Paper end - try { - this.padToFullSector(); - } finally { -@@ -426,6 +432,10 @@ public class RegionFile implements AutoCloseable { - this.file.close(); - } - } -+ } finally { // Paper start - Prevent regionfiles from being closed during use -+ this.fileLock.unlock(); -+ } -+ } // Paper end - - } - -diff --git a/src/main/java/net/minecraft/world/level/chunk/storage/RegionFileStorage.java b/src/main/java/net/minecraft/world/level/chunk/storage/RegionFileStorage.java -index c4eef3aade889c69cefd873bec2d031cc54103ea..3f6955be976064eb542b5c50a9d6d74457c1833c 100644 ---- a/src/main/java/net/minecraft/world/level/chunk/storage/RegionFileStorage.java -+++ b/src/main/java/net/minecraft/world/level/chunk/storage/RegionFileStorage.java -@@ -26,31 +26,99 @@ public class RegionFileStorage implements AutoCloseable { - private final Path folder; - private final boolean sync; - -- RegionFileStorage(RegionStorageInfo storageKey, Path directory, boolean dsync) { -+ // Paper start - cache regionfile does not exist state -+ static final int MAX_NON_EXISTING_CACHE = 1024 * 64; -+ private final it.unimi.dsi.fastutil.longs.LongLinkedOpenHashSet nonExistingRegionFiles = new it.unimi.dsi.fastutil.longs.LongLinkedOpenHashSet(); -+ private synchronized boolean doesRegionFilePossiblyExist(long position) { -+ if (this.nonExistingRegionFiles.contains(position)) { -+ this.nonExistingRegionFiles.addAndMoveToFirst(position); -+ return false; -+ } -+ return true; -+ } -+ -+ private synchronized void createRegionFile(long position) { -+ this.nonExistingRegionFiles.remove(position); -+ } -+ -+ private synchronized void markNonExisting(long position) { -+ if (this.nonExistingRegionFiles.addAndMoveToFirst(position)) { -+ while (this.nonExistingRegionFiles.size() >= MAX_NON_EXISTING_CACHE) { -+ this.nonExistingRegionFiles.removeLastLong(); -+ } -+ } -+ } -+ -+ public synchronized boolean doesRegionFileNotExistNoIO(ChunkPos pos) { -+ long key = ChunkPos.asLong(pos.getRegionX(), pos.getRegionZ()); -+ return !this.doesRegionFilePossiblyExist(key); -+ } -+ // Paper end - cache regionfile does not exist state -+ -+ protected RegionFileStorage(RegionStorageInfo storageKey, Path directory, boolean dsync) { // Paper - protected constructor - this.folder = directory; - this.sync = dsync; - this.info = storageKey; - } - -- private RegionFile getRegionFile(ChunkPos chunkcoordintpair, boolean existingOnly) throws IOException { // CraftBukkit -- long i = ChunkPos.asLong(chunkcoordintpair.getRegionX(), chunkcoordintpair.getRegionZ()); -+ // Paper start -+ public synchronized RegionFile getRegionFileIfLoaded(ChunkPos chunkcoordintpair) { -+ return this.regionCache.getAndMoveToFirst(ChunkPos.asLong(chunkcoordintpair.getRegionX(), chunkcoordintpair.getRegionZ())); -+ } -+ -+ public synchronized boolean chunkExists(ChunkPos pos) throws IOException { -+ RegionFile regionfile = getRegionFile(pos, true); -+ -+ return regionfile != null ? regionfile.hasChunk(pos) : false; -+ } -+ -+ public synchronized RegionFile getRegionFile(ChunkPos chunkcoordintpair, boolean existingOnly) throws IOException { // CraftBukkit -+ return this.getRegionFile(chunkcoordintpair, existingOnly, false); -+ } -+ public synchronized RegionFile getRegionFile(ChunkPos chunkcoordintpair, boolean existingOnly, boolean lock) throws IOException { -+ // Paper end -+ long i = ChunkPos.asLong(chunkcoordintpair.getRegionX(), chunkcoordintpair.getRegionZ()); final long regionPos = i; // Paper - OBFHELPER - RegionFile regionfile = (RegionFile) this.regionCache.getAndMoveToFirst(i); - - if (regionfile != null) { -+ // Paper start -+ if (lock) { -+ // must be in this synchronized block -+ regionfile.fileLock.lock(); -+ } -+ // Paper end - return regionfile; - } else { -+ // Paper start - cache regionfile does not exist state -+ if (existingOnly && !this.doesRegionFilePossiblyExist(regionPos)) { -+ return null; -+ } -+ // Paper end - cache regionfile does not exist state - if (this.regionCache.size() >= io.papermc.paper.configuration.GlobalConfiguration.get().misc.regionFileCacheSize) { // Paper - Sanitise RegionFileCache and make configurable - ((RegionFile) this.regionCache.removeLast()).close(); - } - -- FileUtil.createDirectoriesSafe(this.folder); -+ // Paper - only create directory if not existing only - moved down - Path path = this.folder; - int j = chunkcoordintpair.getRegionX(); - Path path1 = path.resolve("r." + j + "." + chunkcoordintpair.getRegionZ() + ".mca"); -- if (existingOnly && !java.nio.file.Files.exists(path1)) return null; // CraftBukkit -+ if (existingOnly && !java.nio.file.Files.exists(path1)) { // Paper start - cache regionfile does not exist state -+ this.markNonExisting(regionPos); -+ return null; // CraftBukkit -+ } else { -+ this.createRegionFile(regionPos); -+ } -+ // Paper end - cache regionfile does not exist state -+ FileUtil.createDirectoriesSafe(this.folder); // Paper - only create directory if not existing only - moved from above - RegionFile regionfile1 = new RegionFile(this.info, path1, this.folder, this.sync); - - this.regionCache.putAndMoveToFirst(i, regionfile1); -+ // Paper start -+ if (lock) { -+ // must be in this synchronized block -+ regionfile1.fileLock.lock(); -+ } -+ // Paper end - return regionfile1; - } - } -@@ -58,11 +126,12 @@ public class RegionFileStorage implements AutoCloseable { - @Nullable - public CompoundTag read(ChunkPos pos) throws IOException { - // CraftBukkit start - SPIGOT-5680: There's no good reason to preemptively create files on read, save that for writing -- RegionFile regionfile = this.getRegionFile(pos, true); -+ RegionFile regionfile = this.getRegionFile(pos, true, true); // Paper - if (regionfile == null) { - return null; - } - // CraftBukkit end -+ try { // Paper - DataInputStream datainputstream = regionfile.getChunkDataInputStream(pos); - - CompoundTag nbttagcompound; -@@ -99,6 +168,9 @@ public class RegionFileStorage implements AutoCloseable { - } - - return nbttagcompound; -+ } finally { // Paper start -+ regionfile.fileLock.unlock(); -+ } // Paper end - } - - public void scanChunk(ChunkPos chunkPos, StreamTagVisitor scanner) throws IOException { -@@ -133,7 +205,13 @@ public class RegionFileStorage implements AutoCloseable { - } - - protected void write(ChunkPos pos, @Nullable CompoundTag nbt) throws IOException { -- RegionFile regionfile = this.getRegionFile(pos, false); // CraftBukkit -+ // Paper start - rewrite chunk system -+ RegionFile regionfile = this.getRegionFile(pos, nbt == null, true); // CraftBukkit -+ if (nbt == null && regionfile == null) { -+ return; -+ } -+ try { // Try finally to unlock the region file -+ // Paper end - rewrite chunk system - // Paper start - Chunk save reattempt - int attempts = 0; - Exception lastException = null; -@@ -179,9 +257,14 @@ public class RegionFileStorage implements AutoCloseable { - net.minecraft.server.MinecraftServer.LOGGER.error("Failed to save chunk {}", pos, lastException); - } - // Paper end - Chunk save reattempt -+ // Paper start - rewrite chunk system -+ } finally { -+ regionfile.fileLock.unlock(); -+ } -+ // Paper end - rewrite chunk system - } - -- public void close() throws IOException { -+ public synchronized void close() throws IOException { // Paper -> synchronized - ExceptionCollector exceptionsuppressor = new ExceptionCollector<>(); - ObjectIterator objectiterator = this.regionCache.values().iterator(); - -@@ -198,7 +281,7 @@ public class RegionFileStorage implements AutoCloseable { - exceptionsuppressor.throwIfPresent(); - } - -- public void flush() throws IOException { -+ public synchronized void flush() throws IOException { // Paper - synchronize - ObjectIterator objectiterator = this.regionCache.values().iterator(); - - while (objectiterator.hasNext()) { -diff --git a/src/main/java/net/minecraft/world/level/chunk/storage/SectionStorage.java b/src/main/java/net/minecraft/world/level/chunk/storage/SectionStorage.java -index 151fcbca34e02783e19fbb7b54ec4fbec2eed190..883fbe5c81e3be27007a1a0489f80ba1863e5a04 100644 ---- a/src/main/java/net/minecraft/world/level/chunk/storage/SectionStorage.java -+++ b/src/main/java/net/minecraft/world/level/chunk/storage/SectionStorage.java -@@ -12,6 +12,7 @@ import it.unimi.dsi.fastutil.longs.Long2ObjectMap; - import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap; - import it.unimi.dsi.fastutil.longs.LongLinkedOpenHashSet; - import java.io.IOException; -+import java.nio.file.Path; - import java.util.Map; - import java.util.Optional; - import java.util.concurrent.CompletableFuture; -@@ -31,25 +32,30 @@ import net.minecraft.world.level.ChunkPos; - import net.minecraft.world.level.LevelHeightAccessor; - import org.slf4j.Logger; - --public class SectionStorage implements AutoCloseable { -+public class SectionStorage extends RegionFileStorage implements AutoCloseable { // Paper - nuke IOWorker - private static final Logger LOGGER = LogUtils.getLogger(); - private static final String SECTIONS_TAG = "Sections"; -- private final SimpleRegionStorage simpleRegionStorage; -+ // Paper - remove mojang I/O thread - private final Long2ObjectMap> storage = new Long2ObjectOpenHashMap<>(); - private final LongLinkedOpenHashSet dirty = new LongLinkedOpenHashSet(); - private final Function> codec; - private final Function factory; -- private final RegistryAccess registryAccess; -+ public final RegistryAccess registryAccess; // Paper - rewrite chunk system - public - protected final LevelHeightAccessor levelHeightAccessor; - - public SectionStorage( -+ // Paper start -+ RegionStorageInfo regionStorageInfo, -+ Path path, -+ boolean dsync, -+ // Paper end - SimpleRegionStorage storageAccess, - Function> codecFactory, - Function factory, - RegistryAccess registryManager, - LevelHeightAccessor world - ) { -- this.simpleRegionStorage = storageAccess; -+ super(regionStorageInfo, path, dsync); // Paper - remove mojang I/O thread - this.codec = codecFactory; - this.factory = factory; - this.registryAccess = registryManager; -@@ -112,23 +118,21 @@ public class SectionStorage implements AutoCloseable { - } - - private void readColumn(ChunkPos pos) { -- Optional optional = this.tryRead(pos).join(); -- RegistryOps registryOps = this.registryAccess.createSerializationContext(NbtOps.INSTANCE); -- this.readColumn(pos, registryOps, optional.orElse(null)); -+ throw new IllegalStateException("Only chunk system can load in state, offending class:" + this.getClass().getName()); // Paper - rewrite chunk system - } - - private CompletableFuture> tryRead(ChunkPos pos) { -- return this.simpleRegionStorage.read(pos).exceptionally(throwable -> { -- if (throwable instanceof IOException iOException) { -- LOGGER.error("Error reading chunk {} data from disk", pos, iOException); -- return Optional.empty(); -- } else { -- throw new CompletionException(throwable); -- } -- }); -+ // Paper start - rewrite chunk system -+ try { -+ return CompletableFuture.completedFuture(Optional.ofNullable(this.read(pos))); -+ } catch (Throwable thr) { -+ return CompletableFuture.failedFuture(thr); -+ } -+ // Paper end - rewrite chunk system - } - - private void readColumn(ChunkPos pos, RegistryOps ops, @Nullable CompoundTag nbt) { -+ if (true) throw new IllegalStateException("Only chunk system can load in state, offending class:" + this.getClass().getName()); // Paper - rewrite chunk system - if (nbt == null) { - for (int i = this.levelHeightAccessor.getMinSection(); i < this.levelHeightAccessor.getMaxSection(); i++) { - this.storage.put(getKey(pos, i), Optional.empty()); -@@ -138,7 +142,7 @@ public class SectionStorage implements AutoCloseable { - int j = getVersion(dynamic); - int k = SharedConstants.getCurrentVersion().getDataVersion().getVersion(); - boolean bl = j != k; -- Dynamic dynamic2 = this.simpleRegionStorage.upgradeChunkTag(dynamic, j); -+ Dynamic dynamic2 = null; // Paper - rewrite chunk system - OptionalDynamic optionalDynamic = dynamic2.get("Sections"); - - for (int l = this.levelHeightAccessor.getMinSection(); l < this.levelHeightAccessor.getMaxSection(); l++) { -@@ -162,7 +166,7 @@ public class SectionStorage implements AutoCloseable { - Dynamic dynamic = this.writeColumn(pos, registryOps); - Tag tag = dynamic.getValue(); - if (tag instanceof CompoundTag) { -- this.simpleRegionStorage.write(pos, (CompoundTag)tag); -+ try { this.write(pos, (CompoundTag)tag); } catch (IOException ex) { SectionStorage.LOGGER.error("Error writing poi chunk data to disk for chunk " + pos, ex); } // Paper - nuke IOWorker - } else { - LOGGER.error("Expected compound tag, got {}", tag); - } -@@ -212,7 +216,7 @@ public class SectionStorage implements AutoCloseable { - } - - private static int getVersion(Dynamic dynamic) { -- return dynamic.get("DataVersion").asInt(1945); -+ return dynamic.get("DataVersion").asInt(1945); // Paper - diff on change, constant used in ChunkLoadTask - } - - public void flush(ChunkPos pos) { -@@ -229,6 +233,6 @@ public class SectionStorage implements AutoCloseable { - - @Override - public void close() throws IOException { -- this.simpleRegionStorage.close(); -+ super.close(); // Paper - nuke I/O worker - don't call the worker - } - } -diff --git a/src/main/java/net/minecraft/world/level/entity/EntityTickList.java b/src/main/java/net/minecraft/world/level/entity/EntityTickList.java -index 74a285b8b018a9c94ccea519f1ce8b9e2ef3cb64..83a39f900551e39d5af6f17a339a386ddee4feef 100644 ---- a/src/main/java/net/minecraft/world/level/entity/EntityTickList.java -+++ b/src/main/java/net/minecraft/world/level/entity/EntityTickList.java -@@ -9,52 +9,41 @@ import javax.annotation.Nullable; - import net.minecraft.world.entity.Entity; - - public class EntityTickList { -- private Int2ObjectMap active = new Int2ObjectLinkedOpenHashMap<>(); -- private Int2ObjectMap passive = new Int2ObjectLinkedOpenHashMap<>(); -- @Nullable -- private Int2ObjectMap iterated; -+ private final io.papermc.paper.util.maplist.IteratorSafeOrderedReferenceSet entities = new io.papermc.paper.util.maplist.IteratorSafeOrderedReferenceSet<>(true); // Paper - rewrite this, always keep this updated - why would we EVER tick an entity that's not ticking? - - private void ensureActiveIsNotIterated() { -- if (this.iterated == this.active) { -- this.passive.clear(); -- -- for (Entry entry : Int2ObjectMaps.fastIterable(this.active)) { -- this.passive.put(entry.getIntKey(), entry.getValue()); -- } -- -- Int2ObjectMap int2ObjectMap = this.active; -- this.active = this.passive; -- this.passive = int2ObjectMap; -- } -+ // Paper - replace with better logic, do not delay removals - } - - public void add(Entity entity) { -+ io.papermc.paper.util.TickThread.ensureTickThread("Asynchronous entity ticklist addition"); // Paper - this.ensureActiveIsNotIterated(); -- this.active.put(entity.getId(), entity); -+ this.entities.add(entity); // Paper - replace with better logic, do not delay removals/additions - } - - public void remove(Entity entity) { -+ io.papermc.paper.util.TickThread.ensureTickThread("Asynchronous entity ticklist removal"); // Paper - this.ensureActiveIsNotIterated(); -- this.active.remove(entity.getId()); -+ this.entities.remove(entity); // Paper - replace with better logic, do not delay removals/additions - } - - public boolean contains(Entity entity) { -- return this.active.containsKey(entity.getId()); -+ return this.entities.contains(entity); // Paper - replace with better logic, do not delay removals/additions - } - - public void forEach(Consumer action) { -- if (this.iterated != null) { -- throw new UnsupportedOperationException("Only one concurrent iteration supported"); -- } else { -- this.iterated = this.active; -- -- try { -- for (Entity entity : this.active.values()) { -- action.accept(entity); -- } -- } finally { -- this.iterated = null; -+ io.papermc.paper.util.TickThread.ensureTickThread("Asynchronous entity ticklist iteration"); // Paper -+ // Paper start - replace with better logic, do not delay removals/additions -+ // To ensure nothing weird happens with dimension travelling, do not iterate over new entries... -+ // (by dfl iterator() is configured to not iterate over new entries) -+ io.papermc.paper.util.maplist.IteratorSafeOrderedReferenceSet.Iterator iterator = this.entities.iterator(); -+ try { -+ while (iterator.hasNext()) { -+ action.accept(iterator.next()); - } -+ } finally { -+ iterator.finishedIterating(); - } -+ // Paper end - replace with better logic, do not delay removals/additions - } - } -diff --git a/src/main/java/net/minecraft/world/level/levelgen/NoiseBasedChunkGenerator.java b/src/main/java/net/minecraft/world/level/levelgen/NoiseBasedChunkGenerator.java -index 5d15c228c044a36c67014793decb314240cf6be1..dc765b92cc90f5f370254e68bbbdfa5add7935ce 100644 ---- a/src/main/java/net/minecraft/world/level/levelgen/NoiseBasedChunkGenerator.java -+++ b/src/main/java/net/minecraft/world/level/levelgen/NoiseBasedChunkGenerator.java -@@ -87,7 +87,7 @@ public final class NoiseBasedChunkGenerator extends ChunkGenerator { - return CompletableFuture.supplyAsync(Util.wrapThreadWithTaskName("init_biomes", () -> { - this.doCreateBiomes(blender, noiseConfig, structureAccessor, chunk); - return chunk; -- }), Util.backgroundExecutor()); -+ }), executor); // Paper - run with supplied executor - } - - private void doCreateBiomes(Blender blender, RandomState noiseConfig, StructureManager structureAccessor, ChunkAccess chunk) { -@@ -286,7 +286,7 @@ public final class NoiseBasedChunkGenerator extends ChunkGenerator { - - return CompletableFuture.supplyAsync(Util.wrapThreadWithTaskName("wgen_fill_noise", () -> { - return this.doFill(blender, structureAccessor, noiseConfig, chunk, j, k); -- }), Util.backgroundExecutor()).whenCompleteAsync((ichunkaccess1, throwable) -> { -+ }), executor).whenCompleteAsync((ichunkaccess1, throwable) -> { // Paper - run with supplied executor - Iterator iterator = set.iterator(); - - while (iterator.hasNext()) { -diff --git a/src/main/java/net/minecraft/world/level/levelgen/structure/StructureCheck.java b/src/main/java/net/minecraft/world/level/levelgen/structure/StructureCheck.java -index 609100ed7aa0b23aa5a9c6fbf6878ea320bd3a93..7068657b28a9bc175ee23f5a18defb41168f1d76 100644 ---- a/src/main/java/net/minecraft/world/level/levelgen/structure/StructureCheck.java -+++ b/src/main/java/net/minecraft/world/level/levelgen/structure/StructureCheck.java -@@ -47,8 +47,101 @@ public class StructureCheck { - private final BiomeSource biomeSource; - private final long seed; - private final DataFixer fixerUpper; -- private final Long2ObjectMap> loadedChunks = new Long2ObjectOpenHashMap<>(); -- private final Map featureChecks = new HashMap<>(); -+ // Paper start - rewrite chunk system - synchronise this class -+ // additionally, make sure to purge entries from the maps so it does not leak memory -+ private static final int CHUNK_TOTAL_LIMIT = 50 * (2 * 100 + 1) * (2 * 100 + 1); // cache 50 structure lookups -+ private static final int PER_FEATURE_CHECK_LIMIT = 50 * (2 * 100 + 1) * (2 * 100 + 1); // cache 50 structure lookups -+ -+ private final SynchronisedLong2ObjectMap> loadedChunksSafe = new SynchronisedLong2ObjectMap<>(CHUNK_TOTAL_LIMIT); -+ private final java.util.concurrent.ConcurrentHashMap featureChecksSafe = new java.util.concurrent.ConcurrentHashMap<>(); -+ -+ private static final class SynchronisedLong2ObjectMap { -+ private final it.unimi.dsi.fastutil.longs.Long2ObjectLinkedOpenHashMap map = new it.unimi.dsi.fastutil.longs.Long2ObjectLinkedOpenHashMap<>(); -+ private final int limit; -+ -+ public SynchronisedLong2ObjectMap(final int limit) { -+ this.limit = limit; -+ } -+ -+ // must hold lock on map -+ private void purgeEntries() { -+ while (this.map.size() > this.limit) { -+ this.map.removeLast(); -+ } -+ } -+ -+ public V get(final long key) { -+ synchronized (this.map) { -+ return this.map.getAndMoveToFirst(key); -+ } -+ } -+ -+ public V put(final long key, final V value) { -+ synchronized (this.map) { -+ final V ret = this.map.putAndMoveToFirst(key, value); -+ this.purgeEntries(); -+ return ret; -+ } -+ } -+ -+ public V compute(final long key, final java.util.function.BiFunction remappingFunction) { -+ synchronized (this.map) { -+ // first, compute the value - if one is added, it will be at the last entry -+ this.map.compute(key, remappingFunction); -+ // move the entry to first, just in case it was added at last -+ final V ret = this.map.getAndMoveToFirst(key); -+ // now purge the last entries -+ this.purgeEntries(); -+ -+ return ret; -+ } -+ } -+ } -+ -+ private static final class SynchronisedLong2BooleanMap { -+ private final it.unimi.dsi.fastutil.longs.Long2BooleanLinkedOpenHashMap map = new it.unimi.dsi.fastutil.longs.Long2BooleanLinkedOpenHashMap(); -+ private final int limit; -+ -+ public SynchronisedLong2BooleanMap(final int limit) { -+ this.limit = limit; -+ } -+ -+ // must hold lock on map -+ private void purgeEntries() { -+ while (this.map.size() > this.limit) { -+ this.map.removeLastBoolean(); -+ } -+ } -+ -+ public boolean remove(final long key) { -+ synchronized (this.map) { -+ return this.map.remove(key); -+ } -+ } -+ -+ // note: -+ public boolean getOrCompute(final long key, final it.unimi.dsi.fastutil.longs.Long2BooleanFunction ifAbsent) { -+ synchronized (this.map) { -+ if (this.map.containsKey(key)) { -+ return this.map.getAndMoveToFirst(key); -+ } -+ } -+ -+ final boolean put = ifAbsent.get(key); -+ -+ synchronized (this.map) { -+ if (this.map.containsKey(key)) { -+ return this.map.getAndMoveToFirst(key); -+ } -+ this.map.putAndMoveToFirst(key, put); -+ -+ this.purgeEntries(); -+ -+ return put; -+ } -+ } -+ } -+ // Paper end - rewrite chunk system - synchronise this class - - public StructureCheck( - ChunkScanAccess chunkIoWorker, -@@ -90,7 +183,7 @@ public class StructureCheck { - - public StructureCheckResult checkStart(ChunkPos pos, Structure type, StructurePlacement placement, boolean skipReferencedStructures) { - long l = pos.toLong(); -- Object2IntMap object2IntMap = this.loadedChunks.get(l); -+ Object2IntMap object2IntMap = this.loadedChunksSafe.get(l); // Paper - rewrite chunk system - synchronise this class - if (object2IntMap != null) { - return this.checkStructureInfo(object2IntMap, type, skipReferencedStructures); - } else { -@@ -100,9 +193,9 @@ public class StructureCheck { - } else if (!placement.applyAdditionalChunkRestrictions(pos.x, pos.z, this.seed, this.getSaltOverride(type))) { // Paper - add missing structure seed configs - return StructureCheckResult.START_NOT_PRESENT; - } else { -- boolean bl = this.featureChecks -- .computeIfAbsent(type, structure2 -> new Long2BooleanOpenHashMap()) -- .computeIfAbsent(l, chunkPos -> this.canCreateStructure(pos, type)); -+ boolean bl = this.featureChecksSafe // Paper - rewrite chunk system - synchronise this class -+ .computeIfAbsent(type, structure2 -> new SynchronisedLong2BooleanMap(PER_FEATURE_CHECK_LIMIT)) // Paper - rewrite chunk system - synchronise this class -+ .getOrCompute(l, chunkPos -> this.canCreateStructure(pos, type)); // Paper - rewrite chunk system - synchronise this class - return !bl ? StructureCheckResult.START_NOT_PRESENT : StructureCheckResult.CHUNK_LOAD_NEEDED; - } - } -@@ -228,15 +321,26 @@ public class StructureCheck { - } - - private void storeFullResults(long pos, Object2IntMap referencesByStructure) { -- this.loadedChunks.put(pos, deduplicateEmptyMap(referencesByStructure)); -- this.featureChecks.values().forEach(generationPossibilityByChunkPos -> generationPossibilityByChunkPos.remove(pos)); -+ // Paper start - rewrite chunk system - synchronise this class -+ this.loadedChunksSafe.put(pos, deduplicateEmptyMap(referencesByStructure)); -+ // once we insert into loadedChunks, we don't really need to be very careful about removing everything -+ // from this map, as everything that checks this map uses loadedChunks first -+ // so, one way or another it's a race condition that doesn't matter -+ for (SynchronisedLong2BooleanMap value : this.featureChecksSafe.values()) { -+ value.remove(pos); -+ } -+ // Paper end - rewrite chunk system - synchronise this class - } - - public void incrementReference(ChunkPos pos, Structure structure) { -- this.loadedChunks.compute(pos.toLong(), (posx, referencesByStructure) -> { -- if (referencesByStructure == null || referencesByStructure.isEmpty()) { -+ this.loadedChunksSafe.compute(pos.toLong(), (posx, referencesByStructure) -> { // Paper start - rewrite chunk system - synchronise this class -+ // make this COW so that we do not mutate state that may be currently in use -+ if (referencesByStructure == null) { - referencesByStructure = new Object2IntOpenHashMap<>(); -+ } else { -+ referencesByStructure = referencesByStructure instanceof Object2IntOpenHashMap fastClone ? fastClone.clone() : new Object2IntOpenHashMap<>(referencesByStructure); - } -+ // Paper end - rewrite chunk system - synchronise this class - - referencesByStructure.computeInt(structure, (feature, references) -> references == null ? 1 : references + 1); - return referencesByStructure; -diff --git a/src/main/java/net/minecraft/world/ticks/LevelChunkTicks.java b/src/main/java/net/minecraft/world/ticks/LevelChunkTicks.java -index 47c2b2da9799690291396effb9e1b06d71efc6fd..2cdd18f724296f10cd4a522d1e8196723d39cf45 100644 ---- a/src/main/java/net/minecraft/world/ticks/LevelChunkTicks.java -+++ b/src/main/java/net/minecraft/world/ticks/LevelChunkTicks.java -@@ -26,6 +26,19 @@ public class LevelChunkTicks implements SerializableTickContainer, TickCon - @Nullable - private BiConsumer, ScheduledTick> onTickAdded; - -+ // Paper start - add dirty flag -+ private boolean dirty; -+ private long lastSaved = Long.MIN_VALUE; -+ -+ public boolean isDirty(final long tick) { -+ return this.dirty || (!this.tickQueue.isEmpty() && tick != this.lastSaved); -+ } -+ -+ public void clearDirty() { -+ this.dirty = false; -+ } -+ // Paper end - add dirty flag -+ - public LevelChunkTicks() { - } - -@@ -50,6 +63,7 @@ public class LevelChunkTicks implements SerializableTickContainer, TickCon - public ScheduledTick poll() { - ScheduledTick scheduledTick = this.tickQueue.poll(); - if (scheduledTick != null) { -+ this.dirty = true; // Paper - add dirty flag - this.ticksPerPosition.remove(scheduledTick); - } - -@@ -59,6 +73,7 @@ public class LevelChunkTicks implements SerializableTickContainer, TickCon - @Override - public void schedule(ScheduledTick orderedTick) { - if (this.ticksPerPosition.add(orderedTick)) { -+ this.dirty = true; // Paper - add dirty flag - this.scheduleUnchecked(orderedTick); - } - } -@@ -81,7 +96,7 @@ public class LevelChunkTicks implements SerializableTickContainer, TickCon - while (iterator.hasNext()) { - ScheduledTick scheduledTick = iterator.next(); - if (predicate.test(scheduledTick)) { -- iterator.remove(); -+ iterator.remove(); this.dirty = true; // Paper - add dirty flag - this.ticksPerPosition.remove(scheduledTick); - } - } -@@ -98,6 +113,7 @@ public class LevelChunkTicks implements SerializableTickContainer, TickCon - - @Override - public ListTag save(long l, Function function) { -+ this.lastSaved = l; // Paper - add dirty system to level ticks - ListTag listTag = new ListTag(); - if (this.pendingTicks != null) { - for (SavedTick savedTick : this.pendingTicks) { -@@ -114,6 +130,11 @@ public class LevelChunkTicks implements SerializableTickContainer, TickCon - - public void unpack(long time) { - if (this.pendingTicks != null) { -+ // Paper start - add dirty system to level chunk ticks -+ if (this.tickQueue.isEmpty()) { -+ this.lastSaved = time; -+ } -+ // Paper end - add dirty system to level chunk ticks - int i = -this.pendingTicks.size(); - - for (SavedTick savedTick : this.pendingTicks) { -diff --git a/src/main/java/org/bukkit/craftbukkit/CraftChunk.java b/src/main/java/org/bukkit/craftbukkit/CraftChunk.java -index 36a611d06131be00197c915871b8323544bb4972..bb22473df13f68ac3b45a9c000d1de7260e07792 100644 ---- a/src/main/java/org/bukkit/craftbukkit/CraftChunk.java -+++ b/src/main/java/org/bukkit/craftbukkit/CraftChunk.java -@@ -116,7 +116,7 @@ public class CraftChunk implements Chunk { - - @Override - public boolean isEntitiesLoaded() { -- return this.getCraftWorld().getHandle().entityManager.areEntitiesLoaded(ChunkPos.asLong(this.x, this.z)); -+ return this.getCraftWorld().getHandle().areEntitiesLoaded(io.papermc.paper.util.CoordinateUtils.getChunkKey(this.x, this.z)); // Paper - rewrite chunk system - } - - @Override -@@ -125,51 +125,7 @@ public class CraftChunk implements Chunk { - this.getWorld().getChunkAt(this.x, this.z); // Transient load for this tick - } - -- PersistentEntitySectionManager entityManager = this.getCraftWorld().getHandle().entityManager; -- long pair = ChunkPos.asLong(this.x, this.z); -- -- if (entityManager.areEntitiesLoaded(pair)) { -- return entityManager.getEntities(new ChunkPos(this.x, this.z)).stream() -- .map(net.minecraft.world.entity.Entity::getBukkitEntity) -- .filter(Objects::nonNull).toArray(Entity[]::new); -- } -- -- entityManager.ensureChunkQueuedForLoad(pair); // Start entity loading -- -- // SPIGOT-6772: Use entity mailbox and re-schedule entities if they get unloaded -- ProcessorMailbox mailbox = ((EntityStorage) entityManager.permanentStorage).entityDeserializerQueue; -- BooleanSupplier supplier = () -> { -- // only execute inbox if our entities are not present -- if (entityManager.areEntitiesLoaded(pair)) { -- return true; -- } -- -- if (!entityManager.isPending(pair)) { -- // Our entities got unloaded, this should normally not happen. -- entityManager.ensureChunkQueuedForLoad(pair); // Re-start entity loading -- } -- -- // tick loading inbox, which loads the created entities to the world -- // (if present) -- entityManager.tick(); -- // check if our entities are loaded -- return entityManager.areEntitiesLoaded(pair); -- }; -- -- // now we wait until the entities are loaded, -- // the converting from NBT to entity object is done on the main Thread which is why we wait -- while (!supplier.getAsBoolean()) { -- if (mailbox.size() != 0) { -- mailbox.run(); -- } else { -- Thread.yield(); -- LockSupport.parkNanos("waiting for entity loading", 100000L); -- } -- } -- -- return entityManager.getEntities(new ChunkPos(this.x, this.z)).stream() -- .map(net.minecraft.world.entity.Entity::getBukkitEntity) -- .filter(Objects::nonNull).toArray(Entity[]::new); -+ return this.getCraftWorld().getHandle().getChunkEntities(this.x, this.z); // Paper - rewrite chunk system - } - - @Override -diff --git a/src/main/java/org/bukkit/craftbukkit/CraftServer.java b/src/main/java/org/bukkit/craftbukkit/CraftServer.java -index 16a736e8327450712630b1659b156da879a57352..0e30a227948464979e12c991b10bd00cf7320399 100644 ---- a/src/main/java/org/bukkit/craftbukkit/CraftServer.java -+++ b/src/main/java/org/bukkit/craftbukkit/CraftServer.java -@@ -1426,7 +1426,6 @@ public final class CraftServer implements Server { - // Paper - Put world into worldlist before initing the world; move up - - this.getServer().prepareLevels(internal.getChunkSource().chunkMap.progressListener, internal); -- internal.entityManager.tick(); // SPIGOT-6526: Load pending entities so they are available to the API - - this.pluginManager.callEvent(new WorldLoadEvent(internal.getWorld())); - return internal.getWorld(); -@@ -1471,7 +1470,7 @@ public final class CraftServer implements Server { - } - - handle.getChunkSource().close(save); -- handle.entityManager.close(save); // SPIGOT-6722: close entityManager -+ // handle.entityManager.close(save); // SPIGOT-6722: close entityManager // Paper - rewrite chunk system - handle.convertable.close(); - } catch (Exception ex) { - this.getLogger().log(Level.SEVERE, null, ex); -@@ -2507,7 +2506,7 @@ public final class CraftServer implements Server { - - @Override - public boolean isPrimaryThread() { -- return Thread.currentThread().equals(this.console.serverThread) || this.console.hasStopped() || !org.spigotmc.AsyncCatcher.enabled; // All bets are off if we have shut down (e.g. due to watchdog) -+ return io.papermc.paper.util.TickThread.isTickThread(); // Paper - rewrite chunk system - } - - // Paper start - Adventure -diff --git a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java -index 7aee9f6b143c89cf8d65ca55eeda808152b4dd26..9c06c3729b09726e1da6ff8fb975cef2aeba9643 100644 ---- a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java -+++ b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java -@@ -518,10 +518,14 @@ public class CraftWorld extends CraftRegionAccessor implements World { - ChunkHolder playerChunk = this.world.getChunkSource().chunkMap.getVisibleChunkIfPresent(ChunkPos.asLong(x, z)); - if (playerChunk == null) return false; - -- playerChunk.getTickingChunkFuture().thenAccept(either -> { -- either.ifSuccess(chunk -> { -+ // Paper start - rewrite player chunk loader -+ net.minecraft.world.level.chunk.LevelChunk chunk = playerChunk.getSendingChunk(); -+ if (chunk == null) { -+ return false; -+ } -+ // Paper end - rewrite player chunk loader - List playersInRange = playerChunk.playerProvider.getPlayers(playerChunk.getPos(), false); -- if (playersInRange.isEmpty()) return; -+ if (playersInRange.isEmpty()) return true; // Paper - rewrite player chunk loader - - ClientboundLevelChunkWithLightPacket refreshPacket = new ClientboundLevelChunkWithLightPacket(chunk, this.world.getLightEngine(), null, null); - for (ServerPlayer player : playersInRange) { -@@ -529,8 +533,7 @@ public class CraftWorld extends CraftRegionAccessor implements World { - - player.connection.send(refreshPacket); - } -- }); -- }); -+ // Paper - rewrite player chunk loader - - return true; - } -@@ -634,20 +637,7 @@ public class CraftWorld extends CraftRegionAccessor implements World { - @Override - public Collection getPluginChunkTickets(int x, int z) { - DistanceManager chunkDistanceManager = this.world.getChunkSource().chunkMap.distanceManager; -- SortedArraySet> tickets = chunkDistanceManager.tickets.get(ChunkPos.asLong(x, z)); -- -- if (tickets == null) { -- return Collections.emptyList(); -- } -- -- ImmutableList.Builder ret = ImmutableList.builder(); -- for (Ticket ticket : tickets) { -- if (ticket.getType() == TicketType.PLUGIN_TICKET) { -- ret.add((Plugin) ticket.key); -- } -- } -- -- return ret.build(); -+ return chunkDistanceManager.getChunkHolderManager().getPluginChunkTickets(x, z); // Paper - rewrite chunk system - } - - @Override -@@ -655,7 +645,7 @@ public class CraftWorld extends CraftRegionAccessor implements World { - Map> ret = new HashMap<>(); - DistanceManager chunkDistanceManager = this.world.getChunkSource().chunkMap.distanceManager; - -- for (Long2ObjectMap.Entry>> chunkTickets : chunkDistanceManager.tickets.long2ObjectEntrySet()) { -+ for (Long2ObjectMap.Entry>> chunkTickets : chunkDistanceManager.getChunkHolderManager().getTicketsCopy().long2ObjectEntrySet()) { // Paper - rewrite chunk system - long chunkKey = chunkTickets.getLongKey(); - SortedArraySet> tickets = chunkTickets.getValue(); - -@@ -1352,12 +1342,12 @@ public class CraftWorld extends CraftRegionAccessor implements World { - - @Override - public int getViewDistance() { -- return this.world.getChunkSource().chunkMap.serverViewDistance; -+ return this.getHandle().playerChunkLoader.getAPIViewDistance(); // Paper - replace player chunk loader - } - - @Override - public int getSimulationDistance() { -- return this.world.getChunkSource().chunkMap.getDistanceManager().simulationDistance; -+ return this.getHandle().playerChunkLoader.getAPITickDistance(); // Paper - replace player chunk loader - } - - public BlockMetadataStore getBlockMetadata() { -@@ -2520,17 +2510,20 @@ public class CraftWorld extends CraftRegionAccessor implements World { - - @Override - public void setSimulationDistance(final int simulationDistance) { -- throw new UnsupportedOperationException("Not implemented yet"); -+ if (simulationDistance < 2 || simulationDistance > 32) { -+ throw new IllegalArgumentException("Simulation distance " + simulationDistance + " is out of range of [2, 32]"); -+ } -+ this.getHandle().chunkSource.chunkMap.setTickViewDistance(simulationDistance); - } - - @Override - public int getSendViewDistance() { -- return this.getViewDistance(); -+ return this.getHandle().playerChunkLoader.getAPISendViewDistance(); // Paper - replace player chunk loader - } - - @Override - public void setSendViewDistance(final int viewDistance) { -- throw new UnsupportedOperationException("Not implemented yet"); -+ this.getHandle().chunkSource.chunkMap.setSendViewDistance(viewDistance); // Paper - replace player chunk loader - } - - // Paper start - implement pointers -diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java -index 68a0b6b8650e9e80e8e8c4037d92389cae899d72..9aec6efef4094bbdb920101df1a7a5a2a6070dde 100644 ---- a/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java -+++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java -@@ -3463,31 +3463,31 @@ public class CraftPlayer extends CraftHumanEntity implements Player { - - @Override - public int getViewDistance() { -- return io.papermc.paper.chunk.system.ChunkSystem.getLoadViewDistance(this.getHandle()); -+ return io.papermc.paper.chunk.system.RegionizedPlayerChunkLoader.getAPIViewDistance(this); - } - - @Override - public void setViewDistance(final int viewDistance) { -- throw new UnsupportedOperationException("Not implemented yet"); -+ this.getHandle().setLoadViewDistance(viewDistance < 0 ? viewDistance : viewDistance + 1); - } - - @Override - public int getSimulationDistance() { -- return io.papermc.paper.chunk.system.ChunkSystem.getTickViewDistance(this.getHandle()); -+ return io.papermc.paper.chunk.system.RegionizedPlayerChunkLoader.getAPITickViewDistance(this); - } - - @Override - public void setSimulationDistance(final int simulationDistance) { -- throw new UnsupportedOperationException("Not implemented yet"); -+ this.getHandle().setTickViewDistance(simulationDistance); - } - - @Override - public int getSendViewDistance() { -- return io.papermc.paper.chunk.system.ChunkSystem.getSendViewDistance(this.getHandle()); -+ return io.papermc.paper.chunk.system.RegionizedPlayerChunkLoader.getAPISendViewDistance(this); - } - - @Override - public void setSendViewDistance(final int viewDistance) { -- throw new UnsupportedOperationException("Not implemented yet"); -+ this.getHandle().setSendViewDistance(viewDistance); - } - } -diff --git a/src/main/java/org/bukkit/craftbukkit/generator/CustomChunkGenerator.java b/src/main/java/org/bukkit/craftbukkit/generator/CustomChunkGenerator.java -index b65710b648e31ab74204b5abd9397d9e6e26dac4..c77f722131e0e40e9de29bf8d42f9bc5d8fa2f7d 100644 ---- a/src/main/java/org/bukkit/craftbukkit/generator/CustomChunkGenerator.java -+++ b/src/main/java/org/bukkit/craftbukkit/generator/CustomChunkGenerator.java -@@ -264,7 +264,7 @@ public class CustomChunkGenerator extends InternalChunkGenerator { - return ichunkaccess1; - }; - -- return future == null ? CompletableFuture.supplyAsync(() -> function.apply(chunk), net.minecraft.Util.backgroundExecutor()) : future.thenApply(function); -+ return future == null ? CompletableFuture.supplyAsync(() -> function.apply(chunk), executor) : future.thenApply(function); // Paper - run with supplied executor - } - - @Override -diff --git a/src/main/java/org/bukkit/craftbukkit/util/DelegatedGeneratorAccess.java b/src/main/java/org/bukkit/craftbukkit/util/DelegatedGeneratorAccess.java -index cd7f1309cf01a5f01a28aded03a36fe15adb1756..41a291d42667c38d3e5bbe47236772761e85929b 100644 ---- a/src/main/java/org/bukkit/craftbukkit/util/DelegatedGeneratorAccess.java -+++ b/src/main/java/org/bukkit/craftbukkit/util/DelegatedGeneratorAccess.java -@@ -815,19 +815,39 @@ public abstract class DelegatedGeneratorAccess implements WorldGenLevel { - @Nullable - @Override - public BlockState getBlockStateIfLoaded(final BlockPos blockposition) { -- return null; -+ return this.handle.getBlockStateIfLoaded(blockposition); - } - - @Nullable - @Override - public FluidState getFluidIfLoaded(final BlockPos blockposition) { -- return null; -+ return this.handle.getFluidIfLoaded(blockposition); - } - - @Nullable - @Override - public ChunkAccess getChunkIfLoadedImmediately(final int x, final int z) { -- return null; -+ return this.handle.getChunkIfLoadedImmediately(x, z); -+ } -+ -+ @Override -+ public void getHardCollidingEntities(final Entity except, final AABB box, final Predicate predicate, final List into) { -+ this.handle.getHardCollidingEntities(except, box, predicate, into); -+ } -+ -+ @Override -+ public List getHardCollidingEntities(final Entity except, final AABB box, final Predicate predicate) { -+ return this.handle.getHardCollidingEntities(except, box, predicate); -+ } -+ -+ @Override -+ public void getEntities(final Entity except, final AABB box, final Predicate predicate, final List into) { -+ this.handle.getEntities(except, box, predicate, into); -+ } -+ -+ @Override -+ public void getEntitiesByClass(final Class clazz, final Entity except, final AABB box, final List into, final Predicate predicate) { -+ this.handle.getEntitiesByClass(clazz, except, box, into, predicate); - } - // Paper end - } -diff --git a/src/main/java/org/bukkit/craftbukkit/util/DummyGeneratorAccess.java b/src/main/java/org/bukkit/craftbukkit/util/DummyGeneratorAccess.java -index e8a73d34dbb372581b03018aade170a31c266099..210f454a840aa5564f7cbf33b83d31aa74814c84 100644 ---- a/src/main/java/org/bukkit/craftbukkit/util/DummyGeneratorAccess.java -+++ b/src/main/java/org/bukkit/craftbukkit/util/DummyGeneratorAccess.java -@@ -268,4 +268,19 @@ public class DummyGeneratorAccess implements WorldGenLevel { - @Override - public void scheduleTick(BlockPos pos, Fluid fluid, int delay, net.minecraft.world.ticks.TickPriority priority) {} - // Paper end - add more methods -+ // Paper start -+ @Override -+ public List getHardCollidingEntities(Entity except, AABB box, Predicate predicate) { -+ return java.util.Collections.emptyList(); -+ } -+ -+ @Override -+ public void getEntities(Entity except, AABB box, Predicate predicate, List into) {} -+ -+ @Override -+ public void getHardCollidingEntities(Entity except, AABB box, Predicate predicate, List into) {} -+ -+ @Override -+ public void getEntitiesByClass(Class clazz, Entity except, AABB box, List into, Predicate predicate) {} -+ // Paper end - } -diff --git a/src/main/java/org/spigotmc/AsyncCatcher.java b/src/main/java/org/spigotmc/AsyncCatcher.java -index e8e3cc48cf1c58bd8151d1f28df28781859cd0e3..2e074c16dab1ead47914070329da0398c3274048 100644 ---- a/src/main/java/org/spigotmc/AsyncCatcher.java -+++ b/src/main/java/org/spigotmc/AsyncCatcher.java -@@ -9,7 +9,7 @@ public class AsyncCatcher - - public static void catchOp(String reason) - { -- if ( (AsyncCatcher.enabled || io.papermc.paper.util.TickThread.STRICT_THREAD_CHECKS) && Thread.currentThread() != MinecraftServer.getServer().serverThread ) // Paper -+ if (!(io.papermc.paper.util.TickThread.isTickThread())) // Paper - { - MinecraftServer.LOGGER.error("Thread " + Thread.currentThread().getName() + " failed main thread check: " + reason, new Throwable()); // Paper - throw new IllegalStateException( "Asynchronous " + reason + "!" ); -diff --git a/src/main/java/org/spigotmc/WatchdogThread.java b/src/main/java/org/spigotmc/WatchdogThread.java -index ad282d34919716b75acd10426cd071da9d064a51..9e5d08f57aa448552d100ca892c211d44441ef68 100644 ---- a/src/main/java/org/spigotmc/WatchdogThread.java -+++ b/src/main/java/org/spigotmc/WatchdogThread.java -@@ -8,7 +8,7 @@ import java.util.logging.Logger; - import net.minecraft.server.MinecraftServer; - import org.bukkit.Bukkit; - --public class WatchdogThread extends Thread -+public final class WatchdogThread extends io.papermc.paper.util.TickThread // Paper - rewrite chunk system - { - - private static WatchdogThread instance; -@@ -115,6 +115,7 @@ public class WatchdogThread extends Thread - // Paper end - Different message for short timeout - log.log( Level.SEVERE, "------------------------------" ); - log.log( Level.SEVERE, "Server thread dump (Look for plugins here before reporting to Paper!):" ); // Paper -+ io.papermc.paper.chunk.system.scheduling.ChunkTaskScheduler.dumpAllChunkLoadInfo(isLongTimeout); // Paper - rewrite chunk system - WatchdogThread.dumpThread( ManagementFactory.getThreadMXBean().getThreadInfo( MinecraftServer.getServer().serverThread.getId(), Integer.MAX_VALUE ), log ); - log.log( Level.SEVERE, "------------------------------" ); - // diff --git a/patches/server/0001-Setup-Gradle-project.patch b/patches/server/0001-Setup-Gradle-project.patch index baefb46a2de7..4319f4cd05d1 100644 --- a/patches/server/0001-Setup-Gradle-project.patch +++ b/patches/server/0001-Setup-Gradle-project.patch @@ -28,7 +28,7 @@ index 37dab9e868dbfb019c271a547d975a48ad1cb571..3811c0d849a3eb028ed1a6b7a2d4747f +/.factorypath diff --git a/build.gradle.kts b/build.gradle.kts new file mode 100644 -index 0000000000000000000000000000000000000000..82b298d454dee6a6d996aa7822dd745e70a8da74 +index 0000000000000000000000000000000000000000..6ef457b8ea6ff9b89cb74ecbdca20731d9f94e97 --- /dev/null +++ b/build.gradle.kts @@ -0,0 +1,131 @@ @@ -47,8 +47,8 @@ index 0000000000000000000000000000000000000000..82b298d454dee6a6d996aa7822dd745e + } + implementation("org.ow2.asm:asm-commons:9.7.1") + implementation("commons-lang:commons-lang:2.6") -+ runtimeOnly("org.xerial:sqlite-jdbc:3.46.0.0") -+ runtimeOnly("com.mysql:mysql-connector-j:8.4.0") ++ runtimeOnly("org.xerial:sqlite-jdbc:3.46.1.3") ++ runtimeOnly("com.mysql:mysql-connector-j:9.1.0") + + runtimeOnly("org.apache.maven:maven-resolver-provider:3.9.6") + runtimeOnly("org.apache.maven.resolver:maven-resolver-connector-basic:1.9.18") @@ -62,7 +62,7 @@ index 0000000000000000000000000000000000000000..82b298d454dee6a6d996aa7822dd745e +} + +paperweight { -+ craftBukkitPackageVersion.set("v1_21_R1") // also needs to be updated in MappingEnvironment ++ craftBukkitPackageVersion.set("v1_21_R2") // also needs to be updated in MappingEnvironment +} + +tasks.jar { @@ -165,17 +165,17 @@ index 0000000000000000000000000000000000000000..82b298d454dee6a6d996aa7822dd745e +} diff --git a/pom.xml b/pom.xml deleted file mode 100644 -index 71a22e50d5e543d41b0d285fabfa0965a393ab2e..0000000000000000000000000000000000000000 +index c2973479d13a5d2898523cf5f246db39c2ea48e6..0000000000000000000000000000000000000000 --- a/pom.xml +++ /dev/null -@@ -1,612 +0,0 @@ +@@ -1,691 +0,0 @@ - - 4.0.0 - org.spigotmc - spigot - jar -- 1.21.1-R0.1-SNAPSHOT +- 1.21.3-R0.1-SNAPSHOT - Spigot - https://www.spigotmc.org/ - @@ -192,7 +192,7 @@ index 71a22e50d5e543d41b0d285fabfa0965a393ab2e..00000000000000000000000000000000 - UTF-8 - unknown - git -- 1_21_R1 +- 1_21_R2 - 21 - - @@ -235,7 +235,25 @@ index 71a22e50d5e543d41b0d285fabfa0965a393ab2e..00000000000000000000000000000000 - 9.7.1 - compile - -- +- +- +- com.fasterxml.jackson.core +- jackson-annotations +- 2.13.4 +- compile +- +- +- com.fasterxml.jackson.core +- jackson-core +- 2.13.4 +- compile +- +- +- com.fasterxml.jackson.core +- jackson-databind +- 2.13.4.2 +- compile +- - - com.github.oshi - oshi-core @@ -243,15 +261,27 @@ index 71a22e50d5e543d41b0d285fabfa0965a393ab2e..00000000000000000000000000000000 - compile - - +- com.github.stephenc.jcip +- jcip-annotations +- 1.0-1 +- compile +- +- +- com.microsoft.azure +- msal4j +- 1.15.0 +- compile +- +- - com.mojang - authlib -- 6.0.54 +- 6.0.55 - compile - - - com.mojang - brigadier -- 1.2.9 +- 1.3.10 - compile - - @@ -262,8 +292,38 @@ index 71a22e50d5e543d41b0d285fabfa0965a393ab2e..00000000000000000000000000000000 - - - com.mojang +- jtracy +- 1.0.29 +- compile +- +- +- com.mojang - logging -- 1.2.7 +- 1.4.9 +- compile +- +- +- com.nimbusds +- content-type +- 2.3 +- compile +- +- +- com.nimbusds +- lang-tag +- 1.7 +- compile +- +- +- com.nimbusds +- nimbus-jose-jwt +- 9.37.3 +- compile +- +- +- com.nimbusds +- oauth2-oidc-sdk +- 11.9.1 - compile - - @@ -353,6 +413,18 @@ index 71a22e50d5e543d41b0d285fabfa0965a393ab2e..00000000000000000000000000000000 - compile - - +- net.minidev +- accessors-smart +- 2.5.0 +- compile +- +- +- net.minidev +- json-smart +- 2.5.0 +- compile +- +- - net.sf.jopt-simple - jopt-simple - 5.0.4 @@ -366,20 +438,20 @@ index 71a22e50d5e543d41b0d285fabfa0965a393ab2e..00000000000000000000000000000000 - - - org.apache.logging.log4j -- log4j-core +- log4j-api - 2.22.1 - compile - - - org.apache.logging.log4j -- log4j-slf4j2-impl +- log4j-core - 2.22.1 - compile - - -- org.slf4j -- slf4j-api -- 2.0.9 +- org.apache.logging.log4j +- log4j-slf4j2-impl +- 2.22.1 - compile - - @@ -388,6 +460,13 @@ index 71a22e50d5e543d41b0d285fabfa0965a393ab2e..00000000000000000000000000000000 - 1.8.0 - compile - +- +- org.slf4j +- slf4j-api +- 2.0.9 +- compile +- +- - - - commons-lang @@ -411,13 +490,13 @@ index 71a22e50d5e543d41b0d285fabfa0965a393ab2e..00000000000000000000000000000000 - - org.xerial - sqlite-jdbc -- 3.46.0.0 +- 3.46.1.3 - runtime - - - com.mysql - mysql-connector-j -- 8.4.0 +- 9.1.0 - runtime - - diff --git a/patches/server/0002-Remap-fixes.patch b/patches/server/0002-Remap-fixes.patch index d128af05d76f..d7cff4215bd5 100644 --- a/patches/server/0002-Remap-fixes.patch +++ b/patches/server/0002-Remap-fixes.patch @@ -5,7 +5,7 @@ Subject: [PATCH] Remap fixes diff --git a/src/main/java/net/minecraft/core/BlockPos.java b/src/main/java/net/minecraft/core/BlockPos.java -index 766006fe7ff965d6ca17df1df457ecc629b3be18..0ef1a7b2a17e81144d594f29f7b5e54d5038dcf4 100644 +index e8b604659cec5a1b09e93dd7c5166d627aa2fe3d..115cb27d3233863692553f8e07238c74b0fcb61c 100644 --- a/src/main/java/net/minecraft/core/BlockPos.java +++ b/src/main/java/net/minecraft/core/BlockPos.java @@ -323,9 +323,11 @@ public class BlockPos extends Vec3i { @@ -59,7 +59,7 @@ index 7344cff32fa6fe3dedb74ed98126072c55b0abd2..d98b28e9488a5a7736719cf656736bb0 } else { entityliving1 = null; diff --git a/src/main/java/net/minecraft/world/level/storage/loot/LootTable.java b/src/main/java/net/minecraft/world/level/storage/loot/LootTable.java -index 9051f0186c09eeb8ecccf62b0116f6da1800a1df..b231f90317fe7df9133674b12d47873520b481cb 100644 +index f3a2612f0e27c36d5206334307eac1880ce8c4b7..4d4d413b8527e1a109276928611b8c857ad6f6aa 100644 --- a/src/main/java/net/minecraft/world/level/storage/loot/LootTable.java +++ b/src/main/java/net/minecraft/world/level/storage/loot/LootTable.java @@ -259,8 +259,8 @@ public class LootTable { @@ -70,9 +70,22 @@ index 9051f0186c09eeb8ecccf62b0116f6da1800a1df..b231f90317fe7df9133674b12d478735 - private final Builder functions = ImmutableList.builder(); + private final ImmutableList.Builder pools = ImmutableList.builder(); + private final ImmutableList.Builder functions = ImmutableList.builder(); - private LootContextParamSet paramSet; + private ContextKeySet paramSet; private Optional randomSequence; +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftBoat.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftBoat.java +index eaa46bf5954ed7c2be6d4b3772b5f2e971505c78..c101d01b55472efc9fc2829b8c17db5377ed57ff 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftBoat.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftBoat.java +@@ -133,7 +133,7 @@ public abstract class CraftBoat extends CraftVehicle implements Boat { + throw new EnumConstantNotPresentException(Type.class, boatType.toString()); + } + +- public static Status boatStatusFromNms(net.minecraft.world.entity.vehicle.Boat.EnumStatus enumStatus) { ++ public static Status boatStatusFromNms(net.minecraft.world.entity.vehicle.AbstractBoat.Status enumStatus) { // Paper - remap fixes + return switch (enumStatus) { + default -> throw new EnumConstantNotPresentException(Status.class, enumStatus.name()); + case IN_AIR -> Status.IN_AIR; diff --git a/src/test/java/org/bukkit/DyeColorsTest.java b/src/test/java/org/bukkit/DyeColorsTest.java index e96d821da0698dd42651500fb97a0856a9e9ce02..fb7d40181abdaa5b2ce607db47c09d0d0a19c86d 100644 --- a/src/test/java/org/bukkit/DyeColorsTest.java @@ -104,10 +117,10 @@ index e96d821da0698dd42651500fb97a0856a9e9ce02..fb7d40181abdaa5b2ce607db47c09d0d } } diff --git a/src/test/java/org/bukkit/ParticleTest.java b/src/test/java/org/bukkit/ParticleTest.java -index 688c5bae8146f6fc8cddb2632e9cbf304f0745fb..1b0df6220682328b8f1d03416cb8df2f6662a1cf 100644 +index ab72aeeec64c284ea42ac1542f55fd33d314effb..e2a03301073aced927244b07fe86f2de043e40a3 100644 --- a/src/test/java/org/bukkit/ParticleTest.java +++ b/src/test/java/org/bukkit/ParticleTest.java -@@ -251,7 +251,7 @@ public class ParticleTest { +@@ -279,7 +279,7 @@ public class ParticleTest { Check in CraftParticle if the conversion is still correct. """, bukkit.getKey())); @@ -172,7 +185,7 @@ index 4a3ac959f0dcc35f80371443383be1f8b42b6d95..e8520f541fda2d1cd9677f3fc7d7d295 assertNotNull(bukkit, "Bukkit gene null for " + gene); diff --git a/src/test/java/org/bukkit/registry/RegistryConstantsTest.java b/src/test/java/org/bukkit/registry/RegistryConstantsTest.java -index a382ca7c6e0f96e5d8be37fa8139360db13f9c2f..8fb906c77070eedec8cde263154eaf4610ad3197 100644 +index 9654a6b8d583c9c8842b14298e2f697761bdd705..dbd5b8684d4c86ff5a6f20f53fe30d0b30c384bf 100644 --- a/src/test/java/org/bukkit/registry/RegistryConstantsTest.java +++ b/src/test/java/org/bukkit/registry/RegistryConstantsTest.java @@ -31,17 +31,17 @@ public class RegistryConstantsTest { diff --git a/patches/server/0003-Build-system-changes.patch b/patches/server/0003-Build-system-changes.patch index 4e78b9dbf694..31918d8713cb 100644 --- a/patches/server/0003-Build-system-changes.patch +++ b/patches/server/0003-Build-system-changes.patch @@ -9,7 +9,7 @@ public net.minecraft.server.packs.VanillaPackResourcesBuilder safeGetPath(Ljava/ Co-authored-by: Jake Potrebic diff --git a/build.gradle.kts b/build.gradle.kts -index 82b298d454dee6a6d996aa7822dd745e70a8da74..ef88afa5fe914ae3fef9ffb1a971dabf568f75f3 100644 +index 6ef457b8ea6ff9b89cb74ecbdca20731d9f94e97..d4a5229b4df544ff60cdaee80c8ae301faf2a235 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -8,9 +8,7 @@ plugins { @@ -22,7 +22,7 @@ index 82b298d454dee6a6d996aa7822dd745e70a8da74..ef88afa5fe914ae3fef9ffb1a971dabf + implementation("org.apache.logging.log4j:log4j-iostreams:2.22.1") // Paper - remove exclusion implementation("org.ow2.asm:asm-commons:9.7.1") implementation("commons-lang:commons-lang:2.6") - runtimeOnly("org.xerial:sqlite-jdbc:3.46.0.0") + runtimeOnly("org.xerial:sqlite-jdbc:3.46.1.3") @@ -39,6 +37,7 @@ tasks.jar { val gitHash = git("rev-parse", "--short=7", "HEAD").getText().trim() val implementationVersion = System.getenv("BUILD_NUMBER") ?: "\"$gitHash\"" @@ -131,7 +131,7 @@ index feca36209fd2405fab70f564f63e627b8b78ac18..396ec10a76bdadbf5be2f0e15e88eed4 public static PackRepository createPackRepository(Path dataPacksPath, DirectoryValidator symlinkFinder) { diff --git a/src/main/java/org/bukkit/craftbukkit/Main.java b/src/main/java/org/bukkit/craftbukkit/Main.java -index dd64f5c16560d2ce6255c8319ddd0f5157237f0f..4566237fa1d49a559b2df806f9a318be46a6eade 100644 +index ec2854226de69ce87e1a9bb0b5483775ed192044..17e10c4373b4281cc74b748c4a1e173e36eb9196 100644 --- a/src/main/java/org/bukkit/craftbukkit/Main.java +++ b/src/main/java/org/bukkit/craftbukkit/Main.java @@ -199,7 +199,7 @@ public class Main { @@ -142,7 +142,7 @@ index dd64f5c16560d2ce6255c8319ddd0f5157237f0f..4566237fa1d49a559b2df806f9a318be + Date buildDate = new java.text.SimpleDateFormat("yyyy-MM-dd HH:mm:ss Z").parse(Main.class.getPackage().getImplementationVendor()); // Paper Calendar deadline = Calendar.getInstance(); - deadline.add(Calendar.DAY_OF_YEAR, -28); + deadline.add(Calendar.DAY_OF_YEAR, -3); diff --git a/src/main/java/org/bukkit/craftbukkit/util/Versioning.java b/src/main/java/org/bukkit/craftbukkit/util/Versioning.java index 93046379d0cefd5d3236fc59e698809acdc18f80..774556a62eb240da42e84db4502e2ed43495be17 100644 --- a/src/main/java/org/bukkit/craftbukkit/util/Versioning.java @@ -172,10 +172,10 @@ index 0000000000000000000000000000000000000000..288fbe68c6053f40e72f0feedef0ae0f + } +} diff --git a/src/test/java/org/bukkit/support/RegistryHelper.java b/src/test/java/org/bukkit/support/RegistryHelper.java -index e5dbcdcbd4187c724c3c6a3b8141a3796710c7a8..73645ea2e5c17e973ed3b888aab56f733f9c87a3 100644 +index f9ed3fd96cb7474785610fe0f87e550456349287..5781c2fab2d407b4a22d5fc80e1c03e907617316 100644 --- a/src/test/java/org/bukkit/support/RegistryHelper.java +++ b/src/test/java/org/bukkit/support/RegistryHelper.java -@@ -58,6 +58,7 @@ public final class RegistryHelper { +@@ -70,6 +70,7 @@ public final class RegistryHelper { } public static void setup(FeatureFlagSet featureFlagSet) { diff --git a/patches/server/0004-Test-changes.patch b/patches/server/0004-Test-changes.patch index 85407ece86ce..2bf3cdb56617 100644 --- a/patches/server/0004-Test-changes.patch +++ b/patches/server/0004-Test-changes.patch @@ -6,7 +6,7 @@ Subject: [PATCH] Test changes Co-authored-by: yannnicklamprecht diff --git a/build.gradle.kts b/build.gradle.kts -index ef88afa5fe914ae3fef9ffb1a971dabf568f75f3..3c1770864ab3e8708bab6cd50d6ffb5e19af5399 100644 +index d4a5229b4df544ff60cdaee80c8ae301faf2a235..41b000aaa71dca3fb392ae657be16e05bd37a178 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -23,6 +23,7 @@ dependencies { @@ -32,7 +32,7 @@ index ef88afa5fe914ae3fef9ffb1a971dabf568f75f3..3c1770864ab3e8708bab6cd50d6ffb5e } diff --git a/src/test/java/io/papermc/paper/registry/RegistryKeyTest.java b/src/test/java/io/papermc/paper/registry/RegistryKeyTest.java new file mode 100644 -index 0000000000000000000000000000000000000000..fe52d229c31526cc32f6422328efe92edf75a7ff +index 0000000000000000000000000000000000000000..d8857a05858585113bc7efde3416748effb53d01 --- /dev/null +++ b/src/test/java/io/papermc/paper/registry/RegistryKeyTest.java @@ -0,0 +1,34 @@ @@ -65,7 +65,7 @@ index 0000000000000000000000000000000000000000..fe52d229c31526cc32f6422328efe92e + @ParameterizedTest + @MethodSource("data") + void testApiRegistryKeysExist(final RegistryKey key) { -+ final Optional> registry = RegistryHelper.getRegistry().registry(ResourceKey.createRegistryKey(ResourceLocation.parse(key.key().asString()))); ++ final Optional> registry = RegistryHelper.getRegistry().lookup(ResourceKey.createRegistryKey(ResourceLocation.parse(key.key().asString()))); + assertTrue(registry.isPresent(), "Missing vanilla registry for " + key.key().asString()); + + } @@ -352,7 +352,7 @@ index ee5ab15bb0bfeb0ff6fa0d720eeff086d92cb459..7419ea2f68607aad27929a608c402bd4 @Test diff --git a/src/test/java/org/bukkit/registry/RegistryClassTest.java b/src/test/java/org/bukkit/registry/RegistryClassTest.java -index 740073ddd99b5e6aba056b99d5ed3e2a94a13b6c..575a06125e0b60b5bb8b6f85131f7d6cf86f5083 100644 +index 297700b237bbd8d5ff9116919f203c82b4037968..ea3d37f387bdb0dd5ae3fba9231ace31d0cebd64 100644 --- a/src/test/java/org/bukkit/registry/RegistryClassTest.java +++ b/src/test/java/org/bukkit/registry/RegistryClassTest.java @@ -57,6 +57,7 @@ import org.objectweb.asm.Type; @@ -364,7 +364,7 @@ index 740073ddd99b5e6aba056b99d5ed3e2a94a13b6c..575a06125e0b60b5bb8b6f85131f7d6c private static final Map, Data> INIT_DATA = new HashMap<>(); diff --git a/src/test/java/org/bukkit/registry/RegistryConstantsTest.java b/src/test/java/org/bukkit/registry/RegistryConstantsTest.java -index 8fb906c77070eedec8cde263154eaf4610ad3197..8705ee2a28d5dba7bb579eae1436451502e1e3c2 100644 +index dbd5b8684d4c86ff5a6f20f53fe30d0b30c384bf..7848c3bb78356f74e7e2cb708308626baa708b1f 100644 --- a/src/test/java/org/bukkit/registry/RegistryConstantsTest.java +++ b/src/test/java/org/bukkit/registry/RegistryConstantsTest.java @@ -26,7 +26,7 @@ public class RegistryConstantsTest { @@ -377,7 +377,7 @@ index 8fb906c77070eedec8cde263154eaf4610ad3197..8705ee2a28d5dba7bb579eae14364515 @Test diff --git a/src/test/java/org/bukkit/support/DummyServerHelper.java b/src/test/java/org/bukkit/support/DummyServerHelper.java -index 6ecafd167d4d8c79f2cbc43ea9da98a5d61d8751..f81f86cf05a8c6816d356185af6d0d84a0474dd2 100644 +index a678510df2b999b78d0b4d233fd15bf444c97080..0a6ba289a94468b67d282a199250142e1e86f075 100644 --- a/src/test/java/org/bukkit/support/DummyServerHelper.java +++ b/src/test/java/org/bukkit/support/DummyServerHelper.java @@ -47,7 +47,7 @@ public final class DummyServerHelper { @@ -395,7 +395,7 @@ index 6ecafd167d4d8c79f2cbc43ea9da98a5d61d8751..f81f86cf05a8c6816d356185af6d0d84 Preconditions.checkArgument(clazz == org.bukkit.Fluid.class, "Fluid namespace must have fluid type"); - TagKey fluidTagKey = TagKey.create(Registries.FLUID, key); + TagKey fluidTagKey = TagKey.create(Registries.FLUID, key); // Paper - address remapping issues - if (BuiltInRegistries.FLUID.getTag(fluidTagKey).isPresent()) { + if (BuiltInRegistries.FLUID.get(fluidTagKey).isPresent()) { return new CraftFluidTag(BuiltInRegistries.FLUID, fluidTagKey); } } @@ -403,7 +403,7 @@ index 6ecafd167d4d8c79f2cbc43ea9da98a5d61d8751..f81f86cf05a8c6816d356185af6d0d84 Preconditions.checkArgument(clazz == org.bukkit.entity.EntityType.class, "Entity type namespace must have entity type"); - TagKey> entityTagKey = TagKey.create(Registries.ENTITY_TYPE, key); + TagKey> entityTagKey = TagKey.create(Registries.ENTITY_TYPE, key); // Paper - address remapping issues - if (BuiltInRegistries.ENTITY_TYPE.getTag(entityTagKey).isPresent()) { + if (BuiltInRegistries.ENTITY_TYPE.get(entityTagKey).isPresent()) { return new CraftEntityTag(BuiltInRegistries.ENTITY_TYPE, entityTagKey); } } @@ -425,18 +425,30 @@ index 6ecafd167d4d8c79f2cbc43ea9da98a5d61d8751..f81f86cf05a8c6816d356185af6d0d84 } } diff --git a/src/test/java/org/bukkit/support/RegistryHelper.java b/src/test/java/org/bukkit/support/RegistryHelper.java -index 73645ea2e5c17e973ed3b888aab56f733f9c87a3..4d169ff3541044d447de519d2b0973da98b0bd53 100644 +index 5781c2fab2d407b4a22d5fc80e1c03e907617316..68ae998b30bbcacae3604fb6581ceca3da1eda18 100644 --- a/src/test/java/org/bukkit/support/RegistryHelper.java +++ b/src/test/java/org/bukkit/support/RegistryHelper.java -@@ -98,6 +98,11 @@ public final class RegistryHelper { - private static LayeredRegistryAccess createLayers(MultiPackResourceManager resourceManager) { - // add tags and loot tables for unit tests - LayeredRegistryAccess layers = RegistryLayer.createRegistryAccess(); +@@ -65,6 +65,11 @@ public final class RegistryHelper { + List> list1 = TagLoader.buildUpdatedLookups(iregistrycustom_dimension, list); + RegistryAccess.Frozen iregistrycustom_dimension1 = RegistryDataLoader.load((ResourceManager) ireloadableresourcemanager, list1, RegistryDataLoader.WORLDGEN_REGISTRIES); + LayeredRegistryAccess layers = layeredregistryaccess.replaceFrom(RegistryLayer.WORLDGEN, iregistrycustom_dimension1); + // Paper start - load registry here to ensure bukkit object registry are correctly delayed if needed + try { + Class.forName("org.bukkit.Registry"); + } catch (final ClassNotFoundException ignored) {} + // Paper end - load registry here to ensure bukkit object registry are correctly delayed if needed - layers = WorldLoader.loadAndReplaceLayer(resourceManager, layers, RegistryLayer.WORLDGEN, RegistryDataLoader.WORLDGEN_REGISTRIES); - return layers; + return layers.compositeAccess().freeze(); + } +@@ -82,6 +87,11 @@ public final class RegistryHelper { + List> list1 = TagLoader.buildUpdatedLookups(iregistrycustom_dimension, list); + RegistryAccess.Frozen iregistrycustom_dimension1 = RegistryDataLoader.load((ResourceManager) ireloadableresourcemanager, list1, RegistryDataLoader.WORLDGEN_REGISTRIES); + LayeredRegistryAccess layers = layeredregistryaccess.replaceFrom(RegistryLayer.WORLDGEN, iregistrycustom_dimension1); ++ // Paper start - load registry here to ensure bukkit object registry are correctly delayed if needed ++ try { ++ Class.forName("org.bukkit.Registry"); ++ } catch (final ClassNotFoundException ignored) {} ++ // Paper end - load registry here to ensure bukkit object registry are correctly delayed if needed + RegistryHelper.registry = layers.compositeAccess().freeze(); + // Register vanilla pack + RegistryHelper.dataPack = ReloadableServerResources.loadResources(ireloadableresourcemanager, layers, list, featureFlagSet, Commands.CommandSelection.DEDICATED, 0, MoreExecutors.directExecutor(), MoreExecutors.directExecutor()).join(); diff --git a/patches/server/0005-Paper-config-files.patch b/patches/server/0005-Paper-config-files.patch index ecc645f20648..a1e58cd06d02 100644 --- a/patches/server/0005-Paper-config-files.patch +++ b/patches/server/0005-Paper-config-files.patch @@ -15,7 +15,7 @@ public net.minecraft.server.dedicated.DedicatedServerProperties reload(Lnet/mine public net.minecraft.world.level.NaturalSpawner SPAWNING_CATEGORIES diff --git a/build.gradle.kts b/build.gradle.kts -index 2233e51bfab4ac36977984e8fe7fddb04120dddd..7f7b7f0b29107375b2438e3af0bc0f40390db4fa 100644 +index 41b000aaa71dca3fb392ae657be16e05bd37a178..da6b4787fa787e098e4031790e955ce616593ee9 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -10,6 +10,7 @@ dependencies { @@ -24,8 +24,8 @@ index 2233e51bfab4ac36977984e8fe7fddb04120dddd..7f7b7f0b29107375b2438e3af0bc0f40 implementation("org.ow2.asm:asm-commons:9.7.1") + implementation("org.spongepowered:configurate-yaml:4.2.0-SNAPSHOT") // Paper - config files implementation("commons-lang:commons-lang:2.6") - runtimeOnly("org.xerial:sqlite-jdbc:3.46.0.0") - runtimeOnly("com.mysql:mysql-connector-j:8.4.0") + runtimeOnly("org.xerial:sqlite-jdbc:3.46.1.3") + runtimeOnly("com.mysql:mysql-connector-j:9.1.0") diff --git a/src/main/java/com/destroystokyo/paper/PaperConfig.java b/src/main/java/com/destroystokyo/paper/PaperConfig.java new file mode 100644 index 0000000000000000000000000000000000000000..ef41cf3a7d1e6f2bfe81e0fb865d2f969bbc77c1 @@ -487,13 +487,12 @@ index 0000000000000000000000000000000000000000..d9502ba028a96f9cc846f9ed428bd806 +} diff --git a/src/main/java/io/papermc/paper/configuration/GlobalConfiguration.java b/src/main/java/io/papermc/paper/configuration/GlobalConfiguration.java new file mode 100644 -index 0000000000000000000000000000000000000000..b92bfd89e32becde2e7630c6116c16f8a4f6614a +index 0000000000000000000000000000000000000000..f0d470d7770e119f734b9e72021c806d0ea8ecbd --- /dev/null +++ b/src/main/java/io/papermc/paper/configuration/GlobalConfiguration.java -@@ -0,0 +1,331 @@ +@@ -0,0 +1,330 @@ +package io.papermc.paper.configuration; + -+import co.aikar.timings.MinecraftTimings; +import com.mojang.logging.LogUtils; +import io.papermc.paper.configuration.constraint.Constraints; +import io.papermc.paper.configuration.type.number.DoubleOr; @@ -510,7 +509,6 @@ index 0000000000000000000000000000000000000000..b92bfd89e32becde2e7630c6116c16f8 +import org.spongepowered.configurate.objectmapping.meta.Required; +import org.spongepowered.configurate.objectmapping.meta.Setting; + -+import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.OptionalInt; @@ -523,6 +521,45 @@ index 0000000000000000000000000000000000000000..b92bfd89e32becde2e7630c6116c16f8 + public static GlobalConfiguration get() { + return instance; + } ++ ++ public ChunkLoadingBasic chunkLoadingBasic; ++ ++ public class ChunkLoadingBasic extends ConfigurationPart { ++ @Comment("The maximum rate in chunks per second that the server will send to any individual player. Set to -1 to disable this limit.") ++ public double playerMaxChunkSendRate = 75.0; ++ ++ @Comment( ++ "The maximum rate at which chunks will load for any individual player. " + ++ "Note that this setting also affects chunk generations, since a chunk load is always first issued to test if a" + ++ "chunk is already generated. Set to -1 to disable this limit." ++ ) ++ public double playerMaxChunkLoadRate = 100.0; ++ ++ @Comment("The maximum rate at which chunks will generate for any individual player. Set to -1 to disable this limit.") ++ public double playerMaxChunkGenerateRate = -1.0; ++ } ++ ++ public ChunkLoadingAdvanced chunkLoadingAdvanced; ++ ++ public class ChunkLoadingAdvanced extends ConfigurationPart { ++ @Comment( ++ "Set to true if the server will match the chunk send radius that clients have configured" + ++ "in their view distance settings if the client is less-than the server's send distance." ++ ) ++ public boolean autoConfigSendDistance = true; ++ ++ @Comment( ++ "Specifies the maximum amount of concurrent chunk loads that an individual player can have." + ++ "Set to 0 to let the server configure it automatically per player, or set it to -1 to disable the limit." ++ ) ++ public int playerMaxConcurrentChunkLoads = 0; ++ ++ @Comment( ++ "Specifies the maximum amount of concurrent chunk generations that an individual player can have." + ++ "Set to 0 to let the server configure it automatically per player, or set it to -1 to disable the limit." ++ ) ++ public int playerMaxConcurrentChunkGenerates = 0; ++ } + static void set(GlobalConfiguration instance) { + GlobalConfiguration.instance = instance; + } @@ -553,29 +590,6 @@ index 0000000000000000000000000000000000000000..b92bfd89e32becde2e7630c6116c16f8 + public boolean enableImmediately = false; + } + -+ @Deprecated(forRemoval = true) -+ public Timings timings; -+ -+ @Deprecated(forRemoval = true) -+ public class Timings extends ConfigurationPart { -+ public boolean enabled = false; -+ public boolean verbose = true; -+ public String url = "https://timings.aikar.co/"; -+ public boolean serverNamePrivacy = false; -+ public List hiddenConfigEntries = List.of( -+ "database", -+ "proxies.velocity.secret" -+ ); -+ public int historyInterval = 300; -+ public int historyLength = 3600; -+ public String serverName = "Unknown Server"; -+ -+ @PostProcess -+ private void postProcess() { -+ MinecraftTimings.processConfig(this); -+ } -+ } -+ + public Proxies proxies; + + public class Proxies extends ConfigurationPart { @@ -638,21 +652,6 @@ index 0000000000000000000000000000000000000000..b92bfd89e32becde2e7630c6116c16f8 + public int incomingPacketThreshold = 300; + } + -+ public ChunkLoading chunkLoading; -+ -+ public class ChunkLoading extends ConfigurationPart { -+ public int minLoadRadius = 2; -+ public int maxConcurrentSends = 2; -+ public boolean autoconfigSendDistance = true; -+ public double targetPlayerChunkSendRate = 100.0; -+ public double globalMaxChunkSendRate = -1.0; -+ public boolean enableFrustumPriority = false; -+ public double globalMaxChunkLoadRate = -1.0; -+ public double playerMaxConcurrentLoads = 20.0; -+ public double globalMaxConcurrentLoads = 500.0; -+ public double playerMaxChunkLoadRate = -1.0; -+ } -+ + public UnsupportedSettings unsupportedSettings; + + public class UnsupportedSettings extends ConfigurationPart { @@ -711,7 +710,7 @@ index 0000000000000000000000000000000000000000..b92bfd89e32becde2e7630c6116c16f8 + + @PostProcess + private void postProcess() { -+ //io.papermc.paper.chunk.system.scheduling.ChunkTaskScheduler.init(this); ++ + } + } + @@ -1335,10 +1334,10 @@ index 0000000000000000000000000000000000000000..1029b6de6f36b08bf634b4056ef57013 +} diff --git a/src/main/java/io/papermc/paper/configuration/RemovedConfigurations.java b/src/main/java/io/papermc/paper/configuration/RemovedConfigurations.java new file mode 100644 -index 0000000000000000000000000000000000000000..990d1bb46e0f9719f4e9af928d80ac6f8dff23b5 +index 0000000000000000000000000000000000000000..279b24c689b9979884b65df7eb1f059024f0feac --- /dev/null +++ b/src/main/java/io/papermc/paper/configuration/RemovedConfigurations.java -@@ -0,0 +1,82 @@ +@@ -0,0 +1,83 @@ +package io.papermc.paper.configuration; + +import org.spongepowered.configurate.NodePath; @@ -1393,7 +1392,8 @@ index 0000000000000000000000000000000000000000..990d1bb46e0f9719f4e9af928d80ac6f + path("entities", "spawning", "despawn-ranges", "soft"), + path("entities", "spawning", "despawn-ranges", "hard"), + path("fixes", "fix-curing-zombie-villager-discount-exploit"), -+ path("entities", "mob-effects", "undead-immune-to-certain-effects") ++ path("entities", "mob-effects", "undead-immune-to-certain-effects"), ++ path("entities", "entities-target-with-follow-range") + }; + // spawn.keep-spawn-loaded and spawn.keep-spawn-loaded-range are no longer used, but kept + // in the world default config for compatibility with old worlds being migrated to use the gamerule @@ -1423,7 +1423,7 @@ index 0000000000000000000000000000000000000000..990d1bb46e0f9719f4e9af928d80ac6f +} diff --git a/src/main/java/io/papermc/paper/configuration/WorldConfiguration.java b/src/main/java/io/papermc/paper/configuration/WorldConfiguration.java new file mode 100644 -index 0000000000000000000000000000000000000000..fd3b1c10695634f65c7291016bf671c084bc4d57 +index 0000000000000000000000000000000000000000..dad3fcc689ec806f985122a7cbd501a7d0fd0d36 --- /dev/null +++ b/src/main/java/io/papermc/paper/configuration/WorldConfiguration.java @@ -0,0 +1,581 @@ @@ -1568,7 +1568,6 @@ index 0000000000000000000000000000000000000000..fd3b1c10695634f65c7291016bf671c0 + public Entities entities; + + public class Entities extends ConfigurationPart { -+ public boolean entitiesTargetWithFollowRange = false; + public MobEffects mobEffects; + + public class MobEffects extends ConfigurationPart { @@ -1890,7 +1889,7 @@ index 0000000000000000000000000000000000000000..fd3b1c10695634f65c7291016bf671c0 + + public class Fixes extends ConfigurationPart { + public boolean fixItemsMergingThroughWalls = false; -+ public boolean disableUnloadedChunkEnderpearlExploit = true; ++ public boolean disableUnloadedChunkEnderpearlExploit = false; + public boolean preventTntFromMovingInWater = false; + public boolean splitOverstackedLoot = true; + public IntOr.Disabled fallingBlockHeightNerf = IntOr.Disabled.DISABLED; @@ -1998,6 +1997,7 @@ index 0000000000000000000000000000000000000000..fd3b1c10695634f65c7291016bf671c0 + public boolean disableSprintInterruptionOnAttack = false; + public int shieldBlockingDelay = 5; + public boolean disableRelativeProjectileVelocity = false; ++ public boolean legacyEnderPearlBehavior = false; + + public enum RedstoneImplementation { + VANILLA, EIGENCRAFT, ALTERNATE_CURRENT @@ -3140,7 +3140,7 @@ index 0000000000000000000000000000000000000000..36ca88b677e1b55b41c52750948d5b6d +} diff --git a/src/main/java/io/papermc/paper/configuration/serializer/registry/RegistryEntrySerializer.java b/src/main/java/io/papermc/paper/configuration/serializer/registry/RegistryEntrySerializer.java new file mode 100644 -index 0000000000000000000000000000000000000000..9073c619f14feb7a14bf32a504eb935f6d4cfe2e +index 0000000000000000000000000000000000000000..cb0de1a639578320fd38177a915bfa5d1e9a73bd --- /dev/null +++ b/src/main/java/io/papermc/paper/configuration/serializer/registry/RegistryEntrySerializer.java @@ -0,0 +1,64 @@ @@ -3178,7 +3178,7 @@ index 0000000000000000000000000000000000000000..9073c619f14feb7a14bf32a504eb935f + } + + protected final Registry registry() { -+ return this.registryAccess.registryOrThrow(this.registryKey); ++ return this.registryAccess.lookupOrThrow(this.registryKey); + } + + protected abstract T convertFromResourceKey(ResourceKey key) throws SerializationException; @@ -3210,7 +3210,7 @@ index 0000000000000000000000000000000000000000..9073c619f14feb7a14bf32a504eb935f +} diff --git a/src/main/java/io/papermc/paper/configuration/serializer/registry/RegistryHolderSerializer.java b/src/main/java/io/papermc/paper/configuration/serializer/registry/RegistryHolderSerializer.java new file mode 100644 -index 0000000000000000000000000000000000000000..eeae35ede747e473ddba4ca1688f2f6cbc35ce7d +index 0000000000000000000000000000000000000000..76f6219eac049afef7ce03cd30d7c3232b5b9b7c --- /dev/null +++ b/src/main/java/io/papermc/paper/configuration/serializer/registry/RegistryHolderSerializer.java @@ -0,0 +1,34 @@ @@ -3240,7 +3240,7 @@ index 0000000000000000000000000000000000000000..eeae35ede747e473ddba4ca1688f2f6c + + @Override + protected Holder convertFromResourceKey(ResourceKey key) throws SerializationException { -+ return this.registry().getHolder(key).orElseThrow(() -> new SerializationException("Missing holder in " + this.registry().key() + " with key " + key)); ++ return this.registry().get(key).orElseThrow(() -> new SerializationException("Missing holder in " + this.registry().key() + " with key " + key)); + } + + @Override @@ -3250,7 +3250,7 @@ index 0000000000000000000000000000000000000000..eeae35ede747e473ddba4ca1688f2f6c +} diff --git a/src/main/java/io/papermc/paper/configuration/serializer/registry/RegistryValueSerializer.java b/src/main/java/io/papermc/paper/configuration/serializer/registry/RegistryValueSerializer.java new file mode 100644 -index 0000000000000000000000000000000000000000..718377ce91a010a48b2b4a5e59e02ee8a42107a7 +index 0000000000000000000000000000000000000000..6831b7b72c5e1f79eff36019ca2ff56531c26df8 --- /dev/null +++ b/src/main/java/io/papermc/paper/configuration/serializer/registry/RegistryValueSerializer.java @@ -0,0 +1,35 @@ @@ -3277,7 +3277,7 @@ index 0000000000000000000000000000000000000000..718377ce91a010a48b2b4a5e59e02ee8 + + @Override + protected T convertFromResourceKey(ResourceKey key) throws SerializationException { -+ final T value = this.registry().get(key); ++ final T value = this.registry().getValue(key); + if (value == null) { + throw new SerializationException("Missing value in " + this.registry() + " with key " + key.location()); + } @@ -3617,7 +3617,7 @@ index 0000000000000000000000000000000000000000..66073f7a6a96405348cc4044ad1e6922 +} diff --git a/src/main/java/io/papermc/paper/configuration/transformation/world/FeatureSeedsGeneration.java b/src/main/java/io/papermc/paper/configuration/transformation/world/FeatureSeedsGeneration.java new file mode 100644 -index 0000000000000000000000000000000000000000..6cdc40cb4a5f94654c874f9dbdb106fa0e4d41f3 +index 0000000000000000000000000000000000000000..cb1f5f65c098470dc8553b015d0f0f29f28ed956 --- /dev/null +++ b/src/main/java/io/papermc/paper/configuration/transformation/world/FeatureSeedsGeneration.java @@ -0,0 +1,71 @@ @@ -3668,7 +3668,7 @@ index 0000000000000000000000000000000000000000..6cdc40cb4a5f94654c874f9dbdb106fa + final Reference2LongMap>> features = Objects.requireNonNullElseGet(featureNode.get(new TypeToken>>>() {}), Reference2LongOpenHashMap::new); + final Random random = new SecureRandom(); + AtomicInteger counter = new AtomicInteger(0); -+ MinecraftServer.getServer().registryAccess().registryOrThrow(Registries.CONFIGURED_FEATURE).holders().forEach(holder -> { ++ MinecraftServer.getServer().registryAccess().lookupOrThrow(Registries.CONFIGURED_FEATURE).listElements().forEach(holder -> { + if (features.containsKey(holder)) { + return; + } @@ -3694,7 +3694,7 @@ index 0000000000000000000000000000000000000000..6cdc40cb4a5f94654c874f9dbdb106fa +} diff --git a/src/main/java/io/papermc/paper/configuration/transformation/world/LegacyPaperWorldConfig.java b/src/main/java/io/papermc/paper/configuration/transformation/world/LegacyPaperWorldConfig.java new file mode 100644 -index 0000000000000000000000000000000000000000..3332531d902899a156179ef2e9ec85b2f42c1fd1 +index 0000000000000000000000000000000000000000..77e530830dc8ebc861b2e70f787f9b71524a54d2 --- /dev/null +++ b/src/main/java/io/papermc/paper/configuration/transformation/world/LegacyPaperWorldConfig.java @@ -0,0 +1,322 @@ @@ -3805,7 +3805,7 @@ index 0000000000000000000000000000000000000000..3332531d902899a156179ef2e9ec85b2 + ) + .addVersion(26, ConfigurationTransformation.builder().addAction(path("alt-item-despawn-rate", "items", ConfigurationTransformation.WILDCARD_OBJECT), (path, value) -> { + String itemName = path.get(path.size() - 1).toString(); -+ final Optional> item = BuiltInRegistries.ITEM.getHolder(ResourceKey.create(Registries.ITEM, ResourceLocation.parse(itemName.toLowerCase(Locale.ROOT)))); ++ final Optional> item = BuiltInRegistries.ITEM.get(ResourceKey.create(Registries.ITEM, ResourceLocation.parse(itemName.toLowerCase(Locale.ROOT)))); + if (item.isEmpty()) { + itemName = Material.valueOf(itemName).getKey().getKey(); + } @@ -3837,7 +3837,7 @@ index 0000000000000000000000000000000000000000..3332531d902899a156179ef2e9ec85b2 + Map rebuild = new HashMap<>(); + value.childrenMap().forEach((key, node) -> { + String itemName = key.toString(); -+ final Optional> itemHolder = BuiltInRegistries.ITEM.getHolder(ResourceKey.create(Registries.ITEM, ResourceLocation.parse(itemName.toLowerCase(Locale.ROOT)))); ++ final Optional> itemHolder = BuiltInRegistries.ITEM.get(ResourceKey.create(Registries.ITEM, ResourceLocation.parse(itemName.toLowerCase(Locale.ROOT)))); + final @Nullable String item; + if (itemHolder.isEmpty()) { + final @Nullable Material bukkitMat = Material.matchMaterial(itemName); @@ -5074,7 +5074,7 @@ index 0000000000000000000000000000000000000000..614aba60bb07946a144650fd3aedb316 + protected abstract boolean belowZero(O value); +} diff --git a/src/main/java/net/minecraft/server/Main.java b/src/main/java/net/minecraft/server/Main.java -index b334265d4015fec13d7fedbffba2b6c22f4c8bc8..5b4ac7b4fd0077e900e9f788963f1613bbc9a5d0 100644 +index fc5c42e77a76b0ca946b13435144584b9c4bfafa..9bd6056bba6ba48bada7e9cd5883b0a171b0bbc4 100644 --- a/src/main/java/net/minecraft/server/Main.java +++ b/src/main/java/net/minecraft/server/Main.java @@ -129,6 +129,10 @@ public class Main { @@ -5098,10 +5098,10 @@ index b334265d4015fec13d7fedbffba2b6c22f4c8bc8..5b4ac7b4fd0077e900e9f788963f1613 String s = (String) Optional.ofNullable((String) optionset.valueOf("world")).orElse(dedicatedserversettings.getProperties().levelName); LevelStorageSource convertable = LevelStorageSource.createDefault(file.toPath()); diff --git a/src/main/java/net/minecraft/server/MinecraftServer.java b/src/main/java/net/minecraft/server/MinecraftServer.java -index db2031ffbc802bd850ab40acaec2a9624b753f4c..299afe72a2fc2affca196f2fdfbf27e5533c51a6 100644 +index c54fae06acd566742cea3aef537ad1b4e2b7414a..3fc0abef2c4e2c8ceb3b8c4f02c59700aa3d0803 100644 --- a/src/main/java/net/minecraft/server/MinecraftServer.java +++ b/src/main/java/net/minecraft/server/MinecraftServer.java -@@ -309,6 +309,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop S spin(Function serverFactory) { AtomicReference atomicreference = new AtomicReference(); -@@ -405,6 +406,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop minecraftserver.paperConfigurations.createWorldConfig(io.papermc.paper.configuration.PaperConfigurations.createWorldContextMap(convertable_conversionsession.levelDirectory.path(), iworlddataserver.getLevelName(), resourcekey.location(), spigotConfig, minecraftserver.registryAccess(), iworlddataserver.getGameRules()))); // Paper - create paper world configs + // Add env and gen to constructor, IWorldDataServer -> WorldDataServer + public ServerLevel(MinecraftServer minecraftserver, Executor executor, LevelStorageSource.LevelStorageAccess convertable_conversionsession, PrimaryLevelData iworlddataserver, ResourceKey resourcekey, LevelStem worlddimension, ChunkProgressListener worldloadlistener, boolean flag, long i, List list, boolean flag1, @Nullable RandomSequences randomsequences, org.bukkit.World.Environment env, org.bukkit.generator.ChunkGenerator gen, org.bukkit.generator.BiomeProvider biomeProvider) { +- super(iworlddataserver, resourcekey, minecraftserver.registryAccess(), worlddimension.type(), false, flag, i, minecraftserver.getMaxChainedNeighborUpdates(), gen, biomeProvider, env); ++ super(iworlddataserver, resourcekey, minecraftserver.registryAccess(), worlddimension.type(), false, flag, i, minecraftserver.getMaxChainedNeighborUpdates(), gen, biomeProvider, env, spigotConfig -> minecraftserver.paperConfigurations.createWorldConfig(io.papermc.paper.configuration.PaperConfigurations.createWorldContextMap(convertable_conversionsession.levelDirectory.path(), iworlddataserver.getLevelName(), resourcekey.location(), spigotConfig, minecraftserver.registryAccess(), iworlddataserver.getGameRules()))); // Paper - create paper world configs this.pvpMode = minecraftserver.isPvpAllowed(); this.convertable = convertable_conversionsession; this.uuid = WorldUUID.getUUID(convertable_conversionsession.levelDirectory.path().toFile()); diff --git a/src/main/java/net/minecraft/world/level/Level.java b/src/main/java/net/minecraft/world/level/Level.java -index e433037a03ffafabb952887ae3980e1d51411d4c..c061813d275fbc48d7629cc59d90dbb4c347516c 100644 +index 9cf0c141fefe67893828e300cba4f8a8545ba25f..c8e49c1904c80c4ede40ca5c26efad9b12e247d1 100644 --- a/src/main/java/net/minecraft/world/level/Level.java +++ b/src/main/java/net/minecraft/world/level/Level.java -@@ -156,6 +156,12 @@ public abstract class Level implements LevelAccessor, AutoCloseable { +@@ -157,6 +157,12 @@ public abstract class Level implements LevelAccessor, AutoCloseable { public final it.unimi.dsi.fastutil.objects.Object2LongOpenHashMap ticksPerSpawnCategory = new it.unimi.dsi.fastutil.objects.Object2LongOpenHashMap<>(); public boolean populating; public final org.spigotmc.SpigotWorldConfig spigotConfig; // Spigot @@ -5214,31 +5214,31 @@ index e433037a03ffafabb952887ae3980e1d51411d4c..c061813d275fbc48d7629cc59d90dbb4 public final SpigotTimings.WorldTimingsHandler timings; // Spigot public static BlockPos lastPhysicsProblem; // Spigot -@@ -173,8 +179,9 @@ public abstract class Level implements LevelAccessor, AutoCloseable { +@@ -174,8 +180,9 @@ public abstract class Level implements LevelAccessor, AutoCloseable { public abstract ResourceKey getTypeKey(); -- protected Level(WritableLevelData worlddatamutable, ResourceKey resourcekey, RegistryAccess iregistrycustom, Holder holder, Supplier supplier, boolean flag, boolean flag1, long i, int j, org.bukkit.generator.ChunkGenerator gen, org.bukkit.generator.BiomeProvider biomeProvider, org.bukkit.World.Environment env) { -+ protected Level(WritableLevelData worlddatamutable, ResourceKey resourcekey, RegistryAccess iregistrycustom, Holder holder, Supplier supplier, boolean flag, boolean flag1, long i, int j, org.bukkit.generator.ChunkGenerator gen, org.bukkit.generator.BiomeProvider biomeProvider, org.bukkit.World.Environment env, java.util.function.Function paperWorldConfigCreator) { // Paper - create paper world config +- protected Level(WritableLevelData worlddatamutable, ResourceKey resourcekey, RegistryAccess iregistrycustom, Holder holder, boolean flag, boolean flag1, long i, int j, org.bukkit.generator.ChunkGenerator gen, org.bukkit.generator.BiomeProvider biomeProvider, org.bukkit.World.Environment env) { ++ protected Level(WritableLevelData worlddatamutable, ResourceKey resourcekey, RegistryAccess iregistrycustom, Holder holder, boolean flag, boolean flag1, long i, int j, org.bukkit.generator.ChunkGenerator gen, org.bukkit.generator.BiomeProvider biomeProvider, org.bukkit.World.Environment env, java.util.function.Function paperWorldConfigCreator) { // Paper - create paper world config this.spigotConfig = new org.spigotmc.SpigotWorldConfig(((net.minecraft.world.level.storage.PrimaryLevelData) worlddatamutable).getLevelName()); // Spigot + this.paperConfig = paperWorldConfigCreator.apply(this.spigotConfig); // Paper - create paper world config this.generator = gen; this.world = new CraftWorld((ServerLevel) this, gen, biomeProvider, env); diff --git a/src/main/java/org/bukkit/craftbukkit/CraftServer.java b/src/main/java/org/bukkit/craftbukkit/CraftServer.java -index 357c948dd66644497583996f211f25cbe5c78828..d0c41302478bd9b0ec65020e7ef520bd3ff1f347 100644 +index c48b1d5fc73a98eb4967632d8e6e0744961a688f..3882ae04173cd125fe490692a6bc2b4d8b20ff7b 100644 --- a/src/main/java/org/bukkit/craftbukkit/CraftServer.java +++ b/src/main/java/org/bukkit/craftbukkit/CraftServer.java -@@ -964,6 +964,7 @@ public final class CraftServer implements Server { +@@ -967,6 +967,7 @@ public final class CraftServer implements Server { } org.spigotmc.SpigotConfig.init((File) this.console.options.valueOf("spigot-settings")); // Spigot + this.console.paperConfigurations.reloadConfigs(this.console); for (ServerLevel world : this.console.getAllLevels()) { world.serverLevelData.setDifficulty(config.difficulty); - world.setSpawnSettings(config.spawnMonsters, config.spawnAnimals); + world.setSpawnSettings(config.spawnMonsters); diff --git a/src/main/java/org/bukkit/craftbukkit/Main.java b/src/main/java/org/bukkit/craftbukkit/Main.java -index 4566237fa1d49a559b2df806f9a318be46a6eade..0ea2d13ab80559472513c6df362583b8371a9532 100644 +index 17e10c4373b4281cc74b748c4a1e173e36eb9196..755b1d77418ecae0dc9ec5197275d0cd914e7bee 100644 --- a/src/main/java/org/bukkit/craftbukkit/Main.java +++ b/src/main/java/org/bukkit/craftbukkit/Main.java @@ -142,6 +142,19 @@ public class Main { @@ -5363,7 +5363,7 @@ index 0000000000000000000000000000000000000000..0396589795da1f83ddf62426236dde9a + } +} diff --git a/src/test/java/org/bukkit/support/DummyServerHelper.java b/src/test/java/org/bukkit/support/DummyServerHelper.java -index f81f86cf05a8c6816d356185af6d0d84a0474dd2..ba0a2ab9de34fa40dd90cecaeec4a5e54fe3e2d8 100644 +index 0a6ba289a94468b67d282a199250142e1e86f075..bdfa164ea21cba91b30b965d65d47112111a1209 100644 --- a/src/test/java/org/bukkit/support/DummyServerHelper.java +++ b/src/test/java/org/bukkit/support/DummyServerHelper.java @@ -91,6 +91,7 @@ public final class DummyServerHelper { diff --git a/patches/server/0006-MC-Dev-fixes.patch b/patches/server/0006-MC-Dev-fixes.patch index 168146736c83..6c9bb2f227c5 100644 --- a/patches/server/0006-MC-Dev-fixes.patch +++ b/patches/server/0006-MC-Dev-fixes.patch @@ -4,6 +4,19 @@ Date: Wed, 30 Mar 2016 19:36:20 -0400 Subject: [PATCH] MC Dev fixes +diff --git a/src/main/java/net/minecraft/Util.java b/src/main/java/net/minecraft/Util.java +index 5b9ca3d968fd4884b1b7f2c06477ae00c09f202e..0842080f1840a83b34c2cc829dfddba78ba12157 100644 +--- a/src/main/java/net/minecraft/Util.java ++++ b/src/main/java/net/minecraft/Util.java +@@ -533,7 +533,7 @@ public class Util { + public static , V> EnumMap makeEnumMap(Class enumClass, Function mapper) { + EnumMap enumMap = new EnumMap<>(enumClass); + +- for (K enum_ : (Enum[])enumClass.getEnumConstants()) { ++ for (K enum_ : enumClass.getEnumConstants()) { // Paper - decompile error + enumMap.put(enum_, mapper.apply(enum_)); + } + diff --git a/src/main/java/net/minecraft/commands/arguments/item/ItemInput.java b/src/main/java/net/minecraft/commands/arguments/item/ItemInput.java index 643bb8860962ad691b11073f6dbf406bf7ec5fb1..9b8ec1fd158f6e51779be263fd56b9119d0d9bbb 100644 --- a/src/main/java/net/minecraft/commands/arguments/item/ItemInput.java @@ -17,10 +30,10 @@ index 643bb8860962ad691b11073f6dbf406bf7ec5fb1..9b8ec1fd158f6e51779be263fd56b911 } } diff --git a/src/main/java/net/minecraft/core/BlockPos.java b/src/main/java/net/minecraft/core/BlockPos.java -index 0ef1a7b2a17e81144d594f29f7b5e54d5038dcf4..8994a381b05dcdd1163d2e7a0b63a8875b6063ed 100644 +index 115cb27d3233863692553f8e07238c74b0fcb61c..83e7c141d947f8f8096fed1da716560494bc5c62 100644 --- a/src/main/java/net/minecraft/core/BlockPos.java +++ b/src/main/java/net/minecraft/core/BlockPos.java -@@ -439,12 +439,12 @@ public class BlockPos extends Vec3i { +@@ -445,12 +445,12 @@ public class BlockPos extends Vec3i { if (this.index == l) { return this.endOfData(); } else { @@ -39,11 +52,11 @@ index 0ef1a7b2a17e81144d594f29f7b5e54d5038dcf4..8994a381b05dcdd1163d2e7a0b63a887 } }; diff --git a/src/main/java/net/minecraft/core/registries/BuiltInRegistries.java b/src/main/java/net/minecraft/core/registries/BuiltInRegistries.java -index a467eb6b3b74cdb5378ff5f3043efbe6f4a6f06e..34b3b3251da21bce616870d312fd42fd58ba7881 100644 +index 69993496b6328a963288e11d193d36bd7decfcba..f66a2154486b6d3b5873da043e51df91cd396c72 100644 --- a/src/main/java/net/minecraft/core/registries/BuiltInRegistries.java +++ b/src/main/java/net/minecraft/core/registries/BuiltInRegistries.java -@@ -317,7 +317,7 @@ public class BuiltInRegistries { - Bootstrap.checkBootstrapCalled(() -> "registry " + key); +@@ -325,7 +325,7 @@ public class BuiltInRegistries { + Bootstrap.checkBootstrapCalled(() -> "registry " + key.location()); ResourceLocation resourceLocation = key.location(); LOADERS.put(resourceLocation, () -> initializer.run(registry)); - WRITABLE_REGISTRY.register((ResourceKey>)key, registry, RegistrationInfo.BUILT_IN); @@ -70,11 +83,24 @@ index a614e960fcd5958ad17b679eee8a8e6926f58e62..da101bca71f4710812621b98f0a0d8ca } if (!this.hasElementSeparator()) { +diff --git a/src/main/java/net/minecraft/resources/RegistryDataLoader.java b/src/main/java/net/minecraft/resources/RegistryDataLoader.java +index f4f1a99d53ffb953beb2a944f54d28fa6349fa29..a75f6fefdd72188fa8d16df2b5cbb34c4129f52d 100644 +--- a/src/main/java/net/minecraft/resources/RegistryDataLoader.java ++++ b/src/main/java/net/minecraft/resources/RegistryDataLoader.java +@@ -74,7 +74,7 @@ import org.slf4j.Logger; + + public class RegistryDataLoader { + private static final Logger LOGGER = LogUtils.getLogger(); +- private static final Comparator> ERROR_KEY_COMPARATOR = Comparator.comparing(ResourceKey::registry).thenComparing(ResourceKey::location); ++ private static final Comparator> ERROR_KEY_COMPARATOR = Comparator., ResourceLocation>comparing(ResourceKey::registry).thenComparing(ResourceKey::location); // Paper - decompile fix + private static final RegistrationInfo NETWORK_REGISTRATION_INFO = new RegistrationInfo(Optional.empty(), Lifecycle.experimental()); + private static final Function, RegistrationInfo> REGISTRATION_INFO_CACHE = Util.memoize(knownPacks -> { + Lifecycle lifecycle = knownPacks.map(KnownPack::isVanilla).map(vanilla -> Lifecycle.stable()).orElse(Lifecycle.experimental()); diff --git a/src/main/java/net/minecraft/server/MinecraftServer.java b/src/main/java/net/minecraft/server/MinecraftServer.java -index 299afe72a2fc2affca196f2fdfbf27e5533c51a6..40adb6117b9e0d5f70103113202a07715e403e2a 100644 +index 3fc0abef2c4e2c8ceb3b8c4f02c59700aa3d0803..8e16bc7da15824723f1d7d4bff87fac181978500 100644 --- a/src/main/java/net/minecraft/server/MinecraftServer.java +++ b/src/main/java/net/minecraft/server/MinecraftServer.java -@@ -1952,7 +1952,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoopmap(resourcepackrepository::getPack).filter(Objects::nonNull).map(Pack::open).collect(ImmutableList.toImmutableList()); // CraftBukkit - decompile error // Paper - decompile error // todo: is this needed anymore? }, this).thenCompose((immutablelist) -> { MultiPackResourceManager resourcemanager = new MultiPackResourceManager(PackType.SERVER_DATA, immutablelist); - + List> list = TagLoader.loadTagsForExistingRegistries(resourcemanager, this.registries.compositeAccess()); diff --git a/src/main/java/net/minecraft/util/SortedArraySet.java b/src/main/java/net/minecraft/util/SortedArraySet.java index 661a6274a800ca9b91bdb809d026972d23c3b263..ea72dcb064a35bc6245bc5c94d592efedd8faf41 100644 --- a/src/main/java/net/minecraft/util/SortedArraySet.java @@ -96,26 +122,39 @@ index 661a6274a800ca9b91bdb809d026972d23c3b263..ea72dcb064a35bc6245bc5c94d592efe } public static SortedArraySet create(Comparator comparator) { +diff --git a/src/main/java/net/minecraft/world/entity/animal/Dolphin.java b/src/main/java/net/minecraft/world/entity/animal/Dolphin.java +index 33170f2f1d3f030fbf342e44a1baa109a34c31a7..dde1ccca98f58200910334160f0f79eb00dd2388 100644 +--- a/src/main/java/net/minecraft/world/entity/animal/Dolphin.java ++++ b/src/main/java/net/minecraft/world/entity/animal/Dolphin.java +@@ -349,7 +349,7 @@ public class Dolphin extends AgeableWaterCreature { + + @Nullable + @Override +- protected SoundEvent getDeathSound() { ++ public SoundEvent getDeathSound() { // Paper - decompile error + return SoundEvents.DOLPHIN_DEATH; + } + diff --git a/src/main/java/net/minecraft/world/entity/monster/Pillager.java b/src/main/java/net/minecraft/world/entity/monster/Pillager.java -index 8eb1aca72df0bca292473e90ecb74159db4fe034..4b4dcee6abe7a6db43638d04665125eec560496e 100644 +index cf025d7a4392213db3cf04e7ace3e2b166e710eb..3e8631c7bd1e7591051ca21c6ae7acd87d3c7529 100644 --- a/src/main/java/net/minecraft/world/entity/monster/Pillager.java +++ b/src/main/java/net/minecraft/world/entity/monster/Pillager.java -@@ -66,7 +66,7 @@ public class Pillager extends AbstractIllager implements CrossbowAttackMob, Inve - protected void registerGoals() { +@@ -69,7 +69,7 @@ public class Pillager extends AbstractIllager implements CrossbowAttackMob, Inve super.registerGoals(); this.goalSelector.addGoal(0, new FloatGoal(this)); + this.goalSelector.addGoal(1, new AvoidEntityGoal<>(this, Creaking.class, 8.0F, 1.0D, 1.2D)); - this.goalSelector.addGoal(2, new Raider.HoldGroundAttackGoal(this, this, 10.0F)); + this.goalSelector.addGoal(2, new Raider.HoldGroundAttackGoal(this, 10.0F)); // Paper - decomp fix this.goalSelector.addGoal(3, new RangedCrossbowAttackGoal<>(this, 1.0D, 8.0F)); this.goalSelector.addGoal(8, new RandomStrollGoal(this, 0.6D)); this.goalSelector.addGoal(9, new LookAtPlayerGoal(this, Player.class, 15.0F, 1.0F)); diff --git a/src/main/java/net/minecraft/world/level/chunk/status/ChunkStatusTasks.java b/src/main/java/net/minecraft/world/level/chunk/status/ChunkStatusTasks.java -index e2b72817857a7a203aae4c9de4e01ba1396dc95b..82fc5133325a127890984d51c1381883759eb444 100644 +index 5fa94fd51027901591e40cabdacfd61d16a58585..3f552ee8f90566edddb5943311a14309e4bebb61 100644 --- a/src/main/java/net/minecraft/world/level/chunk/status/ChunkStatusTasks.java +++ b/src/main/java/net/minecraft/world/level/chunk/status/ChunkStatusTasks.java -@@ -153,7 +153,7 @@ public class ChunkStatusTasks { - if (protochunk instanceof ImposterProtoChunk) { - chunk1 = ((ImposterProtoChunk) protochunk).getWrapped(); +@@ -151,7 +151,7 @@ public class ChunkStatusTasks { + if (protochunk instanceof ImposterProtoChunk protochunkextension) { + chunk1 = protochunkextension.getWrapped(); } else { - chunk1 = new LevelChunk(worldserver, protochunk, (chunk1) -> { + chunk1 = new LevelChunk(worldserver, protochunk, ($) -> { // Paper - decompile fix diff --git a/patches/server/0007-ConcurrentUtil.patch b/patches/server/0007-ConcurrentUtil.patch index 44a4f07316de..b285b3c6e347 100644 --- a/patches/server/0007-ConcurrentUtil.patch +++ b/patches/server/0007-ConcurrentUtil.patch @@ -1431,167 +1431,12 @@ index 0000000000000000000000000000000000000000..f84a622dc29750139ac280f480b7cd13 + } + } +} -diff --git a/src/main/java/ca/spottedleaf/concurrentutil/collection/SRSWLinkedQueue.java b/src/main/java/ca/spottedleaf/concurrentutil/collection/SRSWLinkedQueue.java +diff --git a/src/main/java/ca/spottedleaf/concurrentutil/completable/CallbackCompletable.java b/src/main/java/ca/spottedleaf/concurrentutil/completable/CallbackCompletable.java new file mode 100644 -index 0000000000000000000000000000000000000000..094eff418b4e3bffce020d650931b4d9e58fa9ed +index 0000000000000000000000000000000000000000..6bad6f8ecc0944d2f406924c7de7e227ff1e70fa --- /dev/null -+++ b/src/main/java/ca/spottedleaf/concurrentutil/collection/SRSWLinkedQueue.java -@@ -0,0 +1,149 @@ -+package ca.spottedleaf.concurrentutil.collection; -+ -+import ca.spottedleaf.concurrentutil.util.ConcurrentUtil; -+import ca.spottedleaf.concurrentutil.util.Validate; -+ -+import java.lang.invoke.VarHandle; -+import java.util.ConcurrentModificationException; -+ -+/** -+ * Single reader thread single writer thread queue. The reader side of the queue is ordered by acquire semantics, -+ * and the writer side of the queue is ordered by release semantics. -+ */ -+// TODO test -+public class SRSWLinkedQueue { -+ -+ // always non-null -+ protected LinkedNode head; -+ -+ // always non-null -+ protected LinkedNode tail; -+ -+ /* IMPL NOTE: Leave hashCode and equals to their defaults */ -+ -+ public SRSWLinkedQueue() { -+ final LinkedNode dummy = new LinkedNode<>(null, null); -+ this.head = this.tail = dummy; -+ } -+ -+ /** -+ * Must be the reader thread. -+ * -+ *

    -+ * Returns, without removing, the first element of this queue. -+ *

    -+ * @return Returns, without removing, the first element of this queue. -+ */ -+ public E peekFirst() { -+ LinkedNode head = this.head; -+ E ret = head.getElementPlain(); -+ if (ret == null) { -+ head = head.getNextAcquire(); -+ if (head == null) { -+ // empty -+ return null; -+ } -+ // update head reference for next poll() call -+ this.head = head; -+ // guaranteed to be non-null -+ ret = head.getElementPlain(); -+ if (ret == null) { -+ throw new ConcurrentModificationException("Multiple reader threads"); -+ } -+ } -+ -+ return ret; -+ } -+ -+ /** -+ * Must be the reader thread. -+ * -+ *

    -+ * Returns and removes the first element of this queue. -+ *

    -+ * @return Returns and removes the first element of this queue. -+ */ -+ public E poll() { -+ LinkedNode head = this.head; -+ E ret = head.getElementPlain(); -+ if (ret == null) { -+ head = head.getNextAcquire(); -+ if (head == null) { -+ // empty -+ return null; -+ } -+ // guaranteed to be non-null -+ ret = head.getElementPlain(); -+ if (ret == null) { -+ throw new ConcurrentModificationException("Multiple reader threads"); -+ } -+ } -+ -+ head.setElementPlain(null); -+ LinkedNode next = head.getNextAcquire(); -+ this.head = next == null ? head : next; -+ -+ return ret; -+ } -+ -+ /** -+ * Must be the writer thread. -+ * -+ *

    -+ * Adds the element to the end of the queue. -+ *

    -+ * -+ * @throws NullPointerException If the provided element is null -+ */ -+ public void addLast(final E element) { -+ Validate.notNull(element, "Provided element cannot be null"); -+ final LinkedNode append = new LinkedNode<>(element, null); -+ -+ this.tail.setNextRelease(append); -+ this.tail = append; -+ } -+ -+ protected static final class LinkedNode { -+ -+ protected volatile Object element; -+ protected volatile LinkedNode next; -+ -+ protected static final VarHandle ELEMENT_HANDLE = ConcurrentUtil.getVarHandle(LinkedNode.class, "element", Object.class); -+ protected static final VarHandle NEXT_HANDLE = ConcurrentUtil.getVarHandle(LinkedNode.class, "next", LinkedNode.class); -+ -+ protected LinkedNode(final Object element, final LinkedNode next) { -+ ELEMENT_HANDLE.set(this, element); -+ NEXT_HANDLE.set(this, next); -+ } -+ -+ /* element */ -+ -+ @SuppressWarnings("unchecked") -+ protected final E getElementPlain() { -+ return (E)ELEMENT_HANDLE.get(this); -+ } -+ -+ protected final void setElementPlain(final E update) { -+ ELEMENT_HANDLE.set(this, (Object)update); -+ } -+ /* next */ -+ -+ @SuppressWarnings("unchecked") -+ protected final LinkedNode getNextPlain() { -+ return (LinkedNode)NEXT_HANDLE.get(this); -+ } -+ -+ @SuppressWarnings("unchecked") -+ protected final LinkedNode getNextAcquire() { -+ return (LinkedNode)NEXT_HANDLE.getAcquire(this); -+ } -+ -+ protected final void setNextPlain(final LinkedNode next) { -+ NEXT_HANDLE.set(this, next); -+ } -+ -+ protected final void setNextRelease(final LinkedNode next) { -+ NEXT_HANDLE.setRelease(this, next); -+ } -+ } -+} -diff --git a/src/main/java/ca/spottedleaf/concurrentutil/completable/Completable.java b/src/main/java/ca/spottedleaf/concurrentutil/completable/Completable.java -new file mode 100644 -index 0000000000000000000000000000000000000000..46d1bd01542ebeeffc0006a5c585a50dbbbff907 ---- /dev/null -+++ b/src/main/java/ca/spottedleaf/concurrentutil/completable/Completable.java -@@ -0,0 +1,112 @@ ++++ b/src/main/java/ca/spottedleaf/concurrentutil/completable/CallbackCompletable.java +@@ -0,0 +1,110 @@ +package ca.spottedleaf.concurrentutil.completable; + +import ca.spottedleaf.concurrentutil.collection.MultiThreadedQueue; @@ -1601,9 +1446,9 @@ index 0000000000000000000000000000000000000000..46d1bd01542ebeeffc0006a5c585a50d +import org.slf4j.LoggerFactory; +import java.util.function.BiConsumer; + -+public final class Completable { ++public final class CallbackCompletable { + -+ private static final Logger LOGGER = LoggerFactory.getLogger(Completable.class); ++ private static final Logger LOGGER = LoggerFactory.getLogger(CallbackCompletable.class); + + private final MultiThreadedQueue> waiters = new MultiThreadedQueue<>(); + private T result; @@ -1655,8 +1500,6 @@ index 0000000000000000000000000000000000000000..46d1bd01542ebeeffc0006a5c585a50d + private void completeWaiter(final BiConsumer consumer, final T result, final Throwable throwable) { + try { + consumer.accept(result, throwable); -+ } catch (final ThreadDeath death) { -+ throw death; + } catch (final Throwable throwable2) { + LOGGER.error("Failed to complete callback " + ConcurrentUtil.genericToString(consumer), throwable2); + } @@ -1700,612 +1543,853 @@ index 0000000000000000000000000000000000000000..46d1bd01542ebeeffc0006a5c585a50d + + @Override + public boolean cancel() { -+ return Completable.this.waiters.remove(this.waiter); ++ return CallbackCompletable.this.waiters.remove(this.waiter); + } + } +} -diff --git a/src/main/java/ca/spottedleaf/concurrentutil/executor/BaseExecutor.java b/src/main/java/ca/spottedleaf/concurrentutil/executor/BaseExecutor.java +\ No newline at end of file +diff --git a/src/main/java/ca/spottedleaf/concurrentutil/completable/Completable.java b/src/main/java/ca/spottedleaf/concurrentutil/completable/Completable.java new file mode 100644 -index 0000000000000000000000000000000000000000..18d646676fd022afd64afaac30ec1bd283a73b0e +index 0000000000000000000000000000000000000000..365616439fa079017d648ed7f6ddf6950a691adf --- /dev/null -+++ b/src/main/java/ca/spottedleaf/concurrentutil/executor/BaseExecutor.java -@@ -0,0 +1,208 @@ -+package ca.spottedleaf.concurrentutil.executor; ++++ b/src/main/java/ca/spottedleaf/concurrentutil/completable/Completable.java +@@ -0,0 +1,737 @@ ++package ca.spottedleaf.concurrentutil.completable; + +import ca.spottedleaf.concurrentutil.util.ConcurrentUtil; -+import java.util.function.BooleanSupplier; ++import ca.spottedleaf.concurrentutil.util.Validate; ++import org.slf4j.Logger; ++import org.slf4j.LoggerFactory; ++import java.lang.invoke.VarHandle; ++import java.util.concurrent.CompletableFuture; ++import java.util.concurrent.CompletionException; ++import java.util.concurrent.CompletionStage; ++import java.util.concurrent.Executor; ++import java.util.concurrent.ForkJoinPool; ++import java.util.concurrent.locks.LockSupport; ++import java.util.function.BiConsumer; ++import java.util.function.BiFunction; ++import java.util.function.Consumer; ++import java.util.function.Function; ++import java.util.function.Supplier; + -+/** -+ * Base implementation for an abstract queue of tasks which are executed either synchronously or asynchronously. -+ * -+ *

    -+ * The implementation supports tracking task executions using {@link #getTotalTasksScheduled()} and -+ * {@link #getTotalTasksExecuted()}, and optionally shutting down the executor using {@link #shutdown()} -+ *

    -+ * -+ *

    -+ * The base implementation does not provide a method to queue a task for execution, rather that is specified in -+ * the specific implementation. However, it is required that a specific implementation provides a method to -+ * queue a task or create a task. A queued task is one which will eventually be executed, -+ * and a created task must be queued to execute via {@link BaseTask#queue()} or be executed manually via -+ * {@link BaseTask#execute()}. This choice of delaying the queueing of a task may be useful to provide a task handle -+ * which may be cancelled or adjusted before the actual real task logic is ready to be executed. -+ *

    -+ */ -+public interface BaseExecutor { ++public final class Completable { + -+ /** -+ * Returns whether every task scheduled to this queue has been removed and executed or cancelled. If no tasks have been queued, -+ * returns {@code true}. -+ * -+ * @return {@code true} if all tasks that have been queued have finished executing or no tasks have been queued, {@code false} otherwise. -+ */ -+ public default boolean haveAllTasksExecuted() { -+ // order is important -+ // if new tasks are scheduled between the reading of these variables, scheduled is guaranteed to be higher - -+ // so our check fails, and we try again -+ final long completed = this.getTotalTasksExecuted(); -+ final long scheduled = this.getTotalTasksScheduled(); ++ private static final Logger LOGGER = LoggerFactory.getLogger(Completable.class); ++ private static final Function DEFAULT_EXCEPTION_HANDLER = (final Throwable thr) -> { ++ LOGGER.error("Unhandled exception during Completable operation", thr); ++ return thr; ++ }; + -+ return completed == scheduled; ++ public static Executor getDefaultExecutor() { ++ return ForkJoinPool.commonPool(); + } + -+ /** -+ * Returns the number of tasks that have been scheduled or execute or are pending to be scheduled. -+ */ -+ public long getTotalTasksScheduled(); ++ private static final Transform COMPLETED_STACK = new Transform<>(null, null, null, null) { ++ @Override ++ public void run() {} ++ }; ++ private volatile Transform completeStack; ++ private static final VarHandle COMPLETE_STACK_HANDLE = ConcurrentUtil.getVarHandle(Completable.class, "completeStack", Transform.class); + -+ /** -+ * Returns the number of tasks that have fully been executed. -+ */ -+ public long getTotalTasksExecuted(); ++ private static final Object NULL_MASK = new Object(); ++ private volatile Object result; ++ private static final VarHandle RESULT_HANDLE = ConcurrentUtil.getVarHandle(Completable.class, "result", Object.class); + -+ /** -+ * Waits until this queue has had all of its tasks executed (NOT removed). See {@link #haveAllTasksExecuted()} -+ *

    -+ * This call is most effective after a {@link #shutdown()} call, as the shutdown call guarantees no tasks can -+ * be executed and the waitUntilAllExecuted call makes sure the queue is empty. Effectively, using shutdown then using -+ * waitUntilAllExecuted ensures this queue is empty - and most importantly, will remain empty. -+ *

    -+ *

    -+ * This method is not guaranteed to be immediately responsive to queue state, so calls may take significantly more -+ * time than expected. Effectively, do not rely on this call being fast - even if there are few tasks scheduled. -+ *

    -+ *

    -+ * Note: Interruptions to the the current thread have no effect. Interrupt status is also not affected by this call. -+ *

    -+ * -+ * @throws IllegalStateException If the current thread is not allowed to wait -+ */ -+ public default void waitUntilAllExecuted() throws IllegalStateException { -+ long failures = 1L; // start at 0.25ms ++ private Object getResultPlain() { ++ return (Object)RESULT_HANDLE.get(this); ++ } + -+ while (!this.haveAllTasksExecuted()) { -+ Thread.yield(); -+ failures = ConcurrentUtil.linearLongBackoff(failures, 250_000L, 5_000_000L); // 500us, 5ms -+ } ++ private Object getResultVolatile() { ++ return (Object)RESULT_HANDLE.getVolatile(this); + } + -+ /** -+ * Executes the next available task. -+ * -+ * @return {@code true} if a task was executed, {@code false} otherwise -+ * @throws IllegalStateException If the current thread is not allowed to execute a task -+ */ -+ public boolean executeTask() throws IllegalStateException; ++ private void pushStackOrRun(final Transform push) { ++ int failures = 0; ++ for (Transform curr = (Transform)COMPLETE_STACK_HANDLE.getVolatile(this);;) { ++ if (curr == COMPLETED_STACK) { ++ push.execute(); ++ return; ++ } + -+ /** -+ * Executes all queued tasks. -+ * -+ * @return {@code true} if a task was executed, {@code false} otherwise -+ * @throws IllegalStateException If the current thread is not allowed to execute a task -+ */ -+ public default boolean executeAll() { -+ if (!this.executeTask()) { -+ return false; ++ push.next = curr; ++ ++ for (int i = 0; i < failures; ++i) { ++ ConcurrentUtil.backoff(); ++ } ++ ++ if (curr == (curr = (Transform)COMPLETE_STACK_HANDLE.compareAndExchange(this, curr, push))) { ++ return; ++ } ++ push.next = null; ++ ++failures; + } ++ } + -+ while (this.executeTask()); ++ private void propagateStack() { ++ Transform topStack = (Transform)COMPLETE_STACK_HANDLE.getAndSet(this, COMPLETED_STACK); ++ while (topStack != null) { ++ topStack.execute(); ++ topStack = topStack.next; ++ } ++ } + -+ return true; ++ private static Object maskNull(final Object res) { ++ return res == null ? NULL_MASK : res; + } + -+ /** -+ * Waits and executes tasks until the condition returns {@code true}. -+ *

    -+ * WARNING: This function is not suitable for waiting until a deadline! -+ * Use {@link #executeUntil(long)} or {@link #executeConditionally(BooleanSupplier, long)} instead. -+ *

    -+ */ -+ public default void executeConditionally(final BooleanSupplier condition) { -+ long failures = 0; -+ while (!condition.getAsBoolean()) { -+ if (this.executeTask()) { -+ failures = failures >>> 2; -+ } else { -+ failures = ConcurrentUtil.linearLongBackoff(failures, 100_000L, 10_000_000L); // 100us, 10ms ++ private static Object unmaskNull(final Object res) { ++ return res == NULL_MASK ? null : res; ++ } ++ ++ private static Executor checkExecutor(final Executor executor) { ++ return Validate.notNull(executor, "Executor may not be null"); ++ } ++ ++ public Completable() {} ++ ++ private Completable(final Object complete) { ++ COMPLETE_STACK_HANDLE.set(this, COMPLETED_STACK); ++ RESULT_HANDLE.setRelease(this, complete); ++ } ++ ++ public static Completable completed(final T value) { ++ return new Completable<>(maskNull(value)); ++ } ++ ++ public static Completable failed(final Throwable ex) { ++ Validate.notNull(ex, "Exception may not be null"); ++ ++ return new Completable<>(new ExceptionResult(ex)); ++ } ++ ++ public static Completable supplied(final Supplier supplier) { ++ return supplied(supplier, DEFAULT_EXCEPTION_HANDLER); ++ } ++ ++ public static Completable supplied(final Supplier supplier, final Function exceptionHandler) { ++ try { ++ return completed(supplier.get()); ++ } catch (final Throwable throwable) { ++ Throwable complete; ++ try { ++ complete = exceptionHandler.apply(throwable); ++ } catch (final Throwable thr2) { ++ throwable.addSuppressed(thr2); ++ complete = throwable; + } ++ return failed(complete); + } + } + -+ /** -+ * Waits and executes tasks until the condition returns {@code true} or {@code System.nanoTime() - deadline >= 0}. -+ */ -+ public default void executeConditionally(final BooleanSupplier condition, final long deadline) { -+ long failures = 0; -+ // double check deadline; we don't know how expensive the condition is -+ while ((System.nanoTime() - deadline < 0L) && !condition.getAsBoolean() && (System.nanoTime() - deadline < 0L)) { -+ if (this.executeTask()) { -+ failures = failures >>> 2; -+ } else { -+ failures = ConcurrentUtil.linearLongBackoffDeadline(failures, 100_000L, 10_000_000L, deadline); // 100us, 10ms ++ public static Completable suppliedAsync(final Supplier supplier, final Executor executor) { ++ return suppliedAsync(supplier, executor, DEFAULT_EXCEPTION_HANDLER); ++ } ++ ++ public static Completable suppliedAsync(final Supplier supplier, final Executor executor, final Function exceptionHandler) { ++ final Completable ret = new Completable<>(); ++ ++ class AsyncSuppliedCompletable implements Runnable, CompletableFuture.AsynchronousCompletionTask { ++ @Override ++ public void run() { ++ try { ++ ret.complete(supplier.get()); ++ } catch (final Throwable throwable) { ++ Throwable complete; ++ try { ++ complete = exceptionHandler.apply(throwable); ++ } catch (final Throwable thr2) { ++ throwable.addSuppressed(thr2); ++ complete = throwable; ++ } ++ ret.completeExceptionally(complete); ++ } + } + } -+ } + -+ /** -+ * Waits and executes tasks until {@code System.nanoTime() - deadline >= 0}. -+ */ -+ public default void executeUntil(final long deadline) { -+ long failures = 0; -+ while (System.nanoTime() - deadline < 0L) { -+ if (this.executeTask()) { -+ failures = failures >>> 2; -+ } else { -+ failures = ConcurrentUtil.linearLongBackoffDeadline(failures, 100_000L, 10_000_000L, deadline); // 100us, 10ms ++ try { ++ executor.execute(new AsyncSuppliedCompletable()); ++ } catch (final Throwable throwable) { ++ Throwable complete; ++ try { ++ complete = exceptionHandler.apply(throwable); ++ } catch (final Throwable thr2) { ++ throwable.addSuppressed(thr2); ++ complete = throwable; + } ++ ret.completeExceptionally(complete); + } ++ ++ return ret; + } + -+ /** -+ * Prevent further additions to this queue. Attempts to add after this call has completed (potentially during) will -+ * result in {@link IllegalStateException} being thrown. -+ *

    -+ * This operation is atomic with respect to other shutdown calls -+ *

    -+ *

    -+ * After this call has completed, regardless of return value, this queue will be shutdown. -+ *

    -+ * -+ * @return {@code true} if the queue was shutdown, {@code false} if it has shut down already -+ * @throws UnsupportedOperationException If this queue does not support shutdown -+ * @see #isShutdown() -+ */ -+ public default boolean shutdown() throws UnsupportedOperationException { -+ throw new UnsupportedOperationException(); ++ private boolean completeRaw(final Object value) { ++ if ((Object)RESULT_HANDLE.getVolatile(this) != null || !(boolean)RESULT_HANDLE.compareAndSet(this, (Object)null, value)) { ++ return false; ++ } ++ ++ this.propagateStack(); ++ return true; + } + -+ /** -+ * Returns whether this queue has shut down. Effectively, whether new tasks will be rejected - this method -+ * does not indicate whether all the tasks scheduled have been executed. -+ * @return Returns whether this queue has shut down. -+ * @see #waitUntilAllExecuted() -+ */ -+ public default boolean isShutdown() { -+ return false; ++ public boolean complete(final T result) { ++ return this.completeRaw(maskNull(result)); + } + -+ /** -+ * Task object returned for any {@link BaseExecutor} scheduled task. -+ * @see BaseExecutor -+ */ -+ public static interface BaseTask extends Cancellable { ++ public boolean completeExceptionally(final Throwable exception) { ++ Validate.notNull(exception, "Exception may not be null"); + -+ /** -+ * Causes a lazily queued task to become queued or executed -+ * -+ * @throws IllegalStateException If the backing queue has shutdown -+ * @return {@code true} If the task was queued, {@code false} if the task was already queued/cancelled/executed -+ */ -+ public boolean queue(); ++ return this.completeRaw(new ExceptionResult(exception)); ++ } + -+ /** -+ * Forces this task to be marked as completed. -+ * -+ * @return {@code true} if the task was cancelled, {@code false} if the task has already completed or is being completed. -+ */ -+ @Override -+ public boolean cancel(); ++ public boolean isDone() { ++ return this.getResultVolatile() != null; ++ } + -+ /** -+ * Executes this task. This will also mark the task as completing. -+ *

    -+ * Exceptions thrown from the runnable will be rethrown. -+ *

    -+ * -+ * @return {@code true} if this task was executed, {@code false} if it was already marked as completed. -+ */ -+ public boolean execute(); ++ public boolean isNormallyComplete() { ++ return this.getResultVolatile() != null && !(this.getResultVolatile() instanceof ExceptionResult); + } -+} -diff --git a/src/main/java/ca/spottedleaf/concurrentutil/executor/Cancellable.java b/src/main/java/ca/spottedleaf/concurrentutil/executor/Cancellable.java -new file mode 100644 -index 0000000000000000000000000000000000000000..11449056361bb6c5a055f543cdd135c4113757c6 ---- /dev/null -+++ b/src/main/java/ca/spottedleaf/concurrentutil/executor/Cancellable.java -@@ -0,0 +1,14 @@ -+package ca.spottedleaf.concurrentutil.executor; + -+/** -+ * Interface specifying that something can be cancelled. -+ */ -+public interface Cancellable { ++ public boolean isExceptionallyComplete() { ++ return this.getResultVolatile() instanceof ExceptionResult; ++ } + -+ /** -+ * Tries to cancel this task. If the task is in a stage that is too late to be cancelled, then this function -+ * will return {@code false}. If the task is already cancelled, then this function returns {@code false}. Only -+ * when this function successfully stops this task from being completed will it return {@code true}. -+ */ -+ public boolean cancel(); -+} -diff --git a/src/main/java/ca/spottedleaf/concurrentutil/executor/standard/DelayedPrioritisedTask.java b/src/main/java/ca/spottedleaf/concurrentutil/executor/standard/DelayedPrioritisedTask.java -new file mode 100644 -index 0000000000000000000000000000000000000000..3ce10053d4ec51855ad7012abb5d97df1c0e557a ---- /dev/null -+++ b/src/main/java/ca/spottedleaf/concurrentutil/executor/standard/DelayedPrioritisedTask.java -@@ -0,0 +1,170 @@ -+package ca.spottedleaf.concurrentutil.executor.standard; ++ public Throwable getException() { ++ final Object res = this.getResultVolatile(); ++ if (res == null) { ++ return null; ++ } + -+import ca.spottedleaf.concurrentutil.util.ConcurrentUtil; -+import java.lang.invoke.VarHandle; ++ if (!(res instanceof ExceptionResult exRes)) { ++ throw new IllegalStateException("Not completed exceptionally"); ++ } + -+public class DelayedPrioritisedTask { ++ return exRes.ex; ++ } + -+ protected volatile int priority; -+ protected static final VarHandle PRIORITY_HANDLE = ConcurrentUtil.getVarHandle(DelayedPrioritisedTask.class, "priority", int.class); ++ public T getNow(final T dfl) throws CompletionException { ++ final Object res = this.getResultVolatile(); ++ if (res == null) { ++ return dfl; ++ } + -+ protected static final int PRIORITY_SET = Integer.MIN_VALUE >>> 0; ++ if (res instanceof ExceptionResult exRes) { ++ throw new CompletionException(exRes.ex); ++ } + -+ protected final int getPriorityVolatile() { -+ return (int)PRIORITY_HANDLE.getVolatile((DelayedPrioritisedTask)this); ++ return (T)unmaskNull(res); + } + -+ protected final int compareAndExchangePriorityVolatile(final int expect, final int update) { -+ return (int)PRIORITY_HANDLE.compareAndExchange((DelayedPrioritisedTask)this, (int)expect, (int)update); ++ public T join() throws CompletionException { ++ if (this.isDone()) { ++ return this.getNow(null); ++ } ++ ++ final UnparkTransform unparkTransform = new UnparkTransform<>(this, Thread.currentThread()); ++ ++ this.pushStackOrRun(unparkTransform); ++ ++ boolean interuptted = false; ++ while (!unparkTransform.isReleasable()) { ++ try { ++ ForkJoinPool.managedBlock(unparkTransform); ++ } catch (final InterruptedException ex) { ++ interuptted = true; ++ } ++ } ++ ++ if (interuptted) { ++ Thread.currentThread().interrupt(); ++ } ++ ++ return this.getNow(null); + } + -+ protected final int getAndOrPriorityVolatile(final int val) { -+ return (int)PRIORITY_HANDLE.getAndBitwiseOr((DelayedPrioritisedTask)this, (int)val); ++ public CompletableFuture toFuture() { ++ final Object rawResult = this.getResultVolatile(); ++ if (rawResult != null) { ++ if (rawResult instanceof ExceptionResult exRes) { ++ return CompletableFuture.failedFuture(exRes.ex); ++ } else { ++ return CompletableFuture.completedFuture((T)unmaskNull(rawResult)); ++ } ++ } ++ ++ final CompletableFuture ret = new CompletableFuture<>(); ++ ++ class ToFuture implements BiConsumer { ++ ++ @Override ++ public void accept(final T res, final Throwable ex) { ++ if (ex != null) { ++ ret.completeExceptionally(ex); ++ } else { ++ ret.complete(res); ++ } ++ } ++ } ++ ++ this.whenComplete(new ToFuture()); ++ ++ return ret; + } + -+ protected final void setPriorityPlain(final int val) { -+ PRIORITY_HANDLE.set((DelayedPrioritisedTask)this, (int)val); ++ public static Completable fromFuture(final CompletionStage stage) { ++ final Completable ret = new Completable<>(); ++ ++ class FromFuture implements BiConsumer { ++ @Override ++ public void accept(final T res, final Throwable ex) { ++ if (ex != null) { ++ ret.completeExceptionally(ex); ++ } else { ++ ret.complete(res); ++ } ++ } ++ } ++ ++ stage.whenComplete(new FromFuture()); ++ ++ return ret; + } + -+ protected volatile PrioritisedExecutor.PrioritisedTask task; -+ protected static final VarHandle TASK_HANDLE = ConcurrentUtil.getVarHandle(DelayedPrioritisedTask.class, "task", PrioritisedExecutor.PrioritisedTask.class); + -+ protected PrioritisedExecutor.PrioritisedTask getTaskPlain() { -+ return (PrioritisedExecutor.PrioritisedTask)TASK_HANDLE.get((DelayedPrioritisedTask)this); ++ public Completable thenApply(final Function function) { ++ return this.thenApply(function, DEFAULT_EXCEPTION_HANDLER); + } + -+ protected PrioritisedExecutor.PrioritisedTask getTaskVolatile() { -+ return (PrioritisedExecutor.PrioritisedTask)TASK_HANDLE.getVolatile((DelayedPrioritisedTask)this); ++ public Completable thenApply(final Function function, final Function exceptionHandler) { ++ Validate.notNull(function, "Function may not be null"); ++ Validate.notNull(exceptionHandler, "Exception handler may not be null"); ++ ++ final Completable ret = new Completable<>(); ++ this.pushStackOrRun(new ApplyTransform<>(null, this, ret, exceptionHandler, function)); ++ return ret; + } + -+ protected final PrioritisedExecutor.PrioritisedTask compareAndExchangeTaskVolatile(final PrioritisedExecutor.PrioritisedTask expect, final PrioritisedExecutor.PrioritisedTask update) { -+ return (PrioritisedExecutor.PrioritisedTask)TASK_HANDLE.compareAndExchange((DelayedPrioritisedTask)this, (PrioritisedExecutor.PrioritisedTask)expect, (PrioritisedExecutor.PrioritisedTask)update); ++ public Completable thenApplyAsync(final Function function) { ++ return this.thenApplyAsync(function, getDefaultExecutor(), DEFAULT_EXCEPTION_HANDLER); + } + -+ public DelayedPrioritisedTask(final PrioritisedExecutor.Priority priority) { -+ this.setPriorityPlain(priority.priority); ++ public Completable thenApplyAsync(final Function function, final Executor executor) { ++ return this.thenApplyAsync(function, executor, DEFAULT_EXCEPTION_HANDLER); + } + -+ // only public for debugging -+ public int getPriorityInternal() { -+ return this.getPriorityVolatile(); ++ public Completable thenApplyAsync(final Function function, final Executor executor, final Function exceptionHandler) { ++ Validate.notNull(function, "Function may not be null"); ++ Validate.notNull(exceptionHandler, "Exception handler may not be null"); ++ ++ final Completable ret = new Completable<>(); ++ this.pushStackOrRun(new ApplyTransform<>(checkExecutor(executor), this, ret, exceptionHandler, function)); ++ return ret; + } + -+ public PrioritisedExecutor.PrioritisedTask getTask() { -+ return this.getTaskVolatile(); ++ ++ public Completable thenAccept(final Consumer consumer) { ++ return this.thenAccept(consumer, DEFAULT_EXCEPTION_HANDLER); + } + -+ public void setTask(final PrioritisedExecutor.PrioritisedTask task) { -+ int priority = this.getPriorityVolatile(); ++ public Completable thenAccept(final Consumer consumer, final Function exceptionHandler) { ++ Validate.notNull(consumer, "Consumer may not be null"); ++ Validate.notNull(exceptionHandler, "Exception handler may not be null"); + -+ if (this.compareAndExchangeTaskVolatile(null, task) != null) { -+ throw new IllegalStateException("setTask() called twice"); -+ } ++ final Completable ret = new Completable<>(); ++ this.pushStackOrRun(new AcceptTransform<>(null, this, ret, exceptionHandler, consumer)); ++ return ret; ++ } + -+ int failures = 0; -+ for (;;) { -+ task.setPriority(PrioritisedExecutor.Priority.getPriority(priority)); ++ public Completable thenAcceptAsync(final Consumer consumer) { ++ return this.thenAcceptAsync(consumer, getDefaultExecutor(), DEFAULT_EXCEPTION_HANDLER); ++ } + -+ if (priority == (priority = this.compareAndExchangePriorityVolatile(priority, priority | PRIORITY_SET))) { -+ return; -+ } ++ public Completable thenAcceptAsync(final Consumer consumer, final Executor executor) { ++ return this.thenAcceptAsync(consumer, executor, DEFAULT_EXCEPTION_HANDLER); ++ } + -+ ++failures; -+ for (int i = 0; i < failures; ++i) { -+ ConcurrentUtil.backoff(); -+ } -+ } ++ public Completable thenAcceptAsync(final Consumer consumer, final Executor executor, final Function exceptionHandler) { ++ Validate.notNull(consumer, "Consumer may not be null"); ++ Validate.notNull(exceptionHandler, "Exception handler may not be null"); ++ ++ final Completable ret = new Completable<>(); ++ this.pushStackOrRun(new AcceptTransform<>(checkExecutor(executor), this, ret, exceptionHandler, consumer)); ++ return ret; + } + -+ public PrioritisedExecutor.Priority getPriority() { -+ final int priority = this.getPriorityVolatile(); -+ if ((priority & PRIORITY_SET) != 0) { -+ return this.task.getPriority(); -+ } + -+ return PrioritisedExecutor.Priority.getPriority(priority); ++ public Completable thenRun(final Runnable run) { ++ return this.thenRun(run, DEFAULT_EXCEPTION_HANDLER); + } + -+ public void raisePriority(final PrioritisedExecutor.Priority priority) { -+ if (!PrioritisedExecutor.Priority.isValidPriority(priority)) { -+ throw new IllegalArgumentException("Invalid priority " + priority); -+ } ++ public Completable thenRun(final Runnable run, final Function exceptionHandler) { ++ Validate.notNull(run, "Run may not be null"); ++ Validate.notNull(exceptionHandler, "Exception handler may not be null"); + -+ int failures = 0; -+ for (int curr = this.getPriorityVolatile();;) { -+ if ((curr & PRIORITY_SET) != 0) { -+ this.getTaskPlain().raisePriority(priority); -+ return; -+ } ++ final Completable ret = new Completable<>(); ++ this.pushStackOrRun(new RunTransform<>(null, this, ret, exceptionHandler, run)); ++ return ret; ++ } + -+ if (!priority.isLowerPriority(curr)) { -+ return; -+ } ++ public Completable thenRunAsync(final Runnable run) { ++ return this.thenRunAsync(run, getDefaultExecutor(), DEFAULT_EXCEPTION_HANDLER); ++ } + -+ if (curr == (curr = this.compareAndExchangePriorityVolatile(curr, priority.priority))) { -+ return; -+ } ++ public Completable thenRunAsync(final Runnable run, final Executor executor) { ++ return this.thenRunAsync(run, executor, DEFAULT_EXCEPTION_HANDLER); ++ } + -+ // failed, retry ++ public Completable thenRunAsync(final Runnable run, final Executor executor, final Function exceptionHandler) { ++ Validate.notNull(run, "Run may not be null"); ++ Validate.notNull(exceptionHandler, "Exception handler may not be null"); + -+ ++failures; -+ for (int i = 0; i < failures; ++i) { -+ ConcurrentUtil.backoff(); -+ } -+ } ++ final Completable ret = new Completable<>(); ++ this.pushStackOrRun(new RunTransform<>(checkExecutor(executor), this, ret, exceptionHandler, run)); ++ return ret; + } + -+ public void setPriority(final PrioritisedExecutor.Priority priority) { -+ if (!PrioritisedExecutor.Priority.isValidPriority(priority)) { -+ throw new IllegalArgumentException("Invalid priority " + priority); -+ } + -+ int failures = 0; -+ for (int curr = this.getPriorityVolatile();;) { -+ if ((curr & PRIORITY_SET) != 0) { -+ this.getTaskPlain().setPriority(priority); -+ return; -+ } ++ public Completable handle(final BiFunction function) { ++ return this.handle(function, DEFAULT_EXCEPTION_HANDLER); ++ } + -+ if (curr == (curr = this.compareAndExchangePriorityVolatile(curr, priority.priority))) { -+ return; -+ } ++ public Completable handle(final BiFunction function, ++ final Function exceptionHandler) { ++ Validate.notNull(function, "Function may not be null"); ++ Validate.notNull(exceptionHandler, "Exception handler may not be null"); + -+ // failed, retry ++ final Completable ret = new Completable<>(); ++ this.pushStackOrRun(new HandleTransform<>(null, this, ret, exceptionHandler, function)); ++ return ret; ++ } + -+ ++failures; -+ for (int i = 0; i < failures; ++i) { -+ ConcurrentUtil.backoff(); -+ } -+ } ++ public Completable handleAsync(final BiFunction function) { ++ return this.handleAsync(function, getDefaultExecutor(), DEFAULT_EXCEPTION_HANDLER); + } + -+ public void lowerPriority(final PrioritisedExecutor.Priority priority) { -+ if (!PrioritisedExecutor.Priority.isValidPriority(priority)) { -+ throw new IllegalArgumentException("Invalid priority " + priority); -+ } ++ public Completable handleAsync(final BiFunction function, ++ final Executor executor) { ++ return this.handleAsync(function, executor, DEFAULT_EXCEPTION_HANDLER); ++ } + -+ int failures = 0; -+ for (int curr = this.getPriorityVolatile();;) { -+ if ((curr & PRIORITY_SET) != 0) { -+ this.getTaskPlain().lowerPriority(priority); -+ return; -+ } ++ public Completable handleAsync(final BiFunction function, ++ final Executor executor, ++ final Function exceptionHandler) { ++ Validate.notNull(function, "Function may not be null"); ++ Validate.notNull(exceptionHandler, "Exception handler may not be null"); + -+ if (!priority.isHigherPriority(curr)) { -+ return; -+ } ++ final Completable ret = new Completable<>(); ++ this.pushStackOrRun(new HandleTransform<>(checkExecutor(executor), this, ret, exceptionHandler, function)); ++ return ret; ++ } + -+ if (curr == (curr = this.compareAndExchangePriorityVolatile(curr, priority.priority))) { -+ return; -+ } + -+ // failed, retry ++ public Completable whenComplete(final BiConsumer consumer) { ++ return this.whenComplete(consumer, DEFAULT_EXCEPTION_HANDLER); ++ } + -+ ++failures; -+ for (int i = 0; i < failures; ++i) { -+ ConcurrentUtil.backoff(); -+ } -+ } ++ public Completable whenComplete(final BiConsumer consumer, final Function exceptionHandler) { ++ Validate.notNull(consumer, "Consumer may not be null"); ++ Validate.notNull(exceptionHandler, "Exception handler may not be null"); ++ ++ final Completable ret = new Completable<>(); ++ this.pushStackOrRun(new WhenTransform<>(null, this, ret, exceptionHandler, consumer)); ++ return ret; + } -+} -diff --git a/src/main/java/ca/spottedleaf/concurrentutil/executor/standard/PrioritisedExecutor.java b/src/main/java/ca/spottedleaf/concurrentutil/executor/standard/PrioritisedExecutor.java -new file mode 100644 -index 0000000000000000000000000000000000000000..91beb6f23f257cf265fe3150f760892e605f217a ---- /dev/null -+++ b/src/main/java/ca/spottedleaf/concurrentutil/executor/standard/PrioritisedExecutor.java -@@ -0,0 +1,276 @@ -+package ca.spottedleaf.concurrentutil.executor.standard; + -+import ca.spottedleaf.concurrentutil.executor.BaseExecutor; ++ public Completable whenCompleteAsync(final BiConsumer consumer) { ++ return this.whenCompleteAsync(consumer, getDefaultExecutor(), DEFAULT_EXCEPTION_HANDLER); ++ } + -+/** -+ * Implementation of {@link BaseExecutor} which schedules tasks to be executed by a given priority. -+ * @see BaseExecutor -+ */ -+public interface PrioritisedExecutor extends BaseExecutor { ++ public Completable whenCompleteAsync(final BiConsumer consumer, final Executor executor) { ++ return this.whenCompleteAsync(consumer, executor, DEFAULT_EXCEPTION_HANDLER); ++ } + -+ public static enum Priority { ++ public Completable whenCompleteAsync(final BiConsumer consumer, final Executor executor, ++ final Function exceptionHandler) { ++ Validate.notNull(consumer, "Consumer may not be null"); ++ Validate.notNull(exceptionHandler, "Exception handler may not be null"); + -+ /** -+ * Priority value indicating the task has completed or is being completed. -+ * This priority cannot be used to schedule tasks. -+ */ -+ COMPLETING(-1), ++ final Completable ret = new Completable<>(); ++ this.pushStackOrRun(new WhenTransform<>(checkExecutor(executor), this, ret, exceptionHandler, consumer)); ++ return ret; ++ } + -+ /** -+ * Absolute highest priority, should only be used for when a task is blocking a time-critical thread. -+ */ -+ BLOCKING(), + -+ /** -+ * Should only be used for urgent but not time-critical tasks. -+ */ -+ HIGHEST(), ++ public Completable exceptionally(final Function function) { ++ return this.exceptionally(function, DEFAULT_EXCEPTION_HANDLER); ++ } + -+ /** -+ * Two priorities above normal. -+ */ -+ HIGHER(), ++ public Completable exceptionally(final Function function, final Function exceptionHandler) { ++ Validate.notNull(function, "Function may not be null"); ++ Validate.notNull(exceptionHandler, "Exception handler may not be null"); + -+ /** -+ * One priority above normal. -+ */ -+ HIGH(), ++ final Completable ret = new Completable<>(); ++ this.pushStackOrRun(new ExceptionallyTransform<>(null, this, ret, exceptionHandler, function)); ++ return ret; ++ } + -+ /** -+ * Default priority. -+ */ -+ NORMAL(), ++ public Completable exceptionallyAsync(final Function function) { ++ return this.exceptionallyAsync(function, getDefaultExecutor(), DEFAULT_EXCEPTION_HANDLER); ++ } + -+ /** -+ * One priority below normal. -+ */ -+ LOW(), ++ public Completable exceptionallyAsync(final Function function, final Executor executor) { ++ return this.exceptionallyAsync(function, executor, DEFAULT_EXCEPTION_HANDLER); ++ } + -+ /** -+ * Two priorities below normal. -+ */ -+ LOWER(), ++ public Completable exceptionallyAsync(final Function function, final Executor executor, ++ final Function exceptionHandler) { ++ Validate.notNull(function, "Function may not be null"); ++ Validate.notNull(exceptionHandler, "Exception handler may not be null"); + -+ /** -+ * Use for tasks that should eventually execute, but are not needed to. -+ */ -+ LOWEST(), ++ final Completable ret = new Completable<>(); ++ this.pushStackOrRun(new ExceptionallyTransform<>(checkExecutor(executor), this, ret, exceptionHandler, function)); ++ return ret; ++ } + -+ /** -+ * Use for tasks that can be delayed indefinitely. -+ */ -+ IDLE(); ++ private static final class ExceptionResult { ++ public final Throwable ex; + -+ // returns whether the priority can be scheduled -+ public static boolean isValidPriority(final Priority priority) { -+ return priority != null && priority != Priority.COMPLETING; ++ public ExceptionResult(final Throwable ex) { ++ this.ex = ex; + } ++ } + -+ // returns the higher priority of the two -+ public static Priority max(final Priority p1, final Priority p2) { -+ return p1.isHigherOrEqualPriority(p2) ? p1 : p2; -+ } ++ private static abstract class Transform implements Runnable, CompletableFuture.AsynchronousCompletionTask { + -+ // returns the lower priroity of the two -+ public static Priority min(final Priority p1, final Priority p2) { -+ return p1.isLowerOrEqualPriority(p2) ? p1 : p2; -+ } ++ private Transform next; + -+ public boolean isHigherOrEqualPriority(final Priority than) { -+ return this.priority <= than.priority; -+ } ++ private final Executor executor; ++ protected final Completable from; ++ protected final Completable to; ++ protected final Function exceptionHandler; + -+ public boolean isHigherPriority(final Priority than) { -+ return this.priority < than.priority; ++ protected Transform(final Executor executor, final Completable from, final Completable to, ++ final Function exceptionHandler) { ++ this.executor = executor; ++ this.from = from; ++ this.to = to; ++ this.exceptionHandler = exceptionHandler; + } + -+ public boolean isLowerOrEqualPriority(final Priority than) { -+ return this.priority >= than.priority; -+ } ++ // force interface call to become virtual call ++ @Override ++ public abstract void run(); + -+ public boolean isLowerPriority(final Priority than) { -+ return this.priority > than.priority; ++ protected void failed(final Throwable throwable) { ++ Throwable complete; ++ try { ++ complete = this.exceptionHandler.apply(throwable); ++ } catch (final Throwable thr2) { ++ throwable.addSuppressed(thr2); ++ complete = throwable; ++ } ++ this.to.completeExceptionally(complete); + } + -+ public boolean isHigherOrEqualPriority(final int than) { -+ return this.priority <= than; -+ } ++ public void execute() { ++ if (this.executor == null) { ++ this.run(); ++ return; ++ } + -+ public boolean isHigherPriority(final int than) { -+ return this.priority < than; ++ try { ++ this.executor.execute(this); ++ } catch (final Throwable throwable) { ++ this.failed(throwable); ++ } + } ++ } + -+ public boolean isLowerOrEqualPriority(final int than) { -+ return this.priority >= than; ++ private static final class ApplyTransform extends Transform { ++ ++ private final Function function; ++ ++ public ApplyTransform(final Executor executor, final Completable from, final Completable to, ++ final Function exceptionHandler, ++ final Function function) { ++ super(executor, from, to, exceptionHandler); ++ this.function = function; + } + -+ public boolean isLowerPriority(final int than) { -+ return this.priority > than; ++ @Override ++ public void run() { ++ final Object result = this.from.getResultPlain(); ++ try { ++ if (result instanceof ExceptionResult exRes) { ++ this.to.completeExceptionally(exRes.ex); ++ } else { ++ this.to.complete(this.function.apply((T)unmaskNull(result))); ++ } ++ } catch (final Throwable throwable) { ++ this.failed(throwable); ++ } + } ++ } + -+ public static boolean isHigherOrEqualPriority(final int priority, final int than) { -+ return priority <= than; ++ private static final class AcceptTransform extends Transform { ++ private final Consumer consumer; ++ ++ public AcceptTransform(final Executor executor, final Completable from, final Completable to, ++ final Function exceptionHandler, ++ final Consumer consumer) { ++ super(executor, from, to, exceptionHandler); ++ this.consumer = consumer; + } + -+ public static boolean isHigherPriority(final int priority, final int than) { -+ return priority < than; ++ @Override ++ public void run() { ++ final Object result = this.from.getResultPlain(); ++ try { ++ if (result instanceof ExceptionResult exRes) { ++ this.to.completeExceptionally(exRes.ex); ++ } else { ++ this.consumer.accept((T)unmaskNull(result)); ++ this.to.complete(null); ++ } ++ } catch (final Throwable throwable) { ++ this.failed(throwable); ++ } + } ++ } ++ ++ private static final class RunTransform extends Transform { ++ private final Runnable run; + -+ public static boolean isLowerOrEqualPriority(final int priority, final int than) { -+ return priority >= than; ++ public RunTransform(final Executor executor, final Completable from, final Completable to, ++ final Function exceptionHandler, ++ final Runnable run) { ++ super(executor, from, to, exceptionHandler); ++ this.run = run; + } + -+ public static boolean isLowerPriority(final int priority, final int than) { -+ return priority > than; ++ @Override ++ public void run() { ++ final Object result = this.from.getResultPlain(); ++ try { ++ if (result instanceof ExceptionResult exRes) { ++ this.to.completeExceptionally(exRes.ex); ++ } else { ++ this.run.run(); ++ this.to.complete(null); ++ } ++ } catch (final Throwable throwable) { ++ this.failed(throwable); ++ } + } ++ } + -+ static final Priority[] PRIORITIES = Priority.values(); ++ private static final class HandleTransform extends Transform { + -+ /** includes special priorities */ -+ public static final int TOTAL_PRIORITIES = PRIORITIES.length; ++ private final BiFunction function; + -+ public static final int TOTAL_SCHEDULABLE_PRIORITIES = TOTAL_PRIORITIES - 1; ++ public HandleTransform(final Executor executor, final Completable from, final Completable to, ++ final Function exceptionHandler, ++ final BiFunction function) { ++ super(executor, from, to, exceptionHandler); ++ this.function = function; ++ } + -+ public static Priority getPriority(final int priority) { -+ return PRIORITIES[priority + 1]; ++ @Override ++ public void run() { ++ final Object result = this.from.getResultPlain(); ++ try { ++ if (result instanceof ExceptionResult exRes) { ++ this.to.complete(this.function.apply(null, exRes.ex)); ++ } else { ++ this.to.complete(this.function.apply((T)unmaskNull(result), null)); ++ } ++ } catch (final Throwable throwable) { ++ this.failed(throwable); ++ } + } ++ } ++ ++ private static final class WhenTransform extends Transform { + -+ private static int priorityCounter; ++ private final BiConsumer consumer; ++ ++ public WhenTransform(final Executor executor, final Completable from, final Completable to, ++ final Function exceptionHandler, ++ final BiConsumer consumer) { ++ super(executor, from, to, exceptionHandler); ++ this.consumer = consumer; ++ } + -+ private static int nextCounter() { -+ return priorityCounter++; ++ @Override ++ public void run() { ++ final Object result = this.from.getResultPlain(); ++ try { ++ if (result instanceof ExceptionResult exRes) { ++ this.consumer.accept(null, exRes.ex); ++ this.to.completeExceptionally(exRes.ex); ++ } else { ++ final T unmasked = (T)unmaskNull(result); ++ this.consumer.accept(unmasked, null); ++ this.to.complete(unmasked); ++ } ++ } catch (final Throwable throwable) { ++ this.failed(throwable); ++ } + } ++ } + -+ public final int priority; ++ private static final class ExceptionallyTransform extends Transform { ++ private final Function function; + -+ Priority() { -+ this(nextCounter()); ++ public ExceptionallyTransform(final Executor executor, final Completable from, final Completable to, ++ final Function exceptionHandler, ++ final Function function) { ++ super(executor, from, to, exceptionHandler); ++ this.function = function; + } + -+ Priority(final int priority) { -+ this.priority = priority; ++ @Override ++ public void run() { ++ final Object result = this.from.getResultPlain(); ++ try { ++ if (result instanceof ExceptionResult exRes) { ++ this.to.complete(this.function.apply(exRes.ex)); ++ } else { ++ this.to.complete((T)unmaskNull(result)); ++ } ++ } catch (final Throwable throwable) { ++ this.failed(throwable); ++ } + } + } + -+ /** -+ * Executes the next available task. -+ *

    -+ * If there is a task with priority {@link PrioritisedExecutor.Priority#BLOCKING} available, then that such task is executed. -+ *

    ++ private static final class UnparkTransform extends Transform implements ForkJoinPool.ManagedBlocker { ++ ++ private volatile Thread thread; ++ ++ public UnparkTransform(final Completable from, final Thread target) { ++ super(null, from, null, null); ++ this.thread = target; ++ } ++ ++ @Override ++ public void run() { ++ final Thread t = this.thread; ++ this.thread = null; ++ LockSupport.unpark(t); ++ } ++ ++ @Override ++ public boolean block() throws InterruptedException { ++ while (!this.isReleasable()) { ++ if (Thread.interrupted()) { ++ throw new InterruptedException(); ++ } ++ LockSupport.park(this); ++ } ++ ++ return true; ++ } ++ ++ @Override ++ public boolean isReleasable() { ++ return this.thread == null; ++ } ++ } ++} +diff --git a/src/main/java/ca/spottedleaf/concurrentutil/executor/Cancellable.java b/src/main/java/ca/spottedleaf/concurrentutil/executor/Cancellable.java +new file mode 100644 +index 0000000000000000000000000000000000000000..11449056361bb6c5a055f543cdd135c4113757c6 +--- /dev/null ++++ b/src/main/java/ca/spottedleaf/concurrentutil/executor/Cancellable.java +@@ -0,0 +1,14 @@ ++package ca.spottedleaf.concurrentutil.executor; ++ ++/** ++ * Interface specifying that something can be cancelled. ++ */ ++public interface Cancellable { ++ ++ /** ++ * Tries to cancel this task. If the task is in a stage that is too late to be cancelled, then this function ++ * will return {@code false}. If the task is already cancelled, then this function returns {@code false}. Only ++ * when this function successfully stops this task from being completed will it return {@code true}. ++ */ ++ public boolean cancel(); ++} +diff --git a/src/main/java/ca/spottedleaf/concurrentutil/executor/PrioritisedExecutor.java b/src/main/java/ca/spottedleaf/concurrentutil/executor/PrioritisedExecutor.java +new file mode 100644 +index 0000000000000000000000000000000000000000..17cbaee1e89bd3f6d905e640d20d0119ab0570a0 +--- /dev/null ++++ b/src/main/java/ca/spottedleaf/concurrentutil/executor/PrioritisedExecutor.java +@@ -0,0 +1,271 @@ ++package ca.spottedleaf.concurrentutil.executor; ++ ++import ca.spottedleaf.concurrentutil.util.Priority; ++ ++public interface PrioritisedExecutor { ++ ++ /** ++ * Returns the number of tasks that have been scheduled are pending to be scheduled. ++ */ ++ public long getTotalTasksScheduled(); ++ ++ /** ++ * Returns the number of tasks that have been executed. ++ */ ++ public long getTotalTasksExecuted(); ++ ++ /** ++ * Generates the next suborder id. ++ * @return The next suborder id. ++ */ ++ public long generateNextSubOrder(); ++ ++ /** ++ * Executes the next available task. ++ *

    ++ * If there is a task with priority {@link Priority#BLOCKING} available, then that such task is executed. ++ *

    + *

    -+ * If there is a task with priority {@link PrioritisedExecutor.Priority#IDLE} available then that task is only executed ++ * If there is a task with priority {@link Priority#IDLE} available then that task is only executed + * when there are no other tasks available with a higher priority. + *

    + *

    -+ * If there are no tasks that have priority {@link PrioritisedExecutor.Priority#BLOCKING} or {@link PrioritisedExecutor.Priority#IDLE}, then ++ * If there are no tasks that have priority {@link Priority#BLOCKING} or {@link Priority#IDLE}, then + * this function will be biased to execute tasks that have higher priorities. + *

    + * + * @return {@code true} if a task was executed, {@code false} otherwise + * @throws IllegalStateException If the current thread is not allowed to execute a task + */ -+ @Override + public boolean executeTask() throws IllegalStateException; + + /** ++ * Prevent further additions to this executor. Attempts to add after this call has completed (potentially during) will ++ * result in {@link IllegalStateException} being thrown. ++ *

    ++ * This operation is atomic with respect to other shutdown calls ++ *

    ++ *

    ++ * After this call has completed, regardless of return value, this executor will be shutdown. ++ *

    ++ * ++ * @return {@code true} if the executor was shutdown, {@code false} if it has shut down already ++ * @see #isShutdown() ++ */ ++ public boolean shutdown(); ++ ++ /** ++ * Returns whether this executor has shut down. Effectively, returns whether new tasks will be rejected. ++ * This method does not indicate whether all the tasks scheduled have been executed. ++ * @return Returns whether this executor has shut down. ++ */ ++ public boolean isShutdown(); ++ ++ /** + * Queues or executes a task at {@link Priority#NORMAL} priority. + * @param task The task to run. + * -+ * @throws IllegalStateException If this queue has shutdown. ++ * @throws IllegalStateException If this executor has shutdown. + * @throws NullPointerException If the task is null + * @return {@code null} if the current thread immediately executed the task, else returns the prioritised task -+ * associated with the parameter ++ * associated with the parameter + */ -+ public default PrioritisedTask queueRunnable(final Runnable task) { -+ return this.queueRunnable(task, Priority.NORMAL); -+ } ++ public PrioritisedTask queueTask(final Runnable task); + + /** + * Queues or executes a task. @@ -2313,50 +2397,107 @@ index 0000000000000000000000000000000000000000..91beb6f23f257cf265fe3150f760892e + * @param task The task to run. + * @param priority The priority for the task. + * -+ * @throws IllegalStateException If this queue has shutdown. ++ * @throws IllegalStateException If this executor has shutdown. + * @throws NullPointerException If the task is null + * @throws IllegalArgumentException If the priority is invalid. + * @return {@code null} if the current thread immediately executed the task, else returns the prioritised task -+ * associated with the parameter ++ * associated with the parameter + */ -+ public PrioritisedTask queueRunnable(final Runnable task, final Priority priority); ++ public PrioritisedTask queueTask(final Runnable task, final Priority priority); + + /** -+ * Creates, but does not execute or queue the task. The task must later be queued via {@link BaseTask#queue()}. ++ * Queues or executes a task. + * + * @param task The task to run. ++ * @param priority The priority for the task. ++ * @param subOrder The task's suborder. + * -+ * @throws IllegalStateException If this queue has shutdown. ++ * @throws IllegalStateException If this executor has shutdown. + * @throws NullPointerException If the task is null + * @throws IllegalArgumentException If the priority is invalid. -+ * @throws UnsupportedOperationException If this executor does not support lazily queueing tasks -+ * @return The prioritised task associated with the parameters ++ * @return {@code null} if the current thread immediately executed the task, else returns the prioritised task ++ * associated with the parameter + */ -+ public default PrioritisedTask createTask(final Runnable task) { -+ return this.createTask(task, Priority.NORMAL); -+ } ++ public PrioritisedTask queueTask(final Runnable task, final Priority priority, final long subOrder); ++ ++ /** ++ * Creates, but does not queue or execute, a task at {@link Priority#NORMAL} priority. ++ * @param task The task to run. ++ * ++ * @throws NullPointerException If the task is null ++ * @return {@code null} if the current thread immediately executed the task, else returns the prioritised task ++ * associated with the parameter ++ */ ++ public PrioritisedTask createTask(final Runnable task); + + /** -+ * Creates, but does not execute or queue the task. The task must later be queued via {@link BaseTask#queue()}. ++ * Creates, but does not queue or execute, a task at {@link Priority#NORMAL} priority. + * + * @param task The task to run. + * @param priority The priority for the task. + * -+ * @throws IllegalStateException If this queue has shutdown. + * @throws NullPointerException If the task is null + * @throws IllegalArgumentException If the priority is invalid. -+ * @throws UnsupportedOperationException If this executor does not support lazily queueing tasks -+ * @return The prioritised task associated with the parameters ++ * @return {@code null} if the current thread immediately executed the task, else returns the prioritised task ++ * associated with the parameter + */ + public PrioritisedTask createTask(final Runnable task, final Priority priority); + + /** -+ * Extension of {@link ca.spottedleaf.concurrentutil.executor.BaseExecutor.BaseTask} which adds functions -+ * to retrieve and modify the task's associated priority. ++ * Creates, but does not queue or execute, a task at {@link Priority#NORMAL} priority. ++ * ++ * @param task The task to run. ++ * @param priority The priority for the task. ++ * @param subOrder The task's suborder. + * -+ * @see ca.spottedleaf.concurrentutil.executor.BaseExecutor.BaseTask ++ * @throws NullPointerException If the task is null ++ * @throws IllegalArgumentException If the priority is invalid. ++ * @return {@code null} if the current thread immediately executed the task, else returns the prioritised task ++ * associated with the parameter + */ -+ public static interface PrioritisedTask extends BaseTask { ++ public PrioritisedTask createTask(final Runnable task, final Priority priority, final long subOrder); ++ ++ public static interface PrioritisedTask extends Cancellable { ++ ++ /** ++ * Returns the executor associated with this task. ++ * @return The executor associated with this task. ++ */ ++ public PrioritisedExecutor getExecutor(); ++ ++ /** ++ * Causes a lazily queued task to become queued or executed ++ * ++ * @throws IllegalStateException If the backing executor has shutdown ++ * @return {@code true} If the task was queued, {@code false} if the task was already queued/cancelled/executed ++ */ ++ public boolean queue(); ++ ++ /** ++ * Returns whether this task has been queued and is not completing. ++ * @return {@code true} If the task has been queued, {@code false} if the task has not been queued or is marked ++ * as completing. ++ */ ++ public boolean isQueued(); ++ ++ /** ++ * Forces this task to be marked as completed. ++ * ++ * @return {@code true} if the task was cancelled, {@code false} if the task has already completed ++ * or is being completed. ++ */ ++ @Override ++ public boolean cancel(); ++ ++ /** ++ * Executes this task. This will also mark the task as completing. ++ *

    ++ * Exceptions thrown from the runnable will be rethrown. ++ *

    ++ * ++ * @return {@code true} if this task was executed, {@code false} if it was already marked as completed. ++ */ ++ public boolean execute(); + + /** + * Returns the current priority. Note that {@link Priority#COMPLETING} will be returned @@ -2371,7 +2512,7 @@ index 0000000000000000000000000000000000000000..91beb6f23f257cf265fe3150f760892e + * + * @throws IllegalArgumentException If the priority is invalid + * @return {@code true} if successful, {@code false} if this task is completing or has completed or the queue -+ * this task was scheduled on was shutdown, or if the priority was already at the specified level. ++ * this task was scheduled on was shutdown, or if the priority was already at the specified level. + */ + public boolean setPriority(final Priority priority); + @@ -2381,7 +2522,8 @@ index 0000000000000000000000000000000000000000..91beb6f23f257cf265fe3150f760892e + * @param priority Priority specified + * + * @throws IllegalArgumentException If the priority is invalid -+ * @return {@code false} if the current task is completing, {@code true} if the priority was raised to the specified level or was already at the specified level or higher. ++ * @return {@code false} if the current task is completing, {@code true} if the priority was raised to the ++ * specified level or was already at the specified level or higher. + */ + public boolean raisePriority(final Priority priority); + @@ -2391,7417 +2533,7165 @@ index 0000000000000000000000000000000000000000..91beb6f23f257cf265fe3150f760892e + * @param priority Priority specified + * + * @throws IllegalArgumentException If the priority is invalid -+ * @return {@code false} if the current task is completing, {@code true} if the priority was lowered to the specified level or was already at the specified level or lower. ++ * @return {@code false} if the current task is completing, {@code true} if the priority was lowered to the ++ * specified level or was already at the specified level or lower. + */ + public boolean lowerPriority(final Priority priority); ++ ++ /** ++ * Returns the suborder id associated with this task. ++ * @return The suborder id associated with this task. ++ */ ++ public long getSubOrder(); ++ ++ /** ++ * Sets the suborder id associated with this task. Ths function has no effect when this task ++ * is completing or is completed. ++ * ++ * @param subOrder Specified new sub order. ++ * ++ * @return {@code true} if successful, {@code false} if this task is completing or has completed or the queue ++ * this task was scheduled on was shutdown, or if the current suborder is the same as the new sub order. ++ */ ++ public boolean setSubOrder(final long subOrder); ++ ++ /** ++ * Attempts to raise the suborder to the suborder specified. ++ * ++ * @param subOrder Specified new sub order. ++ * ++ * @return {@code false} if the current task is completing, {@code true} if the suborder was raised to the ++ * specified suborder or was already at the specified suborder or higher. ++ */ ++ public boolean raiseSubOrder(final long subOrder); ++ ++ /** ++ * Attempts to lower the suborder to the suborder specified. ++ * ++ * @param subOrder Specified new sub order. ++ * ++ * @return {@code false} if the current task is completing, {@code true} if the suborder was lowered to the ++ * specified suborder or was already at the specified suborder or lower. ++ */ ++ public boolean lowerSubOrder(final long subOrder); ++ ++ /** ++ * Sets the priority and suborder id associated with this task. Ths function has no effect when this task ++ * is completing or is completed. ++ * ++ * @param priority Priority specified ++ * @param subOrder Specified new sub order. ++ * @return {@code true} if successful, {@code false} if this task is completing or has completed or the queue ++ * this task was scheduled on was shutdown, or if the current priority and suborder are the same as ++ * the parameters. ++ */ ++ public boolean setPriorityAndSubOrder(final Priority priority, final long subOrder); + } +} -diff --git a/src/main/java/ca/spottedleaf/concurrentutil/executor/standard/PrioritisedQueueExecutorThread.java b/src/main/java/ca/spottedleaf/concurrentutil/executor/standard/PrioritisedQueueExecutorThread.java +diff --git a/src/main/java/ca/spottedleaf/concurrentutil/executor/queue/PrioritisedTaskQueue.java b/src/main/java/ca/spottedleaf/concurrentutil/executor/queue/PrioritisedTaskQueue.java new file mode 100644 -index 0000000000000000000000000000000000000000..d1683ba6350e530373944f98192c0f2baf241e70 +index 0000000000000000000000000000000000000000..edb8c6611bdc9aced2714b963e00bbb7829603d2 --- /dev/null -+++ b/src/main/java/ca/spottedleaf/concurrentutil/executor/standard/PrioritisedQueueExecutorThread.java -@@ -0,0 +1,301 @@ -+package ca.spottedleaf.concurrentutil.executor.standard; ++++ b/src/main/java/ca/spottedleaf/concurrentutil/executor/queue/PrioritisedTaskQueue.java +@@ -0,0 +1,454 @@ ++package ca.spottedleaf.concurrentutil.executor.queue; + ++import ca.spottedleaf.concurrentutil.executor.PrioritisedExecutor; +import ca.spottedleaf.concurrentutil.util.ConcurrentUtil; -+import org.slf4j.Logger; -+import org.slf4j.LoggerFactory; ++import ca.spottedleaf.concurrentutil.util.Priority; +import java.lang.invoke.VarHandle; -+import java.util.concurrent.locks.LockSupport; -+ -+/** -+ * Thread which will continuously drain from a specified queue. -+ *

    -+ * Note: When using this thread, queue additions to the underlying {@link #queue} are not sufficient to get this thread -+ * to execute the task. The function {@link #notifyTasks()} must be used after scheduling a task. For expected behaviour -+ * of task scheduling (thread wakes up after tasks are scheduled), use the methods provided on {@link PrioritisedExecutor} -+ * methods. -+ *

    -+ */ -+public class PrioritisedQueueExecutorThread extends Thread implements PrioritisedExecutor { ++import java.util.Comparator; ++import java.util.Map; ++import java.util.concurrent.ConcurrentSkipListMap; ++import java.util.concurrent.atomic.AtomicBoolean; ++import java.util.concurrent.atomic.AtomicLong; + -+ private static final Logger LOGGER = LoggerFactory.getLogger(PrioritisedQueueExecutorThread.class); ++public final class PrioritisedTaskQueue implements PrioritisedExecutor { + -+ protected final PrioritisedExecutor queue; ++ /** ++ * Required for tie-breaking in the queue ++ */ ++ private final AtomicLong taskIdGenerator = new AtomicLong(); ++ private final AtomicLong scheduledTasks = new AtomicLong(); ++ private final AtomicLong executedTasks = new AtomicLong(); ++ private final AtomicLong subOrderGenerator = new AtomicLong(); ++ private final AtomicBoolean shutdown = new AtomicBoolean(); ++ private final ConcurrentSkipListMap tasks = new ConcurrentSkipListMap<>(PrioritisedQueuedTask.COMPARATOR); + -+ protected volatile boolean threadShutdown; ++ @Override ++ public long getTotalTasksScheduled() { ++ return this.scheduledTasks.get(); ++ } + -+ protected volatile boolean threadParked; -+ protected static final VarHandle THREAD_PARKED_HANDLE = ConcurrentUtil.getVarHandle(PrioritisedQueueExecutorThread.class, "threadParked", boolean.class); ++ @Override ++ public long getTotalTasksExecuted() { ++ return this.executedTasks.get(); ++ } + -+ protected volatile boolean halted; ++ @Override ++ public long generateNextSubOrder() { ++ return this.subOrderGenerator.getAndIncrement(); ++ } + -+ protected final long spinWaitTime; ++ @Override ++ public boolean shutdown() { ++ return !this.shutdown.getAndSet(true); ++ } + -+ static final long DEFAULT_SPINWAIT_TIME = (long)(0.1e6);// 0.1ms ++ @Override ++ public boolean isShutdown() { ++ return this.shutdown.get(); ++ } + -+ public PrioritisedQueueExecutorThread(final PrioritisedExecutor queue) { -+ this(queue, DEFAULT_SPINWAIT_TIME); // 0.1ms ++ public PrioritisedTask peekFirst() { ++ final Map.Entry firstEntry = this.tasks.firstEntry(); ++ return firstEntry == null ? null : firstEntry.getKey().task; + } + -+ public PrioritisedQueueExecutorThread(final PrioritisedExecutor queue, final long spinWaitTime) { // in ns -+ this.queue = queue; -+ this.spinWaitTime = spinWaitTime; ++ public Priority getHighestPriority() { ++ final Map.Entry firstEntry = this.tasks.firstEntry(); ++ return firstEntry == null ? null : Priority.getPriority(firstEntry.getKey().priority); + } + -+ @Override -+ public void run() { -+ final long spinWaitTime = this.spinWaitTime; ++ public boolean hasNoScheduledTasks() { ++ final long executedTasks = this.executedTasks.get(); ++ final long scheduledTasks = this.scheduledTasks.get(); + -+ main_loop: -+ for (;;) { -+ this.pollTasks(); ++ return executedTasks == scheduledTasks; ++ } + -+ // spinwait ++ public PrioritySubOrderPair getHighestPrioritySubOrder() { ++ final Map.Entry firstEntry = this.tasks.firstEntry(); ++ if (firstEntry == null) { ++ return null; ++ } + -+ final long start = System.nanoTime(); ++ final PrioritisedQueuedTask.Holder holder = firstEntry.getKey(); + -+ for (;;) { -+ // If we are interrupted for any reason, park() will always return immediately. Clear so that we don't needlessly use cpu in such an event. -+ Thread.interrupted(); -+ Thread.yield(); -+ LockSupport.parkNanos("Spinwaiting on tasks", 10_000L); // 10us ++ return new PrioritySubOrderPair(Priority.getPriority(holder.priority), holder.subOrder); ++ } + -+ if (this.pollTasks()) { -+ // restart loop, found tasks -+ continue main_loop; ++ public Runnable pollTask() { ++ for (;;) { ++ final Map.Entry firstEntry = this.tasks.pollFirstEntry(); ++ if (firstEntry != null) { ++ final PrioritisedQueuedTask.Holder task = firstEntry.getKey(); ++ task.markRemoved(); ++ if (!task.task.cancel()) { ++ continue; + } ++ return task.task.execute; ++ } + -+ if (this.handleClose()) { -+ return; // we're done -+ } ++ return null; ++ } ++ } + -+ if ((System.nanoTime() - start) >= spinWaitTime) { -+ break; ++ @Override ++ public boolean executeTask() { ++ for (;;) { ++ final Map.Entry firstEntry = this.tasks.pollFirstEntry(); ++ if (firstEntry != null) { ++ final PrioritisedQueuedTask.Holder task = firstEntry.getKey(); ++ task.markRemoved(); ++ if (!task.task.execute()) { ++ continue; + } ++ return true; + } + -+ if (this.handleClose()) { -+ return; -+ } ++ return false; ++ } ++ } + -+ this.setThreadParkedVolatile(true); ++ @Override ++ public PrioritisedTask createTask(final Runnable task) { ++ return this.createTask(task, Priority.NORMAL, this.generateNextSubOrder()); ++ } + -+ // We need to parse here to avoid a race condition where a thread queues a task before we set parked to true -+ // (i.e it will not notify us) -+ if (this.pollTasks()) { -+ this.setThreadParkedVolatile(false); -+ continue; -+ } ++ @Override ++ public PrioritisedTask createTask(final Runnable task, final Priority priority) { ++ return this.createTask(task, priority, this.generateNextSubOrder()); ++ } + -+ if (this.handleClose()) { -+ return; -+ } ++ @Override ++ public PrioritisedTask createTask(final Runnable task, final Priority priority, final long subOrder) { ++ return new PrioritisedQueuedTask(task, priority, subOrder); ++ } + -+ // we don't need to check parked before sleeping, but we do need to check parked in a do-while loop -+ // LockSupport.park() can fail for any reason -+ while (this.getThreadParkedVolatile()) { -+ Thread.interrupted(); -+ LockSupport.park("Waiting on tasks"); -+ } -+ } ++ @Override ++ public PrioritisedTask queueTask(final Runnable task) { ++ return this.queueTask(task, Priority.NORMAL, this.generateNextSubOrder()); + } + -+ /** -+ * Attempts to poll as many tasks as possible, returning when finished. -+ * @return Whether any tasks were executed. -+ */ -+ protected boolean pollTasks() { -+ boolean ret = false; -+ -+ for (;;) { -+ if (this.halted) { -+ break; -+ } -+ try { -+ if (!this.queue.executeTask()) { -+ break; -+ } -+ ret = true; -+ } catch (final ThreadDeath death) { -+ throw death; // goodbye world... -+ } catch (final Throwable throwable) { -+ LOGGER.error("Exception thrown from prioritized runnable task in thread '" + this.getName() + "'", throwable); -+ } -+ } -+ -+ return ret; ++ @Override ++ public PrioritisedTask queueTask(final Runnable task, final Priority priority) { ++ return this.queueTask(task, priority, this.generateNextSubOrder()); + } + -+ protected boolean handleClose() { -+ if (this.threadShutdown) { -+ this.pollTasks(); // this ensures we've emptied the queue -+ return true; -+ } -+ return false; -+ } ++ @Override ++ public PrioritisedTask queueTask(final Runnable task, final Priority priority, final long subOrder) { ++ final PrioritisedQueuedTask ret = new PrioritisedQueuedTask(task, priority, subOrder); + -+ /** -+ * Notify this thread that a task has been added to its queue -+ * @return {@code true} if this thread was waiting for tasks, {@code false} if it is executing tasks -+ */ -+ public boolean notifyTasks() { -+ if (this.getThreadParkedVolatile() && this.exchangeThreadParkedVolatile(false)) { -+ LockSupport.unpark(this); -+ return true; -+ } -+ return false; -+ } ++ ret.queue(); + -+ @Override -+ public PrioritisedTask createTask(final Runnable task, final Priority priority) { -+ final PrioritisedTask queueTask = this.queue.createTask(task, priority); ++ return ret; ++ } + -+ // need to override queue() to notify us of tasks -+ return new PrioritisedTask() { -+ @Override -+ public Priority getPriority() { -+ return queueTask.getPriority(); ++ private final class PrioritisedQueuedTask implements PrioritisedExecutor.PrioritisedTask { ++ public static final Comparator COMPARATOR = (final PrioritisedQueuedTask.Holder t1, final PrioritisedQueuedTask.Holder t2) -> { ++ final int priorityCompare = t1.priority - t2.priority; ++ if (priorityCompare != 0) { ++ return priorityCompare; + } + -+ @Override -+ public boolean setPriority(final Priority priority) { -+ return queueTask.setPriority(priority); ++ final int subOrderCompare = Long.compare(t1.subOrder, t2.subOrder); ++ if (subOrderCompare != 0) { ++ return subOrderCompare; + } + -+ @Override -+ public boolean raisePriority(final Priority priority) { -+ return queueTask.raisePriority(priority); -+ } ++ return Long.compare(t1.id, t2.id); ++ }; + -+ @Override -+ public boolean lowerPriority(final Priority priority) { -+ return queueTask.lowerPriority(priority); -+ } ++ private static final class Holder { ++ private final PrioritisedQueuedTask task; ++ private final int priority; ++ private final long subOrder; ++ private final long id; + -+ @Override -+ public boolean queue() { -+ final boolean ret = queueTask.queue(); -+ if (ret) { -+ PrioritisedQueueExecutorThread.this.notifyTasks(); -+ } -+ return ret; -+ } ++ private volatile boolean removed; ++ private static final VarHandle REMOVED_HANDLE = ConcurrentUtil.getVarHandle(Holder.class, "removed", boolean.class); + -+ @Override -+ public boolean cancel() { -+ return queueTask.cancel(); ++ private Holder(final PrioritisedQueuedTask task, final int priority, final long subOrder, ++ final long id) { ++ this.task = task; ++ this.priority = priority; ++ this.subOrder = subOrder; ++ this.id = id; + } + -+ @Override -+ public boolean execute() { -+ return queueTask.execute(); ++ /** ++ * Returns true if marked as removed ++ */ ++ public boolean markRemoved() { ++ return !(boolean)REMOVED_HANDLE.getAndSet((Holder)this, (boolean)true); + } -+ }; -+ } -+ -+ @Override -+ public PrioritisedTask queueRunnable(final Runnable task, final Priority priority) { -+ final PrioritisedTask ret = this.queue.queueRunnable(task, priority); -+ -+ this.notifyTasks(); ++ } + -+ return ret; -+ } ++ private final long id; ++ private final Runnable execute; + -+ @Override -+ public boolean haveAllTasksExecuted() { -+ return this.queue.haveAllTasksExecuted(); -+ } ++ private Priority priority; ++ private long subOrder; ++ private Holder holder; + -+ @Override -+ public long getTotalTasksExecuted() { -+ return this.queue.getTotalTasksExecuted(); -+ } ++ public PrioritisedQueuedTask(final Runnable execute, final Priority priority, final long subOrder) { ++ if (!Priority.isValidPriority(priority)) { ++ throw new IllegalArgumentException("Invalid priority " + priority); ++ } + -+ @Override -+ public long getTotalTasksScheduled() { -+ return this.queue.getTotalTasksScheduled(); -+ } ++ this.execute = execute; ++ this.priority = priority; ++ this.subOrder = subOrder; ++ this.id = PrioritisedTaskQueue.this.taskIdGenerator.getAndIncrement(); ++ } + -+ /** -+ * {@inheritDoc} -+ * @throws IllegalStateException If the current thread is {@code this} thread, or the underlying queue throws this exception. -+ */ -+ @Override -+ public void waitUntilAllExecuted() throws IllegalStateException { -+ if (Thread.currentThread() == this) { -+ throw new IllegalStateException("Cannot block on our own queue"); ++ @Override ++ public PrioritisedExecutor getExecutor() { ++ return PrioritisedTaskQueue.this; + } -+ this.queue.waitUntilAllExecuted(); -+ } + -+ /** -+ * {@inheritDoc} -+ * @throws IllegalStateException Always -+ */ -+ @Override -+ public boolean executeTask() throws IllegalStateException { -+ throw new IllegalStateException(); -+ } ++ @Override ++ public boolean queue() { ++ synchronized (this) { ++ if (this.holder != null || this.priority == Priority.COMPLETING) { ++ return false; ++ } + -+ /** -+ * Closes this queue executor's queue. Optionally waits for all tasks in queue to be executed if {@code wait} is true. -+ *

    -+ * This function is MT-Safe. -+ *

    -+ * @param wait If this call is to wait until the queue is empty and there are no tasks executing in the queue. -+ * @param killQueue Whether to shutdown this thread's queue -+ * @return whether this thread shut down the queue -+ * @see #halt(boolean) -+ */ -+ public boolean close(final boolean wait, final boolean killQueue) { -+ final boolean ret = killQueue && this.queue.shutdown(); -+ this.threadShutdown = true; ++ if (PrioritisedTaskQueue.this.isShutdown()) { ++ throw new IllegalStateException("Queue is shutdown"); ++ } + -+ // force thread to respond to the shutdown -+ this.setThreadParkedVolatile(false); -+ LockSupport.unpark(this); ++ final Holder holder = new Holder(this, this.priority.priority, this.subOrder, this.id); ++ this.holder = holder; + -+ if (wait) { -+ this.waitUntilAllExecuted(); -+ } ++ PrioritisedTaskQueue.this.scheduledTasks.getAndIncrement(); ++ PrioritisedTaskQueue.this.tasks.put(holder, Boolean.TRUE); ++ } + -+ return ret; -+ } ++ if (PrioritisedTaskQueue.this.isShutdown()) { ++ this.cancel(); ++ throw new IllegalStateException("Queue is shutdown"); ++ } + + -+ /** -+ * Causes this thread to exit without draining the queue. To ensure tasks are completed, use {@link #close(boolean, boolean)}. -+ *

    -+ * This is not safe to call with {@link #close(boolean, boolean)} if wait = true, in which case -+ * the waiting thread may block indefinitely. -+ *

    -+ *

    -+ * This function is MT-Safe. -+ *

    -+ * @param killQueue Whether to shutdown this thread's queue -+ * @see #close(boolean, boolean) -+ */ -+ public void halt(final boolean killQueue) { -+ if (killQueue) { -+ this.queue.shutdown(); ++ return true; + } -+ this.threadShutdown = true; -+ this.halted = true; + -+ // force thread to respond to the shutdown -+ this.setThreadParkedVolatile(false); -+ LockSupport.unpark(this); -+ } ++ @Override ++ public boolean isQueued() { ++ synchronized (this) { ++ return this.holder != null && this.priority != Priority.COMPLETING; ++ } ++ } + -+ protected final boolean getThreadParkedVolatile() { -+ return (boolean)THREAD_PARKED_HANDLE.getVolatile(this); -+ } ++ @Override ++ public boolean cancel() { ++ synchronized (this) { ++ if (this.priority == Priority.COMPLETING) { ++ return false; ++ } + -+ protected final boolean exchangeThreadParkedVolatile(final boolean value) { -+ return (boolean)THREAD_PARKED_HANDLE.getAndSet(this, value); -+ } ++ this.priority = Priority.COMPLETING; + -+ protected final void setThreadParkedVolatile(final boolean value) { -+ THREAD_PARKED_HANDLE.setVolatile(this, value); -+ } -+} -diff --git a/src/main/java/ca/spottedleaf/concurrentutil/executor/standard/PrioritisedThreadPool.java b/src/main/java/ca/spottedleaf/concurrentutil/executor/standard/PrioritisedThreadPool.java -new file mode 100644 -index 0000000000000000000000000000000000000000..2ba36e29d0d8693f2f5e6c6d195ca27f2a5099aa ---- /dev/null -+++ b/src/main/java/ca/spottedleaf/concurrentutil/executor/standard/PrioritisedThreadPool.java -@@ -0,0 +1,632 @@ -+package ca.spottedleaf.concurrentutil.executor.standard; ++ if (this.holder != null) { ++ if (this.holder.markRemoved()) { ++ PrioritisedTaskQueue.this.tasks.remove(this.holder); ++ } ++ PrioritisedTaskQueue.this.executedTasks.getAndIncrement(); ++ } + -+import it.unimi.dsi.fastutil.objects.ReferenceOpenHashSet; -+import org.slf4j.Logger; -+import org.slf4j.LoggerFactory; -+import java.util.ArrayList; -+import java.util.Arrays; -+import java.util.Comparator; -+import java.util.TreeSet; -+import java.util.concurrent.atomic.AtomicBoolean; -+import java.util.function.BiConsumer; ++ return true; ++ } ++ } + -+public final class PrioritisedThreadPool { ++ @Override ++ public boolean execute() { ++ final boolean increaseExecuted; + -+ private static final Logger LOGGER = LoggerFactory.getLogger(PrioritisedThreadPool.class); ++ synchronized (this) { ++ if (this.priority == Priority.COMPLETING) { ++ return false; ++ } + -+ private final PrioritisedThread[] threads; -+ private final TreeSet queues = new TreeSet<>(PrioritisedPoolExecutorImpl.comparator()); -+ private final String name; -+ private final long queueMaxHoldTime; ++ this.priority = Priority.COMPLETING; + -+ private final ReferenceOpenHashSet nonShutdownQueues = new ReferenceOpenHashSet<>(); -+ private final ReferenceOpenHashSet activeQueues = new ReferenceOpenHashSet<>(); ++ if (increaseExecuted = (this.holder != null)) { ++ if (this.holder.markRemoved()) { ++ PrioritisedTaskQueue.this.tasks.remove(this.holder); ++ } ++ } ++ } + -+ private boolean shutdown; ++ try { ++ this.execute.run(); ++ return true; ++ } finally { ++ if (increaseExecuted) { ++ PrioritisedTaskQueue.this.executedTasks.getAndIncrement(); ++ } ++ } ++ } + -+ private long schedulingIdGenerator; ++ @Override ++ public Priority getPriority() { ++ synchronized (this) { ++ return this.priority; ++ } ++ } + -+ private static final long DEFAULT_QUEUE_HOLD_TIME = (long)(5.0e6); ++ @Override ++ public boolean setPriority(final Priority priority) { ++ synchronized (this) { ++ if (this.priority == Priority.COMPLETING || this.priority == priority) { ++ return false; ++ } + -+ /** -+ * @param name Specified debug name of this thread pool -+ * @param threads The number of threads to use -+ */ -+ public PrioritisedThreadPool(final String name, final int threads) { -+ this(name, threads, null); -+ } ++ this.priority = priority; + -+ /** -+ * @param name Specified debug name of this thread pool -+ * @param threads The number of threads to use -+ * @param threadModifier Invoked for each created thread with its incremental id before starting them -+ */ -+ public PrioritisedThreadPool(final String name, final int threads, final BiConsumer threadModifier) { -+ this(name, threads, threadModifier, DEFAULT_QUEUE_HOLD_TIME); // 5ms -+ } ++ if (this.holder != null) { ++ if (this.holder.markRemoved()) { ++ PrioritisedTaskQueue.this.tasks.remove(this.holder); ++ } ++ this.holder = new Holder(this, priority.priority, this.subOrder, this.id); ++ PrioritisedTaskQueue.this.tasks.put(this.holder, Boolean.TRUE); ++ } + -+ /** -+ * @param name Specified debug name of this thread pool -+ * @param threads The number of threads to use -+ * @param threadModifier Invoked for each created thread with its incremental id before starting them -+ * @param queueHoldTime The maximum amount of time to spend executing tasks in a specific queue before attempting -+ * to switch to another queue, per thread -+ */ -+ public PrioritisedThreadPool(final String name, final int threads, final BiConsumer threadModifier, -+ final long queueHoldTime) { // in ns -+ if (threads <= 0) { -+ throw new IllegalArgumentException("Thread count must be > 0, not " + threads); -+ } -+ if (name == null) { -+ throw new IllegalArgumentException("Name cannot be null"); ++ return true; ++ } + } -+ this.name = name; -+ this.queueMaxHoldTime = queueHoldTime; + -+ this.threads = new PrioritisedThread[threads]; -+ for (int i = 0; i < threads; ++i) { -+ this.threads[i] = new PrioritisedThread(this); ++ @Override ++ public boolean raisePriority(final Priority priority) { ++ synchronized (this) { ++ if (this.priority == Priority.COMPLETING || this.priority.isHigherOrEqualPriority(priority)) { ++ return false; ++ } + -+ // set default attributes -+ this.threads[i].setName("Prioritised thread for pool '" + name + "' #" + i); -+ this.threads[i].setUncaughtExceptionHandler((final Thread thread, final Throwable throwable) -> { -+ LOGGER.error("Uncaught exception in thread " + thread.getName(), throwable); -+ }); ++ this.priority = priority; + -+ // let thread modifier override defaults -+ if (threadModifier != null) { -+ threadModifier.accept(this.threads[i], Integer.valueOf(i)); -+ } ++ if (this.holder != null) { ++ if (this.holder.markRemoved()) { ++ PrioritisedTaskQueue.this.tasks.remove(this.holder); ++ } ++ this.holder = new Holder(this, priority.priority, this.subOrder, this.id); ++ PrioritisedTaskQueue.this.tasks.put(this.holder, Boolean.TRUE); ++ } + -+ // now the thread can start -+ this.threads[i].start(); ++ return true; ++ } + } -+ } + -+ /** -+ * Returns an array representing the threads backing this thread pool. -+ */ -+ public Thread[] getThreads() { -+ return Arrays.copyOf(this.threads, this.threads.length, Thread[].class); -+ } ++ @Override ++ public boolean lowerPriority(Priority priority) { ++ synchronized (this) { ++ if (this.priority == Priority.COMPLETING || this.priority.isLowerOrEqualPriority(priority)) { ++ return false; ++ } + -+ /** -+ * Creates and returns a {@link PrioritisedPoolExecutor} to schedule tasks onto. The returned executor will execute -+ * tasks on this thread pool only. -+ * @param name The debug name of the executor. -+ * @param minParallelism The minimum number of threads to be executing tasks from the returned executor -+ * before threads may be allocated to other queues in this thread pool. -+ * @param parallelism The maximum number of threads which may be executing tasks from the returned executor. -+ * @throws IllegalStateException If this thread pool is shut down -+ */ -+ public PrioritisedPoolExecutor createExecutor(final String name, final int minParallelism, final int parallelism) { -+ synchronized (this.nonShutdownQueues) { -+ if (this.shutdown) { -+ throw new IllegalStateException("Queue is shutdown: " + this.toString()); -+ } -+ final PrioritisedPoolExecutorImpl ret = new PrioritisedPoolExecutorImpl( -+ this, name, -+ Math.min(Math.max(1, parallelism), this.threads.length), -+ Math.min(Math.max(0, minParallelism), this.threads.length) -+ ); ++ this.priority = priority; + -+ this.nonShutdownQueues.add(ret); ++ if (this.holder != null) { ++ if (this.holder.markRemoved()) { ++ PrioritisedTaskQueue.this.tasks.remove(this.holder); ++ } ++ this.holder = new Holder(this, priority.priority, this.subOrder, this.id); ++ PrioritisedTaskQueue.this.tasks.put(this.holder, Boolean.TRUE); ++ } + -+ synchronized (this.activeQueues) { -+ this.activeQueues.add(ret); ++ return true; + } ++ } + -+ return ret; ++ @Override ++ public long getSubOrder() { ++ synchronized (this) { ++ return this.subOrder; ++ } + } -+ } + -+ /** -+ * Prevents creation of new queues, shutdowns all non-shutdown queues if specified -+ */ -+ public void halt(final boolean shutdownQueues) { -+ synchronized (this.nonShutdownQueues) { -+ this.shutdown = true; -+ } -+ if (shutdownQueues) { -+ final ArrayList queuesToShutdown; -+ synchronized (this.nonShutdownQueues) { -+ this.shutdown = true; -+ queuesToShutdown = new ArrayList<>(this.nonShutdownQueues); -+ } -+ -+ for (final PrioritisedPoolExecutorImpl queue : queuesToShutdown) { -+ queue.shutdown(); -+ } -+ } ++ @Override ++ public boolean setSubOrder(final long subOrder) { ++ synchronized (this) { ++ if (this.priority == Priority.COMPLETING || this.subOrder == subOrder) { ++ return false; ++ } + ++ this.subOrder = subOrder; + -+ for (final PrioritisedThread thread : this.threads) { -+ // can't kill queue, queue is null -+ thread.halt(false); -+ } -+ } ++ if (this.holder != null) { ++ if (this.holder.markRemoved()) { ++ PrioritisedTaskQueue.this.tasks.remove(this.holder); ++ } ++ this.holder = new Holder(this, priority.priority, this.subOrder, this.id); ++ PrioritisedTaskQueue.this.tasks.put(this.holder, Boolean.TRUE); ++ } + -+ /** -+ * Waits until all threads in this pool have shutdown, or until the specified time has passed. -+ * @param msToWait Maximum time to wait. -+ * @return {@code false} if the maximum time passed, {@code true} otherwise. -+ */ -+ public boolean join(final long msToWait) { -+ try { -+ return this.join(msToWait, false); -+ } catch (final InterruptedException ex) { -+ throw new IllegalStateException(ex); ++ return true; ++ } + } -+ } + -+ /** -+ * Waits until all threads in this pool have shutdown, or until the specified time has passed. -+ * @param msToWait Maximum time to wait. -+ * @return {@code false} if the maximum time passed, {@code true} otherwise. -+ * @throws InterruptedException If this thread is interrupted. -+ */ -+ public boolean joinInterruptable(final long msToWait) throws InterruptedException { -+ return this.join(msToWait, true); -+ } ++ @Override ++ public boolean raiseSubOrder(long subOrder) { ++ synchronized (this) { ++ if (this.priority == Priority.COMPLETING || this.subOrder >= subOrder) { ++ return false; ++ } + -+ protected final boolean join(final long msToWait, final boolean interruptable) throws InterruptedException { -+ final long nsToWait = msToWait * (1000 * 1000); -+ final long start = System.nanoTime(); -+ final long deadline = start + nsToWait; -+ boolean interrupted = false; -+ try { -+ for (final PrioritisedThread thread : this.threads) { -+ for (;;) { -+ if (!thread.isAlive()) { -+ break; -+ } -+ final long current = System.nanoTime(); -+ if (current >= deadline) { -+ return false; -+ } ++ this.subOrder = subOrder; + -+ try { -+ thread.join(Math.max(1L, (deadline - current) / (1000 * 1000))); -+ } catch (final InterruptedException ex) { -+ if (interruptable) { -+ throw ex; -+ } -+ interrupted = true; ++ if (this.holder != null) { ++ if (this.holder.markRemoved()) { ++ PrioritisedTaskQueue.this.tasks.remove(this.holder); + } ++ this.holder = new Holder(this, priority.priority, this.subOrder, this.id); ++ PrioritisedTaskQueue.this.tasks.put(this.holder, Boolean.TRUE); + } -+ } + -+ return true; -+ } finally { -+ if (interrupted) { -+ Thread.currentThread().interrupt(); ++ return true; + } + } -+ } + -+ /** -+ * Shuts down this thread pool, optionally waiting for all tasks to be executed. -+ * This function will invoke {@link PrioritisedPoolExecutor#shutdown()} on all created executors on this -+ * thread pool. -+ * @param wait Whether to wait for tasks to be executed -+ */ -+ public void shutdown(final boolean wait) { -+ final ArrayList queuesToShutdown; -+ synchronized (this.nonShutdownQueues) { -+ this.shutdown = true; -+ queuesToShutdown = new ArrayList<>(this.nonShutdownQueues); -+ } ++ @Override ++ public boolean lowerSubOrder(final long subOrder) { ++ synchronized (this) { ++ if (this.priority == Priority.COMPLETING || this.subOrder <= subOrder) { ++ return false; ++ } + -+ for (final PrioritisedPoolExecutorImpl queue : queuesToShutdown) { -+ queue.shutdown(); -+ } ++ this.subOrder = subOrder; + -+ for (final PrioritisedThread thread : this.threads) { -+ // none of these can be true or else NPE -+ thread.close(false, false); -+ } ++ if (this.holder != null) { ++ if (this.holder.markRemoved()) { ++ PrioritisedTaskQueue.this.tasks.remove(this.holder); ++ } ++ this.holder = new Holder(this, priority.priority, this.subOrder, this.id); ++ PrioritisedTaskQueue.this.tasks.put(this.holder, Boolean.TRUE); ++ } + -+ if (wait) { -+ final ArrayList queues; -+ synchronized (this.activeQueues) { -+ queues = new ArrayList<>(this.activeQueues); -+ } -+ for (final PrioritisedPoolExecutorImpl queue : queues) { -+ queue.waitUntilAllExecuted(); ++ return true; + } + } -+ } -+ -+ protected static final class PrioritisedThread extends PrioritisedQueueExecutorThread { + -+ protected final PrioritisedThreadPool pool; -+ protected final AtomicBoolean alertedHighPriority = new AtomicBoolean(); ++ @Override ++ public boolean setPriorityAndSubOrder(final Priority priority, final long subOrder) { ++ synchronized (this) { ++ if (this.priority == Priority.COMPLETING || (this.priority == priority && this.subOrder == subOrder)) { ++ return false; ++ } + -+ public PrioritisedThread(final PrioritisedThreadPool pool) { -+ super(null); -+ this.pool = pool; -+ } ++ this.priority = priority; ++ this.subOrder = subOrder; + -+ public boolean alertHighPriorityExecutor() { -+ if (!this.notifyTasks()) { -+ if (!this.alertedHighPriority.get()) { -+ this.alertedHighPriority.set(true); ++ if (this.holder != null) { ++ if (this.holder.markRemoved()) { ++ PrioritisedTaskQueue.this.tasks.remove(this.holder); ++ } ++ this.holder = new Holder(this, priority.priority, this.subOrder, this.id); ++ PrioritisedTaskQueue.this.tasks.put(this.holder, Boolean.TRUE); + } -+ return false; -+ } + -+ return true; ++ return true; ++ } + } ++ } + -+ private boolean isAlertedHighPriority() { -+ return this.alertedHighPriority.get() && this.alertedHighPriority.getAndSet(false); -+ } ++ public static record PrioritySubOrderPair(Priority priority, long subOrder) {} ++} +diff --git a/src/main/java/ca/spottedleaf/concurrentutil/executor/thread/PrioritisedQueueExecutorThread.java b/src/main/java/ca/spottedleaf/concurrentutil/executor/thread/PrioritisedQueueExecutorThread.java +new file mode 100644 +index 0000000000000000000000000000000000000000..f5367a13aaa02f0f929813c00a67e6ac7c8652cb +--- /dev/null ++++ b/src/main/java/ca/spottedleaf/concurrentutil/executor/thread/PrioritisedQueueExecutorThread.java +@@ -0,0 +1,402 @@ ++package ca.spottedleaf.concurrentutil.executor.thread; + -+ @Override -+ protected boolean pollTasks() { -+ final PrioritisedThreadPool pool = this.pool; -+ final TreeSet queues = this.pool.queues; ++import ca.spottedleaf.concurrentutil.executor.PrioritisedExecutor; ++import ca.spottedleaf.concurrentutil.util.ConcurrentUtil; ++import ca.spottedleaf.concurrentutil.util.Priority; ++import org.slf4j.Logger; ++import org.slf4j.LoggerFactory; ++import java.lang.invoke.VarHandle; ++import java.util.concurrent.locks.LockSupport; + -+ boolean ret = false; -+ for (;;) { -+ if (this.halted) { -+ break; -+ } -+ // try to find a queue -+ // note that if and ONLY IF the queues set is empty, this means there are no tasks for us to execute. -+ // so we can only break when it's empty -+ final PrioritisedPoolExecutorImpl queue; -+ // select queue -+ synchronized (queues) { -+ queue = queues.pollFirst(); -+ if (queue == null) { -+ // no tasks to execute -+ break; -+ } ++/** ++ * Thread which will continuously drain from a specified queue. ++ *

    ++ * Note: When using this thread, queue additions to the underlying {@link #queue} are not sufficient to get this thread ++ * to execute the task. The function {@link #notifyTasks()} must be used after scheduling a task. For expected behaviour ++ * of task scheduling, use the methods provided on this class to schedule tasks. ++ *

    ++ */ ++public class PrioritisedQueueExecutorThread extends Thread implements PrioritisedExecutor { + -+ queue.schedulingId = ++pool.schedulingIdGenerator; -+ // we own this queue now, so increment the executor count -+ // do we also need to push this queue up for grabs for another executor? -+ if (++queue.concurrentExecutors < queue.maximumExecutors) { -+ // re-add to queues -+ // it's very important this is done in the same synchronised block for polling, as this prevents -+ // us from possibly later adding a queue that should not exist in the set -+ queues.add(queue); -+ queue.isQueued = true; -+ } else { -+ queue.isQueued = false; -+ } -+ // note: we cannot drain entries from the queue while holding this lock, as it will cause deadlock -+ // the queue addition holds the per-queue lock first then acquires the lock we have now, but if we -+ // try to poll now we don't hold the per queue lock but we do hold the global lock... -+ } ++ private static final Logger LOGGER = LoggerFactory.getLogger(PrioritisedQueueExecutorThread.class); + -+ // parse tasks as long as we are allowed -+ final long start = System.nanoTime(); -+ final long deadline = start + pool.queueMaxHoldTime; -+ do { -+ try { -+ if (this.halted) { -+ break; -+ } -+ if (!queue.executeTask()) { -+ // no more tasks, try next queue -+ break; -+ } -+ ret = true; -+ } catch (final ThreadDeath death) { -+ throw death; // goodbye world... -+ } catch (final Throwable throwable) { -+ LOGGER.error("Exception thrown from thread '" + this.getName() + "' in queue '" + queue.toString() + "'", throwable); -+ } -+ } while (!this.isAlertedHighPriority() && System.nanoTime() <= deadline); ++ protected final PrioritisedExecutor queue; + -+ synchronized (queues) { -+ // decrement executors, we are no longer executing -+ if (queue.isQueued) { -+ queues.remove(queue); -+ queue.isQueued = false; -+ } -+ if (--queue.concurrentExecutors == 0 && queue.scheduledPriority == null) { -+ // reset scheduling id once the queue is empty again -+ // this will ensure empty queues are not prioritised suddenly over active queues once tasks are -+ // queued -+ queue.schedulingId = 0L; -+ } ++ protected volatile boolean threadShutdown; + -+ // ensure the executor is queued for execution again -+ if (!queue.isHalted && queue.scheduledPriority != null) { // make sure it actually has tasks -+ queues.add(queue); -+ queue.isQueued = true; -+ } -+ } -+ } ++ protected volatile boolean threadParked; ++ protected static final VarHandle THREAD_PARKED_HANDLE = ConcurrentUtil.getVarHandle(PrioritisedQueueExecutorThread.class, "threadParked", boolean.class); + -+ return ret; -+ } -+ } ++ protected volatile boolean halted; + -+ public interface PrioritisedPoolExecutor extends PrioritisedExecutor { ++ protected final long spinWaitTime; + -+ /** -+ * Removes this queue from the thread pool without shutting the queue down or waiting for queued tasks to be executed -+ */ -+ public void halt(); ++ protected static final long DEFAULT_SPINWAIT_TIME = (long)(0.1e6);// 0.1ms + -+ /** -+ * Returns whether this executor is scheduled to run tasks or is running tasks, otherwise it returns whether -+ * this queue is not halted and not shutdown. -+ */ -+ public boolean isActive(); ++ public PrioritisedQueueExecutorThread(final PrioritisedExecutor queue) { ++ this(queue, DEFAULT_SPINWAIT_TIME); // 0.1ms + } + -+ protected static final class PrioritisedPoolExecutorImpl extends PrioritisedThreadedTaskQueue implements PrioritisedPoolExecutor { ++ public PrioritisedQueueExecutorThread(final PrioritisedExecutor queue, final long spinWaitTime) { // in ns ++ this.queue = queue; ++ this.spinWaitTime = spinWaitTime; ++ } + -+ protected final PrioritisedThreadPool pool; -+ protected final long[] priorityCounts = new long[Priority.TOTAL_SCHEDULABLE_PRIORITIES]; -+ protected long schedulingId; -+ protected int concurrentExecutors; -+ protected Priority scheduledPriority; ++ @Override ++ public final void run() { ++ try { ++ this.begin(); ++ this.doRun(); ++ } finally { ++ this.die(); ++ } ++ } + -+ protected final String name; -+ protected final int maximumExecutors; -+ protected final int minimumExecutors; -+ protected boolean isQueued; ++ public final void doRun() { ++ final long spinWaitTime = this.spinWaitTime; + -+ public PrioritisedPoolExecutorImpl(final PrioritisedThreadPool pool, final String name, final int maximumExecutors, final int minimumExecutors) { -+ this.pool = pool; -+ this.name = name; -+ this.maximumExecutors = maximumExecutors; -+ this.minimumExecutors = minimumExecutors; -+ } ++ main_loop: ++ for (;;) { ++ this.pollTasks(); + -+ public static Comparator comparator() { -+ return (final PrioritisedPoolExecutorImpl p1, final PrioritisedPoolExecutorImpl p2) -> { -+ if (p1 == p2) { -+ return 0; -+ } ++ // spinwait + -+ final int belowMin1 = p1.minimumExecutors - p1.concurrentExecutors; -+ final int belowMin2 = p2.minimumExecutors - p2.concurrentExecutors; ++ final long start = System.nanoTime(); + -+ // test minimum executors -+ if (belowMin1 > 0 || belowMin2 > 0) { -+ // want the largest belowMin to be first -+ final int minCompare = Integer.compare(belowMin2, belowMin1); ++ for (;;) { ++ // If we are interrupted for any reason, park() will always return immediately. Clear so that we don't needlessly use cpu in such an event. ++ Thread.interrupted(); ++ Thread.yield(); ++ LockSupport.parkNanos("Spinwaiting on tasks", 10_000L); // 10us + -+ if (minCompare != 0) { -+ return minCompare; -+ } ++ if (this.pollTasks()) { ++ // restart loop, found tasks ++ continue main_loop; + } + -+ // prefer higher priority -+ final int priorityCompare = p1.scheduledPriority.ordinal() - p2.scheduledPriority.ordinal(); -+ if (priorityCompare != 0) { -+ return priorityCompare; ++ if (this.handleClose()) { ++ return; // we're done + } + -+ // try to spread out the executors so that each can have threads executing -+ final int executorCompare = p1.concurrentExecutors - p2.concurrentExecutors; -+ if (executorCompare != 0) { -+ return executorCompare; ++ if ((System.nanoTime() - start) >= spinWaitTime) { ++ break; + } ++ } + -+ // if all else fails here we just choose whichever executor was queued first -+ return Long.compare(p1.schedulingId, p2.schedulingId); -+ }; -+ } ++ if (this.handleClose()) { ++ return; ++ } + -+ private boolean isHalted; ++ this.setThreadParkedVolatile(true); + -+ @Override -+ public void halt() { -+ final PrioritisedThreadPool pool = this.pool; -+ final TreeSet queues = pool.queues; -+ synchronized (queues) { -+ if (this.isHalted) { -+ return; -+ } -+ this.isHalted = true; -+ if (this.isQueued) { -+ queues.remove(this); -+ this.isQueued = false; -+ } -+ } -+ synchronized (pool.nonShutdownQueues) { -+ pool.nonShutdownQueues.remove(this); -+ } -+ synchronized (pool.activeQueues) { -+ pool.activeQueues.remove(this); ++ // We need to parse here to avoid a race condition where a thread queues a task before we set parked to true ++ // (i.e. it will not notify us) ++ if (this.pollTasks()) { ++ this.setThreadParkedVolatile(false); ++ continue; + } -+ } -+ -+ @Override -+ public boolean isActive() { -+ final PrioritisedThreadPool pool = this.pool; -+ final TreeSet queues = pool.queues; + -+ synchronized (queues) { -+ if (this.concurrentExecutors != 0) { -+ return true; -+ } -+ synchronized (pool.activeQueues) { -+ if (pool.activeQueues.contains(this)) { -+ return true; -+ } -+ } ++ if (this.handleClose()) { ++ return; + } + -+ return false; ++ // we don't need to check parked before sleeping, but we do need to check parked in a do-while loop ++ // LockSupport.park() can fail for any reason ++ while (this.getThreadParkedVolatile()) { ++ Thread.interrupted(); ++ LockSupport.park("Waiting on tasks"); ++ } + } ++ } + -+ private long totalQueuedTasks = 0L; -+ -+ @Override -+ protected void priorityChange(final PrioritisedThreadedTaskQueue.PrioritisedTask task, final Priority from, final Priority to) { -+ // Note: The superclass' queue lock is ALWAYS held when inside this method. So we do NOT need to do any additional synchronisation -+ // for accessing this queue's state. -+ final long[] priorityCounts = this.priorityCounts; -+ final boolean shutdown = this.isShutdown(); ++ protected void begin() {} + -+ if (from == null && to == Priority.COMPLETING) { -+ throw new IllegalStateException("Cannot complete task without queueing it first"); -+ } ++ protected void die() {} + -+ // we should only notify for queueing of tasks, not changing priorities -+ final boolean shouldNotifyTasks = from == null; ++ /** ++ * Attempts to poll as many tasks as possible, returning when finished. ++ * @return Whether any tasks were executed. ++ */ ++ protected boolean pollTasks() { ++ boolean ret = false; + -+ final Priority scheduledPriority = this.scheduledPriority; -+ if (from != null) { -+ --priorityCounts[from.priority]; -+ } -+ if (to != Priority.COMPLETING) { -+ ++priorityCounts[to.priority]; -+ } -+ final long totalQueuedTasks; -+ if (to == Priority.COMPLETING) { -+ totalQueuedTasks = --this.totalQueuedTasks; -+ } else if (from == null) { -+ totalQueuedTasks = ++this.totalQueuedTasks; -+ } else { -+ totalQueuedTasks = this.totalQueuedTasks; ++ for (;;) { ++ if (this.halted) { ++ break; + } -+ -+ // find new highest priority -+ int highest = Math.min(to == Priority.COMPLETING ? Priority.IDLE.priority : to.priority, scheduledPriority == null ? Priority.IDLE.priority : scheduledPriority.priority); -+ int lowestPriority = priorityCounts.length; // exclusive -+ for (;highest < lowestPriority; ++highest) { -+ final long count = priorityCounts[highest]; -+ if (count < 0) { -+ throw new IllegalStateException("Priority " + highest + " has " + count + " scheduled tasks"); -+ } -+ -+ if (count != 0) { ++ try { ++ if (!this.queue.executeTask()) { + break; + } -+ } -+ -+ final Priority newPriority; -+ if (highest == lowestPriority) { -+ // no tasks left -+ newPriority = null; -+ } else if (shutdown) { -+ // whichever is lower, the actual greatest priority or simply HIGHEST -+ // this is so shutdown automatically gets priority -+ newPriority = Priority.getPriority(Math.min(highest, Priority.HIGHEST.priority)); -+ } else { -+ newPriority = Priority.getPriority(highest); -+ } -+ -+ final int executorsWanted; -+ boolean shouldNotifyHighPriority = false; -+ -+ final PrioritisedThreadPool pool = this.pool; -+ final TreeSet queues = pool.queues; -+ -+ synchronized (queues) { -+ if (!this.isQueued) { -+ // see if we need to be queued -+ if (newPriority != null) { -+ if (this.schedulingId == 0L) { -+ this.schedulingId = ++pool.schedulingIdGenerator; -+ } -+ this.scheduledPriority = newPriority; // must be updated before queue add -+ if (!this.isHalted && this.concurrentExecutors < this.maximumExecutors) { -+ shouldNotifyHighPriority = newPriority.isHigherOrEqualPriority(Priority.HIGH); -+ queues.add(this); -+ this.isQueued = true; -+ } -+ } else { -+ // do not queue -+ this.scheduledPriority = null; -+ } -+ } else { -+ // see if we need to NOT be queued -+ if (newPriority == null) { -+ queues.remove(this); -+ this.scheduledPriority = null; -+ this.isQueued = false; -+ } else if (scheduledPriority != newPriority) { -+ // if our priority changed, we need to update it - which means removing and re-adding into the queue -+ queues.remove(this); -+ // only now can we update scheduledPriority, since we are no longer in queue -+ this.scheduledPriority = newPriority; -+ queues.add(this); -+ shouldNotifyHighPriority = (scheduledPriority == null || scheduledPriority.isLowerPriority(Priority.HIGH)) && newPriority.isHigherOrEqualPriority(Priority.HIGH); -+ } -+ } -+ -+ if (this.isQueued) { -+ executorsWanted = Math.min(this.maximumExecutors - this.concurrentExecutors, (int)totalQueuedTasks); -+ } else { -+ executorsWanted = 0; -+ } -+ } -+ -+ if (newPriority == null && shutdown) { -+ synchronized (pool.activeQueues) { -+ pool.activeQueues.remove(this); -+ } -+ } -+ -+ // Wake up the number of executors we want -+ if (executorsWanted > 0 || (shouldNotifyTasks | shouldNotifyHighPriority)) { -+ int notified = 0; -+ for (final PrioritisedThread thread : pool.threads) { -+ if ((shouldNotifyHighPriority ? thread.alertHighPriorityExecutor() : thread.notifyTasks()) -+ && (++notified >= executorsWanted)) { -+ break; -+ } -+ } ++ ret = true; ++ } catch (final Throwable throwable) { ++ LOGGER.error("Exception thrown from prioritized runnable task in thread '" + this.getName() + "'", throwable); + } + } + -+ @Override -+ public boolean shutdown() { -+ final boolean ret = super.shutdown(); -+ if (!ret) { -+ return ret; -+ } -+ -+ final PrioritisedThreadPool pool = this.pool; -+ -+ // remove from active queues -+ synchronized (pool.nonShutdownQueues) { -+ pool.nonShutdownQueues.remove(this); -+ } -+ -+ final TreeSet queues = pool.queues; -+ -+ // try and shift around our priority -+ synchronized (queues) { -+ if (this.scheduledPriority == null) { -+ // no tasks are queued, ensure we aren't in activeQueues -+ synchronized (pool.activeQueues) { -+ pool.activeQueues.remove(this); -+ } -+ -+ return ret; -+ } -+ -+ // try to set scheduled priority to HIGHEST so it drains faster -+ -+ if (this.scheduledPriority.isHigherOrEqualPriority(Priority.HIGHEST)) { -+ // already at target priority (highest or above) -+ return ret; -+ } -+ -+ // shift priority to HIGHEST ++ return ret; ++ } + -+ if (this.isQueued) { -+ queues.remove(this); -+ this.scheduledPriority = Priority.HIGHEST; -+ queues.add(this); -+ } else { -+ this.scheduledPriority = Priority.HIGHEST; -+ } -+ } ++ protected boolean handleClose() { ++ if (this.threadShutdown) { ++ this.pollTasks(); // this ensures we've emptied the queue ++ return true; ++ } ++ return false; ++ } + -+ return ret; ++ /** ++ * Notify this thread that a task has been added to its queue ++ * @return {@code true} if this thread was waiting for tasks, {@code false} if it is executing tasks ++ */ ++ public boolean notifyTasks() { ++ if (this.getThreadParkedVolatile() && this.exchangeThreadParkedVolatile(false)) { ++ LockSupport.unpark(this); ++ return true; + } ++ return false; + } -+} -diff --git a/src/main/java/ca/spottedleaf/concurrentutil/executor/standard/PrioritisedThreadedTaskQueue.java b/src/main/java/ca/spottedleaf/concurrentutil/executor/standard/PrioritisedThreadedTaskQueue.java -new file mode 100644 -index 0000000000000000000000000000000000000000..3e8401b1b1f833c4f01bc87059a2f48d761d989f ---- /dev/null -+++ b/src/main/java/ca/spottedleaf/concurrentutil/executor/standard/PrioritisedThreadedTaskQueue.java -@@ -0,0 +1,378 @@ -+package ca.spottedleaf.concurrentutil.executor.standard; + -+import java.util.ArrayDeque; -+import java.util.concurrent.atomic.AtomicLong; ++ @Override ++ public long getTotalTasksExecuted() { ++ return this.queue.getTotalTasksExecuted(); ++ } + -+public class PrioritisedThreadedTaskQueue implements PrioritisedExecutor { ++ @Override ++ public long getTotalTasksScheduled() { ++ return this.queue.getTotalTasksScheduled(); ++ } + -+ protected final ArrayDeque[] queues = new ArrayDeque[Priority.TOTAL_SCHEDULABLE_PRIORITIES]; { -+ for (int i = 0; i < Priority.TOTAL_SCHEDULABLE_PRIORITIES; ++i) { -+ this.queues[i] = new ArrayDeque<>(); -+ } ++ @Override ++ public long generateNextSubOrder() { ++ return this.queue.generateNextSubOrder(); + } + -+ // Use AtomicLong to separate from the queue field, we don't want false sharing here. -+ protected final AtomicLong totalScheduledTasks = new AtomicLong(); -+ protected final AtomicLong totalCompletedTasks = new AtomicLong(); ++ @Override ++ public boolean shutdown() { ++ throw new UnsupportedOperationException(); ++ } + -+ // this is here to prevent failures to queue stalling flush() calls (as the schedule calls would increment totalScheduledTasks without this check) -+ protected volatile boolean hasShutdown; ++ @Override ++ public boolean isShutdown() { ++ return false; ++ } + -+ protected long taskIdGenerator = 0; ++ /** ++ * {@inheritDoc} ++ * @throws IllegalStateException Always ++ */ ++ @Override ++ public boolean executeTask() throws IllegalStateException { ++ throw new IllegalStateException(); ++ } + + @Override -+ public PrioritisedExecutor.PrioritisedTask queueRunnable(final Runnable task, final Priority priority) throws IllegalStateException, IllegalArgumentException { -+ if (!Priority.isValidPriority(priority)) { -+ throw new IllegalArgumentException("Priority " + priority + " is invalid"); -+ } -+ if (task == null) { -+ throw new NullPointerException("Task cannot be null"); -+ } ++ public PrioritisedTask queueTask(final Runnable task) { ++ final PrioritisedTask ret = this.createTask(task); + -+ if (this.hasShutdown) { -+ // prevent us from stalling flush() calls by incrementing scheduled tasks when we really didn't schedule something -+ throw new IllegalStateException("Queue has shutdown"); -+ } ++ ret.queue(); ++ ++ return ret; ++ } + -+ final PrioritisedTask ret; ++ @Override ++ public PrioritisedTask queueTask(final Runnable task, final Priority priority) { ++ final PrioritisedTask ret = this.createTask(task, priority); + -+ synchronized (this.queues) { -+ if (this.hasShutdown) { -+ throw new IllegalStateException("Queue has shutdown"); -+ } -+ this.getAndAddTotalScheduledTasksVolatile(1L); ++ ret.queue(); + -+ ret = new PrioritisedTask(this.taskIdGenerator++, task, priority, this); ++ return ret; ++ } + -+ this.queues[ret.priority.priority].add(ret); ++ @Override ++ public PrioritisedTask queueTask(final Runnable task, final Priority priority, final long subOrder) { ++ final PrioritisedTask ret = this.createTask(task, priority, subOrder); + -+ // call priority change callback (note: only after we successfully queue!) -+ this.priorityChange(ret, null, priority); -+ } ++ ret.queue(); + + return ret; + } + ++ + @Override -+ public PrioritisedExecutor.PrioritisedTask createTask(final Runnable task, final Priority priority) { -+ if (!Priority.isValidPriority(priority)) { -+ throw new IllegalArgumentException("Priority " + priority + " is invalid"); -+ } -+ if (task == null) { -+ throw new NullPointerException("Task cannot be null"); -+ } ++ public PrioritisedTask createTask(Runnable task) { ++ final PrioritisedTask queueTask = this.queue.createTask(task); + -+ return new PrioritisedTask(task, priority, this); ++ return new WrappedTask(queueTask); + } + + @Override -+ public long getTotalTasksScheduled() { -+ return this.totalScheduledTasks.get(); ++ public PrioritisedTask createTask(final Runnable task, final Priority priority) { ++ final PrioritisedTask queueTask = this.queue.createTask(task, priority); ++ ++ return new WrappedTask(queueTask); + } + + @Override -+ public long getTotalTasksExecuted() { -+ return this.totalCompletedTasks.get(); -+ } ++ public PrioritisedTask createTask(final Runnable task, final Priority priority, final long subOrder) { ++ final PrioritisedTask queueTask = this.queue.createTask(task, priority, subOrder); + -+ // callback method for subclasses to override -+ // from is null when a task is immediately created -+ protected void priorityChange(final PrioritisedTask task, final Priority from, final Priority to) {} ++ return new WrappedTask(queueTask); ++ } + + /** -+ * Polls the highest priority task currently available. {@code null} if none. This will mark the -+ * returned task as completed. ++ * Closes this queue executor's queue. Optionally waits for all tasks in queue to be executed if {@code wait} is true. ++ *

    ++ * This function is MT-Safe. ++ *

    ++ * @param wait If this call is to wait until this thread shuts down. ++ * @param killQueue Whether to shutdown this thread's queue ++ * @return whether this thread shut down the queue ++ * @see #halt(boolean) + */ -+ protected PrioritisedTask poll() { -+ return this.poll(Priority.IDLE); -+ } ++ public boolean close(final boolean wait, final boolean killQueue) { ++ final boolean ret = killQueue && this.queue.shutdown(); ++ this.threadShutdown = true; + -+ protected PrioritisedTask poll(final Priority minPriority) { -+ final ArrayDeque[] queues = this.queues; -+ synchronized (queues) { -+ final int max = minPriority.priority; -+ for (int i = 0; i <= max; ++i) { -+ final ArrayDeque queue = queues[i]; -+ PrioritisedTask task; -+ while ((task = queue.pollFirst()) != null) { -+ if (task.trySetCompleting(i)) { -+ return task; ++ // force thread to respond to the shutdown ++ this.setThreadParkedVolatile(false); ++ LockSupport.unpark(this); ++ ++ if (wait) { ++ boolean interrupted = false; ++ for (;;) { ++ if (this.isAlive()) { ++ if (interrupted) { ++ Thread.currentThread().interrupt(); + } ++ break; ++ } ++ try { ++ this.join(); ++ } catch (final InterruptedException ex) { ++ interrupted = true; + } + } + } + -+ return null; ++ return ret; + } + ++ + /** -+ * Polls and executes the highest priority task currently available. Exceptions thrown during task execution will -+ * be rethrown. -+ * @return {@code true} if a task was executed, {@code false} otherwise. ++ * Causes this thread to exit without draining the queue. To ensure tasks are completed, use {@link #close(boolean, boolean)}. ++ *

    ++ * This is not safe to call with {@link #close(boolean, boolean)} if wait = true, in which case ++ * the waiting thread may block indefinitely. ++ *

    ++ *

    ++ * This function is MT-Safe. ++ *

    ++ * @param killQueue Whether to shutdown this thread's queue ++ * @see #close(boolean, boolean) + */ -+ @Override -+ public boolean executeTask() { -+ final PrioritisedTask task = this.poll(); -+ -+ if (task != null) { -+ task.executeInternal(); -+ return true; -+ } -+ -+ return false; -+ } -+ -+ @Override -+ public boolean shutdown() { -+ synchronized (this.queues) { -+ if (this.hasShutdown) { -+ return false; -+ } -+ this.hasShutdown = true; ++ public void halt(final boolean killQueue) { ++ if (killQueue) { ++ this.queue.shutdown(); + } -+ return true; -+ } -+ -+ @Override -+ public boolean isShutdown() { -+ return this.hasShutdown; -+ } -+ -+ /* totalScheduledTasks */ ++ this.threadShutdown = true; ++ this.halted = true; + -+ protected final long getTotalScheduledTasksVolatile() { -+ return this.totalScheduledTasks.get(); ++ // force thread to respond to the shutdown ++ this.setThreadParkedVolatile(false); ++ LockSupport.unpark(this); + } + -+ protected final long getAndAddTotalScheduledTasksVolatile(final long value) { -+ return this.totalScheduledTasks.getAndAdd(value); ++ protected final boolean getThreadParkedVolatile() { ++ return (boolean)THREAD_PARKED_HANDLE.getVolatile(this); + } + -+ /* totalCompletedTasks */ -+ -+ protected final long getTotalCompletedTasksVolatile() { -+ return this.totalCompletedTasks.get(); ++ protected final boolean exchangeThreadParkedVolatile(final boolean value) { ++ return (boolean)THREAD_PARKED_HANDLE.getAndSet(this, value); + } + -+ protected final long getAndAddTotalCompletedTasksVolatile(final long value) { -+ return this.totalCompletedTasks.getAndAdd(value); ++ protected final void setThreadParkedVolatile(final boolean value) { ++ THREAD_PARKED_HANDLE.setVolatile(this, value); + } + -+ protected static final class PrioritisedTask implements PrioritisedExecutor.PrioritisedTask { -+ protected final PrioritisedThreadedTaskQueue queue; -+ protected long id; -+ protected static final long NOT_SCHEDULED_ID = -1L; -+ -+ protected Runnable runnable; -+ protected volatile Priority priority; -+ -+ protected PrioritisedTask(final long id, final Runnable runnable, final Priority priority, final PrioritisedThreadedTaskQueue queue) { -+ if (!Priority.isValidPriority(priority)) { -+ throw new IllegalArgumentException("Invalid priority " + priority); -+ } ++ /** ++ * Required so that queue() can notify (unpark) this thread ++ */ ++ private final class WrappedTask implements PrioritisedTask { ++ private final PrioritisedTask queueTask; + -+ this.priority = priority; -+ this.runnable = runnable; -+ this.queue = queue; -+ this.id = id; ++ public WrappedTask(final PrioritisedTask queueTask) { ++ this.queueTask = queueTask; + } + -+ protected PrioritisedTask(final Runnable runnable, final Priority priority, final PrioritisedThreadedTaskQueue queue) { -+ if (!Priority.isValidPriority(priority)) { -+ throw new IllegalArgumentException("Invalid priority " + priority); -+ } -+ -+ this.priority = priority; -+ this.runnable = runnable; -+ this.queue = queue; -+ this.id = NOT_SCHEDULED_ID; ++ @Override ++ public PrioritisedExecutor getExecutor() { ++ return PrioritisedQueueExecutorThread.this; + } + + @Override + public boolean queue() { -+ if (this.queue.hasShutdown) { -+ throw new IllegalStateException("Queue has shutdown"); ++ final boolean ret = this.queueTask.queue(); ++ if (ret) { ++ PrioritisedQueueExecutorThread.this.notifyTasks(); + } ++ return ret; ++ } + -+ synchronized (this.queue.queues) { -+ if (this.queue.hasShutdown) { -+ throw new IllegalStateException("Queue has shutdown"); -+ } -+ -+ final Priority priority = this.priority; -+ if (priority == Priority.COMPLETING) { -+ return false; -+ } -+ -+ if (this.id != NOT_SCHEDULED_ID) { -+ return false; -+ } -+ -+ this.queue.getAndAddTotalScheduledTasksVolatile(1L); -+ this.id = this.queue.taskIdGenerator++; -+ this.queue.queues[priority.priority].add(this); -+ -+ this.queue.priorityChange(this, null, priority); -+ -+ return true; -+ } ++ @Override ++ public boolean isQueued() { ++ return this.queueTask.isQueued(); + } + -+ protected boolean trySetCompleting(final int minPriority) { -+ final Priority oldPriority = this.priority; -+ if (oldPriority != Priority.COMPLETING && oldPriority.isHigherOrEqualPriority(minPriority)) { -+ this.priority = Priority.COMPLETING; -+ if (this.id != NOT_SCHEDULED_ID) { -+ this.queue.priorityChange(this, oldPriority, Priority.COMPLETING); -+ } -+ return true; -+ } ++ @Override ++ public boolean cancel() { ++ return this.queueTask.cancel(); ++ } + -+ return false; ++ @Override ++ public boolean execute() { ++ return this.queueTask.execute(); + } + + @Override + public Priority getPriority() { -+ return this.priority; ++ return this.queueTask.getPriority(); + } + + @Override + public boolean setPriority(final Priority priority) { -+ if (!Priority.isValidPriority(priority)) { -+ throw new IllegalArgumentException("Invalid priority " + priority); -+ } -+ synchronized (this.queue.queues) { -+ final Priority curr = this.priority; -+ -+ if (curr == Priority.COMPLETING) { -+ return false; -+ } -+ -+ if (curr == priority) { -+ return true; -+ } ++ return this.queueTask.setPriority(priority); ++ } + -+ this.priority = priority; -+ if (this.id != NOT_SCHEDULED_ID) { -+ this.queue.queues[priority.priority].add(this); ++ @Override ++ public boolean raisePriority(final Priority priority) { ++ return this.queueTask.raisePriority(priority); ++ } + -+ // call priority change callback -+ this.queue.priorityChange(this, curr, priority); -+ } -+ } ++ @Override ++ public boolean lowerPriority(final Priority priority) { ++ return this.queueTask.lowerPriority(priority); ++ } + -+ return true; ++ @Override ++ public long getSubOrder() { ++ return this.queueTask.getSubOrder(); + } + + @Override -+ public boolean raisePriority(final Priority priority) { -+ if (!Priority.isValidPriority(priority)) { -+ throw new IllegalArgumentException("Invalid priority " + priority); -+ } ++ public boolean setSubOrder(final long subOrder) { ++ return this.queueTask.setSubOrder(subOrder); ++ } + -+ synchronized (this.queue.queues) { -+ final Priority curr = this.priority; ++ @Override ++ public boolean raiseSubOrder(final long subOrder) { ++ return this.queueTask.raiseSubOrder(subOrder); ++ } + -+ if (curr == Priority.COMPLETING) { -+ return false; -+ } ++ @Override ++ public boolean lowerSubOrder(final long subOrder) { ++ return this.queueTask.lowerSubOrder(subOrder); ++ } + -+ if (curr.isHigherOrEqualPriority(priority)) { -+ return true; -+ } ++ @Override ++ public boolean setPriorityAndSubOrder(final Priority priority, final long subOrder) { ++ return this.queueTask.setPriorityAndSubOrder(priority, subOrder); ++ } ++ } ++} +diff --git a/src/main/java/ca/spottedleaf/concurrentutil/executor/thread/PrioritisedThreadPool.java b/src/main/java/ca/spottedleaf/concurrentutil/executor/thread/PrioritisedThreadPool.java +new file mode 100644 +index 0000000000000000000000000000000000000000..cb9df914a9a6d0d3f58fa58d8c93f4f583416cd1 +--- /dev/null ++++ b/src/main/java/ca/spottedleaf/concurrentutil/executor/thread/PrioritisedThreadPool.java +@@ -0,0 +1,741 @@ ++package ca.spottedleaf.concurrentutil.executor.thread; + -+ this.priority = priority; -+ if (this.id != NOT_SCHEDULED_ID) { -+ this.queue.queues[priority.priority].add(this); ++import ca.spottedleaf.concurrentutil.executor.PrioritisedExecutor; ++import ca.spottedleaf.concurrentutil.executor.queue.PrioritisedTaskQueue; ++import ca.spottedleaf.concurrentutil.util.Priority; ++import ca.spottedleaf.concurrentutil.util.TimeUtil; ++import org.slf4j.Logger; ++import org.slf4j.LoggerFactory; ++import java.lang.reflect.Array; ++import java.util.Arrays; ++import java.util.concurrent.atomic.AtomicBoolean; ++import java.util.concurrent.atomic.AtomicLong; ++import java.util.function.Consumer; + -+ // call priority change callback -+ this.queue.priorityChange(this, curr, priority); -+ } -+ } ++public final class PrioritisedThreadPool { + -+ return true; ++ private static final Logger LOGGER = LoggerFactory.getLogger(PrioritisedThreadPool.class); ++ ++ private final Consumer threadModifier; ++ private final COWArrayList executors = new COWArrayList<>(ExecutorGroup.class); ++ private final COWArrayList threads = new COWArrayList<>(PrioritisedThread.class); ++ private final COWArrayList aliveThreads = new COWArrayList<>(PrioritisedThread.class); ++ ++ private static final Priority HIGH_PRIORITY_NOTIFY_THRESHOLD = Priority.HIGH; ++ private static final Priority QUEUE_SHUTDOWN_PRIORITY = Priority.HIGH; ++ ++ private boolean shutdown; ++ ++ public PrioritisedThreadPool(final Consumer threadModifier) { ++ this.threadModifier = threadModifier; ++ ++ if (threadModifier == null) { ++ throw new NullPointerException("Thread factory may not be null"); + } ++ } + -+ @Override -+ public boolean lowerPriority(final Priority priority) { -+ if (!Priority.isValidPriority(priority)) { -+ throw new IllegalArgumentException("Invalid priority " + priority); -+ } ++ public Thread[] getAliveThreads() { ++ final PrioritisedThread[] threads = this.aliveThreads.getArray(); + -+ synchronized (this.queue.queues) { -+ final Priority curr = this.priority; ++ return Arrays.copyOf(threads, threads.length, Thread[].class); ++ } + -+ if (curr == Priority.COMPLETING) { -+ return false; -+ } ++ public Thread[] getCoreThreads() { ++ final PrioritisedThread[] threads = this.threads.getArray(); + -+ if (curr.isLowerOrEqualPriority(priority)) { -+ return true; -+ } ++ return Arrays.copyOf(threads, threads.length, Thread[].class); ++ } + -+ this.priority = priority; -+ if (this.id != NOT_SCHEDULED_ID) { -+ this.queue.queues[priority.priority].add(this); ++ /** ++ * Prevents creation of new queues, shutdowns all non-shutdown queues if specified ++ */ ++ public void halt(final boolean shutdownQueues) { ++ synchronized (this) { ++ this.shutdown = true; ++ } + -+ // call priority change callback -+ this.queue.priorityChange(this, curr, priority); ++ if (shutdownQueues) { ++ for (final ExecutorGroup group : this.executors.getArray()) { ++ for (final ExecutorGroup.ThreadPoolExecutor executor : group.executors.getArray()) { ++ executor.shutdown(); + } + } ++ } + -+ return true; ++ for (final PrioritisedThread thread : this.threads.getArray()) { ++ thread.halt(false); + } ++ } + -+ @Override -+ public boolean cancel() { -+ final long id; -+ synchronized (this.queue.queues) { -+ final Priority oldPriority = this.priority; -+ if (oldPriority == Priority.COMPLETING) { -+ return false; -+ } ++ /** ++ * Waits until all threads in this pool have shutdown, or until the specified time has passed. ++ * @param msToWait Maximum time to wait. ++ * @return {@code false} if the maximum time passed, {@code true} otherwise. ++ */ ++ public boolean join(final long msToWait) { ++ try { ++ return this.join(msToWait, false); ++ } catch (final InterruptedException ex) { ++ throw new IllegalStateException(ex); ++ } ++ } + -+ this.priority = Priority.COMPLETING; -+ // call priority change callback -+ if ((id = this.id) != NOT_SCHEDULED_ID) { -+ this.queue.priorityChange(this, oldPriority, Priority.COMPLETING); ++ /** ++ * Waits until all threads in this pool have shutdown, or until the specified time has passed. ++ * @param msToWait Maximum time to wait. ++ * @return {@code false} if the maximum time passed, {@code true} otherwise. ++ * @throws InterruptedException If this thread is interrupted. ++ */ ++ public boolean joinInterruptable(final long msToWait) throws InterruptedException { ++ return this.join(msToWait, true); ++ } ++ ++ protected final boolean join(final long msToWait, final boolean interruptable) throws InterruptedException { ++ final long nsToWait = msToWait * (1000 * 1000); ++ final long start = System.nanoTime(); ++ final long deadline = start + nsToWait; ++ boolean interrupted = false; ++ try { ++ for (final PrioritisedThread thread : this.aliveThreads.getArray()) { ++ for (;;) { ++ if (!thread.isAlive()) { ++ break; ++ } ++ final long current = System.nanoTime(); ++ if (current >= deadline && msToWait > 0L) { ++ return false; ++ } ++ ++ try { ++ thread.join(msToWait <= 0L ? 0L : Math.max(1L, (deadline - current) / (1000 * 1000))); ++ } catch (final InterruptedException ex) { ++ if (interruptable) { ++ throw ex; ++ } ++ interrupted = true; ++ } + } + } -+ this.runnable = null; -+ if (id != NOT_SCHEDULED_ID) { -+ this.queue.getAndAddTotalCompletedTasksVolatile(1L); -+ } ++ + return true; ++ } finally { ++ if (interrupted) { ++ Thread.currentThread().interrupt(); ++ } + } ++ } + -+ protected void executeInternal() { -+ try { -+ final Runnable execute = this.runnable; -+ this.runnable = null; -+ execute.run(); -+ } finally { -+ if (this.id != NOT_SCHEDULED_ID) { -+ this.queue.getAndAddTotalCompletedTasksVolatile(1L); -+ } ++ /** ++ * Shuts down this thread pool, optionally waiting for all tasks to be executed. ++ * This function will invoke {@link PrioritisedExecutor#shutdown()} on all created executors on this ++ * thread pool. ++ * @param wait Whether to wait for tasks to be executed ++ */ ++ public void shutdown(final boolean wait) { ++ synchronized (this) { ++ this.shutdown = true; ++ } ++ ++ for (final ExecutorGroup group : this.executors.getArray()) { ++ for (final ExecutorGroup.ThreadPoolExecutor executor : group.executors.getArray()) { ++ executor.shutdown(); + } + } + -+ @Override -+ public boolean execute() { -+ synchronized (this.queue.queues) { -+ final Priority oldPriority = this.priority; -+ if (oldPriority == Priority.COMPLETING) { -+ return false; -+ } + -+ this.priority = Priority.COMPLETING; -+ // call priority change callback -+ if (this.id != NOT_SCHEDULED_ID) { -+ this.queue.priorityChange(this, oldPriority, Priority.COMPLETING); -+ } -+ } ++ for (final PrioritisedThread thread : this.threads.getArray()) { ++ // none of these can be true or else NPE ++ thread.close(false, false); ++ } + -+ this.executeInternal(); -+ return true; ++ if (wait) { ++ this.join(0L); + } + } -+} -diff --git a/src/main/java/ca/spottedleaf/concurrentutil/function/BiLong1Function.java b/src/main/java/ca/spottedleaf/concurrentutil/function/BiLong1Function.java -new file mode 100644 -index 0000000000000000000000000000000000000000..94bfd7c56ffcea7d6491e94a7804bc3bd60fe9c3 ---- /dev/null -+++ b/src/main/java/ca/spottedleaf/concurrentutil/function/BiLong1Function.java -@@ -0,0 +1,8 @@ -+package ca.spottedleaf.concurrentutil.function; -+ -+@FunctionalInterface -+public interface BiLong1Function { + -+ public R apply(final long t1, final T t2); ++ private void die(final PrioritisedThread thread) { ++ this.aliveThreads.remove(thread); ++ } + -+} -diff --git a/src/main/java/ca/spottedleaf/concurrentutil/function/BiLongObjectConsumer.java b/src/main/java/ca/spottedleaf/concurrentutil/function/BiLongObjectConsumer.java -new file mode 100644 -index 0000000000000000000000000000000000000000..8e7eef07960a18d0593688eba55adfa1c85efadf ---- /dev/null -+++ b/src/main/java/ca/spottedleaf/concurrentutil/function/BiLongObjectConsumer.java -@@ -0,0 +1,8 @@ -+package ca.spottedleaf.concurrentutil.function; ++ public void adjustThreadCount(final int threads) { ++ synchronized (this) { ++ if (this.shutdown) { ++ return; ++ } + -+@FunctionalInterface -+public interface BiLongObjectConsumer { ++ final PrioritisedThread[] currentThreads = this.threads.getArray(); ++ if (threads == currentThreads.length) { ++ // no adjustment needed ++ return; ++ } + -+ public void accept(final long key, final V value); ++ if (threads < currentThreads.length) { ++ // we need to trim threads ++ for (int i = 0, difference = currentThreads.length - threads; i < difference; ++i) { ++ final PrioritisedThread remove = currentThreads[currentThreads.length - i - 1]; + -+} -diff --git a/src/main/java/ca/spottedleaf/concurrentutil/lock/ReentrantAreaLock.java b/src/main/java/ca/spottedleaf/concurrentutil/lock/ReentrantAreaLock.java -new file mode 100644 -index 0000000000000000000000000000000000000000..7ffe4379b06c03c56abbcbdee3bb720894a10702 ---- /dev/null -+++ b/src/main/java/ca/spottedleaf/concurrentutil/lock/ReentrantAreaLock.java -@@ -0,0 +1,350 @@ -+package ca.spottedleaf.concurrentutil.lock; ++ remove.halt(false); ++ this.threads.remove(remove); ++ } ++ } else { ++ // we need to add threads ++ for (int i = 0, difference = threads - currentThreads.length; i < difference; ++i) { ++ final PrioritisedThread thread = new PrioritisedThread(); + -+import ca.spottedleaf.concurrentutil.collection.MultiThreadedQueue; -+import ca.spottedleaf.concurrentutil.map.ConcurrentLong2ReferenceChainedHashTable; -+import ca.spottedleaf.concurrentutil.util.IntPairUtil; -+import java.util.Objects; -+import java.util.concurrent.locks.LockSupport; ++ this.threadModifier.accept(thread); ++ this.aliveThreads.add(thread); ++ this.threads.add(thread); + -+public final class ReentrantAreaLock { ++ thread.start(); ++ } ++ } ++ } ++ } + -+ public final int coordinateShift; ++ private static int compareInsideGroup(final ExecutorGroup.ThreadPoolExecutor src, final Priority srcPriority, ++ final ExecutorGroup.ThreadPoolExecutor dst, final Priority dstPriority) { ++ final int priorityCompare = srcPriority.ordinal() - dstPriority.ordinal(); ++ if (priorityCompare != 0) { ++ return priorityCompare; ++ } + -+ // aggressive load factor to reduce contention -+ private final ConcurrentLong2ReferenceChainedHashTable nodes = ConcurrentLong2ReferenceChainedHashTable.createWithCapacity(128, 0.2f); ++ final int parallelismCompare = src.currentParallelism - dst.currentParallelism; ++ if (parallelismCompare != 0) { ++ return parallelismCompare; ++ } + -+ public ReentrantAreaLock(final int coordinateShift) { -+ this.coordinateShift = coordinateShift; ++ return TimeUtil.compareTimes(src.lastRetrieved, dst.lastRetrieved); + } + -+ public boolean isHeldByCurrentThread(final int x, final int z) { -+ final Thread currThread = Thread.currentThread(); -+ final int shift = this.coordinateShift; -+ final int sectionX = x >> shift; -+ final int sectionZ = z >> shift; ++ private static int compareOutsideGroup(final ExecutorGroup.ThreadPoolExecutor src, final Priority srcPriority, ++ final ExecutorGroup.ThreadPoolExecutor dst, final Priority dstPriority) { ++ if (src.getGroup().division == dst.getGroup().division) { ++ // can only compare priorities inside the same division ++ final int priorityCompare = srcPriority.ordinal() - dstPriority.ordinal(); ++ if (priorityCompare != 0) { ++ return priorityCompare; ++ } ++ } + -+ final long coordinate = IntPairUtil.key(sectionX, sectionZ); -+ final Node node = this.nodes.get(coordinate); ++ final int parallelismCompare = src.getGroup().currentParallelism - dst.getGroup().currentParallelism; ++ if (parallelismCompare != 0) { ++ return parallelismCompare; ++ } + -+ return node != null && node.thread == currThread; ++ return TimeUtil.compareTimes(src.lastRetrieved, dst.lastRetrieved); + } + -+ public boolean isHeldByCurrentThread(final int centerX, final int centerZ, final int radius) { -+ return this.isHeldByCurrentThread(centerX - radius, centerZ - radius, centerX + radius, centerZ + radius); -+ } ++ private ExecutorGroup.ThreadPoolExecutor obtainQueue() { ++ final long time = System.nanoTime(); ++ synchronized (this) { ++ ExecutorGroup.ThreadPoolExecutor ret = null; ++ Priority retPriority = null; ++ ++ for (final ExecutorGroup executorGroup : this.executors.getArray()) { ++ ExecutorGroup.ThreadPoolExecutor highest = null; ++ Priority highestPriority = null; ++ for (final ExecutorGroup.ThreadPoolExecutor executor : executorGroup.executors.getArray()) { ++ final int maxParallelism = executor.maxParallelism; ++ if (maxParallelism > 0 && executor.currentParallelism >= maxParallelism) { ++ continue; ++ } + -+ public boolean isHeldByCurrentThread(final int fromX, final int fromZ, final int toX, final int toZ) { -+ if (fromX > toX || fromZ > toZ) { -+ throw new IllegalArgumentException(); -+ } ++ final Priority priority = executor.getTargetPriority(); + -+ final Thread currThread = Thread.currentThread(); -+ final int shift = this.coordinateShift; -+ final int fromSectionX = fromX >> shift; -+ final int fromSectionZ = fromZ >> shift; -+ final int toSectionX = toX >> shift; -+ final int toSectionZ = toZ >> shift; ++ if (priority == null) { ++ continue; ++ } + -+ for (int currZ = fromSectionZ; currZ <= toSectionZ; ++currZ) { -+ for (int currX = fromSectionX; currX <= toSectionX; ++currX) { -+ final long coordinate = IntPairUtil.key(currX, currZ); ++ if (highestPriority == null || compareInsideGroup(highest, highestPriority, executor, priority) > 0) { ++ highest = executor; ++ highestPriority = priority; ++ } ++ } + -+ final Node node = this.nodes.get(coordinate); ++ if (highest == null) { ++ continue; ++ } + -+ if (node == null || node.thread != currThread) { -+ return false; ++ if (ret == null || compareOutsideGroup(ret, retPriority, highest, highestPriority) > 0) { ++ ret = highest; ++ retPriority = highestPriority; + } + } -+ } + -+ return true; -+ } ++ if (ret != null) { ++ ret.lastRetrieved = time; ++ ++ret.currentParallelism; ++ ++ret.getGroup().currentParallelism; ++ return ret; ++ } + -+ public Node tryLock(final int x, final int z) { -+ return this.tryLock(x, z, x, z); ++ return ret; ++ } + } + -+ public Node tryLock(final int centerX, final int centerZ, final int radius) { -+ return this.tryLock(centerX - radius, centerZ - radius, centerX + radius, centerZ + radius); ++ private void returnQueue(final ExecutorGroup.ThreadPoolExecutor executor) { ++ synchronized (this) { ++ --executor.currentParallelism; ++ --executor.getGroup().currentParallelism; ++ } ++ ++ if (executor.isShutdown() && executor.queue.hasNoScheduledTasks()) { ++ executor.getGroup().executors.remove(executor); ++ } + } + -+ public Node tryLock(final int fromX, final int fromZ, final int toX, final int toZ) { -+ if (fromX > toX || fromZ > toZ) { -+ throw new IllegalArgumentException(); ++ private void notifyAllThreads() { ++ for (final PrioritisedThread thread : this.threads.getArray()) { ++ thread.notifyTasks(); + } ++ } + -+ final Thread currThread = Thread.currentThread(); -+ final int shift = this.coordinateShift; -+ final int fromSectionX = fromX >> shift; -+ final int fromSectionZ = fromZ >> shift; -+ final int toSectionX = toX >> shift; -+ final int toSectionZ = toZ >> shift; ++ public ExecutorGroup createExecutorGroup(final int division, final int flags) { ++ synchronized (this) { ++ if (this.shutdown) { ++ throw new IllegalStateException("Queue is shutdown: " + this.toString()); ++ } + -+ final long[] areaAffected = new long[(toSectionX - fromSectionX + 1) * (toSectionZ - fromSectionZ + 1)]; -+ int areaAffectedLen = 0; ++ final ExecutorGroup ret = new ExecutorGroup(division, flags); + -+ final Node ret = new Node(this, areaAffected, currThread); ++ this.executors.add(ret); + -+ boolean failed = false; ++ return ret; ++ } ++ } + -+ // try to fast acquire area -+ for (int currZ = fromSectionZ; currZ <= toSectionZ; ++currZ) { -+ for (int currX = fromSectionX; currX <= toSectionX; ++currX) { -+ final long coordinate = IntPairUtil.key(currX, currZ); ++ private final class PrioritisedThread extends PrioritisedQueueExecutorThread { + -+ final Node prev = this.nodes.putIfAbsent(coordinate, ret); ++ private final AtomicBoolean alertedHighPriority = new AtomicBoolean(); + -+ if (prev == null) { -+ areaAffected[areaAffectedLen++] = coordinate; -+ continue; -+ } ++ public PrioritisedThread() { ++ super(null); ++ } + -+ if (prev.thread != currThread) { -+ failed = true; -+ break; ++ public boolean alertHighPriorityExecutor() { ++ if (!this.notifyTasks()) { ++ if (!this.alertedHighPriority.get()) { ++ this.alertedHighPriority.set(true); + } ++ return false; + } ++ ++ return true; + } + -+ if (!failed) { -+ return ret; ++ private boolean isAlertedHighPriority() { ++ return this.alertedHighPriority.get() && this.alertedHighPriority.getAndSet(false); + } + -+ // failed, undo logic -+ if (areaAffectedLen != 0) { -+ for (int i = 0; i < areaAffectedLen; ++i) { -+ final long key = areaAffected[i]; ++ @Override ++ protected void die() { ++ PrioritisedThreadPool.this.die(this); ++ } + -+ if (this.nodes.remove(key) != ret) { -+ throw new IllegalStateException(); ++ @Override ++ protected boolean pollTasks() { ++ boolean ret = false; ++ ++ for (;;) { ++ if (this.halted) { ++ break; + } -+ } + -+ areaAffectedLen = 0; ++ final ExecutorGroup.ThreadPoolExecutor executor = PrioritisedThreadPool.this.obtainQueue(); ++ if (executor == null) { ++ break; ++ } ++ final long deadline = System.nanoTime() + executor.queueMaxHoldTime; ++ do { ++ try { ++ if (this.halted || executor.halt) { ++ break; ++ } ++ if (!executor.executeTask()) { ++ // no more tasks, try next queue ++ break; ++ } ++ ret = true; ++ } catch (final Throwable throwable) { ++ LOGGER.error("Exception thrown from thread '" + this.getName() + "' in queue '" + executor.toString() + "'", throwable); ++ } ++ } while (!this.isAlertedHighPriority() && System.nanoTime() <= deadline); + -+ // since we inserted, we need to drain waiters -+ Thread unpark; -+ while ((unpark = ret.pollOrBlockAdds()) != null) { -+ LockSupport.unpark(unpark); ++ PrioritisedThreadPool.this.returnQueue(executor); + } -+ } + -+ return null; -+ } + -+ public Node lock(final int x, final int z) { -+ final Thread currThread = Thread.currentThread(); -+ final int shift = this.coordinateShift; -+ final int sectionX = x >> shift; -+ final int sectionZ = z >> shift; ++ return ret; ++ } ++ } + -+ final long coordinate = IntPairUtil.key(sectionX, sectionZ); -+ final long[] areaAffected = new long[1]; -+ areaAffected[0] = coordinate; ++ public final class ExecutorGroup { + -+ final Node ret = new Node(this, areaAffected, currThread); ++ private final AtomicLong subOrderGenerator = new AtomicLong(); ++ private final COWArrayList executors = new COWArrayList<>(ThreadPoolExecutor.class); + -+ for (long failures = 0L;;) { -+ final Node park; ++ private final int division; ++ private int currentParallelism; + -+ // try to fast acquire area -+ { -+ final Node prev = this.nodes.putIfAbsent(coordinate, ret); ++ private ExecutorGroup(final int division, final int flags) { ++ this.division = division; ++ } + -+ if (prev == null) { -+ ret.areaAffectedLen = 1; -+ return ret; -+ } else if (prev.thread != currThread) { -+ park = prev; -+ } else { -+ // only one node we would want to acquire, and it's owned by this thread already -+ // areaAffectedLen = 0 already -+ return ret; -+ } -+ } ++ public ThreadPoolExecutor[] getAllExecutors() { ++ return this.executors.getArray().clone(); ++ } + -+ ++failures; ++ private PrioritisedThreadPool getThreadPool() { ++ return PrioritisedThreadPool.this; ++ } + -+ if (failures > 128L && park.add(currThread)) { -+ LockSupport.park(); -+ } else { -+ // high contention, spin wait -+ if (failures < 128L) { -+ for (long i = 0; i < failures; ++i) { -+ Thread.onSpinWait(); -+ } -+ failures = failures << 1; -+ } else if (failures < 1_200L) { -+ LockSupport.parkNanos(1_000L); -+ failures = failures + 1L; -+ } else { // scale 0.1ms (100us) per failure -+ Thread.yield(); -+ LockSupport.parkNanos(100_000L * failures); -+ failures = failures + 1L; ++ public ThreadPoolExecutor createExecutor(final int maxParallelism, final long queueMaxHoldTime, final int flags) { ++ synchronized (PrioritisedThreadPool.this) { ++ if (PrioritisedThreadPool.this.shutdown) { ++ throw new IllegalStateException("Queue is shutdown: " + PrioritisedThreadPool.this.toString()); + } -+ } -+ } -+ } + -+ public Node lock(final int centerX, final int centerZ, final int radius) { -+ return this.lock(centerX - radius, centerZ - radius, centerX + radius, centerZ + radius); -+ } ++ final ThreadPoolExecutor ret = new ThreadPoolExecutor(maxParallelism, queueMaxHoldTime, flags); + -+ public Node lock(final int fromX, final int fromZ, final int toX, final int toZ) { -+ if (fromX > toX || fromZ > toZ) { -+ throw new IllegalArgumentException(); ++ this.executors.add(ret); ++ ++ return ret; ++ } + } + -+ final Thread currThread = Thread.currentThread(); -+ final int shift = this.coordinateShift; -+ final int fromSectionX = fromX >> shift; -+ final int fromSectionZ = fromZ >> shift; -+ final int toSectionX = toX >> shift; -+ final int toSectionZ = toZ >> shift; ++ public final class ThreadPoolExecutor implements PrioritisedExecutor { + -+ if (((fromSectionX ^ toSectionX) | (fromSectionZ ^ toSectionZ)) == 0) { -+ return this.lock(fromX, fromZ); -+ } ++ private final PrioritisedTaskQueue queue = new PrioritisedTaskQueue(); + -+ final long[] areaAffected = new long[(toSectionX - fromSectionX + 1) * (toSectionZ - fromSectionZ + 1)]; -+ int areaAffectedLen = 0; ++ private volatile int maxParallelism; ++ private final long queueMaxHoldTime; ++ private volatile int currentParallelism; ++ private volatile boolean halt; ++ private long lastRetrieved = System.nanoTime(); + -+ final Node ret = new Node(this, areaAffected, currThread); ++ private ThreadPoolExecutor(final int maxParallelism, final long queueMaxHoldTime, final int flags) { ++ this.maxParallelism = maxParallelism; ++ this.queueMaxHoldTime = queueMaxHoldTime; ++ } + -+ for (long failures = 0L;;) { -+ Node park = null; -+ boolean addedToArea = false; -+ boolean alreadyOwned = false; -+ boolean allOwned = true; ++ private ExecutorGroup getGroup() { ++ return ExecutorGroup.this; ++ } + -+ // try to fast acquire area -+ for (int currZ = fromSectionZ; currZ <= toSectionZ; ++currZ) { -+ for (int currX = fromSectionX; currX <= toSectionX; ++currX) { -+ final long coordinate = IntPairUtil.key(currX, currZ); ++ private boolean canNotify() { ++ if (this.halt) { ++ return false; ++ } + -+ final Node prev = this.nodes.putIfAbsent(coordinate, ret); ++ final int max = this.maxParallelism; ++ return max < 0 || this.currentParallelism < max; ++ } + -+ if (prev == null) { -+ addedToArea = true; -+ allOwned = false; -+ areaAffected[areaAffectedLen++] = coordinate; -+ continue; ++ private void notifyHighPriority() { ++ if (!this.canNotify()) { ++ return; ++ } ++ for (final PrioritisedThread thread : this.getGroup().getThreadPool().threads.getArray()) { ++ if (thread.alertHighPriorityExecutor()) { ++ return; + } ++ } ++ } + -+ if (prev.thread != currThread) { -+ park = prev; -+ alreadyOwned = true; -+ break; ++ private void notifyScheduled() { ++ if (!this.canNotify()) { ++ return; ++ } ++ for (final PrioritisedThread thread : this.getGroup().getThreadPool().threads.getArray()) { ++ if (thread.notifyTasks()) { ++ return; + } + } + } + -+ // check for failure -+ if ((park != null && addedToArea) || (park == null && alreadyOwned && !allOwned)) { -+ // failure to acquire: added and we need to block, or improper lock usage -+ for (int i = 0; i < areaAffectedLen; ++i) { -+ final long key = areaAffected[i]; ++ /** ++ * Removes this queue from the thread pool without shutting the queue down or waiting for queued tasks to be executed ++ */ ++ public void halt() { ++ this.halt = true; + -+ if (this.nodes.remove(key) != ret) { -+ throw new IllegalStateException(); ++ ExecutorGroup.this.executors.remove(this); ++ } ++ ++ /** ++ * Returns whether this executor is scheduled to run tasks or is running tasks, otherwise it returns whether ++ * this queue is not halted and not shutdown. ++ */ ++ public boolean isActive() { ++ if (this.halt) { ++ return this.currentParallelism > 0; ++ } else { ++ if (!this.isShutdown()) { ++ return true; + } ++ ++ return !this.queue.hasNoScheduledTasks(); + } ++ } + -+ areaAffectedLen = 0; ++ @Override ++ public boolean shutdown() { ++ if (!this.queue.shutdown()) { ++ return false; ++ } + -+ // since we inserted, we need to drain waiters -+ Thread unpark; -+ while ((unpark = ret.pollOrBlockAdds()) != null) { -+ LockSupport.unpark(unpark); ++ if (this.queue.hasNoScheduledTasks()) { ++ ExecutorGroup.this.executors.remove(this); + } ++ ++ return true; + } + -+ if (park == null) { -+ if (alreadyOwned && !allOwned) { -+ throw new IllegalStateException("Improper lock usage: Should never acquire intersecting areas"); ++ @Override ++ public boolean isShutdown() { ++ return this.queue.isShutdown(); ++ } ++ ++ public void setMaxParallelism(final int maxParallelism) { ++ this.maxParallelism = maxParallelism; ++ // assume that we could have increased the parallelism ++ if (this.getTargetPriority() != null) { ++ ExecutorGroup.this.getThreadPool().notifyAllThreads(); + } -+ ret.areaAffectedLen = areaAffectedLen; -+ return ret; + } + -+ // failed ++ Priority getTargetPriority() { ++ final Priority ret = this.queue.getHighestPriority(); ++ if (!this.isShutdown()) { ++ return ret; ++ } + -+ ++failures; ++ return ret == null ? QUEUE_SHUTDOWN_PRIORITY : Priority.max(ret, QUEUE_SHUTDOWN_PRIORITY); ++ } + -+ if (failures > 128L && park.add(currThread)) { -+ LockSupport.park(park); -+ } else { -+ // high contention, spin wait -+ if (failures < 128L) { -+ for (long i = 0; i < failures; ++i) { -+ Thread.onSpinWait(); -+ } -+ failures = failures << 1; -+ } else if (failures < 1_200L) { -+ LockSupport.parkNanos(1_000L); -+ failures = failures + 1L; -+ } else { // scale 0.1ms (100us) per failure -+ Thread.yield(); -+ LockSupport.parkNanos(100_000L * failures); -+ failures = failures + 1L; -+ } ++ @Override ++ public long getTotalTasksScheduled() { ++ return this.queue.getTotalTasksScheduled(); + } + -+ if (addedToArea) { -+ // try again, so we need to allow adds so that other threads can properly block on us -+ ret.allowAdds(); ++ @Override ++ public long getTotalTasksExecuted() { ++ return this.queue.getTotalTasksExecuted(); + } -+ } -+ } + -+ public void unlock(final Node node) { -+ if (node.lock != this) { -+ throw new IllegalStateException("Unlock target lock mismatch"); -+ } ++ @Override ++ public long generateNextSubOrder() { ++ return ExecutorGroup.this.subOrderGenerator.getAndIncrement(); ++ } + -+ final long[] areaAffected = node.areaAffected; -+ final int areaAffectedLen = node.areaAffectedLen; ++ @Override ++ public boolean executeTask() { ++ return this.queue.executeTask(); ++ } + -+ if (areaAffectedLen == 0) { -+ // here we are not in the node map, and so do not need to remove from the node map or unblock any waiters -+ return; -+ } ++ @Override ++ public PrioritisedTask queueTask(final Runnable task) { ++ final PrioritisedTask ret = this.createTask(task); + -+ Objects.checkFromToIndex(0, areaAffectedLen, areaAffected.length); ++ ret.queue(); + -+ // remove from node map; allowing other threads to lock -+ for (int i = 0; i < areaAffectedLen; ++i) { -+ final long coordinate = areaAffected[i]; -+ if (this.nodes.remove(coordinate, node) != node) { -+ throw new IllegalStateException(); ++ return ret; + } -+ } + -+ Thread unpark; -+ while ((unpark = node.pollOrBlockAdds()) != null) { -+ LockSupport.unpark(unpark); -+ } -+ } -+ -+ public static final class Node extends MultiThreadedQueue { -+ -+ private final ReentrantAreaLock lock; -+ private final long[] areaAffected; -+ private int areaAffectedLen; -+ private final Thread thread; ++ @Override ++ public PrioritisedTask queueTask(final Runnable task, final Priority priority) { ++ final PrioritisedTask ret = this.createTask(task, priority); + -+ private Node(final ReentrantAreaLock lock, final long[] areaAffected, final Thread thread) { -+ this.lock = lock; -+ this.areaAffected = areaAffected; -+ this.thread = thread; -+ } ++ ret.queue(); + -+ @Override -+ public String toString() { -+ return "Node{" + -+ "areaAffected=" + IntPairUtil.toString(this.areaAffected, 0, this.areaAffectedLen) + -+ ", thread=" + this.thread + -+ '}'; -+ } -+ } -+} -diff --git a/src/main/java/ca/spottedleaf/concurrentutil/map/ConcurrentLong2ReferenceChainedHashTable.java b/src/main/java/ca/spottedleaf/concurrentutil/map/ConcurrentLong2ReferenceChainedHashTable.java -new file mode 100644 -index 0000000000000000000000000000000000000000..d701998b376579ec652fb94823befa3cc0bc4090 ---- /dev/null -+++ b/src/main/java/ca/spottedleaf/concurrentutil/map/ConcurrentLong2ReferenceChainedHashTable.java -@@ -0,0 +1,1684 @@ -+package ca.spottedleaf.concurrentutil.map; ++ return ret; ++ } + -+import ca.spottedleaf.concurrentutil.function.BiLong1Function; -+import ca.spottedleaf.concurrentutil.util.ConcurrentUtil; -+import ca.spottedleaf.concurrentutil.util.HashUtil; -+import ca.spottedleaf.concurrentutil.util.IntegerUtil; -+import ca.spottedleaf.concurrentutil.util.ThrowUtil; -+import ca.spottedleaf.concurrentutil.util.Validate; -+import java.lang.invoke.VarHandle; -+import java.util.Arrays; -+import java.util.Iterator; -+import java.util.NoSuchElementException; -+import java.util.PrimitiveIterator; -+import java.util.concurrent.atomic.LongAdder; -+import java.util.function.BiFunction; -+import java.util.function.Consumer; -+import java.util.function.Function; -+import java.util.function.LongConsumer; -+import java.util.function.LongFunction; -+import java.util.function.Predicate; ++ @Override ++ public PrioritisedTask queueTask(final Runnable task, final Priority priority, final long subOrder) { ++ final PrioritisedTask ret = this.createTask(task, priority, subOrder); + -+/** -+ * Concurrent hashtable implementation supporting mapping arbitrary {@code long} values onto non-null {@code Object} -+ * values with support for multiple writer and multiple reader threads. -+ * -+ *

    Happens-before relationship

    -+ *

    -+ * As with {@link java.util.concurrent.ConcurrentMap}, there is a happens-before relationship between actions in one thread -+ * prior to writing to the map and access to the results of those actions in another thread. -+ *

    -+ * -+ *

    Atomicity of functional methods

    -+ *

    -+ * Functional methods are functions declared in this class which possibly perform a write (remove, replace, or modify) -+ * to an entry in this map as a result of invoking a function on an input parameter. For example, {@link #compute(long, BiLong1Function)}, -+ * {@link #merge(long, Object, BiFunction)} and {@link #removeIf(long, Predicate)} are examples of functional methods. -+ * Functional methods will be performed atomically, that is, the input parameter is guaranteed to only be invoked at most -+ * once per function call. The consequence of this behavior however is that a critical lock for a bin entry is held, which -+ * means that if the input parameter invocation makes additional calls to write into this hash table that the result -+ * is undefined and deadlock-prone. -+ *

    -+ * -+ * @param -+ * @see java.util.concurrent.ConcurrentMap -+ */ -+public class ConcurrentLong2ReferenceChainedHashTable { ++ ret.queue(); + -+ protected static final int DEFAULT_CAPACITY = 16; -+ protected static final float DEFAULT_LOAD_FACTOR = 0.75f; -+ protected static final int MAXIMUM_CAPACITY = Integer.MIN_VALUE >>> 1; ++ return ret; ++ } + -+ protected final LongAdder size = new LongAdder(); -+ protected final float loadFactor; ++ @Override ++ public PrioritisedTask createTask(final Runnable task) { ++ return this.createTask(task, Priority.NORMAL); ++ } + -+ protected volatile TableEntry[] table; ++ @Override ++ public PrioritisedTask createTask(final Runnable task, final Priority priority) { ++ return this.createTask(task, priority, this.generateNextSubOrder()); ++ } + -+ protected static final int THRESHOLD_NO_RESIZE = -1; -+ protected static final int THRESHOLD_RESIZING = -2; -+ protected volatile int threshold; -+ protected static final VarHandle THRESHOLD_HANDLE = ConcurrentUtil.getVarHandle(ConcurrentLong2ReferenceChainedHashTable.class, "threshold", int.class); ++ @Override ++ public PrioritisedTask createTask(final Runnable task, final Priority priority, final long subOrder) { ++ return new WrappedTask(this.queue.createTask(task, priority, subOrder)); ++ } + -+ protected final int getThresholdAcquire() { -+ return (int)THRESHOLD_HANDLE.getAcquire(this); -+ } ++ private final class WrappedTask implements PrioritisedTask { + -+ protected final int getThresholdVolatile() { -+ return (int)THRESHOLD_HANDLE.getVolatile(this); -+ } ++ private final PrioritisedTask wrapped; + -+ protected final void setThresholdPlain(final int threshold) { -+ THRESHOLD_HANDLE.set(this, threshold); -+ } ++ private WrappedTask(final PrioritisedTask wrapped) { ++ this.wrapped = wrapped; ++ } + -+ protected final void setThresholdRelease(final int threshold) { -+ THRESHOLD_HANDLE.setRelease(this, threshold); -+ } ++ @Override ++ public PrioritisedExecutor getExecutor() { ++ return ThreadPoolExecutor.this; ++ } + -+ protected final void setThresholdVolatile(final int threshold) { -+ THRESHOLD_HANDLE.setVolatile(this, threshold); -+ } ++ @Override ++ public boolean queue() { ++ if (this.wrapped.queue()) { ++ final Priority priority = this.getPriority(); ++ if (priority != Priority.COMPLETING) { ++ if (priority.isHigherOrEqualPriority(HIGH_PRIORITY_NOTIFY_THRESHOLD)) { ++ ThreadPoolExecutor.this.notifyHighPriority(); ++ } else { ++ ThreadPoolExecutor.this.notifyScheduled(); ++ } ++ } ++ return true; ++ } + -+ protected final int compareExchangeThresholdVolatile(final int expect, final int update) { -+ return (int)THRESHOLD_HANDLE.compareAndExchange(this, expect, update); -+ } ++ return false; ++ } + -+ public ConcurrentLong2ReferenceChainedHashTable() { -+ this(DEFAULT_CAPACITY, DEFAULT_LOAD_FACTOR); -+ } ++ @Override ++ public boolean isQueued() { ++ return this.wrapped.isQueued(); ++ } + -+ protected static int getTargetThreshold(final int capacity, final float loadFactor) { -+ final double ret = (double)capacity * (double)loadFactor; -+ if (Double.isInfinite(ret) || ret >= ((double)Integer.MAX_VALUE)) { -+ return THRESHOLD_NO_RESIZE; -+ } ++ @Override ++ public boolean cancel() { ++ return this.wrapped.cancel(); ++ } + -+ return (int)Math.ceil(ret); -+ } ++ @Override ++ public boolean execute() { ++ return this.wrapped.execute(); ++ } + -+ protected static int getCapacityFor(final int capacity) { -+ if (capacity <= 0) { -+ throw new IllegalArgumentException("Invalid capacity: " + capacity); -+ } -+ if (capacity >= MAXIMUM_CAPACITY) { -+ return MAXIMUM_CAPACITY; -+ } -+ return IntegerUtil.roundCeilLog2(capacity); -+ } ++ @Override ++ public Priority getPriority() { ++ return this.wrapped.getPriority(); ++ } + -+ protected ConcurrentLong2ReferenceChainedHashTable(final int capacity, final float loadFactor) { -+ final int tableSize = getCapacityFor(capacity); ++ @Override ++ public boolean setPriority(final Priority priority) { ++ if (this.wrapped.setPriority(priority)) { ++ if (priority.isHigherOrEqualPriority(HIGH_PRIORITY_NOTIFY_THRESHOLD)) { ++ ThreadPoolExecutor.this.notifyHighPriority(); ++ } ++ return true; ++ } + -+ if (loadFactor <= 0.0 || !Float.isFinite(loadFactor)) { -+ throw new IllegalArgumentException("Invalid load factor: " + loadFactor); -+ } ++ return false; ++ } + -+ if (tableSize == MAXIMUM_CAPACITY) { -+ this.setThresholdPlain(THRESHOLD_NO_RESIZE); -+ } else { -+ this.setThresholdPlain(getTargetThreshold(tableSize, loadFactor)); -+ } ++ @Override ++ public boolean raisePriority(final Priority priority) { ++ if (this.wrapped.raisePriority(priority)) { ++ if (priority.isHigherOrEqualPriority(HIGH_PRIORITY_NOTIFY_THRESHOLD)) { ++ ThreadPoolExecutor.this.notifyHighPriority(); ++ } ++ return true; ++ } + -+ this.loadFactor = loadFactor; -+ // noinspection unchecked -+ this.table = (TableEntry[])new TableEntry[tableSize]; -+ } ++ return false; ++ } + -+ public static ConcurrentLong2ReferenceChainedHashTable createWithCapacity(final int capacity) { -+ return createWithCapacity(capacity, DEFAULT_LOAD_FACTOR); -+ } ++ @Override ++ public boolean lowerPriority(final Priority priority) { ++ return this.wrapped.lowerPriority(priority); ++ } + -+ public static ConcurrentLong2ReferenceChainedHashTable createWithCapacity(final int capacity, final float loadFactor) { -+ return new ConcurrentLong2ReferenceChainedHashTable<>(capacity, loadFactor); -+ } ++ @Override ++ public long getSubOrder() { ++ return this.wrapped.getSubOrder(); ++ } + -+ public static ConcurrentLong2ReferenceChainedHashTable createWithExpected(final int expected) { -+ return createWithExpected(expected, DEFAULT_LOAD_FACTOR); -+ } ++ @Override ++ public boolean setSubOrder(final long subOrder) { ++ return this.wrapped.setSubOrder(subOrder); ++ } + -+ public static ConcurrentLong2ReferenceChainedHashTable createWithExpected(final int expected, final float loadFactor) { -+ final int capacity = (int)Math.ceil((double)expected / (double)loadFactor); ++ @Override ++ public boolean raiseSubOrder(final long subOrder) { ++ return this.wrapped.raiseSubOrder(subOrder); ++ } + -+ return createWithCapacity(capacity, loadFactor); -+ } ++ @Override ++ public boolean lowerSubOrder(final long subOrder) { ++ return this.wrapped.lowerSubOrder(subOrder); ++ } + -+ /** must be deterministic given a key */ -+ protected static int getHash(final long key) { -+ return (int)HashUtil.mix(key); -+ } ++ @Override ++ public boolean setPriorityAndSubOrder(final Priority priority, final long subOrder) { ++ if (this.wrapped.setPriorityAndSubOrder(priority, subOrder)) { ++ if (priority.isHigherOrEqualPriority(HIGH_PRIORITY_NOTIFY_THRESHOLD)) { ++ ThreadPoolExecutor.this.notifyHighPriority(); ++ } ++ return true; ++ } + -+ /** -+ * Returns the load factor associated with this map. -+ */ -+ public final float getLoadFactor() { -+ return this.loadFactor; ++ return false; ++ } ++ } ++ } + } + -+ protected static TableEntry getAtIndexVolatile(final TableEntry[] table, final int index) { -+ //noinspection unchecked -+ return (TableEntry)TableEntry.TABLE_ENTRY_ARRAY_HANDLE.getVolatile(table, index); -+ } ++ private static final class COWArrayList { + -+ protected static void setAtIndexRelease(final TableEntry[] table, final int index, final TableEntry value) { -+ TableEntry.TABLE_ENTRY_ARRAY_HANDLE.setRelease(table, index, value); -+ } ++ private volatile E[] array; + -+ protected static void setAtIndexVolatile(final TableEntry[] table, final int index, final TableEntry value) { -+ TableEntry.TABLE_ENTRY_ARRAY_HANDLE.setVolatile(table, index, value); -+ } ++ public COWArrayList(final Class clazz) { ++ this.array = (E[])Array.newInstance(clazz, 0); ++ } + -+ protected static TableEntry compareAndExchangeAtIndexVolatile(final TableEntry[] table, final int index, -+ final TableEntry expect, final TableEntry update) { -+ //noinspection unchecked -+ return (TableEntry)TableEntry.TABLE_ENTRY_ARRAY_HANDLE.compareAndExchange(table, index, expect, update); -+ } ++ public E[] getArray() { ++ return this.array; ++ } + -+ /** -+ * Returns the possible node associated with the key, or {@code null} if there is no such node. The node -+ * returned may have a {@code null} {@link TableEntry#value}, in which case the node is a placeholder for -+ * a compute/computeIfAbsent call. The placeholder node should not be considered mapped in order to preserve -+ * happens-before relationships between writes and reads in the map. -+ */ -+ protected final TableEntry getNode(final long key) { -+ final int hash = getHash(key); ++ public void add(final E element) { ++ synchronized (this) { ++ final E[] array = this.array; + -+ TableEntry[] table = this.table; -+ for (;;) { -+ TableEntry node = getAtIndexVolatile(table, hash & (table.length - 1)); ++ final E[] copy = Arrays.copyOf(array, array.length + 1); ++ copy[array.length] = element; + -+ if (node == null) { -+ // node == null -+ return node; ++ this.array = copy; + } ++ } + -+ if (node.resize) { -+ table = (TableEntry[])node.getValuePlain(); -+ continue; -+ } ++ public boolean remove(final E element) { ++ synchronized (this) { ++ final E[] array = this.array; ++ int index = -1; ++ for (int i = 0, len = array.length; i < len; ++i) { ++ if (array[i] == element) { ++ index = i; ++ break; ++ } ++ } + -+ for (; node != null; node = node.getNextVolatile()) { -+ if (node.key == key) { -+ return node; ++ if (index == -1) { ++ return false; + } ++ ++ final E[] copy = (E[])Array.newInstance(array.getClass().getComponentType(), array.length - 1); ++ ++ System.arraycopy(array, 0, copy, 0, index); ++ System.arraycopy(array, index + 1, copy, index, (array.length - 1) - index); ++ ++ this.array = copy; + } + -+ // node == null -+ return node; ++ return true; + } + } ++} +diff --git a/src/main/java/ca/spottedleaf/concurrentutil/function/BiLong1Function.java b/src/main/java/ca/spottedleaf/concurrentutil/function/BiLong1Function.java +new file mode 100644 +index 0000000000000000000000000000000000000000..94bfd7c56ffcea7d6491e94a7804bc3bd60fe9c3 +--- /dev/null ++++ b/src/main/java/ca/spottedleaf/concurrentutil/function/BiLong1Function.java +@@ -0,0 +1,8 @@ ++package ca.spottedleaf.concurrentutil.function; + -+ /** -+ * Returns the currently mapped value associated with the specified key, or {@code null} if there is none. -+ * -+ * @param key Specified key -+ */ -+ public V get(final long key) { -+ final TableEntry node = this.getNode(key); -+ return node == null ? null : node.getValueVolatile(); -+ } ++@FunctionalInterface ++public interface BiLong1Function { + -+ /** -+ * Returns the currently mapped value associated with the specified key, or the specified default value if there is none. -+ * -+ * @param key Specified key -+ * @param defaultValue Specified default value -+ */ -+ public V getOrDefault(final long key, final V defaultValue) { -+ final TableEntry node = this.getNode(key); -+ if (node == null) { -+ return defaultValue; -+ } ++ public R apply(final long t1, final T t2); + -+ final V ret = node.getValueVolatile(); -+ if (ret == null) { -+ // ret == null for nodes pre-allocated to compute() and friends -+ return defaultValue; -+ } ++} +diff --git a/src/main/java/ca/spottedleaf/concurrentutil/function/BiLongObjectConsumer.java b/src/main/java/ca/spottedleaf/concurrentutil/function/BiLongObjectConsumer.java +new file mode 100644 +index 0000000000000000000000000000000000000000..8e7eef07960a18d0593688eba55adfa1c85efadf +--- /dev/null ++++ b/src/main/java/ca/spottedleaf/concurrentutil/function/BiLongObjectConsumer.java +@@ -0,0 +1,8 @@ ++package ca.spottedleaf.concurrentutil.function; + -+ return ret; -+ } ++@FunctionalInterface ++public interface BiLongObjectConsumer { + -+ /** -+ * Returns whether the specified key is mapped to some value. -+ * @param key Specified key -+ */ -+ public boolean containsKey(final long key) { -+ // cannot use getNode, as the node may be a placeholder for compute() -+ return this.get(key) != null; -+ } ++ public void accept(final long key, final V value); + -+ /** -+ * Returns whether the specified value has a key mapped to it. -+ * @param value Specified value -+ * @throws NullPointerException If value is null -+ */ -+ public boolean containsValue(final V value) { -+ Validate.notNull(value, "Value cannot be null"); ++} +diff --git a/src/main/java/ca/spottedleaf/concurrentutil/lock/ReentrantAreaLock.java b/src/main/java/ca/spottedleaf/concurrentutil/lock/ReentrantAreaLock.java +new file mode 100644 +index 0000000000000000000000000000000000000000..7ffe4379b06c03c56abbcbdee3bb720894a10702 +--- /dev/null ++++ b/src/main/java/ca/spottedleaf/concurrentutil/lock/ReentrantAreaLock.java +@@ -0,0 +1,350 @@ ++package ca.spottedleaf.concurrentutil.lock; + -+ final NodeIterator iterator = new NodeIterator<>(this.table); ++import ca.spottedleaf.concurrentutil.collection.MultiThreadedQueue; ++import ca.spottedleaf.concurrentutil.map.ConcurrentLong2ReferenceChainedHashTable; ++import ca.spottedleaf.concurrentutil.util.IntPairUtil; ++import java.util.Objects; ++import java.util.concurrent.locks.LockSupport; + -+ TableEntry node; -+ while ((node = iterator.findNext()) != null) { -+ // need to use acquire here to ensure the happens-before relationship -+ if (node.getValueAcquire() == value) { -+ return true; -+ } -+ } ++public final class ReentrantAreaLock { + -+ return false; ++ public final int coordinateShift; ++ ++ // aggressive load factor to reduce contention ++ private final ConcurrentLong2ReferenceChainedHashTable nodes = ConcurrentLong2ReferenceChainedHashTable.createWithCapacity(128, 0.2f); ++ ++ public ReentrantAreaLock(final int coordinateShift) { ++ this.coordinateShift = coordinateShift; + } + -+ /** -+ * Returns the number of mappings in this map. -+ */ -+ public int size() { -+ final long ret = this.size.sum(); ++ public boolean isHeldByCurrentThread(final int x, final int z) { ++ final Thread currThread = Thread.currentThread(); ++ final int shift = this.coordinateShift; ++ final int sectionX = x >> shift; ++ final int sectionZ = z >> shift; + -+ if (ret <= 0L) { -+ return 0; -+ } -+ if (ret >= (long)Integer.MAX_VALUE) { -+ return Integer.MAX_VALUE; -+ } ++ final long coordinate = IntPairUtil.key(sectionX, sectionZ); ++ final Node node = this.nodes.get(coordinate); + -+ return (int)ret; ++ return node != null && node.thread == currThread; + } + -+ /** -+ * Returns whether this map has no mappings. -+ */ -+ public boolean isEmpty() { -+ return this.size.sum() <= 0L; ++ public boolean isHeldByCurrentThread(final int centerX, final int centerZ, final int radius) { ++ return this.isHeldByCurrentThread(centerX - radius, centerZ - radius, centerX + radius, centerZ + radius); + } + -+ /** -+ * Adds count to size and checks threshold for resizing -+ */ -+ protected final void addSize(final long count) { -+ this.size.add(count); ++ public boolean isHeldByCurrentThread(final int fromX, final int fromZ, final int toX, final int toZ) { ++ if (fromX > toX || fromZ > toZ) { ++ throw new IllegalArgumentException(); ++ } + -+ final int threshold = this.getThresholdAcquire(); ++ final Thread currThread = Thread.currentThread(); ++ final int shift = this.coordinateShift; ++ final int fromSectionX = fromX >> shift; ++ final int fromSectionZ = fromZ >> shift; ++ final int toSectionX = toX >> shift; ++ final int toSectionZ = toZ >> shift; + -+ if (threshold < 0L) { -+ // resizing or no resizing allowed, in either cases we do not need to do anything -+ return; -+ } ++ for (int currZ = fromSectionZ; currZ <= toSectionZ; ++currZ) { ++ for (int currX = fromSectionX; currX <= toSectionX; ++currX) { ++ final long coordinate = IntPairUtil.key(currX, currZ); + -+ final long sum = this.size.sum(); ++ final Node node = this.nodes.get(coordinate); + -+ if (sum < (long)threshold) { -+ return; ++ if (node == null || node.thread != currThread) { ++ return false; ++ } ++ } + } + -+ if (threshold != this.compareExchangeThresholdVolatile(threshold, THRESHOLD_RESIZING)) { -+ // some other thread resized -+ return; -+ } ++ return true; ++ } + -+ // create new table -+ this.resize(sum); ++ public Node tryLock(final int x, final int z) { ++ return this.tryLock(x, z, x, z); + } + -+ /** -+ * Resizes table, only invoke for the thread which has successfully updated threshold to {@link #THRESHOLD_RESIZING} -+ * @param sum Estimate of current mapping count, must be >= old threshold -+ */ -+ private void resize(final long sum) { -+ int capacity; ++ public Node tryLock(final int centerX, final int centerZ, final int radius) { ++ return this.tryLock(centerX - radius, centerZ - radius, centerX + radius, centerZ + radius); ++ } + -+ // add 1.0, as sum may equal threshold (in which case, sum / loadFactor = current capacity) -+ // adding 1.0 should at least raise the size by a factor of two due to usage of roundCeilLog2 -+ final double targetD = ((double)sum / (double)this.loadFactor) + 1.0; -+ if (targetD >= (double)MAXIMUM_CAPACITY) { -+ capacity = MAXIMUM_CAPACITY; -+ } else { -+ capacity = (int)Math.ceil(targetD); -+ capacity = IntegerUtil.roundCeilLog2(capacity); -+ if (capacity > MAXIMUM_CAPACITY) { -+ capacity = MAXIMUM_CAPACITY; -+ } ++ public Node tryLock(final int fromX, final int fromZ, final int toX, final int toZ) { ++ if (fromX > toX || fromZ > toZ) { ++ throw new IllegalArgumentException(); + } + -+ // create new table data ++ final Thread currThread = Thread.currentThread(); ++ final int shift = this.coordinateShift; ++ final int fromSectionX = fromX >> shift; ++ final int fromSectionZ = fromZ >> shift; ++ final int toSectionX = toX >> shift; ++ final int toSectionZ = toZ >> shift; + -+ final TableEntry[] newTable = new TableEntry[capacity]; -+ // noinspection unchecked -+ final TableEntry resizeNode = new TableEntry<>(0L, (V)newTable, true); ++ final long[] areaAffected = new long[(toSectionX - fromSectionX + 1) * (toSectionZ - fromSectionZ + 1)]; ++ int areaAffectedLen = 0; + -+ // transfer nodes from old table ++ final Node ret = new Node(this, areaAffected, currThread); + -+ // does not need to be volatile read, just plain -+ final TableEntry[] oldTable = this.table; ++ boolean failed = false; + -+ // when resizing, the old entries at bin i (where i = hash % oldTable.length) are assigned to -+ // bin k in the new table (where k = hash % newTable.length) -+ // since both table lengths are powers of two (specifically, newTable is a multiple of oldTable), -+ // the possible number of locations in the new table to assign any given i is newTable.length/oldTable.length ++ // try to fast acquire area ++ for (int currZ = fromSectionZ; currZ <= toSectionZ; ++currZ) { ++ for (int currX = fromSectionX; currX <= toSectionX; ++currX) { ++ final long coordinate = IntPairUtil.key(currX, currZ); + -+ // we can build the new linked nodes for the new table by using a work array sized to newTable.length/oldTable.length -+ // which holds the _last_ entry in the chain per bin ++ final Node prev = this.nodes.putIfAbsent(coordinate, ret); + -+ final int capOldShift = IntegerUtil.floorLog2(oldTable.length); -+ final int capDiffShift = IntegerUtil.floorLog2(capacity) - capOldShift; ++ if (prev == null) { ++ areaAffected[areaAffectedLen++] = coordinate; ++ continue; ++ } + -+ if (capDiffShift == 0) { -+ throw new IllegalStateException("Resizing to same size"); ++ if (prev.thread != currThread) { ++ failed = true; ++ break; ++ } ++ } + } + -+ final TableEntry[] work = new TableEntry[1 << capDiffShift]; // typically, capDiffShift = 1 ++ if (!failed) { ++ return ret; ++ } + -+ for (int i = 0, len = oldTable.length; i < len; ++i) { -+ TableEntry binNode = getAtIndexVolatile(oldTable, i); ++ // failed, undo logic ++ if (areaAffectedLen != 0) { ++ for (int i = 0; i < areaAffectedLen; ++i) { ++ final long key = areaAffected[i]; + -+ for (;;) { -+ if (binNode == null) { -+ // just need to replace the bin node, do not need to move anything -+ if (null == (binNode = compareAndExchangeAtIndexVolatile(oldTable, i, null, resizeNode))) { -+ break; -+ } // else: binNode != null, fall through ++ if (this.nodes.remove(key) != ret) { ++ throw new IllegalStateException(); + } ++ } + -+ // need write lock to block other writers -+ synchronized (binNode) { -+ if (binNode != (binNode = getAtIndexVolatile(oldTable, i))) { -+ continue; -+ } ++ areaAffectedLen = 0; + -+ // an important detail of resizing is that we do not need to be concerned with synchronisation on -+ // writes to the new table, as no access to any nodes on bin i on oldTable will occur until a thread -+ // sees the resizeNode -+ // specifically, as long as the resizeNode is release written there are no cases where another thread -+ // will see our writes to the new table ++ // since we inserted, we need to drain waiters ++ Thread unpark; ++ while ((unpark = ret.pollOrBlockAdds()) != null) { ++ LockSupport.unpark(unpark); ++ } ++ } + -+ TableEntry next = binNode.getNextPlain(); ++ return null; ++ } + -+ if (next == null) { -+ // simple case: do not use work array ++ public Node lock(final int x, final int z) { ++ final Thread currThread = Thread.currentThread(); ++ final int shift = this.coordinateShift; ++ final int sectionX = x >> shift; ++ final int sectionZ = z >> shift; + -+ // do not need to create new node, readers only need to see the state of the map at the -+ // beginning of a call, so any additions onto _next_ don't really matter -+ // additionally, the old node is replaced so that writers automatically forward to the new table, -+ // which resolves any issues -+ newTable[getHash(binNode.key) & (capacity - 1)] = binNode; -+ } else { -+ // reset for next usage -+ Arrays.fill(work, null); ++ final long coordinate = IntPairUtil.key(sectionX, sectionZ); ++ final long[] areaAffected = new long[1]; ++ areaAffected[0] = coordinate; + -+ for (TableEntry curr = binNode; curr != null; curr = curr.getNextPlain()) { -+ final int newTableIdx = getHash(curr.key) & (capacity - 1); -+ final int workIdx = newTableIdx >>> capOldShift; ++ final Node ret = new Node(this, areaAffected, currThread); + -+ final TableEntry replace = new TableEntry<>(curr.key, curr.getValuePlain()); ++ for (long failures = 0L;;) { ++ final Node park; + -+ final TableEntry workNode = work[workIdx]; -+ work[workIdx] = replace; ++ // try to fast acquire area ++ { ++ final Node prev = this.nodes.putIfAbsent(coordinate, ret); + -+ if (workNode == null) { -+ newTable[newTableIdx] = replace; -+ continue; -+ } else { -+ workNode.setNextPlain(replace); -+ continue; -+ } -+ } -+ } ++ if (prev == null) { ++ ret.areaAffectedLen = 1; ++ return ret; ++ } else if (prev.thread != currThread) { ++ park = prev; ++ } else { ++ // only one node we would want to acquire, and it's owned by this thread already ++ // areaAffectedLen = 0 already ++ return ret; ++ } ++ } + -+ setAtIndexRelease(oldTable, i, resizeNode); -+ break; ++ ++failures; ++ ++ if (failures > 128L && park.add(currThread)) { ++ LockSupport.park(); ++ } else { ++ // high contention, spin wait ++ if (failures < 128L) { ++ for (long i = 0; i < failures; ++i) { ++ Thread.onSpinWait(); ++ } ++ failures = failures << 1; ++ } else if (failures < 1_200L) { ++ LockSupport.parkNanos(1_000L); ++ failures = failures + 1L; ++ } else { // scale 0.1ms (100us) per failure ++ Thread.yield(); ++ LockSupport.parkNanos(100_000L * failures); ++ failures = failures + 1L; + } + } + } ++ } + -+ // calculate new threshold -+ final int newThreshold; -+ if (capacity == MAXIMUM_CAPACITY) { -+ newThreshold = THRESHOLD_NO_RESIZE; -+ } else { -+ newThreshold = getTargetThreshold(capacity, loadFactor); ++ public Node lock(final int centerX, final int centerZ, final int radius) { ++ return this.lock(centerX - radius, centerZ - radius, centerX + radius, centerZ + radius); ++ } ++ ++ public Node lock(final int fromX, final int fromZ, final int toX, final int toZ) { ++ if (fromX > toX || fromZ > toZ) { ++ throw new IllegalArgumentException(); + } + -+ this.table = newTable; -+ // finish resize operation by releasing hold on threshold -+ this.setThresholdVolatile(newThreshold); -+ } ++ final Thread currThread = Thread.currentThread(); ++ final int shift = this.coordinateShift; ++ final int fromSectionX = fromX >> shift; ++ final int fromSectionZ = fromZ >> shift; ++ final int toSectionX = toX >> shift; ++ final int toSectionZ = toZ >> shift; + -+ /** -+ * Subtracts count from size -+ */ -+ protected final void subSize(final long count) { -+ this.size.add(-count); -+ } ++ if (((fromSectionX ^ toSectionX) | (fromSectionZ ^ toSectionZ)) == 0) { ++ return this.lock(fromX, fromZ); ++ } + -+ /** -+ * Atomically updates the value associated with {@code key} to {@code value}, or inserts a new mapping with {@code key} -+ * mapped to {@code value}. -+ * @param key Specified key -+ * @param value Specified value -+ * @throws NullPointerException If value is null -+ * @return Old value previously associated with key, or {@code null} if none. -+ */ -+ public V put(final long key, final V value) { -+ Validate.notNull(value, "Value may not be null"); ++ final long[] areaAffected = new long[(toSectionX - fromSectionX + 1) * (toSectionZ - fromSectionZ + 1)]; ++ int areaAffectedLen = 0; + -+ final int hash = getHash(key); ++ final Node ret = new Node(this, areaAffected, currThread); + -+ TableEntry[] table = this.table; -+ table_loop: -+ for (;;) { -+ final int index = hash & (table.length - 1); ++ for (long failures = 0L;;) { ++ Node park = null; ++ boolean addedToArea = false; ++ boolean alreadyOwned = false; ++ boolean allOwned = true; + -+ TableEntry node = getAtIndexVolatile(table, index); -+ node_loop: -+ for (;;) { -+ if (node == null) { -+ if (null == (node = compareAndExchangeAtIndexVolatile(table, index, null, new TableEntry<>(key, value)))) { -+ // successfully inserted -+ this.addSize(1L); -+ return null; -+ } // else: node != null, fall through -+ } ++ // try to fast acquire area ++ for (int currZ = fromSectionZ; currZ <= toSectionZ; ++currZ) { ++ for (int currX = fromSectionX; currX <= toSectionX; ++currX) { ++ final long coordinate = IntPairUtil.key(currX, currZ); + -+ if (node.resize) { -+ table = (TableEntry[])node.getValuePlain(); -+ continue table_loop; -+ } ++ final Node prev = this.nodes.putIfAbsent(coordinate, ret); + -+ synchronized (node) { -+ if (node != (node = getAtIndexVolatile(table, index))) { -+ continue node_loop; -+ } -+ // plain reads are fine during synchronised access, as we are the only writer -+ TableEntry prev = null; -+ for (; node != null; prev = node, node = node.getNextPlain()) { -+ if (node.key == key) { -+ final V ret = node.getValuePlain(); -+ node.setValueVolatile(value); -+ return ret; -+ } ++ if (prev == null) { ++ addedToArea = true; ++ allOwned = false; ++ areaAffected[areaAffectedLen++] = coordinate; ++ continue; + } + -+ // volatile ordering ensured by addSize(), but we need release here -+ // to ensure proper ordering with reads and other writes -+ prev.setNextRelease(new TableEntry<>(key, value)); ++ if (prev.thread != currThread) { ++ park = prev; ++ alreadyOwned = true; ++ break; ++ } + } -+ -+ this.addSize(1L); -+ return null; + } -+ } -+ } -+ -+ /** -+ * Atomically inserts a new mapping with {@code key} mapped to {@code value} if and only if {@code key} is not -+ * currently mapped to some value. -+ * @param key Specified key -+ * @param value Specified value -+ * @throws NullPointerException If value is null -+ * @return Value currently associated with key, or {@code null} if none and {@code value} was associated. -+ */ -+ public V putIfAbsent(final long key, final V value) { -+ Validate.notNull(value, "Value may not be null"); + -+ final int hash = getHash(key); -+ -+ TableEntry[] table = this.table; -+ table_loop: -+ for (;;) { -+ final int index = hash & (table.length - 1); ++ // check for failure ++ if ((park != null && addedToArea) || (park == null && alreadyOwned && !allOwned)) { ++ // failure to acquire: added and we need to block, or improper lock usage ++ for (int i = 0; i < areaAffectedLen; ++i) { ++ final long key = areaAffected[i]; + -+ TableEntry node = getAtIndexVolatile(table, index); -+ node_loop: -+ for (;;) { -+ if (node == null) { -+ if (null == (node = compareAndExchangeAtIndexVolatile(table, index, null, new TableEntry<>(key, value)))) { -+ // successfully inserted -+ this.addSize(1L); -+ return null; -+ } // else: node != null, fall through ++ if (this.nodes.remove(key) != ret) { ++ throw new IllegalStateException(); ++ } + } + -+ if (node.resize) { -+ table = (TableEntry[])node.getValuePlain(); -+ continue table_loop; -+ } ++ areaAffectedLen = 0; + -+ // optimise ifAbsent calls: check if first node is key before attempting lock acquire -+ if (node.key == key) { -+ final V ret = node.getValueVolatile(); -+ if (ret != null) { -+ return ret; -+ } // else: fall back to lock to read the node ++ // since we inserted, we need to drain waiters ++ Thread unpark; ++ while ((unpark = ret.pollOrBlockAdds()) != null) { ++ LockSupport.unpark(unpark); + } ++ } + -+ synchronized (node) { -+ if (node != (node = getAtIndexVolatile(table, index))) { -+ continue node_loop; -+ } -+ // plain reads are fine during synchronised access, as we are the only writer -+ TableEntry prev = null; -+ for (; node != null; prev = node, node = node.getNextPlain()) { -+ if (node.key == key) { -+ return node.getValuePlain(); -+ } -+ } -+ -+ // volatile ordering ensured by addSize(), but we need release here -+ // to ensure proper ordering with reads and other writes -+ prev.setNextRelease(new TableEntry<>(key, value)); ++ if (park == null) { ++ if (alreadyOwned && !allOwned) { ++ throw new IllegalStateException("Improper lock usage: Should never acquire intersecting areas"); + } -+ -+ this.addSize(1L); -+ return null; ++ ret.areaAffectedLen = areaAffectedLen; ++ return ret; + } -+ } -+ } + -+ /** -+ * Atomically updates the value associated with {@code key} to {@code value}, or does nothing if {@code key} is not -+ * associated with a value. -+ * @param key Specified key -+ * @param value Specified value -+ * @throws NullPointerException If value is null -+ * @return Old value previously associated with key, or {@code null} if none. -+ */ -+ public V replace(final long key, final V value) { -+ Validate.notNull(value, "Value may not be null"); -+ -+ final int hash = getHash(key); -+ -+ TableEntry[] table = this.table; -+ table_loop: -+ for (;;) { -+ final int index = hash & (table.length - 1); -+ -+ TableEntry node = getAtIndexVolatile(table, index); -+ node_loop: -+ for (;;) { -+ if (node == null) { -+ return null; -+ } -+ -+ if (node.resize) { -+ table = (TableEntry[])node.getValuePlain(); -+ continue table_loop; -+ } ++ // failed + -+ synchronized (node) { -+ if (node != (node = getAtIndexVolatile(table, index))) { -+ continue node_loop; -+ } ++ ++failures; + -+ // plain reads are fine during synchronised access, as we are the only writer -+ for (; node != null; node = node.getNextPlain()) { -+ if (node.key == key) { -+ final V ret = node.getValuePlain(); -+ node.setValueVolatile(value); -+ return ret; -+ } ++ if (failures > 128L && park.add(currThread)) { ++ LockSupport.park(park); ++ } else { ++ // high contention, spin wait ++ if (failures < 128L) { ++ for (long i = 0; i < failures; ++i) { ++ Thread.onSpinWait(); + } ++ failures = failures << 1; ++ } else if (failures < 1_200L) { ++ LockSupport.parkNanos(1_000L); ++ failures = failures + 1L; ++ } else { // scale 0.1ms (100us) per failure ++ Thread.yield(); ++ LockSupport.parkNanos(100_000L * failures); ++ failures = failures + 1L; + } ++ } + -+ return null; ++ if (addedToArea) { ++ // try again, so we need to allow adds so that other threads can properly block on us ++ ret.allowAdds(); + } + } + } + -+ /** -+ * Atomically updates the value associated with {@code key} to {@code update} if the currently associated -+ * value is reference equal to {@code expect}, otherwise does nothing. -+ * @param key Specified key -+ * @param expect Expected value to check current mapped value with -+ * @param update Update value to replace mapped value with -+ * @throws NullPointerException If value is null -+ * @return If the currently mapped value is not reference equal to {@code expect}, then returns the currently mapped -+ * value. If the key is not mapped to any value, then returns {@code null}. If neither of the two cases are -+ * true, then returns {@code expect}. -+ */ -+ public V replace(final long key, final V expect, final V update) { -+ Validate.notNull(expect, "Expect may not be null"); -+ Validate.notNull(update, "Update may not be null"); ++ public void unlock(final Node node) { ++ if (node.lock != this) { ++ throw new IllegalStateException("Unlock target lock mismatch"); ++ } + -+ final int hash = getHash(key); ++ final long[] areaAffected = node.areaAffected; ++ final int areaAffectedLen = node.areaAffectedLen; + -+ TableEntry[] table = this.table; -+ table_loop: -+ for (;;) { -+ final int index = hash & (table.length - 1); ++ if (areaAffectedLen == 0) { ++ // here we are not in the node map, and so do not need to remove from the node map or unblock any waiters ++ return; ++ } + -+ TableEntry node = getAtIndexVolatile(table, index); -+ node_loop: -+ for (;;) { -+ if (node == null) { -+ return null; -+ } ++ Objects.checkFromToIndex(0, areaAffectedLen, areaAffected.length); + -+ if (node.resize) { -+ table = (TableEntry[])node.getValuePlain(); -+ continue table_loop; -+ } ++ // remove from node map; allowing other threads to lock ++ for (int i = 0; i < areaAffectedLen; ++i) { ++ final long coordinate = areaAffected[i]; ++ if (this.nodes.remove(coordinate, node) != node) { ++ throw new IllegalStateException(); ++ } ++ } + -+ synchronized (node) { -+ if (node != (node = getAtIndexVolatile(table, index))) { -+ continue node_loop; -+ } ++ Thread unpark; ++ while ((unpark = node.pollOrBlockAdds()) != null) { ++ LockSupport.unpark(unpark); ++ } ++ } + -+ // plain reads are fine during synchronised access, as we are the only writer -+ for (; node != null; node = node.getNextPlain()) { -+ if (node.key == key) { -+ final V ret = node.getValuePlain(); ++ public static final class Node extends MultiThreadedQueue { + -+ if (ret != expect) { -+ return ret; -+ } ++ private final ReentrantAreaLock lock; ++ private final long[] areaAffected; ++ private int areaAffectedLen; ++ private final Thread thread; + -+ node.setValueVolatile(update); -+ return ret; -+ } -+ } -+ } ++ private Node(final ReentrantAreaLock lock, final long[] areaAffected, final Thread thread) { ++ this.lock = lock; ++ this.areaAffected = areaAffected; ++ this.thread = thread; ++ } + -+ return null; -+ } ++ @Override ++ public String toString() { ++ return "Node{" + ++ "areaAffected=" + IntPairUtil.toString(this.areaAffected, 0, this.areaAffectedLen) + ++ ", thread=" + this.thread + ++ '}'; + } + } ++} +diff --git a/src/main/java/ca/spottedleaf/concurrentutil/map/ConcurrentLong2ReferenceChainedHashTable.java b/src/main/java/ca/spottedleaf/concurrentutil/map/ConcurrentLong2ReferenceChainedHashTable.java +new file mode 100644 +index 0000000000000000000000000000000000000000..6918f130099e6c19e20a47bfdb54915cdd13732a +--- /dev/null ++++ b/src/main/java/ca/spottedleaf/concurrentutil/map/ConcurrentLong2ReferenceChainedHashTable.java +@@ -0,0 +1,1704 @@ ++package ca.spottedleaf.concurrentutil.map; + -+ /** -+ * Atomically removes the mapping for the specified key and returns the value it was associated with. If the key -+ * is not mapped to a value, then does nothing and returns {@code null}. -+ * @param key Specified key -+ * @return Old value previously associated with key, or {@code null} if none. -+ */ -+ public V remove(final long key) { -+ final int hash = getHash(key); ++import ca.spottedleaf.concurrentutil.function.BiLong1Function; ++import ca.spottedleaf.concurrentutil.util.ConcurrentUtil; ++import ca.spottedleaf.concurrentutil.util.HashUtil; ++import ca.spottedleaf.concurrentutil.util.IntegerUtil; ++import ca.spottedleaf.concurrentutil.util.ThrowUtil; ++import ca.spottedleaf.concurrentutil.util.Validate; ++import java.lang.invoke.VarHandle; ++import java.util.Arrays; ++import java.util.Iterator; ++import java.util.NoSuchElementException; ++import java.util.PrimitiveIterator; ++import java.util.concurrent.atomic.LongAdder; ++import java.util.function.BiFunction; ++import java.util.function.Consumer; ++import java.util.function.Function; ++import java.util.function.LongConsumer; ++import java.util.function.LongFunction; ++import java.util.function.Predicate; + -+ TableEntry[] table = this.table; -+ table_loop: -+ for (;;) { -+ final int index = hash & (table.length - 1); ++/** ++ * Concurrent hashtable implementation supporting mapping arbitrary {@code long} values onto non-null {@code Object} ++ * values with support for multiple writer and multiple reader threads. ++ * ++ *

    Happens-before relationship

    ++ *

    ++ * As with {@link java.util.concurrent.ConcurrentMap}, there is a happens-before relationship between actions in one thread ++ * prior to writing to the map and access to the results of those actions in another thread. ++ *

    ++ * ++ *

    Atomicity of functional methods

    ++ *

    ++ * Functional methods are functions declared in this class which possibly perform a write (remove, replace, or modify) ++ * to an entry in this map as a result of invoking a function on an input parameter. For example, {@link #compute(long, BiLong1Function)}, ++ * {@link #merge(long, Object, BiFunction)} and {@link #removeIf(long, Predicate)} are examples of functional methods. ++ * Functional methods will be performed atomically, that is, the input parameter is guaranteed to only be invoked at most ++ * once per function call. The consequence of this behavior however is that a critical lock for a bin entry is held, which ++ * means that if the input parameter invocation makes additional calls to write into this hash table that the result ++ * is undefined and deadlock-prone. ++ *

    ++ * ++ * @param ++ * @see java.util.concurrent.ConcurrentMap ++ */ ++public class ConcurrentLong2ReferenceChainedHashTable implements Iterable> { + -+ TableEntry node = getAtIndexVolatile(table, index); -+ node_loop: -+ for (;;) { -+ if (node == null) { -+ return null; -+ } ++ protected static final int DEFAULT_CAPACITY = 16; ++ protected static final float DEFAULT_LOAD_FACTOR = 0.75f; ++ protected static final int MAXIMUM_CAPACITY = Integer.MIN_VALUE >>> 1; + -+ if (node.resize) { -+ table = (TableEntry[])node.getValuePlain(); -+ continue table_loop; -+ } ++ protected final LongAdder size = new LongAdder(); ++ protected final float loadFactor; + -+ boolean removed = false; -+ V ret = null; ++ protected volatile TableEntry[] table; + -+ synchronized (node) { -+ if (node != (node = getAtIndexVolatile(table, index))) { -+ continue node_loop; -+ } ++ protected static final int THRESHOLD_NO_RESIZE = -1; ++ protected static final int THRESHOLD_RESIZING = -2; ++ protected volatile int threshold; ++ protected static final VarHandle THRESHOLD_HANDLE = ConcurrentUtil.getVarHandle(ConcurrentLong2ReferenceChainedHashTable.class, "threshold", int.class); + -+ TableEntry prev = null; ++ protected final int getThresholdAcquire() { ++ return (int)THRESHOLD_HANDLE.getAcquire(this); ++ } + -+ // plain reads are fine during synchronised access, as we are the only writer -+ for (; node != null; prev = node, node = node.getNextPlain()) { -+ if (node.key == key) { -+ ret = node.getValuePlain(); -+ removed = true; ++ protected final int getThresholdVolatile() { ++ return (int)THRESHOLD_HANDLE.getVolatile(this); ++ } + -+ // volatile ordering ensured by addSize(), but we need release here -+ // to ensure proper ordering with reads and other writes -+ if (prev == null) { -+ setAtIndexRelease(table, index, node.getNextPlain()); -+ } else { -+ prev.setNextRelease(node.getNextPlain()); -+ } ++ protected final void setThresholdPlain(final int threshold) { ++ THRESHOLD_HANDLE.set(this, threshold); ++ } + -+ break; -+ } -+ } -+ } ++ protected final void setThresholdRelease(final int threshold) { ++ THRESHOLD_HANDLE.setRelease(this, threshold); ++ } + -+ if (removed) { -+ this.subSize(1L); -+ } ++ protected final void setThresholdVolatile(final int threshold) { ++ THRESHOLD_HANDLE.setVolatile(this, threshold); ++ } + -+ return ret; -+ } ++ protected final int compareExchangeThresholdVolatile(final int expect, final int update) { ++ return (int)THRESHOLD_HANDLE.compareAndExchange(this, expect, update); ++ } ++ ++ public ConcurrentLong2ReferenceChainedHashTable() { ++ this(DEFAULT_CAPACITY, DEFAULT_LOAD_FACTOR); ++ } ++ ++ protected static int getTargetThreshold(final int capacity, final float loadFactor) { ++ final double ret = (double)capacity * (double)loadFactor; ++ if (Double.isInfinite(ret) || ret >= ((double)Integer.MAX_VALUE)) { ++ return THRESHOLD_NO_RESIZE; + } ++ ++ return (int)Math.ceil(ret); + } + -+ /** -+ * Atomically removes the mapping for the specified key if it is mapped to {@code expect} and returns {@code expect}. If the key -+ * is not mapped to a value, then does nothing and returns {@code null}. If the key is mapped to a value that is not reference -+ * equal to {@code expect}, then returns that value. -+ * @param key Specified key -+ * @param expect Specified expected value -+ * @return The specified expected value if the key was mapped to {@code expect}. If -+ * the key is not mapped to any value, then returns {@code null}. If neither of those cases are true, -+ * then returns the current (non-null) mapped value for key. -+ */ -+ public V remove(final long key, final V expect) { -+ final int hash = getHash(key); ++ protected static int getCapacityFor(final int capacity) { ++ if (capacity <= 0) { ++ throw new IllegalArgumentException("Invalid capacity: " + capacity); ++ } ++ if (capacity >= MAXIMUM_CAPACITY) { ++ return MAXIMUM_CAPACITY; ++ } ++ return IntegerUtil.roundCeilLog2(capacity); ++ } + -+ TableEntry[] table = this.table; -+ table_loop: -+ for (;;) { -+ final int index = hash & (table.length - 1); ++ protected ConcurrentLong2ReferenceChainedHashTable(final int capacity, final float loadFactor) { ++ final int tableSize = getCapacityFor(capacity); + -+ TableEntry node = getAtIndexVolatile(table, index); -+ node_loop: -+ for (;;) { -+ if (node == null) { -+ return null; -+ } ++ if (loadFactor <= 0.0 || !Float.isFinite(loadFactor)) { ++ throw new IllegalArgumentException("Invalid load factor: " + loadFactor); ++ } + -+ if (node.resize) { -+ table = (TableEntry[])node.getValuePlain(); -+ continue table_loop; -+ } ++ if (tableSize == MAXIMUM_CAPACITY) { ++ this.setThresholdPlain(THRESHOLD_NO_RESIZE); ++ } else { ++ this.setThresholdPlain(getTargetThreshold(tableSize, loadFactor)); ++ } + -+ boolean removed = false; -+ V ret = null; ++ this.loadFactor = loadFactor; ++ // noinspection unchecked ++ this.table = (TableEntry[])new TableEntry[tableSize]; ++ } + -+ synchronized (node) { -+ if (node != (node = getAtIndexVolatile(table, index))) { -+ continue node_loop; -+ } ++ public static ConcurrentLong2ReferenceChainedHashTable createWithCapacity(final int capacity) { ++ return createWithCapacity(capacity, DEFAULT_LOAD_FACTOR); ++ } + -+ TableEntry prev = null; ++ public static ConcurrentLong2ReferenceChainedHashTable createWithCapacity(final int capacity, final float loadFactor) { ++ return new ConcurrentLong2ReferenceChainedHashTable<>(capacity, loadFactor); ++ } + -+ // plain reads are fine during synchronised access, as we are the only writer -+ for (; node != null; prev = node, node = node.getNextPlain()) { -+ if (node.key == key) { -+ ret = node.getValuePlain(); -+ if (ret == expect) { -+ removed = true; ++ public static ConcurrentLong2ReferenceChainedHashTable createWithExpected(final int expected) { ++ return createWithExpected(expected, DEFAULT_LOAD_FACTOR); ++ } + -+ // volatile ordering ensured by addSize(), but we need release here -+ // to ensure proper ordering with reads and other writes -+ if (prev == null) { -+ setAtIndexRelease(table, index, node.getNextPlain()); -+ } else { -+ prev.setNextRelease(node.getNextPlain()); -+ } -+ } -+ break; -+ } -+ } -+ } ++ public static ConcurrentLong2ReferenceChainedHashTable createWithExpected(final int expected, final float loadFactor) { ++ final int capacity = (int)Math.ceil((double)expected / (double)loadFactor); + -+ if (removed) { -+ this.subSize(1L); -+ } ++ return createWithCapacity(capacity, loadFactor); ++ } + -+ return ret; -+ } -+ } ++ /** must be deterministic given a key */ ++ protected static int getHash(final long key) { ++ return (int)HashUtil.mix(key); + } + + /** -+ * Atomically removes the mapping for the specified key the predicate returns true for its currently mapped value. If the key -+ * is not mapped to a value, then does nothing and returns {@code null}. -+ * -+ *

    -+ * This function is a "functional methods" as defined by {@link ConcurrentLong2ReferenceChainedHashTable}. -+ *

    -+ * -+ * @param key Specified key -+ * @param predicate Specified predicate -+ * @throws NullPointerException If predicate is null -+ * @return The specified expected value if the key was mapped to {@code expect}. If -+ * the key is not mapped to any value, then returns {@code null}. If neither of those cases are true, -+ * then returns the current (non-null) mapped value for key. ++ * Returns the load factor associated with this map. + */ -+ public V removeIf(final long key, final Predicate predicate) { -+ Validate.notNull(predicate, "Predicate may not be null"); -+ -+ final int hash = getHash(key); ++ public final float getLoadFactor() { ++ return this.loadFactor; ++ } + -+ TableEntry[] table = this.table; -+ table_loop: -+ for (;;) { -+ final int index = hash & (table.length - 1); ++ protected static TableEntry getAtIndexVolatile(final TableEntry[] table, final int index) { ++ //noinspection unchecked ++ return (TableEntry)TableEntry.TABLE_ENTRY_ARRAY_HANDLE.getVolatile(table, index); ++ } + -+ TableEntry node = getAtIndexVolatile(table, index); -+ node_loop: -+ for (;;) { -+ if (node == null) { -+ return null; -+ } ++ protected static void setAtIndexRelease(final TableEntry[] table, final int index, final TableEntry value) { ++ TableEntry.TABLE_ENTRY_ARRAY_HANDLE.setRelease(table, index, value); ++ } + -+ if (node.resize) { -+ table = (TableEntry[])node.getValuePlain(); -+ continue table_loop; -+ } ++ protected static void setAtIndexVolatile(final TableEntry[] table, final int index, final TableEntry value) { ++ TableEntry.TABLE_ENTRY_ARRAY_HANDLE.setVolatile(table, index, value); ++ } + -+ boolean removed = false; -+ V ret = null; ++ protected static TableEntry compareAndExchangeAtIndexVolatile(final TableEntry[] table, final int index, ++ final TableEntry expect, final TableEntry update) { ++ //noinspection unchecked ++ return (TableEntry)TableEntry.TABLE_ENTRY_ARRAY_HANDLE.compareAndExchange(table, index, expect, update); ++ } + -+ synchronized (node) { -+ if (node != (node = getAtIndexVolatile(table, index))) { -+ continue node_loop; -+ } ++ /** ++ * Returns the possible node associated with the key, or {@code null} if there is no such node. The node ++ * returned may have a {@code null} {@link TableEntry#value}, in which case the node is a placeholder for ++ * a compute/computeIfAbsent call. The placeholder node should not be considered mapped in order to preserve ++ * happens-before relationships between writes and reads in the map. ++ */ ++ protected final TableEntry getNode(final long key) { ++ final int hash = getHash(key); + -+ TableEntry prev = null; ++ TableEntry[] table = this.table; ++ for (;;) { ++ TableEntry node = getAtIndexVolatile(table, hash & (table.length - 1)); + -+ // plain reads are fine during synchronised access, as we are the only writer -+ for (; node != null; prev = node, node = node.getNextPlain()) { -+ if (node.key == key) { -+ ret = node.getValuePlain(); -+ if (predicate.test(ret)) { -+ removed = true; ++ if (node == null) { ++ // node == null ++ return node; ++ } + -+ // volatile ordering ensured by addSize(), but we need release here -+ // to ensure proper ordering with reads and other writes -+ if (prev == null) { -+ setAtIndexRelease(table, index, node.getNextPlain()); -+ } else { -+ prev.setNextRelease(node.getNextPlain()); -+ } -+ } -+ break; -+ } -+ } -+ } ++ if (node.resize) { ++ // noinspection unchecked ++ table = (TableEntry[])node.getValuePlain(); ++ continue; ++ } + -+ if (removed) { -+ this.subSize(1L); ++ for (; node != null; node = node.getNextVolatile()) { ++ if (node.key == key) { ++ return node; + } -+ -+ return ret; + } ++ ++ // node == null ++ return node; + } + } + + /** -+ * See {@link java.util.concurrent.ConcurrentMap#compute(Object, BiFunction)} -+ *

    -+ * This function is a "functional methods" as defined by {@link ConcurrentLong2ReferenceChainedHashTable}. -+ *

    ++ * Returns the currently mapped value associated with the specified key, or {@code null} if there is none. ++ * ++ * @param key Specified key + */ -+ public V compute(final long key, final BiLong1Function function) { -+ final int hash = getHash(key); -+ -+ TableEntry[] table = this.table; -+ table_loop: -+ for (;;) { -+ final int index = hash & (table.length - 1); ++ public V get(final long key) { ++ final TableEntry node = this.getNode(key); ++ return node == null ? null : node.getValueVolatile(); ++ } + -+ TableEntry node = getAtIndexVolatile(table, index); -+ node_loop: -+ for (;;) { -+ V ret = null; -+ if (node == null) { -+ final TableEntry insert = new TableEntry<>(key, null); ++ /** ++ * Returns the currently mapped value associated with the specified key, or the specified default value if there is none. ++ * ++ * @param key Specified key ++ * @param defaultValue Specified default value ++ */ ++ public V getOrDefault(final long key, final V defaultValue) { ++ final TableEntry node = this.getNode(key); ++ if (node == null) { ++ return defaultValue; ++ } + -+ boolean added = false; -+ -+ synchronized (insert) { -+ if (null == (node = compareAndExchangeAtIndexVolatile(table, index, null, insert))) { -+ try { -+ ret = function.apply(key, null); -+ } catch (final Throwable throwable) { -+ setAtIndexVolatile(table, index, null); -+ ThrowUtil.throwUnchecked(throwable); -+ // unreachable -+ return null; -+ } -+ -+ if (ret == null) { -+ setAtIndexVolatile(table, index, null); -+ return ret; -+ } else { -+ // volatile ordering ensured by addSize(), but we need release here -+ // to ensure proper ordering with reads and other writes -+ insert.setValueRelease(ret); -+ added = true; -+ } -+ } // else: node != null, fall through -+ } -+ -+ if (added) { -+ this.addSize(1L); -+ return ret; -+ } -+ } -+ -+ if (node.resize) { -+ table = (TableEntry[])node.getValuePlain(); -+ continue table_loop; -+ } -+ -+ boolean removed = false; -+ boolean added = false; -+ -+ synchronized (node) { -+ if (node != (node = getAtIndexVolatile(table, index))) { -+ continue node_loop; -+ } -+ // plain reads are fine during synchronised access, as we are the only writer -+ TableEntry prev = null; -+ for (; node != null; prev = node, node = node.getNextPlain()) { -+ if (node.key == key) { -+ final V old = node.getValuePlain(); -+ -+ final V computed = function.apply(key, old); -+ -+ if (computed != null) { -+ node.setValueVolatile(computed); -+ return computed; -+ } -+ -+ // volatile ordering ensured by addSize(), but we need release here -+ // to ensure proper ordering with reads and other writes -+ if (prev == null) { -+ setAtIndexRelease(table, index, node.getNextPlain()); -+ } else { -+ prev.setNextRelease(node.getNextPlain()); -+ } -+ -+ removed = true; -+ break; -+ } -+ } -+ -+ if (!removed) { -+ final V computed = function.apply(key, null); -+ if (computed != null) { -+ // volatile ordering ensured by addSize(), but we need release here -+ // to ensure proper ordering with reads and other writes -+ prev.setNextRelease(new TableEntry<>(key, computed)); -+ ret = computed; -+ added = true; -+ } -+ } -+ } -+ -+ if (removed) { -+ this.subSize(1L); -+ } -+ if (added) { -+ this.addSize(1L); -+ } -+ -+ return ret; -+ } -+ } -+ } -+ -+ /** -+ * See {@link java.util.concurrent.ConcurrentMap#computeIfAbsent(Object, Function)} -+ *

    -+ * This function is a "functional methods" as defined by {@link ConcurrentLong2ReferenceChainedHashTable}. -+ *

    -+ */ -+ public V computeIfAbsent(final long key, final LongFunction function) { -+ final int hash = getHash(key); -+ -+ TableEntry[] table = this.table; -+ table_loop: -+ for (;;) { -+ final int index = hash & (table.length - 1); -+ -+ TableEntry node = getAtIndexVolatile(table, index); -+ node_loop: -+ for (;;) { -+ V ret = null; -+ if (node == null) { -+ final TableEntry insert = new TableEntry<>(key, null); -+ -+ boolean added = false; -+ -+ synchronized (insert) { -+ if (null == (node = compareAndExchangeAtIndexVolatile(table, index, null, insert))) { -+ try { -+ ret = function.apply(key); -+ } catch (final Throwable throwable) { -+ setAtIndexVolatile(table, index, null); -+ ThrowUtil.throwUnchecked(throwable); -+ // unreachable -+ return null; -+ } -+ -+ if (ret == null) { -+ setAtIndexVolatile(table, index, null); -+ return null; -+ } else { -+ // volatile ordering ensured by addSize(), but we need release here -+ // to ensure proper ordering with reads and other writes -+ insert.setValueRelease(ret); -+ added = true; -+ } -+ } // else: node != null, fall through -+ } -+ -+ if (added) { -+ this.addSize(1L); -+ return ret; -+ } -+ } -+ -+ if (node.resize) { -+ table = (TableEntry[])node.getValuePlain(); -+ continue table_loop; -+ } -+ -+ // optimise ifAbsent calls: check if first node is key before attempting lock acquire -+ if (node.key == key) { -+ ret = node.getValueVolatile(); -+ if (ret != null) { -+ return ret; -+ } // else: fall back to lock to read the node -+ } -+ -+ boolean added = false; -+ -+ synchronized (node) { -+ if (node != (node = getAtIndexVolatile(table, index))) { -+ continue node_loop; -+ } -+ // plain reads are fine during synchronised access, as we are the only writer -+ TableEntry prev = null; -+ for (; node != null; prev = node, node = node.getNextPlain()) { -+ if (node.key == key) { -+ ret = node.getValuePlain(); -+ return ret; -+ } -+ } -+ -+ final V computed = function.apply(key); -+ if (computed != null) { -+ // volatile ordering ensured by addSize(), but we need release here -+ // to ensure proper ordering with reads and other writes -+ prev.setNextRelease(new TableEntry<>(key, computed)); -+ ret = computed; -+ added = true; -+ } -+ } -+ -+ if (added) { -+ this.addSize(1L); -+ } -+ -+ return ret; -+ } -+ } -+ } -+ -+ /** -+ * See {@link java.util.concurrent.ConcurrentMap#computeIfPresent(Object, BiFunction)} -+ *

    -+ * This function is a "functional methods" as defined by {@link ConcurrentLong2ReferenceChainedHashTable}. -+ *

    -+ */ -+ public V computeIfPresent(final long key, final BiLong1Function function) { -+ final int hash = getHash(key); -+ -+ TableEntry[] table = this.table; -+ table_loop: -+ for (;;) { -+ final int index = hash & (table.length - 1); -+ -+ TableEntry node = getAtIndexVolatile(table, index); -+ node_loop: -+ for (;;) { -+ if (node == null) { -+ return null; -+ } -+ -+ if (node.resize) { -+ table = (TableEntry[])node.getValuePlain(); -+ continue table_loop; -+ } -+ -+ boolean removed = false; -+ -+ synchronized (node) { -+ if (node != (node = getAtIndexVolatile(table, index))) { -+ continue node_loop; -+ } -+ // plain reads are fine during synchronised access, as we are the only writer -+ TableEntry prev = null; -+ for (; node != null; prev = node, node = node.getNextPlain()) { -+ if (node.key == key) { -+ final V old = node.getValuePlain(); -+ -+ final V computed = function.apply(key, old); -+ -+ if (computed != null) { -+ node.setValueVolatile(computed); -+ return computed; -+ } -+ -+ // volatile ordering ensured by addSize(), but we need release here -+ // to ensure proper ordering with reads and other writes -+ if (prev == null) { -+ setAtIndexRelease(table, index, node.getNextPlain()); -+ } else { -+ prev.setNextRelease(node.getNextPlain()); -+ } -+ -+ removed = true; -+ break; -+ } -+ } -+ } -+ -+ if (removed) { -+ this.subSize(1L); -+ } -+ -+ return null; -+ } -+ } -+ } -+ -+ /** -+ * See {@link java.util.concurrent.ConcurrentMap#merge(Object, Object, BiFunction)} -+ *

    -+ * This function is a "functional methods" as defined by {@link ConcurrentLong2ReferenceChainedHashTable}. -+ *

    -+ */ -+ public V merge(final long key, final V def, final BiFunction function) { -+ Validate.notNull(def, "Default value may not be null"); -+ -+ final int hash = getHash(key); -+ -+ TableEntry[] table = this.table; -+ table_loop: -+ for (;;) { -+ final int index = hash & (table.length - 1); -+ -+ TableEntry node = getAtIndexVolatile(table, index); -+ node_loop: -+ for (;;) { -+ if (node == null) { -+ if (null == (node = compareAndExchangeAtIndexVolatile(table, index, null, new TableEntry<>(key, def)))) { -+ // successfully inserted -+ this.addSize(1L); -+ return def; -+ } // else: node != null, fall through -+ } -+ -+ if (node.resize) { -+ table = (TableEntry[])node.getValuePlain(); -+ continue table_loop; -+ } -+ -+ boolean removed = false; -+ boolean added = false; -+ V ret = null; -+ -+ synchronized (node) { -+ if (node != (node = getAtIndexVolatile(table, index))) { -+ continue node_loop; -+ } -+ // plain reads are fine during synchronised access, as we are the only writer -+ TableEntry prev = null; -+ for (; node != null; prev = node, node = node.getNextPlain()) { -+ if (node.key == key) { -+ final V old = node.getValuePlain(); -+ -+ final V computed = function.apply(old, def); -+ -+ if (computed != null) { -+ node.setValueVolatile(computed); -+ return computed; -+ } -+ -+ // volatile ordering ensured by addSize(), but we need release here -+ // to ensure proper ordering with reads and other writes -+ if (prev == null) { -+ setAtIndexRelease(table, index, node.getNextPlain()); -+ } else { -+ prev.setNextRelease(node.getNextPlain()); -+ } -+ -+ removed = true; -+ break; -+ } -+ } -+ -+ if (!removed) { -+ // volatile ordering ensured by addSize(), but we need release here -+ // to ensure proper ordering with reads and other writes -+ prev.setNextRelease(new TableEntry<>(key, def)); -+ ret = def; -+ added = true; -+ } -+ } -+ -+ if (removed) { -+ this.subSize(1L); -+ } -+ if (added) { -+ this.addSize(1L); -+ } -+ -+ return ret; -+ } -+ } -+ } -+ -+ /** -+ * Removes at least all entries currently mapped at the beginning of this call. May not remove entries added during -+ * this call. As a result, only if this map is not modified during the call, that all entries will be removed by -+ * the end of the call. -+ * -+ *

    -+ * This function is not atomic. -+ *

    -+ */ -+ public void clear() { -+ // it is possible to optimise this to directly interact with the table, -+ // but we do need to be careful when interacting with resized tables, -+ // and the NodeIterator already does this logic -+ final NodeIterator nodeIterator = new NodeIterator<>(this.table); -+ -+ TableEntry node; -+ while ((node = nodeIterator.findNext()) != null) { -+ this.remove(node.key); -+ } -+ } -+ -+ /** -+ * Returns an iterator over the entries in this map. The iterator is only guaranteed to see entries that were -+ * added before the beginning of this call, but it may see entries added during. -+ */ -+ public Iterator> entryIterator() { -+ return new EntryIterator<>(this); -+ } -+ -+ /** -+ * Returns an iterator over the keys in this map. The iterator is only guaranteed to see keys that were -+ * added before the beginning of this call, but it may see keys added during. -+ */ -+ public PrimitiveIterator.OfLong keyIterator() { -+ return new KeyIterator<>(this); -+ } -+ -+ /** -+ * Returns an iterator over the values in this map. The iterator is only guaranteed to see values that were -+ * added before the beginning of this call, but it may see values added during. -+ */ -+ public Iterator valueIterator() { -+ return new ValueIterator<>(this); -+ } -+ -+ protected static final class EntryIterator extends BaseIteratorImpl> { -+ -+ protected EntryIterator(final ConcurrentLong2ReferenceChainedHashTable map) { -+ super(map); -+ } -+ -+ @Override -+ public TableEntry next() throws NoSuchElementException { -+ return this.nextNode(); -+ } -+ -+ @Override -+ public void forEachRemaining(final Consumer> action) { -+ Validate.notNull(action, "Action may not be null"); -+ while (this.hasNext()) { -+ action.accept(this.next()); -+ } -+ } -+ } -+ -+ protected static final class KeyIterator extends BaseIteratorImpl implements PrimitiveIterator.OfLong { -+ -+ protected KeyIterator(final ConcurrentLong2ReferenceChainedHashTable map) { -+ super(map); -+ } -+ -+ @Override -+ public Long next() throws NoSuchElementException { -+ return Long.valueOf(this.nextNode().key); -+ } -+ -+ @Override -+ public long nextLong() { -+ return this.nextNode().key; ++ final V ret = node.getValueVolatile(); ++ if (ret == null) { ++ // ret == null for nodes pre-allocated to compute() and friends ++ return defaultValue; + } + -+ @Override -+ public void forEachRemaining(final Consumer action) { -+ Validate.notNull(action, "Action may not be null"); ++ return ret; ++ } + -+ if (action instanceof LongConsumer longConsumer) { -+ this.forEachRemaining(longConsumer); -+ return; -+ } ++ /** ++ * Returns whether the specified key is mapped to some value. ++ * @param key Specified key ++ */ ++ public boolean containsKey(final long key) { ++ // cannot use getNode, as the node may be a placeholder for compute() ++ return this.get(key) != null; ++ } + -+ while (this.hasNext()) { -+ action.accept(this.next()); -+ } -+ } ++ /** ++ * Returns whether the specified value has a key mapped to it. ++ * @param value Specified value ++ * @throws NullPointerException If value is null ++ */ ++ public boolean containsValue(final V value) { ++ Validate.notNull(value, "Value cannot be null"); + -+ @Override -+ public void forEachRemaining(final LongConsumer action) { -+ Validate.notNull(action, "Action may not be null"); -+ while (this.hasNext()) { -+ action.accept(this.nextLong()); ++ final NodeIterator iterator = new NodeIterator<>(this.table); ++ ++ TableEntry node; ++ while ((node = iterator.findNext()) != null) { ++ // need to use acquire here to ensure the happens-before relationship ++ if (node.getValueAcquire() == value) { ++ return true; + } + } ++ ++ return false; + } + -+ protected static final class ValueIterator extends BaseIteratorImpl { ++ /** ++ * Returns the number of mappings in this map. ++ */ ++ public int size() { ++ final long ret = this.size.sum(); + -+ protected ValueIterator(final ConcurrentLong2ReferenceChainedHashTable map) { -+ super(map); ++ if (ret < 0L) { ++ return 0; + } -+ -+ @Override -+ public V next() throws NoSuchElementException { -+ return this.nextNode().getValueVolatile(); ++ if (ret > (long)Integer.MAX_VALUE) { ++ return Integer.MAX_VALUE; + } + -+ @Override -+ public void forEachRemaining(final Consumer action) { -+ Validate.notNull(action, "Action may not be null"); -+ while (this.hasNext()) { -+ action.accept(this.next()); -+ } -+ } ++ return (int)ret; + } + -+ protected static abstract class BaseIteratorImpl extends NodeIterator implements Iterator { -+ -+ protected final ConcurrentLong2ReferenceChainedHashTable map; -+ protected TableEntry lastReturned; -+ protected TableEntry nextToReturn; ++ /** ++ * Returns whether this map has no mappings. ++ */ ++ public boolean isEmpty() { ++ return this.size.sum() <= 0L; ++ } + -+ protected BaseIteratorImpl(final ConcurrentLong2ReferenceChainedHashTable map) { -+ super(map.table); -+ this.map = map; -+ } ++ /** ++ * Adds count to size and checks threshold for resizing ++ */ ++ protected final void addSize(final long count) { ++ this.size.add(count); + -+ @Override -+ public final boolean hasNext() { -+ if (this.nextToReturn != null) { -+ return true; -+ } ++ final int threshold = this.getThresholdAcquire(); + -+ return (this.nextToReturn = this.findNext()) != null; ++ if (threshold < 0L) { ++ // resizing or no resizing allowed, in either cases we do not need to do anything ++ return; + } + -+ protected final TableEntry nextNode() throws NoSuchElementException { -+ TableEntry ret = this.nextToReturn; -+ if (ret != null) { -+ this.lastReturned = ret; -+ this.nextToReturn = null; -+ return ret; -+ } -+ ret = this.findNext(); -+ if (ret != null) { -+ this.lastReturned = ret; -+ return ret; -+ } -+ throw new NoSuchElementException(); -+ } ++ final long sum = this.size.sum(); + -+ @Override -+ public final void remove() { -+ final TableEntry lastReturned = this.nextToReturn; -+ if (lastReturned == null) { -+ throw new NoSuchElementException(); -+ } -+ this.lastReturned = null; -+ this.map.remove(lastReturned.key); ++ if (sum < (long)threshold) { ++ return; + } + -+ @Override -+ public abstract T next() throws NoSuchElementException; -+ -+ // overwritten by subclasses to avoid indirection on hasNext() and next() -+ @Override -+ public abstract void forEachRemaining(final Consumer action); -+ } -+ -+ protected static class NodeIterator { -+ -+ protected TableEntry[] currentTable; -+ protected ResizeChain resizeChain; -+ protected TableEntry last; -+ protected int nextBin; -+ protected int increment; -+ -+ protected NodeIterator(final TableEntry[] baseTable) { -+ this.currentTable = baseTable; -+ this.increment = 1; ++ if (threshold != this.compareExchangeThresholdVolatile(threshold, THRESHOLD_RESIZING)) { ++ // some other thread resized ++ return; + } + -+ private TableEntry[] pullResizeChain(final int index) { -+ final ResizeChain resizeChain = this.resizeChain; -+ if (resizeChain == null) { -+ this.currentTable = null; -+ return null; -+ } -+ -+ final ResizeChain prevChain = resizeChain.prev; -+ this.resizeChain = prevChain; -+ if (prevChain == null) { -+ this.currentTable = null; -+ return null; -+ } -+ -+ final TableEntry[] newTable = prevChain.table; ++ // create new table ++ this.resize(sum); ++ } + -+ // we recover the original index by modding by the new table length, as the increments applied to the index -+ // are a multiple of the new table's length -+ int newIdx = index & (newTable.length - 1); ++ /** ++ * Resizes table, only invoke for the thread which has successfully updated threshold to {@link #THRESHOLD_RESIZING} ++ * @param sum Estimate of current mapping count, must be >= old threshold ++ */ ++ private void resize(final long sum) { ++ int capacity; + -+ // the increment is always the previous table's length -+ final ResizeChain nextPrevChain = prevChain.prev; -+ final int increment; -+ if (nextPrevChain == null) { -+ increment = 1; -+ } else { -+ increment = nextPrevChain.table.length; ++ // add 1.0, as sum may equal threshold (in which case, sum / loadFactor = current capacity) ++ // adding 1.0 should at least raise the size by a factor of two due to usage of roundCeilLog2 ++ final double targetD = ((double)sum / (double)this.loadFactor) + 1.0; ++ if (targetD >= (double)MAXIMUM_CAPACITY) { ++ capacity = MAXIMUM_CAPACITY; ++ } else { ++ capacity = (int)Math.ceil(targetD); ++ capacity = IntegerUtil.roundCeilLog2(capacity); ++ if (capacity > MAXIMUM_CAPACITY) { ++ capacity = MAXIMUM_CAPACITY; + } -+ -+ // done with the upper table, so we can skip the resize node -+ newIdx += increment; -+ -+ this.increment = increment; -+ this.nextBin = newIdx; -+ this.currentTable = newTable; -+ -+ return newTable; + } + -+ private TableEntry[] pushResizeChain(final TableEntry[] table, final TableEntry entry) { -+ final ResizeChain chain = this.resizeChain; ++ // create new table data + -+ if (chain == null) { -+ final TableEntry[] nextTable = (TableEntry[])entry.getValuePlain(); ++ // noinspection unchecked ++ final TableEntry[] newTable = new TableEntry[capacity]; ++ // noinspection unchecked ++ final TableEntry resizeNode = new TableEntry<>(0L, (V)newTable, true); + -+ final ResizeChain oldChain = new ResizeChain<>(table, null, null); -+ final ResizeChain currChain = new ResizeChain<>(nextTable, oldChain, null); -+ oldChain.next = currChain; ++ // transfer nodes from old table + -+ this.increment = table.length; -+ this.resizeChain = currChain; -+ this.currentTable = nextTable; ++ // does not need to be volatile read, just plain ++ final TableEntry[] oldTable = this.table; + -+ return nextTable; -+ } else { -+ ResizeChain currChain = chain.next; -+ if (currChain == null) { -+ final TableEntry[] ret = (TableEntry[])entry.getValuePlain(); -+ currChain = new ResizeChain<>(ret, chain, null); -+ chain.next = currChain; ++ // when resizing, the old entries at bin i (where i = hash % oldTable.length) are assigned to ++ // bin k in the new table (where k = hash % newTable.length) ++ // since both table lengths are powers of two (specifically, newTable is a multiple of oldTable), ++ // the possible number of locations in the new table to assign any given i is newTable.length/oldTable.length + -+ this.increment = table.length; -+ this.resizeChain = currChain; -+ this.currentTable = ret; ++ // we can build the new linked nodes for the new table by using a work array sized to newTable.length/oldTable.length ++ // which holds the _last_ entry in the chain per bin + -+ return ret; -+ } else { -+ this.increment = table.length; -+ this.resizeChain = currChain; -+ return this.currentTable = currChain.table; -+ } -+ } ++ final int capOldShift = IntegerUtil.floorLog2(oldTable.length); ++ final int capDiffShift = IntegerUtil.floorLog2(capacity) - capOldShift; ++ ++ if (capDiffShift == 0) { ++ throw new IllegalStateException("Resizing to same size"); + } + -+ protected final TableEntry findNext() { -+ for (;;) { -+ final TableEntry last = this.last; -+ if (last != null) { -+ final TableEntry next = last.getNextVolatile(); -+ if (next != null) { -+ this.last = next; -+ if (next.getValuePlain() == null) { -+ // compute() node not yet available -+ continue; -+ } -+ return next; -+ } -+ } ++ // noinspection unchecked ++ final TableEntry[] work = new TableEntry[1 << capDiffShift]; // typically, capDiffShift = 1 + -+ TableEntry[] table = this.currentTable; ++ for (int i = 0, len = oldTable.length; i < len; ++i) { ++ TableEntry binNode = getAtIndexVolatile(oldTable, i); + -+ if (table == null) { -+ return null; ++ for (;;) { ++ if (binNode == null) { ++ // just need to replace the bin node, do not need to move anything ++ if (null == (binNode = compareAndExchangeAtIndexVolatile(oldTable, i, null, resizeNode))) { ++ break; ++ } // else: binNode != null, fall through + } + -+ int idx = this.nextBin; -+ int increment = this.increment; -+ for (;;) { -+ if (idx >= table.length) { -+ table = this.pullResizeChain(idx); -+ idx = this.nextBin; -+ increment = this.increment; -+ if (table != null) { -+ continue; -+ } else { -+ this.last = null; -+ return null; -+ } -+ } -+ -+ final TableEntry entry = getAtIndexVolatile(table, idx); -+ if (entry == null) { -+ idx += increment; ++ // need write lock to block other writers ++ synchronized (binNode) { ++ if (binNode != (binNode = getAtIndexVolatile(oldTable, i))) { + continue; + } + -+ if (entry.resize) { -+ // push onto resize chain -+ table = this.pushResizeChain(table, entry); -+ increment = this.increment; -+ continue; -+ } ++ // an important detail of resizing is that we do not need to be concerned with synchronisation on ++ // writes to the new table, as no access to any nodes on bin i on oldTable will occur until a thread ++ // sees the resizeNode ++ // specifically, as long as the resizeNode is release written there are no cases where another thread ++ // will see our writes to the new table + -+ this.last = entry; -+ this.nextBin = idx + increment; -+ if (entry.getValuePlain() != null) { -+ return entry; ++ TableEntry next = binNode.getNextPlain(); ++ ++ if (next == null) { ++ // simple case: do not use work array ++ ++ // do not need to create new node, readers only need to see the state of the map at the ++ // beginning of a call, so any additions onto _next_ don't really matter ++ // additionally, the old node is replaced so that writers automatically forward to the new table, ++ // which resolves any issues ++ newTable[getHash(binNode.key) & (capacity - 1)] = binNode; + } else { -+ // compute() node not yet available -+ break; -+ } -+ } -+ } -+ } ++ // reset for next usage ++ Arrays.fill(work, null); + -+ protected static final class ResizeChain { ++ for (TableEntry curr = binNode; curr != null; curr = curr.getNextPlain()) { ++ final int newTableIdx = getHash(curr.key) & (capacity - 1); ++ final int workIdx = newTableIdx >>> capOldShift; + -+ protected final TableEntry[] table; -+ protected final ResizeChain prev; -+ protected ResizeChain next; ++ final TableEntry replace = new TableEntry<>(curr.key, curr.getValuePlain()); + -+ protected ResizeChain(final TableEntry[] table, final ResizeChain prev, final ResizeChain next) { -+ this.table = table; -+ this.prev = prev; -+ this.next = next; ++ final TableEntry workNode = work[workIdx]; ++ work[workIdx] = replace; ++ ++ if (workNode == null) { ++ newTable[newTableIdx] = replace; ++ continue; ++ } else { ++ workNode.setNextPlain(replace); ++ continue; ++ } ++ } ++ } ++ ++ setAtIndexRelease(oldTable, i, resizeNode); ++ break; ++ } + } + } -+ } + -+ public static final class TableEntry { ++ // calculate new threshold ++ final int newThreshold; ++ if (capacity == MAXIMUM_CAPACITY) { ++ newThreshold = THRESHOLD_NO_RESIZE; ++ } else { ++ newThreshold = getTargetThreshold(capacity, loadFactor); ++ } + -+ protected static final VarHandle TABLE_ENTRY_ARRAY_HANDLE = ConcurrentUtil.getArrayHandle(TableEntry[].class); ++ this.table = newTable; ++ // finish resize operation by releasing hold on threshold ++ this.setThresholdVolatile(newThreshold); ++ } + -+ protected final boolean resize; ++ /** ++ * Subtracts count from size ++ */ ++ protected final void subSize(final long count) { ++ this.size.add(-count); ++ } + -+ protected final long key; ++ /** ++ * Atomically updates the value associated with {@code key} to {@code value}, or inserts a new mapping with {@code key} ++ * mapped to {@code value}. ++ * @param key Specified key ++ * @param value Specified value ++ * @throws NullPointerException If value is null ++ * @return Old value previously associated with key, or {@code null} if none. ++ */ ++ public V put(final long key, final V value) { ++ Validate.notNull(value, "Value may not be null"); + -+ protected volatile V value; -+ protected static final VarHandle VALUE_HANDLE = ConcurrentUtil.getVarHandle(TableEntry.class, "value", Object.class); ++ final int hash = getHash(key); + -+ protected final V getValuePlain() { -+ //noinspection unchecked -+ return (V)VALUE_HANDLE.get(this); -+ } ++ TableEntry[] table = this.table; ++ table_loop: ++ for (;;) { ++ final int index = hash & (table.length - 1); + -+ protected final V getValueAcquire() { -+ //noinspection unchecked -+ return (V)VALUE_HANDLE.getAcquire(this); -+ } ++ TableEntry node = getAtIndexVolatile(table, index); ++ node_loop: ++ for (;;) { ++ if (node == null) { ++ if (null == (node = compareAndExchangeAtIndexVolatile(table, index, null, new TableEntry<>(key, value)))) { ++ // successfully inserted ++ this.addSize(1L); ++ return null; ++ } // else: node != null, fall through ++ } + -+ protected final V getValueVolatile() { -+ //noinspection unchecked -+ return (V)VALUE_HANDLE.getVolatile(this); -+ } ++ if (node.resize) { ++ table = (TableEntry[])node.getValuePlain(); ++ continue table_loop; ++ } + -+ protected final void setValuePlain(final V value) { -+ VALUE_HANDLE.set(this, (Object)value); -+ } ++ synchronized (node) { ++ if (node != (node = getAtIndexVolatile(table, index))) { ++ continue node_loop; ++ } ++ // plain reads are fine during synchronised access, as we are the only writer ++ TableEntry prev = null; ++ for (; node != null; prev = node, node = node.getNextPlain()) { ++ if (node.key == key) { ++ final V ret = node.getValuePlain(); ++ node.setValueVolatile(value); ++ return ret; ++ } ++ } + -+ protected final void setValueRelease(final V value) { -+ VALUE_HANDLE.setRelease(this, (Object)value); -+ } ++ // volatile ordering ensured by addSize(), but we need release here ++ // to ensure proper ordering with reads and other writes ++ prev.setNextRelease(new TableEntry<>(key, value)); ++ } + -+ protected final void setValueVolatile(final V value) { -+ VALUE_HANDLE.setVolatile(this, (Object)value); ++ this.addSize(1L); ++ return null; ++ } + } ++ } + -+ protected volatile TableEntry next; -+ protected static final VarHandle NEXT_HANDLE = ConcurrentUtil.getVarHandle(TableEntry.class, "next", TableEntry.class); -+ -+ protected final TableEntry getNextPlain() { -+ //noinspection unchecked -+ return (TableEntry)NEXT_HANDLE.get(this); -+ } ++ /** ++ * Atomically inserts a new mapping with {@code key} mapped to {@code value} if and only if {@code key} is not ++ * currently mapped to some value. ++ * @param key Specified key ++ * @param value Specified value ++ * @throws NullPointerException If value is null ++ * @return Value currently associated with key, or {@code null} if none and {@code value} was associated. ++ */ ++ public V putIfAbsent(final long key, final V value) { ++ Validate.notNull(value, "Value may not be null"); + -+ protected final TableEntry getNextVolatile() { -+ //noinspection unchecked -+ return (TableEntry)NEXT_HANDLE.getVolatile(this); -+ } ++ final int hash = getHash(key); + -+ protected final void setNextPlain(final TableEntry next) { -+ NEXT_HANDLE.set(this, next); -+ } ++ TableEntry[] table = this.table; ++ table_loop: ++ for (;;) { ++ final int index = hash & (table.length - 1); + -+ protected final void setNextRelease(final TableEntry next) { -+ NEXT_HANDLE.setRelease(this, next); -+ } ++ TableEntry node = getAtIndexVolatile(table, index); ++ node_loop: ++ for (;;) { ++ if (node == null) { ++ if (null == (node = compareAndExchangeAtIndexVolatile(table, index, null, new TableEntry<>(key, value)))) { ++ // successfully inserted ++ this.addSize(1L); ++ return null; ++ } // else: node != null, fall through ++ } + -+ protected final void setNextVolatile(final TableEntry next) { -+ NEXT_HANDLE.setVolatile(this, next); -+ } ++ if (node.resize) { ++ // noinspection unchecked ++ table = (TableEntry[])node.getValuePlain(); ++ continue table_loop; ++ } + -+ public TableEntry(final long key, final V value) { -+ this.resize = false; -+ this.key = key; -+ this.setValuePlain(value); -+ } ++ // optimise ifAbsent calls: check if first node is key before attempting lock acquire ++ if (node.key == key) { ++ final V ret = node.getValueVolatile(); ++ if (ret != null) { ++ return ret; ++ } // else: fall back to lock to read the node ++ } + -+ public TableEntry(final long key, final V value, final boolean resize) { -+ this.resize = resize; -+ this.key = key; -+ this.setValuePlain(value); -+ } ++ synchronized (node) { ++ if (node != (node = getAtIndexVolatile(table, index))) { ++ continue node_loop; ++ } ++ // plain reads are fine during synchronised access, as we are the only writer ++ TableEntry prev = null; ++ for (; node != null; prev = node, node = node.getNextPlain()) { ++ if (node.key == key) { ++ return node.getValuePlain(); ++ } ++ } + -+ public long getKey() { -+ return this.key; -+ } ++ // volatile ordering ensured by addSize(), but we need release here ++ // to ensure proper ordering with reads and other writes ++ prev.setNextRelease(new TableEntry<>(key, value)); ++ } + -+ public V getValue() { -+ return this.getValueVolatile(); ++ this.addSize(1L); ++ return null; ++ } + } + } -+} -diff --git a/src/main/java/ca/spottedleaf/concurrentutil/map/SWMRHashTable.java b/src/main/java/ca/spottedleaf/concurrentutil/map/SWMRHashTable.java -new file mode 100644 -index 0000000000000000000000000000000000000000..83965350d292ccf42a34520d84dcda3f88146cff ---- /dev/null -+++ b/src/main/java/ca/spottedleaf/concurrentutil/map/SWMRHashTable.java -@@ -0,0 +1,1656 @@ -+package ca.spottedleaf.concurrentutil.map; + -+import ca.spottedleaf.concurrentutil.util.CollectionUtil; -+import ca.spottedleaf.concurrentutil.util.ConcurrentUtil; -+import ca.spottedleaf.concurrentutil.util.HashUtil; -+import ca.spottedleaf.concurrentutil.util.IntegerUtil; -+import ca.spottedleaf.concurrentutil.util.Validate; -+import java.lang.invoke.VarHandle; -+import java.util.ArrayList; -+import java.util.Arrays; -+import java.util.Collection; -+import java.util.Iterator; -+import java.util.List; -+import java.util.Map; -+import java.util.NoSuchElementException; -+import java.util.Set; -+import java.util.Spliterator; -+import java.util.Spliterators; -+import java.util.function.BiConsumer; -+import java.util.function.BiFunction; -+import java.util.function.BiPredicate; -+import java.util.function.Consumer; -+import java.util.function.Function; -+import java.util.function.IntFunction; -+import java.util.function.Predicate; ++ /** ++ * Atomically updates the value associated with {@code key} to {@code value}, or does nothing if {@code key} is not ++ * associated with a value. ++ * @param key Specified key ++ * @param value Specified value ++ * @throws NullPointerException If value is null ++ * @return Old value previously associated with key, or {@code null} if none. ++ */ ++ public V replace(final long key, final V value) { ++ Validate.notNull(value, "Value may not be null"); + -+/** -+ *

    -+ * Note: Not really tested, use at your own risk. -+ *

    -+ * This map is safe for reading from multiple threads, however it is only safe to write from a single thread. -+ * {@code null} keys or values are not permitted. Writes to values in this map are guaranteed to be ordered by release semantics, -+ * however immediate visibility to other threads is not guaranteed. However, writes are guaranteed to be made visible eventually. -+ * Reads are ordered by acquire semantics. -+ *

    -+ * Iterators cannot be modified concurrently, and its backing map cannot be modified concurrently. There is no -+ * fast-fail attempt made by iterators, thus modifying the iterator's backing map while iterating will have undefined -+ * behaviour. -+ *

    -+ *

    -+ * Subclasses should override {@link #clone()} to return correct instances of this class. -+ *

    -+ * @param {@inheritDoc} -+ * @param {@inheritDoc} -+ */ -+public class SWMRHashTable implements Map, Iterable> { ++ final int hash = getHash(key); + -+ protected int size; ++ TableEntry[] table = this.table; ++ table_loop: ++ for (;;) { ++ final int index = hash & (table.length - 1); + -+ protected TableEntry[] table; ++ TableEntry node = getAtIndexVolatile(table, index); ++ node_loop: ++ for (;;) { ++ if (node == null) { ++ return null; ++ } + -+ protected final float loadFactor; ++ if (node.resize) { ++ // noinspection unchecked ++ table = (TableEntry[])node.getValuePlain(); ++ continue table_loop; ++ } + -+ protected static final VarHandle SIZE_HANDLE = ConcurrentUtil.getVarHandle(SWMRHashTable.class, "size", int.class); -+ protected static final VarHandle TABLE_HANDLE = ConcurrentUtil.getVarHandle(SWMRHashTable.class, "table", TableEntry[].class); ++ synchronized (node) { ++ if (node != (node = getAtIndexVolatile(table, index))) { ++ continue node_loop; ++ } + -+ /* size */ ++ // plain reads are fine during synchronised access, as we are the only writer ++ for (; node != null; node = node.getNextPlain()) { ++ if (node.key == key) { ++ final V ret = node.getValuePlain(); ++ node.setValueVolatile(value); ++ return ret; ++ } ++ } ++ } + -+ protected final int getSizePlain() { -+ return (int)SIZE_HANDLE.get(this); ++ return null; ++ } ++ } + } + -+ protected final int getSizeOpaque() { -+ return (int)SIZE_HANDLE.getOpaque(this); -+ } ++ /** ++ * Atomically updates the value associated with {@code key} to {@code update} if the currently associated ++ * value is reference equal to {@code expect}, otherwise does nothing. ++ * @param key Specified key ++ * @param expect Expected value to check current mapped value with ++ * @param update Update value to replace mapped value with ++ * @throws NullPointerException If value is null ++ * @return If the currently mapped value is not reference equal to {@code expect}, then returns the currently mapped ++ * value. If the key is not mapped to any value, then returns {@code null}. If neither of the two cases are ++ * true, then returns {@code expect}. ++ */ ++ public V replace(final long key, final V expect, final V update) { ++ Validate.notNull(expect, "Expect may not be null"); ++ Validate.notNull(update, "Update may not be null"); + -+ protected final int getSizeAcquire() { -+ return (int)SIZE_HANDLE.getAcquire(this); -+ } ++ final int hash = getHash(key); + -+ protected final void setSizePlain(final int value) { -+ SIZE_HANDLE.set(this, value); -+ } ++ TableEntry[] table = this.table; ++ table_loop: ++ for (;;) { ++ final int index = hash & (table.length - 1); + -+ protected final void setSizeOpaque(final int value) { -+ SIZE_HANDLE.setOpaque(this, value); -+ } ++ TableEntry node = getAtIndexVolatile(table, index); ++ node_loop: ++ for (;;) { ++ if (node == null) { ++ return null; ++ } + -+ protected final void setSizeRelease(final int value) { -+ SIZE_HANDLE.setRelease(this, value); -+ } ++ if (node.resize) { ++ // noinspection unchecked ++ table = (TableEntry[])node.getValuePlain(); ++ continue table_loop; ++ } + -+ /* table */ ++ synchronized (node) { ++ if (node != (node = getAtIndexVolatile(table, index))) { ++ continue node_loop; ++ } + -+ protected final TableEntry[] getTablePlain() { -+ //noinspection unchecked -+ return (TableEntry[])TABLE_HANDLE.get(this); -+ } ++ // plain reads are fine during synchronised access, as we are the only writer ++ for (; node != null; node = node.getNextPlain()) { ++ if (node.key == key) { ++ final V ret = node.getValuePlain(); + -+ protected final TableEntry[] getTableAcquire() { -+ //noinspection unchecked -+ return (TableEntry[])TABLE_HANDLE.getAcquire(this); -+ } ++ if (ret != expect) { ++ return ret; ++ } + -+ protected final void setTablePlain(final TableEntry[] table) { -+ TABLE_HANDLE.set(this, table); -+ } ++ node.setValueVolatile(update); ++ return ret; ++ } ++ } ++ } + -+ protected final void setTableRelease(final TableEntry[] table) { -+ TABLE_HANDLE.setRelease(this, table); ++ return null; ++ } ++ } + } + -+ protected static final int DEFAULT_CAPACITY = 16; -+ protected static final float DEFAULT_LOAD_FACTOR = 0.75f; -+ protected static final int MAXIMUM_CAPACITY = Integer.MIN_VALUE >>> 1; -+ + /** -+ * Constructs this map with a capacity of {@code 16} and load factor of {@code 0.75f}. ++ * Atomically removes the mapping for the specified key and returns the value it was associated with. If the key ++ * is not mapped to a value, then does nothing and returns {@code null}. ++ * @param key Specified key ++ * @return Old value previously associated with key, or {@code null} if none. + */ -+ public SWMRHashTable() { -+ this(DEFAULT_CAPACITY, DEFAULT_LOAD_FACTOR); -+ } ++ public V remove(final long key) { ++ final int hash = getHash(key); + -+ /** -+ * Constructs this map with the specified capacity and load factor of {@code 0.75f}. -+ * @param capacity specified initial capacity, > 0 -+ */ -+ public SWMRHashTable(final int capacity) { -+ this(capacity, DEFAULT_LOAD_FACTOR); -+ } ++ TableEntry[] table = this.table; ++ table_loop: ++ for (;;) { ++ final int index = hash & (table.length - 1); + -+ /** -+ * Constructs this map with the specified capacity and load factor. -+ * @param capacity specified capacity, > 0 -+ * @param loadFactor specified load factor, > 0 && finite -+ */ -+ public SWMRHashTable(final int capacity, final float loadFactor) { -+ final int tableSize = getCapacityFor(capacity); ++ TableEntry node = getAtIndexVolatile(table, index); ++ node_loop: ++ for (;;) { ++ if (node == null) { ++ return null; ++ } + -+ if (loadFactor <= 0.0 || !Float.isFinite(loadFactor)) { -+ throw new IllegalArgumentException("Invalid load factor: " + loadFactor); -+ } ++ if (node.resize) { ++ // noinspection unchecked ++ table = (TableEntry[])node.getValuePlain(); ++ continue table_loop; ++ } + -+ //noinspection unchecked -+ final TableEntry[] table = new TableEntry[tableSize]; -+ this.setTablePlain(table); ++ boolean removed = false; ++ V ret = null; + -+ if (tableSize == MAXIMUM_CAPACITY) { -+ this.threshold = -1; -+ } else { -+ this.threshold = getTargetCapacity(tableSize, loadFactor); -+ } ++ synchronized (node) { ++ if (node != (node = getAtIndexVolatile(table, index))) { ++ continue node_loop; ++ } ++ ++ TableEntry prev = null; ++ ++ // plain reads are fine during synchronised access, as we are the only writer ++ for (; node != null; prev = node, node = node.getNextPlain()) { ++ if (node.key == key) { ++ ret = node.getValuePlain(); ++ removed = true; ++ ++ // volatile ordering ensured by addSize(), but we need release here ++ // to ensure proper ordering with reads and other writes ++ if (prev == null) { ++ setAtIndexRelease(table, index, node.getNextPlain()); ++ } else { ++ prev.setNextRelease(node.getNextPlain()); ++ } + -+ this.loadFactor = loadFactor; -+ } ++ break; ++ } ++ } ++ } + -+ /** -+ * Constructs this map with a capacity of {@code 16} or the specified map's size, whichever is larger, and -+ * with a load factor of {@code 0.75f}. -+ * All of the specified map's entries are copied into this map. -+ * @param other The specified map. -+ */ -+ public SWMRHashTable(final Map other) { -+ this(DEFAULT_CAPACITY, DEFAULT_LOAD_FACTOR, other); -+ } ++ if (removed) { ++ this.subSize(1L); ++ } + -+ /** -+ * Constructs this map with a minimum capacity of the specified capacity or the specified map's size, whichever is larger, and -+ * with a load factor of {@code 0.75f}. -+ * All of the specified map's entries are copied into this map. -+ * @param capacity specified capacity, > 0 -+ * @param other The specified map. -+ */ -+ public SWMRHashTable(final int capacity, final Map other) { -+ this(capacity, DEFAULT_LOAD_FACTOR, other); ++ return ret; ++ } ++ } + } + + /** -+ * Constructs this map with a min capacity of the specified capacity or the specified map's size, whichever is larger, and -+ * with the specified load factor. -+ * All of the specified map's entries are copied into this map. -+ * @param capacity specified capacity, > 0 -+ * @param loadFactor specified load factor, > 0 && finite -+ * @param other The specified map. ++ * Atomically removes the mapping for the specified key if it is mapped to {@code expect} and returns {@code expect}. If the key ++ * is not mapped to a value, then does nothing and returns {@code null}. If the key is mapped to a value that is not reference ++ * equal to {@code expect}, then returns that value. ++ * @param key Specified key ++ * @param expect Specified expected value ++ * @return The specified expected value if the key was mapped to {@code expect}. If ++ * the key is not mapped to any value, then returns {@code null}. If neither of those cases are true, ++ * then returns the current (non-null) mapped value for key. + */ -+ public SWMRHashTable(final int capacity, final float loadFactor, final Map other) { -+ this(Math.max(Validate.notNull(other, "Null map").size(), capacity), loadFactor); -+ this.putAll(other); -+ } -+ -+ protected static TableEntry getAtIndexOpaque(final TableEntry[] table, final int index) { -+ // noinspection unchecked -+ return (TableEntry)TableEntry.TABLE_ENTRY_ARRAY_HANDLE.getOpaque(table, index); -+ } -+ -+ protected static void setAtIndexRelease(final TableEntry[] table, final int index, final TableEntry value) { -+ TableEntry.TABLE_ENTRY_ARRAY_HANDLE.setRelease(table, index, value); -+ } -+ -+ public final float getLoadFactor() { -+ return this.loadFactor; -+ } ++ public V remove(final long key, final V expect) { ++ final int hash = getHash(key); + -+ protected static int getCapacityFor(final int capacity) { -+ if (capacity <= 0) { -+ throw new IllegalArgumentException("Invalid capacity: " + capacity); -+ } -+ if (capacity >= MAXIMUM_CAPACITY) { -+ return MAXIMUM_CAPACITY; -+ } -+ return IntegerUtil.roundCeilLog2(capacity); -+ } ++ TableEntry[] table = this.table; ++ table_loop: ++ for (;;) { ++ final int index = hash & (table.length - 1); + -+ /** Callers must still use acquire when reading the value of the entry. */ -+ protected final TableEntry getEntryForOpaque(final K key) { -+ final int hash = SWMRHashTable.getHash(key); -+ final TableEntry[] table = this.getTableAcquire(); ++ TableEntry node = getAtIndexVolatile(table, index); ++ node_loop: ++ for (;;) { ++ if (node == null) { ++ return null; ++ } + -+ for (TableEntry curr = getAtIndexOpaque(table, hash & (table.length - 1)); curr != null; curr = curr.getNextOpaque()) { -+ if (hash == curr.hash && (key == curr.key || curr.key.equals(key))) { -+ return curr; -+ } -+ } ++ if (node.resize) { ++ // noinspection unchecked ++ table = (TableEntry[])node.getValuePlain(); ++ continue table_loop; ++ } + -+ return null; -+ } ++ boolean removed = false; ++ V ret = null; + -+ protected final TableEntry getEntryForPlain(final K key) { -+ final int hash = SWMRHashTable.getHash(key); -+ final TableEntry[] table = this.getTablePlain(); ++ synchronized (node) { ++ if (node != (node = getAtIndexVolatile(table, index))) { ++ continue node_loop; ++ } + -+ for (TableEntry curr = table[hash & (table.length - 1)]; curr != null; curr = curr.getNextPlain()) { -+ if (hash == curr.hash && (key == curr.key || curr.key.equals(key))) { -+ return curr; -+ } -+ } ++ TableEntry prev = null; + -+ return null; -+ } ++ // plain reads are fine during synchronised access, as we are the only writer ++ for (; node != null; prev = node, node = node.getNextPlain()) { ++ if (node.key == key) { ++ ret = node.getValuePlain(); ++ if (ret == expect) { ++ removed = true; + -+ /* MT-Safe */ ++ // volatile ordering ensured by addSize(), but we need release here ++ // to ensure proper ordering with reads and other writes ++ if (prev == null) { ++ setAtIndexRelease(table, index, node.getNextPlain()); ++ } else { ++ prev.setNextRelease(node.getNextPlain()); ++ } ++ } ++ break; ++ } ++ } ++ } + -+ /** must be deterministic given a key */ -+ private static int getHash(final Object key) { -+ int hash = key == null ? 0 : key.hashCode(); -+ return HashUtil.mix(hash); -+ } ++ if (removed) { ++ this.subSize(1L); ++ } + -+ // rets -1 if capacity*loadFactor is too large -+ protected static int getTargetCapacity(final int capacity, final float loadFactor) { -+ final double ret = (double)capacity * (double)loadFactor; -+ if (Double.isInfinite(ret) || ret >= ((double)Integer.MAX_VALUE)) { -+ return -1; ++ return ret; ++ } + } -+ -+ return (int)ret; + } + + /** -+ * {@inheritDoc} ++ * Atomically removes the mapping for the specified key the predicate returns true for its currently mapped value. If the key ++ * is not mapped to a value, then does nothing and returns {@code null}. ++ * ++ *

    ++ * This function is a "functional methods" as defined by {@link ConcurrentLong2ReferenceChainedHashTable}. ++ *

    ++ * ++ * @param key Specified key ++ * @param predicate Specified predicate ++ * @throws NullPointerException If predicate is null ++ * @return The specified expected value if the key was mapped to {@code expect}. If ++ * the key is not mapped to any value, then returns {@code null}. If neither of those cases are true, ++ * then returns the current (non-null) mapped value for key. + */ -+ @Override -+ public boolean equals(final Object obj) { -+ if (this == obj) { -+ return true; -+ } -+ /* Make no attempt to deal with concurrent modifications */ -+ if (!(obj instanceof Map other)) { -+ return false; -+ } ++ public V removeIf(final long key, final Predicate predicate) { ++ Validate.notNull(predicate, "Predicate may not be null"); + -+ if (this.size() != other.size()) { -+ return false; -+ } ++ final int hash = getHash(key); + -+ final TableEntry[] table = this.getTableAcquire(); ++ TableEntry[] table = this.table; ++ table_loop: ++ for (;;) { ++ final int index = hash & (table.length - 1); + -+ for (int i = 0, len = table.length; i < len; ++i) { -+ for (TableEntry curr = getAtIndexOpaque(table, i); curr != null; curr = curr.getNextOpaque()) { -+ final V value = curr.getValueAcquire(); ++ TableEntry node = getAtIndexVolatile(table, index); ++ node_loop: ++ for (;;) { ++ if (node == null) { ++ return null; ++ } + -+ final Object otherValue = other.get(curr.key); -+ if (otherValue == null || (value != otherValue && value.equals(otherValue))) { -+ return false; ++ if (node.resize) { ++ // noinspection unchecked ++ table = (TableEntry[])node.getValuePlain(); ++ continue table_loop; + } -+ } -+ } + -+ return true; -+ } ++ boolean removed = false; ++ V ret = null; + -+ /** -+ * {@inheritDoc} -+ */ -+ @Override -+ public int hashCode() { -+ /* Make no attempt to deal with concurrent modifications */ -+ int hash = 0; -+ final TableEntry[] table = this.getTableAcquire(); ++ synchronized (node) { ++ if (node != (node = getAtIndexVolatile(table, index))) { ++ continue node_loop; ++ } + -+ for (int i = 0, len = table.length; i < len; ++i) { -+ for (TableEntry curr = getAtIndexOpaque(table, i); curr != null; curr = curr.getNextOpaque()) { -+ hash += curr.hashCode(); -+ } -+ } ++ TableEntry prev = null; + -+ return hash; -+ } ++ // plain reads are fine during synchronised access, as we are the only writer ++ for (; node != null; prev = node, node = node.getNextPlain()) { ++ if (node.key == key) { ++ ret = node.getValuePlain(); ++ if (predicate.test(ret)) { ++ removed = true; + -+ /** -+ * {@inheritDoc} -+ */ -+ @Override -+ public String toString() { -+ final StringBuilder builder = new StringBuilder(64); -+ builder.append("SWMRHashTable:{"); ++ // volatile ordering ensured by addSize(), but we need release here ++ // to ensure proper ordering with reads and other writes ++ if (prev == null) { ++ setAtIndexRelease(table, index, node.getNextPlain()); ++ } else { ++ prev.setNextRelease(node.getNextPlain()); ++ } ++ } ++ break; ++ } ++ } ++ } + -+ this.forEach((final K key, final V value) -> { -+ builder.append("{key: \"").append(key).append("\", value: \"").append(value).append("\"}"); -+ }); ++ if (removed) { ++ this.subSize(1L); ++ } + -+ return builder.append('}').toString(); ++ return ret; ++ } ++ } + } + + /** -+ * {@inheritDoc} ++ * See {@link java.util.concurrent.ConcurrentMap#compute(Object, BiFunction)} ++ *

    ++ * This function is a "functional methods" as defined by {@link ConcurrentLong2ReferenceChainedHashTable}. ++ *

    + */ -+ @Override -+ public SWMRHashTable clone() { -+ return new SWMRHashTable<>(this.getTableAcquire().length, this.loadFactor, this); -+ } ++ public V compute(final long key, final BiLong1Function function) { ++ final int hash = getHash(key); + -+ /** -+ * {@inheritDoc} -+ */ -+ @Override -+ public Iterator> iterator() { -+ return new EntryIterator<>(this.getTableAcquire(), this); -+ } ++ TableEntry[] table = this.table; ++ table_loop: ++ for (;;) { ++ final int index = hash & (table.length - 1); + -+ /** -+ * {@inheritDoc} -+ */ -+ @Override -+ public void forEach(final Consumer> action) { -+ Validate.notNull(action, "Null action"); ++ TableEntry node = getAtIndexVolatile(table, index); ++ node_loop: ++ for (;;) { ++ V ret = null; ++ if (node == null) { ++ final TableEntry insert = new TableEntry<>(key, null); + -+ final TableEntry[] table = this.getTableAcquire(); -+ for (int i = 0, len = table.length; i < len; ++i) { -+ for (TableEntry curr = getAtIndexOpaque(table, i); curr != null; curr = curr.getNextOpaque()) { -+ action.accept(curr); -+ } -+ } -+ } ++ boolean added = false; + -+ /** -+ * {@inheritDoc} -+ */ -+ @Override -+ public void forEach(final BiConsumer action) { -+ Validate.notNull(action, "Null action"); ++ synchronized (insert) { ++ if (null == (node = compareAndExchangeAtIndexVolatile(table, index, null, insert))) { ++ try { ++ ret = function.apply(key, null); ++ } catch (final Throwable throwable) { ++ setAtIndexVolatile(table, index, null); ++ ThrowUtil.throwUnchecked(throwable); ++ // unreachable ++ return null; ++ } + -+ final TableEntry[] table = this.getTableAcquire(); -+ for (int i = 0, len = table.length; i < len; ++i) { -+ for (TableEntry curr = getAtIndexOpaque(table, i); curr != null; curr = curr.getNextOpaque()) { -+ final V value = curr.getValueAcquire(); ++ if (ret == null) { ++ setAtIndexVolatile(table, index, null); ++ return ret; ++ } else { ++ // volatile ordering ensured by addSize(), but we need release here ++ // to ensure proper ordering with reads and other writes ++ insert.setValueRelease(ret); ++ added = true; ++ } ++ } // else: node != null, fall through ++ } + -+ action.accept(curr.key, value); -+ } -+ } -+ } ++ if (added) { ++ this.addSize(1L); ++ return ret; ++ } ++ } + -+ /** -+ * Provides the specified consumer with all keys contained within this map. -+ * @param action The specified consumer. -+ */ -+ public void forEachKey(final Consumer action) { -+ Validate.notNull(action, "Null action"); ++ if (node.resize) { ++ // noinspection unchecked ++ table = (TableEntry[])node.getValuePlain(); ++ continue table_loop; ++ } + -+ final TableEntry[] table = this.getTableAcquire(); -+ for (int i = 0, len = table.length; i < len; ++i) { -+ for (TableEntry curr = getAtIndexOpaque(table, i); curr != null; curr = curr.getNextOpaque()) { -+ action.accept(curr.key); -+ } -+ } -+ } ++ boolean removed = false; ++ boolean added = false; + -+ /** -+ * Provides the specified consumer with all values contained within this map. Equivalent to {@code map.values().forEach(Consumer)}. -+ * @param action The specified consumer. -+ */ -+ public void forEachValue(final Consumer action) { -+ Validate.notNull(action, "Null action"); ++ synchronized (node) { ++ if (node != (node = getAtIndexVolatile(table, index))) { ++ continue node_loop; ++ } ++ // plain reads are fine during synchronised access, as we are the only writer ++ TableEntry prev = null; ++ for (; node != null; prev = node, node = node.getNextPlain()) { ++ if (node.key == key) { ++ final V old = node.getValuePlain(); + -+ final TableEntry[] table = this.getTableAcquire(); -+ for (int i = 0, len = table.length; i < len; ++i) { -+ for (TableEntry curr = getAtIndexOpaque(table, i); curr != null; curr = curr.getNextOpaque()) { -+ final V value = curr.getValueAcquire(); ++ final V computed = function.apply(key, old); + -+ action.accept(value); -+ } -+ } -+ } ++ if (computed != null) { ++ node.setValueVolatile(computed); ++ return computed; ++ } + -+ /** -+ * {@inheritDoc} -+ */ -+ @Override -+ public V get(final Object key) { -+ Validate.notNull(key, "Null key"); ++ // volatile ordering ensured by addSize(), but we need release here ++ // to ensure proper ordering with reads and other writes ++ if (prev == null) { ++ setAtIndexRelease(table, index, node.getNextPlain()); ++ } else { ++ prev.setNextRelease(node.getNextPlain()); ++ } + -+ //noinspection unchecked -+ final TableEntry entry = this.getEntryForOpaque((K)key); -+ return entry == null ? null : entry.getValueAcquire(); -+ } ++ removed = true; ++ break; ++ } ++ } + -+ /** -+ * {@inheritDoc} -+ */ -+ @Override -+ public boolean containsKey(final Object key) { -+ Validate.notNull(key, "Null key"); ++ if (!removed) { ++ final V computed = function.apply(key, null); ++ if (computed != null) { ++ // volatile ordering ensured by addSize(), but we need release here ++ // to ensure proper ordering with reads and other writes ++ prev.setNextRelease(new TableEntry<>(key, computed)); ++ ret = computed; ++ added = true; ++ } ++ } ++ } + -+ // note: we need to use getValueAcquire, so that the reads from this map are ordered by acquire semantics -+ return this.get(key) != null; ++ if (removed) { ++ this.subSize(1L); ++ } ++ if (added) { ++ this.addSize(1L); ++ } ++ ++ return ret; ++ } ++ } + } + + /** -+ * Returns {@code true} if this map contains an entry with the specified key and value at some point during this call. -+ * @param key The specified key. -+ * @param value The specified value. -+ * @return {@code true} if this map contains an entry with the specified key and value. ++ * See {@link java.util.concurrent.ConcurrentMap#computeIfAbsent(Object, Function)} ++ *

    ++ * This function is a "functional methods" as defined by {@link ConcurrentLong2ReferenceChainedHashTable}. ++ *

    + */ -+ public boolean contains(final Object key, final Object value) { -+ Validate.notNull(key, "Null key"); ++ public V computeIfAbsent(final long key, final LongFunction function) { ++ final int hash = getHash(key); + -+ //noinspection unchecked -+ final TableEntry entry = this.getEntryForOpaque((K)key); ++ TableEntry[] table = this.table; ++ table_loop: ++ for (;;) { ++ final int index = hash & (table.length - 1); + -+ if (entry == null) { -+ return false; -+ } ++ TableEntry node = getAtIndexVolatile(table, index); ++ node_loop: ++ for (;;) { ++ V ret = null; ++ if (node == null) { ++ final TableEntry insert = new TableEntry<>(key, null); + -+ final V entryVal = entry.getValueAcquire(); -+ return entryVal == value || entryVal.equals(value); -+ } ++ boolean added = false; + -+ /** -+ * {@inheritDoc} -+ */ -+ @Override -+ public boolean containsValue(final Object value) { -+ Validate.notNull(value, "Null value"); ++ synchronized (insert) { ++ if (null == (node = compareAndExchangeAtIndexVolatile(table, index, null, insert))) { ++ try { ++ ret = function.apply(key); ++ } catch (final Throwable throwable) { ++ setAtIndexVolatile(table, index, null); ++ ThrowUtil.throwUnchecked(throwable); ++ // unreachable ++ return null; ++ } + -+ final TableEntry[] table = this.getTableAcquire(); -+ for (int i = 0, len = table.length; i < len; ++i) { -+ for (TableEntry curr = getAtIndexOpaque(table, i); curr != null; curr = curr.getNextOpaque()) { -+ final V currVal = curr.getValueAcquire(); -+ if (currVal == value || currVal.equals(value)) { -+ return true; ++ if (ret == null) { ++ setAtIndexVolatile(table, index, null); ++ return null; ++ } else { ++ // volatile ordering ensured by addSize(), but we need release here ++ // to ensure proper ordering with reads and other writes ++ insert.setValueRelease(ret); ++ added = true; ++ } ++ } // else: node != null, fall through ++ } ++ ++ if (added) { ++ this.addSize(1L); ++ return ret; ++ } ++ } ++ ++ if (node.resize) { ++ // noinspection unchecked ++ table = (TableEntry[])node.getValuePlain(); ++ continue table_loop; + } -+ } -+ } + -+ return false; -+ } ++ // optimise ifAbsent calls: check if first node is key before attempting lock acquire ++ if (node.key == key) { ++ ret = node.getValueVolatile(); ++ if (ret != null) { ++ return ret; ++ } // else: fall back to lock to read the node ++ } + -+ /** -+ * {@inheritDoc} -+ */ -+ @Override -+ public V getOrDefault(final Object key, final V defaultValue) { -+ Validate.notNull(key, "Null key"); ++ boolean added = false; + -+ //noinspection unchecked -+ final TableEntry entry = this.getEntryForOpaque((K)key); ++ synchronized (node) { ++ if (node != (node = getAtIndexVolatile(table, index))) { ++ continue node_loop; ++ } ++ // plain reads are fine during synchronised access, as we are the only writer ++ TableEntry prev = null; ++ for (; node != null; prev = node, node = node.getNextPlain()) { ++ if (node.key == key) { ++ ret = node.getValuePlain(); ++ return ret; ++ } ++ } + -+ return entry == null ? defaultValue : entry.getValueAcquire(); -+ } ++ final V computed = function.apply(key); ++ if (computed != null) { ++ // volatile ordering ensured by addSize(), but we need release here ++ // to ensure proper ordering with reads and other writes ++ prev.setNextRelease(new TableEntry<>(key, computed)); ++ ret = computed; ++ added = true; ++ } ++ } + -+ /** -+ * {@inheritDoc} -+ */ -+ @Override -+ public int size() { -+ return this.getSizeAcquire(); ++ if (added) { ++ this.addSize(1L); ++ } ++ ++ return ret; ++ } ++ } + } + + /** -+ * {@inheritDoc} ++ * See {@link java.util.concurrent.ConcurrentMap#computeIfPresent(Object, BiFunction)} ++ *

    ++ * This function is a "functional methods" as defined by {@link ConcurrentLong2ReferenceChainedHashTable}. ++ *

    + */ -+ @Override -+ public boolean isEmpty() { -+ return this.getSizeAcquire() == 0; -+ } -+ -+ protected KeySet keyset; -+ protected ValueCollection values; -+ protected EntrySet entrySet; -+ -+ @Override -+ public Set keySet() { -+ return this.keyset == null ? this.keyset = new KeySet<>(this) : this.keyset; -+ } ++ public V computeIfPresent(final long key, final BiLong1Function function) { ++ final int hash = getHash(key); + -+ @Override -+ public Collection values() { -+ return this.values == null ? this.values = new ValueCollection<>(this) : this.values; -+ } ++ TableEntry[] table = this.table; ++ table_loop: ++ for (;;) { ++ final int index = hash & (table.length - 1); + -+ @Override -+ public Set> entrySet() { -+ return this.entrySet == null ? this.entrySet = new EntrySet<>(this) : this.entrySet; -+ } ++ TableEntry node = getAtIndexVolatile(table, index); ++ node_loop: ++ for (;;) { ++ if (node == null) { ++ return null; ++ } + -+ /* Non-MT-Safe */ ++ if (node.resize) { ++ // noinspection unchecked ++ table = (TableEntry[])node.getValuePlain(); ++ continue table_loop; ++ } + -+ protected int threshold; ++ boolean removed = false; + -+ protected final void checkResize(final int minCapacity) { -+ if (minCapacity <= this.threshold || this.threshold < 0) { -+ return; -+ } ++ synchronized (node) { ++ if (node != (node = getAtIndexVolatile(table, index))) { ++ continue node_loop; ++ } ++ // plain reads are fine during synchronised access, as we are the only writer ++ TableEntry prev = null; ++ for (; node != null; prev = node, node = node.getNextPlain()) { ++ if (node.key == key) { ++ final V old = node.getValuePlain(); + -+ final TableEntry[] table = this.getTablePlain(); -+ int newCapacity = minCapacity >= MAXIMUM_CAPACITY ? MAXIMUM_CAPACITY : IntegerUtil.roundCeilLog2(minCapacity); -+ if (newCapacity < 0) { -+ newCapacity = MAXIMUM_CAPACITY; -+ } -+ if (newCapacity <= table.length) { -+ if (newCapacity == MAXIMUM_CAPACITY) { -+ return; -+ } -+ newCapacity = table.length << 1; -+ } ++ final V computed = function.apply(key, old); + -+ //noinspection unchecked -+ final TableEntry[] newTable = new TableEntry[newCapacity]; -+ final int indexMask = newCapacity - 1; ++ if (computed != null) { ++ node.setValueVolatile(computed); ++ return computed; ++ } + -+ for (int i = 0, len = table.length; i < len; ++i) { -+ for (TableEntry entry = table[i]; entry != null; entry = entry.getNextPlain()) { -+ final int hash = entry.hash; -+ final int index = hash & indexMask; ++ // volatile ordering ensured by addSize(), but we need release here ++ // to ensure proper ordering with reads and other writes ++ if (prev == null) { ++ setAtIndexRelease(table, index, node.getNextPlain()); ++ } else { ++ prev.setNextRelease(node.getNextPlain()); ++ } + -+ /* we need to create a new entry since there could be reading threads */ -+ final TableEntry insert = new TableEntry<>(hash, entry.key, entry.getValuePlain()); ++ removed = true; ++ break; ++ } ++ } ++ } + -+ final TableEntry prev = newTable[index]; ++ if (removed) { ++ this.subSize(1L); ++ } + -+ newTable[index] = insert; -+ insert.setNextPlain(prev); ++ return null; + } + } -+ -+ if (newCapacity == MAXIMUM_CAPACITY) { -+ this.threshold = -1; /* No more resizing */ -+ } else { -+ this.threshold = getTargetCapacity(newCapacity, this.loadFactor); -+ } -+ this.setTableRelease(newTable); /* use release to publish entries in table */ + } + -+ protected final int addToSize(final int num) { -+ final int newSize = this.getSizePlain() + num; -+ -+ this.setSizeOpaque(newSize); -+ this.checkResize(newSize); ++ /** ++ * See {@link java.util.concurrent.ConcurrentMap#merge(Object, Object, BiFunction)} ++ *

    ++ * This function is a "functional methods" as defined by {@link ConcurrentLong2ReferenceChainedHashTable}. ++ *

    ++ */ ++ public V merge(final long key, final V def, final BiFunction function) { ++ Validate.notNull(def, "Default value may not be null"); + -+ return newSize; -+ } ++ final int hash = getHash(key); + -+ protected final int removeFromSize(final int num) { -+ final int newSize = this.getSizePlain() - num; ++ TableEntry[] table = this.table; ++ table_loop: ++ for (;;) { ++ final int index = hash & (table.length - 1); + -+ this.setSizeOpaque(newSize); ++ TableEntry node = getAtIndexVolatile(table, index); ++ node_loop: ++ for (;;) { ++ if (node == null) { ++ if (null == (node = compareAndExchangeAtIndexVolatile(table, index, null, new TableEntry<>(key, def)))) { ++ // successfully inserted ++ this.addSize(1L); ++ return def; ++ } // else: node != null, fall through ++ } + -+ return newSize; -+ } ++ if (node.resize) { ++ // noinspection unchecked ++ table = (TableEntry[])node.getValuePlain(); ++ continue table_loop; ++ } + -+ /* Cannot be used to perform downsizing */ -+ protected final int removeFromSizePlain(final int num) { -+ final int newSize = this.getSizePlain() - num; ++ boolean removed = false; ++ boolean added = false; ++ V ret = null; + -+ this.setSizePlain(newSize); ++ synchronized (node) { ++ if (node != (node = getAtIndexVolatile(table, index))) { ++ continue node_loop; ++ } ++ // plain reads are fine during synchronised access, as we are the only writer ++ TableEntry prev = null; ++ for (; node != null; prev = node, node = node.getNextPlain()) { ++ if (node.key == key) { ++ final V old = node.getValuePlain(); + -+ return newSize; -+ } ++ final V computed = function.apply(old, def); + -+ protected final V put(final K key, final V value, final boolean onlyIfAbsent) { -+ final TableEntry[] table = this.getTablePlain(); -+ final int hash = SWMRHashTable.getHash(key); -+ final int index = hash & (table.length - 1); ++ if (computed != null) { ++ node.setValueVolatile(computed); ++ return computed; ++ } + -+ final TableEntry head = table[index]; -+ if (head == null) { -+ final TableEntry insert = new TableEntry<>(hash, key, value); -+ setAtIndexRelease(table, index, insert); -+ this.addToSize(1); -+ return null; -+ } ++ // volatile ordering ensured by addSize(), but we need release here ++ // to ensure proper ordering with reads and other writes ++ if (prev == null) { ++ setAtIndexRelease(table, index, node.getNextPlain()); ++ } else { ++ prev.setNextRelease(node.getNextPlain()); ++ } + -+ for (TableEntry curr = head;;) { -+ if (curr.hash == hash && (key == curr.key || curr.key.equals(key))) { -+ if (onlyIfAbsent) { -+ return curr.getValuePlain(); ++ removed = true; ++ break; ++ } ++ } ++ ++ if (!removed) { ++ // volatile ordering ensured by addSize(), but we need release here ++ // to ensure proper ordering with reads and other writes ++ prev.setNextRelease(new TableEntry<>(key, def)); ++ ret = def; ++ added = true; ++ } + } + -+ final V currVal = curr.getValuePlain(); -+ curr.setValueRelease(value); -+ return currVal; -+ } ++ if (removed) { ++ this.subSize(1L); ++ } ++ if (added) { ++ this.addSize(1L); ++ } + -+ final TableEntry next = curr.getNextPlain(); -+ if (next != null) { -+ curr = next; -+ continue; ++ return ret; + } -+ -+ final TableEntry insert = new TableEntry<>(hash, key, value); -+ -+ curr.setNextRelease(insert); -+ this.addToSize(1); -+ return null; + } + } + + /** -+ * Removes a key-value pair from this map if the specified predicate returns true. The specified predicate is -+ * tested with every entry in this map. Returns the number of key-value pairs removed. -+ * @param predicate The predicate to test key-value pairs against. -+ * @return The total number of key-value pairs removed from this map. ++ * Removes at least all entries currently mapped at the beginning of this call. May not remove entries added during ++ * this call. As a result, only if this map is not modified during the call, that all entries will be removed by ++ * the end of the call. ++ * ++ *

    ++ * This function is not atomic. ++ *

    + */ -+ public int removeIf(final BiPredicate predicate) { -+ Validate.notNull(predicate, "Null predicate"); -+ -+ int removed = 0; ++ public void clear() { ++ // it is possible to optimise this to directly interact with the table, ++ // but we do need to be careful when interacting with resized tables, ++ // and the NodeIterator already does this logic ++ final NodeIterator nodeIterator = new NodeIterator<>(this.table); + -+ final TableEntry[] table = this.getTablePlain(); ++ TableEntry node; ++ while ((node = nodeIterator.findNext()) != null) { ++ this.remove(node.key); ++ } ++ } + -+ bin_iteration_loop: -+ for (int i = 0, len = table.length; i < len; ++i) { -+ TableEntry curr = table[i]; -+ if (curr == null) { -+ continue; -+ } ++ /** ++ * Returns an iterator over the entries in this map. The iterator is only guaranteed to see entries that were ++ * added before the beginning of this call, but it may see entries added during. ++ */ ++ public Iterator> entryIterator() { ++ return new EntryIterator<>(this); ++ } + -+ /* Handle bin nodes first */ -+ while (predicate.test(curr.key, curr.getValuePlain())) { -+ ++removed; -+ this.removeFromSizePlain(1); /* required in case predicate throws an exception */ ++ @Override ++ public final Iterator> iterator() { ++ return this.entryIterator(); ++ } + -+ setAtIndexRelease(table, i, curr = curr.getNextPlain()); ++ /** ++ * Returns an iterator over the keys in this map. The iterator is only guaranteed to see keys that were ++ * added before the beginning of this call, but it may see keys added during. ++ */ ++ public PrimitiveIterator.OfLong keyIterator() { ++ return new KeyIterator<>(this); ++ } + -+ if (curr == null) { -+ continue bin_iteration_loop; -+ } -+ } ++ /** ++ * Returns an iterator over the values in this map. The iterator is only guaranteed to see values that were ++ * added before the beginning of this call, but it may see values added during. ++ */ ++ public Iterator valueIterator() { ++ return new ValueIterator<>(this); ++ } + -+ TableEntry prev; ++ protected static final class EntryIterator extends BaseIteratorImpl> { + -+ /* curr at this point is the bin node */ ++ public EntryIterator(final ConcurrentLong2ReferenceChainedHashTable map) { ++ super(map); ++ } + -+ for (prev = curr, curr = curr.getNextPlain(); curr != null;) { -+ /* If we want to remove, then we should hold prev, as it will be a valid entry to link on */ -+ if (predicate.test(curr.key, curr.getValuePlain())) { -+ ++removed; -+ this.removeFromSizePlain(1); /* required in case predicate throws an exception */ ++ @Override ++ public TableEntry next() throws NoSuchElementException { ++ return this.nextNode(); ++ } + -+ prev.setNextRelease(curr = curr.getNextPlain()); -+ } else { -+ prev = curr; -+ curr = curr.getNextPlain(); -+ } ++ @Override ++ public void forEachRemaining(final Consumer> action) { ++ Validate.notNull(action, "Action may not be null"); ++ while (this.hasNext()) { ++ action.accept(this.next()); + } + } -+ -+ return removed; + } + -+ /** -+ * Removes a key-value pair from this map if the specified predicate returns true. The specified predicate is -+ * tested with every entry in this map. Returns the number of key-value pairs removed. -+ * @param predicate The predicate to test key-value pairs against. -+ * @return The total number of key-value pairs removed from this map. -+ */ -+ public int removeEntryIf(final Predicate> predicate) { -+ Validate.notNull(predicate, "Null predicate"); ++ protected static final class KeyIterator extends BaseIteratorImpl implements PrimitiveIterator.OfLong { + -+ int removed = 0; ++ public KeyIterator(final ConcurrentLong2ReferenceChainedHashTable map) { ++ super(map); ++ } + -+ final TableEntry[] table = this.getTablePlain(); ++ @Override ++ public Long next() throws NoSuchElementException { ++ return Long.valueOf(this.nextNode().key); ++ } + -+ bin_iteration_loop: -+ for (int i = 0, len = table.length; i < len; ++i) { -+ TableEntry curr = table[i]; -+ if (curr == null) { -+ continue; -+ } ++ @Override ++ public long nextLong() { ++ return this.nextNode().key; ++ } + -+ /* Handle bin nodes first */ -+ while (predicate.test(curr)) { -+ ++removed; -+ this.removeFromSizePlain(1); /* required in case predicate throws an exception */ ++ @Override ++ public void forEachRemaining(final Consumer action) { ++ Validate.notNull(action, "Action may not be null"); + -+ setAtIndexRelease(table, i, curr = curr.getNextPlain()); ++ if (action instanceof LongConsumer longConsumer) { ++ this.forEachRemaining(longConsumer); ++ return; ++ } + -+ if (curr == null) { -+ continue bin_iteration_loop; -+ } ++ while (this.hasNext()) { ++ action.accept(this.next()); + } ++ } + -+ TableEntry prev; ++ @Override ++ public void forEachRemaining(final LongConsumer action) { ++ Validate.notNull(action, "Action may not be null"); ++ while (this.hasNext()) { ++ action.accept(this.nextLong()); ++ } ++ } ++ } + -+ /* curr at this point is the bin node */ ++ protected static final class ValueIterator extends BaseIteratorImpl { + -+ for (prev = curr, curr = curr.getNextPlain(); curr != null;) { -+ /* If we want to remove, then we should hold prev, as it will be a valid entry to link on */ -+ if (predicate.test(curr)) { -+ ++removed; -+ this.removeFromSizePlain(1); /* required in case predicate throws an exception */ ++ public ValueIterator(final ConcurrentLong2ReferenceChainedHashTable map) { ++ super(map); ++ } + -+ prev.setNextRelease(curr = curr.getNextPlain()); -+ } else { -+ prev = curr; -+ curr = curr.getNextPlain(); -+ } -+ } ++ @Override ++ public V next() throws NoSuchElementException { ++ return this.nextNode().getValueVolatile(); + } + -+ return removed; ++ @Override ++ public void forEachRemaining(final Consumer action) { ++ Validate.notNull(action, "Action may not be null"); ++ while (this.hasNext()) { ++ action.accept(this.next()); ++ } ++ } + } + -+ /** -+ * {@inheritDoc} -+ */ -+ @Override -+ public V put(final K key, final V value) { -+ Validate.notNull(key, "Null key"); -+ Validate.notNull(value, "Null value"); -+ -+ return this.put(key, value, false); -+ } ++ protected static abstract class BaseIteratorImpl extends NodeIterator implements Iterator { + -+ /** -+ * {@inheritDoc} -+ */ -+ @Override -+ public V putIfAbsent(final K key, final V value) { -+ Validate.notNull(key, "Null key"); -+ Validate.notNull(value, "Null value"); ++ protected final ConcurrentLong2ReferenceChainedHashTable map; ++ protected TableEntry lastReturned; ++ protected TableEntry nextToReturn; + -+ return this.put(key, value, true); -+ } ++ protected BaseIteratorImpl(final ConcurrentLong2ReferenceChainedHashTable map) { ++ super(map.table); ++ this.map = map; ++ } + -+ /** -+ * {@inheritDoc} -+ */ -+ @Override -+ public boolean remove(final Object key, final Object value) { -+ Validate.notNull(key, "Null key"); -+ Validate.notNull(value, "Null value"); ++ @Override ++ public final boolean hasNext() { ++ if (this.nextToReturn != null) { ++ return true; ++ } + -+ final TableEntry[] table = this.getTablePlain(); -+ final int hash = SWMRHashTable.getHash(key); -+ final int index = hash & (table.length - 1); ++ return (this.nextToReturn = this.findNext()) != null; ++ } + -+ final TableEntry head = table[index]; -+ if (head == null) { -+ return false; ++ protected final TableEntry nextNode() throws NoSuchElementException { ++ TableEntry ret = this.nextToReturn; ++ if (ret != null) { ++ this.lastReturned = ret; ++ this.nextToReturn = null; ++ return ret; ++ } ++ ret = this.findNext(); ++ if (ret != null) { ++ this.lastReturned = ret; ++ return ret; ++ } ++ throw new NoSuchElementException(); + } + -+ if (head.hash == hash && (head.key == key || head.key.equals(key))) { -+ final V currVal = head.getValuePlain(); -+ -+ if (currVal != value && !currVal.equals(value)) { -+ return false; ++ @Override ++ public final void remove() { ++ final TableEntry lastReturned = this.lastReturned; ++ if (lastReturned == null) { ++ throw new NoSuchElementException(); + } ++ this.lastReturned = null; ++ this.map.remove(lastReturned.key); ++ } + -+ setAtIndexRelease(table, index, head.getNextPlain()); -+ this.removeFromSize(1); ++ @Override ++ public abstract T next() throws NoSuchElementException; + -+ return true; -+ } ++ // overwritten by subclasses to avoid indirection on hasNext() and next() ++ @Override ++ public abstract void forEachRemaining(final Consumer action); ++ } + -+ for (TableEntry curr = head.getNextPlain(), prev = head; curr != null; prev = curr, curr = curr.getNextPlain()) { -+ if (curr.hash == hash && (curr.key == key || curr.key.equals(key))) { -+ final V currVal = curr.getValuePlain(); ++ protected static class NodeIterator { + -+ if (currVal != value && !currVal.equals(value)) { -+ return false; -+ } ++ protected TableEntry[] currentTable; ++ protected ResizeChain resizeChain; ++ protected TableEntry last; ++ protected int nextBin; ++ protected int increment; + -+ prev.setNextRelease(curr.getNextPlain()); -+ this.removeFromSize(1); ++ protected NodeIterator(final TableEntry[] baseTable) { ++ this.currentTable = baseTable; ++ this.increment = 1; ++ } + -+ return true; ++ private TableEntry[] pullResizeChain(final int index) { ++ final ResizeChain resizeChain = this.resizeChain; ++ if (resizeChain == null) { ++ this.currentTable = null; ++ return null; + } -+ } + -+ return false; -+ } ++ final ResizeChain prevChain = resizeChain.prev; ++ this.resizeChain = prevChain; ++ if (prevChain == null) { ++ this.currentTable = null; ++ return null; ++ } + -+ protected final V remove(final Object key, final int hash) { -+ final TableEntry[] table = this.getTablePlain(); -+ final int index = (table.length - 1) & hash; ++ final TableEntry[] newTable = prevChain.table; + -+ final TableEntry head = table[index]; -+ if (head == null) { -+ return null; -+ } ++ // we recover the original index by modding by the new table length, as the increments applied to the index ++ // are a multiple of the new table's length ++ int newIdx = index & (newTable.length - 1); + -+ if (hash == head.hash && (head.key == key || head.key.equals(key))) { -+ setAtIndexRelease(table, index, head.getNextPlain()); -+ this.removeFromSize(1); ++ // the increment is always the previous table's length ++ final ResizeChain nextPrevChain = prevChain.prev; ++ final int increment; ++ if (nextPrevChain == null) { ++ increment = 1; ++ } else { ++ increment = nextPrevChain.table.length; ++ } + -+ return head.getValuePlain(); -+ } ++ // done with the upper table, so we can skip the resize node ++ newIdx += increment; + -+ for (TableEntry curr = head.getNextPlain(), prev = head; curr != null; prev = curr, curr = curr.getNextPlain()) { -+ if (curr.hash == hash && (key == curr.key || curr.key.equals(key))) { -+ prev.setNextRelease(curr.getNextPlain()); -+ this.removeFromSize(1); ++ this.increment = increment; ++ this.nextBin = newIdx; ++ this.currentTable = newTable; + -+ return curr.getValuePlain(); -+ } ++ return newTable; + } + -+ return null; -+ } ++ private TableEntry[] pushResizeChain(final TableEntry[] table, final TableEntry entry) { ++ final ResizeChain chain = this.resizeChain; + -+ /** -+ * {@inheritDoc} -+ */ -+ @Override -+ public V remove(final Object key) { -+ Validate.notNull(key, "Null key"); ++ if (chain == null) { ++ // noinspection unchecked ++ final TableEntry[] nextTable = (TableEntry[])entry.getValuePlain(); + -+ return this.remove(key, SWMRHashTable.getHash(key)); -+ } ++ final ResizeChain oldChain = new ResizeChain<>(table, null, null); ++ final ResizeChain currChain = new ResizeChain<>(nextTable, oldChain, null); ++ oldChain.next = currChain; + -+ /** -+ * {@inheritDoc} -+ */ -+ @Override -+ public boolean replace(final K key, final V oldValue, final V newValue) { -+ Validate.notNull(key, "Null key"); -+ Validate.notNull(oldValue, "Null oldValue"); -+ Validate.notNull(newValue, "Null newValue"); ++ this.increment = table.length; ++ this.resizeChain = currChain; ++ this.currentTable = nextTable; + -+ final TableEntry entry = this.getEntryForPlain(key); -+ if (entry == null) { -+ return false; -+ } ++ return nextTable; ++ } else { ++ ResizeChain currChain = chain.next; ++ if (currChain == null) { ++ // noinspection unchecked ++ final TableEntry[] ret = (TableEntry[])entry.getValuePlain(); ++ currChain = new ResizeChain<>(ret, chain, null); ++ chain.next = currChain; + -+ final V currValue = entry.getValuePlain(); -+ if (currValue == oldValue || currValue.equals(oldValue)) { -+ entry.setValueRelease(newValue); -+ return true; ++ this.increment = table.length; ++ this.resizeChain = currChain; ++ this.currentTable = ret; ++ ++ return ret; ++ } else { ++ this.increment = table.length; ++ this.resizeChain = currChain; ++ return this.currentTable = currChain.table; ++ } ++ } + } + -+ return false; -+ } ++ protected final TableEntry findNext() { ++ for (;;) { ++ final TableEntry last = this.last; ++ if (last != null) { ++ final TableEntry next = last.getNextVolatile(); ++ if (next != null) { ++ this.last = next; ++ if (next.getValuePlain() == null) { ++ // compute() node not yet available ++ continue; ++ } ++ return next; ++ } ++ } + -+ /** -+ * {@inheritDoc} -+ */ -+ @Override -+ public V replace(final K key, final V value) { -+ Validate.notNull(key, "Null key"); -+ Validate.notNull(value, "Null value"); ++ TableEntry[] table = this.currentTable; + -+ final TableEntry entry = this.getEntryForPlain(key); -+ if (entry == null) { -+ return null; -+ } ++ if (table == null) { ++ return null; ++ } + -+ final V prev = entry.getValuePlain(); -+ entry.setValueRelease(value); -+ return prev; -+ } ++ int idx = this.nextBin; ++ int increment = this.increment; ++ for (;;) { ++ if (idx >= table.length) { ++ table = this.pullResizeChain(idx); ++ idx = this.nextBin; ++ increment = this.increment; ++ if (table != null) { ++ continue; ++ } else { ++ this.last = null; ++ return null; ++ } ++ } + -+ /** -+ * {@inheritDoc} -+ */ -+ @Override -+ public void replaceAll(final BiFunction function) { -+ Validate.notNull(function, "Null function"); ++ final TableEntry entry = getAtIndexVolatile(table, idx); ++ if (entry == null) { ++ idx += increment; ++ continue; ++ } + -+ final TableEntry[] table = this.getTablePlain(); -+ for (int i = 0, len = table.length; i < len; ++i) { -+ for (TableEntry curr = table[i]; curr != null; curr = curr.getNextPlain()) { -+ final V value = curr.getValuePlain(); ++ if (entry.resize) { ++ // push onto resize chain ++ table = this.pushResizeChain(table, entry); ++ increment = this.increment; ++ continue; ++ } + -+ final V newValue = function.apply(curr.key, value); -+ if (newValue == null) { -+ throw new NullPointerException(); ++ this.last = entry; ++ this.nextBin = idx + increment; ++ if (entry.getValuePlain() != null) { ++ return entry; ++ } else { ++ // compute() node not yet available ++ break; ++ } + } -+ -+ curr.setValueRelease(newValue); + } + } -+ } -+ -+ /** -+ * {@inheritDoc} -+ */ -+ @Override -+ public void putAll(final Map map) { -+ Validate.notNull(map, "Null map"); -+ -+ final int size = map.size(); -+ this.checkResize(Math.max(this.getSizePlain() + size/2, size)); /* preemptively resize */ -+ map.forEach(this::put); -+ } + -+ /** -+ * {@inheritDoc} -+ *

    -+ * This call is non-atomic and the order that which entries are removed is undefined. The clear operation itself -+ * is release ordered, that is, after the clear operation is performed a release fence is performed. -+ *

    -+ */ -+ @Override -+ public void clear() { -+ Arrays.fill(this.getTablePlain(), null); -+ this.setSizeRelease(0); ++ protected static final class ResizeChain { ++ ++ public final TableEntry[] table; ++ public final ResizeChain prev; ++ public ResizeChain next; ++ ++ public ResizeChain(final TableEntry[] table, final ResizeChain prev, final ResizeChain next) { ++ this.table = table; ++ this.prev = prev; ++ this.next = next; ++ } ++ } + } + -+ /** -+ * {@inheritDoc} -+ */ -+ @Override -+ public V compute(final K key, final BiFunction remappingFunction) { -+ Validate.notNull(key, "Null key"); -+ Validate.notNull(remappingFunction, "Null remappingFunction"); ++ public static final class TableEntry { + -+ final int hash = SWMRHashTable.getHash(key); -+ final TableEntry[] table = this.getTablePlain(); -+ final int index = hash & (table.length - 1); ++ private static final VarHandle TABLE_ENTRY_ARRAY_HANDLE = ConcurrentUtil.getArrayHandle(TableEntry[].class); + -+ for (TableEntry curr = table[index], prev = null;;prev = curr, curr = curr.getNextPlain()) { -+ if (curr == null) { -+ final V newVal = remappingFunction.apply(key ,null); ++ private final boolean resize; + -+ if (newVal == null) { -+ return null; -+ } ++ private final long key; + -+ final TableEntry insert = new TableEntry<>(hash, key, newVal); -+ if (prev == null) { -+ setAtIndexRelease(table, index, insert); -+ } else { -+ prev.setNextRelease(insert); -+ } ++ private volatile V value; ++ private static final VarHandle VALUE_HANDLE = ConcurrentUtil.getVarHandle(TableEntry.class, "value", Object.class); + -+ this.addToSize(1); ++ private V getValuePlain() { ++ //noinspection unchecked ++ return (V)VALUE_HANDLE.get(this); ++ } + -+ return newVal; -+ } ++ private V getValueAcquire() { ++ //noinspection unchecked ++ return (V)VALUE_HANDLE.getAcquire(this); ++ } + -+ if (curr.hash == hash && (curr.key == key || curr.key.equals(key))) { -+ final V newVal = remappingFunction.apply(key, curr.getValuePlain()); ++ private V getValueVolatile() { ++ //noinspection unchecked ++ return (V)VALUE_HANDLE.getVolatile(this); ++ } + -+ if (newVal != null) { -+ curr.setValueRelease(newVal); -+ return newVal; -+ } ++ private void setValuePlain(final V value) { ++ VALUE_HANDLE.set(this, (Object)value); ++ } + -+ if (prev == null) { -+ setAtIndexRelease(table, index, curr.getNextPlain()); -+ } else { -+ prev.setNextRelease(curr.getNextPlain()); -+ } ++ private void setValueRelease(final V value) { ++ VALUE_HANDLE.setRelease(this, (Object)value); ++ } + -+ this.removeFromSize(1); ++ private void setValueVolatile(final V value) { ++ VALUE_HANDLE.setVolatile(this, (Object)value); ++ } + -+ return null; -+ } ++ private volatile TableEntry next; ++ private static final VarHandle NEXT_HANDLE = ConcurrentUtil.getVarHandle(TableEntry.class, "next", TableEntry.class); ++ ++ private TableEntry getNextPlain() { ++ //noinspection unchecked ++ return (TableEntry)NEXT_HANDLE.get(this); + } -+ } + -+ /** -+ * {@inheritDoc} -+ */ -+ @Override -+ public V computeIfPresent(final K key, final BiFunction remappingFunction) { -+ Validate.notNull(key, "Null key"); -+ Validate.notNull(remappingFunction, "Null remappingFunction"); ++ private TableEntry getNextVolatile() { ++ //noinspection unchecked ++ return (TableEntry)NEXT_HANDLE.getVolatile(this); ++ } + -+ final int hash = SWMRHashTable.getHash(key); -+ final TableEntry[] table = this.getTablePlain(); -+ final int index = hash & (table.length - 1); ++ private void setNextPlain(final TableEntry next) { ++ NEXT_HANDLE.set(this, next); ++ } + -+ for (TableEntry curr = table[index], prev = null; curr != null; prev = curr, curr = curr.getNextPlain()) { -+ if (curr.hash != hash || (curr.key != key && !curr.key.equals(key))) { -+ continue; -+ } ++ private void setNextRelease(final TableEntry next) { ++ NEXT_HANDLE.setRelease(this, next); ++ } + -+ final V newVal = remappingFunction.apply(key, curr.getValuePlain()); -+ if (newVal != null) { -+ curr.setValueRelease(newVal); -+ return newVal; -+ } ++ private void setNextVolatile(final TableEntry next) { ++ NEXT_HANDLE.setVolatile(this, next); ++ } + -+ if (prev == null) { -+ setAtIndexRelease(table, index, curr.getNextPlain()); -+ } else { -+ prev.setNextRelease(curr.getNextPlain()); -+ } ++ public TableEntry(final long key, final V value) { ++ this.resize = false; ++ this.key = key; ++ this.setValuePlain(value); ++ } + -+ this.removeFromSize(1); ++ public TableEntry(final long key, final V value, final boolean resize) { ++ this.resize = resize; ++ this.key = key; ++ this.setValuePlain(value); ++ } + -+ return null; ++ public long getKey() { ++ return this.key; + } + -+ return null; ++ public V getValue() { ++ return this.getValueVolatile(); ++ } + } ++} +diff --git a/src/main/java/ca/spottedleaf/concurrentutil/map/SWMRHashTable.java b/src/main/java/ca/spottedleaf/concurrentutil/map/SWMRHashTable.java +new file mode 100644 +index 0000000000000000000000000000000000000000..83965350d292ccf42a34520d84dcda3f88146cff +--- /dev/null ++++ b/src/main/java/ca/spottedleaf/concurrentutil/map/SWMRHashTable.java +@@ -0,0 +1,1656 @@ ++package ca.spottedleaf.concurrentutil.map; + -+ /** -+ * {@inheritDoc} -+ */ -+ @Override -+ public V computeIfAbsent(final K key, final Function mappingFunction) { -+ Validate.notNull(key, "Null key"); -+ Validate.notNull(mappingFunction, "Null mappingFunction"); ++import ca.spottedleaf.concurrentutil.util.CollectionUtil; ++import ca.spottedleaf.concurrentutil.util.ConcurrentUtil; ++import ca.spottedleaf.concurrentutil.util.HashUtil; ++import ca.spottedleaf.concurrentutil.util.IntegerUtil; ++import ca.spottedleaf.concurrentutil.util.Validate; ++import java.lang.invoke.VarHandle; ++import java.util.ArrayList; ++import java.util.Arrays; ++import java.util.Collection; ++import java.util.Iterator; ++import java.util.List; ++import java.util.Map; ++import java.util.NoSuchElementException; ++import java.util.Set; ++import java.util.Spliterator; ++import java.util.Spliterators; ++import java.util.function.BiConsumer; ++import java.util.function.BiFunction; ++import java.util.function.BiPredicate; ++import java.util.function.Consumer; ++import java.util.function.Function; ++import java.util.function.IntFunction; ++import java.util.function.Predicate; + -+ final int hash = SWMRHashTable.getHash(key); -+ final TableEntry[] table = this.getTablePlain(); -+ final int index = hash & (table.length - 1); ++/** ++ *

    ++ * Note: Not really tested, use at your own risk. ++ *

    ++ * This map is safe for reading from multiple threads, however it is only safe to write from a single thread. ++ * {@code null} keys or values are not permitted. Writes to values in this map are guaranteed to be ordered by release semantics, ++ * however immediate visibility to other threads is not guaranteed. However, writes are guaranteed to be made visible eventually. ++ * Reads are ordered by acquire semantics. ++ *

    ++ * Iterators cannot be modified concurrently, and its backing map cannot be modified concurrently. There is no ++ * fast-fail attempt made by iterators, thus modifying the iterator's backing map while iterating will have undefined ++ * behaviour. ++ *

    ++ *

    ++ * Subclasses should override {@link #clone()} to return correct instances of this class. ++ *

    ++ * @param {@inheritDoc} ++ * @param {@inheritDoc} ++ */ ++public class SWMRHashTable implements Map, Iterable> { + -+ for (TableEntry curr = table[index], prev = null;;prev = curr, curr = curr.getNextPlain()) { -+ if (curr != null) { -+ if (curr.hash == hash && (curr.key == key || curr.key.equals(key))) { -+ return curr.getValuePlain(); -+ } -+ continue; -+ } ++ protected int size; + -+ final V newVal = mappingFunction.apply(key); ++ protected TableEntry[] table; + -+ if (newVal == null) { -+ return null; -+ } ++ protected final float loadFactor; + -+ final TableEntry insert = new TableEntry<>(hash, key, newVal); -+ if (prev == null) { -+ setAtIndexRelease(table, index, insert); -+ } else { -+ prev.setNextRelease(insert); -+ } ++ protected static final VarHandle SIZE_HANDLE = ConcurrentUtil.getVarHandle(SWMRHashTable.class, "size", int.class); ++ protected static final VarHandle TABLE_HANDLE = ConcurrentUtil.getVarHandle(SWMRHashTable.class, "table", TableEntry[].class); + -+ this.addToSize(1); ++ /* size */ + -+ return newVal; -+ } ++ protected final int getSizePlain() { ++ return (int)SIZE_HANDLE.get(this); + } + -+ /** -+ * {@inheritDoc} -+ */ -+ @Override -+ public V merge(final K key, final V value, final BiFunction remappingFunction) { -+ Validate.notNull(key, "Null key"); -+ Validate.notNull(value, "Null value"); -+ Validate.notNull(remappingFunction, "Null remappingFunction"); -+ -+ final int hash = SWMRHashTable.getHash(key); -+ final TableEntry[] table = this.getTablePlain(); -+ final int index = hash & (table.length - 1); -+ -+ for (TableEntry curr = table[index], prev = null;;prev = curr, curr = curr.getNextPlain()) { -+ if (curr == null) { -+ final TableEntry insert = new TableEntry<>(hash, key, value); -+ if (prev == null) { -+ setAtIndexRelease(table, index, insert); -+ } else { -+ prev.setNextRelease(insert); -+ } ++ protected final int getSizeOpaque() { ++ return (int)SIZE_HANDLE.getOpaque(this); ++ } + -+ this.addToSize(1); ++ protected final int getSizeAcquire() { ++ return (int)SIZE_HANDLE.getAcquire(this); ++ } + -+ return value; -+ } ++ protected final void setSizePlain(final int value) { ++ SIZE_HANDLE.set(this, value); ++ } + -+ if (curr.hash == hash && (curr.key == key || curr.key.equals(key))) { -+ final V newVal = remappingFunction.apply(curr.getValuePlain(), value); ++ protected final void setSizeOpaque(final int value) { ++ SIZE_HANDLE.setOpaque(this, value); ++ } + -+ if (newVal != null) { -+ curr.setValueRelease(newVal); -+ return newVal; -+ } ++ protected final void setSizeRelease(final int value) { ++ SIZE_HANDLE.setRelease(this, value); ++ } + -+ if (prev == null) { -+ setAtIndexRelease(table, index, curr.getNextPlain()); -+ } else { -+ prev.setNextRelease(curr.getNextPlain()); -+ } ++ /* table */ + -+ this.removeFromSize(1); ++ protected final TableEntry[] getTablePlain() { ++ //noinspection unchecked ++ return (TableEntry[])TABLE_HANDLE.get(this); ++ } + -+ return null; -+ } -+ } ++ protected final TableEntry[] getTableAcquire() { ++ //noinspection unchecked ++ return (TableEntry[])TABLE_HANDLE.getAcquire(this); + } + -+ protected static final class TableEntry implements Map.Entry { ++ protected final void setTablePlain(final TableEntry[] table) { ++ TABLE_HANDLE.set(this, table); ++ } + -+ protected static final VarHandle TABLE_ENTRY_ARRAY_HANDLE = ConcurrentUtil.getArrayHandle(TableEntry[].class); ++ protected final void setTableRelease(final TableEntry[] table) { ++ TABLE_HANDLE.setRelease(this, table); ++ } + -+ protected final int hash; -+ protected final K key; -+ protected V value; ++ protected static final int DEFAULT_CAPACITY = 16; ++ protected static final float DEFAULT_LOAD_FACTOR = 0.75f; ++ protected static final int MAXIMUM_CAPACITY = Integer.MIN_VALUE >>> 1; + -+ protected TableEntry next; ++ /** ++ * Constructs this map with a capacity of {@code 16} and load factor of {@code 0.75f}. ++ */ ++ public SWMRHashTable() { ++ this(DEFAULT_CAPACITY, DEFAULT_LOAD_FACTOR); ++ } + -+ protected static final VarHandle VALUE_HANDLE = ConcurrentUtil.getVarHandle(TableEntry.class, "value", Object.class); -+ protected static final VarHandle NEXT_HANDLE = ConcurrentUtil.getVarHandle(TableEntry.class, "next", TableEntry.class); ++ /** ++ * Constructs this map with the specified capacity and load factor of {@code 0.75f}. ++ * @param capacity specified initial capacity, > 0 ++ */ ++ public SWMRHashTable(final int capacity) { ++ this(capacity, DEFAULT_LOAD_FACTOR); ++ } + -+ /* value */ ++ /** ++ * Constructs this map with the specified capacity and load factor. ++ * @param capacity specified capacity, > 0 ++ * @param loadFactor specified load factor, > 0 && finite ++ */ ++ public SWMRHashTable(final int capacity, final float loadFactor) { ++ final int tableSize = getCapacityFor(capacity); + -+ protected final V getValuePlain() { -+ //noinspection unchecked -+ return (V)VALUE_HANDLE.get(this); ++ if (loadFactor <= 0.0 || !Float.isFinite(loadFactor)) { ++ throw new IllegalArgumentException("Invalid load factor: " + loadFactor); + } + -+ protected final V getValueAcquire() { -+ //noinspection unchecked -+ return (V)VALUE_HANDLE.getAcquire(this); -+ } ++ //noinspection unchecked ++ final TableEntry[] table = new TableEntry[tableSize]; ++ this.setTablePlain(table); + -+ protected final void setValueRelease(final V to) { -+ VALUE_HANDLE.setRelease(this, to); ++ if (tableSize == MAXIMUM_CAPACITY) { ++ this.threshold = -1; ++ } else { ++ this.threshold = getTargetCapacity(tableSize, loadFactor); + } + -+ /* next */ ++ this.loadFactor = loadFactor; ++ } + -+ protected final TableEntry getNextPlain() { -+ //noinspection unchecked -+ return (TableEntry)NEXT_HANDLE.get(this); -+ } ++ /** ++ * Constructs this map with a capacity of {@code 16} or the specified map's size, whichever is larger, and ++ * with a load factor of {@code 0.75f}. ++ * All of the specified map's entries are copied into this map. ++ * @param other The specified map. ++ */ ++ public SWMRHashTable(final Map other) { ++ this(DEFAULT_CAPACITY, DEFAULT_LOAD_FACTOR, other); ++ } + -+ protected final TableEntry getNextOpaque() { -+ //noinspection unchecked -+ return (TableEntry)NEXT_HANDLE.getOpaque(this); -+ } ++ /** ++ * Constructs this map with a minimum capacity of the specified capacity or the specified map's size, whichever is larger, and ++ * with a load factor of {@code 0.75f}. ++ * All of the specified map's entries are copied into this map. ++ * @param capacity specified capacity, > 0 ++ * @param other The specified map. ++ */ ++ public SWMRHashTable(final int capacity, final Map other) { ++ this(capacity, DEFAULT_LOAD_FACTOR, other); ++ } + -+ protected final void setNextPlain(final TableEntry next) { -+ NEXT_HANDLE.set(this, next); -+ } ++ /** ++ * Constructs this map with a min capacity of the specified capacity or the specified map's size, whichever is larger, and ++ * with the specified load factor. ++ * All of the specified map's entries are copied into this map. ++ * @param capacity specified capacity, > 0 ++ * @param loadFactor specified load factor, > 0 && finite ++ * @param other The specified map. ++ */ ++ public SWMRHashTable(final int capacity, final float loadFactor, final Map other) { ++ this(Math.max(Validate.notNull(other, "Null map").size(), capacity), loadFactor); ++ this.putAll(other); ++ } + -+ protected final void setNextRelease(final TableEntry next) { -+ NEXT_HANDLE.setRelease(this, next); -+ } ++ protected static TableEntry getAtIndexOpaque(final TableEntry[] table, final int index) { ++ // noinspection unchecked ++ return (TableEntry)TableEntry.TABLE_ENTRY_ARRAY_HANDLE.getOpaque(table, index); ++ } + -+ protected TableEntry(final int hash, final K key, final V value) { -+ this.hash = hash; -+ this.key = key; -+ this.value = value; -+ } ++ protected static void setAtIndexRelease(final TableEntry[] table, final int index, final TableEntry value) { ++ TableEntry.TABLE_ENTRY_ARRAY_HANDLE.setRelease(table, index, value); ++ } + -+ @Override -+ public K getKey() { -+ return this.key; -+ } ++ public final float getLoadFactor() { ++ return this.loadFactor; ++ } + -+ @Override -+ public V getValue() { -+ return this.getValueAcquire(); ++ protected static int getCapacityFor(final int capacity) { ++ if (capacity <= 0) { ++ throw new IllegalArgumentException("Invalid capacity: " + capacity); + } -+ -+ @Override -+ public V setValue(final V value) { -+ throw new UnsupportedOperationException(); ++ if (capacity >= MAXIMUM_CAPACITY) { ++ return MAXIMUM_CAPACITY; + } ++ return IntegerUtil.roundCeilLog2(capacity); ++ } + -+ protected static int hash(final Object key, final Object value) { -+ return key.hashCode() ^ (value == null ? 0 : value.hashCode()); -+ } ++ /** Callers must still use acquire when reading the value of the entry. */ ++ protected final TableEntry getEntryForOpaque(final K key) { ++ final int hash = SWMRHashTable.getHash(key); ++ final TableEntry[] table = this.getTableAcquire(); + -+ /** -+ * {@inheritDoc} -+ */ -+ @Override -+ public int hashCode() { -+ return hash(this.key, this.getValueAcquire()); ++ for (TableEntry curr = getAtIndexOpaque(table, hash & (table.length - 1)); curr != null; curr = curr.getNextOpaque()) { ++ if (hash == curr.hash && (key == curr.key || curr.key.equals(key))) { ++ return curr; ++ } + } + -+ /** -+ * {@inheritDoc} -+ */ -+ @Override -+ public boolean equals(final Object obj) { -+ if (this == obj) { -+ return true; -+ } ++ return null; ++ } + -+ if (!(obj instanceof Map.Entry other)) { -+ return false; -+ } -+ final Object otherKey = other.getKey(); -+ final Object otherValue = other.getValue(); ++ protected final TableEntry getEntryForPlain(final K key) { ++ final int hash = SWMRHashTable.getHash(key); ++ final TableEntry[] table = this.getTablePlain(); + -+ final K thisKey = this.getKey(); -+ final V thisVal = this.getValueAcquire(); -+ return (thisKey == otherKey || thisKey.equals(otherKey)) && -+ (thisVal == otherValue || thisVal.equals(otherValue)); ++ for (TableEntry curr = table[hash & (table.length - 1)]; curr != null; curr = curr.getNextPlain()) { ++ if (hash == curr.hash && (key == curr.key || curr.key.equals(key))) { ++ return curr; ++ } + } -+ } + ++ return null; ++ } + -+ protected static abstract class TableEntryIterator implements Iterator { ++ /* MT-Safe */ + -+ protected final TableEntry[] table; -+ protected final SWMRHashTable map; ++ /** must be deterministic given a key */ ++ private static int getHash(final Object key) { ++ int hash = key == null ? 0 : key.hashCode(); ++ return HashUtil.mix(hash); ++ } + -+ /* bin which our current element resides on */ -+ protected int tableIndex; ++ // rets -1 if capacity*loadFactor is too large ++ protected static int getTargetCapacity(final int capacity, final float loadFactor) { ++ final double ret = (double)capacity * (double)loadFactor; ++ if (Double.isInfinite(ret) || ret >= ((double)Integer.MAX_VALUE)) { ++ return -1; ++ } + -+ protected TableEntry currEntry; /* curr entry, null if no more to iterate or if curr was removed or if we've just init'd */ -+ protected TableEntry nextEntry; /* may not be on the same bin as currEntry */ ++ return (int)ret; ++ } + -+ protected TableEntryIterator(final TableEntry[] table, final SWMRHashTable map) { -+ this.table = table; -+ this.map = map; -+ int tableIndex = 0; -+ for (int len = table.length; tableIndex < len; ++tableIndex) { -+ final TableEntry entry = getAtIndexOpaque(table, tableIndex); -+ if (entry != null) { -+ this.nextEntry = entry; -+ this.tableIndex = tableIndex + 1; -+ return; -+ } -+ } -+ this.tableIndex = tableIndex; ++ /** ++ * {@inheritDoc} ++ */ ++ @Override ++ public boolean equals(final Object obj) { ++ if (this == obj) { ++ return true; + } -+ -+ @Override -+ public boolean hasNext() { -+ return this.nextEntry != null; ++ /* Make no attempt to deal with concurrent modifications */ ++ if (!(obj instanceof Map other)) { ++ return false; + } + -+ protected final TableEntry advanceEntry() { -+ final TableEntry[] table = this.table; -+ final int tableLength = table.length; -+ int tableIndex = this.tableIndex; -+ final TableEntry curr = this.nextEntry; -+ if (curr == null) { -+ return null; -+ } -+ -+ this.currEntry = curr; -+ -+ // set up nextEntry ++ if (this.size() != other.size()) { ++ return false; ++ } + -+ // find next in chain -+ TableEntry next = curr.getNextOpaque(); ++ final TableEntry[] table = this.getTableAcquire(); + -+ if (next != null) { -+ this.nextEntry = next; -+ return curr; -+ } ++ for (int i = 0, len = table.length; i < len; ++i) { ++ for (TableEntry curr = getAtIndexOpaque(table, i); curr != null; curr = curr.getNextOpaque()) { ++ final V value = curr.getValueAcquire(); + -+ // nothing in chain, so find next available bin -+ for (;tableIndex < tableLength; ++tableIndex) { -+ next = getAtIndexOpaque(table, tableIndex); -+ if (next != null) { -+ this.nextEntry = next; -+ this.tableIndex = tableIndex + 1; -+ return curr; ++ final Object otherValue = other.get(curr.key); ++ if (otherValue == null || (value != otherValue && value.equals(otherValue))) { ++ return false; + } + } -+ -+ this.nextEntry = null; -+ this.tableIndex = tableIndex; -+ return curr; + } + -+ @Override -+ public void remove() { -+ final TableEntry curr = this.currEntry; -+ if (curr == null) { -+ throw new IllegalStateException(); -+ } ++ return true; ++ } + -+ this.map.remove(curr.key, curr.hash); ++ /** ++ * {@inheritDoc} ++ */ ++ @Override ++ public int hashCode() { ++ /* Make no attempt to deal with concurrent modifications */ ++ int hash = 0; ++ final TableEntry[] table = this.getTableAcquire(); + -+ this.currEntry = null; ++ for (int i = 0, len = table.length; i < len; ++i) { ++ for (TableEntry curr = getAtIndexOpaque(table, i); curr != null; curr = curr.getNextOpaque()) { ++ hash += curr.hashCode(); ++ } + } ++ ++ return hash; + } + -+ protected static final class ValueIterator extends TableEntryIterator { ++ /** ++ * {@inheritDoc} ++ */ ++ @Override ++ public String toString() { ++ final StringBuilder builder = new StringBuilder(64); ++ builder.append("SWMRHashTable:{"); + -+ protected ValueIterator(final TableEntry[] table, final SWMRHashTable map) { -+ super(table, map); -+ } ++ this.forEach((final K key, final V value) -> { ++ builder.append("{key: \"").append(key).append("\", value: \"").append(value).append("\"}"); ++ }); + -+ @Override -+ public V next() { -+ final TableEntry entry = this.advanceEntry(); ++ return builder.append('}').toString(); ++ } + -+ if (entry == null) { -+ throw new NoSuchElementException(); -+ } ++ /** ++ * {@inheritDoc} ++ */ ++ @Override ++ public SWMRHashTable clone() { ++ return new SWMRHashTable<>(this.getTableAcquire().length, this.loadFactor, this); ++ } + -+ return entry.getValueAcquire(); -+ } ++ /** ++ * {@inheritDoc} ++ */ ++ @Override ++ public Iterator> iterator() { ++ return new EntryIterator<>(this.getTableAcquire(), this); + } + -+ protected static final class KeyIterator extends TableEntryIterator { ++ /** ++ * {@inheritDoc} ++ */ ++ @Override ++ public void forEach(final Consumer> action) { ++ Validate.notNull(action, "Null action"); + -+ protected KeyIterator(final TableEntry[] table, final SWMRHashTable map) { -+ super(table, map); ++ final TableEntry[] table = this.getTableAcquire(); ++ for (int i = 0, len = table.length; i < len; ++i) { ++ for (TableEntry curr = getAtIndexOpaque(table, i); curr != null; curr = curr.getNextOpaque()) { ++ action.accept(curr); ++ } + } ++ } + -+ @Override -+ public K next() { -+ final TableEntry curr = this.advanceEntry(); ++ /** ++ * {@inheritDoc} ++ */ ++ @Override ++ public void forEach(final BiConsumer action) { ++ Validate.notNull(action, "Null action"); + -+ if (curr == null) { -+ throw new NoSuchElementException(); -+ } ++ final TableEntry[] table = this.getTableAcquire(); ++ for (int i = 0, len = table.length; i < len; ++i) { ++ for (TableEntry curr = getAtIndexOpaque(table, i); curr != null; curr = curr.getNextOpaque()) { ++ final V value = curr.getValueAcquire(); + -+ return curr.key; ++ action.accept(curr.key, value); ++ } + } + } + -+ protected static final class EntryIterator extends TableEntryIterator> { ++ /** ++ * Provides the specified consumer with all keys contained within this map. ++ * @param action The specified consumer. ++ */ ++ public void forEachKey(final Consumer action) { ++ Validate.notNull(action, "Null action"); + -+ protected EntryIterator(final TableEntry[] table, final SWMRHashTable map) { -+ super(table, map); ++ final TableEntry[] table = this.getTableAcquire(); ++ for (int i = 0, len = table.length; i < len; ++i) { ++ for (TableEntry curr = getAtIndexOpaque(table, i); curr != null; curr = curr.getNextOpaque()) { ++ action.accept(curr.key); ++ } + } ++ } + -+ @Override -+ public Map.Entry next() { -+ final TableEntry curr = this.advanceEntry(); ++ /** ++ * Provides the specified consumer with all values contained within this map. Equivalent to {@code map.values().forEach(Consumer)}. ++ * @param action The specified consumer. ++ */ ++ public void forEachValue(final Consumer action) { ++ Validate.notNull(action, "Null action"); + -+ if (curr == null) { -+ throw new NoSuchElementException(); -+ } ++ final TableEntry[] table = this.getTableAcquire(); ++ for (int i = 0, len = table.length; i < len; ++i) { ++ for (TableEntry curr = getAtIndexOpaque(table, i); curr != null; curr = curr.getNextOpaque()) { ++ final V value = curr.getValueAcquire(); + -+ return curr; ++ action.accept(value); ++ } + } + } + -+ protected static abstract class ViewCollection implements Collection { ++ /** ++ * {@inheritDoc} ++ */ ++ @Override ++ public V get(final Object key) { ++ Validate.notNull(key, "Null key"); + -+ protected final SWMRHashTable map; ++ //noinspection unchecked ++ final TableEntry entry = this.getEntryForOpaque((K)key); ++ return entry == null ? null : entry.getValueAcquire(); ++ } + -+ protected ViewCollection(final SWMRHashTable map) { -+ this.map = map; -+ } ++ /** ++ * {@inheritDoc} ++ */ ++ @Override ++ public boolean containsKey(final Object key) { ++ Validate.notNull(key, "Null key"); + -+ @Override -+ public boolean add(final T element) { -+ throw new UnsupportedOperationException(); -+ } ++ // note: we need to use getValueAcquire, so that the reads from this map are ordered by acquire semantics ++ return this.get(key) != null; ++ } + -+ @Override -+ public boolean addAll(final Collection collections) { -+ throw new UnsupportedOperationException(); -+ } ++ /** ++ * Returns {@code true} if this map contains an entry with the specified key and value at some point during this call. ++ * @param key The specified key. ++ * @param value The specified value. ++ * @return {@code true} if this map contains an entry with the specified key and value. ++ */ ++ public boolean contains(final Object key, final Object value) { ++ Validate.notNull(key, "Null key"); + -+ @Override -+ public boolean removeAll(final Collection collection) { -+ Validate.notNull(collection, "Null collection"); ++ //noinspection unchecked ++ final TableEntry entry = this.getEntryForOpaque((K)key); + -+ boolean modified = false; -+ for (final Object element : collection) { -+ modified |= this.remove(element); -+ } -+ return modified; ++ if (entry == null) { ++ return false; + } + -+ @Override -+ public int size() { -+ return this.map.size(); -+ } ++ final V entryVal = entry.getValueAcquire(); ++ return entryVal == value || entryVal.equals(value); ++ } + -+ @Override -+ public boolean isEmpty() { -+ return this.size() == 0; -+ } ++ /** ++ * {@inheritDoc} ++ */ ++ @Override ++ public boolean containsValue(final Object value) { ++ Validate.notNull(value, "Null value"); + -+ @Override -+ public void clear() { -+ this.map.clear(); ++ final TableEntry[] table = this.getTableAcquire(); ++ for (int i = 0, len = table.length; i < len; ++i) { ++ for (TableEntry curr = getAtIndexOpaque(table, i); curr != null; curr = curr.getNextOpaque()) { ++ final V currVal = curr.getValueAcquire(); ++ if (currVal == value || currVal.equals(value)) { ++ return true; ++ } ++ } + } + -+ @Override -+ public boolean containsAll(final Collection collection) { -+ Validate.notNull(collection, "Null collection"); ++ return false; ++ } + -+ for (final Object element : collection) { -+ if (!this.contains(element)) { -+ return false; -+ } -+ } ++ /** ++ * {@inheritDoc} ++ */ ++ @Override ++ public V getOrDefault(final Object key, final V defaultValue) { ++ Validate.notNull(key, "Null key"); + -+ return true; -+ } ++ //noinspection unchecked ++ final TableEntry entry = this.getEntryForOpaque((K)key); ++ ++ return entry == null ? defaultValue : entry.getValueAcquire(); ++ } + -+ @Override -+ public Object[] toArray() { -+ final List list = new ArrayList<>(this.size()); ++ /** ++ * {@inheritDoc} ++ */ ++ @Override ++ public int size() { ++ return this.getSizeAcquire(); ++ } + -+ this.forEach(list::add); ++ /** ++ * {@inheritDoc} ++ */ ++ @Override ++ public boolean isEmpty() { ++ return this.getSizeAcquire() == 0; ++ } + -+ return list.toArray(); -+ } ++ protected KeySet keyset; ++ protected ValueCollection values; ++ protected EntrySet entrySet; + -+ @Override -+ public E[] toArray(final E[] array) { -+ final List list = new ArrayList<>(this.size()); ++ @Override ++ public Set keySet() { ++ return this.keyset == null ? this.keyset = new KeySet<>(this) : this.keyset; ++ } + -+ this.forEach(list::add); ++ @Override ++ public Collection values() { ++ return this.values == null ? this.values = new ValueCollection<>(this) : this.values; ++ } + -+ return list.toArray(array); -+ } ++ @Override ++ public Set> entrySet() { ++ return this.entrySet == null ? this.entrySet = new EntrySet<>(this) : this.entrySet; ++ } + -+ @Override -+ public E[] toArray(final IntFunction generator) { -+ final List list = new ArrayList<>(this.size()); ++ /* Non-MT-Safe */ + -+ this.forEach(list::add); ++ protected int threshold; + -+ return list.toArray(generator); ++ protected final void checkResize(final int minCapacity) { ++ if (minCapacity <= this.threshold || this.threshold < 0) { ++ return; + } + -+ @Override -+ public int hashCode() { -+ int hash = 0; -+ for (final T element : this) { -+ hash += element == null ? 0 : element.hashCode(); -+ } -+ return hash; ++ final TableEntry[] table = this.getTablePlain(); ++ int newCapacity = minCapacity >= MAXIMUM_CAPACITY ? MAXIMUM_CAPACITY : IntegerUtil.roundCeilLog2(minCapacity); ++ if (newCapacity < 0) { ++ newCapacity = MAXIMUM_CAPACITY; + } -+ -+ @Override -+ public Spliterator spliterator() { // TODO implement -+ return Spliterators.spliterator(this, Spliterator.NONNULL); ++ if (newCapacity <= table.length) { ++ if (newCapacity == MAXIMUM_CAPACITY) { ++ return; ++ } ++ newCapacity = table.length << 1; + } -+ } + -+ protected static abstract class ViewSet extends ViewCollection implements Set { ++ //noinspection unchecked ++ final TableEntry[] newTable = new TableEntry[newCapacity]; ++ final int indexMask = newCapacity - 1; + -+ protected ViewSet(final SWMRHashTable map) { -+ super(map); -+ } ++ for (int i = 0, len = table.length; i < len; ++i) { ++ for (TableEntry entry = table[i]; entry != null; entry = entry.getNextPlain()) { ++ final int hash = entry.hash; ++ final int index = hash & indexMask; + -+ @Override -+ public boolean equals(final Object obj) { -+ if (this == obj) { -+ return true; -+ } ++ /* we need to create a new entry since there could be reading threads */ ++ final TableEntry insert = new TableEntry<>(hash, entry.key, entry.getValuePlain()); + -+ if (!(obj instanceof Set)) { -+ return false; -+ } ++ final TableEntry prev = newTable[index]; + -+ final Set other = (Set)obj; -+ if (other.size() != this.size()) { -+ return false; ++ newTable[index] = insert; ++ insert.setNextPlain(prev); + } ++ } + -+ return this.containsAll(other); ++ if (newCapacity == MAXIMUM_CAPACITY) { ++ this.threshold = -1; /* No more resizing */ ++ } else { ++ this.threshold = getTargetCapacity(newCapacity, this.loadFactor); + } ++ this.setTableRelease(newTable); /* use release to publish entries in table */ + } + -+ protected static final class EntrySet extends ViewSet> implements Set> { ++ protected final int addToSize(final int num) { ++ final int newSize = this.getSizePlain() + num; + -+ protected EntrySet(final SWMRHashTable map) { -+ super(map); -+ } ++ this.setSizeOpaque(newSize); ++ this.checkResize(newSize); + -+ @Override -+ public boolean remove(final Object object) { -+ if (!(object instanceof Map.Entry entry)) { -+ return false; -+ } ++ return newSize; ++ } + -+ final Object key; -+ final Object value; ++ protected final int removeFromSize(final int num) { ++ final int newSize = this.getSizePlain() - num; + -+ try { -+ key = entry.getKey(); -+ value = entry.getValue(); -+ } catch (final IllegalStateException ex) { -+ return false; -+ } ++ this.setSizeOpaque(newSize); + -+ return this.map.remove(key, value); -+ } ++ return newSize; ++ } + -+ @Override -+ public boolean removeIf(final Predicate> filter) { -+ Validate.notNull(filter, "Null filter"); ++ /* Cannot be used to perform downsizing */ ++ protected final int removeFromSizePlain(final int num) { ++ final int newSize = this.getSizePlain() - num; + -+ return this.map.removeEntryIf(filter) != 0; -+ } ++ this.setSizePlain(newSize); + -+ @Override -+ public boolean retainAll(final Collection collection) { -+ Validate.notNull(collection, "Null collection"); ++ return newSize; ++ } + -+ return this.map.removeEntryIf((final Map.Entry entry) -> { -+ return !collection.contains(entry); -+ }) != 0; -+ } ++ protected final V put(final K key, final V value, final boolean onlyIfAbsent) { ++ final TableEntry[] table = this.getTablePlain(); ++ final int hash = SWMRHashTable.getHash(key); ++ final int index = hash & (table.length - 1); + -+ @Override -+ public Iterator> iterator() { -+ return new EntryIterator<>(this.map.getTableAcquire(), this.map); ++ final TableEntry head = table[index]; ++ if (head == null) { ++ final TableEntry insert = new TableEntry<>(hash, key, value); ++ setAtIndexRelease(table, index, insert); ++ this.addToSize(1); ++ return null; + } + -+ @Override -+ public void forEach(final Consumer> action) { -+ this.map.forEach(action); -+ } ++ for (TableEntry curr = head;;) { ++ if (curr.hash == hash && (key == curr.key || curr.key.equals(key))) { ++ if (onlyIfAbsent) { ++ return curr.getValuePlain(); ++ } + -+ @Override -+ public boolean contains(final Object object) { -+ if (!(object instanceof Map.Entry entry)) { -+ return false; ++ final V currVal = curr.getValuePlain(); ++ curr.setValueRelease(value); ++ return currVal; + } + -+ final Object key; -+ final Object value; -+ -+ try { -+ key = entry.getKey(); -+ value = entry.getValue(); -+ } catch (final IllegalStateException ex) { -+ return false; ++ final TableEntry next = curr.getNextPlain(); ++ if (next != null) { ++ curr = next; ++ continue; + } + -+ return this.map.contains(key, value); -+ } ++ final TableEntry insert = new TableEntry<>(hash, key, value); + -+ @Override -+ public String toString() { -+ return CollectionUtil.toString(this, "SWMRHashTableEntrySet"); ++ curr.setNextRelease(insert); ++ this.addToSize(1); ++ return null; + } + } + -+ protected static final class KeySet extends ViewSet { -+ -+ protected KeySet(final SWMRHashTable map) { -+ super(map); -+ } -+ -+ @Override -+ public Iterator iterator() { -+ return new KeyIterator<>(this.map.getTableAcquire(), this.map); -+ } ++ /** ++ * Removes a key-value pair from this map if the specified predicate returns true. The specified predicate is ++ * tested with every entry in this map. Returns the number of key-value pairs removed. ++ * @param predicate The predicate to test key-value pairs against. ++ * @return The total number of key-value pairs removed from this map. ++ */ ++ public int removeIf(final BiPredicate predicate) { ++ Validate.notNull(predicate, "Null predicate"); + -+ @Override -+ public void forEach(final Consumer action) { -+ Validate.notNull(action, "Null action"); ++ int removed = 0; + -+ this.map.forEachKey(action); -+ } ++ final TableEntry[] table = this.getTablePlain(); + -+ @Override -+ public boolean contains(final Object key) { -+ Validate.notNull(key, "Null key"); ++ bin_iteration_loop: ++ for (int i = 0, len = table.length; i < len; ++i) { ++ TableEntry curr = table[i]; ++ if (curr == null) { ++ continue; ++ } + -+ return this.map.containsKey(key); -+ } ++ /* Handle bin nodes first */ ++ while (predicate.test(curr.key, curr.getValuePlain())) { ++ ++removed; ++ this.removeFromSizePlain(1); /* required in case predicate throws an exception */ + -+ @Override -+ public boolean remove(final Object key) { -+ Validate.notNull(key, "Null key"); ++ setAtIndexRelease(table, i, curr = curr.getNextPlain()); + -+ return this.map.remove(key) != null; -+ } ++ if (curr == null) { ++ continue bin_iteration_loop; ++ } ++ } + -+ @Override -+ public boolean retainAll(final Collection collection) { -+ Validate.notNull(collection, "Null collection"); ++ TableEntry prev; + -+ return this.map.removeIf((final K key, final V value) -> { -+ return !collection.contains(key); -+ }) != 0; -+ } ++ /* curr at this point is the bin node */ + -+ @Override -+ public boolean removeIf(final Predicate filter) { -+ Validate.notNull(filter, "Null filter"); ++ for (prev = curr, curr = curr.getNextPlain(); curr != null;) { ++ /* If we want to remove, then we should hold prev, as it will be a valid entry to link on */ ++ if (predicate.test(curr.key, curr.getValuePlain())) { ++ ++removed; ++ this.removeFromSizePlain(1); /* required in case predicate throws an exception */ + -+ return this.map.removeIf((final K key, final V value) -> { -+ return filter.test(key); -+ }) != 0; ++ prev.setNextRelease(curr = curr.getNextPlain()); ++ } else { ++ prev = curr; ++ curr = curr.getNextPlain(); ++ } ++ } + } + -+ @Override -+ public String toString() { -+ return CollectionUtil.toString(this, "SWMRHashTableKeySet"); -+ } ++ return removed; + } + -+ protected static final class ValueCollection extends ViewSet implements Collection { -+ -+ protected ValueCollection(final SWMRHashTable map) { -+ super(map); -+ } -+ -+ @Override -+ public Iterator iterator() { -+ return new ValueIterator<>(this.map.getTableAcquire(), this.map); -+ } ++ /** ++ * Removes a key-value pair from this map if the specified predicate returns true. The specified predicate is ++ * tested with every entry in this map. Returns the number of key-value pairs removed. ++ * @param predicate The predicate to test key-value pairs against. ++ * @return The total number of key-value pairs removed from this map. ++ */ ++ public int removeEntryIf(final Predicate> predicate) { ++ Validate.notNull(predicate, "Null predicate"); + -+ @Override -+ public void forEach(final Consumer action) { -+ Validate.notNull(action, "Null action"); ++ int removed = 0; + -+ this.map.forEachValue(action); -+ } ++ final TableEntry[] table = this.getTablePlain(); + -+ @Override -+ public boolean contains(final Object object) { -+ Validate.notNull(object, "Null object"); ++ bin_iteration_loop: ++ for (int i = 0, len = table.length; i < len; ++i) { ++ TableEntry curr = table[i]; ++ if (curr == null) { ++ continue; ++ } + -+ return this.map.containsValue(object); -+ } ++ /* Handle bin nodes first */ ++ while (predicate.test(curr)) { ++ ++removed; ++ this.removeFromSizePlain(1); /* required in case predicate throws an exception */ + -+ @Override -+ public boolean remove(final Object object) { -+ Validate.notNull(object, "Null object"); ++ setAtIndexRelease(table, i, curr = curr.getNextPlain()); + -+ final Iterator itr = this.iterator(); -+ while (itr.hasNext()) { -+ final V val = itr.next(); -+ if (val == object || val.equals(object)) { -+ itr.remove(); -+ return true; ++ if (curr == null) { ++ continue bin_iteration_loop; + } + } + -+ return false; -+ } ++ TableEntry prev; + -+ @Override -+ public boolean removeIf(final Predicate filter) { -+ Validate.notNull(filter, "Null filter"); ++ /* curr at this point is the bin node */ + -+ return this.map.removeIf((final K key, final V value) -> { -+ return filter.test(value); -+ }) != 0; ++ for (prev = curr, curr = curr.getNextPlain(); curr != null;) { ++ /* If we want to remove, then we should hold prev, as it will be a valid entry to link on */ ++ if (predicate.test(curr)) { ++ ++removed; ++ this.removeFromSizePlain(1); /* required in case predicate throws an exception */ ++ ++ prev.setNextRelease(curr = curr.getNextPlain()); ++ } else { ++ prev = curr; ++ curr = curr.getNextPlain(); ++ } ++ } + } + -+ @Override -+ public boolean retainAll(final Collection collection) { -+ Validate.notNull(collection, "Null collection"); ++ return removed; ++ } + -+ return this.map.removeIf((final K key, final V value) -> { -+ return !collection.contains(value); -+ }) != 0; -+ } ++ /** ++ * {@inheritDoc} ++ */ ++ @Override ++ public V put(final K key, final V value) { ++ Validate.notNull(key, "Null key"); ++ Validate.notNull(value, "Null value"); + -+ @Override -+ public String toString() { -+ return CollectionUtil.toString(this, "SWMRHashTableValues"); -+ } ++ return this.put(key, value, false); + } -+} -diff --git a/src/main/java/ca/spottedleaf/concurrentutil/map/SWMRLong2ObjectHashTable.java b/src/main/java/ca/spottedleaf/concurrentutil/map/SWMRLong2ObjectHashTable.java -new file mode 100644 -index 0000000000000000000000000000000000000000..bb301a9f4e3ac919552eef68afc73569d50674db ---- /dev/null -+++ b/src/main/java/ca/spottedleaf/concurrentutil/map/SWMRLong2ObjectHashTable.java -@@ -0,0 +1,674 @@ -+package ca.spottedleaf.concurrentutil.map; + -+import ca.spottedleaf.concurrentutil.function.BiLongObjectConsumer; -+import ca.spottedleaf.concurrentutil.util.ConcurrentUtil; -+import ca.spottedleaf.concurrentutil.util.HashUtil; -+import ca.spottedleaf.concurrentutil.util.IntegerUtil; -+import ca.spottedleaf.concurrentutil.util.Validate; -+import java.lang.invoke.VarHandle; -+import java.util.Arrays; -+import java.util.function.Consumer; -+import java.util.function.LongConsumer; ++ /** ++ * {@inheritDoc} ++ */ ++ @Override ++ public V putIfAbsent(final K key, final V value) { ++ Validate.notNull(key, "Null key"); ++ Validate.notNull(value, "Null value"); + -+// trimmed down version of SWMRHashTable -+public class SWMRLong2ObjectHashTable { ++ return this.put(key, value, true); ++ } + -+ protected int size; ++ /** ++ * {@inheritDoc} ++ */ ++ @Override ++ public boolean remove(final Object key, final Object value) { ++ Validate.notNull(key, "Null key"); ++ Validate.notNull(value, "Null value"); + -+ protected TableEntry[] table; ++ final TableEntry[] table = this.getTablePlain(); ++ final int hash = SWMRHashTable.getHash(key); ++ final int index = hash & (table.length - 1); + -+ protected final float loadFactor; ++ final TableEntry head = table[index]; ++ if (head == null) { ++ return false; ++ } + -+ protected static final VarHandle SIZE_HANDLE = ConcurrentUtil.getVarHandle(SWMRLong2ObjectHashTable.class, "size", int.class); -+ protected static final VarHandle TABLE_HANDLE = ConcurrentUtil.getVarHandle(SWMRLong2ObjectHashTable.class, "table", TableEntry[].class); ++ if (head.hash == hash && (head.key == key || head.key.equals(key))) { ++ final V currVal = head.getValuePlain(); + -+ /* size */ ++ if (currVal != value && !currVal.equals(value)) { ++ return false; ++ } + -+ protected final int getSizePlain() { -+ return (int)SIZE_HANDLE.get(this); -+ } ++ setAtIndexRelease(table, index, head.getNextPlain()); ++ this.removeFromSize(1); + -+ protected final int getSizeOpaque() { -+ return (int)SIZE_HANDLE.getOpaque(this); -+ } ++ return true; ++ } + -+ protected final int getSizeAcquire() { -+ return (int)SIZE_HANDLE.getAcquire(this); -+ } ++ for (TableEntry curr = head.getNextPlain(), prev = head; curr != null; prev = curr, curr = curr.getNextPlain()) { ++ if (curr.hash == hash && (curr.key == key || curr.key.equals(key))) { ++ final V currVal = curr.getValuePlain(); + -+ protected final void setSizePlain(final int value) { -+ SIZE_HANDLE.set(this, value); -+ } ++ if (currVal != value && !currVal.equals(value)) { ++ return false; ++ } + -+ protected final void setSizeOpaque(final int value) { -+ SIZE_HANDLE.setOpaque(this, value); -+ } ++ prev.setNextRelease(curr.getNextPlain()); ++ this.removeFromSize(1); + -+ protected final void setSizeRelease(final int value) { -+ SIZE_HANDLE.setRelease(this, value); ++ return true; ++ } ++ } ++ ++ return false; + } + -+ /* table */ ++ protected final V remove(final Object key, final int hash) { ++ final TableEntry[] table = this.getTablePlain(); ++ final int index = (table.length - 1) & hash; + -+ protected final TableEntry[] getTablePlain() { -+ //noinspection unchecked -+ return (TableEntry[])TABLE_HANDLE.get(this); -+ } ++ final TableEntry head = table[index]; ++ if (head == null) { ++ return null; ++ } + -+ protected final TableEntry[] getTableAcquire() { -+ //noinspection unchecked -+ return (TableEntry[])TABLE_HANDLE.getAcquire(this); -+ } ++ if (hash == head.hash && (head.key == key || head.key.equals(key))) { ++ setAtIndexRelease(table, index, head.getNextPlain()); ++ this.removeFromSize(1); + -+ protected final void setTablePlain(final TableEntry[] table) { -+ TABLE_HANDLE.set(this, table); -+ } ++ return head.getValuePlain(); ++ } + -+ protected final void setTableRelease(final TableEntry[] table) { -+ TABLE_HANDLE.setRelease(this, table); -+ } ++ for (TableEntry curr = head.getNextPlain(), prev = head; curr != null; prev = curr, curr = curr.getNextPlain()) { ++ if (curr.hash == hash && (key == curr.key || curr.key.equals(key))) { ++ prev.setNextRelease(curr.getNextPlain()); ++ this.removeFromSize(1); + -+ protected static final int DEFAULT_CAPACITY = 16; -+ protected static final float DEFAULT_LOAD_FACTOR = 0.75f; -+ protected static final int MAXIMUM_CAPACITY = Integer.MIN_VALUE >>> 1; ++ return curr.getValuePlain(); ++ } ++ } + -+ /** -+ * Constructs this map with a capacity of {@code 16} and load factor of {@code 0.75f}. -+ */ -+ public SWMRLong2ObjectHashTable() { -+ this(DEFAULT_CAPACITY, DEFAULT_LOAD_FACTOR); ++ return null; + } + + /** -+ * Constructs this map with the specified capacity and load factor of {@code 0.75f}. -+ * @param capacity specified initial capacity, > 0 ++ * {@inheritDoc} + */ -+ public SWMRLong2ObjectHashTable(final int capacity) { -+ this(capacity, DEFAULT_LOAD_FACTOR); ++ @Override ++ public V remove(final Object key) { ++ Validate.notNull(key, "Null key"); ++ ++ return this.remove(key, SWMRHashTable.getHash(key)); + } + + /** -+ * Constructs this map with the specified capacity and load factor. -+ * @param capacity specified capacity, > 0 -+ * @param loadFactor specified load factor, > 0 && finite ++ * {@inheritDoc} + */ -+ public SWMRLong2ObjectHashTable(final int capacity, final float loadFactor) { -+ final int tableSize = getCapacityFor(capacity); ++ @Override ++ public boolean replace(final K key, final V oldValue, final V newValue) { ++ Validate.notNull(key, "Null key"); ++ Validate.notNull(oldValue, "Null oldValue"); ++ Validate.notNull(newValue, "Null newValue"); + -+ if (loadFactor <= 0.0 || !Float.isFinite(loadFactor)) { -+ throw new IllegalArgumentException("Invalid load factor: " + loadFactor); ++ final TableEntry entry = this.getEntryForPlain(key); ++ if (entry == null) { ++ return false; + } + -+ //noinspection unchecked -+ final TableEntry[] table = new TableEntry[tableSize]; -+ this.setTablePlain(table); -+ -+ if (tableSize == MAXIMUM_CAPACITY) { -+ this.threshold = -1; -+ } else { -+ this.threshold = getTargetCapacity(tableSize, loadFactor); ++ final V currValue = entry.getValuePlain(); ++ if (currValue == oldValue || currValue.equals(oldValue)) { ++ entry.setValueRelease(newValue); ++ return true; + } + -+ this.loadFactor = loadFactor; ++ return false; + } + + /** -+ * Constructs this map with a capacity of {@code 16} or the specified map's size, whichever is larger, and -+ * with a load factor of {@code 0.75f}. -+ * All of the specified map's entries are copied into this map. -+ * @param other The specified map. ++ * {@inheritDoc} + */ -+ public SWMRLong2ObjectHashTable(final SWMRLong2ObjectHashTable other) { -+ this(DEFAULT_CAPACITY, DEFAULT_LOAD_FACTOR, other); ++ @Override ++ public V replace(final K key, final V value) { ++ Validate.notNull(key, "Null key"); ++ Validate.notNull(value, "Null value"); ++ ++ final TableEntry entry = this.getEntryForPlain(key); ++ if (entry == null) { ++ return null; ++ } ++ ++ final V prev = entry.getValuePlain(); ++ entry.setValueRelease(value); ++ return prev; + } + + /** -+ * Constructs this map with a minimum capacity of the specified capacity or the specified map's size, whichever is larger, and -+ * with a load factor of {@code 0.75f}. -+ * All of the specified map's entries are copied into this map. -+ * @param capacity specified capacity, > 0 -+ * @param other The specified map. ++ * {@inheritDoc} + */ -+ public SWMRLong2ObjectHashTable(final int capacity, final SWMRLong2ObjectHashTable other) { -+ this(capacity, DEFAULT_LOAD_FACTOR, other); ++ @Override ++ public void replaceAll(final BiFunction function) { ++ Validate.notNull(function, "Null function"); ++ ++ final TableEntry[] table = this.getTablePlain(); ++ for (int i = 0, len = table.length; i < len; ++i) { ++ for (TableEntry curr = table[i]; curr != null; curr = curr.getNextPlain()) { ++ final V value = curr.getValuePlain(); ++ ++ final V newValue = function.apply(curr.key, value); ++ if (newValue == null) { ++ throw new NullPointerException(); ++ } ++ ++ curr.setValueRelease(newValue); ++ } ++ } + } + + /** -+ * Constructs this map with a min capacity of the specified capacity or the specified map's size, whichever is larger, and -+ * with the specified load factor. -+ * All of the specified map's entries are copied into this map. -+ * @param capacity specified capacity, > 0 -+ * @param loadFactor specified load factor, > 0 && finite -+ * @param other The specified map. ++ * {@inheritDoc} + */ -+ public SWMRLong2ObjectHashTable(final int capacity, final float loadFactor, final SWMRLong2ObjectHashTable other) { -+ this(Math.max(Validate.notNull(other, "Null map").size(), capacity), loadFactor); -+ this.putAll(other); -+ } ++ @Override ++ public void putAll(final Map map) { ++ Validate.notNull(map, "Null map"); + -+ protected static TableEntry getAtIndexOpaque(final TableEntry[] table, final int index) { -+ // noinspection unchecked -+ return (TableEntry)TableEntry.TABLE_ENTRY_ARRAY_HANDLE.getOpaque(table, index); ++ final int size = map.size(); ++ this.checkResize(Math.max(this.getSizePlain() + size/2, size)); /* preemptively resize */ ++ map.forEach(this::put); + } + -+ protected static void setAtIndexRelease(final TableEntry[] table, final int index, final TableEntry value) { -+ TableEntry.TABLE_ENTRY_ARRAY_HANDLE.setRelease(table, index, value); ++ /** ++ * {@inheritDoc} ++ *

    ++ * This call is non-atomic and the order that which entries are removed is undefined. The clear operation itself ++ * is release ordered, that is, after the clear operation is performed a release fence is performed. ++ *

    ++ */ ++ @Override ++ public void clear() { ++ Arrays.fill(this.getTablePlain(), null); ++ this.setSizeRelease(0); + } + -+ public final float getLoadFactor() { -+ return this.loadFactor; -+ } ++ /** ++ * {@inheritDoc} ++ */ ++ @Override ++ public V compute(final K key, final BiFunction remappingFunction) { ++ Validate.notNull(key, "Null key"); ++ Validate.notNull(remappingFunction, "Null remappingFunction"); + -+ protected static int getCapacityFor(final int capacity) { -+ if (capacity <= 0) { -+ throw new IllegalArgumentException("Invalid capacity: " + capacity); -+ } -+ if (capacity >= MAXIMUM_CAPACITY) { -+ return MAXIMUM_CAPACITY; -+ } -+ return IntegerUtil.roundCeilLog2(capacity); -+ } ++ final int hash = SWMRHashTable.getHash(key); ++ final TableEntry[] table = this.getTablePlain(); ++ final int index = hash & (table.length - 1); + -+ /** Callers must still use acquire when reading the value of the entry. */ -+ protected final TableEntry getEntryForOpaque(final long key) { -+ final int hash = SWMRLong2ObjectHashTable.getHash(key); -+ final TableEntry[] table = this.getTableAcquire(); ++ for (TableEntry curr = table[index], prev = null;;prev = curr, curr = curr.getNextPlain()) { ++ if (curr == null) { ++ final V newVal = remappingFunction.apply(key ,null); + -+ for (TableEntry curr = getAtIndexOpaque(table, hash & (table.length - 1)); curr != null; curr = curr.getNextOpaque()) { -+ if (key == curr.key) { -+ return curr; -+ } -+ } ++ if (newVal == null) { ++ return null; ++ } + -+ return null; -+ } ++ final TableEntry insert = new TableEntry<>(hash, key, newVal); ++ if (prev == null) { ++ setAtIndexRelease(table, index, insert); ++ } else { ++ prev.setNextRelease(insert); ++ } + -+ protected final TableEntry getEntryForPlain(final long key) { -+ final int hash = SWMRLong2ObjectHashTable.getHash(key); -+ final TableEntry[] table = this.getTablePlain(); ++ this.addToSize(1); + -+ for (TableEntry curr = table[hash & (table.length - 1)]; curr != null; curr = curr.getNextPlain()) { -+ if (key == curr.key) { -+ return curr; ++ return newVal; + } -+ } + -+ return null; -+ } ++ if (curr.hash == hash && (curr.key == key || curr.key.equals(key))) { ++ final V newVal = remappingFunction.apply(key, curr.getValuePlain()); + -+ /* MT-Safe */ ++ if (newVal != null) { ++ curr.setValueRelease(newVal); ++ return newVal; ++ } + -+ /** must be deterministic given a key */ -+ protected static int getHash(final long key) { -+ return (int)HashUtil.mix(key); -+ } ++ if (prev == null) { ++ setAtIndexRelease(table, index, curr.getNextPlain()); ++ } else { ++ prev.setNextRelease(curr.getNextPlain()); ++ } + -+ // rets -1 if capacity*loadFactor is too large -+ protected static int getTargetCapacity(final int capacity, final float loadFactor) { -+ final double ret = (double)capacity * (double)loadFactor; -+ if (Double.isInfinite(ret) || ret >= ((double)Integer.MAX_VALUE)) { -+ return -1; -+ } ++ this.removeFromSize(1); + -+ return (int)ret; ++ return null; ++ } ++ } + } + + /** + * {@inheritDoc} + */ + @Override -+ public boolean equals(final Object obj) { -+ if (this == obj) { -+ return true; -+ } -+ /* Make no attempt to deal with concurrent modifications */ -+ if (!(obj instanceof SWMRLong2ObjectHashTable other)) { -+ return false; -+ } -+ -+ if (this.size() != other.size()) { -+ return false; -+ } ++ public V computeIfPresent(final K key, final BiFunction remappingFunction) { ++ Validate.notNull(key, "Null key"); ++ Validate.notNull(remappingFunction, "Null remappingFunction"); + -+ final TableEntry[] table = this.getTableAcquire(); ++ final int hash = SWMRHashTable.getHash(key); ++ final TableEntry[] table = this.getTablePlain(); ++ final int index = hash & (table.length - 1); + -+ for (int i = 0, len = table.length; i < len; ++i) { -+ for (TableEntry curr = getAtIndexOpaque(table, i); curr != null; curr = curr.getNextOpaque()) { -+ final V value = curr.getValueAcquire(); ++ for (TableEntry curr = table[index], prev = null; curr != null; prev = curr, curr = curr.getNextPlain()) { ++ if (curr.hash != hash || (curr.key != key && !curr.key.equals(key))) { ++ continue; ++ } + -+ final Object otherValue = other.get(curr.key); -+ if (otherValue == null || (value != otherValue && value.equals(otherValue))) { -+ return false; -+ } ++ final V newVal = remappingFunction.apply(key, curr.getValuePlain()); ++ if (newVal != null) { ++ curr.setValueRelease(newVal); ++ return newVal; + } -+ } + -+ return true; -+ } ++ if (prev == null) { ++ setAtIndexRelease(table, index, curr.getNextPlain()); ++ } else { ++ prev.setNextRelease(curr.getNextPlain()); ++ } + -+ /** -+ * {@inheritDoc} -+ */ -+ @Override -+ public int hashCode() { -+ /* Make no attempt to deal with concurrent modifications */ -+ int hash = 0; -+ final TableEntry[] table = this.getTableAcquire(); ++ this.removeFromSize(1); + -+ for (int i = 0, len = table.length; i < len; ++i) { -+ for (TableEntry curr = getAtIndexOpaque(table, i); curr != null; curr = curr.getNextOpaque()) { -+ hash += curr.hashCode(); -+ } ++ return null; + } + -+ return hash; ++ return null; + } + + /** + * {@inheritDoc} + */ + @Override -+ public String toString() { -+ final StringBuilder builder = new StringBuilder(64); -+ builder.append("SingleWriterMultiReaderHashMap:{"); ++ public V computeIfAbsent(final K key, final Function mappingFunction) { ++ Validate.notNull(key, "Null key"); ++ Validate.notNull(mappingFunction, "Null mappingFunction"); + -+ this.forEach((final long key, final V value) -> { -+ builder.append("{key: \"").append(key).append("\", value: \"").append(value).append("\"}"); -+ }); ++ final int hash = SWMRHashTable.getHash(key); ++ final TableEntry[] table = this.getTablePlain(); ++ final int index = hash & (table.length - 1); + -+ return builder.append('}').toString(); -+ } ++ for (TableEntry curr = table[index], prev = null;;prev = curr, curr = curr.getNextPlain()) { ++ if (curr != null) { ++ if (curr.hash == hash && (curr.key == key || curr.key.equals(key))) { ++ return curr.getValuePlain(); ++ } ++ continue; ++ } + -+ /** -+ * {@inheritDoc} -+ */ -+ @Override -+ public SWMRLong2ObjectHashTable clone() { -+ return new SWMRLong2ObjectHashTable<>(this.getTableAcquire().length, this.loadFactor, this); -+ } ++ final V newVal = mappingFunction.apply(key); + -+ /** -+ * {@inheritDoc} -+ */ -+ public void forEach(final Consumer> action) { -+ Validate.notNull(action, "Null action"); ++ if (newVal == null) { ++ return null; ++ } + -+ final TableEntry[] table = this.getTableAcquire(); -+ for (int i = 0, len = table.length; i < len; ++i) { -+ for (TableEntry curr = getAtIndexOpaque(table, i); curr != null; curr = curr.getNextOpaque()) { -+ action.accept(curr); ++ final TableEntry insert = new TableEntry<>(hash, key, newVal); ++ if (prev == null) { ++ setAtIndexRelease(table, index, insert); ++ } else { ++ prev.setNextRelease(insert); + } ++ ++ this.addToSize(1); ++ ++ return newVal; + } + } + + /** + * {@inheritDoc} + */ -+ public void forEach(final BiLongObjectConsumer action) { -+ Validate.notNull(action, "Null action"); ++ @Override ++ public V merge(final K key, final V value, final BiFunction remappingFunction) { ++ Validate.notNull(key, "Null key"); ++ Validate.notNull(value, "Null value"); ++ Validate.notNull(remappingFunction, "Null remappingFunction"); + -+ final TableEntry[] table = this.getTableAcquire(); -+ for (int i = 0, len = table.length; i < len; ++i) { -+ for (TableEntry curr = getAtIndexOpaque(table, i); curr != null; curr = curr.getNextOpaque()) { -+ final V value = curr.getValueAcquire(); ++ final int hash = SWMRHashTable.getHash(key); ++ final TableEntry[] table = this.getTablePlain(); ++ final int index = hash & (table.length - 1); + -+ action.accept(curr.key, value); -+ } -+ } -+ } ++ for (TableEntry curr = table[index], prev = null;;prev = curr, curr = curr.getNextPlain()) { ++ if (curr == null) { ++ final TableEntry insert = new TableEntry<>(hash, key, value); ++ if (prev == null) { ++ setAtIndexRelease(table, index, insert); ++ } else { ++ prev.setNextRelease(insert); ++ } + -+ /** -+ * Provides the specified consumer with all keys contained within this map. -+ * @param action The specified consumer. -+ */ -+ public void forEachKey(final LongConsumer action) { -+ Validate.notNull(action, "Null action"); ++ this.addToSize(1); + -+ final TableEntry[] table = this.getTableAcquire(); -+ for (int i = 0, len = table.length; i < len; ++i) { -+ for (TableEntry curr = getAtIndexOpaque(table, i); curr != null; curr = curr.getNextOpaque()) { -+ action.accept(curr.key); ++ return value; + } -+ } -+ } + -+ /** -+ * Provides the specified consumer with all values contained within this map. Equivalent to {@code map.values().forEach(Consumer)}. -+ * @param action The specified consumer. -+ */ -+ public void forEachValue(final Consumer action) { -+ Validate.notNull(action, "Null action"); ++ if (curr.hash == hash && (curr.key == key || curr.key.equals(key))) { ++ final V newVal = remappingFunction.apply(curr.getValuePlain(), value); + -+ final TableEntry[] table = this.getTableAcquire(); -+ for (int i = 0, len = table.length; i < len; ++i) { -+ for (TableEntry curr = getAtIndexOpaque(table, i); curr != null; curr = curr.getNextOpaque()) { -+ final V value = curr.getValueAcquire(); ++ if (newVal != null) { ++ curr.setValueRelease(newVal); ++ return newVal; ++ } + -+ action.accept(value); ++ if (prev == null) { ++ setAtIndexRelease(table, index, curr.getNextPlain()); ++ } else { ++ prev.setNextRelease(curr.getNextPlain()); ++ } ++ ++ this.removeFromSize(1); ++ ++ return null; + } + } + } + -+ /** -+ * {@inheritDoc} -+ */ -+ public V get(final long key) { -+ final TableEntry entry = this.getEntryForOpaque(key); -+ return entry == null ? null : entry.getValueAcquire(); -+ } ++ protected static final class TableEntry implements Map.Entry { + -+ /** -+ * {@inheritDoc} -+ */ -+ public boolean containsKey(final long key) { -+ // note: we need to use getValueAcquire, so that the reads from this map are ordered by acquire semantics -+ return this.get(key) != null; -+ } ++ protected static final VarHandle TABLE_ENTRY_ARRAY_HANDLE = ConcurrentUtil.getArrayHandle(TableEntry[].class); + -+ /** -+ * {@inheritDoc} -+ */ -+ public V getOrDefault(final long key, final V defaultValue) { -+ final TableEntry entry = this.getEntryForOpaque(key); ++ protected final int hash; ++ protected final K key; ++ protected V value; + -+ return entry == null ? defaultValue : entry.getValueAcquire(); -+ } ++ protected TableEntry next; + -+ /** -+ * {@inheritDoc} -+ */ -+ public int size() { -+ return this.getSizeAcquire(); -+ } ++ protected static final VarHandle VALUE_HANDLE = ConcurrentUtil.getVarHandle(TableEntry.class, "value", Object.class); ++ protected static final VarHandle NEXT_HANDLE = ConcurrentUtil.getVarHandle(TableEntry.class, "next", TableEntry.class); + -+ /** -+ * {@inheritDoc} -+ */ -+ public boolean isEmpty() { -+ return this.getSizeAcquire() == 0; -+ } ++ /* value */ + -+ /* Non-MT-Safe */ ++ protected final V getValuePlain() { ++ //noinspection unchecked ++ return (V)VALUE_HANDLE.get(this); ++ } + -+ protected int threshold; ++ protected final V getValueAcquire() { ++ //noinspection unchecked ++ return (V)VALUE_HANDLE.getAcquire(this); ++ } + -+ protected final void checkResize(final int minCapacity) { -+ if (minCapacity <= this.threshold || this.threshold < 0) { -+ return; ++ protected final void setValueRelease(final V to) { ++ VALUE_HANDLE.setRelease(this, to); + } + -+ final TableEntry[] table = this.getTablePlain(); -+ int newCapacity = minCapacity >= MAXIMUM_CAPACITY ? MAXIMUM_CAPACITY : IntegerUtil.roundCeilLog2(minCapacity); -+ if (newCapacity < 0) { -+ newCapacity = MAXIMUM_CAPACITY; ++ /* next */ ++ ++ protected final TableEntry getNextPlain() { ++ //noinspection unchecked ++ return (TableEntry)NEXT_HANDLE.get(this); + } -+ if (newCapacity <= table.length) { -+ if (newCapacity == MAXIMUM_CAPACITY) { -+ return; -+ } -+ newCapacity = table.length << 1; ++ ++ protected final TableEntry getNextOpaque() { ++ //noinspection unchecked ++ return (TableEntry)NEXT_HANDLE.getOpaque(this); + } + -+ //noinspection unchecked -+ final TableEntry[] newTable = new TableEntry[newCapacity]; -+ final int indexMask = newCapacity - 1; ++ protected final void setNextPlain(final TableEntry next) { ++ NEXT_HANDLE.set(this, next); ++ } + -+ for (int i = 0, len = table.length; i < len; ++i) { -+ for (TableEntry entry = table[i]; entry != null; entry = entry.getNextPlain()) { -+ final long key = entry.key; -+ final int hash = SWMRLong2ObjectHashTable.getHash(key); -+ final int index = hash & indexMask; ++ protected final void setNextRelease(final TableEntry next) { ++ NEXT_HANDLE.setRelease(this, next); ++ } + -+ /* we need to create a new entry since there could be reading threads */ -+ final TableEntry insert = new TableEntry<>(key, entry.getValuePlain()); ++ protected TableEntry(final int hash, final K key, final V value) { ++ this.hash = hash; ++ this.key = key; ++ this.value = value; ++ } + -+ final TableEntry prev = newTable[index]; ++ @Override ++ public K getKey() { ++ return this.key; ++ } + -+ newTable[index] = insert; -+ insert.setNextPlain(prev); -+ } ++ @Override ++ public V getValue() { ++ return this.getValueAcquire(); ++ } ++ ++ @Override ++ public V setValue(final V value) { ++ throw new UnsupportedOperationException(); ++ } ++ ++ protected static int hash(final Object key, final Object value) { ++ return key.hashCode() ^ (value == null ? 0 : value.hashCode()); ++ } ++ ++ /** ++ * {@inheritDoc} ++ */ ++ @Override ++ public int hashCode() { ++ return hash(this.key, this.getValueAcquire()); + } + -+ if (newCapacity == MAXIMUM_CAPACITY) { -+ this.threshold = -1; /* No more resizing */ -+ } else { -+ this.threshold = getTargetCapacity(newCapacity, this.loadFactor); ++ /** ++ * {@inheritDoc} ++ */ ++ @Override ++ public boolean equals(final Object obj) { ++ if (this == obj) { ++ return true; ++ } ++ ++ if (!(obj instanceof Map.Entry other)) { ++ return false; ++ } ++ final Object otherKey = other.getKey(); ++ final Object otherValue = other.getValue(); ++ ++ final K thisKey = this.getKey(); ++ final V thisVal = this.getValueAcquire(); ++ return (thisKey == otherKey || thisKey.equals(otherKey)) && ++ (thisVal == otherValue || thisVal.equals(otherValue)); + } -+ this.setTableRelease(newTable); /* use release to publish entries in table */ + } + -+ protected final int addToSize(final int num) { -+ final int newSize = this.getSizePlain() + num; -+ -+ this.setSizeOpaque(newSize); -+ this.checkResize(newSize); -+ -+ return newSize; -+ } + -+ protected final int removeFromSize(final int num) { -+ final int newSize = this.getSizePlain() - num; ++ protected static abstract class TableEntryIterator implements Iterator { + -+ this.setSizeOpaque(newSize); ++ protected final TableEntry[] table; ++ protected final SWMRHashTable map; + -+ return newSize; -+ } ++ /* bin which our current element resides on */ ++ protected int tableIndex; + -+ protected final V put(final long key, final V value, final boolean onlyIfAbsent) { -+ final TableEntry[] table = this.getTablePlain(); -+ final int hash = SWMRLong2ObjectHashTable.getHash(key); -+ final int index = hash & (table.length - 1); ++ protected TableEntry currEntry; /* curr entry, null if no more to iterate or if curr was removed or if we've just init'd */ ++ protected TableEntry nextEntry; /* may not be on the same bin as currEntry */ + -+ final TableEntry head = table[index]; -+ if (head == null) { -+ final TableEntry insert = new TableEntry<>(key, value); -+ setAtIndexRelease(table, index, insert); -+ this.addToSize(1); -+ return null; ++ protected TableEntryIterator(final TableEntry[] table, final SWMRHashTable map) { ++ this.table = table; ++ this.map = map; ++ int tableIndex = 0; ++ for (int len = table.length; tableIndex < len; ++tableIndex) { ++ final TableEntry entry = getAtIndexOpaque(table, tableIndex); ++ if (entry != null) { ++ this.nextEntry = entry; ++ this.tableIndex = tableIndex + 1; ++ return; ++ } ++ } ++ this.tableIndex = tableIndex; + } + -+ for (TableEntry curr = head;;) { -+ if (key == curr.key) { -+ if (onlyIfAbsent) { -+ return curr.getValuePlain(); -+ } ++ @Override ++ public boolean hasNext() { ++ return this.nextEntry != null; ++ } + -+ final V currVal = curr.getValuePlain(); -+ curr.setValueRelease(value); -+ return currVal; ++ protected final TableEntry advanceEntry() { ++ final TableEntry[] table = this.table; ++ final int tableLength = table.length; ++ int tableIndex = this.tableIndex; ++ final TableEntry curr = this.nextEntry; ++ if (curr == null) { ++ return null; + } + -+ final TableEntry next = curr.getNextPlain(); ++ this.currEntry = curr; ++ ++ // set up nextEntry ++ ++ // find next in chain ++ TableEntry next = curr.getNextOpaque(); ++ + if (next != null) { -+ curr = next; -+ continue; ++ this.nextEntry = next; ++ return curr; + } + -+ final TableEntry insert = new TableEntry<>(key, value); ++ // nothing in chain, so find next available bin ++ for (;tableIndex < tableLength; ++tableIndex) { ++ next = getAtIndexOpaque(table, tableIndex); ++ if (next != null) { ++ this.nextEntry = next; ++ this.tableIndex = tableIndex + 1; ++ return curr; ++ } ++ } + -+ curr.setNextRelease(insert); -+ this.addToSize(1); -+ return null; ++ this.nextEntry = null; ++ this.tableIndex = tableIndex; ++ return curr; + } -+ } -+ -+ /** -+ * {@inheritDoc} -+ */ -+ public V put(final long key, final V value) { -+ Validate.notNull(value, "Null value"); + -+ return this.put(key, value, false); -+ } ++ @Override ++ public void remove() { ++ final TableEntry curr = this.currEntry; ++ if (curr == null) { ++ throw new IllegalStateException(); ++ } + -+ /** -+ * {@inheritDoc} -+ */ -+ public V putIfAbsent(final long key, final V value) { -+ Validate.notNull(value, "Null value"); ++ this.map.remove(curr.key, curr.hash); + -+ return this.put(key, value, true); ++ this.currEntry = null; ++ } + } + -+ protected final V remove(final long key, final int hash) { -+ final TableEntry[] table = this.getTablePlain(); -+ final int index = (table.length - 1) & hash; ++ protected static final class ValueIterator extends TableEntryIterator { + -+ final TableEntry head = table[index]; -+ if (head == null) { -+ return null; ++ protected ValueIterator(final TableEntry[] table, final SWMRHashTable map) { ++ super(table, map); + } + -+ if (head.key == key) { -+ setAtIndexRelease(table, index, head.getNextPlain()); -+ this.removeFromSize(1); ++ @Override ++ public V next() { ++ final TableEntry entry = this.advanceEntry(); + -+ return head.getValuePlain(); ++ if (entry == null) { ++ throw new NoSuchElementException(); ++ } ++ ++ return entry.getValueAcquire(); + } ++ } + -+ for (TableEntry curr = head.getNextPlain(), prev = head; curr != null; prev = curr, curr = curr.getNextPlain()) { -+ if (key == curr.key) { -+ prev.setNextRelease(curr.getNextPlain()); -+ this.removeFromSize(1); ++ protected static final class KeyIterator extends TableEntryIterator { + -+ return curr.getValuePlain(); -+ } ++ protected KeyIterator(final TableEntry[] table, final SWMRHashTable map) { ++ super(table, map); + } + -+ return null; -+ } ++ @Override ++ public K next() { ++ final TableEntry curr = this.advanceEntry(); + -+ protected final V remove(final long key, final int hash, final V expect) { -+ final TableEntry[] table = this.getTablePlain(); -+ final int index = (table.length - 1) & hash; ++ if (curr == null) { ++ throw new NoSuchElementException(); ++ } + -+ final TableEntry head = table[index]; -+ if (head == null) { -+ return null; ++ return curr.key; + } ++ } + -+ if (head.key == key) { -+ final V val = head.value; -+ if (val == expect || val.equals(expect)) { -+ setAtIndexRelease(table, index, head.getNextPlain()); -+ this.removeFromSize(1); ++ protected static final class EntryIterator extends TableEntryIterator> { + -+ return head.getValuePlain(); -+ } else { -+ return null; -+ } ++ protected EntryIterator(final TableEntry[] table, final SWMRHashTable map) { ++ super(table, map); + } + -+ for (TableEntry curr = head.getNextPlain(), prev = head; curr != null; prev = curr, curr = curr.getNextPlain()) { -+ if (key == curr.key) { -+ final V val = curr.value; -+ if (val == expect || val.equals(expect)) { -+ prev.setNextRelease(curr.getNextPlain()); -+ this.removeFromSize(1); ++ @Override ++ public Map.Entry next() { ++ final TableEntry curr = this.advanceEntry(); + -+ return curr.getValuePlain(); -+ } else { -+ return null; -+ } ++ if (curr == null) { ++ throw new NoSuchElementException(); + } -+ } + -+ return null; ++ return curr; ++ } + } + -+ /** -+ * {@inheritDoc} -+ */ -+ public V remove(final long key) { -+ return this.remove(key, SWMRLong2ObjectHashTable.getHash(key)); -+ } ++ protected static abstract class ViewCollection implements Collection { + -+ public boolean remove(final long key, final V expect) { -+ return this.remove(key, SWMRLong2ObjectHashTable.getHash(key), expect) != null; -+ } ++ protected final SWMRHashTable map; + -+ /** -+ * {@inheritDoc} -+ */ -+ public void putAll(final SWMRLong2ObjectHashTable map) { -+ Validate.notNull(map, "Null map"); ++ protected ViewCollection(final SWMRHashTable map) { ++ this.map = map; ++ } + -+ final int size = map.size(); -+ this.checkResize(Math.max(this.getSizePlain() + size/2, size)); /* preemptively resize */ -+ map.forEach(this::put); -+ } ++ @Override ++ public boolean add(final T element) { ++ throw new UnsupportedOperationException(); ++ } + -+ /** -+ * {@inheritDoc} -+ *

    -+ * This call is non-atomic and the order that which entries are removed is undefined. The clear operation itself -+ * is release ordered, that is, after the clear operation is performed a release fence is performed. -+ *

    -+ */ -+ public void clear() { -+ Arrays.fill(this.getTablePlain(), null); -+ this.setSizeRelease(0); -+ } ++ @Override ++ public boolean addAll(final Collection collections) { ++ throw new UnsupportedOperationException(); ++ } + -+ public static final class TableEntry { ++ @Override ++ public boolean removeAll(final Collection collection) { ++ Validate.notNull(collection, "Null collection"); ++ ++ boolean modified = false; ++ for (final Object element : collection) { ++ modified |= this.remove(element); ++ } ++ return modified; ++ } + -+ protected static final VarHandle TABLE_ENTRY_ARRAY_HANDLE = ConcurrentUtil.getArrayHandle(TableEntry[].class); ++ @Override ++ public int size() { ++ return this.map.size(); ++ } + -+ protected final long key; -+ protected V value; ++ @Override ++ public boolean isEmpty() { ++ return this.size() == 0; ++ } + -+ protected TableEntry next; ++ @Override ++ public void clear() { ++ this.map.clear(); ++ } + -+ protected static final VarHandle VALUE_HANDLE = ConcurrentUtil.getVarHandle(TableEntry.class, "value", Object.class); -+ protected static final VarHandle NEXT_HANDLE = ConcurrentUtil.getVarHandle(TableEntry.class, "next", TableEntry.class); ++ @Override ++ public boolean containsAll(final Collection collection) { ++ Validate.notNull(collection, "Null collection"); + -+ /* value */ ++ for (final Object element : collection) { ++ if (!this.contains(element)) { ++ return false; ++ } ++ } + -+ protected final V getValuePlain() { -+ //noinspection unchecked -+ return (V)VALUE_HANDLE.get(this); ++ return true; + } + -+ protected final V getValueAcquire() { -+ //noinspection unchecked -+ return (V)VALUE_HANDLE.getAcquire(this); -+ } ++ @Override ++ public Object[] toArray() { ++ final List list = new ArrayList<>(this.size()); + -+ protected final void setValueRelease(final V to) { -+ VALUE_HANDLE.setRelease(this, to); ++ this.forEach(list::add); ++ ++ return list.toArray(); + } + -+ /* next */ ++ @Override ++ public E[] toArray(final E[] array) { ++ final List list = new ArrayList<>(this.size()); + -+ protected final TableEntry getNextPlain() { -+ //noinspection unchecked -+ return (TableEntry)NEXT_HANDLE.get(this); -+ } ++ this.forEach(list::add); + -+ protected final TableEntry getNextOpaque() { -+ //noinspection unchecked -+ return (TableEntry)NEXT_HANDLE.getOpaque(this); ++ return list.toArray(array); + } + -+ protected final void setNextPlain(final TableEntry next) { -+ NEXT_HANDLE.set(this, next); -+ } ++ @Override ++ public E[] toArray(final IntFunction generator) { ++ final List list = new ArrayList<>(this.size()); + -+ protected final void setNextRelease(final TableEntry next) { -+ NEXT_HANDLE.setRelease(this, next); -+ } ++ this.forEach(list::add); + -+ protected TableEntry(final long key, final V value) { -+ this.key = key; -+ this.value = value; ++ return list.toArray(generator); + } + -+ public long getKey() { -+ return this.key; ++ @Override ++ public int hashCode() { ++ int hash = 0; ++ for (final T element : this) { ++ hash += element == null ? 0 : element.hashCode(); ++ } ++ return hash; + } + -+ public V getValue() { -+ return this.getValueAcquire(); ++ @Override ++ public Spliterator spliterator() { // TODO implement ++ return Spliterators.spliterator(this, Spliterator.NONNULL); + } + } -+} -diff --git a/src/main/java/ca/spottedleaf/concurrentutil/scheduler/SchedulerThreadPool.java b/src/main/java/ca/spottedleaf/concurrentutil/scheduler/SchedulerThreadPool.java -new file mode 100644 -index 0000000000000000000000000000000000000000..8197ccb1c4e5878dbd8007b5fb514640765ec8e4 ---- /dev/null -+++ b/src/main/java/ca/spottedleaf/concurrentutil/scheduler/SchedulerThreadPool.java -@@ -0,0 +1,558 @@ -+package ca.spottedleaf.concurrentutil.scheduler; + -+import ca.spottedleaf.concurrentutil.set.LinkedSortedSet; -+import ca.spottedleaf.concurrentutil.util.ConcurrentUtil; -+import ca.spottedleaf.concurrentutil.util.TimeUtil; -+import java.lang.invoke.VarHandle; -+import java.util.BitSet; -+import java.util.Comparator; -+import java.util.PriorityQueue; -+import java.util.concurrent.ThreadFactory; -+import java.util.concurrent.atomic.AtomicInteger; -+import java.util.concurrent.atomic.AtomicLong; -+import java.util.concurrent.locks.LockSupport; -+import java.util.function.BooleanSupplier; ++ protected static abstract class ViewSet extends ViewCollection implements Set { + -+public class SchedulerThreadPool { ++ protected ViewSet(final SWMRHashTable map) { ++ super(map); ++ } + -+ public static final long DEADLINE_NOT_SET = Long.MIN_VALUE; ++ @Override ++ public boolean equals(final Object obj) { ++ if (this == obj) { ++ return true; ++ } + -+ private static final Comparator TICK_COMPARATOR_BY_TIME = (final SchedulableTick t1, final SchedulableTick t2) -> { -+ final int timeCompare = TimeUtil.compareTimes(t1.scheduledStart, t2.scheduledStart); -+ if (timeCompare != 0) { -+ return timeCompare; ++ if (!(obj instanceof Set)) { ++ return false; ++ } ++ ++ final Set other = (Set)obj; ++ if (other.size() != this.size()) { ++ return false; ++ } ++ ++ return this.containsAll(other); + } ++ } + -+ return Long.compare(t1.id, t2.id); -+ }; ++ protected static final class EntrySet extends ViewSet> implements Set> { + -+ private final TickThreadRunner[] runners; -+ private final Thread[] threads; -+ private final LinkedSortedSet awaiting = new LinkedSortedSet<>(TICK_COMPARATOR_BY_TIME); -+ private final PriorityQueue queued = new PriorityQueue<>(TICK_COMPARATOR_BY_TIME); -+ private final BitSet idleThreads; ++ protected EntrySet(final SWMRHashTable map) { ++ super(map); ++ } + -+ private final Object scheduleLock = new Object(); ++ @Override ++ public boolean remove(final Object object) { ++ if (!(object instanceof Map.Entry entry)) { ++ return false; ++ } + -+ private volatile boolean halted; ++ final Object key; ++ final Object value; + -+ /** -+ * Creates, but does not start, a scheduler thread pool with the specified number of threads -+ * created using the specified thread factory. -+ * @param threads Specified number of threads -+ * @param threadFactory Specified thread factory -+ * @see #start() -+ */ -+ public SchedulerThreadPool(final int threads, final ThreadFactory threadFactory) { -+ final BitSet idleThreads = new BitSet(threads); -+ for (int i = 0; i < threads; ++i) { -+ idleThreads.set(i); ++ try { ++ key = entry.getKey(); ++ value = entry.getValue(); ++ } catch (final IllegalStateException ex) { ++ return false; ++ } ++ ++ return this.map.remove(key, value); + } -+ this.idleThreads = idleThreads; + -+ final TickThreadRunner[] runners = new TickThreadRunner[threads]; -+ final Thread[] t = new Thread[threads]; -+ for (int i = 0; i < threads; ++i) { -+ runners[i] = new TickThreadRunner(i, this); -+ t[i] = threadFactory.newThread(runners[i]); ++ @Override ++ public boolean removeIf(final Predicate> filter) { ++ Validate.notNull(filter, "Null filter"); ++ ++ return this.map.removeEntryIf(filter) != 0; + } + -+ this.threads = t; -+ this.runners = runners; -+ } ++ @Override ++ public boolean retainAll(final Collection collection) { ++ Validate.notNull(collection, "Null collection"); + -+ /** -+ * Starts the threads in this pool. -+ */ -+ public void start() { -+ for (final Thread thread : this.threads) { -+ thread.start(); ++ return this.map.removeEntryIf((final Map.Entry entry) -> { ++ return !collection.contains(entry); ++ }) != 0; + } -+ } + -+ /** -+ * Attempts to prevent further execution of tasks, optionally waiting for the scheduler threads to die. -+ * -+ * @param sync Whether to wait for the scheduler threads to die. -+ * @param maxWaitNS The maximum time, in ns, to wait for the scheduler threads to die. -+ * @return {@code true} if sync was false, or if sync was true and the scheduler threads died before the timeout. -+ * Otherwise, returns {@code false} if the time elapsed exceeded the maximum wait time. -+ */ -+ public boolean halt(final boolean sync, final long maxWaitNS) { -+ this.halted = true; -+ for (final Thread thread : this.threads) { -+ // force response to halt -+ LockSupport.unpark(thread); ++ @Override ++ public Iterator> iterator() { ++ return new EntryIterator<>(this.map.getTableAcquire(), this.map); + } -+ final long time = System.nanoTime(); -+ if (sync) { -+ // start at 10 * 0.5ms -> 5ms -+ for (long failures = 9L;; failures = ConcurrentUtil.linearLongBackoff(failures, 500_000L, 50_000_000L)) { -+ boolean allDead = true; -+ for (final Thread thread : this.threads) { -+ if (thread.isAlive()) { -+ allDead = false; -+ break; -+ } -+ } -+ if (allDead) { -+ return true; -+ } -+ if ((System.nanoTime() - time) >= maxWaitNS) { -+ return false; -+ } -+ } ++ ++ @Override ++ public void forEach(final Consumer> action) { ++ this.map.forEach(action); + } + -+ return true; -+ } ++ @Override ++ public boolean contains(final Object object) { ++ if (!(object instanceof Map.Entry entry)) { ++ return false; ++ } + -+ /** -+ * Returns an array of the underlying scheduling threads. -+ */ -+ public Thread[] getThreads() { -+ return this.threads.clone(); ++ final Object key; ++ final Object value; ++ ++ try { ++ key = entry.getKey(); ++ value = entry.getValue(); ++ } catch (final IllegalStateException ex) { ++ return false; ++ } ++ ++ return this.map.contains(key, value); ++ } ++ ++ @Override ++ public String toString() { ++ return CollectionUtil.toString(this, "SWMRHashTableEntrySet"); ++ } + } + -+ private void insertFresh(final SchedulableTick task) { -+ final TickThreadRunner[] runners = this.runners; ++ protected static final class KeySet extends ViewSet { + -+ final int firstIdleThread = this.idleThreads.nextSetBit(0); ++ protected KeySet(final SWMRHashTable map) { ++ super(map); ++ } + -+ if (firstIdleThread != -1) { -+ // push to idle thread -+ this.idleThreads.clear(firstIdleThread); -+ final TickThreadRunner runner = runners[firstIdleThread]; -+ task.awaitingLink = this.awaiting.addLast(task); -+ runner.acceptTask(task); -+ return; ++ @Override ++ public Iterator iterator() { ++ return new KeyIterator<>(this.map.getTableAcquire(), this.map); + } + -+ // try to replace the last awaiting task -+ final SchedulableTick last = this.awaiting.last(); ++ @Override ++ public void forEach(final Consumer action) { ++ Validate.notNull(action, "Null action"); + -+ if (last != null && TICK_COMPARATOR_BY_TIME.compare(task, last) < 0) { -+ // need to replace the last task -+ this.awaiting.pollLast(); -+ last.awaitingLink = null; -+ task.awaitingLink = this.awaiting.addLast(task); -+ // need to add task to queue to be picked up later -+ this.queued.add(last); ++ this.map.forEachKey(action); ++ } + -+ final TickThreadRunner runner = last.ownedBy; -+ runner.replaceTask(task); ++ @Override ++ public boolean contains(final Object key) { ++ Validate.notNull(key, "Null key"); + -+ return; ++ return this.map.containsKey(key); + } + -+ // add to queue, will be picked up later -+ this.queued.add(task); -+ } ++ @Override ++ public boolean remove(final Object key) { ++ Validate.notNull(key, "Null key"); + -+ private void takeTask(final TickThreadRunner runner, final SchedulableTick tick) { -+ if (!this.awaiting.remove(tick.awaitingLink)) { -+ throw new IllegalStateException("Task is not in awaiting"); ++ return this.map.remove(key) != null; + } -+ tick.awaitingLink = null; -+ } + -+ private SchedulableTick returnTask(final TickThreadRunner runner, final SchedulableTick reschedule) { -+ if (reschedule != null) { -+ this.queued.add(reschedule); ++ @Override ++ public boolean retainAll(final Collection collection) { ++ Validate.notNull(collection, "Null collection"); ++ ++ return this.map.removeIf((final K key, final V value) -> { ++ return !collection.contains(key); ++ }) != 0; + } -+ final SchedulableTick ret = this.queued.poll(); -+ if (ret == null) { -+ this.idleThreads.set(runner.id); -+ } else { -+ ret.awaitingLink = this.awaiting.addLast(ret); ++ ++ @Override ++ public boolean removeIf(final Predicate filter) { ++ Validate.notNull(filter, "Null filter"); ++ ++ return this.map.removeIf((final K key, final V value) -> { ++ return filter.test(key); ++ }) != 0; + } + -+ return ret; ++ @Override ++ public String toString() { ++ return CollectionUtil.toString(this, "SWMRHashTableKeySet"); ++ } + } + -+ /** -+ * Schedules the specified task to be executed on this thread pool. -+ * @param task Specified task -+ * @throws IllegalStateException If the task is already scheduled -+ * @see SchedulableTick -+ */ -+ public void schedule(final SchedulableTick task) { -+ synchronized (this.scheduleLock) { -+ if (!task.tryMarkScheduled()) { -+ throw new IllegalStateException("Task " + task + " is already scheduled or cancelled"); -+ } ++ protected static final class ValueCollection extends ViewSet implements Collection { + -+ task.schedulerOwnedBy = this; ++ protected ValueCollection(final SWMRHashTable map) { ++ super(map); ++ } + -+ this.insertFresh(task); ++ @Override ++ public Iterator iterator() { ++ return new ValueIterator<>(this.map.getTableAcquire(), this.map); + } -+ } + -+ /** -+ * Updates the tasks scheduled start to the maximum of its current scheduled start and the specified -+ * new start. If the task is not scheduled, returns {@code false}. Otherwise, returns whether the -+ * scheduled start was updated. Undefined behavior of the specified task is scheduled in another executor. -+ * @param task Specified task -+ * @param newStart Specified new start -+ */ -+ public boolean updateTickStartToMax(final SchedulableTick task, final long newStart) { -+ synchronized (this.scheduleLock) { -+ if (TimeUtil.compareTimes(newStart, task.getScheduledStart()) <= 0) { -+ return false; -+ } -+ if (this.queued.remove(task)) { -+ task.setScheduledStart(newStart); -+ this.queued.add(task); -+ return true; -+ } -+ if (task.awaitingLink != null) { -+ this.awaiting.remove(task.awaitingLink); -+ task.awaitingLink = null; ++ @Override ++ public void forEach(final Consumer action) { ++ Validate.notNull(action, "Null action"); + -+ // re-queue task -+ task.setScheduledStart(newStart); -+ this.queued.add(task); ++ this.map.forEachValue(action); ++ } + -+ // now we need to replace the task the runner was waiting for -+ final TickThreadRunner runner = task.ownedBy; -+ final SchedulableTick replace = this.queued.poll(); ++ @Override ++ public boolean contains(final Object object) { ++ Validate.notNull(object, "Null object"); + -+ // replace cannot be null, since we have added a task to queued -+ if (replace != task) { -+ runner.replaceTask(replace); -+ } ++ return this.map.containsValue(object); ++ } + -+ return true; ++ @Override ++ public boolean remove(final Object object) { ++ Validate.notNull(object, "Null object"); ++ ++ final Iterator itr = this.iterator(); ++ while (itr.hasNext()) { ++ final V val = itr.next(); ++ if (val == object || val.equals(object)) { ++ itr.remove(); ++ return true; ++ } + } + + return false; + } -+ } -+ -+ /** -+ * Returns {@code null} if the task is not scheduled, returns {@code TRUE} if the task was cancelled -+ * and was queued to execute, returns {@code FALSE} if the task was cancelled but was executing. -+ */ -+ public Boolean tryRetire(final SchedulableTick task) { -+ if (task.schedulerOwnedBy != this) { -+ return null; -+ } + -+ synchronized (this.scheduleLock) { -+ if (this.queued.remove(task)) { -+ // cancelled, and no runner owns it - so return -+ return Boolean.TRUE; -+ } -+ if (task.awaitingLink != null) { -+ this.awaiting.remove(task.awaitingLink); -+ task.awaitingLink = null; -+ // here we need to replace the task the runner was waiting for -+ final TickThreadRunner runner = task.ownedBy; -+ final SchedulableTick replace = this.queued.poll(); ++ @Override ++ public boolean removeIf(final Predicate filter) { ++ Validate.notNull(filter, "Null filter"); + -+ if (replace == null) { -+ // nothing to replace with, set to idle -+ this.idleThreads.set(runner.id); -+ runner.forceIdle(); -+ } else { -+ runner.replaceTask(replace); -+ } ++ return this.map.removeIf((final K key, final V value) -> { ++ return filter.test(value); ++ }) != 0; ++ } + -+ return Boolean.TRUE; -+ } ++ @Override ++ public boolean retainAll(final Collection collection) { ++ Validate.notNull(collection, "Null collection"); + -+ // could not find it in queue -+ return task.tryMarkCancelled() ? Boolean.FALSE : null; ++ return this.map.removeIf((final K key, final V value) -> { ++ return !collection.contains(value); ++ }) != 0; + } -+ } + -+ /** -+ * Indicates that intermediate tasks are available to be executed by the task. -+ *

    -+ * Note: currently a no-op -+ *

    -+ * @param task The specified task -+ * @see SchedulableTick -+ */ -+ public void notifyTasks(final SchedulableTick task) { -+ // Not implemented ++ @Override ++ public String toString() { ++ return CollectionUtil.toString(this, "SWMRHashTableValues"); ++ } + } ++} +diff --git a/src/main/java/ca/spottedleaf/concurrentutil/map/SWMRLong2ObjectHashTable.java b/src/main/java/ca/spottedleaf/concurrentutil/map/SWMRLong2ObjectHashTable.java +new file mode 100644 +index 0000000000000000000000000000000000000000..bb301a9f4e3ac919552eef68afc73569d50674db +--- /dev/null ++++ b/src/main/java/ca/spottedleaf/concurrentutil/map/SWMRLong2ObjectHashTable.java +@@ -0,0 +1,674 @@ ++package ca.spottedleaf.concurrentutil.map; + -+ /** -+ * Represents a tickable task that can be scheduled into a {@link SchedulerThreadPool}. -+ *

    -+ * A tickable task is expected to run on a fixed interval, which is determined by -+ * the {@link SchedulerThreadPool}. -+ *

    -+ *

    -+ * A tickable task can have intermediate tasks that can be executed before its tick method is ran. Instead of -+ * the {@link SchedulerThreadPool} parking in-between ticks, the scheduler will instead drain -+ * intermediate tasks from scheduled tasks. The parsing of intermediate tasks allows the scheduler to take -+ * advantage of downtime to reduce the intermediate task load from tasks once they begin ticking. -+ *

    -+ *

    -+ * It is guaranteed that {@link #runTick()} and {@link #runTasks(BooleanSupplier)} are never -+ * invoked in parallel. -+ * It is required that when intermediate tasks are scheduled, that {@link SchedulerThreadPool#notifyTasks(SchedulableTick)} -+ * is invoked for any scheduled task - otherwise, {@link #runTasks(BooleanSupplier)} may not be invoked to -+ * parse intermediate tasks. -+ *

    -+ */ -+ public static abstract class SchedulableTick { -+ private static final AtomicLong ID_GENERATOR = new AtomicLong(); -+ public final long id = ID_GENERATOR.getAndIncrement(); ++import ca.spottedleaf.concurrentutil.function.BiLongObjectConsumer; ++import ca.spottedleaf.concurrentutil.util.ConcurrentUtil; ++import ca.spottedleaf.concurrentutil.util.HashUtil; ++import ca.spottedleaf.concurrentutil.util.IntegerUtil; ++import ca.spottedleaf.concurrentutil.util.Validate; ++import java.lang.invoke.VarHandle; ++import java.util.Arrays; ++import java.util.function.Consumer; ++import java.util.function.LongConsumer; + -+ private static final int SCHEDULE_STATE_NOT_SCHEDULED = 0; -+ private static final int SCHEDULE_STATE_SCHEDULED = 1; -+ private static final int SCHEDULE_STATE_CANCELLED = 2; ++// trimmed down version of SWMRHashTable ++public class SWMRLong2ObjectHashTable { + -+ private final AtomicInteger scheduled = new AtomicInteger(); -+ private SchedulerThreadPool schedulerOwnedBy; -+ private long scheduledStart = DEADLINE_NOT_SET; -+ private TickThreadRunner ownedBy; ++ protected int size; + -+ private LinkedSortedSet.Link awaitingLink; ++ protected TableEntry[] table; + -+ private boolean tryMarkScheduled() { -+ return this.scheduled.compareAndSet(SCHEDULE_STATE_NOT_SCHEDULED, SCHEDULE_STATE_SCHEDULED); -+ } ++ protected final float loadFactor; + -+ private boolean tryMarkCancelled() { -+ return this.scheduled.compareAndSet(SCHEDULE_STATE_SCHEDULED, SCHEDULE_STATE_CANCELLED); -+ } ++ protected static final VarHandle SIZE_HANDLE = ConcurrentUtil.getVarHandle(SWMRLong2ObjectHashTable.class, "size", int.class); ++ protected static final VarHandle TABLE_HANDLE = ConcurrentUtil.getVarHandle(SWMRLong2ObjectHashTable.class, "table", TableEntry[].class); + -+ private boolean isScheduled() { -+ return this.scheduled.get() == SCHEDULE_STATE_SCHEDULED; -+ } ++ /* size */ + -+ protected final long getScheduledStart() { -+ return this.scheduledStart; -+ } ++ protected final int getSizePlain() { ++ return (int)SIZE_HANDLE.get(this); ++ } + -+ /** -+ * If this task is scheduled, then this may only be invoked during {@link #runTick()}, -+ * and {@link #runTasks(BooleanSupplier)} -+ */ -+ protected final void setScheduledStart(final long value) { -+ this.scheduledStart = value; -+ } ++ protected final int getSizeOpaque() { ++ return (int)SIZE_HANDLE.getOpaque(this); ++ } + -+ /** -+ * Executes the tick. -+ *

    -+ * It is the callee's responsibility to invoke {@link #setScheduledStart(long)} to adjust the start of -+ * the next tick. -+ *

    -+ * @return {@code true} if the task should continue to be scheduled, {@code false} otherwise. -+ */ -+ public abstract boolean runTick(); ++ protected final int getSizeAcquire() { ++ return (int)SIZE_HANDLE.getAcquire(this); ++ } + -+ /** -+ * Returns whether this task has any intermediate tasks that can be executed. -+ */ -+ public abstract boolean hasTasks(); ++ protected final void setSizePlain(final int value) { ++ SIZE_HANDLE.set(this, value); ++ } + -+ /** -+ * Returns {@code null} if this task should not be scheduled, otherwise returns -+ * {@code Boolean.TRUE} if there are more intermediate tasks to execute and -+ * {@code Boolean.FALSE} if there are no more intermediate tasks to execute. -+ */ -+ public abstract Boolean runTasks(final BooleanSupplier canContinue); ++ protected final void setSizeOpaque(final int value) { ++ SIZE_HANDLE.setOpaque(this, value); ++ } + -+ @Override -+ public String toString() { -+ return "SchedulableTick:{" + -+ "class=" + this.getClass().getName() + "," + -+ "scheduled_state=" + this.scheduled.get() + "," -+ + "}"; -+ } ++ protected final void setSizeRelease(final int value) { ++ SIZE_HANDLE.setRelease(this, value); + } + -+ private static final class TickThreadRunner implements Runnable { ++ /* table */ + -+ /** -+ * There are no tasks in this thread's runqueue, so it is parked. -+ *

    -+ * stateTarget = null -+ *

    -+ */ -+ private static final int STATE_IDLE = 0; ++ protected final TableEntry[] getTablePlain() { ++ //noinspection unchecked ++ return (TableEntry[])TABLE_HANDLE.get(this); ++ } + -+ /** -+ * The runner is waiting to tick a task, as it has no intermediate tasks to execute. -+ *

    -+ * stateTarget = the task awaiting tick -+ *

    -+ */ -+ private static final int STATE_AWAITING_TICK = 1; ++ protected final TableEntry[] getTableAcquire() { ++ //noinspection unchecked ++ return (TableEntry[])TABLE_HANDLE.getAcquire(this); ++ } + -+ /** -+ * The runner is executing a tick for one of the tasks that was in its runqueue. -+ *

    -+ * stateTarget = the task being ticked -+ *

    -+ */ -+ private static final int STATE_EXECUTING_TICK = 2; ++ protected final void setTablePlain(final TableEntry[] table) { ++ TABLE_HANDLE.set(this, table); ++ } + -+ public final int id; -+ public final SchedulerThreadPool scheduler; ++ protected final void setTableRelease(final TableEntry[] table) { ++ TABLE_HANDLE.setRelease(this, table); ++ } + -+ private volatile Thread thread; -+ private volatile TickThreadRunnerState state = new TickThreadRunnerState(null, STATE_IDLE); -+ private static final VarHandle STATE_HANDLE = ConcurrentUtil.getVarHandle(TickThreadRunner.class, "state", TickThreadRunnerState.class); ++ protected static final int DEFAULT_CAPACITY = 16; ++ protected static final float DEFAULT_LOAD_FACTOR = 0.75f; ++ protected static final int MAXIMUM_CAPACITY = Integer.MIN_VALUE >>> 1; + -+ private void setStatePlain(final TickThreadRunnerState state) { -+ STATE_HANDLE.set(this, state); -+ } ++ /** ++ * Constructs this map with a capacity of {@code 16} and load factor of {@code 0.75f}. ++ */ ++ public SWMRLong2ObjectHashTable() { ++ this(DEFAULT_CAPACITY, DEFAULT_LOAD_FACTOR); ++ } + -+ private void setStateOpaque(final TickThreadRunnerState state) { -+ STATE_HANDLE.setOpaque(this, state); -+ } ++ /** ++ * Constructs this map with the specified capacity and load factor of {@code 0.75f}. ++ * @param capacity specified initial capacity, > 0 ++ */ ++ public SWMRLong2ObjectHashTable(final int capacity) { ++ this(capacity, DEFAULT_LOAD_FACTOR); ++ } + -+ private void setStateVolatile(final TickThreadRunnerState state) { -+ STATE_HANDLE.setVolatile(this, state); ++ /** ++ * Constructs this map with the specified capacity and load factor. ++ * @param capacity specified capacity, > 0 ++ * @param loadFactor specified load factor, > 0 && finite ++ */ ++ public SWMRLong2ObjectHashTable(final int capacity, final float loadFactor) { ++ final int tableSize = getCapacityFor(capacity); ++ ++ if (loadFactor <= 0.0 || !Float.isFinite(loadFactor)) { ++ throw new IllegalArgumentException("Invalid load factor: " + loadFactor); + } + -+ private static record TickThreadRunnerState(SchedulableTick stateTarget, int state) {} ++ //noinspection unchecked ++ final TableEntry[] table = new TableEntry[tableSize]; ++ this.setTablePlain(table); + -+ public TickThreadRunner(final int id, final SchedulerThreadPool scheduler) { -+ this.id = id; -+ this.scheduler = scheduler; ++ if (tableSize == MAXIMUM_CAPACITY) { ++ this.threshold = -1; ++ } else { ++ this.threshold = getTargetCapacity(tableSize, loadFactor); + } + -+ private Thread getRunnerThread() { -+ return this.thread; ++ this.loadFactor = loadFactor; ++ } ++ ++ /** ++ * Constructs this map with a capacity of {@code 16} or the specified map's size, whichever is larger, and ++ * with a load factor of {@code 0.75f}. ++ * All of the specified map's entries are copied into this map. ++ * @param other The specified map. ++ */ ++ public SWMRLong2ObjectHashTable(final SWMRLong2ObjectHashTable other) { ++ this(DEFAULT_CAPACITY, DEFAULT_LOAD_FACTOR, other); ++ } ++ ++ /** ++ * Constructs this map with a minimum capacity of the specified capacity or the specified map's size, whichever is larger, and ++ * with a load factor of {@code 0.75f}. ++ * All of the specified map's entries are copied into this map. ++ * @param capacity specified capacity, > 0 ++ * @param other The specified map. ++ */ ++ public SWMRLong2ObjectHashTable(final int capacity, final SWMRLong2ObjectHashTable other) { ++ this(capacity, DEFAULT_LOAD_FACTOR, other); ++ } ++ ++ /** ++ * Constructs this map with a min capacity of the specified capacity or the specified map's size, whichever is larger, and ++ * with the specified load factor. ++ * All of the specified map's entries are copied into this map. ++ * @param capacity specified capacity, > 0 ++ * @param loadFactor specified load factor, > 0 && finite ++ * @param other The specified map. ++ */ ++ public SWMRLong2ObjectHashTable(final int capacity, final float loadFactor, final SWMRLong2ObjectHashTable other) { ++ this(Math.max(Validate.notNull(other, "Null map").size(), capacity), loadFactor); ++ this.putAll(other); ++ } ++ ++ protected static TableEntry getAtIndexOpaque(final TableEntry[] table, final int index) { ++ // noinspection unchecked ++ return (TableEntry)TableEntry.TABLE_ENTRY_ARRAY_HANDLE.getOpaque(table, index); ++ } ++ ++ protected static void setAtIndexRelease(final TableEntry[] table, final int index, final TableEntry value) { ++ TableEntry.TABLE_ENTRY_ARRAY_HANDLE.setRelease(table, index, value); ++ } ++ ++ public final float getLoadFactor() { ++ return this.loadFactor; ++ } ++ ++ protected static int getCapacityFor(final int capacity) { ++ if (capacity <= 0) { ++ throw new IllegalArgumentException("Invalid capacity: " + capacity); + } ++ if (capacity >= MAXIMUM_CAPACITY) { ++ return MAXIMUM_CAPACITY; ++ } ++ return IntegerUtil.roundCeilLog2(capacity); ++ } + -+ private void acceptTask(final SchedulableTick task) { -+ if (task.ownedBy != null) { -+ throw new IllegalStateException("Already owned by another runner"); -+ } -+ task.ownedBy = this; -+ final TickThreadRunnerState state = this.state; -+ if (state.state != STATE_IDLE) { -+ throw new IllegalStateException("Cannot accept task in state " + state); ++ /** Callers must still use acquire when reading the value of the entry. */ ++ protected final TableEntry getEntryForOpaque(final long key) { ++ final int hash = SWMRLong2ObjectHashTable.getHash(key); ++ final TableEntry[] table = this.getTableAcquire(); ++ ++ for (TableEntry curr = getAtIndexOpaque(table, hash & (table.length - 1)); curr != null; curr = curr.getNextOpaque()) { ++ if (key == curr.key) { ++ return curr; + } -+ this.setStateVolatile(new TickThreadRunnerState(task, STATE_AWAITING_TICK)); -+ LockSupport.unpark(this.getRunnerThread()); + } + -+ private void replaceTask(final SchedulableTick task) { -+ final TickThreadRunnerState state = this.state; -+ if (state.state != STATE_AWAITING_TICK) { -+ throw new IllegalStateException("Cannot replace task in state " + state); -+ } -+ if (task.ownedBy != null) { -+ throw new IllegalStateException("Already owned by another runner"); ++ return null; ++ } ++ ++ protected final TableEntry getEntryForPlain(final long key) { ++ final int hash = SWMRLong2ObjectHashTable.getHash(key); ++ final TableEntry[] table = this.getTablePlain(); ++ ++ for (TableEntry curr = table[hash & (table.length - 1)]; curr != null; curr = curr.getNextPlain()) { ++ if (key == curr.key) { ++ return curr; + } -+ task.ownedBy = this; ++ } + -+ state.stateTarget.ownedBy = null; ++ return null; ++ } + -+ this.setStateVolatile(new TickThreadRunnerState(task, STATE_AWAITING_TICK)); -+ LockSupport.unpark(this.getRunnerThread()); ++ /* MT-Safe */ ++ ++ /** must be deterministic given a key */ ++ protected static int getHash(final long key) { ++ return (int)HashUtil.mix(key); ++ } ++ ++ // rets -1 if capacity*loadFactor is too large ++ protected static int getTargetCapacity(final int capacity, final float loadFactor) { ++ final double ret = (double)capacity * (double)loadFactor; ++ if (Double.isInfinite(ret) || ret >= ((double)Integer.MAX_VALUE)) { ++ return -1; + } + -+ private void forceIdle() { -+ final TickThreadRunnerState state = this.state; -+ if (state.state != STATE_AWAITING_TICK) { -+ throw new IllegalStateException("Cannot replace task in state " + state); -+ } -+ state.stateTarget.ownedBy = null; -+ this.setStateOpaque(new TickThreadRunnerState(null, STATE_IDLE)); -+ // no need to unpark ++ return (int)ret; ++ } ++ ++ /** ++ * {@inheritDoc} ++ */ ++ @Override ++ public boolean equals(final Object obj) { ++ if (this == obj) { ++ return true; ++ } ++ /* Make no attempt to deal with concurrent modifications */ ++ if (!(obj instanceof SWMRLong2ObjectHashTable other)) { ++ return false; + } + -+ private boolean takeTask(final TickThreadRunnerState state, final SchedulableTick task) { -+ synchronized (this.scheduler.scheduleLock) { -+ if (this.state != state) { ++ if (this.size() != other.size()) { ++ return false; ++ } ++ ++ final TableEntry[] table = this.getTableAcquire(); ++ ++ for (int i = 0, len = table.length; i < len; ++i) { ++ for (TableEntry curr = getAtIndexOpaque(table, i); curr != null; curr = curr.getNextOpaque()) { ++ final V value = curr.getValueAcquire(); ++ ++ final Object otherValue = other.get(curr.key); ++ if (otherValue == null || (value != otherValue && value.equals(otherValue))) { + return false; + } -+ this.setStatePlain(new TickThreadRunnerState(task, STATE_EXECUTING_TICK)); -+ this.scheduler.takeTask(this, task); -+ return true; + } + } + -+ private void returnTask(final SchedulableTick task, final boolean reschedule) { -+ synchronized (this.scheduler.scheduleLock) { -+ task.ownedBy = null; ++ return true; ++ } + -+ final SchedulableTick newWait = this.scheduler.returnTask(this, reschedule && task.isScheduled() ? task : null); -+ if (newWait == null) { -+ this.setStatePlain(new TickThreadRunnerState(null, STATE_IDLE)); -+ } else { -+ if (newWait.ownedBy != null) { -+ throw new IllegalStateException("Already owned by another runner"); -+ } -+ newWait.ownedBy = this; -+ this.setStatePlain(new TickThreadRunnerState(newWait, STATE_AWAITING_TICK)); -+ } ++ /** ++ * {@inheritDoc} ++ */ ++ @Override ++ public int hashCode() { ++ /* Make no attempt to deal with concurrent modifications */ ++ int hash = 0; ++ final TableEntry[] table = this.getTableAcquire(); ++ ++ for (int i = 0, len = table.length; i < len; ++i) { ++ for (TableEntry curr = getAtIndexOpaque(table, i); curr != null; curr = curr.getNextOpaque()) { ++ hash += curr.hashCode(); + } + } + -+ @Override -+ public void run() { -+ this.thread = Thread.currentThread(); ++ return hash; ++ } + -+ main_state_loop: -+ for (;;) { -+ final TickThreadRunnerState startState = this.state; -+ final int startStateType = startState.state; -+ final SchedulableTick startStateTask = startState.stateTarget; ++ /** ++ * {@inheritDoc} ++ */ ++ @Override ++ public String toString() { ++ final StringBuilder builder = new StringBuilder(64); ++ builder.append("SingleWriterMultiReaderHashMap:{"); + -+ if (this.scheduler.halted) { -+ return; -+ } ++ this.forEach((final long key, final V value) -> { ++ builder.append("{key: \"").append(key).append("\", value: \"").append(value).append("\"}"); ++ }); + -+ switch (startStateType) { -+ case STATE_IDLE: { -+ while (this.state.state == STATE_IDLE) { -+ LockSupport.park(); -+ if (this.scheduler.halted) { -+ return; -+ } -+ } -+ continue main_state_loop; -+ } ++ return builder.append('}').toString(); ++ } + -+ case STATE_AWAITING_TICK: { -+ final long deadline = startStateTask.getScheduledStart(); -+ for (;;) { -+ if (this.state != startState) { -+ continue main_state_loop; -+ } -+ final long diff = deadline - System.nanoTime(); -+ if (diff <= 0L) { -+ break; -+ } -+ LockSupport.parkNanos(startState, diff); -+ if (this.scheduler.halted) { -+ return; -+ } -+ } ++ /** ++ * {@inheritDoc} ++ */ ++ @Override ++ public SWMRLong2ObjectHashTable clone() { ++ return new SWMRLong2ObjectHashTable<>(this.getTableAcquire().length, this.loadFactor, this); ++ } + -+ if (!this.takeTask(startState, startStateTask)) { -+ continue main_state_loop; -+ } ++ /** ++ * {@inheritDoc} ++ */ ++ public void forEach(final Consumer> action) { ++ Validate.notNull(action, "Null action"); + -+ // TODO exception handling -+ final boolean reschedule = startStateTask.runTick(); ++ final TableEntry[] table = this.getTableAcquire(); ++ for (int i = 0, len = table.length; i < len; ++i) { ++ for (TableEntry curr = getAtIndexOpaque(table, i); curr != null; curr = curr.getNextOpaque()) { ++ action.accept(curr); ++ } ++ } ++ } + -+ this.returnTask(startStateTask, reschedule); ++ /** ++ * {@inheritDoc} ++ */ ++ public void forEach(final BiLongObjectConsumer action) { ++ Validate.notNull(action, "Null action"); + -+ continue main_state_loop; -+ } ++ final TableEntry[] table = this.getTableAcquire(); ++ for (int i = 0, len = table.length; i < len; ++i) { ++ for (TableEntry curr = getAtIndexOpaque(table, i); curr != null; curr = curr.getNextOpaque()) { ++ final V value = curr.getValueAcquire(); + -+ case STATE_EXECUTING_TICK: { -+ throw new IllegalStateException("Tick execution must be set by runner thread, not by any other thread"); -+ } ++ action.accept(curr.key, value); ++ } ++ } ++ } + -+ default: { -+ throw new IllegalStateException("Unknown state: " + startState); -+ } -+ } ++ /** ++ * Provides the specified consumer with all keys contained within this map. ++ * @param action The specified consumer. ++ */ ++ public void forEachKey(final LongConsumer action) { ++ Validate.notNull(action, "Null action"); ++ ++ final TableEntry[] table = this.getTableAcquire(); ++ for (int i = 0, len = table.length; i < len; ++i) { ++ for (TableEntry curr = getAtIndexOpaque(table, i); curr != null; curr = curr.getNextOpaque()) { ++ action.accept(curr.key); + } + } + } -+} -diff --git a/src/main/java/ca/spottedleaf/concurrentutil/set/LinkedSortedSet.java b/src/main/java/ca/spottedleaf/concurrentutil/set/LinkedSortedSet.java -new file mode 100644 -index 0000000000000000000000000000000000000000..212bc9ae2fc7d37d4a089a2921b00de1e97f7cc1 ---- /dev/null -+++ b/src/main/java/ca/spottedleaf/concurrentutil/set/LinkedSortedSet.java -@@ -0,0 +1,272 @@ -+package ca.spottedleaf.concurrentutil.set; + -+import java.util.Comparator; -+import java.util.Iterator; -+import java.util.NoSuchElementException; ++ /** ++ * Provides the specified consumer with all values contained within this map. Equivalent to {@code map.values().forEach(Consumer)}. ++ * @param action The specified consumer. ++ */ ++ public void forEachValue(final Consumer action) { ++ Validate.notNull(action, "Null action"); + -+public final class LinkedSortedSet implements Iterable { ++ final TableEntry[] table = this.getTableAcquire(); ++ for (int i = 0, len = table.length; i < len; ++i) { ++ for (TableEntry curr = getAtIndexOpaque(table, i); curr != null; curr = curr.getNextOpaque()) { ++ final V value = curr.getValueAcquire(); + -+ public final Comparator comparator; ++ action.accept(value); ++ } ++ } ++ } + -+ protected Link head; -+ protected Link tail; ++ /** ++ * {@inheritDoc} ++ */ ++ public V get(final long key) { ++ final TableEntry entry = this.getEntryForOpaque(key); ++ return entry == null ? null : entry.getValueAcquire(); ++ } + -+ public LinkedSortedSet() { -+ this((Comparator)Comparator.naturalOrder()); ++ /** ++ * {@inheritDoc} ++ */ ++ public boolean containsKey(final long key) { ++ // note: we need to use getValueAcquire, so that the reads from this map are ordered by acquire semantics ++ return this.get(key) != null; + } + -+ public LinkedSortedSet(final Comparator comparator) { -+ this.comparator = comparator; ++ /** ++ * {@inheritDoc} ++ */ ++ public V getOrDefault(final long key, final V defaultValue) { ++ final TableEntry entry = this.getEntryForOpaque(key); ++ ++ return entry == null ? defaultValue : entry.getValueAcquire(); + } + -+ public void clear() { -+ this.head = this.tail = null; ++ /** ++ * {@inheritDoc} ++ */ ++ public int size() { ++ return this.getSizeAcquire(); + } + ++ /** ++ * {@inheritDoc} ++ */ + public boolean isEmpty() { -+ return this.head == null; ++ return this.getSizeAcquire() == 0; + } + -+ public E first() { -+ final Link head = this.head; -+ return head == null ? null : head.element; -+ } ++ /* Non-MT-Safe */ + -+ public E last() { -+ final Link tail = this.tail; -+ return tail == null ? null : tail.element; -+ } ++ protected int threshold; + -+ public boolean containsFirst(final E element) { -+ final Comparator comparator = this.comparator; -+ for (Link curr = this.head; curr != null; curr = curr.next) { -+ if (comparator.compare(element, curr.element) == 0) { -+ return true; -+ } ++ protected final void checkResize(final int minCapacity) { ++ if (minCapacity <= this.threshold || this.threshold < 0) { ++ return; + } -+ return false; -+ } + -+ public boolean containsLast(final E element) { -+ final Comparator comparator = this.comparator; -+ for (Link curr = this.tail; curr != null; curr = curr.prev) { -+ if (comparator.compare(element, curr.element) == 0) { -+ return true; ++ final TableEntry[] table = this.getTablePlain(); ++ int newCapacity = minCapacity >= MAXIMUM_CAPACITY ? MAXIMUM_CAPACITY : IntegerUtil.roundCeilLog2(minCapacity); ++ if (newCapacity < 0) { ++ newCapacity = MAXIMUM_CAPACITY; ++ } ++ if (newCapacity <= table.length) { ++ if (newCapacity == MAXIMUM_CAPACITY) { ++ return; + } ++ newCapacity = table.length << 1; + } -+ return false; -+ } + -+ private void removeNode(final Link node) { -+ final Link prev = node.prev; -+ final Link next = node.next; ++ //noinspection unchecked ++ final TableEntry[] newTable = new TableEntry[newCapacity]; ++ final int indexMask = newCapacity - 1; + -+ // help GC -+ node.element = null; -+ node.prev = null; -+ node.next = null; ++ for (int i = 0, len = table.length; i < len; ++i) { ++ for (TableEntry entry = table[i]; entry != null; entry = entry.getNextPlain()) { ++ final long key = entry.key; ++ final int hash = SWMRLong2ObjectHashTable.getHash(key); ++ final int index = hash & indexMask; + -+ if (prev == null) { -+ this.head = next; -+ } else { -+ prev.next = next; ++ /* we need to create a new entry since there could be reading threads */ ++ final TableEntry insert = new TableEntry<>(key, entry.getValuePlain()); ++ ++ final TableEntry prev = newTable[index]; ++ ++ newTable[index] = insert; ++ insert.setNextPlain(prev); ++ } + } + -+ if (next == null) { -+ this.tail = prev; ++ if (newCapacity == MAXIMUM_CAPACITY) { ++ this.threshold = -1; /* No more resizing */ + } else { -+ next.prev = prev; ++ this.threshold = getTargetCapacity(newCapacity, this.loadFactor); + } ++ this.setTableRelease(newTable); /* use release to publish entries in table */ + } + -+ public boolean remove(final Link link) { -+ if (link.element == null) { -+ return false; -+ } -+ -+ this.removeNode(link); -+ return true; -+ } ++ protected final int addToSize(final int num) { ++ final int newSize = this.getSizePlain() + num; + -+ public boolean removeFirst(final E element) { -+ final Comparator comparator = this.comparator; -+ for (Link curr = this.head; curr != null; curr = curr.next) { -+ if (comparator.compare(element, curr.element) == 0) { -+ this.removeNode(curr); -+ return true; -+ } -+ } -+ return false; -+ } ++ this.setSizeOpaque(newSize); ++ this.checkResize(newSize); + -+ public boolean removeLast(final E element) { -+ final Comparator comparator = this.comparator; -+ for (Link curr = this.tail; curr != null; curr = curr.prev) { -+ if (comparator.compare(element, curr.element) == 0) { -+ this.removeNode(curr); -+ return true; -+ } -+ } -+ return false; ++ return newSize; + } + -+ @Override -+ public Iterator iterator() { -+ return new Iterator<>() { -+ private Link next = LinkedSortedSet.this.head; ++ protected final int removeFromSize(final int num) { ++ final int newSize = this.getSizePlain() - num; + -+ @Override -+ public boolean hasNext() { -+ return this.next != null; -+ } ++ this.setSizeOpaque(newSize); + -+ @Override -+ public E next() { -+ final Link next = this.next; -+ if (next == null) { -+ throw new NoSuchElementException(); -+ } -+ this.next = next.next; -+ return next.element; -+ } -+ }; ++ return newSize; + } + -+ public E pollFirst() { -+ final Link head = this.head; ++ protected final V put(final long key, final V value, final boolean onlyIfAbsent) { ++ final TableEntry[] table = this.getTablePlain(); ++ final int hash = SWMRLong2ObjectHashTable.getHash(key); ++ final int index = hash & (table.length - 1); ++ ++ final TableEntry head = table[index]; + if (head == null) { ++ final TableEntry insert = new TableEntry<>(key, value); ++ setAtIndexRelease(table, index, insert); ++ this.addToSize(1); + return null; + } + -+ final E ret = head.element; -+ final Link next = head.next; ++ for (TableEntry curr = head;;) { ++ if (key == curr.key) { ++ if (onlyIfAbsent) { ++ return curr.getValuePlain(); ++ } + -+ // unlink head -+ this.head = next; -+ if (next == null) { -+ this.tail = null; -+ } else { -+ next.prev = null; -+ } ++ final V currVal = curr.getValuePlain(); ++ curr.setValueRelease(value); ++ return currVal; ++ } + -+ // help GC -+ head.element = null; -+ head.next = null; ++ final TableEntry next = curr.getNextPlain(); ++ if (next != null) { ++ curr = next; ++ continue; ++ } + -+ return ret; -+ } ++ final TableEntry insert = new TableEntry<>(key, value); + -+ public E pollLast() { -+ final Link tail = this.tail; -+ if (tail == null) { ++ curr.setNextRelease(insert); ++ this.addToSize(1); + return null; + } ++ } + -+ final E ret = tail.element; -+ final Link prev = tail.prev; ++ /** ++ * {@inheritDoc} ++ */ ++ public V put(final long key, final V value) { ++ Validate.notNull(value, "Null value"); + -+ // unlink tail -+ this.tail = prev; -+ if (prev == null) { -+ this.head = null; -+ } else { -+ prev.next = null; -+ } ++ return this.put(key, value, false); ++ } + -+ // help GC -+ tail.element = null; -+ tail.prev = null; ++ /** ++ * {@inheritDoc} ++ */ ++ public V putIfAbsent(final long key, final V value) { ++ Validate.notNull(value, "Null value"); + -+ return ret; ++ return this.put(key, value, true); + } + -+ public Link addLast(final E element) { -+ final Comparator comparator = this.comparator; ++ protected final V remove(final long key, final int hash) { ++ final TableEntry[] table = this.getTablePlain(); ++ final int index = (table.length - 1) & hash; + -+ Link curr = this.tail; -+ if (curr != null) { -+ int compare; ++ final TableEntry head = table[index]; ++ if (head == null) { ++ return null; ++ } + -+ while ((compare = comparator.compare(element, curr.element)) < 0) { -+ Link prev = curr; -+ curr = curr.prev; -+ if (curr != null) { -+ continue; -+ } -+ return this.head = prev.prev = new Link<>(element, null, prev); -+ } ++ if (head.key == key) { ++ setAtIndexRelease(table, index, head.getNextPlain()); ++ this.removeFromSize(1); + -+ if (compare != 0) { -+ // insert after curr -+ final Link next = curr.next; -+ final Link insert = new Link<>(element, curr, next); -+ curr.next = insert; ++ return head.getValuePlain(); ++ } + -+ if (next == null) { -+ this.tail = insert; -+ } else { -+ next.prev = insert; -+ } -+ return insert; -+ } ++ for (TableEntry curr = head.getNextPlain(), prev = head; curr != null; prev = curr, curr = curr.getNextPlain()) { ++ if (key == curr.key) { ++ prev.setNextRelease(curr.getNextPlain()); ++ this.removeFromSize(1); + -+ return null; -+ } else { -+ return this.head = this.tail = new Link<>(element); ++ return curr.getValuePlain(); ++ } + } ++ ++ return null; + } + -+ public Link addFirst(final E element) { -+ final Comparator comparator = this.comparator; ++ protected final V remove(final long key, final int hash, final V expect) { ++ final TableEntry[] table = this.getTablePlain(); ++ final int index = (table.length - 1) & hash; ++ ++ final TableEntry head = table[index]; ++ if (head == null) { ++ return null; ++ } + -+ Link curr = this.head; -+ if (curr != null) { -+ int compare; ++ if (head.key == key) { ++ final V val = head.value; ++ if (val == expect || val.equals(expect)) { ++ setAtIndexRelease(table, index, head.getNextPlain()); ++ this.removeFromSize(1); + -+ while ((compare = comparator.compare(element, curr.element)) > 0) { -+ Link prev = curr; -+ curr = curr.next; -+ if (curr != null) { -+ continue; -+ } -+ return this.tail = prev.next = new Link<>(element, prev, null); ++ return head.getValuePlain(); ++ } else { ++ return null; + } ++ } + -+ if (compare != 0) { -+ // insert before curr -+ final Link prev = curr.prev; -+ final Link insert = new Link<>(element, prev, curr); -+ curr.prev = insert; ++ for (TableEntry curr = head.getNextPlain(), prev = head; curr != null; prev = curr, curr = curr.getNextPlain()) { ++ if (key == curr.key) { ++ final V val = curr.value; ++ if (val == expect || val.equals(expect)) { ++ prev.setNextRelease(curr.getNextPlain()); ++ this.removeFromSize(1); + -+ if (prev == null) { -+ this.head = insert; ++ return curr.getValuePlain(); + } else { -+ prev.next = insert; ++ return null; + } -+ return insert; + } -+ -+ return null; -+ } else { -+ return this.head = this.tail = new Link<>(element); + } ++ ++ return null; + } + -+ public static final class Link { -+ private E element; -+ private Link prev; -+ private Link next; ++ /** ++ * {@inheritDoc} ++ */ ++ public V remove(final long key) { ++ return this.remove(key, SWMRLong2ObjectHashTable.getHash(key)); ++ } + -+ private Link() {} ++ public boolean remove(final long key, final V expect) { ++ return this.remove(key, SWMRLong2ObjectHashTable.getHash(key), expect) != null; ++ } + -+ private Link(final E element) { -+ this.element = element; -+ } ++ /** ++ * {@inheritDoc} ++ */ ++ public void putAll(final SWMRLong2ObjectHashTable map) { ++ Validate.notNull(map, "Null map"); + -+ private Link(final E element, final Link prev, final Link next) { -+ this.element = element; -+ this.prev = prev; -+ this.next = next; -+ } ++ final int size = map.size(); ++ this.checkResize(Math.max(this.getSizePlain() + size/2, size)); /* preemptively resize */ ++ map.forEach(this::put); + } -+} -diff --git a/src/main/java/ca/spottedleaf/concurrentutil/util/ArrayUtil.java b/src/main/java/ca/spottedleaf/concurrentutil/util/ArrayUtil.java -new file mode 100644 -index 0000000000000000000000000000000000000000..ebb1ab06165addb173fea4d295001fe37f4e79d3 ---- /dev/null -+++ b/src/main/java/ca/spottedleaf/concurrentutil/util/ArrayUtil.java -@@ -0,0 +1,816 @@ -+package ca.spottedleaf.concurrentutil.util; + -+import java.lang.invoke.VarHandle; ++ /** ++ * {@inheritDoc} ++ *

    ++ * This call is non-atomic and the order that which entries are removed is undefined. The clear operation itself ++ * is release ordered, that is, after the clear operation is performed a release fence is performed. ++ *

    ++ */ ++ public void clear() { ++ Arrays.fill(this.getTablePlain(), null); ++ this.setSizeRelease(0); ++ } + -+public final class ArrayUtil { ++ public static final class TableEntry { + -+ public static final VarHandle BOOLEAN_ARRAY_HANDLE = ConcurrentUtil.getArrayHandle(boolean[].class); ++ protected static final VarHandle TABLE_ENTRY_ARRAY_HANDLE = ConcurrentUtil.getArrayHandle(TableEntry[].class); + -+ public static final VarHandle BYTE_ARRAY_HANDLE = ConcurrentUtil.getArrayHandle(byte[].class); ++ protected final long key; ++ protected V value; + -+ public static final VarHandle SHORT_ARRAY_HANDLE = ConcurrentUtil.getArrayHandle(short[].class); ++ protected TableEntry next; + -+ public static final VarHandle INT_ARRAY_HANDLE = ConcurrentUtil.getArrayHandle(int[].class); ++ protected static final VarHandle VALUE_HANDLE = ConcurrentUtil.getVarHandle(TableEntry.class, "value", Object.class); ++ protected static final VarHandle NEXT_HANDLE = ConcurrentUtil.getVarHandle(TableEntry.class, "next", TableEntry.class); + -+ public static final VarHandle LONG_ARRAY_HANDLE = ConcurrentUtil.getArrayHandle(long[].class); ++ /* value */ + -+ public static final VarHandle OBJECT_ARRAY_HANDLE = ConcurrentUtil.getArrayHandle(Object[].class); ++ protected final V getValuePlain() { ++ //noinspection unchecked ++ return (V)VALUE_HANDLE.get(this); ++ } + -+ private ArrayUtil() { -+ throw new RuntimeException(); -+ } ++ protected final V getValueAcquire() { ++ //noinspection unchecked ++ return (V)VALUE_HANDLE.getAcquire(this); ++ } + -+ /* byte array */ ++ protected final void setValueRelease(final V to) { ++ VALUE_HANDLE.setRelease(this, to); ++ } + -+ public static byte getPlain(final byte[] array, final int index) { -+ return (byte)BYTE_ARRAY_HANDLE.get(array, index); -+ } ++ /* next */ + -+ public static byte getOpaque(final byte[] array, final int index) { -+ return (byte)BYTE_ARRAY_HANDLE.getOpaque(array, index); -+ } ++ protected final TableEntry getNextPlain() { ++ //noinspection unchecked ++ return (TableEntry)NEXT_HANDLE.get(this); ++ } + -+ public static byte getAcquire(final byte[] array, final int index) { -+ return (byte)BYTE_ARRAY_HANDLE.getAcquire(array, index); -+ } ++ protected final TableEntry getNextOpaque() { ++ //noinspection unchecked ++ return (TableEntry)NEXT_HANDLE.getOpaque(this); ++ } + -+ public static byte getVolatile(final byte[] array, final int index) { -+ return (byte)BYTE_ARRAY_HANDLE.getVolatile(array, index); -+ } ++ protected final void setNextPlain(final TableEntry next) { ++ NEXT_HANDLE.set(this, next); ++ } + -+ public static void setPlain(final byte[] array, final int index, final byte value) { -+ BYTE_ARRAY_HANDLE.set(array, index, value); -+ } ++ protected final void setNextRelease(final TableEntry next) { ++ NEXT_HANDLE.setRelease(this, next); ++ } + -+ public static void setOpaque(final byte[] array, final int index, final byte value) { -+ BYTE_ARRAY_HANDLE.setOpaque(array, index, value); -+ } ++ protected TableEntry(final long key, final V value) { ++ this.key = key; ++ this.value = value; ++ } + -+ public static void setRelease(final byte[] array, final int index, final byte value) { -+ BYTE_ARRAY_HANDLE.setRelease(array, index, value); -+ } ++ public long getKey() { ++ return this.key; ++ } + -+ public static void setVolatile(final byte[] array, final int index, final byte value) { -+ BYTE_ARRAY_HANDLE.setVolatile(array, index, value); ++ public V getValue() { ++ return this.getValueAcquire(); ++ } + } ++} +diff --git a/src/main/java/ca/spottedleaf/concurrentutil/scheduler/SchedulerThreadPool.java b/src/main/java/ca/spottedleaf/concurrentutil/scheduler/SchedulerThreadPool.java +new file mode 100644 +index 0000000000000000000000000000000000000000..85e6ef636d435a0ee4bf3e0760b0c87422c520a1 +--- /dev/null ++++ b/src/main/java/ca/spottedleaf/concurrentutil/scheduler/SchedulerThreadPool.java +@@ -0,0 +1,564 @@ ++package ca.spottedleaf.concurrentutil.scheduler; + -+ public static void setVolatileContended(final byte[] array, final int index, final byte param) { -+ int failures = 0; ++import ca.spottedleaf.concurrentutil.set.LinkedSortedSet; ++import ca.spottedleaf.concurrentutil.util.ConcurrentUtil; ++import ca.spottedleaf.concurrentutil.util.TimeUtil; ++import java.lang.invoke.VarHandle; ++import java.util.BitSet; ++import java.util.Comparator; ++import java.util.PriorityQueue; ++import java.util.concurrent.ThreadFactory; ++import java.util.concurrent.atomic.AtomicInteger; ++import java.util.concurrent.atomic.AtomicLong; ++import java.util.concurrent.locks.LockSupport; ++import java.util.function.BooleanSupplier; + -+ for (byte curr = getVolatile(array, index);;++failures) { -+ for (int i = 0; i < failures; ++i) { -+ ConcurrentUtil.backoff(); -+ } ++/** ++ * @deprecated To be replaced ++ */ ++@Deprecated ++public class SchedulerThreadPool { + -+ if (curr == (curr = compareAndExchangeVolatileContended(array, index, curr, param))) { -+ return; -+ } ++ public static final long DEADLINE_NOT_SET = Long.MIN_VALUE; ++ ++ private static final Comparator TICK_COMPARATOR_BY_TIME = (final SchedulableTick t1, final SchedulableTick t2) -> { ++ final int timeCompare = TimeUtil.compareTimes(t1.scheduledStart, t2.scheduledStart); ++ if (timeCompare != 0) { ++ return timeCompare; + } -+ } + -+ public static byte compareAndExchangeVolatile(final byte[] array, final int index, final byte expect, final byte update) { -+ return (byte)BYTE_ARRAY_HANDLE.compareAndExchange(array, index, expect, update); -+ } ++ return Long.compare(t1.id, t2.id); ++ }; + -+ public static byte getAndAddVolatile(final byte[] array, final int index, final byte param) { -+ return (byte)BYTE_ARRAY_HANDLE.getAndAdd(array, index, param); -+ } ++ private final TickThreadRunner[] runners; ++ private final Thread[] threads; ++ private final LinkedSortedSet awaiting = new LinkedSortedSet<>(TICK_COMPARATOR_BY_TIME); ++ private final PriorityQueue queued = new PriorityQueue<>(TICK_COMPARATOR_BY_TIME); ++ private final BitSet idleThreads; + -+ public static byte getAndAndVolatile(final byte[] array, final int index, final byte param) { -+ return (byte)BYTE_ARRAY_HANDLE.getAndBitwiseAnd(array, index, param); -+ } ++ private final Object scheduleLock = new Object(); + -+ public static byte getAndOrVolatile(final byte[] array, final int index, final byte param) { -+ return (byte)BYTE_ARRAY_HANDLE.getAndBitwiseOr(array, index, param); -+ } ++ private volatile boolean halted; + -+ public static byte getAndXorVolatile(final byte[] array, final int index, final byte param) { -+ return (byte)BYTE_ARRAY_HANDLE.getAndBitwiseXor(array, index, param); -+ } ++ /** ++ * Creates, but does not start, a scheduler thread pool with the specified number of threads ++ * created using the specified thread factory. ++ * @param threads Specified number of threads ++ * @param threadFactory Specified thread factory ++ * @see #start() ++ */ ++ public SchedulerThreadPool(final int threads, final ThreadFactory threadFactory) { ++ final BitSet idleThreads = new BitSet(threads); ++ for (int i = 0; i < threads; ++i) { ++ idleThreads.set(i); ++ } ++ this.idleThreads = idleThreads; + -+ public static byte getAndSetVolatile(final byte[] array, final int index, final byte param) { -+ return (byte)BYTE_ARRAY_HANDLE.getAndSet(array, index, param); -+ } ++ final TickThreadRunner[] runners = new TickThreadRunner[threads]; ++ final Thread[] t = new Thread[threads]; ++ for (int i = 0; i < threads; ++i) { ++ runners[i] = new TickThreadRunner(i, this); ++ t[i] = threadFactory.newThread(runners[i]); ++ } + -+ public static byte compareAndExchangeVolatileContended(final byte[] array, final int index, final byte expect, final byte update) { -+ return (byte)BYTE_ARRAY_HANDLE.compareAndExchange(array, index, expect, update); ++ this.threads = t; ++ this.runners = runners; + } + -+ public static byte getAndAddVolatileContended(final byte[] array, final int index, final byte param) { -+ int failures = 0; -+ -+ for (byte curr = getVolatile(array, index);;++failures) { -+ for (int i = 0; i < failures; ++i) { -+ ConcurrentUtil.backoff(); -+ } -+ -+ if (curr == (curr = compareAndExchangeVolatileContended(array, index, curr, (byte) (curr + param)))) { -+ return curr; -+ } ++ /** ++ * Starts the threads in this pool. ++ */ ++ public void start() { ++ for (final Thread thread : this.threads) { ++ thread.start(); + } + } + -+ public static byte getAndAndVolatileContended(final byte[] array, final int index, final byte param) { -+ int failures = 0; -+ -+ for (byte curr = getVolatile(array, index);;++failures) { -+ for (int i = 0; i < failures; ++i) { -+ ConcurrentUtil.backoff(); -+ } -+ -+ if (curr == (curr = compareAndExchangeVolatileContended(array, index, curr, (byte) (curr & param)))) { -+ return curr; ++ /** ++ * Attempts to prevent further execution of tasks, optionally waiting for the scheduler threads to die. ++ * ++ * @param sync Whether to wait for the scheduler threads to die. ++ * @param maxWaitNS The maximum time, in ns, to wait for the scheduler threads to die. ++ * @return {@code true} if sync was false, or if sync was true and the scheduler threads died before the timeout. ++ * Otherwise, returns {@code false} if the time elapsed exceeded the maximum wait time. ++ */ ++ public boolean halt(final boolean sync, final long maxWaitNS) { ++ this.halted = true; ++ for (final Thread thread : this.threads) { ++ // force response to halt ++ LockSupport.unpark(thread); ++ } ++ final long time = System.nanoTime(); ++ if (sync) { ++ // start at 10 * 0.5ms -> 5ms ++ for (long failures = 9L;; failures = ConcurrentUtil.linearLongBackoff(failures, 500_000L, 50_000_000L)) { ++ boolean allDead = true; ++ for (final Thread thread : this.threads) { ++ if (thread.isAlive()) { ++ allDead = false; ++ break; ++ } ++ } ++ if (allDead) { ++ return true; ++ } ++ if ((System.nanoTime() - time) >= maxWaitNS) { ++ return false; ++ } + } + } -+ } -+ -+ public static byte getAndOrVolatileContended(final byte[] array, final int index, final byte param) { -+ int failures = 0; + -+ for (byte curr = getVolatile(array, index);;++failures) { -+ for (int i = 0; i < failures; ++i) { -+ ConcurrentUtil.backoff(); -+ } ++ return true; ++ } + -+ if (curr == (curr = compareAndExchangeVolatileContended(array, index, curr, (byte) (curr | param)))) { -+ return curr; -+ } -+ } ++ /** ++ * Returns an array of the underlying scheduling threads. ++ */ ++ public Thread[] getThreads() { ++ return this.threads.clone(); + } + -+ public static byte getAndXorVolatileContended(final byte[] array, final int index, final byte param) { -+ int failures = 0; ++ private void insertFresh(final SchedulableTick task) { ++ final TickThreadRunner[] runners = this.runners; + -+ for (byte curr = getVolatile(array, index);;++failures) { -+ for (int i = 0; i < failures; ++i) { -+ ConcurrentUtil.backoff(); -+ } ++ final int firstIdleThread = this.idleThreads.nextSetBit(0); + -+ if (curr == (curr = compareAndExchangeVolatileContended(array, index, curr, (byte) (curr ^ param)))) { -+ return curr; -+ } ++ if (firstIdleThread != -1) { ++ // push to idle thread ++ this.idleThreads.clear(firstIdleThread); ++ final TickThreadRunner runner = runners[firstIdleThread]; ++ task.awaitingLink = this.awaiting.addLast(task); ++ runner.acceptTask(task); ++ return; + } -+ } -+ -+ public static byte getAndSetVolatileContended(final byte[] array, final int index, final byte param) { -+ int failures = 0; + -+ for (byte curr = getVolatile(array, index);;++failures) { -+ for (int i = 0; i < failures; ++i) { -+ ConcurrentUtil.backoff(); -+ } ++ // try to replace the last awaiting task ++ final SchedulableTick last = this.awaiting.last(); + -+ if (curr == (curr = compareAndExchangeVolatileContended(array, index, curr, param))) { -+ return curr; -+ } -+ } -+ } ++ if (last != null && TICK_COMPARATOR_BY_TIME.compare(task, last) < 0) { ++ // need to replace the last task ++ this.awaiting.pollLast(); ++ last.awaitingLink = null; ++ task.awaitingLink = this.awaiting.addLast(task); ++ // need to add task to queue to be picked up later ++ this.queued.add(last); + -+ /* short array */ ++ final TickThreadRunner runner = last.ownedBy; ++ runner.replaceTask(task); + -+ public static short getPlain(final short[] array, final int index) { -+ return (short)SHORT_ARRAY_HANDLE.get(array, index); -+ } ++ return; ++ } + -+ public static short getOpaque(final short[] array, final int index) { -+ return (short)SHORT_ARRAY_HANDLE.getOpaque(array, index); ++ // add to queue, will be picked up later ++ this.queued.add(task); + } + -+ public static short getAcquire(final short[] array, final int index) { -+ return (short)SHORT_ARRAY_HANDLE.getAcquire(array, index); ++ private void takeTask(final TickThreadRunner runner, final SchedulableTick tick) { ++ if (!this.awaiting.remove(tick.awaitingLink)) { ++ throw new IllegalStateException("Task is not in awaiting"); ++ } ++ tick.awaitingLink = null; + } + -+ public static short getVolatile(final short[] array, final int index) { -+ return (short)SHORT_ARRAY_HANDLE.getVolatile(array, index); -+ } ++ private SchedulableTick returnTask(final TickThreadRunner runner, final SchedulableTick reschedule) { ++ if (reschedule != null) { ++ this.queued.add(reschedule); ++ } ++ final SchedulableTick ret = this.queued.poll(); ++ if (ret == null) { ++ this.idleThreads.set(runner.id); ++ } else { ++ ret.awaitingLink = this.awaiting.addLast(ret); ++ } + -+ public static void setPlain(final short[] array, final int index, final short value) { -+ SHORT_ARRAY_HANDLE.set(array, index, value); ++ return ret; + } + -+ public static void setOpaque(final short[] array, final int index, final short value) { -+ SHORT_ARRAY_HANDLE.setOpaque(array, index, value); -+ } ++ /** ++ * Schedules the specified task to be executed on this thread pool. ++ * @param task Specified task ++ * @throws IllegalStateException If the task is already scheduled ++ * @see SchedulableTick ++ */ ++ public void schedule(final SchedulableTick task) { ++ synchronized (this.scheduleLock) { ++ if (!task.tryMarkScheduled()) { ++ throw new IllegalStateException("Task " + task + " is already scheduled or cancelled"); ++ } + -+ public static void setRelease(final short[] array, final int index, final short value) { -+ SHORT_ARRAY_HANDLE.setRelease(array, index, value); -+ } ++ task.schedulerOwnedBy = this; + -+ public static void setVolatile(final short[] array, final int index, final short value) { -+ SHORT_ARRAY_HANDLE.setVolatile(array, index, value); ++ this.insertFresh(task); ++ } + } + -+ public static void setVolatileContended(final short[] array, final int index, final short param) { -+ int failures = 0; -+ -+ for (short curr = getVolatile(array, index);;++failures) { -+ for (int i = 0; i < failures; ++i) { -+ ConcurrentUtil.backoff(); ++ /** ++ * Updates the tasks scheduled start to the maximum of its current scheduled start and the specified ++ * new start. If the task is not scheduled, returns {@code false}. Otherwise, returns whether the ++ * scheduled start was updated. Undefined behavior of the specified task is scheduled in another executor. ++ * @param task Specified task ++ * @param newStart Specified new start ++ */ ++ public boolean updateTickStartToMax(final SchedulableTick task, final long newStart) { ++ synchronized (this.scheduleLock) { ++ if (TimeUtil.compareTimes(newStart, task.getScheduledStart()) <= 0) { ++ return false; + } -+ -+ if (curr == (curr = compareAndExchangeVolatileContended(array, index, curr, param))) { -+ return; ++ if (this.queued.remove(task)) { ++ task.setScheduledStart(newStart); ++ this.queued.add(task); ++ return true; + } -+ } -+ } ++ if (task.awaitingLink != null) { ++ this.awaiting.remove(task.awaitingLink); ++ task.awaitingLink = null; + -+ public static short compareAndExchangeVolatile(final short[] array, final int index, final short expect, final short update) { -+ return (short)SHORT_ARRAY_HANDLE.compareAndExchange(array, index, expect, update); -+ } ++ // re-queue task ++ task.setScheduledStart(newStart); ++ this.queued.add(task); + -+ public static short getAndAddVolatile(final short[] array, final int index, final short param) { -+ return (short)SHORT_ARRAY_HANDLE.getAndAdd(array, index, param); -+ } ++ // now we need to replace the task the runner was waiting for ++ final TickThreadRunner runner = task.ownedBy; ++ final SchedulableTick replace = this.queued.poll(); + -+ public static short getAndAndVolatile(final short[] array, final int index, final short param) { -+ return (short)SHORT_ARRAY_HANDLE.getAndBitwiseAnd(array, index, param); -+ } ++ // replace cannot be null, since we have added a task to queued ++ if (replace != task) { ++ runner.replaceTask(replace); ++ } + -+ public static short getAndOrVolatile(final short[] array, final int index, final short param) { -+ return (short)SHORT_ARRAY_HANDLE.getAndBitwiseOr(array, index, param); -+ } ++ return true; ++ } + -+ public static short getAndXorVolatile(final short[] array, final int index, final short param) { -+ return (short)SHORT_ARRAY_HANDLE.getAndBitwiseXor(array, index, param); ++ return false; ++ } + } + -+ public static short getAndSetVolatile(final short[] array, final int index, final short param) { -+ return (short)SHORT_ARRAY_HANDLE.getAndSet(array, index, param); -+ } ++ /** ++ * Returns {@code null} if the task is not scheduled, returns {@code TRUE} if the task was cancelled ++ * and was queued to execute, returns {@code FALSE} if the task was cancelled but was executing. ++ */ ++ public Boolean tryRetire(final SchedulableTick task) { ++ if (task.schedulerOwnedBy != this) { ++ return null; ++ } + -+ public static short compareAndExchangeVolatileContended(final short[] array, final int index, final short expect, final short update) { -+ return (short)SHORT_ARRAY_HANDLE.compareAndExchange(array, index, expect, update); -+ } ++ synchronized (this.scheduleLock) { ++ if (this.queued.remove(task)) { ++ // cancelled, and no runner owns it - so return ++ return Boolean.TRUE; ++ } ++ if (task.awaitingLink != null) { ++ this.awaiting.remove(task.awaitingLink); ++ task.awaitingLink = null; ++ // here we need to replace the task the runner was waiting for ++ final TickThreadRunner runner = task.ownedBy; ++ final SchedulableTick replace = this.queued.poll(); + -+ public static short getAndAddVolatileContended(final short[] array, final int index, final short param) { -+ int failures = 0; ++ if (replace == null) { ++ // nothing to replace with, set to idle ++ this.idleThreads.set(runner.id); ++ runner.forceIdle(); ++ } else { ++ runner.replaceTask(replace); ++ } + -+ for (short curr = getVolatile(array, index);;++failures) { -+ for (int i = 0; i < failures; ++i) { -+ ConcurrentUtil.backoff(); ++ return Boolean.TRUE; + } + -+ if (curr == (curr = compareAndExchangeVolatileContended(array, index, curr, (short) (curr + param)))) { -+ return curr; -+ } ++ // could not find it in queue ++ return task.tryMarkCancelled() ? Boolean.FALSE : null; + } + } + -+ public static short getAndAndVolatileContended(final short[] array, final int index, final short param) { -+ int failures = 0; -+ -+ for (short curr = getVolatile(array, index);;++failures) { -+ for (int i = 0; i < failures; ++i) { -+ ConcurrentUtil.backoff(); -+ } -+ -+ if (curr == (curr = compareAndExchangeVolatileContended(array, index, curr, (short) (curr & param)))) { -+ return curr; -+ } -+ } ++ /** ++ * Indicates that intermediate tasks are available to be executed by the task. ++ *

    ++ * Note: currently a no-op ++ *

    ++ * @param task The specified task ++ * @see SchedulableTick ++ */ ++ public void notifyTasks(final SchedulableTick task) { ++ // Not implemented + } + -+ public static short getAndOrVolatileContended(final short[] array, final int index, final short param) { -+ int failures = 0; ++ /** ++ * Represents a tickable task that can be scheduled into a {@link SchedulerThreadPool}. ++ *

    ++ * A tickable task is expected to run on a fixed interval, which is determined by ++ * the {@link SchedulerThreadPool}. ++ *

    ++ *

    ++ * A tickable task can have intermediate tasks that can be executed before its tick method is ran. Instead of ++ * the {@link SchedulerThreadPool} parking in-between ticks, the scheduler will instead drain ++ * intermediate tasks from scheduled tasks. The parsing of intermediate tasks allows the scheduler to take ++ * advantage of downtime to reduce the intermediate task load from tasks once they begin ticking. ++ *

    ++ *

    ++ * It is guaranteed that {@link #runTick()} and {@link #runTasks(BooleanSupplier)} are never ++ * invoked in parallel. ++ * It is required that when intermediate tasks are scheduled, that {@link SchedulerThreadPool#notifyTasks(SchedulableTick)} ++ * is invoked for any scheduled task - otherwise, {@link #runTasks(BooleanSupplier)} may not be invoked to ++ * parse intermediate tasks. ++ *

    ++ * @deprecated To be replaced ++ */ ++ @Deprecated ++ public static abstract class SchedulableTick { ++ private static final AtomicLong ID_GENERATOR = new AtomicLong(); ++ public final long id = ID_GENERATOR.getAndIncrement(); + -+ for (short curr = getVolatile(array, index);;++failures) { -+ for (int i = 0; i < failures; ++i) { -+ ConcurrentUtil.backoff(); -+ } ++ private static final int SCHEDULE_STATE_NOT_SCHEDULED = 0; ++ private static final int SCHEDULE_STATE_SCHEDULED = 1; ++ private static final int SCHEDULE_STATE_CANCELLED = 2; + -+ if (curr == (curr = compareAndExchangeVolatileContended(array, index, curr, (short) (curr | param)))) { -+ return curr; -+ } -+ } -+ } ++ private final AtomicInteger scheduled = new AtomicInteger(); ++ private SchedulerThreadPool schedulerOwnedBy; ++ private long scheduledStart = DEADLINE_NOT_SET; ++ private TickThreadRunner ownedBy; + -+ public static short getAndXorVolatileContended(final short[] array, final int index, final short param) { -+ int failures = 0; ++ private LinkedSortedSet.Link awaitingLink; + -+ for (short curr = getVolatile(array, index);;++failures) { -+ for (int i = 0; i < failures; ++i) { -+ ConcurrentUtil.backoff(); -+ } ++ private boolean tryMarkScheduled() { ++ return this.scheduled.compareAndSet(SCHEDULE_STATE_NOT_SCHEDULED, SCHEDULE_STATE_SCHEDULED); ++ } + -+ if (curr == (curr = compareAndExchangeVolatileContended(array, index, curr, (short) (curr ^ param)))) { -+ return curr; -+ } ++ private boolean tryMarkCancelled() { ++ return this.scheduled.compareAndSet(SCHEDULE_STATE_SCHEDULED, SCHEDULE_STATE_CANCELLED); + } -+ } + -+ public static short getAndSetVolatileContended(final short[] array, final int index, final short param) { -+ int failures = 0; ++ private boolean isScheduled() { ++ return this.scheduled.get() == SCHEDULE_STATE_SCHEDULED; ++ } + -+ for (short curr = getVolatile(array, index);;++failures) { -+ for (int i = 0; i < failures; ++i) { -+ ConcurrentUtil.backoff(); -+ } ++ protected final long getScheduledStart() { ++ return this.scheduledStart; ++ } + -+ if (curr == (curr = compareAndExchangeVolatileContended(array, index, curr, param))) { -+ return curr; -+ } ++ /** ++ * If this task is scheduled, then this may only be invoked during {@link #runTick()}, ++ * and {@link #runTasks(BooleanSupplier)} ++ */ ++ protected final void setScheduledStart(final long value) { ++ this.scheduledStart = value; + } -+ } + -+ /* int array */ ++ /** ++ * Executes the tick. ++ *

    ++ * It is the callee's responsibility to invoke {@link #setScheduledStart(long)} to adjust the start of ++ * the next tick. ++ *

    ++ * @return {@code true} if the task should continue to be scheduled, {@code false} otherwise. ++ */ ++ public abstract boolean runTick(); + -+ public static int getPlain(final int[] array, final int index) { -+ return (int)INT_ARRAY_HANDLE.get(array, index); -+ } ++ /** ++ * Returns whether this task has any intermediate tasks that can be executed. ++ */ ++ public abstract boolean hasTasks(); + -+ public static int getOpaque(final int[] array, final int index) { -+ return (int)INT_ARRAY_HANDLE.getOpaque(array, index); -+ } ++ /** ++ * Returns {@code null} if this task should not be scheduled, otherwise returns ++ * {@code Boolean.TRUE} if there are more intermediate tasks to execute and ++ * {@code Boolean.FALSE} if there are no more intermediate tasks to execute. ++ */ ++ public abstract Boolean runTasks(final BooleanSupplier canContinue); + -+ public static int getAcquire(final int[] array, final int index) { -+ return (int)INT_ARRAY_HANDLE.getAcquire(array, index); ++ @Override ++ public String toString() { ++ return "SchedulableTick:{" + ++ "class=" + this.getClass().getName() + "," + ++ "scheduled_state=" + this.scheduled.get() + "," ++ + "}"; ++ } + } + -+ public static int getVolatile(final int[] array, final int index) { -+ return (int)INT_ARRAY_HANDLE.getVolatile(array, index); -+ } ++ private static final class TickThreadRunner implements Runnable { + -+ public static void setPlain(final int[] array, final int index, final int value) { -+ INT_ARRAY_HANDLE.set(array, index, value); -+ } ++ /** ++ * There are no tasks in this thread's runqueue, so it is parked. ++ *

    ++ * stateTarget = null ++ *

    ++ */ ++ private static final int STATE_IDLE = 0; + -+ public static void setOpaque(final int[] array, final int index, final int value) { -+ INT_ARRAY_HANDLE.setOpaque(array, index, value); -+ } ++ /** ++ * The runner is waiting to tick a task, as it has no intermediate tasks to execute. ++ *

    ++ * stateTarget = the task awaiting tick ++ *

    ++ */ ++ private static final int STATE_AWAITING_TICK = 1; + -+ public static void setRelease(final int[] array, final int index, final int value) { -+ INT_ARRAY_HANDLE.setRelease(array, index, value); -+ } ++ /** ++ * The runner is executing a tick for one of the tasks that was in its runqueue. ++ *

    ++ * stateTarget = the task being ticked ++ *

    ++ */ ++ private static final int STATE_EXECUTING_TICK = 2; + -+ public static void setVolatile(final int[] array, final int index, final int value) { -+ INT_ARRAY_HANDLE.setVolatile(array, index, value); -+ } ++ public final int id; ++ public final SchedulerThreadPool scheduler; + -+ public static void setVolatileContended(final int[] array, final int index, final int param) { -+ int failures = 0; ++ private volatile Thread thread; ++ private volatile TickThreadRunnerState state = new TickThreadRunnerState(null, STATE_IDLE); ++ private static final VarHandle STATE_HANDLE = ConcurrentUtil.getVarHandle(TickThreadRunner.class, "state", TickThreadRunnerState.class); + -+ for (int curr = getVolatile(array, index);;++failures) { -+ for (int i = 0; i < failures; ++i) { -+ ConcurrentUtil.backoff(); -+ } ++ private void setStatePlain(final TickThreadRunnerState state) { ++ STATE_HANDLE.set(this, state); ++ } + -+ if (curr == (curr = compareAndExchangeVolatileContended(array, index, curr, param))) { -+ return; -+ } ++ private void setStateOpaque(final TickThreadRunnerState state) { ++ STATE_HANDLE.setOpaque(this, state); + } -+ } + -+ public static int compareAndExchangeVolatile(final int[] array, final int index, final int expect, final int update) { -+ return (int)INT_ARRAY_HANDLE.compareAndExchange(array, index, expect, update); -+ } ++ private void setStateVolatile(final TickThreadRunnerState state) { ++ STATE_HANDLE.setVolatile(this, state); ++ } + -+ public static int getAndAddVolatile(final int[] array, final int index, final int param) { -+ return (int)INT_ARRAY_HANDLE.getAndAdd(array, index, param); -+ } ++ private static record TickThreadRunnerState(SchedulableTick stateTarget, int state) {} + -+ public static int getAndAndVolatile(final int[] array, final int index, final int param) { -+ return (int)INT_ARRAY_HANDLE.getAndBitwiseAnd(array, index, param); -+ } ++ public TickThreadRunner(final int id, final SchedulerThreadPool scheduler) { ++ this.id = id; ++ this.scheduler = scheduler; ++ } + -+ public static int getAndOrVolatile(final int[] array, final int index, final int param) { -+ return (int)INT_ARRAY_HANDLE.getAndBitwiseOr(array, index, param); -+ } ++ private Thread getRunnerThread() { ++ return this.thread; ++ } + -+ public static int getAndXorVolatile(final int[] array, final int index, final int param) { -+ return (int)INT_ARRAY_HANDLE.getAndBitwiseXor(array, index, param); -+ } ++ private void acceptTask(final SchedulableTick task) { ++ if (task.ownedBy != null) { ++ throw new IllegalStateException("Already owned by another runner"); ++ } ++ task.ownedBy = this; ++ final TickThreadRunnerState state = this.state; ++ if (state.state != STATE_IDLE) { ++ throw new IllegalStateException("Cannot accept task in state " + state); ++ } ++ this.setStateVolatile(new TickThreadRunnerState(task, STATE_AWAITING_TICK)); ++ LockSupport.unpark(this.getRunnerThread()); ++ } + -+ public static int getAndSetVolatile(final int[] array, final int index, final int param) { -+ return (int)INT_ARRAY_HANDLE.getAndSet(array, index, param); -+ } ++ private void replaceTask(final SchedulableTick task) { ++ final TickThreadRunnerState state = this.state; ++ if (state.state != STATE_AWAITING_TICK) { ++ throw new IllegalStateException("Cannot replace task in state " + state); ++ } ++ if (task.ownedBy != null) { ++ throw new IllegalStateException("Already owned by another runner"); ++ } ++ task.ownedBy = this; + -+ public static int compareAndExchangeVolatileContended(final int[] array, final int index, final int expect, final int update) { -+ return (int)INT_ARRAY_HANDLE.compareAndExchange(array, index, expect, update); -+ } ++ state.stateTarget.ownedBy = null; + -+ public static int getAndAddVolatileContended(final int[] array, final int index, final int param) { -+ int failures = 0; ++ this.setStateVolatile(new TickThreadRunnerState(task, STATE_AWAITING_TICK)); ++ LockSupport.unpark(this.getRunnerThread()); ++ } + -+ for (int curr = getVolatile(array, index);;++failures) { -+ for (int i = 0; i < failures; ++i) { -+ ConcurrentUtil.backoff(); ++ private void forceIdle() { ++ final TickThreadRunnerState state = this.state; ++ if (state.state != STATE_AWAITING_TICK) { ++ throw new IllegalStateException("Cannot replace task in state " + state); + } ++ state.stateTarget.ownedBy = null; ++ this.setStateOpaque(new TickThreadRunnerState(null, STATE_IDLE)); ++ // no need to unpark ++ } + -+ if (curr == (curr = compareAndExchangeVolatileContended(array, index, curr, (int) (curr + param)))) { -+ return curr; ++ private boolean takeTask(final TickThreadRunnerState state, final SchedulableTick task) { ++ synchronized (this.scheduler.scheduleLock) { ++ if (this.state != state) { ++ return false; ++ } ++ this.setStatePlain(new TickThreadRunnerState(task, STATE_EXECUTING_TICK)); ++ this.scheduler.takeTask(this, task); ++ return true; + } + } -+ } + -+ public static int getAndAndVolatileContended(final int[] array, final int index, final int param) { -+ int failures = 0; -+ -+ for (int curr = getVolatile(array, index);;++failures) { -+ for (int i = 0; i < failures; ++i) { -+ ConcurrentUtil.backoff(); -+ } ++ private void returnTask(final SchedulableTick task, final boolean reschedule) { ++ synchronized (this.scheduler.scheduleLock) { ++ task.ownedBy = null; + -+ if (curr == (curr = compareAndExchangeVolatileContended(array, index, curr, (int) (curr & param)))) { -+ return curr; ++ final SchedulableTick newWait = this.scheduler.returnTask(this, reschedule && task.isScheduled() ? task : null); ++ if (newWait == null) { ++ this.setStatePlain(new TickThreadRunnerState(null, STATE_IDLE)); ++ } else { ++ if (newWait.ownedBy != null) { ++ throw new IllegalStateException("Already owned by another runner"); ++ } ++ newWait.ownedBy = this; ++ this.setStatePlain(new TickThreadRunnerState(newWait, STATE_AWAITING_TICK)); ++ } + } + } -+ } + -+ public static int getAndOrVolatileContended(final int[] array, final int index, final int param) { -+ int failures = 0; ++ @Override ++ public void run() { ++ this.thread = Thread.currentThread(); + -+ for (int curr = getVolatile(array, index);;++failures) { -+ for (int i = 0; i < failures; ++i) { -+ ConcurrentUtil.backoff(); -+ } ++ main_state_loop: ++ for (;;) { ++ final TickThreadRunnerState startState = this.state; ++ final int startStateType = startState.state; ++ final SchedulableTick startStateTask = startState.stateTarget; + -+ if (curr == (curr = compareAndExchangeVolatileContended(array, index, curr, (int) (curr | param)))) { -+ return curr; -+ } -+ } -+ } ++ if (this.scheduler.halted) { ++ return; ++ } + -+ public static int getAndXorVolatileContended(final int[] array, final int index, final int param) { -+ int failures = 0; ++ switch (startStateType) { ++ case STATE_IDLE: { ++ while (this.state.state == STATE_IDLE) { ++ LockSupport.park(); ++ if (this.scheduler.halted) { ++ return; ++ } ++ } ++ continue main_state_loop; ++ } + -+ for (int curr = getVolatile(array, index);;++failures) { -+ for (int i = 0; i < failures; ++i) { -+ ConcurrentUtil.backoff(); -+ } ++ case STATE_AWAITING_TICK: { ++ final long deadline = startStateTask.getScheduledStart(); ++ for (;;) { ++ if (this.state != startState) { ++ continue main_state_loop; ++ } ++ final long diff = deadline - System.nanoTime(); ++ if (diff <= 0L) { ++ break; ++ } ++ LockSupport.parkNanos(startState, diff); ++ if (this.scheduler.halted) { ++ return; ++ } ++ } + -+ if (curr == (curr = compareAndExchangeVolatileContended(array, index, curr, (int) (curr ^ param)))) { -+ return curr; -+ } -+ } -+ } ++ if (!this.takeTask(startState, startStateTask)) { ++ continue main_state_loop; ++ } + -+ public static int getAndSetVolatileContended(final int[] array, final int index, final int param) { -+ int failures = 0; ++ // TODO exception handling ++ final boolean reschedule = startStateTask.runTick(); + -+ for (int curr = getVolatile(array, index);;++failures) { -+ for (int i = 0; i < failures; ++i) { -+ ConcurrentUtil.backoff(); -+ } ++ this.returnTask(startStateTask, reschedule); + -+ if (curr == (curr = compareAndExchangeVolatileContended(array, index, curr, param))) { -+ return curr; ++ continue main_state_loop; ++ } ++ ++ case STATE_EXECUTING_TICK: { ++ throw new IllegalStateException("Tick execution must be set by runner thread, not by any other thread"); ++ } ++ ++ default: { ++ throw new IllegalStateException("Unknown state: " + startState); ++ } ++ } + } + } + } ++} +diff --git a/src/main/java/ca/spottedleaf/concurrentutil/set/LinkedSortedSet.java b/src/main/java/ca/spottedleaf/concurrentutil/set/LinkedSortedSet.java +new file mode 100644 +index 0000000000000000000000000000000000000000..82c4c11b0b564c97ac92bd5f54e3754a7ba95184 +--- /dev/null ++++ b/src/main/java/ca/spottedleaf/concurrentutil/set/LinkedSortedSet.java +@@ -0,0 +1,270 @@ ++package ca.spottedleaf.concurrentutil.set; + -+ /* long array */ ++import java.util.Comparator; ++import java.util.Iterator; ++import java.util.NoSuchElementException; + -+ public static long getPlain(final long[] array, final int index) { -+ return (long)LONG_ARRAY_HANDLE.get(array, index); -+ } ++public final class LinkedSortedSet implements Iterable { + -+ public static long getOpaque(final long[] array, final int index) { -+ return (long)LONG_ARRAY_HANDLE.getOpaque(array, index); -+ } ++ public final Comparator comparator; ++ ++ private Link head; ++ private Link tail; + -+ public static long getAcquire(final long[] array, final int index) { -+ return (long)LONG_ARRAY_HANDLE.getAcquire(array, index); ++ public LinkedSortedSet() { ++ this((Comparator)Comparator.naturalOrder()); + } + -+ public static long getVolatile(final long[] array, final int index) { -+ return (long)LONG_ARRAY_HANDLE.getVolatile(array, index); ++ public LinkedSortedSet(final Comparator comparator) { ++ this.comparator = comparator; + } + -+ public static void setPlain(final long[] array, final int index, final long value) { -+ LONG_ARRAY_HANDLE.set(array, index, value); ++ public void clear() { ++ this.head = this.tail = null; + } + -+ public static void setOpaque(final long[] array, final int index, final long value) { -+ LONG_ARRAY_HANDLE.setOpaque(array, index, value); ++ public boolean isEmpty() { ++ return this.head == null; + } + -+ public static void setRelease(final long[] array, final int index, final long value) { -+ LONG_ARRAY_HANDLE.setRelease(array, index, value); ++ public E first() { ++ final Link head = this.head; ++ return head == null ? null : head.element; + } + -+ public static void setVolatile(final long[] array, final int index, final long value) { -+ LONG_ARRAY_HANDLE.setVolatile(array, index, value); ++ public E last() { ++ final Link tail = this.tail; ++ return tail == null ? null : tail.element; + } + -+ public static void setVolatileContended(final long[] array, final int index, final long param) { -+ int failures = 0; -+ -+ for (long curr = getVolatile(array, index);;++failures) { -+ for (int i = 0; i < failures; ++i) { -+ ConcurrentUtil.backoff(); -+ } -+ -+ if (curr == (curr = compareAndExchangeVolatileContended(array, index, curr, param))) { -+ return; ++ public boolean containsFirst(final E element) { ++ final Comparator comparator = this.comparator; ++ for (Link curr = this.head; curr != null; curr = curr.next) { ++ if (comparator.compare(element, curr.element) == 0) { ++ return true; + } + } ++ return false; + } + -+ public static long compareAndExchangeVolatile(final long[] array, final int index, final long expect, final long update) { -+ return (long)LONG_ARRAY_HANDLE.compareAndExchange(array, index, expect, update); ++ public boolean containsLast(final E element) { ++ final Comparator comparator = this.comparator; ++ for (Link curr = this.tail; curr != null; curr = curr.prev) { ++ if (comparator.compare(element, curr.element) == 0) { ++ return true; ++ } ++ } ++ return false; + } + -+ public static long getAndAddVolatile(final long[] array, final int index, final long param) { -+ return (long)LONG_ARRAY_HANDLE.getAndAdd(array, index, param); -+ } ++ private void removeNode(final Link node) { ++ final Link prev = node.prev; ++ final Link next = node.next; + -+ public static long getAndAndVolatile(final long[] array, final int index, final long param) { -+ return (long)LONG_ARRAY_HANDLE.getAndBitwiseAnd(array, index, param); -+ } ++ // help GC ++ node.element = null; ++ node.prev = null; ++ node.next = null; + -+ public static long getAndOrVolatile(final long[] array, final int index, final long param) { -+ return (long)LONG_ARRAY_HANDLE.getAndBitwiseOr(array, index, param); -+ } ++ if (prev == null) { ++ this.head = next; ++ } else { ++ prev.next = next; ++ } + -+ public static long getAndXorVolatile(final long[] array, final int index, final long param) { -+ return (long)LONG_ARRAY_HANDLE.getAndBitwiseXor(array, index, param); ++ if (next == null) { ++ this.tail = prev; ++ } else { ++ next.prev = prev; ++ } + } + -+ public static long getAndSetVolatile(final long[] array, final int index, final long param) { -+ return (long)LONG_ARRAY_HANDLE.getAndSet(array, index, param); -+ } ++ public boolean remove(final Link link) { ++ if (link.element == null) { ++ return false; ++ } + -+ public static long compareAndExchangeVolatileContended(final long[] array, final int index, final long expect, final long update) { -+ return (long)LONG_ARRAY_HANDLE.compareAndExchange(array, index, expect, update); ++ this.removeNode(link); ++ return true; + } + -+ public static long getAndAddVolatileContended(final long[] array, final int index, final long param) { -+ int failures = 0; -+ -+ for (long curr = getVolatile(array, index);;++failures) { -+ for (int i = 0; i < failures; ++i) { -+ ConcurrentUtil.backoff(); ++ public boolean removeFirst(final E element) { ++ final Comparator comparator = this.comparator; ++ for (Link curr = this.head; curr != null; curr = curr.next) { ++ if (comparator.compare(element, curr.element) == 0) { ++ this.removeNode(curr); ++ return true; + } ++ } ++ return false; ++ } + -+ if (curr == (curr = compareAndExchangeVolatileContended(array, index, curr, (long) (curr + param)))) { -+ return curr; ++ public boolean removeLast(final E element) { ++ final Comparator comparator = this.comparator; ++ for (Link curr = this.tail; curr != null; curr = curr.prev) { ++ if (comparator.compare(element, curr.element) == 0) { ++ this.removeNode(curr); ++ return true; + } + } ++ return false; + } + -+ public static long getAndAndVolatileContended(final long[] array, final int index, final long param) { -+ int failures = 0; ++ @Override ++ public Iterator iterator() { ++ return new Iterator<>() { ++ private Link next = LinkedSortedSet.this.head; + -+ for (long curr = getVolatile(array, index);;++failures) { -+ for (int i = 0; i < failures; ++i) { -+ ConcurrentUtil.backoff(); ++ @Override ++ public boolean hasNext() { ++ return this.next != null; + } + -+ if (curr == (curr = compareAndExchangeVolatileContended(array, index, curr, (long) (curr & param)))) { -+ return curr; ++ @Override ++ public E next() { ++ final Link next = this.next; ++ if (next == null) { ++ throw new NoSuchElementException(); ++ } ++ this.next = next.next; ++ return next.element; + } -+ } ++ }; + } + -+ public static long getAndOrVolatileContended(final long[] array, final int index, final long param) { -+ int failures = 0; ++ public E pollFirst() { ++ final Link head = this.head; ++ if (head == null) { ++ return null; ++ } + -+ for (long curr = getVolatile(array, index);;++failures) { -+ for (int i = 0; i < failures; ++i) { -+ ConcurrentUtil.backoff(); -+ } ++ final E ret = head.element; ++ final Link next = head.next; + -+ if (curr == (curr = compareAndExchangeVolatileContended(array, index, curr, (long) (curr | param)))) { -+ return curr; -+ } ++ // unlink head ++ this.head = next; ++ if (next == null) { ++ this.tail = null; ++ } else { ++ next.prev = null; + } ++ ++ // help GC ++ head.element = null; ++ head.next = null; ++ ++ return ret; + } + -+ public static long getAndXorVolatileContended(final long[] array, final int index, final long param) { -+ int failures = 0; ++ public E pollLast() { ++ final Link tail = this.tail; ++ if (tail == null) { ++ return null; ++ } + -+ for (long curr = getVolatile(array, index);;++failures) { -+ for (int i = 0; i < failures; ++i) { -+ ConcurrentUtil.backoff(); -+ } ++ final E ret = tail.element; ++ final Link prev = tail.prev; + -+ if (curr == (curr = compareAndExchangeVolatileContended(array, index, curr, (long) (curr ^ param)))) { -+ return curr; -+ } ++ // unlink tail ++ this.tail = prev; ++ if (prev == null) { ++ this.head = null; ++ } else { ++ prev.next = null; + } ++ ++ // help GC ++ tail.element = null; ++ tail.prev = null; ++ ++ return ret; + } + -+ public static long getAndSetVolatileContended(final long[] array, final int index, final long param) { -+ int failures = 0; ++ public Link addLast(final E element) { ++ final Comparator comparator = this.comparator; + -+ for (long curr = getVolatile(array, index);;++failures) { -+ for (int i = 0; i < failures; ++i) { -+ ConcurrentUtil.backoff(); ++ Link curr = this.tail; ++ if (curr != null) { ++ int compare; ++ ++ while ((compare = comparator.compare(element, curr.element)) < 0) { ++ Link prev = curr; ++ curr = curr.prev; ++ if (curr != null) { ++ continue; ++ } ++ return this.head = prev.prev = new Link<>(element, null, prev); + } + -+ if (curr == (curr = compareAndExchangeVolatileContended(array, index, curr, param))) { -+ return curr; ++ if (compare != 0) { ++ // insert after curr ++ final Link next = curr.next; ++ final Link insert = new Link<>(element, curr, next); ++ curr.next = insert; ++ ++ if (next == null) { ++ this.tail = insert; ++ } else { ++ next.prev = insert; ++ } ++ return insert; + } ++ ++ return null; ++ } else { ++ return this.head = this.tail = new Link<>(element); + } + } + -+ /* boolean array */ ++ public Link addFirst(final E element) { ++ final Comparator comparator = this.comparator; + -+ public static boolean getPlain(final boolean[] array, final int index) { -+ return (boolean)BOOLEAN_ARRAY_HANDLE.get(array, index); -+ } ++ Link curr = this.head; ++ if (curr != null) { ++ int compare; + -+ public static boolean getOpaque(final boolean[] array, final int index) { -+ return (boolean)BOOLEAN_ARRAY_HANDLE.getOpaque(array, index); -+ } ++ while ((compare = comparator.compare(element, curr.element)) > 0) { ++ Link prev = curr; ++ curr = curr.next; ++ if (curr != null) { ++ continue; ++ } ++ return this.tail = prev.next = new Link<>(element, prev, null); ++ } + -+ public static boolean getAcquire(final boolean[] array, final int index) { -+ return (boolean)BOOLEAN_ARRAY_HANDLE.getAcquire(array, index); -+ } ++ if (compare != 0) { ++ // insert before curr ++ final Link prev = curr.prev; ++ final Link insert = new Link<>(element, prev, curr); ++ curr.prev = insert; + -+ public static boolean getVolatile(final boolean[] array, final int index) { -+ return (boolean)BOOLEAN_ARRAY_HANDLE.getVolatile(array, index); -+ } ++ if (prev == null) { ++ this.head = insert; ++ } else { ++ prev.next = insert; ++ } ++ return insert; ++ } + -+ public static void setPlain(final boolean[] array, final int index, final boolean value) { -+ BOOLEAN_ARRAY_HANDLE.set(array, index, value); ++ return null; ++ } else { ++ return this.head = this.tail = new Link<>(element); ++ } + } + -+ public static void setOpaque(final boolean[] array, final int index, final boolean value) { -+ BOOLEAN_ARRAY_HANDLE.setOpaque(array, index, value); -+ } ++ public static final class Link { ++ private E element; ++ private Link prev; ++ private Link next; + -+ public static void setRelease(final boolean[] array, final int index, final boolean value) { -+ BOOLEAN_ARRAY_HANDLE.setRelease(array, index, value); -+ } ++ private Link(final E element) { ++ this.element = element; ++ } + -+ public static void setVolatile(final boolean[] array, final int index, final boolean value) { -+ BOOLEAN_ARRAY_HANDLE.setVolatile(array, index, value); ++ private Link(final E element, final Link prev, final Link next) { ++ this.element = element; ++ this.prev = prev; ++ this.next = next; ++ } + } ++} +diff --git a/src/main/java/ca/spottedleaf/concurrentutil/set/LinkedUnsortedList.java b/src/main/java/ca/spottedleaf/concurrentutil/set/LinkedUnsortedList.java +new file mode 100644 +index 0000000000000000000000000000000000000000..bd8eb4f25d1dee00fbf9c05c14b0d94c5c641a55 +--- /dev/null ++++ b/src/main/java/ca/spottedleaf/concurrentutil/set/LinkedUnsortedList.java +@@ -0,0 +1,204 @@ ++package ca.spottedleaf.concurrentutil.set; + -+ public static void setVolatileContended(final boolean[] array, final int index, final boolean param) { -+ int failures = 0; ++import java.util.Iterator; ++import java.util.NoSuchElementException; ++import java.util.Objects; + -+ for (boolean curr = getVolatile(array, index);;++failures) { -+ for (int i = 0; i < failures; ++i) { -+ ConcurrentUtil.backoff(); -+ } ++public final class LinkedUnsortedList implements Iterable { + -+ if (curr == (curr = compareAndExchangeVolatileContended(array, index, curr, param))) { -+ return; -+ } -+ } -+ } ++ private Link head; ++ private Link tail; + -+ public static boolean compareAndExchangeVolatile(final boolean[] array, final int index, final boolean expect, final boolean update) { -+ return (boolean)BOOLEAN_ARRAY_HANDLE.compareAndExchange(array, index, expect, update); -+ } ++ public LinkedUnsortedList() {} + -+ public static boolean getAndOrVolatile(final boolean[] array, final int index, final boolean param) { -+ return (boolean)BOOLEAN_ARRAY_HANDLE.getAndBitwiseOr(array, index, param); ++ public void clear() { ++ this.head = this.tail = null; + } + -+ public static boolean getAndXorVolatile(final boolean[] array, final int index, final boolean param) { -+ return (boolean)BOOLEAN_ARRAY_HANDLE.getAndBitwiseXor(array, index, param); ++ public boolean isEmpty() { ++ return this.head == null; + } + -+ public static boolean getAndSetVolatile(final boolean[] array, final int index, final boolean param) { -+ return (boolean)BOOLEAN_ARRAY_HANDLE.getAndSet(array, index, param); ++ public E first() { ++ final Link head = this.head; ++ return head == null ? null : head.element; + } + -+ public static boolean compareAndExchangeVolatileContended(final boolean[] array, final int index, final boolean expect, final boolean update) { -+ return (boolean)BOOLEAN_ARRAY_HANDLE.compareAndExchange(array, index, expect, update); ++ public E last() { ++ final Link tail = this.tail; ++ return tail == null ? null : tail.element; + } + -+ public static boolean getAndAndVolatileContended(final boolean[] array, final int index, final boolean param) { -+ int failures = 0; -+ -+ for (boolean curr = getVolatile(array, index);;++failures) { -+ for (int i = 0; i < failures; ++i) { -+ ConcurrentUtil.backoff(); ++ public boolean containsFirst(final E element) { ++ for (Link curr = this.head; curr != null; curr = curr.next) { ++ if (Objects.equals(element, curr.element)) { ++ return true; + } ++ } ++ return false; ++ } + -+ if (curr == (curr = compareAndExchangeVolatileContended(array, index, curr, (boolean) (curr & param)))) { -+ return curr; ++ public boolean containsLast(final E element) { ++ for (Link curr = this.tail; curr != null; curr = curr.prev) { ++ if (Objects.equals(element, curr.element)) { ++ return true; + } + } ++ return false; + } + -+ public static boolean getAndOrVolatileContended(final boolean[] array, final int index, final boolean param) { -+ int failures = 0; ++ private void removeNode(final Link node) { ++ final Link prev = node.prev; ++ final Link next = node.next; + -+ for (boolean curr = getVolatile(array, index);;++failures) { -+ for (int i = 0; i < failures; ++i) { -+ ConcurrentUtil.backoff(); -+ } ++ // help GC ++ node.element = null; ++ node.prev = null; ++ node.next = null; + -+ if (curr == (curr = compareAndExchangeVolatileContended(array, index, curr, (boolean) (curr | param)))) { -+ return curr; -+ } ++ if (prev == null) { ++ this.head = next; ++ } else { ++ prev.next = next; ++ } ++ ++ if (next == null) { ++ this.tail = prev; ++ } else { ++ next.prev = prev; + } + } + -+ public static boolean getAndXorVolatileContended(final boolean[] array, final int index, final boolean param) { -+ int failures = 0; ++ public boolean remove(final Link link) { ++ if (link.element == null) { ++ return false; ++ } + -+ for (boolean curr = getVolatile(array, index);;++failures) { -+ for (int i = 0; i < failures; ++i) { -+ ConcurrentUtil.backoff(); -+ } ++ this.removeNode(link); ++ return true; ++ } + -+ if (curr == (curr = compareAndExchangeVolatileContended(array, index, curr, (boolean) (curr ^ param)))) { -+ return curr; ++ public boolean removeFirst(final E element) { ++ for (Link curr = this.head; curr != null; curr = curr.next) { ++ if (Objects.equals(element, curr.element)) { ++ this.removeNode(curr); ++ return true; + } + } ++ return false; + } + -+ public static boolean getAndSetVolatileContended(final boolean[] array, final int index, final boolean param) { -+ int failures = 0; -+ -+ for (boolean curr = getVolatile(array, index);;++failures) { -+ for (int i = 0; i < failures; ++i) { -+ ConcurrentUtil.backoff(); -+ } -+ -+ if (curr == (curr = compareAndExchangeVolatileContended(array, index, curr, param))) { -+ return curr; ++ public boolean removeLast(final E element) { ++ for (Link curr = this.tail; curr != null; curr = curr.prev) { ++ if (Objects.equals(element, curr.element)) { ++ this.removeNode(curr); ++ return true; + } + } ++ return false; + } + -+ @SuppressWarnings("unchecked") -+ public static T getPlain(final T[] array, final int index) { -+ final Object ret = OBJECT_ARRAY_HANDLE.get((Object[])array, index); -+ return (T)ret; -+ } ++ @Override ++ public Iterator iterator() { ++ return new Iterator<>() { ++ private Link next = LinkedUnsortedList.this.head; + -+ @SuppressWarnings("unchecked") -+ public static T getOpaque(final T[] array, final int index) { -+ final Object ret = OBJECT_ARRAY_HANDLE.getOpaque((Object[])array, index); -+ return (T)ret; -+ } ++ @Override ++ public boolean hasNext() { ++ return this.next != null; ++ } + -+ @SuppressWarnings("unchecked") -+ public static T getAcquire(final T[] array, final int index) { -+ final Object ret = OBJECT_ARRAY_HANDLE.getAcquire((Object[])array, index); -+ return (T)ret; ++ @Override ++ public E next() { ++ final Link next = this.next; ++ if (next == null) { ++ throw new NoSuchElementException(); ++ } ++ this.next = next.next; ++ return next.element; ++ } ++ }; + } + -+ @SuppressWarnings("unchecked") -+ public static T getVolatile(final T[] array, final int index) { -+ final Object ret = OBJECT_ARRAY_HANDLE.getVolatile((Object[])array, index); -+ return (T)ret; -+ } ++ public E pollFirst() { ++ final Link head = this.head; ++ if (head == null) { ++ return null; ++ } + -+ public static void setPlain(final T[] array, final int index, final T value) { -+ OBJECT_ARRAY_HANDLE.set((Object[])array, index, (Object)value); -+ } ++ final E ret = head.element; ++ final Link next = head.next; + -+ public static void setOpaque(final T[] array, final int index, final T value) { -+ OBJECT_ARRAY_HANDLE.setOpaque((Object[])array, index, (Object)value); -+ } ++ // unlink head ++ this.head = next; ++ if (next == null) { ++ this.tail = null; ++ } else { ++ next.prev = null; ++ } + -+ public static void setRelease(final T[] array, final int index, final T value) { -+ OBJECT_ARRAY_HANDLE.setRelease((Object[])array, index, (Object)value); -+ } ++ // help GC ++ head.element = null; ++ head.next = null; + -+ public static void setVolatile(final T[] array, final int index, final T value) { -+ OBJECT_ARRAY_HANDLE.setVolatile((Object[])array, index, (Object)value); ++ return ret; + } + -+ public static void setVolatileContended(final T[] array, final int index, final T param) { -+ int failures = 0; ++ public E pollLast() { ++ final Link tail = this.tail; ++ if (tail == null) { ++ return null; ++ } + -+ for (T curr = getVolatile(array, index);;++failures) { -+ for (int i = 0; i < failures; ++i) { -+ ConcurrentUtil.backoff(); -+ } ++ final E ret = tail.element; ++ final Link prev = tail.prev; + -+ if (curr == (curr = compareAndExchangeVolatileContended(array, index, curr, param))) { -+ return; -+ } ++ // unlink tail ++ this.tail = prev; ++ if (prev == null) { ++ this.head = null; ++ } else { ++ prev.next = null; + } -+ } + -+ @SuppressWarnings("unchecked") -+ public static T compareAndExchangeVolatile(final T[] array, final int index, final T expect, final T update) { -+ final Object ret = OBJECT_ARRAY_HANDLE.compareAndExchange((Object[])array, index, (Object)expect, (Object)update); -+ return (T)ret; ++ // help GC ++ tail.element = null; ++ tail.prev = null; ++ ++ return ret; + } + -+ @SuppressWarnings("unchecked") -+ public static T getAndSetVolatile(final T[] array, final int index, final T param) { -+ final Object ret = BYTE_ARRAY_HANDLE.getAndSet((Object[])array, index, (Object)param); -+ return (T)ret; ++ public Link addLast(final E element) { ++ final Link curr = this.tail; ++ if (curr != null) { ++ return this.tail = new Link<>(element, curr, null); ++ } else { ++ return this.head = this.tail = new Link<>(element); ++ } + } + -+ @SuppressWarnings("unchecked") -+ public static T compareAndExchangeVolatileContended(final T[] array, final int index, final T expect, final T update) { -+ final Object ret = OBJECT_ARRAY_HANDLE.compareAndExchange((Object[])array, index, (Object)expect, (Object)update); -+ return (T)ret; ++ public Link addFirst(final E element) { ++ final Link curr = this.head; ++ if (curr != null) { ++ return this.head = new Link<>(element, null, curr); ++ } else { ++ return this.head = this.tail = new Link<>(element); ++ } + } + -+ public static T getAndSetVolatileContended(final T[] array, final int index, final T param) { -+ int failures = 0; ++ public static final class Link { ++ private E element; ++ private Link prev; ++ private Link next; + -+ for (T curr = getVolatile(array, index);;++failures) { -+ for (int i = 0; i < failures; ++i) { -+ ConcurrentUtil.backoff(); -+ } ++ private Link(final E element) { ++ this.element = element; ++ } + -+ if (curr == (curr = compareAndExchangeVolatileContended(array, index, curr, param))) { -+ return curr; -+ } ++ private Link(final E element, final Link prev, final Link next) { ++ this.element = element; ++ this.prev = prev; ++ this.next = next; + } + } +} @@ -10185,10 +10075,10 @@ index 0000000000000000000000000000000000000000..4e61c477a56e645228d5a2015c268169 +} diff --git a/src/main/java/ca/spottedleaf/concurrentutil/util/IntegerUtil.java b/src/main/java/ca/spottedleaf/concurrentutil/util/IntegerUtil.java new file mode 100644 -index 0000000000000000000000000000000000000000..77699c5fa9681f9ec7aa05cbb50eb60828e193ab +index 0000000000000000000000000000000000000000..9d7b9b8158cd01d12adbd7896ff77bee9828e101 --- /dev/null +++ b/src/main/java/ca/spottedleaf/concurrentutil/util/IntegerUtil.java -@@ -0,0 +1,176 @@ +@@ -0,0 +1,196 @@ +package ca.spottedleaf.concurrentutil.util; + +public final class IntegerUtil { @@ -10361,11 +10251,183 @@ index 0000000000000000000000000000000000000000..77699c5fa9681f9ec7aa05cbb50eb608 + return (mask ^ val) - mask; // if val < 0, then (0 ^ val) - 0 else (-1 ^ val) + 1 + } + ++ // https://lemire.me/blog/2019/02/08/faster-remainders-when-the-divisor-is-a-constant-beating-compilers-and-libdivide ++ /** ++ * ++ * Usage: ++ *
    ++     * {@code
    ++     *     static final long mult = getSimpleMultiplier(divisor, bits);
    ++     *     long x = ...;
    ++     *     long magic = x * mult;
    ++     *     long divQ = magic >>> bits;
    ++     *     long divR = ((magic & ((1 << bits) - 1)) * divisor) >>> bits;
    ++     * }
    ++     * 
    ++ * ++ * @param bits The number of bits of precision for the returned result ++ */ ++ public static long getUnsignedDivisorMagic(final long divisor, final int bits) { ++ return (((1L << bits) - 1L) / divisor) + 1; ++ } ++ + private IntegerUtil() { + throw new RuntimeException(); + } +} \ No newline at end of file +diff --git a/src/main/java/ca/spottedleaf/concurrentutil/util/Priority.java b/src/main/java/ca/spottedleaf/concurrentutil/util/Priority.java +new file mode 100644 +index 0000000000000000000000000000000000000000..2919bbaa07b70f182438c3be8f9ebbe0649809b6 +--- /dev/null ++++ b/src/main/java/ca/spottedleaf/concurrentutil/util/Priority.java +@@ -0,0 +1,145 @@ ++package ca.spottedleaf.concurrentutil.util; ++ ++public enum Priority { ++ ++ /** ++ * Priority value indicating the task has completed or is being completed. ++ * This priority cannot be used to schedule tasks. ++ */ ++ COMPLETING(-1), ++ ++ /** ++ * Absolute highest priority, should only be used for when a task is blocking a time-critical thread. ++ */ ++ BLOCKING(), ++ ++ /** ++ * Should only be used for urgent but not time-critical tasks. ++ */ ++ HIGHEST(), ++ ++ /** ++ * Two priorities above normal. ++ */ ++ HIGHER(), ++ ++ /** ++ * One priority above normal. ++ */ ++ HIGH(), ++ ++ /** ++ * Default priority. ++ */ ++ NORMAL(), ++ ++ /** ++ * One priority below normal. ++ */ ++ LOW(), ++ ++ /** ++ * Two priorities below normal. ++ */ ++ LOWER(), ++ ++ /** ++ * Use for tasks that should eventually execute, but are not needed to. ++ */ ++ LOWEST(), ++ ++ /** ++ * Use for tasks that can be delayed indefinitely. ++ */ ++ IDLE(); ++ ++ // returns whether the priority can be scheduled ++ public static boolean isValidPriority(final Priority priority) { ++ return priority != null && priority != priority.COMPLETING; ++ } ++ ++ // returns the higher priority of the two ++ public static Priority max(final Priority p1, final Priority p2) { ++ return p1.isHigherOrEqualPriority(p2) ? p1 : p2; ++ } ++ ++ // returns the lower priroity of the two ++ public static Priority min(final Priority p1, final Priority p2) { ++ return p1.isLowerOrEqualPriority(p2) ? p1 : p2; ++ } ++ ++ public boolean isHigherOrEqualPriority(final Priority than) { ++ return this.priority <= than.priority; ++ } ++ ++ public boolean isHigherPriority(final Priority than) { ++ return this.priority < than.priority; ++ } ++ ++ public boolean isLowerOrEqualPriority(final Priority than) { ++ return this.priority >= than.priority; ++ } ++ ++ public boolean isLowerPriority(final Priority than) { ++ return this.priority > than.priority; ++ } ++ ++ public boolean isHigherOrEqualPriority(final int than) { ++ return this.priority <= than; ++ } ++ ++ public boolean isHigherPriority(final int than) { ++ return this.priority < than; ++ } ++ ++ public boolean isLowerOrEqualPriority(final int than) { ++ return this.priority >= than; ++ } ++ ++ public boolean isLowerPriority(final int than) { ++ return this.priority > than; ++ } ++ ++ public static boolean isHigherOrEqualPriority(final int priority, final int than) { ++ return priority <= than; ++ } ++ ++ public static boolean isHigherPriority(final int priority, final int than) { ++ return priority < than; ++ } ++ ++ public static boolean isLowerOrEqualPriority(final int priority, final int than) { ++ return priority >= than; ++ } ++ ++ public static boolean isLowerPriority(final int priority, final int than) { ++ return priority > than; ++ } ++ ++ static final Priority[] PRIORITIES = Priority.values(); ++ ++ /** includes special priorities */ ++ public static final int TOTAL_PRIORITIES = PRIORITIES.length; ++ ++ public static final int TOTAL_SCHEDULABLE_PRIORITIES = TOTAL_PRIORITIES - 1; ++ ++ public static Priority getPriority(final int priority) { ++ return PRIORITIES[priority + 1]; ++ } ++ ++ private static int priorityCounter; ++ ++ private static int nextCounter() { ++ return priorityCounter++; ++ } ++ ++ public final int priority; ++ ++ private Priority() { ++ this(nextCounter()); ++ } ++ ++ private Priority(final int priority) { ++ this.priority = priority; ++ } ++} +\ No newline at end of file diff --git a/src/main/java/ca/spottedleaf/concurrentutil/util/ThrowUtil.java b/src/main/java/ca/spottedleaf/concurrentutil/util/ThrowUtil.java new file mode 100644 index 0000000000000000000000000000000000000000..a3a8b5c6795c4d116e094e4c910553416f565b93 diff --git a/patches/server/0008-CB-fixes.patch b/patches/server/0008-CB-fixes.patch index 50bdb07720be..3c0b7b9d42c7 100644 --- a/patches/server/0008-CB-fixes.patch +++ b/patches/server/0008-CB-fixes.patch @@ -19,10 +19,10 @@ Subject: [PATCH] CB fixes Co-authored-by: Spottedleaf diff --git a/src/main/java/net/minecraft/server/level/ServerLevel.java b/src/main/java/net/minecraft/server/level/ServerLevel.java -index a696a0d168987aaa4e59c471a23eeb48d683c1b2..9d11fcb3df12182ae00ce73f7e30091fd199a341 100644 +index a17846ccd8581c3d6da962e977623aaab8314ec7..f6a3606b972064c4ec78487374e6197c0c447e27 100644 --- a/src/main/java/net/minecraft/server/level/ServerLevel.java +++ b/src/main/java/net/minecraft/server/level/ServerLevel.java -@@ -303,7 +303,7 @@ public class ServerLevel extends Level implements WorldGenLevel { +@@ -293,7 +293,7 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe long l = minecraftserver.getWorldData().worldGenOptions().seed(); @@ -32,7 +32,7 @@ index a696a0d168987aaa4e59c471a23eeb48d683c1b2..9d11fcb3df12182ae00ce73f7e30091f if ((this.dimension() == Level.END && this.dimensionTypeRegistration().is(BuiltinDimensionTypes.END)) || env == org.bukkit.World.Environment.THE_END) { // CraftBukkit - Allow to create EnderDragonBattle in default and custom END this.dragonFight = new EndDragonFight(this, this.serverLevelData.worldGenOptions().seed(), this.serverLevelData.endDragonFightData()); // CraftBukkit diff --git a/src/main/java/net/minecraft/world/item/enchantment/effects/SummonEntityEffect.java b/src/main/java/net/minecraft/world/item/enchantment/effects/SummonEntityEffect.java -index d927357d541cf206bb3019b2fda3473a77b44ec4..4601c7069ba82ccfe87e9dc304b6f3262f7bbfbf 100644 +index ed78039b89884c41ce10d520786c5b56f7d9b154..0239495abcc7b796864976b37ece184efa9a747e 100644 --- a/src/main/java/net/minecraft/world/item/enchantment/effects/SummonEntityEffect.java +++ b/src/main/java/net/minecraft/world/item/enchantment/effects/SummonEntityEffect.java @@ -54,7 +54,7 @@ public record SummonEntityEffect(HolderSet> entityTypes, boolean j @@ -45,7 +45,7 @@ index d927357d541cf206bb3019b2fda3473a77b44ec4..4601c7069ba82ccfe87e9dc304b6f326 } diff --git a/src/main/java/net/minecraft/world/level/levelgen/structure/StructureCheck.java b/src/main/java/net/minecraft/world/level/levelgen/structure/StructureCheck.java -index 0dc7f88877020bddd5a84db51d349f52b673048e..aae73586265593ee7830fb8dd5c2e3d7560057f0 100644 +index 4f68394a94308513269f0a4c749b6a36738e3ca0..953ab7638f7242b5a11dd1de8786172443a0558c 100644 --- a/src/main/java/net/minecraft/world/level/levelgen/structure/StructureCheck.java +++ b/src/main/java/net/minecraft/world/level/levelgen/structure/StructureCheck.java @@ -40,7 +40,7 @@ public class StructureCheck { @@ -67,7 +67,7 @@ index 0dc7f88877020bddd5a84db51d349f52b673048e..aae73586265593ee7830fb8dd5c2e3d7 RandomState noiseConfig, LevelHeightAccessor world, diff --git a/src/main/java/org/bukkit/craftbukkit/CraftLootTable.java b/src/main/java/org/bukkit/craftbukkit/CraftLootTable.java -index 85c7f3027978b1d7d6c31b7ad21b3377cdda5925..e34deaf398dc6722c3128bdd6b9bc16da2d33bf7 100644 +index a70e6872add1c952a89e74be0e6d09a53cc16559..90b82ad996b2b85628c9a5ddeef9410150b7f70c 100644 --- a/src/main/java/org/bukkit/craftbukkit/CraftLootTable.java +++ b/src/main/java/org/bukkit/craftbukkit/CraftLootTable.java @@ -187,4 +187,11 @@ public class CraftLootTable implements org.bukkit.loot.LootTable { @@ -83,7 +83,7 @@ index 85c7f3027978b1d7d6c31b7ad21b3377cdda5925..e34deaf398dc6722c3128bdd6b9bc16d + // Paper end } diff --git a/src/main/java/org/bukkit/craftbukkit/Main.java b/src/main/java/org/bukkit/craftbukkit/Main.java -index 0ea2d13ab80559472513c6df362583b8371a9532..13c37384defda4475de584f33d1860a2d53ce05e 100644 +index 755b1d77418ecae0dc9ec5197275d0cd914e7bee..94004204b6cdbbbf35263faae56e3e06cb6b650c 100644 --- a/src/main/java/org/bukkit/craftbukkit/Main.java +++ b/src/main/java/org/bukkit/craftbukkit/Main.java @@ -123,6 +123,7 @@ public class Main { @@ -95,7 +95,7 @@ index 0ea2d13ab80559472513c6df362583b8371a9532..13c37384defda4475de584f33d1860a2 this.acceptsAll(Main.asList("nojline"), "Disables jline and emulates the vanilla console"); diff --git a/src/main/java/org/bukkit/craftbukkit/scheduler/CraftScheduler.java b/src/main/java/org/bukkit/craftbukkit/scheduler/CraftScheduler.java -index 905adf97c0d1f0d1c774a6835a5dffcfea884e58..c017ce2ca1bc535795c958a2e509af2adf88efa9 100644 +index 163d54a8bf4cedbd1471e86b7ab1a1b850ed3f39..6effe47b32a8551aa6f6b11bc0315714a119e199 100644 --- a/src/main/java/org/bukkit/craftbukkit/scheduler/CraftScheduler.java +++ b/src/main/java/org/bukkit/craftbukkit/scheduler/CraftScheduler.java @@ -26,6 +26,7 @@ import org.bukkit.scheduler.BukkitWorker; @@ -115,7 +115,7 @@ index 905adf97c0d1f0d1c774a6835a5dffcfea884e58..c017ce2ca1bc535795c958a2e509af2a public class CraftScheduler implements BukkitScheduler { diff --git a/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java b/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java -index a0f228ee83fd3a913666f88381c946f8a6340644..60dc550431fa4641f8c6d964329b0e2698d1fdd6 100644 +index e95c367d1c14dc28b1bf83cc461c80f454af22af..5187d48ba067d0edf55fecfc912ae5ac34452da3 100644 --- a/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java +++ b/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java @@ -246,7 +246,7 @@ public final class CraftMagicNumbers implements UnsafeValues { diff --git a/patches/server/0009-MC-Utils.patch b/patches/server/0009-MC-Utils.patch index 59c2e99b59a1..f656e93a6489 100644 --- a/patches/server/0009-MC-Utils.patch +++ b/patches/server/0009-MC-Utils.patch @@ -12,12 +12,135 @@ public net.minecraft.server.level.ServerChunkCache mainThreadProcessor public net.minecraft.server.level.ServerChunkCache$MainThreadExecutor public net.minecraft.world.level.chunk.LevelChunkSection states +diff --git a/src/main/java/ca/spottedleaf/moonrise/common/PlatformHooks.java b/src/main/java/ca/spottedleaf/moonrise/common/PlatformHooks.java +new file mode 100644 +index 0000000000000000000000000000000000000000..6c98d420ea84c10ef4f15d4deb3f04e610ed8548 +--- /dev/null ++++ b/src/main/java/ca/spottedleaf/moonrise/common/PlatformHooks.java +@@ -0,0 +1,117 @@ ++package ca.spottedleaf.moonrise.common; ++ ++import com.mojang.datafixers.DSL; ++import com.mojang.datafixers.DataFixer; ++import net.minecraft.core.BlockPos; ++import net.minecraft.nbt.CompoundTag; ++import net.minecraft.server.level.ChunkHolder; ++import net.minecraft.server.level.GenerationChunkHolder; ++import net.minecraft.server.level.ServerLevel; ++import net.minecraft.server.level.ServerPlayer; ++import net.minecraft.world.entity.Entity; ++import net.minecraft.world.level.BlockGetter; ++import net.minecraft.world.level.ChunkPos; ++import net.minecraft.world.level.Level; ++import net.minecraft.world.level.block.state.BlockState; ++import net.minecraft.world.level.chunk.ChunkAccess; ++import net.minecraft.world.level.chunk.LevelChunk; ++import net.minecraft.world.level.chunk.ProtoChunk; ++import net.minecraft.world.level.chunk.storage.SerializableChunkData; ++import net.minecraft.world.level.entity.EntityTypeTest; ++import net.minecraft.world.phys.AABB; ++import java.util.List; ++import java.util.ServiceLoader; ++import java.util.function.Predicate; ++ ++public interface PlatformHooks { ++ public static PlatformHooks get() { ++ return Holder.INSTANCE; ++ } ++ ++ public String getBrand(); ++ ++ public int getLightEmission(final BlockState blockState, final BlockGetter world, final BlockPos pos); ++ ++ public Predicate maybeHasLightEmission(); ++ ++ public boolean hasCurrentlyLoadingChunk(); ++ ++ public LevelChunk getCurrentlyLoadingChunk(final GenerationChunkHolder holder); ++ ++ public void setCurrentlyLoading(final GenerationChunkHolder holder, final LevelChunk levelChunk); ++ ++ public void chunkFullStatusComplete(final LevelChunk newChunk, final ProtoChunk original); ++ ++ public boolean allowAsyncTicketUpdates(); ++ ++ public void onChunkHolderTicketChange(final ServerLevel world, final ChunkHolder holder, final int oldLevel, final int newLevel); ++ ++ public void chunkUnloadFromWorld(final LevelChunk chunk); ++ ++ public void chunkSyncSave(final ServerLevel world, final ChunkAccess chunk, final SerializableChunkData data); ++ ++ public void onChunkWatch(final ServerLevel world, final LevelChunk chunk, final ServerPlayer player); ++ ++ public void onChunkUnWatch(final ServerLevel world, final ChunkPos chunk, final ServerPlayer player); ++ ++ public void addToGetEntities(final Level world, final Entity entity, final AABB boundingBox, final Predicate predicate, ++ final List into); ++ ++ public void addToGetEntities(final Level world, final EntityTypeTest entityTypeTest, ++ final AABB boundingBox, final Predicate predicate, ++ final List into, final int maxCount); ++ ++ public void entityMove(final Entity entity, final long oldSection, final long newSection); ++ ++ public boolean screenEntity(final ServerLevel world, final Entity entity, final boolean fromDisk, final boolean event); ++ ++ public boolean configFixMC224294(); ++ ++ public boolean configAutoConfigSendDistance(); ++ ++ public double configPlayerMaxLoadRate(); ++ ++ public double configPlayerMaxGenRate(); ++ ++ public double configPlayerMaxSendRate(); ++ ++ public int configPlayerMaxConcurrentLoads(); ++ ++ public int configPlayerMaxConcurrentGens(); ++ ++ public long configAutoSaveInterval(final ServerLevel world); ++ ++ public int configMaxAutoSavePerTick(final ServerLevel world); ++ ++ public boolean configFixMC159283(); ++ ++ // support for CB chunk mustNotSave ++ public boolean forceNoSave(final ChunkAccess chunk); ++ ++ public CompoundTag convertNBT(final DSL.TypeReference type, final DataFixer dataFixer, final CompoundTag nbt, ++ final int fromVersion, final int toVersion); ++ ++ public boolean hasMainChunkLoadHook(); ++ ++ public void mainChunkLoad(final ChunkAccess chunk, final SerializableChunkData chunkData); ++ ++ public List modifySavedEntities(final ServerLevel world, final int chunkX, final int chunkZ, final List entities); ++ ++ public void unloadEntity(final Entity entity); ++ ++ public void postLoadProtoChunk(final ServerLevel world, final ProtoChunk chunk); ++ ++ public int modifyEntityTrackingRange(final Entity entity, final int currentRange); ++ ++ public static final class Holder { ++ private Holder() { ++ } ++ ++ private static final PlatformHooks INSTANCE; ++ ++ static { ++ INSTANCE = ServiceLoader.load(PlatformHooks.class, PlatformHooks.class.getClassLoader()).findFirst() ++ .orElseThrow(() -> new RuntimeException("Failed to locate PlatformHooks")); ++ } ++ } ++} diff --git a/src/main/java/ca/spottedleaf/moonrise/common/list/EntityList.java b/src/main/java/ca/spottedleaf/moonrise/common/list/EntityList.java new file mode 100644 -index 0000000000000000000000000000000000000000..ba68998f6ef57b24c72fd833bd7de440de9501cc +index 0000000000000000000000000000000000000000..7fed43a1e7bcf35c4d7fd3224837a47fedd59860 --- /dev/null +++ b/src/main/java/ca/spottedleaf/moonrise/common/list/EntityList.java -@@ -0,0 +1,129 @@ +@@ -0,0 +1,128 @@ +package ca.spottedleaf.moonrise.common.list; + +import it.unimi.dsi.fastutil.ints.Int2IntOpenHashMap; @@ -33,15 +156,15 @@ index 0000000000000000000000000000000000000000..ba68998f6ef57b24c72fd833bd7de440 + */ +public final class EntityList implements Iterable { + -+ protected final Int2IntOpenHashMap entityToIndex = new Int2IntOpenHashMap(2, 0.8f); ++ private final Int2IntOpenHashMap entityToIndex = new Int2IntOpenHashMap(2, 0.8f); + { + this.entityToIndex.defaultReturnValue(Integer.MIN_VALUE); + } + -+ protected static final Entity[] EMPTY_LIST = new Entity[0]; ++ private static final Entity[] EMPTY_LIST = new Entity[0]; + -+ protected Entity[] entities = EMPTY_LIST; -+ protected int count; ++ private Entity[] entities = EMPTY_LIST; ++ private int count; + + public int size() { + return this.count; @@ -114,10 +237,9 @@ index 0000000000000000000000000000000000000000..ba68998f6ef57b24c72fd833bd7de440 + + @Override + public Iterator iterator() { -+ return new Iterator() { -+ -+ Entity lastRet; -+ int current; ++ return new Iterator<>() { ++ private Entity lastRet; ++ private int current; + + @Override + public boolean hasNext() { @@ -147,138 +269,89 @@ index 0000000000000000000000000000000000000000..ba68998f6ef57b24c72fd833bd7de440 + }; + } +} -diff --git a/src/main/java/ca/spottedleaf/moonrise/common/list/IBlockDataList.java b/src/main/java/ca/spottedleaf/moonrise/common/list/IBlockDataList.java +diff --git a/src/main/java/ca/spottedleaf/moonrise/common/list/IntList.java b/src/main/java/ca/spottedleaf/moonrise/common/list/IntList.java new file mode 100644 -index 0000000000000000000000000000000000000000..fcfbca333234c09f7c056bbfcd9ac8860b20a8db +index 0000000000000000000000000000000000000000..9f3b25bb2439f283f878db93973a02fcdcd14eed --- /dev/null -+++ b/src/main/java/ca/spottedleaf/moonrise/common/list/IBlockDataList.java -@@ -0,0 +1,125 @@ ++++ b/src/main/java/ca/spottedleaf/moonrise/common/list/IntList.java +@@ -0,0 +1,77 @@ +package ca.spottedleaf.moonrise.common.list; + -+import it.unimi.dsi.fastutil.longs.LongIterator; -+import it.unimi.dsi.fastutil.shorts.Short2LongOpenHashMap; ++import it.unimi.dsi.fastutil.ints.Int2IntOpenHashMap; +import java.util.Arrays; -+import net.minecraft.world.level.block.Block; -+import net.minecraft.world.level.block.state.BlockState; -+import net.minecraft.world.level.chunk.GlobalPalette; + -+public final class IBlockDataList { ++public final class IntList { + -+ private static final GlobalPalette GLOBAL_PALETTE = new GlobalPalette<>(Block.BLOCK_STATE_REGISTRY); -+ -+ // map of location -> (index | (location << 16) | (palette id << 32)) -+ private final Short2LongOpenHashMap map = new Short2LongOpenHashMap(2, 0.8f); ++ private final Int2IntOpenHashMap map = new Int2IntOpenHashMap(); + { -+ this.map.defaultReturnValue(Long.MAX_VALUE); ++ this.map.defaultReturnValue(Integer.MIN_VALUE); + } + -+ private static final long[] EMPTY_LIST = new long[0]; -+ -+ private long[] byIndex = EMPTY_LIST; -+ private int size; ++ private static final int[] EMPTY_LIST = new int[0]; + -+ public static int getLocationKey(final int x, final int y, final int z) { -+ return (x & 15) | (((z & 15) << 4)) | ((y & 255) << (4 + 4)); -+ } -+ -+ public static BlockState getBlockDataFromRaw(final long raw) { -+ return GLOBAL_PALETTE.valueFor((int)(raw >>> 32)); -+ } -+ -+ public static int getIndexFromRaw(final long raw) { -+ return (int)(raw & 0xFFFF); -+ } -+ -+ public static int getLocationFromRaw(final long raw) { -+ return (int)((raw >>> 16) & 0xFFFF); -+ } ++ private int[] byIndex = EMPTY_LIST; ++ private int count; + -+ public static long getRawFromValues(final int index, final int location, final BlockState data) { -+ return (long)index | ((long)location << 16) | (((long)GLOBAL_PALETTE.idFor(data)) << 32); ++ public int size() { ++ return this.count; + } + -+ public static long setIndexRawValues(final long value, final int index) { -+ return value & ~(0xFFFF) | (index); ++ public void setMinCapacity(final int len) { ++ final int[] byIndex = this.byIndex; ++ if (byIndex.length < len) { ++ this.byIndex = Arrays.copyOf(byIndex, len); ++ } + } + -+ public long add(final int x, final int y, final int z, final BlockState data) { -+ return this.add(getLocationKey(x, y, z), data); ++ public int getRaw(final int index) { ++ return this.byIndex[index]; + } + -+ public long add(final int location, final BlockState data) { -+ final long curr = this.map.get((short)location); -+ -+ if (curr == Long.MAX_VALUE) { -+ final int index = this.size++; -+ final long raw = getRawFromValues(index, location, data); -+ this.map.put((short)location, raw); -+ -+ if (index >= this.byIndex.length) { -+ this.byIndex = Arrays.copyOf(this.byIndex, (int)Math.max(4L, this.byIndex.length * 2L)); -+ } ++ public boolean add(final int value) { ++ final int count = this.count; ++ final int currIndex = this.map.putIfAbsent(value, count); + -+ this.byIndex[index] = raw; -+ return raw; -+ } else { -+ final int index = getIndexFromRaw(curr); -+ final long raw = this.byIndex[index] = getRawFromValues(index, location, data); ++ if (currIndex != Integer.MIN_VALUE) { ++ return false; // already in this list ++ } + -+ this.map.put((short)location, raw); ++ int[] list = this.byIndex; + -+ return raw; ++ if (list.length == count) { ++ // resize required ++ list = this.byIndex = Arrays.copyOf(list, (int)Math.max(4L, count * 2L)); // overflow results in negative + } -+ } + -+ public long remove(final int x, final int y, final int z) { -+ return this.remove(getLocationKey(x, y, z)); ++ list[count] = value; ++ this.count = count + 1; ++ ++ return true; + } + -+ public long remove(final int location) { -+ final long ret = this.map.remove((short)location); -+ final int index = getIndexFromRaw(ret); -+ if (ret == Long.MAX_VALUE) { -+ return ret; ++ public boolean remove(final int value) { ++ final int index = this.map.remove(value); ++ if (index == Integer.MIN_VALUE) { ++ return false; + } + + // move the entry at the end to this index -+ final int endIndex = --this.size; -+ final long end = this.byIndex[endIndex]; ++ final int endIndex = --this.count; ++ final int end = this.byIndex[endIndex]; + if (index != endIndex) { + // not empty after this call -+ this.map.put((short)getLocationFromRaw(end), setIndexRawValues(end, index)); ++ this.map.put(end, index); + } + this.byIndex[index] = end; -+ this.byIndex[endIndex] = 0L; -+ -+ return ret; -+ } -+ -+ public int size() { -+ return this.size; -+ } ++ this.byIndex[endIndex] = 0; + -+ public long getRaw(final int index) { -+ return this.byIndex[index]; -+ } -+ -+ public int getLocation(final int index) { -+ return getLocationFromRaw(this.getRaw(index)); -+ } -+ -+ public BlockState getData(final int index) { -+ return getBlockDataFromRaw(this.getRaw(index)); ++ return true; + } + + public void clear() { -+ this.size = 0; ++ this.count = 0; + this.map.clear(); + } -+ -+ public LongIterator getRawIterator() { -+ return this.map.values().iterator(); -+ } +} -\ No newline at end of file diff --git a/src/main/java/ca/spottedleaf/moonrise/common/list/IteratorSafeOrderedReferenceSet.java b/src/main/java/ca/spottedleaf/moonrise/common/list/IteratorSafeOrderedReferenceSet.java new file mode 100644 index 0000000000000000000000000000000000000000..c21e00812f1aaa1279834a0562d360d6b89e146c @@ -745,6 +818,89 @@ index 0000000000000000000000000000000000000000..2e876b918672e8ef3b5197b7e6b15972 + }; + } +} +diff --git a/src/main/java/ca/spottedleaf/moonrise/common/list/ShortList.java b/src/main/java/ca/spottedleaf/moonrise/common/list/ShortList.java +new file mode 100644 +index 0000000000000000000000000000000000000000..2bae9949ef325d0001aa638150fbbdf968367e75 +--- /dev/null ++++ b/src/main/java/ca/spottedleaf/moonrise/common/list/ShortList.java +@@ -0,0 +1,77 @@ ++package ca.spottedleaf.moonrise.common.list; ++ ++import it.unimi.dsi.fastutil.shorts.Short2ShortOpenHashMap; ++import java.util.Arrays; ++ ++public final class ShortList { ++ ++ private final Short2ShortOpenHashMap map = new Short2ShortOpenHashMap(); ++ { ++ this.map.defaultReturnValue(Short.MIN_VALUE); ++ } ++ ++ private static final short[] EMPTY_LIST = new short[0]; ++ ++ private short[] byIndex = EMPTY_LIST; ++ private short count; ++ ++ public int size() { ++ return (int)this.count; ++ } ++ ++ public short getRaw(final int index) { ++ return this.byIndex[index]; ++ } ++ ++ public void setMinCapacity(final int len) { ++ final short[] byIndex = this.byIndex; ++ if (byIndex.length < len) { ++ this.byIndex = Arrays.copyOf(byIndex, len); ++ } ++ } ++ ++ public boolean add(final short value) { ++ final int count = (int)this.count; ++ final short currIndex = this.map.putIfAbsent(value, (short)count); ++ ++ if (currIndex != Short.MIN_VALUE) { ++ return false; // already in this list ++ } ++ ++ short[] list = this.byIndex; ++ ++ if (list.length == count) { ++ // resize required ++ list = this.byIndex = Arrays.copyOf(list, (int)Math.max(4L, count * 2L)); // overflow results in negative ++ } ++ ++ list[count] = value; ++ this.count = (short)(count + 1); ++ ++ return true; ++ } ++ ++ public boolean remove(final short value) { ++ final short index = this.map.remove(value); ++ if (index == Short.MIN_VALUE) { ++ return false; ++ } ++ ++ // move the entry at the end to this index ++ final short endIndex = --this.count; ++ final short end = this.byIndex[endIndex]; ++ if (index != endIndex) { ++ // not empty after this call ++ this.map.put(end, index); ++ } ++ this.byIndex[(int)index] = end; ++ this.byIndex[(int)endIndex] = (short)0; ++ ++ return true; ++ } ++ ++ public void clear() { ++ this.count = (short)0; ++ this.map.clear(); ++ } ++} diff --git a/src/main/java/ca/spottedleaf/moonrise/common/list/SortedList.java b/src/main/java/ca/spottedleaf/moonrise/common/list/SortedList.java new file mode 100644 index 0000000000000000000000000000000000000000..db92261a6cb3758391108361096417c61bc82cdc @@ -2413,25 +2569,57 @@ index 0000000000000000000000000000000000000000..ab2fa1563d5e32a5313dfcc1da411cab + } + } +} +diff --git a/src/main/java/ca/spottedleaf/moonrise/common/misc/LazyRunnable.java b/src/main/java/ca/spottedleaf/moonrise/common/misc/LazyRunnable.java +new file mode 100644 +index 0000000000000000000000000000000000000000..c2d917c2eac55b8a4411a6e159f177f9428b1150 +--- /dev/null ++++ b/src/main/java/ca/spottedleaf/moonrise/common/misc/LazyRunnable.java +@@ -0,0 +1,22 @@ ++package ca.spottedleaf.moonrise.common.misc; ++ ++import ca.spottedleaf.concurrentutil.util.ConcurrentUtil; ++import java.lang.invoke.VarHandle; ++ ++public final class LazyRunnable implements Runnable { ++ ++ private volatile Runnable toRun; ++ private static final VarHandle TO_RUN_HANDLE = ConcurrentUtil.getVarHandle(LazyRunnable.class, "toRun", Runnable.class); ++ ++ public void setRunnable(final Runnable run) { ++ final Runnable prev = (Runnable)TO_RUN_HANDLE.compareAndExchange(this, (Runnable)null, run); ++ if (prev != null) { ++ throw new IllegalStateException("Runnable already set"); ++ } ++ } ++ ++ @Override ++ public void run() { ++ ((Runnable)TO_RUN_HANDLE.getVolatile(this)).run(); ++ } ++} diff --git a/src/main/java/ca/spottedleaf/moonrise/common/misc/NearbyPlayers.java b/src/main/java/ca/spottedleaf/moonrise/common/misc/NearbyPlayers.java new file mode 100644 -index 0000000000000000000000000000000000000000..ab093b0e8ac6f762921eb1d15f5217345c4eba05 +index 0000000000000000000000000000000000000000..bb44de17a37082e57f2292a4f470740be1d09b11 --- /dev/null +++ b/src/main/java/ca/spottedleaf/moonrise/common/misc/NearbyPlayers.java -@@ -0,0 +1,211 @@ +@@ -0,0 +1,273 @@ +package ca.spottedleaf.moonrise.common.misc; + +import ca.spottedleaf.moonrise.common.list.ReferenceList; +import ca.spottedleaf.moonrise.common.util.CoordinateUtils; +import ca.spottedleaf.moonrise.common.util.MoonriseConstants; +import ca.spottedleaf.moonrise.common.util.ChunkSystem; ++import ca.spottedleaf.moonrise.patches.chunk_system.level.ChunkSystemLevel; ++import ca.spottedleaf.moonrise.patches.chunk_system.level.chunk.ChunkData; +import ca.spottedleaf.moonrise.patches.chunk_tick_iteration.ChunkTickConstants; ++import ca.spottedleaf.moonrise.patches.chunk_tick_iteration.ChunkTickServerLevel; +import it.unimi.dsi.fastutil.longs.Long2ReferenceOpenHashMap; +import it.unimi.dsi.fastutil.objects.Reference2ReferenceOpenHashMap; +import net.minecraft.core.BlockPos; +import net.minecraft.server.level.ServerLevel; +import net.minecraft.server.level.ServerPlayer; +import net.minecraft.world.level.ChunkPos; ++import java.util.ArrayList; + +public final class NearbyPlayers { + @@ -2441,7 +2629,27 @@ index 0000000000000000000000000000000000000000..ab093b0e8ac6f762921eb1d15f521734 + GENERAL_REALLY_SMALL, + TICK_VIEW_DISTANCE, + VIEW_DISTANCE, -+ SPAWN_RANGE, // Moonrise - chunk tick iteration ++ // Moonrise start - chunk tick iteration ++ SPAWN_RANGE { ++ @Override ++ void addTo(final ServerPlayer player, final ServerLevel world, final int chunkX, final int chunkZ) { ++ ((ChunkTickServerLevel)world).moonrise$addPlayerTickingRequest(chunkX, chunkZ); ++ } ++ ++ @Override ++ void removeFrom(final ServerPlayer player, final ServerLevel world, final int chunkX, final int chunkZ) { ++ ((ChunkTickServerLevel)world).moonrise$removePlayerTickingRequest(chunkX, chunkZ); ++ } ++ }; ++ // Moonrise end - chunk tick iteration ++ ++ void addTo(final ServerPlayer player, final ServerLevel world, final int chunkX, final int chunkZ) { ++ ++ } ++ ++ void removeFrom(final ServerPlayer player, final ServerLevel world, final int chunkX, final int chunkZ) { ++ ++ } + } + + private static final NearbyMapType[] MAP_TYPES = NearbyMapType.values(); @@ -2458,6 +2666,12 @@ index 0000000000000000000000000000000000000000..ab093b0e8ac6f762921eb1d15f521734 + private final ServerLevel world; + private final Reference2ReferenceOpenHashMap players = new Reference2ReferenceOpenHashMap<>(); + private final Long2ReferenceOpenHashMap byChunk = new Long2ReferenceOpenHashMap<>(); ++ private final Long2ReferenceOpenHashMap>[] directByChunk = new Long2ReferenceOpenHashMap[TOTAL_MAP_TYPES]; ++ { ++ for (int i = 0; i < this.directByChunk.length; ++i) { ++ this.directByChunk[i] = new Long2ReferenceOpenHashMap<>(); ++ } ++ } + + public NearbyPlayers(final ServerLevel world) { + this.world = world; @@ -2491,6 +2705,16 @@ index 0000000000000000000000000000000000000000..ab093b0e8ac6f762921eb1d15f521734 + } + } + ++ public void clear() { ++ if (this.players.isEmpty()) { ++ return; ++ } ++ ++ for (final ServerPlayer player : new ArrayList<>(this.players.keySet())) { ++ this.removePlayer(player); ++ } ++ } ++ + public void tickPlayer(final ServerPlayer player) { + final TrackedPlayer[] players = this.players.get(player); + if (players == null) { @@ -2515,38 +2739,41 @@ index 0000000000000000000000000000000000000000..ab093b0e8ac6f762921eb1d15f521734 + return this.byChunk.get(CoordinateUtils.getChunkKey(pos)); + } + -+ public ReferenceList getPlayers(final BlockPos pos, final NearbyMapType type) { -+ final TrackedChunk chunk = this.byChunk.get(CoordinateUtils.getChunkKey(pos)); ++ public TrackedChunk getChunk(final int chunkX, final int chunkZ) { ++ return this.byChunk.get(CoordinateUtils.getChunkKey(chunkX, chunkZ)); ++ } + -+ return chunk == null ? null : chunk.players[type.ordinal()]; ++ public ReferenceList getPlayers(final BlockPos pos, final NearbyMapType type) { ++ return this.directByChunk[type.ordinal()].get(CoordinateUtils.getChunkKey(pos)); + } + + public ReferenceList getPlayers(final ChunkPos pos, final NearbyMapType type) { -+ final TrackedChunk chunk = this.byChunk.get(CoordinateUtils.getChunkKey(pos)); -+ -+ return chunk == null ? null : chunk.players[type.ordinal()]; ++ return this.directByChunk[type.ordinal()].get(CoordinateUtils.getChunkKey(pos)); + } + + public ReferenceList getPlayersByChunk(final int chunkX, final int chunkZ, final NearbyMapType type) { -+ final TrackedChunk chunk = this.byChunk.get(CoordinateUtils.getChunkKey(chunkX, chunkZ)); -+ -+ return chunk == null ? null : chunk.players[type.ordinal()]; ++ return this.directByChunk[type.ordinal()].get(CoordinateUtils.getChunkKey(chunkX, chunkZ)); + } + + public ReferenceList getPlayersByBlock(final int blockX, final int blockZ, final NearbyMapType type) { -+ final TrackedChunk chunk = this.byChunk.get(CoordinateUtils.getChunkKey(blockX >> 4, blockZ >> 4)); -+ -+ return chunk == null ? null : chunk.players[type.ordinal()]; ++ return this.directByChunk[type.ordinal()].get(CoordinateUtils.getChunkKey(blockX >> 4, blockZ >> 4)); + } + + public static final class TrackedChunk { + + private static final ServerPlayer[] EMPTY_PLAYERS_ARRAY = new ServerPlayer[0]; + ++ private final long chunkKey; ++ private final NearbyPlayers nearbyPlayers; + private final ReferenceList[] players = new ReferenceList[TOTAL_MAP_TYPES]; + private int nonEmptyLists; + private long updateCount; + ++ public TrackedChunk(final long chunkKey, final NearbyPlayers nearbyPlayers) { ++ this.chunkKey = chunkKey; ++ this.nearbyPlayers = nearbyPlayers; ++ } ++ + public boolean isEmpty() { + return this.nonEmptyLists == 0; + } @@ -2566,7 +2793,9 @@ index 0000000000000000000000000000000000000000..ab093b0e8ac6f762921eb1d15f521734 + final ReferenceList list = this.players[idx]; + if (list == null) { + ++this.nonEmptyLists; -+ (this.players[idx] = new ReferenceList<>(EMPTY_PLAYERS_ARRAY)).add(player); ++ final ReferenceList players = (this.players[idx] = new ReferenceList<>(EMPTY_PLAYERS_ARRAY)); ++ this.nearbyPlayers.directByChunk[idx].put(this.chunkKey, players); ++ players.add(player); + return; + } + @@ -2590,6 +2819,7 @@ index 0000000000000000000000000000000000000000..ab093b0e8ac6f762921eb1d15f521734 + + if (list.size() == 0) { + this.players[idx] = null; ++ this.nearbyPlayers.directByChunk[idx].remove(this.chunkKey); + --this.nonEmptyLists; + } + } @@ -2608,9 +2838,19 @@ index 0000000000000000000000000000000000000000..ab093b0e8ac6f762921eb1d15f521734 + protected void addCallback(final ServerPlayer parameter, final int chunkX, final int chunkZ) { + final long chunkKey = CoordinateUtils.getChunkKey(chunkX, chunkZ); + -+ NearbyPlayers.this.byChunk.computeIfAbsent(chunkKey, (final long keyInMap) -> { -+ return new TrackedChunk(); -+ }).addPlayer(parameter, this.type); ++ final TrackedChunk chunk = NearbyPlayers.this.byChunk.get(chunkKey); ++ final NearbyMapType type = this.type; ++ if (chunk != null) { ++ chunk.addPlayer(parameter, type); ++ type.addTo(parameter, NearbyPlayers.this.world, chunkX, chunkZ); ++ } else { ++ final TrackedChunk created = new TrackedChunk(chunkKey, NearbyPlayers.this); ++ NearbyPlayers.this.byChunk.put(chunkKey, created); ++ created.addPlayer(parameter, type); ++ type.addTo(parameter, NearbyPlayers.this.world, chunkX, chunkZ); ++ ++ ((ChunkSystemLevel)NearbyPlayers.this.world).moonrise$requestChunkData(chunkKey).nearbyPlayers = created; ++ } + } + + @Override @@ -2622,24 +2862,31 @@ index 0000000000000000000000000000000000000000..ab093b0e8ac6f762921eb1d15f521734 + throw new IllegalStateException("Chunk should exist at " + new ChunkPos(chunkKey)); + } + -+ chunk.removePlayer(parameter, this.type); ++ final NearbyMapType type = this.type; ++ chunk.removePlayer(parameter, type); ++ type.removeFrom(parameter, NearbyPlayers.this.world, chunkX, chunkZ); + + if (chunk.isEmpty()) { + NearbyPlayers.this.byChunk.remove(chunkKey); ++ final ChunkData chunkData = ((ChunkSystemLevel)NearbyPlayers.this.world).moonrise$releaseChunkData(chunkKey); ++ if (chunkData != null) { ++ chunkData.nearbyPlayers = null; ++ } + } + } + } +} diff --git a/src/main/java/ca/spottedleaf/moonrise/common/misc/PositionCountingAreaMap.java b/src/main/java/ca/spottedleaf/moonrise/common/misc/PositionCountingAreaMap.java new file mode 100644 -index 0000000000000000000000000000000000000000..efefd94b652228d877db5dbca8b28354ad42529f +index 0000000000000000000000000000000000000000..90560769d09538f7a740753a41a3b8e017b0b92a --- /dev/null +++ b/src/main/java/ca/spottedleaf/moonrise/common/misc/PositionCountingAreaMap.java -@@ -0,0 +1,94 @@ +@@ -0,0 +1,99 @@ +package ca.spottedleaf.moonrise.common.misc; + +import ca.spottedleaf.concurrentutil.util.IntPairUtil; +import it.unimi.dsi.fastutil.longs.Long2IntOpenHashMap; ++import it.unimi.dsi.fastutil.longs.LongSet; +import it.unimi.dsi.fastutil.objects.Reference2ReferenceOpenHashMap; +import it.unimi.dsi.fastutil.objects.ReferenceSet; + @@ -2652,6 +2899,10 @@ index 0000000000000000000000000000000000000000..efefd94b652228d877db5dbca8b28354 + return this.counters.keySet(); + } + ++ public LongSet getPositions() { ++ return this.positions.keySet(); ++ } ++ + public int getTotalPositions() { + return this.positions.size(); + } @@ -3060,13 +3311,14 @@ index 0000000000000000000000000000000000000000..4123edddc556c47f3f8d83523c125fd2 +} diff --git a/src/main/java/ca/spottedleaf/moonrise/common/util/ChunkSystem.java b/src/main/java/ca/spottedleaf/moonrise/common/util/ChunkSystem.java new file mode 100644 -index 0000000000000000000000000000000000000000..da323a1105347d5cf4b946df10ded78a953236f2 +index 0000000000000000000000000000000000000000..94bba2b71918d79f54b3e28c35e76098ba0afd8c --- /dev/null +++ b/src/main/java/ca/spottedleaf/moonrise/common/util/ChunkSystem.java -@@ -0,0 +1,284 @@ +@@ -0,0 +1,288 @@ +package ca.spottedleaf.moonrise.common.util; + -+import ca.spottedleaf.concurrentutil.executor.standard.PrioritisedExecutor; ++import ca.spottedleaf.concurrentutil.util.Priority; ++import ca.spottedleaf.moonrise.common.PlatformHooks; +import com.mojang.logging.LogUtils; +import net.minecraft.server.level.ChunkHolder; +import net.minecraft.server.level.FullChunkStatus; @@ -3090,15 +3342,15 @@ index 0000000000000000000000000000000000000000..da323a1105347d5cf4b946df10ded78a + } + + public static void scheduleChunkTask(final ServerLevel level, final int chunkX, final int chunkZ, final Runnable run) { -+ scheduleChunkTask(level, chunkX, chunkZ, run, PrioritisedExecutor.Priority.NORMAL); ++ scheduleChunkTask(level, chunkX, chunkZ, run, Priority.NORMAL); + } + -+ public static void scheduleChunkTask(final ServerLevel level, final int chunkX, final int chunkZ, final Runnable run, final PrioritisedExecutor.Priority priority) { ++ public static void scheduleChunkTask(final ServerLevel level, final int chunkX, final int chunkZ, final Runnable run, final Priority priority) { + level.chunkSource.mainThreadProcessor.execute(run); + } + + public static void scheduleChunkLoad(final ServerLevel level, final int chunkX, final int chunkZ, final boolean gen, -+ final ChunkStatus toStatus, final boolean addTicket, final PrioritisedExecutor.Priority priority, ++ final ChunkStatus toStatus, final boolean addTicket, final Priority priority, + final Consumer onComplete) { + if (gen) { + scheduleChunkLoad(level, chunkX, chunkZ, toStatus, addTicket, priority, onComplete); @@ -3125,7 +3377,7 @@ index 0000000000000000000000000000000000000000..da323a1105347d5cf4b946df10ded78a + + private static long chunkLoadCounter = 0L; + public static void scheduleChunkLoad(final ServerLevel level, final int chunkX, final int chunkZ, final ChunkStatus toStatus, -+ final boolean addTicket, final PrioritisedExecutor.Priority priority, final Consumer onComplete) { ++ final boolean addTicket, final Priority priority, final Consumer onComplete) { + if (!org.bukkit.Bukkit.isPrimaryThread()) { + scheduleChunkTask(level, chunkX, chunkZ, () -> { + scheduleChunkLoad(level, chunkX, chunkZ, toStatus, addTicket, priority, onComplete); @@ -3179,13 +3431,13 @@ index 0000000000000000000000000000000000000000..da323a1105347d5cf4b946df10ded78a + } + loadCallback.accept(result.orElse(null)); + }, (final Runnable r) -> { -+ scheduleChunkTask(level, chunkX, chunkZ, r, PrioritisedExecutor.Priority.HIGHEST); ++ scheduleChunkTask(level, chunkX, chunkZ, r, Priority.HIGHEST); + }); + } + + public static void scheduleTickingState(final ServerLevel level, final int chunkX, final int chunkZ, + final FullChunkStatus toStatus, final boolean addTicket, -+ final PrioritisedExecutor.Priority priority, final Consumer onComplete) { ++ final Priority priority, final Consumer onComplete) { + // This method goes unused until the chunk system rewrite + if (toStatus == FullChunkStatus.INACCESSIBLE) { + throw new IllegalArgumentException("Cannot wait for INACCESSIBLE status"); @@ -3262,7 +3514,7 @@ index 0000000000000000000000000000000000000000..da323a1105347d5cf4b946df10ded78a + } + loadCallback.accept(result.orElse(null)); + }, (final Runnable r) -> { -+ scheduleChunkTask(level, chunkX, chunkZ, r, PrioritisedExecutor.Priority.HIGHEST); ++ scheduleChunkTask(level, chunkX, chunkZ, r, Priority.HIGHEST); + }); + } + @@ -3286,7 +3538,10 @@ index 0000000000000000000000000000000000000000..da323a1105347d5cf4b946df10ded78a + return getUpdatingChunkHolderCount(level) != 0; + } + -+ public static boolean screenEntity(final ServerLevel level, final Entity entity) { ++ public static boolean screenEntity(final ServerLevel level, final Entity entity, final boolean fromDisk, final boolean event) { ++ if (!PlatformHooks.get().screenEntity(level, entity, fromDisk, event)) { ++ return false; ++ } + return true; + } + @@ -3640,93 +3895,212 @@ index 0000000000000000000000000000000000000000..91efda726b87a8a8f28dee84e31b6a70 +} diff --git a/src/main/java/ca/spottedleaf/moonrise/common/util/MixinWorkarounds.java b/src/main/java/ca/spottedleaf/moonrise/common/util/MixinWorkarounds.java new file mode 100644 -index 0000000000000000000000000000000000000000..ac6f284ee4469d16c5655328b2488d7612832353 +index 0000000000000000000000000000000000000000..97848869df61648fc415e4d39f409f433202c274 --- /dev/null +++ b/src/main/java/ca/spottedleaf/moonrise/common/util/MixinWorkarounds.java -@@ -0,0 +1,10 @@ +@@ -0,0 +1,14 @@ +package ca.spottedleaf.moonrise.common.util; + +public final class MixinWorkarounds { + + // mixins tries to find the owner of the clone() method, which doesn't exist and NPEs ++ // https://github.com/FabricMC/Mixin/pull/147 + public static long[] clone(final long[] values) { + return values.clone(); + } + ++ public static byte[] clone(final byte[] values) { ++ return values.clone(); ++ } +} diff --git a/src/main/java/ca/spottedleaf/moonrise/common/util/MoonriseCommon.java b/src/main/java/ca/spottedleaf/moonrise/common/util/MoonriseCommon.java new file mode 100644 -index 0000000000000000000000000000000000000000..3abe0bd2a820352b85306d554bf14a4cf6123091 +index 0000000000000000000000000000000000000000..c125c70a68130be373acc989053a6c0e487be924 --- /dev/null +++ b/src/main/java/ca/spottedleaf/moonrise/common/util/MoonriseCommon.java -@@ -0,0 +1,46 @@ +@@ -0,0 +1,101 @@ +package ca.spottedleaf.moonrise.common.util; + -+import ca.spottedleaf.concurrentutil.executor.standard.PrioritisedThreadPool; ++import ca.spottedleaf.concurrentutil.executor.thread.PrioritisedThreadPool; ++import ca.spottedleaf.moonrise.common.PlatformHooks; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; -+import java.io.File; ++import java.util.concurrent.TimeUnit; ++import java.util.concurrent.atomic.AtomicInteger; ++import java.util.function.Consumer; + +public final class MoonriseCommon { + + private static final Logger LOGGER = LoggerFactory.getLogger(MoonriseCommon.class); + -+ // Paper start -+ public static PrioritisedThreadPool WORKER_POOL; -+ public static int WORKER_THREADS; -+ public static void init(io.papermc.paper.configuration.GlobalConfiguration.ChunkSystem chunkSystem) { -+ // Paper end ++ public static final PrioritisedThreadPool WORKER_POOL = new PrioritisedThreadPool( ++ new Consumer<>() { ++ private final AtomicInteger idGenerator = new AtomicInteger(); ++ ++ @Override ++ public void accept(Thread thread) { ++ thread.setDaemon(true); ++ thread.setName(PlatformHooks.get().getBrand() + " Common Worker #" + this.idGenerator.getAndIncrement()); ++ thread.setUncaughtExceptionHandler(new Thread.UncaughtExceptionHandler() { ++ @Override ++ public void uncaughtException(final Thread thread, final Throwable throwable) { ++ LOGGER.error("Uncaught exception in thread " + thread.getName(), throwable); ++ } ++ }); ++ } ++ } ++ ); ++ public static final long WORKER_QUEUE_HOLD_TIME = (long)(20.0e6); // 20ms ++ public static final int CLIENT_DIVISION = 0; ++ public static final PrioritisedThreadPool.ExecutorGroup RENDER_EXECUTOR_GROUP = MoonriseCommon.WORKER_POOL.createExecutorGroup(CLIENT_DIVISION, 0); ++ public static final int SERVER_DIVISION = 1; ++ public static final PrioritisedThreadPool.ExecutorGroup PARALLEL_GEN_GROUP = MoonriseCommon.WORKER_POOL.createExecutorGroup(SERVER_DIVISION, 0); ++ public static final PrioritisedThreadPool.ExecutorGroup RADIUS_AWARE_GROUP = MoonriseCommon.WORKER_POOL.createExecutorGroup(SERVER_DIVISION, 0); ++ public static final PrioritisedThreadPool.ExecutorGroup LOAD_GROUP = MoonriseCommon.WORKER_POOL.createExecutorGroup(SERVER_DIVISION, 0); ++ ++ public static void adjustWorkerThreads(final int configWorkerThreads, final int configIoThreads) { + int defaultWorkerThreads = Runtime.getRuntime().availableProcessors() / 2; + if (defaultWorkerThreads <= 4) { + defaultWorkerThreads = defaultWorkerThreads <= 3 ? 1 : 2; + } else { + defaultWorkerThreads = defaultWorkerThreads / 2; + } -+ defaultWorkerThreads = Integer.getInteger("Paper.WorkerThreadCount", Integer.valueOf(defaultWorkerThreads)); // Paper ++ defaultWorkerThreads = Integer.getInteger(PlatformHooks.get().getBrand() + ".WorkerThreadCount", Integer.valueOf(defaultWorkerThreads)); + -+ int workerThreads = chunkSystem.workerThreads; // Paper ++ int workerThreads = configWorkerThreads; + + if (workerThreads <= 0) { + workerThreads = defaultWorkerThreads; + } + -+ WORKER_POOL = new PrioritisedThreadPool( -+ "Paper Worker Pool", workerThreads, // Paper -+ (final Thread thread, final Integer id) -> { -+ thread.setName("Paper Common Worker #" + id.intValue()); // Paper ++ final int ioThreads = Math.max(1, configIoThreads); ++ ++ WORKER_POOL.adjustThreadCount(workerThreads); ++ IO_POOL.adjustThreadCount(ioThreads); ++ ++ LOGGER.info(PlatformHooks.get().getBrand() + " is using " + workerThreads + " worker threads, " + ioThreads + " I/O threads"); ++ } ++ ++ public static final PrioritisedThreadPool IO_POOL = new PrioritisedThreadPool( ++ new Consumer<>() { ++ private final AtomicInteger idGenerator = new AtomicInteger(); ++ ++ @Override ++ public void accept(final Thread thread) { ++ thread.setDaemon(true); ++ thread.setName(PlatformHooks.get().getBrand() + " I/O Worker #" + this.idGenerator.getAndIncrement()); + thread.setUncaughtExceptionHandler(new Thread.UncaughtExceptionHandler() { + @Override + public void uncaughtException(final Thread thread, final Throwable throwable) { + LOGGER.error("Uncaught exception in thread " + thread.getName(), throwable); + } + }); -+ }, (long)(20.0e6)); // 20ms -+ WORKER_THREADS = workerThreads; ++ } ++ } ++ ); ++ public static final long IO_QUEUE_HOLD_TIME = (long)(100.0e6); // 100ms ++ public static final PrioritisedThreadPool.ExecutorGroup CLIENT_PROFILER_IO_GROUP = IO_POOL.createExecutorGroup(CLIENT_DIVISION, 0); ++ public static final PrioritisedThreadPool.ExecutorGroup SERVER_REGION_IO_GROUP = IO_POOL.createExecutorGroup(SERVER_DIVISION, 0); ++ ++ public static void haltExecutors() { ++ MoonriseCommon.WORKER_POOL.shutdown(false); ++ LOGGER.info("Awaiting termination of worker pool for up to 60s..."); ++ if (!MoonriseCommon.WORKER_POOL.join(TimeUnit.SECONDS.toMillis(60L))) { ++ LOGGER.error("Worker pool did not shut down in time!"); ++ MoonriseCommon.WORKER_POOL.halt(false); ++ } ++ ++ MoonriseCommon.IO_POOL.shutdown(false); ++ LOGGER.info("Awaiting termination of I/O pool for up to 60s..."); ++ if (!MoonriseCommon.IO_POOL.join(TimeUnit.SECONDS.toMillis(60L))) { ++ LOGGER.error("I/O pool did not shut down in time!"); ++ MoonriseCommon.IO_POOL.halt(false); ++ } + } + + private MoonriseCommon() {} +} diff --git a/src/main/java/ca/spottedleaf/moonrise/common/util/MoonriseConstants.java b/src/main/java/ca/spottedleaf/moonrise/common/util/MoonriseConstants.java new file mode 100644 -index 0000000000000000000000000000000000000000..1cf32d7d1bbc8a0a3f7cb9024c793f6744199f64 +index 0000000000000000000000000000000000000000..559c959aff3c9deef867b9e425fba3e2e669cac6 --- /dev/null +++ b/src/main/java/ca/spottedleaf/moonrise/common/util/MoonriseConstants.java -@@ -0,0 +1,9 @@ +@@ -0,0 +1,11 @@ +package ca.spottedleaf.moonrise.common.util; + ++import ca.spottedleaf.moonrise.common.PlatformHooks; ++ +public final class MoonriseConstants { + -+ public static final int MAX_VIEW_DISTANCE = 32; ++ public static final int MAX_VIEW_DISTANCE = Integer.getInteger(PlatformHooks.get().getBrand() + ".MaxViewDistance", 32); + + private MoonriseConstants() {} + +} +diff --git a/src/main/java/ca/spottedleaf/moonrise/common/util/SimpleRandom.java b/src/main/java/ca/spottedleaf/moonrise/common/util/SimpleRandom.java +new file mode 100644 +index 0000000000000000000000000000000000000000..a9ff1c1a70faf4b7a64b265932f07a8b8f00c1ff +--- /dev/null ++++ b/src/main/java/ca/spottedleaf/moonrise/common/util/SimpleRandom.java +@@ -0,0 +1,52 @@ ++package ca.spottedleaf.moonrise.common.util; ++ ++import net.minecraft.world.level.levelgen.LegacyRandomSource; ++ ++/** ++ * Avoid costly CAS of superclass ++ */ ++public final class SimpleRandom extends LegacyRandomSource { ++ ++ private static final long MULTIPLIER = 25214903917L; ++ private static final long ADDEND = 11L; ++ private static final int BITS = 48; ++ private static final long MASK = (1L << BITS) - 1; ++ ++ private long value; ++ ++ public SimpleRandom(final long seed) { ++ super(0L); ++ this.value = seed; ++ } ++ ++ @Override ++ public void setSeed(final long seed) { ++ this.value = (seed ^ MULTIPLIER) & MASK; ++ } ++ ++ private long advanceSeed() { ++ return this.value = ((this.value * MULTIPLIER) + ADDEND) & MASK; ++ } ++ ++ @Override ++ public int next(final int bits) { ++ return (int)(this.advanceSeed() >>> (BITS - bits)); ++ } ++ ++ @Override ++ public int nextInt() { ++ final long seed = this.advanceSeed(); ++ return (int)(seed >>> (BITS - Integer.SIZE)); ++ } ++ ++ @Override ++ public int nextInt(final int bound) { ++ if (bound <= 0) { ++ throw new IllegalArgumentException(); ++ } ++ ++ // https://lemire.me/blog/2016/06/27/a-fast-alternative-to-the-modulo-reduction/ ++ final long value = this.advanceSeed() >>> (BITS - Integer.SIZE); ++ return (int)((value * (long)bound) >>> Integer.SIZE); ++ } ++} diff --git a/src/main/java/ca/spottedleaf/moonrise/common/util/TickThread.java b/src/main/java/ca/spottedleaf/moonrise/common/util/TickThread.java new file mode 100644 -index 0000000000000000000000000000000000000000..11b7f15755dde766140c29bedca456c80d53293f +index 0000000000000000000000000000000000000000..217d1f908a36a5177ba3cbb80a33f73d4dab0fa0 --- /dev/null +++ b/src/main/java/ca/spottedleaf/moonrise/common/util/TickThread.java -@@ -0,0 +1,139 @@ +@@ -0,0 +1,143 @@ +package ca.spottedleaf.moonrise.common.util; + +import net.minecraft.core.BlockPos; @@ -3806,11 +4180,15 @@ index 0000000000000000000000000000000000000000..11b7f15755dde766140c29bedca456c8 + } + + public TickThread(final Runnable run, final String name) { -+ this(run, name, ID_GENERATOR.incrementAndGet()); ++ this(null, run, name); ++ } ++ ++ public TickThread(final ThreadGroup group, final Runnable run, final String name) { ++ this(group, run, name, ID_GENERATOR.incrementAndGet()); + } + -+ private TickThread(final Runnable run, final String name, final int id) { -+ super(run, name); ++ private TickThread(final ThreadGroup group, final Runnable run, final String name, final int id) { ++ super(group, run, name); + this.id = id; + } + @@ -3868,10 +4246,10 @@ index 0000000000000000000000000000000000000000..11b7f15755dde766140c29bedca456c8 +} diff --git a/src/main/java/ca/spottedleaf/moonrise/common/util/WorldUtil.java b/src/main/java/ca/spottedleaf/moonrise/common/util/WorldUtil.java new file mode 100644 -index 0000000000000000000000000000000000000000..af9623240ff2d389aa7090623f507720e7dbab7d +index 0000000000000000000000000000000000000000..efda2688ae1254a82ba7f6bf8bf597ef224cbb86 --- /dev/null +++ b/src/main/java/ca/spottedleaf/moonrise/common/util/WorldUtil.java -@@ -0,0 +1,54 @@ +@@ -0,0 +1,62 @@ +package ca.spottedleaf.moonrise.common.util; + +import net.minecraft.world.level.Level; @@ -3882,11 +4260,19 @@ index 0000000000000000000000000000000000000000..af9623240ff2d389aa7090623f507720 + // min, max are inclusive + + public static int getMaxSection(final LevelHeightAccessor world) { -+ return world.getMaxSection() - 1; // getMaxSection() is exclusive ++ return world.getMaxSectionY(); ++ } ++ ++ public static int getMaxSection(final Level world) { ++ return world.getMaxSectionY(); + } + + public static int getMinSection(final LevelHeightAccessor world) { -+ return world.getMinSection(); ++ return world.getMinSectionY(); ++ } ++ ++ public static int getMinSection(final Level world) { ++ return world.getMinSectionY(); + } + + public static int getMaxLightSection(final LevelHeightAccessor world) { @@ -3926,6 +4312,221 @@ index 0000000000000000000000000000000000000000..af9623240ff2d389aa7090623f507720 + throw new RuntimeException(); + } +} +diff --git a/src/main/java/ca/spottedleaf/moonrise/paper/PaperHooks.java b/src/main/java/ca/spottedleaf/moonrise/paper/PaperHooks.java +new file mode 100644 +index 0000000000000000000000000000000000000000..1aa6be257ce594d7a69fdff008cd29014a04fd75 +--- /dev/null ++++ b/src/main/java/ca/spottedleaf/moonrise/paper/PaperHooks.java +@@ -0,0 +1,209 @@ ++package ca.spottedleaf.moonrise.paper; ++ ++import ca.spottedleaf.moonrise.common.PlatformHooks; ++import com.mojang.datafixers.DSL; ++import com.mojang.datafixers.DataFixer; ++import com.mojang.serialization.Dynamic; ++import net.minecraft.core.BlockPos; ++import net.minecraft.nbt.CompoundTag; ++import net.minecraft.nbt.NbtOps; ++import net.minecraft.server.level.ChunkHolder; ++import net.minecraft.server.level.GenerationChunkHolder; ++import net.minecraft.server.level.ServerLevel; ++import net.minecraft.server.level.ServerPlayer; ++import net.minecraft.world.entity.Entity; ++import net.minecraft.world.level.BlockGetter; ++import net.minecraft.world.level.ChunkPos; ++import net.minecraft.world.level.Level; ++import net.minecraft.world.level.block.state.BlockState; ++import net.minecraft.world.level.chunk.ChunkAccess; ++import net.minecraft.world.level.chunk.LevelChunk; ++import net.minecraft.world.level.chunk.ProtoChunk; ++import net.minecraft.world.level.chunk.storage.SerializableChunkData; ++import net.minecraft.world.level.entity.EntityTypeTest; ++import net.minecraft.world.phys.AABB; ++import java.util.List; ++import java.util.function.Predicate; ++ ++public final class PaperHooks implements PlatformHooks { ++ ++ @Override ++ public String getBrand() { ++ return "Paper"; ++ } ++ ++ @Override ++ public int getLightEmission(final BlockState blockState, final BlockGetter world, final BlockPos pos) { ++ return blockState.getLightEmission(); ++ } ++ ++ @Override ++ public Predicate maybeHasLightEmission() { ++ return (final BlockState state) -> { ++ return state.getLightEmission() != 0; ++ }; ++ } ++ ++ @Override ++ public boolean hasCurrentlyLoadingChunk() { ++ return false; ++ } ++ ++ @Override ++ public LevelChunk getCurrentlyLoadingChunk(final GenerationChunkHolder holder) { ++ return null; ++ } ++ ++ @Override ++ public void setCurrentlyLoading(final GenerationChunkHolder holder, final LevelChunk levelChunk) { ++ ++ } ++ ++ @Override ++ public void chunkFullStatusComplete(final LevelChunk newChunk, final ProtoChunk original) { ++ ++ } ++ ++ @Override ++ public boolean allowAsyncTicketUpdates() { ++ return true; ++ } ++ ++ @Override ++ public void onChunkHolderTicketChange(final ServerLevel world, final ChunkHolder holder, final int oldLevel, final int newLevel) { ++ ++ } ++ ++ @Override ++ public void chunkUnloadFromWorld(final LevelChunk chunk) { ++ ++ } ++ ++ @Override ++ public void chunkSyncSave(final ServerLevel world, final ChunkAccess chunk, final SerializableChunkData data) { ++ ++ } ++ ++ @Override ++ public void onChunkWatch(final ServerLevel world, final LevelChunk chunk, final ServerPlayer player) { ++ ++ } ++ ++ @Override ++ public void onChunkUnWatch(final ServerLevel world, final ChunkPos chunk, final ServerPlayer player) { ++ ++ } ++ ++ @Override ++ public void addToGetEntities(final Level world, final Entity entity, final AABB boundingBox, final Predicate predicate, final List into) { ++ ++ } ++ ++ @Override ++ public void addToGetEntities(final Level world, final EntityTypeTest entityTypeTest, final AABB boundingBox, final Predicate predicate, final List into, final int maxCount) { ++ ++ } ++ ++ @Override ++ public void entityMove(final Entity entity, final long oldSection, final long newSection) { ++ ++ } ++ ++ @Override ++ public boolean screenEntity(final ServerLevel world, final Entity entity, final boolean fromDisk, final boolean event) { ++ return true; ++ } ++ ++ @Override ++ public boolean configFixMC224294() { ++ return true; ++ } ++ ++ @Override ++ public boolean configAutoConfigSendDistance() { ++ return io.papermc.paper.configuration.GlobalConfiguration.get().chunkLoadingAdvanced.autoConfigSendDistance; ++ } ++ ++ @Override ++ public double configPlayerMaxLoadRate() { ++ return io.papermc.paper.configuration.GlobalConfiguration.get().chunkLoadingBasic.playerMaxChunkLoadRate; ++ } ++ ++ @Override ++ public double configPlayerMaxGenRate() { ++ return io.papermc.paper.configuration.GlobalConfiguration.get().chunkLoadingBasic.playerMaxChunkGenerateRate; ++ } ++ ++ @Override ++ public double configPlayerMaxSendRate() { ++ return io.papermc.paper.configuration.GlobalConfiguration.get().chunkLoadingBasic.playerMaxChunkSendRate; ++ } ++ ++ @Override ++ public int configPlayerMaxConcurrentLoads() { ++ return io.papermc.paper.configuration.GlobalConfiguration.get().chunkLoadingAdvanced.playerMaxConcurrentChunkLoads; ++ } ++ ++ @Override ++ public int configPlayerMaxConcurrentGens() { ++ return io.papermc.paper.configuration.GlobalConfiguration.get().chunkLoadingAdvanced.playerMaxConcurrentChunkGenerates; ++ } ++ ++ @Override ++ public long configAutoSaveInterval(final ServerLevel world) { ++ return world.paperConfig().chunks.autoSaveInterval.value(); ++ } ++ ++ @Override ++ public int configMaxAutoSavePerTick(final ServerLevel world) { ++ return world.paperConfig().chunks.maxAutoSaveChunksPerTick; ++ } ++ ++ @Override ++ public boolean configFixMC159283() { ++ return true; ++ } ++ ++ @Override ++ public boolean forceNoSave(final ChunkAccess chunk) { ++ return chunk instanceof LevelChunk levelChunk && levelChunk.mustNotSave; ++ } ++ ++ @Override ++ public CompoundTag convertNBT(final DSL.TypeReference type, final DataFixer dataFixer, final CompoundTag nbt, ++ final int fromVersion, final int toVersion) { ++ return (CompoundTag)dataFixer.update( ++ type, new Dynamic<>(NbtOps.INSTANCE, nbt), fromVersion, toVersion ++ ).getValue(); ++ } ++ ++ @Override ++ public boolean hasMainChunkLoadHook() { ++ return false; ++ } ++ ++ @Override ++ public void mainChunkLoad(final ChunkAccess chunk, final SerializableChunkData chunkData) { ++ ++ } ++ ++ @Override ++ public List modifySavedEntities(final ServerLevel world, final int chunkX, final int chunkZ, final List entities) { ++ return entities; ++ } ++ ++ @Override ++ public void unloadEntity(final Entity entity) { ++ entity.setRemoved(Entity.RemovalReason.UNLOADED_TO_CHUNK, org.bukkit.event.entity.EntityRemoveEvent.Cause.UNLOAD); ++ } ++ ++ @Override ++ public void postLoadProtoChunk(final ServerLevel world, final ProtoChunk chunk) { ++ net.minecraft.world.level.chunk.status.ChunkStatusTasks.postLoadProtoChunk(world, chunk.getEntities()); ++ } ++ ++ @Override ++ public int modifyEntityTrackingRange(final Entity entity, final int currentRange) { ++ return org.spigotmc.TrackingRange.getEntityTrackingRange(entity, currentRange); ++ } ++} diff --git a/src/main/java/com/mojang/logging/LogUtils.java b/src/main/java/com/mojang/logging/LogUtils.java index 46cab7a8c7b87ab01b26074b04f5a02b3907cfc4..49019b4a9bc4e634d54a9b0acaf9229a5c896f85 100644 --- a/src/main/java/com/mojang/logging/LogUtils.java @@ -3940,6 +4541,19 @@ index 46cab7a8c7b87ab01b26074b04f5a02b3907cfc4..49019b4a9bc4e634d54a9b0acaf9229a + } + // Paper end } +diff --git a/src/main/java/io/papermc/paper/configuration/GlobalConfiguration.java b/src/main/java/io/papermc/paper/configuration/GlobalConfiguration.java +index f0d470d7770e119f734b9e72021c806d0ea8ecbd..c3fe4481dd35f80815716e48beeeb07b1f51e30b 100644 +--- a/src/main/java/io/papermc/paper/configuration/GlobalConfiguration.java ++++ b/src/main/java/io/papermc/paper/configuration/GlobalConfiguration.java +@@ -217,7 +217,7 @@ public class GlobalConfiguration extends ConfigurationPart { + + @PostProcess + private void postProcess() { +- ++ ca.spottedleaf.moonrise.common.util.MoonriseCommon.adjustWorkerThreads(this.workerThreads, this.ioThreads); + } + } + diff --git a/src/main/java/io/papermc/paper/util/IntervalledCounter.java b/src/main/java/io/papermc/paper/util/IntervalledCounter.java new file mode 100644 index 0000000000000000000000000000000000000000..197224e31175252d8438a8df585bbb65f2288d7f @@ -4077,10 +4691,10 @@ index 0000000000000000000000000000000000000000..197224e31175252d8438a8df585bbb65 +} diff --git a/src/main/java/io/papermc/paper/util/MCUtil.java b/src/main/java/io/papermc/paper/util/MCUtil.java new file mode 100644 -index 0000000000000000000000000000000000000000..0449d4619e3a0752dea0981fb149542e23076c52 +index 0000000000000000000000000000000000000000..422bc104e5bdd4ae786b14d97eb779dc76bfad69 --- /dev/null +++ b/src/main/java/io/papermc/paper/util/MCUtil.java -@@ -0,0 +1,176 @@ +@@ -0,0 +1,190 @@ +package io.papermc.paper.util; + +import com.google.common.util.concurrent.ThreadFactoryBuilder; @@ -4098,11 +4712,14 @@ index 0000000000000000000000000000000000000000..0449d4619e3a0752dea0981fb149542e +import java.util.function.Supplier; +import net.minecraft.core.BlockPos; +import net.minecraft.core.Vec3i; ++import net.minecraft.resources.ResourceKey; +import net.minecraft.server.MinecraftServer; +import net.minecraft.world.level.ChunkPos; +import net.minecraft.world.level.Level; +import net.minecraft.world.phys.Vec3; +import org.bukkit.Location; ++import org.bukkit.NamespacedKey; ++import org.bukkit.craftbukkit.util.CraftNamespacedKey; +import org.bukkit.craftbukkit.util.Waitable; + +public final class MCUtil { @@ -4256,6 +4873,17 @@ index 0000000000000000000000000000000000000000..0449d4619e3a0752dea0981fb149542e + public static void scheduleAsyncTask(Runnable run) { + asyncExecutor.execute(run); + } ++ ++ public static ResourceKey toResourceKey( ++ final ResourceKey> registry, ++ final NamespacedKey namespacedKey ++ ) { ++ return ResourceKey.create(registry, CraftNamespacedKey.toMinecraft(namespacedKey)); ++ } ++ ++ public static NamespacedKey fromResourceKey(final ResourceKey key) { ++ return CraftNamespacedKey.fromMinecraft(key.location()); ++ } +} diff --git a/src/main/java/io/papermc/paper/util/StackWalkerUtil.java b/src/main/java/io/papermc/paper/util/StackWalkerUtil.java new file mode 100644 @@ -4288,10 +4916,10 @@ index 0000000000000000000000000000000000000000..f7114d5b8f2f93f62883e24da29afaf9 + } +} diff --git a/src/main/java/net/minecraft/Util.java b/src/main/java/net/minecraft/Util.java -index 5135cd504ec5864a4603c004e748947a7d88d2b4..396f368a7e21a7c7b1630b4e20cdbc452c4b0f84 100644 +index 0842080f1840a83b34c2cc829dfddba78ba12157..2ff5a6517d717bbd4c944572040bd30866347341 100644 --- a/src/main/java/net/minecraft/Util.java +++ b/src/main/java/net/minecraft/Util.java -@@ -133,7 +133,7 @@ public class Util { +@@ -136,7 +136,7 @@ public class Util { } public static long getNanos() { @@ -4416,10 +5044,10 @@ index 3e5a85a7ad6149b04622c254fbc2e174896a4128..3f662692ed4846e026a9d48595e7b3b2 + } diff --git a/src/main/java/net/minecraft/server/MinecraftServer.java b/src/main/java/net/minecraft/server/MinecraftServer.java -index 40adb6117b9e0d5f70103113202a07715e403e2a..c9cab509796599b13ca3422de1049aed78348704 100644 +index 8e16bc7da15824723f1d7d4bff87fac181978500..709330ca9caa82a6de71767b3d5c32f97ea1d68b 100644 --- a/src/main/java/net/minecraft/server/MinecraftServer.java +++ b/src/main/java/net/minecraft/server/MinecraftServer.java -@@ -973,6 +973,9 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop mainThreadExecutor, LightChunkGetter chunkProvider, ChunkGenerator chunkGenerator, ChunkProgressListener worldGenerationProgressListener, ChunkStatusUpdateListener chunkStatusChangeListener, Supplier persistentStateManagerFactory, int viewDistance, boolean dsync) { super(new RegionStorageInfo(session.getLevelId(), world.dimension(), "chunk"), session.getDimensionPath(world.dimension()).resolve("region"), dataFixer, dsync); this.visibleChunkMap = this.updatingChunkMap.clone(); -@@ -223,6 +229,12 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider - this.worldGenContext = new WorldGenContext(world, chunkGenerator, structureTemplateManager, this.lightEngine, this.mainThreadMailbox); +@@ -229,6 +235,12 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider + this.chunksToEagerlySave.add(pos.toLong()); } + // Paper start @@ -4575,7 +5195,7 @@ index 5b920beb39dad8d392b4e5e12a89880720e41942..449608e60f3900778247101581ff598f protected ChunkGenerator generator() { return this.worldGenContext.generator(); } -@@ -378,9 +390,9 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider +@@ -385,9 +397,9 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider }; stringbuilder.append("Updating:").append(System.lineSeparator()); @@ -4587,17 +5207,17 @@ index 5b920beb39dad8d392b4e5e12a89880720e41942..449608e60f3900778247101581ff598f CrashReport crashreport = CrashReport.forThrowable(exception, "Chunk loading"); CrashReportCategory crashreportsystemdetails = crashreport.addCategory("Chunk loading"); -@@ -422,6 +434,9 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider +@@ -429,6 +441,9 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider holder.setTicketLevel(level); } else { - holder = new ChunkHolder(new ChunkPos(pos), level, this.level, this.lightEngine, this.queueSorter, this); + holder = new ChunkHolder(new ChunkPos(pos), level, this.level, this.lightEngine, this::onLevelChange, this); + // Paper start + ca.spottedleaf.moonrise.common.util.ChunkSystem.onChunkHolderCreate(this.level, holder); + // Paper end } this.updatingChunkMap.put(pos, holder); -@@ -445,7 +460,7 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider +@@ -458,7 +473,7 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider protected void saveAllChunks(boolean flush) { if (flush) { @@ -4606,58 +5226,50 @@ index 5b920beb39dad8d392b4e5e12a89880720e41942..449608e60f3900778247101581ff598f MutableBoolean mutableboolean = new MutableBoolean(); do { -@@ -468,7 +483,7 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider - }); - this.flushWorker(); +@@ -484,7 +499,7 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider } else { -- this.visibleChunkMap.values().forEach(this::saveChunkIfNeeded); -+ ca.spottedleaf.moonrise.common.util.ChunkSystem.getVisibleChunkHolders(this.level).forEach(this::saveChunkIfNeeded); - } + this.nextChunkSaveTime.clear(); + long i = Util.getMillis(); +- ObjectIterator objectiterator = this.visibleChunkMap.values().iterator(); ++ Iterator objectiterator = ca.spottedleaf.moonrise.common.util.ChunkSystem.getVisibleChunkHolders(this.level).iterator(); // Paper - } -@@ -487,7 +502,7 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider + while (objectiterator.hasNext()) { + ChunkHolder playerchunk = (ChunkHolder) objectiterator.next(); +@@ -509,7 +524,7 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider } public boolean hasWork() { -- return this.lightEngine.hasLightWork() || !this.pendingUnloads.isEmpty() || !this.updatingChunkMap.isEmpty() || this.poiManager.hasWork() || !this.toDrop.isEmpty() || !this.unloadQueue.isEmpty() || this.queueSorter.hasWork() || this.distanceManager.hasTickets(); -+ return this.lightEngine.hasLightWork() || !this.pendingUnloads.isEmpty() || ca.spottedleaf.moonrise.common.util.ChunkSystem.hasAnyChunkHolders(this.level) || this.poiManager.hasWork() || !this.toDrop.isEmpty() || !this.unloadQueue.isEmpty() || this.queueSorter.hasWork() || this.distanceManager.hasTickets(); // Paper +- return this.lightEngine.hasLightWork() || !this.pendingUnloads.isEmpty() || !this.updatingChunkMap.isEmpty() || this.poiManager.hasWork() || !this.toDrop.isEmpty() || !this.unloadQueue.isEmpty() || this.worldgenTaskDispatcher.hasWork() || this.lightTaskDispatcher.hasWork() || this.distanceManager.hasTickets(); ++ return this.lightEngine.hasLightWork() || !this.pendingUnloads.isEmpty() || ca.spottedleaf.moonrise.common.util.ChunkSystem.hasAnyChunkHolders(this.level) || !this.updatingChunkMap.isEmpty() || this.poiManager.hasWork() || !this.toDrop.isEmpty() || !this.unloadQueue.isEmpty() || this.worldgenTaskDispatcher.hasWork() || this.lightTaskDispatcher.hasWork() || this.distanceManager.hasTickets(); } private void processUnloads(BooleanSupplier shouldKeepTicking) { -@@ -523,7 +538,7 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider - } - - int l = 0; -- ObjectIterator objectiterator = this.visibleChunkMap.values().iterator(); -+ Iterator objectiterator = ca.spottedleaf.moonrise.common.util.ChunkSystem.getVisibleChunkHolders(this.level).iterator(); // Paper - - while (l < 20 && shouldKeepTicking.getAsBoolean() && objectiterator.hasNext()) { - if (this.saveChunkIfNeeded((ChunkHolder) objectiterator.next())) { -@@ -541,7 +556,11 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider +@@ -568,8 +583,11 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider + this.scheduleUnload(pos, chunk); } else { - ChunkAccess ichunkaccess = holder.getLatestChunk(); - -- if (this.pendingUnloads.remove(pos, holder) && ichunkaccess != null) { + ChunkAccess ichunkaccess = chunk.getLatestChunk(); +- +- if (this.pendingUnloads.remove(pos, chunk) && ichunkaccess != null) { + // Paper start + boolean removed; -+ if ((removed = this.pendingUnloads.remove(pos, holder)) && ichunkaccess != null) { -+ ca.spottedleaf.moonrise.common.util.ChunkSystem.onChunkHolderDelete(this.level, holder); ++ if ((removed = this.pendingUnloads.remove(pos, chunk)) && ichunkaccess != null) { ++ ca.spottedleaf.moonrise.common.util.ChunkSystem.onChunkHolderDelete(this.level, chunk); + // Paper end - LevelChunk chunk; + LevelChunk chunk1; if (ichunkaccess instanceof LevelChunk) { -@@ -559,7 +578,9 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider +@@ -587,7 +605,9 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider this.lightEngine.tryScheduleUpdate(); this.progressListener.onStatusChange(ichunkaccess.getPos(), (ChunkStatus) null); - this.chunkSaveCooldowns.remove(ichunkaccess.getPos().toLong()); + this.nextChunkSaveTime.remove(ichunkaccess.getPos().toLong()); - } + } else if (removed) { // Paper start -+ ca.spottedleaf.moonrise.common.util.ChunkSystem.onChunkHolderDelete(this.level, holder); ++ ca.spottedleaf.moonrise.common.util.ChunkSystem.onChunkHolderDelete(this.level, chunk); + } // Paper end } }; -@@ -896,7 +917,7 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider +@@ -936,7 +956,7 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider } } @@ -4666,7 +5278,7 @@ index 5b920beb39dad8d392b4e5e12a89880720e41942..449608e60f3900778247101581ff598f int j = Mth.clamp(watchDistance, 2, 32); if (j != this.serverViewDistance) { -@@ -913,7 +934,7 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider +@@ -953,7 +973,7 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider } @@ -4675,7 +5287,7 @@ index 5b920beb39dad8d392b4e5e12a89880720e41942..449608e60f3900778247101581ff598f return Mth.clamp(player.requestedViewDistance(), 2, this.serverViewDistance); } -@@ -942,7 +963,7 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider +@@ -982,7 +1002,7 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider } public int size() { @@ -4684,7 +5296,7 @@ index 5b920beb39dad8d392b4e5e12a89880720e41942..449608e60f3900778247101581ff598f } public DistanceManager getDistanceManager() { -@@ -950,19 +971,19 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider +@@ -990,19 +1010,19 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider } protected Iterable getChunks() { @@ -4709,7 +5321,7 @@ index 5b920beb39dad8d392b4e5e12a89880720e41942..449608e60f3900778247101581ff598f Optional optional = Optional.ofNullable(playerchunk.getLatestChunk()); Optional optional1 = optional.flatMap((ichunkaccess) -> { return ichunkaccess instanceof LevelChunk ? Optional.of((LevelChunk) ichunkaccess) : Optional.empty(); -@@ -1385,7 +1406,7 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider +@@ -1445,7 +1465,7 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider }); } @@ -4719,10 +5331,10 @@ index 5b920beb39dad8d392b4e5e12a89880720e41942..449608e60f3900778247101581ff598f protected ChunkDistanceManager(final Executor workerExecutor, final Executor mainThreadExecutor) { super(workerExecutor, mainThreadExecutor); diff --git a/src/main/java/net/minecraft/server/level/DistanceManager.java b/src/main/java/net/minecraft/server/level/DistanceManager.java -index d3769ad50ddb556cff515ea8cf853d28e490ca94..dfa0456f352ce25bc4edd1b0f04ca5a14434d7fa 100644 +index 019228b1809e3816b0b4dfb9f19b8d42876cc240..6c2339d6a93172e25040c4868a3a47473a1f5336 100644 --- a/src/main/java/net/minecraft/server/level/DistanceManager.java +++ b/src/main/java/net/minecraft/server/level/DistanceManager.java -@@ -370,7 +370,7 @@ public abstract class DistanceManager { +@@ -385,7 +385,7 @@ public abstract class DistanceManager { } public void removeTicketsOnClosing() { @@ -4732,18 +5344,10 @@ index d3769ad50ddb556cff515ea8cf853d28e490ca94..dfa0456f352ce25bc4edd1b0f04ca5a1 while (objectiterator.hasNext()) { diff --git a/src/main/java/net/minecraft/server/level/ServerChunkCache.java b/src/main/java/net/minecraft/server/level/ServerChunkCache.java -index d39268911ed7c4d60ee6a82178be23245aae58c4..cf94dd9ddcc1eabcf3fd336e70720f4ed3e52175 100644 +index 9cdcab885a915990a679f3fc9ae6885f7d125bfd..c615510f3f59292715bcff1bd9e4e896c9733436 100644 --- a/src/main/java/net/minecraft/server/level/ServerChunkCache.java +++ b/src/main/java/net/minecraft/server/level/ServerChunkCache.java -@@ -48,6 +48,7 @@ import net.minecraft.world.level.storage.LevelStorageSource; - - public class ServerChunkCache extends ChunkSource { - -+ public static final org.slf4j.Logger LOGGER = com.mojang.logging.LogUtils.getLogger(); // Paper - private static final List CHUNK_STATUSES = ChunkStatus.getStatusList(); - private final DistanceManager distanceManager; - final ServerLevel level; -@@ -66,6 +67,10 @@ public class ServerChunkCache extends ChunkSource { +@@ -74,6 +74,10 @@ public class ServerChunkCache extends ChunkSource { @Nullable @VisibleForDebug private NaturalSpawner.SpawnState lastSpawnState; @@ -4754,7 +5358,7 @@ index d39268911ed7c4d60ee6a82178be23245aae58c4..cf94dd9ddcc1eabcf3fd336e70720f4e public ServerChunkCache(ServerLevel world, LevelStorageSource.LevelStorageAccess session, DataFixer dataFixer, StructureTemplateManager structureTemplateManager, Executor workerExecutor, ChunkGenerator chunkGenerator, int viewDistance, int simulationDistance, boolean dsync, ChunkProgressListener worldGenerationProgressListener, ChunkStatusUpdateListener chunkStatusChangeListener, Supplier persistentStateManagerFactory) { this.level = world; -@@ -91,6 +96,54 @@ public class ServerChunkCache extends ChunkSource { +@@ -104,6 +108,54 @@ public class ServerChunkCache extends ChunkSource { return chunk.getFullChunkNow() != null; } // CraftBukkit end @@ -4809,7 +5413,7 @@ index d39268911ed7c4d60ee6a82178be23245aae58c4..cf94dd9ddcc1eabcf3fd336e70720f4e @Override public ThreadedLevelLightEngine getLightEngine() { -@@ -286,7 +339,7 @@ public class ServerChunkCache extends ChunkSource { +@@ -299,7 +351,7 @@ public class ServerChunkCache extends ChunkSource { return this.mainThreadProcessor.pollTask(); } @@ -4818,7 +5422,7 @@ index d39268911ed7c4d60ee6a82178be23245aae58c4..cf94dd9ddcc1eabcf3fd336e70720f4e boolean flag = this.distanceManager.runAllUpdates(this.chunkMap); boolean flag1 = this.chunkMap.promoteChunkMap(); -@@ -299,6 +352,12 @@ public class ServerChunkCache extends ChunkSource { +@@ -312,6 +364,12 @@ public class ServerChunkCache extends ChunkSource { } } @@ -4829,13 +5433,13 @@ index d39268911ed7c4d60ee6a82178be23245aae58c4..cf94dd9ddcc1eabcf3fd336e70720f4e + // Paper end + public boolean isPositionTicking(long pos) { - ChunkHolder playerchunk = this.getVisibleChunkIfPresent(pos); - + if (!this.level.shouldTickBlocksAt(pos)) { + return false; diff --git a/src/main/java/net/minecraft/server/level/ServerLevel.java b/src/main/java/net/minecraft/server/level/ServerLevel.java -index 9d11fcb3df12182ae00ce73f7e30091fd199a341..4c39d9e0466240b5cd459ee649a22fe3a72bf9f0 100644 +index f6a3606b972064c4ec78487374e6197c0c447e27..c6ded1ac73ddbc0336000f77c0f99fa20551a0de 100644 --- a/src/main/java/net/minecraft/server/level/ServerLevel.java +++ b/src/main/java/net/minecraft/server/level/ServerLevel.java -@@ -236,6 +236,98 @@ public class ServerLevel extends Level implements WorldGenLevel { +@@ -239,6 +239,98 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe return this.convertable.dimensionType; } @@ -4869,7 +5473,7 @@ index 9d11fcb3df12182ae00ce73f7e30091fd199a341..4c39d9e0466240b5cd459ee649a22fe3 + return true; + } + -+ public final void loadChunksForMoveAsync(AABB axisalignedbb, ca.spottedleaf.concurrentutil.executor.standard.PrioritisedExecutor.Priority priority, ++ public final void loadChunksForMoveAsync(AABB axisalignedbb, ca.spottedleaf.concurrentutil.util.Priority priority, + java.util.function.Consumer> onLoad) { + if (Thread.currentThread() != this.thread) { + this.getChunkSource().mainThreadProcessor.execute(() -> { @@ -4933,12 +5537,12 @@ index 9d11fcb3df12182ae00ce73f7e30091fd199a341..4c39d9e0466240b5cd459ee649a22fe3 + // Add env and gen to constructor, IWorldDataServer -> WorldDataServer public ServerLevel(MinecraftServer minecraftserver, Executor executor, LevelStorageSource.LevelStorageAccess convertable_conversionsession, PrimaryLevelData iworlddataserver, ResourceKey resourcekey, LevelStem worlddimension, ChunkProgressListener worldloadlistener, boolean flag, long i, List list, boolean flag1, @Nullable RandomSequences randomsequences, org.bukkit.World.Environment env, org.bukkit.generator.ChunkGenerator gen, org.bukkit.generator.BiomeProvider biomeProvider) { - // IRegistryCustom.Dimension iregistrycustom_dimension = minecraftserver.registryAccess(); // CraftBukkit - decompile error + super(iworlddataserver, resourcekey, minecraftserver.registryAccess(), worlddimension.type(), false, flag, i, minecraftserver.getMaxChainedNeighborUpdates(), gen, biomeProvider, env, spigotConfig -> minecraftserver.paperConfigurations.createWorldConfig(io.papermc.paper.configuration.PaperConfigurations.createWorldContextMap(convertable_conversionsession.levelDirectory.path(), iworlddataserver.getLevelName(), resourcekey.location(), spigotConfig, minecraftserver.registryAccess(), iworlddataserver.getGameRules()))); // Paper - create paper world configs diff --git a/src/main/java/net/minecraft/server/level/ServerPlayer.java b/src/main/java/net/minecraft/server/level/ServerPlayer.java -index ff1a8e62d2bb3de62e0c27b2335cb512ea91dedd..cb136a30287a17947ed018cdc48e6f91ed904072 100644 +index 0c211366be68e33b24da2b055142626968ed6b0b..32e7c6e6f09e53fe8b5ade22dad8142cd09e0163 100644 --- a/src/main/java/net/minecraft/server/level/ServerPlayer.java +++ b/src/main/java/net/minecraft/server/level/ServerPlayer.java -@@ -281,6 +281,7 @@ public class ServerPlayer extends net.minecraft.world.entity.player.Player { +@@ -309,6 +309,7 @@ public class ServerPlayer extends net.minecraft.world.entity.player.Player { public boolean sentListPacket = false; public String kickLeaveMessage = null; // SPIGOT-3034: Forward leave message to PlayerQuitEvent // CraftBukkit end @@ -4947,7 +5551,7 @@ index ff1a8e62d2bb3de62e0c27b2335cb512ea91dedd..cb136a30287a17947ed018cdc48e6f91 public ServerPlayer(MinecraftServer server, ServerLevel world, GameProfile profile, ClientInformation clientOptions) { super(world, world.getSharedSpawnPos(), world.getSharedSpawnAngle(), profile); diff --git a/src/main/java/net/minecraft/server/level/TicketType.java b/src/main/java/net/minecraft/server/level/TicketType.java -index 045b754b5b70bbd1e7732ad2142dfadd6cc2305c..f56e5c0f53f9b52a9247b9be9265b949494fc924 100644 +index 011eeab289d68b9b535c6438016f1244d0666286..e701f57ac8b0efdf739389f9be7a255220bb3e21 100644 --- a/src/main/java/net/minecraft/server/level/TicketType.java +++ b/src/main/java/net/minecraft/server/level/TicketType.java @@ -7,6 +7,7 @@ import net.minecraft.util.Unit; @@ -4959,7 +5563,7 @@ index 045b754b5b70bbd1e7732ad2142dfadd6cc2305c..f56e5c0f53f9b52a9247b9be9265b949 private final String name; private final Comparator comparator; diff --git a/src/main/java/net/minecraft/server/level/WorldGenRegion.java b/src/main/java/net/minecraft/server/level/WorldGenRegion.java -index 7d69da7e761ccfe736656e8c89dd1ae08956695f..421f146ea9c35b852251c0ddb29856c13e11aef3 100644 +index a1f4ebcd0877a6d0c41493eff5d70a408bf98e59..f1725ef766c35aa623ace58fe8bf31fc9b2bb6b3 100644 --- a/src/main/java/net/minecraft/server/level/WorldGenRegion.java +++ b/src/main/java/net/minecraft/server/level/WorldGenRegion.java @@ -169,6 +169,26 @@ public class WorldGenRegion implements WorldGenLevel { @@ -4990,10 +5594,10 @@ index 7d69da7e761ccfe736656e8c89dd1ae08956695f..421f146ea9c35b852251c0ddb29856c1 public BlockState getBlockState(BlockPos pos) { return this.getChunk(SectionPos.blockToSectionCoord(pos.getX()), SectionPos.blockToSectionCoord(pos.getZ())).getBlockState(pos); diff --git a/src/main/java/net/minecraft/server/players/PlayerList.java b/src/main/java/net/minecraft/server/players/PlayerList.java -index 0914b2f9fef1f49df9f0ce7c85cdde94c2c39b6c..6abe921099ff00ecfaf0f423ef27d708420f6f48 100644 +index 23a611c9a83d1cea5ab2f64cdbd696c39872477d..bf660a057d135563b47e7b74d927a82a20b8ef75 100644 --- a/src/main/java/net/minecraft/server/players/PlayerList.java +++ b/src/main/java/net/minecraft/server/players/PlayerList.java -@@ -180,6 +180,7 @@ public abstract class PlayerList { +@@ -181,6 +181,7 @@ public abstract class PlayerList { } public void placeNewPlayer(Connection connection, ServerPlayer player, CommonListenerCookie clientData) { @@ -5002,10 +5606,10 @@ index 0914b2f9fef1f49df9f0ce7c85cdde94c2c39b6c..6abe921099ff00ecfaf0f423ef27d708 GameProfileCache usercache = this.server.getProfileCache(); // Optional optional; // CraftBukkit - decompile error diff --git a/src/main/java/net/minecraft/util/thread/BlockableEventLoop.java b/src/main/java/net/minecraft/util/thread/BlockableEventLoop.java -index aede9b65e799a1f123f71f9390fb05acddda676b..ca94a1aaccdcc9f28b5f7936b871216a75ab762a 100644 +index ae25aec117a7272735c824a00c1ed117fa52a921..d6e942aca1bcc769c390504a4119d6619872c4d4 100644 --- a/src/main/java/net/minecraft/util/thread/BlockableEventLoop.java +++ b/src/main/java/net/minecraft/util/thread/BlockableEventLoop.java -@@ -79,6 +79,13 @@ public abstract class BlockableEventLoop implements Profiler +@@ -82,6 +82,13 @@ public abstract class BlockableEventLoop implements Profiler runnable.run(); } } @@ -5013,17 +5617,17 @@ index aede9b65e799a1f123f71f9390fb05acddda676b..ca94a1aaccdcc9f28b5f7936b871216a + public void scheduleOnMain(Runnable runnable) { + // postToMainThread does not work the same as older versions of mc + // This method is actually used to create a TickTask, which can then be posted onto main -+ this.tell(this.wrapRunnable(runnable)); ++ this.schedule(this.wrapRunnable(runnable)); + } + // Paper end @Override - public void tell(R runnable) { + public void schedule(R runnable) { diff --git a/src/main/java/net/minecraft/world/entity/Entity.java b/src/main/java/net/minecraft/world/entity/Entity.java -index 05a4056b242159b1c85aa6ebf43b69cf85c00021..06cbe7a7ea131a8bead857cbfbd27810a9093320 100644 +index 1631ea62aab53d6e5e0ae44f63367416ac424410..c010d18061f58a583c69e85fc29305497523f569 100644 --- a/src/main/java/net/minecraft/world/entity/Entity.java +++ b/src/main/java/net/minecraft/world/entity/Entity.java -@@ -339,6 +339,11 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess +@@ -342,6 +342,11 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess return this.level.hasChunk((int) Math.floor(this.getX()) >> 4, (int) Math.floor(this.getZ()) >> 4); } // CraftBukkit end @@ -5036,10 +5640,10 @@ index 05a4056b242159b1c85aa6ebf43b69cf85c00021..06cbe7a7ea131a8bead857cbfbd27810 public Entity(EntityType type, Level world) { this.id = Entity.ENTITY_COUNTER.incrementAndGet(); diff --git a/src/main/java/net/minecraft/world/entity/LivingEntity.java b/src/main/java/net/minecraft/world/entity/LivingEntity.java -index c1db114edd9e31273b76374cbd19710b01cada2b..26064174397dc95f9b117d901e22c55abebf3c39 100644 +index 47712d062ece9914de058153272ab1fa535af372..9aa4e70f1d1c4de2138d31701dceaed25062e69c 100644 --- a/src/main/java/net/minecraft/world/entity/LivingEntity.java +++ b/src/main/java/net/minecraft/world/entity/LivingEntity.java -@@ -282,6 +282,7 @@ public abstract class LivingEntity extends Entity implements Attackable { +@@ -296,6 +296,7 @@ public abstract class LivingEntity extends Entity implements Attackable { public boolean collides = true; public Set collidableExemptions = new HashSet<>(); public boolean bukkitPickUpLoot; @@ -5048,10 +5652,10 @@ index c1db114edd9e31273b76374cbd19710b01cada2b..26064174397dc95f9b117d901e22c55a @Override public float getBukkitYaw() { diff --git a/src/main/java/net/minecraft/world/item/ItemStack.java b/src/main/java/net/minecraft/world/item/ItemStack.java -index 3e00b12d166c5a6a82e61574bfefd5973ffcbb59..142d762750b9745428ae27802f7a428147a74771 100644 +index 0495b02157a40742ebf6817841010aacea179806..b0e45e28d17780b760a81ccbff61581274e033df 100644 --- a/src/main/java/net/minecraft/world/item/ItemStack.java +++ b/src/main/java/net/minecraft/world/item/ItemStack.java -@@ -953,6 +953,25 @@ public final class ItemStack implements DataComponentHolder { +@@ -1025,6 +1025,25 @@ public final class ItemStack implements DataComponentHolder { } } @@ -5077,7 +5681,7 @@ index 3e00b12d166c5a6a82e61574bfefd5973ffcbb59..142d762750b9745428ae27802f7a4281 public void applyComponents(DataComponentPatch changes) { this.components.applyPatch(changes); this.getItem().verifyComponentsAfterLoad(this); -@@ -1219,6 +1238,7 @@ public final class ItemStack implements DataComponentHolder { +@@ -1300,6 +1319,7 @@ public final class ItemStack implements DataComponentHolder { // CraftBukkit start @Deprecated public void setItem(Item item) { @@ -5086,10 +5690,10 @@ index 3e00b12d166c5a6a82e61574bfefd5973ffcbb59..142d762750b9745428ae27802f7a4281 } // CraftBukkit end diff --git a/src/main/java/net/minecraft/world/level/BlockGetter.java b/src/main/java/net/minecraft/world/level/BlockGetter.java -index 1c71d2c1b16bdba1e14a8230787e4cb4ad530163..d6d8bbc98fc71997cb52521d59ebb59d727d3c22 100644 +index 1a69fa49d43c904d0f762159129b8ed1eea36a43..6850ac324ee4d202f112dbd057ea1bde9de17ea9 100644 --- a/src/main/java/net/minecraft/world/level/BlockGetter.java +++ b/src/main/java/net/minecraft/world/level/BlockGetter.java -@@ -9,6 +9,7 @@ import javax.annotation.Nullable; +@@ -12,6 +12,7 @@ import javax.annotation.Nullable; import net.minecraft.core.BlockPos; import net.minecraft.core.Direction; import net.minecraft.util.Mth; @@ -5097,7 +5701,7 @@ index 1c71d2c1b16bdba1e14a8230787e4cb4ad530163..d6d8bbc98fc71997cb52521d59ebb59d import net.minecraft.world.level.block.entity.BlockEntity; import net.minecraft.world.level.block.entity.BlockEntityType; import net.minecraft.world.level.block.state.BlockState; -@@ -30,6 +31,15 @@ public interface BlockGetter extends LevelHeightAccessor { +@@ -35,6 +36,15 @@ public interface BlockGetter extends LevelHeightAccessor { } BlockState getBlockState(BlockPos pos); @@ -5114,10 +5718,10 @@ index 1c71d2c1b16bdba1e14a8230787e4cb4ad530163..d6d8bbc98fc71997cb52521d59ebb59d FluidState getFluidState(BlockPos pos); diff --git a/src/main/java/net/minecraft/world/level/ChunkPos.java b/src/main/java/net/minecraft/world/level/ChunkPos.java -index 171c9c4ab2d1a7988935e09b49286f30e36741e2..fa58eeec2b652f0fa251eedf11cfabde5fd3198b 100644 +index a59d3f56859ba7c07dcb45b1e199e3a7132dcff7..0639e4565c3324d757dec1226adb4e99d841f2c0 100644 --- a/src/main/java/net/minecraft/world/level/ChunkPos.java +++ b/src/main/java/net/minecraft/world/level/ChunkPos.java -@@ -20,6 +20,7 @@ public class ChunkPos { +@@ -46,6 +46,7 @@ public class ChunkPos { public static final int REGION_MAX_INDEX = 31; public final int x; public final int z; @@ -5125,7 +5729,7 @@ index 171c9c4ab2d1a7988935e09b49286f30e36741e2..fa58eeec2b652f0fa251eedf11cfabde private static final int HASH_A = 1664525; private static final int HASH_C = 1013904223; private static final int HASH_Z_XOR = -559038737; -@@ -27,16 +28,19 @@ public class ChunkPos { +@@ -53,16 +54,19 @@ public class ChunkPos { public ChunkPos(int x, int z) { this.x = x; this.z = z; @@ -5145,7 +5749,7 @@ index 171c9c4ab2d1a7988935e09b49286f30e36741e2..fa58eeec2b652f0fa251eedf11cfabde } public static ChunkPos minFromRegion(int x, int z) { -@@ -48,7 +52,7 @@ public class ChunkPos { +@@ -74,7 +78,7 @@ public class ChunkPos { } public long toLong() { @@ -5155,7 +5759,7 @@ index 171c9c4ab2d1a7988935e09b49286f30e36741e2..fa58eeec2b652f0fa251eedf11cfabde public static long asLong(int chunkX, int chunkZ) { diff --git a/src/main/java/net/minecraft/world/level/EmptyBlockGetter.java b/src/main/java/net/minecraft/world/level/EmptyBlockGetter.java -index 3c707d6674b2594b09503b959a31c1f4ad3981e6..db61b6b0158a9bcc0e1d735e34fe3671f8c89e21 100644 +index 8741264b0a9529b40c41a4b9bf25b50d27c830bc..87af0b3cfd3a3170955894028fd667f108ea8121 100644 --- a/src/main/java/net/minecraft/world/level/EmptyBlockGetter.java +++ b/src/main/java/net/minecraft/world/level/EmptyBlockGetter.java @@ -17,6 +17,18 @@ public enum EmptyBlockGetter implements BlockGetter { @@ -5178,10 +5782,10 @@ index 3c707d6674b2594b09503b959a31c1f4ad3981e6..db61b6b0158a9bcc0e1d735e34fe3671 public BlockState getBlockState(BlockPos pos) { return Blocks.AIR.defaultBlockState(); diff --git a/src/main/java/net/minecraft/world/level/Level.java b/src/main/java/net/minecraft/world/level/Level.java -index c061813d275fbc48d7629cc59d90dbb4c347516c..2bc1d0d3ea8a6e3327e9c11bd1f0666d210e9bbe 100644 +index c8e49c1904c80c4ede40ca5c26efad9b12e247d1..3fb17bbcecf6dc4af3b231835adff25f86e1379f 100644 --- a/src/main/java/net/minecraft/world/level/Level.java +++ b/src/main/java/net/minecraft/world/level/Level.java -@@ -95,6 +95,7 @@ import org.bukkit.craftbukkit.CraftServer; +@@ -97,6 +97,7 @@ import org.bukkit.craftbukkit.CraftServer; import org.bukkit.craftbukkit.CraftWorld; import org.bukkit.craftbukkit.SpigotTimings; // Spigot import org.bukkit.craftbukkit.block.CapturedBlockState; @@ -5259,7 +5863,7 @@ index c061813d275fbc48d7629cc59d90dbb4c347516c..2bc1d0d3ea8a6e3327e9c11bd1f0666d ChunkAccess ichunkaccess = this.getChunkSource().getChunk(chunkX, chunkZ, leastStatus, create); if (ichunkaccess == null && create) { -@@ -551,7 +593,7 @@ public abstract class Level implements LevelAccessor, AutoCloseable { +@@ -553,7 +595,7 @@ public abstract class Level implements LevelAccessor, AutoCloseable { if (this.isOutsideBuildHeight(pos)) { return Blocks.VOID_AIR.defaultBlockState(); } else { @@ -5269,7 +5873,7 @@ index c061813d275fbc48d7629cc59d90dbb4c347516c..2bc1d0d3ea8a6e3327e9c11bd1f0666d return chunk.getBlockState(pos); } diff --git a/src/main/java/net/minecraft/world/level/LevelReader.java b/src/main/java/net/minecraft/world/level/LevelReader.java -index 749e4ea1be56b393877b5fdd72dc3669dbf5a3dd..a0ae26d6197e1069ca09982b4f8b706c55ae8491 100644 +index 35b338fed1df844846ecc876cbbab19e0efbfcb0..5eb8982678110fabb82a93c5ec67c666b7fde017 100644 --- a/src/main/java/net/minecraft/world/level/LevelReader.java +++ b/src/main/java/net/minecraft/world/level/LevelReader.java @@ -26,6 +26,9 @@ public interface LevelReader extends BlockAndTintGetter, CollisionGetter, Signal @@ -5283,18 +5887,18 @@ index 749e4ea1be56b393877b5fdd72dc3669dbf5a3dd..a0ae26d6197e1069ca09982b4f8b706c boolean hasChunk(int chunkX, int chunkZ); diff --git a/src/main/java/net/minecraft/world/level/PathNavigationRegion.java b/src/main/java/net/minecraft/world/level/PathNavigationRegion.java -index 497792978bdf0e6a53d772304770e8df3e7416ea..c5454b92ca2565461c799d7340160f9fb72c1b0f 100644 +index b682c7c51204f727e21bfc20c691837deb7137ba..5375ea8dee8f74c843964824ad1a374e15711b60 100644 --- a/src/main/java/net/minecraft/world/level/PathNavigationRegion.java +++ b/src/main/java/net/minecraft/world/level/PathNavigationRegion.java -@@ -9,6 +9,7 @@ import net.minecraft.core.Holder; +@@ -8,6 +8,7 @@ import net.minecraft.core.BlockPos; + import net.minecraft.core.Holder; import net.minecraft.core.SectionPos; import net.minecraft.core.registries.Registries; - import net.minecraft.util.profiling.ProfilerFiller; +import net.minecraft.server.level.ServerLevel; import net.minecraft.world.entity.Entity; import net.minecraft.world.level.biome.Biome; import net.minecraft.world.level.biome.Biomes; -@@ -67,7 +68,7 @@ public class PathNavigationRegion implements BlockGetter, CollisionGetter { +@@ -66,7 +67,7 @@ public class PathNavigationRegion implements CollisionGetter { private ChunkAccess getChunk(int chunkX, int chunkZ) { int i = chunkX - this.centerX; int j = chunkZ - this.centerZ; @@ -5303,7 +5907,7 @@ index 497792978bdf0e6a53d772304770e8df3e7416ea..c5454b92ca2565461c799d7340160f9f ChunkAccess chunkAccess = this.chunks[i][j]; return (ChunkAccess)(chunkAccess != null ? chunkAccess : new EmptyLevelChunk(this.level, new ChunkPos(chunkX, chunkZ), this.plains.get())); } else { -@@ -75,6 +76,30 @@ public class PathNavigationRegion implements BlockGetter, CollisionGetter { +@@ -74,6 +75,30 @@ public class PathNavigationRegion implements CollisionGetter { } } @@ -5335,10 +5939,10 @@ index 497792978bdf0e6a53d772304770e8df3e7416ea..c5454b92ca2565461c799d7340160f9f public WorldBorder getWorldBorder() { return this.level.getWorldBorder(); diff --git a/src/main/java/net/minecraft/world/level/block/state/BlockBehaviour.java b/src/main/java/net/minecraft/world/level/block/state/BlockBehaviour.java -index e0594a1c381487b43bfc55212044e1b3122cee66..59fcaca90b67c03e1a6799e58061dbae3b1f1ceb 100644 +index 107d387f0f81e1a1400cefbec4758b743a64ca3b..0bae4e8d1e9fcc4608b3ef1c981c65f3b03de22b 100644 --- a/src/main/java/net/minecraft/world/level/block/state/BlockBehaviour.java +++ b/src/main/java/net/minecraft/world/level/block/state/BlockBehaviour.java -@@ -841,12 +841,14 @@ public abstract class BlockBehaviour implements FeatureElement { +@@ -883,12 +883,14 @@ public abstract class BlockBehaviour implements FeatureElement { } } @@ -5352,9 +5956,9 @@ index e0594a1c381487b43bfc55212044e1b3122cee66..59fcaca90b67c03e1a6799e58061dbae + this.shapeExceedsCube = this.cache == null || this.cache.largeCollisionShape; // Paper - moved from actual method to here this.legacySolid = this.calculateSolid(); - } -@@ -893,8 +895,8 @@ public abstract class BlockBehaviour implements FeatureElement { - return this.getBlock().getOcclusionShape(this.asState(), world, pos); + this.occlusionShape = this.canOcclude ? ((Block) this.owner).getOcclusionShape(this.asState()) : Shapes.empty(); +@@ -955,8 +957,8 @@ public abstract class BlockBehaviour implements FeatureElement { + return this.occlusionShape; } - public boolean hasLargeCollisionShape() { @@ -5365,19 +5969,19 @@ index e0594a1c381487b43bfc55212044e1b3122cee66..59fcaca90b67c03e1a6799e58061dbae public boolean useShapeForLightOcclusion() { diff --git a/src/main/java/net/minecraft/world/level/chunk/ChunkAccess.java b/src/main/java/net/minecraft/world/level/chunk/ChunkAccess.java -index 35c4bf87870c0dfa1f648547115238dacbb87426..db4d95ce98eb1490d5306d1f74b282d27264871a 100644 +index 15e3b8c8a850039d0adda3c345bd5b9288d06299..37795b9e264c571efe9c718fa9996197dca4ed54 100644 --- a/src/main/java/net/minecraft/world/level/chunk/ChunkAccess.java +++ b/src/main/java/net/minecraft/world/level/chunk/ChunkAccess.java -@@ -65,7 +65,7 @@ public abstract class ChunkAccess implements BlockGetter, BiomeManager.NoiseBiom +@@ -65,7 +65,7 @@ public abstract class ChunkAccess implements BiomeManager.NoiseBiomeSource, Ligh protected final ShortList[] postProcessing; - protected volatile boolean unsaved; + private volatile boolean unsaved; private volatile boolean isLightCorrect; - protected final ChunkPos chunkPos; + protected final ChunkPos chunkPos; public final long coordinateKey; public final int locX; public final int locZ; // Paper - cache coordinate key private long inhabitedTime; /** @deprecated */ @Nullable -@@ -91,7 +91,8 @@ public abstract class ChunkAccess implements BlockGetter, BiomeManager.NoiseBiom +@@ -91,7 +91,8 @@ public abstract class ChunkAccess implements BiomeManager.NoiseBiomeSource, Ligh // CraftBukkit end public ChunkAccess(ChunkPos pos, UpgradeData upgradeData, LevelHeightAccessor heightLimitView, Registry biomeRegistry, long inhabitedTime, @Nullable LevelChunkSection[] sectionArray, @Nullable BlendingData blendingData) { @@ -5405,10 +6009,10 @@ index a52077f0d93c94b0ea644bc14b9b28e84fd1b154..dcc0acd259920463a4464213b9a5e793 @Nullable @Override diff --git a/src/main/java/net/minecraft/world/level/chunk/LevelChunk.java b/src/main/java/net/minecraft/world/level/chunk/LevelChunk.java -index 5b73fcfe278f57de249f3a96da58dc08eda7aff6..25380a44e5cc94f3924cfee6a03c3091fea04ae2 100644 +index 7252096639078cd0b16997354530bf181a61e999..d76591694c3b167b8b8f17b61a373a43140a8b68 100644 --- a/src/main/java/net/minecraft/world/level/chunk/LevelChunk.java +++ b/src/main/java/net/minecraft/world/level/chunk/LevelChunk.java -@@ -116,6 +116,10 @@ public class LevelChunk extends ChunkAccess { +@@ -120,6 +120,10 @@ public class LevelChunk extends ChunkAccess { public boolean needsDecoration; // CraftBukkit end @@ -5419,7 +6023,7 @@ index 5b73fcfe278f57de249f3a96da58dc08eda7aff6..25380a44e5cc94f3924cfee6a03c3091 public LevelChunk(ServerLevel world, ProtoChunk protoChunk, @Nullable LevelChunk.PostLoadProcessor entityLoader) { this(world, protoChunk.getPos(), protoChunk.getUpgradeData(), protoChunk.unpackBlockTicks(), protoChunk.unpackFluidTicks(), protoChunk.getInhabitedTime(), protoChunk.getSections(), entityLoader, protoChunk.getBlendingData()); Iterator iterator = protoChunk.getBlockEntities().values().iterator(); -@@ -181,8 +185,25 @@ public class LevelChunk extends ChunkAccess { +@@ -204,8 +208,25 @@ public class LevelChunk extends ChunkAccess { } } @@ -5445,7 +6049,7 @@ index 5b73fcfe278f57de249f3a96da58dc08eda7aff6..25380a44e5cc94f3924cfee6a03c3091 int i = pos.getX(); int j = pos.getY(); int k = pos.getZ(); -@@ -224,6 +245,18 @@ public class LevelChunk extends ChunkAccess { +@@ -247,6 +268,18 @@ public class LevelChunk extends ChunkAccess { } } @@ -5464,7 +6068,7 @@ index 5b73fcfe278f57de249f3a96da58dc08eda7aff6..25380a44e5cc94f3924cfee6a03c3091 @Override public FluidState getFluidState(BlockPos pos) { return this.getFluidState(pos.getX(), pos.getY(), pos.getZ()); -@@ -554,7 +587,11 @@ public class LevelChunk extends ChunkAccess { +@@ -579,7 +612,11 @@ public class LevelChunk extends ChunkAccess { // CraftBukkit start public void loadCallback() { @@ -5476,7 +6080,7 @@ index 5b73fcfe278f57de249f3a96da58dc08eda7aff6..25380a44e5cc94f3924cfee6a03c3091 if (server != null) { /* * If it's a new world, the first few chunks are generated inside -@@ -595,6 +632,10 @@ public class LevelChunk extends ChunkAccess { +@@ -620,6 +657,10 @@ public class LevelChunk extends ChunkAccess { server.getPluginManager().callEvent(unloadEvent); // note: saving can be prevented, but not forced if no saving is actually required this.mustNotSave = !unloadEvent.isSaveChunk(); @@ -5488,7 +6092,7 @@ index 5b73fcfe278f57de249f3a96da58dc08eda7aff6..25380a44e5cc94f3924cfee6a03c3091 @Override diff --git a/src/main/java/net/minecraft/world/level/chunk/LevelChunkSection.java b/src/main/java/net/minecraft/world/level/chunk/LevelChunkSection.java -index ca4c8f74a1ab2a8b36e193a2c40c3bd76565d258..2c153af611399e884752f8256bee4fe32de5c572 100644 +index 0cf916776ce8735dcfa4c765b18e77037501a322..771529ba28a16664ad19ed9c0f4bf95eeb7da76b 100644 --- a/src/main/java/net/minecraft/world/level/chunk/LevelChunkSection.java +++ b/src/main/java/net/minecraft/world/level/chunk/LevelChunkSection.java @@ -19,7 +19,7 @@ public class LevelChunkSection { @@ -5501,11 +6105,11 @@ index ca4c8f74a1ab2a8b36e193a2c40c3bd76565d258..2c153af611399e884752f8256bee4fe3 private short tickingFluidCount; public final PalettedContainer states; diff --git a/src/main/java/net/minecraft/world/level/chunk/ProtoChunk.java b/src/main/java/net/minecraft/world/level/chunk/ProtoChunk.java -index ae16b014abd52ee10d523fb003cce166b846b222..7f302405a88766c2112539d24d3dd2e513f94985 100644 +index 8bd5fb4971be46a51534c202e10a362723ad8664..5321109ca638036572df9a7e17eafcef2b4f5112 100644 --- a/src/main/java/net/minecraft/world/level/chunk/ProtoChunk.java +++ b/src/main/java/net/minecraft/world/level/chunk/ProtoChunk.java @@ -83,6 +83,18 @@ public class ProtoChunk extends ChunkAccess { - return new ChunkAccess.TicksToSave(this.blockTicks, this.fluidTicks); + return new ChunkAccess.PackedTicks(this.blockTicks.pack(time), this.fluidTicks.pack(time)); } + // Paper start - If loaded util @@ -5523,34 +6127,45 @@ index ae16b014abd52ee10d523fb003cce166b846b222..7f302405a88766c2112539d24d3dd2e5 @Override public BlockState getBlockState(BlockPos pos) { int i = pos.getY(); +diff --git a/src/main/java/net/minecraft/world/level/chunk/status/ChunkStatusTasks.java b/src/main/java/net/minecraft/world/level/chunk/status/ChunkStatusTasks.java +index 3f552ee8f90566edddb5943311a14309e4bebb61..412caefe776df6c8e931f6a1a48ea2525ec6610e 100644 +--- a/src/main/java/net/minecraft/world/level/chunk/status/ChunkStatusTasks.java ++++ b/src/main/java/net/minecraft/world/level/chunk/status/ChunkStatusTasks.java +@@ -168,7 +168,7 @@ public class ChunkStatusTasks { + }, context.mainThreadExecutor()); + } + +- private static void postLoadProtoChunk(ServerLevel world, List entities) { ++ public static void postLoadProtoChunk(ServerLevel world, List entities) { // Paper - public + if (!entities.isEmpty()) { + // CraftBukkit start - these are spawned serialized (DefinedStructure) and we don't call an add event below at the moment due to ordering complexities + world.addWorldGenChunkEntities(EntityType.loadEntitiesRecursive(entities, world, EntitySpawnReason.LOAD).filter((entity) -> { diff --git a/src/main/java/net/minecraft/world/level/entity/PersistentEntitySectionManager.java b/src/main/java/net/minecraft/world/level/entity/PersistentEntitySectionManager.java -index 34933c5324126f9afdc5cba9dea997ace8f01806..1cfc906317f07a44f06a4adf021c44e34a2f1d07 100644 +index 34933c5324126f9afdc5cba9dea997ace8f01806..4eb0b0969325f39a7ae65492cccd482515a50142 100644 --- a/src/main/java/net/minecraft/world/level/entity/PersistentEntitySectionManager.java +++ b/src/main/java/net/minecraft/world/level/entity/PersistentEntitySectionManager.java -@@ -91,6 +91,18 @@ public class PersistentEntitySectionManager implements A +@@ -91,6 +91,16 @@ public class PersistentEntitySectionManager implements A } private boolean addEntity(T entity, boolean existing) { + // Paper start - chunk system hooks -+ if (existing) { -+ // I don't want to know why this is a generic type. -+ Entity entityCasted = (Entity)entity; -+ boolean wasRemoved = entityCasted.isRemoved(); -+ boolean screened = ca.spottedleaf.moonrise.common.util.ChunkSystem.screenEntity((net.minecraft.server.level.ServerLevel)entityCasted.level(), entityCasted); -+ if ((!wasRemoved && entityCasted.isRemoved()) || !screened) { -+ // removed by callback -+ return false; -+ } ++ // I don't want to know why this is a generic type. ++ Entity entityCasted = (Entity)entity; ++ boolean wasRemoved = entityCasted.isRemoved(); ++ boolean screened = ca.spottedleaf.moonrise.common.util.ChunkSystem.screenEntity((net.minecraft.server.level.ServerLevel)entityCasted.level(), entityCasted, existing, true); ++ if ((!wasRemoved && entityCasted.isRemoved()) || !screened) { ++ // removed by callback ++ return false; + } + // Paper end - chunk system hooks if (!this.addEntityUuid(entity)) { return false; } else { diff --git a/src/main/java/org/bukkit/craftbukkit/CraftServer.java b/src/main/java/org/bukkit/craftbukkit/CraftServer.java -index d0c41302478bd9b0ec65020e7ef520bd3ff1f347..99178e22846c41f1d800b69d4fed064088bbe92c 100644 +index 3882ae04173cd125fe490692a6bc2b4d8b20ff7b..eb61712ea067b277e7f32f887e3528faca275450 100644 --- a/src/main/java/org/bukkit/craftbukkit/CraftServer.java +++ b/src/main/java/org/bukkit/craftbukkit/CraftServer.java -@@ -2600,4 +2600,9 @@ public final class CraftServer implements Server { +@@ -2617,4 +2617,9 @@ public final class CraftServer implements Server { return this.spigot; } // Spigot end @@ -5561,10 +6176,10 @@ index d0c41302478bd9b0ec65020e7ef520bd3ff1f347..99178e22846c41f1d800b69d4fed0640 + } } diff --git a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java -index 07012c01e1d8eacf37b8a86a5e4e56fb0d81c989..ac6d4c8ac26696a0c6dfa471eee764b871dd2029 100644 +index 799444e4101283c972a160742a9e2548e604173f..8b58884d6cb1088a2fffb36a99bfe4dc568326d1 100644 --- a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java +++ b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java -@@ -252,8 +252,8 @@ public class CraftWorld extends CraftRegionAccessor implements World { +@@ -256,8 +256,8 @@ public class CraftWorld extends CraftRegionAccessor implements World { @Override public Chunk[] getLoadedChunks() { @@ -5575,7 +6190,7 @@ index 07012c01e1d8eacf37b8a86a5e4e56fb0d81c989..ac6d4c8ac26696a0c6dfa471eee764b8 } @Override -@@ -328,7 +328,7 @@ public class CraftWorld extends CraftRegionAccessor implements World { +@@ -334,7 +334,7 @@ public class CraftWorld extends CraftRegionAccessor implements World { @Override public boolean refreshChunk(int x, int z) { @@ -5584,7 +6199,7 @@ index 07012c01e1d8eacf37b8a86a5e4e56fb0d81c989..ac6d4c8ac26696a0c6dfa471eee764b8 if (playerChunk == null) return false; playerChunk.getTickingChunkFuture().thenAccept(either -> { -@@ -2100,4 +2100,55 @@ public class CraftWorld extends CraftRegionAccessor implements World { +@@ -2110,4 +2110,55 @@ public class CraftWorld extends CraftRegionAccessor implements World { return this.spigot; } // Spigot end @@ -5597,11 +6212,11 @@ index 07012c01e1d8eacf37b8a86a5e4e56fb0d81c989..ac6d4c8ac26696a0c6dfa471eee764b8 + } + } + -+ ca.spottedleaf.concurrentutil.executor.standard.PrioritisedExecutor.Priority priority; ++ ca.spottedleaf.concurrentutil.util.Priority priority; + if (urgent) { -+ priority = ca.spottedleaf.concurrentutil.executor.standard.PrioritisedExecutor.Priority.HIGHER; ++ priority = ca.spottedleaf.concurrentutil.util.Priority.HIGHER; + } else { -+ priority = ca.spottedleaf.concurrentutil.executor.standard.PrioritisedExecutor.Priority.NORMAL; ++ priority = ca.spottedleaf.concurrentutil.util.Priority.NORMAL; + } + + java.util.concurrent.CompletableFuture ret = new java.util.concurrent.CompletableFuture<>(); @@ -5641,10 +6256,10 @@ index 07012c01e1d8eacf37b8a86a5e4e56fb0d81c989..ac6d4c8ac26696a0c6dfa471eee764b8 + // Paper end } diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java -index 4cbf0d797fc2d57c822877c111698b2077399d32..9cd22b09b295ecd95d17d1dd5edb96f916eb6c09 100644 +index 031eed24638659f2633bef0ad2e178832ca058e9..a3550d84c273c9720491484382a4bc50dc3246a6 100644 --- a/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java +++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java -@@ -2420,4 +2420,34 @@ public class CraftPlayer extends CraftHumanEntity implements Player { +@@ -2447,4 +2447,34 @@ public class CraftPlayer extends CraftHumanEntity implements Player { return this.spigot; } // Spigot end @@ -5680,10 +6295,10 @@ index 4cbf0d797fc2d57c822877c111698b2077399d32..9cd22b09b295ecd95d17d1dd5edb96f9 + } } diff --git a/src/main/java/org/bukkit/craftbukkit/inventory/CraftItemStack.java b/src/main/java/org/bukkit/craftbukkit/inventory/CraftItemStack.java -index 952c6ebde7031dc060efe98992f82c02bf3534ea..17fa2d3db112762bcb8b941b69b8ddcc53f47224 100644 +index f6e6f0ddef6693c58f28b89cf3df005a8d47e08d..101eea3452c9e387e770b716543c3a4f17b9a737 100644 --- a/src/main/java/org/bukkit/craftbukkit/inventory/CraftItemStack.java +++ b/src/main/java/org/bukkit/craftbukkit/inventory/CraftItemStack.java -@@ -23,6 +23,20 @@ import org.jetbrains.annotations.ApiStatus; +@@ -31,6 +31,20 @@ import org.jetbrains.annotations.ApiStatus; @DelegateDeserialization(ItemStack.class) public final class CraftItemStack extends ItemStack { @@ -5705,10 +6320,10 @@ index 952c6ebde7031dc060efe98992f82c02bf3534ea..17fa2d3db112762bcb8b941b69b8ddcc if (original instanceof CraftItemStack) { CraftItemStack stack = (CraftItemStack) original; diff --git a/src/main/java/org/bukkit/craftbukkit/util/DelegatedGeneratorAccess.java b/src/main/java/org/bukkit/craftbukkit/util/DelegatedGeneratorAccess.java -index 5fd6eb754c4edebed6798c65b06507a4e89ca48f..524b51a0ab808a0629c871ad813115abd4b49dbd 100644 +index b8a865305cc61954aeebff4a7cd1d1973c5f46ab..e444662ee4d9405eeea7caa41b9cd6b36586d840 100644 --- a/src/main/java/org/bukkit/craftbukkit/util/DelegatedGeneratorAccess.java +++ b/src/main/java/org/bukkit/craftbukkit/util/DelegatedGeneratorAccess.java -@@ -58,6 +58,7 @@ import net.minecraft.world.phys.shapes.VoxelShape; +@@ -56,6 +56,7 @@ import net.minecraft.world.phys.shapes.VoxelShape; import net.minecraft.world.ticks.LevelTickAccess; import net.minecraft.world.ticks.TickPriority; import org.bukkit.event.entity.CreatureSpawnEvent; @@ -5716,9 +6331,9 @@ index 5fd6eb754c4edebed6798c65b06507a4e89ca48f..524b51a0ab808a0629c871ad813115ab public abstract class DelegatedGeneratorAccess implements WorldGenLevel { -@@ -807,4 +808,24 @@ public abstract class DelegatedGeneratorAccess implements WorldGenLevel { - public int getMoonPhase() { - return this.handle.getMoonPhase(); +@@ -788,4 +789,25 @@ public abstract class DelegatedGeneratorAccess implements WorldGenLevel { + public boolean isFluidAtPosition(BlockPos pos, Predicate state) { + return this.handle.isFluidAtPosition(pos, state); } + + // Paper start @@ -5741,6 +6356,7 @@ index 5fd6eb754c4edebed6798c65b06507a4e89ca48f..524b51a0ab808a0629c871ad813115ab + } + // Paper end } ++ diff --git a/src/main/java/org/bukkit/craftbukkit/util/DummyGeneratorAccess.java b/src/main/java/org/bukkit/craftbukkit/util/DummyGeneratorAccess.java index e837d76e833d73d888bc1dad3515c2b82bc0e437..4705aed1dd98378c146bf9e346df1a17f719ad36 100644 --- a/src/main/java/org/bukkit/craftbukkit/util/DummyGeneratorAccess.java @@ -5770,7 +6386,7 @@ index e837d76e833d73d888bc1dad3515c2b82bc0e437..4705aed1dd98378c146bf9e346df1a17 public WorldBorder getWorldBorder() { throw new UnsupportedOperationException("Not supported yet."); diff --git a/src/main/java/org/spigotmc/ActivationRange.java b/src/main/java/org/spigotmc/ActivationRange.java -index 0c7c97f27853843ec714e47f5b570f9d09bbba14..ff422d4d4f2b764370f0ee2af13034853c1d3fe1 100644 +index 1bf87b4915edf341ad55f8274cef324e0bc28547..3591b79481ac17bd02e59ac3c623d1c6991abd84 100644 --- a/src/main/java/org/spigotmc/ActivationRange.java +++ b/src/main/java/org/spigotmc/ActivationRange.java @@ -34,6 +34,9 @@ public class ActivationRange @@ -5783,3 +6399,10 @@ index 0c7c97f27853843ec714e47f5b570f9d09bbba14..ff422d4d4f2b764370f0ee2af1303485 MONSTER, ANIMAL, RAIDER, +diff --git a/src/main/resources/META-INF/services/ca.spottedleaf.moonrise.common.PlatformHooks b/src/main/resources/META-INF/services/ca.spottedleaf.moonrise.common.PlatformHooks +new file mode 100644 +index 0000000000000000000000000000000000000000..e57c3ca79677b1dfe7cf3db36f0406de7ea5bd0a +--- /dev/null ++++ b/src/main/resources/META-INF/services/ca.spottedleaf.moonrise.common.PlatformHooks +@@ -0,0 +1 @@ ++ca.spottedleaf.moonrise.paper.PaperHooks diff --git a/patches/server/0010-Adventure.patch b/patches/server/0010-Adventure.patch index d9da34e2e729..7375271d2050 100644 --- a/patches/server/0010-Adventure.patch +++ b/patches/server/0010-Adventure.patch @@ -14,10 +14,10 @@ Co-authored-by: Jake Potrebic diff --git a/src/main/java/io/papermc/paper/adventure/AdventureCodecs.java b/src/main/java/io/papermc/paper/adventure/AdventureCodecs.java new file mode 100644 -index 0000000000000000000000000000000000000000..8dcedc5f4d4453fd942787dbcb9c757274ec7715 +index 0000000000000000000000000000000000000000..2c5702a42c4a3d8b37deeb26e1bd7fbdcca3554e --- /dev/null +++ b/src/main/java/io/papermc/paper/adventure/AdventureCodecs.java -@@ -0,0 +1,446 @@ +@@ -0,0 +1,450 @@ +package io.papermc.paper.adventure; + +import com.google.gson.JsonElement; @@ -61,6 +61,7 @@ index 0000000000000000000000000000000000000000..8dcedc5f4d4453fd942787dbcb9c7572 +import net.kyori.adventure.text.format.TextColor; +import net.kyori.adventure.text.format.TextDecoration; +import net.kyori.adventure.text.serializer.plain.PlainTextComponentSerializer; ++import net.minecraft.commands.arguments.selector.SelectorPattern; +import net.minecraft.core.UUIDUtil; +import net.minecraft.core.registries.BuiltInRegistries; +import net.minecraft.nbt.CompoundTag; @@ -146,7 +147,7 @@ index 0000000000000000000000000000000000000000..8dcedc5f4d4453fd942787dbcb9c7572 + @Subst("key") final String typeKey = isi.item.unwrapKey().orElseThrow().location().toString(); + return HoverEvent.ShowItem.showItem(Key.key(typeKey), isi.count, PaperAdventure.asAdventure(isi.getItemStack().getComponentsPatch())); + }, si -> { -+ final Item itemType = BuiltInRegistries.ITEM.get(PaperAdventure.asVanilla(si.item())); ++ final Item itemType = BuiltInRegistries.ITEM.getValue(PaperAdventure.asVanilla(si.item())); + final Map dataComponentsMap = si.dataComponents(); + final ItemStack stack = new ItemStack(BuiltInRegistries.ITEM.wrapAsHolder(itemType), si.count(), PaperAdventure.asVanilla(dataComponentsMap)); + return new net.minecraft.network.chat.HoverEvent.ItemStackInfo(stack); @@ -315,7 +316,10 @@ index 0000000000000000000000000000000000000000..8dcedc5f4d4453fd942787dbcb9c7572 + }); + + static final MapCodec KEYBIND_COMPONENT_MAP_CODEC = KeybindContents.CODEC.xmap(k -> Component.keybind(k.getName()), k -> new KeybindContents(k.keybind())); -+ static final MapCodec SCORE_COMPONENT_INNER_MAP_CODEC = ScoreContents.INNER_CODEC.xmap(s -> Component.score(s.getName(), s.getObjective()), s -> new ScoreContents(s.name(), s.objective())); ++ static final MapCodec SCORE_COMPONENT_INNER_MAP_CODEC = ScoreContents.INNER_CODEC.xmap( ++ s -> Component.score(s.name().map(SelectorPattern::pattern, Function.identity()), s.objective()), ++ s -> new ScoreContents(SelectorPattern.parse(s.name()).>map(Either::left).result().orElse(Either.right(s.name())), s.objective()) ++ ); // TODO we might want to ask adventure for a nice way we can avoid parsing and flattening the SelectorPattern on every conversion. + static final MapCodec SCORE_COMPONENT_MAP_CODEC = SCORE_COMPONENT_INNER_MAP_CODEC.fieldOf("score"); + static final MapCodec SELECTOR_COMPONENT_MAP_CODEC = mapCodec((instance) -> { + return instance.group( @@ -1157,7 +1161,7 @@ index 0000000000000000000000000000000000000000..2fd6c3e65354071af71c7d8ebb97b559 +} diff --git a/src/main/java/io/papermc/paper/adventure/PaperAdventure.java b/src/main/java/io/papermc/paper/adventure/PaperAdventure.java new file mode 100644 -index 0000000000000000000000000000000000000000..dc4837c577676115f0653acc35f55962a432e425 +index 0000000000000000000000000000000000000000..161bc8c577643094d824ea96fb6974c76e5e77f0 --- /dev/null +++ b/src/main/java/io/papermc/paper/adventure/PaperAdventure.java @@ -0,0 +1,479 @@ @@ -1580,7 +1584,7 @@ index 0000000000000000000000000000000000000000..dc4837c577676115f0653acc35f55962 + } + final DataComponentPatch.Builder builder = DataComponentPatch.builder(); + map.forEach((key, dataComponentValue) -> { -+ final DataComponentType type = requireNonNull(BuiltInRegistries.DATA_COMPONENT_TYPE.get(asVanilla(key))); ++ final DataComponentType type = requireNonNull(BuiltInRegistries.DATA_COMPONENT_TYPE.getValue(asVanilla(key))); + if (dataComponentValue instanceof DataComponentValue.Removed) { + builder.remove(type); + return; @@ -1839,7 +1843,7 @@ index 0000000000000000000000000000000000000000..8323f135d6bf2e1f12525e05094ffa3f +} diff --git a/src/main/java/io/papermc/paper/adventure/providers/DataComponentValueConverterProviderImpl.java b/src/main/java/io/papermc/paper/adventure/providers/DataComponentValueConverterProviderImpl.java new file mode 100644 -index 0000000000000000000000000000000000000000..6b37c0ebda4a0cfcf9c3b2c3483ffababe622555 +index 0000000000000000000000000000000000000000..ee2076fd098ae2164596f39b88f56b3700ed3687 --- /dev/null +++ b/src/main/java/io/papermc/paper/adventure/providers/DataComponentValueConverterProviderImpl.java @@ -0,0 +1,82 @@ @@ -1895,7 +1899,7 @@ index 0000000000000000000000000000000000000000..6b37c0ebda4a0cfcf9c3b2c3483ffaba + GsonDataComponentValue.class, + PaperAdventure.DataComponentValueImpl.class, + (key, dataComponentValue) -> { -+ final @Nullable DataComponentType type = BuiltInRegistries.DATA_COMPONENT_TYPE.get(PaperAdventure.asVanilla(key)); ++ final @Nullable DataComponentType type = BuiltInRegistries.DATA_COMPONENT_TYPE.getValue(PaperAdventure.asVanilla(key)); + if (type == null) { + throw new IllegalArgumentException("Unknown data component type: " + key); + } @@ -1911,7 +1915,7 @@ index 0000000000000000000000000000000000000000..6b37c0ebda4a0cfcf9c3b2c3483ffaba + DataComponentValue.TagSerializable.class, + PaperAdventure.DataComponentValueImpl.class, + (key, tagSerializable) -> { -+ final @Nullable DataComponentType type = BuiltInRegistries.DATA_COMPONENT_TYPE.get(PaperAdventure.asVanilla(key)); ++ final @Nullable DataComponentType type = BuiltInRegistries.DATA_COMPONENT_TYPE.getValue(PaperAdventure.asVanilla(key)); + if (type == null) { + throw new IllegalArgumentException("Unknown data component type: " + key); + } @@ -2179,10 +2183,10 @@ index 08dcd817bfe1ba0121d4ce701825e4aee384db85..d5f63d06d921d731b4e64b3822837771 public static ChatFormatting getById(int colorIndex) { if (colorIndex < 0) { diff --git a/src/main/java/net/minecraft/commands/CommandSourceStack.java b/src/main/java/net/minecraft/commands/CommandSourceStack.java -index f30cdfa5fd294479e35680b2f758b3295f659b74..ec34e402104d7a696ea95e0b11ee70189b678ab9 100644 +index d6ea21d6d33b701f249a8acd3e7304eb2c02e1ca..2fbd7f7c976fb55b7238f1e512afad79e52a5b2c 100644 --- a/src/main/java/net/minecraft/commands/CommandSourceStack.java +++ b/src/main/java/net/minecraft/commands/CommandSourceStack.java -@@ -65,6 +65,7 @@ public class CommandSourceStack implements ExecutionCommandSource implements ServerInfo, ChunkIOErrorReporter, CommandSource, AutoCloseable { +@@ -208,6 +208,7 @@ import org.bukkit.craftbukkit.SpigotTimings; // Spigot + public abstract class MinecraftServer extends ReentrantBlockableEventLoop implements ServerInfo, ChunkIOErrorReporter, CommandSource { public static final Logger LOGGER = LogUtils.getLogger(); + public static final net.kyori.adventure.text.logger.slf4j.ComponentLogger COMPONENT_LOGGER = net.kyori.adventure.text.logger.slf4j.ComponentLogger.logger(LOGGER.getName()); // Paper public static final String VANILLA_BRAND = "vanilla"; private static final float AVERAGE_TICK_TIME_SMOOTHING = 0.8F; private static final int TICK_STATS_SPAN = 100; -@@ -251,8 +252,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop { boolean flag1 = true; -@@ -2025,8 +2024,13 @@ public class ServerPlayer extends net.minecraft.world.entity.player.Player { +@@ -2288,8 +2287,13 @@ public class ServerPlayer extends net.minecraft.world.entity.player.Player { } public void sendChatMessage(OutgoingChatMessage message, boolean filterMaskEnabled, ChatType.Bound params) { @@ -2777,7 +2781,7 @@ index cb136a30287a17947ed018cdc48e6f91ed904072..ee5188f3aa2ff71306f5af8046e8ddf9 } } -@@ -2053,6 +2057,7 @@ public class ServerPlayer extends net.minecraft.world.entity.player.Player { +@@ -2316,6 +2320,7 @@ public class ServerPlayer extends net.minecraft.world.entity.player.Player { } // CraftBukkit end this.language = clientOptions.language(); @@ -2786,10 +2790,10 @@ index cb136a30287a17947ed018cdc48e6f91ed904072..ee5188f3aa2ff71306f5af8046e8ddf9 this.chatVisibility = clientOptions.chatVisibility(); this.canChatColor = clientOptions.chatColors(); diff --git a/src/main/java/net/minecraft/server/network/ServerCommonPacketListenerImpl.java b/src/main/java/net/minecraft/server/network/ServerCommonPacketListenerImpl.java -index 16320a5fccc7faf7ceac95d471532104a61d27a5..b2bddc6183204b9f519549073e38741e1a9322c4 100644 +index 64b13cc2f32ab59dd6bea6d33c475228384b7c3e..99f89854e43ed6742dc9ac1624fa7140b4594b3b 100644 --- a/src/main/java/net/minecraft/server/network/ServerCommonPacketListenerImpl.java +++ b/src/main/java/net/minecraft/server/network/ServerCommonPacketListenerImpl.java -@@ -72,7 +72,7 @@ public abstract class ServerCommonPacketListenerImpl implements ServerCommonPack +@@ -73,7 +73,7 @@ public abstract class ServerCommonPacketListenerImpl implements ServerCommonPack private static final Component TIMEOUT_DISCONNECTION_MESSAGE = Component.translatable("disconnect.timeout"); static final Component DISCONNECT_UNEXPECTED_QUERY = Component.translatable("multiplayer.disconnect.unexpected_query_response"); protected final MinecraftServer server; @@ -2798,7 +2802,7 @@ index 16320a5fccc7faf7ceac95d471532104a61d27a5..b2bddc6183204b9f519549073e38741e private final boolean transferred; private long keepAliveTime; private boolean keepAlivePending; -@@ -81,6 +81,7 @@ public abstract class ServerCommonPacketListenerImpl implements ServerCommonPack +@@ -82,6 +82,7 @@ public abstract class ServerCommonPacketListenerImpl implements ServerCommonPack private boolean closed = false; private int latency; private volatile boolean suspendFlushingOnServerThread = false; @@ -2806,7 +2810,7 @@ index 16320a5fccc7faf7ceac95d471532104a61d27a5..b2bddc6183204b9f519549073e38741e public ServerCommonPacketListenerImpl(MinecraftServer minecraftserver, Connection networkmanager, CommonListenerCookie commonlistenercookie, ServerPlayer player) { // CraftBukkit this.server = minecraftserver; -@@ -194,6 +195,18 @@ public abstract class ServerCommonPacketListenerImpl implements ServerCommonPack +@@ -201,6 +202,18 @@ public abstract class ServerCommonPacketListenerImpl implements ServerCommonPack ServerCommonPacketListenerImpl.LOGGER.info("Disconnecting {} due to resource pack {} rejection", this.playerProfile().getName(), packet.id()); this.disconnect((Component) Component.translatable("multiplayer.requiredTexturePrompt.disconnect")); } @@ -2825,7 +2829,7 @@ index 16320a5fccc7faf7ceac95d471532104a61d27a5..b2bddc6183204b9f519549073e38741e this.cserver.getPluginManager().callEvent(new PlayerResourcePackStatusEvent(this.getCraftPlayer(), packet.id(), PlayerResourcePackStatusEvent.Status.values()[packet.action().ordinal()])); // CraftBukkit } -@@ -280,6 +293,12 @@ public abstract class ServerCommonPacketListenerImpl implements ServerCommonPack +@@ -287,6 +300,12 @@ public abstract class ServerCommonPacketListenerImpl implements ServerCommonPack } } @@ -2838,7 +2842,7 @@ index 16320a5fccc7faf7ceac95d471532104a61d27a5..b2bddc6183204b9f519549073e38741e public void disconnect(Component reason) { this.disconnect(new DisconnectionDetails(reason)); } -@@ -310,9 +329,9 @@ public abstract class ServerCommonPacketListenerImpl implements ServerCommonPack +@@ -317,9 +336,9 @@ public abstract class ServerCommonPacketListenerImpl implements ServerCommonPack return; } @@ -2850,7 +2854,7 @@ index 16320a5fccc7faf7ceac95d471532104a61d27a5..b2bddc6183204b9f519549073e38741e if (this.cserver.getServer().isRunning()) { this.cserver.getPluginManager().callEvent(event); -@@ -324,7 +343,7 @@ public abstract class ServerCommonPacketListenerImpl implements ServerCommonPack +@@ -331,7 +350,7 @@ public abstract class ServerCommonPacketListenerImpl implements ServerCommonPack } this.player.kickLeaveMessage = event.getLeaveMessage(); // CraftBukkit - SPIGOT-3034: Forward leave message to PlayerQuitEvent // Send the possibly modified leave message @@ -2872,7 +2876,7 @@ index e7c407039fef88ef01ba9b6be9ae5bcc3edc026f..5457358bc76889153036818fdfd70a04 @Override diff --git a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java -index 2de70e3a3038b2d2bb47b58f0e14d937c35d55ba..4acee8121ff62413dbbf2294d17da3bd2f974d5a 100644 +index 7e276fb310fc9e9217fc12934003611cd616def1..18d56058073b6cc4f9020f0a6137e4ac26eed0b2 100644 --- a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java +++ b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java @@ -45,6 +45,7 @@ import net.minecraft.nbt.CompoundTag; @@ -2883,7 +2887,7 @@ index 2de70e3a3038b2d2bb47b58f0e14d937c35d55ba..4acee8121ff62413dbbf2294d17da3bd import net.minecraft.network.chat.ChatType; import net.minecraft.network.chat.Component; import net.minecraft.network.chat.LastSeenMessages; -@@ -195,6 +196,8 @@ import net.minecraft.world.phys.shapes.VoxelShape; +@@ -201,6 +202,8 @@ import net.minecraft.world.phys.shapes.VoxelShape; import org.slf4j.Logger; // CraftBukkit start @@ -2892,7 +2896,7 @@ index 2de70e3a3038b2d2bb47b58f0e14d937c35d55ba..4acee8121ff62413dbbf2294d17da3bd import com.mojang.datafixers.util.Pair; import java.util.Arrays; import java.util.concurrent.ExecutionException; -@@ -1728,9 +1731,11 @@ public class ServerGamePacketListenerImpl extends ServerCommonPacketListenerImpl +@@ -1747,9 +1750,11 @@ public class ServerGamePacketListenerImpl extends ServerCommonPacketListenerImpl */ this.player.disconnect(); @@ -2907,7 +2911,7 @@ index 2de70e3a3038b2d2bb47b58f0e14d937c35d55ba..4acee8121ff62413dbbf2294d17da3bd } // CraftBukkit end this.player.getTextFilter().leave(); -@@ -1791,10 +1796,10 @@ public class ServerGamePacketListenerImpl extends ServerCommonPacketListenerImpl +@@ -1810,10 +1815,10 @@ public class ServerGamePacketListenerImpl extends ServerCommonPacketListenerImpl } CompletableFuture completablefuture = this.filterTextPacket(playerchatmessage.signedContent()).thenApplyAsync(Function.identity(), this.server.chatExecutor); // CraftBukkit - async chat @@ -2921,7 +2925,7 @@ index 2de70e3a3038b2d2bb47b58f0e14d937c35d55ba..4acee8121ff62413dbbf2294d17da3bd this.broadcastChatMessage(playerchatmessage1); }); -@@ -2014,7 +2019,15 @@ public class ServerGamePacketListenerImpl extends ServerCommonPacketListenerImpl +@@ -2033,7 +2038,15 @@ public class ServerGamePacketListenerImpl extends ServerCommonPacketListenerImpl this.handleCommand(s); } else if (this.player.getChatVisibility() == ChatVisiblity.SYSTEM) { // Do nothing, this is coming from a plugin @@ -2938,7 +2942,7 @@ index 2de70e3a3038b2d2bb47b58f0e14d937c35d55ba..4acee8121ff62413dbbf2294d17da3bd Player player = this.getCraftPlayer(); AsyncPlayerChatEvent event = new AsyncPlayerChatEvent(async, player, s, new LazyPlayerSet(this.server)); String originalFormat = event.getFormat(), originalMessage = event.getMessage(); -@@ -3011,6 +3024,7 @@ public class ServerGamePacketListenerImpl extends ServerCommonPacketListenerImpl +@@ -3066,6 +3079,7 @@ public class ServerGamePacketListenerImpl extends ServerCommonPacketListenerImpl public void handleClientInformation(ServerboundClientInformationPacket packet) { PacketUtils.ensureRunningOnSameThread(packet, this, this.player.serverLevel()); this.player.updateOptions(packet.information()); @@ -2947,7 +2951,7 @@ index 2de70e3a3038b2d2bb47b58f0e14d937c35d55ba..4acee8121ff62413dbbf2294d17da3bd @Override diff --git a/src/main/java/net/minecraft/server/network/ServerLoginPacketListenerImpl.java b/src/main/java/net/minecraft/server/network/ServerLoginPacketListenerImpl.java -index b53e3e5e02ed501d30c3fca4c3130263c32cd2f5..b2f7c76207f2dc0c62f608f21aba4b531f8f523f 100644 +index c209ac2be7fabcd36cfcc0400308e44a26d78263..8cf3b9f1b7eef2d6278830e21ae012852687e02b 100644 --- a/src/main/java/net/minecraft/server/network/ServerLoginPacketListenerImpl.java +++ b/src/main/java/net/minecraft/server/network/ServerLoginPacketListenerImpl.java @@ -338,7 +338,7 @@ public class ServerLoginPacketListenerImpl implements ServerLoginPacketListener, @@ -2988,10 +2992,10 @@ index 3848cc836785829c5aa82bdb5d37d36a97f94a04..f08700abb005f487aca95c0457c09cef @Override diff --git a/src/main/java/net/minecraft/server/players/PlayerList.java b/src/main/java/net/minecraft/server/players/PlayerList.java -index 6abe921099ff00ecfaf0f423ef27d708420f6f48..e9109526880159e2341cc97b53939ba2bcfaeaf9 100644 +index bf660a057d135563b47e7b74d927a82a20b8ef75..a70e0ecedc7b70031334bc7fee981bd790ce26a4 100644 --- a/src/main/java/net/minecraft/server/players/PlayerList.java +++ b/src/main/java/net/minecraft/server/players/PlayerList.java -@@ -271,7 +271,7 @@ public abstract class PlayerList { +@@ -274,7 +274,7 @@ public abstract class PlayerList { } // CraftBukkit start ichatmutablecomponent.withStyle(ChatFormatting.YELLOW); @@ -3000,7 +3004,7 @@ index 6abe921099ff00ecfaf0f423ef27d708420f6f48..e9109526880159e2341cc97b53939ba2 playerconnection.teleport(player.getX(), player.getY(), player.getZ(), player.getYRot(), player.getXRot()); ServerStatus serverping = this.server.getStatus(); -@@ -292,19 +292,18 @@ public abstract class PlayerList { +@@ -295,19 +295,18 @@ public abstract class PlayerList { // Ensure that player inventory is populated with its viewer player.containerMenu.transferTo(player.containerMenu, bukkitPlayer); @@ -3025,7 +3029,7 @@ index 6abe921099ff00ecfaf0f423ef27d708420f6f48..e9109526880159e2341cc97b53939ba2 } // CraftBukkit end -@@ -492,7 +491,7 @@ public abstract class PlayerList { +@@ -451,7 +450,7 @@ public abstract class PlayerList { } @@ -3034,7 +3038,7 @@ index 6abe921099ff00ecfaf0f423ef27d708420f6f48..e9109526880159e2341cc97b53939ba2 ServerLevel worldserver = entityplayer.serverLevel(); entityplayer.awardStat(Stats.LEAVE_GAME); -@@ -503,7 +502,7 @@ public abstract class PlayerList { +@@ -462,7 +461,7 @@ public abstract class PlayerList { entityplayer.closeContainer(); } @@ -3043,7 +3047,7 @@ index 6abe921099ff00ecfaf0f423ef27d708420f6f48..e9109526880159e2341cc97b53939ba2 this.cserver.getPluginManager().callEvent(playerQuitEvent); entityplayer.getBukkitEntity().disconnect(playerQuitEvent.getQuitMessage()); -@@ -556,7 +555,7 @@ public abstract class PlayerList { +@@ -523,7 +522,7 @@ public abstract class PlayerList { this.cserver.getScoreboardManager().removePlayer(entityplayer.getBukkitEntity()); // CraftBukkit end @@ -3052,7 +3056,7 @@ index 6abe921099ff00ecfaf0f423ef27d708420f6f48..e9109526880159e2341cc97b53939ba2 } // CraftBukkit start - Whole method, SocketAddress to LoginListener, added hostname to signature, return EntityPlayer -@@ -603,11 +602,11 @@ public abstract class PlayerList { +@@ -570,11 +569,11 @@ public abstract class PlayerList { } // return chatmessage; @@ -3067,7 +3071,7 @@ index 6abe921099ff00ecfaf0f423ef27d708420f6f48..e9109526880159e2341cc97b53939ba2 IpBanListEntry ipbanentry = this.ipBans.get(socketaddress); ichatmutablecomponent = Component.translatable("multiplayer.disconnect.banned_ip.reason", ipbanentry.getReason()); -@@ -616,17 +615,17 @@ public abstract class PlayerList { +@@ -583,17 +582,17 @@ public abstract class PlayerList { } // return chatmessage; @@ -3088,7 +3092,7 @@ index 6abe921099ff00ecfaf0f423ef27d708420f6f48..e9109526880159e2341cc97b53939ba2 return null; } return entity; -@@ -1122,7 +1121,7 @@ public abstract class PlayerList { +@@ -1091,7 +1090,7 @@ public abstract class PlayerList { public void removeAll() { // CraftBukkit start - disconnect safely for (ServerPlayer player : this.players) { @@ -3097,7 +3101,7 @@ index 6abe921099ff00ecfaf0f423ef27d708420f6f48..e9109526880159e2341cc97b53939ba2 } // CraftBukkit end -@@ -1163,24 +1162,43 @@ public abstract class PlayerList { +@@ -1132,24 +1131,43 @@ public abstract class PlayerList { } public void broadcastChatMessage(PlayerChatMessage message, ServerPlayer sender, ChatType.Bound params) { @@ -3144,7 +3148,7 @@ index 6abe921099ff00ecfaf0f423ef27d708420f6f48..e9109526880159e2341cc97b53939ba2 } if (flag1 && sender != null) { -@@ -1189,7 +1207,7 @@ public abstract class PlayerList { +@@ -1158,7 +1176,7 @@ public abstract class PlayerList { } @@ -3242,7 +3246,7 @@ index ed54c81a3269360acce674aa4e1d54ccb2461841..c9c849534c3998cfcab7ddcb12a71ccb } diff --git a/src/main/java/net/minecraft/world/item/ItemStack.java b/src/main/java/net/minecraft/world/item/ItemStack.java -index 142d762750b9745428ae27802f7a428147a74771..d0c946b0383dbd8242a554a92bc70dcc2f5cccc6 100644 +index b0e45e28d17780b760a81ccbff61581274e033df..1ebaedc9617e5b79458fa119887fd72cb1f39852 100644 --- a/src/main/java/net/minecraft/world/item/ItemStack.java +++ b/src/main/java/net/minecraft/world/item/ItemStack.java @@ -183,7 +183,15 @@ public final class ItemStack implements DataComponentHolder { @@ -3262,10 +3266,10 @@ index 142d762750b9745428ae27802f7a428147a74771..d0c946b0383dbd8242a554a92bc70dcc } }; diff --git a/src/main/java/net/minecraft/world/level/block/entity/SignBlockEntity.java b/src/main/java/net/minecraft/world/level/block/entity/SignBlockEntity.java -index 8ec376f453ac1f4c9423483f5ae1625b295858c7..e535fb3b5194b8412c0c26c0799340916c7542eb 100644 +index 657b5bab0d9e513e52b511e8a1b8fac5799f2281..cdaa050f237c6d6d5a0df0faf23daf3775249451 100644 --- a/src/main/java/net/minecraft/world/level/block/entity/SignBlockEntity.java +++ b/src/main/java/net/minecraft/world/level/block/entity/SignBlockEntity.java -@@ -210,22 +210,22 @@ public class SignBlockEntity extends BlockEntity implements CommandSource { // C +@@ -210,22 +210,22 @@ public class SignBlockEntity extends BlockEntity { // CraftBukkit start Player player = ((ServerPlayer) entityhuman).getBukkitEntity(); @@ -3294,10 +3298,10 @@ index 8ec376f453ac1f4c9423483f5ae1625b295858c7..e535fb3b5194b8412c0c26c079934091 } } diff --git a/src/main/java/net/minecraft/world/level/saveddata/maps/MapItemSavedData.java b/src/main/java/net/minecraft/world/level/saveddata/maps/MapItemSavedData.java -index 6acee03278c8005a06d9cd2577761f2f5355a7ec..5e469bd4d9ca428abdd9d758993164635dc86f27 100644 +index 2decbf562161fa51b931fcb208a3503d7663bb2e..c21ae4975206398e7d20b37a749b830b9219c746 100644 --- a/src/main/java/net/minecraft/world/level/saveddata/maps/MapItemSavedData.java +++ b/src/main/java/net/minecraft/world/level/saveddata/maps/MapItemSavedData.java -@@ -45,6 +45,7 @@ import net.minecraft.world.level.saveddata.SavedData; +@@ -48,6 +48,7 @@ import net.minecraft.world.level.saveddata.SavedData; import org.slf4j.Logger; // CraftBukkit start @@ -3305,7 +3309,7 @@ index 6acee03278c8005a06d9cd2577761f2f5355a7ec..5e469bd4d9ca428abdd9d75899316463 import java.util.UUID; import org.bukkit.Bukkit; import org.bukkit.craftbukkit.CraftServer; -@@ -615,7 +616,7 @@ public class MapItemSavedData extends SavedData { +@@ -650,7 +651,7 @@ public class MapItemSavedData extends SavedData { for (org.bukkit.map.MapCursor cursor : render.cursors) { if (cursor.isVisible()) { @@ -3327,10 +3331,10 @@ index 49c037e961c5ca5ba8d6a870cb32ffe8719adc91..2772c19f58a35713d61aab24f6f0d6f5 } } diff --git a/src/main/java/org/bukkit/craftbukkit/CraftServer.java b/src/main/java/org/bukkit/craftbukkit/CraftServer.java -index 99178e22846c41f1d800b69d4fed064088bbe92c..9f7634053ddfc4fc67f89cc99524e9ebd6096f46 100644 +index eb61712ea067b277e7f32f887e3528faca275450..16a9142bdbbfbbbb69d1486bd119dc610094484b 100644 --- a/src/main/java/org/bukkit/craftbukkit/CraftServer.java +++ b/src/main/java/org/bukkit/craftbukkit/CraftServer.java -@@ -645,8 +645,10 @@ public final class CraftServer implements Server { +@@ -648,8 +648,10 @@ public final class CraftServer implements Server { } @Override @@ -3341,7 +3345,7 @@ index 99178e22846c41f1d800b69d4fed064088bbe92c..9f7634053ddfc4fc67f89cc99524e9eb } @Override -@@ -1623,7 +1625,15 @@ public final class CraftServer implements Server { +@@ -1627,7 +1629,15 @@ public final class CraftServer implements Server { return this.configuration.getInt("settings.spawn-radius", -1); } @@ -3357,7 +3361,7 @@ index 99178e22846c41f1d800b69d4fed064088bbe92c..9f7634053ddfc4fc67f89cc99524e9eb public String getShutdownMessage() { return this.configuration.getString("settings.shutdown-message"); } -@@ -1797,7 +1807,20 @@ public final class CraftServer implements Server { +@@ -1801,7 +1811,20 @@ public final class CraftServer implements Server { } @Override @@ -3378,7 +3382,7 @@ index 99178e22846c41f1d800b69d4fed064088bbe92c..9f7634053ddfc4fc67f89cc99524e9eb Set recipients = new HashSet<>(); for (Permissible permissible : this.getPluginManager().getPermissionSubscriptions(permission)) { if (permissible instanceof CommandSender && permissible.hasPermission(permission)) { -@@ -1805,14 +1828,14 @@ public final class CraftServer implements Server { +@@ -1809,14 +1832,14 @@ public final class CraftServer implements Server { } } @@ -3395,7 +3399,7 @@ index 99178e22846c41f1d800b69d4fed064088bbe92c..9f7634053ddfc4fc67f89cc99524e9eb for (CommandSender recipient : recipients) { recipient.sendMessage(message); -@@ -2074,6 +2097,14 @@ public final class CraftServer implements Server { +@@ -2078,6 +2101,14 @@ public final class CraftServer implements Server { return CraftInventoryCreator.INSTANCE.createInventory(owner, type); } @@ -3410,7 +3414,7 @@ index 99178e22846c41f1d800b69d4fed064088bbe92c..9f7634053ddfc4fc67f89cc99524e9eb @Override public Inventory createInventory(InventoryHolder owner, InventoryType type, String title) { Preconditions.checkArgument(type != null, "InventoryType cannot be null"); -@@ -2088,13 +2119,28 @@ public final class CraftServer implements Server { +@@ -2092,13 +2123,28 @@ public final class CraftServer implements Server { return CraftInventoryCreator.INSTANCE.createInventory(owner, size); } @@ -3439,7 +3443,7 @@ index 99178e22846c41f1d800b69d4fed064088bbe92c..9f7634053ddfc4fc67f89cc99524e9eb public Merchant createMerchant(String title) { return new CraftMerchantCustom(title == null ? InventoryType.MERCHANT.getDefaultTitle() : title); } -@@ -2159,6 +2205,17 @@ public final class CraftServer implements Server { +@@ -2163,6 +2209,17 @@ public final class CraftServer implements Server { return Thread.currentThread().equals(this.console.serverThread) || this.console.hasStopped() || !org.spigotmc.AsyncCatcher.enabled; // All bets are off if we have shut down (e.g. due to watchdog) } @@ -3457,7 +3461,7 @@ index 99178e22846c41f1d800b69d4fed064088bbe92c..9f7634053ddfc4fc67f89cc99524e9eb @Override public String getMotd() { return this.console.getMotd(); -@@ -2605,4 +2662,57 @@ public final class CraftServer implements Server { +@@ -2622,4 +2679,57 @@ public final class CraftServer implements Server { public double[] getTPS() { return new double[]{0, 0, 0}; // TODO } @@ -3554,10 +3558,10 @@ index cbdb1a56a97150c164515a4ce6d3ba06428bf321..b214e7b302abbfe1641485a05f1371ac public URI getUrl() { return this.handle.link(); diff --git a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java -index ac6d4c8ac26696a0c6dfa471eee764b871dd2029..85c99c3b23df1c4fd8721e7676d8e6b82f9e6abf 100644 +index 8b58884d6cb1088a2fffb36a99bfe4dc568326d1..9a79b948264150d0f7a843a8ddd2ea9245ae66f3 100644 --- a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java +++ b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java -@@ -162,6 +162,7 @@ public class CraftWorld extends CraftRegionAccessor implements World { +@@ -166,6 +166,7 @@ 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); @@ -3565,7 +3569,7 @@ index ac6d4c8ac26696a0c6dfa471eee764b871dd2029..85c99c3b23df1c4fd8721e7676d8e6b8 private static final Random rand = new Random(); -@@ -1709,6 +1710,42 @@ public class CraftWorld extends CraftRegionAccessor implements World { +@@ -1705,6 +1706,15 @@ public class CraftWorld extends CraftRegionAccessor implements World { entityTracker.broadcastAndSend(packet); } } @@ -3578,7 +3582,13 @@ index ac6d4c8ac26696a0c6dfa471eee764b871dd2029..85c99c3b23df1c4fd8721e7676d8e6b8 + player.connection.send(io.papermc.paper.adventure.PaperAdventure.asSoundPacket(sound, player.getX(), player.getY(), player.getZ(), seed, null)); + } + } -+ + + @Override + public void playSound(Entity entity, String sound, org.bukkit.SoundCategory category, float volume, float pitch, long seed) { +@@ -1717,6 +1727,33 @@ public class CraftWorld extends CraftRegionAccessor implements World { + } + } + + @Override + public void playSound(final net.kyori.adventure.sound.Sound sound, final double x, final double y, final double z) { + org.spigotmc.AsyncCatcher.catchOp("play sound"); // Paper @@ -3604,11 +3614,12 @@ index ac6d4c8ac26696a0c6dfa471eee764b871dd2029..85c99c3b23df1c4fd8721e7676d8e6b8 + private java.util.function.BiConsumer, Float> playSound0(final double x, final double y, final double z) { + return (packet, distance) -> this.world.getServer().getPlayerList().broadcast(null, x, y, z, distance, this.world.dimension(), packet); + } -+ // Paper end - - private static Map> gamerules; - public static synchronized Map> getGameRulesNMS() { -@@ -2150,5 +2187,18 @@ public class CraftWorld extends CraftRegionAccessor implements World { ++ // Paper end - Adventure ++ + private Map> gamerules; + public synchronized Map> getGameRulesNMS() { + if (this.gamerules != null) { +@@ -2160,5 +2197,18 @@ public class CraftWorld extends CraftRegionAccessor implements World { public void setSendViewDistance(final int viewDistance) { throw new UnsupportedOperationException("Not implemented yet"); } @@ -3628,7 +3639,7 @@ index ac6d4c8ac26696a0c6dfa471eee764b871dd2029..85c99c3b23df1c4fd8721e7676d8e6b8 // Paper end } diff --git a/src/main/java/org/bukkit/craftbukkit/Main.java b/src/main/java/org/bukkit/craftbukkit/Main.java -index 13c37384defda4475de584f33d1860a2d53ce05e..3b0370256b4d8c96053b340a657c6bcc6be6c9de 100644 +index 94004204b6cdbbbf35263faae56e3e06cb6b650c..2e33acc428dbfd3e123dfd6ef90bc020b8a08daf 100644 --- a/src/main/java/org/bukkit/craftbukkit/Main.java +++ b/src/main/java/org/bukkit/craftbukkit/Main.java @@ -20,6 +20,12 @@ public class Main { @@ -3645,10 +3656,10 @@ index 13c37384defda4475de584f33d1860a2d53ce05e..3b0370256b4d8c96053b340a657c6bcc OptionParser parser = new OptionParser() { { diff --git a/src/main/java/org/bukkit/craftbukkit/block/CraftBeacon.java b/src/main/java/org/bukkit/craftbukkit/block/CraftBeacon.java -index 9ef8b0327e377817faaf58b82a8fdfa5e801eac8..2dfbe061a064b0c79b96f644a1c3639bb900eca4 100644 +index 0949f9e6bcb66da94c30439ce4ed4c8415537526..8021ac39cb9c1ff45123d51e6f13b840d1290bb2 100644 --- a/src/main/java/org/bukkit/craftbukkit/block/CraftBeacon.java +++ b/src/main/java/org/bukkit/craftbukkit/block/CraftBeacon.java -@@ -72,6 +72,19 @@ public class CraftBeacon extends CraftBlockEntityState implem +@@ -81,6 +81,19 @@ public class CraftBeacon extends CraftBlockEntityState implem this.getSnapshot().secondaryPower = (effect != null) ? CraftPotionEffectType.bukkitToMinecraftHolder(effect) : null; } @@ -3690,11 +3701,11 @@ index f9b89a7c6ac9f7fdbd29567a5b6550398dbc7345..f5b0bec4c1164fe7ef6da1f19a6ce9bb + // Paper end } diff --git a/src/main/java/org/bukkit/craftbukkit/block/CraftContainer.java b/src/main/java/org/bukkit/craftbukkit/block/CraftContainer.java -index 007b6d66dd837ca6352c0fba5c2399139f6b5425..513402b61e6b8388b7bc163d98e54ffae0e18254 100644 +index 32f3c1f1903baf26c69b1262ff2956d7dcb84a90..76888df4b47ee0d134bca4a3aecddbb1bf4c09b0 100644 --- a/src/main/java/org/bukkit/craftbukkit/block/CraftContainer.java +++ b/src/main/java/org/bukkit/craftbukkit/block/CraftContainer.java -@@ -32,6 +32,19 @@ public abstract class CraftContainer extends - this.getSnapshot().lockKey = (key == null) ? LockCode.NO_LOCK : new LockCode(key); +@@ -57,6 +57,19 @@ public abstract class CraftContainer extends + } } + // Paper start @@ -4033,10 +4044,10 @@ index 2d0c82ca240e371a756d71f28e2e04d1aa8e6ad2..f73017bff613bd62b86c974b29576e24 @Override public String getTranslationKey() { diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java -index d09102fe44dffa61dff883488f47715effaa8211..269326e7689eba91bcfd3475006e8cbf8f5694ef 100644 +index 8b53a3c4f4285321b12ac0d51f94c04f39cc8ec8..978397e517a6fdb24c7d2b3f242545af07deeab0 100644 --- a/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java +++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java -@@ -69,6 +69,7 @@ public abstract class CraftEntity implements org.bukkit.entity.Entity { +@@ -70,6 +70,7 @@ public abstract class CraftEntity implements org.bukkit.entity.Entity { private final EntityType entityType; private EntityDamageEvent lastDamageEvent; private final CraftPersistentDataContainer persistentDataContainer = new CraftPersistentDataContainer(CraftEntity.DATA_TYPE_REGISTRY); @@ -4044,7 +4055,7 @@ index d09102fe44dffa61dff883488f47715effaa8211..269326e7689eba91bcfd3475006e8cbf public CraftEntity(final CraftServer server, final Entity entity) { this.server = server; -@@ -525,6 +526,32 @@ public abstract class CraftEntity implements org.bukkit.entity.Entity { +@@ -526,6 +527,32 @@ public abstract class CraftEntity implements org.bukkit.entity.Entity { return this.getHandle().getVehicle().getBukkitEntity(); } @@ -4077,7 +4088,7 @@ index d09102fe44dffa61dff883488f47715effaa8211..269326e7689eba91bcfd3475006e8cbf @Override public void setCustomName(String name) { // sane limit for name length -@@ -621,6 +648,17 @@ public abstract class CraftEntity implements org.bukkit.entity.Entity { +@@ -622,6 +649,17 @@ public abstract class CraftEntity implements org.bukkit.entity.Entity { public String getName() { return CraftChatMessage.fromComponent(this.getHandle().getName()); } @@ -4096,10 +4107,10 @@ index d09102fe44dffa61dff883488f47715effaa8211..269326e7689eba91bcfd3475006e8cbf @Override public boolean isPermissionSet(String name) { diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftHumanEntity.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftHumanEntity.java -index f77f14d549479a5e6d4ef823c137954de746a5ce..a93895a6e656c25e819354ecf5c73ff4bae83675 100644 +index 3091c174f7f8454035d015e96278e87284d5f399..bfa44c4e37618df3f745bccc6e775ce16c19490d 100644 --- a/src/main/java/org/bukkit/craftbukkit/entity/CraftHumanEntity.java +++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftHumanEntity.java -@@ -329,9 +329,12 @@ public class CraftHumanEntity extends CraftLivingEntity implements HumanEntity { +@@ -330,9 +330,12 @@ public class CraftHumanEntity extends CraftLivingEntity implements HumanEntity { container = CraftEventFactory.callInventoryOpenEvent(player, container); if (container == null) return; @@ -4114,7 +4125,7 @@ index f77f14d549479a5e6d4ef823c137954de746a5ce..a93895a6e656c25e819354ecf5c73ff4 player.containerMenu = container; player.initMenu(container); } -@@ -401,8 +404,12 @@ public class CraftHumanEntity extends CraftLivingEntity implements HumanEntity { +@@ -402,8 +405,12 @@ public class CraftHumanEntity extends CraftLivingEntity implements HumanEntity { // Now open the window MenuType windowType = CraftContainer.getNotchInventoryType(inventory.getTopInventory()); @@ -4148,10 +4159,10 @@ index 55945b83a5426b352bad9507cc9e94afb1278032..9ea1537408ff2d790747b6e5a681d917 public boolean isOp() { return true; diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java -index 9cd22b09b295ecd95d17d1dd5edb96f916eb6c09..69b8d0f73ced69cd88029a5d7e11aca40d70f9d1 100644 +index a3550d84c273c9720491484382a4bc50dc3246a6..24631135f90bb74bf829160ed079e152573666a2 100644 --- a/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java +++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java -@@ -390,14 +390,40 @@ public class CraftPlayer extends CraftHumanEntity implements Player { +@@ -395,14 +395,40 @@ public class CraftPlayer extends CraftHumanEntity implements Player { @Override public String getDisplayName() { @@ -4192,7 +4203,7 @@ index 9cd22b09b295ecd95d17d1dd5edb96f916eb6c09..69b8d0f73ced69cd88029a5d7e11aca4 @Override public String getPlayerListName() { return this.getHandle().listName == null ? this.getName() : CraftChatMessage.fromComponent(this.getHandle().listName); -@@ -409,6 +435,7 @@ public class CraftPlayer extends CraftHumanEntity implements Player { +@@ -414,6 +440,7 @@ public class CraftPlayer extends CraftHumanEntity implements Player { name = this.getName(); } this.getHandle().listName = name.equals(this.getName()) ? null : CraftChatMessage.fromStringOrNull(name); @@ -4200,8 +4211,8 @@ index 9cd22b09b295ecd95d17d1dd5edb96f916eb6c09..69b8d0f73ced69cd88029a5d7e11aca4 for (ServerPlayer player : (List) this.server.getHandle().players) { if (player.getBukkitEntity().canSee(this)) { player.connection.send(new ClientboundPlayerInfoUpdatePacket(ClientboundPlayerInfoUpdatePacket.Action.UPDATE_DISPLAY_NAME, this.getHandle())); -@@ -416,42 +443,42 @@ public class CraftPlayer extends CraftHumanEntity implements Player { - } +@@ -433,42 +460,42 @@ public class CraftPlayer extends CraftHumanEntity implements Player { + this.getHandle().listOrder = order; } - private Component playerListHeader; @@ -4252,7 +4263,7 @@ index 9cd22b09b295ecd95d17d1dd5edb96f916eb6c09..69b8d0f73ced69cd88029a5d7e11aca4 this.getHandle().connection.send(packet); } -@@ -481,6 +508,23 @@ public class CraftPlayer extends CraftHumanEntity implements Player { +@@ -498,6 +525,23 @@ public class CraftPlayer extends CraftHumanEntity implements Player { this.getHandle().transferCookieConnection.kickPlayer(CraftChatMessage.fromStringOrEmpty(message, true)); } @@ -4276,7 +4287,7 @@ index 9cd22b09b295ecd95d17d1dd5edb96f916eb6c09..69b8d0f73ced69cd88029a5d7e11aca4 @Override public void setCompassTarget(Location loc) { Preconditions.checkArgument(loc != null, "Location cannot be null"); -@@ -777,6 +821,24 @@ public class CraftPlayer extends CraftHumanEntity implements Player { +@@ -794,6 +838,24 @@ public class CraftPlayer extends CraftHumanEntity implements Player { this.getHandle().connection.send(packet); } @@ -4301,7 +4312,7 @@ index 9cd22b09b295ecd95d17d1dd5edb96f916eb6c09..69b8d0f73ced69cd88029a5d7e11aca4 @Override public void sendSignChange(Location loc, String[] lines) { this.sendSignChange(loc, lines, DyeColor.BLACK); -@@ -800,6 +862,12 @@ public class CraftPlayer extends CraftHumanEntity implements Player { +@@ -817,6 +879,12 @@ public class CraftPlayer extends CraftHumanEntity implements Player { if (this.getHandle().connection == null) return; Component[] components = CraftSign.sanitizeLines(lines); @@ -4314,7 +4325,7 @@ index 9cd22b09b295ecd95d17d1dd5edb96f916eb6c09..69b8d0f73ced69cd88029a5d7e11aca4 SignBlockEntity sign = new SignBlockEntity(CraftLocation.toBlockPosition(loc), Blocks.OAK_SIGN.defaultBlockState()); SignText text = sign.getFrontText(); text = text.setColor(net.minecraft.world.item.DyeColor.byId(dyeColor.getWoolData())); -@@ -1816,7 +1884,7 @@ public class CraftPlayer extends CraftHumanEntity implements Player { +@@ -1843,7 +1911,7 @@ public class CraftPlayer extends CraftHumanEntity implements Player { @Override public void setResourcePack(String url) { @@ -4323,7 +4334,7 @@ index 9cd22b09b295ecd95d17d1dd5edb96f916eb6c09..69b8d0f73ced69cd88029a5d7e11aca4 } @Override -@@ -1831,7 +1899,7 @@ public class CraftPlayer extends CraftHumanEntity implements Player { +@@ -1858,7 +1926,7 @@ public class CraftPlayer extends CraftHumanEntity implements Player { @Override public void setResourcePack(String url, byte[] hash, boolean force) { @@ -4332,7 +4343,7 @@ index 9cd22b09b295ecd95d17d1dd5edb96f916eb6c09..69b8d0f73ced69cd88029a5d7e11aca4 } @Override -@@ -1868,6 +1936,59 @@ public class CraftPlayer extends CraftHumanEntity implements Player { +@@ -1895,6 +1963,59 @@ public class CraftPlayer extends CraftHumanEntity implements Player { this.handlePushResourcePack(new ClientboundResourcePackPushPacket(id, url, hashStr, force, CraftChatMessage.fromStringOrOptional(prompt, true)), false); } @@ -4392,7 +4403,7 @@ index 9cd22b09b295ecd95d17d1dd5edb96f916eb6c09..69b8d0f73ced69cd88029a5d7e11aca4 @Override public void removeResourcePack(UUID id) { Preconditions.checkArgument(id != null, "Resource pack id cannot be null"); -@@ -2288,6 +2409,12 @@ public class CraftPlayer extends CraftHumanEntity implements Player { +@@ -2315,6 +2436,12 @@ public class CraftPlayer extends CraftHumanEntity implements Player { return (this.getHandle().requestedViewDistance() == 0) ? Bukkit.getViewDistance() : this.getHandle().requestedViewDistance(); } @@ -4405,7 +4416,7 @@ index 9cd22b09b295ecd95d17d1dd5edb96f916eb6c09..69b8d0f73ced69cd88029a5d7e11aca4 @Override public int getPing() { return this.getHandle().connection.latency(); -@@ -2338,6 +2465,248 @@ public class CraftPlayer extends CraftHumanEntity implements Player { +@@ -2365,6 +2492,248 @@ public class CraftPlayer extends CraftHumanEntity implements Player { return this.getHandle().allowsListing(); } @@ -4430,10 +4441,10 @@ index 9cd22b09b295ecd95d17d1dd5edb96f916eb6c09..69b8d0f73ced69cd88029a5d7e11aca4 + } + + private net.minecraft.network.chat.ChatType.Bound toHandle(net.kyori.adventure.chat.ChatType.Bound boundChatType) { -+ net.minecraft.core.Registry chatTypeRegistry = this.getHandle().level().registryAccess().registryOrThrow(net.minecraft.core.registries.Registries.CHAT_TYPE); ++ net.minecraft.core.Registry chatTypeRegistry = this.getHandle().level().registryAccess().lookupOrThrow(net.minecraft.core.registries.Registries.CHAT_TYPE); + + return new net.minecraft.network.chat.ChatType.Bound( -+ chatTypeRegistry.getHolderOrThrow(net.minecraft.resources.ResourceKey.create(net.minecraft.core.registries.Registries.CHAT_TYPE, io.papermc.paper.adventure.PaperAdventure.asVanilla(boundChatType.type().key()))), ++ chatTypeRegistry.getOrThrow(net.minecraft.resources.ResourceKey.create(net.minecraft.core.registries.Registries.CHAT_TYPE, io.papermc.paper.adventure.PaperAdventure.asVanilla(boundChatType.type().key()))), + io.papermc.paper.adventure.PaperAdventure.asVanilla(boundChatType.name()), + Optional.ofNullable(io.papermc.paper.adventure.PaperAdventure.asVanilla(boundChatType.target())) + ); @@ -4476,7 +4487,7 @@ index 9cd22b09b295ecd95d17d1dd5edb96f916eb6c09..69b8d0f73ced69cd88029a5d7e11aca4 + @Override + public void sendMessage(final net.kyori.adventure.identity.Identity identity, final net.kyori.adventure.text.Component message, final net.kyori.adventure.audience.MessageType type) { + if (getHandle().connection == null) return; -+ final net.minecraft.core.Registry chatTypeRegistry = this.getHandle().level().registryAccess().registryOrThrow(net.minecraft.core.registries.Registries.CHAT_TYPE); ++ final net.minecraft.core.Registry chatTypeRegistry = this.getHandle().level().registryAccess().lookupOrThrow(net.minecraft.core.registries.Registries.CHAT_TYPE); + this.getHandle().connection.send(new net.minecraft.network.protocol.game.ClientboundSystemChatPacket(message, false)); + } + @@ -4677,10 +4688,10 @@ index 5725b0281ac53a2354b233223259d6784353bc6e..9ef939b76d06874b856e0c850addb364 @Override public int getLineWidth() { diff --git a/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java b/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java -index 7719afbb1af4091f688a75c92ee8cae8669974ff..34636f7a05d868c4df86316a65c4e008f54db634 100644 +index b5034787283b5a0403c11281895563372fccb5d2..8ea4d63833cd1500d7f413f761aa9a7cf26520c0 100644 --- a/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java +++ b/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java -@@ -917,7 +917,7 @@ public class CraftEventFactory { +@@ -915,7 +915,7 @@ public class CraftEventFactory { return event; } @@ -4688,8 +4699,8 @@ index 7719afbb1af4091f688a75c92ee8cae8669974ff..34636f7a05d868c4df86316a65c4e008 + public static PlayerDeathEvent callPlayerDeathEvent(ServerPlayer victim, DamageSource damageSource, List drops, net.kyori.adventure.text.Component deathMessage, boolean keepInventory) { // Paper - Adventure CraftPlayer entity = victim.getBukkitEntity(); CraftDamageSource bukkitDamageSource = new CraftDamageSource(damageSource); - PlayerDeathEvent event = new PlayerDeathEvent(entity, bukkitDamageSource, drops, victim.getExpReward(damageSource.getEntity()), 0, deathMessage); -@@ -950,7 +950,7 @@ public class CraftEventFactory { + PlayerDeathEvent event = new PlayerDeathEvent(entity, bukkitDamageSource, drops, victim.getExpReward(victim.serverLevel(), damageSource.getEntity()), 0, deathMessage); +@@ -948,7 +948,7 @@ public class CraftEventFactory { * Server methods */ public static ServerListPingEvent callServerListPingEvent(SocketAddress address, String motd, int numPlayers, int maxPlayers) { @@ -4821,11 +4832,11 @@ index d89b178dc82c7e2ad6d586217c5a039688563e29..d674289b07748022b94cc6a7e6c6eb45 public String getTitle() { return this.title; diff --git a/src/main/java/org/bukkit/craftbukkit/inventory/CraftItemFactory.java b/src/main/java/org/bukkit/craftbukkit/inventory/CraftItemFactory.java -index 500bfe5dd745327f953a314e863f7318953bf3bc..e2daeeba1bacbb5c5ca2aa922fa67b02cd050755 100644 +index 6e111222e251e2f7959390e9fa0c31a163207c14..5d7e6b61102a6244c5426917c4ff280fa9e12cf1 100644 --- a/src/main/java/org/bukkit/craftbukkit/inventory/CraftItemFactory.java +++ b/src/main/java/org/bukkit/craftbukkit/inventory/CraftItemFactory.java -@@ -206,4 +206,21 @@ public final class CraftItemFactory implements ItemFactory { - Optional> optional = (allowTreasures) ? Optional.empty() : registry.registryOrThrow(Registries.ENCHANTMENT).getTag(EnchantmentTags.IN_ENCHANTING_TABLE); +@@ -209,4 +209,21 @@ public final class CraftItemFactory implements ItemFactory { + Optional> optional = (allowTreasures) ? Optional.empty() : registry.lookupOrThrow(Registries.ENCHANTMENT).get(EnchantmentTags.IN_ENCHANTING_TABLE); return CraftItemStack.asCraftMirror(EnchantmentHelper.enchantItem(source, craft.handle, level, registry, optional)); } + @@ -5191,11 +5202,11 @@ index c71a4971f127fdfc753306019313ce1a31201120..fd3b12477c30d1eabdbe57ea77902793 + // Paper end } diff --git a/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaItem.java b/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaItem.java -index 8bba749bce211814f51af8a33f2ed60ba7a8b52d..0c73854243f7fa21d1ffdb3b4c85ee0a69c9c5e4 100644 +index bc68a45af66699f013851869646a2c11d5a871ee..626fe2af05fecd41b777b5dd5decbedb2f17b43a 100644 --- a/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaItem.java +++ b/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaItem.java -@@ -961,6 +961,18 @@ class CraftMetaItem implements ItemMeta, Damageable, Repairable, BlockDataMeta { - return !(this.hasDisplayName() || this.hasItemName() || this.hasLocalizedName() || this.hasEnchants() || (this.lore != null) || this.hasCustomModelData() || this.hasBlockData() || this.hasRepairCost() || !this.unhandledTags.build().isEmpty() || !this.removedTags.isEmpty() || !this.persistentDataContainer.isEmpty() || this.hideFlag != 0 || this.isHideTooltip() || this.isUnbreakable() || this.hasEnchantmentGlintOverride() || this.isFireResistant() || this.hasMaxStackSize() || this.hasRarity() || this.hasFood() || this.hasTool() || this.hasJukeboxPlayable() || this.hasDamage() || this.hasMaxDamage() || this.hasAttributeModifiers() || this.customTag != null); +@@ -1097,6 +1097,18 @@ class CraftMetaItem implements ItemMeta, Damageable, Repairable, BlockDataMeta { + return !(this.hasDisplayName() || this.hasItemName() || this.hasLocalizedName() || this.hasEnchants() || (this.lore != null) || this.hasCustomModelData() || this.hasEnchantable() || this.hasBlockData() || this.hasRepairCost() || !this.unhandledTags.build().isEmpty() || !this.removedTags.isEmpty() || !this.persistentDataContainer.isEmpty() || this.hideFlag != 0 || this.isHideTooltip() || this.hasTooltipStyle() || this.hasItemModel() || this.isUnbreakable() || this.hasEnchantmentGlintOverride() || this.isGlider() || this.hasDamageResistant() || this.hasMaxStackSize() || this.hasRarity() || this.hasUseRemainder() || this.hasUseCooldown() || this.hasFood() || this.hasTool() || this.hasJukeboxPlayable() || this.hasEquippable() || this.hasDamage() || this.hasMaxDamage() || this.hasAttributeModifiers() || this.customTag != null); } + // Paper start @@ -5213,7 +5224,7 @@ index 8bba749bce211814f51af8a33f2ed60ba7a8b52d..0c73854243f7fa21d1ffdb3b4c85ee0a @Override public String getDisplayName() { return CraftChatMessage.fromComponent(this.displayName); -@@ -991,6 +1003,18 @@ class CraftMetaItem implements ItemMeta, Damageable, Repairable, BlockDataMeta { +@@ -1127,6 +1139,18 @@ class CraftMetaItem implements ItemMeta, Damageable, Repairable, BlockDataMeta { return this.itemName != null; } @@ -5232,7 +5243,7 @@ index 8bba749bce211814f51af8a33f2ed60ba7a8b52d..0c73854243f7fa21d1ffdb3b4c85ee0a @Override public String getLocalizedName() { return this.getDisplayName(); -@@ -1010,6 +1034,18 @@ class CraftMetaItem implements ItemMeta, Damageable, Repairable, BlockDataMeta { +@@ -1146,6 +1170,18 @@ class CraftMetaItem implements ItemMeta, Damageable, Repairable, BlockDataMeta { return this.lore != null && !this.lore.isEmpty(); } @@ -5252,7 +5263,7 @@ index 8bba749bce211814f51af8a33f2ed60ba7a8b52d..0c73854243f7fa21d1ffdb3b4c85ee0a public boolean hasRepairCost() { return this.repairCost > 0; diff --git a/src/main/java/org/bukkit/craftbukkit/inventory/trim/CraftTrimMaterial.java b/src/main/java/org/bukkit/craftbukkit/inventory/trim/CraftTrimMaterial.java -index 8952e5526cfe90ad16a7af28b0a8d9c65b159f90..cd3e35867075e65f46051fb88d8a2460a8bb4b53 100644 +index 38fb47bbfcec739be795b46cfb7c2c41a8379fea..caf7e4312e95e90dd0822355c8832006e69a2700 100644 --- a/src/main/java/org/bukkit/craftbukkit/inventory/trim/CraftTrimMaterial.java +++ b/src/main/java/org/bukkit/craftbukkit/inventory/trim/CraftTrimMaterial.java @@ -60,6 +60,14 @@ public class CraftTrimMaterial implements TrimMaterial, Handleable diff --git a/build.gradle.kts b/build.gradle.kts -index c4572c0a6d9695088613d158838f236fe64efb48..83daf789efc4ce90ff54420e040b783569eb5cab 100644 +index da6b4787fa787e098e4031790e955ce616593ee9..02a3dd42d82df410b6a6d22c0350fa3e44ccf70f 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -5,9 +5,29 @@ plugins { @@ -216,7 +216,7 @@ index 0000000000000000000000000000000000000000..8f07539a82f449ad217e316a7513a170 + +} diff --git a/src/main/java/io/papermc/paper/adventure/PaperAdventure.java b/src/main/java/io/papermc/paper/adventure/PaperAdventure.java -index dc4837c577676115f0653acc35f55962a432e425..22fe529890f34f66534c01248f654dc911b44c3b 100644 +index 161bc8c577643094d824ea96fb6974c76e5e77f0..cfcaf215c4a901dd2938c7ce41db502c57b42bbf 100644 --- a/src/main/java/io/papermc/paper/adventure/PaperAdventure.java +++ b/src/main/java/io/papermc/paper/adventure/PaperAdventure.java @@ -32,6 +32,7 @@ import net.kyori.adventure.text.flattener.ComponentFlattener; @@ -260,10 +260,10 @@ index 8323f135d6bf2e1f12525e05094ffa3f2420e7e1..a143ea1e58464a3122fbd8ccafe417bd } } diff --git a/src/main/java/net/minecraft/server/MinecraftServer.java b/src/main/java/net/minecraft/server/MinecraftServer.java -index 387552877ac82baa3af0da723dbb7c331fad6cf4..b334cfc9b98d96f9bfba1d5b14c03c336d30ff23 100644 +index daf20aa9a83a2583c0c61a4123cc2e528d19801d..afb67956031f4bcccee12f0ece4bb8a7e6f02cfc 100644 --- a/src/main/java/net/minecraft/server/MinecraftServer.java +++ b/src/main/java/net/minecraft/server/MinecraftServer.java -@@ -154,7 +154,7 @@ import com.mojang.serialization.Dynamic; +@@ -161,7 +161,7 @@ import com.mojang.serialization.Dynamic; import com.mojang.serialization.Lifecycle; import java.io.File; import java.util.Random; @@ -272,7 +272,7 @@ index 387552877ac82baa3af0da723dbb7c331fad6cf4..b334cfc9b98d96f9bfba1d5b14c03c33 import joptsimple.OptionSet; import net.minecraft.nbt.NbtException; import net.minecraft.nbt.ReportedNbtException; -@@ -296,7 +296,6 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop processQueue = new java.util.concurrent.ConcurrentLinkedQueue(); public int autosavePeriod; -@@ -383,7 +382,9 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop 0) { // Trim to filter lines which are just spaces @@ -340,7 +340,7 @@ index cd26616aba6abd44abc5eb8b01cc96f29248aecd..b41eb920b5665b7a1b7cd9f38955c31e } // CraftBukkit end } -@@ -151,6 +154,8 @@ public class DedicatedServer extends MinecraftServer implements ServerInterface +@@ -150,6 +153,8 @@ public class DedicatedServer extends MinecraftServer implements ServerInterface DedicatedServer.LOGGER.error("Exception handling console input", ioexception); } @@ -349,7 +349,7 @@ index cd26616aba6abd44abc5eb8b01cc96f29248aecd..b41eb920b5665b7a1b7cd9f38955c31e } }; -@@ -162,6 +167,9 @@ public class DedicatedServer extends MinecraftServer implements ServerInterface +@@ -161,6 +166,9 @@ public class DedicatedServer extends MinecraftServer implements ServerInterface } global.addHandler(new org.bukkit.craftbukkit.util.ForwardLogHandler()); @@ -359,7 +359,7 @@ index cd26616aba6abd44abc5eb8b01cc96f29248aecd..b41eb920b5665b7a1b7cd9f38955c31e final org.apache.logging.log4j.core.Logger logger = ((org.apache.logging.log4j.core.Logger) LogManager.getRootLogger()); for (org.apache.logging.log4j.core.Appender appender : logger.getAppenders().values()) { if (appender instanceof org.apache.logging.log4j.core.appender.ConsoleAppender) { -@@ -172,6 +180,8 @@ public class DedicatedServer extends MinecraftServer implements ServerInterface +@@ -171,6 +179,8 @@ public class DedicatedServer extends MinecraftServer implements ServerInterface TerminalConsoleWriterThread writerThread = new TerminalConsoleWriterThread(System.out, this.reader); this.reader.setCompletionHandler(new TerminalCompletionHandler(writerThread, this.reader.getCompletionHandler())); writerThread.start(); @@ -382,10 +382,10 @@ index 3d92c61f7781221cfdc0324d11bd0088954e4a68..84a2c6c397604279ba821286f5c3c855 if (!SwingUtilities.isEventDispatchThread()) { SwingUtilities.invokeLater(() -> { diff --git a/src/main/java/net/minecraft/server/players/PlayerList.java b/src/main/java/net/minecraft/server/players/PlayerList.java -index e9109526880159e2341cc97b53939ba2bcfaeaf9..9dcfcea63f57f45a5584bb80c34fe445d65849e8 100644 +index a70e0ecedc7b70031334bc7fee981bd790ce26a4..56f046bac04205a813441907058c4ce21982d927 100644 --- a/src/main/java/net/minecraft/server/players/PlayerList.java +++ b/src/main/java/net/minecraft/server/players/PlayerList.java -@@ -161,8 +161,7 @@ public abstract class PlayerList { +@@ -162,8 +162,7 @@ public abstract class PlayerList { public PlayerList(MinecraftServer server, LayeredRegistryAccess registryManager, PlayerDataStorage saveHandler, int maxPlayers) { this.cserver = server.server = new CraftServer((DedicatedServer) server, this); @@ -396,7 +396,7 @@ index e9109526880159e2341cc97b53939ba2bcfaeaf9..9dcfcea63f57f45a5584bb80c34fe445 this.bans = new UserBanList(PlayerList.USERBANLIST_FILE); diff --git a/src/main/java/org/bukkit/craftbukkit/CraftServer.java b/src/main/java/org/bukkit/craftbukkit/CraftServer.java -index 9f7634053ddfc4fc67f89cc99524e9ebd6096f46..752933ceabd7ebe7b1e9f7d41198128f6f2182a6 100644 +index 16a9142bdbbfbbbb69d1486bd119dc610094484b..2affb23b83e4368a94345b36410f23139f5d36c8 100644 --- a/src/main/java/org/bukkit/craftbukkit/CraftServer.java +++ b/src/main/java/org/bukkit/craftbukkit/CraftServer.java @@ -43,7 +43,7 @@ import java.util.logging.Level; @@ -408,7 +408,7 @@ index 9f7634053ddfc4fc67f89cc99524e9ebd6096f46..752933ceabd7ebe7b1e9f7d41198128f import net.minecraft.advancements.AdvancementHolder; import net.minecraft.commands.CommandSourceStack; import net.minecraft.commands.Commands; -@@ -1356,9 +1356,13 @@ public final class CraftServer implements Server { +@@ -1359,9 +1359,13 @@ public final class CraftServer implements Server { return this.logger; } @@ -423,7 +423,7 @@ index 9f7634053ddfc4fc67f89cc99524e9ebd6096f46..752933ceabd7ebe7b1e9f7d41198128f @Override public PluginCommand getPluginCommand(String name) { diff --git a/src/main/java/org/bukkit/craftbukkit/Main.java b/src/main/java/org/bukkit/craftbukkit/Main.java -index 3b0370256b4d8c96053b340a657c6bcc6be6c9de..bdb8e882ec1efbe5afec9ec5a5df57bf38d4ba2b 100644 +index 2e33acc428dbfd3e123dfd6ef90bc020b8a08daf..4a99cf5a146abe0d2b40ffc1189fdc5540f14d55 100644 --- a/src/main/java/org/bukkit/craftbukkit/Main.java +++ b/src/main/java/org/bukkit/craftbukkit/Main.java @@ -13,7 +13,6 @@ import java.util.logging.Logger; diff --git a/patches/server/0012-Handle-plugin-prefixes-using-Log4J-configuration.patch b/patches/server/0012-Handle-plugin-prefixes-using-Log4J-configuration.patch index 8128ee5424c7..8fbbff759a3e 100644 --- a/patches/server/0012-Handle-plugin-prefixes-using-Log4J-configuration.patch +++ b/patches/server/0012-Handle-plugin-prefixes-using-Log4J-configuration.patch @@ -15,7 +15,7 @@ This may cause additional prefixes to be disabled for plugins bypassing the plugin logger. diff --git a/build.gradle.kts b/build.gradle.kts -index 0a05e753ff5e7b1d741c7719524715d7364cac4f..d82d1e90cbda544b3d20edcc13d1cb955c48f731 100644 +index 02a3dd42d82df410b6a6d22c0350fa3e44ccf70f..a6fafa32af547efd912468ca3a5c76d8d193651d 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -23,7 +23,7 @@ dependencies { diff --git a/patches/server/0014-Use-AsyncAppender-to-keep-logging-IO-off-main-thread.patch b/patches/server/0014-Use-AsyncAppender-to-keep-logging-IO-off-main-thread.patch index 1d920095cb46..6c4c11016c03 100644 --- a/patches/server/0014-Use-AsyncAppender-to-keep-logging-IO-off-main-thread.patch +++ b/patches/server/0014-Use-AsyncAppender-to-keep-logging-IO-off-main-thread.patch @@ -5,13 +5,13 @@ Subject: [PATCH] Use AsyncAppender to keep logging IO off main thread diff --git a/build.gradle.kts b/build.gradle.kts -index d82d1e90cbda544b3d20edcc13d1cb955c48f731..3bd5c2a2add9b462523beb9dfaf2eb5a00d470b9 100644 +index a6fafa32af547efd912468ca3a5c76d8d193651d..b36f2e9e9980f6d2596c57791e0769c6ce734d11 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -34,6 +34,7 @@ dependencies { implementation("commons-lang:commons-lang:2.6") - runtimeOnly("org.xerial:sqlite-jdbc:3.46.0.0") - runtimeOnly("com.mysql:mysql-connector-j:8.4.0") + runtimeOnly("org.xerial:sqlite-jdbc:3.46.1.3") + runtimeOnly("com.mysql:mysql-connector-j:9.1.0") + runtimeOnly("com.lmax:disruptor:3.4.4") // Paper runtimeOnly("org.apache.maven:maven-resolver-provider:3.9.6") diff --git a/patches/server/0015-Deobfuscate-stacktraces-in-log-messages-crash-report.patch b/patches/server/0015-Deobfuscate-stacktraces-in-log-messages-crash-report.patch index a6358b2bba66..2c9e91fed774 100644 --- a/patches/server/0015-Deobfuscate-stacktraces-in-log-messages-crash-report.patch +++ b/patches/server/0015-Deobfuscate-stacktraces-in-log-messages-crash-report.patch @@ -6,7 +6,7 @@ Subject: [PATCH] Deobfuscate stacktraces in log messages, crash reports, and diff --git a/build.gradle.kts b/build.gradle.kts -index 7c81605648c6ba1360936faae25ef5fc11f0bcb4..4838ca75b9536ce5fd30906b51bdab86789668ec 100644 +index b36f2e9e9980f6d2596c57791e0769c6ce734d11..c73ab51caa985089ade4df0616ee8d3333632980 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -46,6 +46,7 @@ dependencies { @@ -454,7 +454,7 @@ index 1938ae691dafec1fc1e5a68792d1191bd52b4e5c..268310642181a715815d3b2d1c0f090e this.exception = cause; this.systemReport.setDetail("CraftBukkit Information", new org.bukkit.craftbukkit.CraftCrashReport()); // CraftBukkit diff --git a/src/main/java/net/minecraft/CrashReportCategory.java b/src/main/java/net/minecraft/CrashReportCategory.java -index f367ba058018074bfe6e4fe88bcc875ea9794d9e..2176171954609fd88f97f93408e14e018c1d6eaa 100644 +index 81831a061d7fbf513f4aa7880e3b18ef3fdc05d7..1e9873d7b258ce1f0b2437cb1e487157a16f6834 100644 --- a/src/main/java/net/minecraft/CrashReportCategory.java +++ b/src/main/java/net/minecraft/CrashReportCategory.java @@ -110,6 +110,7 @@ public class CrashReportCategory { @@ -496,10 +496,10 @@ index 77985072928a1b892fb4f7dec1d0899324780082..f5e6610d271ef2c997fb3d1a5f65e0bf protected void channelRead0(ChannelHandlerContext channelhandlercontext, Packet packet) { diff --git a/src/main/java/net/minecraft/server/dedicated/DedicatedServer.java b/src/main/java/net/minecraft/server/dedicated/DedicatedServer.java -index b41eb920b5665b7a1b7cd9f38955c31eeb350847..bb59986c211f7d6ea50b1ad4bd5565227bec8a6c 100644 +index dcb9258d3fbdfdfd41065d4c0919ed4300eac3ae..a61a92078a8bb4979f231c02ef5aa990b8ab57ad 100644 --- a/src/main/java/net/minecraft/server/dedicated/DedicatedServer.java +++ b/src/main/java/net/minecraft/server/dedicated/DedicatedServer.java -@@ -210,6 +210,7 @@ public class DedicatedServer extends MinecraftServer implements ServerInterface +@@ -209,6 +209,7 @@ public class DedicatedServer extends MinecraftServer implements ServerInterface org.spigotmc.SpigotConfig.init((java.io.File) this.options.valueOf("spigot-settings")); org.spigotmc.SpigotConfig.registerCommands(); // Spigot end diff --git a/patches/server/0017-Paper-command.patch b/patches/server/0017-Paper-command.patch index 4d47273f6565..4b47a9acbbb3 100644 --- a/patches/server/0017-Paper-command.patch +++ b/patches/server/0017-Paper-command.patch @@ -605,10 +605,10 @@ index 0000000000000000000000000000000000000000..ae60bd96b5284d54676d8e7e4dd5d170 + } +} diff --git a/src/main/java/net/minecraft/server/dedicated/DedicatedServer.java b/src/main/java/net/minecraft/server/dedicated/DedicatedServer.java -index bb59986c211f7d6ea50b1ad4bd5565227bec8a6c..9c950fc1de15b5039e34a9fdf893e97a8cc13237 100644 +index a61a92078a8bb4979f231c02ef5aa990b8ab57ad..cd9e4bfdb3f335213001ced27540bb7efbc04130 100644 --- a/src/main/java/net/minecraft/server/dedicated/DedicatedServer.java +++ b/src/main/java/net/minecraft/server/dedicated/DedicatedServer.java -@@ -215,6 +215,7 @@ public class DedicatedServer extends MinecraftServer implements ServerInterface +@@ -214,6 +214,7 @@ public class DedicatedServer extends MinecraftServer implements ServerInterface this.paperConfigurations.initializeGlobalConfiguration(this.registryAccess()); this.paperConfigurations.initializeWorldDefaultsConfiguration(this.registryAccess()); // Paper end - initialize global and world-defaults configuration @@ -617,10 +617,10 @@ index bb59986c211f7d6ea50b1ad4bd5565227bec8a6c..9c950fc1de15b5039e34a9fdf893e97a this.setPvpAllowed(dedicatedserverproperties.pvp); this.setFlightAllowed(dedicatedserverproperties.allowFlight); diff --git a/src/main/java/org/bukkit/craftbukkit/CraftServer.java b/src/main/java/org/bukkit/craftbukkit/CraftServer.java -index 752933ceabd7ebe7b1e9f7d41198128f6f2182a6..492af80bc4a252ad3b82aef0c46c3e6625ec9146 100644 +index 2affb23b83e4368a94345b36410f23139f5d36c8..1b66c5173dd37433d895c0d804257141a3a8c588 100644 --- a/src/main/java/org/bukkit/craftbukkit/CraftServer.java +++ b/src/main/java/org/bukkit/craftbukkit/CraftServer.java -@@ -988,6 +988,7 @@ public final class CraftServer implements Server { +@@ -991,6 +991,7 @@ public final class CraftServer implements Server { this.commandMap.clearCommands(); this.reloadData(); org.spigotmc.SpigotConfig.registerCommands(); // Spigot @@ -628,7 +628,7 @@ index 752933ceabd7ebe7b1e9f7d41198128f6f2182a6..492af80bc4a252ad3b82aef0c46c3e66 this.overrideAllCommandBlockCommands = this.commandsConfiguration.getStringList("command-block-overrides").contains("*"); this.ignoreVanillaPermissions = this.commandsConfiguration.getBoolean("ignore-vanilla-permissions"); -@@ -2710,6 +2711,34 @@ public final class CraftServer implements Server { +@@ -2727,6 +2728,34 @@ public final class CraftServer implements Server { // Paper end // Paper start diff --git a/patches/server/0018-Paper-Metrics.patch b/patches/server/0018-Paper-Metrics.patch index 46c111b03f3e..9ad0f5d70ea5 100644 --- a/patches/server/0018-Paper-Metrics.patch +++ b/patches/server/0018-Paper-Metrics.patch @@ -698,10 +698,10 @@ index 0000000000000000000000000000000000000000..6aaed8e8bf8c721fc834da5c76ac72a4 + } +} diff --git a/src/main/java/net/minecraft/server/dedicated/DedicatedServer.java b/src/main/java/net/minecraft/server/dedicated/DedicatedServer.java -index 9c950fc1de15b5039e34a9fdf893e97a8cc13237..6ba90739c20995362a5275e2259cac9e17fbcf59 100644 +index cd9e4bfdb3f335213001ced27540bb7efbc04130..3b403e9edf4e860160dd230977870f21a0e32a7a 100644 --- a/src/main/java/net/minecraft/server/dedicated/DedicatedServer.java +++ b/src/main/java/net/minecraft/server/dedicated/DedicatedServer.java -@@ -216,6 +216,7 @@ public class DedicatedServer extends MinecraftServer implements ServerInterface +@@ -215,6 +215,7 @@ public class DedicatedServer extends MinecraftServer implements ServerInterface this.paperConfigurations.initializeWorldDefaultsConfiguration(this.registryAccess()); // Paper end - initialize global and world-defaults configuration io.papermc.paper.command.PaperCommands.registerCommands(this); // Paper - setup /paper command @@ -710,7 +710,7 @@ index 9c950fc1de15b5039e34a9fdf893e97a8cc13237..6ba90739c20995362a5275e2259cac9e this.setPvpAllowed(dedicatedserverproperties.pvp); this.setFlightAllowed(dedicatedserverproperties.allowFlight); diff --git a/src/main/java/org/spigotmc/SpigotConfig.java b/src/main/java/org/spigotmc/SpigotConfig.java -index b717c9d8b6edc2cafc9281140913b7bdb6108cf0..ba621fdc82896245f6ce448e084847edc4d3fe08 100644 +index 744edd40128c910c3ad2f3657bde995612e0a1e4..d9e73e37b54e2f6e13313977c76cb4212c240992 100644 --- a/src/main/java/org/spigotmc/SpigotConfig.java +++ b/src/main/java/org/spigotmc/SpigotConfig.java @@ -83,6 +83,7 @@ public class SpigotConfig diff --git a/patches/server/0019-Paper-Plugins.patch b/patches/server/0019-Paper-Plugins.patch index 1fdf462bb383..2ca42e58a647 100644 --- a/patches/server/0019-Paper-Plugins.patch +++ b/patches/server/0019-Paper-Plugins.patch @@ -7203,10 +7203,10 @@ index f7114d5b8f2f93f62883e24da29afaf9f74ee1a6..8bf0630c0e06950cd99b7ae9898137f7 return foundFrame.orElse(null); diff --git a/src/main/java/net/minecraft/core/registries/BuiltInRegistries.java b/src/main/java/net/minecraft/core/registries/BuiltInRegistries.java -index 34b3b3251da21bce616870d312fd42fd58ba7881..cbc1658e0df4070605a6b2fbe99167b3bc001223 100644 +index f66a2154486b6d3b5873da043e51df91cd396c72..3f72e30b57fb2a4231e22a2234729408c1240af4 100644 --- a/src/main/java/net/minecraft/core/registries/BuiltInRegistries.java +++ b/src/main/java/net/minecraft/core/registries/BuiltInRegistries.java -@@ -322,7 +322,13 @@ public class BuiltInRegistries { +@@ -330,7 +330,13 @@ public class BuiltInRegistries { } public static void bootStrap() { @@ -7221,10 +7221,10 @@ index 34b3b3251da21bce616870d312fd42fd58ba7881..cbc1658e0df4070605a6b2fbe99167b3 validate(REGISTRY); } diff --git a/src/main/java/net/minecraft/server/Bootstrap.java b/src/main/java/net/minecraft/server/Bootstrap.java -index 394792291e3b89e5fd757907eecd85ccc71183e2..8f1992188f7fd9e735569e099b36a7eafed47aae 100644 +index aa38282b0f5461fc623a69d9860523a678a6bcd6..9abd1dea58ffc612114d5fd8e8944d4aa8c68236 100644 --- a/src/main/java/net/minecraft/server/Bootstrap.java +++ b/src/main/java/net/minecraft/server/Bootstrap.java -@@ -62,6 +62,7 @@ public class Bootstrap { +@@ -63,6 +63,7 @@ public class Bootstrap { Bootstrap.isBootstrapped = true; Instant instant = Instant.now(); @@ -7232,7 +7232,7 @@ index 394792291e3b89e5fd757907eecd85ccc71183e2..8f1992188f7fd9e735569e099b36a7ea if (BuiltInRegistries.REGISTRY.keySet().isEmpty()) { throw new IllegalStateException("Unable to load registries"); } else { -@@ -73,7 +74,10 @@ public class Bootstrap { +@@ -74,7 +75,10 @@ public class Bootstrap { EntitySelectorOptions.bootStrap(); DispenseItemBehavior.bootStrap(); CauldronInteraction.bootStrap(); @@ -7245,7 +7245,7 @@ index 394792291e3b89e5fd757907eecd85ccc71183e2..8f1992188f7fd9e735569e099b36a7ea Bootstrap.wrapStreams(); Bootstrap.bootstrapDuration.set(Duration.between(instant, Instant.now()).toMillis()); diff --git a/src/main/java/net/minecraft/server/Main.java b/src/main/java/net/minecraft/server/Main.java -index 5b4ac7b4fd0077e900e9f788963f1613bbc9a5d0..6afede80c10503a261d0f735c351d943597be9ff 100644 +index 9bd6056bba6ba48bada7e9cd5883b0a171b0bbc4..7399358f18dc7869fbfe414186cf18414c1eaafc 100644 --- a/src/main/java/net/minecraft/server/Main.java +++ b/src/main/java/net/minecraft/server/Main.java @@ -119,6 +119,7 @@ public class Main { @@ -7257,10 +7257,10 @@ index 5b4ac7b4fd0077e900e9f788963f1613bbc9a5d0..6afede80c10503a261d0f735c351d943 Bootstrap.validate(); Util.startTimerHackThread(); diff --git a/src/main/java/org/bukkit/craftbukkit/CraftServer.java b/src/main/java/org/bukkit/craftbukkit/CraftServer.java -index 492af80bc4a252ad3b82aef0c46c3e6625ec9146..3d98004af5c872f8a183476183d1cc89a2aac4b6 100644 +index 1b66c5173dd37433d895c0d804257141a3a8c588..e8c1ceed228a1dfb9f3c76b54a91d712c511ffb5 100644 --- a/src/main/java/org/bukkit/craftbukkit/CraftServer.java +++ b/src/main/java/org/bukkit/craftbukkit/CraftServer.java -@@ -277,7 +277,8 @@ public final class CraftServer implements Server { +@@ -280,7 +280,8 @@ public final class CraftServer implements Server { private final CraftCommandMap commandMap = new CraftCommandMap(this); private final SimpleHelpMap helpMap = new SimpleHelpMap(this); private final StandardMessenger messenger = new StandardMessenger(); @@ -7270,7 +7270,7 @@ index 492af80bc4a252ad3b82aef0c46c3e6625ec9146..3d98004af5c872f8a183476183d1cc89 private final StructureManager structureManager; protected final DedicatedServer console; protected final DedicatedPlayerList playerList; -@@ -455,24 +456,7 @@ public final class CraftServer implements Server { +@@ -458,24 +459,7 @@ public final class CraftServer implements Server { } public void loadPlugins() { @@ -7296,7 +7296,7 @@ index 492af80bc4a252ad3b82aef0c46c3e6625ec9146..3d98004af5c872f8a183476183d1cc89 } public void enablePlugins(PluginLoadOrder type) { -@@ -561,15 +545,17 @@ public final class CraftServer implements Server { +@@ -564,15 +548,17 @@ public final class CraftServer implements Server { private void enablePlugin(Plugin plugin) { try { List perms = plugin.getDescription().getPermissions(); @@ -7320,7 +7320,7 @@ index 492af80bc4a252ad3b82aef0c46c3e6625ec9146..3d98004af5c872f8a183476183d1cc89 this.pluginManager.enablePlugin(plugin); } catch (Throwable ex) { -@@ -1012,6 +998,7 @@ public final class CraftServer implements Server { +@@ -1015,6 +1001,7 @@ public final class CraftServer implements Server { "This plugin is not properly shutting down its async tasks when it is being reloaded. This may cause conflicts with the newly loaded version of the plugin" )); } @@ -7329,12 +7329,12 @@ index 492af80bc4a252ad3b82aef0c46c3e6625ec9146..3d98004af5c872f8a183476183d1cc89 this.enablePlugins(PluginLoadOrder.STARTUP); this.enablePlugins(PluginLoadOrder.POSTWORLD); diff --git a/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java b/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java -index 655ceaa92b9979fe29725f9812507731bb0bb484..a339a0aa6fd7cc7be810e93bc9eb192437519c75 100644 +index aa02e932ea19325694b1058d749b0858465530e8..0ff9c62636e8709a6df252e9aaf931c9c17bd9bf 100644 --- a/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java +++ b/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java @@ -401,6 +401,16 @@ public final class CraftMagicNumbers implements UnsafeValues { net.minecraft.world.item.ItemStack nmsItemStack = CraftItemStack.asNMSCopy(itemStack); - return nmsItemStack.getItem().getDescriptionId(nmsItemStack); + return nmsItemStack.getItem().getDescriptionId(); } + // Paper start + @Override @@ -8154,7 +8154,7 @@ index 0000000000000000000000000000000000000000..ba271c35eb2804f94cfc893bf94affb9 + } +} diff --git a/src/test/java/org/bukkit/support/DummyServerHelper.java b/src/test/java/org/bukkit/support/DummyServerHelper.java -index ba0a2ab9de34fa40dd90cecaeec4a5e54fe3e2d8..5d24b95e3eec351ec1e9444533dd5f9d376ec4c6 100644 +index bdfa164ea21cba91b30b965d65d47112111a1209..2fed099bc91a8591a2415493b333f9c18bfe35f6 100644 --- a/src/test/java/org/bukkit/support/DummyServerHelper.java +++ b/src/test/java/org/bukkit/support/DummyServerHelper.java @@ -87,7 +87,7 @@ public final class DummyServerHelper { diff --git a/patches/server/0020-Plugin-remapping.patch b/patches/server/0020-Plugin-remapping.patch index b3cdef4eca93..74c5a9fc9fa3 100644 --- a/patches/server/0020-Plugin-remapping.patch +++ b/patches/server/0020-Plugin-remapping.patch @@ -6,7 +6,7 @@ Subject: [PATCH] Plugin remapping Co-authored-by: Nassim Jahnke diff --git a/build.gradle.kts b/build.gradle.kts -index 3c9a9d2c44a3b177408ddfb3cad1e09169bf148b..ccf6e24f016991ac5987b5872037bf44d17e698f 100644 +index c73ab51caa985089ade4df0616ee8d3333632980..9086a65f5d593eea72a89d1153f6d353dfac1619 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -47,6 +47,7 @@ dependencies { @@ -1553,10 +1553,10 @@ index 0000000000000000000000000000000000000000..badff5d6ae6dd8d209c82bc7e8afe370 + } +} diff --git a/src/main/java/net/minecraft/server/MinecraftServer.java b/src/main/java/net/minecraft/server/MinecraftServer.java -index b334cfc9b98d96f9bfba1d5b14c03c336d30ff23..e880543e94189fea3ba62eb7fb05ff48bc425299 100644 +index afb67956031f4bcccee12f0ece4bb8a7e6f02cfc..3322865949fe5ddaab2dffc39260b75093f0f204 100644 --- a/src/main/java/net/minecraft/server/MinecraftServer.java +++ b/src/main/java/net/minecraft/server/MinecraftServer.java -@@ -642,6 +642,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop diff --git a/build.gradle.kts b/build.gradle.kts -index 381a4885c4f52c1e094af350fcb7e04b590f849a..6d8f4c3b290609d60dbcabe3d2c8274b017246c8 100644 +index 9086a65f5d593eea72a89d1153f6d353dfac1619..e1cfa2188dbe583a0180be2243a7a554dc5412d7 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -48,6 +48,12 @@ dependencies { @@ -608,7 +608,7 @@ index 0000000000000000000000000000000000000000..a3045afbc0cc057e99189b909367b21c + } +} diff --git a/src/main/java/io/papermc/paper/util/MappingEnvironment.java b/src/main/java/io/papermc/paper/util/MappingEnvironment.java -index 8e4229634d41a42b3d93948eebb77def7c0c72b1..fb6aa9e2575b22e3089ea7fb32e6abfa6bb07d18 100644 +index 8e4229634d41a42b3d93948eebb77def7c0c72b1..4477944f632a6b3936960ee80f9d898d3b7eed19 100644 --- a/src/main/java/io/papermc/paper/util/MappingEnvironment.java +++ b/src/main/java/io/papermc/paper/util/MappingEnvironment.java @@ -10,6 +10,8 @@ import org.checkerframework.framework.qual.DefaultQualifier; @@ -616,7 +616,7 @@ index 8e4229634d41a42b3d93948eebb77def7c0c72b1..fb6aa9e2575b22e3089ea7fb32e6abfa @DefaultQualifier(NonNull.class) public final class MappingEnvironment { + public static final boolean DISABLE_PLUGIN_REMAPPING = Boolean.getBoolean("paper.disablePluginRemapping"); -+ public static final String LEGACY_CB_VERSION = "v1_21_R1"; ++ public static final String LEGACY_CB_VERSION = "v1_21_R2"; private static final @Nullable String MAPPINGS_HASH = readMappingsHash(); private static final boolean REOBF = checkReobf(); @@ -705,7 +705,7 @@ index 2a29f60c3e82239ab7acd85242fc3390cb9129cd..91c6721201b095eb32c5fd5a1aaf2cbc final Set rerouteMethodData = new HashSet<>(); String className; diff --git a/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java b/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java -index a339a0aa6fd7cc7be810e93bc9eb192437519c75..33d3085d0d44d748fcb1fc203dfd14c9e1b4aff0 100644 +index 0ff9c62636e8709a6df252e9aaf931c9c17bd9bf..4b2377a1de608b9142a28c66389d04290f7c0330 100644 --- a/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java +++ b/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java @@ -74,6 +74,7 @@ import org.bukkit.potion.PotionType; diff --git a/patches/server/0023-Further-improve-server-tick-loop.patch b/patches/server/0023-Further-improve-server-tick-loop.patch new file mode 100644 index 000000000000..87be9b027e28 --- /dev/null +++ b/patches/server/0023-Further-improve-server-tick-loop.patch @@ -0,0 +1,233 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Aikar +Date: Tue, 1 Mar 2016 23:09:29 -0600 +Subject: [PATCH] Further improve server tick loop + +Improves how the catchup buffer is handled, allowing it to roll both ways +increasing the effeciency of the thread sleep so it only will sleep once. + +Also increases the buffer of the catchup to ensure server stays at 20 TPS unless extreme conditions + +Previous implementation did not calculate TPS correctly. +Switch to a realistic rolling average and factor in std deviation as an extra reporting variable + +diff --git a/src/main/java/net/minecraft/server/MinecraftServer.java b/src/main/java/net/minecraft/server/MinecraftServer.java +index 3322865949fe5ddaab2dffc39260b75093f0f204..4a573e8b7cd90f65c190982662e92a11f79a1d3e 100644 +--- a/src/main/java/net/minecraft/server/MinecraftServer.java ++++ b/src/main/java/net/minecraft/server/MinecraftServer.java +@@ -307,7 +307,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop processQueue = new java.util.concurrent.ConcurrentLinkedQueue(); + public int autosavePeriod; + public Commands vanillaCommandDispatcher; +@@ -316,7 +316,8 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop 0 && args[0].equals("mem") && sender.hasPermission("bukkit.command.tpsmemory")) { ++ sender.sendMessage(net.kyori.adventure.text.Component.text() ++ .append(net.kyori.adventure.text.Component.text("Current Memory Usage: ", net.kyori.adventure.text.format.NamedTextColor.GOLD)) ++ .append(net.kyori.adventure.text.Component.text(((Runtime.getRuntime().totalMemory() - Runtime.getRuntime().freeMemory()) / (1024 * 1024)) + "/" + (Runtime.getRuntime().totalMemory() / (1024 * 1024)) + " mb (Max: " + (Runtime.getRuntime().maxMemory() / (1024 * 1024)) + " mb)", net.kyori.adventure.text.format.NamedTextColor.GREEN)) ++ ); ++ if (!this.hasShownMemoryWarning) { ++ sender.sendMessage(WARN_MSG); ++ this.hasShownMemoryWarning = true; ++ } + } +- sender.sendMessage( sb.substring( 0, sb.length() - 2 ) ); +- sender.sendMessage(ChatColor.GOLD + "Current Memory Usage: " + ChatColor.GREEN + ((Runtime.getRuntime().totalMemory() - Runtime.getRuntime().freeMemory()) / (1024 * 1024)) + "/" + (Runtime.getRuntime().totalMemory() / (1024 * 1024)) + " mb (Max: " +- + (Runtime.getRuntime().maxMemory() / (1024 * 1024)) + " mb)"); ++ // Paper end + + return true; + } + +- private String format(double tps) ++ private boolean hasShownMemoryWarning; // Paper ++ private static net.kyori.adventure.text.Component format(double tps) // Paper - Made static + { +- return ( ( tps > 18.0 ) ? ChatColor.GREEN : ( tps > 16.0 ) ? ChatColor.YELLOW : ChatColor.RED ).toString() +- + ( ( tps > 20.0 ) ? "*" : "" ) + Math.min( Math.round( tps * 100.0 ) / 100.0, 20.0 ); ++ // Paper ++ net.kyori.adventure.text.format.TextColor color = ( ( tps > 18.0 ) ? net.kyori.adventure.text.format.NamedTextColor.GREEN : ( tps > 16.0 ) ? net.kyori.adventure.text.format.NamedTextColor.YELLOW : net.kyori.adventure.text.format.NamedTextColor.RED ); ++ String amount = Math.min(Math.round(tps * 100.0) / 100.0, 20.0) + (tps > 21.0 ? "*" : ""); // Paper - only print * at 21, we commonly peak to 20.02 as the tick sleep is not accurate enough, stop the noise ++ return net.kyori.adventure.text.Component.text(amount, color); ++ // Paper end + } + } diff --git a/patches/server/0023-Timings-v2.patch b/patches/server/0023-Timings-v2.patch deleted file mode 100644 index 61ba53e72285..000000000000 --- a/patches/server/0023-Timings-v2.patch +++ /dev/null @@ -1,2077 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Aikar -Date: Thu, 3 Mar 2016 04:00:11 -0600 -Subject: [PATCH] Timings v2 - - -diff --git a/src/main/java/co/aikar/timings/MinecraftTimings.java b/src/main/java/co/aikar/timings/MinecraftTimings.java -new file mode 100644 -index 0000000000000000000000000000000000000000..4bd813161a5d76a83cdbd0a9209b9ea9e60ffe1b ---- /dev/null -+++ b/src/main/java/co/aikar/timings/MinecraftTimings.java -@@ -0,0 +1,169 @@ -+package co.aikar.timings; -+ -+import com.google.common.collect.MapMaker; -+import io.papermc.paper.configuration.GlobalConfiguration; -+import net.minecraft.commands.functions.CommandFunction; -+import net.minecraft.network.protocol.Packet; -+import net.minecraft.world.level.block.Block; -+import net.minecraft.world.level.block.entity.BlockEntity; -+import org.bukkit.plugin.Plugin; -+import org.bukkit.scheduler.BukkitTask; -+ -+import org.bukkit.craftbukkit.scheduler.CraftTask; -+ -+import java.util.Map; -+ -+// TODO: Re-implement missing timers -+@Deprecated(forRemoval = true) -+public final class MinecraftTimings { -+ -+ public static final Timing serverOversleep = Timings.ofSafe("Server Oversleep"); -+ public static final Timing playerListTimer = Timings.ofSafe("Player List"); -+ public static final Timing commandFunctionsTimer = Timings.ofSafe("Command Functions"); -+ public static final Timing connectionTimer = Timings.ofSafe("Connection Handler"); -+ public static final Timing tickablesTimer = Timings.ofSafe("Tickables"); -+ public static final Timing minecraftSchedulerTimer = Timings.ofSafe("Minecraft Scheduler"); -+ public static final Timing bukkitSchedulerTimer = Timings.ofSafe("Bukkit Scheduler"); -+ public static final Timing bukkitSchedulerPendingTimer = Timings.ofSafe("Bukkit Scheduler - Pending"); -+ public static final Timing bukkitSchedulerFinishTimer = Timings.ofSafe("Bukkit Scheduler - Finishing"); -+ public static final Timing chunkIOTickTimer = Timings.ofSafe("ChunkIOTick"); -+ public static final Timing timeUpdateTimer = Timings.ofSafe("Time Update"); -+ public static final Timing serverCommandTimer = Timings.ofSafe("Server Command"); -+ public static final Timing savePlayers = Timings.ofSafe("Save Players"); -+ -+ public static final Timing tickEntityTimer = Timings.ofSafe("## tickEntity"); -+ public static final Timing tickTileEntityTimer = Timings.ofSafe("## tickTileEntity"); -+ public static final Timing packetProcessTimer = Timings.ofSafe("## Packet Processing"); -+ public static final Timing scheduledBlocksTimer = Timings.ofSafe("## Scheduled Blocks"); -+ public static final Timing structureGenerationTimer = Timings.ofSafe("Structure Generation"); -+ -+ public static final Timing processQueueTimer = Timings.ofSafe("processQueue"); -+ public static final Timing processTasksTimer = Timings.ofSafe("processTasks"); -+ -+ public static final Timing playerCommandTimer = Timings.ofSafe("playerCommand"); -+ -+ public static final Timing entityActivationCheckTimer = Timings.ofSafe("entityActivationCheck"); -+ -+ public static final Timing antiXrayUpdateTimer = Timings.ofSafe("anti-xray - update"); -+ public static final Timing antiXrayObfuscateTimer = Timings.ofSafe("anti-xray - obfuscate"); -+ -+ private static final Map, String> taskNameCache = new MapMaker().weakKeys().makeMap(); -+ -+ private MinecraftTimings() {} -+ -+ public static Timing getInternalTaskName(String taskName) { -+ return Timings.ofSafe(taskName); -+ } -+ -+ /** -+ * Gets a timer associated with a plugins tasks. -+ * @param bukkitTask -+ * @param period -+ * @return -+ */ -+ public static Timing getPluginTaskTimings(BukkitTask bukkitTask, long period) { -+ if (!bukkitTask.isSync()) { -+ return NullTimingHandler.NULL; -+ } -+ Plugin plugin; -+ -+ CraftTask craftTask = (CraftTask) bukkitTask; -+ -+ final Class taskClass = craftTask.getTaskClass(); -+ if (bukkitTask.getOwner() != null) { -+ plugin = bukkitTask.getOwner(); -+ } else { -+ plugin = TimingsManager.getPluginByClassloader(taskClass); -+ } -+ -+ final String taskname = taskNameCache.computeIfAbsent(taskClass, clazz -> { -+ try { -+ String clsName = !clazz.isMemberClass() -+ ? clazz.getName() -+ : clazz.getCanonicalName(); -+ if (clsName != null && clsName.contains("$Lambda$")) { -+ clsName = clsName.replaceAll("(Lambda\\$.*?)/.*", "$1"); -+ } -+ return clsName != null ? clsName : "UnknownTask"; -+ } catch (Throwable ex) { -+ new Exception("Error occurred detecting class name", ex).printStackTrace(); -+ return "MangledClassFile"; -+ } -+ }); -+ -+ StringBuilder name = new StringBuilder(64); -+ name.append("Task: ").append(taskname); -+ if (period > 0) { -+ name.append(" (interval:").append(period).append(")"); -+ } else { -+ name.append(" (Single)"); -+ } -+ -+ if (plugin == null) { -+ return Timings.ofSafe(null, name.toString()); -+ } -+ -+ return Timings.ofSafe(plugin, name.toString()); -+ } -+ -+ /** -+ * Get a named timer for the specified entity type to track type specific timings. -+ * @param entityType -+ * @return -+ */ -+ public static Timing getEntityTimings(String entityType, String type) { -+ return Timings.ofSafe("Minecraft", "## tickEntity - " + entityType + " - " + type, tickEntityTimer); -+ } -+ -+ /** -+ * Get a named timer for the specified tile entity type to track type specific timings. -+ * @param entity -+ * @return -+ */ -+ public static Timing getTileEntityTimings(BlockEntity entity) { -+ String entityType = entity.getClass().getName(); -+ return Timings.ofSafe("Minecraft", "## tickTileEntity - " + entityType, tickTileEntityTimer); -+ } -+ public static Timing getCancelTasksTimer() { -+ return Timings.ofSafe("Cancel Tasks"); -+ } -+ public static Timing getCancelTasksTimer(Plugin plugin) { -+ return Timings.ofSafe(plugin, "Cancel Tasks"); -+ } -+ -+ public static void stopServer() { -+ TimingsManager.stopServer(); -+ } -+ -+ public static Timing getBlockTiming(Block block) { -+ return Timings.ofSafe("## Scheduled Block: " + block.toString(), scheduledBlocksTimer); -+ } -+/* -+ public static Timing getStructureTiming(StructureGenerator structureGenerator) { -+ return Timings.ofSafe("Structure Generator - " + structureGenerator.getName(), structureGenerationTimer); -+ }*/ -+ -+ public static Timing getPacketTiming(Packet packet) { -+ return Timings.ofSafe("## Packet - " + packet.getClass().getName(), packetProcessTimer); -+ } -+ -+ public static Timing getCommandFunctionTiming(CommandFunction function) { -+ return Timings.ofSafe("Command Function - " + function.id()); -+ } -+ -+ public static void processConfig(GlobalConfiguration.Timings config) { -+ TimingsManager.url = config.url; -+ if (!TimingsManager.url.endsWith("/")) { -+ TimingsManager.url += "/"; -+ } -+ TimingsManager.privacy = config.serverNamePrivacy; -+ if (!config.hiddenConfigEntries.contains("proxies.velocity.secret")) { -+ config.hiddenConfigEntries.add("proxies.velocity.secret"); -+ } -+ TimingsManager.hiddenConfigs.addAll(config.hiddenConfigEntries); -+ co.aikar.timings.Timings.setVerboseTimingsEnabled(config.verbose); -+ co.aikar.timings.Timings.setTimingsEnabled(config.enabled); -+ co.aikar.timings.Timings.setHistoryInterval(config.historyInterval * 20); -+ co.aikar.timings.Timings.setHistoryLength(config.historyLength * 20); -+ } -+} -diff --git a/src/main/java/co/aikar/timings/TimingsExport.java b/src/main/java/co/aikar/timings/TimingsExport.java -new file mode 100644 -index 0000000000000000000000000000000000000000..49028463ba47e760281545c2f7597e3db8d6c453 ---- /dev/null -+++ b/src/main/java/co/aikar/timings/TimingsExport.java -@@ -0,0 +1,388 @@ -+/* -+ * This file is licensed under the MIT License (MIT). -+ * -+ * Copyright (c) 2014 Daniel Ennis -+ * -+ * Permission is hereby granted, free of charge, to any person obtaining a copy -+ * of this software and associated documentation files (the "Software"), to deal -+ * in the Software without restriction, including without limitation the rights -+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -+ * copies of the Software, and to permit persons to whom the Software is -+ * furnished to do so, subject to the following conditions: -+ * -+ * The above copyright notice and this permission notice shall be included in -+ * all copies or substantial portions of the Software. -+ * -+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -+ * THE SOFTWARE. -+ */ -+package co.aikar.timings; -+ -+import com.google.common.collect.Sets; -+import io.papermc.paper.adventure.PaperAdventure; -+import net.kyori.adventure.text.event.ClickEvent; -+import net.kyori.adventure.text.format.NamedTextColor; -+import net.kyori.adventure.text.serializer.plain.PlainTextComponentSerializer; -+import net.minecraft.server.MinecraftServer; -+import org.apache.commons.lang.StringUtils; -+import org.bukkit.Bukkit; -+import org.bukkit.Material; -+import org.bukkit.configuration.ConfigurationSection; -+import org.bukkit.configuration.MemorySection; -+import org.bukkit.entity.EntityType; -+import org.json.simple.JSONObject; -+import org.json.simple.JSONValue; -+import oshi.SystemInfo; -+import oshi.hardware.HardwareAbstractionLayer; -+ -+import java.io.ByteArrayOutputStream; -+import java.io.IOException; -+import java.io.InputStream; -+import java.io.OutputStream; -+import java.lang.management.ManagementFactory; -+import java.lang.management.OperatingSystemMXBean; -+import java.lang.management.RuntimeMXBean; -+import java.net.HttpURLConnection; -+import java.net.InetAddress; -+import java.net.URL; -+import java.util.List; -+import java.util.Map; -+import java.util.Set; -+import java.util.logging.Level; -+import java.util.zip.GZIPOutputStream; -+ -+import static co.aikar.timings.TimingsManager.HISTORY; -+import static co.aikar.util.JSONUtil.appendObjectData; -+import static co.aikar.util.JSONUtil.createObject; -+import static co.aikar.util.JSONUtil.pair; -+import static co.aikar.util.JSONUtil.toArray; -+import static co.aikar.util.JSONUtil.toArrayMapper; -+import static co.aikar.util.JSONUtil.toObjectMapper; -+import static net.kyori.adventure.text.Component.text; -+ -+@SuppressWarnings({"rawtypes", "SuppressionAnnotation"}) -+@Deprecated(forRemoval = true) -+public class TimingsExport extends Thread { -+ -+ private final TimingsReportListener listeners; -+ private final Map out; -+ private final TimingHistory[] history; -+ private static long lastReport = 0; -+ -+ private TimingsExport(TimingsReportListener listeners, Map out, TimingHistory[] history) { -+ super("Timings paste thread"); -+ this.listeners = listeners; -+ this.out = out; -+ this.history = history; -+ } -+ -+ /** -+ * Checks if any pending reports are being requested, and builds one if needed. -+ */ -+ public static void reportTimings() { -+ if (Timings.requestingReport.isEmpty()) { -+ return; -+ } -+ TimingsReportListener listeners = new TimingsReportListener(Timings.requestingReport); -+ listeners.addConsoleIfNeeded(); -+ -+ Timings.requestingReport.clear(); -+ long now = System.currentTimeMillis(); -+ final long lastReportDiff = now - lastReport; -+ if (lastReportDiff < 60000) { -+ listeners.sendMessage(text("Please wait at least 1 minute in between Timings reports. (" + (int)((60000 - lastReportDiff) / 1000) + " seconds)", NamedTextColor.RED)); -+ listeners.done(); -+ return; -+ } -+ final long lastStartDiff = now - TimingsManager.timingStart; -+ if (lastStartDiff < 180000) { -+ listeners.sendMessage(text("Please wait at least 3 minutes before generating a Timings report. Unlike Timings v1, v2 benefits from longer timings and is not as useful with short timings. (" + (int)((180000 - lastStartDiff) / 1000) + " seconds)", NamedTextColor.RED)); -+ listeners.done(); -+ return; -+ } -+ listeners.sendMessage(text("Preparing Timings Report...", NamedTextColor.GREEN)); -+ lastReport = now; -+ Map parent = createObject( -+ // Get some basic system details about the server -+ pair("version", Bukkit.getVersion()), -+ pair("maxplayers", Bukkit.getMaxPlayers()), -+ pair("start", TimingsManager.timingStart / 1000), -+ pair("end", System.currentTimeMillis() / 1000), -+ pair("online-mode", Bukkit.getServer().getOnlineMode()), -+ pair("sampletime", (System.currentTimeMillis() - TimingsManager.timingStart) / 1000), -+ pair("datapacks", toArrayMapper(MinecraftServer.getServer().getPackRepository().getSelectedPacks(), pack -> { -+ return PlainTextComponentSerializer.plainText().serialize(PaperAdventure.asAdventure(pack.getChatLink(true))); -+ })) -+ ); -+ if (!TimingsManager.privacy) { -+ appendObjectData(parent, -+ pair("server", Bukkit.getUnsafe().getTimingsServerName()), -+ pair("motd", Bukkit.getServer().getMotd()), -+ pair("icon", Bukkit.getServer().getServerIcon().getData()) -+ ); -+ } -+ -+ final Runtime runtime = Runtime.getRuntime(); -+ RuntimeMXBean runtimeBean = ManagementFactory.getRuntimeMXBean(); -+ -+ OperatingSystemMXBean osInfo = ManagementFactory.getOperatingSystemMXBean(); -+ -+ HardwareAbstractionLayer hardwareInfo = new SystemInfo().getHardware(); -+ -+ parent.put("system", createObject( -+ pair("timingcost", getCost()), -+ pair("loadavg", osInfo.getSystemLoadAverage()), -+ pair("name", System.getProperty("os.name")), -+ pair("version", System.getProperty("os.version")), -+ pair("jvmversion", System.getProperty("java.version")), -+ pair("jvmvendor", System.getProperty("java.vendor")), -+ pair("jvmvendorversion", System.getProperty("java.vendor.version")), -+ pair("arch", System.getProperty("os.arch")), -+ pair("maxmem", runtime.maxMemory()), -+ pair("memory", createObject( -+ pair("heap", ManagementFactory.getMemoryMXBean().getHeapMemoryUsage().toString()), -+ pair("nonheap", ManagementFactory.getMemoryMXBean().getNonHeapMemoryUsage().toString()), -+ pair("finalizing", ManagementFactory.getMemoryMXBean().getObjectPendingFinalizationCount()) -+ )), -+ pair("cpu", runtime.availableProcessors()), -+ pair("cpuname", hardwareInfo.getProcessor().getProcessorIdentifier().getName().trim()), -+ pair("runtime", runtimeBean.getUptime()), -+ pair("flags", StringUtils.join(runtimeBean.getInputArguments(), " ")), -+ pair("gc", toObjectMapper(ManagementFactory.getGarbageCollectorMXBeans(), input -> pair(input.getName(), toArray(input.getCollectionCount(), input.getCollectionTime())))) -+ ) -+ ); -+ -+ parent.put("worlds", toObjectMapper(MinecraftServer.getServer().getAllLevels(), world -> { -+ if (world.getWorld().getName().equals("worldeditregentempworld")) return null; -+ return pair(world.getWorld().getName(), createObject( -+ pair("gamerules", toObjectMapper(world.getWorld().getGameRules(), rule -> { -+ return pair(rule, world.getWorld().getGameRuleValue(rule)); -+ })), -+ pair("ticking-distance", world.getWorld().getSimulationDistance()), -+ pair("no-ticking-distance", world.getWorld().getViewDistance()), -+ pair("sending-distance", world.getWorld().getSendViewDistance()) -+ )); -+ })); -+ -+ Set tileEntityTypeSet = Sets.newHashSet(); -+ Set entityTypeSet = Sets.newHashSet(); -+ -+ int size = HISTORY.size(); -+ TimingHistory[] history = new TimingHistory[size + 1]; -+ int i = 0; -+ for (TimingHistory timingHistory : HISTORY) { -+ tileEntityTypeSet.addAll(timingHistory.tileEntityTypeSet); -+ entityTypeSet.addAll(timingHistory.entityTypeSet); -+ history[i++] = timingHistory; -+ } -+ -+ history[i] = new TimingHistory(); // Current snapshot -+ tileEntityTypeSet.addAll(history[i].tileEntityTypeSet); -+ entityTypeSet.addAll(history[i].entityTypeSet); -+ -+ -+ Map handlers = createObject(); -+ Map groupData; -+ synchronized (TimingIdentifier.GROUP_MAP) { -+ for (TimingIdentifier.TimingGroup group : TimingIdentifier.GROUP_MAP.values()) { -+ synchronized (group.handlers) { -+ for (TimingHandler id : group.handlers) { -+ -+ if (!id.isTimed() && !id.isSpecial()) { -+ continue; -+ } -+ -+ String name = id.identifier.name; -+ if (name.startsWith("##")) { -+ name = name.substring(3); -+ } -+ handlers.put(id.id, toArray( -+ group.id, -+ name -+ )); -+ } -+ } -+ } -+ -+ groupData = toObjectMapper( -+ TimingIdentifier.GROUP_MAP.values(), group -> pair(group.id, group.name)); -+ } -+ -+ parent.put("idmap", createObject( -+ pair("groups", groupData), -+ pair("handlers", handlers), -+ pair("worlds", toObjectMapper(TimingHistory.worldMap.entrySet(), input -> pair(input.getValue(), input.getKey()))), -+ pair("tileentity", -+ toObjectMapper(tileEntityTypeSet, input -> pair(input.ordinal(), input.name()))), -+ pair("entity", -+ toObjectMapper(entityTypeSet, input -> pair(input.ordinal(), input.name()))) -+ )); -+ -+ // Information about loaded plugins -+ -+ parent.put("plugins", toObjectMapper(Bukkit.getPluginManager().getPlugins(), -+ plugin -> pair(plugin.getName(), createObject( -+ pair("version", plugin.getDescription().getVersion()), -+ pair("description", String.valueOf(plugin.getDescription().getDescription()).trim()), -+ pair("website", plugin.getDescription().getWebsite()), -+ pair("authors", StringUtils.join(plugin.getDescription().getAuthors(), ", ")) -+ )))); -+ -+ -+ -+ // Information on the users Config -+ -+ parent.put("config", createObject( -+ pair("spigot", mapAsJSON(Bukkit.spigot().getSpigotConfig(), null)), -+ pair("bukkit", mapAsJSON(Bukkit.spigot().getBukkitConfig(), null)), -+ pair("paper", mapAsJSON(Bukkit.spigot().getPaperConfig(), null)) -+ )); -+ -+ new TimingsExport(listeners, parent, history).start(); -+ } -+ -+ static long getCost() { -+ // Benchmark the users System.nanotime() for cost basis -+ int passes = 100; -+ TimingHandler SAMPLER1 = Timings.ofSafe("Timings Sampler 1"); -+ TimingHandler SAMPLER2 = Timings.ofSafe("Timings Sampler 2"); -+ TimingHandler SAMPLER3 = Timings.ofSafe("Timings Sampler 3"); -+ TimingHandler SAMPLER4 = Timings.ofSafe("Timings Sampler 4"); -+ TimingHandler SAMPLER5 = Timings.ofSafe("Timings Sampler 5"); -+ TimingHandler SAMPLER6 = Timings.ofSafe("Timings Sampler 6"); -+ -+ long start = System.nanoTime(); -+ for (int i = 0; i < passes; i++) { -+ SAMPLER1.startTiming(); -+ SAMPLER2.startTiming(); -+ SAMPLER3.startTiming(); -+ SAMPLER3.stopTiming(); -+ SAMPLER4.startTiming(); -+ SAMPLER5.startTiming(); -+ SAMPLER6.startTiming(); -+ SAMPLER6.stopTiming(); -+ SAMPLER5.stopTiming(); -+ SAMPLER4.stopTiming(); -+ SAMPLER2.stopTiming(); -+ SAMPLER1.stopTiming(); -+ } -+ long timingsCost = (System.nanoTime() - start) / passes / 6; -+ SAMPLER1.reset(true); -+ SAMPLER2.reset(true); -+ SAMPLER3.reset(true); -+ SAMPLER4.reset(true); -+ SAMPLER5.reset(true); -+ SAMPLER6.reset(true); -+ return timingsCost; -+ } -+ -+ private static JSONObject mapAsJSON(ConfigurationSection config, String parentKey) { -+ -+ JSONObject object = new JSONObject(); -+ for (String key : config.getKeys(false)) { -+ String fullKey = (parentKey != null ? parentKey + "." + key : key); -+ if (fullKey.equals("database") || fullKey.equals("settings.bungeecord-addresses") || TimingsManager.hiddenConfigs.contains(fullKey) || key.startsWith("seed-") || key.equals("worldeditregentempworld")) { -+ continue; -+ } -+ final Object val = config.get(key); -+ -+ object.put(key, valAsJSON(val, fullKey)); -+ } -+ return object; -+ } -+ -+ private static Object valAsJSON(Object val, final String parentKey) { -+ if (!(val instanceof MemorySection)) { -+ if (val instanceof List) { -+ Iterable v = (Iterable) val; -+ return toArrayMapper(v, input -> valAsJSON(input, parentKey)); -+ } else { -+ return String.valueOf(val); -+ } -+ } else { -+ return mapAsJSON((ConfigurationSection) val, parentKey); -+ } -+ } -+ -+ @Override -+ public void run() { -+ out.put("data", toArrayMapper(history, TimingHistory::export)); -+ -+ -+ String response = null; -+ String timingsURL = null; -+ try { -+ HttpURLConnection con = (HttpURLConnection) new URL(TimingsManager.url + "post").openConnection(); -+ con.setDoOutput(true); -+ String hostName = "BrokenHost"; -+ try { -+ hostName = InetAddress.getLocalHost().getHostName(); -+ } catch (Exception ignored) {} -+ con.setRequestProperty("User-Agent", "Paper/" + Bukkit.getUnsafe().getTimingsServerName() + "/" + hostName); -+ con.setRequestMethod("POST"); -+ con.setInstanceFollowRedirects(false); -+ -+ OutputStream request = new GZIPOutputStream(con.getOutputStream()) {{ -+ this.def.setLevel(7); -+ }}; -+ -+ request.write(JSONValue.toJSONString(out).getBytes("UTF-8")); -+ request.close(); -+ -+ response = getResponse(con); -+ -+ if (con.getResponseCode() != 302) { -+ listeners.sendMessage(text( "Upload Error: " + con.getResponseCode() + ": " + con.getResponseMessage(), NamedTextColor.RED)); -+ listeners.sendMessage(text("Check your logs for more information", NamedTextColor.RED)); -+ if (response != null) { -+ Bukkit.getLogger().log(Level.SEVERE, response); -+ } -+ return; -+ } -+ -+ timingsURL = con.getHeaderField("Location"); -+ listeners.sendMessage(text("View Timings Report: ", NamedTextColor.GREEN).append(text(timingsURL).clickEvent(ClickEvent.clickEvent(ClickEvent.Action.OPEN_URL, timingsURL)))); -+ -+ if (response != null && !response.isEmpty()) { -+ Bukkit.getLogger().log(Level.INFO, "Timing Response: " + response); -+ } -+ } catch (IOException ex) { -+ listeners.sendMessage(text("Error uploading timings, check your logs for more information", NamedTextColor.RED)); -+ if (response != null) { -+ Bukkit.getLogger().log(Level.SEVERE, response); -+ } -+ Bukkit.getLogger().log(Level.SEVERE, "Could not paste timings", ex); -+ } finally { -+ this.listeners.done(timingsURL); -+ } -+ } -+ -+ private String getResponse(HttpURLConnection con) throws IOException { -+ InputStream is = null; -+ try { -+ is = con.getInputStream(); -+ ByteArrayOutputStream bos = new ByteArrayOutputStream(); -+ -+ byte[] b = new byte[1024]; -+ int bytesRead; -+ while ((bytesRead = is.read(b)) != -1) { -+ bos.write(b, 0, bytesRead); -+ } -+ return bos.toString(); -+ -+ } catch (IOException ex) { -+ listeners.sendMessage(text("Error uploading timings, check your logs for more information", NamedTextColor.RED)); -+ Bukkit.getLogger().log(Level.WARNING, con.getResponseMessage(), ex); -+ return null; -+ } finally { -+ if (is != null) { -+ is.close(); -+ } -+ } -+ } -+} -diff --git a/src/main/java/co/aikar/timings/WorldTimingsHandler.java b/src/main/java/co/aikar/timings/WorldTimingsHandler.java -new file mode 100644 -index 0000000000000000000000000000000000000000..2f0d9b953802dee821cfde82d22b0567cce8ee91 ---- /dev/null -+++ b/src/main/java/co/aikar/timings/WorldTimingsHandler.java -@@ -0,0 +1,120 @@ -+package co.aikar.timings; -+ -+import net.minecraft.server.level.ServerLevel; -+import net.minecraft.world.level.Level; -+import net.minecraft.world.level.storage.PrimaryLevelData; -+ -+/** -+ * Set of timers per world, to track world specific timings. -+ */ -+// TODO: Re-implement missing timers -+@Deprecated(forRemoval = true) -+public class WorldTimingsHandler { -+ public final Timing mobSpawn; -+ public final Timing doChunkUnload; -+ public final Timing doPortalForcer; -+ public final Timing scheduledBlocks; -+ public final Timing scheduledBlocksCleanup; -+ public final Timing scheduledBlocksTicking; -+ public final Timing chunkTicks; -+ public final Timing lightChunk; -+ public final Timing chunkTicksBlocks; -+ public final Timing doVillages; -+ public final Timing doChunkMap; -+ public final Timing doChunkMapUpdate; -+ public final Timing doChunkMapToUpdate; -+ public final Timing doChunkMapSortMissing; -+ public final Timing doChunkMapSortSendToPlayers; -+ public final Timing doChunkMapPlayersNeedingChunks; -+ public final Timing doChunkMapPendingSendToPlayers; -+ public final Timing doChunkMapUnloadChunks; -+ public final Timing doChunkGC; -+ public final Timing doSounds; -+ public final Timing entityRemoval; -+ public final Timing entityTick; -+ public final Timing tileEntityTick; -+ public final Timing tileEntityPending; -+ public final Timing tracker1; -+ public final Timing tracker2; -+ public final Timing doTick; -+ public final Timing tickEntities; -+ public final Timing chunks; -+ public final Timing newEntities; -+ public final Timing raids; -+ public final Timing chunkProviderTick; -+ public final Timing broadcastChunkUpdates; -+ public final Timing countNaturalMobs; -+ -+ public final Timing chunkLoad; -+ public final Timing chunkLoadPopulate; -+ public final Timing syncChunkLoad; -+ public final Timing chunkLoadLevelTimer; -+ public final Timing chunkIO; -+ public final Timing chunkPostLoad; -+ public final Timing worldSave; -+ public final Timing worldSaveChunks; -+ public final Timing worldSaveLevel; -+ public final Timing chunkSaveData; -+ -+ -+ public final Timing miscMobSpawning; -+ -+ public WorldTimingsHandler(Level server) { -+ String name = ((PrimaryLevelData) server.getLevelData()).getLevelName() + " - "; -+ -+ mobSpawn = Timings.ofSafe(name + "mobSpawn"); -+ doChunkUnload = Timings.ofSafe(name + "doChunkUnload"); -+ scheduledBlocks = Timings.ofSafe(name + "Scheduled Blocks"); -+ scheduledBlocksCleanup = Timings.ofSafe(name + "Scheduled Blocks - Cleanup"); -+ scheduledBlocksTicking = Timings.ofSafe(name + "Scheduled Blocks - Ticking"); -+ chunkTicks = Timings.ofSafe(name + "Chunk Ticks"); -+ lightChunk = Timings.ofSafe(name + "Light Chunk"); -+ chunkTicksBlocks = Timings.ofSafe(name + "Chunk Ticks - Blocks"); -+ doVillages = Timings.ofSafe(name + "doVillages"); -+ doChunkMap = Timings.ofSafe(name + "doChunkMap"); -+ doChunkMapUpdate = Timings.ofSafe(name + "doChunkMap - Update"); -+ doChunkMapToUpdate = Timings.ofSafe(name + "doChunkMap - To Update"); -+ doChunkMapSortMissing = Timings.ofSafe(name + "doChunkMap - Sort Missing"); -+ doChunkMapSortSendToPlayers = Timings.ofSafe(name + "doChunkMap - Sort Send To Players"); -+ doChunkMapPlayersNeedingChunks = Timings.ofSafe(name + "doChunkMap - Players Needing Chunks"); -+ doChunkMapPendingSendToPlayers = Timings.ofSafe(name + "doChunkMap - Pending Send To Players"); -+ doChunkMapUnloadChunks = Timings.ofSafe(name + "doChunkMap - Unload Chunks"); -+ doSounds = Timings.ofSafe(name + "doSounds"); -+ doChunkGC = Timings.ofSafe(name + "doChunkGC"); -+ doPortalForcer = Timings.ofSafe(name + "doPortalForcer"); -+ entityTick = Timings.ofSafe(name + "entityTick"); -+ entityRemoval = Timings.ofSafe(name + "entityRemoval"); -+ tileEntityTick = Timings.ofSafe(name + "tileEntityTick"); -+ tileEntityPending = Timings.ofSafe(name + "tileEntityPending"); -+ -+ chunkLoad = Timings.ofSafe(name + "Chunk Load"); -+ chunkLoadPopulate = Timings.ofSafe(name + "Chunk Load - Populate"); -+ syncChunkLoad = Timings.ofSafe(name + "Sync Chunk Load"); -+ chunkLoadLevelTimer = Timings.ofSafe(name + "Chunk Load - Load Level"); -+ chunkIO = Timings.ofSafe(name + "Chunk Load - DiskIO"); -+ chunkPostLoad = Timings.ofSafe(name + "Chunk Load - Post Load"); -+ worldSave = Timings.ofSafe(name + "World Save"); -+ worldSaveLevel = Timings.ofSafe(name + "World Save - Level"); -+ worldSaveChunks = Timings.ofSafe(name + "World Save - Chunks"); -+ chunkSaveData = Timings.ofSafe(name + "Chunk Save - Data"); -+ -+ tracker1 = Timings.ofSafe(name + "tracker stage 1"); -+ tracker2 = Timings.ofSafe(name + "tracker stage 2"); -+ doTick = Timings.ofSafe(name + "doTick"); -+ tickEntities = Timings.ofSafe(name + "tickEntities"); -+ -+ chunks = Timings.ofSafe(name + "Chunks"); -+ newEntities = Timings.ofSafe(name + "New entity registration"); -+ raids = Timings.ofSafe(name + "Raids"); -+ chunkProviderTick = Timings.ofSafe(name + "Chunk provider tick"); -+ broadcastChunkUpdates = Timings.ofSafe(name + "Broadcast chunk updates"); -+ countNaturalMobs = Timings.ofSafe(name + "Count natural mobs"); -+ -+ -+ miscMobSpawning = Timings.ofSafe(name + "Mob spawning - Misc"); -+ } -+ -+ public static Timing getTickList(ServerLevel worldserver, String timingsType) { -+ return Timings.ofSafe(((PrimaryLevelData) worldserver.getLevelData()).getLevelName() + " - Scheduled " + timingsType); -+ } -+} -diff --git a/src/main/java/net/minecraft/network/protocol/PacketUtils.java b/src/main/java/net/minecraft/network/protocol/PacketUtils.java -index f7197f1347251a37dd0f6d9ffa2f09bc3a4e1233..d0d36a57ec4896bcb74970f8fb24d8f3e17db133 100644 ---- a/src/main/java/net/minecraft/network/protocol/PacketUtils.java -+++ b/src/main/java/net/minecraft/network/protocol/PacketUtils.java -@@ -31,7 +31,8 @@ public class PacketUtils { - engine.executeIfPossible(() -> { - if (listener instanceof ServerCommonPacketListenerImpl serverCommonPacketListener && serverCommonPacketListener.processedDisconnect) return; // CraftBukkit - Don't handle sync packets for kicked players - if (listener.shouldHandleMessage(packet)) { -- try { -+ co.aikar.timings.Timing timing = co.aikar.timings.MinecraftTimings.getPacketTiming(packet); // Paper - timings -+ try (co.aikar.timings.Timing ignored = timing.startTiming()) { // Paper - timings - packet.handle(listener); - } catch (Exception exception) { - if (exception instanceof ReportedException) { -diff --git a/src/main/java/net/minecraft/server/MinecraftServer.java b/src/main/java/net/minecraft/server/MinecraftServer.java -index e880543e94189fea3ba62eb7fb05ff48bc425299..49de4625c57689a3624ed421c0b03512507c97c3 100644 ---- a/src/main/java/net/minecraft/server/MinecraftServer.java -+++ b/src/main/java/net/minecraft/server/MinecraftServer.java -@@ -196,7 +196,7 @@ import org.bukkit.craftbukkit.Main; - import org.bukkit.event.server.ServerLoadEvent; - // CraftBukkit end - --import org.bukkit.craftbukkit.SpigotTimings; // Spigot -+import co.aikar.timings.MinecraftTimings; // Paper - - public abstract class MinecraftServer extends ReentrantBlockableEventLoop implements ServerInfo, ChunkIOErrorReporter, CommandSource, AutoCloseable { - -@@ -911,6 +911,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop { -- return !this.haveTime(); -+ return !this.canSleepForTickNoOversleep(); // Paper - move oversleep into full server tick - }); - } - -@@ -1321,9 +1334,17 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop { -+ return !this.canOversleep(); -+ }); -+ isOversleep = false;MinecraftTimings.serverOversleep.stopTiming(); -+ // Paper end -+ - ++this.tickCount; - this.tickRateManager.tick(); - this.tickChildren(shouldKeepTicking); -@@ -1337,14 +1358,17 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop 0 && this.ticksUntilAutosave <= 0) { - this.ticksUntilAutosave = this.autosavePeriod; - // CraftBukkit end -- SpigotTimings.worldSaveTimer.startTiming(); // Spigot - MinecraftServer.LOGGER.debug("Autosave started"); - this.profiler.push("save"); - this.saveEverything(true, false, false); - this.profiler.pop(); - MinecraftServer.LOGGER.debug("Autosave finished"); -- SpigotTimings.worldSaveTimer.stopTiming(); // Spigot - } -+ // Paper start - move executeAll() into full server tick timing -+ try (co.aikar.timings.Timing ignored = MinecraftTimings.processTasksTimer.startTiming()) { -+ this.runAllTasks(); -+ } -+ // Paper end - this.profiler.push("tallying"); - long j = Util.getNanos() - i; - int k = this.tickCount % 100; -@@ -1356,8 +1380,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop { - entityplayer.connection.suspendFlushing(); - }); -- SpigotTimings.schedulerTimer.startTiming(); // Spigot -+ MinecraftTimings.bukkitSchedulerTimer.startTiming(); // Spigot // Paper - this.server.getScheduler().mainThreadHeartbeat(this.tickCount); // CraftBukkit -- SpigotTimings.schedulerTimer.stopTiming(); // Spigot -+ MinecraftTimings.bukkitSchedulerTimer.stopTiming(); // Spigot // Paper - io.papermc.paper.adventure.providers.ClickCallbackProviderImpl.CALLBACK_MANAGER.handleQueue(this.tickCount); // Paper - this.profiler.push("commandFunctions"); -- SpigotTimings.commandFunctionsTimer.startTiming(); // Spigot -+ MinecraftTimings.commandFunctionsTimer.startTiming(); // Spigot // Paper - this.getFunctions().tick(); -- SpigotTimings.commandFunctionsTimer.stopTiming(); // Spigot -+ MinecraftTimings.commandFunctionsTimer.stopTiming(); // Spigot // Paper - this.profiler.popPush("levels"); - Iterator iterator = this.getAllLevels().iterator(); - - // CraftBukkit start - // Run tasks that are waiting on processing -- SpigotTimings.processQueueTimer.startTiming(); // Spigot -+ MinecraftTimings.processQueueTimer.startTiming(); // Spigot - while (!this.processQueue.isEmpty()) { - this.processQueue.remove().run(); - } -- SpigotTimings.processQueueTimer.stopTiming(); // Spigot -+ MinecraftTimings.processQueueTimer.stopTiming(); // Spigot - -- SpigotTimings.timeUpdateTimer.startTiming(); // Spigot -+ MinecraftTimings.timeUpdateTimer.startTiming(); // Spigot // Paper - // Send time updates to everyone, it will get the right time from the world the player is in. - if (this.tickCount % 20 == 0) { - for (int i = 0; i < this.getPlayerList().players.size(); ++i) { -@@ -1455,7 +1478,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop command = new java.util.concurrent.atomic.AtomicReference<>(s); // Paper - this.executeBlocking(() -> { - CommandSourceStack wrapper = rconConsoleSource.createCommandSourceStack(); - RemoteServerCommandEvent event = new RemoteServerCommandEvent(rconConsoleSource.getBukkitSender(wrapper), s); -@@ -720,9 +723,39 @@ public class DedicatedServer extends MinecraftServer implements ServerInterface - if (event.isCancelled()) { - return; - } -+ // Paper start -+ command.set(event.getCommand()); -+ if (event.getCommand().toLowerCase(java.util.Locale.ROOT).startsWith("timings") && event.getCommand().toLowerCase(java.util.Locale.ROOT).matches("timings (report|paste|get|merged|seperate)")) { -+ org.bukkit.command.BufferedCommandSender sender = new org.bukkit.command.BufferedCommandSender(); -+ Waitable waitable = new Waitable<>() { -+ @Override -+ protected String evaluate() { -+ return sender.getBuffer(); -+ } -+ }; -+ waitableArray[0] = waitable; -+ co.aikar.timings.Timings.generateReport(new co.aikar.timings.TimingsReportListener(sender, waitable)); -+ } else { -+ // Paper end - ConsoleInput serverCommand = new ConsoleInput(event.getCommand(), wrapper); - this.server.dispatchServerCommand(event.getSender(), serverCommand); -+ } // Paper - }); -+ // Paper start -+ if (waitableArray[0] != null) { -+ //noinspection unchecked -+ Waitable waitable = waitableArray[0]; -+ try { -+ return waitable.get(); -+ } catch (java.util.concurrent.ExecutionException e) { -+ throw new RuntimeException("Exception processing rcon command " + command.get(), e.getCause()); -+ } catch (InterruptedException e) { -+ Thread.currentThread().interrupt(); // Maintain interrupted state -+ throw new RuntimeException("Interrupted processing rcon command " + command.get(), e); -+ } -+ -+ } -+ // Paper end - return rconConsoleSource.getCommandResponse(); - // CraftBukkit end - } -diff --git a/src/main/java/net/minecraft/server/level/ChunkMap.java b/src/main/java/net/minecraft/server/level/ChunkMap.java -index 449608e60f3900778247101581ff598f1637499b..bb5115b3593c314f5edfb5947a3c35a489bf71af 100644 ---- a/src/main/java/net/minecraft/server/level/ChunkMap.java -+++ b/src/main/java/net/minecraft/server/level/ChunkMap.java -@@ -1,8 +1,10 @@ - package net.minecraft.server.level; - -+import co.aikar.timings.Timing; // Paper - import com.google.common.collect.ImmutableList; - import com.google.common.collect.ImmutableList.Builder; - import com.google.common.collect.Iterables; -+import com.google.common.collect.ComparisonChain; // Paper - import com.google.common.collect.Lists; - import com.google.common.collect.Queues; - import com.google.common.collect.Sets; -@@ -1296,6 +1298,7 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider - List list = Lists.newArrayList(); - List list1 = this.level.players(); - ObjectIterator objectiterator = this.entityMap.values().iterator(); -+ level.timings.tracker1.startTiming(); // Paper - - ChunkMap.TrackedEntity playerchunkmap_entitytracker; - -@@ -1320,14 +1323,17 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider - playerchunkmap_entitytracker.serverEntity.sendChanges(); - } - } -+ level.timings.tracker1.stopTiming(); // Paper - - if (!list.isEmpty()) { - objectiterator = this.entityMap.values().iterator(); - -+ level.timings.tracker2.startTiming(); // Paper - while (objectiterator.hasNext()) { - playerchunkmap_entitytracker = (ChunkMap.TrackedEntity) objectiterator.next(); - playerchunkmap_entitytracker.updatePlayers(list); - } -+ level.timings.tracker2.stopTiming(); // Paper - } - - } -diff --git a/src/main/java/net/minecraft/server/level/ServerChunkCache.java b/src/main/java/net/minecraft/server/level/ServerChunkCache.java -index cf94dd9ddcc1eabcf3fd336e70720f4ed3e52175..e0c8b89767087cba34fc3c3809db4c386dacb193 100644 ---- a/src/main/java/net/minecraft/server/level/ServerChunkCache.java -+++ b/src/main/java/net/minecraft/server/level/ServerChunkCache.java -@@ -195,13 +195,15 @@ public class ServerChunkCache extends ChunkSource { - } - - gameprofilerfiller.incrementCounter("getChunkCacheMiss"); -- this.level.timings.syncChunkLoadTimer.startTiming(); // Spigot - CompletableFuture> completablefuture = this.getChunkFutureMainThread(x, z, leastStatus, create); - ServerChunkCache.MainThreadExecutor chunkproviderserver_b = this.mainThreadProcessor; - - Objects.requireNonNull(completablefuture); -+ if (!completablefuture.isDone()) { // Paper -+ this.level.timings.syncChunkLoad.startTiming(); // Paper - chunkproviderserver_b.managedBlock(completablefuture::isDone); -- this.level.timings.syncChunkLoadTimer.stopTiming(); // Spigot -+ this.level.timings.syncChunkLoad.stopTiming(); // Paper -+ } // Paper - ChunkResult chunkresult = (ChunkResult) completablefuture.join(); - ChunkAccess ichunkaccess1 = (ChunkAccess) chunkresult.orElse(null); // CraftBukkit - decompile error - -@@ -366,7 +368,9 @@ public class ServerChunkCache extends ChunkSource { - - public void save(boolean flush) { - this.runDistanceManagerUpdates(); -+ try (co.aikar.timings.Timing timed = level.timings.chunkSaveData.startTiming()) { // Paper - Timings - this.chunkMap.saveAllChunks(flush); -+ } // Paper - Timings - } - - @Override -@@ -408,10 +412,10 @@ public class ServerChunkCache extends ChunkSource { - this.level.timings.doChunkMap.stopTiming(); // Spigot - this.level.getProfiler().popPush("chunks"); - if (tickChunks) { -+ this.level.timings.chunks.startTiming(); // Paper - timings - this.tickChunks(); -- this.level.timings.tracker.startTiming(); // Spigot -+ this.level.timings.chunks.stopTiming(); // Paper - timings - this.chunkMap.tick(); -- this.level.timings.tracker.stopTiming(); // Spigot - } - - this.level.timings.doChunkUnload.startTiming(); // Spigot -@@ -434,6 +438,7 @@ public class ServerChunkCache extends ChunkSource { - gameprofilerfiller.push("filteringLoadedChunks"); - List list = Lists.newArrayListWithCapacity(this.chunkMap.size()); - Iterator iterator = this.chunkMap.getChunks().iterator(); -+ if (this.level.getServer().tickRateManager().runsNormally()) this.level.timings.chunkTicks.startTiming(); // Paper - - while (iterator.hasNext()) { - ChunkHolder playerchunk = (ChunkHolder) iterator.next(); -@@ -446,8 +451,10 @@ public class ServerChunkCache extends ChunkSource { - - if (this.level.tickRateManager().runsNormally()) { - gameprofilerfiller.popPush("naturalSpawnCount"); -+ this.level.timings.countNaturalMobs.startTiming(); // Paper - timings - int k = this.distanceManager.getNaturalSpawnChunkCount(); - NaturalSpawner.SpawnState spawnercreature_d = NaturalSpawner.createState(k, this.level.getAllEntities(), this::getFullChunk, new LocalMobCapCalculator(this.chunkMap)); -+ this.level.timings.countNaturalMobs.stopTiming(); // Paper - timings - - this.lastSpawnState = spawnercreature_d; - gameprofilerfiller.popPush("spawnAndTick"); -@@ -470,22 +477,25 @@ public class ServerChunkCache extends ChunkSource { - } - - if (this.level.shouldTickBlocksAt(chunkcoordintpair.toLong())) { -- this.level.timings.doTickTiles.startTiming(); // Spigot - this.level.tickChunk(chunk1, l); -- this.level.timings.doTickTiles.stopTiming(); // Spigot - } - } - } -+ this.level.timings.chunkTicks.stopTiming(); // Paper - - gameprofilerfiller.popPush("customSpawners"); - if (flag) { -+ try (co.aikar.timings.Timing ignored = this.level.timings.miscMobSpawning.startTiming()) { // Paper - timings - this.level.tickCustomSpawners(this.spawnEnemies, this.spawnFriendlies); -+ } // Paper - timings - } - } - - gameprofilerfiller.popPush("broadcast"); - list.forEach((chunkproviderserver_a1) -> { -+ this.level.timings.broadcastChunkUpdates.startTiming(); // Paper - timing - chunkproviderserver_a1.holder.broadcastChanges(chunkproviderserver_a1.chunk); -+ this.level.timings.broadcastChunkUpdates.stopTiming(); // Paper - timing - }); - gameprofilerfiller.pop(); - gameprofilerfiller.pop(); -diff --git a/src/main/java/net/minecraft/server/level/ServerLevel.java b/src/main/java/net/minecraft/server/level/ServerLevel.java -index 4c39d9e0466240b5cd459ee649a22fe3a72bf9f0..eb98bb1bd76869fd76b34885223c8e57a04e0c51 100644 ---- a/src/main/java/net/minecraft/server/level/ServerLevel.java -+++ b/src/main/java/net/minecraft/server/level/ServerLevel.java -@@ -1,6 +1,8 @@ - package net.minecraft.server.level; - - import com.google.common.annotations.VisibleForTesting; -+import co.aikar.timings.TimingHistory; // Paper -+import co.aikar.timings.Timings; // Paper - import com.google.common.collect.Lists; - import com.mojang.datafixers.DataFixer; - import com.mojang.datafixers.util.Pair; -@@ -173,7 +175,6 @@ import net.minecraft.world.ticks.LevelTicks; - import org.slf4j.Logger; - import org.bukkit.Bukkit; - import org.bukkit.WeatherType; --import org.bukkit.craftbukkit.SpigotTimings; // Spigot - import org.bukkit.craftbukkit.event.CraftEventFactory; - import org.bukkit.craftbukkit.generator.CustomWorldChunkManager; - import org.bukkit.craftbukkit.util.WorldUUID; -@@ -478,7 +479,7 @@ public class ServerLevel extends Level implements WorldGenLevel { - } - - gameprofilerfiller.popPush("tickPending"); -- this.timings.doTickPending.startTiming(); // Spigot -+ this.timings.scheduledBlocks.startTiming(); // Paper - if (!this.isDebug() && flag) { - j = this.getGameTime(); - gameprofilerfiller.push("blockTicks"); -@@ -487,15 +488,19 @@ public class ServerLevel extends Level implements WorldGenLevel { - this.fluidTicks.tick(j, 65536, this::tickFluid); - gameprofilerfiller.pop(); - } -- this.timings.doTickPending.stopTiming(); // Spigot -+ this.timings.scheduledBlocks.stopTiming(); // Paper - - gameprofilerfiller.popPush("raid"); - if (flag) { -+ this.timings.raids.startTiming(); // Paper - timings - this.raids.tick(); -+ this.timings.raids.stopTiming(); // Paper - timings - } - - gameprofilerfiller.popPush("chunkSource"); -+ this.timings.chunkProviderTick.startTiming(); // Paper - timings - this.getChunkSource().tick(shouldKeepTicking, true); -+ this.timings.chunkProviderTick.stopTiming(); // Paper - timings - gameprofilerfiller.popPush("blockEvents"); - if (flag) { - this.timings.doSounds.startTiming(); // Spigot -@@ -648,6 +653,7 @@ public class ServerLevel extends Level implements WorldGenLevel { - } - - gameprofilerfiller.popPush("tickBlocks"); -+ timings.chunkTicksBlocks.startTiming(); // Paper - if (randomTickSpeed > 0) { - LevelChunkSection[] achunksection = chunk.getSections(); - -@@ -680,6 +686,7 @@ public class ServerLevel extends Level implements WorldGenLevel { - } - } - -+ timings.chunkTicksBlocks.stopTiming(); // Paper - gameprofilerfiller.pop(); - } - -@@ -956,14 +963,22 @@ public class ServerLevel extends Level implements WorldGenLevel { - } - - public void tickNonPassenger(Entity entity) { -+ ++TimingHistory.entityTicks; // Paper - timings - // Spigot start -+ co.aikar.timings.Timing timer; // Paper - if (!org.spigotmc.ActivationRange.checkIfActive(entity)) { - entity.tickCount++; -+ timer = entity.getType().inactiveTickTimer.startTiming(); try { // Paper - timings - entity.inactiveTick(); -+ } finally { timer.stopTiming(); } // Paper - return; - } - // Spigot end -- entity.tickTimer.startTiming(); // Spigot -+ // Paper start- timings -+ TimingHistory.activatedEntityTicks++; -+ timer = entity.getVehicle() != null ? entity.getType().passengerTickTimer.startTiming() : entity.getType().tickTimer.startTiming(); -+ try { -+ // Paper end - timings - entity.setOldPosAndRot(); - ProfilerFiller gameprofilerfiller = this.getProfiler(); - -@@ -982,7 +997,7 @@ public class ServerLevel extends Level implements WorldGenLevel { - - this.tickPassenger(entity, entity1); - } -- entity.tickTimer.stopTiming(); // Spigot -+ } finally { timer.stopTiming(); } // Paper - timings - - } - -@@ -1024,6 +1039,7 @@ public class ServerLevel extends Level implements WorldGenLevel { - - if (!savingDisabled) { - org.bukkit.Bukkit.getPluginManager().callEvent(new org.bukkit.event.world.WorldSaveEvent(this.getWorld())); // CraftBukkit -+ try (co.aikar.timings.Timing ignored = timings.worldSave.startTiming()) { // Paper - if (progressListener != null) { - progressListener.progressStartNoAbort(Component.translatable("menu.savingLevel")); - } -@@ -1033,7 +1049,10 @@ public class ServerLevel extends Level implements WorldGenLevel { - progressListener.progressStage(Component.translatable("menu.savingChunks")); - } - -+ timings.worldSaveChunks.startTiming(); // Paper - chunkproviderserver.save(flush); -+ timings.worldSaveChunks.stopTiming(); // Paper -+ }// Paper - if (flush) { - this.entityManager.saveAll(); - } else { -diff --git a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java -index 4acee8121ff62413dbbf2294d17da3bd2f974d5a..3a67b2b6a6d3204b2a7bbe8adbf2b0ecf7898551 100644 ---- a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java -+++ b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java -@@ -330,7 +330,6 @@ public class ServerGamePacketListenerImpl extends ServerCommonPacketListenerImpl - - @Override - public void tick() { -- org.bukkit.craftbukkit.SpigotTimings.playerConnectionTimer.startTiming(); // Spigot - if (this.ackBlockChangesUpTo > -1) { - this.send(new ClientboundBlockChangedAckPacket(this.ackBlockChangesUpTo)); - this.ackBlockChangesUpTo = -1; -@@ -397,7 +396,6 @@ public class ServerGamePacketListenerImpl extends ServerCommonPacketListenerImpl - this.player.resetLastActionTime(); // CraftBukkit - SPIGOT-854 - this.disconnect((Component) Component.translatable("multiplayer.disconnect.idling")); - } -- org.bukkit.craftbukkit.SpigotTimings.playerConnectionTimer.stopTiming(); // Spigot - - } - -@@ -2103,7 +2101,7 @@ public class ServerGamePacketListenerImpl extends ServerCommonPacketListenerImpl - } - - private void handleCommand(String s) { -- org.bukkit.craftbukkit.SpigotTimings.playerCommandTimer.startTiming(); // Spigot -+ co.aikar.timings.MinecraftTimings.playerCommandTimer.startTiming(); // Paper - if ( org.spigotmc.SpigotConfig.logCommands ) // Spigot - this.LOGGER.info(this.player.getScoreboardName() + " issued server command: " + s); - -@@ -2113,7 +2111,7 @@ public class ServerGamePacketListenerImpl extends ServerCommonPacketListenerImpl - this.cserver.getPluginManager().callEvent(event); - - if (event.isCancelled()) { -- org.bukkit.craftbukkit.SpigotTimings.playerCommandTimer.stopTiming(); // Spigot -+ co.aikar.timings.MinecraftTimings.playerCommandTimer.stopTiming(); // Paper - return; - } - -@@ -2126,7 +2124,7 @@ public class ServerGamePacketListenerImpl extends ServerCommonPacketListenerImpl - java.util.logging.Logger.getLogger(ServerGamePacketListenerImpl.class.getName()).log(java.util.logging.Level.SEVERE, null, ex); - return; - } finally { -- org.bukkit.craftbukkit.SpigotTimings.playerCommandTimer.stopTiming(); // Spigot -+ co.aikar.timings.MinecraftTimings.playerCommandTimer.stopTiming(); // Paper - } - } - // CraftBukkit end -diff --git a/src/main/java/net/minecraft/server/players/PlayerList.java b/src/main/java/net/minecraft/server/players/PlayerList.java -index 9dcfcea63f57f45a5584bb80c34fe445d65849e8..765c412cd0c5cd410c224b4bc55dbf431fd6617b 100644 ---- a/src/main/java/net/minecraft/server/players/PlayerList.java -+++ b/src/main/java/net/minecraft/server/players/PlayerList.java -@@ -1,5 +1,6 @@ - package net.minecraft.server.players; - -+import co.aikar.timings.MinecraftTimings; - import com.google.common.collect.Lists; - import com.google.common.collect.Maps; - import com.google.common.collect.Sets; -@@ -1007,10 +1008,11 @@ public abstract class PlayerList { - } - - public void saveAll() { -+ MinecraftTimings.savePlayers.startTiming(); // Paper - for (int i = 0; i < this.players.size(); ++i) { - this.save((ServerPlayer) this.players.get(i)); - } -- -+ MinecraftTimings.savePlayers.stopTiming(); // Paper - } - - public UserWhiteList getWhiteList() { -diff --git a/src/main/java/net/minecraft/world/entity/Entity.java b/src/main/java/net/minecraft/world/entity/Entity.java -index 06cbe7a7ea131a8bead857cbfbd27810a9093320..0a3ed94165430774c7037e78fd7bffc205c6f72f 100644 ---- a/src/main/java/net/minecraft/world/entity/Entity.java -+++ b/src/main/java/net/minecraft/world/entity/Entity.java -@@ -140,7 +140,6 @@ import org.bukkit.command.CommandSender; - import org.bukkit.entity.Hanging; - import org.bukkit.entity.LivingEntity; - import org.bukkit.entity.Vehicle; --import org.spigotmc.CustomTimingsHandler; // Spigot - import org.bukkit.event.entity.EntityCombustByEntityEvent; - import org.bukkit.event.hanging.HangingBreakByEntityEvent; - import org.bukkit.event.vehicle.VehicleBlockCollisionEvent; -@@ -323,7 +322,6 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess - // Marks an entity, that it was removed by a plugin via Entity#remove - // Main use case currently is for SPIGOT-7487, preventing dropping of leash when leash is removed - public boolean pluginRemoved = false; -- public CustomTimingsHandler tickTimer = org.bukkit.craftbukkit.SpigotTimings.getEntityTimings(this); // Spigot - // Spigot start - public final org.spigotmc.ActivationRange.ActivationType activationType = org.spigotmc.ActivationRange.initializeEntityActivationType(this); - public final boolean defaultActivationState; -@@ -840,7 +838,6 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess - } - - public void move(MoverType movementType, Vec3 movement) { -- org.bukkit.craftbukkit.SpigotTimings.entityMoveTimer.startTiming(); // Spigot - if (this.noPhysics) { - this.setPos(this.getX() + movement.x, this.getY() + movement.y, this.getZ() + movement.z); - } else { -@@ -1001,7 +998,6 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess - this.level().getProfiler().pop(); - } - } -- org.bukkit.craftbukkit.SpigotTimings.entityMoveTimer.stopTiming(); // Spigot - } - - private boolean isStateClimbable(BlockState state) { -diff --git a/src/main/java/net/minecraft/world/entity/EntityType.java b/src/main/java/net/minecraft/world/entity/EntityType.java -index e465aaa4fd29b4966ea8d88316c6d8f217da2e73..474f020371bb9e5fd2c5b22e44d7902977c4fc18 100644 ---- a/src/main/java/net/minecraft/world/entity/EntityType.java -+++ b/src/main/java/net/minecraft/world/entity/EntityType.java -@@ -339,6 +339,15 @@ public class EntityType implements FeatureElement, EntityTypeT - } - - public EntityType(EntityType.EntityFactory factory, MobCategory spawnGroup, boolean saveable, boolean summonable, boolean fireImmune, boolean spawnableFarFromPlayer, ImmutableSet canSpawnInside, EntityDimensions dimensions, float spawnBoxScale, int maxTrackDistance, int trackTickInterval, FeatureFlagSet requiredFeatures) { -+ // Paper start -+ this(factory, spawnGroup, saveable, summonable, fireImmune, spawnableFarFromPlayer, canSpawnInside, dimensions, spawnBoxScale, maxTrackDistance, trackTickInterval, requiredFeatures, "custom"); -+ } -+ public EntityType(EntityType.EntityFactory factory, MobCategory spawnGroup, boolean saveable, boolean summonable, boolean fireImmune, boolean spawnableFarFromPlayer, ImmutableSet canSpawnInside, EntityDimensions dimensions, float spawnBoxScale, int maxTrackDistance, int trackTickInterval, FeatureFlagSet requiredFeatures, String id) { -+ this.tickTimer = co.aikar.timings.MinecraftTimings.getEntityTimings(id, "tick"); -+ this.inactiveTickTimer = co.aikar.timings.MinecraftTimings.getEntityTimings(id, "inactiveTick"); -+ this.passengerTickTimer = co.aikar.timings.MinecraftTimings.getEntityTimings(id, "passengerTick"); -+ this.passengerInactiveTickTimer = co.aikar.timings.MinecraftTimings.getEntityTimings(id, "passengerInactiveTick"); -+ // Paper end - this.builtInRegistryHolder = BuiltInRegistries.ENTITY_TYPE.createIntrusiveHolder(this); - this.factory = factory; - this.category = spawnGroup; -@@ -654,6 +663,12 @@ public class EntityType implements FeatureElement, EntityTypeT - return this.updateInterval; - } - -+ // Paper start - timings -+ public final co.aikar.timings.Timing tickTimer; -+ public final co.aikar.timings.Timing inactiveTickTimer; -+ public final co.aikar.timings.Timing passengerTickTimer; -+ public final co.aikar.timings.Timing passengerInactiveTickTimer; -+ // Paper end - public boolean trackDeltas() { - return this != EntityType.PLAYER && this != EntityType.LLAMA_SPIT && this != EntityType.WITHER && this != EntityType.BAT && this != EntityType.ITEM_FRAME && this != EntityType.GLOW_ITEM_FRAME && this != EntityType.LEASH_KNOT && this != EntityType.PAINTING && this != EntityType.END_CRYSTAL && this != EntityType.EVOKER_FANGS; - } -@@ -823,7 +838,7 @@ public class EntityType implements FeatureElement, EntityTypeT - Util.fetchChoiceType(References.ENTITY_TREE, id); - } - -- return new EntityType<>(this.factory, this.category, this.serialize, this.summon, this.fireImmune, this.canSpawnFarFromPlayer, this.immuneTo, this.dimensions.withAttachments(this.attachments), this.spawnDimensionsScale, this.clientTrackingRange, this.updateInterval, this.requiredFeatures); -+ return new EntityType<>(this.factory, this.category, this.serialize, this.summon, this.fireImmune, this.canSpawnFarFromPlayer, this.immuneTo, this.dimensions.withAttachments(this.attachments), this.spawnDimensionsScale, this.clientTrackingRange, this.updateInterval, this.requiredFeatures, id); // Paper - add id - } - } - -diff --git a/src/main/java/net/minecraft/world/entity/LivingEntity.java b/src/main/java/net/minecraft/world/entity/LivingEntity.java -index 26064174397dc95f9b117d901e22c55abebf3c39..d1e042ec0c1a818d713b31c3d81b48327c3578d5 100644 ---- a/src/main/java/net/minecraft/world/entity/LivingEntity.java -+++ b/src/main/java/net/minecraft/world/entity/LivingEntity.java -@@ -156,7 +156,7 @@ import org.bukkit.event.entity.EntityTeleportEvent; - import org.bukkit.event.player.PlayerItemConsumeEvent; - // CraftBukkit end - --import org.bukkit.craftbukkit.SpigotTimings; // Spigot -+import co.aikar.timings.MinecraftTimings; // Paper - - public abstract class LivingEntity extends Entity implements Attackable { - -@@ -2977,7 +2977,6 @@ public abstract class LivingEntity extends Entity implements Attackable { - - @Override - public void tick() { -- SpigotTimings.timerEntityBaseTick.startTiming(); // Spigot - super.tick(); - this.updatingUsingItem(); - this.updateSwimAmount(); -@@ -3019,9 +3018,7 @@ public abstract class LivingEntity extends Entity implements Attackable { - } - - if (!this.isRemoved()) { -- SpigotTimings.timerEntityBaseTick.stopTiming(); // Spigot - this.aiStep(); -- SpigotTimings.timerEntityTickRest.startTiming(); // Spigot - } - - double d0 = this.getX() - this.xo; -@@ -3112,7 +3109,6 @@ public abstract class LivingEntity extends Entity implements Attackable { - this.refreshDimensions(); - } - -- SpigotTimings.timerEntityTickRest.stopTiming(); // Spigot - } - - public void detectEquipmentUpdatesPublic() { // CraftBukkit -@@ -3328,7 +3324,6 @@ public abstract class LivingEntity extends Entity implements Attackable { - - this.setDeltaMovement(d0, d1, d2); - this.level().getProfiler().push("ai"); -- SpigotTimings.timerEntityAI.startTiming(); // Spigot - if (this.isImmobile()) { - this.jumping = false; - this.xxa = 0.0F; -@@ -3338,7 +3333,6 @@ public abstract class LivingEntity extends Entity implements Attackable { - this.serverAiStep(); - this.level().getProfiler().pop(); - } -- SpigotTimings.timerEntityAI.stopTiming(); // Spigot - - this.level().getProfiler().pop(); - this.level().getProfiler().push("jump"); -@@ -3378,7 +3372,6 @@ public abstract class LivingEntity extends Entity implements Attackable { - this.resetFallDistance(); - } - -- SpigotTimings.timerEntityAIMove.startTiming(); // Spigot - label104: - { - LivingEntity entityliving = this.getControllingPassenger(); -@@ -3392,7 +3385,6 @@ public abstract class LivingEntity extends Entity implements Attackable { - - this.travel(vec3d1); - } -- SpigotTimings.timerEntityAIMove.stopTiming(); // Spigot - - this.level().getProfiler().pop(); - this.level().getProfiler().push("freezing"); -@@ -3419,9 +3411,7 @@ public abstract class LivingEntity extends Entity implements Attackable { - this.checkAutoSpinAttack(axisalignedbb, this.getBoundingBox()); - } - -- SpigotTimings.timerEntityAICollision.startTiming(); // Spigot - this.pushEntities(); -- SpigotTimings.timerEntityAICollision.stopTiming(); // Spigot - this.level().getProfiler().pop(); - if (!this.level().isClientSide && this.isSensitiveToWater() && this.isInWaterRainOrBubble()) { - this.hurt(this.damageSources().drown(), 1.0F); -diff --git a/src/main/java/net/minecraft/world/level/Level.java b/src/main/java/net/minecraft/world/level/Level.java -index 2bc1d0d3ea8a6e3327e9c11bd1f0666d210e9bbe..79d5423be919dfe4db75ad7dd0ce403ad0214462 100644 ---- a/src/main/java/net/minecraft/world/level/Level.java -+++ b/src/main/java/net/minecraft/world/level/Level.java -@@ -93,7 +93,6 @@ import net.minecraft.network.protocol.game.ClientboundSetBorderWarningDistancePa - import org.bukkit.Bukkit; - import org.bukkit.craftbukkit.CraftServer; - import org.bukkit.craftbukkit.CraftWorld; --import org.bukkit.craftbukkit.SpigotTimings; // Spigot - import org.bukkit.craftbukkit.block.CapturedBlockState; - import org.bukkit.craftbukkit.block.CraftBlockState; - import org.bukkit.craftbukkit.block.data.CraftBlockData; -@@ -164,7 +163,7 @@ public abstract class Level implements LevelAccessor, AutoCloseable { - } - // Paper end - add paper world config - -- public final SpigotTimings.WorldTimingsHandler timings; // Spigot -+ public final co.aikar.timings.WorldTimingsHandler timings; // Paper - public static BlockPos lastPhysicsProblem; // Spigot - private org.spigotmc.TickLimiter entityLimiter; - private org.spigotmc.TickLimiter tileLimiter; -@@ -259,7 +258,7 @@ public abstract class Level implements LevelAccessor, AutoCloseable { - public void onBorderSetDamageSafeZOne(WorldBorder border, double safeZoneRadius) {} - }); - // CraftBukkit end -- this.timings = new SpigotTimings.WorldTimingsHandler(this); // Spigot - code below can generate new world and access timings -+ this.timings = new co.aikar.timings.WorldTimingsHandler(this); // Paper - code below can generate new world and access timings - this.entityLimiter = new org.spigotmc.TickLimiter(this.spigotConfig.entityMaxTickTime); - this.tileLimiter = new org.spigotmc.TickLimiter(this.spigotConfig.tileMaxTickTime); - } -@@ -723,15 +722,14 @@ public abstract class Level implements LevelAccessor, AutoCloseable { - - this.timings.tileEntityTick.stopTiming(); // Spigot - this.tickingBlockEntities = false; -+ co.aikar.timings.TimingHistory.tileEntityTicks += this.blockEntityTickers.size(); // Paper - gameprofilerfiller.pop(); - this.spigotConfig.currentPrimedTnt = 0; // Spigot - } - - public void guardEntityTick(Consumer tickConsumer, T entity) { - try { -- SpigotTimings.tickEntityTimer.startTiming(); // Spigot - tickConsumer.accept(entity); -- SpigotTimings.tickEntityTimer.stopTiming(); // Spigot - } catch (Throwable throwable) { - CrashReport crashreport = CrashReport.forThrowable(throwable, "Ticking entity"); - CrashReportCategory crashreportsystemdetails = crashreport.addCategory("Entity being ticked"); -diff --git a/src/main/java/net/minecraft/world/level/block/Block.java b/src/main/java/net/minecraft/world/level/block/Block.java -index d1cfcc8a36964f006f1af6764c52b5ca458b478d..def3e28edc206e0ba41111e26332db468223fb2e 100644 ---- a/src/main/java/net/minecraft/world/level/block/Block.java -+++ b/src/main/java/net/minecraft/world/level/block/Block.java -@@ -88,6 +88,15 @@ public class Block extends BlockBehaviour implements ItemLike { - public static final int UPDATE_LIMIT = 512; - protected final StateDefinition stateDefinition; - private BlockState defaultBlockState; -+ // Paper start -+ public co.aikar.timings.Timing timing; -+ public co.aikar.timings.Timing getTiming() { -+ if (timing == null) { -+ timing = co.aikar.timings.MinecraftTimings.getBlockTiming(this); -+ } -+ return timing; -+ } -+ // Paper end - @Nullable - private String descriptionId; - @Nullable -diff --git a/src/main/java/net/minecraft/world/level/block/entity/BlockEntity.java b/src/main/java/net/minecraft/world/level/block/entity/BlockEntity.java -index 6a4e5bfa70b31311a9076eb314c0a0146d5b08a5..17cda4c8b61efd99c1a43f921ed604827bb064f3 100644 ---- a/src/main/java/net/minecraft/world/level/block/entity/BlockEntity.java -+++ b/src/main/java/net/minecraft/world/level/block/entity/BlockEntity.java -@@ -34,10 +34,12 @@ import org.bukkit.inventory.InventoryHolder; - // CraftBukkit end - - import org.spigotmc.CustomTimingsHandler; // Spigot -+import co.aikar.timings.MinecraftTimings; // Paper -+import co.aikar.timings.Timing; // Paper - - public abstract class BlockEntity { - -- public CustomTimingsHandler tickTimer = org.bukkit.craftbukkit.SpigotTimings.getTileEntityTimings(this); // Spigot -+ public Timing tickTimer = MinecraftTimings.getTileEntityTimings(this); // Paper - // CraftBukkit start - data containers - private static final CraftPersistentDataTypeRegistry DATA_TYPE_REGISTRY = new CraftPersistentDataTypeRegistry(); - public CraftPersistentDataContainer persistentDataContainer; -diff --git a/src/main/java/net/minecraft/world/level/chunk/LevelChunk.java b/src/main/java/net/minecraft/world/level/chunk/LevelChunk.java -index 25380a44e5cc94f3924cfee6a03c3091fea04ae2..418ba374886d93f69afd614e4be05f6561e1f897 100644 ---- a/src/main/java/net/minecraft/world/level/chunk/LevelChunk.java -+++ b/src/main/java/net/minecraft/world/level/chunk/LevelChunk.java -@@ -602,6 +602,7 @@ public class LevelChunk extends ChunkAccess { - server.getPluginManager().callEvent(new org.bukkit.event.world.ChunkLoadEvent(bukkitChunk, this.needsDecoration)); - - if (this.needsDecoration) { -+ try (co.aikar.timings.Timing ignored = this.level.timings.chunkLoadPopulate.startTiming()) { // Paper - this.needsDecoration = false; - java.util.Random random = new java.util.Random(); - random.setSeed(this.level.getSeed()); -@@ -621,6 +622,7 @@ public class LevelChunk extends ChunkAccess { - } - } - server.getPluginManager().callEvent(new org.bukkit.event.world.ChunkPopulateEvent(bukkitChunk)); -+ } // Paper - } - } - } -diff --git a/src/main/java/net/minecraft/world/level/chunk/storage/ChunkSerializer.java b/src/main/java/net/minecraft/world/level/chunk/storage/ChunkSerializer.java -index 46a090123e205394791cdbde2af84c58ce55f7e1..47f5f3d58bb3bf85cf35f9baae77df7fab5c844f 100644 ---- a/src/main/java/net/minecraft/world/level/chunk/storage/ChunkSerializer.java -+++ b/src/main/java/net/minecraft/world/level/chunk/storage/ChunkSerializer.java -@@ -472,13 +472,10 @@ public class ChunkSerializer { - ListTag nbttaglist1 = ChunkSerializer.getListOfCompoundsOrNull(nbt, "block_entities"); - - return nbttaglist == null && nbttaglist1 == null ? null : (chunk) -> { -- world.timings.syncChunkLoadEntitiesTimer.startTiming(); // Spigot - if (nbttaglist != null) { - world.addLegacyChunkEntities(EntityType.loadEntitiesRecursive(nbttaglist, world)); - } -- world.timings.syncChunkLoadEntitiesTimer.stopTiming(); // Spigot - -- world.timings.syncChunkLoadTileEntitiesTimer.startTiming(); // Spigot - if (nbttaglist1 != null) { - for (int i = 0; i < nbttaglist1.size(); ++i) { - CompoundTag nbttagcompound1 = nbttaglist1.getCompound(i); -@@ -496,7 +493,6 @@ public class ChunkSerializer { - } - } - } -- world.timings.syncChunkLoadTileEntitiesTimer.stopTiming(); // Spigot - - }; - } -diff --git a/src/main/java/org/bukkit/craftbukkit/CraftServer.java b/src/main/java/org/bukkit/craftbukkit/CraftServer.java -index cd6b6109ea3844f7d78ad7a1cbd6cf5dc3d90b5c..0fda501dedcc1f3f7d35e3b66a4b40394c4b3cb5 100644 ---- a/src/main/java/org/bukkit/craftbukkit/CraftServer.java -+++ b/src/main/java/org/bukkit/craftbukkit/CraftServer.java -@@ -376,7 +376,7 @@ public final class CraftServer implements Server { - this.saveCommandsConfig(); - this.overrideAllCommandBlockCommands = this.commandsConfiguration.getStringList("command-block-overrides").contains("*"); - this.ignoreVanillaPermissions = this.commandsConfiguration.getBoolean("ignore-vanilla-permissions"); -- this.pluginManager.useTimings(this.configuration.getBoolean("settings.plugin-profiling")); -+ //this.pluginManager.useTimings(this.configuration.getBoolean("settings.plugin-profiling")); // Paper - we already moved this - this.overrideSpawnLimits(); - console.autosavePeriod = this.configuration.getInt("ticks-per.autosave"); - this.warningState = WarningState.value(this.configuration.getString("settings.deprecated-verbose")); -@@ -2619,12 +2619,31 @@ public final class CraftServer implements Server { - private final org.bukkit.Server.Spigot spigot = new org.bukkit.Server.Spigot() - { - -+ @Deprecated - @Override - public YamlConfiguration getConfig() - { - return org.spigotmc.SpigotConfig.config; - } - -+ @Override -+ public YamlConfiguration getBukkitConfig() -+ { -+ return configuration; -+ } -+ -+ @Override -+ public YamlConfiguration getSpigotConfig() -+ { -+ return org.spigotmc.SpigotConfig.config; -+ } -+ -+ @Override -+ public YamlConfiguration getPaperConfig() -+ { -+ return CraftServer.this.console.paperConfigurations.createLegacyObject(CraftServer.this.console); -+ } -+ - @Override - public void restart() { - org.spigotmc.RestartCommand.restart(); -diff --git a/src/main/java/org/bukkit/craftbukkit/SpigotTimings.java b/src/main/java/org/bukkit/craftbukkit/SpigotTimings.java -deleted file mode 100644 -index b0ffa23faf62629043dfd613315eaf9c5fcc2cfe..0000000000000000000000000000000000000000 ---- a/src/main/java/org/bukkit/craftbukkit/SpigotTimings.java -+++ /dev/null -@@ -1,163 +0,0 @@ --package org.bukkit.craftbukkit; -- --import java.util.HashMap; --import net.minecraft.world.entity.Entity; --import net.minecraft.world.level.Level; --import net.minecraft.world.level.block.entity.BlockEntity; --import net.minecraft.world.level.storage.PrimaryLevelData; --import org.bukkit.craftbukkit.scheduler.CraftTask; --import org.bukkit.plugin.java.JavaPluginLoader; --import org.bukkit.scheduler.BukkitTask; --import org.spigotmc.CustomTimingsHandler; -- --public class SpigotTimings { -- -- public static final CustomTimingsHandler serverTickTimer = new CustomTimingsHandler("** Full Server Tick"); -- public static final CustomTimingsHandler playerListTimer = new CustomTimingsHandler("Player List"); -- public static final CustomTimingsHandler commandFunctionsTimer = new CustomTimingsHandler("Command Functions"); -- public static final CustomTimingsHandler connectionTimer = new CustomTimingsHandler("Connection Handler"); -- public static final CustomTimingsHandler playerConnectionTimer = new CustomTimingsHandler("** PlayerConnection"); -- public static final CustomTimingsHandler tickablesTimer = new CustomTimingsHandler("Tickables"); -- public static final CustomTimingsHandler schedulerTimer = new CustomTimingsHandler("Scheduler"); -- public static final CustomTimingsHandler timeUpdateTimer = new CustomTimingsHandler("Time Update"); -- public static final CustomTimingsHandler serverCommandTimer = new CustomTimingsHandler("Server Command"); -- public static final CustomTimingsHandler worldSaveTimer = new CustomTimingsHandler("World Save"); -- -- public static final CustomTimingsHandler entityMoveTimer = new CustomTimingsHandler("** entityMove"); -- public static final CustomTimingsHandler tickEntityTimer = new CustomTimingsHandler("** tickEntity"); -- public static final CustomTimingsHandler activatedEntityTimer = new CustomTimingsHandler("** activatedTickEntity"); -- public static final CustomTimingsHandler tickTileEntityTimer = new CustomTimingsHandler("** tickTileEntity"); -- -- public static final CustomTimingsHandler timerEntityBaseTick = new CustomTimingsHandler("** livingEntityBaseTick"); -- public static final CustomTimingsHandler timerEntityAI = new CustomTimingsHandler("** livingEntityAI"); -- public static final CustomTimingsHandler timerEntityAICollision = new CustomTimingsHandler("** livingEntityAICollision"); -- public static final CustomTimingsHandler timerEntityAIMove = new CustomTimingsHandler("** livingEntityAIMove"); -- public static final CustomTimingsHandler timerEntityTickRest = new CustomTimingsHandler("** livingEntityTickRest"); -- -- public static final CustomTimingsHandler processQueueTimer = new CustomTimingsHandler("processQueue"); -- public static final CustomTimingsHandler schedulerSyncTimer = new CustomTimingsHandler("** Scheduler - Sync Tasks", JavaPluginLoader.pluginParentTimer); -- -- public static final CustomTimingsHandler playerCommandTimer = new CustomTimingsHandler("** playerCommand"); -- -- public static final CustomTimingsHandler entityActivationCheckTimer = new CustomTimingsHandler("entityActivationCheck"); -- public static final CustomTimingsHandler checkIfActiveTimer = new CustomTimingsHandler("** checkIfActive"); -- -- public static final HashMap entityTypeTimingMap = new HashMap(); -- public static final HashMap tileEntityTypeTimingMap = new HashMap(); -- public static final HashMap pluginTaskTimingMap = new HashMap(); -- -- /** -- * Gets a timer associated with a plugins tasks. -- * @param task -- * @param period -- * @return -- */ -- public static CustomTimingsHandler getPluginTaskTimings(BukkitTask task, long period) { -- if (!task.isSync()) { -- return null; -- } -- String plugin; -- final CraftTask ctask = (CraftTask) task; -- -- if (task.getOwner() != null) { -- plugin = task.getOwner().getDescription().getFullName(); -- } else { -- plugin = "Unknown"; -- } -- String taskname = ctask.getTaskName(); -- -- String name = "Task: " + plugin + " Runnable: " + taskname; -- if (period > 0) { -- name += "(interval:" + period + ")"; -- } else { -- name += "(Single)"; -- } -- CustomTimingsHandler result = SpigotTimings.pluginTaskTimingMap.get(name); -- if (result == null) { -- result = new CustomTimingsHandler(name, SpigotTimings.schedulerSyncTimer); -- SpigotTimings.pluginTaskTimingMap.put(name, result); -- } -- return result; -- } -- -- /** -- * Get a named timer for the specified entity type to track type specific timings. -- * @param entity -- * @return -- */ -- public static CustomTimingsHandler getEntityTimings(Entity entity) { -- String entityType = entity.getClass().getName(); -- CustomTimingsHandler result = SpigotTimings.entityTypeTimingMap.get(entityType); -- if (result == null) { -- result = new CustomTimingsHandler("** tickEntity - " + entity.getClass().getSimpleName(), SpigotTimings.activatedEntityTimer); -- SpigotTimings.entityTypeTimingMap.put(entityType, result); -- } -- return result; -- } -- -- /** -- * Get a named timer for the specified tile entity type to track type specific timings. -- * @param entity -- * @return -- */ -- public static CustomTimingsHandler getTileEntityTimings(BlockEntity entity) { -- String entityType = entity.getClass().getName(); -- CustomTimingsHandler result = SpigotTimings.tileEntityTypeTimingMap.get(entityType); -- if (result == null) { -- result = new CustomTimingsHandler("** tickTileEntity - " + entity.getClass().getSimpleName(), SpigotTimings.tickTileEntityTimer); -- SpigotTimings.tileEntityTypeTimingMap.put(entityType, result); -- } -- return result; -- } -- -- /** -- * Set of timers per world, to track world specific timings. -- */ -- public static class WorldTimingsHandler { -- public final CustomTimingsHandler mobSpawn; -- public final CustomTimingsHandler doChunkUnload; -- public final CustomTimingsHandler doTickPending; -- public final CustomTimingsHandler doTickTiles; -- public final CustomTimingsHandler doChunkMap; -- public final CustomTimingsHandler doSounds; -- public final CustomTimingsHandler entityTick; -- public final CustomTimingsHandler tileEntityTick; -- public final CustomTimingsHandler tileEntityPending; -- public final CustomTimingsHandler tracker; -- public final CustomTimingsHandler doTick; -- public final CustomTimingsHandler tickEntities; -- -- public final CustomTimingsHandler syncChunkLoadTimer; -- public final CustomTimingsHandler syncChunkLoadStructuresTimer; -- public final CustomTimingsHandler syncChunkLoadEntitiesTimer; -- public final CustomTimingsHandler syncChunkLoadTileEntitiesTimer; -- public final CustomTimingsHandler syncChunkLoadTileTicksTimer; -- public final CustomTimingsHandler syncChunkLoadPostTimer; -- -- public WorldTimingsHandler(Level server) { -- String name = ((PrimaryLevelData) server.levelData).getLevelName() + " - "; -- -- this.mobSpawn = new CustomTimingsHandler("** " + name + "mobSpawn"); -- this.doChunkUnload = new CustomTimingsHandler("** " + name + "doChunkUnload"); -- this.doTickPending = new CustomTimingsHandler("** " + name + "doTickPending"); -- this.doTickTiles = new CustomTimingsHandler("** " + name + "doTickTiles"); -- this.doChunkMap = new CustomTimingsHandler("** " + name + "doChunkMap"); -- this.doSounds = new CustomTimingsHandler("** " + name + "doSounds"); -- this.entityTick = new CustomTimingsHandler("** " + name + "entityTick"); -- this.tileEntityTick = new CustomTimingsHandler("** " + name + "tileEntityTick"); -- this.tileEntityPending = new CustomTimingsHandler("** " + name + "tileEntityPending"); -- -- this.syncChunkLoadTimer = new CustomTimingsHandler("** " + name + "syncChunkLoad"); -- this.syncChunkLoadStructuresTimer = new CustomTimingsHandler("** " + name + "chunkLoad - Structures"); -- this.syncChunkLoadEntitiesTimer = new CustomTimingsHandler("** " + name + "chunkLoad - Entities"); -- this.syncChunkLoadTileEntitiesTimer = new CustomTimingsHandler("** " + name + "chunkLoad - TileEntities"); -- this.syncChunkLoadTileTicksTimer = new CustomTimingsHandler("** " + name + "chunkLoad - TileTicks"); -- this.syncChunkLoadPostTimer = new CustomTimingsHandler("** " + name + "chunkLoad - Post"); -- -- -- this.tracker = new CustomTimingsHandler(name + "tracker"); -- this.doTick = new CustomTimingsHandler(name + "doTick"); -- this.tickEntities = new CustomTimingsHandler(name + "tickEntities"); -- } -- } --} -diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java -index 69b8d0f73ced69cd88029a5d7e11aca40d70f9d1..e4ea877228102ccf93fe8c92b0cec8ebd89771a0 100644 ---- a/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java -+++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java -@@ -2782,6 +2782,14 @@ public class CraftPlayer extends CraftHumanEntity implements Player { - - CraftPlayer.this.getHandle().connection.send(new net.minecraft.network.protocol.game.ClientboundSystemChatPacket(components, position == net.md_5.bungee.api.ChatMessageType.ACTION_BAR)); - } -+ -+ // Paper start -+ @Override -+ public int getPing() -+ { -+ return CraftPlayer.this.getPing(); -+ } -+ // Paper end - }; - - public Player.Spigot spigot() -diff --git a/src/main/java/org/bukkit/craftbukkit/scheduler/CraftScheduler.java b/src/main/java/org/bukkit/craftbukkit/scheduler/CraftScheduler.java -index c017ce2ca1bc535795c958a2e509af2adf88efa9..6c0debe3f3b693ed90dd2a39f481cccd8e4f7634 100644 ---- a/src/main/java/org/bukkit/craftbukkit/scheduler/CraftScheduler.java -+++ b/src/main/java/org/bukkit/craftbukkit/scheduler/CraftScheduler.java -@@ -1,5 +1,6 @@ - package org.bukkit.craftbukkit.scheduler; - -+import co.aikar.timings.MinecraftTimings; // Paper - import com.google.common.base.Preconditions; - import com.google.common.util.concurrent.ThreadFactoryBuilder; - import java.util.ArrayList; -@@ -271,7 +272,7 @@ public class CraftScheduler implements BukkitScheduler { - } - return false; - } -- }); -+ }){{this.timings=co.aikar.timings.MinecraftTimings.getCancelTasksTimer();}}; // Paper - this.handle(task, 0L); - for (CraftTask taskPending = this.head.getNext(); taskPending != null; taskPending = taskPending.getNext()) { - if (taskPending == task) { -@@ -306,7 +307,7 @@ public class CraftScheduler implements BukkitScheduler { - } - } - } -- }); -+ }){{this.timings=co.aikar.timings.MinecraftTimings.getCancelTasksTimer(plugin);}}; // Paper - this.handle(task, 0L); - for (CraftTask taskPending = this.head.getNext(); taskPending != null; taskPending = taskPending.getNext()) { - if (taskPending == task) { -@@ -413,9 +414,7 @@ public class CraftScheduler implements BukkitScheduler { - if (task.isSync()) { - this.currentTask = task; - try { -- task.timings.startTiming(); // Spigot - task.run(); -- task.timings.stopTiming(); // Spigot - } catch (final Throwable throwable) { - task.getOwner().getLogger().log( - Level.WARNING, -@@ -442,8 +441,10 @@ public class CraftScheduler implements BukkitScheduler { - this.runners.remove(task.getTaskId()); - } - } -+ MinecraftTimings.bukkitSchedulerFinishTimer.startTiming(); // Paper - this.pending.addAll(temp); - temp.clear(); -+ MinecraftTimings.bukkitSchedulerFinishTimer.stopTiming(); // Paper - this.debugHead = this.debugHead.getNextHead(currentTick); - } - -@@ -480,6 +481,7 @@ public class CraftScheduler implements BukkitScheduler { - } - - private void parsePending() { -+ MinecraftTimings.bukkitSchedulerPendingTimer.startTiming(); - CraftTask head = this.head; - CraftTask task = head.getNext(); - CraftTask lastTask = head; -@@ -498,6 +500,7 @@ public class CraftScheduler implements BukkitScheduler { - task.setNext(null); - } - this.head = lastTask; -+ MinecraftTimings.bukkitSchedulerPendingTimer.stopTiming(); - } - - private boolean isReady(final int currentTick) { -diff --git a/src/main/java/org/bukkit/craftbukkit/scheduler/CraftTask.java b/src/main/java/org/bukkit/craftbukkit/scheduler/CraftTask.java -index e4d1eb4a0ce2c9874922585f6bb0d9ead433fde1..ba369f3dcfdf498e971dc4405d39657a9b6e97cc 100644 ---- a/src/main/java/org/bukkit/craftbukkit/scheduler/CraftTask.java -+++ b/src/main/java/org/bukkit/craftbukkit/scheduler/CraftTask.java -@@ -1,12 +1,15 @@ - package org.bukkit.craftbukkit.scheduler; - - import java.util.function.Consumer; -+ -+import co.aikar.timings.NullTimingHandler; - import org.bukkit.Bukkit; - import org.bukkit.plugin.Plugin; - import org.bukkit.scheduler.BukkitTask; - --import org.bukkit.craftbukkit.SpigotTimings; // Spigot - import org.spigotmc.CustomTimingsHandler; // Spigot -+import co.aikar.timings.MinecraftTimings; // Paper -+import co.aikar.timings.Timing; // Paper - - public class CraftTask implements BukkitTask, Runnable { // Spigot - -@@ -26,13 +29,13 @@ public class CraftTask implements BukkitTask, Runnable { // Spigot - */ - private volatile long period; - private long nextRun; -- private final Runnable rTask; -- private final Consumer cTask; -+ public final Runnable rTask; // Paper -+ public final Consumer cTask; // Paper -+ public Timing timings; // Paper - private final Plugin plugin; - private final int id; - private final long createdAt = System.nanoTime(); - -- final CustomTimingsHandler timings; // Spigot - CraftTask() { - this(null, null, CraftTask.NO_REPEATING, CraftTask.NO_REPEATING); - } -@@ -58,7 +61,7 @@ public class CraftTask implements BukkitTask, Runnable { // Spigot - } - this.id = id; - this.period = period; -- this.timings = this.isSync() ? SpigotTimings.getPluginTaskTimings(this, period) : null; // Spigot -+ timings = task != null ? MinecraftTimings.getPluginTaskTimings(this, period) : NullTimingHandler.NULL; // Paper - } - - @Override -@@ -78,11 +81,13 @@ public class CraftTask implements BukkitTask, Runnable { // Spigot - - @Override - public void run() { -+ try (Timing ignored = timings.startTiming()) { // Paper - if (this.rTask != null) { - this.rTask.run(); - } else { - this.cTask.accept(this); - } -+ } // Paper - } - - long getCreatedAt() { -@@ -113,7 +118,7 @@ public class CraftTask implements BukkitTask, Runnable { // Spigot - this.next = next; - } - -- Class getTaskClass() { -+ public Class getTaskClass() { // Paper - return (this.rTask != null) ? this.rTask.getClass() : ((this.cTask != null) ? this.cTask.getClass() : null); - } - -@@ -137,9 +142,4 @@ public class CraftTask implements BukkitTask, Runnable { // Spigot - return true; - } - -- // Spigot start -- public String getTaskName() { -- return (this.getTaskClass() == null) ? "Unknown" : this.getTaskClass().getName(); -- } -- // Spigot end - } -diff --git a/src/main/java/org/bukkit/craftbukkit/util/CraftIconCache.java b/src/main/java/org/bukkit/craftbukkit/util/CraftIconCache.java -index f97eccb6a17c7876e1e002d798eb67bbe80571a0..76effc345d362047e64d064eb64a5222612aec14 100644 ---- a/src/main/java/org/bukkit/craftbukkit/util/CraftIconCache.java -+++ b/src/main/java/org/bukkit/craftbukkit/util/CraftIconCache.java -@@ -8,4 +8,11 @@ public class CraftIconCache implements CachedServerIcon { - public CraftIconCache(final byte[] value) { - this.value = value; - } -+ -+ public String getData() { -+ if (value == null) { -+ return null; -+ } -+ return "data:image/png;base64," + new String(java.util.Base64.getEncoder().encode(value), java.nio.charset.StandardCharsets.UTF_8); -+ } // Paper - } -diff --git a/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java b/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java -index 33d3085d0d44d748fcb1fc203dfd14c9e1b4aff0..f7b80cf8c89ae5eb9d8f0893e05ffc753fdace19 100644 ---- a/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java -+++ b/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java -@@ -199,6 +199,12 @@ public final class CraftMagicNumbers implements UnsafeValues { - return CraftNamespacedKey.toMinecraft(mat.getKey()); - } - // ======================================================================== -+ // Paper start -+ @Override -+ public void reportTimings() { -+ co.aikar.timings.TimingsExport.reportTimings(); -+ } -+ // Paper end - - public static byte toLegacyData(BlockState data) { - return CraftLegacy.toLegacyData(data); -@@ -448,6 +454,12 @@ public final class CraftMagicNumbers implements UnsafeValues { - public DamageSource.Builder createDamageSourceBuilder(DamageType damageType) { - return new CraftDamageSourceBuilder(damageType); - } -+ // Paper start -+ @Override -+ public String getTimingsServerName() { -+ return io.papermc.paper.configuration.GlobalConfiguration.get().timings.serverName; -+ } -+ // Paper end - - @Override - public String get(Class aClass, String s) { -diff --git a/src/main/java/org/spigotmc/ActivationRange.java b/src/main/java/org/spigotmc/ActivationRange.java -index ff422d4d4f2b764370f0ee2af13034853c1d3fe1..a5da6c1cae0afbde684be250e2fc3c0c32a1265b 100644 ---- a/src/main/java/org/spigotmc/ActivationRange.java -+++ b/src/main/java/org/spigotmc/ActivationRange.java -@@ -27,7 +27,7 @@ import net.minecraft.world.entity.projectile.ThrownTrident; - import net.minecraft.world.entity.raid.Raider; - import net.minecraft.world.level.Level; - import net.minecraft.world.phys.AABB; --import org.bukkit.craftbukkit.SpigotTimings; -+import co.aikar.timings.MinecraftTimings; - - public class ActivationRange - { -@@ -74,8 +74,8 @@ public class ActivationRange - /** - * These entities are excluded from Activation range checks. - * -- * @param entity -- * @param config -+ * @param entity Entity to initialize -+ * @param config Spigot config to determine ranges - * @return boolean If it should always tick. - */ - public static boolean initializeEntityActivationState(Entity entity, SpigotWorldConfig config) -@@ -110,7 +110,7 @@ public class ActivationRange - */ - public static void activateEntities(Level world) - { -- SpigotTimings.entityActivationCheckTimer.startTiming(); -+ MinecraftTimings.entityActivationCheckTimer.startTiming(); - final int miscActivationRange = world.spigotConfig.miscActivationRange; - final int raiderActivationRange = world.spigotConfig.raiderActivationRange; - final int animalActivationRange = world.spigotConfig.animalActivationRange; -@@ -137,7 +137,7 @@ public class ActivationRange - - world.getEntities().get(ActivationRange.maxBB, ActivationRange::activateEntity); - } -- SpigotTimings.entityActivationCheckTimer.stopTiming(); -+ MinecraftTimings.entityActivationCheckTimer.stopTiming(); - } - - /** -@@ -232,10 +232,8 @@ public class ActivationRange - */ - public static boolean checkIfActive(Entity entity) - { -- SpigotTimings.checkIfActiveTimer.startTiming(); - // Never safe to skip fireworks or entities not yet added to chunk - if ( entity instanceof FireworkRocketEntity ) { -- SpigotTimings.checkIfActiveTimer.stopTiming(); - return true; - } - -@@ -259,7 +257,6 @@ public class ActivationRange - { - isActive = false; - } -- SpigotTimings.checkIfActiveTimer.stopTiming(); - return isActive; - } - } diff --git a/patches/server/0024-Further-improve-server-tick-loop.patch b/patches/server/0024-Further-improve-server-tick-loop.patch deleted file mode 100644 index 6cd0eb9aee1c..000000000000 --- a/patches/server/0024-Further-improve-server-tick-loop.patch +++ /dev/null @@ -1,233 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Aikar -Date: Tue, 1 Mar 2016 23:09:29 -0600 -Subject: [PATCH] Further improve server tick loop - -Improves how the catchup buffer is handled, allowing it to roll both ways -increasing the effeciency of the thread sleep so it only will sleep once. - -Also increases the buffer of the catchup to ensure server stays at 20 TPS unless extreme conditions - -Previous implementation did not calculate TPS correctly. -Switch to a realistic rolling average and factor in std deviation as an extra reporting variable - -diff --git a/src/main/java/net/minecraft/server/MinecraftServer.java b/src/main/java/net/minecraft/server/MinecraftServer.java -index 49de4625c57689a3624ed421c0b03512507c97c3..46e03617bb32e4037d700c1b3698d397bd75de5c 100644 ---- a/src/main/java/net/minecraft/server/MinecraftServer.java -+++ b/src/main/java/net/minecraft/server/MinecraftServer.java -@@ -296,7 +296,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop processQueue = new java.util.concurrent.ConcurrentLinkedQueue(); - public int autosavePeriod; - public Commands vanillaCommandDispatcher; -@@ -305,7 +305,8 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop 0 && args[0].equals("mem") && sender.hasPermission("bukkit.command.tpsmemory")) { -+ sender.sendMessage(net.kyori.adventure.text.Component.text() -+ .append(net.kyori.adventure.text.Component.text("Current Memory Usage: ", net.kyori.adventure.text.format.NamedTextColor.GOLD)) -+ .append(net.kyori.adventure.text.Component.text(((Runtime.getRuntime().totalMemory() - Runtime.getRuntime().freeMemory()) / (1024 * 1024)) + "/" + (Runtime.getRuntime().totalMemory() / (1024 * 1024)) + " mb (Max: " + (Runtime.getRuntime().maxMemory() / (1024 * 1024)) + " mb)", net.kyori.adventure.text.format.NamedTextColor.GREEN)) -+ ); -+ if (!this.hasShownMemoryWarning) { -+ sender.sendMessage(WARN_MSG); -+ this.hasShownMemoryWarning = true; -+ } - } -- sender.sendMessage( sb.substring( 0, sb.length() - 2 ) ); -- sender.sendMessage(ChatColor.GOLD + "Current Memory Usage: " + ChatColor.GREEN + ((Runtime.getRuntime().totalMemory() - Runtime.getRuntime().freeMemory()) / (1024 * 1024)) + "/" + (Runtime.getRuntime().totalMemory() / (1024 * 1024)) + " mb (Max: " -- + (Runtime.getRuntime().maxMemory() / (1024 * 1024)) + " mb)"); -+ // Paper end - - return true; - } - -- private String format(double tps) -+ private boolean hasShownMemoryWarning; // Paper -+ private static net.kyori.adventure.text.Component format(double tps) // Paper - Made static - { -- return ( ( tps > 18.0 ) ? ChatColor.GREEN : ( tps > 16.0 ) ? ChatColor.YELLOW : ChatColor.RED ).toString() -- + ( ( tps > 20.0 ) ? "*" : "" ) + Math.min( Math.round( tps * 100.0 ) / 100.0, 20.0 ); -+ // Paper -+ net.kyori.adventure.text.format.TextColor color = ( ( tps > 18.0 ) ? net.kyori.adventure.text.format.NamedTextColor.GREEN : ( tps > 16.0 ) ? net.kyori.adventure.text.format.NamedTextColor.YELLOW : net.kyori.adventure.text.format.NamedTextColor.RED ); -+ String amount = Math.min(Math.round(tps * 100.0) / 100.0, 20.0) + (tps > 21.0 ? "*" : ""); // Paper - only print * at 21, we commonly peak to 20.02 as the tick sleep is not accurate enough, stop the noise -+ return net.kyori.adventure.text.Component.text(amount, color); -+ // Paper end - } - } diff --git a/patches/server/0024-Remove-Spigot-timings.patch b/patches/server/0024-Remove-Spigot-timings.patch new file mode 100644 index 000000000000..6913a8e21f09 --- /dev/null +++ b/patches/server/0024-Remove-Spigot-timings.patch @@ -0,0 +1,967 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Aikar +Date: Thu, 3 Mar 2016 04:00:11 -0600 +Subject: [PATCH] Remove Spigot timings + + +diff --git a/src/main/java/net/minecraft/server/MinecraftServer.java b/src/main/java/net/minecraft/server/MinecraftServer.java +index 4a573e8b7cd90f65c190982662e92a11f79a1d3e..709c6361aa5eb78071ce9d0f2a65ce8a56af1443 100644 +--- a/src/main/java/net/minecraft/server/MinecraftServer.java ++++ b/src/main/java/net/minecraft/server/MinecraftServer.java +@@ -203,7 +203,6 @@ import org.bukkit.craftbukkit.Main; + import org.bukkit.event.server.ServerLoadEvent; + // CraftBukkit end + +-import org.bukkit.craftbukkit.SpigotTimings; // Spigot + + public abstract class MinecraftServer extends ReentrantBlockableEventLoop implements ServerInfo, ChunkIOErrorReporter, CommandSource { + +@@ -1456,7 +1455,6 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop { + entityplayer.connection.suspendFlushing(); + }); +- SpigotTimings.schedulerTimer.startTiming(); // Spigot + this.server.getScheduler().mainThreadHeartbeat(); // CraftBukkit +- SpigotTimings.schedulerTimer.stopTiming(); // Spigot + io.papermc.paper.adventure.providers.ClickCallbackProviderImpl.CALLBACK_MANAGER.handleQueue(this.tickCount); // Paper + gameprofilerfiller.push("commandFunctions"); +- SpigotTimings.commandFunctionsTimer.startTiming(); // Spigot + this.getFunctions().tick(); +- SpigotTimings.commandFunctionsTimer.stopTiming(); // Spigot + gameprofilerfiller.popPush("levels"); + Iterator iterator = this.getAllLevels().iterator(); + + // CraftBukkit start + // Run tasks that are waiting on processing +- SpigotTimings.processQueueTimer.startTiming(); // Spigot + while (!this.processQueue.isEmpty()) { + this.processQueue.remove().run(); + } +- SpigotTimings.processQueueTimer.stopTiming(); // Spigot + +- SpigotTimings.timeUpdateTimer.startTiming(); // Spigot + // Send time updates to everyone, it will get the right time from the world the player is in. + if (this.tickCount % 20 == 0) { + for (int i = 0; i < this.getPlayerList().players.size(); ++i) { +@@ -1596,7 +1584,6 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop> completablefuture = this.getChunkFutureMainThread(x, z, leastStatus, create); + ServerChunkCache.MainThreadExecutor chunkproviderserver_b = this.mainThreadProcessor; + + Objects.requireNonNull(completablefuture); + chunkproviderserver_b.managedBlock(completablefuture::isDone); +- this.level.timings.syncChunkLoadTimer.stopTiming(); // Spigot + ChunkResult chunkresult = (ChunkResult) completablefuture.join(); + ChunkAccess ichunkaccess1 = (ChunkAccess) chunkresult.orElse(null); // CraftBukkit - decompile error + +@@ -420,25 +418,19 @@ public class ServerChunkCache extends ChunkSource { + ProfilerFiller gameprofilerfiller = Profiler.get(); + + gameprofilerfiller.push("purge"); +- this.level.timings.doChunkMap.startTiming(); // Spigot + if (this.level.tickRateManager().runsNormally() || !tickChunks || this.level.spigotConfig.unloadFrozenChunks) { // Spigot + this.distanceManager.purgeStaleTickets(); + } + + this.runDistanceManagerUpdates(); +- this.level.timings.doChunkMap.stopTiming(); // Spigot + gameprofilerfiller.popPush("chunks"); + if (tickChunks) { + this.tickChunks(); +- this.level.timings.tracker.startTiming(); // Spigot + this.chunkMap.tick(); +- this.level.timings.tracker.stopTiming(); // Spigot + } + +- this.level.timings.doChunkUnload.startTiming(); // Spigot + gameprofilerfiller.popPush("unload"); + this.chunkMap.tick(shouldKeepTicking); +- this.level.timings.doChunkUnload.stopTiming(); // Spigot + gameprofilerfiller.pop(); + this.clearCache(); + } +@@ -531,9 +523,7 @@ public class ServerChunkCache extends ChunkSource { + } + + if (this.level.shouldTickBlocksAt(chunkcoordintpair.toLong())) { +- this.level.timings.doTickTiles.startTiming(); // Spigot + this.level.tickChunk(chunk, k); +- this.level.timings.doTickTiles.stopTiming(); // Spigot + } + } + +diff --git a/src/main/java/net/minecraft/server/level/ServerLevel.java b/src/main/java/net/minecraft/server/level/ServerLevel.java +index c6ded1ac73ddbc0336000f77c0f99fa20551a0de..f3633da64f990972cddc03f2fcfd34ced2955a7a 100644 +--- a/src/main/java/net/minecraft/server/level/ServerLevel.java ++++ b/src/main/java/net/minecraft/server/level/ServerLevel.java +@@ -176,7 +176,6 @@ import net.minecraft.world.ticks.LevelTicks; + import org.slf4j.Logger; + import org.bukkit.Bukkit; + import org.bukkit.WeatherType; +-import org.bukkit.craftbukkit.SpigotTimings; // Spigot + import org.bukkit.craftbukkit.event.CraftEventFactory; + import org.bukkit.craftbukkit.generator.CustomWorldChunkManager; + import org.bukkit.craftbukkit.util.WorldUUID; +@@ -469,7 +468,6 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe + } + + gameprofilerfiller.push("tickPending"); +- this.timings.doTickPending.startTiming(); // Spigot + if (!this.isDebug() && flag) { + j = this.getGameTime(); + gameprofilerfiller.push("blockTicks"); +@@ -478,7 +476,6 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe + this.fluidTicks.tick(j, 65536, this::tickFluid); + gameprofilerfiller.pop(); + } +- this.timings.doTickPending.stopTiming(); // Spigot + + gameprofilerfiller.popPush("raid"); + if (flag) { +@@ -489,9 +486,7 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe + this.getChunkSource().tick(shouldKeepTicking, true); + gameprofilerfiller.popPush("blockEvents"); + if (flag) { +- this.timings.doSounds.startTiming(); // Spigot + this.runBlockEvents(); +- this.timings.doSounds.stopTiming(); // Spigot + } + + this.handlingTick = false; +@@ -504,7 +499,6 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe + + if (flag1 || this.emptyTime++ < 300) { + gameprofilerfiller.push("entities"); +- this.timings.tickEntities.startTiming(); // Spigot + if (this.dragonFight != null && flag) { + gameprofilerfiller.push("dragonFight"); + this.dragonFight.tick(); +@@ -512,7 +506,6 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe + } + + org.spigotmc.ActivationRange.activateEntities(this); // Spigot +- this.timings.entityTick.startTiming(); // Spigot + this.entityTickList.forEach((entity) -> { + if (!entity.isRemoved()) { + if (!tickratemanager.isEntityFrozen(entity)) { +@@ -537,8 +530,6 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe + } + } + }); +- this.timings.entityTick.stopTiming(); // Spigot +- this.timings.tickEntities.stopTiming(); // Spigot + gameprofilerfiller.pop(); + this.tickBlockEntities(); + } +@@ -951,7 +942,6 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe + return; + } + // Spigot end +- entity.tickTimer.startTiming(); // Spigot + entity.setOldPosAndRot(); + ProfilerFiller gameprofilerfiller = Profiler.get(); + +@@ -970,7 +960,6 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe + + this.tickPassenger(entity, entity1); + } +- entity.tickTimer.stopTiming(); // Spigot + + } + +diff --git a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java +index 18d56058073b6cc4f9020f0a6137e4ac26eed0b2..fddc6b5abbad66ebe556ff8565c38c60b7883fce 100644 +--- a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java ++++ b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java +@@ -339,7 +339,6 @@ public class ServerGamePacketListenerImpl extends ServerCommonPacketListenerImpl + + @Override + public void tick() { +- org.bukkit.craftbukkit.SpigotTimings.playerConnectionTimer.startTiming(); // Spigot + if (this.ackBlockChangesUpTo > -1) { + this.send(new ClientboundBlockChangedAckPacket(this.ackBlockChangesUpTo)); + this.ackBlockChangesUpTo = -1; +@@ -395,7 +394,6 @@ public class ServerGamePacketListenerImpl extends ServerCommonPacketListenerImpl + this.player.resetLastActionTime(); // CraftBukkit - SPIGOT-854 + this.disconnect((Component) Component.translatable("multiplayer.disconnect.idling")); + } +- org.bukkit.craftbukkit.SpigotTimings.playerConnectionTimer.stopTiming(); // Spigot + + } + +@@ -2122,7 +2120,6 @@ public class ServerGamePacketListenerImpl extends ServerCommonPacketListenerImpl + } + + private void handleCommand(String s) { +- org.bukkit.craftbukkit.SpigotTimings.playerCommandTimer.startTiming(); // Spigot + if ( org.spigotmc.SpigotConfig.logCommands ) // Spigot + this.LOGGER.info(this.player.getScoreboardName() + " issued server command: " + s); + +@@ -2132,7 +2129,6 @@ public class ServerGamePacketListenerImpl extends ServerCommonPacketListenerImpl + this.cserver.getPluginManager().callEvent(event); + + if (event.isCancelled()) { +- org.bukkit.craftbukkit.SpigotTimings.playerCommandTimer.stopTiming(); // Spigot + return; + } + +@@ -2145,7 +2141,6 @@ public class ServerGamePacketListenerImpl extends ServerCommonPacketListenerImpl + java.util.logging.Logger.getLogger(ServerGamePacketListenerImpl.class.getName()).log(java.util.logging.Level.SEVERE, null, ex); + return; + } finally { +- org.bukkit.craftbukkit.SpigotTimings.playerCommandTimer.stopTiming(); // Spigot + } + } + // CraftBukkit end +diff --git a/src/main/java/net/minecraft/world/entity/Entity.java b/src/main/java/net/minecraft/world/entity/Entity.java +index c010d18061f58a583c69e85fc29305497523f569..c8b8102d84119dfb6093f4b79aa3124c594f9a88 100644 +--- a/src/main/java/net/minecraft/world/entity/Entity.java ++++ b/src/main/java/net/minecraft/world/entity/Entity.java +@@ -148,7 +148,6 @@ import org.bukkit.command.CommandSender; + import org.bukkit.entity.Hanging; + import org.bukkit.entity.LivingEntity; + import org.bukkit.entity.Vehicle; +-import org.spigotmc.CustomTimingsHandler; // Spigot + import org.bukkit.event.entity.EntityCombustByEntityEvent; + import org.bukkit.event.hanging.HangingBreakByEntityEvent; + import org.bukkit.event.vehicle.VehicleBlockCollisionEvent; +@@ -326,7 +325,6 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess + // Marks an entity, that it was removed by a plugin via Entity#remove + // Main use case currently is for SPIGOT-7487, preventing dropping of leash when leash is removed + public boolean pluginRemoved = false; +- public CustomTimingsHandler tickTimer = org.bukkit.craftbukkit.SpigotTimings.getEntityTimings(this); // Spigot + // Spigot start + public final org.spigotmc.ActivationRange.ActivationType activationType = org.spigotmc.ActivationRange.initializeEntityActivationType(this); + public final boolean defaultActivationState; +@@ -866,7 +864,6 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess + } + + public void move(MoverType type, Vec3 movement) { +- org.bukkit.craftbukkit.SpigotTimings.entityMoveTimer.startTiming(); // Spigot + if (this.noPhysics) { + this.setPos(this.getX() + movement.x, this.getY() + movement.y, this.getZ() + movement.z); + } else { +@@ -978,7 +975,6 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess + gameprofilerfiller.pop(); + } + } +- org.bukkit.craftbukkit.SpigotTimings.entityMoveTimer.stopTiming(); // Spigot + } + + private void applyMovementEmissionAndPlaySound(Entity.MovementEmission moveEffect, Vec3 movement, BlockPos landingPos, BlockState landingState) { +diff --git a/src/main/java/net/minecraft/world/entity/LivingEntity.java b/src/main/java/net/minecraft/world/entity/LivingEntity.java +index 9aa4e70f1d1c4de2138d31701dceaed25062e69c..6dba567e9f7a197af16598647f216b5323d1b601 100644 +--- a/src/main/java/net/minecraft/world/entity/LivingEntity.java ++++ b/src/main/java/net/minecraft/world/entity/LivingEntity.java +@@ -162,8 +162,6 @@ import org.bukkit.event.entity.EntityTeleportEvent; + import org.bukkit.event.player.PlayerItemConsumeEvent; + // CraftBukkit end + +-import org.bukkit.craftbukkit.SpigotTimings; // Spigot +- + public abstract class LivingEntity extends Entity implements Attackable { + + private static final Logger LOGGER = LogUtils.getLogger(); +@@ -3090,7 +3088,6 @@ public abstract class LivingEntity extends Entity implements Attackable { + + @Override + public void tick() { +- SpigotTimings.timerEntityBaseTick.startTiming(); // Spigot + super.tick(); + this.updatingUsingItem(); + this.updateSwimAmount(); +@@ -3132,9 +3129,7 @@ public abstract class LivingEntity extends Entity implements Attackable { + } + + if (!this.isRemoved()) { +- SpigotTimings.timerEntityBaseTick.stopTiming(); // Spigot + this.aiStep(); +- SpigotTimings.timerEntityTickRest.startTiming(); // Spigot + } + + double d0 = this.getX() - this.xo; +@@ -3228,7 +3223,6 @@ public abstract class LivingEntity extends Entity implements Attackable { + } + + this.elytraAnimationState.tick(); +- SpigotTimings.timerEntityTickRest.stopTiming(); // Spigot + } + + public void detectEquipmentUpdatesPublic() { // CraftBukkit +@@ -3435,7 +3429,6 @@ public abstract class LivingEntity extends Entity implements Attackable { + ProfilerFiller gameprofilerfiller = Profiler.get(); + + gameprofilerfiller.push("ai"); +- SpigotTimings.timerEntityAI.startTiming(); // Spigot + if (this.isImmobile()) { + this.jumping = false; + this.xxa = 0.0F; +@@ -3445,7 +3438,6 @@ public abstract class LivingEntity extends Entity implements Attackable { + this.serverAiStep(); + gameprofilerfiller.pop(); + } +- SpigotTimings.timerEntityAI.stopTiming(); // Spigot + + gameprofilerfiller.pop(); + gameprofilerfiller.push("jump"); +@@ -3488,7 +3480,6 @@ public abstract class LivingEntity extends Entity implements Attackable { + this.resetFallDistance(); + } + +- SpigotTimings.timerEntityAIMove.startTiming(); // Spigot + label112: + { + LivingEntity entityliving = this.getControllingPassenger(); +@@ -3502,7 +3493,6 @@ public abstract class LivingEntity extends Entity implements Attackable { + + this.travel(vec3d1); + } +- SpigotTimings.timerEntityAIMove.stopTiming(); // Spigot + + if (!this.level().isClientSide() || this.isControlledByLocalInstance()) { + this.applyEffectsFromBlocks(); +@@ -3538,9 +3528,7 @@ public abstract class LivingEntity extends Entity implements Attackable { + this.checkAutoSpinAttack(axisalignedbb, this.getBoundingBox()); + } + +- SpigotTimings.timerEntityAICollision.startTiming(); // Spigot + this.pushEntities(); +- SpigotTimings.timerEntityAICollision.stopTiming(); // Spigot + gameprofilerfiller.pop(); + world = this.level(); + if (world instanceof ServerLevel worldserver) { +diff --git a/src/main/java/net/minecraft/world/level/Level.java b/src/main/java/net/minecraft/world/level/Level.java +index 3fb17bbcecf6dc4af3b231835adff25f86e1379f..c5e480c43668211a091e44ae50b40b025ff1dca9 100644 +--- a/src/main/java/net/minecraft/world/level/Level.java ++++ b/src/main/java/net/minecraft/world/level/Level.java +@@ -95,7 +95,6 @@ import net.minecraft.network.protocol.game.ClientboundSetBorderWarningDistancePa + import org.bukkit.Bukkit; + import org.bukkit.craftbukkit.CraftServer; + import org.bukkit.craftbukkit.CraftWorld; +-import org.bukkit.craftbukkit.SpigotTimings; // Spigot + import org.bukkit.craftbukkit.block.CapturedBlockState; + import org.bukkit.craftbukkit.block.CraftBlockState; + import org.bukkit.craftbukkit.block.data.CraftBlockData; +@@ -165,7 +164,6 @@ public abstract class Level implements LevelAccessor, AutoCloseable { + } + // Paper end - add paper world config + +- public final SpigotTimings.WorldTimingsHandler timings; // Spigot + public static BlockPos lastPhysicsProblem; // Spigot + private org.spigotmc.TickLimiter entityLimiter; + private org.spigotmc.TickLimiter tileLimiter; +@@ -259,7 +257,6 @@ public abstract class Level implements LevelAccessor, AutoCloseable { + public void onBorderSetDamageSafeZOne(WorldBorder border, double safeZoneRadius) {} + }); + // CraftBukkit end +- this.timings = new SpigotTimings.WorldTimingsHandler(this); // Spigot - code below can generate new world and access timings + this.entityLimiter = new org.spigotmc.TickLimiter(this.spigotConfig.entityMaxTickTime); + this.tileLimiter = new org.spigotmc.TickLimiter(this.spigotConfig.tileMaxTickTime); + } +@@ -692,15 +689,12 @@ public abstract class Level implements LevelAccessor, AutoCloseable { + ProfilerFiller gameprofilerfiller = Profiler.get(); + + gameprofilerfiller.push("blockEntities"); +- this.timings.tileEntityPending.startTiming(); // Spigot + this.tickingBlockEntities = true; + if (!this.pendingBlockEntityTickers.isEmpty()) { + this.blockEntityTickers.addAll(this.pendingBlockEntityTickers); + this.pendingBlockEntityTickers.clear(); + } +- this.timings.tileEntityPending.stopTiming(); // Spigot + +- this.timings.tileEntityTick.startTiming(); // Spigot + // Spigot start + // Iterator iterator = this.blockEntityTickers.iterator(); + boolean flag = this.tickRateManager().runsNormally(); +@@ -723,7 +717,6 @@ public abstract class Level implements LevelAccessor, AutoCloseable { + } + } + +- this.timings.tileEntityTick.stopTiming(); // Spigot + this.tickingBlockEntities = false; + gameprofilerfiller.pop(); + this.spigotConfig.currentPrimedTnt = 0; // Spigot +@@ -731,9 +724,7 @@ public abstract class Level implements LevelAccessor, AutoCloseable { + + public void guardEntityTick(Consumer tickConsumer, T entity) { + try { +- SpigotTimings.tickEntityTimer.startTiming(); // Spigot + tickConsumer.accept(entity); +- SpigotTimings.tickEntityTimer.stopTiming(); // Spigot + } catch (Throwable throwable) { + CrashReport crashreport = CrashReport.forThrowable(throwable, "Ticking entity"); + CrashReportCategory crashreportsystemdetails = crashreport.addCategory("Entity being ticked"); +diff --git a/src/main/java/net/minecraft/world/level/NaturalSpawner.java b/src/main/java/net/minecraft/world/level/NaturalSpawner.java +index 1fe93e01c5e37397aded5d1f99214bf1bffe70b7..9389fd53f2bff0a9ca389694b312dc6da58befaf 100644 +--- a/src/main/java/net/minecraft/world/level/NaturalSpawner.java ++++ b/src/main/java/net/minecraft/world/level/NaturalSpawner.java +@@ -148,7 +148,6 @@ public final class NaturalSpawner { + ProfilerFiller gameprofilerfiller = Profiler.get(); + + gameprofilerfiller.push("spawner"); +- world.timings.mobSpawn.startTiming(); // Spigot + Iterator iterator = spawnableGroups.iterator(); + + while (iterator.hasNext()) { +@@ -163,7 +162,6 @@ public final class NaturalSpawner { + } + } + +- world.timings.mobSpawn.stopTiming(); // Spigot + gameprofilerfiller.pop(); + } + +diff --git a/src/main/java/net/minecraft/world/level/block/entity/BlockEntity.java b/src/main/java/net/minecraft/world/level/block/entity/BlockEntity.java +index 4c3bc3a495990bc486fce7ba1758bf731c3baf02..751a5ae04e4de3f5d85eda20092a87ef4f547436 100644 +--- a/src/main/java/net/minecraft/world/level/block/entity/BlockEntity.java ++++ b/src/main/java/net/minecraft/world/level/block/entity/BlockEntity.java +@@ -33,11 +33,8 @@ import org.bukkit.craftbukkit.persistence.CraftPersistentDataTypeRegistry; + import org.bukkit.inventory.InventoryHolder; + // CraftBukkit end + +-import org.spigotmc.CustomTimingsHandler; // Spigot +- + public abstract class BlockEntity { + +- public CustomTimingsHandler tickTimer = org.bukkit.craftbukkit.SpigotTimings.getTileEntityTimings(this); // Spigot + // CraftBukkit start - data containers + private static final CraftPersistentDataTypeRegistry DATA_TYPE_REGISTRY = new CraftPersistentDataTypeRegistry(); + public CraftPersistentDataContainer persistentDataContainer; +diff --git a/src/main/java/net/minecraft/world/level/chunk/LevelChunk.java b/src/main/java/net/minecraft/world/level/chunk/LevelChunk.java +index d76591694c3b167b8b8f17b61a373a43140a8b68..f63d55bfe42b117c5b437e690124a98d94752a9a 100644 +--- a/src/main/java/net/minecraft/world/level/chunk/LevelChunk.java ++++ b/src/main/java/net/minecraft/world/level/chunk/LevelChunk.java +@@ -971,7 +971,6 @@ public class LevelChunk extends ChunkAccess { + ProfilerFiller gameprofilerfiller = Profiler.get(); + + gameprofilerfiller.push(this::getType); +- this.blockEntity.tickTimer.startTiming(); // Spigot + BlockState iblockdata = LevelChunk.this.getBlockState(blockposition); + + if (this.blockEntity.getType().isValid(iblockdata)) { +@@ -990,9 +989,6 @@ public class LevelChunk extends ChunkAccess { + this.blockEntity.fillCrashReportCategory(crashreportsystemdetails); + throw new ReportedException(crashreport); + // Spigot start +- } finally { +- this.blockEntity.tickTimer.stopTiming(); +- // Spigot end + } + } + } +diff --git a/src/main/java/net/minecraft/world/level/chunk/storage/SerializableChunkData.java b/src/main/java/net/minecraft/world/level/chunk/storage/SerializableChunkData.java +index d1b82dec25069a7027aaf53086b1829e511fc301..4367ccc628bb4f404d6a081083002518442f462b 100644 +--- a/src/main/java/net/minecraft/world/level/chunk/storage/SerializableChunkData.java ++++ b/src/main/java/net/minecraft/world/level/chunk/storage/SerializableChunkData.java +@@ -576,15 +576,12 @@ public record SerializableChunkData(Registry biomeRegistry, ChunkPos chun + @Nullable + private static LevelChunk.PostLoadProcessor postLoadChunk(ServerLevel world, List entities, List blockEntities) { + return entities.isEmpty() && blockEntities.isEmpty() ? null : (chunk) -> { +- world.timings.syncChunkLoadEntitiesTimer.startTiming(); // Spigot + if (!entities.isEmpty()) { + world.addLegacyChunkEntities(EntityType.loadEntitiesRecursive(entities, world, EntitySpawnReason.LOAD)); + } +- world.timings.syncChunkLoadEntitiesTimer.stopTiming(); // Spigot + + Iterator iterator = blockEntities.iterator(); + +- world.timings.syncChunkLoadTileEntitiesTimer.startTiming(); // Spigot + while (iterator.hasNext()) { + CompoundTag nbttagcompound = (CompoundTag) iterator.next(); + boolean flag = nbttagcompound.getBoolean("keepPacked"); +@@ -600,7 +597,6 @@ public record SerializableChunkData(Registry biomeRegistry, ChunkPos chun + } + } + } +- world.timings.syncChunkLoadTileEntitiesTimer.stopTiming(); // Spigot + + }; + } +diff --git a/src/main/java/org/bukkit/craftbukkit/CraftServer.java b/src/main/java/org/bukkit/craftbukkit/CraftServer.java +index 7c5b0db8115dc4032a3a364299ca06c88efd9a26..5650b4cfcd4008ac7f351d5bfb1fb8cc81da4caa 100644 +--- a/src/main/java/org/bukkit/craftbukkit/CraftServer.java ++++ b/src/main/java/org/bukkit/craftbukkit/CraftServer.java +@@ -379,7 +379,6 @@ public final class CraftServer implements Server { + this.saveCommandsConfig(); + this.overrideAllCommandBlockCommands = this.commandsConfiguration.getStringList("command-block-overrides").contains("*"); + this.ignoreVanillaPermissions = this.commandsConfiguration.getBoolean("ignore-vanilla-permissions"); +- this.pluginManager.useTimings(this.configuration.getBoolean("settings.plugin-profiling")); + this.overrideSpawnLimits(); + console.autosavePeriod = this.configuration.getInt("ticks-per.autosave"); + this.warningState = WarningState.value(this.configuration.getString("settings.deprecated-verbose")); +@@ -2636,12 +2635,31 @@ public final class CraftServer implements Server { + private final org.bukkit.Server.Spigot spigot = new org.bukkit.Server.Spigot() + { + ++ @Deprecated + @Override + public YamlConfiguration getConfig() + { + return org.spigotmc.SpigotConfig.config; + } + ++ @Override ++ public YamlConfiguration getBukkitConfig() ++ { ++ return configuration; ++ } ++ ++ @Override ++ public YamlConfiguration getSpigotConfig() ++ { ++ return org.spigotmc.SpigotConfig.config; ++ } ++ ++ @Override ++ public YamlConfiguration getPaperConfig() ++ { ++ return CraftServer.this.console.paperConfigurations.createLegacyObject(CraftServer.this.console); ++ } ++ + @Override + public void restart() { + org.spigotmc.RestartCommand.restart(); +diff --git a/src/main/java/org/bukkit/craftbukkit/SpigotTimings.java b/src/main/java/org/bukkit/craftbukkit/SpigotTimings.java +deleted file mode 100644 +index b0ffa23faf62629043dfd613315eaf9c5fcc2cfe..0000000000000000000000000000000000000000 +--- a/src/main/java/org/bukkit/craftbukkit/SpigotTimings.java ++++ /dev/null +@@ -1,163 +0,0 @@ +-package org.bukkit.craftbukkit; +- +-import java.util.HashMap; +-import net.minecraft.world.entity.Entity; +-import net.minecraft.world.level.Level; +-import net.minecraft.world.level.block.entity.BlockEntity; +-import net.minecraft.world.level.storage.PrimaryLevelData; +-import org.bukkit.craftbukkit.scheduler.CraftTask; +-import org.bukkit.plugin.java.JavaPluginLoader; +-import org.bukkit.scheduler.BukkitTask; +-import org.spigotmc.CustomTimingsHandler; +- +-public class SpigotTimings { +- +- public static final CustomTimingsHandler serverTickTimer = new CustomTimingsHandler("** Full Server Tick"); +- public static final CustomTimingsHandler playerListTimer = new CustomTimingsHandler("Player List"); +- public static final CustomTimingsHandler commandFunctionsTimer = new CustomTimingsHandler("Command Functions"); +- public static final CustomTimingsHandler connectionTimer = new CustomTimingsHandler("Connection Handler"); +- public static final CustomTimingsHandler playerConnectionTimer = new CustomTimingsHandler("** PlayerConnection"); +- public static final CustomTimingsHandler tickablesTimer = new CustomTimingsHandler("Tickables"); +- public static final CustomTimingsHandler schedulerTimer = new CustomTimingsHandler("Scheduler"); +- public static final CustomTimingsHandler timeUpdateTimer = new CustomTimingsHandler("Time Update"); +- public static final CustomTimingsHandler serverCommandTimer = new CustomTimingsHandler("Server Command"); +- public static final CustomTimingsHandler worldSaveTimer = new CustomTimingsHandler("World Save"); +- +- public static final CustomTimingsHandler entityMoveTimer = new CustomTimingsHandler("** entityMove"); +- public static final CustomTimingsHandler tickEntityTimer = new CustomTimingsHandler("** tickEntity"); +- public static final CustomTimingsHandler activatedEntityTimer = new CustomTimingsHandler("** activatedTickEntity"); +- public static final CustomTimingsHandler tickTileEntityTimer = new CustomTimingsHandler("** tickTileEntity"); +- +- public static final CustomTimingsHandler timerEntityBaseTick = new CustomTimingsHandler("** livingEntityBaseTick"); +- public static final CustomTimingsHandler timerEntityAI = new CustomTimingsHandler("** livingEntityAI"); +- public static final CustomTimingsHandler timerEntityAICollision = new CustomTimingsHandler("** livingEntityAICollision"); +- public static final CustomTimingsHandler timerEntityAIMove = new CustomTimingsHandler("** livingEntityAIMove"); +- public static final CustomTimingsHandler timerEntityTickRest = new CustomTimingsHandler("** livingEntityTickRest"); +- +- public static final CustomTimingsHandler processQueueTimer = new CustomTimingsHandler("processQueue"); +- public static final CustomTimingsHandler schedulerSyncTimer = new CustomTimingsHandler("** Scheduler - Sync Tasks", JavaPluginLoader.pluginParentTimer); +- +- public static final CustomTimingsHandler playerCommandTimer = new CustomTimingsHandler("** playerCommand"); +- +- public static final CustomTimingsHandler entityActivationCheckTimer = new CustomTimingsHandler("entityActivationCheck"); +- public static final CustomTimingsHandler checkIfActiveTimer = new CustomTimingsHandler("** checkIfActive"); +- +- public static final HashMap entityTypeTimingMap = new HashMap(); +- public static final HashMap tileEntityTypeTimingMap = new HashMap(); +- public static final HashMap pluginTaskTimingMap = new HashMap(); +- +- /** +- * Gets a timer associated with a plugins tasks. +- * @param task +- * @param period +- * @return +- */ +- public static CustomTimingsHandler getPluginTaskTimings(BukkitTask task, long period) { +- if (!task.isSync()) { +- return null; +- } +- String plugin; +- final CraftTask ctask = (CraftTask) task; +- +- if (task.getOwner() != null) { +- plugin = task.getOwner().getDescription().getFullName(); +- } else { +- plugin = "Unknown"; +- } +- String taskname = ctask.getTaskName(); +- +- String name = "Task: " + plugin + " Runnable: " + taskname; +- if (period > 0) { +- name += "(interval:" + period + ")"; +- } else { +- name += "(Single)"; +- } +- CustomTimingsHandler result = SpigotTimings.pluginTaskTimingMap.get(name); +- if (result == null) { +- result = new CustomTimingsHandler(name, SpigotTimings.schedulerSyncTimer); +- SpigotTimings.pluginTaskTimingMap.put(name, result); +- } +- return result; +- } +- +- /** +- * Get a named timer for the specified entity type to track type specific timings. +- * @param entity +- * @return +- */ +- public static CustomTimingsHandler getEntityTimings(Entity entity) { +- String entityType = entity.getClass().getName(); +- CustomTimingsHandler result = SpigotTimings.entityTypeTimingMap.get(entityType); +- if (result == null) { +- result = new CustomTimingsHandler("** tickEntity - " + entity.getClass().getSimpleName(), SpigotTimings.activatedEntityTimer); +- SpigotTimings.entityTypeTimingMap.put(entityType, result); +- } +- return result; +- } +- +- /** +- * Get a named timer for the specified tile entity type to track type specific timings. +- * @param entity +- * @return +- */ +- public static CustomTimingsHandler getTileEntityTimings(BlockEntity entity) { +- String entityType = entity.getClass().getName(); +- CustomTimingsHandler result = SpigotTimings.tileEntityTypeTimingMap.get(entityType); +- if (result == null) { +- result = new CustomTimingsHandler("** tickTileEntity - " + entity.getClass().getSimpleName(), SpigotTimings.tickTileEntityTimer); +- SpigotTimings.tileEntityTypeTimingMap.put(entityType, result); +- } +- return result; +- } +- +- /** +- * Set of timers per world, to track world specific timings. +- */ +- public static class WorldTimingsHandler { +- public final CustomTimingsHandler mobSpawn; +- public final CustomTimingsHandler doChunkUnload; +- public final CustomTimingsHandler doTickPending; +- public final CustomTimingsHandler doTickTiles; +- public final CustomTimingsHandler doChunkMap; +- public final CustomTimingsHandler doSounds; +- public final CustomTimingsHandler entityTick; +- public final CustomTimingsHandler tileEntityTick; +- public final CustomTimingsHandler tileEntityPending; +- public final CustomTimingsHandler tracker; +- public final CustomTimingsHandler doTick; +- public final CustomTimingsHandler tickEntities; +- +- public final CustomTimingsHandler syncChunkLoadTimer; +- public final CustomTimingsHandler syncChunkLoadStructuresTimer; +- public final CustomTimingsHandler syncChunkLoadEntitiesTimer; +- public final CustomTimingsHandler syncChunkLoadTileEntitiesTimer; +- public final CustomTimingsHandler syncChunkLoadTileTicksTimer; +- public final CustomTimingsHandler syncChunkLoadPostTimer; +- +- public WorldTimingsHandler(Level server) { +- String name = ((PrimaryLevelData) server.levelData).getLevelName() + " - "; +- +- this.mobSpawn = new CustomTimingsHandler("** " + name + "mobSpawn"); +- this.doChunkUnload = new CustomTimingsHandler("** " + name + "doChunkUnload"); +- this.doTickPending = new CustomTimingsHandler("** " + name + "doTickPending"); +- this.doTickTiles = new CustomTimingsHandler("** " + name + "doTickTiles"); +- this.doChunkMap = new CustomTimingsHandler("** " + name + "doChunkMap"); +- this.doSounds = new CustomTimingsHandler("** " + name + "doSounds"); +- this.entityTick = new CustomTimingsHandler("** " + name + "entityTick"); +- this.tileEntityTick = new CustomTimingsHandler("** " + name + "tileEntityTick"); +- this.tileEntityPending = new CustomTimingsHandler("** " + name + "tileEntityPending"); +- +- this.syncChunkLoadTimer = new CustomTimingsHandler("** " + name + "syncChunkLoad"); +- this.syncChunkLoadStructuresTimer = new CustomTimingsHandler("** " + name + "chunkLoad - Structures"); +- this.syncChunkLoadEntitiesTimer = new CustomTimingsHandler("** " + name + "chunkLoad - Entities"); +- this.syncChunkLoadTileEntitiesTimer = new CustomTimingsHandler("** " + name + "chunkLoad - TileEntities"); +- this.syncChunkLoadTileTicksTimer = new CustomTimingsHandler("** " + name + "chunkLoad - TileTicks"); +- this.syncChunkLoadPostTimer = new CustomTimingsHandler("** " + name + "chunkLoad - Post"); +- +- +- this.tracker = new CustomTimingsHandler(name + "tracker"); +- this.doTick = new CustomTimingsHandler(name + "doTick"); +- this.tickEntities = new CustomTimingsHandler(name + "tickEntities"); +- } +- } +-} +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java +index 24631135f90bb74bf829160ed079e152573666a2..f7833cd528797ba46b001db5208c29eb11ae2529 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java +@@ -2809,6 +2809,14 @@ public class CraftPlayer extends CraftHumanEntity implements Player { + + CraftPlayer.this.getHandle().connection.send(new net.minecraft.network.protocol.game.ClientboundSystemChatPacket(components, position == net.md_5.bungee.api.ChatMessageType.ACTION_BAR)); + } ++ ++ // Paper start ++ @Override ++ public int getPing() ++ { ++ return CraftPlayer.this.getPing(); ++ } ++ // Paper end + }; + + public Player.Spigot spigot() +diff --git a/src/main/java/org/bukkit/craftbukkit/scheduler/CraftScheduler.java b/src/main/java/org/bukkit/craftbukkit/scheduler/CraftScheduler.java +index 6effe47b32a8551aa6f6b11bc0315714a119e199..2c36d0796714997191c6540c34a9df60718065f6 100644 +--- a/src/main/java/org/bukkit/craftbukkit/scheduler/CraftScheduler.java ++++ b/src/main/java/org/bukkit/craftbukkit/scheduler/CraftScheduler.java +@@ -413,9 +413,7 @@ public class CraftScheduler implements BukkitScheduler { + if (task.isSync()) { + this.currentTask = task; + try { +- task.timings.startTiming(); // Spigot + task.run(); +- task.timings.stopTiming(); // Spigot + } catch (final Throwable throwable) { + task.getOwner().getLogger().log( + Level.WARNING, +diff --git a/src/main/java/org/bukkit/craftbukkit/scheduler/CraftTask.java b/src/main/java/org/bukkit/craftbukkit/scheduler/CraftTask.java +index e4d1eb4a0ce2c9874922585f6bb0d9ead433fde1..17680f112d0c7e7aee07e34477daa21ef2ddaa6f 100644 +--- a/src/main/java/org/bukkit/craftbukkit/scheduler/CraftTask.java ++++ b/src/main/java/org/bukkit/craftbukkit/scheduler/CraftTask.java +@@ -1,13 +1,11 @@ + package org.bukkit.craftbukkit.scheduler; + + import java.util.function.Consumer; ++ + import org.bukkit.Bukkit; + import org.bukkit.plugin.Plugin; + import org.bukkit.scheduler.BukkitTask; + +-import org.bukkit.craftbukkit.SpigotTimings; // Spigot +-import org.spigotmc.CustomTimingsHandler; // Spigot +- + public class CraftTask implements BukkitTask, Runnable { // Spigot + + private volatile CraftTask next = null; +@@ -26,13 +24,12 @@ public class CraftTask implements BukkitTask, Runnable { // Spigot + */ + private volatile long period; + private long nextRun; +- private final Runnable rTask; +- private final Consumer cTask; ++ public final Runnable rTask; ++ public final Consumer cTask; + private final Plugin plugin; + private final int id; + private final long createdAt = System.nanoTime(); + +- final CustomTimingsHandler timings; // Spigot + CraftTask() { + this(null, null, CraftTask.NO_REPEATING, CraftTask.NO_REPEATING); + } +@@ -58,7 +55,6 @@ public class CraftTask implements BukkitTask, Runnable { // Spigot + } + this.id = id; + this.period = period; +- this.timings = this.isSync() ? SpigotTimings.getPluginTaskTimings(this, period) : null; // Spigot + } + + @Override +@@ -137,9 +133,4 @@ public class CraftTask implements BukkitTask, Runnable { // Spigot + return true; + } + +- // Spigot start +- public String getTaskName() { +- return (this.getTaskClass() == null) ? "Unknown" : this.getTaskClass().getName(); +- } +- // Spigot end + } +diff --git a/src/main/java/org/bukkit/craftbukkit/util/CraftIconCache.java b/src/main/java/org/bukkit/craftbukkit/util/CraftIconCache.java +index f97eccb6a17c7876e1e002d798eb67bbe80571a0..dba31a2cbcfebe1f28883545ce4a70fcb9251aa6 100644 +--- a/src/main/java/org/bukkit/craftbukkit/util/CraftIconCache.java ++++ b/src/main/java/org/bukkit/craftbukkit/util/CraftIconCache.java +@@ -1,6 +1,7 @@ + package org.bukkit.craftbukkit.util; + + import org.bukkit.util.CachedServerIcon; ++import org.jetbrains.annotations.Nullable; + + public class CraftIconCache implements CachedServerIcon { + public final byte[] value; +@@ -8,4 +9,12 @@ public class CraftIconCache implements CachedServerIcon { + public CraftIconCache(final byte[] value) { + this.value = value; + } ++ ++ @Override ++ public @Nullable String getData() { ++ if (this.value == null) { ++ return null; ++ } ++ return "data:image/png;base64," + new String(java.util.Base64.getEncoder().encode(this.value), java.nio.charset.StandardCharsets.UTF_8); ++ } + } +diff --git a/src/main/java/org/spigotmc/ActivationRange.java b/src/main/java/org/spigotmc/ActivationRange.java +index 3591b79481ac17bd02e59ac3c623d1c6991abd84..263df52c7f5e172a6b9118b4bc4672e443adedba 100644 +--- a/src/main/java/org/spigotmc/ActivationRange.java ++++ b/src/main/java/org/spigotmc/ActivationRange.java +@@ -27,7 +27,6 @@ import net.minecraft.world.entity.projectile.ThrownTrident; + import net.minecraft.world.entity.raid.Raider; + import net.minecraft.world.level.Level; + import net.minecraft.world.phys.AABB; +-import org.bukkit.craftbukkit.SpigotTimings; + + public class ActivationRange + { +@@ -110,7 +109,6 @@ public class ActivationRange + */ + public static void activateEntities(Level world) + { +- SpigotTimings.entityActivationCheckTimer.startTiming(); + final int miscActivationRange = world.spigotConfig.miscActivationRange; + final int raiderActivationRange = world.spigotConfig.raiderActivationRange; + final int animalActivationRange = world.spigotConfig.animalActivationRange; +@@ -137,7 +135,6 @@ public class ActivationRange + + world.getEntities().get(ActivationRange.maxBB, ActivationRange::activateEntity); + } +- SpigotTimings.entityActivationCheckTimer.stopTiming(); + } + + /** +@@ -232,10 +229,8 @@ public class ActivationRange + */ + public static boolean checkIfActive(Entity entity) + { +- SpigotTimings.checkIfActiveTimer.startTiming(); + // Never safe to skip fireworks or entities not yet added to chunk + if ( entity instanceof FireworkRocketEntity ) { +- SpigotTimings.checkIfActiveTimer.stopTiming(); + return true; + } + +@@ -259,7 +254,6 @@ public class ActivationRange + { + isActive = false; + } +- SpigotTimings.checkIfActiveTimer.stopTiming(); + return isActive; + } + } diff --git a/patches/server/0025-Add-command-line-option-to-load-extra-plugin-jars-no.patch b/patches/server/0025-Add-command-line-option-to-load-extra-plugin-jars-no.patch index 3fce66b91896..5e58214e0f38 100644 --- a/patches/server/0025-Add-command-line-option-to-load-extra-plugin-jars-no.patch +++ b/patches/server/0025-Add-command-line-option-to-load-extra-plugin-jars-no.patch @@ -7,10 +7,10 @@ Subject: [PATCH] Add command line option to load extra plugin jars not in the ex: java -jar paperclip.jar nogui -add-plugin=/path/to/plugin.jar -add-plugin=/path/to/another/plugin_jar.jar diff --git a/src/main/java/org/bukkit/craftbukkit/CraftServer.java b/src/main/java/org/bukkit/craftbukkit/CraftServer.java -index 2c3efcf07f933278a8b897cf7b1f6f24b452a7c5..b8b2e582115cc42a913b03a35861fa810d774cbb 100644 +index 5650b4cfcd4008ac7f351d5bfb1fb8cc81da4caa..db7cad60c8f805dd1b4089673f5f9d073a429a67 100644 --- a/src/main/java/org/bukkit/craftbukkit/CraftServer.java +++ b/src/main/java/org/bukkit/craftbukkit/CraftServer.java -@@ -459,6 +459,35 @@ public final class CraftServer implements Server { +@@ -461,6 +461,35 @@ public final class CraftServer implements Server { io.papermc.paper.plugin.entrypoint.LaunchEntryPointHandler.INSTANCE.enter(io.papermc.paper.plugin.entrypoint.Entrypoint.PLUGIN); // Paper - replace implementation } @@ -47,7 +47,7 @@ index 2c3efcf07f933278a8b897cf7b1f6f24b452a7c5..b8b2e582115cc42a913b03a35861fa81 if (type == PluginLoadOrder.STARTUP) { this.helpMap.clear(); diff --git a/src/main/java/org/bukkit/craftbukkit/Main.java b/src/main/java/org/bukkit/craftbukkit/Main.java -index bdb8e882ec1efbe5afec9ec5a5df57bf38d4ba2b..034d68c2715b6a90f31e56f949ff3d27235a26eb 100644 +index 4a99cf5a146abe0d2b40ffc1189fdc5540f14d55..03790abcc3474999db6d8986e50ebc2caf6eba0c 100644 --- a/src/main/java/org/bukkit/craftbukkit/Main.java +++ b/src/main/java/org/bukkit/craftbukkit/Main.java @@ -160,6 +160,12 @@ public class Main { diff --git a/patches/server/0026-Support-components-in-ItemMeta.patch b/patches/server/0026-Support-components-in-ItemMeta.patch index 55e209aaed08..e9fa61498eeb 100644 --- a/patches/server/0026-Support-components-in-ItemMeta.patch +++ b/patches/server/0026-Support-components-in-ItemMeta.patch @@ -5,10 +5,10 @@ Subject: [PATCH] Support components in ItemMeta diff --git a/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaItem.java b/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaItem.java -index 0c73854243f7fa21d1ffdb3b4c85ee0a69c9c5e4..f6ac13f91f08498a8adda7d34518a5cfe34c15b2 100644 +index 626fe2af05fecd41b777b5dd5decbedb2f17b43a..1c943638bfbda8281d2c9038e9024591823e2b5e 100644 --- a/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaItem.java +++ b/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaItem.java -@@ -978,11 +978,23 @@ class CraftMetaItem implements ItemMeta, Damageable, Repairable, BlockDataMeta { +@@ -1114,11 +1114,23 @@ class CraftMetaItem implements ItemMeta, Damageable, Repairable, BlockDataMeta { return CraftChatMessage.fromComponent(this.displayName); } @@ -32,7 +32,7 @@ index 0c73854243f7fa21d1ffdb3b4c85ee0a69c9c5e4..f6ac13f91f08498a8adda7d34518a5cf @Override public boolean hasDisplayName() { return this.displayName != null; -@@ -1156,6 +1168,14 @@ class CraftMetaItem implements ItemMeta, Damageable, Repairable, BlockDataMeta { +@@ -1292,6 +1304,14 @@ class CraftMetaItem implements ItemMeta, Damageable, Repairable, BlockDataMeta { return this.lore == null ? null : new ArrayList(Lists.transform(this.lore, CraftChatMessage::fromComponent)); } @@ -47,7 +47,7 @@ index 0c73854243f7fa21d1ffdb3b4c85ee0a69c9c5e4..f6ac13f91f08498a8adda7d34518a5cf @Override public void setLore(List lore) { if (lore == null || lore.isEmpty()) { -@@ -1170,6 +1190,21 @@ class CraftMetaItem implements ItemMeta, Damageable, Repairable, BlockDataMeta { +@@ -1306,6 +1326,21 @@ class CraftMetaItem implements ItemMeta, Damageable, Repairable, BlockDataMeta { } } @@ -69,7 +69,7 @@ index 0c73854243f7fa21d1ffdb3b4c85ee0a69c9c5e4..f6ac13f91f08498a8adda7d34518a5cf @Override public boolean hasCustomModelData() { return this.customModelData != null; -@@ -1882,6 +1917,11 @@ class CraftMetaItem implements ItemMeta, Damageable, Repairable, BlockDataMeta { +@@ -2188,6 +2223,11 @@ class CraftMetaItem implements ItemMeta, Damageable, Repairable, BlockDataMeta { } for (Object object : addFrom) { diff --git a/patches/server/0027-Configurable-cactus-bamboo-and-reed-growth-height.patch b/patches/server/0027-Configurable-cactus-bamboo-and-reed-growth-height.patch index d25f48a93d78..e5e4b807eed1 100644 --- a/patches/server/0027-Configurable-cactus-bamboo-and-reed-growth-height.patch +++ b/patches/server/0027-Configurable-cactus-bamboo-and-reed-growth-height.patch @@ -7,7 +7,7 @@ Bamboo - Both the minimum fully-grown height and the maximum are configurable - Machine_Maker diff --git a/src/main/java/net/minecraft/world/level/block/BambooStalkBlock.java b/src/main/java/net/minecraft/world/level/block/BambooStalkBlock.java -index eda75b316acd09120539c92ff8adb97d92e9523f..e2951dd077441fe9cda461a2d3ef0c0671308316 100644 +index 80bf98e7681cfde3a41ce5676425d5e96089500d..5e88bd02f5c53124f1aeec3eae727a1f83cc8238 100644 --- a/src/main/java/net/minecraft/world/level/block/BambooStalkBlock.java +++ b/src/main/java/net/minecraft/world/level/block/BambooStalkBlock.java @@ -137,7 +137,7 @@ public class BambooStalkBlock extends Block implements BonemealableBlock { @@ -19,7 +19,7 @@ index eda75b316acd09120539c92ff8adb97d92e9523f..e2951dd077441fe9cda461a2d3ef0c06 this.growBamboo(state, world, pos, random, i); } } -@@ -168,7 +168,7 @@ public class BambooStalkBlock extends Block implements BonemealableBlock { +@@ -164,7 +164,7 @@ public class BambooStalkBlock extends Block implements BonemealableBlock { int i = this.getHeightAboveUpToMax(world, pos); int j = this.getHeightBelowUpToMax(world, pos); @@ -28,7 +28,7 @@ index eda75b316acd09120539c92ff8adb97d92e9523f..e2951dd077441fe9cda461a2d3ef0c06 } @Override -@@ -187,7 +187,7 @@ public class BambooStalkBlock extends Block implements BonemealableBlock { +@@ -183,7 +183,7 @@ public class BambooStalkBlock extends Block implements BonemealableBlock { BlockPos blockposition1 = pos.above(i); BlockState iblockdata1 = world.getBlockState(blockposition1); @@ -37,7 +37,7 @@ index eda75b316acd09120539c92ff8adb97d92e9523f..e2951dd077441fe9cda461a2d3ef0c06 return; } -@@ -228,7 +228,7 @@ public class BambooStalkBlock extends Block implements BonemealableBlock { +@@ -224,7 +224,7 @@ public class BambooStalkBlock extends Block implements BonemealableBlock { } int j = (Integer) state.getValue(BambooStalkBlock.AGE) != 1 && !iblockdata2.is(Blocks.BAMBOO) ? 0 : 1; @@ -46,7 +46,7 @@ index eda75b316acd09120539c92ff8adb97d92e9523f..e2951dd077441fe9cda461a2d3ef0c06 // CraftBukkit start if (org.bukkit.craftbukkit.event.CraftEventFactory.handleBlockSpreadEvent(world, pos, pos.above(), (BlockState) ((BlockState) ((BlockState) this.defaultBlockState().setValue(BambooStalkBlock.AGE, j)).setValue(BambooStalkBlock.LEAVES, blockpropertybamboosize)).setValue(BambooStalkBlock.STAGE, k), 3)) { -@@ -243,7 +243,7 @@ public class BambooStalkBlock extends Block implements BonemealableBlock { +@@ -239,7 +239,7 @@ public class BambooStalkBlock extends Block implements BonemealableBlock { protected int getHeightAboveUpToMax(BlockGetter world, BlockPos pos) { int i; @@ -55,7 +55,7 @@ index eda75b316acd09120539c92ff8adb97d92e9523f..e2951dd077441fe9cda461a2d3ef0c06 ; } -@@ -253,7 +253,7 @@ public class BambooStalkBlock extends Block implements BonemealableBlock { +@@ -249,7 +249,7 @@ public class BambooStalkBlock extends Block implements BonemealableBlock { protected int getHeightBelowUpToMax(BlockGetter world, BlockPos pos) { int i; @@ -65,10 +65,10 @@ index eda75b316acd09120539c92ff8adb97d92e9523f..e2951dd077441fe9cda461a2d3ef0c06 } diff --git a/src/main/java/net/minecraft/world/level/block/CactusBlock.java b/src/main/java/net/minecraft/world/level/block/CactusBlock.java -index c7e462a187196da906aec3b528f7945afec9f6b0..fd344c5cf0d6d523abe34d5e3f8d939106942cbb 100644 +index de3df6606979171fd39fa6c5207fd9b0b668ba4e..de1b64e0cbe7f2de63f04262428c9e6ec340916e 100644 --- a/src/main/java/net/minecraft/world/level/block/CactusBlock.java +++ b/src/main/java/net/minecraft/world/level/block/CactusBlock.java -@@ -61,7 +61,7 @@ public class CactusBlock extends Block { +@@ -62,7 +62,7 @@ public class CactusBlock extends Block { ; } @@ -78,7 +78,7 @@ index c7e462a187196da906aec3b528f7945afec9f6b0..fd344c5cf0d6d523abe34d5e3f8d9391 int modifier = world.spigotConfig.cactusModifier; // Spigot - SPIGOT-7159: Better modifier resolution diff --git a/src/main/java/net/minecraft/world/level/block/SugarCaneBlock.java b/src/main/java/net/minecraft/world/level/block/SugarCaneBlock.java -index f034a7dbc0124353f8cb9b2c841226e73d83423a..c48c622e92cedeaa46b929c7adfedec98dd5a3fb 100644 +index 161f70de43105c0b49b6c4d0a371dc6036c6813c..547ea09ed84595286c97c128b3b96f6d387ae25f 100644 --- a/src/main/java/net/minecraft/world/level/block/SugarCaneBlock.java +++ b/src/main/java/net/minecraft/world/level/block/SugarCaneBlock.java @@ -59,7 +59,7 @@ public class SugarCaneBlock extends Block { diff --git a/patches/server/0028-Configurable-baby-zombie-movement-speed.patch b/patches/server/0028-Configurable-baby-zombie-movement-speed.patch index a55a0a394400..a7c6ba5835a4 100644 --- a/patches/server/0028-Configurable-baby-zombie-movement-speed.patch +++ b/patches/server/0028-Configurable-baby-zombie-movement-speed.patch @@ -5,10 +5,10 @@ Subject: [PATCH] Configurable baby zombie movement speed diff --git a/src/main/java/net/minecraft/world/entity/monster/Zombie.java b/src/main/java/net/minecraft/world/entity/monster/Zombie.java -index 60a9db4131bcf69a33003b83db6117c9a7a83276..393a9c704f4637a0e8031328d2a0facef4723dd8 100644 +index 6845a8e13cdc9dd03015ac53b2a62dd706def5cd..3836d9255ac326a7220e1decd2e9d98be7884c17 100644 --- a/src/main/java/net/minecraft/world/entity/monster/Zombie.java +++ b/src/main/java/net/minecraft/world/entity/monster/Zombie.java -@@ -75,7 +75,7 @@ import org.bukkit.event.entity.EntityTransformEvent; +@@ -77,7 +77,7 @@ import org.bukkit.event.entity.EntityTransformEvent; public class Zombie extends Monster { private static final ResourceLocation SPEED_MODIFIER_BABY_ID = ResourceLocation.withDefaultNamespace("baby"); @@ -17,7 +17,7 @@ index 60a9db4131bcf69a33003b83db6117c9a7a83276..393a9c704f4637a0e8031328d2a0face private static final ResourceLocation REINFORCEMENT_CALLER_CHARGE_ID = ResourceLocation.withDefaultNamespace("reinforcement_caller_charge"); private static final AttributeModifier ZOMBIE_REINFORCEMENT_CALLEE_CHARGE = new AttributeModifier(ResourceLocation.withDefaultNamespace("reinforcement_callee_charge"), -0.05000000074505806D, AttributeModifier.Operation.ADD_VALUE); private static final ResourceLocation LEADER_ZOMBIE_BONUS_ID = ResourceLocation.withDefaultNamespace("leader_zombie_bonus"); -@@ -188,9 +188,9 @@ public class Zombie extends Monster { +@@ -186,9 +186,9 @@ public class Zombie extends Monster { if (this.level() != null && !this.level().isClientSide) { AttributeInstance attributemodifiable = this.getAttribute(Attributes.MOVEMENT_SPEED); diff --git a/patches/server/0029-Configurable-fishing-time-ranges.patch b/patches/server/0029-Configurable-fishing-time-ranges.patch index 90d886761f89..4599ac587dab 100644 --- a/patches/server/0029-Configurable-fishing-time-ranges.patch +++ b/patches/server/0029-Configurable-fishing-time-ranges.patch @@ -5,11 +5,11 @@ Subject: [PATCH] Configurable fishing time ranges diff --git a/src/main/java/net/minecraft/world/entity/projectile/FishingHook.java b/src/main/java/net/minecraft/world/entity/projectile/FishingHook.java -index 0acb45014039d4392988c7d853595f96e856af4a..ed43ad94ca007a54e3c32d5e17c141048eeb5835 100644 +index 0f3e3f1b918ed2971440b7414ae62ee018a85b3e..ed378bc8135c329cb7423da06eb26fff69ee4954 100644 --- a/src/main/java/net/minecraft/world/entity/projectile/FishingHook.java +++ b/src/main/java/net/minecraft/world/entity/projectile/FishingHook.java -@@ -94,6 +94,10 @@ public class FishingHook extends Projectile { - this.noCulling = true; +@@ -93,6 +93,10 @@ public class FishingHook extends Projectile { + this.currentState = FishingHook.FishHookState.FLYING; this.luck = Math.max(0, luckBonus); this.lureSpeed = Math.max(0, waitTimeReductionTicks); + // Paper start - Configurable fishing time ranges @@ -19,7 +19,7 @@ index 0acb45014039d4392988c7d853595f96e856af4a..ed43ad94ca007a54e3c32d5e17c14104 } public FishingHook(EntityType type, Level world) { -@@ -411,7 +415,7 @@ public class FishingHook extends Projectile { +@@ -416,7 +420,7 @@ public class FishingHook extends Projectile { } else { // CraftBukkit start - logic to modify fishing wait time this.timeUntilLured = Mth.nextInt(this.random, this.minWaitTime, this.maxWaitTime); diff --git a/patches/server/0030-Allow-nerfed-mobs-to-jump.patch b/patches/server/0030-Allow-nerfed-mobs-to-jump.patch index a43842f5bcb1..9aa6c3381e44 100644 --- a/patches/server/0030-Allow-nerfed-mobs-to-jump.patch +++ b/patches/server/0030-Allow-nerfed-mobs-to-jump.patch @@ -5,10 +5,10 @@ Subject: [PATCH] Allow nerfed mobs to jump diff --git a/src/main/java/net/minecraft/world/entity/Mob.java b/src/main/java/net/minecraft/world/entity/Mob.java -index 930b5002aa6eaa1137314f7b38fad99778b6edaa..0593d828c911c94c9833bf12b9c294e5dac1f4e8 100644 +index 02a2fdf2a4bdfb390d23bf45211b71798de422fa..9b26f6a7526f875535738b1f22d9aa458845eb8e 100644 --- a/src/main/java/net/minecraft/world/entity/Mob.java +++ b/src/main/java/net/minecraft/world/entity/Mob.java -@@ -124,6 +124,7 @@ public abstract class Mob extends LivingEntity implements EquipmentUser, Leashab +@@ -127,6 +127,7 @@ public abstract class Mob extends LivingEntity implements EquipmentUser, Leashab private final BodyRotationControl bodyRotationControl; protected PathNavigation navigation; public GoalSelector goalSelector; @@ -16,7 +16,7 @@ index 930b5002aa6eaa1137314f7b38fad99778b6edaa..0593d828c911c94c9833bf12b9c294e5 public GoalSelector targetSelector; @Nullable private LivingEntity target; -@@ -888,7 +889,15 @@ public abstract class Mob extends LivingEntity implements EquipmentUser, Leashab +@@ -886,7 +887,15 @@ public abstract class Mob extends LivingEntity implements EquipmentUser, Leashab @Override protected final void serverAiStep() { ++this.noActionTime; @@ -30,7 +30,7 @@ index 930b5002aa6eaa1137314f7b38fad99778b6edaa..0593d828c911c94c9833bf12b9c294e5 + return; + } + // Paper end - Allow nerfed mobs to jump and float - ProfilerFiller gameprofilerfiller = this.level().getProfiler(); + ProfilerFiller gameprofilerfiller = Profiler.get(); gameprofilerfiller.push("sensing"); diff --git a/src/main/java/net/minecraft/world/entity/ai/goal/FloatGoal.java b/src/main/java/net/minecraft/world/entity/ai/goal/FloatGoal.java diff --git a/patches/server/0031-Add-configurable-entity-despawn-distances.patch b/patches/server/0031-Add-configurable-entity-despawn-distances.patch index d7a7254cdd63..f2d77f7042c3 100644 --- a/patches/server/0031-Add-configurable-entity-despawn-distances.patch +++ b/patches/server/0031-Add-configurable-entity-despawn-distances.patch @@ -5,10 +5,10 @@ Subject: [PATCH] Add configurable entity despawn distances diff --git a/src/main/java/net/minecraft/world/entity/Mob.java b/src/main/java/net/minecraft/world/entity/Mob.java -index 0593d828c911c94c9833bf12b9c294e5dac1f4e8..02c8c52930640114592148fd5509ad8fffee5435 100644 +index 9b26f6a7526f875535738b1f22d9aa458845eb8e..efa1d813699286c0a2632f44c4d7eecd06e8aa64 100644 --- a/src/main/java/net/minecraft/world/entity/Mob.java +++ b/src/main/java/net/minecraft/world/entity/Mob.java -@@ -863,20 +863,24 @@ public abstract class Mob extends LivingEntity implements EquipmentUser, Leashab +@@ -861,20 +861,24 @@ public abstract class Mob extends LivingEntity implements EquipmentUser, Leashab Player entityhuman = this.level().getNearestPlayer(this, -1.0D); if (entityhuman != null) { diff --git a/patches/server/0032-Drop-falling-block-and-tnt-entities-at-the-specified.patch b/patches/server/0032-Drop-falling-block-and-tnt-entities-at-the-specified.patch index ce9c29089120..1fc91e5693fe 100644 --- a/patches/server/0032-Drop-falling-block-and-tnt-entities-at-the-specified.patch +++ b/patches/server/0032-Drop-falling-block-and-tnt-entities-at-the-specified.patch @@ -6,17 +6,17 @@ Subject: [PATCH] Drop falling block and tnt entities at the specified height Co-authored-by: Jake Potrebic diff --git a/src/main/java/net/minecraft/world/entity/item/FallingBlockEntity.java b/src/main/java/net/minecraft/world/entity/item/FallingBlockEntity.java -index a4e8fa5267b8853603e4683bf9d002db7465e4b6..c3a16691e8a843c02e0aea6469822cd8869ad593 100644 +index e9e8ad33371749f52a88a0ee089540eb26fb0f28..a5543a6b4811628ff5178a0ec01933ec4b30dfa4 100644 --- a/src/main/java/net/minecraft/world/entity/item/FallingBlockEntity.java +++ b/src/main/java/net/minecraft/world/entity/item/FallingBlockEntity.java -@@ -149,6 +149,16 @@ public class FallingBlockEntity extends Entity { - ++this.time; +@@ -159,6 +159,16 @@ public class FallingBlockEntity extends Entity { this.applyGravity(); this.move(MoverType.SELF, this.getDeltaMovement()); + this.applyEffectsFromBlocks(); + // Paper start - Configurable falling blocks height nerf + if (this.level().paperConfig().fixes.fallingBlockHeightNerf.test(v -> this.getY() > v)) { -+ if (this.dropItem && this.level().getGameRules().getBoolean(GameRules.RULE_DOENTITYDROPS)) { -+ this.spawnAtLocation(block); ++ if (this.dropItem && this.level() instanceof final ServerLevel serverLevel && serverLevel.getGameRules().getBoolean(GameRules.RULE_DOENTITYDROPS)) { ++ this.spawnAtLocation(serverLevel, block); + } + + this.discard(EntityRemoveEvent.Cause.OUT_OF_WORLD); @@ -24,16 +24,16 @@ index a4e8fa5267b8853603e4683bf9d002db7465e4b6..c3a16691e8a843c02e0aea6469822cd8 + } + // Paper end - Configurable falling blocks height nerf this.handlePortal(); - if (!this.level().isClientSide && (this.isAlive() || this.forceTickAfterTeleportToDuplicate)) { - BlockPos blockposition = this.blockPosition(); + Level world = this.level(); + diff --git a/src/main/java/net/minecraft/world/entity/item/PrimedTnt.java b/src/main/java/net/minecraft/world/entity/item/PrimedTnt.java -index 8f693bb45099124bca62849528c81d717131a48c..15432b512fc0d0d38bf28499e2afa5e48fec7aaa 100644 +index 91381122b5b63139be880f80dadde4d8cf31a260..3d489fdc14b3e29bab63f330d5edbbc1d354382a 100644 --- a/src/main/java/net/minecraft/world/entity/item/PrimedTnt.java +++ b/src/main/java/net/minecraft/world/entity/item/PrimedTnt.java -@@ -99,6 +99,12 @@ public class PrimedTnt extends Entity implements TraceableEntity { - this.handlePortal(); +@@ -106,6 +106,12 @@ public class PrimedTnt extends Entity implements TraceableEntity { this.applyGravity(); this.move(MoverType.SELF, this.getDeltaMovement()); + this.applyEffectsFromBlocks(); + // Paper start - Configurable TNT height nerf + if (this.level().paperConfig().fixes.tntEntityHeightNerf.test(v -> this.getY() > v)) { + this.discard(EntityRemoveEvent.Cause.OUT_OF_WORLD); @@ -44,10 +44,10 @@ index 8f693bb45099124bca62849528c81d717131a48c..15432b512fc0d0d38bf28499e2afa5e4 if (this.onGround()) { this.setDeltaMovement(this.getDeltaMovement().multiply(0.7D, -0.5D, 0.7D)); diff --git a/src/main/java/net/minecraft/world/entity/vehicle/MinecartTNT.java b/src/main/java/net/minecraft/world/entity/vehicle/MinecartTNT.java -index c112c4b103c34e05f4de973eec94a969f60b085e..b7036f8399e2500ba01736c6006b972f7ca4838e 100644 +index ef87b0b4dbf4f08b7ec00eed0eb4df2e3f13d4a7..0652494dc5f3d999116d4380032dea0e7402caec 100644 --- a/src/main/java/net/minecraft/world/entity/vehicle/MinecartTNT.java +++ b/src/main/java/net/minecraft/world/entity/vehicle/MinecartTNT.java -@@ -54,6 +54,12 @@ public class MinecartTNT extends AbstractMinecart { +@@ -52,6 +52,12 @@ public class MinecartTNT extends AbstractMinecart { public void tick() { super.tick(); if (this.fuse > 0) { diff --git a/patches/server/0033-Expose-server-build-information.patch b/patches/server/0033-Expose-server-build-information.patch index 3dfb0b097f86..83f13af41a6a 100644 --- a/patches/server/0033-Expose-server-build-information.patch +++ b/patches/server/0033-Expose-server-build-information.patch @@ -11,7 +11,7 @@ Co-authored-by: Jake Potrebic Co-authored-by: masmc05 diff --git a/build.gradle.kts b/build.gradle.kts -index 6d8f4c3b290609d60dbcabe3d2c8274b017246c8..0c349354ba76dfd2c5f16fb232263b18e77a9a40 100644 +index e1cfa2188dbe583a0180be2243a7a554dc5412d7..da2f9c5afb2994f403a1128af0f7acbd6b73b862 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -1,4 +1,5 @@ @@ -533,18 +533,18 @@ index 0000000000000000000000000000000000000000..790bad0494454ca12ee152e3de6da3da + } +} diff --git a/src/main/java/net/minecraft/server/MinecraftServer.java b/src/main/java/net/minecraft/server/MinecraftServer.java -index 46e03617bb32e4037d700c1b3698d397bd75de5c..2f57739431eb695149019724e2923f0d02e88d07 100644 +index 709c6361aa5eb78071ce9d0f2a65ce8a56af1443..b86d8a3756cb8c1adb1aceda57f60b0ccdb3f659 100644 --- a/src/main/java/net/minecraft/server/MinecraftServer.java +++ b/src/main/java/net/minecraft/server/MinecraftServer.java -@@ -43,7 +43,6 @@ import java.util.Set; +@@ -45,7 +45,6 @@ import java.util.Set; import java.util.UUID; import java.util.concurrent.CompletableFuture; import java.util.concurrent.Executor; -import java.util.concurrent.RejectedExecutionException; import java.util.concurrent.atomic.AtomicReference; + import java.util.concurrent.locks.LockSupport; import java.util.function.BooleanSupplier; - import java.util.function.Consumer; -@@ -191,8 +190,6 @@ import net.minecraft.world.phys.Vec2; +@@ -198,8 +197,6 @@ import net.minecraft.world.phys.Vec2; import net.minecraft.world.phys.Vec3; import org.bukkit.Bukkit; import org.bukkit.craftbukkit.CraftRegistry; @@ -553,7 +553,7 @@ index 46e03617bb32e4037d700c1b3698d397bd75de5c..2f57739431eb695149019724e2923f0d import org.bukkit.event.server.ServerLoadEvent; // CraftBukkit end -@@ -1700,7 +1697,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop getOnlinePlayers() { return this.playerView; diff --git a/src/main/java/org/bukkit/craftbukkit/Main.java b/src/main/java/org/bukkit/craftbukkit/Main.java -index 034d68c2715b6a90f31e56f949ff3d27235a26eb..a7fbbe40f2382e7af185c4bfdd451fc1998b3636 100644 +index 03790abcc3474999db6d8986e50ebc2caf6eba0c..13f811173c67533ee02f70cc4b6b398cd527c84b 100644 --- a/src/main/java/org/bukkit/craftbukkit/Main.java +++ b/src/main/java/org/bukkit/craftbukkit/Main.java @@ -15,6 +15,7 @@ import joptsimple.OptionSet; @@ -671,7 +671,7 @@ index 034d68c2715b6a90f31e56f949ff3d27235a26eb..a7fbbe40f2382e7af185c4bfdd451fc1 public static boolean useConsole = true; @@ -241,15 +242,17 @@ public class Main { - deadline.add(Calendar.DAY_OF_YEAR, -28); + deadline.add(Calendar.DAY_OF_YEAR, -3); if (buildDate.before(deadline.getTime())) { System.err.println("*** Error, this build is outdated ***"); - System.err.println("*** Please download a new build as per instructions from https://www.spigotmc.org/go/outdated-spigot ***"); @@ -692,21 +692,23 @@ index 034d68c2715b6a90f31e56f949ff3d27235a26eb..a7fbbe40f2382e7af185c4bfdd451fc1 t.printStackTrace(); } diff --git a/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java b/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java -index f7b80cf8c89ae5eb9d8f0893e05ffc753fdace19..432f019afff36aa6143c052f7387a6c275a09de8 100644 +index 4b2377a1de608b9142a28c66389d04290f7c0330..2b60572307e3ec23e21e09c34a04de9a1c57f136 100644 --- a/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java +++ b/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java -@@ -459,6 +459,11 @@ public final class CraftMagicNumbers implements UnsafeValues { - public String getTimingsServerName() { - return io.papermc.paper.configuration.GlobalConfiguration.get().timings.serverName; +@@ -464,6 +464,13 @@ public final class CraftMagicNumbers implements UnsafeValues { + return CraftRegistry.get(registry, namespacedKey, ApiVersion.CURRENT); } -+ + ++ // Paper start + @Override + public com.destroystokyo.paper.util.VersionFetcher getVersionFetcher() { + return new com.destroystokyo.paper.PaperVersionFetcher(); + } - // Paper end - - @Override ++ // Paper end ++ + /** + * This helper class represents the different NBT Tags. + *

    diff --git a/src/main/java/org/spigotmc/WatchdogThread.java b/src/main/java/org/spigotmc/WatchdogThread.java index f697d45e0ac4e9cdc8a46121510a04c0f294d91f..e086765dec32241bc5a77afcf072c77a40c6d785 100644 --- a/src/main/java/org/spigotmc/WatchdogThread.java diff --git a/patches/server/0034-Player-affects-spawning-API.patch b/patches/server/0034-Player-affects-spawning-API.patch index 654234cc3612..8439cb2b0ad4 100644 --- a/patches/server/0034-Player-affects-spawning-API.patch +++ b/patches/server/0034-Player-affects-spawning-API.patch @@ -5,11 +5,11 @@ Subject: [PATCH] Player affects spawning API diff --git a/src/main/java/net/minecraft/world/entity/EntitySelector.java b/src/main/java/net/minecraft/world/entity/EntitySelector.java -index 3126e8cab3c40e3af47f4c8925e1c6a9523309ba..3207166061bf9c4d7bf3f38e5a9f7aff23ccd5c1 100644 +index 048c8af16fad8708a486bb29304db22e2fb1ecb3..a617ea34cfc28cefd68dd14ffbb334b87f98f65c 100644 --- a/src/main/java/net/minecraft/world/entity/EntitySelector.java +++ b/src/main/java/net/minecraft/world/entity/EntitySelector.java -@@ -30,6 +30,11 @@ public final class EntitySelector { - public static final Predicate CAN_BE_COLLIDED_WITH = EntitySelector.NO_SPECTATORS.and(Entity::canBeCollidedWith); +@@ -29,6 +29,11 @@ public final class EntitySelector { + public static final Predicate CAN_BE_PICKED = EntitySelector.NO_SPECTATORS.and(Entity::isPickable); private EntitySelector() {} + // Paper start - Affects Spawning API @@ -21,10 +21,10 @@ index 3126e8cab3c40e3af47f4c8925e1c6a9523309ba..3207166061bf9c4d7bf3f38e5a9f7aff public static Predicate withinDistance(double x, double y, double z, double max) { double d4 = max * max; diff --git a/src/main/java/net/minecraft/world/entity/Mob.java b/src/main/java/net/minecraft/world/entity/Mob.java -index 4ce1586351b417aede1fd401907388b5fa0e9fd6..d8beadc96a7f779c39c8e22ffe52d872ac49a0ad 100644 +index efa1d813699286c0a2632f44c4d7eecd06e8aa64..9655466953cf850b82716246821a3ebb968a5478 100644 --- a/src/main/java/net/minecraft/world/entity/Mob.java +++ b/src/main/java/net/minecraft/world/entity/Mob.java -@@ -860,7 +860,7 @@ public abstract class Mob extends LivingEntity implements EquipmentUser, Leashab +@@ -858,7 +858,7 @@ public abstract class Mob extends LivingEntity implements EquipmentUser, Leashab if (this.level().getDifficulty() == Difficulty.PEACEFUL && this.shouldDespawnInPeaceful()) { this.discard(EntityRemoveEvent.Cause.DESPAWN); // CraftBukkit - add Bukkit remove cause } else if (!this.isPersistenceRequired() && !this.requiresCustomPersistence()) { @@ -34,7 +34,7 @@ index 4ce1586351b417aede1fd401907388b5fa0e9fd6..d8beadc96a7f779c39c8e22ffe52d872 if (entityhuman != null) { // Paper start - Configurable despawn distances diff --git a/src/main/java/net/minecraft/world/entity/animal/horse/SkeletonTrapGoal.java b/src/main/java/net/minecraft/world/entity/animal/horse/SkeletonTrapGoal.java -index 3b7fc11b7832a72fb9b0806fe9847f4e30759e7b..3cb84856c10347162a8736ae1ef65165183ec8fe 100644 +index c86e2d75ac627e9c92a6e006cb4c06ec6a2cb91e..521b09ac14372f524b06ffdce57932d0a590700b 100644 --- a/src/main/java/net/minecraft/world/entity/animal/horse/SkeletonTrapGoal.java +++ b/src/main/java/net/minecraft/world/entity/animal/horse/SkeletonTrapGoal.java @@ -27,7 +27,7 @@ public class SkeletonTrapGoal extends Goal { @@ -47,33 +47,33 @@ index 3b7fc11b7832a72fb9b0806fe9847f4e30759e7b..3cb84856c10347162a8736ae1ef65165 @Override diff --git a/src/main/java/net/minecraft/world/entity/monster/Silverfish.java b/src/main/java/net/minecraft/world/entity/monster/Silverfish.java -index 95e9d38dbccbd1c43ababd707e18dfe6779256c1..9ff42b0ae2b82dc3092e38e1439d89b4ab554b17 100644 +index 0594b6adcb849bba2c810de245a3bdaeeca0be60..52d8ea3e40cdb01eab428f5d3d945c0c9f6088ce 100644 --- a/src/main/java/net/minecraft/world/entity/monster/Silverfish.java +++ b/src/main/java/net/minecraft/world/entity/monster/Silverfish.java -@@ -118,7 +118,7 @@ public class Silverfish extends Monster { - if (checkAnyLightMonsterSpawnRules(type, world, spawnReason, pos, random)) { +@@ -123,7 +123,7 @@ public class Silverfish extends Monster { + } else { Player entityhuman = world.getNearestPlayer((double) pos.getX() + 0.5D, (double) pos.getY() + 0.5D, (double) pos.getZ() + 0.5D, 5.0D, true); - return entityhuman == null; + return !(entityhuman != null && !entityhuman.affectsSpawning) && entityhuman == null; // Paper - Affects Spawning API - } else { - return false; } + } + diff --git a/src/main/java/net/minecraft/world/entity/monster/Zombie.java b/src/main/java/net/minecraft/world/entity/monster/Zombie.java -index 393a9c704f4637a0e8031328d2a0facef4723dd8..d97c3c139f10a45febc0cfb1057ff6e33266228e 100644 +index 3836d9255ac326a7220e1decd2e9d98be7884c17..73c4585870b7af409f84474f126a58497ed0495f 100644 --- a/src/main/java/net/minecraft/world/entity/monster/Zombie.java +++ b/src/main/java/net/minecraft/world/entity/monster/Zombie.java -@@ -328,7 +328,7 @@ public class Zombie extends Monster { +@@ -349,7 +349,7 @@ public class Zombie extends Monster { - if (SpawnPlacements.isSpawnPositionOk(entitytypes, this.level(), blockposition) && SpawnPlacements.checkSpawnRules(entitytypes, worldserver, MobSpawnType.REINFORCEMENT, blockposition, this.level().random)) { + if (SpawnPlacements.isSpawnPositionOk(entitytypes, world, blockposition) && SpawnPlacements.checkSpawnRules(entitytypes, world, EntitySpawnReason.REINFORCEMENT, blockposition, world.random)) { entityzombie.setPos((double) i1, (double) j1, (double) k1); -- if (!this.level().hasNearbyAlivePlayer((double) i1, (double) j1, (double) k1, 7.0D) && this.level().isUnobstructed(entityzombie) && this.level().noCollision((Entity) entityzombie) && !this.level().containsAnyLiquid(entityzombie.getBoundingBox())) { -+ if (!this.level().hasNearbyAlivePlayerThatAffectsSpawning((double) i1, (double) j1, (double) k1, 7.0D) && this.level().isUnobstructed(entityzombie) && this.level().noCollision((Entity) entityzombie) && !this.level().containsAnyLiquid(entityzombie.getBoundingBox())) { // Paper - Affects Spawning API +- if (!world.hasNearbyAlivePlayer((double) i1, (double) j1, (double) k1, 7.0D) && world.isUnobstructed(entityzombie) && world.noCollision((Entity) entityzombie) && (entityzombie.canSpawnInLiquids() || !world.containsAnyLiquid(entityzombie.getBoundingBox()))) { ++ if (!world.hasNearbyAlivePlayerThatAffectsSpawning((double) i1, (double) j1, (double) k1, 7.0D) && world.isUnobstructed(entityzombie) && world.noCollision((Entity) entityzombie) && (entityzombie.canSpawnInLiquids() || !world.containsAnyLiquid(entityzombie.getBoundingBox()))) { // Paper - affects spawning api entityzombie.setTarget(entityliving, EntityTargetEvent.TargetReason.REINFORCEMENT_TARGET, true); // CraftBukkit - entityzombie.finalizeSpawn(worldserver, this.level().getCurrentDifficultyAt(entityzombie.blockPosition()), MobSpawnType.REINFORCEMENT, (SpawnGroupData) null); - worldserver.addFreshEntityWithPassengers(entityzombie, CreatureSpawnEvent.SpawnReason.REINFORCEMENTS); // CraftBukkit + entityzombie.finalizeSpawn(world, world.getCurrentDifficultyAt(entityzombie.blockPosition()), EntitySpawnReason.REINFORCEMENT, (SpawnGroupData) null); + world.addFreshEntityWithPassengers(entityzombie, CreatureSpawnEvent.SpawnReason.REINFORCEMENTS); // CraftBukkit diff --git a/src/main/java/net/minecraft/world/entity/player/Player.java b/src/main/java/net/minecraft/world/entity/player/Player.java -index 56f4d85d0d2014dfe4fb2598fa813ffc4a768d25..ba279ab6ec0ab41309607333b62a941e35dbf581 100644 +index a879d715a78ff32cfb377dd29cd9facaf21b3f32..b953b8e7e184c0c0eb7231564ad120765d66823a 100644 --- a/src/main/java/net/minecraft/world/entity/player/Player.java +++ b/src/main/java/net/minecraft/world/entity/player/Player.java @@ -195,6 +195,7 @@ public abstract class Player extends LivingEntity { @@ -85,7 +85,7 @@ index 56f4d85d0d2014dfe4fb2598fa813ffc4a768d25..ba279ab6ec0ab41309607333b62a941e // CraftBukkit start public boolean fauxSleeping; diff --git a/src/main/java/net/minecraft/world/level/BaseSpawner.java b/src/main/java/net/minecraft/world/level/BaseSpawner.java -index cc54da2987fafcbd69153c33033a6f272dd3be66..418e29971326008ebca0cc4b696a41a49c1c7bb7 100644 +index b2bed46f809abee056aa99f39f26f8c0fbf0036c..366661561544f8e99f238583259991e9fcbab8af 100644 --- a/src/main/java/net/minecraft/world/level/BaseSpawner.java +++ b/src/main/java/net/minecraft/world/level/BaseSpawner.java @@ -58,7 +58,7 @@ public abstract class BaseSpawner { @@ -98,10 +98,10 @@ index cc54da2987fafcbd69153c33033a6f272dd3be66..418e29971326008ebca0cc4b696a41a4 public void clientTick(Level world, BlockPos pos) { diff --git a/src/main/java/net/minecraft/world/level/EntityGetter.java b/src/main/java/net/minecraft/world/level/EntityGetter.java -index f38f62e777d88a783e1e3b7e1a48da921cc67cf4..77ae7882a08441d9a80b50492be5e48487a2fdab 100644 +index f689b2ca0ebc15c099f36ebfd14e455bda540296..fb043d67eaa6336fc0b5d62774b8f1107f9dfa1e 100644 --- a/src/main/java/net/minecraft/world/level/EntityGetter.java +++ b/src/main/java/net/minecraft/world/level/EntityGetter.java -@@ -74,6 +74,11 @@ public interface EntityGetter { +@@ -71,6 +71,11 @@ public interface EntityGetter { } } @@ -113,7 +113,7 @@ index f38f62e777d88a783e1e3b7e1a48da921cc67cf4..77ae7882a08441d9a80b50492be5e484 @Nullable default Player getNearestPlayer(double x, double y, double z, double maxDistance, @Nullable Predicate targetPredicate) { double d = -1.0; -@@ -103,6 +108,20 @@ public interface EntityGetter { +@@ -100,6 +105,20 @@ public interface EntityGetter { return this.getNearestPlayer(x, y, z, maxDistance, predicate); } @@ -135,10 +135,10 @@ index f38f62e777d88a783e1e3b7e1a48da921cc67cf4..77ae7882a08441d9a80b50492be5e484 for (Player player : this.players()) { if (EntitySelector.NO_SPECTATORS.test(player) && EntitySelector.LIVING_ENTITY_STILL_ALIVE.test(player)) { diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java -index 68b9b4aba7dba31e4526165e6efb8c40f9e841bc..1b2ead250233d9f228bfe8e79f988026506895f9 100644 +index f7833cd528797ba46b001db5208c29eb11ae2529..2be8dc983d008cb3da597f3aabd5efc0df51f9e8 100644 --- a/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java +++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java -@@ -2425,6 +2425,17 @@ public class CraftPlayer extends CraftHumanEntity implements Player { +@@ -2452,6 +2452,17 @@ public class CraftPlayer extends CraftHumanEntity implements Player { return this.getHandle().language; } diff --git a/patches/server/0035-Only-refresh-abilities-if-needed.patch b/patches/server/0035-Only-refresh-abilities-if-needed.patch index 3af5f88628f1..330dfcd4914d 100644 --- a/patches/server/0035-Only-refresh-abilities-if-needed.patch +++ b/patches/server/0035-Only-refresh-abilities-if-needed.patch @@ -5,10 +5,10 @@ Subject: [PATCH] Only refresh abilities if needed diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java -index 4cc6b3162cea049134f194ed84a7552830cb85af..c2be21491183f5f113dbfc71a7e0ccd195679296 100644 +index 2be8dc983d008cb3da597f3aabd5efc0df51f9e8..5600dce95548ffffa1a338f9c9f6d682d30cf02f 100644 --- a/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java +++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java -@@ -2093,12 +2093,13 @@ public class CraftPlayer extends CraftHumanEntity implements Player { +@@ -2120,12 +2120,13 @@ public class CraftPlayer extends CraftHumanEntity implements Player { @Override public void setFlying(boolean value) { diff --git a/patches/server/0036-Entity-Origin-API.patch b/patches/server/0036-Entity-Origin-API.patch index eaf335d0d358..47721fa03df7 100644 --- a/patches/server/0036-Entity-Origin-API.patch +++ b/patches/server/0036-Entity-Origin-API.patch @@ -5,10 +5,10 @@ Subject: [PATCH] Entity Origin API diff --git a/src/main/java/net/minecraft/server/level/ServerLevel.java b/src/main/java/net/minecraft/server/level/ServerLevel.java -index eb98bb1bd76869fd76b34885223c8e57a04e0c51..754045e71f180862fa57fd1c97e5d7deb1d788e6 100644 +index f3633da64f990972cddc03f2fcfd34ced2955a7a..025363e6b51ff8aa089715b1ec2a0fa1caab65d6 100644 --- a/src/main/java/net/minecraft/server/level/ServerLevel.java +++ b/src/main/java/net/minecraft/server/level/ServerLevel.java -@@ -2141,6 +2141,15 @@ public class ServerLevel extends Level implements WorldGenLevel { +@@ -2182,6 +2182,15 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe entity.updateDynamicGameEventListener(DynamicGameEventListener::add); entity.inWorld = true; // CraftBukkit - Mark entity as in world entity.valid = true; // CraftBukkit @@ -25,10 +25,10 @@ index eb98bb1bd76869fd76b34885223c8e57a04e0c51..754045e71f180862fa57fd1c97e5d7de public void onTrackingEnd(Entity entity) { diff --git a/src/main/java/net/minecraft/world/entity/Entity.java b/src/main/java/net/minecraft/world/entity/Entity.java -index 63f45a77c8511e05954030cf117c5e4cda0a518f..dbe5239b1a1769ef9f2ef66c32b1a68cd684428e 100644 +index c8b8102d84119dfb6093f4b79aa3124c594f9a88..e97853cbfa6da8ecdb4c92cf634831492e1fc7e3 100644 --- a/src/main/java/net/minecraft/world/entity/Entity.java +++ b/src/main/java/net/minecraft/world/entity/Entity.java -@@ -328,7 +328,27 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess +@@ -331,7 +331,27 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess public long activatedTick = Integer.MIN_VALUE; public void inactiveTick() { } // Spigot end @@ -56,7 +56,7 @@ index 63f45a77c8511e05954030cf117c5e4cda0a518f..dbe5239b1a1769ef9f2ef66c32b1a68c public float getBukkitYaw() { return this.yRot; } -@@ -2153,6 +2173,15 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess +@@ -2269,6 +2289,15 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess this.bukkitEntity.storeBukkitValues(nbttagcompound); } // CraftBukkit end @@ -72,7 +72,7 @@ index 63f45a77c8511e05954030cf117c5e4cda0a518f..dbe5239b1a1769ef9f2ef66c32b1a68c return nbttagcompound; } catch (Throwable throwable) { CrashReport crashreport = CrashReport.forThrowable(throwable, "Saving entity NBT"); -@@ -2280,6 +2309,20 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess +@@ -2397,6 +2426,20 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess } // CraftBukkit end @@ -94,10 +94,10 @@ index 63f45a77c8511e05954030cf117c5e4cda0a518f..dbe5239b1a1769ef9f2ef66c32b1a68c CrashReport crashreport = CrashReport.forThrowable(throwable, "Loading entity NBT"); CrashReportCategory crashreportsystemdetails = crashreport.addCategory("Entity being loaded"); diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java -index 269326e7689eba91bcfd3475006e8cbf8f5694ef..7b45a1216ff824f1b528bb5759d10b70858832a1 100644 +index 978397e517a6fdb24c7d2b3f242545af07deeab0..ea27931d01c1f3c721b2f7ec12d41ea843fa158a 100644 --- a/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java +++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java -@@ -963,4 +963,21 @@ public abstract class CraftEntity implements org.bukkit.entity.Entity { +@@ -964,4 +964,21 @@ public abstract class CraftEntity implements org.bukkit.entity.Entity { return this.spigot; } // Spigot end diff --git a/patches/server/0037-Prevent-block-entity-and-entity-crashes.patch b/patches/server/0037-Prevent-block-entity-and-entity-crashes.patch index 59cb59cc0fe8..72263726831b 100644 --- a/patches/server/0037-Prevent-block-entity-and-entity-crashes.patch +++ b/patches/server/0037-Prevent-block-entity-and-entity-crashes.patch @@ -5,10 +5,10 @@ Subject: [PATCH] Prevent block entity and entity crashes diff --git a/src/main/java/net/minecraft/world/level/Level.java b/src/main/java/net/minecraft/world/level/Level.java -index 79d5423be919dfe4db75ad7dd0ce403ad0214462..e8b8475dd6fd7b89651f744da2cb9696c73ddc3e 100644 +index c5e480c43668211a091e44ae50b40b025ff1dca9..071545e60f838fa6c930edc35f46e0ce9b73fb44 100644 --- a/src/main/java/net/minecraft/world/level/Level.java +++ b/src/main/java/net/minecraft/world/level/Level.java -@@ -731,11 +731,11 @@ public abstract class Level implements LevelAccessor, AutoCloseable { +@@ -726,11 +726,11 @@ public abstract class Level implements LevelAccessor, AutoCloseable { try { tickConsumer.accept(entity); } catch (Throwable throwable) { @@ -26,10 +26,10 @@ index 79d5423be919dfe4db75ad7dd0ce403ad0214462..e8b8475dd6fd7b89651f744da2cb9696 } diff --git a/src/main/java/net/minecraft/world/level/block/entity/BlockEntity.java b/src/main/java/net/minecraft/world/level/block/entity/BlockEntity.java -index 17cda4c8b61efd99c1a43f921ed604827bb064f3..d6ad310d3b472c40c128cfb459171d9f48e50915 100644 +index 751a5ae04e4de3f5d85eda20092a87ef4f547436..3d7c5db5514f9d4401da22d2df88c5614051e568 100644 --- a/src/main/java/net/minecraft/world/level/block/entity/BlockEntity.java +++ b/src/main/java/net/minecraft/world/level/block/entity/BlockEntity.java -@@ -272,7 +272,12 @@ public abstract class BlockEntity { +@@ -268,7 +268,12 @@ public abstract class BlockEntity { public void fillCrashReportCategory(CrashReportCategory crashReportSection) { crashReportSection.setDetail("Name", this::getNameForReporting); if (this.level != null) { @@ -44,10 +44,10 @@ index 17cda4c8b61efd99c1a43f921ed604827bb064f3..d6ad310d3b472c40c128cfb459171d9f } } diff --git a/src/main/java/net/minecraft/world/level/chunk/LevelChunk.java b/src/main/java/net/minecraft/world/level/chunk/LevelChunk.java -index 418ba374886d93f69afd614e4be05f6561e1f897..6889991885cc2075e0936b2c480befeef30d308c 100644 +index f63d55bfe42b117c5b437e690124a98d94752a9a..79cf3089c128ef3c17d956da1e380670279ee5da 100644 --- a/src/main/java/net/minecraft/world/level/chunk/LevelChunk.java +++ b/src/main/java/net/minecraft/world/level/chunk/LevelChunk.java -@@ -953,11 +953,11 @@ public class LevelChunk extends ChunkAccess { +@@ -983,11 +983,11 @@ public class LevelChunk extends ChunkAccess { gameprofilerfiller.pop(); } catch (Throwable throwable) { @@ -62,5 +62,5 @@ index 418ba374886d93f69afd614e4be05f6561e1f897..6889991885cc2075e0936b2c480befee + LevelChunk.this.removeBlockEntity(this.getPos()); + // Paper end - Prevent block entity and entity crashes // Spigot start - } finally { - this.blockEntity.tickTimer.stopTiming(); + } + } diff --git a/patches/server/0038-Configurable-top-of-nether-void-damage.patch b/patches/server/0038-Configurable-top-of-nether-void-damage.patch index 8a30b0f3c310..5f97b3e150aa 100644 --- a/patches/server/0038-Configurable-top-of-nether-void-damage.patch +++ b/patches/server/0038-Configurable-top-of-nether-void-damage.patch @@ -6,16 +6,16 @@ Subject: [PATCH] Configurable top of nether void damage Co-authored-by: Jake Potrebic diff --git a/src/main/java/net/minecraft/world/entity/Entity.java b/src/main/java/net/minecraft/world/entity/Entity.java -index dbe5239b1a1769ef9f2ef66c32b1a68cd684428e..d5f5864b7c1ad4c30f37b360b317b63c129e3a3f 100644 +index e97853cbfa6da8ecdb4c92cf634831492e1fc7e3..49df5f4b09926556986e3a45d52ff299b878af76 100644 --- a/src/main/java/net/minecraft/world/entity/Entity.java +++ b/src/main/java/net/minecraft/world/entity/Entity.java -@@ -706,7 +706,11 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess +@@ -720,7 +720,11 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess } public void checkBelowWorld() { -- if (this.getY() < (double) (this.level().getMinBuildHeight() - 64)) { +- if (this.getY() < (double) (this.level().getMinY() - 64)) { + // Paper start - Configurable nether ceiling damage -+ if (this.getY() < (double) (this.level.getMinBuildHeight() - 64) || (this.level.getWorld().getEnvironment() == org.bukkit.World.Environment.NETHER ++ if (this.getY() < (double) (this.level.getMinY() - 64) || (this.level.getWorld().getEnvironment() == org.bukkit.World.Environment.NETHER + && this.level.paperConfig().environment.netherCeilingVoidDamageHeight.test(v -> this.getY() >= v) + && (!(this instanceof Player player) || !player.getAbilities().invulnerable))) { + // Paper end - Configurable nether ceiling damage @@ -23,7 +23,7 @@ index dbe5239b1a1769ef9f2ef66c32b1a68cd684428e..d5f5864b7c1ad4c30f37b360b317b63c } diff --git a/src/main/java/net/minecraft/world/level/portal/PortalForcer.java b/src/main/java/net/minecraft/world/level/portal/PortalForcer.java -index 46448259cd60ea5e2e08fb58cd2b2b7f8a4ec3cc..aef1dd28da7e0c0a13a0a7a5b52daa27635c48ea 100644 +index 36accb58ed269a129f92d2b64f5a0b14416de735..355f1ce10f9564c7c0be505a5af849e0428fec17 100644 --- a/src/main/java/net/minecraft/world/level/portal/PortalForcer.java +++ b/src/main/java/net/minecraft/world/level/portal/PortalForcer.java @@ -58,7 +58,7 @@ public class PortalForcer { @@ -38,7 +38,7 @@ index 46448259cd60ea5e2e08fb58cd2b2b7f8a4ec3cc..aef1dd28da7e0c0a13a0a7a5b52daa27 @@ -79,6 +79,11 @@ public class PortalForcer { BlockPos blockposition2 = null; WorldBorder worldborder = this.level.getWorldBorder(); - int i = Math.min(this.level.getMaxBuildHeight(), this.level.getMinBuildHeight() + this.level.getLogicalHeight()) - 1; + int i = Math.min(this.level.getMaxY(), this.level.getMinY() + this.level.getLogicalHeight() - 1); + // Paper start - Configurable nether ceiling damage; make sure the max height doesn't exceed the void damage height + if (this.level.getTypeKey() == net.minecraft.world.level.dimension.LevelStem.NETHER && this.level.paperConfig().environment.netherCeilingVoidDamageHeight.enabled()) { + i = Math.min(i, this.level.paperConfig().environment.netherCeilingVoidDamageHeight.intValue() - 1); diff --git a/patches/server/0040-Add-more-entities-to-activation-range-ignore-list.patch b/patches/server/0040-Add-more-entities-to-activation-range-ignore-list.patch index c402bdafa28a..2c4e1fe5780d 100644 --- a/patches/server/0040-Add-more-entities-to-activation-range-ignore-list.patch +++ b/patches/server/0040-Add-more-entities-to-activation-range-ignore-list.patch @@ -5,10 +5,10 @@ Subject: [PATCH] Add more entities to activation range ignore list diff --git a/src/main/java/org/spigotmc/ActivationRange.java b/src/main/java/org/spigotmc/ActivationRange.java -index a5da6c1cae0afbde684be250e2fc3c0c32a1265b..6d51464f6368151e8acc532414ee223714584e96 100644 +index 263df52c7f5e172a6b9118b4bc4672e443adedba..dd1c5bc7522a4710cbfdd4764f6431e1e28d63cc 100644 --- a/src/main/java/org/spigotmc/ActivationRange.java +++ b/src/main/java/org/spigotmc/ActivationRange.java -@@ -92,6 +92,9 @@ public class ActivationRange +@@ -91,6 +91,9 @@ public class ActivationRange || entity instanceof AbstractHurtingProjectile || entity instanceof LightningBolt || entity instanceof PrimedTnt diff --git a/patches/server/0041-Configurable-end-credits.patch b/patches/server/0041-Configurable-end-credits.patch index 65f5ee3bd5b5..3514b6e2efe4 100644 --- a/patches/server/0041-Configurable-end-credits.patch +++ b/patches/server/0041-Configurable-end-credits.patch @@ -5,10 +5,10 @@ Subject: [PATCH] Configurable end credits diff --git a/src/main/java/net/minecraft/world/level/block/EndPortalBlock.java b/src/main/java/net/minecraft/world/level/block/EndPortalBlock.java -index e2d6693da4abe6204c0ecb5e924a3903fa80ab7d..a9c7a74b38a57c118c1ad67a77ba6f2e5c05d91e 100644 +index 8bea909328bc15eb6af739850db13d624270dee4..5744944455b08d45a7c0fe2289414b50b6c0d66a 100644 --- a/src/main/java/net/minecraft/world/level/block/EndPortalBlock.java +++ b/src/main/java/net/minecraft/world/level/block/EndPortalBlock.java -@@ -71,6 +71,7 @@ public class EndPortalBlock extends BaseEntityBlock implements Portal { +@@ -76,6 +76,7 @@ public class EndPortalBlock extends BaseEntityBlock implements Portal { if (!world.isClientSide && world.dimension() == Level.END && entity instanceof ServerPlayer) { ServerPlayer entityplayer = (ServerPlayer) entity; diff --git a/patches/server/0042-Fix-lag-from-explosions-processing-dead-entities.patch b/patches/server/0042-Fix-lag-from-explosions-processing-dead-entities.patch index ae3fb7c15b5d..9dc78a5f91dd 100644 --- a/patches/server/0042-Fix-lag-from-explosions-processing-dead-entities.patch +++ b/patches/server/0042-Fix-lag-from-explosions-processing-dead-entities.patch @@ -4,16 +4,16 @@ Date: Wed, 2 Mar 2016 01:39:52 -0600 Subject: [PATCH] Fix lag from explosions processing dead entities -diff --git a/src/main/java/net/minecraft/world/level/Explosion.java b/src/main/java/net/minecraft/world/level/Explosion.java -index 4a30928cd11f528f8ac06950b8052ebb7f2dd33c..458020575050284544761ec61c52abac7bfd15be 100644 ---- a/src/main/java/net/minecraft/world/level/Explosion.java -+++ b/src/main/java/net/minecraft/world/level/Explosion.java -@@ -227,7 +227,7 @@ public class Explosion { - int i1 = Mth.floor(this.y + (double) f2 + 1.0D); - int j1 = Mth.floor(this.z - (double) f2 - 1.0D); - int k1 = Mth.floor(this.z + (double) f2 + 1.0D); -- List list = this.level.getEntities(this.source, new AABB((double) i, (double) l, (double) j1, (double) j, (double) i1, (double) k1)); -+ List list = this.level.getEntities(this.source, new AABB((double) i, (double) l, (double) j1, (double) j, (double) i1, (double) k1), (com.google.common.base.Predicate) entity -> entity.isAlive() && !entity.isSpectator()); // Paper - Fix lag from explosions processing dead entities - Vec3 vec3d = new Vec3(this.x, this.y, this.z); +diff --git a/src/main/java/net/minecraft/world/level/ServerExplosion.java b/src/main/java/net/minecraft/world/level/ServerExplosion.java +index 1df33018470136344d8843e5e429aa7f9b40b750..0eb25fabfff0e8a050c6dfb8cd24e703f679db76 100644 +--- a/src/main/java/net/minecraft/world/level/ServerExplosion.java ++++ b/src/main/java/net/minecraft/world/level/ServerExplosion.java +@@ -185,7 +185,7 @@ public class ServerExplosion implements Explosion { + int l = Mth.floor(this.center.y + (double) f + 1.0D); + int i1 = Mth.floor(this.center.z - (double) f - 1.0D); + int j1 = Mth.floor(this.center.z + (double) f + 1.0D); +- List list = this.level.getEntities(this.source, new AABB((double) i, (double) k, (double) i1, (double) j, (double) l, (double) j1)); ++ List list = this.level.getEntities(this.source, new AABB((double) i, (double) k, (double) i1, (double) j, (double) l, (double) j1), (com.google.common.base.Predicate) entity -> entity.isAlive() && !entity.isSpectator()); // Paper - Fix lag from explosions processing dead entities Iterator iterator = list.iterator(); + while (iterator.hasNext()) { diff --git a/patches/server/0043-Optimize-explosions.patch b/patches/server/0043-Optimize-explosions.patch index 7aea454fd3c1..a5a195040211 100644 --- a/patches/server/0043-Optimize-explosions.patch +++ b/patches/server/0043-Optimize-explosions.patch @@ -10,34 +10,47 @@ This patch adds a per-tick cache that is used for storing and retrieving an entity's exposure during an explosion. diff --git a/src/main/java/net/minecraft/server/MinecraftServer.java b/src/main/java/net/minecraft/server/MinecraftServer.java -index 2f57739431eb695149019724e2923f0d02e88d07..1ba2b1aaf0936963aca73a43632f978c95bf95e6 100644 +index b86d8a3756cb8c1adb1aceda57f60b0ccdb3f659..cc4c5fcbeca4862d8ff78b127cb3f2c07956dfaf 100644 --- a/src/main/java/net/minecraft/server/MinecraftServer.java +++ b/src/main/java/net/minecraft/server/MinecraftServer.java -@@ -1571,6 +1571,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop explosionDensityCache = new HashMap<>(); // Paper - Optimize explosions -- double d12 = (1.0D - d7) * (double) Explosion.getSeenPercent(vec3d, entity) * (double) this.damageCalculator.getKnockbackMultiplier(entity); -+ double d12 = (1.0D - d7) * this.getBlockDensity(vec3d, entity) * (double) this.damageCalculator.getKnockbackMultiplier(entity); // Paper - Optimize explosions - double d13; + public CraftWorld getWorld() { + return this.world; +diff --git a/src/main/java/net/minecraft/world/level/ServerExplosion.java b/src/main/java/net/minecraft/world/level/ServerExplosion.java +index 0eb25fabfff0e8a050c6dfb8cd24e703f679db76..f4b5c81d0daae24e06ba6409fc4584b4f1406fd2 100644 +--- a/src/main/java/net/minecraft/world/level/ServerExplosion.java ++++ b/src/main/java/net/minecraft/world/level/ServerExplosion.java +@@ -206,7 +206,7 @@ public class ServerExplosion implements Explosion { + d3 /= d4; + boolean flag = this.damageCalculator.shouldDamageEntity(this, entity); + float f1 = this.damageCalculator.getKnockbackMultiplier(entity); +- float f2 = !flag && f1 == 0.0F ? 0.0F : ServerExplosion.getSeenPercent(this.center, entity); ++ float f2 = !flag && f1 == 0.0F ? 0.0F : this.getBlockDensity(this.center, entity); // Paper - Optimize explosions - if (entity instanceof LivingEntity) { -@@ -536,4 +536,84 @@ public class Explosion { + if (flag) { + // CraftBukkit start +@@ -483,4 +483,85 @@ public class ServerExplosion implements Explosion { - private BlockInteraction() {} + } } ++ + // Paper start - Optimize explosions + private float getBlockDensity(Vec3 vec3d, Entity entity) { + if (!this.level.paperConfig().environment.optimizeExplosions) { @@ -60,10 +73,10 @@ index 35d541c549cb07508e68388b18f38a4ffd788176..23a0a8d9beb7ca400134fb6a65b3133b + private final double maxX, maxY, maxZ; + + public CacheKey(Explosion explosion, AABB aabb) { -+ this.world = explosion.level; -+ this.posX = explosion.x; -+ this.posY = explosion.y; -+ this.posZ = explosion.z; ++ this.world = explosion.level(); ++ this.posX = explosion.center().x; ++ this.posY = explosion.center().y; ++ this.posZ = explosion.center().z; + this.minX = aabb.minX; + this.minY = aabb.minY; + this.minZ = aabb.minZ; @@ -119,15 +132,3 @@ index 35d541c549cb07508e68388b18f38a4ffd788176..23a0a8d9beb7ca400134fb6a65b3133b + } + // Paper end } -diff --git a/src/main/java/net/minecraft/world/level/Level.java b/src/main/java/net/minecraft/world/level/Level.java -index e8b8475dd6fd7b89651f744da2cb9696c73ddc3e..a272aaff11ac077853c06f729a5d8b09f866e0f8 100644 ---- a/src/main/java/net/minecraft/world/level/Level.java -+++ b/src/main/java/net/minecraft/world/level/Level.java -@@ -168,6 +168,7 @@ public abstract class Level implements LevelAccessor, AutoCloseable { - private org.spigotmc.TickLimiter entityLimiter; - private org.spigotmc.TickLimiter tileLimiter; - private int tileTickPosition; -+ public final Map explosionDensityCache = new HashMap<>(); // Paper - Optimize explosions - - public CraftWorld getWorld() { - return this.world; diff --git a/patches/server/0044-Disable-explosion-knockback.patch b/patches/server/0044-Disable-explosion-knockback.patch index 233aa3058123..21d2161c676e 100644 --- a/patches/server/0044-Disable-explosion-knockback.patch +++ b/patches/server/0044-Disable-explosion-knockback.patch @@ -4,25 +4,25 @@ Date: Wed, 2 Mar 2016 14:48:03 -0600 Subject: [PATCH] Disable explosion knockback -diff --git a/src/main/java/net/minecraft/world/level/Explosion.java b/src/main/java/net/minecraft/world/level/Explosion.java -index 55d66aa8264d5b444a23e2132206bcc9835cfe00..d93ed33d5ae72e9dd3e6cf044ef79e4b9689dc1c 100644 ---- a/src/main/java/net/minecraft/world/level/Explosion.java -+++ b/src/main/java/net/minecraft/world/level/Explosion.java -@@ -285,7 +285,7 @@ public class Explosion { +diff --git a/src/main/java/net/minecraft/world/level/ServerExplosion.java b/src/main/java/net/minecraft/world/level/ServerExplosion.java +index f4b5c81d0daae24e06ba6409fc4584b4f1406fd2..3afacd201683f46fd75cd6f9a7f3d43a7050cf4a 100644 +--- a/src/main/java/net/minecraft/world/level/ServerExplosion.java ++++ b/src/main/java/net/minecraft/world/level/ServerExplosion.java +@@ -246,7 +246,7 @@ public class ServerExplosion implements Explosion { if (entity instanceof LivingEntity) { LivingEntity entityliving = (LivingEntity) entity; -- d13 = d12 * (1.0D - entityliving.getAttributeValue(Attributes.EXPLOSION_KNOCKBACK_RESISTANCE)); -+ d13 = entity instanceof Player && this.level.paperConfig().environment.disableExplosionKnockback ? 0 : d12 * (1.0D - entityliving.getAttributeValue(Attributes.EXPLOSION_KNOCKBACK_RESISTANCE)); +- d6 = d5 * (1.0D - entityliving.getAttributeValue(Attributes.EXPLOSION_KNOCKBACK_RESISTANCE)); ++ d6 = entity instanceof Player && this.level.paperConfig().environment.disableExplosionKnockback ? 0 : d5 * (1.0D - entityliving.getAttributeValue(Attributes.EXPLOSION_KNOCKBACK_RESISTANCE)); // Paper } else { - d13 = d12; + d6 = d5; } -@@ -310,7 +310,7 @@ public class Explosion { +@@ -271,7 +271,7 @@ public class ServerExplosion implements Explosion { if (entity instanceof Player) { Player entityhuman = (Player) entity; - if (!entityhuman.isSpectator() && (!entityhuman.isCreative() || !entityhuman.getAbilities().flying)) { + if (!entityhuman.isSpectator() && (!entityhuman.isCreative() || !entityhuman.getAbilities().flying) && !level.paperConfig().environment.disableExplosionKnockback) { // Paper - Option to disable explosion knockback - this.hitPlayers.put(entityhuman, vec3d1); + this.hitPlayers.put(entityhuman, vec3d); } } diff --git a/patches/server/0045-Disable-thunder.patch b/patches/server/0045-Disable-thunder.patch index de8845153827..78a2a9fd6a91 100644 --- a/patches/server/0045-Disable-thunder.patch +++ b/patches/server/0045-Disable-thunder.patch @@ -5,11 +5,11 @@ Subject: [PATCH] Disable thunder diff --git a/src/main/java/net/minecraft/server/level/ServerLevel.java b/src/main/java/net/minecraft/server/level/ServerLevel.java -index 754045e71f180862fa57fd1c97e5d7deb1d788e6..aca595551c90d5515309c9c82ad6ffcfa1d680c8 100644 +index 025363e6b51ff8aa089715b1ec2a0fa1caab65d6..abb819c4263e6ea16152fbf55b044101ec8ec0d6 100644 --- a/src/main/java/net/minecraft/server/level/ServerLevel.java +++ b/src/main/java/net/minecraft/server/level/ServerLevel.java -@@ -616,7 +616,7 @@ public class ServerLevel extends Level implements WorldGenLevel { - ProfilerFiller gameprofilerfiller = this.getProfiler(); +@@ -589,7 +589,7 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe + ProfilerFiller gameprofilerfiller = Profiler.get(); gameprofilerfiller.push("thunder"); - if (flag && this.isThundering() && this.spigotConfig.thunderChance > 0 && this.random.nextInt(this.spigotConfig.thunderChance) == 0) { // Spigot diff --git a/patches/server/0046-Disable-ice-and-snow.patch b/patches/server/0046-Disable-ice-and-snow.patch index ae60e5cb8ca4..73dcf9fa4794 100644 --- a/patches/server/0046-Disable-ice-and-snow.patch +++ b/patches/server/0046-Disable-ice-and-snow.patch @@ -5,10 +5,10 @@ Subject: [PATCH] Disable ice and snow diff --git a/src/main/java/net/minecraft/server/level/ServerLevel.java b/src/main/java/net/minecraft/server/level/ServerLevel.java -index aca595551c90d5515309c9c82ad6ffcfa1d680c8..6d89dc22e28a9c3557d9972be0935d75719e7f7d 100644 +index abb819c4263e6ea16152fbf55b044101ec8ec0d6..0e967e4c73dabc6cf6580d863c640cf4dff4f3bf 100644 --- a/src/main/java/net/minecraft/server/level/ServerLevel.java +++ b/src/main/java/net/minecraft/server/level/ServerLevel.java -@@ -646,11 +646,13 @@ public class ServerLevel extends Level implements WorldGenLevel { +@@ -619,11 +619,13 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe gameprofilerfiller.popPush("iceandsnow"); @@ -21,4 +21,4 @@ index aca595551c90d5515309c9c82ad6ffcfa1d680c8..6d89dc22e28a9c3557d9972be0935d75 + } // Paper - Option to disable ice and snow gameprofilerfiller.popPush("tickBlocks"); - timings.chunkTicksBlocks.startTiming(); // Paper + if (randomTickSpeed > 0) { diff --git a/patches/server/0047-Configurable-mob-spawner-tick-rate.patch b/patches/server/0047-Configurable-mob-spawner-tick-rate.patch index deb1ddde3f00..a3dc9ff7d3c4 100644 --- a/patches/server/0047-Configurable-mob-spawner-tick-rate.patch +++ b/patches/server/0047-Configurable-mob-spawner-tick-rate.patch @@ -5,7 +5,7 @@ Subject: [PATCH] Configurable mob spawner tick rate diff --git a/src/main/java/net/minecraft/world/level/BaseSpawner.java b/src/main/java/net/minecraft/world/level/BaseSpawner.java -index 418e29971326008ebca0cc4b696a41a49c1c7bb7..aa54237205989f619ac6a3faa2e4285427b9e31d 100644 +index 366661561544f8e99f238583259991e9fcbab8af..7b918001d36a8f14ed0d3ee4d6783588f48eb78f 100644 --- a/src/main/java/net/minecraft/world/level/BaseSpawner.java +++ b/src/main/java/net/minecraft/world/level/BaseSpawner.java @@ -49,6 +49,7 @@ public abstract class BaseSpawner { diff --git a/patches/server/0048-Use-null-Locale-by-default.patch b/patches/server/0048-Use-null-Locale-by-default.patch index 3fcadee5b58b..0ffa4b3c957e 100644 --- a/patches/server/0048-Use-null-Locale-by-default.patch +++ b/patches/server/0048-Use-null-Locale-by-default.patch @@ -5,10 +5,10 @@ Subject: [PATCH] Use null Locale by default diff --git a/src/main/java/net/minecraft/server/level/ServerPlayer.java b/src/main/java/net/minecraft/server/level/ServerPlayer.java -index ee5188f3aa2ff71306f5af8046e8ddf919c8601b..13a069483db22f25008bf4081d4aa2b36a75bc68 100644 +index 4303bde198050cd037f006234d269af406606eff..911ec630c5925b160cc05f99f0d5bb5ac46384bb 100644 --- a/src/main/java/net/minecraft/server/level/ServerPlayer.java +++ b/src/main/java/net/minecraft/server/level/ServerPlayer.java -@@ -237,7 +237,7 @@ public class ServerPlayer extends net.minecraft.world.entity.player.Player { +@@ -261,7 +261,7 @@ public class ServerPlayer extends net.minecraft.world.entity.player.Player { private int levitationStartTime; private boolean disconnected; private int requestedViewDistance; @@ -17,16 +17,16 @@ index ee5188f3aa2ff71306f5af8046e8ddf919c8601b..13a069483db22f25008bf4081d4aa2b3 public java.util.Locale adventure$locale = java.util.Locale.US; // Paper @Nullable private Vec3 startingToFallPosition; -@@ -293,7 +293,7 @@ public class ServerPlayer extends net.minecraft.world.entity.player.Player { +@@ -321,7 +321,7 @@ public class ServerPlayer extends net.minecraft.world.entity.player.Player { + this.canChatColor = true; this.lastActionTime = Util.getMillis(); - this.recipeBook = new ServerRecipeBook(); this.requestedViewDistance = 2; - this.language = "en_us"; + this.language = null; // Paper - default to null this.lastSectionPos = SectionPos.of(0, 0, 0); this.chunkTrackingView = ChunkTrackingView.EMPTY; this.respawnDimension = Level.OVERWORLD; -@@ -2051,7 +2051,7 @@ public class ServerPlayer extends net.minecraft.world.entity.player.Player { +@@ -2314,7 +2314,7 @@ public class ServerPlayer extends net.minecraft.world.entity.player.Player { PlayerChangedMainHandEvent event = new PlayerChangedMainHandEvent(this.getBukkitEntity(), this.getMainArm() == HumanoidArm.LEFT ? MainHand.LEFT : MainHand.RIGHT); this.server.server.getPluginManager().callEvent(event); } @@ -36,10 +36,10 @@ index ee5188f3aa2ff71306f5af8046e8ddf919c8601b..13a069483db22f25008bf4081d4aa2b3 this.server.server.getPluginManager().callEvent(event); } diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java -index 1a003335a4f3cc2fdeadca9c0c6cdafa61a6a1ac..fb5548b92a0fe7866cf98e25293c4b0702344c9b 100644 +index 5600dce95548ffffa1a338f9c9f6d682d30cf02f..17a5b37a7591274a45a6818144b62b5cd1412a40 100644 --- a/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java +++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java -@@ -2423,7 +2423,10 @@ public class CraftPlayer extends CraftHumanEntity implements Player { +@@ -2450,7 +2450,10 @@ public class CraftPlayer extends CraftHumanEntity implements Player { @Override public String getLocale() { diff --git a/patches/server/0049-Add-BeaconEffectEvent.patch b/patches/server/0049-Add-BeaconEffectEvent.patch index f26dbf1ae4bc..ce80109f252b 100644 --- a/patches/server/0049-Add-BeaconEffectEvent.patch +++ b/patches/server/0049-Add-BeaconEffectEvent.patch @@ -5,7 +5,7 @@ Subject: [PATCH] Add BeaconEffectEvent diff --git a/src/main/java/net/minecraft/world/level/block/entity/BeaconBlockEntity.java b/src/main/java/net/minecraft/world/level/block/entity/BeaconBlockEntity.java -index 9fc5f72ac2d23a03584d3c0357bc1a55ea40bab3..fc915797d2a085447747d9ce23a5a354fb3eb6b6 100644 +index 94fcd67edd81120d56478ffd30f3c1d7dee955e6..8332296663b845df1d09d403b49a4769b2d54afc 100644 --- a/src/main/java/net/minecraft/world/level/block/entity/BeaconBlockEntity.java +++ b/src/main/java/net/minecraft/world/level/block/entity/BeaconBlockEntity.java @@ -1,5 +1,6 @@ diff --git a/patches/server/0050-Configurable-container-update-tick-rate.patch b/patches/server/0050-Configurable-container-update-tick-rate.patch index 290d38244cd2..02f5fc8fd0e4 100644 --- a/patches/server/0050-Configurable-container-update-tick-rate.patch +++ b/patches/server/0050-Configurable-container-update-tick-rate.patch @@ -5,18 +5,18 @@ Subject: [PATCH] Configurable container update tick rate diff --git a/src/main/java/net/minecraft/server/level/ServerPlayer.java b/src/main/java/net/minecraft/server/level/ServerPlayer.java -index e61460a06708429738e0ed5f903a4226158aa334..ed920ed90569b7b8886a09a1c3772fd7147d44f9 100644 +index 911ec630c5925b160cc05f99f0d5bb5ac46384bb..78e4f07019e3231fbaa3f23bcdc8846e2d79ae18 100644 --- a/src/main/java/net/minecraft/server/level/ServerPlayer.java +++ b/src/main/java/net/minecraft/server/level/ServerPlayer.java -@@ -268,6 +268,7 @@ public class ServerPlayer extends net.minecraft.world.entity.player.Player { - public final Object object; +@@ -295,6 +295,7 @@ public class ServerPlayer extends net.minecraft.world.entity.player.Player { + private final CommandSource commandSource; private int containerCounter; public boolean wonGame; + private int containerUpdateDelay; // Paper - Configurable container update tick rate // CraftBukkit start public CraftPlayer.TransferCookieConnection transferCookieConnection; -@@ -696,7 +697,12 @@ public class ServerPlayer extends net.minecraft.world.entity.player.Player { +@@ -924,7 +925,12 @@ public class ServerPlayer extends net.minecraft.world.entity.player.Player { --this.invulnerableTime; } diff --git a/patches/server/0052-Disable-spigot-tick-limiters.patch b/patches/server/0052-Disable-spigot-tick-limiters.patch index 2e19fda1e14f..2872d59fb9a2 100644 --- a/patches/server/0052-Disable-spigot-tick-limiters.patch +++ b/patches/server/0052-Disable-spigot-tick-limiters.patch @@ -5,10 +5,10 @@ Subject: [PATCH] Disable spigot tick limiters diff --git a/src/main/java/net/minecraft/world/level/Level.java b/src/main/java/net/minecraft/world/level/Level.java -index da7b1b705da9f17de858f72a20d3a932cd8f7fad..68436413645e0e33f22cdee0ea101ca01b343d75 100644 +index e90dac1e46bca60896b027f670ba521d67c02c1e..ed15f5c4a365a199d04e92d688035e4ab755e8db 100644 --- a/src/main/java/net/minecraft/world/level/Level.java +++ b/src/main/java/net/minecraft/world/level/Level.java -@@ -704,9 +704,7 @@ public abstract class Level implements LevelAccessor, AutoCloseable { +@@ -701,9 +701,7 @@ public abstract class Level implements LevelAccessor, AutoCloseable { boolean flag = this.tickRateManager().runsNormally(); int tilesThisCycle = 0; diff --git a/patches/server/0053-Fix-spawn-location-event-changing-location.patch b/patches/server/0053-Fix-spawn-location-event-changing-location.patch index e9256b27836b..15086ed363cf 100644 --- a/patches/server/0053-Fix-spawn-location-event-changing-location.patch +++ b/patches/server/0053-Fix-spawn-location-event-changing-location.patch @@ -7,7 +7,7 @@ Subject: [PATCH] Fix spawn location event changing location public net.minecraft.world.entity.Entity setRot(FF)V diff --git a/src/main/java/net/minecraft/server/players/PlayerList.java b/src/main/java/net/minecraft/server/players/PlayerList.java -index 765c412cd0c5cd410c224b4bc55dbf431fd6617b..8a91a44e46a2d49e2f4b9e9970c2b77f2e87767e 100644 +index 56f046bac04205a813441907058c4ce21982d927..50b1e493a9adfa687dc22c66044c6aa243340b3f 100644 --- a/src/main/java/net/minecraft/server/players/PlayerList.java +++ b/src/main/java/net/minecraft/server/players/PlayerList.java @@ -234,7 +234,10 @@ public abstract class PlayerList { diff --git a/patches/server/0054-Configurable-Disabling-Cat-Chest-Detection.patch b/patches/server/0054-Configurable-Disabling-Cat-Chest-Detection.patch index 232eb8f5f4fe..410ee5115f41 100644 --- a/patches/server/0054-Configurable-Disabling-Cat-Chest-Detection.patch +++ b/patches/server/0054-Configurable-Disabling-Cat-Chest-Detection.patch @@ -6,10 +6,10 @@ Subject: [PATCH] Configurable Disabling Cat Chest Detection Offers a gameplay feature to stop cats from blocking chests diff --git a/src/main/java/net/minecraft/world/level/block/ChestBlock.java b/src/main/java/net/minecraft/world/level/block/ChestBlock.java -index e197891f61580f92787d9400ff486439a92a54c7..8fbfd18b3caeed769396b3ffb1b1778b2f38edc0 100644 +index 62440d5985341d6c1b9ec5f6cce81a5ad6bd9040..590837cb242eda62dca3c937a26b8ba26c41850c 100644 --- a/src/main/java/net/minecraft/world/level/block/ChestBlock.java +++ b/src/main/java/net/minecraft/world/level/block/ChestBlock.java -@@ -349,6 +349,11 @@ public class ChestBlock extends AbstractChestBlock implements +@@ -348,6 +348,11 @@ public class ChestBlock extends AbstractChestBlock implements } private static boolean isCatSittingOnChest(LevelAccessor world, BlockPos pos) { diff --git a/patches/server/0055-Improve-Player-chat-API-handling.patch b/patches/server/0055-Improve-Player-chat-API-handling.patch index 05814e9ee54a..8c63e9bdb414 100644 --- a/patches/server/0055-Improve-Player-chat-API-handling.patch +++ b/patches/server/0055-Improve-Player-chat-API-handling.patch @@ -17,10 +17,10 @@ Co-authored-by: Jake Potrebic Co-authored-by: SoSeDiK diff --git a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java -index 3a67b2b6a6d3204b2a7bbe8adbf2b0ecf7898551..02b9e1ed57f5d65698c461387ff7d48450e6a70f 100644 +index fddc6b5abbad66ebe556ff8565c38c60b7883fce..55d61a4c93233c0d3994e75f41e29065c2f1ea93 100644 --- a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java +++ b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java -@@ -2013,7 +2013,7 @@ public class ServerGamePacketListenerImpl extends ServerCommonPacketListenerImpl +@@ -2032,7 +2032,7 @@ public class ServerGamePacketListenerImpl extends ServerCommonPacketListenerImpl } OutgoingChatMessage outgoing = OutgoingChatMessage.create(original); @@ -29,21 +29,19 @@ index 3a67b2b6a6d3204b2a7bbe8adbf2b0ecf7898551..02b9e1ed57f5d65698c461387ff7d484 this.handleCommand(s); } else if (this.player.getChatVisibility() == ChatVisiblity.SYSTEM) { // Do nothing, this is coming from a plugin -@@ -2100,7 +2100,8 @@ public class ServerGamePacketListenerImpl extends ServerCommonPacketListenerImpl - } +@@ -2120,6 +2120,7 @@ public class ServerGamePacketListenerImpl extends ServerCommonPacketListenerImpl } -- private void handleCommand(String s) { -+ public void handleCommand(String s) { // Paper - private -> public + private void handleCommand(String s) { + org.spigotmc.AsyncCatcher.catchOp("Command Dispatched Async: " + s); // Paper - Add async catcher - co.aikar.timings.MinecraftTimings.playerCommandTimer.startTiming(); // Paper if ( org.spigotmc.SpigotConfig.logCommands ) // Spigot this.LOGGER.info(this.player.getScoreboardName() + " issued server command: " + s); + diff --git a/src/main/java/org/bukkit/craftbukkit/CraftServer.java b/src/main/java/org/bukkit/craftbukkit/CraftServer.java -index 8f79d7611c3ee1c817d5671152e459ec4f413418..441a8d3355e0db773a5da330bf9361234da1ca89 100644 +index 2bfa790a9f0ca07217c9d9f7dd916950d859530c..ec0d7e44235378380b7180ca1a9ca56b14dac1c7 100644 --- a/src/main/java/org/bukkit/craftbukkit/CraftServer.java +++ b/src/main/java/org/bukkit/craftbukkit/CraftServer.java -@@ -934,7 +934,7 @@ public final class CraftServer implements Server { +@@ -936,7 +936,7 @@ public final class CraftServer implements Server { public boolean dispatchCommand(CommandSender sender, String commandLine) { Preconditions.checkArgument(sender != null, "sender cannot be null"); Preconditions.checkArgument(commandLine != null, "commandLine cannot be null"); @@ -53,10 +51,10 @@ index 8f79d7611c3ee1c817d5671152e459ec4f413418..441a8d3355e0db773a5da330bf936123 if (this.commandMap.dispatch(sender, commandLine)) { return true; diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java -index 545da59af2436b9a4363ec459456c8f15650b79f..9751cace64d9ad2b8b55080b13834e8ccb056b42 100644 +index 17a5b37a7591274a45a6818144b62b5cd1412a40..51bf9e36ec13af09240c329e40391d79f715a021 100644 --- a/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java +++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java -@@ -546,7 +546,20 @@ public class CraftPlayer extends CraftHumanEntity implements Player { +@@ -563,7 +563,20 @@ public class CraftPlayer extends CraftHumanEntity implements Player { if (this.getHandle().connection == null) return; diff --git a/patches/server/0056-All-chunks-are-slime-spawn-chunks-toggle.patch b/patches/server/0056-All-chunks-are-slime-spawn-chunks-toggle.patch index 3cded231dd0e..7afface4f663 100644 --- a/patches/server/0056-All-chunks-are-slime-spawn-chunks-toggle.patch +++ b/patches/server/0056-All-chunks-are-slime-spawn-chunks-toggle.patch @@ -5,20 +5,20 @@ Subject: [PATCH] All chunks are slime spawn chunks toggle diff --git a/src/main/java/net/minecraft/world/entity/monster/Slime.java b/src/main/java/net/minecraft/world/entity/monster/Slime.java -index b7e6f6e195d8b947c06e2bf58f4c644bda8eba99..b1f7ea02e533660322675e1bddb070f0a41084f2 100644 +index 7b45d1b706550d7d0a0267f30fb0b86813edfeb3..131fce812eb0dcdebab02b529ed18e81eb1861eb 100644 --- a/src/main/java/net/minecraft/world/entity/monster/Slime.java +++ b/src/main/java/net/minecraft/world/entity/monster/Slime.java -@@ -350,7 +350,7 @@ public class Slime extends Mob implements Enemy { - } +@@ -342,7 +342,7 @@ public class Slime extends Mob implements Enemy { + } - ChunkPos chunkcoordintpair = new ChunkPos(pos); -- boolean flag = WorldgenRandom.seedSlimeChunk(chunkcoordintpair.x, chunkcoordintpair.z, ((WorldGenLevel) world).getSeed(), world.getMinecraftWorld().spigotConfig.slimeSeed).nextInt(10) == 0; // Spigot + ChunkPos chunkcoordintpair = new ChunkPos(pos); +- boolean flag = WorldgenRandom.seedSlimeChunk(chunkcoordintpair.x, chunkcoordintpair.z, ((WorldGenLevel) world).getSeed(), world.getMinecraftWorld().spigotConfig.slimeSeed).nextInt(10) == 0; // Spigot + boolean flag = world.getMinecraftWorld().paperConfig().entities.spawning.allChunksAreSlimeChunks || WorldgenRandom.seedSlimeChunk(chunkcoordintpair.x, chunkcoordintpair.z, ((WorldGenLevel) world).getSeed(), world.getMinecraftWorld().spigotConfig.slimeSeed).nextInt(10) == 0; // Spigot // Paper - if (random.nextInt(10) == 0 && flag && pos.getY() < 40) { - return checkMobSpawnRules(type, world, spawnReason, pos, random); + if (random.nextInt(10) == 0 && flag && pos.getY() < 40) { + return checkMobSpawnRules(type, world, spawnReason, pos, random); diff --git a/src/main/java/org/bukkit/craftbukkit/CraftChunk.java b/src/main/java/org/bukkit/craftbukkit/CraftChunk.java -index 5db1be69f0c4ef6976789587866c7a9bb6d19b0d..e37dae711e7059834612ead5f4fcea9f28ad436f 100644 +index 19f165b855a0ca10732fd43c7ee093b11e535471..91d2b6eaa2af0abb1bdf11849f0fd59660f765dd 100644 --- a/src/main/java/org/bukkit/craftbukkit/CraftChunk.java +++ b/src/main/java/org/bukkit/craftbukkit/CraftChunk.java @@ -218,7 +218,7 @@ public class CraftChunk implements Chunk { diff --git a/patches/server/0057-Expose-server-CommandMap.patch b/patches/server/0057-Expose-server-CommandMap.patch index 3b09e092796b..bc0e6fd63e13 100644 --- a/patches/server/0057-Expose-server-CommandMap.patch +++ b/patches/server/0057-Expose-server-CommandMap.patch @@ -5,10 +5,10 @@ Subject: [PATCH] Expose server CommandMap diff --git a/src/main/java/org/bukkit/craftbukkit/CraftServer.java b/src/main/java/org/bukkit/craftbukkit/CraftServer.java -index 441a8d3355e0db773a5da330bf9361234da1ca89..32d1e4bc8279a9adb83501c99fc7ff33d4c7f81c 100644 +index ec0d7e44235378380b7180ca1a9ca56b14dac1c7..63573ec7e8f855a1afd892c9615a45c1bc742ac1 100644 --- a/src/main/java/org/bukkit/craftbukkit/CraftServer.java +++ b/src/main/java/org/bukkit/craftbukkit/CraftServer.java -@@ -2179,6 +2179,7 @@ public final class CraftServer implements Server { +@@ -2182,6 +2182,7 @@ public final class CraftServer implements Server { return this.helpMap; } diff --git a/patches/server/0058-Be-a-bit-more-informative-in-maxHealth-exception.patch b/patches/server/0058-Be-a-bit-more-informative-in-maxHealth-exception.patch index 4490b5096c73..dc513f3e9610 100644 --- a/patches/server/0058-Be-a-bit-more-informative-in-maxHealth-exception.patch +++ b/patches/server/0058-Be-a-bit-more-informative-in-maxHealth-exception.patch @@ -5,10 +5,10 @@ Subject: [PATCH] Be a bit more informative in maxHealth exception diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftLivingEntity.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftLivingEntity.java -index 5ce03fa25e0a03025501a7f86a8f3c64f3fd7c77..ff2118fb463168e061ca9152183e19d593b476e9 100644 +index 8146e3df0fbcdb5f7e474167814bfbd569984fa0..ea7cbb5b5e7f140c06029ef05a01567d51b39f91 100644 --- a/src/main/java/org/bukkit/craftbukkit/entity/CraftLivingEntity.java +++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftLivingEntity.java -@@ -103,7 +103,12 @@ public class CraftLivingEntity extends CraftEntity implements LivingEntity { +@@ -108,7 +108,12 @@ public class CraftLivingEntity extends CraftEntity implements LivingEntity { @Override public void setHealth(double health) { health = (float) health; diff --git a/patches/server/0059-Player-Tab-List-and-Title-APIs.patch b/patches/server/0059-Player-Tab-List-and-Title-APIs.patch index 93c3f67151c8..79923e2cfc4e 100644 --- a/patches/server/0059-Player-Tab-List-and-Title-APIs.patch +++ b/patches/server/0059-Player-Tab-List-and-Title-APIs.patch @@ -5,10 +5,10 @@ Subject: [PATCH] Player Tab List and Title APIs diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java -index 9751cace64d9ad2b8b55080b13834e8ccb056b42..951b89ced33b2a12dff90e32201643bee64962e1 100644 +index 51bf9e36ec13af09240c329e40391d79f715a021..e3b8cc11a5523614594f1c3a144991fa70227e57 100644 --- a/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java +++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java -@@ -388,6 +388,98 @@ public class CraftPlayer extends CraftHumanEntity implements Player { +@@ -393,6 +393,98 @@ public class CraftPlayer extends CraftHumanEntity implements Player { } } diff --git a/patches/server/0060-Add-configurable-portal-search-radius.patch b/patches/server/0060-Add-configurable-portal-search-radius.patch index 9ba19afb3eed..9307176401b7 100644 --- a/patches/server/0060-Add-configurable-portal-search-radius.patch +++ b/patches/server/0060-Add-configurable-portal-search-radius.patch @@ -5,10 +5,10 @@ Subject: [PATCH] Add configurable portal search radius diff --git a/src/main/java/net/minecraft/world/level/block/NetherPortalBlock.java b/src/main/java/net/minecraft/world/level/block/NetherPortalBlock.java -index 462afb22cce2376789e44283032e63a6264cf851..8072e67f7b2f5944670159d3de1b01090bd1019d 100644 +index 5244f272947a4eb600f5ba183db2bb17824784ce..5d4c0d7fec42bf843b11875f7a09bcb9279b3b54 100644 --- a/src/main/java/net/minecraft/world/level/block/NetherPortalBlock.java +++ b/src/main/java/net/minecraft/world/level/block/NetherPortalBlock.java -@@ -139,8 +139,14 @@ public class NetherPortalBlock extends Block implements Portal { +@@ -145,8 +145,14 @@ public class NetherPortalBlock extends Block implements Portal { WorldBorder worldborder = worldserver1.getWorldBorder(); double d0 = DimensionType.getTeleportationScale(world.dimensionType(), worldserver1.dimensionType()); BlockPos blockposition1 = worldborder.clampToBounds(entity.getX() * d0, entity.getY(), entity.getZ() * d0); @@ -25,7 +25,7 @@ index 462afb22cce2376789e44283032e63a6264cf851..8072e67f7b2f5944670159d3de1b0109 return null; } diff --git a/src/main/java/net/minecraft/world/level/portal/PortalForcer.java b/src/main/java/net/minecraft/world/level/portal/PortalForcer.java -index aef1dd28da7e0c0a13a0a7a5b52daa27635c48ea..5c4b2a33d4007c36aef68604bca40a4eba510b4e 100644 +index 355f1ce10f9564c7c0be505a5af849e0428fec17..eb409fb5e673d2a343813946cc59cb5da2328eec 100644 --- a/src/main/java/net/minecraft/world/level/portal/PortalForcer.java +++ b/src/main/java/net/minecraft/world/level/portal/PortalForcer.java @@ -42,6 +42,7 @@ public class PortalForcer { diff --git a/patches/server/0061-Add-velocity-warnings.patch b/patches/server/0061-Add-velocity-warnings.patch index 996e71c32283..aafd524684f2 100644 --- a/patches/server/0061-Add-velocity-warnings.patch +++ b/patches/server/0061-Add-velocity-warnings.patch @@ -5,10 +5,10 @@ Subject: [PATCH] Add velocity warnings diff --git a/src/main/java/org/bukkit/craftbukkit/CraftServer.java b/src/main/java/org/bukkit/craftbukkit/CraftServer.java -index 4bfb836513d5194be271f4a82990ace98de69640..fd31d0e76d1a953b128e777b1bc27e24b1e03ed7 100644 +index 63573ec7e8f855a1afd892c9615a45c1bc742ac1..bb562ada45b828c79d83afdd1687edd32cbaf1dc 100644 --- a/src/main/java/org/bukkit/craftbukkit/CraftServer.java +++ b/src/main/java/org/bukkit/craftbukkit/CraftServer.java -@@ -302,6 +302,7 @@ public final class CraftServer implements Server { +@@ -305,6 +305,7 @@ public final class CraftServer implements Server { private final List playerView; public int reloadCount; public Set activeCompatibilities = Collections.emptySet(); @@ -17,10 +17,10 @@ index 4bfb836513d5194be271f4a82990ace98de69640..fd31d0e76d1a953b128e777b1bc27e24 static { ConfigurationSerialization.registerClass(CraftOfflinePlayer.class); diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java -index 7b45a1216ff824f1b528bb5759d10b70858832a1..df6da730134da754d0ff23bd1b57c82486b9ab73 100644 +index ea27931d01c1f3c721b2f7ec12d41ea843fa158a..0bec53dc1be4aa997be9f03bc3cde30d22cc8160 100644 --- a/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java +++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java -@@ -131,10 +131,40 @@ public abstract class CraftEntity implements org.bukkit.entity.Entity { +@@ -132,10 +132,40 @@ public abstract class CraftEntity implements org.bukkit.entity.Entity { public void setVelocity(Vector velocity) { Preconditions.checkArgument(velocity != null, "velocity"); velocity.checkFinite(); diff --git a/patches/server/0062-Add-exception-reporting-event.patch b/patches/server/0062-Add-exception-reporting-event.patch index 36051c183577..52fe575b573e 100644 --- a/patches/server/0062-Add-exception-reporting-event.patch +++ b/patches/server/0062-Add-exception-reporting-event.patch @@ -79,11 +79,11 @@ index 68551947f5b7d3471f15bd74ccd86519ab34c1c1..a0b0614ac7d2009db5c6c10ab4a5f09d } // CraftBukkit end diff --git a/src/main/java/net/minecraft/world/entity/ai/village/VillageSiege.java b/src/main/java/net/minecraft/world/entity/ai/village/VillageSiege.java -index 8183c26b4a5ad169a53702b8c45fd05cda934e80..36dec6cd78a0990ba3c09a4a748c259ef5c0a2ff 100644 +index a5b18a04f482d05d3ca74918a580499b21c2fc3c..bd3f71c3eaa33258ff56062ea3a2099cef310b7a 100644 --- a/src/main/java/net/minecraft/world/entity/ai/village/VillageSiege.java +++ b/src/main/java/net/minecraft/world/entity/ai/village/VillageSiege.java @@ -117,6 +117,7 @@ public class VillageSiege implements CustomSpawner { - entityzombie.finalizeSpawn(world, world.getCurrentDifficultyAt(entityzombie.blockPosition()), MobSpawnType.EVENT, (SpawnGroupData) null); + entityzombie.finalizeSpawn(world, world.getCurrentDifficultyAt(entityzombie.blockPosition()), EntitySpawnReason.EVENT, (SpawnGroupData) null); } catch (Exception exception) { VillageSiege.LOGGER.warn("Failed to create zombie for village siege at {}", vec3d, exception); + com.destroystokyo.paper.exception.ServerInternalException.reportInternalException(exception); // Paper - ServerExceptionEvent @@ -91,10 +91,10 @@ index 8183c26b4a5ad169a53702b8c45fd05cda934e80..36dec6cd78a0990ba3c09a4a748c259e } diff --git a/src/main/java/net/minecraft/world/level/Level.java b/src/main/java/net/minecraft/world/level/Level.java -index b4293991dddc9039c26106ac8c047e5aeec35a56..32b42d25631aecdd31db4954a8bbf38bcda98d6b 100644 +index ed15f5c4a365a199d04e92d688035e4ab755e8db..f900c86697f5a3da45b724944a663fca8f89413e 100644 --- a/src/main/java/net/minecraft/world/level/Level.java +++ b/src/main/java/net/minecraft/world/level/Level.java -@@ -733,6 +733,7 @@ public abstract class Level implements LevelAccessor, AutoCloseable { +@@ -728,6 +728,7 @@ public abstract class Level implements LevelAccessor, AutoCloseable { // Paper start - Prevent block entity and entity crashes final String msg = String.format("Entity threw exception at %s:%s,%s,%s", entity.level().getWorld().getName(), entity.getX(), entity.getY(), entity.getZ()); MinecraftServer.LOGGER.error(msg, throwable); @@ -103,10 +103,10 @@ index b4293991dddc9039c26106ac8c047e5aeec35a56..32b42d25631aecdd31db4954a8bbf38b // Paper end - Prevent block entity and entity crashes } diff --git a/src/main/java/net/minecraft/world/level/NaturalSpawner.java b/src/main/java/net/minecraft/world/level/NaturalSpawner.java -index 6a80479554f0c860a8dd6baa1a6506858fca83e3..6324689f52363f19501143c1649f0885684cb796 100644 +index 9389fd53f2bff0a9ca389694b312dc6da58befaf..da0ddb8285e157be0cc7b940a9590be5e3061e3d 100644 --- a/src/main/java/net/minecraft/world/level/NaturalSpawner.java +++ b/src/main/java/net/minecraft/world/level/NaturalSpawner.java -@@ -274,6 +274,7 @@ public final class NaturalSpawner { +@@ -294,6 +294,7 @@ public final class NaturalSpawner { NaturalSpawner.LOGGER.warn("Can't spawn entity of type: {}", BuiltInRegistries.ENTITY_TYPE.getKey(type)); } catch (Exception exception) { NaturalSpawner.LOGGER.warn("Failed to create mob", exception); @@ -114,8 +114,8 @@ index 6a80479554f0c860a8dd6baa1a6506858fca83e3..6324689f52363f19501143c1649f0885 } return null; -@@ -362,6 +363,7 @@ public final class NaturalSpawner { - entity = biomesettingsmobs_c.type.create(world.getLevel()); +@@ -382,6 +383,7 @@ public final class NaturalSpawner { + entity = biomesettingsmobs_c.type.create(world.getLevel(), EntitySpawnReason.NATURAL); } catch (Exception exception) { NaturalSpawner.LOGGER.warn("Failed to create mob", exception); + com.destroystokyo.paper.exception.ServerInternalException.reportInternalException(exception); // Paper - ServerExceptionEvent @@ -123,10 +123,10 @@ index 6a80479554f0c860a8dd6baa1a6506858fca83e3..6324689f52363f19501143c1649f0885 } diff --git a/src/main/java/net/minecraft/world/level/chunk/LevelChunk.java b/src/main/java/net/minecraft/world/level/chunk/LevelChunk.java -index 6889991885cc2075e0936b2c480befeef30d308c..4495c5051b0c7a04d433c39d91f74cb7924d3ff2 100644 +index 79cf3089c128ef3c17d956da1e380670279ee5da..96ea7a5d5d4a69c83c2401e64750d41cd70088fc 100644 --- a/src/main/java/net/minecraft/world/level/chunk/LevelChunk.java +++ b/src/main/java/net/minecraft/world/level/chunk/LevelChunk.java -@@ -471,8 +471,13 @@ public class LevelChunk extends ChunkAccess { +@@ -496,8 +496,13 @@ public class LevelChunk extends ChunkAccess { BlockState iblockdata = this.getBlockState(blockposition); if (!iblockdata.hasBlockEntity()) { @@ -142,7 +142,7 @@ index 6889991885cc2075e0936b2c480befeef30d308c..4495c5051b0c7a04d433c39d91f74cb7 } else { BlockState iblockdata1 = blockEntity.getBlockState(); -@@ -956,6 +961,7 @@ public class LevelChunk extends ChunkAccess { +@@ -986,6 +991,7 @@ public class LevelChunk extends ChunkAccess { // Paper start - Prevent block entity and entity crashes final String msg = String.format("BlockEntity threw exception at %s:%s,%s,%s", LevelChunk.this.getLevel().getWorld().getName(), this.getPos().getX(), this.getPos().getY(), this.getPos().getZ()); net.minecraft.server.MinecraftServer.LOGGER.error(msg, throwable); @@ -171,10 +171,10 @@ index 15f273aa592828719de6e092d79a407dc8652dfe..b24e8255ab18eb5b2e4968aa62aa3d72 try { filechannel.close(); diff --git a/src/main/java/org/bukkit/craftbukkit/scheduler/CraftScheduler.java b/src/main/java/org/bukkit/craftbukkit/scheduler/CraftScheduler.java -index 6c0debe3f3b693ed90dd2a39f481cccd8e4f7634..cf9f04e005940f5dd7baf50435f3703fa7c2d4f0 100644 +index 2c36d0796714997191c6540c34a9df60718065f6..0c0115ccd8541ac62975f4759b4e2083ac560332 100644 --- a/src/main/java/org/bukkit/craftbukkit/scheduler/CraftScheduler.java +++ b/src/main/java/org/bukkit/craftbukkit/scheduler/CraftScheduler.java -@@ -416,20 +416,25 @@ public class CraftScheduler implements BukkitScheduler { +@@ -415,20 +415,25 @@ public class CraftScheduler implements BukkitScheduler { try { task.run(); } catch (final Throwable throwable) { @@ -199,7 +199,7 @@ index 6c0debe3f3b693ed90dd2a39f481cccd8e4f7634..cf9f04e005940f5dd7baf50435f3703f } this.parsePending(); } else { - this.debugTail = this.debugTail.setNext(new CraftAsyncDebugger(currentTick + CraftScheduler.RECENT_TICKS, task.getOwner(), task.getTaskClass())); + this.debugTail = this.debugTail.setNext(new CraftAsyncDebugger(this.currentTick + CraftScheduler.RECENT_TICKS, task.getOwner(), task.getTaskClass())); - this.executor.execute(task); + this.executor.execute(new com.destroystokyo.paper.ServerSchedulerReportingWrapper(task)); // Paper // We don't need to parse pending diff --git a/patches/server/0063-Disable-Scoreboards-for-non-players-by-default.patch b/patches/server/0063-Disable-Scoreboards-for-non-players-by-default.patch index ba39658f013e..3d53b13e30fb 100644 --- a/patches/server/0063-Disable-Scoreboards-for-non-players-by-default.patch +++ b/patches/server/0063-Disable-Scoreboards-for-non-players-by-default.patch @@ -11,10 +11,10 @@ So avoid looking up scoreboards and short circuit to the "not on a team" logic which is most likely to be true. diff --git a/src/main/java/net/minecraft/world/entity/Entity.java b/src/main/java/net/minecraft/world/entity/Entity.java -index d5f5864b7c1ad4c30f37b360b317b63c129e3a3f..82e57978b79b5275b98a1fa7731c6a23ee861a2f 100644 +index 49df5f4b09926556986e3a45d52ff299b878af76..8a61ca3cba2888e03e440519714705fe50b81267 100644 --- a/src/main/java/net/minecraft/world/entity/Entity.java +++ b/src/main/java/net/minecraft/world/entity/Entity.java -@@ -2934,6 +2934,7 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess +@@ -3057,6 +3057,7 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess @Nullable public PlayerTeam getTeam() { @@ -23,10 +23,10 @@ index d5f5864b7c1ad4c30f37b360b317b63c129e3a3f..82e57978b79b5275b98a1fa7731c6a23 } diff --git a/src/main/java/net/minecraft/world/entity/LivingEntity.java b/src/main/java/net/minecraft/world/entity/LivingEntity.java -index 97ad63aad3c559feb1e762af1e00fd550aa5b251..ba194f42217f1176ac08123d3bb5cb24e1a6c119 100644 +index 6dba567e9f7a197af16598647f216b5323d1b601..47403887721914b632565947efae0dfa7c841588 100644 --- a/src/main/java/net/minecraft/world/entity/LivingEntity.java +++ b/src/main/java/net/minecraft/world/entity/LivingEntity.java -@@ -848,6 +848,7 @@ public abstract class LivingEntity extends Entity implements Attackable { +@@ -871,6 +871,7 @@ public abstract class LivingEntity extends Entity implements Attackable { String s = nbt.getString("Team"); Scoreboard scoreboard = this.level().getScoreboard(); PlayerTeam scoreboardteam = scoreboard.getPlayerTeam(s); diff --git a/patches/server/0064-Add-methods-for-working-with-arrows-stuck-in-living-.patch b/patches/server/0064-Add-methods-for-working-with-arrows-stuck-in-living-.patch index dc2456c4af06..6da97b4baac7 100644 --- a/patches/server/0064-Add-methods-for-working-with-arrows-stuck-in-living-.patch +++ b/patches/server/0064-Add-methods-for-working-with-arrows-stuck-in-living-.patch @@ -7,10 +7,10 @@ Upstream added methods for this, original methods are now deprecated diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftLivingEntity.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftLivingEntity.java -index ff2118fb463168e061ca9152183e19d593b476e9..5ba4105356e4a4808293e86c679d08a3c4cdd245 100644 +index ea7cbb5b5e7f140c06029ef05a01567d51b39f91..6bb32e4eab357c5f67a3daafa2de035b0d125635 100644 --- a/src/main/java/org/bukkit/craftbukkit/entity/CraftLivingEntity.java +++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftLivingEntity.java -@@ -284,10 +284,29 @@ public class CraftLivingEntity extends CraftEntity implements LivingEntity { +@@ -289,10 +289,29 @@ public class CraftLivingEntity extends CraftEntity implements LivingEntity { } @Override @@ -40,8 +40,8 @@ index ff2118fb463168e061ca9152183e19d593b476e9..5ba4105356e4a4808293e86c679d08a3 + // Paper end - Add methods for working with arrows stuck in living entities @Override - public void damage(double amount) { -@@ -813,4 +832,16 @@ public class CraftLivingEntity extends CraftEntity implements LivingEntity { + public boolean isInvulnerable() { +@@ -833,4 +852,16 @@ public class CraftLivingEntity extends CraftEntity implements LivingEntity { this.getHandle().persistentInvisibility = invisible; this.getHandle().setSharedFlag(5, invisible); } diff --git a/patches/server/0066-Complete-resource-pack-API.patch b/patches/server/0066-Complete-resource-pack-API.patch index 64768be860b5..f330036b9408 100644 --- a/patches/server/0066-Complete-resource-pack-API.patch +++ b/patches/server/0066-Complete-resource-pack-API.patch @@ -5,10 +5,10 @@ Subject: [PATCH] Complete resource pack API diff --git a/src/main/java/net/minecraft/server/network/ServerCommonPacketListenerImpl.java b/src/main/java/net/minecraft/server/network/ServerCommonPacketListenerImpl.java -index b2bddc6183204b9f519549073e38741e1a9322c4..9bfdcdf427f7c0689d346d17942b5902a9138a4e 100644 +index 99f89854e43ed6742dc9ac1624fa7140b4594b3b..d4527831f66bf1c55e6273c7f8923d6efbbf100f 100644 --- a/src/main/java/net/minecraft/server/network/ServerCommonPacketListenerImpl.java +++ b/src/main/java/net/minecraft/server/network/ServerCommonPacketListenerImpl.java -@@ -207,7 +207,11 @@ public abstract class ServerCommonPacketListenerImpl implements ServerCommonPack +@@ -214,7 +214,11 @@ public abstract class ServerCommonPacketListenerImpl implements ServerCommonPack callback.packEventReceived(packet.id(), net.kyori.adventure.resource.ResourcePackStatus.valueOf(packet.action().name()), this.getCraftPlayer()); } // Paper end @@ -22,10 +22,10 @@ index b2bddc6183204b9f519549073e38741e1a9322c4..9bfdcdf427f7c0689d346d17942b5902 } diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java -index 951b89ced33b2a12dff90e32201643bee64962e1..a19e1c7821c6abfa4288d8a16e30b2160be742f5 100644 +index e3b8cc11a5523614594f1c3a144991fa70227e57..e904d8558c425d3b2027053f4083999f0deb36fb 100644 --- a/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java +++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java -@@ -208,6 +208,7 @@ public class CraftPlayer extends CraftHumanEntity implements Player { +@@ -213,6 +213,7 @@ public class CraftPlayer extends CraftHumanEntity implements Player { private double healthScale = 20; private CraftWorldBorder clientWorldBorder = null; private BorderChangeListener clientWorldBorderListener = this.createWorldBorderListener(); @@ -33,7 +33,7 @@ index 951b89ced33b2a12dff90e32201643bee64962e1..a19e1c7821c6abfa4288d8a16e30b216 public CraftPlayer(CraftServer server, ServerPlayer entity) { super(server, entity); -@@ -2094,6 +2095,13 @@ public class CraftPlayer extends CraftHumanEntity implements Player { +@@ -2121,6 +2122,13 @@ public class CraftPlayer extends CraftHumanEntity implements Player { } // Paper end - adventure diff --git a/patches/server/0067-Default-loading-permissions.yml-before-plugins.patch b/patches/server/0067-Default-loading-permissions.yml-before-plugins.patch index 09d482562060..e933d73ae3c6 100644 --- a/patches/server/0067-Default-loading-permissions.yml-before-plugins.patch +++ b/patches/server/0067-Default-loading-permissions.yml-before-plugins.patch @@ -16,10 +16,10 @@ modify that. Under the previous logic, plugins were unable (cleanly) override pe A config option has been added for those who depend on the previous behavior, but I don't expect that. diff --git a/src/main/java/org/bukkit/craftbukkit/CraftServer.java b/src/main/java/org/bukkit/craftbukkit/CraftServer.java -index 3043ec179592606050deede2874b0e251b859892..dfd8776ff6cd8be750e77dc4c30f59c28ff1732a 100644 +index bb562ada45b828c79d83afdd1687edd32cbaf1dc..d581bfad059e60a693bc27285a723979e7d7dd34 100644 --- a/src/main/java/org/bukkit/craftbukkit/CraftServer.java +++ b/src/main/java/org/bukkit/craftbukkit/CraftServer.java -@@ -488,6 +488,7 @@ public final class CraftServer implements Server { +@@ -490,6 +490,7 @@ public final class CraftServer implements Server { if (type == PluginLoadOrder.STARTUP) { this.helpMap.clear(); this.helpMap.initializeGeneralTopics(); @@ -27,7 +27,7 @@ index 3043ec179592606050deede2874b0e251b859892..dfd8776ff6cd8be750e77dc4c30f59c2 } Plugin[] plugins = this.pluginManager.getPlugins(); -@@ -507,7 +508,7 @@ public final class CraftServer implements Server { +@@ -509,7 +510,7 @@ public final class CraftServer implements Server { this.commandMap.registerServerAliases(); DefaultPermissions.registerCorePermissions(); CraftDefaultPermissions.registerCorePermissions(); diff --git a/patches/server/0068-Allow-Reloading-of-Custom-Permissions.patch b/patches/server/0068-Allow-Reloading-of-Custom-Permissions.patch index c66e0d8e2656..8205a118dd47 100644 --- a/patches/server/0068-Allow-Reloading-of-Custom-Permissions.patch +++ b/patches/server/0068-Allow-Reloading-of-Custom-Permissions.patch @@ -6,10 +6,10 @@ Subject: [PATCH] Allow Reloading of Custom Permissions https://github.com/PaperMC/Paper/issues/49 diff --git a/src/main/java/org/bukkit/craftbukkit/CraftServer.java b/src/main/java/org/bukkit/craftbukkit/CraftServer.java -index dfd8776ff6cd8be750e77dc4c30f59c28ff1732a..2b0f69b89d69258a1e9a6a81db5c9d4497417cd7 100644 +index d581bfad059e60a693bc27285a723979e7d7dd34..2b4c1c3cc4b740d5aceb483c4066f23433c175d9 100644 --- a/src/main/java/org/bukkit/craftbukkit/CraftServer.java +++ b/src/main/java/org/bukkit/craftbukkit/CraftServer.java -@@ -2792,5 +2792,23 @@ public final class CraftServer implements Server { +@@ -2808,5 +2808,23 @@ public final class CraftServer implements Server { } return this.adventure$audiences; } diff --git a/patches/server/0069-Remove-Metadata-on-reload.patch b/patches/server/0069-Remove-Metadata-on-reload.patch index 6754fe94caa1..e5592929ff10 100644 --- a/patches/server/0069-Remove-Metadata-on-reload.patch +++ b/patches/server/0069-Remove-Metadata-on-reload.patch @@ -7,10 +7,10 @@ Metadata is not meant to persist reload as things break badly with non primitive This will remove metadata on reload so it does not crash everything if a plugin uses it. diff --git a/src/main/java/org/bukkit/craftbukkit/CraftServer.java b/src/main/java/org/bukkit/craftbukkit/CraftServer.java -index 2b0f69b89d69258a1e9a6a81db5c9d4497417cd7..b7539a5f7bc20f20f3cd7fb30d87ab7ffc1133c3 100644 +index 2b4c1c3cc4b740d5aceb483c4066f23433c175d9..c6ea35f752c9a0bf9e5e07e3b501fd3fe8c30084 100644 --- a/src/main/java/org/bukkit/craftbukkit/CraftServer.java +++ b/src/main/java/org/bukkit/craftbukkit/CraftServer.java -@@ -1003,8 +1003,16 @@ public final class CraftServer implements Server { +@@ -1005,8 +1005,16 @@ public final class CraftServer implements Server { world.spigotConfig.init(); // Spigot } diff --git a/patches/server/0070-Handle-Item-Meta-Inconsistencies.patch b/patches/server/0070-Handle-Item-Meta-Inconsistencies.patch index b282519efe64..e6681a5fd83f 100644 --- a/patches/server/0070-Handle-Item-Meta-Inconsistencies.patch +++ b/patches/server/0070-Handle-Item-Meta-Inconsistencies.patch @@ -18,7 +18,7 @@ For consistency, the old API methods now forward to use the ItemMeta API equivalents, and should deprecate the old API's. diff --git a/src/main/java/net/minecraft/world/item/enchantment/ItemEnchantments.java b/src/main/java/net/minecraft/world/item/enchantment/ItemEnchantments.java -index a4f8cb2c9dc464e94483f5574cddab85ef407048..8ac485d82c2d2b32f4d54e02c18c2cb2c3df4fa4 100644 +index bc6c2c24174181315c5622ba0dbe578b4dbcc627..cfc6a657cae92c68868a76c1b7b1febe2a16e9f4 100644 --- a/src/main/java/net/minecraft/world/item/enchantment/ItemEnchantments.java +++ b/src/main/java/net/minecraft/world/item/enchantment/ItemEnchantments.java @@ -26,12 +26,25 @@ import net.minecraft.tags.TagKey; @@ -35,7 +35,7 @@ index a4f8cb2c9dc464e94483f5574cddab85ef407048..8ac485d82c2d2b32f4d54e02c18c2cb2 + private static final java.util.Comparator> ENCHANTMENT_ORDER = java.util.Comparator.comparing(Holder::getRegisteredName); + public static final ItemEnchantments EMPTY = new ItemEnchantments(new Object2IntAVLTreeMap<>(ENCHANTMENT_ORDER), true); + // Paper end - private static final Codec LEVEL_CODEC = Codec.intRange(0, 255); + private static final Codec LEVEL_CODEC = Codec.intRange(1, 255); - private static final Codec>> LEVELS_CODEC = Codec.unboundedMap(Enchantment.CODEC, LEVEL_CODEC) - .xmap(Object2IntOpenHashMap::new, Function.identity()); + private static final Codec>> LEVELS_CODEC = Codec.unboundedMap( @@ -70,7 +70,7 @@ index a4f8cb2c9dc464e94483f5574cddab85ef407048..8ac485d82c2d2b32f4d54e02c18c2cb2 this.enchantments = enchantments; this.showInTooltip = showInTooltip; -@@ -141,7 +154,7 @@ public class ItemEnchantments implements TooltipProvider { +@@ -139,7 +152,7 @@ public class ItemEnchantments implements TooltipProvider { } public static class Mutable { @@ -80,10 +80,10 @@ index a4f8cb2c9dc464e94483f5574cddab85ef407048..8ac485d82c2d2b32f4d54e02c18c2cb2 public Mutable(ItemEnchantments enchantmentsComponent) { diff --git a/src/main/java/org/bukkit/craftbukkit/inventory/CraftItemStack.java b/src/main/java/org/bukkit/craftbukkit/inventory/CraftItemStack.java -index 17fa2d3db112762bcb8b941b69b8ddcc53f47224..6c76aeddb34239a5acc204a17b2aa2d80e6b2c88 100644 +index 101eea3452c9e387e770b716543c3a4f17b9a737..aea09533fada5bd3d42e2cc147921167a5e7c1a5 100644 --- a/src/main/java/org/bukkit/craftbukkit/inventory/CraftItemStack.java +++ b/src/main/java/org/bukkit/craftbukkit/inventory/CraftItemStack.java -@@ -214,16 +214,13 @@ public final class CraftItemStack extends ItemStack { +@@ -229,16 +229,13 @@ public final class CraftItemStack extends ItemStack { public void addUnsafeEnchantment(Enchantment ench, int level) { Preconditions.checkArgument(ench != null, "Enchantment cannot be null"); @@ -106,7 +106,7 @@ index 17fa2d3db112762bcb8b941b69b8ddcc53f47224..6c76aeddb34239a5acc204a17b2aa2d8 } static boolean makeTag(net.minecraft.world.item.ItemStack item) { -@@ -252,24 +249,15 @@ public final class CraftItemStack extends ItemStack { +@@ -267,24 +264,15 @@ public final class CraftItemStack extends ItemStack { public int removeEnchantment(Enchantment ench) { Preconditions.checkArgument(ench != null, "Enchantment cannot be null"); @@ -139,7 +139,7 @@ index 17fa2d3db112762bcb8b941b69b8ddcc53f47224..6c76aeddb34239a5acc204a17b2aa2d8 return level; } -@@ -281,7 +269,7 @@ public final class CraftItemStack extends ItemStack { +@@ -296,7 +284,7 @@ public final class CraftItemStack extends ItemStack { @Override public Map getEnchantments() { @@ -149,7 +149,7 @@ index 17fa2d3db112762bcb8b941b69b8ddcc53f47224..6c76aeddb34239a5acc204a17b2aa2d8 static Map getEnchantments(net.minecraft.world.item.ItemStack item) { diff --git a/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaItem.java b/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaItem.java -index f6ac13f91f08498a8adda7d34518a5cfe34c15b2..9ab1abd4414a0a40284e12dfebff37e21e7f150f 100644 +index 1c943638bfbda8281d2c9038e9024591823e2b5e..b2683d6efd53b03d7043098b19a1504885a5b3c7 100644 --- a/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaItem.java +++ b/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaItem.java @@ -6,6 +6,7 @@ import com.google.common.collect.ImmutableList; @@ -168,16 +168,16 @@ index f6ac13f91f08498a8adda7d34518a5cfe34c15b2..9ab1abd4414a0a40284e12dfebff37e2 import java.util.EnumSet; import java.util.HashMap; import java.util.Iterator; -@@ -250,7 +252,7 @@ class CraftMetaItem implements ItemMeta, Damageable, Repairable, BlockDataMeta { - private List lore; // null and empty are two different states internally +@@ -281,7 +283,7 @@ class CraftMetaItem implements ItemMeta, Damageable, Repairable, BlockDataMeta { private Integer customModelData; + private Integer enchantableValue; private Map blockData; - private Map enchantments; + private EnchantmentMap enchantments; // Paper private Multimap attributeModifiers; private int repairCost; private int hideFlag; -@@ -292,7 +294,7 @@ class CraftMetaItem implements ItemMeta, Damageable, Repairable, BlockDataMeta { +@@ -330,7 +332,7 @@ class CraftMetaItem implements ItemMeta, Damageable, Repairable, BlockDataMeta { this.blockData = meta.blockData; if (meta.enchantments != null) { @@ -186,7 +186,7 @@ index f6ac13f91f08498a8adda7d34518a5cfe34c15b2..9ab1abd4414a0a40284e12dfebff37e2 } if (meta.hasAttributeModifiers()) { -@@ -438,8 +440,8 @@ class CraftMetaItem implements ItemMeta, Damageable, Repairable, BlockDataMeta { +@@ -509,8 +511,8 @@ class CraftMetaItem implements ItemMeta, Damageable, Repairable, BlockDataMeta { } } @@ -197,7 +197,7 @@ index f6ac13f91f08498a8adda7d34518a5cfe34c15b2..9ab1abd4414a0a40284e12dfebff37e2 tag.entrySet().forEach((entry) -> { Holder id = entry.getKey(); -@@ -736,13 +738,13 @@ class CraftMetaItem implements ItemMeta, Damageable, Repairable, BlockDataMeta { +@@ -844,13 +846,13 @@ class CraftMetaItem implements ItemMeta, Damageable, Repairable, BlockDataMeta { return modifiers; } @@ -213,7 +213,7 @@ index f6ac13f91f08498a8adda7d34518a5cfe34c15b2..9ab1abd4414a0a40284e12dfebff37e2 for (Map.Entry entry : ench.entrySet()) { Enchantment enchantment = CraftEnchantment.stringToBukkit(entry.getKey().toString()); if ((enchantment != null) && (entry.getValue() instanceof Integer)) { -@@ -1081,14 +1083,14 @@ class CraftMetaItem implements ItemMeta, Damageable, Repairable, BlockDataMeta { +@@ -1217,14 +1219,14 @@ class CraftMetaItem implements ItemMeta, Damageable, Repairable, BlockDataMeta { @Override public Map getEnchants() { @@ -230,8 +230,8 @@ index f6ac13f91f08498a8adda7d34518a5cfe34c15b2..9ab1abd4414a0a40284e12dfebff37e2 } if (ignoreRestrictions || level >= ench.getStartLevel() && level <= ench.getMaxLevel()) { -@@ -1688,7 +1690,7 @@ class CraftMetaItem implements ItemMeta, Damageable, Repairable, BlockDataMeta { - clone.customModelData = this.customModelData; +@@ -1955,7 +1957,7 @@ class CraftMetaItem implements ItemMeta, Damageable, Repairable, BlockDataMeta { + clone.enchantableValue = this.enchantableValue; clone.blockData = this.blockData; if (this.enchantments != null) { - clone.enchantments = new LinkedHashMap(this.enchantments); @@ -239,7 +239,7 @@ index f6ac13f91f08498a8adda7d34518a5cfe34c15b2..9ab1abd4414a0a40284e12dfebff37e2 } if (this.hasAttributeModifiers()) { clone.attributeModifiers = LinkedHashMultimap.create(this.attributeModifiers); -@@ -2038,4 +2040,22 @@ class CraftMetaItem implements ItemMeta, Damageable, Repairable, BlockDataMeta { +@@ -2351,4 +2353,22 @@ class CraftMetaItem implements ItemMeta, Damageable, Repairable, BlockDataMeta { return (result != null) ? result : Optional.empty(); } diff --git a/patches/server/0071-Configurable-Non-Player-Arrow-Despawn-Rate.patch b/patches/server/0071-Configurable-Non-Player-Arrow-Despawn-Rate.patch index e1e0b3df9cb3..f59a8a6609bd 100644 --- a/patches/server/0071-Configurable-Non-Player-Arrow-Despawn-Rate.patch +++ b/patches/server/0071-Configurable-Non-Player-Arrow-Despawn-Rate.patch @@ -6,10 +6,10 @@ Subject: [PATCH] Configurable Non Player Arrow Despawn Rate Can set a much shorter despawn rate for arrows that players can not pick up. diff --git a/src/main/java/net/minecraft/world/entity/projectile/AbstractArrow.java b/src/main/java/net/minecraft/world/entity/projectile/AbstractArrow.java -index 89eebea583550fe703005e5a7020fa4063778a4a..230040bef7d2cf9a463cfd9cb3b1b1aa208a7119 100644 +index a4a932c9b0ad8f83c32bb06428c74d5b4c1741d1..a1b1c6385d3a1fbe38f5ae4471b8e4f6ef2c80b3 100644 --- a/src/main/java/net/minecraft/world/entity/projectile/AbstractArrow.java +++ b/src/main/java/net/minecraft/world/entity/projectile/AbstractArrow.java -@@ -356,7 +356,7 @@ public abstract class AbstractArrow extends Projectile { +@@ -379,7 +379,7 @@ public abstract class AbstractArrow extends Projectile { protected void tickDespawn() { ++this.life; diff --git a/patches/server/0072-Add-World-Util-Methods.patch b/patches/server/0072-Add-World-Util-Methods.patch index a0b554c53b60..4c7b9c4d2737 100644 --- a/patches/server/0072-Add-World-Util-Methods.patch +++ b/patches/server/0072-Add-World-Util-Methods.patch @@ -6,10 +6,10 @@ Subject: [PATCH] Add World Util Methods Methods that can be used for other patches to help improve logic. diff --git a/src/main/java/net/minecraft/world/level/Level.java b/src/main/java/net/minecraft/world/level/Level.java -index 32b42d25631aecdd31db4954a8bbf38bcda98d6b..dfb349ed8a0fb981b3234baf040bd3297a555ebf 100644 +index f900c86697f5a3da45b724944a663fca8f89413e..d1b117f25bafb294f00c18be02be593061120956 100644 --- a/src/main/java/net/minecraft/world/level/Level.java +++ b/src/main/java/net/minecraft/world/level/Level.java -@@ -341,6 +341,22 @@ public abstract class Level implements LevelAccessor, AutoCloseable { +@@ -339,6 +339,22 @@ public abstract class Level implements LevelAccessor, AutoCloseable { return chunk == null ? null : chunk.getFluidState(blockposition); } diff --git a/patches/server/0073-Custom-replacement-for-eaten-items.patch b/patches/server/0073-Custom-replacement-for-eaten-items.patch index c7a07a43588b..25ce9478525b 100644 --- a/patches/server/0073-Custom-replacement-for-eaten-items.patch +++ b/patches/server/0073-Custom-replacement-for-eaten-items.patch @@ -5,11 +5,11 @@ Subject: [PATCH] Custom replacement for eaten items diff --git a/src/main/java/net/minecraft/world/entity/LivingEntity.java b/src/main/java/net/minecraft/world/entity/LivingEntity.java -index a5f7289158623c8fb9fd02beecc2ce9f2057a00c..b8a69688bcc69dff11e4bceb06ab435bfc6b3fc9 100644 +index 47403887721914b632565947efae0dfa7c841588..031b7f51959d1b8ca30e4a9fda0a2832516c9c0c 100644 --- a/src/main/java/net/minecraft/world/entity/LivingEntity.java +++ b/src/main/java/net/minecraft/world/entity/LivingEntity.java -@@ -3858,10 +3858,11 @@ public abstract class LivingEntity extends Entity implements Attackable { - this.triggerItemUseEffects(this.useItem, 16); +@@ -3982,10 +3982,11 @@ public abstract class LivingEntity extends Entity implements Attackable { + if (!this.useItem.isEmpty() && this.isUsingItem()) { // CraftBukkit start - fire PlayerItemConsumeEvent ItemStack itemstack; + PlayerItemConsumeEvent event = null; // Paper @@ -21,7 +21,7 @@ index a5f7289158623c8fb9fd02beecc2ce9f2057a00c..b8a69688bcc69dff11e4bceb06ab435b this.level().getCraftServer().getPluginManager().callEvent(event); if (event.isCancelled()) { -@@ -3878,6 +3879,12 @@ public abstract class LivingEntity extends Entity implements Attackable { +@@ -4003,6 +4004,12 @@ public abstract class LivingEntity extends Entity implements Attackable { } else { itemstack = this.useItem.finishUsingItem(this.level(), this); } @@ -34,7 +34,7 @@ index a5f7289158623c8fb9fd02beecc2ce9f2057a00c..b8a69688bcc69dff11e4bceb06ab435b // CraftBukkit end if (itemstack != this.useItem) { -@@ -3885,6 +3892,11 @@ public abstract class LivingEntity extends Entity implements Attackable { +@@ -4010,6 +4017,11 @@ public abstract class LivingEntity extends Entity implements Attackable { } this.stopUsingItem(); diff --git a/patches/server/0074-handle-NaN-health-absorb-values-and-repair-bad-data.patch b/patches/server/0074-handle-NaN-health-absorb-values-and-repair-bad-data.patch index 1d38bc8302cb..d019f5b8cbeb 100644 --- a/patches/server/0074-handle-NaN-health-absorb-values-and-repair-bad-data.patch +++ b/patches/server/0074-handle-NaN-health-absorb-values-and-repair-bad-data.patch @@ -5,10 +5,10 @@ Subject: [PATCH] handle NaN health/absorb values and repair bad data diff --git a/src/main/java/net/minecraft/world/entity/LivingEntity.java b/src/main/java/net/minecraft/world/entity/LivingEntity.java -index e7b308ba4a253b270aebebd19e2671514c5357ca..fae7c493c9be741f019fc49f45a59976e6b01ed7 100644 +index 031b7f51959d1b8ca30e4a9fda0a2832516c9c0c..e5d1877f570b302f0b193e77ff5fdd7e89532513 100644 --- a/src/main/java/net/minecraft/world/entity/LivingEntity.java +++ b/src/main/java/net/minecraft/world/entity/LivingEntity.java -@@ -808,7 +808,13 @@ public abstract class LivingEntity extends Entity implements Attackable { +@@ -831,7 +831,13 @@ public abstract class LivingEntity extends Entity implements Attackable { @Override public void readAdditionalSaveData(CompoundTag nbt) { @@ -23,7 +23,7 @@ index e7b308ba4a253b270aebebd19e2671514c5357ca..fae7c493c9be741f019fc49f45a59976 if (nbt.contains("attributes", 9) && this.level() != null && !this.level().isClientSide) { this.getAttributes().load(nbt.getList("attributes", 10)); } -@@ -1342,6 +1348,10 @@ public abstract class LivingEntity extends Entity implements Attackable { +@@ -1371,6 +1377,10 @@ public abstract class LivingEntity extends Entity implements Attackable { } public void setHealth(float health) { @@ -34,7 +34,7 @@ index e7b308ba4a253b270aebebd19e2671514c5357ca..fae7c493c9be741f019fc49f45a59976 // CraftBukkit start - Handle scaled health if (this instanceof ServerPlayer) { org.bukkit.craftbukkit.entity.CraftPlayer player = ((ServerPlayer) this).getBukkitEntity(); -@@ -3688,7 +3698,7 @@ public abstract class LivingEntity extends Entity implements Attackable { +@@ -3839,7 +3849,7 @@ public abstract class LivingEntity extends Entity implements Attackable { } public final void setAbsorptionAmount(float absorptionAmount) { @@ -44,10 +44,10 @@ index e7b308ba4a253b270aebebd19e2671514c5357ca..fae7c493c9be741f019fc49f45a59976 protected void internalSetAbsorptionAmount(float absorptionAmount) { diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java -index 174c86a7213ecf3c8dc03aeaf55c56a4c4ce55ad..c399264ccffe646be10b3f13ebe0c0103d97f62c 100644 +index e904d8558c425d3b2027053f4083999f0deb36fb..d578d65883f23cc4aaa5b0a7cf1fc88bb337f3a5 100644 --- a/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java +++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java -@@ -2338,6 +2338,7 @@ public class CraftPlayer extends CraftHumanEntity implements Player { +@@ -2365,6 +2365,7 @@ public class CraftPlayer extends CraftHumanEntity implements Player { } public void setRealHealth(double health) { diff --git a/patches/server/0075-Use-a-Shared-Random-for-Entities.patch b/patches/server/0075-Use-a-Shared-Random-for-Entities.patch index 0dc711a42bc2..b053616a5f74 100644 --- a/patches/server/0075-Use-a-Shared-Random-for-Entities.patch +++ b/patches/server/0075-Use-a-Shared-Random-for-Entities.patch @@ -6,10 +6,10 @@ Subject: [PATCH] Use a Shared Random for Entities Reduces memory usage and provides ensures more randomness, Especially since a lot of garbage entity objects get created. diff --git a/src/main/java/net/minecraft/world/entity/Entity.java b/src/main/java/net/minecraft/world/entity/Entity.java -index 82e57978b79b5275b98a1fa7731c6a23ee861a2f..bd17157631a74f80e3b5ce50bb1f681abe1dd6a7 100644 +index 8a61ca3cba2888e03e440519714705fe50b81267..847fd720f11e89d906430c820bc92afe0454761d 100644 --- a/src/main/java/net/minecraft/world/entity/Entity.java +++ b/src/main/java/net/minecraft/world/entity/Entity.java -@@ -175,6 +175,79 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess +@@ -183,6 +183,79 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess return tag.contains("Bukkit.updateLevel") && tag.getInt("Bukkit.updateLevel") >= level; } @@ -89,7 +89,7 @@ index 82e57978b79b5275b98a1fa7731c6a23ee861a2f..bd17157631a74f80e3b5ce50bb1f681a private CraftEntity bukkitEntity; public CraftEntity getBukkitEntity() { -@@ -370,7 +443,7 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess +@@ -373,7 +446,7 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess this.bb = Entity.INITIAL_AABB; this.stuckSpeedMultiplier = Vec3.ZERO; this.nextStep = 1.0F; @@ -99,10 +99,10 @@ index 82e57978b79b5275b98a1fa7731c6a23ee861a2f..bd17157631a74f80e3b5ce50bb1f681a this.fluidHeight = new Object2DoubleArrayMap(2); this.fluidOnEyes = new HashSet(); diff --git a/src/main/java/net/minecraft/world/entity/animal/Squid.java b/src/main/java/net/minecraft/world/entity/animal/Squid.java -index 97d5bc9bab95c21afb3b95d2a7eb7df0b64698ed..42f4e544fe7fbc342f15eacb5e38d40849e3c419 100644 +index bcccd7d808a3bef4acafe2e6b484ba0ed8059507..f9fdc600dc680c55219fcbf9bc8f151a733a093c 100644 --- a/src/main/java/net/minecraft/world/entity/animal/Squid.java +++ b/src/main/java/net/minecraft/world/entity/animal/Squid.java -@@ -41,7 +41,7 @@ public class Squid extends WaterAnimal { +@@ -46,7 +46,7 @@ public class Squid extends AgeableWaterCreature { public Squid(EntityType type, Level world) { super(type, world); diff --git a/patches/server/0076-Configurable-spawn-chances-for-skeleton-horses.patch b/patches/server/0076-Configurable-spawn-chances-for-skeleton-horses.patch index 5d754792063d..4cf4bf11817d 100644 --- a/patches/server/0076-Configurable-spawn-chances-for-skeleton-horses.patch +++ b/patches/server/0076-Configurable-spawn-chances-for-skeleton-horses.patch @@ -5,10 +5,10 @@ Subject: [PATCH] Configurable spawn chances for skeleton horses diff --git a/src/main/java/net/minecraft/server/level/ServerLevel.java b/src/main/java/net/minecraft/server/level/ServerLevel.java -index 6d89dc22e28a9c3557d9972be0935d75719e7f7d..de1708e5d39561115d2825eea4a5f6e1f1f4e5b8 100644 +index 0e967e4c73dabc6cf6580d863c640cf4dff4f3bf..e623f8e75ec895d18d854a1f2c0dbe41a67dffc8 100644 --- a/src/main/java/net/minecraft/server/level/ServerLevel.java +++ b/src/main/java/net/minecraft/server/level/ServerLevel.java -@@ -621,7 +621,7 @@ public class ServerLevel extends Level implements WorldGenLevel { +@@ -594,7 +594,7 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe if (this.isRainingAt(blockposition)) { DifficultyInstance difficultydamagescaler = this.getCurrentDifficultyAt(blockposition); @@ -16,4 +16,4 @@ index 6d89dc22e28a9c3557d9972be0935d75719e7f7d..de1708e5d39561115d2825eea4a5f6e1 + boolean flag1 = this.getGameRules().getBoolean(GameRules.RULE_DOMOBSPAWNING) && this.random.nextDouble() < (double) difficultydamagescaler.getEffectiveDifficulty() * this.paperConfig().entities.spawning.skeletonHorseThunderSpawnChance.or(0.01D) && !this.getBlockState(blockposition.below()).is(Blocks.LIGHTNING_ROD); // Paper - Configurable spawn chances for skeleton horses if (flag1) { - SkeletonHorse entityhorseskeleton = (SkeletonHorse) EntityType.SKELETON_HORSE.create(this); + SkeletonHorse entityhorseskeleton = (SkeletonHorse) EntityType.SKELETON_HORSE.create(this, EntitySpawnReason.EVENT); diff --git a/patches/server/0077-Only-process-BlockPhysicsEvent-if-a-plugin-has-a-lis.patch b/patches/server/0077-Only-process-BlockPhysicsEvent-if-a-plugin-has-a-lis.patch index 8730c44f6b2b..169cb0c090db 100644 --- a/patches/server/0077-Only-process-BlockPhysicsEvent-if-a-plugin-has-a-lis.patch +++ b/patches/server/0077-Only-process-BlockPhysicsEvent-if-a-plugin-has-a-lis.patch @@ -6,22 +6,22 @@ Subject: [PATCH] Only process BlockPhysicsEvent if a plugin has a listener Saves on some object allocation and processing when no plugin listens to this diff --git a/src/main/java/net/minecraft/server/MinecraftServer.java b/src/main/java/net/minecraft/server/MinecraftServer.java -index 1ba2b1aaf0936963aca73a43632f978c95bf95e6..639dd562b7eda25004ea10d6c481173afb214773 100644 +index cc4c5fcbeca4862d8ff78b127cb3f2c07956dfaf..ece7f630937abd6d341a2498bac2f0afcfb1ce0f 100644 --- a/src/main/java/net/minecraft/server/MinecraftServer.java +++ b/src/main/java/net/minecraft/server/MinecraftServer.java -@@ -1542,6 +1542,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop 0; // Paper - BlockPhysicsEvent - this.profiler.push(() -> { + gameprofilerfiller.push(() -> { String s = String.valueOf(worldserver); diff --git a/src/main/java/net/minecraft/server/level/ServerLevel.java b/src/main/java/net/minecraft/server/level/ServerLevel.java -index de1708e5d39561115d2825eea4a5f6e1f1f4e5b8..1e5b42fc3903b14537d232c8adbbaf79078d8d8e 100644 +index e623f8e75ec895d18d854a1f2c0dbe41a67dffc8..50296ff319fd6f97e27ec1cb6bdcd7b85a6ce926 100644 --- a/src/main/java/net/minecraft/server/level/ServerLevel.java +++ b/src/main/java/net/minecraft/server/level/ServerLevel.java -@@ -227,6 +227,7 @@ public class ServerLevel extends Level implements WorldGenLevel { +@@ -228,6 +228,7 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe // CraftBukkit start public final LevelStorageSource.LevelStorageAccess convertable; public final UUID uuid; @@ -30,10 +30,10 @@ index de1708e5d39561115d2825eea4a5f6e1f1f4e5b8..1e5b42fc3903b14537d232c8adbbaf79 public LevelChunk getChunkIfLoaded(int x, int z) { return this.chunkSource.getChunk(x, z, false); diff --git a/src/main/java/net/minecraft/world/level/Level.java b/src/main/java/net/minecraft/world/level/Level.java -index dfb349ed8a0fb981b3234baf040bd3297a555ebf..024da1029baecae639d7c05b5f7c30dbaa67b006 100644 +index d1b117f25bafb294f00c18be02be593061120956..9e88e9c473d1ab02344afd9634c625b95b5f38ef 100644 --- a/src/main/java/net/minecraft/world/level/Level.java +++ b/src/main/java/net/minecraft/world/level/Level.java -@@ -491,7 +491,7 @@ public abstract class Level implements LevelAccessor, AutoCloseable { +@@ -489,7 +489,7 @@ public abstract class Level implements LevelAccessor, AutoCloseable { // CraftBukkit start iblockdata1.updateIndirectNeighbourShapes(this, blockposition, k, j - 1); // Don't call an event for the old block to limit event spam CraftWorld world = ((ServerLevel) this).getWorld(); @@ -43,23 +43,23 @@ index dfb349ed8a0fb981b3234baf040bd3297a555ebf..024da1029baecae639d7c05b5f7c30db this.getCraftServer().getPluginManager().callEvent(event); diff --git a/src/main/java/net/minecraft/world/level/block/BushBlock.java b/src/main/java/net/minecraft/world/level/block/BushBlock.java -index 75109c79daa724340268438725bbf6d39b955691..a7b4b5600e3c889c69ac22294899713d50b5fe5c 100644 +index 4db8f94dc279d05ed1cdf52e49ef780025828067..eb324fda54ada3ed7941713a784ed2d686ec8c4b 100644 --- a/src/main/java/net/minecraft/world/level/block/BushBlock.java +++ b/src/main/java/net/minecraft/world/level/block/BushBlock.java -@@ -28,7 +28,7 @@ public abstract class BushBlock extends Block { - protected BlockState updateShape(BlockState state, Direction direction, BlockState neighborState, LevelAccessor world, BlockPos pos, BlockPos neighborPos) { +@@ -31,7 +31,7 @@ public abstract class BushBlock extends Block { // CraftBukkit start if (!state.canSurvive(world, pos)) { -- if (!org.bukkit.craftbukkit.event.CraftEventFactory.callBlockPhysicsEvent(world, pos).isCancelled()) { -+ if (!(world instanceof net.minecraft.server.level.ServerLevel && ((net.minecraft.server.level.ServerLevel) world).hasPhysicsEvent) || !org.bukkit.craftbukkit.event.CraftEventFactory.callBlockPhysicsEvent(world, pos).isCancelled()) { // Paper + // Suppress during worldgen +- if (!(world instanceof Level world1) || !org.bukkit.craftbukkit.event.CraftEventFactory.callBlockPhysicsEvent(world1, pos).isCancelled()) { ++ if (!(world instanceof net.minecraft.server.level.ServerLevel world1 && world1.hasPhysicsEvent) || !org.bukkit.craftbukkit.event.CraftEventFactory.callBlockPhysicsEvent(world1, pos).isCancelled()) { // Paper return Blocks.AIR.defaultBlockState(); } } diff --git a/src/main/java/net/minecraft/world/level/block/DoublePlantBlock.java b/src/main/java/net/minecraft/world/level/block/DoublePlantBlock.java -index cb76568cd1f97024c7a40328d9d72dd8a3e72e8b..7fdf744a2be55313cc75c1322f6534f55cf463f5 100644 +index 46047e0567af62612aad479fff6bea88903f108a..edb3b6cdb617c48140539728af1373993e78648f 100644 --- a/src/main/java/net/minecraft/world/level/block/DoublePlantBlock.java +++ b/src/main/java/net/minecraft/world/level/block/DoublePlantBlock.java -@@ -102,7 +102,7 @@ public class DoublePlantBlock extends BushBlock { +@@ -104,7 +104,7 @@ public class DoublePlantBlock extends BushBlock { protected static void preventDropFromBottomPart(Level world, BlockPos pos, BlockState state, Player player) { // CraftBukkit start diff --git a/patches/server/0078-Entity-AddTo-RemoveFrom-World-Events.patch b/patches/server/0078-Entity-AddTo-RemoveFrom-World-Events.patch index 83aad502c32e..2c0ae7a0fd1e 100644 --- a/patches/server/0078-Entity-AddTo-RemoveFrom-World-Events.patch +++ b/patches/server/0078-Entity-AddTo-RemoveFrom-World-Events.patch @@ -5,10 +5,10 @@ Subject: [PATCH] Entity AddTo/RemoveFrom World Events diff --git a/src/main/java/net/minecraft/server/level/ServerLevel.java b/src/main/java/net/minecraft/server/level/ServerLevel.java -index 1e5b42fc3903b14537d232c8adbbaf79078d8d8e..e6cf145fa7a2968c70e9e467e3927fd38e199e06 100644 +index 50296ff319fd6f97e27ec1cb6bdcd7b85a6ce926..c36032803e8b7e4a8537f02738075e1a6baed3b1 100644 --- a/src/main/java/net/minecraft/server/level/ServerLevel.java +++ b/src/main/java/net/minecraft/server/level/ServerLevel.java -@@ -2153,6 +2153,7 @@ public class ServerLevel extends Level implements WorldGenLevel { +@@ -2194,6 +2194,7 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe entity.setOrigin(entity.getOriginVector().toLocation(getWorld())); } // Paper end - Entity origin API @@ -16,7 +16,7 @@ index 1e5b42fc3903b14537d232c8adbbaf79078d8d8e..e6cf145fa7a2968c70e9e467e3927fd3 } public void onTrackingEnd(Entity entity) { -@@ -2223,6 +2224,7 @@ public class ServerLevel extends Level implements WorldGenLevel { +@@ -2264,6 +2265,7 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe } } // CraftBukkit end diff --git a/patches/server/0079-Configurable-Chunk-Inhabited-Time.patch b/patches/server/0079-Configurable-Chunk-Inhabited-Time.patch index 9eb058e66940..d5d3a231fca0 100644 --- a/patches/server/0079-Configurable-Chunk-Inhabited-Time.patch +++ b/patches/server/0079-Configurable-Chunk-Inhabited-Time.patch @@ -11,11 +11,11 @@ For people who want all chunks to be treated equally, you can chose a fixed valu This allows to fine-tune vanilla gameplay. diff --git a/src/main/java/net/minecraft/world/level/chunk/LevelChunk.java b/src/main/java/net/minecraft/world/level/chunk/LevelChunk.java -index fb953b2172c322e8abf5aa50060adbc00fe92832..a0f9fb2ac15f2fa51a3bbe915550155ac2e76649 100644 +index 96ea7a5d5d4a69c83c2401e64750d41cd70088fc..a1b6c13d496519ef6ce240036cec6642626903b9 100644 --- a/src/main/java/net/minecraft/world/level/chunk/LevelChunk.java +++ b/src/main/java/net/minecraft/world/level/chunk/LevelChunk.java -@@ -172,6 +172,13 @@ public class LevelChunk extends ChunkAccess { - return new ChunkAccess.TicksToSave(this.blockTicks, this.fluidTicks); +@@ -195,6 +195,13 @@ public class LevelChunk extends ChunkAccess { + return new ChunkAccess.PackedTicks(this.blockTicks.pack(time), this.fluidTicks.pack(time)); } + // Paper start diff --git a/patches/server/0080-EntityPathfindEvent.patch b/patches/server/0080-EntityPathfindEvent.patch index e29cff85e675..f852d81661da 100644 --- a/patches/server/0080-EntityPathfindEvent.patch +++ b/patches/server/0080-EntityPathfindEvent.patch @@ -19,10 +19,10 @@ index d3a279a1a14f99aee8dd516552e5c60de92b4969..a3e0c5af4cc9323c02e88e768cbda9e4 @Override diff --git a/src/main/java/net/minecraft/world/entity/ai/navigation/GroundPathNavigation.java b/src/main/java/net/minecraft/world/entity/ai/navigation/GroundPathNavigation.java -index f1a63db8dac13efcfd73b59c7074f6817cc5dbde..62634bedd97c5be9ecce24ab0cff205715a68da8 100644 +index d41ff85a96df07e4d6d9844289379bd3f0c38dc4..f73b559b8e60859020f762dab88b67b8c912bf8f 100644 --- a/src/main/java/net/minecraft/world/entity/ai/navigation/GroundPathNavigation.java +++ b/src/main/java/net/minecraft/world/entity/ai/navigation/GroundPathNavigation.java -@@ -41,7 +41,7 @@ public class GroundPathNavigation extends PathNavigation { +@@ -42,7 +42,7 @@ public class GroundPathNavigation extends PathNavigation { } @Override @@ -31,30 +31,30 @@ index f1a63db8dac13efcfd73b59c7074f6817cc5dbde..62634bedd97c5be9ecce24ab0cff2057 LevelChunk levelChunk = this.level .getChunkSource() .getChunkNow(SectionPos.blockToSectionCoord(target.getX()), SectionPos.blockToSectionCoord(target.getZ())); -@@ -56,7 +56,7 @@ public class GroundPathNavigation extends PathNavigation { +@@ -57,7 +57,7 @@ public class GroundPathNavigation extends PathNavigation { } - if (blockPos.getY() > this.level.getMinBuildHeight()) { -- return super.createPath(blockPos.above(), distance); -+ return super.createPath(blockPos.above(), entity, distance); // Paper - EntityPathfindEvent + if (mutableBlockPos.getY() > this.level.getMinY()) { +- return super.createPath(mutableBlockPos.above(), distance); ++ return super.createPath(mutableBlockPos.above(), entity, distance); // Paper - EntityPathfindEvent } - while (blockPos.getY() < this.level.getMaxBuildHeight() && levelChunk.getBlockState(blockPos).isAir()) { -@@ -67,7 +67,7 @@ public class GroundPathNavigation extends PathNavigation { + mutableBlockPos.setY(target.getY() + 1); +@@ -70,7 +70,7 @@ public class GroundPathNavigation extends PathNavigation { } if (!levelChunk.getBlockState(target).isSolid()) { - return super.createPath(target, distance); + return super.createPath(target, entity, distance); // Paper - EntityPathfindEvent } else { - BlockPos blockPos2 = target.above(); + BlockPos.MutableBlockPos mutableBlockPos2 = target.mutable().move(Direction.UP); -@@ -75,14 +75,14 @@ public class GroundPathNavigation extends PathNavigation { - blockPos2 = blockPos2.above(); +@@ -78,14 +78,14 @@ public class GroundPathNavigation extends PathNavigation { + mutableBlockPos2.move(Direction.UP); } -- return super.createPath(blockPos2, distance); -+ return super.createPath(blockPos2, entity, distance); // Paper - EntityPathfindEvent +- return super.createPath(mutableBlockPos2.immutable(), distance); ++ return super.createPath(mutableBlockPos2.immutable(), entity, distance); // Paper - EntityPathfindEvent } } } @@ -67,10 +67,10 @@ index f1a63db8dac13efcfd73b59c7074f6817cc5dbde..62634bedd97c5be9ecce24ab0cff2057 private int getSurfaceY() { diff --git a/src/main/java/net/minecraft/world/entity/ai/navigation/PathNavigation.java b/src/main/java/net/minecraft/world/entity/ai/navigation/PathNavigation.java -index 92addc1d3e7d0630c5db76e2a00d61b95b1430cc..21bbc98b26b270b3ad6a3b34d6e50dfb796c3d5a 100644 +index 7d3cb358fe543253e988531df3434ae1274814d3..1d5ce4caf99a3fb376b350968a6bd1ac8471ffec 100644 --- a/src/main/java/net/minecraft/world/entity/ai/navigation/PathNavigation.java +++ b/src/main/java/net/minecraft/world/entity/ai/navigation/PathNavigation.java -@@ -109,7 +109,13 @@ public abstract class PathNavigation { +@@ -125,7 +125,13 @@ public abstract class PathNavigation { @Nullable public Path createPath(BlockPos target, int distance) { @@ -85,7 +85,7 @@ index 92addc1d3e7d0630c5db76e2a00d61b95b1430cc..21bbc98b26b270b3ad6a3b34d6e50dfb } @Nullable -@@ -119,7 +125,7 @@ public abstract class PathNavigation { +@@ -135,7 +141,7 @@ public abstract class PathNavigation { @Nullable public Path createPath(Entity entity, int distance) { @@ -94,7 +94,7 @@ index 92addc1d3e7d0630c5db76e2a00d61b95b1430cc..21bbc98b26b270b3ad6a3b34d6e50dfb } @Nullable -@@ -129,6 +135,17 @@ public abstract class PathNavigation { +@@ -145,6 +151,17 @@ public abstract class PathNavigation { @Nullable protected Path createPath(Set positions, int range, boolean useHeadPos, int distance, float followRange) { @@ -111,8 +111,8 @@ index 92addc1d3e7d0630c5db76e2a00d61b95b1430cc..21bbc98b26b270b3ad6a3b34d6e50dfb + // Paper end - EntityPathfindEvent if (positions.isEmpty()) { return null; - } else if (this.mob.getY() < (double)this.level.getMinBuildHeight()) { -@@ -138,6 +155,23 @@ public abstract class PathNavigation { + } else if (this.mob.getY() < (double)this.level.getMinY()) { +@@ -154,6 +171,23 @@ public abstract class PathNavigation { } else if (this.path != null && !this.path.isDone() && positions.contains(this.targetPos)) { return this.path; } else { @@ -133,9 +133,9 @@ index 92addc1d3e7d0630c5db76e2a00d61b95b1430cc..21bbc98b26b270b3ad6a3b34d6e50dfb + } + } + // Paper end - EntityPathfindEvent - this.level.getProfiler().push("pathfind"); + ProfilerFiller profilerFiller = Profiler.get(); + profilerFiller.push("pathfind"); BlockPos blockPos = useHeadPos ? this.mob.blockPosition().above() : this.mob.blockPosition(); - int i = (int)(followRange + (float)range); diff --git a/src/main/java/net/minecraft/world/entity/ai/navigation/WallClimberNavigation.java b/src/main/java/net/minecraft/world/entity/ai/navigation/WallClimberNavigation.java index 1a1bc30b425858d82dbfb84b4a94d7793cab7125..5bbfa43d1e97970f035fcb101c3252c01ffead0d 100644 --- a/src/main/java/net/minecraft/world/entity/ai/navigation/WallClimberNavigation.java diff --git a/patches/server/0082-Do-not-load-chunks-for-Pathfinding.patch b/patches/server/0082-Do-not-load-chunks-for-Pathfinding.patch index 76cfe02b0f87..f67f87234bfe 100644 --- a/patches/server/0082-Do-not-load-chunks-for-Pathfinding.patch +++ b/patches/server/0082-Do-not-load-chunks-for-Pathfinding.patch @@ -5,7 +5,7 @@ Subject: [PATCH] Do not load chunks for Pathfinding diff --git a/src/main/java/net/minecraft/world/level/pathfinder/WalkNodeEvaluator.java b/src/main/java/net/minecraft/world/level/pathfinder/WalkNodeEvaluator.java -index 6a8f44600a426041e3974aa52bdec0cc35e26591..d5004290e40a1ff5e0fcfe75f8da34ae15962359 100644 +index 448e4d68790e795e1848236e700bf6955098e0d9..c84fd369d92932903c76bb2012602617d3e2d213 100644 --- a/src/main/java/net/minecraft/world/level/pathfinder/WalkNodeEvaluator.java +++ b/src/main/java/net/minecraft/world/level/pathfinder/WalkNodeEvaluator.java @@ -478,7 +478,12 @@ public class WalkNodeEvaluator extends NodeEvaluator { diff --git a/patches/server/0083-Add-PlayerUseUnknownEntityEvent.patch b/patches/server/0083-Add-PlayerUseUnknownEntityEvent.patch index 99eaab4c612a..d895ad41174a 100644 --- a/patches/server/0083-Add-PlayerUseUnknownEntityEvent.patch +++ b/patches/server/0083-Add-PlayerUseUnknownEntityEvent.patch @@ -28,10 +28,10 @@ index 1e9c68cd1868d083e6a790d56006dd4aa432010a..8a0ee9564fc36a2badf1357f7e6c47b5 + // Paper end - PlayerUseUnknownEntityEvent } diff --git a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java -index 02b9e1ed57f5d65698c461387ff7d48450e6a70f..0d18b12d0755c14bd041e0f98177469612262fde 100644 +index 55d61a4c93233c0d3994e75f41e29065c2f1ea93..79db369b905744ab2fd69de1afc34feec589ec82 100644 --- a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java +++ b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java -@@ -2493,7 +2493,26 @@ public class ServerGamePacketListenerImpl extends ServerCommonPacketListenerImpl +@@ -2510,7 +2510,26 @@ public class ServerGamePacketListenerImpl extends ServerCommonPacketListenerImpl }); } } @@ -59,10 +59,10 @@ index 02b9e1ed57f5d65698c461387ff7d48450e6a70f..0d18b12d0755c14bd041e0f981774696 @Override diff --git a/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java b/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java -index 8f468a3bfa8ef381eabb45ebb3dd9a4942e98dd5..96e0fc5c8a018fd579f24529175decdac634cfa1 100644 +index 8ea4d63833cd1500d7f413f761aa9a7cf26520c0..9100ea65e85a0e55cad736634fa63815366334a8 100644 --- a/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java +++ b/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java -@@ -1931,4 +1931,13 @@ public class CraftEventFactory { +@@ -1929,4 +1929,13 @@ public class CraftEventFactory { Bukkit.getPluginManager().callEvent(new EntityRemoveEvent(entity.getBukkitEntity(), cause)); } diff --git a/patches/server/0084-Configurable-random-tick-rates-for-blocks.patch b/patches/server/0084-Configurable-random-tick-rates-for-blocks.patch index c936bd291e11..f55ff2c3104f 100644 --- a/patches/server/0084-Configurable-random-tick-rates-for-blocks.patch +++ b/patches/server/0084-Configurable-random-tick-rates-for-blocks.patch @@ -9,7 +9,7 @@ of a variety of blocks that are random ticked. Co-authored-by: MrPowerGamerBR diff --git a/src/main/java/net/minecraft/world/level/block/FarmBlock.java b/src/main/java/net/minecraft/world/level/block/FarmBlock.java -index a88aa3150552cea020589dd9fabf0535cec816bc..529f9f57249bd1ffa2698da76ffa9d4a284088db 100644 +index 38b3c14d393137026720f42bd9f14b856b8377d0..a87f8345aa5520a867a8dd769b43526b20b8c16a 100644 --- a/src/main/java/net/minecraft/world/level/block/FarmBlock.java +++ b/src/main/java/net/minecraft/world/level/block/FarmBlock.java @@ -93,6 +93,8 @@ public class FarmBlock extends Block { @@ -22,7 +22,7 @@ index a88aa3150552cea020589dd9fabf0535cec816bc..529f9f57249bd1ffa2698da76ffa9d4a if (!FarmBlock.isNearWater(world, pos) && !world.isRainingAt(pos.above())) { if (i > 0) { diff --git a/src/main/java/net/minecraft/world/level/block/SpreadingSnowyDirtBlock.java b/src/main/java/net/minecraft/world/level/block/SpreadingSnowyDirtBlock.java -index 7a47a3c9a56bcd3c1e817c6a7db8db8465c484e1..5a39e8d359dc13383711e49ffb2d1294dad26192 100644 +index df77ac357d645a574814014ce69413a674b81bfc..b4b826c53548bcf6952f6d0ee8037975ceb8c6e1 100644 --- a/src/main/java/net/minecraft/world/level/block/SpreadingSnowyDirtBlock.java +++ b/src/main/java/net/minecraft/world/level/block/SpreadingSnowyDirtBlock.java @@ -43,6 +43,7 @@ public abstract class SpreadingSnowyDirtBlock extends SnowyDirtBlock { diff --git a/patches/server/0085-Fix-Cancelling-BlockPlaceEvent-triggering-physics.patch b/patches/server/0085-Fix-Cancelling-BlockPlaceEvent-triggering-physics.patch index ab40c40da1b5..6e252c8f8ba5 100644 --- a/patches/server/0085-Fix-Cancelling-BlockPlaceEvent-triggering-physics.patch +++ b/patches/server/0085-Fix-Cancelling-BlockPlaceEvent-triggering-physics.patch @@ -5,14 +5,20 @@ Subject: [PATCH] Fix Cancelling BlockPlaceEvent triggering physics diff --git a/src/main/java/net/minecraft/server/level/ServerLevel.java b/src/main/java/net/minecraft/server/level/ServerLevel.java -index e6cf145fa7a2968c70e9e467e3927fd38e199e06..223f8d9be5d73e296f5815db7123b95c3b345162 100644 +index c36032803e8b7e4a8537f02738075e1a6baed3b1..828f47c6aadb609402f7237f8c234d595f39c970 100644 --- a/src/main/java/net/minecraft/server/level/ServerLevel.java +++ b/src/main/java/net/minecraft/server/level/ServerLevel.java -@@ -1389,6 +1389,7 @@ public class ServerLevel extends Level implements WorldGenLevel { +@@ -1372,11 +1372,13 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe @Override - public void updateNeighborsAt(BlockPos pos, Block sourceBlock) { + public void updateNeighborsAt(BlockPos pos, Block block) { + if (captureBlockStates) { return; } // Paper - Cancel all physics during placement - this.neighborUpdater.updateNeighborsAtExceptFromFacing(pos, sourceBlock, (Direction) null); + this.updateNeighborsAt(pos, block, ExperimentalRedstoneUtils.initialOrientation(this, (Direction) null, (Direction) null)); + } + + @Override + public void updateNeighborsAt(BlockPos pos, Block sourceBlock, @Nullable Orientation orientation) { ++ if (captureBlockStates) { return; } // Paper - Cancel all physics during placement + this.neighborUpdater.updateNeighborsAtExceptFromFacing(pos, sourceBlock, (Direction) null, orientation); } diff --git a/patches/server/0088-Configurable-Player-Collision.patch b/patches/server/0088-Configurable-Player-Collision.patch index d8b666b4b6b0..ef8bb51af960 100644 --- a/patches/server/0088-Configurable-Player-Collision.patch +++ b/patches/server/0088-Configurable-Player-Collision.patch @@ -18,10 +18,10 @@ index 9a1a961eabd4362c171da78c6be82c867f3696a4..1d0c473442b5c72245c356054440323e ComponentSerialization.TRUSTED_STREAM_CODEC.encode(buf, this.playerPrefix); ComponentSerialization.TRUSTED_STREAM_CODEC.encode(buf, this.playerSuffix); diff --git a/src/main/java/net/minecraft/server/MinecraftServer.java b/src/main/java/net/minecraft/server/MinecraftServer.java -index 639dd562b7eda25004ea10d6c481173afb214773..30fbbe053ee325f4b9f7722416fb5fb92121e2ad 100644 +index ece7f630937abd6d341a2498bac2f0afcfb1ce0f..2e6531f39c6b65d79fc544459629ef61b2ad961a 100644 --- a/src/main/java/net/minecraft/server/MinecraftServer.java +++ b/src/main/java/net/minecraft/server/MinecraftServer.java -@@ -639,6 +639,20 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop registryManager, PlayerDataStorage saveHandler, int maxPlayers) { this.cserver = server.server = new CraftServer((DedicatedServer) server, this); -@@ -390,6 +391,13 @@ public abstract class PlayerList { - +@@ -348,6 +349,13 @@ public abstract class PlayerList { + player.loadAndSpawnParentVehicle(optional); player.initInventoryMenu(); // CraftBukkit - Moved from above, added world + // Paper start - Configurable player collision; Add to collideRule team if needed @@ -68,7 +68,7 @@ index 8a91a44e46a2d49e2f4b9e9970c2b77f2e87767e..98862db2334508ee1a783aeabfb14675 PlayerList.LOGGER.info("{}[{}] logged in with entity id {} at ([{}]{}, {}, {})", player.getName().getString(), s1, player.getId(), worldserver1.serverLevelData.getLevelName(), player.getX(), player.getY(), player.getZ()); } -@@ -512,6 +520,16 @@ public abstract class PlayerList { +@@ -470,6 +478,16 @@ public abstract class PlayerList { entityplayer.doTick(); // SPIGOT-924 // CraftBukkit end @@ -85,7 +85,7 @@ index 8a91a44e46a2d49e2f4b9e9970c2b77f2e87767e..98862db2334508ee1a783aeabfb14675 this.save(entityplayer); if (entityplayer.isPassenger()) { Entity entity = entityplayer.getRootVehicle(); -@@ -1129,6 +1147,13 @@ public abstract class PlayerList { +@@ -1096,6 +1114,13 @@ public abstract class PlayerList { } // CraftBukkit end @@ -100,10 +100,10 @@ index 8a91a44e46a2d49e2f4b9e9970c2b77f2e87767e..98862db2334508ee1a783aeabfb14675 // CraftBukkit start diff --git a/src/main/java/net/minecraft/world/entity/EntitySelector.java b/src/main/java/net/minecraft/world/entity/EntitySelector.java -index 3207166061bf9c4d7bf3f38e5a9f7aff23ccd5c1..f7014bf5faae03a04c31cbdaeb27188c47156c2d 100644 +index a617ea34cfc28cefd68dd14ffbb334b87f98f65c..3a4c1d4afddd7d8d1f43554a7a08855686cadf56 100644 --- a/src/main/java/net/minecraft/world/entity/EntitySelector.java +++ b/src/main/java/net/minecraft/world/entity/EntitySelector.java -@@ -51,7 +51,7 @@ public final class EntitySelector { +@@ -50,7 +50,7 @@ public final class EntitySelector { return (Predicate) (scoreboardteambase_enumteampush == Team.CollisionRule.NEVER ? Predicates.alwaysFalse() : EntitySelector.NO_SPECTATORS.and((entity1) -> { if (!entity1.canCollideWithBukkit(entity) || !entity.canCollideWithBukkit(entity1)) { // CraftBukkit - collidable API return false; diff --git a/patches/server/0090-Configurable-RCON-IP-address.patch b/patches/server/0090-Configurable-RCON-IP-address.patch index 96c8df2d2c87..2d1bfec9fe8d 100644 --- a/patches/server/0090-Configurable-RCON-IP-address.patch +++ b/patches/server/0090-Configurable-RCON-IP-address.patch @@ -9,7 +9,7 @@ For servers with multiple IP's, ability to bind to a specific interface. public net.minecraft.server.dedicated.Settings getStringRaw(Ljava/lang/String;)Ljava/lang/String; diff --git a/src/main/java/net/minecraft/server/dedicated/DedicatedServerProperties.java b/src/main/java/net/minecraft/server/dedicated/DedicatedServerProperties.java -index 36a0a8b5f85edad4dcfcdc75c4db2aa69261eae6..eb27ef574445e1311b763d84aa1b37128baa75f7 100644 +index 341123c078e97862ca8895534e85c324f256f4d6..47835226b61b726c750fe192fd94d3f8ba47565c 100644 --- a/src/main/java/net/minecraft/server/dedicated/DedicatedServerProperties.java +++ b/src/main/java/net/minecraft/server/dedicated/DedicatedServerProperties.java @@ -110,6 +110,8 @@ public class DedicatedServerProperties extends Settings= this.saturatedRegenRate) { // CraftBukkit float f = Math.min(this.saturationLevel, 6.0F); diff --git a/patches/server/0092-Add-ability-to-configure-frosted_ice-properties.patch b/patches/server/0092-Add-ability-to-configure-frosted_ice-properties.patch index 5470fbc9aba1..5fe3cf9edead 100644 --- a/patches/server/0092-Add-ability-to-configure-frosted_ice-properties.patch +++ b/patches/server/0092-Add-ability-to-configure-frosted_ice-properties.patch @@ -5,18 +5,18 @@ Subject: [PATCH] Add ability to configure frosted_ice properties diff --git a/src/main/java/net/minecraft/world/level/block/FrostedIceBlock.java b/src/main/java/net/minecraft/world/level/block/FrostedIceBlock.java -index cdf3cc2a08c52d0e5e5efd5c798f6dc156c0df55..ae6a952e49d20b7c6ad753169608794abe720ee0 100644 +index 39f4eeb965655b8495802608ad9de4ce2ea42ad0..2e47d1a37b783264ec139536b7dc89b8a55046a6 100644 --- a/src/main/java/net/minecraft/world/level/block/FrostedIceBlock.java +++ b/src/main/java/net/minecraft/world/level/block/FrostedIceBlock.java -@@ -40,6 +40,7 @@ public class FrostedIceBlock extends IceBlock { +@@ -42,6 +42,7 @@ public class FrostedIceBlock extends IceBlock { @Override protected void tick(BlockState state, ServerLevel world, BlockPos pos, RandomSource random) { + if (!world.paperConfig().environment.frostedIce.enabled) return; // Paper - Frosted ice options if ((random.nextInt(3) == 0 || this.fewerNeigboursThan(world, pos, 4)) - && world.getMaxLocalRawBrightness(pos) > 11 - state.getValue(AGE) - state.getLightBlock(world, pos) + && world.getMaxLocalRawBrightness(pos) > 11 - state.getValue(AGE) - state.getLightBlock() && this.slightlyMelt(state, world, pos)) { -@@ -49,11 +50,11 @@ public class FrostedIceBlock extends IceBlock { +@@ -51,11 +52,11 @@ public class FrostedIceBlock extends IceBlock { mutableBlockPos.setWithOffset(pos, direction); BlockState blockState = world.getBlockState(mutableBlockPos); if (blockState.is(this) && !this.slightlyMelt(blockState, world, mutableBlockPos)) { diff --git a/patches/server/0093-remove-null-possibility-for-getServer-singleton.patch b/patches/server/0093-remove-null-possibility-for-getServer-singleton.patch index 471757470725..3ebd34b206f0 100644 --- a/patches/server/0093-remove-null-possibility-for-getServer-singleton.patch +++ b/patches/server/0093-remove-null-possibility-for-getServer-singleton.patch @@ -6,26 +6,26 @@ Subject: [PATCH] remove null possibility for getServer singleton to stop IDE complaining about potential NPE diff --git a/src/main/java/net/minecraft/server/MinecraftServer.java b/src/main/java/net/minecraft/server/MinecraftServer.java -index 30fbbe053ee325f4b9f7722416fb5fb92121e2ad..f16ade2019e58a9374a550f58113313c118c2f2b 100644 +index 2e6531f39c6b65d79fc544459629ef61b2ad961a..bdc4eafcad57bb0073662e904bde87e9ac5ed0f3 100644 --- a/src/main/java/net/minecraft/server/MinecraftServer.java +++ b/src/main/java/net/minecraft/server/MinecraftServer.java -@@ -197,6 +197,7 @@ import co.aikar.timings.MinecraftTimings; // Paper +@@ -203,6 +203,7 @@ import org.bukkit.event.server.ServerLoadEvent; - public abstract class MinecraftServer extends ReentrantBlockableEventLoop implements ServerInfo, ChunkIOErrorReporter, CommandSource, AutoCloseable { + public abstract class MinecraftServer extends ReentrantBlockableEventLoop implements ServerInfo, ChunkIOErrorReporter, CommandSource { + private static MinecraftServer SERVER; // Paper public static final Logger LOGGER = LogUtils.getLogger(); public static final net.kyori.adventure.text.logger.slf4j.ComponentLogger COMPONENT_LOGGER = net.kyori.adventure.text.logger.slf4j.ComponentLogger.logger(LOGGER.getName()); // Paper public static final String VANILLA_BRAND = "vanilla"; -@@ -330,6 +331,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop { -@@ -2546,9 +2548,8 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop lootTable; - public long lootTableSeed; +diff --git a/src/main/java/net/minecraft/world/entity/vehicle/AbstractChestBoat.java b/src/main/java/net/minecraft/world/entity/vehicle/AbstractChestBoat.java +index 9c871c74ddc9983f6b4df27c7614f7224b682269..8033abfd77bcc20326b992a9d81e2faa9582fb83 100644 +--- a/src/main/java/net/minecraft/world/entity/vehicle/AbstractChestBoat.java ++++ b/src/main/java/net/minecraft/world/entity/vehicle/AbstractChestBoat.java +@@ -181,7 +181,7 @@ public abstract class AbstractChestBoat extends AbstractBoat implements HasCusto + @Nullable + @Override + public AbstractContainerMenu createMenu(int syncId, Inventory playerInventory, Player player) { +- if (this.lootTable != null && player.isSpectator()) { ++ if (this.lootTable != null && player.isSpectator()) { // Paper - LootTable API (TODO spectators can open chests that aren't ready to be re-generated but this doesn't support that) + return null; + } else { + this.unpackLootTable(playerInventory.player); +@@ -229,6 +229,14 @@ public abstract class AbstractChestBoat extends AbstractBoat implements HasCusto + this.level().gameEvent((Holder) GameEvent.CONTAINER_CLOSE, this.position(), GameEvent.Context.of((Entity) player)); + } + // Paper start - LootTable API + final com.destroystokyo.paper.loottable.PaperLootableInventoryData lootableData = new com.destroystokyo.paper.loottable.PaperLootableInventoryData(); @@ -627,22 +636,13 @@ index 67840327e934b631a85cf2d64911f5cfab4402b1..9549eee0d92f322bd5232abd7e695213 // CraftBukkit start public List transaction = new java.util.ArrayList(); private int maxStack = MAX_STACK; -diff --git a/src/main/java/net/minecraft/world/entity/vehicle/ChestBoat.java b/src/main/java/net/minecraft/world/entity/vehicle/ChestBoat.java -index 42f8e2d961f83c3e9ce384158b9dfc4014eb0a5f..4cdf3b54187ebcb1f5ddfa6114386127a2846f01 100644 ---- a/src/main/java/net/minecraft/world/entity/vehicle/ChestBoat.java -+++ b/src/main/java/net/minecraft/world/entity/vehicle/ChestBoat.java -@@ -217,7 +217,7 @@ public class ChestBoat extends Boat implements HasCustomInventoryScreen, Contain - @Nullable - @Override - public AbstractContainerMenu createMenu(int syncId, Inventory playerInventory, Player player) { -- if (this.lootTable != null && player.isSpectator()) { -+ if (this.lootTable != null && player.isSpectator()) { // Paper - LootTable API (TODO spectators can open chests that aren't ready to be re-generated but this doesn't support that) - return null; - } else { - this.unpackLootTable(playerInventory.player); -@@ -265,6 +265,14 @@ public class ChestBoat extends Boat implements HasCustomInventoryScreen, Contain - this.level().gameEvent((Holder) GameEvent.CONTAINER_CLOSE, this.position(), GameEvent.Context.of((Entity) player)); - } +diff --git a/src/main/java/net/minecraft/world/entity/vehicle/AbstractMinecartContainer.java b/src/main/java/net/minecraft/world/entity/vehicle/AbstractMinecartContainer.java +index a4be7b19b626957efdf2f2507121f0085ba1da50..d528e8e4aea266c495377365f01e314001eb1970 100644 +--- a/src/main/java/net/minecraft/world/entity/vehicle/AbstractMinecartContainer.java ++++ b/src/main/java/net/minecraft/world/entity/vehicle/AbstractMinecartContainer.java +@@ -36,6 +36,14 @@ public abstract class AbstractMinecartContainer extends AbstractMinecart impleme + public ResourceKey lootTable; + public long lootTableSeed; + // Paper start - LootTable API + final com.destroystokyo.paper.loottable.PaperLootableInventoryData lootableData = new com.destroystokyo.paper.loottable.PaperLootableInventoryData(); @@ -656,61 +656,62 @@ index 42f8e2d961f83c3e9ce384158b9dfc4014eb0a5f..4cdf3b54187ebcb1f5ddfa6114386127 public List transaction = new java.util.ArrayList(); private int maxStack = MAX_STACK; diff --git a/src/main/java/net/minecraft/world/entity/vehicle/ContainerEntity.java b/src/main/java/net/minecraft/world/entity/vehicle/ContainerEntity.java -index 3ee99193de5deb6a38d6ded561fe8f2fbf711327..ccc7367ab2740bea0f2b907223a0920b11665092 100644 +index beba927cffdeedcd68d8048708f5bf1a409ff965..874a44ab77248665c2db243764e8542bfc0d6514 100644 --- a/src/main/java/net/minecraft/world/entity/vehicle/ContainerEntity.java +++ b/src/main/java/net/minecraft/world/entity/vehicle/ContainerEntity.java @@ -62,22 +62,26 @@ public interface ContainerEntity extends Container, MenuProvider { - default void addChestVehicleSaveData(CompoundTag nbt, HolderLookup.Provider registriesLookup) { - if (this.getLootTable() != null) { - nbt.putString("LootTable", this.getLootTable().location().toString()); + default void addChestVehicleSaveData(CompoundTag nbt, HolderLookup.Provider registries) { + if (this.getContainerLootTable() != null) { + nbt.putString("LootTable", this.getContainerLootTable().location().toString()); + this.lootableData().saveNbt(nbt); // Paper - if (this.getLootTableSeed() != 0L) { - nbt.putLong("LootTableSeed", this.getLootTableSeed()); + if (this.getContainerLootTableSeed() != 0L) { + nbt.putLong("LootTableSeed", this.getContainerLootTableSeed()); } - } else { -- ContainerHelper.saveAllItems(nbt, this.getItemStacks(), registriesLookup); +- ContainerHelper.saveAllItems(nbt, this.getItemStacks(), registries); } -+ ContainerHelper.saveAllItems(nbt, this.getItemStacks(), registriesLookup); // Paper - always save the items, table may still remain ++ ContainerHelper.saveAllItems(nbt, this.getItemStacks(), registries); // Paper - always save the items, table may still remain } - default void readChestVehicleSaveData(CompoundTag nbt, HolderLookup.Provider registriesLookup) { + default void readChestVehicleSaveData(CompoundTag nbt, HolderLookup.Provider registries) { this.clearItemStacks(); if (nbt.contains("LootTable", 8)) { - this.setLootTable(ResourceKey.create(Registries.LOOT_TABLE, ResourceLocation.parse(nbt.getString("LootTable")))); + this.setContainerLootTable(ResourceKey.create(Registries.LOOT_TABLE, ResourceLocation.parse(nbt.getString("LootTable")))); + // Paper start - LootTable API -+ if (this.getLootTable() != null) { ++ if (this.getContainerLootTable() != null) { + this.lootableData().loadNbt(nbt); + } + // Paper end - LootTable API - this.setLootTableSeed(nbt.getLong("LootTableSeed")); + this.setContainerLootTableSeed(nbt.getLong("LootTableSeed")); - } else { -- ContainerHelper.loadAllItems(nbt, this.getItemStacks(), registriesLookup); +- ContainerHelper.loadAllItems(nbt, this.getItemStacks(), registries); } -+ ContainerHelper.loadAllItems(nbt, this.getItemStacks(), registriesLookup); // Paper - always save the items, table may still remain ++ ContainerHelper.loadAllItems(nbt, this.getItemStacks(), registries); // Paper - always save the items, table may still remain } - default void chestVehicleDestroyed(DamageSource source, Level world, Entity vehicle) { -@@ -99,13 +103,17 @@ public interface ContainerEntity extends Container, MenuProvider { + default void chestVehicleDestroyed(DamageSource source, ServerLevel world, Entity vehicle) { +@@ -97,13 +101,18 @@ public interface ContainerEntity extends Container, MenuProvider { default void unpackChestVehicleLootTable(@Nullable Player player) { MinecraftServer minecraftServer = this.level().getServer(); -- if (this.getLootTable() != null && minecraftServer != null) { +- if (this.getContainerLootTable() != null && minecraftServer != null) { + if (minecraftServer != null && this.lootableData().shouldReplenish(this, com.destroystokyo.paper.loottable.PaperLootableInventoryData.ENTITY, player)) { // Paper - LootTable API - LootTable lootTable = minecraftServer.reloadableRegistries().getLootTable(this.getLootTable()); + LootTable lootTable = minecraftServer.reloadableRegistries().getLootTable(this.getContainerLootTable()); if (player != null) { - CriteriaTriggers.GENERATE_LOOT.trigger((ServerPlayer)player, this.getLootTable()); + CriteriaTriggers.GENERATE_LOOT.trigger((ServerPlayer)player, this.getContainerLootTable()); } -- this.setLootTable(null); +- this.setContainerLootTable(null); + // Paper start - LootTable API + if (this.lootableData().shouldClearLootTable(this, com.destroystokyo.paper.loottable.PaperLootableInventoryData.ENTITY, player)) { -+ this.setLootTable(null); ++ this.setContainerLootTable(null); + } + // Paper end - LootTable API ++ LootParams.Builder builder = new LootParams.Builder((ServerLevel)this.level()).withParameter(LootContextParams.ORIGIN, this.position()); if (player != null) { builder.withLuck(player.getLuck()).withParameter(LootContextParams.THIS_ENTITY, player); -@@ -175,4 +183,14 @@ public interface ContainerEntity extends Container, MenuProvider { +@@ -173,4 +182,14 @@ public interface ContainerEntity extends Container, MenuProvider { default boolean isChestVehicleStillValid(Player player) { return !this.isRemoved() && player.canInteractWithEntity(this.getBoundingBox(), 4.0); } @@ -726,10 +727,10 @@ index 3ee99193de5deb6a38d6ded561fe8f2fbf711327..ccc7367ab2740bea0f2b907223a0920b + // Paper end - LootTable API } diff --git a/src/main/java/net/minecraft/world/level/block/ShulkerBoxBlock.java b/src/main/java/net/minecraft/world/level/block/ShulkerBoxBlock.java -index d85da0661096a3587917c6636728bfd2e3eb90a2..6323c96d9b0cd14f89609b38da37d7fcc12d211b 100644 +index 5fd4594c26ef13ddef79cc4d4c8b446fdd3ba1f1..a0607cb6c6f74285363dfbd49033a8bde5ca6ae3 100644 --- a/src/main/java/net/minecraft/world/level/block/ShulkerBoxBlock.java +++ b/src/main/java/net/minecraft/world/level/block/ShulkerBoxBlock.java -@@ -148,7 +148,7 @@ public class ShulkerBoxBlock extends BaseEntityBlock { +@@ -143,7 +143,7 @@ public class ShulkerBoxBlock extends BaseEntityBlock { itemEntity.setDefaultPickUpDelay(); world.addFreshEntity(itemEntity); } else { @@ -738,7 +739,7 @@ index d85da0661096a3587917c6636728bfd2e3eb90a2..6323c96d9b0cd14f89609b38da37d7fc } } -@@ -158,7 +158,15 @@ public class ShulkerBoxBlock extends BaseEntityBlock { +@@ -153,7 +153,15 @@ public class ShulkerBoxBlock extends BaseEntityBlock { @Override protected List getDrops(BlockState state, LootParams.Builder builder) { BlockEntity blockEntity = builder.getOptionalParameter(LootContextParams.BLOCK_ENTITY); @@ -754,7 +755,7 @@ index d85da0661096a3587917c6636728bfd2e3eb90a2..6323c96d9b0cd14f89609b38da37d7fc builder = builder.withDynamicDrop(CONTENTS, lootConsumer -> { for (int i = 0; i < shulkerBoxBlockEntity.getContainerSize(); i++) { lootConsumer.accept(shulkerBoxBlockEntity.getItem(i)); -@@ -166,7 +174,13 @@ public class ShulkerBoxBlock extends BaseEntityBlock { +@@ -161,7 +169,13 @@ public class ShulkerBoxBlock extends BaseEntityBlock { }); } @@ -769,7 +770,7 @@ index d85da0661096a3587917c6636728bfd2e3eb90a2..6323c96d9b0cd14f89609b38da37d7fc @Override diff --git a/src/main/java/net/minecraft/world/level/block/entity/RandomizableContainerBlockEntity.java b/src/main/java/net/minecraft/world/level/block/entity/RandomizableContainerBlockEntity.java -index c2493c15d8fe4587d6ee2db100cc13303b66b39b..13c9a68b604d4c7c6e09e72b3cea7ab2214b06ab 100644 +index 74c833e589160f0fe31f3b5e515f3515201159bd..fc657b6052d4310ad9c28988042c2cf37cf5d213 100644 --- a/src/main/java/net/minecraft/world/level/block/entity/RandomizableContainerBlockEntity.java +++ b/src/main/java/net/minecraft/world/level/block/entity/RandomizableContainerBlockEntity.java @@ -115,4 +115,13 @@ public abstract class RandomizableContainerBlockEntity extends BaseContainerBloc @@ -850,20 +851,20 @@ index 74315a46f6101775321b1cf4944c124c69aed182..f23fbb8ed39a754b36d2eb162358877e @Override public abstract CraftLootable copy(); diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftChestBoat.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftChestBoat.java -index cfde210ea9d4b62fe514d3ab0dbab2f43eda0c7a..e4f899a6a1d055b3ea17d1114ed0228fbba53352 100644 +index 62accb551344c41671fc22b15d7b25b6fc97d915..a1e04bb965f18ffd07e2f5bf827c5e4ddd6aeeda 100644 --- a/src/main/java/org/bukkit/craftbukkit/entity/CraftChestBoat.java +++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftChestBoat.java @@ -7,8 +7,7 @@ import org.bukkit.craftbukkit.inventory.CraftInventory; import org.bukkit.inventory.Inventory; import org.bukkit.loot.LootTable; --public class CraftChestBoat extends CraftBoat implements org.bukkit.entity.ChestBoat { +-public abstract class CraftChestBoat extends CraftBoat implements org.bukkit.entity.ChestBoat { - -+public class CraftChestBoat extends CraftBoat implements org.bukkit.entity.ChestBoat, com.destroystokyo.paper.loottable.PaperLootableEntityInventory { // Paper ++public abstract class CraftChestBoat extends CraftBoat implements org.bukkit.entity.ChestBoat, com.destroystokyo.paper.loottable.PaperLootableEntityInventory { // Paper private final Inventory inventory; - public CraftChestBoat(CraftServer server, ChestBoat entity) { -@@ -31,28 +30,5 @@ public class CraftChestBoat extends CraftBoat implements org.bukkit.entity.Chest + public CraftChestBoat(CraftServer server, AbstractChestBoat entity) { +@@ -31,28 +30,6 @@ public abstract class CraftChestBoat extends CraftBoat implements org.bukkit.ent return this.inventory; } @@ -871,10 +872,11 @@ index cfde210ea9d4b62fe514d3ab0dbab2f43eda0c7a..e4f899a6a1d055b3ea17d1114ed0228f - public void setLootTable(LootTable table) { - this.setLootTable(table, this.getSeed()); - } -- ++ // Paper - moved loot table logic to PaperLootableEntityInventory + - @Override - public LootTable getLootTable() { -- return CraftLootTable.minecraftToBukkit(this.getHandle().getLootTable()); +- return CraftLootTable.minecraftToBukkit(this.getHandle().getContainerLootTable()); - } - - @Override @@ -884,14 +886,13 @@ index cfde210ea9d4b62fe514d3ab0dbab2f43eda0c7a..e4f899a6a1d055b3ea17d1114ed0228f - - @Override - public long getSeed() { -- return this.getHandle().getLootTableSeed(); +- return this.getHandle().getContainerLootTableSeed(); - } - - private void setLootTable(LootTable table, long seed) { -- this.getHandle().setLootTable(CraftLootTable.bukkitToMinecraft(table)); -- this.getHandle().setLootTableSeed(seed); +- this.getHandle().setContainerLootTable(CraftLootTable.bukkitToMinecraft(table)); +- this.getHandle().setContainerLootTableSeed(seed); - } -+ // Paper - moved loot table logic to PaperLootableEntityInventory } diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftMinecartChest.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftMinecartChest.java index fd42f0b20132d08039ca7735d31a61806a6b07dc..b1a708de6790bbe336202b13ab862ced78de084f 100644 diff --git a/patches/server/0097-Async-GameProfileCache-saving.patch b/patches/server/0097-Async-GameProfileCache-saving.patch index b2368721938c..2c6cfabd1c8a 100644 --- a/patches/server/0097-Async-GameProfileCache-saving.patch +++ b/patches/server/0097-Async-GameProfileCache-saving.patch @@ -5,10 +5,10 @@ Subject: [PATCH] Async GameProfileCache saving diff --git a/src/main/java/net/minecraft/server/MinecraftServer.java b/src/main/java/net/minecraft/server/MinecraftServer.java -index f16ade2019e58a9374a550f58113313c118c2f2b..10d69b48158460e5739d1e41a83fcaeec819fac6 100644 +index bdc4eafcad57bb0073662e904bde87e9ac5ed0f3..383808406dd3a6c5b6098db93e638749489b80e3 100644 --- a/src/main/java/net/minecraft/server/MinecraftServer.java +++ b/src/main/java/net/minecraft/server/MinecraftServer.java -@@ -998,7 +998,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop (viewer.getPlayer().getX() - this.getX()) * (viewer.getPlayer().getY() - this.getY()) * (viewer.getPlayer().getZ() - this.getZ()) < 16 * 16) @@ -36,9 +36,9 @@ index 15432b512fc0d0d38bf28499e2afa5e48fec7aaa..47f2a480f6a4b15e55cedbfa8a58459d } private void explode() { -@@ -202,4 +223,11 @@ public class PrimedTnt extends Entity implements TraceableEntity { - - return entity; +@@ -221,4 +242,11 @@ public class PrimedTnt extends Entity implements TraceableEntity { + public final boolean hurtServer(ServerLevel world, DamageSource source, float amount) { + return false; } + + // Paper start - Option to prevent TNT from moving in water diff --git a/patches/server/0099-Faster-redstone-torch-rapid-clock-removal.patch b/patches/server/0099-Faster-redstone-torch-rapid-clock-removal.patch index 18322dc88fce..9d7f0e9161ce 100644 --- a/patches/server/0099-Faster-redstone-torch-rapid-clock-removal.patch +++ b/patches/server/0099-Faster-redstone-torch-rapid-clock-removal.patch @@ -6,22 +6,22 @@ Subject: [PATCH] Faster redstone torch rapid clock removal Only resize the the redstone torch list once, since resizing arrays / lists is costly diff --git a/src/main/java/net/minecraft/world/level/Level.java b/src/main/java/net/minecraft/world/level/Level.java -index 622ecc1aeb9140d5c0d181fa472d123c42e5c5f4..4abb586f964e342425c7cf0384ab8bf8cdedaea3 100644 +index 9e88e9c473d1ab02344afd9634c625b95b5f38ef..675dcb6eb5c33f6ec582ff20118d2f73745914a9 100644 --- a/src/main/java/net/minecraft/world/level/Level.java +++ b/src/main/java/net/minecraft/world/level/Level.java @@ -169,6 +169,7 @@ public abstract class Level implements LevelAccessor, AutoCloseable { private org.spigotmc.TickLimiter tileLimiter; private int tileTickPosition; - public final Map explosionDensityCache = new HashMap<>(); // Paper - Optimize explosions + public final Map explosionDensityCache = new HashMap<>(); // Paper - Optimize explosions + public java.util.ArrayDeque redstoneUpdateInfos; // Paper - Faster redstone torch rapid clock removal; Move from Map in BlockRedstoneTorch to here public CraftWorld getWorld() { return this.world; diff --git a/src/main/java/net/minecraft/world/level/block/RedstoneTorchBlock.java b/src/main/java/net/minecraft/world/level/block/RedstoneTorchBlock.java -index 20d20db2639030c103e9d1b3c8da0f51344b81c2..ceba9617748a8b4f3a9bd459475952c9c6c9ed7c 100644 +index ca09910f87b69f6a4af7e3d9da4ad8f917acd66f..26319e8247e9dfa2068700d7fadc21ea5f715561 100644 --- a/src/main/java/net/minecraft/world/level/block/RedstoneTorchBlock.java +++ b/src/main/java/net/minecraft/world/level/block/RedstoneTorchBlock.java -@@ -24,7 +24,7 @@ public class RedstoneTorchBlock extends BaseTorchBlock { +@@ -28,7 +28,7 @@ public class RedstoneTorchBlock extends BaseTorchBlock { public static final MapCodec CODEC = simpleCodec(RedstoneTorchBlock::new); public static final BooleanProperty LIT = BlockStateProperties.LIT; @@ -30,7 +30,7 @@ index 20d20db2639030c103e9d1b3c8da0f51344b81c2..ceba9617748a8b4f3a9bd459475952c9 public static final int RECENT_TOGGLE_TIMER = 60; public static final int MAX_RECENT_TOGGLES = 8; public static final int RESTART_DELAY = 160; -@@ -80,11 +80,15 @@ public class RedstoneTorchBlock extends BaseTorchBlock { +@@ -81,11 +81,15 @@ public class RedstoneTorchBlock extends BaseTorchBlock { @Override protected void tick(BlockState state, ServerLevel world, BlockPos pos, RandomSource random) { boolean flag = this.hasNeighborSignal(world, pos, state); @@ -50,7 +50,7 @@ index 20d20db2639030c103e9d1b3c8da0f51344b81c2..ceba9617748a8b4f3a9bd459475952c9 // CraftBukkit start org.bukkit.plugin.PluginManager manager = world.getCraftServer().getPluginManager(); -@@ -160,9 +164,12 @@ public class RedstoneTorchBlock extends BaseTorchBlock { +@@ -161,9 +165,12 @@ public class RedstoneTorchBlock extends BaseTorchBlock { } private static boolean isToggledTooFrequently(Level world, BlockPos pos, boolean addNew) { diff --git a/patches/server/0100-Add-server-name-parameter.patch b/patches/server/0100-Add-server-name-parameter.patch index 5ff9f1d96102..32b0259d08d2 100644 --- a/patches/server/0100-Add-server-name-parameter.patch +++ b/patches/server/0100-Add-server-name-parameter.patch @@ -5,7 +5,7 @@ Subject: [PATCH] Add server-name parameter diff --git a/src/main/java/org/bukkit/craftbukkit/Main.java b/src/main/java/org/bukkit/craftbukkit/Main.java -index 14f63c179428bee61d3b931ea309f4c94b89a6cc..75a3c7ed5500f0451c9c1efdfc3cb809445c8acf 100644 +index 13f811173c67533ee02f70cc4b6b398cd527c84b..fc9e56427dc04a6ef7286bf027fef6b7ff0bac0c 100644 --- a/src/main/java/org/bukkit/craftbukkit/Main.java +++ b/src/main/java/org/bukkit/craftbukkit/Main.java @@ -168,6 +168,14 @@ public class Main { diff --git a/patches/server/0101-Fix-global-sound-handling.patch b/patches/server/0101-Fix-global-sound-handling.patch index 8f4acbadadf7..8af9a82deaab 100644 --- a/patches/server/0101-Fix-global-sound-handling.patch +++ b/patches/server/0101-Fix-global-sound-handling.patch @@ -11,10 +11,10 @@ Co-authored-by: lexikiq Co-authored-by: Aikar diff --git a/src/main/java/net/minecraft/server/level/ServerLevel.java b/src/main/java/net/minecraft/server/level/ServerLevel.java -index 223f8d9be5d73e296f5815db7123b95c3b345162..d728afbe1d6882f1ace4ead9d87f4b7d2af43ba2 100644 +index 828f47c6aadb609402f7237f8c234d595f39c970..88c93a2e67c8d1bc227c7fa35bb919a40009f931 100644 --- a/src/main/java/net/minecraft/server/level/ServerLevel.java +++ b/src/main/java/net/minecraft/server/level/ServerLevel.java -@@ -1322,7 +1322,7 @@ public class ServerLevel extends Level implements WorldGenLevel { +@@ -1305,7 +1305,7 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe @Override public void levelEvent(@Nullable Player player, int eventId, BlockPos pos, int data) { @@ -23,79 +23,79 @@ index 223f8d9be5d73e296f5815db7123b95c3b345162..d728afbe1d6882f1ace4ead9d87f4b7d } public int getLogicalHeight() { +@@ -2122,6 +2122,17 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe + return this.serverLevelData.getGameRules(); + } + ++ // Paper start - respect global sound events gamerule ++ public List getPlayersForGlobalSoundGamerule() { ++ return this.getGameRules().getBoolean(GameRules.RULE_GLOBAL_SOUND_EVENTS) ? ((ServerLevel) this).getServer().getPlayerList().players : ((ServerLevel) this).players(); ++ } ++ ++ public double getGlobalSoundRangeSquared(java.util.function.Function rangeFunction) { ++ final double range = rangeFunction.apply(this.spigotConfig); ++ return range <= 0 ? 64.0 * 64.0 : range * range; // 64 is taken from default in ServerLevel#levelEvent ++ } ++ // Paper end - respect global sound events gamerule ++ + @Override + public CrashReportCategory fillReportDetails(CrashReport report) { + CrashReportCategory crashreportsystemdetails = super.fillReportDetails(report); diff --git a/src/main/java/net/minecraft/world/entity/boss/enderdragon/EnderDragon.java b/src/main/java/net/minecraft/world/entity/boss/enderdragon/EnderDragon.java -index 0be5ae83d2fa86142e3404393729039c51ae0639..25a429a2d1725d562a28b9d07dba630cfe49d32a 100644 +index 2bb62325d30ac509583de50407ea21f562f6e74f..95d69e3ca1a9095dfb340e9be0ec322ab6c5eb5e 100644 --- a/src/main/java/net/minecraft/world/entity/boss/enderdragon/EnderDragon.java +++ b/src/main/java/net/minecraft/world/entity/boss/enderdragon/EnderDragon.java -@@ -695,11 +695,12 @@ public class EnderDragon extends Mob implements Enemy { +@@ -666,11 +666,12 @@ public class EnderDragon extends Mob implements Enemy { // CraftBukkit start - Use relative location for far away sounds - // this.level().globalLevelEvent(1028, this.blockPosition(), 0); - int viewDistance = ((ServerLevel) this.level()).getCraftServer().getViewDistance() * 16; -- for (net.minecraft.server.level.ServerPlayer player : this.level().getServer().getPlayerList().players) { -+ for (net.minecraft.server.level.ServerPlayer player : this.level().getPlayersForGlobalSoundGamerule()) { // Paper - respect global sound events gamerule + // worldserver.globalLevelEvent(1028, this.blockPosition(), 0); + int viewDistance = worldserver.getCraftServer().getViewDistance() * 16; +- for (net.minecraft.server.level.ServerPlayer player : worldserver.getServer().getPlayerList().players) { ++ for (net.minecraft.server.level.ServerPlayer player : worldserver.getPlayersForGlobalSoundGamerule()) { // Paper - respect global sound events gamerule double deltaX = this.getX() - player.getX(); double deltaZ = this.getZ() - player.getZ(); double distanceSquared = deltaX * deltaX + deltaZ * deltaZ; -- if ( this.level().spigotConfig.dragonDeathSoundRadius > 0 && distanceSquared > this.level().spigotConfig.dragonDeathSoundRadius * this.level().spigotConfig.dragonDeathSoundRadius ) continue; // Spigot -+ final double soundRadiusSquared = this.level().getGlobalSoundRangeSquared(config -> config.dragonDeathSoundRadius); // Paper - respect global sound events gamerule -+ if ( !this.level().getGameRules().getBoolean(GameRules.RULE_GLOBAL_SOUND_EVENTS) && distanceSquared > soundRadiusSquared ) continue; // Spigot // Paper - respect global sound events gamerule +- if ( worldserver.spigotConfig.dragonDeathSoundRadius > 0 && distanceSquared > worldserver.spigotConfig.dragonDeathSoundRadius * worldserver.spigotConfig.dragonDeathSoundRadius ) continue; // Spigot ++ final double soundRadiusSquared = worldserver.getGlobalSoundRangeSquared(config -> config.dragonDeathSoundRadius); // Paper - respect global sound events gamerule ++ if ( !worldserver.getGameRules().getBoolean(GameRules.RULE_GLOBAL_SOUND_EVENTS) && distanceSquared > soundRadiusSquared ) continue; // Spigot // Paper - respect global sound events gamerule if (distanceSquared > viewDistance * viewDistance) { double deltaLength = Math.sqrt(distanceSquared); double relativeX = player.getX() + (deltaX / deltaLength) * viewDistance; diff --git a/src/main/java/net/minecraft/world/entity/boss/wither/WitherBoss.java b/src/main/java/net/minecraft/world/entity/boss/wither/WitherBoss.java -index d288bc209a0b0fdf2d73197a8e7d179e8e8c31e7..3ee24382ef3614ff0c5d5cdc614a41286ba4af5e 100644 +index 4c284ccd5b2eb05f487aba18e1daa0b59c3e8129..10c79cbc25383c0b65fb22a7347513134b7dee1d 100644 --- a/src/main/java/net/minecraft/world/entity/boss/wither/WitherBoss.java +++ b/src/main/java/net/minecraft/world/entity/boss/wither/WitherBoss.java -@@ -278,11 +278,12 @@ public class WitherBoss extends Monster implements PowerableMob, RangedAttackMob +@@ -276,11 +276,12 @@ public class WitherBoss extends Monster implements RangedAttackMob { // CraftBukkit start - Use relative location for far away sounds - // this.level().globalLevelEvent(1023, new BlockPosition(this), 0); - int viewDistance = ((ServerLevel) this.level()).getCraftServer().getViewDistance() * 16; + // worldserver.globalLevelEvent(1023, new BlockPosition(this), 0); + int viewDistance = world.getCraftServer().getViewDistance() * 16; - for (ServerPlayer player : (List) MinecraftServer.getServer().getPlayerList().players) { -+ for (ServerPlayer player : this.level().getPlayersForGlobalSoundGamerule()) { // Paper - respect global sound events gamerule ++ for (ServerPlayer player : world.getPlayersForGlobalSoundGamerule()) { // Paper - respect global sound events gamerule double deltaX = this.getX() - player.getX(); double deltaZ = this.getZ() - player.getZ(); double distanceSquared = deltaX * deltaX + deltaZ * deltaZ; -- if ( this.level().spigotConfig.witherSpawnSoundRadius > 0 && distanceSquared > this.level().spigotConfig.witherSpawnSoundRadius * this.level().spigotConfig.witherSpawnSoundRadius ) continue; // Spigot -+ final double soundRadiusSquared = this.level().getGlobalSoundRangeSquared(config -> config.witherSpawnSoundRadius); // Paper - respect global sound events gamerule -+ if ( !this.level().getGameRules().getBoolean(GameRules.RULE_GLOBAL_SOUND_EVENTS) && distanceSquared > soundRadiusSquared ) continue; // Spigot // Paper - respect global sound events gamerule +- if ( world.spigotConfig.witherSpawnSoundRadius > 0 && distanceSquared > world.spigotConfig.witherSpawnSoundRadius * world.spigotConfig.witherSpawnSoundRadius ) continue; // Spigot ++ final double soundRadiusSquared = world.getGlobalSoundRangeSquared(config -> config.witherSpawnSoundRadius); // Paper - respect global sound events gamerule ++ if ( !world.getGameRules().getBoolean(GameRules.RULE_GLOBAL_SOUND_EVENTS) && distanceSquared > soundRadiusSquared ) continue; // Spigot // Paper - respect global sound events gamerule if (distanceSquared > viewDistance * viewDistance) { double deltaLength = Math.sqrt(distanceSquared); double relativeX = player.getX() + (deltaX / deltaLength) * viewDistance; diff --git a/src/main/java/net/minecraft/world/item/EnderEyeItem.java b/src/main/java/net/minecraft/world/item/EnderEyeItem.java -index 0224a0e901f9430ef06c30432a7988149a67037d..391579b515c5a07066f82b33c4f9ef8ee1d05530 100644 +index 04fce5cc4350df81c7ea103b74b845313dd6cc37..08cbf02bba3633a84cce90c202d13f2beb5b88a2 100644 --- a/src/main/java/net/minecraft/world/item/EnderEyeItem.java +++ b/src/main/java/net/minecraft/world/item/EnderEyeItem.java -@@ -67,11 +67,12 @@ public class EnderEyeItem extends Item { - // world.b(1038, blockposition1.c(1, 0, 1), 0); +@@ -66,11 +66,13 @@ public class EnderEyeItem extends Item { + // world.globalLevelEvent(1038, blockposition1.offset(1, 0, 1), 0); int viewDistance = world.getCraftServer().getViewDistance() * 16; BlockPos soundPos = blockposition1.offset(1, 0, 1); - for (ServerPlayer player : world.getServer().getPlayerList().players) { -+ for (ServerPlayer player : world.getPlayersForGlobalSoundGamerule()) { // Paper - respect global sound events gamerule ++ final net.minecraft.server.level.ServerLevel serverLevel = (net.minecraft.server.level.ServerLevel) world; // Paper - respect global sound events gamerule - ensured by isClientSide check above ++ for (ServerPlayer player : serverLevel.getPlayersForGlobalSoundGamerule()) { // Paper - respect global sound events gamerule double deltaX = soundPos.getX() - player.getX(); double deltaZ = soundPos.getZ() - player.getZ(); double distanceSquared = deltaX * deltaX + deltaZ * deltaZ; - if (world.spigotConfig.endPortalSoundRadius > 0 && distanceSquared > world.spigotConfig.endPortalSoundRadius * world.spigotConfig.endPortalSoundRadius) continue; // Spigot -+ final double soundRadiusSquared = world.getGlobalSoundRangeSquared(config -> config.endPortalSoundRadius); // Paper - respect global sound events gamerule -+ if (!world.getGameRules().getBoolean(net.minecraft.world.level.GameRules.RULE_GLOBAL_SOUND_EVENTS) && distanceSquared > soundRadiusSquared) continue; // Spigot // Paper - respect global sound events gamerule ++ final double soundRadiusSquared = serverLevel.getGlobalSoundRangeSquared(config -> config.endPortalSoundRadius); // Paper - respect global sound events gamerule ++ if (!serverLevel.getGameRules().getBoolean(net.minecraft.world.level.GameRules.RULE_GLOBAL_SOUND_EVENTS) && distanceSquared > soundRadiusSquared) continue; // Spigot // Paper - respect global sound events gamerule if (distanceSquared > viewDistance * viewDistance) { double deltaLength = Math.sqrt(distanceSquared); double relativeX = player.getX() + (deltaX / deltaLength) * viewDistance; -diff --git a/src/main/java/net/minecraft/world/level/Level.java b/src/main/java/net/minecraft/world/level/Level.java -index 240db0aae0b0e306c90bcc4a537c9afcb290acb3..59992bea10218e48397fa781f895d36e0e1df46e 100644 ---- a/src/main/java/net/minecraft/world/level/Level.java -+++ b/src/main/java/net/minecraft/world/level/Level.java -@@ -1277,4 +1277,14 @@ public abstract class Level implements LevelAccessor, AutoCloseable { - return this.id; - } - } -+ // Paper start - respect global sound events gamerule -+ public List getPlayersForGlobalSoundGamerule() { -+ return this.getGameRules().getBoolean(GameRules.RULE_GLOBAL_SOUND_EVENTS) ? ((ServerLevel) this).getServer().getPlayerList().players : ((ServerLevel) this).players(); -+ } -+ -+ public double getGlobalSoundRangeSquared(java.util.function.Function rangeFunction) { -+ final double range = rangeFunction.apply(this.spigotConfig); -+ return range <= 0 ? 64.0 * 64.0 : range * range; // 64 is taken from default in ServerLevel#levelEvent -+ } -+ // Paper end - respect global sound events gamerule - } diff --git a/patches/server/0103-Don-t-lookup-game-profiles-that-have-no-UUID-and-no-.patch b/patches/server/0103-Don-t-lookup-game-profiles-that-have-no-UUID-and-no-.patch index 82dc93463f67..d99e7ec08b84 100644 --- a/patches/server/0103-Don-t-lookup-game-profiles-that-have-no-UUID-and-no-.patch +++ b/patches/server/0103-Don-t-lookup-game-profiles-that-have-no-UUID-and-no-.patch @@ -5,7 +5,7 @@ Subject: [PATCH] Don't lookup game profiles that have no UUID and no name diff --git a/src/main/java/net/minecraft/server/players/GameProfileCache.java b/src/main/java/net/minecraft/server/players/GameProfileCache.java -index 5b219b5eee0c5958b80093c9223feeed0dec830b..bc7143ad915f001726e8558c8ca4160f3f9ace68 100644 +index 197e2ec9f1445a8184d0dde0e9b02b39e3302b91..22ed1e67ccb358655fe74cd1b1e19415f4e5bdd7 100644 --- a/src/main/java/net/minecraft/server/players/GameProfileCache.java +++ b/src/main/java/net/minecraft/server/players/GameProfileCache.java @@ -89,6 +89,7 @@ public class GameProfileCache { diff --git a/patches/server/0104-Add-setting-for-proxy-online-mode-status.patch b/patches/server/0104-Add-setting-for-proxy-online-mode-status.patch index e08249957a39..58541b2ca3bd 100644 --- a/patches/server/0104-Add-setting-for-proxy-online-mode-status.patch +++ b/patches/server/0104-Add-setting-for-proxy-online-mode-status.patch @@ -6,10 +6,10 @@ Subject: [PATCH] Add setting for proxy online mode status TODO: Add isProxyOnlineMode check to Metrics diff --git a/src/main/java/net/minecraft/server/dedicated/DedicatedServer.java b/src/main/java/net/minecraft/server/dedicated/DedicatedServer.java -index c1f41dcf49c75c3d69485c0e1ec821205438ed1e..c466ec011d059b9960606ef2ee51ea3a3a65f8d0 100644 +index 5d9772a6c35e8f849e8879f510b2c586b148074c..7c2b2dbc03bf4c3308a3e2a34260fee56d646488 100644 --- a/src/main/java/net/minecraft/server/dedicated/DedicatedServer.java +++ b/src/main/java/net/minecraft/server/dedicated/DedicatedServer.java -@@ -593,7 +593,11 @@ public class DedicatedServer extends MinecraftServer implements ServerInterface +@@ -571,7 +571,11 @@ public class DedicatedServer extends MinecraftServer implements ServerInterface public boolean enforceSecureProfile() { DedicatedServerProperties dedicatedserverproperties = this.getProperties(); @@ -23,7 +23,7 @@ index c1f41dcf49c75c3d69485c0e1ec821205438ed1e..c466ec011d059b9960606ef2ee51ea3a @Override diff --git a/src/main/java/net/minecraft/server/players/GameProfileCache.java b/src/main/java/net/minecraft/server/players/GameProfileCache.java -index bc7143ad915f001726e8558c8ca4160f3f9ace68..aeb0c7ce9b6f93dadd407dbdefba053568f2e2fe 100644 +index 22ed1e67ccb358655fe74cd1b1e19415f4e5bdd7..c89f4a885982f06823886c81fd386b8de029a3dd 100644 --- a/src/main/java/net/minecraft/server/players/GameProfileCache.java +++ b/src/main/java/net/minecraft/server/players/GameProfileCache.java @@ -89,7 +89,8 @@ public class GameProfileCache { @@ -60,10 +60,10 @@ index a0b0614ac7d2009db5c6c10ab4a5f09dd447c635..653856d0b8dcf2baf4cc77a276f17c8c } else { String[] astring1 = astring; diff --git a/src/main/java/org/bukkit/craftbukkit/CraftServer.java b/src/main/java/org/bukkit/craftbukkit/CraftServer.java -index b7539a5f7bc20f20f3cd7fb30d87ab7ffc1133c3..b125872e57e15081d6f5a7b3e108f7633046b228 100644 +index c6ea35f752c9a0bf9e5e07e3b501fd3fe8c30084..f970f821f5ac8cbad90ebb8dd8e8bf222ae229ae 100644 --- a/src/main/java/org/bukkit/craftbukkit/CraftServer.java +++ b/src/main/java/org/bukkit/craftbukkit/CraftServer.java -@@ -1888,7 +1888,7 @@ public final class CraftServer implements Server { +@@ -1891,7 +1891,7 @@ public final class CraftServer implements Server { if (result == null) { GameProfile profile = null; // Only fetch an online UUID in online mode diff --git a/patches/server/0105-Optimise-BlockState-s-hashCode-equals.patch b/patches/server/0105-Optimise-BlockState-s-hashCode-equals.patch index 348097f8cebe..057a037d8969 100644 --- a/patches/server/0105-Optimise-BlockState-s-hashCode-equals.patch +++ b/patches/server/0105-Optimise-BlockState-s-hashCode-equals.patch @@ -8,26 +8,12 @@ object identity checks safely. Use a simpler optimized hashcode -diff --git a/src/main/java/net/minecraft/world/level/block/state/properties/BooleanProperty.java b/src/main/java/net/minecraft/world/level/block/state/properties/BooleanProperty.java -index ee2f1bff22fed14cda434173dc6286e4d4520822..b63116b333b6e06494091a82588acfb639bddb71 100644 ---- a/src/main/java/net/minecraft/world/level/block/state/properties/BooleanProperty.java -+++ b/src/main/java/net/minecraft/world/level/block/state/properties/BooleanProperty.java -@@ -30,8 +30,7 @@ public class BooleanProperty extends Property { - return value.toString(); - } - -- @Override -- public boolean equals(Object object) { -+ public boolean equals_unused(Object object) { // Paper - Perf: Optimize hashCode/equals - if (this == object) { - return true; - } else { diff --git a/src/main/java/net/minecraft/world/level/block/state/properties/EnumProperty.java b/src/main/java/net/minecraft/world/level/block/state/properties/EnumProperty.java -index f4d9856420a6e7e857fc9b9cb6182a54fe341568..3097298fe356df98967cf4bdeaaede69dfe8a441 100644 +index e0259a3f66248b011276b6bc5135c5fc96446640..85a197232be9377c0313ec00e8f935551e2c60e0 100644 --- a/src/main/java/net/minecraft/world/level/block/state/properties/EnumProperty.java +++ b/src/main/java/net/minecraft/world/level/block/state/properties/EnumProperty.java -@@ -44,8 +44,7 @@ public class EnumProperty & StringRepresentable> extends Prope - return value.getSerializedName(); +@@ -59,8 +59,7 @@ public final class EnumProperty & StringRepresentable> extends + return this.ordinalToIndex[enum_.ordinal()]; } - @Override @@ -37,10 +23,10 @@ index f4d9856420a6e7e857fc9b9cb6182a54fe341568..3097298fe356df98967cf4bdeaaede69 return true; } else { diff --git a/src/main/java/net/minecraft/world/level/block/state/properties/IntegerProperty.java b/src/main/java/net/minecraft/world/level/block/state/properties/IntegerProperty.java -index 3c590d400032d8266de63aae301fedbd83d37a1d..3a850321a4bcc68058483b5fd53e829c425a68af 100644 +index 96798f09abe9676679bb25c74942810919e71924..55a87592a99105dbf57b26fb6ccba695295fce24 100644 --- a/src/main/java/net/minecraft/world/level/block/state/properties/IntegerProperty.java +++ b/src/main/java/net/minecraft/world/level/block/state/properties/IntegerProperty.java -@@ -35,8 +35,7 @@ public class IntegerProperty extends Property { +@@ -28,8 +28,7 @@ public final class IntegerProperty extends Property { return this.values; } @@ -51,10 +37,10 @@ index 3c590d400032d8266de63aae301fedbd83d37a1d..3a850321a4bcc68058483b5fd53e829c return true; } else { diff --git a/src/main/java/net/minecraft/world/level/block/state/properties/Property.java b/src/main/java/net/minecraft/world/level/block/state/properties/Property.java -index 7cbc5e6e75f389f47ef07045f9876cec192f14e4..9055f15af0cae55effa6942913a9d7edf3857e07 100644 +index e2bf1055d9ee09c29884505112ccc748cc049bfc..fcf04c5c58ff35d38c5bf0df562ae2f8dc98a0ee 100644 --- a/src/main/java/net/minecraft/world/level/block/state/properties/Property.java +++ b/src/main/java/net/minecraft/world/level/block/state/properties/Property.java -@@ -70,7 +70,7 @@ public abstract class Property> { +@@ -72,7 +72,7 @@ public abstract class Property> { @Override public boolean equals(Object object) { diff --git a/patches/server/0106-Configurable-packet-in-spam-threshold.patch b/patches/server/0106-Configurable-packet-in-spam-threshold.patch index 65d911de77c1..b28e8580373f 100644 --- a/patches/server/0106-Configurable-packet-in-spam-threshold.patch +++ b/patches/server/0106-Configurable-packet-in-spam-threshold.patch @@ -5,10 +5,10 @@ Subject: [PATCH] Configurable packet in spam threshold diff --git a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java -index 14a821bfc6b20475889d3138b8da9e6bfaf1787c..905a7941597306b0cd23ec9a883ef3ee9a684788 100644 +index 79db369b905744ab2fd69de1afc34feec589ec82..be690a8a22c126b007e623c8fe627bab00a28bad 100644 --- a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java +++ b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java -@@ -1534,13 +1534,14 @@ public class ServerGamePacketListenerImpl extends ServerCommonPacketListenerImpl +@@ -1545,13 +1545,14 @@ public class ServerGamePacketListenerImpl extends ServerCommonPacketListenerImpl // Spigot start - limit place/interactions private int limitedPackets; private long lastLimitedPacket = -1; diff --git a/patches/server/0107-Configurable-flying-kick-messages.patch b/patches/server/0107-Configurable-flying-kick-messages.patch index 7a80c997cc37..00bd53261422 100644 --- a/patches/server/0107-Configurable-flying-kick-messages.patch +++ b/patches/server/0107-Configurable-flying-kick-messages.patch @@ -5,10 +5,10 @@ Subject: [PATCH] Configurable flying kick messages diff --git a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java -index 905a7941597306b0cd23ec9a883ef3ee9a684788..34aeb823950fac8eaef3f38b302c1585a45f7498 100644 +index be690a8a22c126b007e623c8fe627bab00a28bad..bc0d7bfb01a613c8fa567238f973f834cf3e3f17 100644 --- a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java +++ b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java -@@ -346,7 +346,7 @@ public class ServerGamePacketListenerImpl extends ServerCommonPacketListenerImpl +@@ -355,7 +355,7 @@ public class ServerGamePacketListenerImpl extends ServerCommonPacketListenerImpl if (this.clientIsFloating && !this.player.isSleeping() && !this.player.isPassenger() && !this.player.isDeadOrDying()) { if (++this.aboveGroundTickCount > this.getMaximumFlyingTicks(this.player)) { ServerGamePacketListenerImpl.LOGGER.warn("{} was kicked for floating too long!", this.player.getName().getString()); @@ -17,7 +17,7 @@ index 905a7941597306b0cd23ec9a883ef3ee9a684788..34aeb823950fac8eaef3f38b302c1585 return; } } else { -@@ -365,7 +365,7 @@ public class ServerGamePacketListenerImpl extends ServerCommonPacketListenerImpl +@@ -374,7 +374,7 @@ public class ServerGamePacketListenerImpl extends ServerCommonPacketListenerImpl if (this.clientVehicleIsFloating && this.lastVehicle.getControllingPassenger() == this.player) { if (++this.aboveGroundVehicleTickCount > this.getMaximumFlyingTicks(this.lastVehicle)) { ServerGamePacketListenerImpl.LOGGER.warn("{} was kicked for floating a vehicle too long!", this.player.getName().getString()); diff --git a/patches/server/0108-Add-EntityZapEvent.patch b/patches/server/0108-Add-EntityZapEvent.patch index cb6ab1da64c7..c788e393ecf2 100644 --- a/patches/server/0108-Add-EntityZapEvent.patch +++ b/patches/server/0108-Add-EntityZapEvent.patch @@ -4,34 +4,89 @@ Date: Sun, 16 Oct 2016 23:19:30 -0700 Subject: [PATCH] Add EntityZapEvent +diff --git a/src/main/java/net/minecraft/world/entity/ConversionParams.java b/src/main/java/net/minecraft/world/entity/ConversionParams.java +index 3300104ad3e1f1e39cbe928ec6ad635e6ab76327..c18bc54721e90ed67312cd8baf52ccc8fe04d4cb 100644 +--- a/src/main/java/net/minecraft/world/entity/ConversionParams.java ++++ b/src/main/java/net/minecraft/world/entity/ConversionParams.java +@@ -12,4 +12,11 @@ public record ConversionParams(ConversionType type, boolean keepEquipment, boole + public interface AfterConversion { + void finalizeConversion(T convertedEntity); + } ++ ++ // Paper start - entity zap event - allow conversion to be cancelled during finalization ++ @FunctionalInterface ++ public interface CancellingAfterConversion { ++ boolean finalizeConversionOrCancel(final T convertedEntity); ++ } ++ // Paper start - entity zap event - allow conversion to be cancelled during finalization + } +diff --git a/src/main/java/net/minecraft/world/entity/Mob.java b/src/main/java/net/minecraft/world/entity/Mob.java +index 9655466953cf850b82716246821a3ebb968a5478..03d289abc30927793aa00f6758ed9db6fb765999 100644 +--- a/src/main/java/net/minecraft/world/entity/Mob.java ++++ b/src/main/java/net/minecraft/world/entity/Mob.java +@@ -1498,6 +1498,12 @@ public abstract class Mob extends LivingEntity implements EquipmentUser, Leashab + + @Nullable + public T convertTo(EntityType entitytypes, ConversionParams conversionparams, EntitySpawnReason entityspawnreason, ConversionParams.AfterConversion conversionparams_a, EntityTransformEvent.TransformReason transformReason, CreatureSpawnEvent.SpawnReason spawnReason) { ++ // Paper start - entity zap event - allow cancellation of conversion post creation ++ return this.convertTo(entitytypes, conversionparams, entityspawnreason, e -> { conversionparams_a.finalizeConversion(e); return true; }, transformReason, spawnReason); ++ } ++ @Nullable ++ public T convertTo(EntityType entitytypes, ConversionParams conversionparams, EntitySpawnReason entityspawnreason, ConversionParams.CancellingAfterConversion conversionparams_a, EntityTransformEvent.TransformReason transformReason, CreatureSpawnEvent.SpawnReason spawnReason) { ++ // Paper end - entity zap event - allow cancellation of conversion post creation + // CraftBukkit end + if (this.isRemoved()) { + return null; +@@ -1508,7 +1514,7 @@ public abstract class Mob extends LivingEntity implements EquipmentUser, Leashab + return null; + } else { + conversionparams.type().convert(this, t0, conversionparams); +- conversionparams_a.finalizeConversion(t0); ++ if (!conversionparams_a.finalizeConversionOrCancel(t0)) return null; // Paper - entity zap event - return null if conversion was cancelled + Level world = this.level(); + + // CraftBukkit start +@@ -1544,6 +1550,11 @@ public abstract class Mob extends LivingEntity implements EquipmentUser, Leashab + + @Nullable + public T convertTo(EntityType entitytypes, ConversionParams conversionparams, ConversionParams.AfterConversion conversionparams_a, EntityTransformEvent.TransformReason transformReason, CreatureSpawnEvent.SpawnReason spawnReason) { ++ // Paper start - entity zap event - allow cancellation of conversion post creation ++ return this.convertTo(entitytypes, conversionparams, e -> { conversionparams_a.finalizeConversion(e); return true; }, transformReason, spawnReason); ++ } ++ public T convertTo(EntityType entitytypes, ConversionParams conversionparams, ConversionParams.CancellingAfterConversion conversionparams_a, EntityTransformEvent.TransformReason transformReason, CreatureSpawnEvent.SpawnReason spawnReason) { ++ // Paper start - entity zap event - allow cancellation of conversion post creation + return this.convertTo(entitytypes, conversionparams, EntitySpawnReason.CONVERSION, conversionparams_a, transformReason, spawnReason); + // CraftBukkit end + } diff --git a/src/main/java/net/minecraft/world/entity/npc/Villager.java b/src/main/java/net/minecraft/world/entity/npc/Villager.java -index 63c10be6eacd7108b8b4795d76bf624e0614440a..243eb1e54293c763a06febff551c051398d43535 100644 +index e52487a3537db6c7d6845f972355f8f437ea9156..624f06d630b55cdcaa97cb66736b69c7ad45dd83 100644 --- a/src/main/java/net/minecraft/world/entity/npc/Villager.java +++ b/src/main/java/net/minecraft/world/entity/npc/Villager.java -@@ -846,10 +846,17 @@ public class Villager extends AbstractVillager implements ReputationEventHandler +@@ -834,11 +834,18 @@ public class Villager extends AbstractVillager implements ReputationEventHandler @Override public void thunderHit(ServerLevel world, LightningBolt lightning) { if (world.getDifficulty() != Difficulty.PEACEFUL) { - Villager.LOGGER.info("Villager {} was struck by lightning {}.", this, lightning); + // Paper - Add EntityZapEvent; move log down, event can cancel - Witch entitywitch = (Witch) EntityType.WITCH.create(world); + Witch entitywitch = (Witch) this.convertTo(EntityType.WITCH, ConversionParams.single(this, false, false), (entitywitch1) -> { ++ // Paper start - Add EntityZapEvent ++ if (org.bukkit.craftbukkit.event.CraftEventFactory.callEntityZapEvent(this, lightning, entitywitch1).isCancelled()) { ++ return false; ++ } ++ if (org.spigotmc.SpigotConfig.logVillagerDeaths) Villager.LOGGER.info("Villager {} was struck by lightning {}.", this, lightning); // Move down ++ // Paper end - Add EntityZapEvent + entitywitch1.finalizeSpawn(world, world.getCurrentDifficultyAt(entitywitch1.blockPosition()), EntitySpawnReason.CONVERSION, (SpawnGroupData) null); + entitywitch1.setPersistenceRequired(); + this.releaseAllPois(); ++ return true; // Paper start - Add EntityZapEvent + }, EntityTransformEvent.TransformReason.LIGHTNING, org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason.LIGHTNING); // CraftBukkit - if (entitywitch != null) { -+ // Paper start - Add EntityZapEvent -+ if (org.bukkit.craftbukkit.event.CraftEventFactory.callEntityZapEvent(this, lightning, entitywitch).isCancelled()) { -+ return; -+ } -+ if (org.spigotmc.SpigotConfig.logVillagerDeaths) Villager.LOGGER.info("Villager {} was struck by lightning {}.", this, lightning); // Move down -+ // Paper end - Add EntityZapEvent -+ - entitywitch.moveTo(this.getX(), this.getY(), this.getZ(), this.getYRot(), this.getXRot()); - entitywitch.finalizeSpawn(world, world.getCurrentDifficultyAt(entitywitch.blockPosition()), MobSpawnType.CONVERSION, (SpawnGroupData) null); - entitywitch.setNoAi(this.isNoAi()); + if (entitywitch == null) { diff --git a/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java b/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java -index 96e0fc5c8a018fd579f24529175decdac634cfa1..50f33c6029c190f947b6bf6215004416b034c0cc 100644 +index 9100ea65e85a0e55cad736634fa63815366334a8..40c298cf4444e7f458cb99b81d64ee6d58a2f128 100644 --- a/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java +++ b/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java -@@ -1217,6 +1217,14 @@ public class CraftEventFactory { +@@ -1215,6 +1215,14 @@ public class CraftEventFactory { return !event.isCancelled(); } diff --git a/patches/server/0109-Filter-bad-block-entity-nbt-data-from-falling-blocks.patch b/patches/server/0109-Filter-bad-block-entity-nbt-data-from-falling-blocks.patch index 00673bc0e766..bf9d10bb7ea0 100644 --- a/patches/server/0109-Filter-bad-block-entity-nbt-data-from-falling-blocks.patch +++ b/patches/server/0109-Filter-bad-block-entity-nbt-data-from-falling-blocks.patch @@ -5,10 +5,10 @@ Subject: [PATCH] Filter bad block entity nbt data from falling blocks diff --git a/src/main/java/net/minecraft/world/entity/item/FallingBlockEntity.java b/src/main/java/net/minecraft/world/entity/item/FallingBlockEntity.java -index c3a16691e8a843c02e0aea6469822cd8869ad593..01ac7bb0ef8ab13e7c4b5b56b768b7c0a642b300 100644 +index a5543a6b4811628ff5178a0ec01933ec4b30dfa4..72abeb4f37b70094498ed3b18e8f73346ba0ead0 100644 --- a/src/main/java/net/minecraft/world/entity/item/FallingBlockEntity.java +++ b/src/main/java/net/minecraft/world/entity/item/FallingBlockEntity.java -@@ -340,7 +340,7 @@ public class FallingBlockEntity extends Entity { +@@ -356,7 +356,7 @@ public class FallingBlockEntity extends Entity { this.dropItem = nbt.getBoolean("DropItem"); } diff --git a/patches/server/0110-Cache-user-authenticator-threads.patch b/patches/server/0110-Cache-user-authenticator-threads.patch index f16083a26ef9..471804336aec 100644 --- a/patches/server/0110-Cache-user-authenticator-threads.patch +++ b/patches/server/0110-Cache-user-authenticator-threads.patch @@ -5,7 +5,7 @@ Subject: [PATCH] Cache user authenticator threads diff --git a/src/main/java/net/minecraft/server/network/ServerLoginPacketListenerImpl.java b/src/main/java/net/minecraft/server/network/ServerLoginPacketListenerImpl.java -index b2f7c76207f2dc0c62f608f21aba4b531f8f523f..e65c582635317b9f8a1af4e6f6a5fb916f73cc35 100644 +index 8cf3b9f1b7eef2d6278830e21ae012852687e02b..80ac468321a9ccb3486e97b3448dd3fccd8e766e 100644 --- a/src/main/java/net/minecraft/server/network/ServerLoginPacketListenerImpl.java +++ b/src/main/java/net/minecraft/server/network/ServerLoginPacketListenerImpl.java @@ -76,6 +76,7 @@ public class ServerLoginPacketListenerImpl implements ServerLoginPacketListener, diff --git a/patches/server/0111-Allow-Reloading-of-Command-Aliases.patch b/patches/server/0111-Allow-Reloading-of-Command-Aliases.patch index 9bf9ad3da9e7..aa356f414e51 100644 --- a/patches/server/0111-Allow-Reloading-of-Command-Aliases.patch +++ b/patches/server/0111-Allow-Reloading-of-Command-Aliases.patch @@ -6,10 +6,10 @@ Subject: [PATCH] Allow Reloading of Command Aliases Reload the aliases stored in commands.yml diff --git a/src/main/java/org/bukkit/craftbukkit/CraftServer.java b/src/main/java/org/bukkit/craftbukkit/CraftServer.java -index b125872e57e15081d6f5a7b3e108f7633046b228..8e951ed126453cf1ffa81e5c8aa6e6ea5db03089 100644 +index f970f821f5ac8cbad90ebb8dd8e8bf222ae229ae..c36bba560d5b17c69c2034cd1fcd711234c13414 100644 --- a/src/main/java/org/bukkit/craftbukkit/CraftServer.java +++ b/src/main/java/org/bukkit/craftbukkit/CraftServer.java -@@ -2818,5 +2818,24 @@ public final class CraftServer implements Server { +@@ -2834,5 +2834,24 @@ public final class CraftServer implements Server { DefaultPermissions.registerCorePermissions(); CraftDefaultPermissions.registerCorePermissions(); } diff --git a/patches/server/0112-Add-source-to-PlayerExpChangeEvent.patch b/patches/server/0112-Add-source-to-PlayerExpChangeEvent.patch index 539523b8dd39..cb2ac725a281 100644 --- a/patches/server/0112-Add-source-to-PlayerExpChangeEvent.patch +++ b/patches/server/0112-Add-source-to-PlayerExpChangeEvent.patch @@ -5,10 +5,10 @@ Subject: [PATCH] Add source to PlayerExpChangeEvent diff --git a/src/main/java/net/minecraft/world/entity/ExperienceOrb.java b/src/main/java/net/minecraft/world/entity/ExperienceOrb.java -index 56402312e44d12c859e2c4b39902d31b7cfd1573..25a45e680f9fdea90f43d59de87a3a500f4ee8c0 100644 +index 8254f76a386bd6791b844a35b146370e817b09ad..7f3ac3e8631e30c968ef664f994ad208d05eb4a3 100644 --- a/src/main/java/net/minecraft/world/entity/ExperienceOrb.java +++ b/src/main/java/net/minecraft/world/entity/ExperienceOrb.java -@@ -264,7 +264,7 @@ public class ExperienceOrb extends Entity { +@@ -268,7 +268,7 @@ public class ExperienceOrb extends Entity { int i = this.repairPlayerItems(entityplayer, this.value); if (i > 0) { @@ -18,10 +18,10 @@ index 56402312e44d12c859e2c4b39902d31b7cfd1573..25a45e680f9fdea90f43d59de87a3a50 --this.count; diff --git a/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java b/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java -index 50f33c6029c190f947b6bf6215004416b034c0cc..d36e039aa5f1cf84179def5df5addaf448f54bd2 100644 +index 40c298cf4444e7f458cb99b81d64ee6d58a2f128..00267c18a1a6fba610b271770112a55efe732955 100644 --- a/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java +++ b/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java -@@ -1170,6 +1170,17 @@ public class CraftEventFactory { +@@ -1168,6 +1168,17 @@ public class CraftEventFactory { return event; } diff --git a/patches/server/0113-Add-ProjectileCollideEvent.patch b/patches/server/0113-Add-ProjectileCollideEvent.patch index f2c7317621b9..2b0113c50b86 100644 --- a/patches/server/0113-Add-ProjectileCollideEvent.patch +++ b/patches/server/0113-Add-ProjectileCollideEvent.patch @@ -6,10 +6,10 @@ Subject: [PATCH] Add ProjectileCollideEvent Deprecated now and replaced with ProjectileHitEvent diff --git a/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java b/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java -index d36e039aa5f1cf84179def5df5addaf448f54bd2..91180d7ad705ca97c0df43debc14b204127165d0 100644 +index 00267c18a1a6fba610b271770112a55efe732955..0a3c89dba691760c7e0be58914003cc438269a03 100644 --- a/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java +++ b/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java -@@ -1323,6 +1323,16 @@ public class CraftEventFactory { +@@ -1321,6 +1321,16 @@ public class CraftEventFactory { Bukkit.getPluginManager().callEvent(crafterCraftEvent); return crafterCraftEvent; } @@ -26,7 +26,7 @@ index d36e039aa5f1cf84179def5df5addaf448f54bd2..91180d7ad705ca97c0df43debc14b204 public static ProjectileLaunchEvent callProjectileLaunchEvent(Entity entity) { Projectile bukkitEntity = (Projectile) entity.getBukkitEntity(); -@@ -1348,8 +1358,15 @@ public class CraftEventFactory { +@@ -1346,8 +1356,15 @@ public class CraftEventFactory { if (position.getType() == HitResult.Type.ENTITY) { hitEntity = ((EntityHitResult) position).getEntity().getBukkitEntity(); } diff --git a/patches/server/0114-Prevent-Pathfinding-out-of-World-Border.patch b/patches/server/0114-Prevent-Pathfinding-out-of-World-Border.patch index ce3053228da4..21235871ad7a 100644 --- a/patches/server/0114-Prevent-Pathfinding-out-of-World-Border.patch +++ b/patches/server/0114-Prevent-Pathfinding-out-of-World-Border.patch @@ -13,10 +13,10 @@ by adding code to all overrides in: to return BLOCKED if it is outside the world border. diff --git a/src/main/java/net/minecraft/world/entity/ai/navigation/PathNavigation.java b/src/main/java/net/minecraft/world/entity/ai/navigation/PathNavigation.java -index 21bbc98b26b270b3ad6a3b34d6e50dfb796c3d5a..188904c9f0f81db1d63eec953d6746f2dc23dc81 100644 +index 1d5ce4caf99a3fb376b350968a6bd1ac8471ffec..436812c3bfe53358b4d76bb72d777d6661bb6d60 100644 --- a/src/main/java/net/minecraft/world/entity/ai/navigation/PathNavigation.java +++ b/src/main/java/net/minecraft/world/entity/ai/navigation/PathNavigation.java -@@ -158,7 +158,7 @@ public abstract class PathNavigation { +@@ -174,7 +174,7 @@ public abstract class PathNavigation { // Paper start - EntityPathfindEvent boolean copiedSet = false; for (BlockPos possibleTarget : positions) { diff --git a/patches/server/0115-Optimize-Level.hasChunkAt-BlockPosition-Z.patch b/patches/server/0115-Optimize-Level.hasChunkAt-BlockPosition-Z.patch index ee46f05dc04c..526301e39dcc 100644 --- a/patches/server/0115-Optimize-Level.hasChunkAt-BlockPosition-Z.patch +++ b/patches/server/0115-Optimize-Level.hasChunkAt-BlockPosition-Z.patch @@ -6,10 +6,10 @@ Subject: [PATCH] Optimize Level.hasChunkAt(BlockPosition)Z Reduce method invocations for World.isLoaded(BlockPosition)Z diff --git a/src/main/java/net/minecraft/world/level/Level.java b/src/main/java/net/minecraft/world/level/Level.java -index 008be3aad044d20be14da3a9e96933d265104587..03b0720c6ebf1a876d56d18a941e0a06ed26dbf0 100644 +index 675dcb6eb5c33f6ec582ff20118d2f73745914a9..943c14b26cf5b1c9f9ea1acec058cecac3b93bf7 100644 --- a/src/main/java/net/minecraft/world/level/Level.java +++ b/src/main/java/net/minecraft/world/level/Level.java -@@ -342,6 +342,11 @@ public abstract class Level implements LevelAccessor, AutoCloseable { +@@ -340,6 +340,11 @@ public abstract class Level implements LevelAccessor, AutoCloseable { return chunk == null ? null : chunk.getFluidState(blockposition); } diff --git a/patches/server/0116-Bound-Treasure-Maps-to-World-Border.patch b/patches/server/0116-Bound-Treasure-Maps-to-World-Border.patch index b4596cd20bfc..0444bae2bab0 100644 --- a/patches/server/0116-Bound-Treasure-Maps-to-World-Border.patch +++ b/patches/server/0116-Bound-Treasure-Maps-to-World-Border.patch @@ -11,7 +11,7 @@ that is outside happens to be closer, but unreachable, yet another reachable one is in border that would of been missed. diff --git a/src/main/java/net/minecraft/world/level/border/WorldBorder.java b/src/main/java/net/minecraft/world/level/border/WorldBorder.java -index a3ba3a09378e3bd0517464130ad2c702b0b0165d..3442e33a1146318228c4727a2a5afde685f69bf7 100644 +index 5aa04c48e04c067a366383b252a7b713d85eaee9..b50090df116697a12f5498d65dd2e5d6d5297fb5 100644 --- a/src/main/java/net/minecraft/world/level/border/WorldBorder.java +++ b/src/main/java/net/minecraft/world/level/border/WorldBorder.java @@ -46,6 +46,18 @@ public class WorldBorder { @@ -34,7 +34,7 @@ index a3ba3a09378e3bd0517464130ad2c702b0b0165d..3442e33a1146318228c4727a2a5afde6 return this.isWithinBounds(box.minX, box.minZ, box.maxX - 9.999999747378752E-6D, box.maxZ - 9.999999747378752E-6D); } diff --git a/src/main/java/net/minecraft/world/level/chunk/ChunkGenerator.java b/src/main/java/net/minecraft/world/level/chunk/ChunkGenerator.java -index a53fa9bebdd46939a710e46466ca9a350ecefb27..0a779632c9d11496fcfc147870fba2699d9cc274 100644 +index a84434a95dbe3c458f358d9824de87c503a8b1dc..115deba41ec48143570489e8494785a3a48cd789 100644 --- a/src/main/java/net/minecraft/world/level/chunk/ChunkGenerator.java +++ b/src/main/java/net/minecraft/world/level/chunk/ChunkGenerator.java @@ -222,6 +222,7 @@ public abstract class ChunkGenerator { diff --git a/patches/server/0117-Configurable-Cartographer-Treasure-Maps.patch b/patches/server/0117-Configurable-Cartographer-Treasure-Maps.patch index a93dd268ae06..1ca587b32ee0 100644 --- a/patches/server/0117-Configurable-Cartographer-Treasure-Maps.patch +++ b/patches/server/0117-Configurable-Cartographer-Treasure-Maps.patch @@ -9,10 +9,10 @@ Also allow turning off treasure maps all together as they can eat up Map ID's which are limited in quantity. diff --git a/src/main/java/net/minecraft/world/entity/npc/VillagerTrades.java b/src/main/java/net/minecraft/world/entity/npc/VillagerTrades.java -index 9358498491cd25ef760527ebd83188b76f8458f1..73a222d91dab1d5d1390b018f9537624aaff0798 100644 +index 914134d274c4a484c99bbe59521e30881c120799..a20c23db72f207b069f4ae0eb83ab6b6dca12072 100644 --- a/src/main/java/net/minecraft/world/entity/npc/VillagerTrades.java +++ b/src/main/java/net/minecraft/world/entity/npc/VillagerTrades.java -@@ -1828,7 +1828,8 @@ public class VillagerTrades { +@@ -1826,7 +1826,8 @@ public class VillagerTrades { return null; } else { ServerLevel serverLevel = (ServerLevel)entity.level(); @@ -23,11 +23,11 @@ index 9358498491cd25ef760527ebd83188b76f8458f1..73a222d91dab1d5d1390b018f9537624 ItemStack itemStack = MapItem.create(serverLevel, blockPos.getX(), blockPos.getZ(), (byte)2, true, true); MapItem.renderBiomePreviewMap(serverLevel, itemStack); diff --git a/src/main/java/net/minecraft/world/level/storage/loot/functions/ExplorationMapFunction.java b/src/main/java/net/minecraft/world/level/storage/loot/functions/ExplorationMapFunction.java -index e7f2cc799e7c50a9525845347a3f235f7bfefe41..709bc6838d20c420c5a4b3a994a041e594bd6210 100644 +index 4a4a692686159a295aeae94daa8e55bcaa503e0d..c30943031db0e72c8a412552d0706d4da9e9cebc 100644 --- a/src/main/java/net/minecraft/world/level/storage/loot/functions/ExplorationMapFunction.java +++ b/src/main/java/net/minecraft/world/level/storage/loot/functions/ExplorationMapFunction.java -@@ -84,8 +84,17 @@ public class ExplorationMapFunction extends LootItemConditionalFunction { - Vec3 vec3 = context.getParamOrNull(LootContextParams.ORIGIN); +@@ -83,8 +83,17 @@ public class ExplorationMapFunction extends LootItemConditionalFunction { + Vec3 vec3 = context.getOptionalParameter(LootContextParams.ORIGIN); if (vec3 != null) { ServerLevel serverLevel = context.getLevel(); + // Paper start - Configurable cartographer treasure maps diff --git a/patches/server/0118-Add-API-methods-to-control-if-armor-stands-can-move.patch b/patches/server/0118-Add-API-methods-to-control-if-armor-stands-can-move.patch index ace73cb96950..e79a04b269a1 100644 --- a/patches/server/0118-Add-API-methods-to-control-if-armor-stands-can-move.patch +++ b/patches/server/0118-Add-API-methods-to-control-if-armor-stands-can-move.patch @@ -5,10 +5,10 @@ Subject: [PATCH] Add API methods to control if armor stands can move diff --git a/src/main/java/net/minecraft/world/entity/decoration/ArmorStand.java b/src/main/java/net/minecraft/world/entity/decoration/ArmorStand.java -index af954b2c0c7106a231fb15172da3fa8e1d281d56..dae6835696e90bc5a541cacd37ea7aa88c60f4f4 100644 +index c3a4b967a73a1a04192850a6880d42ebd22c2a14..aea97a30a9226275f8fbf9cb2c15d5ddf36371ac 100644 --- a/src/main/java/net/minecraft/world/entity/decoration/ArmorStand.java +++ b/src/main/java/net/minecraft/world/entity/decoration/ArmorStand.java -@@ -95,6 +95,7 @@ public class ArmorStand extends LivingEntity { +@@ -107,6 +107,7 @@ public class ArmorStand extends LivingEntity { public Rotations rightArmPose; public Rotations leftLegPose; public Rotations rightLegPose; @@ -16,7 +16,7 @@ index af954b2c0c7106a231fb15172da3fa8e1d281d56..dae6835696e90bc5a541cacd37ea7aa8 public ArmorStand(EntityType type, Level world) { super(type, world); -@@ -949,4 +950,13 @@ public class ArmorStand extends LivingEntity { +@@ -946,4 +947,13 @@ public class ArmorStand extends LivingEntity { public boolean canBeSeenByAnyone() { return !this.isInvisible() && !this.isMarker(); } @@ -31,12 +31,12 @@ index af954b2c0c7106a231fb15172da3fa8e1d281d56..dae6835696e90bc5a541cacd37ea7aa8 + // Paper end } diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftArmorStand.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftArmorStand.java -index 04a7735d278c9e610a33294e65a17796e120fe7e..52ffc401bbb9fa768534a4b871f9cc7dbebb8b20 100644 +index 9ed8d228729d067f21f34224458d141988845348..56fcd9dd40e6a63e1af5fbd470ece0d6100292a2 100644 --- a/src/main/java/org/bukkit/craftbukkit/entity/CraftArmorStand.java +++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftArmorStand.java @@ -222,4 +222,15 @@ public class CraftArmorStand extends CraftLivingEntity implements ArmorStand { public boolean hasEquipmentLock(EquipmentSlot equipmentSlot, LockType lockType) { - return (this.getHandle().disabledSlots & (1 << CraftEquipmentSlot.getNMS(equipmentSlot).getFilterFlag() + lockType.ordinal() * 8)) != 0; + return (this.getHandle().disabledSlots & (1 << CraftEquipmentSlot.getNMS(equipmentSlot).getFilterBit(lockType.ordinal() * 8))) != 0; } + // Paper start + @Override diff --git a/patches/server/0119-String-based-Action-Bar-API.patch b/patches/server/0119-String-based-Action-Bar-API.patch index 31024a0f8e48..028bc2e2c01e 100644 --- a/patches/server/0119-String-based-Action-Bar-API.patch +++ b/patches/server/0119-String-based-Action-Bar-API.patch @@ -5,10 +5,10 @@ Subject: [PATCH] String based Action Bar API diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java -index cdcb2fb6229277872db36b6a4c439080f083f64c..258808bcb6f853c5679476305074823a7bb8b379 100644 +index d578d65883f23cc4aaa5b0a7cf1fc88bb337f3a5..bf8453b2fa3549c827bff784b0d98aa827053634 100644 --- a/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java +++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java -@@ -390,6 +390,28 @@ public class CraftPlayer extends CraftHumanEntity implements Player { +@@ -395,6 +395,28 @@ public class CraftPlayer extends CraftHumanEntity implements Player { } // Paper start diff --git a/patches/server/0120-Properly-fix-item-duplication-bug.patch b/patches/server/0120-Properly-fix-item-duplication-bug.patch index 9fb45d2886f8..bfc6ea0e9e37 100644 --- a/patches/server/0120-Properly-fix-item-duplication-bug.patch +++ b/patches/server/0120-Properly-fix-item-duplication-bug.patch @@ -6,10 +6,10 @@ Subject: [PATCH] Properly fix item duplication bug Credit to prplz for figuring out the real issue diff --git a/src/main/java/net/minecraft/server/level/ServerPlayer.java b/src/main/java/net/minecraft/server/level/ServerPlayer.java -index 984dc7f7f7315b8a8cdb9744ef8454a330888ba7..f067b10e13f01e751fc4ebf088740c7d40afcb99 100644 +index 78e4f07019e3231fbaa3f23bcdc8846e2d79ae18..5890aa22912eed9d645393f5a7189d6884fb2c66 100644 --- a/src/main/java/net/minecraft/server/level/ServerPlayer.java +++ b/src/main/java/net/minecraft/server/level/ServerPlayer.java -@@ -2627,7 +2627,7 @@ public class ServerPlayer extends net.minecraft.world.entity.player.Player { +@@ -2982,7 +2982,7 @@ public class ServerPlayer extends net.minecraft.world.entity.player.Player { @Override public boolean isImmobile() { @@ -19,10 +19,10 @@ index 984dc7f7f7315b8a8cdb9744ef8454a330888ba7..f067b10e13f01e751fc4ebf088740c7d @Override diff --git a/src/main/java/net/minecraft/server/network/ServerCommonPacketListenerImpl.java b/src/main/java/net/minecraft/server/network/ServerCommonPacketListenerImpl.java -index 9bfdcdf427f7c0689d346d17942b5902a9138a4e..5f3b3f03936cfe23ed792c57d342a9932ea2e962 100644 +index d4527831f66bf1c55e6273c7f8923d6efbbf100f..04ada45eabd5a6c752c320cdff1a65c7ac83eb22 100644 --- a/src/main/java/net/minecraft/server/network/ServerCommonPacketListenerImpl.java +++ b/src/main/java/net/minecraft/server/network/ServerCommonPacketListenerImpl.java -@@ -184,7 +184,7 @@ public abstract class ServerCommonPacketListenerImpl implements ServerCommonPack +@@ -191,7 +191,7 @@ public abstract class ServerCommonPacketListenerImpl implements ServerCommonPack } public final boolean isDisconnected() { diff --git a/patches/server/0121-Firework-API-s.patch b/patches/server/0121-Firework-API-s.patch index c86538a2cf8b..5efca78eb3bf 100644 --- a/patches/server/0121-Firework-API-s.patch +++ b/patches/server/0121-Firework-API-s.patch @@ -7,10 +7,10 @@ Subject: [PATCH] Firework API's public net.minecraft.world.entity.projectile.FireworkRocketEntity attachedToEntity diff --git a/src/main/java/net/minecraft/world/entity/projectile/FireworkRocketEntity.java b/src/main/java/net/minecraft/world/entity/projectile/FireworkRocketEntity.java -index cc99d67bc52c89b50171b6c808c6e3bf293999f5..09d465947a5720e05c350d455c86002682104079 100644 +index e5d245c71ce2b2a1fe7f156e69831dec73354221..8a4e7e1c0c4919d2ee34121c14f9665b9ad95273 100644 --- a/src/main/java/net/minecraft/world/entity/projectile/FireworkRocketEntity.java +++ b/src/main/java/net/minecraft/world/entity/projectile/FireworkRocketEntity.java -@@ -44,6 +44,7 @@ public class FireworkRocketEntity extends Projectile implements ItemSupplier { +@@ -45,6 +45,7 @@ public class FireworkRocketEntity extends Projectile implements ItemSupplier { public int lifetime; @Nullable public LivingEntity attachedToEntity; @@ -18,7 +18,7 @@ index cc99d67bc52c89b50171b6c808c6e3bf293999f5..09d465947a5720e05c350d455c860026 public FireworkRocketEntity(EntityType type, Level world) { super(type, world); -@@ -301,6 +302,11 @@ public class FireworkRocketEntity extends Projectile implements ItemSupplier { +@@ -324,6 +325,11 @@ public class FireworkRocketEntity extends Projectile implements ItemSupplier { nbt.putInt("LifeTime", this.lifetime); nbt.put("FireworksItem", this.getItem().save(this.registryAccess())); nbt.putBoolean("ShotAtAngle", (Boolean) this.entityData.get(FireworkRocketEntity.DATA_SHOT_AT_ANGLE)); @@ -30,7 +30,7 @@ index cc99d67bc52c89b50171b6c808c6e3bf293999f5..09d465947a5720e05c350d455c860026 } @Override -@@ -317,7 +323,11 @@ public class FireworkRocketEntity extends Projectile implements ItemSupplier { +@@ -340,7 +346,11 @@ public class FireworkRocketEntity extends Projectile implements ItemSupplier { if (nbt.contains("ShotAtAngle")) { this.entityData.set(FireworkRocketEntity.DATA_SHOT_AT_ANGLE, nbt.getBoolean("ShotAtAngle")); } @@ -44,10 +44,10 @@ index cc99d67bc52c89b50171b6c808c6e3bf293999f5..09d465947a5720e05c350d455c860026 private List getExplosions() { diff --git a/src/main/java/net/minecraft/world/item/CrossbowItem.java b/src/main/java/net/minecraft/world/item/CrossbowItem.java -index 99a39f05e7aeefa2ea4372159b4837d80963eabf..f64cdfac1fc1333845ea4ea5efb7922f0ae39619 100644 +index 40cae3332a18e3f7be890d2ba8014bfe3d2d1c0e..710181cf04563f06690eee5b46a5a0d84844ac29 100644 --- a/src/main/java/net/minecraft/world/item/CrossbowItem.java +++ b/src/main/java/net/minecraft/world/item/CrossbowItem.java -@@ -160,7 +160,11 @@ public class CrossbowItem extends ProjectileWeaponItem { +@@ -163,7 +163,11 @@ public class CrossbowItem extends ProjectileWeaponItem { @Override protected Projectile createProjectile(Level world, LivingEntity shooter, ItemStack weaponStack, ItemStack projectileStack, boolean critical) { if (projectileStack.is(Items.FIREWORK_ROCKET)) { @@ -61,25 +61,27 @@ index 99a39f05e7aeefa2ea4372159b4837d80963eabf..f64cdfac1fc1333845ea4ea5efb7922f Projectile projectile = super.createProjectile(world, shooter, weaponStack, projectileStack, critical); if (projectile instanceof AbstractArrow abstractArrow) { diff --git a/src/main/java/net/minecraft/world/item/FireworkRocketItem.java b/src/main/java/net/minecraft/world/item/FireworkRocketItem.java -index da89ccaf91e2baa7caa15681c113bc283f40cd21..38b33eb92d21d0099285a304c6e064bbf56db4eb 100644 +index 100a66b9211429b5c32f9ed226c7ddafb9c7df81..3929cb76f1c98c0a22eb2ab64c2ed09805ffe448 100644 --- a/src/main/java/net/minecraft/world/item/FireworkRocketItem.java +++ b/src/main/java/net/minecraft/world/item/FireworkRocketItem.java -@@ -42,6 +42,7 @@ public class FireworkRocketItem extends Item implements ProjectileItem { - vec3.z + (double)direction.getStepZ() * 0.15, - itemStack +@@ -43,7 +43,7 @@ public class FireworkRocketItem extends Item implements ProjectileItem { + itemStack + ), + serverLevel, +- itemStack ++ itemStack, f -> f.spawningEntity = context.getPlayer() == null ? null : context.getPlayer().getUUID() // Paper - firework api - assign spawning entity uuid ); -+ fireworkRocketEntity.spawningEntity = context.getPlayer() == null ? null : context.getPlayer().getUUID(); // Paper - level.addFreshEntity(fireworkRocketEntity); itemStack.shrink(1); } -@@ -55,6 +56,7 @@ public class FireworkRocketItem extends Item implements ProjectileItem { +@@ -56,7 +56,7 @@ public class FireworkRocketItem extends Item implements ProjectileItem { + if (user.isFallFlying()) { ItemStack itemStack = user.getItemInHand(hand); - if (!world.isClientSide) { - FireworkRocketEntity fireworkRocketEntity = new FireworkRocketEntity(world, itemStack, user); -+ fireworkRocketEntity.spawningEntity = user.getUUID(); // Paper - world.addFreshEntity(fireworkRocketEntity); + if (world instanceof ServerLevel serverLevel) { +- Projectile.spawnProjectile(new FireworkRocketEntity(world, itemStack, user), serverLevel, itemStack); ++ Projectile.spawnProjectile(new FireworkRocketEntity(world, itemStack, user), serverLevel, itemStack, f -> f.spawningEntity = user.getUUID()); // Paper - firework api - assign spawning entity uuid itemStack.consume(1, user); user.awardStat(Stats.ITEM_USED.get(this)); + } diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftFirework.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftFirework.java index 5ae87c370e47c545cef27a36e40da137e1ec656b..c9e15a9d82dee935293b2e7e233f5b9b2d822448 100644 --- a/src/main/java/org/bukkit/craftbukkit/entity/CraftFirework.java diff --git a/patches/server/0122-PlayerTeleportEndGatewayEvent.patch b/patches/server/0122-PlayerTeleportEndGatewayEvent.patch index 69691d86303b..0c781b1e9def 100644 --- a/patches/server/0122-PlayerTeleportEndGatewayEvent.patch +++ b/patches/server/0122-PlayerTeleportEndGatewayEvent.patch @@ -7,29 +7,18 @@ Allows you to access the Gateway being used in a teleport event Fix the offset used for player teleportation diff --git a/src/main/java/net/minecraft/server/level/ServerPlayer.java b/src/main/java/net/minecraft/server/level/ServerPlayer.java -index f067b10e13f01e751fc4ebf088740c7d40afcb99..e13692373d0efba9402c35ae5cce615dce0a5e1e 100644 +index 5890aa22912eed9d645393f5a7189d6884fb2c66..b99bd43bf5185bed21fad7dac31baf1a30bdd1fe 100644 --- a/src/main/java/net/minecraft/server/level/ServerPlayer.java +++ b/src/main/java/net/minecraft/server/level/ServerPlayer.java -@@ -1192,11 +1192,22 @@ public class ServerPlayer extends net.minecraft.world.entity.player.Player { - ResourceKey resourcekey = worldserver1.getTypeKey(); - - if (worldserver != null && worldserver.dimension() == worldserver1.dimension()) { // CraftBukkit -+ // Paper start - gateway-specific teleport event -+ if (this.portalProcess != null && this.portalProcess.isSamePortal(((net.minecraft.world.level.block.EndGatewayBlock) net.minecraft.world.level.block.Blocks.END_GATEWAY)) && this.serverLevel().getBlockEntity(this.portalProcess.getEntryPosition()) instanceof net.minecraft.world.level.block.entity.TheEndGatewayBlockEntity theEndGatewayBlockEntity) { -+ Location to = CraftLocation.toBukkit(teleportTarget.pos(), this.serverLevel().getWorld(), teleportTarget.yRot(), teleportTarget.xRot()); -+ final com.destroystokyo.paper.event.player.PlayerTeleportEndGatewayEvent event = new com.destroystokyo.paper.event.player.PlayerTeleportEndGatewayEvent(this.getBukkitEntity(), this.getBukkitEntity().getLocation(), to, new org.bukkit.craftbukkit.block.CraftEndGateway(to.getWorld(), theEndGatewayBlockEntity)); -+ if (!event.callEvent() || event.getTo() == null) { -+ return null; -+ } -+ this.connection.teleport(event.getTo()); -+ } else { -+ // Paper end - gateway-specific teleport event - boolean result = this.connection.teleport(teleportTarget.pos().x, teleportTarget.pos().y, teleportTarget.pos().z, teleportTarget.yRot(), teleportTarget.xRot(), teleportTarget.cause()); - if (!result) { - return null; - } - // CraftBukkit end -+ } // Paper - this.connection.resetPosition(); - teleportTarget.postDimensionTransition().onTransition(this); - return this; +@@ -1455,6 +1455,11 @@ public class ServerPlayer extends net.minecraft.world.entity.player.Player { + PositionMoveRotation absolutePosition = PositionMoveRotation.calculateAbsolute(PositionMoveRotation.of(this), PositionMoveRotation.of(teleportTarget), teleportTarget.relatives()); + Location exit = (worldserver == null) ? null : CraftLocation.toBukkit(absolutePosition.position(), worldserver.getWorld(), absolutePosition.yRot(), absolutePosition.xRot()); + PlayerTeleportEvent tpEvent = new PlayerTeleportEvent(this.getBukkitEntity(), enter, exit, teleportTarget.cause()); ++ // Paper start - gateway-specific teleport event ++ if (this.portalProcess != null && this.portalProcess.isSamePortal(((net.minecraft.world.level.block.EndGatewayBlock) net.minecraft.world.level.block.Blocks.END_GATEWAY)) && this.serverLevel().getBlockEntity(this.portalProcess.getEntryPosition()) instanceof net.minecraft.world.level.block.entity.TheEndGatewayBlockEntity theEndGatewayBlockEntity) { ++ tpEvent = new com.destroystokyo.paper.event.player.PlayerTeleportEndGatewayEvent(this.getBukkitEntity(), enter, exit, new org.bukkit.craftbukkit.block.CraftEndGateway(this.serverLevel().getWorld(), theEndGatewayBlockEntity)); ++ } ++ // Paper end - gateway-specific teleport event + Bukkit.getServer().getPluginManager().callEvent(tpEvent); + Location newExit = tpEvent.getTo(); + if (tpEvent.isCancelled() || newExit == null) { diff --git a/patches/server/0123-Provide-E-TE-Chunk-count-stat-methods.patch b/patches/server/0123-Provide-E-TE-Chunk-count-stat-methods.patch index f1025e5537da..96ee3335c033 100644 --- a/patches/server/0123-Provide-E-TE-Chunk-count-stat-methods.patch +++ b/patches/server/0123-Provide-E-TE-Chunk-count-stat-methods.patch @@ -7,10 +7,10 @@ Provides counts without the ineffeciency of using .getEntities().size() which creates copy of the collections. diff --git a/src/main/java/net/minecraft/world/level/Level.java b/src/main/java/net/minecraft/world/level/Level.java -index 03b0720c6ebf1a876d56d18a941e0a06ed26dbf0..4fcbea7b8b12be10157da0c1f35c06e47c0334ad 100644 +index 943c14b26cf5b1c9f9ea1acec058cecac3b93bf7..e5eac1977f77b7ce1112bfe7ac1b77d9ef4d90f4 100644 --- a/src/main/java/net/minecraft/world/level/Level.java +++ b/src/main/java/net/minecraft/world/level/Level.java -@@ -114,7 +114,7 @@ public abstract class Level implements LevelAccessor, AutoCloseable { +@@ -116,7 +116,7 @@ public abstract class Level implements LevelAccessor, AutoCloseable { public static final int TICKS_PER_DAY = 24000; public static final int MAX_ENTITY_SPAWN_Y = 20000000; public static final int MIN_ENTITY_SPAWN_Y = -20000000; @@ -20,10 +20,10 @@ index 03b0720c6ebf1a876d56d18a941e0a06ed26dbf0..4fcbea7b8b12be10157da0c1f35c06e4 private final List pendingBlockEntityTickers = Lists.newArrayList(); private boolean tickingBlockEntities; diff --git a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java -index 4878a1b085a83dd4a8ffdc86250b8fb4fbac5192..1b7660a2d003bfb19ef80ba8066b6417f85328ec 100644 +index 9a79b948264150d0f7a843a8ddd2ea9245ae66f3..9389c0ebdcaba5022bdae47b2b2ff06b40406127 100644 --- a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java +++ b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java -@@ -164,6 +164,56 @@ public class CraftWorld extends CraftRegionAccessor implements World { +@@ -168,6 +168,56 @@ public class CraftWorld extends CraftRegionAccessor implements World { private final CraftPersistentDataContainer persistentDataContainer = new CraftPersistentDataContainer(CraftWorld.DATA_TYPE_REGISTRY); private net.kyori.adventure.pointer.Pointers adventure$pointers; // Paper - implement pointers diff --git a/patches/server/0124-Enforce-Sync-Player-Saves.patch b/patches/server/0124-Enforce-Sync-Player-Saves.patch index 5211b4bc78e0..bbf020dcb342 100644 --- a/patches/server/0124-Enforce-Sync-Player-Saves.patch +++ b/patches/server/0124-Enforce-Sync-Player-Saves.patch @@ -7,20 +7,18 @@ Saving players async is extremely dangerous. This will force it to main the same way we handle async chunk loads. diff --git a/src/main/java/net/minecraft/server/players/PlayerList.java b/src/main/java/net/minecraft/server/players/PlayerList.java -index 98862db2334508ee1a783aeabfb146751552a4d9..e17f4f3882ae77a67e8bfad04646f02c2bbb6669 100644 +index d0d2b687ec851e3fa08864897774897a1fcd7df8..f9ffae36beb13db416432068d8ee00de21d0c1fe 100644 --- a/src/main/java/net/minecraft/server/players/PlayerList.java +++ b/src/main/java/net/minecraft/server/players/PlayerList.java -@@ -1029,11 +1029,13 @@ public abstract class PlayerList { +@@ -997,10 +997,12 @@ public abstract class PlayerList { } public void saveAll() { + io.papermc.paper.util.MCUtil.ensureMain("Save Players" , () -> { // Paper - Ensure main - MinecraftTimings.savePlayers.startTiming(); // Paper for (int i = 0; i < this.players.size(); ++i) { -- this.save((ServerPlayer) this.players.get(i)); -+ this.save(this.players.get(i)); + this.save((ServerPlayer) this.players.get(i)); } - MinecraftTimings.savePlayers.stopTiming(); // Paper + + return null; }); // Paper - ensure main } diff --git a/patches/server/0125-ExperienceOrbs-API-for-Reason-Source-Triggering-play.patch b/patches/server/0125-ExperienceOrbs-API-for-Reason-Source-Triggering-play.patch index 1ec647cf88db..122c225e0199 100644 --- a/patches/server/0125-ExperienceOrbs-API-for-Reason-Source-Triggering-play.patch +++ b/patches/server/0125-ExperienceOrbs-API-for-Reason-Source-Triggering-play.patch @@ -8,10 +8,10 @@ Adds lots of information about why this orb exists. Replaces isFromBottle() with logic that persists entity reloads too. diff --git a/src/main/java/net/minecraft/server/level/ServerPlayerGameMode.java b/src/main/java/net/minecraft/server/level/ServerPlayerGameMode.java -index 99771070840a545537fe352bda1c4aaeedd638ca..2fab84c5e2dc4de39281956390588a9a71d02f68 100644 +index 0de7b8b8d2670c6ec50eb56348ca28a315b961e5..73b6aa34ad2579d79f388c5660cdfbef41a769f2 100644 --- a/src/main/java/net/minecraft/server/level/ServerPlayerGameMode.java +++ b/src/main/java/net/minecraft/server/level/ServerPlayerGameMode.java -@@ -435,7 +435,7 @@ public class ServerPlayerGameMode { +@@ -433,7 +433,7 @@ public class ServerPlayerGameMode { // Drop event experience if (flag && event != null) { @@ -21,7 +21,7 @@ index 99771070840a545537fe352bda1c4aaeedd638ca..2fab84c5e2dc4de39281956390588a9a return true; diff --git a/src/main/java/net/minecraft/world/entity/ExperienceOrb.java b/src/main/java/net/minecraft/world/entity/ExperienceOrb.java -index 25a45e680f9fdea90f43d59de87a3a500f4ee8c0..0330a62a6a0060d2a96de191db68774588fc7ae5 100644 +index 7f3ac3e8631e30c968ef664f994ad208d05eb4a3..b9160ebca0d11dbbf96da5f0f5810d302cfcea9a 100644 --- a/src/main/java/net/minecraft/world/entity/ExperienceOrb.java +++ b/src/main/java/net/minecraft/world/entity/ExperienceOrb.java @@ -44,9 +44,63 @@ public class ExperienceOrb extends Entity { @@ -88,7 +88,7 @@ index 25a45e680f9fdea90f43d59de87a3a500f4ee8c0..0330a62a6a0060d2a96de191db687745 this.setPos(x, y, z); this.setYRot((float) (this.random.nextDouble() * 360.0D)); this.setDeltaMovement((this.random.nextDouble() * 0.20000000298023224D - 0.10000000149011612D) * 2.0D, this.random.nextDouble() * 0.2D * 2.0D, (this.random.nextDouble() * 0.20000000298023224D - 0.10000000149011612D) * 2.0D); -@@ -170,12 +224,20 @@ public class ExperienceOrb extends Entity { +@@ -171,12 +225,20 @@ public class ExperienceOrb extends Entity { } public static void award(ServerLevel world, Vec3 pos, int amount) { @@ -110,7 +110,7 @@ index 25a45e680f9fdea90f43d59de87a3a500f4ee8c0..0330a62a6a0060d2a96de191db687745 } } -@@ -245,6 +307,7 @@ public class ExperienceOrb extends Entity { +@@ -249,6 +311,7 @@ public class ExperienceOrb extends Entity { nbt.putShort("Age", (short) this.age); nbt.putShort("Value", (short) this.value); nbt.putInt("Count", this.count); @@ -118,7 +118,7 @@ index 25a45e680f9fdea90f43d59de87a3a500f4ee8c0..0330a62a6a0060d2a96de191db687745 } @Override -@@ -253,6 +316,7 @@ public class ExperienceOrb extends Entity { +@@ -257,6 +320,7 @@ public class ExperienceOrb extends Entity { this.age = nbt.getShort("Age"); this.value = nbt.getShort("Value"); this.count = Math.max(nbt.getInt("Count"), 1); @@ -127,23 +127,23 @@ index 25a45e680f9fdea90f43d59de87a3a500f4ee8c0..0330a62a6a0060d2a96de191db687745 @Override diff --git a/src/main/java/net/minecraft/world/entity/LivingEntity.java b/src/main/java/net/minecraft/world/entity/LivingEntity.java -index 16d611e09cbe56525cde0e0dced93b7f09c12158..b6a7c9ee6e6885e0cc44e2f2ff3ea7bba9cb8f3d 100644 +index 4a6f9a8b7db8d0e1c3cca5a0a4856d38cb38b94e..7a648a8c3f25397e1c883a42648b21a05901513f 100644 --- a/src/main/java/net/minecraft/world/entity/LivingEntity.java +++ b/src/main/java/net/minecraft/world/entity/LivingEntity.java -@@ -1799,7 +1799,7 @@ public abstract class LivingEntity extends Entity implements Attackable { - protected void dropExperience(@Nullable Entity attacker) { +@@ -1835,7 +1835,7 @@ public abstract class LivingEntity extends Entity implements Attackable { + protected void dropExperience(ServerLevel world, @Nullable Entity attacker) { // CraftBukkit start - Update getExpReward() above if the removed if() changes! if (!(this instanceof net.minecraft.world.entity.boss.enderdragon.EnderDragon)) { // CraftBukkit - SPIGOT-2420: Special case ender dragon will drop the xp over time -- ExperienceOrb.award((ServerLevel) this.level(), this.position(), this.expToDrop); -+ ExperienceOrb.award((ServerLevel) this.level(), this.position(), this.expToDrop, this instanceof ServerPlayer ? org.bukkit.entity.ExperienceOrb.SpawnReason.PLAYER_DEATH : org.bukkit.entity.ExperienceOrb.SpawnReason.ENTITY_DEATH, attacker, this); // Paper +- ExperienceOrb.award(world, this.position(), this.expToDrop); ++ ExperienceOrb.award(world, this.position(), this.expToDrop, this instanceof ServerPlayer ? org.bukkit.entity.ExperienceOrb.SpawnReason.PLAYER_DEATH : org.bukkit.entity.ExperienceOrb.SpawnReason.ENTITY_DEATH, attacker, this); // Paper this.expToDrop = 0; } // CraftBukkit end diff --git a/src/main/java/net/minecraft/world/entity/animal/Animal.java b/src/main/java/net/minecraft/world/entity/animal/Animal.java -index 48d0cbe7859c62bbf281a7b43ef9af658667cb7b..b46352b328178df2a48d1c9e895bed3fabd2c292 100644 +index e359bb8e6366b0c695fe3bb14967556a875524f4..775bfac26aaa6db998c2647ec81247b67d0bf784 100644 --- a/src/main/java/net/minecraft/world/entity/animal/Animal.java +++ b/src/main/java/net/minecraft/world/entity/animal/Animal.java -@@ -251,12 +251,14 @@ public abstract class Animal extends AgeableMob { +@@ -277,12 +277,14 @@ public abstract class Animal extends AgeableMob { public void finalizeSpawnChildFromBreeding(ServerLevel worldserver, Animal entityanimal, @Nullable AgeableMob entityageable, int experience) { // CraftBukkit end @@ -162,7 +162,7 @@ index 48d0cbe7859c62bbf281a7b43ef9af658667cb7b..b46352b328178df2a48d1c9e895bed3f this.setAge(6000); entityanimal.setAge(6000); this.resetLove(); -@@ -265,7 +267,7 @@ public abstract class Animal extends AgeableMob { +@@ -291,7 +293,7 @@ public abstract class Animal extends AgeableMob { if (worldserver.getGameRules().getBoolean(GameRules.RULE_DOMOBLOOT)) { // CraftBukkit start - use event experience if (experience > 0) { @@ -172,11 +172,11 @@ index 48d0cbe7859c62bbf281a7b43ef9af658667cb7b..b46352b328178df2a48d1c9e895bed3f // CraftBukkit end } diff --git a/src/main/java/net/minecraft/world/entity/animal/Fox.java b/src/main/java/net/minecraft/world/entity/animal/Fox.java -index de2a25db9465bc4ae3cbf7ff6d3af756df679f4a..a6788da1505f9e119c03b94488f5e006da13e918 100644 +index 258fad440d4d2402a273c761da57130a57202b3c..faffc3a9ed8bc306277cad37bc43af2ef7303493 100644 --- a/src/main/java/net/minecraft/world/entity/animal/Fox.java +++ b/src/main/java/net/minecraft/world/entity/animal/Fox.java -@@ -897,7 +897,7 @@ public class Fox extends Animal implements VariantHolder { - if (this.level.getGameRules().getBoolean(GameRules.RULE_DOMOBLOOT)) { +@@ -882,7 +882,7 @@ public class Fox extends Animal implements VariantHolder { + if (worldserver.getGameRules().getBoolean(GameRules.RULE_DOMOBLOOT)) { // CraftBukkit start - use event experience if (experience > 0) { - this.level.addFreshEntity(new ExperienceOrb(this.level, this.animal.getX(), this.animal.getY(), this.animal.getZ(), experience)); @@ -185,45 +185,45 @@ index de2a25db9465bc4ae3cbf7ff6d3af756df679f4a..a6788da1505f9e119c03b94488f5e006 // CraftBukkit end } diff --git a/src/main/java/net/minecraft/world/entity/animal/Turtle.java b/src/main/java/net/minecraft/world/entity/animal/Turtle.java -index d4659ce017692c6f8cabb56137a231bc566614b0..6f90ee749aed98b97868aa40fc233d164ddc2ef6 100644 +index 81982515b7757febb964627e9c5046d52bf5edf5..a2c5042e99a8f4cd45d502320cf1c06e8af0bd0e 100644 --- a/src/main/java/net/minecraft/world/entity/animal/Turtle.java +++ b/src/main/java/net/minecraft/world/entity/animal/Turtle.java -@@ -455,7 +455,7 @@ public class Turtle extends Animal { +@@ -462,7 +462,7 @@ public class Turtle extends Animal { RandomSource randomsource = this.animal.getRandom(); - if (this.level.getGameRules().getBoolean(GameRules.RULE_DOMOBLOOT)) { + if (getServerLevel((Level) this.level).getGameRules().getBoolean(GameRules.RULE_DOMOBLOOT)) { - this.level.addFreshEntity(new ExperienceOrb(this.level, this.animal.getX(), this.animal.getY(), this.animal.getZ(), randomsource.nextInt(7) + 1)); + this.level.addFreshEntity(new ExperienceOrb(this.level, this.animal.getX(), this.animal.getY(), this.animal.getZ(), randomsource.nextInt(7) + 1, org.bukkit.entity.ExperienceOrb.SpawnReason.BREED, entityplayer)); // Paper; } } diff --git a/src/main/java/net/minecraft/world/entity/boss/enderdragon/EnderDragon.java b/src/main/java/net/minecraft/world/entity/boss/enderdragon/EnderDragon.java -index 25a429a2d1725d562a28b9d07dba630cfe49d32a..f8283405b3c5bd43746a5738be55f600beea40e3 100644 +index 95d69e3ca1a9095dfb340e9be0ec322ab6c5eb5e..0012ab1f56180081d210c8836e2a59d543b950b8 100644 --- a/src/main/java/net/minecraft/world/entity/boss/enderdragon/EnderDragon.java +++ b/src/main/java/net/minecraft/world/entity/boss/enderdragon/EnderDragon.java -@@ -688,7 +688,7 @@ public class EnderDragon extends Mob implements Enemy { +@@ -659,7 +659,7 @@ public class EnderDragon extends Mob implements Enemy { - if (this.level() instanceof ServerLevel) { + if (world instanceof ServerLevel worldserver) { if (this.dragonDeathTime > 150 && this.dragonDeathTime % 5 == 0 && true) { // CraftBukkit - SPIGOT-2420: Already checked for the game rule when calculating the xp -- ExperienceOrb.award((ServerLevel) this.level(), this.position(), Mth.floor((float) short0 * 0.08F)); -+ ExperienceOrb.award((ServerLevel) this.level(), this.position(), Mth.floor((float) short0 * 0.08F), org.bukkit.entity.ExperienceOrb.SpawnReason.ENTITY_DEATH, this.lastHurtByPlayer, this); // Paper +- ExperienceOrb.award(worldserver, this.position(), Mth.floor((float) short0 * 0.08F)); ++ ExperienceOrb.award(worldserver, this.position(), Mth.floor((float) short0 * 0.08F), org.bukkit.entity.ExperienceOrb.SpawnReason.ENTITY_DEATH, this.lastHurtByPlayer, this); // Paper } if (this.dragonDeathTime == 1 && !this.isSilent()) { -@@ -717,7 +717,7 @@ public class EnderDragon extends Mob implements Enemy { - this.move(MoverType.SELF, new Vec3(0.0D, 0.10000000149011612D, 0.0D)); - if (this.dragonDeathTime == 200 && this.level() instanceof ServerLevel) { - if (true) { // CraftBukkit - SPIGOT-2420: Already checked for the game rule when calculating the xp -- ExperienceOrb.award((ServerLevel) this.level(), this.position(), Mth.floor((float) short0 * 0.2F)); -+ ExperienceOrb.award((ServerLevel) this.level(), this.position(), Mth.floor((float) short0 * 0.2F), org.bukkit.entity.ExperienceOrb.SpawnReason.ENTITY_DEATH, this.lastHurtByPlayer, this); // Paper - } +@@ -691,7 +691,7 @@ public class EnderDragon extends Mob implements Enemy { + if (world instanceof ServerLevel) { + ServerLevel worldserver = (ServerLevel) world; // CraftBukkit - decompile error + if (true) { // CraftBukkit - SPIGOT-2420: Already checked for the game rule when calculating the xp +- ExperienceOrb.award(worldserver, this.position(), Mth.floor((float) short0 * 0.2F)); ++ ExperienceOrb.award(worldserver, this.position(), Mth.floor((float) short0 * 0.2F), org.bukkit.entity.ExperienceOrb.SpawnReason.ENTITY_DEATH, this.lastHurtByPlayer, this); // Paper + } - if (this.dragonFight != null) { + if (this.dragonFight != null) { diff --git a/src/main/java/net/minecraft/world/entity/npc/Villager.java b/src/main/java/net/minecraft/world/entity/npc/Villager.java -index 243eb1e54293c763a06febff551c051398d43535..79fdc8284f57a4f11e1954936ad574f7b8df5435 100644 +index 624f06d630b55cdcaa97cb66736b69c7ad45dd83..83bb48891d03534468d61cf7683438b3efb131cf 100644 --- a/src/main/java/net/minecraft/world/entity/npc/Villager.java +++ b/src/main/java/net/minecraft/world/entity/npc/Villager.java -@@ -634,7 +634,7 @@ public class Villager extends AbstractVillager implements ReputationEventHandler +@@ -630,7 +630,7 @@ public class Villager extends AbstractVillager implements ReputationEventHandler } if (offer.shouldRewardExp()) { @@ -233,10 +233,10 @@ index 243eb1e54293c763a06febff551c051398d43535..79fdc8284f57a4f11e1954936ad574f7 } diff --git a/src/main/java/net/minecraft/world/entity/npc/WanderingTrader.java b/src/main/java/net/minecraft/world/entity/npc/WanderingTrader.java -index 0f2e2e42e732e942664d70a72dd9c4e47c7e95b6..e51cb9c96e1bd13c00bf938436f4fc26d80055a1 100644 +index 380a876b3cbd660b34dd504cd20f6031b35a613a..8034588a9a87b907c35e28e220280d463f34554e 100644 --- a/src/main/java/net/minecraft/world/entity/npc/WanderingTrader.java +++ b/src/main/java/net/minecraft/world/entity/npc/WanderingTrader.java -@@ -207,7 +207,7 @@ public class WanderingTrader extends net.minecraft.world.entity.npc.AbstractVill +@@ -208,7 +208,7 @@ public class WanderingTrader extends net.minecraft.world.entity.npc.AbstractVill if (offer.shouldRewardExp()) { int i = 3 + this.random.nextInt(4); @@ -246,10 +246,10 @@ index 0f2e2e42e732e942664d70a72dd9c4e47c7e95b6..e51cb9c96e1bd13c00bf938436f4fc26 } diff --git a/src/main/java/net/minecraft/world/entity/projectile/FishingHook.java b/src/main/java/net/minecraft/world/entity/projectile/FishingHook.java -index ed43ad94ca007a54e3c32d5e17c141048eeb5835..0b4c67b9de6893601f032a8fae103e8a98f2c767 100644 +index ed378bc8135c329cb7423da06eb26fff69ee4954..9d24d4c3802c525546dae92530c9c5b3cf77924e 100644 --- a/src/main/java/net/minecraft/world/entity/projectile/FishingHook.java +++ b/src/main/java/net/minecraft/world/entity/projectile/FishingHook.java -@@ -525,7 +525,7 @@ public class FishingHook extends Projectile { +@@ -530,7 +530,7 @@ public class FishingHook extends Projectile { this.level().addFreshEntity(entityitem); // CraftBukkit start - this.random.nextInt(6) + 1 -> playerFishEvent.getExpToDrop() if (playerFishEvent.getExpToDrop() > 0) { @@ -259,10 +259,10 @@ index ed43ad94ca007a54e3c32d5e17c141048eeb5835..0b4c67b9de6893601f032a8fae103e8a // CraftBukkit end if (itemstack1.is(ItemTags.FISHES)) { diff --git a/src/main/java/net/minecraft/world/entity/projectile/ThrownExperienceBottle.java b/src/main/java/net/minecraft/world/entity/projectile/ThrownExperienceBottle.java -index 9963db38420b91ae817a18ff084311cb45c0edee..70ceef96c6305324aef3b006f6603817ef187e9f 100644 +index 89292bd47e80f7c8bd6a382a44b912a43037b58e..268e46777bdea5b539b0c6833eee08ea8a6c61c2 100644 --- a/src/main/java/net/minecraft/world/entity/projectile/ThrownExperienceBottle.java +++ b/src/main/java/net/minecraft/world/entity/projectile/ThrownExperienceBottle.java -@@ -54,7 +54,7 @@ public class ThrownExperienceBottle extends ThrowableItemProjectile { +@@ -55,7 +55,7 @@ public class ThrownExperienceBottle extends ThrowableItemProjectile { } // CraftBukkit end @@ -272,7 +272,7 @@ index 9963db38420b91ae817a18ff084311cb45c0edee..70ceef96c6305324aef3b006f6603817 } diff --git a/src/main/java/net/minecraft/world/inventory/GrindstoneMenu.java b/src/main/java/net/minecraft/world/inventory/GrindstoneMenu.java -index 1c0a9ca0ccfd89dd26736012ce1c329dffb7f913..637d77d6b07ff9ee5ac1cb0470cbefcba5c7495e 100644 +index 56d8ed71861b0a47692fde4c5acc97aaba8166da..13bc52bc856031c930370828b0381e43920aabd2 100644 --- a/src/main/java/net/minecraft/world/inventory/GrindstoneMenu.java +++ b/src/main/java/net/minecraft/world/inventory/GrindstoneMenu.java @@ -98,7 +98,7 @@ public class GrindstoneMenu extends AbstractContainerMenu { @@ -285,10 +285,10 @@ index 1c0a9ca0ccfd89dd26736012ce1c329dffb7f913..637d77d6b07ff9ee5ac1cb0470cbefcb world.levelEvent(1042, blockposition, 0); diff --git a/src/main/java/net/minecraft/world/level/block/Block.java b/src/main/java/net/minecraft/world/level/block/Block.java -index def3e28edc206e0ba41111e26332db468223fb2e..6d0a90e9c637edff5c5ce1355a3b45f0fb7f4154 100644 +index 27cbec37c6ea278232970ae035795fdecca71735..f1711f774f844024ca7b678385daaace6cda9f46 100644 --- a/src/main/java/net/minecraft/world/level/block/Block.java +++ b/src/main/java/net/minecraft/world/level/block/Block.java -@@ -360,8 +360,13 @@ public class Block extends BlockBehaviour implements ItemLike { +@@ -354,8 +354,13 @@ public class Block extends BlockBehaviour implements ItemLike { } public void popExperience(ServerLevel world, BlockPos pos, int size) { @@ -304,10 +304,10 @@ index def3e28edc206e0ba41111e26332db468223fb2e..6d0a90e9c637edff5c5ce1355a3b45f0 } diff --git a/src/main/java/net/minecraft/world/level/block/entity/AbstractFurnaceBlockEntity.java b/src/main/java/net/minecraft/world/level/block/entity/AbstractFurnaceBlockEntity.java -index c0dea02bb38dffb5003293c2d91e28e998da0a9c..e2a587ca5b732c62c4956e6f39ad795cd1411cc4 100644 +index 7341e14645eac007312889776a29d16fc390c5bf..119ea31f6e15185b6d6171053f790e39c24f6823 100644 --- a/src/main/java/net/minecraft/world/level/block/entity/AbstractFurnaceBlockEntity.java +++ b/src/main/java/net/minecraft/world/level/block/entity/AbstractFurnaceBlockEntity.java -@@ -633,7 +633,7 @@ public abstract class AbstractFurnaceBlockEntity extends BaseContainerBlockEntit +@@ -517,7 +517,7 @@ public abstract class AbstractFurnaceBlockEntity extends BaseContainerBlockEntit j = event.getExpToDrop(); // CraftBukkit end @@ -317,10 +317,10 @@ index c0dea02bb38dffb5003293c2d91e28e998da0a9c..e2a587ca5b732c62c4956e6f39ad795c @Override diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftEntityTypes.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftEntityTypes.java -index 5b7bdf06ae6df1eeccb5a8da143745235f58e07e..9b2b0bbb1755b6be1c23cf56e29a68b0002fd755 100644 +index 3dc11c82e4ccdb57a5e7510bddc5247869075d3e..1c3aacda9242444c0c7bd92cb391e6fff963bb79 100644 --- a/src/main/java/org/bukkit/craftbukkit/entity/CraftEntityTypes.java +++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftEntityTypes.java -@@ -372,7 +372,7 @@ public final class CraftEntityTypes { +@@ -426,7 +426,7 @@ public final class CraftEntityTypes { return item; })); register(new EntityTypeData<>(EntityType.EXPERIENCE_ORB, ExperienceOrb.class, CraftExperienceOrb::new, @@ -328,7 +328,7 @@ index 5b7bdf06ae6df1eeccb5a8da143745235f58e07e..9b2b0bbb1755b6be1c23cf56e29a68b0 + spawnData -> new net.minecraft.world.entity.ExperienceOrb(spawnData.minecraftWorld(), spawnData.x(), spawnData.y(), spawnData.z(), 0, org.bukkit.entity.ExperienceOrb.SpawnReason.CUSTOM, null, null) // Paper )); register(new EntityTypeData<>(EntityType.AREA_EFFECT_CLOUD, AreaEffectCloud.class, CraftAreaEffectCloud::new, spawnData -> new net.minecraft.world.entity.AreaEffectCloud(spawnData.minecraftWorld(), spawnData.x(), spawnData.y(), spawnData.z()))); - register(new EntityTypeData<>(EntityType.EGG, Egg.class, CraftEgg::new, spawnData -> new ThrownEgg(spawnData.minecraftWorld(), spawnData.x(), spawnData.y(), spawnData.z()))); + register(new EntityTypeData<>(EntityType.EGG, Egg.class, CraftEgg::new, spawnData -> new ThrownEgg(spawnData.minecraftWorld(), spawnData.x(), spawnData.y(), spawnData.z(), new net.minecraft.world.item.ItemStack(Items.EGG)))); diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftExperienceOrb.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftExperienceOrb.java index 9231511af4cba747594000364f0b8fceeeab4819..5a7d314ec0562e472f5dc45924a7b24841cff126 100644 --- a/src/main/java/org/bukkit/craftbukkit/entity/CraftExperienceOrb.java diff --git a/patches/server/0126-Cap-Entity-Collisions.patch b/patches/server/0126-Cap-Entity-Collisions.patch index 8feda191fe79..0a1d097e37bb 100644 --- a/patches/server/0126-Cap-Entity-Collisions.patch +++ b/patches/server/0126-Cap-Entity-Collisions.patch @@ -12,10 +12,10 @@ just as it does in Vanilla, but entity pushing logic will be capped. You can set this to 0 to disable collisions. diff --git a/src/main/java/net/minecraft/world/entity/Entity.java b/src/main/java/net/minecraft/world/entity/Entity.java -index bd17157631a74f80e3b5ce50bb1f681abe1dd6a7..46a21ed2408a42aafd16647e17e556730e799cbd 100644 +index 847fd720f11e89d906430c820bc92afe0454761d..b1b26e7c0f66f0697bcfe9eb045d45f31cd9ab06 100644 --- a/src/main/java/net/minecraft/world/entity/Entity.java +++ b/src/main/java/net/minecraft/world/entity/Entity.java -@@ -401,6 +401,7 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess +@@ -404,6 +404,7 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess public long activatedTick = Integer.MIN_VALUE; public void inactiveTick() { } // Spigot end @@ -24,10 +24,10 @@ index bd17157631a74f80e3b5ce50bb1f681abe1dd6a7..46a21ed2408a42aafd16647e17e55673 @javax.annotation.Nullable private org.bukkit.util.Vector origin; diff --git a/src/main/java/net/minecraft/world/entity/LivingEntity.java b/src/main/java/net/minecraft/world/entity/LivingEntity.java -index b6a7c9ee6e6885e0cc44e2f2ff3ea7bba9cb8f3d..f297b2227d0793f44cb5770aa24a474ec7283b15 100644 +index 7a648a8c3f25397e1c883a42648b21a05901513f..bcc0137fec45406e70231b4e2a5bd69dc08ffb5a 100644 --- a/src/main/java/net/minecraft/world/entity/LivingEntity.java +++ b/src/main/java/net/minecraft/world/entity/LivingEntity.java -@@ -3502,10 +3502,12 @@ public abstract class LivingEntity extends Entity implements Attackable { +@@ -3640,10 +3640,12 @@ public abstract class LivingEntity extends Entity implements Attackable { } Iterator iterator1 = list.iterator(); diff --git a/patches/server/0127-Remove-CraftScheduler-Async-Task-Debugger.patch b/patches/server/0127-Remove-CraftScheduler-Async-Task-Debugger.patch index eff18e42d8c7..15ad3dc3e232 100644 --- a/patches/server/0127-Remove-CraftScheduler-Async-Task-Debugger.patch +++ b/patches/server/0127-Remove-CraftScheduler-Async-Task-Debugger.patch @@ -9,28 +9,28 @@ One report of a suspected memory leak with the system. This adds additional overhead to asynchronous task dispatching diff --git a/src/main/java/org/bukkit/craftbukkit/scheduler/CraftScheduler.java b/src/main/java/org/bukkit/craftbukkit/scheduler/CraftScheduler.java -index cf9f04e005940f5dd7baf50435f3703fa7c2d4f0..f1145585eed18be0aa5c795a50589103fdc9cc2f 100644 +index 0c0115ccd8541ac62975f4759b4e2083ac560332..300d31e31a55dbee3489320e21e42f14ac429478 100644 --- a/src/main/java/org/bukkit/craftbukkit/scheduler/CraftScheduler.java +++ b/src/main/java/org/bukkit/craftbukkit/scheduler/CraftScheduler.java -@@ -433,7 +433,7 @@ public class CraftScheduler implements BukkitScheduler { +@@ -432,7 +432,7 @@ public class CraftScheduler implements BukkitScheduler { } this.parsePending(); } else { -- this.debugTail = this.debugTail.setNext(new CraftAsyncDebugger(currentTick + CraftScheduler.RECENT_TICKS, task.getOwner(), task.getTaskClass())); -+ // this.debugTail = this.debugTail.setNext(new CraftAsyncDebugger(currentTick + CraftScheduler.RECENT_TICKS, task.getOwner(), task.getTaskClass())); // Paper +- this.debugTail = this.debugTail.setNext(new CraftAsyncDebugger(this.currentTick + CraftScheduler.RECENT_TICKS, task.getOwner(), task.getTaskClass())); ++ // this.debugTail = this.debugTail.setNext(new CraftAsyncDebugger(this.currentTick + CraftScheduler.RECENT_TICKS, task.getOwner(), task.getTaskClass())); // Paper this.executor.execute(new com.destroystokyo.paper.ServerSchedulerReportingWrapper(task)); // Paper // We don't need to parse pending // (async tasks must live with race-conditions if they attempt to cancel between these few lines of code) -@@ -450,7 +450,7 @@ public class CraftScheduler implements BukkitScheduler { +@@ -447,7 +447,7 @@ public class CraftScheduler implements BukkitScheduler { + } this.pending.addAll(temp); temp.clear(); - MinecraftTimings.bukkitSchedulerFinishTimer.stopTiming(); // Paper -- this.debugHead = this.debugHead.getNextHead(currentTick); -+ //this.debugHead = this.debugHead.getNextHead(currentTick); // Paper +- this.debugHead = this.debugHead.getNextHead(this.currentTick); ++ //this.debugHead = this.debugHead.getNextHead(this.currentTick); // Paper } private void addTask(final CraftTask task) { -@@ -514,10 +514,15 @@ public class CraftScheduler implements BukkitScheduler { +@@ -509,10 +509,15 @@ public class CraftScheduler implements BukkitScheduler { @Override public String toString() { diff --git a/patches/server/0128-Properly-handle-async-calls-to-restart-the-server.patch b/patches/server/0128-Properly-handle-async-calls-to-restart-the-server.patch index f9160abe0034..2c9eb1dd3cb7 100644 --- a/patches/server/0128-Properly-handle-async-calls-to-restart-the-server.patch +++ b/patches/server/0128-Properly-handle-async-calls-to-restart-the-server.patch @@ -30,10 +30,10 @@ will have plugins and worlds saving to the disk has a high potential to result in corruption/dataloss. diff --git a/src/main/java/net/minecraft/server/MinecraftServer.java b/src/main/java/net/minecraft/server/MinecraftServer.java -index 10d69b48158460e5739d1e41a83fcaeec819fac6..5d03477919ce892f6dfb4b2304c03733e10e3721 100644 +index 383808406dd3a6c5b6098db93e638749489b80e3..0adba72139779a20eed8f489f7cfaff9e84e24f4 100644 --- a/src/main/java/net/minecraft/server/MinecraftServer.java +++ b/src/main/java/net/minecraft/server/MinecraftServer.java -@@ -242,6 +242,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop, ServerLevel> levels; private PlayerList playerList; private volatile boolean running; @@ -41,7 +41,7 @@ index 10d69b48158460e5739d1e41a83fcaeec819fac6..5d03477919ce892f6dfb4b2304c03733 private boolean stopped; private int tickCount; private int ticksUntilAutosave; -@@ -937,7 +938,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop 0.5F || this.isInWater()) || this.abilities.flying || this.isSleeping() || this.isInPowderSnow) { diff --git a/patches/server/0130-Add-configuration-option-to-prevent-player-names-fro.patch b/patches/server/0130-Add-configuration-option-to-prevent-player-names-fro.patch index 84c423eeac72..3f1a854c8ecc 100644 --- a/patches/server/0130-Add-configuration-option-to-prevent-player-names-fro.patch +++ b/patches/server/0130-Add-configuration-option-to-prevent-player-names-fro.patch @@ -6,10 +6,10 @@ Subject: [PATCH] Add configuration option to prevent player names from being diff --git a/src/main/java/org/bukkit/craftbukkit/CraftServer.java b/src/main/java/org/bukkit/craftbukkit/CraftServer.java -index 8e951ed126453cf1ffa81e5c8aa6e6ea5db03089..cfc59f1fb52dffb13fb214dd7c9cf71d40354ef0 100644 +index c36bba560d5b17c69c2034cd1fcd711234c13414..3f14b4bf9b986cce9abded06d1cfe72ca3c9bcaf 100644 --- a/src/main/java/org/bukkit/craftbukkit/CraftServer.java +++ b/src/main/java/org/bukkit/craftbukkit/CraftServer.java -@@ -2837,5 +2837,10 @@ public final class CraftServer implements Server { +@@ -2853,5 +2853,10 @@ public final class CraftServer implements Server { commandMap.registerServerAliases(); return true; } diff --git a/patches/server/0131-provide-a-configurable-option-to-disable-creeper-lin.patch b/patches/server/0131-provide-a-configurable-option-to-disable-creeper-lin.patch index 8c1151fd1ba8..c55f4a428dc8 100644 --- a/patches/server/0131-provide-a-configurable-option-to-disable-creeper-lin.patch +++ b/patches/server/0131-provide-a-configurable-option-to-disable-creeper-lin.patch @@ -6,10 +6,10 @@ Subject: [PATCH] provide a configurable option to disable creeper lingering diff --git a/src/main/java/net/minecraft/world/entity/monster/Creeper.java b/src/main/java/net/minecraft/world/entity/monster/Creeper.java -index 976de2547f654bec58f53a4eff54df5176e815aa..39486215ac080220a6018a35a8437092dbc8fe9d 100644 +index 71ea942d663a15a45c97b9b6d5c7519ec5a828d7..31405c3a95e8e8217a6efede1e4599c14583081f 100644 --- a/src/main/java/net/minecraft/world/entity/monster/Creeper.java +++ b/src/main/java/net/minecraft/world/entity/monster/Creeper.java -@@ -287,7 +287,7 @@ public class Creeper extends Monster implements PowerableMob { +@@ -287,7 +287,7 @@ public class Creeper extends Monster { private void spawnLingeringCloud() { Collection collection = this.getActiveEffects(); diff --git a/patches/server/0132-Item-canEntityPickup.patch b/patches/server/0132-Item-canEntityPickup.patch index 58d3e70770c2..d68202e1be15 100644 --- a/patches/server/0132-Item-canEntityPickup.patch +++ b/patches/server/0132-Item-canEntityPickup.patch @@ -5,23 +5,23 @@ Subject: [PATCH] Item#canEntityPickup diff --git a/src/main/java/net/minecraft/world/entity/Mob.java b/src/main/java/net/minecraft/world/entity/Mob.java -index d8beadc96a7f779c39c8e22ffe52d872ac49a0ad..fe00b73b0aeac4e4c1496dcaf81595f57a4a073f 100644 +index 03d289abc30927793aa00f6758ed9db6fb765999..51381c32c8650400331a122a4b6c8d95c9d0632b 100644 --- a/src/main/java/net/minecraft/world/entity/Mob.java +++ b/src/main/java/net/minecraft/world/entity/Mob.java -@@ -673,6 +673,11 @@ public abstract class Mob extends LivingEntity implements EquipmentUser, Leashab - ItemEntity entityitem = (ItemEntity) iterator.next(); +@@ -672,6 +672,11 @@ public abstract class Mob extends LivingEntity implements EquipmentUser, Leashab + ItemEntity entityitem = (ItemEntity) iterator.next(); - if (!entityitem.isRemoved() && !entityitem.getItem().isEmpty() && !entityitem.hasPickUpDelay() && this.wantsToPickUp(entityitem.getItem())) { -+ // Paper start - Item#canEntityPickup -+ if (!entityitem.canMobPickup) { -+ continue; -+ } -+ // Paper end - Item#canEntityPickup - this.pickUpItem(entityitem); + if (!entityitem.isRemoved() && !entityitem.getItem().isEmpty() && !entityitem.hasPickUpDelay() && this.wantsToPickUp(worldserver, entityitem.getItem())) { ++ // Paper start - Item#canEntityPickup ++ if (!entityitem.canMobPickup) { ++ continue; ++ } ++ // Paper end - Item#canEntityPickup + this.pickUpItem(worldserver, entityitem); + } } - } diff --git a/src/main/java/net/minecraft/world/entity/item/ItemEntity.java b/src/main/java/net/minecraft/world/entity/item/ItemEntity.java -index c4cd7cf1567cdf57bfe9f2d80cd4a613efe10dc7..759c246540bbd5cb99c78a722c39b72fbc1951d4 100644 +index c1859488af5a3a5c7a5a70e6789b40ee44e1d01e..95d43e451f3343a9a9488b23695908235b55d6c5 100644 --- a/src/main/java/net/minecraft/world/entity/item/ItemEntity.java +++ b/src/main/java/net/minecraft/world/entity/item/ItemEntity.java @@ -60,6 +60,7 @@ public class ItemEntity extends Entity implements TraceableEntity { diff --git a/patches/server/0133-PlayerPickupItemEvent-setFlyAtPlayer.patch b/patches/server/0133-PlayerPickupItemEvent-setFlyAtPlayer.patch index 282fdd4f1e76..081b08fc3a35 100644 --- a/patches/server/0133-PlayerPickupItemEvent-setFlyAtPlayer.patch +++ b/patches/server/0133-PlayerPickupItemEvent-setFlyAtPlayer.patch @@ -5,10 +5,10 @@ Subject: [PATCH] PlayerPickupItemEvent#setFlyAtPlayer diff --git a/src/main/java/net/minecraft/world/entity/item/ItemEntity.java b/src/main/java/net/minecraft/world/entity/item/ItemEntity.java -index 759c246540bbd5cb99c78a722c39b72fbc1951d4..f51f04758e135294bab5c7d1f891a8d67fea78f5 100644 +index 95d43e451f3343a9a9488b23695908235b55d6c5..92b52ccca3ec7aaab1ffcdc1b85bf6b3e507087f 100644 --- a/src/main/java/net/minecraft/world/entity/item/ItemEntity.java +++ b/src/main/java/net/minecraft/world/entity/item/ItemEntity.java -@@ -436,6 +436,7 @@ public class ItemEntity extends Entity implements TraceableEntity { +@@ -452,6 +452,7 @@ public class ItemEntity extends Entity implements TraceableEntity { // CraftBukkit start - fire PlayerPickupItemEvent int canHold = player.getInventory().canHold(itemstack); int remaining = i - canHold; @@ -16,7 +16,7 @@ index 759c246540bbd5cb99c78a722c39b72fbc1951d4..f51f04758e135294bab5c7d1f891a8d6 if (this.pickupDelay <= 0 && canHold > 0) { itemstack.setCount(canHold); -@@ -443,8 +444,14 @@ public class ItemEntity extends Entity implements TraceableEntity { +@@ -459,8 +460,14 @@ public class ItemEntity extends Entity implements TraceableEntity { PlayerPickupItemEvent playerEvent = new PlayerPickupItemEvent((Player) player.getBukkitEntity(), (org.bukkit.entity.Item) this.getBukkitEntity(), remaining); playerEvent.setCancelled(!playerEvent.getPlayer().getCanPickupItems()); this.level().getCraftServer().getPluginManager().callEvent(playerEvent); @@ -31,7 +31,7 @@ index 759c246540bbd5cb99c78a722c39b72fbc1951d4..f51f04758e135294bab5c7d1f891a8d6 return; } -@@ -474,6 +481,7 @@ public class ItemEntity extends Entity implements TraceableEntity { +@@ -490,6 +497,7 @@ public class ItemEntity extends Entity implements TraceableEntity { // CraftBukkit end if (this.pickupDelay == 0 && (this.target == null || this.target.equals(player.getUUID())) && player.getInventory().add(itemstack)) { diff --git a/patches/server/0134-PlayerAttemptPickupItemEvent.patch b/patches/server/0134-PlayerAttemptPickupItemEvent.patch index 50726dbd1762..11b2bbe0782f 100644 --- a/patches/server/0134-PlayerAttemptPickupItemEvent.patch +++ b/patches/server/0134-PlayerAttemptPickupItemEvent.patch @@ -5,7 +5,7 @@ Subject: [PATCH] PlayerAttemptPickupItemEvent diff --git a/src/main/java/net/minecraft/world/entity/item/ItemEntity.java b/src/main/java/net/minecraft/world/entity/item/ItemEntity.java -index f51f04758e135294bab5c7d1f891a8d67fea78f5..f9dfd6e7b610cfee75524a525ab0e72bed5522da 100644 +index 92b52ccca3ec7aaab1ffcdc1b85bf6b3e507087f..75ebf09777e19645eee296a9edabac39c858ffb9 100644 --- a/src/main/java/net/minecraft/world/entity/item/ItemEntity.java +++ b/src/main/java/net/minecraft/world/entity/item/ItemEntity.java @@ -40,6 +40,7 @@ import org.bukkit.event.entity.EntityPickupItemEvent; @@ -16,7 +16,7 @@ index f51f04758e135294bab5c7d1f891a8d67fea78f5..f9dfd6e7b610cfee75524a525ab0e72b public class ItemEntity extends Entity implements TraceableEntity { -@@ -438,6 +439,22 @@ public class ItemEntity extends Entity implements TraceableEntity { +@@ -454,6 +455,22 @@ public class ItemEntity extends Entity implements TraceableEntity { int remaining = i - canHold; boolean flyAtPlayer = false; // Paper diff --git a/patches/server/0135-Do-not-submit-profile-lookups-to-worldgen-threads.patch b/patches/server/0135-Do-not-submit-profile-lookups-to-worldgen-threads.patch index 415b98a1a9e8..11222ddf7db3 100644 --- a/patches/server/0135-Do-not-submit-profile-lookups-to-worldgen-threads.patch +++ b/patches/server/0135-Do-not-submit-profile-lookups-to-worldgen-threads.patch @@ -10,13 +10,13 @@ out due to a sync load, as the worldgen threads will be stalling on profile lookups. diff --git a/src/main/java/net/minecraft/Util.java b/src/main/java/net/minecraft/Util.java -index 396f368a7e21a7c7b1630b4e20cdbc452c4b0f84..54562fa04d14a937451ea7aa9d80194f2c31b471 100644 +index 2ff5a6517d717bbd4c944572040bd30866347341..8cac2075077b1d9c2b01e09c99780ff9e204abb2 100644 --- a/src/main/java/net/minecraft/Util.java +++ b/src/main/java/net/minecraft/Util.java -@@ -92,6 +92,22 @@ public class Util { - private static final ExecutorService BACKGROUND_EXECUTOR = makeExecutor("Main"); - private static final ExecutorService IO_POOL = makeIoExecutor("IO-Worker-", false); - private static final ExecutorService DOWNLOAD_POOL = makeIoExecutor("Download-", true); +@@ -95,6 +95,22 @@ public class Util { + private static final TracingExecutor BACKGROUND_EXECUTOR = makeExecutor("Main"); + private static final TracingExecutor IO_POOL = makeIoExecutor("IO-Worker-", false); + private static final TracingExecutor DOWNLOAD_POOL = makeIoExecutor("Download-", true); + // Paper start - don't submit BLOCKING PROFILE LOOKUPS to the world gen thread + public static final ExecutorService PROFILE_EXECUTOR = Executors.newFixedThreadPool(2, new java.util.concurrent.ThreadFactory() { + @@ -37,33 +37,33 @@ index 396f368a7e21a7c7b1630b4e20cdbc452c4b0f84..54562fa04d14a937451ea7aa9d80194f public static final int LINEAR_LOOKUP_THRESHOLD = 8; private static final Set ALLOWED_UNTRUSTED_LINK_PROTOCOLS = Set.of("http", "https"); diff --git a/src/main/java/net/minecraft/server/players/GameProfileCache.java b/src/main/java/net/minecraft/server/players/GameProfileCache.java -index aeb0c7ce9b6f93dadd407dbdefba053568f2e2fe..416b26c2ab62b29d640169166980e398d5824b14 100644 +index c89f4a885982f06823886c81fd386b8de029a3dd..416b26c2ab62b29d640169166980e398d5824b14 100644 --- a/src/main/java/net/minecraft/server/players/GameProfileCache.java +++ b/src/main/java/net/minecraft/server/players/GameProfileCache.java @@ -169,7 +169,7 @@ public class GameProfileCache { } else { CompletableFuture> completablefuture1 = CompletableFuture.supplyAsync(() -> { return this.get(username); -- }, Util.backgroundExecutor()).whenCompleteAsync((optional, throwable) -> { +- }, Util.backgroundExecutor().forName("getProfile")).whenCompleteAsync((optional, throwable) -> { + }, Util.PROFILE_EXECUTOR).whenCompleteAsync((optional, throwable) -> { // Paper - don't submit BLOCKING PROFILE LOOKUPS to the world gen thread this.requests.remove(username); }, this.executor); diff --git a/src/main/java/net/minecraft/world/level/block/entity/SkullBlockEntity.java b/src/main/java/net/minecraft/world/level/block/entity/SkullBlockEntity.java -index f720c5ef71edd65de76bcac9632173184576ea9e..c278caa030ceccec8e2721068848a34be513de88 100644 +index 59187889db52ccb132460b0e937d0bd5cbeb77f4..4b7176779c455a876419a497a8178163a68553fc 100644 --- a/src/main/java/net/minecraft/world/level/block/entity/SkullBlockEntity.java +++ b/src/main/java/net/minecraft/world/level/block/entity/SkullBlockEntity.java @@ -105,7 +105,7 @@ public class SkullBlockEntity extends BlockEntity { ProfileResult profileResult = apiServices.sessionService().fetchProfile(uuid, true); return Optional.ofNullable(profileResult).map(ProfileResult::profile); } -- }, Util.backgroundExecutor()); +- }, Util.backgroundExecutor().forName("fetchProfile")); + }, Util.PROFILE_EXECUTOR); // Paper - don't submit BLOCKING PROFILE LOOKUPS to the world gen thread } public static void clear() { diff --git a/src/main/java/org/bukkit/craftbukkit/profile/CraftPlayerProfile.java b/src/main/java/org/bukkit/craftbukkit/profile/CraftPlayerProfile.java -index 56a71862eac5c45fde2e1d9e96e763d9dbf24cc4..0c250e5b0bd3232d829b0c028f237e6bf5fd334d 100644 +index 26a7c2d37e6a8284ab444a9edad967bdcad1e5a3..210fdad5f2041368fc359b275f1cbf05b70b6989 100644 --- a/src/main/java/org/bukkit/craftbukkit/profile/CraftPlayerProfile.java +++ b/src/main/java/org/bukkit/craftbukkit/profile/CraftPlayerProfile.java @@ -161,7 +161,7 @@ public final class CraftPlayerProfile implements PlayerProfile { diff --git a/patches/server/0136-Basic-PlayerProfile-API.patch b/patches/server/0136-Basic-PlayerProfile-API.patch index d0564f4df66c..44c9e746de79 100644 --- a/patches/server/0136-Basic-PlayerProfile-API.patch +++ b/patches/server/0136-Basic-PlayerProfile-API.patch @@ -591,7 +591,7 @@ index 0000000000000000000000000000000000000000..332700f84c5587e47a4d2056bfbb5413 + @NotNull ResolvableProfile buildResolvableProfile(); +} diff --git a/src/main/java/net/minecraft/server/Main.java b/src/main/java/net/minecraft/server/Main.java -index 6afede80c10503a261d0f735c351d943597be9ff..993296f9c2457809bd6b844c309895f417eb42a5 100644 +index 7399358f18dc7869fbfe414186cf18414c1eaafc..a9dadd106ff0ac0f788c16048a73dfc3320b4944 100644 --- a/src/main/java/net/minecraft/server/Main.java +++ b/src/main/java/net/minecraft/server/Main.java @@ -168,7 +168,7 @@ public class Main { @@ -626,10 +626,10 @@ index 416b26c2ab62b29d640169166980e398d5824b14..774d81c702edb76a2f6184d4dc53687d String s1 = name.toLowerCase(Locale.ROOT); GameProfileCache.GameProfileInfo usercache_usercacheentry = (GameProfileCache.GameProfileInfo) this.profilesByName.get(s1); diff --git a/src/main/java/org/bukkit/craftbukkit/CraftServer.java b/src/main/java/org/bukkit/craftbukkit/CraftServer.java -index cfc59f1fb52dffb13fb214dd7c9cf71d40354ef0..f2b2a352cdc0301601ffd4b4fb7a99304ba526e5 100644 +index 3f14b4bf9b986cce9abded06d1cfe72ca3c9bcaf..cc71aac4de75eafd7b7128b06b46d0439b77c486 100644 --- a/src/main/java/org/bukkit/craftbukkit/CraftServer.java +++ b/src/main/java/org/bukkit/craftbukkit/CraftServer.java -@@ -262,6 +262,9 @@ import org.yaml.snakeyaml.error.MarkedYAMLException; +@@ -265,6 +265,9 @@ import org.yaml.snakeyaml.error.MarkedYAMLException; import net.md_5.bungee.api.chat.BaseComponent; // Spigot @@ -639,7 +639,7 @@ index cfc59f1fb52dffb13fb214dd7c9cf71d40354ef0..f2b2a352cdc0301601ffd4b4fb7a9930 public final class CraftServer implements Server { private final String serverName = io.papermc.paper.ServerBuildInfo.buildInfo().brandName(); // Paper private final String serverVersion; -@@ -307,6 +310,7 @@ public final class CraftServer implements Server { +@@ -310,6 +313,7 @@ public final class CraftServer implements Server { static { ConfigurationSerialization.registerClass(CraftOfflinePlayer.class); ConfigurationSerialization.registerClass(CraftPlayerProfile.class); @@ -647,7 +647,7 @@ index cfc59f1fb52dffb13fb214dd7c9cf71d40354ef0..f2b2a352cdc0301601ffd4b4fb7a9930 CraftItemFactory.instance(); CraftEntityFactory.instance(); } -@@ -2842,5 +2846,39 @@ public final class CraftServer implements Server { +@@ -2858,5 +2862,39 @@ public final class CraftServer implements Server { public boolean suggestPlayerNamesWhenNullTabCompletions() { return io.papermc.paper.configuration.GlobalConfiguration.get().commands.suggestPlayerNamesWhenNullTabCompletions; } diff --git a/patches/server/0137-Add-UnknownCommandEvent.patch b/patches/server/0137-Add-UnknownCommandEvent.patch index 25a7aa2070d6..652711b60656 100644 --- a/patches/server/0137-Add-UnknownCommandEvent.patch +++ b/patches/server/0137-Add-UnknownCommandEvent.patch @@ -6,10 +6,10 @@ Subject: [PATCH] Add UnknownCommandEvent Co-authored-by: Jake Potrebic diff --git a/src/main/java/net/minecraft/commands/CommandSourceStack.java b/src/main/java/net/minecraft/commands/CommandSourceStack.java -index ec34e402104d7a696ea95e0b11ee70189b678ab9..d9fc3c25bef251df6a53ee47ec224b07240a931c 100644 +index 2fbd7f7c976fb55b7238f1e512afad79e52a5b2c..4d5f1dd1c3bd742b1bc5e3914101a699041caa7e 100644 --- a/src/main/java/net/minecraft/commands/CommandSourceStack.java +++ b/src/main/java/net/minecraft/commands/CommandSourceStack.java -@@ -330,8 +330,13 @@ public class CommandSourceStack implements ExecutionCommandSource { +@@ -334,7 +334,7 @@ public class Commands { + Profiler.get().push(() -> { return "/" + s; }); - ContextChain contextchain = Commands.finishParsing(parseresults, s, commandlistenerwrapper, label); // CraftBukkit @@ -37,7 +37,7 @@ index f94c0106b44d614483184e372c01c1504cb886b0..72756ef14b8ec8afd80313b9f6aaf767 try { if (contextchain != null) { -@@ -363,14 +363,18 @@ public class Commands { +@@ -368,14 +368,18 @@ public class Commands { } @Nullable @@ -58,7 +58,7 @@ index f94c0106b44d614483184e372c01c1504cb886b0..72756ef14b8ec8afd80313b9f6aaf767 if (commandsyntaxexception.getInput() != null && commandsyntaxexception.getCursor() >= 0) { int i = Math.min(commandsyntaxexception.getInput().length(), commandsyntaxexception.getCursor()); MutableComponent ichatmutablecomponent = Component.empty().withStyle(ChatFormatting.GRAY).withStyle((chatmodifier) -> { -@@ -389,7 +393,17 @@ public class Commands { +@@ -394,7 +398,17 @@ public class Commands { } ichatmutablecomponent.append((Component) Component.translatable("command.context.here").withStyle(ChatFormatting.RED, ChatFormatting.ITALIC)); @@ -78,10 +78,10 @@ index f94c0106b44d614483184e372c01c1504cb886b0..72756ef14b8ec8afd80313b9f6aaf767 return null; diff --git a/src/main/java/org/bukkit/craftbukkit/CraftServer.java b/src/main/java/org/bukkit/craftbukkit/CraftServer.java -index f2b2a352cdc0301601ffd4b4fb7a99304ba526e5..31f87ce0865dd7560cd9ba634855a1defdc80df2 100644 +index cc71aac4de75eafd7b7128b06b46d0439b77c486..2d1f92f75e83aacd39440d2619befdf6cd545af4 100644 --- a/src/main/java/org/bukkit/craftbukkit/CraftServer.java +++ b/src/main/java/org/bukkit/craftbukkit/CraftServer.java -@@ -948,7 +948,13 @@ public final class CraftServer implements Server { +@@ -950,7 +950,13 @@ public final class CraftServer implements Server { // Spigot start if (!org.spigotmc.SpigotConfig.unknownCommandMessage.isEmpty()) { diff --git a/patches/server/0138-Shoulder-Entities-Release-API.patch b/patches/server/0138-Shoulder-Entities-Release-API.patch index c169c6edc622..3c6d15f32361 100644 --- a/patches/server/0138-Shoulder-Entities-Release-API.patch +++ b/patches/server/0138-Shoulder-Entities-Release-API.patch @@ -5,14 +5,14 @@ Subject: [PATCH] Shoulder Entities Release API diff --git a/src/main/java/net/minecraft/world/entity/player/Player.java b/src/main/java/net/minecraft/world/entity/player/Player.java -index 7995a4626b3ed68234d468418408b9a8e179b6f6..362d75522aaa721e9fb1d12a149d0efec1ac17b1 100644 +index fb6b7be09fffbb9ffefba3f0c0c97b0f90ff6a94..357c71409af5f67a0a6aaa0cb08fd93a4a4f99de 100644 --- a/src/main/java/net/minecraft/world/entity/player/Player.java +++ b/src/main/java/net/minecraft/world/entity/player/Player.java -@@ -1995,20 +1995,45 @@ public abstract class Player extends LivingEntity { +@@ -1948,7 +1948,31 @@ public abstract class Player extends LivingEntity { } -+ // Paper start ++ // Paper start - release entity api + public Entity releaseLeftShoulderEntity() { + Entity entity = this.respawnEntityOnShoulder0(this.getShoulderEntityLeft()); + if (entity != null) { @@ -28,42 +28,39 @@ index 7995a4626b3ed68234d468418408b9a8e179b6f6..362d75522aaa721e9fb1d12a149d0efe + } + return entity; + } -+ // Paper - maintain old signature ++ // Paper end - release entity api + private boolean respawnEntityOnShoulder(CompoundTag nbttagcompound) { // CraftBukkit void->boolean -- if (!this.level().isClientSide && !nbttagcompound.isEmpty()) { ++ // Paper start - release entity api - return entity - overload + return this.respawnEntityOnShoulder0(nbttagcompound) != null; + } + -+ // Paper - return entity + private Entity respawnEntityOnShoulder0(CompoundTag nbttagcompound) { // CraftBukkit void->boolean -+ if (!this.level().isClientSide && nbttagcompound != null && !nbttagcompound.isEmpty()) { - return EntityType.create(nbttagcompound, this.level()).map((entity) -> { // CraftBukkit ++ // Paper end - release entity api - return entity - overload + if (!this.level().isClientSide && !nbttagcompound.isEmpty()) { + return EntityType.create(nbttagcompound, this.level(), EntitySpawnReason.LOAD).map((entity) -> { // CraftBukkit if (entity instanceof TamableAnimal) { - ((TamableAnimal) entity).setOwnerUUID(this.uuid); +@@ -1956,11 +1980,11 @@ public abstract class Player extends LivingEntity { } entity.setPos(this.getX(), this.getY() + 0.699999988079071D, this.getZ()); - return ((ServerLevel) this.level()).addWithUUID(entity, CreatureSpawnEvent.SpawnReason.SHOULDER_ENTITY); // CraftBukkit - }).orElse(true); // CraftBukkit -+ boolean addedToWorld = ((ServerLevel) this.level()).addWithUUID(entity, CreatureSpawnEvent.SpawnReason.SHOULDER_ENTITY); // CraftBukkit -+ return addedToWorld ? entity : null; -+ }).orElse(null); // CraftBukkit // Paper - true -> null ++ return ((ServerLevel) this.level()).addWithUUID(entity, CreatureSpawnEvent.SpawnReason.SHOULDER_ENTITY) ? entity : null; // CraftBukkit // Paper start - release entity api - return entity ++ }).orElse(null); // CraftBukkit // Paper end - release entity api - return entity } - return true; // CraftBukkit + return null; // Paper - return null } -+ // Paper end @Override - public abstract boolean isSpectator(); diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftHumanEntity.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftHumanEntity.java -index a93895a6e656c25e819354ecf5c73ff4bae83675..f0efea03165039525a98dc30c34d876972d9fe71 100644 +index bfa44c4e37618df3f745bccc6e775ce16c19490d..768a6e3f5d75d37ae114ffcf2b090fe9de769381 100644 --- a/src/main/java/org/bukkit/craftbukkit/entity/CraftHumanEntity.java +++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftHumanEntity.java -@@ -520,6 +520,32 @@ public class CraftHumanEntity extends CraftLivingEntity implements HumanEntity { - this.getHandle().getCooldowns().addCooldown(CraftItemType.bukkitToMinecraft(material), ticks); +@@ -544,6 +544,32 @@ public class CraftHumanEntity extends CraftLivingEntity implements HumanEntity { + this.getHandle().getCooldowns().addCooldown(CraftItemStack.asNMSCopy(item), ticks); } + // Paper start diff --git a/patches/server/0140-Block-player-logins-during-server-shutdown.patch b/patches/server/0140-Block-player-logins-during-server-shutdown.patch index 5609f8375723..d3fdf86a5866 100644 --- a/patches/server/0140-Block-player-logins-during-server-shutdown.patch +++ b/patches/server/0140-Block-player-logins-during-server-shutdown.patch @@ -5,7 +5,7 @@ Subject: [PATCH] Block player logins during server shutdown diff --git a/src/main/java/net/minecraft/server/network/ServerLoginPacketListenerImpl.java b/src/main/java/net/minecraft/server/network/ServerLoginPacketListenerImpl.java -index e65c582635317b9f8a1af4e6f6a5fb916f73cc35..0c2137dda44f2341bffe0928dc77be8ca6371bc9 100644 +index 80ac468321a9ccb3486e97b3448dd3fccd8e766e..ca4c054f17a79bfe0f4f49783a13e30eb0f3e2ff 100644 --- a/src/main/java/net/minecraft/server/network/ServerLoginPacketListenerImpl.java +++ b/src/main/java/net/minecraft/server/network/ServerLoginPacketListenerImpl.java @@ -102,6 +102,12 @@ public class ServerLoginPacketListenerImpl implements ServerLoginPacketListener, diff --git a/patches/server/0141-Entity-fromMobSpawner.patch b/patches/server/0141-Entity-fromMobSpawner.patch index 8c8147c6ec17..4d78b98a0b6d 100644 --- a/patches/server/0141-Entity-fromMobSpawner.patch +++ b/patches/server/0141-Entity-fromMobSpawner.patch @@ -5,10 +5,10 @@ Subject: [PATCH] Entity#fromMobSpawner() diff --git a/src/main/java/net/minecraft/world/entity/Entity.java b/src/main/java/net/minecraft/world/entity/Entity.java -index 85d8c4865b65792b99f812b579d7c23823d76e52..0e800e3aeeec84f5f7ed0a391c9b66ae5689bd40 100644 +index b1b26e7c0f66f0697bcfe9eb045d45f31cd9ab06..99794276626d26849150d4956abbbc2296543907 100644 --- a/src/main/java/net/minecraft/world/entity/Entity.java +++ b/src/main/java/net/minecraft/world/entity/Entity.java -@@ -402,6 +402,7 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess +@@ -405,6 +405,7 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess public void inactiveTick() { } // Spigot end protected int numCollisions = 0; // Paper - Cap entity collisions @@ -16,7 +16,7 @@ index 85d8c4865b65792b99f812b579d7c23823d76e52..0e800e3aeeec84f5f7ed0a391c9b66ae // Paper start - Entity origin API @javax.annotation.Nullable private org.bukkit.util.Vector origin; -@@ -2259,6 +2260,10 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess +@@ -2375,6 +2376,10 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess } nbttagcompound.put("Paper.Origin", this.newDoubleList(origin.getX(), origin.getY(), origin.getZ())); } @@ -27,7 +27,7 @@ index 85d8c4865b65792b99f812b579d7c23823d76e52..0e800e3aeeec84f5f7ed0a391c9b66ae // Paper end return nbttagcompound; } catch (Throwable throwable) { -@@ -2399,6 +2404,8 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess +@@ -2516,6 +2521,8 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess this.originWorld = originWorld; origin = new org.bukkit.util.Vector(originTag.getDouble(0), originTag.getDouble(1), originTag.getDouble(2)); } @@ -37,7 +37,7 @@ index 85d8c4865b65792b99f812b579d7c23823d76e52..0e800e3aeeec84f5f7ed0a391c9b66ae } catch (Throwable throwable) { diff --git a/src/main/java/net/minecraft/world/level/BaseSpawner.java b/src/main/java/net/minecraft/world/level/BaseSpawner.java -index aa54237205989f619ac6a3faa2e4285427b9e31d..43d399e1a0ba2fb0541f851a28032fa60fc01b33 100644 +index 7b918001d36a8f14ed0d3ee4d6783588f48eb78f..e424c23c22809a307175b5dcc7f7b0084464493f 100644 --- a/src/main/java/net/minecraft/world/level/BaseSpawner.java +++ b/src/main/java/net/minecraft/world/level/BaseSpawner.java @@ -176,6 +176,7 @@ public abstract class BaseSpawner { @@ -49,7 +49,7 @@ index aa54237205989f619ac6a3faa2e4285427b9e31d..43d399e1a0ba2fb0541f851a28032fa6 if (org.bukkit.craftbukkit.event.CraftEventFactory.callSpawnerSpawnEvent(entity, pos).isCancelled()) { continue; diff --git a/src/main/java/net/minecraft/world/level/block/entity/trialspawner/TrialSpawner.java b/src/main/java/net/minecraft/world/level/block/entity/trialspawner/TrialSpawner.java -index 55d63de57ec740fc4ea5faa3db9a092b8e4fed95..c3f6522a7dc0777c5b3fa2bac63a8901f96d9f38 100644 +index 28c8b8cd2ad0a368f7856a407d91742978490728..f0163b7fa8b27823db9df5b8d2b6adcb63023164 100644 --- a/src/main/java/net/minecraft/world/level/block/entity/trialspawner/TrialSpawner.java +++ b/src/main/java/net/minecraft/world/level/block/entity/trialspawner/TrialSpawner.java @@ -230,6 +230,7 @@ public final class TrialSpawner { @@ -61,10 +61,10 @@ index 55d63de57ec740fc4ea5faa3db9a092b8e4fed95..c3f6522a7dc0777c5b3fa2bac63a8901 if (org.bukkit.craftbukkit.event.CraftEventFactory.callTrialSpawnerSpawnEvent(entity, pos).isCancelled()) { return Optional.empty(); diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java -index df6da730134da754d0ff23bd1b57c82486b9ab73..69b5946625a53a1351ffc4bdf61c6874949bbeae 100644 +index 0bec53dc1be4aa997be9f03bc3cde30d22cc8160..0480fbeffd19011d3cd63021225f376c464b480c 100644 --- a/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java +++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java -@@ -1010,4 +1010,11 @@ public abstract class CraftEntity implements org.bukkit.entity.Entity { +@@ -1011,4 +1011,11 @@ public abstract class CraftEntity implements org.bukkit.entity.Entity { return originVector.toLocation(world); } // Paper end - entity origin API diff --git a/patches/server/0143-ensureServerConversions-API.patch b/patches/server/0143-ensureServerConversions-API.patch index ba8fe15e57ae..8c69d762bca7 100644 --- a/patches/server/0143-ensureServerConversions-API.patch +++ b/patches/server/0143-ensureServerConversions-API.patch @@ -7,10 +7,10 @@ This will take a Bukkit ItemStack and run it through any conversions a server pr to ensure it meets latest minecraft expectations. diff --git a/src/main/java/org/bukkit/craftbukkit/inventory/CraftItemFactory.java b/src/main/java/org/bukkit/craftbukkit/inventory/CraftItemFactory.java -index e2daeeba1bacbb5c5ca2aa922fa67b02cd050755..07df02997d95275cbf26ab9b76eb587da7117d37 100644 +index 5d7e6b61102a6244c5426917c4ff280fa9e12cf1..91010267b8c4df582415a8f7cd7386723b556cc0 100644 --- a/src/main/java/org/bukkit/craftbukkit/inventory/CraftItemFactory.java +++ b/src/main/java/org/bukkit/craftbukkit/inventory/CraftItemFactory.java -@@ -223,4 +223,12 @@ public final class CraftItemFactory implements ItemFactory { +@@ -226,4 +226,12 @@ public final class CraftItemFactory implements ItemFactory { return io.papermc.paper.adventure.PaperAdventure.asAdventure(CraftItemStack.asNMSCopy(itemStack).getDisplayName()); } // Paper end - Adventure diff --git a/patches/server/0144-Implement-getI18NDisplayName.patch b/patches/server/0144-Implement-getI18NDisplayName.patch index 6dfc69b23c28..32d65af2dc9b 100644 --- a/patches/server/0144-Implement-getI18NDisplayName.patch +++ b/patches/server/0144-Implement-getI18NDisplayName.patch @@ -8,10 +8,10 @@ Currently the server only supports the English language. To override this, You must replace the language file embedded in the server jar. diff --git a/src/main/java/org/bukkit/craftbukkit/inventory/CraftItemFactory.java b/src/main/java/org/bukkit/craftbukkit/inventory/CraftItemFactory.java -index 07df02997d95275cbf26ab9b76eb587da7117d37..fd0df053cd5e8802991e665185e7f90f8001d80c 100644 +index 91010267b8c4df582415a8f7cd7386723b556cc0..b407968b111ff9cb9f428319d211e5d9c4c99138 100644 --- a/src/main/java/org/bukkit/craftbukkit/inventory/CraftItemFactory.java +++ b/src/main/java/org/bukkit/craftbukkit/inventory/CraftItemFactory.java -@@ -231,4 +231,19 @@ public final class CraftItemFactory implements ItemFactory { +@@ -234,4 +234,19 @@ public final class CraftItemFactory implements ItemFactory { return CraftItemStack.asCraftMirror(CraftItemStack.asNMSCopy(item)); } // Paper end - ensure server conversions API @@ -27,7 +27,7 @@ index 07df02997d95275cbf26ab9b76eb587da7117d37..fd0df053cd5e8802991e665185e7f90f + nms = CraftItemStack.asNMSCopy(item); + } + -+ return nms != null ? net.minecraft.locale.Language.getInstance().getOrDefault(nms.getItem().getDescriptionId(nms)) : null; ++ return nms != null ? nms.getItem().getName(nms).getString() : null; + } + // Paper end - add getI18NDisplayName } diff --git a/patches/server/0145-ProfileWhitelistVerifyEvent.patch b/patches/server/0145-ProfileWhitelistVerifyEvent.patch index ab82c08f0748..988ef567abf9 100644 --- a/patches/server/0145-ProfileWhitelistVerifyEvent.patch +++ b/patches/server/0145-ProfileWhitelistVerifyEvent.patch @@ -5,10 +5,10 @@ Subject: [PATCH] ProfileWhitelistVerifyEvent diff --git a/src/main/java/net/minecraft/server/players/PlayerList.java b/src/main/java/net/minecraft/server/players/PlayerList.java -index d5f17d7e3b56ca87ec9070b69265ce098de61f69..c08ffdbc9afb2fe7abbf5567dc1fb1e2bcb01b96 100644 +index 1f3dc7f2376854be577cb6742aa5d87d898b9882..5cb84271abdabc0f2d44ad9425828431f8818a47 100644 --- a/src/main/java/net/minecraft/server/players/PlayerList.java +++ b/src/main/java/net/minecraft/server/players/PlayerList.java -@@ -624,9 +624,9 @@ public abstract class PlayerList { +@@ -590,9 +590,9 @@ public abstract class PlayerList { // return chatmessage; event.disallow(PlayerLoginEvent.Result.KICK_BANNED, io.papermc.paper.adventure.PaperAdventure.asAdventure(ichatmutablecomponent)); // Paper - Adventure @@ -21,7 +21,7 @@ index d5f17d7e3b56ca87ec9070b69265ce098de61f69..c08ffdbc9afb2fe7abbf5567dc1fb1e2 } else if (this.getIpBans().isBanned(socketaddress) && !this.getIpBans().get(socketaddress).hasExpired()) { IpBanListEntry ipbanentry = this.ipBans.get(socketaddress); -@@ -993,7 +993,25 @@ public abstract class PlayerList { +@@ -961,7 +961,25 @@ public abstract class PlayerList { } public boolean isWhiteListed(GameProfile profile) { diff --git a/patches/server/0146-Fix-this-stupid-bullshit.patch b/patches/server/0146-Fix-this-stupid-bullshit.patch index 6b9e17c4d047..fbc9a69c3579 100644 --- a/patches/server/0146-Fix-this-stupid-bullshit.patch +++ b/patches/server/0146-Fix-this-stupid-bullshit.patch @@ -9,10 +9,10 @@ modified in order to prevent merge conflicts when Spigot changes/disables the wa and to provide some level of hint without being disruptive. diff --git a/src/main/java/net/minecraft/server/Bootstrap.java b/src/main/java/net/minecraft/server/Bootstrap.java -index 8f1992188f7fd9e735569e099b36a7eafed47aae..061c89b985dafc79c808dd5f0e296b9fbac2fdfc 100644 +index 9abd1dea58ffc612114d5fd8e8944d4aa8c68236..4840893082cbcd9b00f79149df1a7805af279dcb 100644 --- a/src/main/java/net/minecraft/server/Bootstrap.java +++ b/src/main/java/net/minecraft/server/Bootstrap.java -@@ -43,7 +43,7 @@ public class Bootstrap { +@@ -44,7 +44,7 @@ public class Bootstrap { public static void bootStrap() { if (!Bootstrap.isBootstrapped) { // CraftBukkit start @@ -21,7 +21,7 @@ index 8f1992188f7fd9e735569e099b36a7eafed47aae..061c89b985dafc79c808dd5f0e296b9f switch (name) { case "DispenserRegistry": break; -@@ -57,7 +57,7 @@ public class Bootstrap { +@@ -58,7 +58,7 @@ public class Bootstrap { System.err.println("*** WARNING: This server jar is unsupported, use at your own risk. ***"); System.err.println("**********************************************************************"); break; @@ -31,12 +31,12 @@ index 8f1992188f7fd9e735569e099b36a7eafed47aae..061c89b985dafc79c808dd5f0e296b9f Bootstrap.isBootstrapped = true; Instant instant = Instant.now(); diff --git a/src/main/java/org/bukkit/craftbukkit/Main.java b/src/main/java/org/bukkit/craftbukkit/Main.java -index a41674e0965a13acd2ec2fcc431a2baa26fc4361..bcc9d3b1503b1be1841c9ab40e879a1cbb0549f2 100644 +index fc9e56427dc04a6ef7286bf027fef6b7ff0bac0c..1efb42f7ad357f9b4185dea79106ccd38c9f5325 100644 --- a/src/main/java/org/bukkit/craftbukkit/Main.java +++ b/src/main/java/org/bukkit/craftbukkit/Main.java @@ -249,10 +249,12 @@ public class Main { Calendar deadline = Calendar.getInstance(); - deadline.add(Calendar.DAY_OF_YEAR, -28); + deadline.add(Calendar.DAY_OF_YEAR, -3); if (buildDate.before(deadline.getTime())) { - System.err.println("*** Error, this build is outdated ***"); + // Paper start - This is some stupid bullshit diff --git a/patches/server/0147-LivingEntity-setKiller.patch b/patches/server/0147-LivingEntity-setKiller.patch index a75578ad0685..1a07e6b288a1 100644 --- a/patches/server/0147-LivingEntity-setKiller.patch +++ b/patches/server/0147-LivingEntity-setKiller.patch @@ -7,10 +7,10 @@ Subject: [PATCH] LivingEntity#setKiller public net.minecraft.world.entity.LivingEntity lastHurtByPlayerTime diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftLivingEntity.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftLivingEntity.java -index 5ba4105356e4a4808293e86c679d08a3c4cdd245..7a01569f62b3b27a9eb6def0e2ec72e1d392258d 100644 +index 6bb32e4eab357c5f67a3daafa2de035b0d125635..452fe788152f7c38ab4b6c627e3ba37da3d9b9d9 100644 --- a/src/main/java/org/bukkit/craftbukkit/entity/CraftLivingEntity.java +++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftLivingEntity.java -@@ -407,6 +407,16 @@ public class CraftLivingEntity extends CraftEntity implements LivingEntity { +@@ -417,6 +417,16 @@ public class CraftLivingEntity extends CraftEntity implements LivingEntity { return this.getHandle().lastHurtByPlayer == null ? null : (Player) this.getHandle().lastHurtByPlayer.getBukkitEntity(); } diff --git a/patches/server/0148-Ocelot-despawns-should-honor-nametags-and-leash.patch b/patches/server/0148-Ocelot-despawns-should-honor-nametags-and-leash.patch index a61d1533c3fd..ac704548ec28 100644 --- a/patches/server/0148-Ocelot-despawns-should-honor-nametags-and-leash.patch +++ b/patches/server/0148-Ocelot-despawns-should-honor-nametags-and-leash.patch @@ -5,10 +5,10 @@ Subject: [PATCH] Ocelot despawns should honor nametags and leash diff --git a/src/main/java/net/minecraft/world/entity/animal/Ocelot.java b/src/main/java/net/minecraft/world/entity/animal/Ocelot.java -index 695f057d05d6651860d8d15be5a2b48a18a35d22..97f4cc522706ec5914672aa4fdfbc35edc94aeb6 100644 +index d97bc15010a488839e0c063a9ae4a725d4d2b9e9..0554ee499c452db6c1e6852f5022b1f197adb024 100644 --- a/src/main/java/net/minecraft/world/entity/animal/Ocelot.java +++ b/src/main/java/net/minecraft/world/entity/animal/Ocelot.java -@@ -133,7 +133,7 @@ public class Ocelot extends Animal { +@@ -132,7 +132,7 @@ public class Ocelot extends Animal { @Override public boolean removeWhenFarAway(double distanceSquared) { diff --git a/patches/server/0149-Reset-spawner-timer-when-spawner-event-is-cancelled.patch b/patches/server/0149-Reset-spawner-timer-when-spawner-event-is-cancelled.patch index 129e8461dc46..e27bff4361a8 100644 --- a/patches/server/0149-Reset-spawner-timer-when-spawner-event-is-cancelled.patch +++ b/patches/server/0149-Reset-spawner-timer-when-spawner-event-is-cancelled.patch @@ -5,7 +5,7 @@ Subject: [PATCH] Reset spawner timer when spawner event is cancelled diff --git a/src/main/java/net/minecraft/world/level/BaseSpawner.java b/src/main/java/net/minecraft/world/level/BaseSpawner.java -index 43d399e1a0ba2fb0541f851a28032fa60fc01b33..31fe2faf9137ac8b1acca9a5ffc5bbcc8aab16c1 100644 +index e424c23c22809a307175b5dcc7f7b0084464493f..6c29e55239fdcf8df3b9dc012aa80cebcd3a837a 100644 --- a/src/main/java/net/minecraft/world/level/BaseSpawner.java +++ b/src/main/java/net/minecraft/world/level/BaseSpawner.java @@ -177,6 +177,7 @@ public abstract class BaseSpawner { diff --git a/patches/server/0150-Allow-specifying-a-custom-authentication-servers-dow.patch b/patches/server/0150-Allow-specifying-a-custom-authentication-servers-dow.patch index 062c5f0c8d00..49defd6c9004 100644 --- a/patches/server/0150-Allow-specifying-a-custom-authentication-servers-dow.patch +++ b/patches/server/0150-Allow-specifying-a-custom-authentication-servers-dow.patch @@ -6,7 +6,7 @@ Subject: [PATCH] Allow specifying a custom "authentication servers down" kick diff --git a/src/main/java/net/minecraft/server/network/ServerLoginPacketListenerImpl.java b/src/main/java/net/minecraft/server/network/ServerLoginPacketListenerImpl.java -index 0c2137dda44f2341bffe0928dc77be8ca6371bc9..0d995abe33f41b96823d3e5a51e33f3dcb11d564 100644 +index ca4c054f17a79bfe0f4f49783a13e30eb0f3e2ff..f489ac450dd0275805d0cc8616050f0331f4c39a 100644 --- a/src/main/java/net/minecraft/server/network/ServerLoginPacketListenerImpl.java +++ b/src/main/java/net/minecraft/server/network/ServerLoginPacketListenerImpl.java @@ -308,7 +308,7 @@ public class ServerLoginPacketListenerImpl implements ServerLoginPacketListener, diff --git a/patches/server/0151-Add-PlayerJumpEvent.patch b/patches/server/0151-Add-PlayerJumpEvent.patch index 2e506e2c59f2..6e1404774e5e 100644 --- a/patches/server/0151-Add-PlayerJumpEvent.patch +++ b/patches/server/0151-Add-PlayerJumpEvent.patch @@ -5,10 +5,10 @@ Subject: [PATCH] Add PlayerJumpEvent diff --git a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java -index 44cc60d92c6e83ecfa2c232b59986968d0161672..7906e163f8d03ba39480526d0293ad48534f11bf 100644 +index 7be9c0054ae3bf0d9b9327271a4ddf56a81d6448..414077a1fed2955bd64ac7091af1daeda2a92e2c 100644 --- a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java +++ b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java -@@ -1199,7 +1199,34 @@ public class ServerGamePacketListenerImpl extends ServerCommonPacketListenerImpl +@@ -1209,7 +1209,34 @@ public class ServerGamePacketListenerImpl extends ServerCommonPacketListenerImpl boolean flag1 = d7 > 0.0D; if (this.player.onGround() && !packet.isOnGround() && flag1) { @@ -37,7 +37,7 @@ index 44cc60d92c6e83ecfa2c232b59986968d0161672..7906e163f8d03ba39480526d0293ad48 + this.player.jumpFromGround(); + } else { + from = event.getFrom(); -+ this.internalTeleport(from.getX(), from.getY(), from.getZ(), from.getYaw(), from.getPitch(), Collections.emptySet()); ++ this.internalTeleport(new PositionMoveRotation(org.bukkit.craftbukkit.util.CraftLocation.toVec3D(from), Vec3.ZERO, from.getYaw(), from.getPitch()), Collections.emptySet()); + return; + } + // Paper end - Add PlayerJumpEvent diff --git a/patches/server/0152-handle-ServerboundKeepAlivePacket-async.patch b/patches/server/0152-handle-ServerboundKeepAlivePacket-async.patch index f3e7e73c5c55..db2c92f2c340 100644 --- a/patches/server/0152-handle-ServerboundKeepAlivePacket-async.patch +++ b/patches/server/0152-handle-ServerboundKeepAlivePacket-async.patch @@ -15,10 +15,10 @@ also adding some additional logging in order to help work out what is causing random disconnections for clients. diff --git a/src/main/java/net/minecraft/server/network/ServerCommonPacketListenerImpl.java b/src/main/java/net/minecraft/server/network/ServerCommonPacketListenerImpl.java -index 5f3b3f03936cfe23ed792c57d342a9932ea2e962..5e5e669f564c6130d1dea004ae8810939954da98 100644 +index 04ada45eabd5a6c752c320cdff1a65c7ac83eb22..6fd8204a8d66d26c3011d197c004398d320dc469 100644 --- a/src/main/java/net/minecraft/server/network/ServerCommonPacketListenerImpl.java +++ b/src/main/java/net/minecraft/server/network/ServerCommonPacketListenerImpl.java -@@ -122,14 +122,18 @@ public abstract class ServerCommonPacketListenerImpl implements ServerCommonPack +@@ -129,14 +129,18 @@ public abstract class ServerCommonPacketListenerImpl implements ServerCommonPack @Override public void handleKeepAlive(ServerboundKeepAlivePacket packet) { diff --git a/patches/server/0153-Expose-client-protocol-version-and-virtual-host.patch b/patches/server/0153-Expose-client-protocol-version-and-virtual-host.patch index 9dfb157c0319..2c7a6a791154 100644 --- a/patches/server/0153-Expose-client-protocol-version-and-virtual-host.patch +++ b/patches/server/0153-Expose-client-protocol-version-and-virtual-host.patch @@ -90,10 +90,10 @@ index 7ae4279768b70a4fdc8f4438898871a17c8fe402..582bbb376c75ab5bf737f3015ce8ad45 private void beginLogin(ClientIntentionPacket packet, boolean transfer) { diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java -index 258808bcb6f853c5679476305074823a7bb8b379..d099b898208392f380eb9ccd49bf84d54c194e67 100644 +index bf8453b2fa3549c827bff784b0d98aa827053634..63499a4fa349b3fa61040244db8be2d5d2569b96 100644 --- a/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java +++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java -@@ -336,6 +336,20 @@ public class CraftPlayer extends CraftHumanEntity implements Player { +@@ -341,6 +341,20 @@ public class CraftPlayer extends CraftHumanEntity implements Player { this.getHandle().transferCookieConnection.sendPacket(new ClientboundTransferPacket(host, port)); } diff --git a/patches/server/0154-revert-serverside-behavior-of-keepalives.patch b/patches/server/0154-revert-serverside-behavior-of-keepalives.patch index ee6e8a16c50d..78c569400ba8 100644 --- a/patches/server/0154-revert-serverside-behavior-of-keepalives.patch +++ b/patches/server/0154-revert-serverside-behavior-of-keepalives.patch @@ -17,10 +17,10 @@ from networking or during connections flood of chunk packets on slower clients, at the cost of dead connections being kept open for longer. diff --git a/src/main/java/net/minecraft/server/network/ServerCommonPacketListenerImpl.java b/src/main/java/net/minecraft/server/network/ServerCommonPacketListenerImpl.java -index 5e5e669f564c6130d1dea004ae8810939954da98..9b1df397e1d2d8ca04b34012808be2110526f401 100644 +index 6fd8204a8d66d26c3011d197c004398d320dc469..b36528ad18c8603994c6a821b19b99581717b44c 100644 --- a/src/main/java/net/minecraft/server/network/ServerCommonPacketListenerImpl.java +++ b/src/main/java/net/minecraft/server/network/ServerCommonPacketListenerImpl.java -@@ -74,7 +74,7 @@ public abstract class ServerCommonPacketListenerImpl implements ServerCommonPack +@@ -75,7 +75,7 @@ public abstract class ServerCommonPacketListenerImpl implements ServerCommonPack protected final MinecraftServer server; public final Connection connection; // Paper private final boolean transferred; @@ -29,7 +29,7 @@ index 5e5e669f564c6130d1dea004ae8810939954da98..9b1df397e1d2d8ca04b34012808be211 private boolean keepAlivePending; private long keepAliveChallenge; private long closedListenerTime; -@@ -82,6 +82,7 @@ public abstract class ServerCommonPacketListenerImpl implements ServerCommonPack +@@ -83,6 +83,7 @@ public abstract class ServerCommonPacketListenerImpl implements ServerCommonPack private int latency; private volatile boolean suspendFlushingOnServerThread = false; public final java.util.Map packCallbacks = new java.util.concurrent.ConcurrentHashMap<>(); // Paper - adventure resource pack callbacks @@ -37,10 +37,10 @@ index 5e5e669f564c6130d1dea004ae8810939954da98..9b1df397e1d2d8ca04b34012808be211 public ServerCommonPacketListenerImpl(MinecraftServer minecraftserver, Connection networkmanager, CommonListenerCookie commonlistenercookie, ServerPlayer player) { // CraftBukkit this.server = minecraftserver; -@@ -232,18 +233,22 @@ public abstract class ServerCommonPacketListenerImpl implements ServerCommonPack +@@ -239,18 +240,22 @@ public abstract class ServerCommonPacketListenerImpl implements ServerCommonPack protected void keepConnectionAlive() { - this.server.getProfiler().push("keepAlive"); + Profiler.get().push("keepAlive"); - long i = 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 @@ -64,5 +64,5 @@ index 5e5e669f564c6130d1dea004ae8810939954da98..9b1df397e1d2d8ca04b34012808be211 } + // Paper end - give clients a longer time to respond to pings as per pre 1.12.2 timings - this.server.getProfiler().pop(); + Profiler.get().pop(); } diff --git a/patches/server/0155-Send-attack-SoundEffects-only-to-players-who-can-see.patch b/patches/server/0155-Send-attack-SoundEffects-only-to-players-who-can-see.patch index f4f07b3245b9..af858331064f 100644 --- a/patches/server/0155-Send-attack-SoundEffects-only-to-players-who-can-see.patch +++ b/patches/server/0155-Send-attack-SoundEffects-only-to-players-who-can-see.patch @@ -6,10 +6,10 @@ Subject: [PATCH] Send attack SoundEffects only to players who can see the diff --git a/src/main/java/net/minecraft/world/entity/player/Player.java b/src/main/java/net/minecraft/world/entity/player/Player.java -index 362d75522aaa721e9fb1d12a149d0efec1ac17b1..34654395536ea022848db3d9f0291512081fc558 100644 +index 357c71409af5f67a0a6aaa0cb08fd93a4a4f99de..1cc11284b0e155ea41c198641b3644028adda97d 100644 --- a/src/main/java/net/minecraft/world/entity/player/Player.java +++ b/src/main/java/net/minecraft/world/entity/player/Player.java -@@ -1257,7 +1257,7 @@ public abstract class Player extends LivingEntity { +@@ -1227,7 +1227,7 @@ public abstract class Player extends LivingEntity { boolean flag1; if (this.isSprinting() && flag) { @@ -18,7 +18,7 @@ index 362d75522aaa721e9fb1d12a149d0efec1ac17b1..34654395536ea022848db3d9f0291512 flag1 = true; } else { flag1 = false; -@@ -1338,7 +1338,7 @@ public abstract class Player extends LivingEntity { +@@ -1308,7 +1308,7 @@ public abstract class Player extends LivingEntity { } } @@ -27,7 +27,7 @@ index 362d75522aaa721e9fb1d12a149d0efec1ac17b1..34654395536ea022848db3d9f0291512 this.sweepAttack(); } -@@ -1366,15 +1366,15 @@ public abstract class Player extends LivingEntity { +@@ -1336,15 +1336,15 @@ public abstract class Player extends LivingEntity { } if (flag2) { @@ -46,7 +46,7 @@ index 362d75522aaa721e9fb1d12a149d0efec1ac17b1..34654395536ea022848db3d9f0291512 } } -@@ -1430,7 +1430,7 @@ public abstract class Player extends LivingEntity { +@@ -1400,7 +1400,7 @@ public abstract class Player extends LivingEntity { this.causeFoodExhaustion(this.level().spigotConfig.combatExhaustion, EntityExhaustionEvent.ExhaustionReason.ATTACK); // CraftBukkit - EntityExhaustionEvent // Spigot - Change to use configurable value } else { @@ -55,7 +55,7 @@ index 362d75522aaa721e9fb1d12a149d0efec1ac17b1..34654395536ea022848db3d9f0291512 // CraftBukkit start - resync on cancelled event if (this instanceof ServerPlayer) { ((ServerPlayer) this).getBukkitEntity().updateInventory(); -@@ -1825,6 +1825,14 @@ public abstract class Player extends LivingEntity { +@@ -1784,6 +1784,14 @@ public abstract class Player extends LivingEntity { public int getXpNeededForNextLevel() { return this.experienceLevel >= 30 ? 112 + (this.experienceLevel - 30) * 9 : (this.experienceLevel >= 15 ? 37 + (this.experienceLevel - 15) * 5 : 7 + this.experienceLevel * 2); } diff --git a/patches/server/0156-Add-PlayerArmorChangeEvent.patch b/patches/server/0156-Add-PlayerArmorChangeEvent.patch index 9eb204ce612d..1f465410c30c 100644 --- a/patches/server/0156-Add-PlayerArmorChangeEvent.patch +++ b/patches/server/0156-Add-PlayerArmorChangeEvent.patch @@ -5,13 +5,13 @@ Subject: [PATCH] Add PlayerArmorChangeEvent diff --git a/src/main/java/net/minecraft/world/entity/LivingEntity.java b/src/main/java/net/minecraft/world/entity/LivingEntity.java -index 021c72410c7580d659bfaa1e327fae3727acabd2..7a7c404778757e6778305c9f8334a4fba1f466a6 100644 +index bcc0137fec45406e70231b4e2a5bd69dc08ffb5a..84e11e2c62e643f959f1a570a27f6ad07df165d4 100644 --- a/src/main/java/net/minecraft/world/entity/LivingEntity.java +++ b/src/main/java/net/minecraft/world/entity/LivingEntity.java -@@ -3168,6 +3168,13 @@ public abstract class LivingEntity extends Entity implements Attackable { - ItemStack itemstack2 = this.getItemBySlot(enumitemslot); +@@ -3283,6 +3283,13 @@ public abstract class LivingEntity extends Entity implements Attackable { - if (this.equipmentHasChanged(itemstack1, itemstack2)) { + itemstack = this.getItemBySlot(enumitemslot); + if (this.equipmentHasChanged(itemstack2, itemstack)) { + // Paper start - PlayerArmorChangeEvent + if (this instanceof ServerPlayer && enumitemslot.getType() == EquipmentSlot.Type.HUMANOID_ARMOR) { + final org.bukkit.inventory.ItemStack oldItem = CraftItemStack.asBukkitCopy(itemstack1); @@ -24,7 +24,7 @@ index 021c72410c7580d659bfaa1e327fae3727acabd2..7a7c404778757e6778305c9f8334a4fb } diff --git a/src/test/java/io/papermc/paper/inventory/item/ArmorSlotTypeMaterialTest.java b/src/test/java/io/papermc/paper/inventory/item/ArmorSlotTypeMaterialTest.java new file mode 100644 -index 0000000000000000000000000000000000000000..8943cef5cdb8269080b9f0e2edbad5d5ea8b421d +index 0000000000000000000000000000000000000000..1431b3faf081dc3ee5b33639bdc90c5057ae7027 --- /dev/null +++ b/src/test/java/io/papermc/paper/inventory/item/ArmorSlotTypeMaterialTest.java @@ -0,0 +1,75 @@ @@ -34,10 +34,10 @@ index 0000000000000000000000000000000000000000..8943cef5cdb8269080b9f0e2edbad5d5 +import java.util.ArrayList; +import java.util.List; +import java.util.stream.Stream; ++import net.minecraft.core.component.DataComponents; +import net.minecraft.world.entity.EquipmentSlot; -+import net.minecraft.world.item.Equipable; +import net.minecraft.world.item.Item; -+import net.minecraft.world.item.ItemStack; ++import net.minecraft.world.item.equipment.Equippable; +import org.bukkit.Material; +import org.bukkit.craftbukkit.util.CraftMagicNumbers; +import org.bukkit.support.environment.AllFeatures; @@ -65,23 +65,23 @@ index 0000000000000000000000000000000000000000..8943cef5cdb8269080b9f0e2edbad5d5 + @MethodSource("slotTypeParams") + public void testSlotType(PlayerArmorChangeEvent.SlotType slotType, Material item) { + final Item nmsItem = CraftMagicNumbers.getItem(item); -+ final Equipable equipable = Equipable.get(new ItemStack(nmsItem)); -+ assertNotNull(equipable, item + " isn't equipable"); ++ final Equippable equippable = nmsItem.components().get(DataComponents.EQUIPPABLE); ++ assertNotNull(equippable, item + " isn't equipable"); + final EquipmentSlot slot = switch (slotType) { + case HEAD -> EquipmentSlot.HEAD; + case CHEST -> EquipmentSlot.CHEST; + case LEGS -> EquipmentSlot.LEGS; + case FEET -> EquipmentSlot.FEET; + }; -+ assertEquals(equipable.getEquipmentSlot(), slot, item + " isn't set to the right slot"); ++ assertEquals(equippable.slot(), slot, item + " isn't set to the right slot"); + } + + public static Stream equipableParams() { + final List parameters = new ArrayList<>(); + for (final Item item : net.minecraft.core.registries.BuiltInRegistries.ITEM) { -+ final Equipable equipable = Equipable.get(new ItemStack(item)); -+ if (equipable != null) { -+ parameters.add(new Object[]{equipable, item}); ++ final Equippable equippable = item.components().get(DataComponents.EQUIPPABLE); ++ if (equippable != null) { ++ parameters.add(new Object[]{equippable, item}); + } + } + return parameters.stream(); @@ -89,8 +89,8 @@ index 0000000000000000000000000000000000000000..8943cef5cdb8269080b9f0e2edbad5d5 + + @ParameterizedTest(name = "{argumentsWithNames}") + @MethodSource("equipableParams") -+ public void testEquipable(Equipable equipable, Item item) { -+ final EquipmentSlot equipmentSlot = equipable.getEquipmentSlot(); ++ public void testEquipable(Equippable equipable, Item item) { ++ final EquipmentSlot equipmentSlot = equipable.slot(); + PlayerArmorChangeEvent.SlotType slotType = switch (equipmentSlot) { + case HEAD -> PlayerArmorChangeEvent.SlotType.HEAD; + case CHEST -> PlayerArmorChangeEvent.SlotType.CHEST; diff --git a/patches/server/0157-Prevent-logins-from-being-processed-when-the-player-.patch b/patches/server/0157-Prevent-logins-from-being-processed-when-the-player-.patch index 5d453de7b929..95804a7b7c93 100644 --- a/patches/server/0157-Prevent-logins-from-being-processed-when-the-player-.patch +++ b/patches/server/0157-Prevent-logins-from-being-processed-when-the-player-.patch @@ -6,7 +6,7 @@ Subject: [PATCH] Prevent logins from being processed when the player has diff --git a/src/main/java/net/minecraft/server/network/ServerLoginPacketListenerImpl.java b/src/main/java/net/minecraft/server/network/ServerLoginPacketListenerImpl.java -index 0d995abe33f41b96823d3e5a51e33f3dcb11d564..bfda68254b39f301ba2d3d70beeb35317d262c43 100644 +index f489ac450dd0275805d0cc8616050f0331f4c39a..c5737e1c8cb82f282bab2e6b7b6c26aa077801f3 100644 --- a/src/main/java/net/minecraft/server/network/ServerLoginPacketListenerImpl.java +++ b/src/main/java/net/minecraft/server/network/ServerLoginPacketListenerImpl.java @@ -109,7 +109,9 @@ public class ServerLoginPacketListenerImpl implements ServerLoginPacketListener, diff --git a/patches/server/0158-Fix-MC-117075-Block-entity-unload-lag-spike.patch b/patches/server/0158-Fix-MC-117075-Block-entity-unload-lag-spike.patch index 88b000fa9fda..12f777016d06 100644 --- a/patches/server/0158-Fix-MC-117075-Block-entity-unload-lag-spike.patch +++ b/patches/server/0158-Fix-MC-117075-Block-entity-unload-lag-spike.patch @@ -5,10 +5,10 @@ Subject: [PATCH] Fix MC-117075: Block entity unload lag spike diff --git a/src/main/java/net/minecraft/world/level/Level.java b/src/main/java/net/minecraft/world/level/Level.java -index 4fcbea7b8b12be10157da0c1f35c06e47c0334ad..ab511f4c056f07aa79aab7b662073bb8db4b1526 100644 +index e5eac1977f77b7ce1112bfe7ac1b77d9ef4d90f4..b8a3116ab104405c26d97f4103eb426f5367071a 100644 --- a/src/main/java/net/minecraft/world/level/Level.java +++ b/src/main/java/net/minecraft/world/level/Level.java -@@ -726,6 +726,8 @@ public abstract class Level implements LevelAccessor, AutoCloseable { +@@ -723,6 +723,8 @@ public abstract class Level implements LevelAccessor, AutoCloseable { boolean flag = this.tickRateManager().runsNormally(); int tilesThisCycle = 0; @@ -17,7 +17,7 @@ index 4fcbea7b8b12be10157da0c1f35c06e47c0334ad..ab511f4c056f07aa79aab7b662073bb8 for (tileTickPosition = 0; tileTickPosition < this.blockEntityTickers.size(); tileTickPosition++) { // Paper - Disable tick limiters this.tileTickPosition = (this.tileTickPosition < this.blockEntityTickers.size()) ? this.tileTickPosition : 0; TickingBlockEntity tickingblockentity = (TickingBlockEntity) this.blockEntityTickers.get(this.tileTickPosition); -@@ -734,12 +736,13 @@ public abstract class Level implements LevelAccessor, AutoCloseable { +@@ -731,12 +733,13 @@ public abstract class Level implements LevelAccessor, AutoCloseable { if (tickingblockentity.isRemoved()) { // Spigot start tilesThisCycle--; @@ -30,5 +30,5 @@ index 4fcbea7b8b12be10157da0c1f35c06e47c0334ad..ab511f4c056f07aa79aab7b662073bb8 } + this.blockEntityTickers.removeAll(toRemove); // Paper - Fix MC-117075 - this.timings.tileEntityTick.stopTiming(); // Spigot this.tickingBlockEntities = false; + gameprofilerfiller.pop(); diff --git a/patches/server/0159-use-CB-BlockState-implementations-for-captured-block.patch b/patches/server/0159-use-CB-BlockState-implementations-for-captured-block.patch index eb8b3bd4dc87..983ff7742bc4 100644 --- a/patches/server/0159-use-CB-BlockState-implementations-for-captured-block.patch +++ b/patches/server/0159-use-CB-BlockState-implementations-for-captured-block.patch @@ -18,10 +18,10 @@ the blockstate that will be valid for restoration, as opposed to dropping information on restoration when the event is cancelled. diff --git a/src/main/java/net/minecraft/world/level/Level.java b/src/main/java/net/minecraft/world/level/Level.java -index ab511f4c056f07aa79aab7b662073bb8db4b1526..27e2718124aad69546c1d9adb9c8e69fa4a43ca7 100644 +index b8a3116ab104405c26d97f4103eb426f5367071a..e0986a141384de0ede38c88c2b116e083c63efe4 100644 --- a/src/main/java/net/minecraft/world/level/Level.java +++ b/src/main/java/net/minecraft/world/level/Level.java -@@ -150,7 +150,7 @@ public abstract class Level implements LevelAccessor, AutoCloseable { +@@ -151,7 +151,7 @@ public abstract class Level implements LevelAccessor, AutoCloseable { public boolean preventPoiUpdated = false; // CraftBukkit - SPIGOT-5710 public boolean captureBlockStates = false; public boolean captureTreeGeneration = false; @@ -30,7 +30,7 @@ index ab511f4c056f07aa79aab7b662073bb8db4b1526..27e2718124aad69546c1d9adb9c8e69f public Map capturedTileEntities = new HashMap<>(); public List captureDrops; public final it.unimi.dsi.fastutil.objects.Object2LongOpenHashMap ticksPerSpawnCategory = new it.unimi.dsi.fastutil.objects.Object2LongOpenHashMap<>(); -@@ -384,7 +384,7 @@ public abstract class Level implements LevelAccessor, AutoCloseable { +@@ -382,7 +382,7 @@ public abstract class Level implements LevelAccessor, AutoCloseable { public boolean setBlock(BlockPos pos, BlockState state, int flags, int maxUpdateDepth) { // CraftBukkit start - tree generation if (this.captureTreeGeneration) { @@ -39,7 +39,7 @@ index ab511f4c056f07aa79aab7b662073bb8db4b1526..27e2718124aad69546c1d9adb9c8e69f if (blockstate == null) { blockstate = CapturedBlockState.getTreeBlockState(this, pos, flags); this.capturedBlockStates.put(pos.immutable(), blockstate); -@@ -405,7 +405,8 @@ public abstract class Level implements LevelAccessor, AutoCloseable { +@@ -403,7 +403,8 @@ public abstract class Level implements LevelAccessor, AutoCloseable { // CraftBukkit start - capture blockstates boolean captured = false; if (this.captureBlockStates && !this.capturedBlockStates.containsKey(pos)) { diff --git a/patches/server/0160-API-to-get-a-BlockState-without-a-snapshot.patch b/patches/server/0160-API-to-get-a-BlockState-without-a-snapshot.patch index cd8d24869a3c..97e12295c92a 100644 --- a/patches/server/0160-API-to-get-a-BlockState-without-a-snapshot.patch +++ b/patches/server/0160-API-to-get-a-BlockState-without-a-snapshot.patch @@ -13,10 +13,10 @@ also Avoid NPE during CraftBlockEntityState load if could not get TE If Tile Entity was null, correct Sign to return empty lines instead of null diff --git a/src/main/java/net/minecraft/world/level/block/entity/BlockEntity.java b/src/main/java/net/minecraft/world/level/block/entity/BlockEntity.java -index d6ad310d3b472c40c128cfb459171d9f48e50915..48bee70ba4188a4a55beb6584224b0f23784dd88 100644 +index 3d7c5db5514f9d4401da22d2df88c5614051e568..50413d317ce0282752c57535637f87d529f4c09f 100644 --- a/src/main/java/net/minecraft/world/level/block/entity/BlockEntity.java +++ b/src/main/java/net/minecraft/world/level/block/entity/BlockEntity.java -@@ -59,6 +59,7 @@ public abstract class BlockEntity { +@@ -54,6 +54,7 @@ public abstract class BlockEntity { this.worldPosition = pos.immutable(); this.validateBlockState(state); this.blockState = state; @@ -24,16 +24,16 @@ index d6ad310d3b472c40c128cfb459171d9f48e50915..48bee70ba4188a4a55beb6584224b0f2 } private void validateBlockState(BlockState state) { -@@ -92,7 +93,7 @@ public abstract class BlockEntity { +@@ -87,7 +88,7 @@ public abstract class BlockEntity { // CraftBukkit start - read container - protected void loadAdditional(CompoundTag nbt, HolderLookup.Provider registryLookup) { + protected void loadAdditional(CompoundTag nbt, HolderLookup.Provider registries) { - this.persistentDataContainer = new CraftPersistentDataContainer(BlockEntity.DATA_TYPE_REGISTRY); + this.persistentDataContainer.clear(); // Paper - clear instead of init net.minecraft.nbt.Tag persistentDataTag = nbt.get("PublicBukkitValues"); if (persistentDataTag instanceof CompoundTag) { -@@ -379,8 +380,15 @@ public abstract class BlockEntity { +@@ -375,8 +376,15 @@ public abstract class BlockEntity { // CraftBukkit start - add method public InventoryHolder getOwner() { @@ -51,7 +51,7 @@ index d6ad310d3b472c40c128cfb459171d9f48e50915..48bee70ba4188a4a55beb6584224b0f2 return null; } diff --git a/src/main/java/org/bukkit/craftbukkit/block/CraftBlock.java b/src/main/java/org/bukkit/craftbukkit/block/CraftBlock.java -index a1c1a101aa424e74309f6f4c0a53a6a8db5df441..013298c424025cd88f15d61e50d196f70fa4c58b 100644 +index c9b1167b15990235e72d68ba6d1d2f0385eb5755..e163ff416f19766132d73fff2c8eb7f3f098f8c6 100644 --- a/src/main/java/org/bukkit/craftbukkit/block/CraftBlock.java +++ b/src/main/java/org/bukkit/craftbukkit/block/CraftBlock.java @@ -328,6 +328,13 @@ public class CraftBlock implements Block { @@ -114,10 +114,10 @@ index 483a89ba477506ae108f680e586b37c142119696..80418a05d53516d2c539383aa9994099 + // Paper end } diff --git a/src/main/java/org/bukkit/craftbukkit/block/CraftBlockStates.java b/src/main/java/org/bukkit/craftbukkit/block/CraftBlockStates.java -index 0173a8eac2153d889536e48970f967040490e4b7..83ac5fc6cbbd249b5865ab203b150f53f01c9f05 100644 +index b849ed6eefbc73a86d4dd823bbc5bbb2321c330f..1a8dcde39a252a45046866349b848d79e1b13260 100644 --- a/src/main/java/org/bukkit/craftbukkit/block/CraftBlockStates.java +++ b/src/main/java/org/bukkit/craftbukkit/block/CraftBlockStates.java -@@ -387,15 +387,30 @@ public final class CraftBlockStates { +@@ -393,15 +393,30 @@ public final class CraftBlockStates { } public static BlockState getBlockState(Block block) { diff --git a/patches/server/0161-AsyncTabCompleteEvent.patch b/patches/server/0161-AsyncTabCompleteEvent.patch index ca24a4366fd1..2436de291140 100644 --- a/patches/server/0161-AsyncTabCompleteEvent.patch +++ b/patches/server/0161-AsyncTabCompleteEvent.patch @@ -16,10 +16,10 @@ Also adds isCommand and getLocation to the sync TabCompleteEvent Co-authored-by: Aikar diff --git a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java -index 87f56f9c14e3a827d0afc03591bfce8cbf61e307..6cdbd0281e38d7107f239e6e052c08e4ab12b552 100644 +index 414077a1fed2955bd64ac7091af1daeda2a92e2c..79d9a58c66382fd94ab5f6020285e341b0114f6a 100644 --- a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java +++ b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java -@@ -711,21 +711,58 @@ public class ServerGamePacketListenerImpl extends ServerCommonPacketListenerImpl +@@ -722,21 +722,58 @@ public class ServerGamePacketListenerImpl extends ServerCommonPacketListenerImpl } @@ -32,7 +32,7 @@ index 87f56f9c14e3a827d0afc03591bfce8cbf61e307..6cdbd0281e38d7107f239e6e052c08e4 - PacketUtils.ensureRunningOnSameThread(packet, this, this.player.serverLevel()); + // PacketUtils.ensureRunningOnSameThread(packet, this, this.player.serverLevel()); // Paper - AsyncTabCompleteEvent; run this async // CraftBukkit start - if (this.chatSpamTickCount.addAndGet(1) > 500 && !this.server.getPlayerList().isOp(this.player.getGameProfile())) { + if (!this.chatSpamThrottler.isIncrementAndUnderThreshold(1, 500) && !this.server.getPlayerList().isOp(this.player.getGameProfile()) && !this.server.isSingleplayerOwner(this.player.getGameProfile())) { this.disconnect(Component.translatable("disconnect.spam")); return; } @@ -80,10 +80,10 @@ index 87f56f9c14e3a827d0afc03591bfce8cbf61e307..6cdbd0281e38d7107f239e6e052c08e4 this.server.getCommands().getDispatcher().getCompletionSuggestions(parseresults).thenAccept((suggestions) -> { diff --git a/src/main/java/org/bukkit/craftbukkit/CraftServer.java b/src/main/java/org/bukkit/craftbukkit/CraftServer.java -index 31f87ce0865dd7560cd9ba634855a1defdc80df2..39e3730eae983c20522b97fcd547823cd192cb2d 100644 +index 2d1f92f75e83aacd39440d2619befdf6cd545af4..1dd440057cb1d8acce7bfaa7ab1814937a4c63af 100644 --- a/src/main/java/org/bukkit/craftbukkit/CraftServer.java +++ b/src/main/java/org/bukkit/craftbukkit/CraftServer.java -@@ -2294,7 +2294,7 @@ public final class CraftServer implements Server { +@@ -2297,7 +2297,7 @@ public final class CraftServer implements Server { offers = this.tabCompleteChat(player, message); } diff --git a/patches/server/0162-PlayerPickupExperienceEvent.patch b/patches/server/0162-PlayerPickupExperienceEvent.patch index 591fd50b0205..a07fa0d16024 100644 --- a/patches/server/0162-PlayerPickupExperienceEvent.patch +++ b/patches/server/0162-PlayerPickupExperienceEvent.patch @@ -6,10 +6,10 @@ Subject: [PATCH] PlayerPickupExperienceEvent Allows plugins to cancel a player picking up an experience orb diff --git a/src/main/java/net/minecraft/world/entity/ExperienceOrb.java b/src/main/java/net/minecraft/world/entity/ExperienceOrb.java -index 0330a62a6a0060d2a96de191db68774588fc7ae5..2d438cd3e69503fdc45a706f25c219af6f7a5db3 100644 +index b9160ebca0d11dbbf96da5f0f5810d302cfcea9a..74a0bebbf829fdb2bbae87100c4e2523c34f95a0 100644 --- a/src/main/java/net/minecraft/world/entity/ExperienceOrb.java +++ b/src/main/java/net/minecraft/world/entity/ExperienceOrb.java -@@ -322,7 +322,7 @@ public class ExperienceOrb extends Entity { +@@ -326,7 +326,7 @@ public class ExperienceOrb extends Entity { @Override public void playerTouch(Player player) { if (player instanceof ServerPlayer entityplayer) { diff --git a/patches/server/0163-Ability-to-apply-mending-to-XP-API.patch b/patches/server/0163-Ability-to-apply-mending-to-XP-API.patch index aacf03b70085..590dfbe62799 100644 --- a/patches/server/0163-Ability-to-apply-mending-to-XP-API.patch +++ b/patches/server/0163-Ability-to-apply-mending-to-XP-API.patch @@ -14,15 +14,15 @@ public net.minecraft.world.entity.ExperienceOrb durabilityToXp(I)I public net.minecraft.world.entity.ExperienceOrb xpToDurability(I)I diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java -index f53504221e660bfe86220a8cc1ae28750794f0cf..93550342c6f181b7622f5d649cd3e5075a464e55 100644 +index 63499a4fa349b3fa61040244db8be2d5d2569b96..128fcd537783986d816dae6d1ce2afb7af07d45a 100644 --- a/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java +++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java -@@ -1638,7 +1638,41 @@ public class CraftPlayer extends CraftHumanEntity implements Player { +@@ -1665,7 +1665,41 @@ public class CraftPlayer extends CraftHumanEntity implements Player { } @Override - public void giveExp(int exp) { -+ // Paper start ++ // Paper start - ability to apply mending + public int applyMending(int amount) { + ServerPlayer handle = this.getHandle(); + // Logic copied from EntityExperienceOrb and remapped to unobfuscated methods/properties @@ -31,7 +31,7 @@ index f53504221e660bfe86220a8cc1ae28750794f0cf..93550342c6f181b7622f5d649cd3e507 + .getRandomItemWith(net.minecraft.world.item.enchantment.EnchantmentEffectComponents.REPAIR_WITH_XP, handle, net.minecraft.world.item.ItemStack::isDamaged); + final net.minecraft.world.item.ItemStack itemstack = stackEntry.map(net.minecraft.world.item.enchantment.EnchantedItemInUse::itemStack).orElse(net.minecraft.world.item.ItemStack.EMPTY); + if (!itemstack.isEmpty() && itemstack.getItem().components().has(net.minecraft.core.component.DataComponents.MAX_DAMAGE)) { -+ net.minecraft.world.entity.ExperienceOrb orb = net.minecraft.world.entity.EntityType.EXPERIENCE_ORB.create(handle.level()); ++ net.minecraft.world.entity.ExperienceOrb orb = net.minecraft.world.entity.EntityType.EXPERIENCE_ORB.create(handle.level(), net.minecraft.world.entity.EntitySpawnReason.COMMAND); + orb.value = amount; + orb.spawnReason = org.bukkit.entity.ExperienceOrb.SpawnReason.CUSTOM; + orb.setPosRaw(handle.getX(), handle.getY(), handle.getZ()); @@ -56,7 +56,7 @@ index f53504221e660bfe86220a8cc1ae28750794f0cf..93550342c6f181b7622f5d649cd3e507 + if (applyMending) { + exp = this.applyMending(exp); + } -+ // Paper end ++ // Paper end - ability to apply mending this.getHandle().giveExperiencePoints(exp); } diff --git a/patches/server/0164-PlayerNaturallySpawnCreaturesEvent.patch b/patches/server/0164-PlayerNaturallySpawnCreaturesEvent.patch index 2b8c16af6b11..2c2148e2532b 100644 --- a/patches/server/0164-PlayerNaturallySpawnCreaturesEvent.patch +++ b/patches/server/0164-PlayerNaturallySpawnCreaturesEvent.patch @@ -9,10 +9,10 @@ from triggering monster spawns on a server. Also a highly more effecient way to blanket block spawns in a world diff --git a/src/main/java/net/minecraft/server/level/ChunkMap.java b/src/main/java/net/minecraft/server/level/ChunkMap.java -index d60c70d59d0a6ef21224c597c9015cb3f51dabb8..63bcd7698fdb86366441dacedbb616771f6b1a3d 100644 +index 261943f1f188643793a72bd239dfc5fe604e3b99..985ba48a5ac027d3c3dcd9b710b53748508966fb 100644 --- a/src/main/java/net/minecraft/server/level/ChunkMap.java +++ b/src/main/java/net/minecraft/server/level/ChunkMap.java -@@ -1037,7 +1037,9 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider +@@ -1098,7 +1098,9 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider chunkRange = (chunkRange > this.level.spigotConfig.viewDistance) ? (byte) this.level.spigotConfig.viewDistance : chunkRange; chunkRange = (chunkRange > 8) ? 8 : chunkRange; @@ -21,49 +21,49 @@ index d60c70d59d0a6ef21224c597c9015cb3f51dabb8..63bcd7698fdb86366441dacedbb61677 + //double blockRange = (reducedRange) ? Math.pow(chunkRange << 4, 2) : 16384.0D; // Paper - use from event + double blockRange = 16384.0D; // Paper // Spigot end - if (!this.distanceManager.hasPlayersNearby(chunkcoordintpair.toLong())) { - return false; -@@ -1052,6 +1054,15 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider - } + Iterator iterator = this.playerMap.getAllPlayers().iterator(); - entityplayer = (ServerPlayer) iterator.next(); -+ // Paper start - PlayerNaturallySpawnCreaturesEvent -+ com.destroystokyo.paper.event.entity.PlayerNaturallySpawnCreaturesEvent event; -+ blockRange = 16384.0D; -+ if (reducedRange) { -+ event = entityplayer.playerNaturallySpawnedEvent; -+ if (event == null || event.isCancelled()) return false; -+ blockRange = (double) ((event.getSpawnRadius() << 4) * (event.getSpawnRadius() << 4)); -+ } -+ // Paper end - PlayerNaturallySpawnCreaturesEvent - } while (!this.playerIsCloseEnoughForSpawning(entityplayer, chunkcoordintpair, blockRange)); // Spigot +@@ -1110,6 +1112,15 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider + } - return true; + entityplayer = (ServerPlayer) iterator.next(); ++ // Paper start - PlayerNaturallySpawnCreaturesEvent ++ com.destroystokyo.paper.event.entity.PlayerNaturallySpawnCreaturesEvent event; ++ blockRange = 16384.0D; ++ if (reducedRange) { ++ event = entityplayer.playerNaturallySpawnedEvent; ++ if (event == null || event.isCancelled()) return false; ++ blockRange = (double) ((event.getSpawnRadius() << 4) * (event.getSpawnRadius() << 4)); ++ } ++ // Paper end - PlayerNaturallySpawnCreaturesEvent + } while (!this.playerIsCloseEnoughForSpawning(entityplayer, chunkcoordintpair, blockRange)); // Spigot + + return true; diff --git a/src/main/java/net/minecraft/server/level/ServerChunkCache.java b/src/main/java/net/minecraft/server/level/ServerChunkCache.java -index e0c8b89767087cba34fc3c3809db4c386dacb193..a939bad7da9c852827a2d67d9ace5d0df4911a31 100644 +index ccb6f28689a3cf7da4ea323c5dd8f595036c4b43..d4eb7608a3e40d2da4c427e9b3a2ce916be86df1 100644 --- a/src/main/java/net/minecraft/server/level/ServerChunkCache.java +++ b/src/main/java/net/minecraft/server/level/ServerChunkCache.java -@@ -461,6 +461,15 @@ public class ServerChunkCache extends ChunkSource { - boolean flag = this.level.getGameRules().getBoolean(GameRules.RULE_DOMOBSPAWNING) && !this.level.players().isEmpty(); // CraftBukkit +@@ -504,6 +504,15 @@ public class ServerChunkCache extends ChunkSource { + List list1; + + if (flag && (this.spawnEnemies || this.spawnFriendlies)) { ++ // Paper start - PlayerNaturallySpawnCreaturesEvent ++ int chunkRange = level.spigotConfig.mobSpawnRange; ++ chunkRange = (chunkRange > level.spigotConfig.viewDistance) ? (byte) level.spigotConfig.viewDistance : chunkRange; ++ chunkRange = Math.min(chunkRange, 8); ++ for (ServerPlayer entityPlayer : this.level.players()) { ++ entityPlayer.playerNaturallySpawnedEvent = new com.destroystokyo.paper.event.entity.PlayerNaturallySpawnCreaturesEvent(entityPlayer.getBukkitEntity(), (byte) chunkRange); ++ entityPlayer.playerNaturallySpawnedEvent.callEvent(); ++ } ++ // Paper end - PlayerNaturallySpawnCreaturesEvent + boolean flag1 = this.level.ticksPerSpawnCategory.getLong(org.bukkit.entity.SpawnCategory.ANIMAL) != 0L && this.level.getLevelData().getGameTime() % this.level.ticksPerSpawnCategory.getLong(org.bukkit.entity.SpawnCategory.ANIMAL) == 0L; // CraftBukkit - Util.shuffle(list, this.level.random); -+ // Paper start - PlayerNaturallySpawnCreaturesEvent -+ int chunkRange = level.spigotConfig.mobSpawnRange; -+ chunkRange = (chunkRange > level.spigotConfig.viewDistance) ? (byte) level.spigotConfig.viewDistance : chunkRange; -+ chunkRange = Math.min(chunkRange, 8); -+ for (ServerPlayer entityPlayer : this.level.players()) { -+ entityPlayer.playerNaturallySpawnedEvent = new com.destroystokyo.paper.event.entity.PlayerNaturallySpawnCreaturesEvent(entityPlayer.getBukkitEntity(), (byte) chunkRange); -+ entityPlayer.playerNaturallySpawnedEvent.callEvent(); -+ } -+ // Paper end - PlayerNaturallySpawnCreaturesEvent - int l = this.level.getGameRules().getInt(GameRules.RULE_RANDOMTICKING); - boolean flag1 = this.level.ticksPerSpawnCategory.getLong(org.bukkit.entity.SpawnCategory.ANIMAL) != 0L && this.level.getLevelData().getGameTime() % this.level.ticksPerSpawnCategory.getLong(org.bukkit.entity.SpawnCategory.ANIMAL) == 0L; // CraftBukkit - Iterator iterator1 = list.iterator(); + list1 = NaturalSpawner.getFilteredSpawningCategories(spawnercreature_d, this.spawnFriendlies, this.spawnEnemies, flag1, this.level); // CraftBukkit diff --git a/src/main/java/net/minecraft/server/level/ServerPlayer.java b/src/main/java/net/minecraft/server/level/ServerPlayer.java -index a049a54ee70839706787f8de661ca6e6b1f54071..db72318d822b876eb937f0f0f7f2b2139fb77df7 100644 +index b99bd43bf5185bed21fad7dac31baf1a30bdd1fe..98aeafcc51e23a7534c8d57e4db0eb58abb3f30b 100644 --- a/src/main/java/net/minecraft/server/level/ServerPlayer.java +++ b/src/main/java/net/minecraft/server/level/ServerPlayer.java -@@ -286,6 +286,7 @@ public class ServerPlayer extends net.minecraft.world.entity.player.Player { +@@ -314,6 +314,7 @@ public class ServerPlayer extends net.minecraft.world.entity.player.Player { public String kickLeaveMessage = null; // SPIGOT-3034: Forward leave message to PlayerQuitEvent // CraftBukkit end public boolean isRealPlayer; // Paper diff --git a/patches/server/0166-PreCreatureSpawnEvent.patch b/patches/server/0166-PreCreatureSpawnEvent.patch index 0f4459f539a5..0f7306bfc5f9 100644 --- a/patches/server/0166-PreCreatureSpawnEvent.patch +++ b/patches/server/0166-PreCreatureSpawnEvent.patch @@ -15,23 +15,23 @@ instead and save a lot of server resources. See: https://github.com/PaperMC/Paper/issues/917 diff --git a/src/main/java/net/minecraft/util/SpawnUtil.java b/src/main/java/net/minecraft/util/SpawnUtil.java -index 3f2cad4c9c0400bf93932cb7f7219c2185fc7370..5c8e36ea8287029b1789719c687bac1a2c4c3a69 100644 +index e139ed6bc6f2dd07fe546588b31309ba30ed9755..34c3bf85473b3ad89355ebc21b68c59b3c683b84 100644 --- a/src/main/java/net/minecraft/util/SpawnUtil.java +++ b/src/main/java/net/minecraft/util/SpawnUtil.java -@@ -21,10 +21,10 @@ public class SpawnUtil { +@@ -22,10 +22,10 @@ public class SpawnUtil { - public static Optional trySpawnMob(EntityType entityType, MobSpawnType reason, ServerLevel world, BlockPos pos, int tries, int horizontalRange, int verticalRange, SpawnUtil.Strategy requirements) { + public static Optional trySpawnMob(EntityType entityType, EntitySpawnReason reason, ServerLevel world, BlockPos pos, int tries, int horizontalRange, int verticalRange, SpawnUtil.Strategy requirements) { // CraftBukkit start - return SpawnUtil.trySpawnMob(entityType, reason, world, pos, tries, horizontalRange, verticalRange, requirements, org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason.DEFAULT); + return SpawnUtil.trySpawnMob(entityType, reason, world, pos, tries, horizontalRange, verticalRange, requirements, org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason.DEFAULT, null); // Paper } -- public static Optional trySpawnMob(EntityType entitytypes, MobSpawnType enummobspawn, ServerLevel worldserver, BlockPos blockposition, int i, int j, int k, SpawnUtil.Strategy spawnutil_a, org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason reason) { -+ public static Optional trySpawnMob(EntityType entitytypes, MobSpawnType enummobspawn, ServerLevel worldserver, BlockPos blockposition, int i, int j, int k, SpawnUtil.Strategy spawnutil_a, org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason reason, @javax.annotation.Nullable Runnable onAbort) { // Paper +- public static Optional trySpawnMob(EntityType entitytypes, EntitySpawnReason entityspawnreason, ServerLevel worldserver, BlockPos blockposition, int i, int j, int k, SpawnUtil.Strategy spawnutil_a, org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason reason) { ++ public static Optional trySpawnMob(EntityType entitytypes, EntitySpawnReason entityspawnreason, ServerLevel worldserver, BlockPos blockposition, int i, int j, int k, SpawnUtil.Strategy spawnutil_a, org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason reason, @javax.annotation.Nullable Runnable onAbort) { // Paper - pre creature spawn event // CraftBukkit end BlockPos.MutableBlockPos blockposition_mutableblockposition = blockposition.mutable(); -@@ -34,6 +34,22 @@ public class SpawnUtil { +@@ -35,6 +35,22 @@ public class SpawnUtil { blockposition_mutableblockposition.setWithOffset(blockposition, i1, k, j1); if (worldserver.getWorldBorder().isWithinBounds((BlockPos) blockposition_mutableblockposition) && SpawnUtil.moveToPossibleSpawnPosition(worldserver, k, blockposition_mutableblockposition, spawnutil_a)) { @@ -51,16 +51,16 @@ index 3f2cad4c9c0400bf93932cb7f7219c2185fc7370..5c8e36ea8287029b1789719c687bac1a + break; + } + // Paper end - PreCreatureSpawnEvent - T t0 = entitytypes.create(worldserver, (Consumer) null, blockposition_mutableblockposition, enummobspawn, false, false); // CraftBukkit - decompile error + T t0 = entitytypes.create(worldserver, (Consumer) null, blockposition_mutableblockposition, entityspawnreason, false, false); // CraftBukkit - decompile error if (t0 != null) { diff --git a/src/main/java/net/minecraft/world/entity/EntityType.java b/src/main/java/net/minecraft/world/entity/EntityType.java -index 474f020371bb9e5fd2c5b22e44d7902977c4fc18..69a661f01e43d17262fd2845dde5528416bbe456 100644 +index 17ab230c95901f0533997ac117d5b3d852fcd467..8f4ec4f0ea7ff2f9a952120785aea65f6559f897 100644 --- a/src/main/java/net/minecraft/world/entity/EntityType.java +++ b/src/main/java/net/minecraft/world/entity/EntityType.java -@@ -430,6 +430,16 @@ public class EntityType implements FeatureElement, EntityTypeT +@@ -497,6 +497,16 @@ public class EntityType implements FeatureElement, EntityTypeT @Nullable - public T spawn(ServerLevel worldserver, @Nullable Consumer consumer, BlockPos blockposition, MobSpawnType enummobspawn, boolean flag, boolean flag1, org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason spawnReason) { + public T spawn(ServerLevel worldserver, @Nullable Consumer consumer, BlockPos blockposition, EntitySpawnReason entityspawnreason, boolean flag, boolean flag1, org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason spawnReason) { // CraftBukkit end + // Paper start - PreCreatureSpawnEvent + com.destroystokyo.paper.event.entity.PreCreatureSpawnEvent event = new com.destroystokyo.paper.event.entity.PreCreatureSpawnEvent( @@ -72,28 +72,28 @@ index 474f020371bb9e5fd2c5b22e44d7902977c4fc18..69a661f01e43d17262fd2845dde55284 + return null; + } + // Paper end - PreCreatureSpawnEvent - T t0 = this.create(worldserver, consumer, blockposition, enummobspawn, flag, flag1); + T t0 = this.create(worldserver, consumer, blockposition, entityspawnreason, flag, flag1); if (t0 != null) { diff --git a/src/main/java/net/minecraft/world/entity/npc/Villager.java b/src/main/java/net/minecraft/world/entity/npc/Villager.java -index 79fdc8284f57a4f11e1954936ad574f7b8df5435..e23674dd5db3c429efd3b7c71fe36b420494c03a 100644 +index 83bb48891d03534468d61cf7683438b3efb131cf..82ed0ce824e84ea09ea963caa61fbb75f6ce6fe7 100644 --- a/src/main/java/net/minecraft/world/entity/npc/Villager.java +++ b/src/main/java/net/minecraft/world/entity/npc/Villager.java -@@ -970,7 +970,7 @@ public class Villager extends AbstractVillager implements ReputationEventHandler - }).limit(5L).collect(Collectors.toList()); +@@ -944,7 +944,7 @@ public class Villager extends AbstractVillager implements ReputationEventHandler + }).limit(5L).toList(); if (list1.size() >= requiredCount) { -- if (!SpawnUtil.trySpawnMob(EntityType.IRON_GOLEM, MobSpawnType.MOB_SUMMONED, world, this.blockPosition(), 10, 8, 6, SpawnUtil.Strategy.LEGACY_IRON_GOLEM, org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason.VILLAGE_DEFENSE).isEmpty()) { // CraftBukkit -+ if (SpawnUtil.trySpawnMob(EntityType.IRON_GOLEM, MobSpawnType.MOB_SUMMONED, world, this.blockPosition(), 10, 8, 6, SpawnUtil.Strategy.LEGACY_IRON_GOLEM, org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason.VILLAGE_DEFENSE, () -> {GolemSensor.golemDetected(this);}).isPresent()) { // CraftBukkit // Paper - Set Golem Last Seen to stop it from spawning another one +- if (!SpawnUtil.trySpawnMob(EntityType.IRON_GOLEM, EntitySpawnReason.MOB_SUMMONED, world, this.blockPosition(), 10, 8, 6, SpawnUtil.Strategy.LEGACY_IRON_GOLEM, org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason.VILLAGE_DEFENSE).isEmpty()) { // CraftBukkit ++ if (SpawnUtil.trySpawnMob(EntityType.IRON_GOLEM, EntitySpawnReason.MOB_SUMMONED, world, this.blockPosition(), 10, 8, 6, SpawnUtil.Strategy.LEGACY_IRON_GOLEM, org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason.VILLAGE_DEFENSE, () -> {GolemSensor.golemDetected(this);}).isPresent()) { // CraftBukkit // Paper - Set Golem Last Seen to stop it from spawning another one list.forEach(GolemSensor::golemDetected); } } diff --git a/src/main/java/net/minecraft/world/level/BaseSpawner.java b/src/main/java/net/minecraft/world/level/BaseSpawner.java -index 31fe2faf9137ac8b1acca9a5ffc5bbcc8aab16c1..d13abdcc7a54bdecf853c883911ef535733610b4 100644 +index 6c29e55239fdcf8df3b9dc012aa80cebcd3a837a..bb3f3bec350dda43dbf5eda0a8c8057a413694b2 100644 --- a/src/main/java/net/minecraft/world/level/BaseSpawner.java +++ b/src/main/java/net/minecraft/world/level/BaseSpawner.java @@ -132,6 +132,20 @@ public abstract class BaseSpawner { - } else if (!SpawnPlacements.checkSpawnRules((EntityType) optional.get(), world, MobSpawnType.SPAWNER, blockposition1, world.getRandom())) { + } else if (!SpawnPlacements.checkSpawnRules((EntityType) optional.get(), world, EntitySpawnReason.SPAWNER, blockposition1, world.getRandom())) { continue; } + // Paper start - PreCreatureSpawnEvent @@ -111,13 +111,13 @@ index 31fe2faf9137ac8b1acca9a5ffc5bbcc8aab16c1..d13abdcc7a54bdecf853c883911ef535 + } + // Paper end - PreCreatureSpawnEvent - Entity entity = EntityType.loadEntityRecursive(nbttagcompound, world, (entity1) -> { + Entity entity = EntityType.loadEntityRecursive(nbttagcompound, world, EntitySpawnReason.SPAWNER, (entity1) -> { entity1.moveTo(d0, d1, d2, entity1.getYRot(), entity1.getXRot()); diff --git a/src/main/java/net/minecraft/world/level/NaturalSpawner.java b/src/main/java/net/minecraft/world/level/NaturalSpawner.java -index 6324689f52363f19501143c1649f0885684cb796..bce78beaadbfd0e400457bd14bcf6538be702879 100644 +index da0ddb8285e157be0cc7b940a9590be5e3061e3d..5a0ea71dc39c582ef6c843dc0532adb04ba120ce 100644 --- a/src/main/java/net/minecraft/world/level/NaturalSpawner.java +++ b/src/main/java/net/minecraft/world/level/NaturalSpawner.java -@@ -208,7 +208,13 @@ public final class NaturalSpawner { +@@ -228,7 +228,13 @@ public final class NaturalSpawner { j1 = biomesettingsmobs_c.minCount + world.random.nextInt(1 + biomesettingsmobs_c.maxCount - biomesettingsmobs_c.minCount); } @@ -132,7 +132,7 @@ index 6324689f52363f19501143c1649f0885684cb796..bce78beaadbfd0e400457bd14bcf6538 Mob entityinsentient = NaturalSpawner.getMobForSpawn(world, biomesettingsmobs_c.type); if (entityinsentient == null) { -@@ -256,10 +262,31 @@ public final class NaturalSpawner { +@@ -276,10 +282,31 @@ public final class NaturalSpawner { return squaredDistance <= 576.0D ? false : (world.getSharedSpawnPos().closerToCenterThan(new Vec3((double) pos.getX() + 0.5D, (double) pos.getY(), (double) pos.getZ() + 0.5D), 24.0D) ? false : Objects.equals(new ChunkPos(pos), chunk.getPos()) || world.isNaturalSpawningAllowed((BlockPos) pos)); } @@ -148,7 +148,7 @@ index 6324689f52363f19501143c1649f0885684cb796..bce78beaadbfd0e400457bd14bcf6538 + // Paper end - PreCreatureSpawnEvent EntityType entitytypes = spawnEntry.type; -- return entitytypes.getCategory() == MobCategory.MISC ? false : (!entitytypes.canSpawnFarFromPlayer() && squaredDistance > (double) (entitytypes.getCategory().getDespawnDistance() * entitytypes.getCategory().getDespawnDistance()) ? false : (entitytypes.canSummon() && NaturalSpawner.canSpawnMobAt(world, structureAccessor, chunkGenerator, group, spawnEntry, pos) ? (!SpawnPlacements.isSpawnPositionOk(entitytypes, world, pos) ? false : (!SpawnPlacements.checkSpawnRules(entitytypes, world, MobSpawnType.NATURAL, pos, world.random) ? false : world.noCollision(entitytypes.getSpawnAABB((double) pos.getX() + 0.5D, (double) pos.getY(), (double) pos.getZ() + 0.5D)))) : false)); +- return entitytypes.getCategory() == MobCategory.MISC ? false : (!entitytypes.canSpawnFarFromPlayer() && squaredDistance > (double) (entitytypes.getCategory().getDespawnDistance() * entitytypes.getCategory().getDespawnDistance()) ? false : (entitytypes.canSummon() && NaturalSpawner.canSpawnMobAt(world, structureAccessor, chunkGenerator, group, spawnEntry, pos) ? (!SpawnPlacements.isSpawnPositionOk(entitytypes, world, pos) ? false : (!SpawnPlacements.checkSpawnRules(entitytypes, world, EntitySpawnReason.NATURAL, pos, world.random) ? false : world.noCollision(entitytypes.getSpawnAABB((double) pos.getX() + 0.5D, (double) pos.getY(), (double) pos.getZ() + 0.5D)))) : false)); + // Paper start - PreCreatureSpawnEvent + com.destroystokyo.paper.event.entity.PreCreatureSpawnEvent event = new com.destroystokyo.paper.event.entity.PreCreatureSpawnEvent( + io.papermc.paper.util.MCUtil.toLocation(world, pos), @@ -162,7 +162,7 @@ index 6324689f52363f19501143c1649f0885684cb796..bce78beaadbfd0e400457bd14bcf6538 + } + // Paper end - PreCreatureSpawnEvent + -+ return entitytypes.getCategory() == MobCategory.MISC ? PreSpawnStatus.FAIL : (!entitytypes.canSpawnFarFromPlayer() && squaredDistance > (double) (entitytypes.getCategory().getDespawnDistance() * entitytypes.getCategory().getDespawnDistance()) ? PreSpawnStatus.FAIL : (entitytypes.canSummon() && NaturalSpawner.canSpawnMobAt(world, structureAccessor, chunkGenerator, group, spawnEntry, pos) ? (!SpawnPlacements.isSpawnPositionOk(entitytypes, world, pos) ? PreSpawnStatus.FAIL : (!SpawnPlacements.checkSpawnRules(entitytypes, world, MobSpawnType.NATURAL, pos, world.random) ? PreSpawnStatus.FAIL : world.noCollision(entitytypes.getSpawnAABB((double) pos.getX() + 0.5D, (double) pos.getY(), (double) pos.getZ() + 0.5D)) ? PreSpawnStatus.SUCCESS : PreSpawnStatus.FAIL)) : PreSpawnStatus.FAIL)); // Paper - PreCreatureSpawnEvent ++ return entitytypes.getCategory() == MobCategory.MISC ? PreSpawnStatus.FAIL : (!entitytypes.canSpawnFarFromPlayer() && squaredDistance > (double) (entitytypes.getCategory().getDespawnDistance() * entitytypes.getCategory().getDespawnDistance()) ? PreSpawnStatus.FAIL : (entitytypes.canSummon() && NaturalSpawner.canSpawnMobAt(world, structureAccessor, chunkGenerator, group, spawnEntry, pos) ? (!SpawnPlacements.isSpawnPositionOk(entitytypes, world, pos) ? PreSpawnStatus.FAIL : (!SpawnPlacements.checkSpawnRules(entitytypes, world, EntitySpawnReason.NATURAL, pos, world.random) ? PreSpawnStatus.FAIL : world.noCollision(entitytypes.getSpawnAABB((double) pos.getX() + 0.5D, (double) pos.getY(), (double) pos.getZ() + 0.5D)) ? PreSpawnStatus.SUCCESS : PreSpawnStatus.FAIL)) : PreSpawnStatus.FAIL)); // Paper - PreCreatureSpawnEvent } @Nullable diff --git a/patches/server/0167-Fill-Profile-Property-Events.patch b/patches/server/0167-Fill-Profile-Property-Events.patch index 4a73aa80a3d9..0c89bc936ac3 100644 --- a/patches/server/0167-Fill-Profile-Property-Events.patch +++ b/patches/server/0167-Fill-Profile-Property-Events.patch @@ -59,7 +59,7 @@ index d8ed3404e8c3c61b2daff110ef32ef890a77a461..78863e72239a0f3535bc85758479da84 return new ResolvableProfile(gameProfile); }) : SkullBlockEntity.fetchGameProfile(this.name.orElseThrow()).thenApply(profile -> { diff --git a/src/main/java/net/minecraft/world/level/block/entity/SkullBlockEntity.java b/src/main/java/net/minecraft/world/level/block/entity/SkullBlockEntity.java -index c278caa030ceccec8e2721068848a34be513de88..26e92e208ddd3122da6d44767b8841d7a8b90d98 100644 +index 4b7176779c455a876419a497a8178163a68553fc..43ddf23db92c7bfd29da4ab79538fc9a5765232e 100644 --- a/src/main/java/net/minecraft/world/level/block/entity/SkullBlockEntity.java +++ b/src/main/java/net/minecraft/world/level/block/entity/SkullBlockEntity.java @@ -41,7 +41,7 @@ public class SkullBlockEntity extends BlockEntity { diff --git a/patches/server/0168-Add-PlayerAdvancementCriterionGrantEvent.patch b/patches/server/0168-Add-PlayerAdvancementCriterionGrantEvent.patch index ddac15d31104..af41f90a899f 100644 --- a/patches/server/0168-Add-PlayerAdvancementCriterionGrantEvent.patch +++ b/patches/server/0168-Add-PlayerAdvancementCriterionGrantEvent.patch @@ -5,7 +5,7 @@ Subject: [PATCH] Add PlayerAdvancementCriterionGrantEvent diff --git a/src/main/java/net/minecraft/server/PlayerAdvancements.java b/src/main/java/net/minecraft/server/PlayerAdvancements.java -index 81bef7427478fe911e81f024102654d8d540dbb7..4c85abf29441645039b6a554a50e1d3274229de6 100644 +index 18a700ffb88f0c60beb2ba4d6e3554ef762a9455..0542b61053ed7039e54856eab86ba5842403e4fc 100644 --- a/src/main/java/net/minecraft/server/PlayerAdvancements.java +++ b/src/main/java/net/minecraft/server/PlayerAdvancements.java @@ -225,6 +225,12 @@ public class PlayerAdvancements { diff --git a/patches/server/0169-Add-ArmorStand-Item-Meta.patch b/patches/server/0169-Add-ArmorStand-Item-Meta.patch index 490b0155ea39..7e6ae430c931 100644 --- a/patches/server/0169-Add-ArmorStand-Item-Meta.patch +++ b/patches/server/0169-Add-ArmorStand-Item-Meta.patch @@ -13,7 +13,7 @@ starting point for future additions in this area. Fixes GH-559 diff --git a/src/main/java/org/bukkit/craftbukkit/inventory/CraftItemMetas.java b/src/main/java/org/bukkit/craftbukkit/inventory/CraftItemMetas.java -index eef3517833ff5c0cf41b89973ebc972b8ed31e0f..c9fbc01be0b0e7fd1cafb091d06496f4ba1e7c2c 100644 +index 7e3199567f25cc65046ba7374fe1a83fd2e5b191..4f5568707d725195ee19b65274454fec8bf99d64 100644 --- a/src/main/java/org/bukkit/craftbukkit/inventory/CraftItemMetas.java +++ b/src/main/java/org/bukkit/craftbukkit/inventory/CraftItemMetas.java @@ -1,5 +1,6 @@ @@ -23,7 +23,7 @@ index eef3517833ff5c0cf41b89973ebc972b8ed31e0f..c9fbc01be0b0e7fd1cafb091d06496f4 import java.util.function.BiFunction; import java.util.function.Function; import net.minecraft.world.item.BannerItem; -@@ -102,7 +103,7 @@ public final class CraftItemMetas { +@@ -103,7 +104,7 @@ public final class CraftItemMetas { item -> new CraftMetaSpawnEgg(item.getComponentsPatch()), (type, meta) -> meta instanceof CraftMetaSpawnEgg spawnEgg ? spawnEgg : new CraftMetaSpawnEgg(meta)); diff --git a/patches/server/0170-Extend-Player-Interact-cancellation.patch b/patches/server/0170-Extend-Player-Interact-cancellation.patch index 0317d6e0e452..430e2a5e628b 100644 --- a/patches/server/0170-Extend-Player-Interact-cancellation.patch +++ b/patches/server/0170-Extend-Player-Interact-cancellation.patch @@ -10,10 +10,10 @@ Flower pots are also not updated on the client when interaction is cancelled, th also resolves this. diff --git a/src/main/java/net/minecraft/server/level/ServerPlayerGameMode.java b/src/main/java/net/minecraft/server/level/ServerPlayerGameMode.java -index 2fab84c5e2dc4de39281956390588a9a71d02f68..a5b0efd6142075ca1ecb604afbc1d0162199e7a4 100644 +index 73b6aa34ad2579d79f388c5660cdfbef41a769f2..4c8189a2a7edea824545a24dccb376b8eceac001 100644 --- a/src/main/java/net/minecraft/server/level/ServerPlayerGameMode.java +++ b/src/main/java/net/minecraft/server/level/ServerPlayerGameMode.java -@@ -519,7 +519,11 @@ public class ServerPlayerGameMode { +@@ -525,7 +525,11 @@ public class ServerPlayerGameMode { // send a correcting update to the client for the block above as well, this because of replaceable blocks (such as grass, sea grass etc) player.connection.send(new ClientboundBlockUpdatePacket(world, blockposition.above())); diff --git a/patches/server/0172-Toggleable-player-crits.patch b/patches/server/0172-Toggleable-player-crits.patch index 03d3f5c4f1e0..3a3f237d5736 100644 --- a/patches/server/0172-Toggleable-player-crits.patch +++ b/patches/server/0172-Toggleable-player-crits.patch @@ -5,10 +5,10 @@ Subject: [PATCH] Toggleable player crits diff --git a/src/main/java/net/minecraft/world/entity/player/Player.java b/src/main/java/net/minecraft/world/entity/player/Player.java -index 34654395536ea022848db3d9f0291512081fc558..3f0425f6c1a8d5ad4df9d837490c09e175d0df4b 100644 +index 1cc11284b0e155ea41c198641b3644028adda97d..35cb06706e1584cd6d25aa68ff5c9b40a5768e03 100644 --- a/src/main/java/net/minecraft/world/entity/player/Player.java +++ b/src/main/java/net/minecraft/world/entity/player/Player.java -@@ -1266,6 +1266,7 @@ public abstract class Player extends LivingEntity { +@@ -1236,6 +1236,7 @@ public abstract class Player extends LivingEntity { f += itemstack.getItem().getAttackDamageBonus(target, f, damagesource); boolean flag2 = flag && this.fallDistance > 0.0F && !this.onGround() && !this.onClimbable() && !this.isInWater() && !this.hasEffect(MobEffects.BLINDNESS) && !this.isPassenger() && target instanceof LivingEntity && !this.isSprinting(); diff --git a/patches/server/0174-Implement-extended-PaperServerListPingEvent.patch b/patches/server/0174-Implement-extended-PaperServerListPingEvent.patch index 79d1bf0a285d..e0dbee4fe8c0 100644 --- a/patches/server/0174-Implement-extended-PaperServerListPingEvent.patch +++ b/patches/server/0174-Implement-extended-PaperServerListPingEvent.patch @@ -170,7 +170,7 @@ index 0000000000000000000000000000000000000000..30a19d10869f73d67b794e8e4c035bc5 + +} diff --git a/src/main/java/net/minecraft/server/MinecraftServer.java b/src/main/java/net/minecraft/server/MinecraftServer.java -index 5d03477919ce892f6dfb4b2304c03733e10e3721..cc0968182ab597892dbae8dd9b3e803fb62b7065 100644 +index 0adba72139779a20eed8f489f7cfaff9e84e24f4..260d755666efc94e2ea2c8fdb38d7deddda82c08 100644 --- a/src/main/java/net/minecraft/server/MinecraftServer.java +++ b/src/main/java/net/minecraft/server/MinecraftServer.java @@ -3,6 +3,9 @@ package net.minecraft.server; @@ -183,7 +183,7 @@ index 5d03477919ce892f6dfb4b2304c03733e10e3721..cc0968182ab597892dbae8dd9b3e803f import com.google.common.collect.Lists; import com.google.common.collect.Maps; import com.google.common.collect.Sets; -@@ -1515,7 +1518,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop Co-authored-by: MCMDEV diff --git a/src/main/java/net/minecraft/server/network/ServerLoginPacketListenerImpl.java b/src/main/java/net/minecraft/server/network/ServerLoginPacketListenerImpl.java -index bfda68254b39f301ba2d3d70beeb35317d262c43..30e55f693b963496b85afa32da9c15cacb738836 100644 +index c5737e1c8cb82f282bab2e6b7b6c26aa077801f3..4a62c1abc6f3c48bbda40325b4ce46632db3f28d 100644 --- a/src/main/java/net/minecraft/server/network/ServerLoginPacketListenerImpl.java +++ b/src/main/java/net/minecraft/server/network/ServerLoginPacketListenerImpl.java @@ -191,7 +191,7 @@ public class ServerLoginPacketListenerImpl implements ServerLoginPacketListener, diff --git a/patches/server/0176-Player.setPlayerProfile-API.patch b/patches/server/0176-Player.setPlayerProfile-API.patch index b118853bc7f8..d971b0d45541 100644 --- a/patches/server/0176-Player.setPlayerProfile-API.patch +++ b/patches/server/0176-Player.setPlayerProfile-API.patch @@ -8,21 +8,8 @@ This can be useful for changing name or skins after a player has logged in. == AT == public-f net.minecraft.world.entity.player.Player gameProfile -diff --git a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java -index 6cdbd0281e38d7107f239e6e052c08e4ab12b552..0073c6c5433be3193a01257a26c7035e544f37dd 100644 ---- a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java -+++ b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java -@@ -1472,7 +1472,7 @@ public class ServerGamePacketListenerImpl extends ServerCommonPacketListenerImpl - this.internalTeleport(dest.getX(), dest.getY(), dest.getZ(), dest.getYaw(), dest.getPitch(), Collections.emptySet()); - } - -- private void internalTeleport(double d0, double d1, double d2, float f, float f1, Set set) { -+ public void internalTeleport(double d0, double d1, double d2, float f, float f1, Set set) { // Paper - // CraftBukkit start - if (Float.isNaN(f)) { - f = 0; diff --git a/src/main/java/net/minecraft/server/network/ServerLoginPacketListenerImpl.java b/src/main/java/net/minecraft/server/network/ServerLoginPacketListenerImpl.java -index 30e55f693b963496b85afa32da9c15cacb738836..636b8aef2348fa4cfe63a9b7d77a64b14dc7a42c 100644 +index 4a62c1abc6f3c48bbda40325b4ce46632db3f28d..bab8c53041afb9606db55923e5466eab25640226 100644 --- a/src/main/java/net/minecraft/server/network/ServerLoginPacketListenerImpl.java +++ b/src/main/java/net/minecraft/server/network/ServerLoginPacketListenerImpl.java @@ -341,11 +341,11 @@ public class ServerLoginPacketListenerImpl implements ServerLoginPacketListener, @@ -40,10 +27,10 @@ index 30e55f693b963496b85afa32da9c15cacb738836..636b8aef2348fa4cfe63a9b7d77a64b1 playerName = gameprofile.getName(); uniqueId = gameprofile.getId(); diff --git a/src/main/java/net/minecraft/server/players/PlayerList.java b/src/main/java/net/minecraft/server/players/PlayerList.java -index c08ffdbc9afb2fe7abbf5567dc1fb1e2bcb01b96..3ee00025eb9d936b9780b25fdc25d144acc496cd 100644 +index 5cb84271abdabc0f2d44ad9425828431f8818a47..a929c809dc1dcb2bdab4db0d2a8ca794189e93d9 100644 --- a/src/main/java/net/minecraft/server/players/PlayerList.java +++ b/src/main/java/net/minecraft/server/players/PlayerList.java -@@ -831,10 +831,16 @@ public abstract class PlayerList { +@@ -799,10 +799,16 @@ public abstract class PlayerList { } public void sendPlayerPermissionLevel(ServerPlayer player) { @@ -77,10 +64,10 @@ index 818df09e9245b5d89b4180b1eaa51470b7539341..f6b2ca92fd3510a76cbf56d0ea55aa6c public Server getServer() { diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java -index 8521f728088d16ecbaa0119983a9f12a649ac847..9136feac48244dc68bff92b52643f2adefba5205 100644 +index 128fcd537783986d816dae6d1ce2afb7af07d45a..32eeca2467189c6c97f7da5529d4fe9375e8a848 100644 --- a/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java +++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java -@@ -249,11 +249,6 @@ public class CraftPlayer extends CraftHumanEntity implements Player { +@@ -254,11 +254,6 @@ public class CraftPlayer extends CraftHumanEntity implements Player { return this.server.getPlayer(this.getUniqueId()) != null; } @@ -92,7 +79,7 @@ index 8521f728088d16ecbaa0119983a9f12a649ac847..9136feac48244dc68bff92b52643f2ad @Override public InetSocketAddress getAddress() { if (this.getHandle().connection.protocol() == null) return null; -@@ -1792,8 +1787,15 @@ public class CraftPlayer extends CraftHumanEntity implements Player { +@@ -1819,8 +1814,15 @@ public class CraftPlayer extends CraftHumanEntity implements Player { private void untrackAndHideEntity(org.bukkit.entity.Entity entity) { // Remove this entity from the hidden player's EntityTrackerEntry @@ -109,7 +96,7 @@ index 8521f728088d16ecbaa0119983a9f12a649ac847..9136feac48244dc68bff92b52643f2ad ChunkMap.TrackedEntity entry = tracker.entityMap.get(other.getId()); if (entry != null) { entry.removePlayer(this.getHandle()); -@@ -1806,8 +1808,6 @@ public class CraftPlayer extends CraftHumanEntity implements Player { +@@ -1833,8 +1835,6 @@ public class CraftPlayer extends CraftHumanEntity implements Player { this.getHandle().connection.send(new ClientboundPlayerInfoRemovePacket(List.of(otherPlayer.getUUID()))); } } @@ -118,7 +105,7 @@ index 8521f728088d16ecbaa0119983a9f12a649ac847..9136feac48244dc68bff92b52643f2ad } void resetAndHideEntity(org.bukkit.entity.Entity entity) { -@@ -1872,12 +1872,25 @@ public class CraftPlayer extends CraftHumanEntity implements Player { +@@ -1899,12 +1899,25 @@ public class CraftPlayer extends CraftHumanEntity implements Player { } private void trackAndShowEntity(org.bukkit.entity.Entity entity) { @@ -144,7 +131,7 @@ index 8521f728088d16ecbaa0119983a9f12a649ac847..9136feac48244dc68bff92b52643f2ad } ChunkMap.TrackedEntity entry = tracker.entityMap.get(other.getId()); -@@ -1887,6 +1900,39 @@ public class CraftPlayer extends CraftHumanEntity implements Player { +@@ -1914,6 +1927,39 @@ public class CraftPlayer extends CraftHumanEntity implements Player { this.server.getPluginManager().callEvent(new PlayerShowEntityEvent(this, entity)); } @@ -184,7 +171,7 @@ index 8521f728088d16ecbaa0119983a9f12a649ac847..9136feac48244dc68bff92b52643f2ad void resetAndShowEntity(org.bukkit.entity.Entity entity) { // SPIGOT-7312: Can't show/hide self -@@ -1898,6 +1944,34 @@ public class CraftPlayer extends CraftHumanEntity implements Player { +@@ -1925,6 +1971,34 @@ public class CraftPlayer extends CraftHumanEntity implements Player { this.trackAndShowEntity(entity); } } @@ -203,7 +190,7 @@ index 8521f728088d16ecbaa0119983a9f12a649ac847..9136feac48244dc68bff92b52643f2ad + ServerLevel worldserver = handle.serverLevel(); + connection.send(new net.minecraft.network.protocol.game.ClientboundRespawnPacket(handle.createCommonSpawnInfo(worldserver), net.minecraft.network.protocol.game.ClientboundRespawnPacket.KEEP_ALL_DATA)); + handle.onUpdateAbilities(); -+ connection.internalTeleport(loc.getX(), loc.getY(), loc.getZ(), loc.getYaw(), loc.getPitch(), java.util.Collections.emptySet()); ++ connection.internalTeleport(net.minecraft.world.entity.PositionMoveRotation.of(this.getHandle()), java.util.Collections.emptySet()); + net.minecraft.server.players.PlayerList playerList = handle.server.getPlayerList(); + playerList.sendPlayerPermissionLevel(handle, false); + playerList.sendLevelInfo(handle, worldserver); diff --git a/patches/server/0177-getPlayerUniqueId-API.patch b/patches/server/0177-getPlayerUniqueId-API.patch index 968566c3a4af..c4f1f1285d44 100644 --- a/patches/server/0177-getPlayerUniqueId-API.patch +++ b/patches/server/0177-getPlayerUniqueId-API.patch @@ -9,10 +9,10 @@ In Offline Mode, will return an Offline UUID This is a more performant way to obtain a UUID for a name than loading an OfflinePlayer diff --git a/src/main/java/org/bukkit/craftbukkit/CraftServer.java b/src/main/java/org/bukkit/craftbukkit/CraftServer.java -index 39e3730eae983c20522b97fcd547823cd192cb2d..a94b972e328d2eff635de95847dc622c3a58fd9f 100644 +index 1dd440057cb1d8acce7bfaa7ab1814937a4c63af..6bb1d3f3548c382379323059457844978438f802 100644 --- a/src/main/java/org/bukkit/craftbukkit/CraftServer.java +++ b/src/main/java/org/bukkit/craftbukkit/CraftServer.java -@@ -1888,6 +1888,25 @@ public final class CraftServer implements Server { +@@ -1891,6 +1891,25 @@ public final class CraftServer implements Server { return recipients.size(); } diff --git a/patches/server/0178-Improved-Async-Task-Scheduler.patch b/patches/server/0178-Improved-Async-Task-Scheduler.patch index a1211ae89e80..d53657768ca0 100644 --- a/patches/server/0178-Improved-Async-Task-Scheduler.patch +++ b/patches/server/0178-Improved-Async-Task-Scheduler.patch @@ -32,7 +32,7 @@ operations are decoupled from the sync tasks queue. diff --git a/src/main/java/org/bukkit/craftbukkit/scheduler/CraftAsyncScheduler.java b/src/main/java/org/bukkit/craftbukkit/scheduler/CraftAsyncScheduler.java new file mode 100644 -index 0000000000000000000000000000000000000000..3c1992e212a6d6f1db4d5b807b38d71913619fc0 +index 0000000000000000000000000000000000000000..0ca279fb71d39c81b1f608e0ee9ba3e498d55fa3 --- /dev/null +++ b/src/main/java/org/bukkit/craftbukkit/scheduler/CraftAsyncScheduler.java @@ -0,0 +1,122 @@ @@ -106,8 +106,8 @@ index 0000000000000000000000000000000000000000..3c1992e212a6d6f1db4d5b807b38d719 + } + + @Override -+ public void mainThreadHeartbeat(int currentTick) { -+ this.currentTick = currentTick; ++ public void mainThreadHeartbeat() { ++ this.currentTick++; + this.management.execute(() -> this.runTasks(currentTick)); + } + @@ -159,10 +159,10 @@ index 0000000000000000000000000000000000000000..3c1992e212a6d6f1db4d5b807b38d719 + } +} diff --git a/src/main/java/org/bukkit/craftbukkit/scheduler/CraftScheduler.java b/src/main/java/org/bukkit/craftbukkit/scheduler/CraftScheduler.java -index f1145585eed18be0aa5c795a50589103fdc9cc2f..02835e4f0a0b262af27acff0939c981cae728db4 100644 +index 300d31e31a55dbee3489320e21e42f14ac429478..7de4db0099b380c81d6a809a298d580f0f6e4acc 100644 --- a/src/main/java/org/bukkit/craftbukkit/scheduler/CraftScheduler.java +++ b/src/main/java/org/bukkit/craftbukkit/scheduler/CraftScheduler.java -@@ -76,7 +76,7 @@ public class CraftScheduler implements BukkitScheduler { +@@ -75,7 +75,7 @@ public class CraftScheduler implements BukkitScheduler { /** * Main thread logic only */ @@ -171,7 +171,7 @@ index f1145585eed18be0aa5c795a50589103fdc9cc2f..02835e4f0a0b262af27acff0939c981c new Comparator() { @Override public int compare(final CraftTask o1, final CraftTask o2) { -@@ -93,12 +93,13 @@ public class CraftScheduler implements BukkitScheduler { +@@ -92,12 +92,13 @@ public class CraftScheduler implements BukkitScheduler { /** * These are tasks that are currently active. It's provided for 'viewing' the current state. */ @@ -187,7 +187,7 @@ index f1145585eed18be0aa5c795a50589103fdc9cc2f..02835e4f0a0b262af27acff0939c981c private final Executor executor = Executors.newCachedThreadPool(new ThreadFactoryBuilder().setNameFormat("Craft Scheduler Thread - %d").build()); private CraftAsyncDebugger debugHead = new CraftAsyncDebugger(-1, null, null) { @Override -@@ -107,12 +108,31 @@ public class CraftScheduler implements BukkitScheduler { +@@ -106,12 +107,31 @@ public class CraftScheduler implements BukkitScheduler { } }; private CraftAsyncDebugger debugTail = this.debugHead; @@ -219,7 +219,7 @@ index f1145585eed18be0aa5c795a50589103fdc9cc2f..02835e4f0a0b262af27acff0939c981c @Override public int scheduleSyncDelayedTask(final Plugin plugin, final Runnable task) { return this.scheduleSyncDelayedTask(plugin, task, 0L); -@@ -229,7 +249,7 @@ public class CraftScheduler implements BukkitScheduler { +@@ -228,7 +248,7 @@ public class CraftScheduler implements BukkitScheduler { } else if (period < CraftTask.NO_REPEATING) { period = CraftTask.NO_REPEATING; } @@ -228,7 +228,7 @@ index f1145585eed18be0aa5c795a50589103fdc9cc2f..02835e4f0a0b262af27acff0939c981c } @Override -@@ -245,6 +265,11 @@ public class CraftScheduler implements BukkitScheduler { +@@ -244,6 +264,11 @@ public class CraftScheduler implements BukkitScheduler { if (taskId <= 0) { return; } @@ -240,7 +240,7 @@ index f1145585eed18be0aa5c795a50589103fdc9cc2f..02835e4f0a0b262af27acff0939c981c CraftTask task = this.runners.get(taskId); if (task != null) { task.cancel0(); -@@ -287,6 +312,11 @@ public class CraftScheduler implements BukkitScheduler { +@@ -286,6 +311,11 @@ public class CraftScheduler implements BukkitScheduler { @Override public void cancelTasks(final Plugin plugin) { Preconditions.checkArgument(plugin != null, "Cannot cancel tasks of null plugin"); @@ -252,7 +252,7 @@ index f1145585eed18be0aa5c795a50589103fdc9cc2f..02835e4f0a0b262af27acff0939c981c final CraftTask task = new CraftTask( new Runnable() { @Override -@@ -326,6 +356,13 @@ public class CraftScheduler implements BukkitScheduler { +@@ -325,6 +355,13 @@ public class CraftScheduler implements BukkitScheduler { @Override public boolean isCurrentlyRunning(final int taskId) { @@ -266,7 +266,7 @@ index f1145585eed18be0aa5c795a50589103fdc9cc2f..02835e4f0a0b262af27acff0939c981c final CraftTask task = this.runners.get(taskId); if (task == null) { return false; -@@ -344,6 +381,11 @@ public class CraftScheduler implements BukkitScheduler { +@@ -343,6 +380,11 @@ public class CraftScheduler implements BukkitScheduler { if (taskId <= 0) { return false; } @@ -278,7 +278,7 @@ index f1145585eed18be0aa5c795a50589103fdc9cc2f..02835e4f0a0b262af27acff0939c981c for (CraftTask task = this.head.getNext(); task != null; task = task.getNext()) { if (task.getTaskId() == taskId) { return task.getPeriod() >= CraftTask.NO_REPEATING; // The task will run -@@ -355,6 +397,12 @@ public class CraftScheduler implements BukkitScheduler { +@@ -354,6 +396,12 @@ public class CraftScheduler implements BukkitScheduler { @Override public List getActiveWorkers() { @@ -291,7 +291,7 @@ index f1145585eed18be0aa5c795a50589103fdc9cc2f..02835e4f0a0b262af27acff0939c981c final ArrayList workers = new ArrayList(); for (final CraftTask taskObj : this.runners.values()) { // Iterator will be a best-effort (may fail to grab very new values) if called from an async thread -@@ -392,6 +440,11 @@ public class CraftScheduler implements BukkitScheduler { +@@ -391,6 +439,11 @@ public class CraftScheduler implements BukkitScheduler { pending.add(task); } } @@ -304,28 +304,28 @@ index f1145585eed18be0aa5c795a50589103fdc9cc2f..02835e4f0a0b262af27acff0939c981c } @@ -399,6 +452,11 @@ public class CraftScheduler implements BukkitScheduler { - * This method is designed to never block or wait for locks; an immediate execution of all current tasks. */ - public void mainThreadHeartbeat(final int currentTick) { + public void mainThreadHeartbeat() { + this.currentTick++; + // Paper start + if (!this.isAsyncScheduler) { -+ this.asyncScheduler.mainThreadHeartbeat(currentTick); ++ this.asyncScheduler.mainThreadHeartbeat(); + } + // Paper end - this.currentTick = currentTick; final List temp = this.temp; this.parsePending(); -@@ -434,7 +492,7 @@ public class CraftScheduler implements BukkitScheduler { + while (this.isReady(this.currentTick)) { +@@ -433,7 +491,7 @@ public class CraftScheduler implements BukkitScheduler { this.parsePending(); } else { - // this.debugTail = this.debugTail.setNext(new CraftAsyncDebugger(currentTick + CraftScheduler.RECENT_TICKS, task.getOwner(), task.getTaskClass())); // Paper + // this.debugTail = this.debugTail.setNext(new CraftAsyncDebugger(this.currentTick + CraftScheduler.RECENT_TICKS, task.getOwner(), task.getTaskClass())); // Paper - this.executor.execute(new com.destroystokyo.paper.ServerSchedulerReportingWrapper(task)); // Paper + task.getOwner().getLogger().log(Level.SEVERE, "Unexpected Async Task in the Sync Scheduler. Report this to Paper"); // Paper // We don't need to parse pending // (async tasks must live with race-conditions if they attempt to cancel between these few lines of code) } -@@ -453,7 +511,7 @@ public class CraftScheduler implements BukkitScheduler { - //this.debugHead = this.debugHead.getNextHead(currentTick); // Paper +@@ -450,7 +508,7 @@ public class CraftScheduler implements BukkitScheduler { + //this.debugHead = this.debugHead.getNextHead(this.currentTick); // Paper } - private void addTask(final CraftTask task) { @@ -333,7 +333,7 @@ index f1145585eed18be0aa5c795a50589103fdc9cc2f..02835e4f0a0b262af27acff0939c981c final AtomicReference tail = this.tail; CraftTask tailTask = tail.get(); while (!tail.compareAndSet(tailTask, task)) { -@@ -462,7 +520,13 @@ public class CraftScheduler implements BukkitScheduler { +@@ -459,7 +517,13 @@ public class CraftScheduler implements BukkitScheduler { tailTask.setNext(task); } @@ -348,23 +348,12 @@ index f1145585eed18be0aa5c795a50589103fdc9cc2f..02835e4f0a0b262af27acff0939c981c task.setNextRun(this.currentTick + delay); this.addTask(task); return task; -@@ -485,8 +549,8 @@ public class CraftScheduler implements BukkitScheduler { +@@ -482,7 +546,7 @@ public class CraftScheduler implements BukkitScheduler { return id; } - private void parsePending() { -- MinecraftTimings.bukkitSchedulerPendingTimer.startTiming(); + void parsePending() { // Paper -+ if (!this.isAsyncScheduler) MinecraftTimings.bukkitSchedulerPendingTimer.startTiming(); // Paper CraftTask head = this.head; CraftTask task = head.getNext(); CraftTask lastTask = head; -@@ -505,7 +569,7 @@ public class CraftScheduler implements BukkitScheduler { - task.setNext(null); - } - this.head = lastTask; -- MinecraftTimings.bukkitSchedulerPendingTimer.stopTiming(); -+ if (!this.isAsyncScheduler) MinecraftTimings.bukkitSchedulerPendingTimer.stopTiming(); // Paper - } - - private boolean isReady(final int currentTick) { diff --git a/patches/server/0181-Flag-to-disable-the-channel-limit.patch b/patches/server/0181-Flag-to-disable-the-channel-limit.patch index 0745b45aae29..867f7c6540e4 100644 --- a/patches/server/0181-Flag-to-disable-the-channel-limit.patch +++ b/patches/server/0181-Flag-to-disable-the-channel-limit.patch @@ -9,10 +9,10 @@ e.g. servers which allow and support the usage of mod packs. provide an optional flag to disable this check, at your own risk. diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java -index 422c25577a0d95b31b5528fad8fc9b3ae97fa7f0..4e5dba1da323f12d77a36635c9227b1239856254 100644 +index 32eeca2467189c6c97f7da5529d4fe9375e8a848..a4333eae8aa2ae3fefdc8a765a4434c36e123b12 100644 --- a/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java +++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java -@@ -209,6 +209,7 @@ public class CraftPlayer extends CraftHumanEntity implements Player { +@@ -214,6 +214,7 @@ public class CraftPlayer extends CraftHumanEntity implements Player { private CraftWorldBorder clientWorldBorder = null; private BorderChangeListener clientWorldBorderListener = this.createWorldBorderListener(); public org.bukkit.event.player.PlayerResourcePackStatusEvent.Status resourcePackStatus; // Paper - more resource pack API @@ -20,7 +20,7 @@ index 422c25577a0d95b31b5528fad8fc9b3ae97fa7f0..4e5dba1da323f12d77a36635c9227b12 public CraftPlayer(CraftServer server, ServerPlayer entity) { super(server, entity); -@@ -2269,7 +2270,7 @@ public class CraftPlayer extends CraftHumanEntity implements Player { +@@ -2296,7 +2297,7 @@ public class CraftPlayer extends CraftHumanEntity implements Player { } public void addChannel(String channel) { diff --git a/patches/server/0182-Add-openSign-method-to-HumanEntity.patch b/patches/server/0182-Add-openSign-method-to-HumanEntity.patch index 51a6489c0670..c90098c049bc 100644 --- a/patches/server/0182-Add-openSign-method-to-HumanEntity.patch +++ b/patches/server/0182-Add-openSign-method-to-HumanEntity.patch @@ -5,10 +5,10 @@ Subject: [PATCH] Add openSign method to HumanEntity diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftHumanEntity.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftHumanEntity.java -index f0efea03165039525a98dc30c34d876972d9fe71..e59fee587c2d90df5a6aa7a3df0eefc0cb5165ac 100644 +index 768a6e3f5d75d37ae114ffcf2b090fe9de769381..6d4e0a90c70f7a66450cbb18ebec1d7bf9200af2 100644 --- a/src/main/java/org/bukkit/craftbukkit/entity/CraftHumanEntity.java +++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftHumanEntity.java -@@ -630,6 +630,12 @@ public class CraftHumanEntity extends CraftLivingEntity implements HumanEntity { +@@ -654,6 +654,12 @@ public class CraftHumanEntity extends CraftLivingEntity implements HumanEntity { } } diff --git a/patches/server/0183-Configurable-sprint-interruption-on-attack.patch b/patches/server/0183-Configurable-sprint-interruption-on-attack.patch index 65dd42f5eb59..1fe54fe26f90 100644 --- a/patches/server/0183-Configurable-sprint-interruption-on-attack.patch +++ b/patches/server/0183-Configurable-sprint-interruption-on-attack.patch @@ -6,10 +6,10 @@ Subject: [PATCH] Configurable sprint interruption on attack If the sprint interruption is disabled players continue sprinting when they attack entities. diff --git a/src/main/java/net/minecraft/world/entity/player/Player.java b/src/main/java/net/minecraft/world/entity/player/Player.java -index 3f0425f6c1a8d5ad4df9d837490c09e175d0df4b..00fecf255214d87837867b8743cc8f161ebfcb4c 100644 +index 35cb06706e1584cd6d25aa68ff5c9b40a5768e03..605f043bb132b21ff263ba7ab31a0f468a912d13 100644 --- a/src/main/java/net/minecraft/world/entity/player/Player.java +++ b/src/main/java/net/minecraft/world/entity/player/Player.java -@@ -1307,7 +1307,11 @@ public abstract class Player extends LivingEntity { +@@ -1277,7 +1277,11 @@ public abstract class Player extends LivingEntity { } this.setDeltaMovement(this.getDeltaMovement().multiply(0.6D, 1.0D, 0.6D)); diff --git a/patches/server/0184-EndermanEscapeEvent.patch b/patches/server/0184-EndermanEscapeEvent.patch index 9a4685f86876..cd9bf53000c6 100644 --- a/patches/server/0184-EndermanEscapeEvent.patch +++ b/patches/server/0184-EndermanEscapeEvent.patch @@ -8,7 +8,7 @@ Fires an event anytime an enderman intends to teleport away from the player You may cancel this, enabling ranged attacks to damage the enderman for example. diff --git a/src/main/java/net/minecraft/world/entity/monster/EnderMan.java b/src/main/java/net/minecraft/world/entity/monster/EnderMan.java -index bbe024a88d0feeb02a5cc4248f4bcdd3a06daf04..51e9988685b1a9a3d4d2effec63560b0ae9e8d3a 100644 +index a6c9ca2928216ed89f69c1d140590d3368b354d6..d7145b3a19402a03d0af02822ffcb0ef07eaa663 100644 --- a/src/main/java/net/minecraft/world/entity/monster/EnderMan.java +++ b/src/main/java/net/minecraft/world/entity/monster/EnderMan.java @@ -121,6 +121,12 @@ public class EnderMan extends Monster implements NeutralMob { @@ -24,18 +24,18 @@ index bbe024a88d0feeb02a5cc4248f4bcdd3a06daf04..51e9988685b1a9a3d4d2effec63560b0 @Override public boolean setTarget(LivingEntity entityliving, EntityTargetEvent.TargetReason reason, boolean fireEvent) { if (!super.setTarget(entityliving, reason, fireEvent)) { -@@ -270,7 +276,7 @@ public class EnderMan extends Monster implements NeutralMob { - if (this.level().isDay() && this.tickCount >= this.targetChangeTime + 600) { +@@ -257,7 +263,7 @@ public class EnderMan extends Monster implements NeutralMob { + if (world.isDay() && this.tickCount >= this.targetChangeTime + 600) { float f = this.getLightLevelDependentMagicValue(); -- if (f > 0.5F && this.level().canSeeSky(this.blockPosition()) && this.random.nextFloat() * 30.0F < (f - 0.4F) * 2.0F) { -+ if (f > 0.5F && this.level().canSeeSky(this.blockPosition()) && this.random.nextFloat() * 30.0F < (f - 0.4F) * 2.0F && this.tryEscape(com.destroystokyo.paper.event.entity.EndermanEscapeEvent.Reason.RUNAWAY)) { // Paper - EndermanEscapeEvent +- if (f > 0.5F && world.canSeeSky(this.blockPosition()) && this.random.nextFloat() * 30.0F < (f - 0.4F) * 2.0F) { ++ if (f > 0.5F && world.canSeeSky(this.blockPosition()) && this.random.nextFloat() * 30.0F < (f - 0.4F) * 2.0F && this.tryEscape(com.destroystokyo.paper.event.entity.EndermanEscapeEvent.Reason.RUNAWAY)) { // Paper - EndermanEscapeEvent this.setTarget((LivingEntity) null); this.teleport(); } -@@ -396,11 +402,13 @@ public class EnderMan extends Monster implements NeutralMob { +@@ -383,11 +389,13 @@ public class EnderMan extends Monster implements NeutralMob { } else { - flag1 = flag && this.hurtWithCleanWater(source, (ThrownPotion) source.getDirectEntity(), amount); + flag1 = flag && this.hurtWithCleanWater(world, source, (ThrownPotion) source.getDirectEntity(), amount); + if (this.tryEscape(com.destroystokyo.paper.event.entity.EndermanEscapeEvent.Reason.INDIRECT)) { // Paper - EndermanEscapeEvent for (int i = 0; i < 64; ++i) { @@ -47,10 +47,10 @@ index bbe024a88d0feeb02a5cc4248f4bcdd3a06daf04..51e9988685b1a9a3d4d2effec63560b0 return flag1; } -@@ -625,7 +633,7 @@ public class EnderMan extends Monster implements NeutralMob { +@@ -612,7 +620,7 @@ public class EnderMan extends Monster implements NeutralMob { } else { if (this.target != null && !this.enderman.isPassenger()) { - if (this.enderman.isLookingAtMe((Player) this.target)) { + if (this.enderman.isBeingStaredBy((Player) this.target)) { - if (this.target.distanceToSqr((Entity) this.enderman) < 16.0D) { + if (this.target.distanceToSqr((Entity) this.enderman) < 16.0D && this.enderman.tryEscape(com.destroystokyo.paper.event.entity.EndermanEscapeEvent.Reason.STARE)) { // Paper - EndermanEscapeEvent this.enderman.teleport(); diff --git a/patches/server/0186-Block-Enderpearl-Travel-Exploit.patch b/patches/server/0186-Block-Enderpearl-Travel-Exploit.patch deleted file mode 100644 index c538b3c38a80..000000000000 --- a/patches/server/0186-Block-Enderpearl-Travel-Exploit.patch +++ /dev/null @@ -1,46 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Aikar -Date: Mon, 30 Apr 2018 17:15:26 -0400 -Subject: [PATCH] Block Enderpearl Travel Exploit - -Players are able to use alt accounts and enderpearls to travel -long distances utilizing the pearls in unloaded chunks and loading -the chunk later when convenient. - -This disables that by not saving the thrower when the chunk is unloaded. - -This is mainly useful for survival servers that do not allow freeform teleporting. - -== AT == -public net.minecraft.world.entity.projectile.Projectile cachedOwner -public net.minecraft.world.entity.projectile.Projectile ownerUUID - -diff --git a/src/main/java/net/minecraft/server/level/ServerLevel.java b/src/main/java/net/minecraft/server/level/ServerLevel.java -index d728afbe1d6882f1ace4ead9d87f4b7d2af43ba2..7c6b094de5e29cb2663e8a7072a27d7adbceb4b2 100644 ---- a/src/main/java/net/minecraft/server/level/ServerLevel.java -+++ b/src/main/java/net/minecraft/server/level/ServerLevel.java -@@ -2111,6 +2111,12 @@ public class ServerLevel extends Level implements WorldGenLevel { - - public void onTickingEnd(Entity entity) { - ServerLevel.this.entityTickList.remove(entity); -+ // Paper start - Reset pearls when they stop being ticked -+ if (paperConfig().fixes.disableUnloadedChunkEnderpearlExploit && entity instanceof net.minecraft.world.entity.projectile.ThrownEnderpearl pearl) { -+ pearl.cachedOwner = null; -+ pearl.ownerUUID = null; -+ } -+ // Paper end - Reset pearls when they stop being ticked - } - - public void onTrackingStart(Entity entity) { -diff --git a/src/main/java/net/minecraft/world/entity/projectile/Projectile.java b/src/main/java/net/minecraft/world/entity/projectile/Projectile.java -index add45e6e95688649ba4b8d67747f763a261da51e..a39de724848d6dc796dd99dde5206f20e513fd18 100644 ---- a/src/main/java/net/minecraft/world/entity/projectile/Projectile.java -+++ b/src/main/java/net/minecraft/world/entity/projectile/Projectile.java -@@ -108,6 +108,7 @@ public abstract class Projectile extends Entity implements TraceableEntity { - if (nbt.hasUUID("Owner")) { - this.ownerUUID = nbt.getUUID("Owner"); - this.cachedOwner = null; -+ if (this instanceof ThrownEnderpearl && this.level() != null && this.level().paperConfig().fixes.disableUnloadedChunkEnderpearlExploit) { this.ownerUUID = null; } // Paper - Reset pearls when they stop being ticked; Don't store shooter name for pearls to block enderpearl travel exploit - } - - this.leftOwner = nbt.getBoolean("LeftOwner"); diff --git a/patches/server/0186-Expand-World.spawnParticle-API-and-add-Builder.patch b/patches/server/0186-Expand-World.spawnParticle-API-and-add-Builder.patch new file mode 100644 index 000000000000..da14ccdaa5bd --- /dev/null +++ b/patches/server/0186-Expand-World.spawnParticle-API-and-add-Builder.patch @@ -0,0 +1,60 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Aikar +Date: Tue, 15 Aug 2017 22:29:12 -0400 +Subject: [PATCH] Expand World.spawnParticle API and add Builder + +Adds ability to control who receives it and who is the source/sender (vanish API) +the standard API is to send the packet to everyone in the world, which is ineffecient. +Adds an option to control the force mode of the particle. + +This adds a new Builder API which is much friendlier to use. + +diff --git a/src/main/java/net/minecraft/server/level/ServerLevel.java b/src/main/java/net/minecraft/server/level/ServerLevel.java +index 88c93a2e67c8d1bc227c7fa35bb919a40009f931..0c0d19708832a49734ea08ae05696e0cb20616e4 100644 +--- a/src/main/java/net/minecraft/server/level/ServerLevel.java ++++ b/src/main/java/net/minecraft/server/level/ServerLevel.java +@@ -1536,12 +1536,17 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe + } + + public int sendParticles(ServerPlayer sender, T t0, double d0, double d1, double d2, int i, double d3, double d4, double d5, double d6, boolean force) { ++ // Paper start - Particle API ++ return sendParticles(players, sender, t0, d0, d1, d2, i, d3, d4, d5, d6, force); ++ } ++ public int sendParticles(List receivers, @Nullable ServerPlayer sender, T t0, double d0, double d1, double d2, int i, double d3, double d4, double d5, double d6, boolean force) { ++ // Paper end - Particle API + ClientboundLevelParticlesPacket packetplayoutworldparticles = new ClientboundLevelParticlesPacket(t0, force, d0, d1, d2, (float) d3, (float) d4, (float) d5, (float) d6, i); + // CraftBukkit end + int j = 0; + +- for (int k = 0; k < this.players.size(); ++k) { +- ServerPlayer entityplayer = (ServerPlayer) this.players.get(k); ++ for (Player entityhuman : receivers) { // Paper - Particle API ++ ServerPlayer entityplayer = (ServerPlayer) entityhuman; // Paper - Particle API + if (sender != null && !entityplayer.getBukkitEntity().canSee(sender.getBukkitEntity())) continue; // CraftBukkit + + if (this.sendParticles(entityplayer, force, d0, d1, d2, packetplayoutworldparticles)) { // CraftBukkit +diff --git a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java +index 9389c0ebdcaba5022bdae47b2b2ff06b40406127..5231548e886e884f565ff6cc5d45518141fbab2d 100644 +--- a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java ++++ b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java +@@ -1992,8 +1992,19 @@ public class CraftWorld extends CraftRegionAccessor implements World { + + @Override + public void spawnParticle(Particle particle, double x, double y, double z, int count, double offsetX, double offsetY, double offsetZ, double extra, T data, boolean force) { ++ // Paper start - Particle API ++ this.spawnParticle(particle, null, null, x, y, z, count, offsetX, offsetY, offsetZ, extra, data, force); ++ } ++ @Override ++ public void spawnParticle(Particle particle, List receivers, Player sender, double x, double y, double z, int count, double offsetX, double offsetY, double offsetZ, double extra, T data, boolean force) { ++ // Paper end - Particle API ++ data = CraftParticle.convertLegacy(data); ++ if (data != null) { ++ Preconditions.checkArgument(particle.getDataType().isInstance(data), "data (%s) should be %s", data.getClass(), particle.getDataType()); ++ } + this.getHandle().sendParticles( +- null, // Sender ++ receivers == null ? this.getHandle().players() : receivers.stream().map(player -> ((CraftPlayer) player).getHandle()).collect(java.util.stream.Collectors.toList()), // Paper - Particle API ++ sender != null ? ((CraftPlayer) sender).getHandle() : null, // Sender // Paper - Particle API + CraftParticle.createParticleParam(particle, data), // Particle + x, y, z, // Position + count, // Count diff --git a/patches/server/0187-Expand-World.spawnParticle-API-and-add-Builder.patch b/patches/server/0187-Expand-World.spawnParticle-API-and-add-Builder.patch deleted file mode 100644 index d98048d69b7a..000000000000 --- a/patches/server/0187-Expand-World.spawnParticle-API-and-add-Builder.patch +++ /dev/null @@ -1,60 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Aikar -Date: Tue, 15 Aug 2017 22:29:12 -0400 -Subject: [PATCH] Expand World.spawnParticle API and add Builder - -Adds ability to control who receives it and who is the source/sender (vanish API) -the standard API is to send the packet to everyone in the world, which is ineffecient. -Adds an option to control the force mode of the particle. - -This adds a new Builder API which is much friendlier to use. - -diff --git a/src/main/java/net/minecraft/server/level/ServerLevel.java b/src/main/java/net/minecraft/server/level/ServerLevel.java -index 7c6b094de5e29cb2663e8a7072a27d7adbceb4b2..1fac100819e59d00f50e530d3a4157b56d966dba 100644 ---- a/src/main/java/net/minecraft/server/level/ServerLevel.java -+++ b/src/main/java/net/minecraft/server/level/ServerLevel.java -@@ -1508,12 +1508,17 @@ public class ServerLevel extends Level implements WorldGenLevel { - } - - public int sendParticles(ServerPlayer sender, T t0, double d0, double d1, double d2, int i, double d3, double d4, double d5, double d6, boolean force) { -+ // Paper start - Particle API -+ return sendParticles(players, sender, t0, d0, d1, d2, i, d3, d4, d5, d6, force); -+ } -+ public int sendParticles(List receivers, @Nullable ServerPlayer sender, T t0, double d0, double d1, double d2, int i, double d3, double d4, double d5, double d6, boolean force) { -+ // Paper end - Particle API - ClientboundLevelParticlesPacket packetplayoutworldparticles = new ClientboundLevelParticlesPacket(t0, force, d0, d1, d2, (float) d3, (float) d4, (float) d5, (float) d6, i); - // CraftBukkit end - int j = 0; - -- for (int k = 0; k < this.players.size(); ++k) { -- ServerPlayer entityplayer = (ServerPlayer) this.players.get(k); -+ for (Player entityhuman : receivers) { // Paper - Particle API -+ ServerPlayer entityplayer = (ServerPlayer) entityhuman; // Paper - Particle API - if (sender != null && !entityplayer.getBukkitEntity().canSee(sender.getBukkitEntity())) continue; // CraftBukkit - - if (this.sendParticles(entityplayer, force, d0, d1, d2, packetplayoutworldparticles)) { // CraftBukkit -diff --git a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java -index 1b7660a2d003bfb19ef80ba8066b6417f85328ec..3c00eaf52ab04d1396f226cc074d9dd013c57027 100644 ---- a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java -+++ b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java -@@ -1982,8 +1982,19 @@ public class CraftWorld extends CraftRegionAccessor implements World { - - @Override - public void spawnParticle(Particle particle, double x, double y, double z, int count, double offsetX, double offsetY, double offsetZ, double extra, T data, boolean force) { -+ // Paper start - Particle API -+ this.spawnParticle(particle, null, null, x, y, z, count, offsetX, offsetY, offsetZ, extra, data, force); -+ } -+ @Override -+ public void spawnParticle(Particle particle, List receivers, Player sender, double x, double y, double z, int count, double offsetX, double offsetY, double offsetZ, double extra, T data, boolean force) { -+ // Paper end - Particle API -+ data = CraftParticle.convertLegacy(data); -+ if (data != null) { -+ Preconditions.checkArgument(particle.getDataType().isInstance(data), "data (%s) should be %s", data.getClass(), particle.getDataType()); -+ } - this.getHandle().sendParticles( -- null, // Sender -+ receivers == null ? this.getHandle().players() : receivers.stream().map(player -> ((CraftPlayer) player).getHandle()).collect(java.util.stream.Collectors.toList()), // Paper - Particle API -+ sender != null ? ((CraftPlayer) sender).getHandle() : null, // Sender // Paper - Particle API - CraftParticle.createParticleParam(particle, data), // Particle - x, y, z, // Position - count, // Count diff --git a/patches/server/0187-Fix-exploit-that-allowed-colored-signs-to-be-created.patch b/patches/server/0187-Fix-exploit-that-allowed-colored-signs-to-be-created.patch new file mode 100644 index 000000000000..9b28d06fce16 --- /dev/null +++ b/patches/server/0187-Fix-exploit-that-allowed-colored-signs-to-be-created.patch @@ -0,0 +1,22 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: 0x22 <0x22@futureclient.net> +Date: Thu, 26 Apr 2018 04:41:11 -0400 +Subject: [PATCH] Fix exploit that allowed colored signs to be created + + +diff --git a/src/main/java/net/minecraft/world/level/block/entity/SignBlockEntity.java b/src/main/java/net/minecraft/world/level/block/entity/SignBlockEntity.java +index cdaa050f237c6d6d5a0df0faf23daf3775249451..469d0046ba98289e98aa0f3f66e3ed27026a98a2 100644 +--- a/src/main/java/net/minecraft/world/level/block/entity/SignBlockEntity.java ++++ b/src/main/java/net/minecraft/world/level/block/entity/SignBlockEntity.java +@@ -202,9 +202,9 @@ public class SignBlockEntity extends BlockEntity { + Style chatmodifier = signtext.getMessage(i, entityhuman.isTextFilteringEnabled()).getStyle(); + + if (entityhuman.isTextFilteringEnabled()) { +- signtext = signtext.setMessage(i, Component.literal(filteredtext.filteredOrEmpty()).setStyle(chatmodifier)); ++ signtext = signtext.setMessage(i, Component.literal(net.minecraft.util.StringUtil.filterText(filteredtext.filteredOrEmpty())).setStyle(chatmodifier)); // Paper - filter sign text to chat only + } else { +- signtext = signtext.setMessage(i, Component.literal(filteredtext.raw()).setStyle(chatmodifier), Component.literal(filteredtext.filteredOrEmpty()).setStyle(chatmodifier)); ++ signtext = signtext.setMessage(i, Component.literal(net.minecraft.util.StringUtil.filterText(filteredtext.raw())).setStyle(chatmodifier), Component.literal(net.minecraft.util.StringUtil.filterText(filteredtext.filteredOrEmpty())).setStyle(chatmodifier)); // Paper - filter sign text to chat only + } + } + diff --git a/patches/server/0188-EndermanAttackPlayerEvent.patch b/patches/server/0188-EndermanAttackPlayerEvent.patch new file mode 100644 index 000000000000..7ec73bf7df77 --- /dev/null +++ b/patches/server/0188-EndermanAttackPlayerEvent.patch @@ -0,0 +1,28 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Aikar +Date: Tue, 1 May 2018 20:18:54 -0400 +Subject: [PATCH] EndermanAttackPlayerEvent + +Allow control over whether or not an enderman aggros a player. + +This allows you to override/extend the pumpkin/stare logic. + +diff --git a/src/main/java/net/minecraft/world/entity/monster/EnderMan.java b/src/main/java/net/minecraft/world/entity/monster/EnderMan.java +index d7145b3a19402a03d0af02822ffcb0ef07eaa663..5f1bbb4302013c2c1788db6b64eafba2a11a373a 100644 +--- a/src/main/java/net/minecraft/world/entity/monster/EnderMan.java ++++ b/src/main/java/net/minecraft/world/entity/monster/EnderMan.java +@@ -234,6 +234,14 @@ public class EnderMan extends Monster implements NeutralMob { + } + + boolean isBeingStaredBy(Player player) { ++ // Paper start - EndermanAttackPlayerEvent ++ boolean shouldAttack = isBeingStaredBy0(player); ++ com.destroystokyo.paper.event.entity.EndermanAttackPlayerEvent event = new com.destroystokyo.paper.event.entity.EndermanAttackPlayerEvent((org.bukkit.entity.Enderman) getBukkitEntity(), (org.bukkit.entity.Player) player.getBukkitEntity()); ++ event.setCancelled(!shouldAttack); ++ return event.callEvent(); ++ } ++ private boolean isBeingStaredBy0(Player player) { ++ // Paper end - EndermanAttackPlayerEvent + return this.isLookingAtMe(player, 0.025D, true, false, LivingEntity.PLAYER_NOT_WEARING_DISGUISE_ITEM, new DoubleSupplier[]{this::getEyeY}); + } + diff --git a/patches/server/0188-Fix-exploit-that-allowed-colored-signs-to-be-created.patch b/patches/server/0188-Fix-exploit-that-allowed-colored-signs-to-be-created.patch deleted file mode 100644 index 5f4980ec0474..000000000000 --- a/patches/server/0188-Fix-exploit-that-allowed-colored-signs-to-be-created.patch +++ /dev/null @@ -1,22 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: 0x22 <0x22@futureclient.net> -Date: Thu, 26 Apr 2018 04:41:11 -0400 -Subject: [PATCH] Fix exploit that allowed colored signs to be created - - -diff --git a/src/main/java/net/minecraft/world/level/block/entity/SignBlockEntity.java b/src/main/java/net/minecraft/world/level/block/entity/SignBlockEntity.java -index e535fb3b5194b8412c0c26c0799340916c7542eb..394d0b03f18d55b43f091a9ae1a47e06a6fa4c0d 100644 ---- a/src/main/java/net/minecraft/world/level/block/entity/SignBlockEntity.java -+++ b/src/main/java/net/minecraft/world/level/block/entity/SignBlockEntity.java -@@ -202,9 +202,9 @@ public class SignBlockEntity extends BlockEntity implements CommandSource { // C - Style chatmodifier = signtext.getMessage(i, entityhuman.isTextFilteringEnabled()).getStyle(); - - if (entityhuman.isTextFilteringEnabled()) { -- signtext = signtext.setMessage(i, Component.literal(filteredtext.filteredOrEmpty()).setStyle(chatmodifier)); -+ signtext = signtext.setMessage(i, Component.literal(net.minecraft.util.StringUtil.filterText(filteredtext.filteredOrEmpty())).setStyle(chatmodifier)); // Paper - filter sign text to chat only - } else { -- signtext = signtext.setMessage(i, Component.literal(filteredtext.raw()).setStyle(chatmodifier), Component.literal(filteredtext.filteredOrEmpty()).setStyle(chatmodifier)); -+ signtext = signtext.setMessage(i, Component.literal(net.minecraft.util.StringUtil.filterText(filteredtext.raw())).setStyle(chatmodifier), Component.literal(net.minecraft.util.StringUtil.filterText(filteredtext.filteredOrEmpty())).setStyle(chatmodifier)); // Paper - filter sign text to chat only - } - } - diff --git a/patches/server/0189-EndermanAttackPlayerEvent.patch b/patches/server/0189-EndermanAttackPlayerEvent.patch deleted file mode 100644 index a4e97fbce346..000000000000 --- a/patches/server/0189-EndermanAttackPlayerEvent.patch +++ /dev/null @@ -1,30 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Aikar -Date: Tue, 1 May 2018 20:18:54 -0400 -Subject: [PATCH] EndermanAttackPlayerEvent - -Allow control over whether or not an enderman aggros a player. - -This allows you to override/extend the pumpkin/stare logic. - -diff --git a/src/main/java/net/minecraft/world/entity/monster/EnderMan.java b/src/main/java/net/minecraft/world/entity/monster/EnderMan.java -index 51e9988685b1a9a3d4d2effec63560b0ae9e8d3a..70888dd25b6a1d1ab7702d73a64a47eebafe76fe 100644 ---- a/src/main/java/net/minecraft/world/entity/monster/EnderMan.java -+++ b/src/main/java/net/minecraft/world/entity/monster/EnderMan.java -@@ -233,7 +233,15 @@ public class EnderMan extends Monster implements NeutralMob { - this.readPersistentAngerSaveData(this.level(), nbt); - } - -- boolean isLookingAtMe(Player player) { -+ // Paper start - EndermanAttackPlayerEvent -+ private boolean isLookingAtMe(Player player) { -+ boolean shouldAttack = isLookingAtMe_check(player); -+ com.destroystokyo.paper.event.entity.EndermanAttackPlayerEvent event = new com.destroystokyo.paper.event.entity.EndermanAttackPlayerEvent((org.bukkit.entity.Enderman) getBukkitEntity(), (org.bukkit.entity.Player) player.getBukkitEntity()); -+ event.setCancelled(!shouldAttack); -+ return event.callEvent(); -+ } -+ private boolean isLookingAtMe_check(Player player) { -+ // Paper end - EndermanAttackPlayerEvent - ItemStack itemstack = (ItemStack) player.getInventory().armor.get(3); - - if (itemstack.is(Blocks.CARVED_PUMPKIN.asItem())) { diff --git a/patches/server/0189-WitchConsumePotionEvent.patch b/patches/server/0189-WitchConsumePotionEvent.patch new file mode 100644 index 000000000000..d63befdcdb85 --- /dev/null +++ b/patches/server/0189-WitchConsumePotionEvent.patch @@ -0,0 +1,24 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Aikar +Date: Wed, 16 May 2018 20:35:16 -0400 +Subject: [PATCH] WitchConsumePotionEvent + +Fires when a witch consumes the potion in their hand + +diff --git a/src/main/java/net/minecraft/world/entity/monster/Witch.java b/src/main/java/net/minecraft/world/entity/monster/Witch.java +index 4fe13665f298503d28d866551fa7871437ca683b..93cc6a6345d8b9c349eba8bc5ba3d23cb36d76cc 100644 +--- a/src/main/java/net/minecraft/world/entity/monster/Witch.java ++++ b/src/main/java/net/minecraft/world/entity/monster/Witch.java +@@ -124,6 +124,12 @@ public class Witch extends Raider implements RangedAttackMob { + + this.setItemSlot(EquipmentSlot.MAINHAND, ItemStack.EMPTY); + PotionContents potioncontents = (PotionContents) itemstack.get(DataComponents.POTION_CONTENTS); ++ // Paper start - WitchConsumePotionEvent ++ if (itemstack.is(Items.POTION)) { ++ com.destroystokyo.paper.event.entity.WitchConsumePotionEvent event = new com.destroystokyo.paper.event.entity.WitchConsumePotionEvent((org.bukkit.entity.Witch) this.getBukkitEntity(), org.bukkit.craftbukkit.inventory.CraftItemStack.asCraftMirror(itemstack)); ++ potioncontents = event.callEvent() ? org.bukkit.craftbukkit.inventory.CraftItemStack.unwrap(event.getPotion()).get(DataComponents.POTION_CONTENTS) : null; ++ } ++ // Paper end - WitchConsumePotionEvent + + if (itemstack.is(Items.POTION) && potioncontents != null) { + potioncontents.forEachEffect((effect) -> this.addEffect(effect, org.bukkit.event.entity.EntityPotionEffectEvent.Cause.ATTACK)); // CraftBukkit diff --git a/patches/server/0190-WitchConsumePotionEvent.patch b/patches/server/0190-WitchConsumePotionEvent.patch deleted file mode 100644 index fead120a99f4..000000000000 --- a/patches/server/0190-WitchConsumePotionEvent.patch +++ /dev/null @@ -1,24 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Aikar -Date: Wed, 16 May 2018 20:35:16 -0400 -Subject: [PATCH] WitchConsumePotionEvent - -Fires when a witch consumes the potion in their hand - -diff --git a/src/main/java/net/minecraft/world/entity/monster/Witch.java b/src/main/java/net/minecraft/world/entity/monster/Witch.java -index a4d10409f06316ee628b683b231b3cc2a08bf593..f9cd71379a1d195731e7348ac6928372a4b2c29c 100644 ---- a/src/main/java/net/minecraft/world/entity/monster/Witch.java -+++ b/src/main/java/net/minecraft/world/entity/monster/Witch.java -@@ -123,6 +123,12 @@ public class Witch extends Raider implements RangedAttackMob { - - this.setItemSlot(EquipmentSlot.MAINHAND, ItemStack.EMPTY); - PotionContents potioncontents = (PotionContents) itemstack.get(DataComponents.POTION_CONTENTS); -+ // Paper start - WitchConsumePotionEvent -+ if (itemstack.is(Items.POTION)) { -+ com.destroystokyo.paper.event.entity.WitchConsumePotionEvent event = new com.destroystokyo.paper.event.entity.WitchConsumePotionEvent((org.bukkit.entity.Witch) this.getBukkitEntity(), org.bukkit.craftbukkit.inventory.CraftItemStack.asCraftMirror(itemstack)); -+ potioncontents = event.callEvent() ? org.bukkit.craftbukkit.inventory.CraftItemStack.unwrap(event.getPotion()).get(DataComponents.POTION_CONTENTS) : null; -+ } -+ // Paper end - WitchConsumePotionEvent - - if (itemstack.is(Items.POTION) && potioncontents != null) { - potioncontents.forEachEffect((effect) -> this.addEffect(effect, org.bukkit.event.entity.EntityPotionEffectEvent.Cause.ATTACK)); // CraftBukkit diff --git a/patches/server/0190-WitchThrowPotionEvent.patch b/patches/server/0190-WitchThrowPotionEvent.patch new file mode 100644 index 000000000000..fc4a2c07db53 --- /dev/null +++ b/patches/server/0190-WitchThrowPotionEvent.patch @@ -0,0 +1,25 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Aikar +Date: Wed, 16 May 2018 20:44:58 -0400 +Subject: [PATCH] WitchThrowPotionEvent + +Fired when a witch throws a potion at a player + +diff --git a/src/main/java/net/minecraft/world/entity/monster/Witch.java b/src/main/java/net/minecraft/world/entity/monster/Witch.java +index 93cc6a6345d8b9c349eba8bc5ba3d23cb36d76cc..52475eb5fd0240e80826a52d6cd10bc1fbe8f903 100644 +--- a/src/main/java/net/minecraft/world/entity/monster/Witch.java ++++ b/src/main/java/net/minecraft/world/entity/monster/Witch.java +@@ -237,6 +237,13 @@ public class Witch extends Raider implements RangedAttackMob { + ServerLevel worldserver = (ServerLevel) world; + ItemStack itemstack = PotionContents.createItemStack(Items.SPLASH_POTION, holder); + ++ // Paper start - WitchThrowPotionEvent ++ com.destroystokyo.paper.event.entity.WitchThrowPotionEvent event = new com.destroystokyo.paper.event.entity.WitchThrowPotionEvent((org.bukkit.entity.Witch) this.getBukkitEntity(), (org.bukkit.entity.LivingEntity) target.getBukkitEntity(), org.bukkit.craftbukkit.inventory.CraftItemStack.asCraftMirror(itemstack)); ++ if (!event.callEvent()) { ++ return; ++ } ++ itemstack = org.bukkit.craftbukkit.inventory.CraftItemStack.asNMSCopy(event.getPotion()); ++ // Paper end - WitchThrowPotionEvent + Projectile.spawnProjectileUsingShoot(ThrownPotion::new, worldserver, itemstack, this, d0, d1 + d3 * 0.2D, d2, 0.75F, 8.0F); + } + diff --git a/patches/server/0191-WitchReadyPotionEvent.patch b/patches/server/0191-WitchReadyPotionEvent.patch new file mode 100644 index 000000000000..5275662a1d55 --- /dev/null +++ b/patches/server/0191-WitchReadyPotionEvent.patch @@ -0,0 +1,42 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Aikar +Date: Tue, 5 Jun 2018 22:47:26 -0400 +Subject: [PATCH] WitchReadyPotionEvent + + +diff --git a/src/main/java/net/minecraft/world/entity/monster/Witch.java b/src/main/java/net/minecraft/world/entity/monster/Witch.java +index 52475eb5fd0240e80826a52d6cd10bc1fbe8f903..120f0c729c48ddfa598472029cdfbab3dc6db50f 100644 +--- a/src/main/java/net/minecraft/world/entity/monster/Witch.java ++++ b/src/main/java/net/minecraft/world/entity/monster/Witch.java +@@ -152,7 +152,11 @@ public class Witch extends Raider implements RangedAttackMob { + } + + if (holder != null) { +- this.setItemSlot(EquipmentSlot.MAINHAND, PotionContents.createItemStack(Items.POTION, holder)); ++ // Paper start ++ ItemStack potion = PotionContents.createItemStack(Items.POTION, holder); ++ potion = org.bukkit.craftbukkit.event.CraftEventFactory.handleWitchReadyPotionEvent(this, potion); ++ this.setItemSlot(EquipmentSlot.MAINHAND, potion); ++ // Paper end + this.usingTime = this.getMainHandItem().getUseDuration(this); + this.setUsingItem(true); + if (!this.isSilent()) { +diff --git a/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java b/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java +index 0a3c89dba691760c7e0be58914003cc438269a03..f05a19f868be0d685c4fbf64e1dfbefda71131ac 100644 +--- a/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java ++++ b/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java +@@ -1974,4 +1974,14 @@ public class CraftEventFactory { + ).callEvent(); + } + // Paper end - PlayerUseUnknownEntityEvent ++ ++ // Paper start - WitchReadyPotionEvent ++ public static ItemStack handleWitchReadyPotionEvent(net.minecraft.world.entity.monster.Witch witch, @Nullable ItemStack potion) { ++ com.destroystokyo.paper.event.entity.WitchReadyPotionEvent event = new com.destroystokyo.paper.event.entity.WitchReadyPotionEvent((org.bukkit.entity.Witch) witch.getBukkitEntity(), CraftItemStack.asCraftMirror(potion)); ++ if (!event.callEvent() || event.getPotion() == null) { ++ return ItemStack.EMPTY; ++ } ++ return org.bukkit.craftbukkit.inventory.CraftItemStack.asNMSCopy(event.getPotion()); ++ } ++ // Paper end - WitchReadyPotionEvent + } diff --git a/patches/server/0191-WitchThrowPotionEvent.patch b/patches/server/0191-WitchThrowPotionEvent.patch deleted file mode 100644 index d6be87ca72d7..000000000000 --- a/patches/server/0191-WitchThrowPotionEvent.patch +++ /dev/null @@ -1,30 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Aikar -Date: Wed, 16 May 2018 20:44:58 -0400 -Subject: [PATCH] WitchThrowPotionEvent - -Fired when a witch throws a potion at a player - -diff --git a/src/main/java/net/minecraft/world/entity/monster/Witch.java b/src/main/java/net/minecraft/world/entity/monster/Witch.java -index f9cd71379a1d195731e7348ac6928372a4b2c29c..a14e00d55930628333cc63b18727ea56dbdc4ee3 100644 ---- a/src/main/java/net/minecraft/world/entity/monster/Witch.java -+++ b/src/main/java/net/minecraft/world/entity/monster/Witch.java -@@ -230,9 +230,16 @@ public class Witch extends Raider implements RangedAttackMob { - holder = Potions.WEAKNESS; - } - -+ // Paper start - WitchThrowPotionEvent -+ ItemStack potion = PotionContents.createItemStack(Items.SPLASH_POTION, holder); -+ com.destroystokyo.paper.event.entity.WitchThrowPotionEvent event = new com.destroystokyo.paper.event.entity.WitchThrowPotionEvent((org.bukkit.entity.Witch) this.getBukkitEntity(), (org.bukkit.entity.LivingEntity) target.getBukkitEntity(), org.bukkit.craftbukkit.inventory.CraftItemStack.asCraftMirror(potion)); -+ if (!event.callEvent()) { -+ return; -+ } -+ potion = org.bukkit.craftbukkit.inventory.CraftItemStack.asNMSCopy(event.getPotion()); - ThrownPotion entitypotion = new ThrownPotion(this.level(), this); -- -- entitypotion.setItem(PotionContents.createItemStack(Items.SPLASH_POTION, holder)); -+ entitypotion.setItem(potion); -+ // Paper end - WitchThrowPotionEvent - entitypotion.setXRot(entitypotion.getXRot() - -20.0F); - entitypotion.shoot(d0, d1 + d3 * 0.2D, d2, 0.75F, 8.0F); - if (!this.isSilent()) { diff --git a/patches/server/0192-ItemStack-getMaxItemUseDuration.patch b/patches/server/0192-ItemStack-getMaxItemUseDuration.patch new file mode 100644 index 000000000000..d3f513e3a92f --- /dev/null +++ b/patches/server/0192-ItemStack-getMaxItemUseDuration.patch @@ -0,0 +1,33 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Aikar +Date: Tue, 5 Jun 2018 23:00:29 -0400 +Subject: [PATCH] ItemStack#getMaxItemUseDuration + +Allows you to determine how long it takes to use a usable/consumable item + +diff --git a/src/main/java/org/bukkit/craftbukkit/inventory/CraftItemStack.java b/src/main/java/org/bukkit/craftbukkit/inventory/CraftItemStack.java +index aea09533fada5bd3d42e2cc147921167a5e7c1a5..60062ea5b18b95a14c459f2f3a0743c1e1ac0f12 100644 +--- a/src/main/java/org/bukkit/craftbukkit/inventory/CraftItemStack.java ++++ b/src/main/java/org/bukkit/craftbukkit/inventory/CraftItemStack.java +@@ -225,6 +225,21 @@ public final class CraftItemStack extends ItemStack { + return (this.handle == null) ? Material.AIR.getMaxStackSize() : this.handle.getMaxStackSize(); + } + ++ // Paper start ++ @Override ++ public int getMaxItemUseDuration(final org.bukkit.entity.LivingEntity entity) { ++ if (handle == null) { ++ return 0; ++ } ++ ++ // Make sure plugins calling the old method don't blow up ++ if (entity == null && (handle.is(net.minecraft.world.item.Items.CROSSBOW) || handle.is(net.minecraft.world.item.Items.GOAT_HORN))) { ++ throw new UnsupportedOperationException("This item requires an entity to determine the max use duration"); ++ } ++ return handle.getUseDuration(entity != null ? ((org.bukkit.craftbukkit.entity.CraftLivingEntity) entity).getHandle() : null); ++ } ++ // Paper end ++ + @Override + public void addUnsafeEnchantment(Enchantment ench, int level) { + Preconditions.checkArgument(ench != null, "Enchantment cannot be null"); diff --git a/patches/server/0192-WitchReadyPotionEvent.patch b/patches/server/0192-WitchReadyPotionEvent.patch deleted file mode 100644 index 8cad7e440983..000000000000 --- a/patches/server/0192-WitchReadyPotionEvent.patch +++ /dev/null @@ -1,42 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Aikar -Date: Tue, 5 Jun 2018 22:47:26 -0400 -Subject: [PATCH] WitchReadyPotionEvent - - -diff --git a/src/main/java/net/minecraft/world/entity/monster/Witch.java b/src/main/java/net/minecraft/world/entity/monster/Witch.java -index a14e00d55930628333cc63b18727ea56dbdc4ee3..f6d01d21745391595d61b191832be4c28a3e58cb 100644 ---- a/src/main/java/net/minecraft/world/entity/monster/Witch.java -+++ b/src/main/java/net/minecraft/world/entity/monster/Witch.java -@@ -151,7 +151,11 @@ public class Witch extends Raider implements RangedAttackMob { - } - - if (holder != null) { -- this.setItemSlot(EquipmentSlot.MAINHAND, PotionContents.createItemStack(Items.POTION, holder)); -+ // Paper start -+ ItemStack potion = PotionContents.createItemStack(Items.POTION, holder); -+ potion = org.bukkit.craftbukkit.event.CraftEventFactory.handleWitchReadyPotionEvent(this, potion); -+ this.setItemSlot(EquipmentSlot.MAINHAND, potion); -+ // Paper end - this.usingTime = this.getMainHandItem().getUseDuration(this); - this.setUsingItem(true); - if (!this.isSilent()) { -diff --git a/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java b/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java -index 91180d7ad705ca97c0df43debc14b204127165d0..e3af0db8082e4e90902197f96f1c833405bf5f63 100644 ---- a/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java -+++ b/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java -@@ -1976,4 +1976,14 @@ public class CraftEventFactory { - ).callEvent(); - } - // Paper end - PlayerUseUnknownEntityEvent -+ -+ // Paper start - WitchReadyPotionEvent -+ public static ItemStack handleWitchReadyPotionEvent(net.minecraft.world.entity.monster.Witch witch, @Nullable ItemStack potion) { -+ com.destroystokyo.paper.event.entity.WitchReadyPotionEvent event = new com.destroystokyo.paper.event.entity.WitchReadyPotionEvent((org.bukkit.entity.Witch) witch.getBukkitEntity(), CraftItemStack.asCraftMirror(potion)); -+ if (!event.callEvent() || event.getPotion() == null) { -+ return ItemStack.EMPTY; -+ } -+ return org.bukkit.craftbukkit.inventory.CraftItemStack.asNMSCopy(event.getPotion()); -+ } -+ // Paper end - WitchReadyPotionEvent - } diff --git a/patches/server/0193-Add-EntityTeleportEndGatewayEvent.patch b/patches/server/0193-Add-EntityTeleportEndGatewayEvent.patch new file mode 100644 index 000000000000..2a5ab478ab9b --- /dev/null +++ b/patches/server/0193-Add-EntityTeleportEndGatewayEvent.patch @@ -0,0 +1,29 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Shane Freeder +Date: Sat, 9 Jun 2018 14:08:39 +0200 +Subject: [PATCH] Add EntityTeleportEndGatewayEvent + + +diff --git a/src/main/java/net/minecraft/world/entity/Entity.java b/src/main/java/net/minecraft/world/entity/Entity.java +index 99794276626d26849150d4956abbbc2296543907..087f030985180b91a809fb45244e23106da62e34 100644 +--- a/src/main/java/net/minecraft/world/entity/Entity.java ++++ b/src/main/java/net/minecraft/world/entity/Entity.java +@@ -3429,8 +3429,16 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess + // CraftBukkit start + PositionMoveRotation absolutePosition = PositionMoveRotation.calculateAbsolute(PositionMoveRotation.of(this), PositionMoveRotation.of(teleportTarget), teleportTarget.relatives()); + Location to = CraftLocation.toBukkit(absolutePosition.position(), teleportTarget.newLevel().getWorld(), absolutePosition.yRot(), absolutePosition.xRot()); +- EntityTeleportEvent teleEvent = CraftEventFactory.callEntityTeleportEvent(this, to); +- if (teleEvent.isCancelled()) { ++ // Paper start - gateway-specific teleport event ++ final EntityTeleportEvent teleEvent; ++ if (this.portalProcess != null && this.portalProcess.isSamePortal(((net.minecraft.world.level.block.EndGatewayBlock) net.minecraft.world.level.block.Blocks.END_GATEWAY)) && this.level.getBlockEntity(this.portalProcess.getEntryPosition()) instanceof net.minecraft.world.level.block.entity.TheEndGatewayBlockEntity theEndGatewayBlockEntity) { ++ teleEvent = new com.destroystokyo.paper.event.entity.EntityTeleportEndGatewayEvent(this.getBukkitEntity(), this.getBukkitEntity().getLocation(), to, new org.bukkit.craftbukkit.block.CraftEndGateway(to.getWorld(), theEndGatewayBlockEntity)); ++ teleEvent.callEvent(); ++ } else { ++ teleEvent = CraftEventFactory.callEntityTeleportEvent(this, to); ++ } ++ // Paper end - gateway-specific teleport event ++ if (teleEvent.isCancelled() || teleEvent.getTo() == null) { + return null; + } + if (!to.equals(teleEvent.getTo())) { diff --git a/patches/server/0193-ItemStack-getMaxItemUseDuration.patch b/patches/server/0193-ItemStack-getMaxItemUseDuration.patch deleted file mode 100644 index 67996fcc593d..000000000000 --- a/patches/server/0193-ItemStack-getMaxItemUseDuration.patch +++ /dev/null @@ -1,51 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Aikar -Date: Tue, 5 Jun 2018 23:00:29 -0400 -Subject: [PATCH] ItemStack#getMaxItemUseDuration - -Allows you to determine how long it takes to use a usable/consumable item - -diff --git a/src/main/java/org/bukkit/craftbukkit/inventory/CraftItemStack.java b/src/main/java/org/bukkit/craftbukkit/inventory/CraftItemStack.java -index 6c76aeddb34239a5acc204a17b2aa2d80e6b2c88..e8a455eb5e17bcfcae3f03664f2b47773fbdf37e 100644 ---- a/src/main/java/org/bukkit/craftbukkit/inventory/CraftItemStack.java -+++ b/src/main/java/org/bukkit/craftbukkit/inventory/CraftItemStack.java -@@ -7,14 +7,17 @@ import net.minecraft.core.Holder; - import net.minecraft.core.component.DataComponentPatch; - import net.minecraft.core.component.DataComponents; - import net.minecraft.world.item.Item; -+import net.minecraft.world.item.Items; - import net.minecraft.world.item.enchantment.EnchantmentHelper; - import net.minecraft.world.item.enchantment.ItemEnchantments; - import org.bukkit.Material; - import org.bukkit.configuration.serialization.DelegateDeserialization; - import org.bukkit.craftbukkit.enchantments.CraftEnchantment; -+import org.bukkit.craftbukkit.entity.CraftLivingEntity; - import org.bukkit.craftbukkit.util.CraftLegacy; - import org.bukkit.craftbukkit.util.CraftMagicNumbers; - import org.bukkit.enchantments.Enchantment; -+import org.bukkit.entity.LivingEntity; - import org.bukkit.inventory.ItemStack; - import org.bukkit.inventory.meta.ItemMeta; - import org.bukkit.material.MaterialData; -@@ -210,6 +213,21 @@ public final class CraftItemStack extends ItemStack { - return (this.handle == null) ? Material.AIR.getMaxStackSize() : this.handle.getMaxStackSize(); - } - -+ // Paper start -+ @Override -+ public int getMaxItemUseDuration(final LivingEntity entity) { -+ if (handle == null) { -+ return 0; -+ } -+ -+ // Make sure plugins calling the old method don't blow up -+ if (entity == null && handle.is(Items.CROSSBOW)) { -+ throw new UnsupportedOperationException("This item requires an entity to determine the max use duration"); -+ } -+ return handle.getUseDuration(entity != null ? ((CraftLivingEntity) entity).getHandle() : null); -+ } -+ // Paper end -+ - @Override - public void addUnsafeEnchantment(Enchantment ench, int level) { - Preconditions.checkArgument(ench != null, "Enchantment cannot be null"); diff --git a/patches/server/0194-Add-EntityTeleportEndGatewayEvent.patch b/patches/server/0194-Add-EntityTeleportEndGatewayEvent.patch deleted file mode 100644 index 56df3551eaa2..000000000000 --- a/patches/server/0194-Add-EntityTeleportEndGatewayEvent.patch +++ /dev/null @@ -1,29 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Shane Freeder -Date: Sat, 9 Jun 2018 14:08:39 +0200 -Subject: [PATCH] Add EntityTeleportEndGatewayEvent - - -diff --git a/src/main/java/net/minecraft/world/entity/Entity.java b/src/main/java/net/minecraft/world/entity/Entity.java -index 946f289e0e681524c6fde696921965dbdedda372..d6017d9d71fb4b3a3df6eaa44da0ebda54c83da4 100644 ---- a/src/main/java/net/minecraft/world/entity/Entity.java -+++ b/src/main/java/net/minecraft/world/entity/Entity.java -@@ -3301,8 +3301,16 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess - if (!this.isRemoved()) { - // CraftBukkit start - Location to = new Location(teleportTarget.newLevel().getWorld(), teleportTarget.pos().x, teleportTarget.pos().y, teleportTarget.pos().z, teleportTarget.yRot(), teleportTarget.xRot()); -- EntityTeleportEvent teleEvent = CraftEventFactory.callEntityTeleportEvent(this, to); -- if (teleEvent.isCancelled()) { -+ // Paper start - gateway-specific teleport event -+ final EntityTeleportEvent teleEvent; -+ if (this.portalProcess != null && this.portalProcess.isSamePortal(((net.minecraft.world.level.block.EndGatewayBlock) net.minecraft.world.level.block.Blocks.END_GATEWAY)) && this.level.getBlockEntity(this.portalProcess.getEntryPosition()) instanceof net.minecraft.world.level.block.entity.TheEndGatewayBlockEntity theEndGatewayBlockEntity) { -+ teleEvent = new com.destroystokyo.paper.event.entity.EntityTeleportEndGatewayEvent(this.getBukkitEntity(), this.getBukkitEntity().getLocation(), to, new org.bukkit.craftbukkit.block.CraftEndGateway(to.getWorld(), theEndGatewayBlockEntity)); -+ teleEvent.callEvent(); -+ } else { -+ teleEvent = CraftEventFactory.callEntityTeleportEvent(this, to); -+ } -+ // Paper end - gateway-specific teleport event -+ if (teleEvent.isCancelled() || teleEvent.getTo() == null) { - return null; - } - to = teleEvent.getTo(); diff --git a/patches/server/0194-Unset-Ignited-flag-on-cancel-of-Explosion-Event.patch b/patches/server/0194-Unset-Ignited-flag-on-cancel-of-Explosion-Event.patch new file mode 100644 index 000000000000..70526b8bcbbb --- /dev/null +++ b/patches/server/0194-Unset-Ignited-flag-on-cancel-of-Explosion-Event.patch @@ -0,0 +1,19 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Aikar +Date: Sun, 10 Jun 2018 01:18:49 -0400 +Subject: [PATCH] Unset Ignited flag on cancel of Explosion Event + +Otherwise the creeper infinite explodes + +diff --git a/src/main/java/net/minecraft/world/entity/monster/Creeper.java b/src/main/java/net/minecraft/world/entity/monster/Creeper.java +index 31405c3a95e8e8217a6efede1e4599c14583081f..0552cc23391ec305754339d000630ccab0729100 100644 +--- a/src/main/java/net/minecraft/world/entity/monster/Creeper.java ++++ b/src/main/java/net/minecraft/world/entity/monster/Creeper.java +@@ -278,6 +278,7 @@ public class Creeper extends Monster { + // CraftBukkit start + } else { + this.swell = 0; ++ this.entityData.set(DATA_IS_IGNITED, Boolean.valueOf(false)); // Paper + } + // CraftBukkit end + } diff --git a/patches/server/0195-Fix-CraftEntity-hashCode.patch b/patches/server/0195-Fix-CraftEntity-hashCode.patch new file mode 100644 index 000000000000..aa86c5b03608 --- /dev/null +++ b/patches/server/0195-Fix-CraftEntity-hashCode.patch @@ -0,0 +1,46 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Aikar +Date: Sun, 10 Jun 2018 20:20:15 -0400 +Subject: [PATCH] Fix CraftEntity hashCode + +hashCodes are not allowed to change, however bukkit used a value +that does change, the entityId. + +When an entity is teleported dimensions, the entity reference is +replaced with a new one with a new entity ID. + +For hashCode, we can simply use the UUID's hashCode to keep +the hashCode from changing. + +equals() is ok to use getEntityId() because equals() should only +be true if both the left and right are the same reference. + +Since entity ids can not duplicate during runtime, this +check is essentially the same as this.getHandle() == other.getHandle() + +However, replaced it too to make it clearer of intent. + +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java +index 0480fbeffd19011d3cd63021225f376c464b480c..0e5cc680ee2418ec2af5fc3e215618ad4e768ed0 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java +@@ -503,14 +503,15 @@ public abstract class CraftEntity implements org.bukkit.entity.Entity { + return false; + } + final CraftEntity other = (CraftEntity) obj; +- return (this.getEntityId() == other.getEntityId()); ++ return (this.getHandle() == other.getHandle()); // Paper - while logically the same, this is clearer + } + ++ // Paper - Fix hashCode. entity ID's are not static. ++ // A CraftEntity can change reference to a new entity with a new ID, and hash codes should never change + @Override + public int hashCode() { +- int hash = 7; +- hash = 29 * hash + this.getEntityId(); +- return hash; ++ return getUniqueId().hashCode(); ++ // Paper end + } + + @Override diff --git a/patches/server/0195-Unset-Ignited-flag-on-cancel-of-Explosion-Event.patch b/patches/server/0195-Unset-Ignited-flag-on-cancel-of-Explosion-Event.patch deleted file mode 100644 index 100c67ec88f1..000000000000 --- a/patches/server/0195-Unset-Ignited-flag-on-cancel-of-Explosion-Event.patch +++ /dev/null @@ -1,19 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Aikar -Date: Sun, 10 Jun 2018 01:18:49 -0400 -Subject: [PATCH] Unset Ignited flag on cancel of Explosion Event - -Otherwise the creeper infinite explodes - -diff --git a/src/main/java/net/minecraft/world/entity/monster/Creeper.java b/src/main/java/net/minecraft/world/entity/monster/Creeper.java -index 39486215ac080220a6018a35a8437092dbc8fe9d..95df4ac539ec284654c53d39955870a46478c27d 100644 ---- a/src/main/java/net/minecraft/world/entity/monster/Creeper.java -+++ b/src/main/java/net/minecraft/world/entity/monster/Creeper.java -@@ -278,6 +278,7 @@ public class Creeper extends Monster implements PowerableMob { - // CraftBukkit start - } else { - this.swell = 0; -+ this.entityData.set(DATA_IS_IGNITED, Boolean.valueOf(false)); // Paper - } - // CraftBukkit end - } diff --git a/patches/server/0197-Configurable-LootPool-luck-formula.patch b/patches/server/0196-Configurable-LootPool-luck-formula.patch similarity index 100% rename from patches/server/0197-Configurable-LootPool-luck-formula.patch rename to patches/server/0196-Configurable-LootPool-luck-formula.patch diff --git a/patches/server/0196-Fix-CraftEntity-hashCode.patch b/patches/server/0196-Fix-CraftEntity-hashCode.patch deleted file mode 100644 index 56dcc01026c3..000000000000 --- a/patches/server/0196-Fix-CraftEntity-hashCode.patch +++ /dev/null @@ -1,46 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Aikar -Date: Sun, 10 Jun 2018 20:20:15 -0400 -Subject: [PATCH] Fix CraftEntity hashCode - -hashCodes are not allowed to change, however bukkit used a value -that does change, the entityId. - -When an entity is teleported dimensions, the entity reference is -replaced with a new one with a new entity ID. - -For hashCode, we can simply use the UUID's hashCode to keep -the hashCode from changing. - -equals() is ok to use getEntityId() because equals() should only -be true if both the left and right are the same reference. - -Since entity ids can not duplicate during runtime, this -check is essentially the same as this.getHandle() == other.getHandle() - -However, replaced it too to make it clearer of intent. - -diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java -index 69b5946625a53a1351ffc4bdf61c6874949bbeae..bddf98bdf60473eb1d2e533cf533ed7eee797aaa 100644 ---- a/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java -+++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java -@@ -502,14 +502,15 @@ public abstract class CraftEntity implements org.bukkit.entity.Entity { - return false; - } - final CraftEntity other = (CraftEntity) obj; -- return (this.getEntityId() == other.getEntityId()); -+ return (this.getHandle() == other.getHandle()); // Paper - while logically the same, this is clearer - } - -+ // Paper - Fix hashCode. entity ID's are not static. -+ // A CraftEntity can change reference to a new entity with a new ID, and hash codes should never change - @Override - public int hashCode() { -- int hash = 7; -- hash = 29 * hash + this.getEntityId(); -- return hash; -+ return getUniqueId().hashCode(); -+ // Paper end - } - - @Override diff --git a/patches/server/0197-Print-Error-details-when-failing-to-save-player-data.patch b/patches/server/0197-Print-Error-details-when-failing-to-save-player-data.patch new file mode 100644 index 000000000000..0885ca8d10ba --- /dev/null +++ b/patches/server/0197-Print-Error-details-when-failing-to-save-player-data.patch @@ -0,0 +1,19 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Aikar +Date: Fri, 15 Jun 2018 20:37:03 -0400 +Subject: [PATCH] Print Error details when failing to save player data + + +diff --git a/src/main/java/net/minecraft/world/level/storage/PlayerDataStorage.java b/src/main/java/net/minecraft/world/level/storage/PlayerDataStorage.java +index cd013567dd6224c86c0f1813d8a3d5fb7b8cabb5..b54a3741cd3ba615c83c98985cb4b3c4c586ed7a 100644 +--- a/src/main/java/net/minecraft/world/level/storage/PlayerDataStorage.java ++++ b/src/main/java/net/minecraft/world/level/storage/PlayerDataStorage.java +@@ -47,7 +47,7 @@ public class PlayerDataStorage { + + Util.safeReplaceFile(path2, path1, path3); + } catch (Exception exception) { +- PlayerDataStorage.LOGGER.warn("Failed to save player data for {}", player.getName().getString()); ++ PlayerDataStorage.LOGGER.warn("Failed to save player data for {}", player.getScoreboardName(), exception); // Paper - Print exception + } + + } diff --git a/patches/server/0198-Make-shield-blocking-delay-configurable.patch b/patches/server/0198-Make-shield-blocking-delay-configurable.patch new file mode 100644 index 000000000000..0c056446f0f1 --- /dev/null +++ b/patches/server/0198-Make-shield-blocking-delay-configurable.patch @@ -0,0 +1,56 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: BillyGalbreath +Date: Sat, 16 Jun 2018 01:18:16 -0500 +Subject: [PATCH] Make shield blocking delay configurable + + +diff --git a/src/main/java/net/minecraft/world/entity/LivingEntity.java b/src/main/java/net/minecraft/world/entity/LivingEntity.java +index 84e11e2c62e643f959f1a570a27f6ad07df165d4..08a2fbca50e26938e46e49dae7b101cfc375b02e 100644 +--- a/src/main/java/net/minecraft/world/entity/LivingEntity.java ++++ b/src/main/java/net/minecraft/world/entity/LivingEntity.java +@@ -4101,12 +4101,24 @@ public abstract class LivingEntity extends Entity implements Attackable { + if (this.isUsingItem() && !this.useItem.isEmpty()) { + Item item = this.useItem.getItem(); + +- return item.getUseAnimation(this.useItem) != ItemUseAnimation.BLOCK ? null : (item.getUseDuration(this.useItem, this) - this.useItemRemaining < 5 ? null : this.useItem); ++ return item.getUseAnimation(this.useItem) != ItemUseAnimation.BLOCK ? null : (item.getUseDuration(this.useItem, this) - this.useItemRemaining < getShieldBlockingDelay() ? null : this.useItem); // Paper - Make shield blocking delay configurable + } else { + return null; + } + } + ++ // Paper start - Make shield blocking delay configurable ++ public int shieldBlockingDelay = this.level().paperConfig().misc.shieldBlockingDelay; ++ ++ public int getShieldBlockingDelay() { ++ return shieldBlockingDelay; ++ } ++ ++ public void setShieldBlockingDelay(int shieldBlockingDelay) { ++ this.shieldBlockingDelay = shieldBlockingDelay; ++ } ++ // Paper end - Make shield blocking delay configurable ++ + public boolean isSuppressingSlidingDownLadder() { + return this.isShiftKeyDown(); + } +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftLivingEntity.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftLivingEntity.java +index 452fe788152f7c38ab4b6c627e3ba37da3d9b9d9..e5186fbd4030d7b39443090b711e855dcbc39426 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftLivingEntity.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftLivingEntity.java +@@ -873,5 +873,15 @@ public class CraftLivingEntity extends CraftEntity implements LivingEntity { + public void setArrowsStuck(final int arrows) { + this.getHandle().setArrowCount(arrows); + } ++ ++ @Override ++ public int getShieldBlockingDelay() { ++ return getHandle().getShieldBlockingDelay(); ++ } ++ ++ @Override ++ public void setShieldBlockingDelay(int delay) { ++ getHandle().setShieldBlockingDelay(delay); ++ } + // Paper end + } diff --git a/patches/server/0198-Print-Error-details-when-failing-to-save-player-data.patch b/patches/server/0198-Print-Error-details-when-failing-to-save-player-data.patch deleted file mode 100644 index 19f2e71553a0..000000000000 --- a/patches/server/0198-Print-Error-details-when-failing-to-save-player-data.patch +++ /dev/null @@ -1,19 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Aikar -Date: Fri, 15 Jun 2018 20:37:03 -0400 -Subject: [PATCH] Print Error details when failing to save player data - - -diff --git a/src/main/java/net/minecraft/world/level/storage/PlayerDataStorage.java b/src/main/java/net/minecraft/world/level/storage/PlayerDataStorage.java -index e11c8523e633d2a8e3cea7ecd54978b2958b9684..1d287dd7379e56f7fd4b425880b850cd843f5789 100644 ---- a/src/main/java/net/minecraft/world/level/storage/PlayerDataStorage.java -+++ b/src/main/java/net/minecraft/world/level/storage/PlayerDataStorage.java -@@ -47,7 +47,7 @@ public class PlayerDataStorage { - - Util.safeReplaceFile(path2, path1, path3); - } catch (Exception exception) { -- PlayerDataStorage.LOGGER.warn("Failed to save player data for {}", player.getName().getString()); -+ PlayerDataStorage.LOGGER.warn("Failed to save player data for {}", player.getScoreboardName(), exception); // Paper - Print exception - } - - } diff --git a/patches/server/0199-Improve-EntityShootBowEvent.patch b/patches/server/0199-Improve-EntityShootBowEvent.patch new file mode 100644 index 000000000000..213e6f61c1f2 --- /dev/null +++ b/patches/server/0199-Improve-EntityShootBowEvent.patch @@ -0,0 +1,45 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Aikar +Date: Sat, 15 Jun 2013 19:51:17 -0400 +Subject: [PATCH] Improve EntityShootBowEvent + +Adds missing call to Illagers and also adds Arrow ItemStack to skeletons + +== AT == +public net.minecraft.world.entity.projectile.AbstractArrow getPickupItem()Lnet.minecraft.world.item.ItemStack; + +diff --git a/src/main/java/net/minecraft/world/entity/monster/AbstractSkeleton.java b/src/main/java/net/minecraft/world/entity/monster/AbstractSkeleton.java +index b16979ae9ad9636d91d3f2cd80baf01250ec534d..8f63e27d904abb33492daf627d48d33d1193deef 100644 +--- a/src/main/java/net/minecraft/world/entity/monster/AbstractSkeleton.java ++++ b/src/main/java/net/minecraft/world/entity/monster/AbstractSkeleton.java +@@ -208,7 +208,7 @@ public abstract class AbstractSkeleton extends Monster implements RangedAttackMo + + if (world instanceof ServerLevel worldserver) { + // CraftBukkit start +- org.bukkit.event.entity.EntityShootBowEvent event = org.bukkit.craftbukkit.event.CraftEventFactory.callEntityShootBowEvent(this, this.getMainHandItem(), null, entityarrow, net.minecraft.world.InteractionHand.MAIN_HAND, 0.8F, true); ++ org.bukkit.event.entity.EntityShootBowEvent event = org.bukkit.craftbukkit.event.CraftEventFactory.callEntityShootBowEvent(this, this.getMainHandItem(), entityarrow.getPickupItem(), entityarrow, net.minecraft.world.InteractionHand.MAIN_HAND, 0.8F, true); // Paper - improve entity shhot bow event - add arrow stack to event + if (event.isCancelled()) { + event.getProjectile().remove(); + return; +diff --git a/src/main/java/net/minecraft/world/entity/monster/Illusioner.java b/src/main/java/net/minecraft/world/entity/monster/Illusioner.java +index 60388ab18d7a4ecb235e54ecfdb4088b690a6178..db3aac9ba711dcd18ffc35c4a745ecaec89d0166 100644 +--- a/src/main/java/net/minecraft/world/entity/monster/Illusioner.java ++++ b/src/main/java/net/minecraft/world/entity/monster/Illusioner.java +@@ -184,7 +184,17 @@ public class Illusioner extends SpellcasterIllager implements RangedAttackMob { + Level world = this.level(); + + if (world instanceof ServerLevel worldserver) { ++ // Paper start - EntityShootBowEvent ++ org.bukkit.event.entity.EntityShootBowEvent event = org.bukkit.craftbukkit.event.CraftEventFactory.callEntityShootBowEvent(this, this.getMainHandItem(), entityarrow.getPickupItem(), entityarrow, target.getUsedItemHand(), 0.8F, true); ++ if (event.isCancelled()) { ++ event.getProjectile().remove(); ++ return; ++ } ++ ++ if (event.getProjectile() == entityarrow.getBukkitEntity()) { + Projectile.spawnProjectileUsingShoot(entityarrow, worldserver, itemstack1, d0, d1 + d3 * 0.20000000298023224D, d2, 1.6F, (float) (14 - worldserver.getDifficulty().getId() * 4)); ++ } ++ // Paper end - EntityShootBowEvent + } + + this.playSound(SoundEvents.SKELETON_SHOOT, 1.0F, 1.0F / (this.getRandom().nextFloat() * 0.4F + 0.8F)); diff --git a/patches/server/0199-Make-shield-blocking-delay-configurable.patch b/patches/server/0199-Make-shield-blocking-delay-configurable.patch deleted file mode 100644 index c9b6092f8f7d..000000000000 --- a/patches/server/0199-Make-shield-blocking-delay-configurable.patch +++ /dev/null @@ -1,56 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: BillyGalbreath -Date: Sat, 16 Jun 2018 01:18:16 -0500 -Subject: [PATCH] Make shield blocking delay configurable - - -diff --git a/src/main/java/net/minecraft/world/entity/LivingEntity.java b/src/main/java/net/minecraft/world/entity/LivingEntity.java -index 7a7c404778757e6778305c9f8334a4fba1f466a6..d58439f85f4d3a9b863ecadb3b42b2ee3270a772 100644 ---- a/src/main/java/net/minecraft/world/entity/LivingEntity.java -+++ b/src/main/java/net/minecraft/world/entity/LivingEntity.java -@@ -3969,12 +3969,24 @@ public abstract class LivingEntity extends Entity implements Attackable { - if (this.isUsingItem() && !this.useItem.isEmpty()) { - Item item = this.useItem.getItem(); - -- return item.getUseAnimation(this.useItem) != UseAnim.BLOCK ? false : item.getUseDuration(this.useItem, this) - this.useItemRemaining >= 5; -+ return item.getUseAnimation(this.useItem) != UseAnim.BLOCK ? false : item.getUseDuration(this.useItem, this) - this.useItemRemaining >= getShieldBlockingDelay(); // Paper - Make shield blocking delay configurable - } else { - return false; - } - } - -+ // Paper start - Make shield blocking delay configurable -+ public int shieldBlockingDelay = this.level().paperConfig().misc.shieldBlockingDelay; -+ -+ public int getShieldBlockingDelay() { -+ return shieldBlockingDelay; -+ } -+ -+ public void setShieldBlockingDelay(int shieldBlockingDelay) { -+ this.shieldBlockingDelay = shieldBlockingDelay; -+ } -+ // Paper end - Make shield blocking delay configurable -+ - public boolean isSuppressingSlidingDownLadder() { - return this.isShiftKeyDown(); - } -diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftLivingEntity.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftLivingEntity.java -index 7a01569f62b3b27a9eb6def0e2ec72e1d392258d..73c72b8cdece357193afb3c5f474e055086311ea 100644 ---- a/src/main/java/org/bukkit/craftbukkit/entity/CraftLivingEntity.java -+++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftLivingEntity.java -@@ -853,5 +853,15 @@ public class CraftLivingEntity extends CraftEntity implements LivingEntity { - public void setArrowsStuck(final int arrows) { - this.getHandle().setArrowCount(arrows); - } -+ -+ @Override -+ public int getShieldBlockingDelay() { -+ return getHandle().getShieldBlockingDelay(); -+ } -+ -+ @Override -+ public void setShieldBlockingDelay(int delay) { -+ getHandle().setShieldBlockingDelay(delay); -+ } - // Paper end - } diff --git a/patches/server/0200-Improve-EntityShootBowEvent.patch b/patches/server/0200-Improve-EntityShootBowEvent.patch deleted file mode 100644 index cf1605426243..000000000000 --- a/patches/server/0200-Improve-EntityShootBowEvent.patch +++ /dev/null @@ -1,47 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Aikar -Date: Sat, 15 Jun 2013 19:51:17 -0400 -Subject: [PATCH] Improve EntityShootBowEvent - -Adds missing call to Illagers and also adds Arrow ItemStack to skeletons - -== AT == -public net.minecraft.world.entity.projectile.AbstractArrow getPickupItem()Lnet.minecraft.world.item.ItemStack; - -diff --git a/src/main/java/net/minecraft/world/entity/monster/AbstractSkeleton.java b/src/main/java/net/minecraft/world/entity/monster/AbstractSkeleton.java -index 1bf013a10502395d9f432f80c517d5c9a50f5eab..aec440d32eb97fa8ce738b98dae1cdc346e8a59b 100644 ---- a/src/main/java/net/minecraft/world/entity/monster/AbstractSkeleton.java -+++ b/src/main/java/net/minecraft/world/entity/monster/AbstractSkeleton.java -@@ -205,7 +205,7 @@ public abstract class AbstractSkeleton extends Monster implements RangedAttackMo - - entityarrow.shoot(d0, d1 + d3 * 0.20000000298023224D, d2, 1.6F, (float) (14 - this.level().getDifficulty().getId() * 4)); - // CraftBukkit start -- org.bukkit.event.entity.EntityShootBowEvent event = org.bukkit.craftbukkit.event.CraftEventFactory.callEntityShootBowEvent(this, this.getMainHandItem(), null, entityarrow, net.minecraft.world.InteractionHand.MAIN_HAND, 0.8F, true); -+ org.bukkit.event.entity.EntityShootBowEvent event = org.bukkit.craftbukkit.event.CraftEventFactory.callEntityShootBowEvent(this, this.getMainHandItem(), entityarrow.getPickupItem(), entityarrow, net.minecraft.world.InteractionHand.MAIN_HAND, 0.8F, true); // Paper - if (event.isCancelled()) { - event.getProjectile().remove(); - return; -diff --git a/src/main/java/net/minecraft/world/entity/monster/Illusioner.java b/src/main/java/net/minecraft/world/entity/monster/Illusioner.java -index 93e3454de0b0d62895f165b0772526f3eae1e333..c858556ea457931aa14e338e20672cb50cb19f0e 100644 ---- a/src/main/java/net/minecraft/world/entity/monster/Illusioner.java -+++ b/src/main/java/net/minecraft/world/entity/monster/Illusioner.java -@@ -185,8 +185,18 @@ public class Illusioner extends SpellcasterIllager implements RangedAttackMob { - double d3 = Math.sqrt(d0 * d0 + d2 * d2); - - entityarrow.shoot(d0, d1 + d3 * 0.20000000298023224D, d2, 1.6F, (float) (14 - this.level().getDifficulty().getId() * 4)); -+ // Paper start - EntityShootBowEvent -+ org.bukkit.event.entity.EntityShootBowEvent event = org.bukkit.craftbukkit.event.CraftEventFactory.callEntityShootBowEvent(this, this.getMainHandItem(), entityarrow.getPickupItem(), entityarrow, target.getUsedItemHand(), 0.8F, true); -+ if (event.isCancelled()) { -+ event.getProjectile().remove(); -+ return; -+ } -+ -+ if (event.getProjectile() == entityarrow.getBukkitEntity()) { -+ this.level().addFreshEntity(entityarrow); -+ } -+ // Paper end - EntityShootBowEvent - this.playSound(SoundEvents.SKELETON_SHOOT, 1.0F, 1.0F / (this.getRandom().nextFloat() * 0.4F + 0.8F)); -- this.level().addFreshEntity(entityarrow); - } - - @Override diff --git a/patches/server/0200-PlayerReadyArrowEvent.patch b/patches/server/0200-PlayerReadyArrowEvent.patch new file mode 100644 index 000000000000..8d16572d48da --- /dev/null +++ b/patches/server/0200-PlayerReadyArrowEvent.patch @@ -0,0 +1,44 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Aikar +Date: Mon, 18 Jun 2018 01:12:53 -0400 +Subject: [PATCH] PlayerReadyArrowEvent + +Called when a player is firing a bow and the server is choosing an arrow to use. +Plugins can skip selection of certain arrows and control which is used. + +diff --git a/src/main/java/net/minecraft/world/entity/player/Player.java b/src/main/java/net/minecraft/world/entity/player/Player.java +index 605f043bb132b21ff263ba7ab31a0f468a912d13..df0cfd316d24e285f927c713ab1f9a912857928c 100644 +--- a/src/main/java/net/minecraft/world/entity/player/Player.java ++++ b/src/main/java/net/minecraft/world/entity/player/Player.java +@@ -2185,18 +2185,29 @@ public abstract class Player extends LivingEntity { + return ImmutableList.of(Pose.STANDING, Pose.CROUCHING, Pose.SWIMMING); + } + ++ // Paper start - PlayerReadyArrowEvent ++ protected boolean tryReadyArrow(ItemStack bow, ItemStack itemstack) { ++ return !(this instanceof ServerPlayer) || ++ new com.destroystokyo.paper.event.player.PlayerReadyArrowEvent( ++ ((ServerPlayer) this).getBukkitEntity(), ++ org.bukkit.craftbukkit.inventory.CraftItemStack.asCraftMirror(bow), ++ org.bukkit.craftbukkit.inventory.CraftItemStack.asCraftMirror(itemstack) ++ ).callEvent(); ++ } ++ // Paper end - PlayerReadyArrowEvent ++ + @Override + public ItemStack getProjectile(ItemStack stack) { + if (!(stack.getItem() instanceof ProjectileWeaponItem)) { + return ItemStack.EMPTY; + } else { +- Predicate predicate = ((ProjectileWeaponItem) stack.getItem()).getSupportedHeldProjectiles(); ++ Predicate predicate = ((ProjectileWeaponItem) stack.getItem()).getSupportedHeldProjectiles().and(item -> tryReadyArrow(stack, item)); // Paper - PlayerReadyArrowEvent + ItemStack itemstack1 = ProjectileWeaponItem.getHeldProjectile(this, predicate); + + if (!itemstack1.isEmpty()) { + return itemstack1; + } else { +- predicate = ((ProjectileWeaponItem) stack.getItem()).getAllSupportedProjectiles(); ++ predicate = ((ProjectileWeaponItem) stack.getItem()).getAllSupportedProjectiles().and(item -> tryReadyArrow(stack, item)); // Paper - PlayerReadyArrowEvent + + for (int i = 0; i < this.inventory.getContainerSize(); ++i) { + ItemStack itemstack2 = this.inventory.getItem(i); diff --git a/patches/server/0201-Add-entity-knockback-events.patch b/patches/server/0201-Add-entity-knockback-events.patch new file mode 100644 index 000000000000..8615d22e3dc7 --- /dev/null +++ b/patches/server/0201-Add-entity-knockback-events.patch @@ -0,0 +1,319 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Brokkonaut +Date: Mon, 18 Jun 2018 15:46:23 +0200 +Subject: [PATCH] Add entity knockback events + +- EntityKnockbackEvent +- EntityPushedByEntityAttackEvent +- EntityKnockbackByEntityEvent + +Co-authored-by: aerulion +Co-authored-by: Jake Potrebic + +diff --git a/src/main/java/net/minecraft/world/entity/Entity.java b/src/main/java/net/minecraft/world/entity/Entity.java +index 087f030985180b91a809fb45244e23106da62e34..011006bc2e88a9fec98796f939c07d884b92126b 100644 +--- a/src/main/java/net/minecraft/world/entity/Entity.java ++++ b/src/main/java/net/minecraft/world/entity/Entity.java +@@ -2050,7 +2050,21 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess + } + + public void push(double deltaX, double deltaY, double deltaZ) { +- this.setDeltaMovement(this.getDeltaMovement().add(deltaX, deltaY, deltaZ)); ++ // Paper start - Add EntityKnockbackByEntityEvent and EntityPushedByEntityAttackEvent ++ this.push(deltaX, deltaY, deltaZ, null); ++ } ++ ++ public void push(double deltaX, double deltaY, double deltaZ, @org.jetbrains.annotations.Nullable Entity pushingEntity) { ++ org.bukkit.util.Vector delta = new org.bukkit.util.Vector(deltaX, deltaY, deltaZ); ++ if (pushingEntity != null) { ++ io.papermc.paper.event.entity.EntityPushedByEntityAttackEvent event = new io.papermc.paper.event.entity.EntityPushedByEntityAttackEvent(this.getBukkitEntity(), io.papermc.paper.event.entity.EntityKnockbackEvent.Cause.PUSH, pushingEntity.getBukkitEntity(), delta); ++ if (!event.callEvent()) { ++ return; ++ } ++ delta = event.getKnockback(); ++ } ++ this.setDeltaMovement(this.getDeltaMovement().add(delta.getX(), delta.getY(), delta.getZ())); ++ // Paper end - Add EntityKnockbackByEntityEvent and EntityPushedByEntityAttackEvent + this.hasImpulse = true; + } + +diff --git a/src/main/java/net/minecraft/world/entity/LivingEntity.java b/src/main/java/net/minecraft/world/entity/LivingEntity.java +index 08a2fbca50e26938e46e49dae7b101cfc375b02e..cfff596d720efe5f5ee4ad1990c3ee0fd6e4e836 100644 +--- a/src/main/java/net/minecraft/world/entity/LivingEntity.java ++++ b/src/main/java/net/minecraft/world/entity/LivingEntity.java +@@ -1563,7 +1563,7 @@ public abstract class LivingEntity extends Entity implements Attackable { + d1 = source.getSourcePosition().z() - this.getZ(); + } + +- this.knockback(0.4000000059604645D, d0, d1, entity1, entity1 == null ? EntityKnockbackEvent.KnockbackCause.DAMAGE : EntityKnockbackEvent.KnockbackCause.ENTITY_ATTACK); // CraftBukkit ++ this.knockback(0.4000000059604645D, d0, d1, entity1, entity1 == null ? io.papermc.paper.event.entity.EntityKnockbackEvent.Cause.DAMAGE : io.papermc.paper.event.entity.EntityKnockbackEvent.Cause.ENTITY_ATTACK); // CraftBukkit // Paper - knockback events + if (!flag) { + this.indicateDamage(d0, d1); + } +@@ -1620,7 +1620,7 @@ public abstract class LivingEntity extends Entity implements Attackable { + } + + protected void blockedByShield(LivingEntity target) { +- target.knockback(0.5D, target.getX() - this.getX(), target.getZ() - this.getZ(), null, EntityKnockbackEvent.KnockbackCause.SHIELD_BLOCK); // CraftBukkit ++ target.knockback(0.5D, target.getX() - this.getX(), target.getZ() - this.getZ(), this, io.papermc.paper.event.entity.EntityKnockbackEvent.Cause.SHIELD_BLOCK); // CraftBukkit // Paper - fix attacker & knockback events + } + + private boolean checkTotemDeathProtection(DamageSource source) { +@@ -1906,10 +1906,10 @@ public abstract class LivingEntity extends Entity implements Attackable { + + public void knockback(double strength, double x, double z) { + // CraftBukkit start - EntityKnockbackEvent +- this.knockback(strength, x, z, null, EntityKnockbackEvent.KnockbackCause.UNKNOWN); ++ this.knockback(strength, x, z, null, io.papermc.paper.event.entity.EntityKnockbackEvent.Cause.UNKNOWN); // Paper - knockback events + } + +- public void knockback(double d0, double d1, double d2, Entity attacker, EntityKnockbackEvent.KnockbackCause cause) { ++ public void knockback(double d0, double d1, double d2, @Nullable Entity attacker, io.papermc.paper.event.entity.EntityKnockbackEvent.Cause cause) { // Paper - knockback events + d0 *= 1.0D - this.getAttributeValue(Attributes.KNOCKBACK_RESISTANCE); + if (true || d0 > 0.0D) { // CraftBukkit - Call event even when force is 0 + //this.hasImpulse = true; // CraftBukkit - Move down +@@ -1922,13 +1922,17 @@ public abstract class LivingEntity extends Entity implements Attackable { + + Vec3 vec3d1 = (new Vec3(d1, 0.0D, d2)).normalize().scale(d0); + +- EntityKnockbackEvent event = CraftEventFactory.callEntityKnockbackEvent((org.bukkit.craftbukkit.entity.CraftLivingEntity) this.getBukkitEntity(), attacker, cause, d0, vec3d1, vec3d.x / 2.0D - vec3d1.x, this.onGround() ? Math.min(0.4D, vec3d.y / 2.0D + d0) : vec3d.y, vec3d.z / 2.0D - vec3d1.z); ++ // Paper start - knockback events ++ Vec3 finalVelocity = new Vec3(vec3d.x / 2.0D - vec3d1.x, this.onGround() ? Math.min(0.4D, vec3d.y / 2.0D + d0) : vec3d.y, vec3d.z / 2.0D - vec3d1.z); ++ Vec3 diff = finalVelocity.subtract(vec3d); ++ io.papermc.paper.event.entity.EntityKnockbackEvent event = CraftEventFactory.callEntityKnockbackEvent((org.bukkit.craftbukkit.entity.CraftLivingEntity) this.getBukkitEntity(), attacker, attacker, cause, d0, diff); ++ // Paper end - knockback events + if (event.isCancelled()) { + return; + } + + this.hasImpulse = true; +- this.setDeltaMovement(event.getFinalKnockback().getX(), event.getFinalKnockback().getY(), event.getFinalKnockback().getZ()); ++ this.setDeltaMovement(vec3d.add(event.getKnockback().getX(), event.getKnockback().getY(), event.getKnockback().getZ())); // Paper - knockback events + // CraftBukkit end + } + } +diff --git a/src/main/java/net/minecraft/world/entity/Mob.java b/src/main/java/net/minecraft/world/entity/Mob.java +index 51381c32c8650400331a122a4b6c8d95c9d0632b..2b8bfccbf520f9a356f816522ac3a5caa51e44e1 100644 +--- a/src/main/java/net/minecraft/world/entity/Mob.java ++++ b/src/main/java/net/minecraft/world/entity/Mob.java +@@ -1685,7 +1685,7 @@ public abstract class Mob extends LivingEntity implements EquipmentUser, Leashab + + if (f1 > 0.0F && target instanceof LivingEntity) { + entityliving = (LivingEntity) target; +- entityliving.knockback((double) (f1 * 0.5F), (double) Mth.sin(this.getYRot() * 0.017453292F), (double) (-Mth.cos(this.getYRot() * 0.017453292F)), this, org.bukkit.event.entity.EntityKnockbackEvent.KnockbackCause.ENTITY_ATTACK); // CraftBukkit ++ entityliving.knockback((double) (f1 * 0.5F), (double) Mth.sin(this.getYRot() * 0.017453292F), (double) (-Mth.cos(this.getYRot() * 0.017453292F)), this, io.papermc.paper.event.entity.EntityKnockbackEvent.Cause.ENTITY_ATTACK); // CraftBukkit // Paper - knockback events + this.setDeltaMovement(this.getDeltaMovement().multiply(0.6D, 1.0D, 0.6D)); + } + +diff --git a/src/main/java/net/minecraft/world/entity/ai/behavior/RamTarget.java b/src/main/java/net/minecraft/world/entity/ai/behavior/RamTarget.java +index 8bf5f18b5835ac97114dfea7dde3876c13433ab4..de81e34cf65318fa5e3034405719ff2a2b05458c 100644 +--- a/src/main/java/net/minecraft/world/entity/ai/behavior/RamTarget.java ++++ b/src/main/java/net/minecraft/world/entity/ai/behavior/RamTarget.java +@@ -89,7 +89,7 @@ public class RamTarget extends Behavior { + float f = 0.25F * (float)(i - j); + float g = Mth.clamp(entity.getSpeed() * 1.65F, 0.2F, 3.0F) + f; + float h = livingEntity.isDamageSourceBlocked(world.damageSources().mobAttack(entity)) ? 0.5F : 1.0F; +- livingEntity.knockback((double)(h * g) * this.getKnockbackForce.applyAsDouble(entity), this.ramDirection.x(), this.ramDirection.z()); ++ livingEntity.knockback(h * g * this.getKnockbackForce.applyAsDouble(entity), this.ramDirection.x(), this.ramDirection.z(), entity, io.papermc.paper.event.entity.EntityKnockbackEvent.Cause.ENTITY_ATTACK); // Paper - Add EntityKnockbackByEntityEvent and EntityPushedByEntityAttackEvent + this.finishRam(world, entity); + world.playSound(null, entity, this.getImpactSound.apply(entity), SoundSource.NEUTRAL, 1.0F, 1.0F); + } else if (this.hasRammedHornBreakingBlock(world, entity)) { +diff --git a/src/main/java/net/minecraft/world/entity/ai/behavior/warden/SonicBoom.java b/src/main/java/net/minecraft/world/entity/ai/behavior/warden/SonicBoom.java +index dbae113f3447f66e154b302ce0634b0fe446381c..d0fb32feb10f75444e3ed0a50c70ef6f12fb8e18 100644 +--- a/src/main/java/net/minecraft/world/entity/ai/behavior/warden/SonicBoom.java ++++ b/src/main/java/net/minecraft/world/entity/ai/behavior/warden/SonicBoom.java +@@ -83,7 +83,7 @@ public class SonicBoom extends Behavior { + if (target.hurtServer(world, world.damageSources().sonicBoom(entity), 10.0F)) { + double d = 0.5 * (1.0 - target.getAttributeValue(Attributes.KNOCKBACK_RESISTANCE)); + double e = 2.5 * (1.0 - target.getAttributeValue(Attributes.KNOCKBACK_RESISTANCE)); +- target.push(vec33.x() * e, vec33.y() * d, vec33.z() * e); ++ target.push(vec33.x() * e, vec33.y() * d, vec33.z() * e, entity); // Paper - Add EntityKnockbackByEntityEvent and EntityPushedByEntityAttackEvent + } + }); + } +diff --git a/src/main/java/net/minecraft/world/entity/boss/enderdragon/EnderDragon.java b/src/main/java/net/minecraft/world/entity/boss/enderdragon/EnderDragon.java +index 0012ab1f56180081d210c8836e2a59d543b950b8..ba1bb0f82634054e02c5f4bc062c1822a356e2a6 100644 +--- a/src/main/java/net/minecraft/world/entity/boss/enderdragon/EnderDragon.java ++++ b/src/main/java/net/minecraft/world/entity/boss/enderdragon/EnderDragon.java +@@ -441,7 +441,7 @@ public class EnderDragon extends Mob implements Enemy { + double d3 = entity.getZ() - d1; + double d4 = Math.max(d2 * d2 + d3 * d3, 0.1D); + +- entity.push(d2 / d4 * 4.0D, 0.20000000298023224D, d3 / d4 * 4.0D); ++ entity.push(d2 / d4 * 4.0D, 0.20000000298023224D, d3 / d4 * 4.0D, this); // Paper - Add EntityKnockbackByEntityEvent and EntityPushedByEntityAttackEvent + if (!this.phaseManager.getCurrentPhase().isSitting() && entityliving.getLastHurtByMobTimestamp() < entity.tickCount - 2) { + DamageSource damagesource = this.damageSources().mobAttack(this); + +diff --git a/src/main/java/net/minecraft/world/entity/decoration/BlockAttachedEntity.java b/src/main/java/net/minecraft/world/entity/decoration/BlockAttachedEntity.java +index 19f9f4bceca158d9b35be2cdc4d7aa502f44a4b9..cd0a2270b58b6c0dba67270e7e1d1ba253eb7953 100644 +--- a/src/main/java/net/minecraft/world/entity/decoration/BlockAttachedEntity.java ++++ b/src/main/java/net/minecraft/world/entity/decoration/BlockAttachedEntity.java +@@ -161,7 +161,7 @@ public abstract class BlockAttachedEntity extends Entity { + } + + @Override +- public void push(double deltaX, double deltaY, double deltaZ) { ++ public void push(double deltaX, double deltaY, double deltaZ, @Nullable Entity pushingEntity) { // Paper - override correct overload + Level world = this.level(); + + if (world instanceof ServerLevel worldserver) { +diff --git a/src/main/java/net/minecraft/world/entity/decoration/ItemFrame.java b/src/main/java/net/minecraft/world/entity/decoration/ItemFrame.java +index 6a90feef8eb2d65c925c44bc97305a51e3fb7cf0..7dfbcb2ed831cff5d002d79fb81514e6abf587a4 100644 +--- a/src/main/java/net/minecraft/world/entity/decoration/ItemFrame.java ++++ b/src/main/java/net/minecraft/world/entity/decoration/ItemFrame.java +@@ -129,9 +129,9 @@ public class ItemFrame extends HangingEntity { + } + + @Override +- public void push(double deltaX, double deltaY, double deltaZ) { ++ public void push(double deltaX, double deltaY, double deltaZ, @org.jetbrains.annotations.Nullable Entity pushingEntity) { // Paper - add push source entity param + if (!this.fixed) { +- super.push(deltaX, deltaY, deltaZ); ++ super.push(deltaX, deltaY, deltaZ, pushingEntity); // Paper - add push source entity param + } + + } +diff --git a/src/main/java/net/minecraft/world/entity/monster/Ravager.java b/src/main/java/net/minecraft/world/entity/monster/Ravager.java +index c4397afa53a36ecf867525c4dcf2231a52d058dd..32973d9c7bf4c4105d7bd82d3723e0b70cd79644 100644 +--- a/src/main/java/net/minecraft/world/entity/monster/Ravager.java ++++ b/src/main/java/net/minecraft/world/entity/monster/Ravager.java +@@ -292,7 +292,7 @@ public class Ravager extends Raider { + double d1 = entity.getZ() - this.getZ(); + double d2 = Math.max(d0 * d0 + d1 * d1, 0.001D); + +- entity.push(d0 / d2 * 4.0D, 0.2D, d1 / d2 * 4.0D); ++ entity.push(d0 / d2 * 4.0D, 0.2D, d1 / d2 * 4.0D, this); // Paper - Add EntityKnockbackByEntityEvent and EntityPushedByEntityAttackEvent + } + + @Override +diff --git a/src/main/java/net/minecraft/world/entity/monster/hoglin/HoglinBase.java b/src/main/java/net/minecraft/world/entity/monster/hoglin/HoglinBase.java +index 8b80f5c2749106f634d962fd0f381aa20e7ef335..a1e3375519fe676b14788d6729a7e71d089cca37 100644 +--- a/src/main/java/net/minecraft/world/entity/monster/hoglin/HoglinBase.java ++++ b/src/main/java/net/minecraft/world/entity/monster/hoglin/HoglinBase.java +@@ -45,7 +45,7 @@ public interface HoglinBase { + double j = f * (double)(attacker.level().random.nextFloat() * 0.5F + 0.2F); + Vec3 vec3 = new Vec3(g, 0.0, h).normalize().scale(j).yRot(i); + double k = f * (double)attacker.level().random.nextFloat() * 0.5; +- target.push(vec3.x, k, vec3.z); ++ target.push(vec3.x, k, vec3.z, attacker); // Paper - Add EntityKnockbackByEntityEvent and EntityPushedByEntityAttackEvent + target.hurtMarked = true; + } + } +diff --git a/src/main/java/net/minecraft/world/entity/player/Player.java b/src/main/java/net/minecraft/world/entity/player/Player.java +index df0cfd316d24e285f927c713ab1f9a912857928c..ba0ea4d6b67c9c52f170e0235e4b972aad3ce211 100644 +--- a/src/main/java/net/minecraft/world/entity/player/Player.java ++++ b/src/main/java/net/minecraft/world/entity/player/Player.java +@@ -1271,9 +1271,9 @@ public abstract class Player extends LivingEntity { + if (target instanceof LivingEntity) { + LivingEntity entityliving1 = (LivingEntity) target; + +- entityliving1.knockback((double) (f5 * 0.5F), (double) Mth.sin(this.getYRot() * 0.017453292F), (double) (-Mth.cos(this.getYRot() * 0.017453292F))); ++ entityliving1.knockback((double) (f5 * 0.5F), (double) Mth.sin(this.getYRot() * 0.017453292F), (double) (-Mth.cos(this.getYRot() * 0.017453292F)), this, io.papermc.paper.event.entity.EntityKnockbackEvent.Cause.ENTITY_ATTACK); // Paper - knockback events + } else { +- target.push((double) (-Mth.sin(this.getYRot() * 0.017453292F) * f5 * 0.5F), 0.1D, (double) (Mth.cos(this.getYRot() * 0.017453292F) * f5 * 0.5F)); ++ target.push((double) (-Mth.sin(this.getYRot() * 0.017453292F) * f5 * 0.5F), 0.1D, (double) (Mth.cos(this.getYRot() * 0.017453292F) * f5 * 0.5F), this); // Paper - Add EntityKnockbackByEntityEvent and EntityPushedByEntityAttackEvent + } + + this.setDeltaMovement(this.getDeltaMovement().multiply(0.6D, 1.0D, 0.6D)); +@@ -1301,7 +1301,7 @@ public abstract class Player extends LivingEntity { + continue; + } + // CraftBukkit end +- entityliving2.knockback(0.4000000059604645D, (double) Mth.sin(this.getYRot() * 0.017453292F), (double) (-Mth.cos(this.getYRot() * 0.017453292F))); ++ entityliving2.knockback(0.4000000059604645D, (double) Mth.sin(this.getYRot() * 0.017453292F), (double) (-Mth.cos(this.getYRot() * 0.017453292F)), this, io.papermc.paper.event.entity.EntityKnockbackEvent.Cause.SWEEP_ATTACK); // CraftBukkit // Paper - knockback events + // entityliving2.hurt(damagesource, f7); // CraftBukkit - moved up + Level world = this.level(); + +diff --git a/src/main/java/net/minecraft/world/entity/projectile/AbstractArrow.java b/src/main/java/net/minecraft/world/entity/projectile/AbstractArrow.java +index a1b1c6385d3a1fbe38f5ae4471b8e4f6ef2c80b3..78053060dbff7f8a859f9fecb356491f497eed7e 100644 +--- a/src/main/java/net/minecraft/world/entity/projectile/AbstractArrow.java ++++ b/src/main/java/net/minecraft/world/entity/projectile/AbstractArrow.java +@@ -553,7 +553,7 @@ public abstract class AbstractArrow extends Projectile { + Vec3 vec3d = this.getDeltaMovement().multiply(1.0D, 0.0D, 1.0D).normalize().scale(d0 * 0.6D * d1); + + if (vec3d.lengthSqr() > 0.0D) { +- target.push(vec3d.x, 0.1D, vec3d.z); ++ target.push(vec3d.x, 0.1D, vec3d.z, this); // Paper - Add EntityKnockbackByEntityEvent and EntityPushedByEntityAttackEvent + } + } + +diff --git a/src/main/java/net/minecraft/world/entity/projectile/windcharge/AbstractWindCharge.java b/src/main/java/net/minecraft/world/entity/projectile/windcharge/AbstractWindCharge.java +index 6541ab0d8da28785b19fa50d18d6a484ea38cef8..a325a223d06e155c01f664562db0edd0b180e356 100644 +--- a/src/main/java/net/minecraft/world/entity/projectile/windcharge/AbstractWindCharge.java ++++ b/src/main/java/net/minecraft/world/entity/projectile/windcharge/AbstractWindCharge.java +@@ -101,7 +101,7 @@ public abstract class AbstractWindCharge extends AbstractHurtingProjectile imple + } + + @Override +- public void push(double deltaX, double deltaY, double deltaZ) {} ++ public void push(double deltaX, double deltaY, double deltaZ, @org.jetbrains.annotations.Nullable Entity pushingEntity) {} // Paper - Add EntityKnockbackByEntityEvent and EntityPushedByEntityAttackEvent + + public abstract void explode(Vec3 pos); + +diff --git a/src/main/java/net/minecraft/world/level/ServerExplosion.java b/src/main/java/net/minecraft/world/level/ServerExplosion.java +index 3afacd201683f46fd75cd6f9a7f3d43a7050cf4a..9f37d7284c81d529551107e2836627977efabd65 100644 +--- a/src/main/java/net/minecraft/world/level/ServerExplosion.java ++++ b/src/main/java/net/minecraft/world/level/ServerExplosion.java +@@ -258,13 +258,10 @@ public class ServerExplosion implements Explosion { + + // CraftBukkit start - Call EntityKnockbackEvent + if (entity instanceof LivingEntity) { +- Vec3 result = entity.getDeltaMovement().add(vec3d); +- org.bukkit.event.entity.EntityKnockbackEvent event = CraftEventFactory.callEntityKnockbackEvent((org.bukkit.craftbukkit.entity.CraftLivingEntity) entity.getBukkitEntity(), this.source, org.bukkit.event.entity.EntityKnockbackEvent.KnockbackCause.EXPLOSION, d6, vec3d, result.x, result.y, result.z); +- +- // SPIGOT-7640: Need to subtract entity movement from the event result, +- // since the code below (the setDeltaMovement call as well as the hitPlayers map) +- // want the vector to be the relative velocity will the event provides the absolute velocity +- vec3d = (event.isCancelled()) ? Vec3.ZERO : new Vec3(event.getFinalKnockback().getX(), event.getFinalKnockback().getY(), event.getFinalKnockback().getZ()).subtract(entity.getDeltaMovement()); ++ // Paper start - knockback events ++ io.papermc.paper.event.entity.EntityKnockbackEvent event = CraftEventFactory.callEntityKnockbackEvent((org.bukkit.craftbukkit.entity.CraftLivingEntity) entity.getBukkitEntity(), this.source, this.damageSource.getEntity() != null ? this.damageSource.getEntity() : this.source, io.papermc.paper.event.entity.EntityKnockbackEvent.Cause.EXPLOSION, d6, vec3d); ++ vec3d = event.isCancelled() ? Vec3.ZERO : org.bukkit.craftbukkit.util.CraftVector.toNMS(event.getKnockback()); ++ // Paper end - knockback events + } + // CraftBukkit end + entity.setDeltaMovement(entity.getDeltaMovement().add(vec3d)); +diff --git a/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java b/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java +index f05a19f868be0d685c4fbf64e1dfbefda71131ac..60a16ac62a27a92b763bf90e72fab4ed01aeb592 100644 +--- a/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java ++++ b/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java +@@ -1936,19 +1936,33 @@ public class CraftEventFactory { + return event; + } + +- public static EntityKnockbackEvent callEntityKnockbackEvent(CraftLivingEntity entity, Entity attacker, EntityKnockbackEvent.KnockbackCause cause, double force, Vec3 raw, double x, double y, double z) { +- Vector bukkitRaw = new Vector(-raw.x, raw.y, -raw.z); // Due to how the knockback calculation works, we need to invert x and z. ++ // Paper start - replace knockback events ++ public static io.papermc.paper.event.entity.EntityKnockbackEvent callEntityKnockbackEvent(CraftLivingEntity entity, Entity pusher, Entity attacker, io.papermc.paper.event.entity.EntityKnockbackEvent.Cause cause, double force, Vec3 knockback) { ++ Vector apiKnockback = CraftVector.toBukkit(knockback); ++ ++ final Vector currentVelocity = entity.getVelocity(); ++ final Vector legacyFinalKnockback = currentVelocity.clone().add(apiKnockback); ++ final org.bukkit.event.entity.EntityKnockbackEvent.KnockbackCause legacyCause = org.bukkit.event.entity.EntityKnockbackEvent.KnockbackCause.valueOf(cause.name()); ++ EntityKnockbackEvent legacyEvent; ++ if (pusher != null) { ++ legacyEvent = new EntityKnockbackByEntityEvent(entity, pusher.getBukkitEntity(), legacyCause, force, apiKnockback, legacyFinalKnockback); ++ } else { ++ legacyEvent = new EntityKnockbackEvent(entity, legacyCause, force, apiKnockback, legacyFinalKnockback); ++ } ++ legacyEvent.callEvent(); + +- EntityKnockbackEvent event; ++ final io.papermc.paper.event.entity.EntityKnockbackEvent event; ++ apiKnockback = legacyEvent.getFinalKnockback().subtract(currentVelocity); + if (attacker != null) { +- event = new EntityKnockbackByEntityEvent(entity, attacker.getBukkitEntity(), cause, force, new Vector(-raw.x, raw.y, -raw.z), new Vector(x, y, z)); ++ event = new com.destroystokyo.paper.event.entity.EntityKnockbackByEntityEvent(entity, attacker.getBukkitEntity(), cause, (float) force, apiKnockback); + } else { +- event = new EntityKnockbackEvent(entity, cause, force, new Vector(-raw.x, raw.y, -raw.z), new Vector(x, y, z)); ++ event = new io.papermc.paper.event.entity.EntityKnockbackEvent(entity, cause, apiKnockback); + } +- +- Bukkit.getPluginManager().callEvent(event); ++ event.setCancelled(legacyEvent.isCancelled()); ++ event.callEvent(); + return event; + } ++ // Paper end - replace knockback events + + public static void callEntityRemoveEvent(Entity entity, EntityRemoveEvent.Cause cause) { + if (entity instanceof ServerPlayer) { diff --git a/patches/server/0201-PlayerReadyArrowEvent.patch b/patches/server/0201-PlayerReadyArrowEvent.patch deleted file mode 100644 index 37f2fbca4e11..000000000000 --- a/patches/server/0201-PlayerReadyArrowEvent.patch +++ /dev/null @@ -1,44 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Aikar -Date: Mon, 18 Jun 2018 01:12:53 -0400 -Subject: [PATCH] PlayerReadyArrowEvent - -Called when a player is firing a bow and the server is choosing an arrow to use. -Plugins can skip selection of certain arrows and control which is used. - -diff --git a/src/main/java/net/minecraft/world/entity/player/Player.java b/src/main/java/net/minecraft/world/entity/player/Player.java -index 00fecf255214d87837867b8743cc8f161ebfcb4c..d6bbeaf3833a4c44ed05243445edd813e3f15dcb 100644 ---- a/src/main/java/net/minecraft/world/entity/player/Player.java -+++ b/src/main/java/net/minecraft/world/entity/player/Player.java -@@ -2232,18 +2232,29 @@ public abstract class Player extends LivingEntity { - return ImmutableList.of(Pose.STANDING, Pose.CROUCHING, Pose.SWIMMING); - } - -+ // Paper start - PlayerReadyArrowEvent -+ protected boolean tryReadyArrow(ItemStack bow, ItemStack itemstack) { -+ return !(this instanceof ServerPlayer) || -+ new com.destroystokyo.paper.event.player.PlayerReadyArrowEvent( -+ ((ServerPlayer) this).getBukkitEntity(), -+ org.bukkit.craftbukkit.inventory.CraftItemStack.asCraftMirror(bow), -+ org.bukkit.craftbukkit.inventory.CraftItemStack.asCraftMirror(itemstack) -+ ).callEvent(); -+ } -+ // Paper end - PlayerReadyArrowEvent -+ - @Override - public ItemStack getProjectile(ItemStack stack) { - if (!(stack.getItem() instanceof ProjectileWeaponItem)) { - return ItemStack.EMPTY; - } else { -- Predicate predicate = ((ProjectileWeaponItem) stack.getItem()).getSupportedHeldProjectiles(); -+ Predicate predicate = ((ProjectileWeaponItem) stack.getItem()).getSupportedHeldProjectiles().and(item -> tryReadyArrow(stack, item)); // Paper - PlayerReadyArrowEvent - ItemStack itemstack1 = ProjectileWeaponItem.getHeldProjectile(this, predicate); - - if (!itemstack1.isEmpty()) { - return itemstack1; - } else { -- predicate = ((ProjectileWeaponItem) stack.getItem()).getAllSupportedProjectiles(); -+ predicate = ((ProjectileWeaponItem) stack.getItem()).getAllSupportedProjectiles().and(item -> tryReadyArrow(stack, item)); // Paper - PlayerReadyArrowEvent - - for (int i = 0; i < this.inventory.getContainerSize(); ++i) { - ItemStack itemstack2 = this.inventory.getItem(i); diff --git a/patches/server/0202-Add-entity-knockback-events.patch b/patches/server/0202-Add-entity-knockback-events.patch deleted file mode 100644 index 699b9c97039b..000000000000 --- a/patches/server/0202-Add-entity-knockback-events.patch +++ /dev/null @@ -1,319 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Brokkonaut -Date: Mon, 18 Jun 2018 15:46:23 +0200 -Subject: [PATCH] Add entity knockback events - -- EntityKnockbackEvent -- EntityPushedByEntityAttackEvent -- EntityKnockbackByEntityEvent - -Co-authored-by: aerulion -Co-authored-by: Jake Potrebic - -diff --git a/src/main/java/net/minecraft/world/entity/Entity.java b/src/main/java/net/minecraft/world/entity/Entity.java -index fd49b9d739b1bbab8cf110659cb83bad03b56102..a2b727dea997ed0d7b1ef677a94b5957f12d5abb 100644 ---- a/src/main/java/net/minecraft/world/entity/Entity.java -+++ b/src/main/java/net/minecraft/world/entity/Entity.java -@@ -1962,7 +1962,21 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess - } - - public void push(double deltaX, double deltaY, double deltaZ) { -- this.setDeltaMovement(this.getDeltaMovement().add(deltaX, deltaY, deltaZ)); -+ // Paper start - Add EntityKnockbackByEntityEvent and EntityPushedByEntityAttackEvent -+ this.push(deltaX, deltaY, deltaZ, null); -+ } -+ -+ public void push(double deltaX, double deltaY, double deltaZ, @org.jetbrains.annotations.Nullable Entity pushingEntity) { -+ org.bukkit.util.Vector delta = new org.bukkit.util.Vector(deltaX, deltaY, deltaZ); -+ if (pushingEntity != null) { -+ io.papermc.paper.event.entity.EntityPushedByEntityAttackEvent event = new io.papermc.paper.event.entity.EntityPushedByEntityAttackEvent(this.getBukkitEntity(), io.papermc.paper.event.entity.EntityKnockbackEvent.Cause.PUSH, pushingEntity.getBukkitEntity(), delta); -+ if (!event.callEvent()) { -+ return; -+ } -+ delta = event.getKnockback(); -+ } -+ this.setDeltaMovement(this.getDeltaMovement().add(delta.getX(), delta.getY(), delta.getZ())); -+ // Paper end - Add EntityKnockbackByEntityEvent and EntityPushedByEntityAttackEvent - this.hasImpulse = true; - } - -diff --git a/src/main/java/net/minecraft/world/entity/LivingEntity.java b/src/main/java/net/minecraft/world/entity/LivingEntity.java -index cccc60602360f25f0aeddbd16dad2bb63a1728a8..ada79af49d1cafe25ca6c1fb456e1c4c3a42cb73 100644 ---- a/src/main/java/net/minecraft/world/entity/LivingEntity.java -+++ b/src/main/java/net/minecraft/world/entity/LivingEntity.java -@@ -1528,7 +1528,7 @@ public abstract class LivingEntity extends Entity implements Attackable { - d1 = source.getSourcePosition().z() - this.getZ(); - } - -- this.knockback(0.4000000059604645D, d0, d1, entity1, entity1 == null ? EntityKnockbackEvent.KnockbackCause.DAMAGE : EntityKnockbackEvent.KnockbackCause.ENTITY_ATTACK); // CraftBukkit -+ this.knockback(0.4000000059604645D, d0, d1, entity1, entity1 == null ? io.papermc.paper.event.entity.EntityKnockbackEvent.Cause.DAMAGE : io.papermc.paper.event.entity.EntityKnockbackEvent.Cause.ENTITY_ATTACK); // CraftBukkit // Paper - knockback events - if (!flag) { - this.indicateDamage(d0, d1); - } -@@ -1581,7 +1581,7 @@ public abstract class LivingEntity extends Entity implements Attackable { - } - - protected void blockedByShield(LivingEntity target) { -- target.knockback(0.5D, target.getX() - this.getX(), target.getZ() - this.getZ(), null, EntityKnockbackEvent.KnockbackCause.SHIELD_BLOCK); // CraftBukkit -+ target.knockback(0.5D, target.getX() - this.getX(), target.getZ() - this.getZ(), this, io.papermc.paper.event.entity.EntityKnockbackEvent.Cause.SHIELD_BLOCK); // CraftBukkit // Paper - fix attacker & knockback events - } - - private boolean checkTotemDeathProtection(DamageSource source) { -@@ -1842,10 +1842,10 @@ public abstract class LivingEntity extends Entity implements Attackable { - - public void knockback(double strength, double x, double z) { - // CraftBukkit start - EntityKnockbackEvent -- this.knockback(strength, x, z, null, EntityKnockbackEvent.KnockbackCause.UNKNOWN); -+ this.knockback(strength, x, z, null, io.papermc.paper.event.entity.EntityKnockbackEvent.Cause.UNKNOWN); // Paper - knockback events - } - -- public void knockback(double d0, double d1, double d2, Entity attacker, EntityKnockbackEvent.KnockbackCause cause) { -+ public void knockback(double d0, double d1, double d2, @Nullable Entity attacker, io.papermc.paper.event.entity.EntityKnockbackEvent.Cause cause) { // Paper - knockback events - d0 *= 1.0D - this.getAttributeValue(Attributes.KNOCKBACK_RESISTANCE); - if (true || d0 > 0.0D) { // CraftBukkit - Call event even when force is 0 - //this.hasImpulse = true; // CraftBukkit - Move down -@@ -1858,13 +1858,17 @@ public abstract class LivingEntity extends Entity implements Attackable { - - Vec3 vec3d1 = (new Vec3(d1, 0.0D, d2)).normalize().scale(d0); - -- EntityKnockbackEvent event = CraftEventFactory.callEntityKnockbackEvent((org.bukkit.craftbukkit.entity.CraftLivingEntity) this.getBukkitEntity(), attacker, cause, d0, vec3d1, vec3d.x / 2.0D - vec3d1.x, this.onGround() ? Math.min(0.4D, vec3d.y / 2.0D + d0) : vec3d.y, vec3d.z / 2.0D - vec3d1.z); -+ // Paper start - knockback events -+ Vec3 finalVelocity = new Vec3(vec3d.x / 2.0D - vec3d1.x, this.onGround() ? Math.min(0.4D, vec3d.y / 2.0D + d0) : vec3d.y, vec3d.z / 2.0D - vec3d1.z); -+ Vec3 diff = finalVelocity.subtract(vec3d); -+ io.papermc.paper.event.entity.EntityKnockbackEvent event = CraftEventFactory.callEntityKnockbackEvent((org.bukkit.craftbukkit.entity.CraftLivingEntity) this.getBukkitEntity(), attacker, attacker, cause, d0, diff); -+ // Paper end - knockback events - if (event.isCancelled()) { - return; - } - - this.hasImpulse = true; -- this.setDeltaMovement(event.getFinalKnockback().getX(), event.getFinalKnockback().getY(), event.getFinalKnockback().getZ()); -+ this.setDeltaMovement(vec3d.add(event.getKnockback().getX(), event.getKnockback().getY(), event.getKnockback().getZ())); // Paper - knockback events - // CraftBukkit end - } - } -diff --git a/src/main/java/net/minecraft/world/entity/Mob.java b/src/main/java/net/minecraft/world/entity/Mob.java -index 62c3b3d992aff2aa20857e2b3bee464a377a0857..25a71cc5ca8cf8a5070cd24eb56fe0d79e765669 100644 ---- a/src/main/java/net/minecraft/world/entity/Mob.java -+++ b/src/main/java/net/minecraft/world/entity/Mob.java -@@ -1695,7 +1695,7 @@ public abstract class Mob extends LivingEntity implements EquipmentUser, Leashab - if (f1 > 0.0F && target instanceof LivingEntity) { - LivingEntity entityliving = (LivingEntity) target; - -- entityliving.knockback((double) (f1 * 0.5F), (double) Mth.sin(this.getYRot() * 0.017453292F), (double) (-Mth.cos(this.getYRot() * 0.017453292F)), this, org.bukkit.event.entity.EntityKnockbackEvent.KnockbackCause.ENTITY_ATTACK); // CraftBukkit -+ entityliving.knockback((double) (f1 * 0.5F), (double) Mth.sin(this.getYRot() * 0.017453292F), (double) (-Mth.cos(this.getYRot() * 0.017453292F)), this, io.papermc.paper.event.entity.EntityKnockbackEvent.Cause.ENTITY_ATTACK); // CraftBukkit // Paper - knockback events - this.setDeltaMovement(this.getDeltaMovement().multiply(0.6D, 1.0D, 0.6D)); - } - -diff --git a/src/main/java/net/minecraft/world/entity/ai/behavior/RamTarget.java b/src/main/java/net/minecraft/world/entity/ai/behavior/RamTarget.java -index a7601321fd2cbf62b45162a2d3ee9d59c2f7d077..2d0ca87649702437aa2d7acd8d2cbb57231c77de 100644 ---- a/src/main/java/net/minecraft/world/entity/ai/behavior/RamTarget.java -+++ b/src/main/java/net/minecraft/world/entity/ai/behavior/RamTarget.java -@@ -89,7 +89,7 @@ public class RamTarget extends Behavior { - float f = 0.25F * (float)(i - j); - float g = Mth.clamp(entity.getSpeed() * 1.65F, 0.2F, 3.0F) + f; - float h = livingEntity.isDamageSourceBlocked(world.damageSources().mobAttack(entity)) ? 0.5F : 1.0F; -- livingEntity.knockback((double)(h * g) * this.getKnockbackForce.applyAsDouble(entity), this.ramDirection.x(), this.ramDirection.z()); -+ livingEntity.knockback(h * g * this.getKnockbackForce.applyAsDouble(entity), this.ramDirection.x(), this.ramDirection.z(), entity, io.papermc.paper.event.entity.EntityKnockbackEvent.Cause.ENTITY_ATTACK); // Paper - Add EntityKnockbackByEntityEvent and EntityPushedByEntityAttackEvent - this.finishRam(world, entity); - world.playSound(null, entity, this.getImpactSound.apply(entity), SoundSource.NEUTRAL, 1.0F, 1.0F); - } else if (this.hasRammedHornBreakingBlock(world, entity)) { -diff --git a/src/main/java/net/minecraft/world/entity/ai/behavior/warden/SonicBoom.java b/src/main/java/net/minecraft/world/entity/ai/behavior/warden/SonicBoom.java -index aa8909498c26f095060a1df364b9e20d964a6cc3..30502849f79ce0f472e4289043c7d8ec460d3f20 100644 ---- a/src/main/java/net/minecraft/world/entity/ai/behavior/warden/SonicBoom.java -+++ b/src/main/java/net/minecraft/world/entity/ai/behavior/warden/SonicBoom.java -@@ -83,7 +83,7 @@ public class SonicBoom extends Behavior { - if (target.hurt(world.damageSources().sonicBoom(entity), 10.0F)) { - double d = 0.5 * (1.0 - target.getAttributeValue(Attributes.KNOCKBACK_RESISTANCE)); - double e = 2.5 * (1.0 - target.getAttributeValue(Attributes.KNOCKBACK_RESISTANCE)); -- target.push(vec33.x() * e, vec33.y() * d, vec33.z() * e); -+ target.push(vec33.x() * e, vec33.y() * d, vec33.z() * e, entity); // Paper - Add EntityKnockbackByEntityEvent and EntityPushedByEntityAttackEvent - } - }); - } -diff --git a/src/main/java/net/minecraft/world/entity/boss/enderdragon/EnderDragon.java b/src/main/java/net/minecraft/world/entity/boss/enderdragon/EnderDragon.java -index f8283405b3c5bd43746a5738be55f600beea40e3..b02a77b486f8d5eee31850de4a1b033fe6a107c7 100644 ---- a/src/main/java/net/minecraft/world/entity/boss/enderdragon/EnderDragon.java -+++ b/src/main/java/net/minecraft/world/entity/boss/enderdragon/EnderDragon.java -@@ -465,7 +465,7 @@ public class EnderDragon extends Mob implements Enemy { - double d3 = entity.getZ() - d1; - double d4 = Math.max(d2 * d2 + d3 * d3, 0.1D); - -- entity.push(d2 / d4 * 4.0D, 0.20000000298023224D, d3 / d4 * 4.0D); -+ entity.push(d2 / d4 * 4.0D, 0.20000000298023224D, d3 / d4 * 4.0D, this); // Paper - Add EntityKnockbackByEntityEvent and EntityPushedByEntityAttackEvent - if (!this.phaseManager.getCurrentPhase().isSitting() && entityliving.getLastHurtByMobTimestamp() < entity.tickCount - 2) { - DamageSource damagesource = this.damageSources().mobAttack(this); - -diff --git a/src/main/java/net/minecraft/world/entity/decoration/BlockAttachedEntity.java b/src/main/java/net/minecraft/world/entity/decoration/BlockAttachedEntity.java -index cf65d8ef8d69a24ceb44d2a5d84c83dfee322a1d..e4eece7bbd14514ec60da26a8744672baa8956f9 100644 ---- a/src/main/java/net/minecraft/world/entity/decoration/BlockAttachedEntity.java -+++ b/src/main/java/net/minecraft/world/entity/decoration/BlockAttachedEntity.java -@@ -142,7 +142,7 @@ public abstract class BlockAttachedEntity extends Entity { - } - - @Override -- public void push(double deltaX, double deltaY, double deltaZ) { -+ public void push(double deltaX, double deltaY, double deltaZ, @Nullable Entity pushingEntity) { // Paper - override correct overload - if (false && !this.level().isClientSide && !this.isRemoved() && deltaX * deltaX + deltaY * deltaY + deltaZ * deltaZ > 0.0D) { // CraftBukkit - not needed - this.kill(); - this.dropItem((Entity) null); -diff --git a/src/main/java/net/minecraft/world/entity/decoration/ItemFrame.java b/src/main/java/net/minecraft/world/entity/decoration/ItemFrame.java -index c6e1e1cb3bf36e86dd910037dc7e70498c1c311f..84dcd662981b1eeb03128e7717f6af44c2b9cff6 100644 ---- a/src/main/java/net/minecraft/world/entity/decoration/ItemFrame.java -+++ b/src/main/java/net/minecraft/world/entity/decoration/ItemFrame.java -@@ -128,9 +128,9 @@ public class ItemFrame extends HangingEntity { - } - - @Override -- public void push(double deltaX, double deltaY, double deltaZ) { -+ public void push(double deltaX, double deltaY, double deltaZ, @org.jetbrains.annotations.Nullable Entity pushingEntity) { // Paper - add push source entity param - if (!this.fixed) { -- super.push(deltaX, deltaY, deltaZ); -+ super.push(deltaX, deltaY, deltaZ, pushingEntity); // Paper - add push source entity param - } - - } -diff --git a/src/main/java/net/minecraft/world/entity/monster/Ravager.java b/src/main/java/net/minecraft/world/entity/monster/Ravager.java -index 1954c95e65abd98973393c636f5257b2a9f27377..4d91bc4b90a208fec789325dbcec8cc56d1a2a8b 100644 ---- a/src/main/java/net/minecraft/world/entity/monster/Ravager.java -+++ b/src/main/java/net/minecraft/world/entity/monster/Ravager.java -@@ -258,7 +258,7 @@ public class Ravager extends Raider { - double d1 = entity.getZ() - this.getZ(); - double d2 = Math.max(d0 * d0 + d1 * d1, 0.001D); - -- entity.push(d0 / d2 * 4.0D, 0.2D, d1 / d2 * 4.0D); -+ entity.push(d0 / d2 * 4.0D, 0.2D, d1 / d2 * 4.0D, this); // Paper - Add EntityKnockbackByEntityEvent and EntityPushedByEntityAttackEvent - } - - @Override -diff --git a/src/main/java/net/minecraft/world/entity/monster/hoglin/HoglinBase.java b/src/main/java/net/minecraft/world/entity/monster/hoglin/HoglinBase.java -index a6c33abcbbfc0851c8fa979163de145a578f97a6..18389b3befe31b224010e55244fbcb7c2802845e 100644 ---- a/src/main/java/net/minecraft/world/entity/monster/hoglin/HoglinBase.java -+++ b/src/main/java/net/minecraft/world/entity/monster/hoglin/HoglinBase.java -@@ -47,7 +47,7 @@ public interface HoglinBase { - double j = f * (double)(attacker.level().random.nextFloat() * 0.5F + 0.2F); - Vec3 vec3 = new Vec3(g, 0.0, h).normalize().scale(j).yRot(i); - double k = f * (double)attacker.level().random.nextFloat() * 0.5; -- target.push(vec3.x, k, vec3.z); -+ target.push(vec3.x, k, vec3.z, attacker); // Paper - Add EntityKnockbackByEntityEvent and EntityPushedByEntityAttackEvent - target.hurtMarked = true; - } - } -diff --git a/src/main/java/net/minecraft/world/entity/player/Player.java b/src/main/java/net/minecraft/world/entity/player/Player.java -index d6bbeaf3833a4c44ed05243445edd813e3f15dcb..1b13096da1f0bc49efe25677ec24e6abe7ff2879 100644 ---- a/src/main/java/net/minecraft/world/entity/player/Player.java -+++ b/src/main/java/net/minecraft/world/entity/player/Player.java -@@ -1301,9 +1301,9 @@ public abstract class Player extends LivingEntity { - if (target instanceof LivingEntity) { - LivingEntity entityliving1 = (LivingEntity) target; - -- entityliving1.knockback((double) (f5 * 0.5F), (double) Mth.sin(this.getYRot() * 0.017453292F), (double) (-Mth.cos(this.getYRot() * 0.017453292F))); -+ entityliving1.knockback((double) (f5 * 0.5F), (double) Mth.sin(this.getYRot() * 0.017453292F), (double) (-Mth.cos(this.getYRot() * 0.017453292F)), this, io.papermc.paper.event.entity.EntityKnockbackEvent.Cause.ENTITY_ATTACK); // Paper - knockback events - } else { -- target.push((double) (-Mth.sin(this.getYRot() * 0.017453292F) * f5 * 0.5F), 0.1D, (double) (Mth.cos(this.getYRot() * 0.017453292F) * f5 * 0.5F)); -+ target.push((double) (-Mth.sin(this.getYRot() * 0.017453292F) * f5 * 0.5F), 0.1D, (double) (Mth.cos(this.getYRot() * 0.017453292F) * f5 * 0.5F), this); // Paper - Add EntityKnockbackByEntityEvent and EntityPushedByEntityAttackEvent - } - - this.setDeltaMovement(this.getDeltaMovement().multiply(0.6D, 1.0D, 0.6D)); -@@ -1331,7 +1331,7 @@ public abstract class Player extends LivingEntity { - continue; - } - // CraftBukkit end -- entityliving2.knockback(0.4000000059604645D, (double) Mth.sin(this.getYRot() * 0.017453292F), (double) (-Mth.cos(this.getYRot() * 0.017453292F))); -+ entityliving2.knockback(0.4000000059604645D, (double) Mth.sin(this.getYRot() * 0.017453292F), (double) (-Mth.cos(this.getYRot() * 0.017453292F)), this, io.papermc.paper.event.entity.EntityKnockbackEvent.Cause.SWEEP_ATTACK); // CraftBukkit // Paper - knockback events - // entityliving2.hurt(damagesource, f7); // CraftBukkit - moved up - Level world = this.level(); - -diff --git a/src/main/java/net/minecraft/world/entity/projectile/AbstractArrow.java b/src/main/java/net/minecraft/world/entity/projectile/AbstractArrow.java -index 230040bef7d2cf9a463cfd9cb3b1b1aa208a7119..6c79997ba46e641de5aa12ff8a3d790d04a5a475 100644 ---- a/src/main/java/net/minecraft/world/entity/projectile/AbstractArrow.java -+++ b/src/main/java/net/minecraft/world/entity/projectile/AbstractArrow.java -@@ -519,7 +519,7 @@ public abstract class AbstractArrow extends Projectile { - Vec3 vec3d = this.getDeltaMovement().multiply(1.0D, 0.0D, 1.0D).normalize().scale(d0 * 0.6D * d1); - - if (vec3d.lengthSqr() > 0.0D) { -- target.push(vec3d.x, 0.1D, vec3d.z); -+ target.push(vec3d.x, 0.1D, vec3d.z, this); // Paper - Add EntityKnockbackByEntityEvent and EntityPushedByEntityAttackEvent - } - } - -diff --git a/src/main/java/net/minecraft/world/entity/projectile/windcharge/AbstractWindCharge.java b/src/main/java/net/minecraft/world/entity/projectile/windcharge/AbstractWindCharge.java -index de2bc78415ab4efb651030be6560d9c9778a1d17..1e00df3fa3c3b61daa3d59ee1173269a6eae3a43 100644 ---- a/src/main/java/net/minecraft/world/entity/projectile/windcharge/AbstractWindCharge.java -+++ b/src/main/java/net/minecraft/world/entity/projectile/windcharge/AbstractWindCharge.java -@@ -103,7 +103,7 @@ public abstract class AbstractWindCharge extends AbstractHurtingProjectile imple - } - - @Override -- public void push(double deltaX, double deltaY, double deltaZ) {} -+ public void push(double deltaX, double deltaY, double deltaZ, @org.jetbrains.annotations.Nullable Entity pushingEntity) {} // Paper - Add EntityKnockbackByEntityEvent and EntityPushedByEntityAttackEvent - - public abstract void explode(Vec3 pos); - -diff --git a/src/main/java/net/minecraft/world/level/Explosion.java b/src/main/java/net/minecraft/world/level/Explosion.java -index 6476c644d3da824c5ee4190cb45cde678ff1188f..b216140a8be65e210250358af8daf49344850f20 100644 ---- a/src/main/java/net/minecraft/world/level/Explosion.java -+++ b/src/main/java/net/minecraft/world/level/Explosion.java -@@ -297,13 +297,10 @@ public class Explosion { - - // CraftBukkit start - Call EntityKnockbackEvent - if (entity instanceof LivingEntity) { -- Vec3 result = entity.getDeltaMovement().add(vec3d1); -- org.bukkit.event.entity.EntityKnockbackEvent event = CraftEventFactory.callEntityKnockbackEvent((org.bukkit.craftbukkit.entity.CraftLivingEntity) entity.getBukkitEntity(), this.source, org.bukkit.event.entity.EntityKnockbackEvent.KnockbackCause.EXPLOSION, d13, vec3d1, result.x, result.y, result.z); -- -- // SPIGOT-7640: Need to subtract entity movement from the event result, -- // since the code below (the setDeltaMovement call as well as the hitPlayers map) -- // want the vector to be the relative velocity will the event provides the absolute velocity -- vec3d1 = (event.isCancelled()) ? Vec3.ZERO : new Vec3(event.getFinalKnockback().getX(), event.getFinalKnockback().getY(), event.getFinalKnockback().getZ()).subtract(entity.getDeltaMovement()); -+ // Paper start - knockback events -+ io.papermc.paper.event.entity.EntityKnockbackEvent event = CraftEventFactory.callEntityKnockbackEvent((org.bukkit.craftbukkit.entity.CraftLivingEntity) entity.getBukkitEntity(), this.source, this.damageSource.getEntity() != null ? this.damageSource.getEntity() : this.source, io.papermc.paper.event.entity.EntityKnockbackEvent.Cause.EXPLOSION, d13, vec3d1); -+ vec3d1 = event.isCancelled() ? Vec3.ZERO : org.bukkit.craftbukkit.util.CraftVector.toNMS(event.getKnockback()); -+ // Paper end - knockback events - } - // CraftBukkit end - entity.setDeltaMovement(entity.getDeltaMovement().add(vec3d1)); -diff --git a/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java b/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java -index e3af0db8082e4e90902197f96f1c833405bf5f63..3059a21b554db99af96c76f72dd08591c00e3e08 100644 ---- a/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java -+++ b/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java -@@ -1938,19 +1938,33 @@ public class CraftEventFactory { - return event; - } - -- public static EntityKnockbackEvent callEntityKnockbackEvent(CraftLivingEntity entity, Entity attacker, EntityKnockbackEvent.KnockbackCause cause, double force, Vec3 raw, double x, double y, double z) { -- Vector bukkitRaw = new Vector(-raw.x, raw.y, -raw.z); // Due to how the knockback calculation works, we need to invert x and z. -+ // Paper start - replace knockback events -+ public static io.papermc.paper.event.entity.EntityKnockbackEvent callEntityKnockbackEvent(CraftLivingEntity entity, Entity pusher, Entity attacker, io.papermc.paper.event.entity.EntityKnockbackEvent.Cause cause, double force, Vec3 knockback) { -+ Vector apiKnockback = CraftVector.toBukkit(knockback); -+ -+ final Vector currentVelocity = entity.getVelocity(); -+ final Vector legacyFinalKnockback = currentVelocity.clone().add(apiKnockback); -+ final org.bukkit.event.entity.EntityKnockbackEvent.KnockbackCause legacyCause = org.bukkit.event.entity.EntityKnockbackEvent.KnockbackCause.valueOf(cause.name()); -+ EntityKnockbackEvent legacyEvent; -+ if (pusher != null) { -+ legacyEvent = new EntityKnockbackByEntityEvent(entity, pusher.getBukkitEntity(), legacyCause, force, apiKnockback, legacyFinalKnockback); -+ } else { -+ legacyEvent = new EntityKnockbackEvent(entity, legacyCause, force, apiKnockback, legacyFinalKnockback); -+ } -+ legacyEvent.callEvent(); - -- EntityKnockbackEvent event; -+ final io.papermc.paper.event.entity.EntityKnockbackEvent event; -+ apiKnockback = legacyEvent.getFinalKnockback().subtract(currentVelocity); - if (attacker != null) { -- event = new EntityKnockbackByEntityEvent(entity, attacker.getBukkitEntity(), cause, force, new Vector(-raw.x, raw.y, -raw.z), new Vector(x, y, z)); -+ event = new com.destroystokyo.paper.event.entity.EntityKnockbackByEntityEvent(entity, attacker.getBukkitEntity(), cause, (float) force, apiKnockback); - } else { -- event = new EntityKnockbackEvent(entity, cause, force, new Vector(-raw.x, raw.y, -raw.z), new Vector(x, y, z)); -+ event = new io.papermc.paper.event.entity.EntityKnockbackEvent(entity, cause, apiKnockback); - } -- -- Bukkit.getPluginManager().callEvent(event); -+ event.setCancelled(legacyEvent.isCancelled()); -+ event.callEvent(); - return event; - } -+ // Paper end - replace knockback events - - public static void callEntityRemoveEvent(Entity entity, EntityRemoveEvent.Cause cause) { - if (entity instanceof ServerPlayer) { diff --git a/patches/server/0202-Expand-Explosions-API.patch b/patches/server/0202-Expand-Explosions-API.patch new file mode 100644 index 000000000000..1952633714ed --- /dev/null +++ b/patches/server/0202-Expand-Explosions-API.patch @@ -0,0 +1,87 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Aikar +Date: Wed, 20 Jun 2018 23:17:24 -0400 +Subject: [PATCH] Expand Explosions API + +Add Entity as a Source capability, and add more API choices, and on Location. + +Co-authored-by: Esoteric Enderman <90862990+EsotericEnderman@users.noreply.github.com> +Co-authored-by: Bjarne Koll + +diff --git a/src/main/java/net/minecraft/server/level/ServerLevel.java b/src/main/java/net/minecraft/server/level/ServerLevel.java +index 0c0d19708832a49734ea08ae05696e0cb20616e4..3324156c004e0506df8be23050f497d462b4b9c1 100644 +--- a/src/main/java/net/minecraft/server/level/ServerLevel.java ++++ b/src/main/java/net/minecraft/server/level/ServerLevel.java +@@ -1419,6 +1419,11 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe + } + + public ServerExplosion explode0(@Nullable Entity entity, @Nullable DamageSource damagesource, @Nullable ExplosionDamageCalculator explosiondamagecalculator, double d0, double d1, double d2, float f, boolean flag, Level.ExplosionInteraction world_a, ParticleOptions particleparam, ParticleOptions particleparam1, Holder holder) { ++ // Paper start - Allow explosions to damage source ++ return this.explode0(entity, damagesource, explosiondamagecalculator, d0, d1, d2, f, flag, world_a, particleparam, particleparam1, holder, null); ++ } ++ public ServerExplosion explode0(@Nullable Entity entity, @Nullable DamageSource damagesource, @Nullable ExplosionDamageCalculator explosiondamagecalculator, double d0, double d1, double d2, float f, boolean flag, Level.ExplosionInteraction world_a, ParticleOptions particleparam, ParticleOptions particleparam1, Holder holder, java.util.function.Consumer configurator) { ++ // Paper end - Allow explosions to damage source + // CraftBukkit end + Explosion.BlockInteraction explosion_effect; + +@@ -1450,6 +1455,7 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe + Explosion.BlockInteraction explosion_effect1 = explosion_effect; + Vec3 vec3d = new Vec3(d0, d1, d2); + ServerExplosion serverexplosion = new ServerExplosion(this, entity, damagesource, explosiondamagecalculator, vec3d, f, flag, explosion_effect1); ++ if (configurator != null) configurator.accept(serverexplosion);// Paper - Allow explosions to damage source + + serverexplosion.explode(); + // CraftBukkit start +diff --git a/src/main/java/net/minecraft/world/level/ServerExplosion.java b/src/main/java/net/minecraft/world/level/ServerExplosion.java +index 9f37d7284c81d529551107e2836627977efabd65..d1878f597c3d8119e9b248f4fe8af435ce8c9710 100644 +--- a/src/main/java/net/minecraft/world/level/ServerExplosion.java ++++ b/src/main/java/net/minecraft/world/level/ServerExplosion.java +@@ -63,6 +63,7 @@ public class ServerExplosion implements Explosion { + public boolean wasCanceled = false; + public float yield; + // CraftBukkit end ++ public boolean excludeSourceFromDamage = true; // Paper - Allow explosions to damage source + + public ServerExplosion(ServerLevel world, @Nullable Entity entity, @Nullable DamageSource damageSource, @Nullable ExplosionDamageCalculator behavior, Vec3 pos, float power, boolean createFire, Explosion.BlockInteraction destructionType) { + this.level = world; +@@ -185,7 +186,7 @@ public class ServerExplosion implements Explosion { + int l = Mth.floor(this.center.y + (double) f + 1.0D); + int i1 = Mth.floor(this.center.z - (double) f - 1.0D); + int j1 = Mth.floor(this.center.z + (double) f + 1.0D); +- List list = this.level.getEntities(this.source, new AABB((double) i, (double) k, (double) i1, (double) j, (double) l, (double) j1), (com.google.common.base.Predicate) entity -> entity.isAlive() && !entity.isSpectator()); // Paper - Fix lag from explosions processing dead entities ++ List list = this.level.getEntities(excludeSourceFromDamage ? this.source : null, new AABB((double) i, (double) k, (double) i1, (double) j, (double) l, (double) j1), (com.google.common.base.Predicate) entity -> entity.isAlive() && !entity.isSpectator()); // Paper - Fix lag from explosions processing dead entities, Allow explosions to damage source + Iterator iterator = list.iterator(); + + while (iterator.hasNext()) { +diff --git a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java +index 5231548e886e884f565ff6cc5d45518141fbab2d..e285c8486c36e8d2bc4ddc43e0029943ea5c7fe7 100644 +--- a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java ++++ b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java +@@ -784,6 +784,11 @@ public class CraftWorld extends CraftRegionAccessor implements World { + + @Override + public boolean createExplosion(double x, double y, double z, float power, boolean setFire, boolean breakBlocks, Entity source) { ++ // Paper start - expand explosion API ++ return this.createExplosion(x, y, z, power, setFire, breakBlocks, source, null); ++ } ++ public boolean createExplosion(double x, double y, double z, float power, boolean setFire, boolean breakBlocks, Entity source, Consumer configurator) { ++ // Paper end - expand explosion API + net.minecraft.world.level.Level.ExplosionInteraction explosionType; + if (!breakBlocks) { + explosionType = net.minecraft.world.level.Level.ExplosionInteraction.NONE; // Don't break blocks +@@ -794,8 +799,14 @@ public class CraftWorld extends CraftRegionAccessor implements World { + } + + net.minecraft.world.entity.Entity entity = (source == null) ? null : ((CraftEntity) source).getHandle(); +- return !this.world.explode0(entity, Explosion.getDefaultDamageSource(this.world, entity), null, x, y, z, power, setFire, explosionType, ParticleTypes.EXPLOSION, ParticleTypes.EXPLOSION_EMITTER, SoundEvents.GENERIC_EXPLODE).wasCanceled; ++ return !this.world.explode0(entity, Explosion.getDefaultDamageSource(this.world, entity), null, x, y, z, power, setFire, explosionType, ParticleTypes.EXPLOSION, ParticleTypes.EXPLOSION_EMITTER, SoundEvents.GENERIC_EXPLODE, configurator).wasCanceled; // Paper - expand explosion API + } ++ // Paper start ++ @Override ++ public boolean createExplosion(Entity source, Location loc, float power, boolean setFire, boolean breakBlocks, boolean excludeSourceFromDamage) { ++ return this.createExplosion(loc.x(), loc.getY(), loc.getZ(), power, setFire, breakBlocks, source, e -> e.excludeSourceFromDamage = excludeSourceFromDamage); ++ } ++ // Paper end + + @Override + public boolean createExplosion(Location loc, float power) { diff --git a/patches/server/0203-Expand-Explosions-API.patch b/patches/server/0203-Expand-Explosions-API.patch deleted file mode 100644 index cdd902874622..000000000000 --- a/patches/server/0203-Expand-Explosions-API.patch +++ /dev/null @@ -1,116 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Aikar -Date: Wed, 20 Jun 2018 23:17:24 -0400 -Subject: [PATCH] Expand Explosions API - -Add Entity as a Source capability, and add more API choices, and on Location. - -Co-authored-by: Esoteric Enderman <90862990+EsotericEnderman@users.noreply.github.com> -Co-authored-by: Bjarne Koll - -diff --git a/src/main/java/net/minecraft/server/level/ServerLevel.java b/src/main/java/net/minecraft/server/level/ServerLevel.java -index 1fac100819e59d00f50e530d3a4157b56d966dba..f2e75b36cbceb9cfa0755bd045f23073712d0e8a 100644 ---- a/src/main/java/net/minecraft/server/level/ServerLevel.java -+++ b/src/main/java/net/minecraft/server/level/ServerLevel.java -@@ -1424,8 +1424,10 @@ public class ServerLevel extends Level implements WorldGenLevel { - } - - @Override -- public Explosion explode(@Nullable Entity entity, @Nullable DamageSource damageSource, @Nullable ExplosionDamageCalculator behavior, double x, double y, double z, float power, boolean createFire, Level.ExplosionInteraction explosionSourceType, ParticleOptions particle, ParticleOptions emitterParticle, Holder soundEvent) { -- Explosion explosion = this.explode(entity, damageSource, behavior, x, y, z, power, createFire, explosionSourceType, false, particle, emitterParticle, soundEvent); -+ // Paper start - Allow explosions to damage source -+ public Explosion explode(@Nullable Entity entity, @Nullable DamageSource damageSource, @Nullable ExplosionDamageCalculator behavior, double x, double y, double z, float power, boolean createFire, Level.ExplosionInteraction explosionSourceType, ParticleOptions particle, ParticleOptions emitterParticle, Holder soundEvent, java.util.function.Consumer configurator) { -+ Explosion explosion = this.explode(entity, damageSource, behavior, x, y, z, power, createFire, explosionSourceType, false, particle, emitterParticle, soundEvent, configurator); -+ // Paper end - Allow explosions to damage source - // CraftBukkit start - if (explosion.wasCanceled) { - return explosion; -diff --git a/src/main/java/net/minecraft/world/level/Explosion.java b/src/main/java/net/minecraft/world/level/Explosion.java -index b216140a8be65e210250358af8daf49344850f20..ce8ac06b47e81161ad5ff6cc865ad6d3d59807c1 100644 ---- a/src/main/java/net/minecraft/world/level/Explosion.java -+++ b/src/main/java/net/minecraft/world/level/Explosion.java -@@ -74,6 +74,7 @@ public class Explosion { - public boolean wasCanceled = false; - public float yield; - // CraftBukkit end -+ public boolean excludeSourceFromDamage = true; // Paper - Allow explosions to damage source - - public static DamageSource getDefaultDamageSource(Level world, @Nullable Entity source) { - return world.damageSources().explosion(source, Explosion.getIndirectSourceEntityInternal(source)); -@@ -227,7 +228,7 @@ public class Explosion { - int i1 = Mth.floor(this.y + (double) f2 + 1.0D); - int j1 = Mth.floor(this.z - (double) f2 - 1.0D); - int k1 = Mth.floor(this.z + (double) f2 + 1.0D); -- List list = this.level.getEntities(this.source, new AABB((double) i, (double) l, (double) j1, (double) j, (double) i1, (double) k1), (com.google.common.base.Predicate) entity -> entity.isAlive() && !entity.isSpectator()); // Paper - Fix lag from explosions processing dead entities -+ List list = this.level.getEntities(excludeSourceFromDamage ? this.source : null, new AABB((double) i, (double) l, (double) j1, (double) j, (double) i1, (double) k1), (com.google.common.base.Predicate) entity -> entity.isAlive() && !entity.isSpectator()); // Paper - Fix lag from explosions processing dead entities, Allow explosions to damage source - Vec3 vec3d = new Vec3(this.x, this.y, this.z); - Iterator iterator = list.iterator(); - -diff --git a/src/main/java/net/minecraft/world/level/Level.java b/src/main/java/net/minecraft/world/level/Level.java -index 6944c0b0cfcde9fa4dd78742aee3e3b87d679abf..7138fb36062eceaf92ae8513f9b8aef3d1238656 100644 ---- a/src/main/java/net/minecraft/world/level/Level.java -+++ b/src/main/java/net/minecraft/world/level/Level.java -@@ -782,7 +782,12 @@ public abstract class Level implements LevelAccessor, AutoCloseable { - } - - public Explosion explode(@Nullable Entity entity, double x, double y, double z, float power, boolean createFire, Level.ExplosionInteraction explosionSourceType) { -- return this.explode(entity, Explosion.getDefaultDamageSource(this, entity), (ExplosionDamageCalculator) null, x, y, z, power, createFire, explosionSourceType, ParticleTypes.EXPLOSION, ParticleTypes.EXPLOSION_EMITTER, SoundEvents.GENERIC_EXPLODE); -+ // Paper start - Allow explosions to damage source -+ return this.explode(entity, x, y, z, power, createFire, explosionSourceType, null); -+ } -+ public Explosion explode(@Nullable Entity entity, double x, double y, double z, float power, boolean createFire, Level.ExplosionInteraction explosionSourceType, @Nullable Consumer configurator) { -+ return this.explode(entity, Explosion.getDefaultDamageSource(this, entity), (ExplosionDamageCalculator) null, x, y, z, power, createFire, explosionSourceType, ParticleTypes.EXPLOSION, ParticleTypes.EXPLOSION_EMITTER, SoundEvents.GENERIC_EXPLODE, configurator); -+ // Paper end - Allow explosions to damage source - } - - public Explosion explode(@Nullable Entity entity, @Nullable DamageSource damageSource, @Nullable ExplosionDamageCalculator behavior, Vec3 pos, float power, boolean createFire, Level.ExplosionInteraction explosionSourceType) { -@@ -794,10 +799,21 @@ public abstract class Level implements LevelAccessor, AutoCloseable { - } - - public Explosion explode(@Nullable Entity entity, @Nullable DamageSource damageSource, @Nullable ExplosionDamageCalculator behavior, double x, double y, double z, float power, boolean createFire, Level.ExplosionInteraction explosionSourceType, ParticleOptions particle, ParticleOptions emitterParticle, Holder soundEvent) { -- return this.explode(entity, damageSource, behavior, x, y, z, power, createFire, explosionSourceType, true, particle, emitterParticle, soundEvent); -+ // Paper start - Allow explosions to damage source -+ return this.explode(entity, damageSource, behavior, x, y, z, power, createFire, explosionSourceType, particle, emitterParticle, soundEvent, null); -+ } -+ -+ public Explosion explode(@Nullable Entity entity, @Nullable DamageSource damageSource, @Nullable ExplosionDamageCalculator behavior, double x, double y, double z, float power, boolean createFire, Level.ExplosionInteraction explosionSourceType, ParticleOptions particle, ParticleOptions emitterParticle, Holder soundEvent, @Nullable Consumer configurator) { -+ return this.explode(entity, damageSource, behavior, x, y, z, power, createFire, explosionSourceType, true, particle, emitterParticle, soundEvent, configurator); -+ // Paper end - Allow explosions to damage source - } - - public Explosion explode(@Nullable Entity entity, @Nullable DamageSource damageSource, @Nullable ExplosionDamageCalculator behavior, double x, double y, double z, float power, boolean createFire, Level.ExplosionInteraction explosionSourceType, boolean particles, ParticleOptions particle, ParticleOptions emitterParticle, Holder soundEvent) { -+ // Paper start - Allow explosions to damage source -+ return this.explode(entity, damageSource, behavior, x, y, z, power, createFire, explosionSourceType, particle, emitterParticle, soundEvent, null); -+ } -+ public Explosion explode(@Nullable Entity entity, @Nullable DamageSource damageSource, @Nullable ExplosionDamageCalculator behavior, double x, double y, double z, float power, boolean createFire, Level.ExplosionInteraction explosionSourceType, boolean particles, ParticleOptions particle, ParticleOptions emitterParticle, Holder soundEvent, @Nullable Consumer configurator) { -+ // Paper end - Allow explosions to damage source - Explosion.BlockInteraction explosion_effect; - - switch (explosionSourceType.ordinal()) { -@@ -827,6 +843,7 @@ public abstract class Level implements LevelAccessor, AutoCloseable { - - Explosion.BlockInteraction explosion_effect1 = explosion_effect; - Explosion explosion = new Explosion(this, entity, damageSource, behavior, x, y, z, power, createFire, explosion_effect1, particle, emitterParticle, soundEvent); -+ if (configurator != null) configurator.accept(explosion); // Paper - Allow explosions to damage source - - explosion.explode(); - explosion.finalizeExplosion(particles); -diff --git a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java -index 3c00eaf52ab04d1396f226cc074d9dd013c57027..2c13ece6592700126365a155f104d6971aab4ede 100644 ---- a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java -+++ b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java -@@ -789,6 +789,14 @@ public class CraftWorld extends CraftRegionAccessor implements World { - - return !this.world.explode(source == null ? null : ((CraftEntity) source).getHandle(), x, y, z, power, setFire, explosionType).wasCanceled; - } -+ // Paper start -+ @Override -+ public boolean createExplosion(Entity source, Location loc, float power, boolean setFire, boolean breakBlocks, boolean excludeSourceFromDamage) { -+ return !world.explode(source != null ? ((org.bukkit.craftbukkit.entity.CraftEntity) source).getHandle() : null, loc.getX(), loc.getY(), loc.getZ(), power, setFire, breakBlocks ? net.minecraft.world.level.Level.ExplosionInteraction.MOB : net.minecraft.world.level.Level.ExplosionInteraction.NONE, explosion -> { -+ explosion.excludeSourceFromDamage = excludeSourceFromDamage; -+ }).wasCanceled; -+ } -+ // Paper end - - @Override - public boolean createExplosion(Location loc, float power) { diff --git a/patches/server/0203-LivingEntity-Active-Item-API.patch b/patches/server/0203-LivingEntity-Active-Item-API.patch new file mode 100644 index 000000000000..f7de817b6bbb --- /dev/null +++ b/patches/server/0203-LivingEntity-Active-Item-API.patch @@ -0,0 +1,72 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Aikar +Date: Fri, 29 Jun 2018 00:21:28 -0400 +Subject: [PATCH] LivingEntity Active Item API + +API relating to items being actively used by a LivingEntity +such as a bow or eating food. + +== AT == +public net/minecraft/world/entity/LivingEntity completeUsingItem()V +public net/minecraft/server/level/ServerPlayer completeUsingItem()V + +Co-authored-by: Jake Potrebic + +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftLivingEntity.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftLivingEntity.java +index e5186fbd4030d7b39443090b711e855dcbc39426..2033354daafc739a8bd9ddf4a6128d3204d32094 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftLivingEntity.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftLivingEntity.java +@@ -884,4 +884,53 @@ public class CraftLivingEntity extends CraftEntity implements LivingEntity { + getHandle().setShieldBlockingDelay(delay); + } + // Paper end ++ ++ // Paper start - active item API ++ @Override ++ public void startUsingItem(org.bukkit.inventory.EquipmentSlot hand) { ++ Preconditions.checkArgument(hand != null, "hand must not be null"); ++ switch (hand) { ++ case HAND -> getHandle().startUsingItem(InteractionHand.MAIN_HAND); ++ case OFF_HAND -> getHandle().startUsingItem(InteractionHand.OFF_HAND); ++ default -> throw new IllegalArgumentException("hand may only be HAND or OFF_HAND"); ++ } ++ } ++ ++ @Override ++ public void completeUsingActiveItem() { ++ getHandle().completeUsingItem(); ++ } ++ ++ @Override ++ public ItemStack getActiveItem() { ++ return this.getHandle().getUseItem().asBukkitMirror(); ++ } ++ ++ @Override ++ public int getActiveItemRemainingTime() { ++ return this.getHandle().getUseItemRemainingTicks(); ++ } ++ ++ @Override ++ public void setActiveItemRemainingTime(final int ticks) { ++ Preconditions.checkArgument(ticks >= 0, "ticks must be >= 0"); ++ Preconditions.checkArgument(ticks <= this.getHandle().getUseItem().getUseDuration(this.getHandle()), "ticks must be <= item use duration"); ++ this.getHandle().useItemRemaining = ticks; ++ } ++ ++ @Override ++ public int getActiveItemUsedTime() { ++ return this.getHandle().getTicksUsingItem(); ++ } ++ ++ @Override ++ public boolean hasActiveItem() { ++ return this.getHandle().isUsingItem(); ++ } ++ ++ @Override ++ public org.bukkit.inventory.EquipmentSlot getActiveItemHand() { ++ return org.bukkit.craftbukkit.CraftEquipmentSlot.getHand(this.getHandle().getUsedItemHand()); ++ } ++ // Paper end - active item API + } diff --git a/patches/server/0204-LivingEntity-Active-Item-API.patch b/patches/server/0204-LivingEntity-Active-Item-API.patch deleted file mode 100644 index aeca01c5eb16..000000000000 --- a/patches/server/0204-LivingEntity-Active-Item-API.patch +++ /dev/null @@ -1,72 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Aikar -Date: Fri, 29 Jun 2018 00:21:28 -0400 -Subject: [PATCH] LivingEntity Active Item API - -API relating to items being actively used by a LivingEntity -such as a bow or eating food. - -== AT == -public net/minecraft/world/entity/LivingEntity completeUsingItem()V -public net/minecraft/server/level/ServerPlayer completeUsingItem()V - -Co-authored-by: Jake Potrebic - -diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftLivingEntity.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftLivingEntity.java -index 73c72b8cdece357193afb3c5f474e055086311ea..2612e5016646591bb65ac255804b612b348a32fd 100644 ---- a/src/main/java/org/bukkit/craftbukkit/entity/CraftLivingEntity.java -+++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftLivingEntity.java -@@ -864,4 +864,53 @@ public class CraftLivingEntity extends CraftEntity implements LivingEntity { - getHandle().setShieldBlockingDelay(delay); - } - // Paper end -+ -+ // Paper start - active item API -+ @Override -+ public void startUsingItem(org.bukkit.inventory.EquipmentSlot hand) { -+ Preconditions.checkArgument(hand != null, "hand must not be null"); -+ switch (hand) { -+ case HAND -> getHandle().startUsingItem(InteractionHand.MAIN_HAND); -+ case OFF_HAND -> getHandle().startUsingItem(InteractionHand.OFF_HAND); -+ default -> throw new IllegalArgumentException("hand may only be HAND or OFF_HAND"); -+ } -+ } -+ -+ @Override -+ public void completeUsingActiveItem() { -+ getHandle().completeUsingItem(); -+ } -+ -+ @Override -+ public ItemStack getActiveItem() { -+ return this.getHandle().getUseItem().asBukkitMirror(); -+ } -+ -+ @Override -+ public int getActiveItemRemainingTime() { -+ return this.getHandle().getUseItemRemainingTicks(); -+ } -+ -+ @Override -+ public void setActiveItemRemainingTime(final int ticks) { -+ Preconditions.checkArgument(ticks >= 0, "ticks must be >= 0"); -+ Preconditions.checkArgument(ticks <= this.getHandle().getUseItem().getUseDuration(this.getHandle()), "ticks must be <= item use duration"); -+ this.getHandle().useItemRemaining = ticks; -+ } -+ -+ @Override -+ public int getActiveItemUsedTime() { -+ return this.getHandle().getTicksUsingItem(); -+ } -+ -+ @Override -+ public boolean hasActiveItem() { -+ return this.getHandle().isUsingItem(); -+ } -+ -+ @Override -+ public org.bukkit.inventory.EquipmentSlot getActiveItemHand() { -+ return org.bukkit.craftbukkit.CraftEquipmentSlot.getHand(this.getHandle().getUsedItemHand()); -+ } -+ // Paper end - active item API - } diff --git a/patches/server/0205-RangedEntity-API.patch b/patches/server/0204-RangedEntity-API.patch similarity index 100% rename from patches/server/0205-RangedEntity-API.patch rename to patches/server/0204-RangedEntity-API.patch diff --git a/patches/server/0205-Add-config-to-disable-ender-dragon-legacy-check.patch b/patches/server/0205-Add-config-to-disable-ender-dragon-legacy-check.patch new file mode 100644 index 000000000000..7c711b2484fc --- /dev/null +++ b/patches/server/0205-Add-config-to-disable-ender-dragon-legacy-check.patch @@ -0,0 +1,23 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: BillyGalbreath +Date: Fri, 22 Jun 2018 10:38:31 -0500 +Subject: [PATCH] Add config to disable ender dragon legacy check + + +diff --git a/src/main/java/net/minecraft/world/level/dimension/end/EndDragonFight.java b/src/main/java/net/minecraft/world/level/dimension/end/EndDragonFight.java +index 3fa3dae36bcf66dfdc8a279d37fb60a624c1ae96..a16229f5eaa9e8320751e4fd1faf016f796d3414 100644 +--- a/src/main/java/net/minecraft/world/level/dimension/end/EndDragonFight.java ++++ b/src/main/java/net/minecraft/world/level/dimension/end/EndDragonFight.java +@@ -117,6 +117,12 @@ public class EndDragonFight { + if (data.isRespawning) { + this.respawnStage = DragonRespawnAnimation.START; + } ++ // Paper start - Add config to disable ender dragon legacy check ++ if (data == EndDragonFight.Data.DEFAULT && !world.paperConfig().entities.spawning.scanForLegacyEnderDragon) { ++ this.needsStateScanning = false; ++ this.dragonKilled = true; ++ } ++ // Paper end - Add config to disable ender dragon legacy check + + this.portalLocation = (BlockPos) data.exitPortalLocation.orElse(null); // CraftBukkit - decompile error + this.gateways.addAll((Collection) data.gateways.orElseGet(() -> { diff --git a/patches/server/0206-Add-config-to-disable-ender-dragon-legacy-check.patch b/patches/server/0206-Add-config-to-disable-ender-dragon-legacy-check.patch deleted file mode 100644 index 0d69ebd2b471..000000000000 --- a/patches/server/0206-Add-config-to-disable-ender-dragon-legacy-check.patch +++ /dev/null @@ -1,23 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: BillyGalbreath -Date: Fri, 22 Jun 2018 10:38:31 -0500 -Subject: [PATCH] Add config to disable ender dragon legacy check - - -diff --git a/src/main/java/net/minecraft/world/level/dimension/end/EndDragonFight.java b/src/main/java/net/minecraft/world/level/dimension/end/EndDragonFight.java -index 1a56247191172622f2bde6d799bc44f70b9ce3ae..93337fd026fefb76c8a288674fed05cb3c1eca38 100644 ---- a/src/main/java/net/minecraft/world/level/dimension/end/EndDragonFight.java -+++ b/src/main/java/net/minecraft/world/level/dimension/end/EndDragonFight.java -@@ -116,6 +116,12 @@ public class EndDragonFight { - if (data.isRespawning) { - this.respawnStage = DragonRespawnAnimation.START; - } -+ // Paper start - Add config to disable ender dragon legacy check -+ if (data == EndDragonFight.Data.DEFAULT && !world.paperConfig().entities.spawning.scanForLegacyEnderDragon) { -+ this.needsStateScanning = false; -+ this.dragonKilled = true; -+ } -+ // Paper end - Add config to disable ender dragon legacy check - - this.portalLocation = (BlockPos) data.exitPortalLocation.orElse(null); // CraftBukkit - decompile error - this.gateways.addAll((Collection) data.gateways.orElseGet(() -> { diff --git a/patches/server/0206-Implement-World.getEntity-UUID-API.patch b/patches/server/0206-Implement-World.getEntity-UUID-API.patch new file mode 100644 index 000000000000..5e9929379abf --- /dev/null +++ b/patches/server/0206-Implement-World.getEntity-UUID-API.patch @@ -0,0 +1,26 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Brokkonaut +Date: Tue, 3 Jul 2018 16:08:14 +0200 +Subject: [PATCH] Implement World.getEntity(UUID) API + + +diff --git a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java +index e285c8486c36e8d2bc4ddc43e0029943ea5c7fe7..e9840a1159419593145d166b54e523fd3e6684f0 100644 +--- a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java ++++ b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java +@@ -1137,6 +1137,15 @@ public class CraftWorld extends CraftRegionAccessor implements World { + return list; + } + ++ // Paper start - getEntity by UUID API ++ @Override ++ public Entity getEntity(UUID uuid) { ++ Preconditions.checkArgument(uuid != null, "UUID cannot be null"); ++ net.minecraft.world.entity.Entity entity = world.getEntity(uuid); ++ return entity == null ? null : entity.getBukkitEntity(); ++ } ++ // Paper end ++ + @Override + public void save() { + org.spigotmc.AsyncCatcher.catchOp("world save"); // Spigot diff --git a/patches/server/0207-Implement-World.getEntity-UUID-API.patch b/patches/server/0207-Implement-World.getEntity-UUID-API.patch deleted file mode 100644 index 24247c437d41..000000000000 --- a/patches/server/0207-Implement-World.getEntity-UUID-API.patch +++ /dev/null @@ -1,26 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Brokkonaut -Date: Tue, 3 Jul 2018 16:08:14 +0200 -Subject: [PATCH] Implement World.getEntity(UUID) API - - -diff --git a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java -index 2c13ece6592700126365a155f104d6971aab4ede..f54038554bc184ecefbd32c69c234db8aada0309 100644 ---- a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java -+++ b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java -@@ -1127,6 +1127,15 @@ public class CraftWorld extends CraftRegionAccessor implements World { - return list; - } - -+ // Paper start - getEntity by UUID API -+ @Override -+ public Entity getEntity(UUID uuid) { -+ Preconditions.checkArgument(uuid != null, "UUID cannot be null"); -+ net.minecraft.world.entity.Entity entity = world.getEntity(uuid); -+ return entity == null ? null : entity.getBukkitEntity(); -+ } -+ // Paper end -+ - @Override - public void save() { - org.spigotmc.AsyncCatcher.catchOp("world save"); // Spigot diff --git a/patches/server/0207-InventoryCloseEvent-Reason-API.patch b/patches/server/0207-InventoryCloseEvent-Reason-API.patch new file mode 100644 index 000000000000..8e604276488b --- /dev/null +++ b/patches/server/0207-InventoryCloseEvent-Reason-API.patch @@ -0,0 +1,212 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Aikar +Date: Tue, 3 Jul 2018 21:56:23 -0400 +Subject: [PATCH] InventoryCloseEvent Reason API + +Allows you to determine why an inventory was closed, enabling plugin developers +to "confirm" things based on if it was player triggered close or not. + +diff --git a/src/main/java/net/minecraft/server/level/ServerLevel.java b/src/main/java/net/minecraft/server/level/ServerLevel.java +index 3324156c004e0506df8be23050f497d462b4b9c1..75b2caba214c312f9afdd231ee3d75689380a5f3 100644 +--- a/src/main/java/net/minecraft/server/level/ServerLevel.java ++++ b/src/main/java/net/minecraft/server/level/ServerLevel.java +@@ -1204,7 +1204,7 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe + for (net.minecraft.world.level.block.entity.BlockEntity tileentity : chunk.getBlockEntities().values()) { + if (tileentity instanceof net.minecraft.world.Container) { + for (org.bukkit.entity.HumanEntity h : Lists.newArrayList(((net.minecraft.world.Container) tileentity).getViewers())) { +- h.closeInventory(); ++ h.closeInventory(org.bukkit.event.inventory.InventoryCloseEvent.Reason.UNLOADED); // Paper - Inventory close reason + } + } + } +@@ -2249,7 +2249,7 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe + // Spigot Start + if (entity.getBukkitEntity() instanceof org.bukkit.inventory.InventoryHolder && (!(entity instanceof ServerPlayer) || entity.getRemovalReason() != Entity.RemovalReason.KILLED)) { // SPIGOT-6876: closeInventory clears death message + for (org.bukkit.entity.HumanEntity h : Lists.newArrayList(((org.bukkit.inventory.InventoryHolder) entity.getBukkitEntity()).getInventory().getViewers())) { +- h.closeInventory(); ++ h.closeInventory(org.bukkit.event.inventory.InventoryCloseEvent.Reason.UNLOADED); // Paper - Inventory close reason + } + } + // Spigot End +diff --git a/src/main/java/net/minecraft/server/level/ServerPlayer.java b/src/main/java/net/minecraft/server/level/ServerPlayer.java +index 98aeafcc51e23a7534c8d57e4db0eb58abb3f30b..29b836a75b835f0d5233db419fc5ca8dde885fdb 100644 +--- a/src/main/java/net/minecraft/server/level/ServerPlayer.java ++++ b/src/main/java/net/minecraft/server/level/ServerPlayer.java +@@ -933,7 +933,7 @@ public class ServerPlayer extends net.minecraft.world.entity.player.Player { + } + // Paper end - Configurable container update tick rate + if (!this.level().isClientSide && !this.containerMenu.stillValid(this)) { +- this.closeContainer(); ++ this.closeContainer(org.bukkit.event.inventory.InventoryCloseEvent.Reason.CANT_USE); // Paper - Inventory close reason + this.containerMenu = this.inventoryMenu; + } + +@@ -1185,7 +1185,7 @@ public class ServerPlayer extends net.minecraft.world.entity.player.Player { + + // SPIGOT-943 - only call if they have an inventory open + if (this.containerMenu != this.inventoryMenu) { +- this.closeContainer(); ++ this.closeContainer(org.bukkit.event.inventory.InventoryCloseEvent.Reason.DEATH); // Paper - Inventory close reason + } + + net.kyori.adventure.text.Component deathMessage = event.deathMessage() != null ? event.deathMessage() : net.kyori.adventure.text.Component.empty(); // Paper - Adventure +@@ -1859,7 +1859,7 @@ public class ServerPlayer extends net.minecraft.world.entity.player.Player { + } + // CraftBukkit end + if (this.containerMenu != this.inventoryMenu) { +- this.closeContainer(); ++ this.closeContainer(org.bukkit.event.inventory.InventoryCloseEvent.Reason.OPEN_NEW); // Paper - Inventory close reason + } + + // this.nextContainerCounter(); // CraftBukkit - moved up +@@ -1889,7 +1889,13 @@ public class ServerPlayer extends net.minecraft.world.entity.player.Player { + + @Override + public void closeContainer() { +- CraftEventFactory.handleInventoryCloseEvent(this); // CraftBukkit ++ // Paper start - Inventory close reason ++ this.closeContainer(org.bukkit.event.inventory.InventoryCloseEvent.Reason.UNKNOWN); ++ } ++ @Override ++ public void closeContainer(org.bukkit.event.inventory.InventoryCloseEvent.Reason reason) { ++ CraftEventFactory.handleInventoryCloseEvent(this, reason); // CraftBukkit ++ // Paper end - Inventory close reason + this.connection.send(new ClientboundContainerClosePacket(this.containerMenu.containerId)); + this.doCloseContainer(); + } +diff --git a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java +index 79d9a58c66382fd94ab5f6020285e341b0114f6a..f36d72153c1ec0426790ed3033500c3cb766af2d 100644 +--- a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java ++++ b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java +@@ -2638,10 +2638,15 @@ public class ServerGamePacketListenerImpl extends ServerCommonPacketListenerImpl + + @Override + public void handleContainerClose(ServerboundContainerClosePacket packet) { ++ // Paper start - Inventory close reason ++ this.handleContainerClose(packet, org.bukkit.event.inventory.InventoryCloseEvent.Reason.PLAYER); ++ } ++ public void handleContainerClose(ServerboundContainerClosePacket packet, org.bukkit.event.inventory.InventoryCloseEvent.Reason reason) { ++ // Paper end - Inventory close reason + PacketUtils.ensureRunningOnSameThread(packet, this, this.player.serverLevel()); + + if (this.player.isImmobile()) return; // CraftBukkit +- CraftEventFactory.handleInventoryCloseEvent(this.player); // CraftBukkit ++ CraftEventFactory.handleInventoryCloseEvent(this.player, reason); // CraftBukkit // Paper + + this.player.doCloseContainer(); + } +diff --git a/src/main/java/net/minecraft/server/players/PlayerList.java b/src/main/java/net/minecraft/server/players/PlayerList.java +index a929c809dc1dcb2bdab4db0d2a8ca794189e93d9..c9c3ebcb7239bf01617a89f03cd0ad12dd0b2c34 100644 +--- a/src/main/java/net/minecraft/server/players/PlayerList.java ++++ b/src/main/java/net/minecraft/server/players/PlayerList.java +@@ -468,7 +468,7 @@ public abstract class PlayerList { + // CraftBukkit start - Quitting must be before we do final save of data, in case plugins need to modify it + // See SPIGOT-5799, SPIGOT-6145 + if (entityplayer.containerMenu != entityplayer.inventoryMenu) { +- entityplayer.closeContainer(); ++ entityplayer.closeContainer(org.bukkit.event.inventory.InventoryCloseEvent.Reason.DISCONNECT); // Paper - Inventory close reason + } + + PlayerQuitEvent playerQuitEvent = new PlayerQuitEvent(entityplayer.getBukkitEntity(), net.kyori.adventure.text.Component.translatable("multiplayer.player.left", net.kyori.adventure.text.format.NamedTextColor.YELLOW, io.papermc.paper.configuration.GlobalConfiguration.get().messages.useDisplayNameInQuitMessage ? entityplayer.getBukkitEntity().displayName() : io.papermc.paper.adventure.PaperAdventure.asAdventure(entityplayer.getDisplayName()))); // Paper - Adventure +diff --git a/src/main/java/net/minecraft/world/entity/player/Player.java b/src/main/java/net/minecraft/world/entity/player/Player.java +index ba0ea4d6b67c9c52f170e0235e4b972aad3ce211..7eb4563299e7c54ec4df53c966f6d1a86fb2ec4e 100644 +--- a/src/main/java/net/minecraft/world/entity/player/Player.java ++++ b/src/main/java/net/minecraft/world/entity/player/Player.java +@@ -279,7 +279,7 @@ public abstract class Player extends LivingEntity { + this.updateIsUnderwater(); + super.tick(); + if (!this.level().isClientSide && this.containerMenu != null && !this.containerMenu.stillValid(this)) { +- this.closeContainer(); ++ this.closeContainer(org.bukkit.event.inventory.InventoryCloseEvent.Reason.CANT_USE); // Paper - Inventory close reason + this.containerMenu = this.inventoryMenu; + } + +@@ -531,6 +531,13 @@ public abstract class Player extends LivingEntity { + + } + ++ // Paper start - Inventory close reason; unused code, but to keep signatures aligned ++ public void closeContainer(org.bukkit.event.inventory.InventoryCloseEvent.Reason reason) { ++ closeContainer(); ++ this.containerMenu = this.inventoryMenu; ++ } ++ // Paper end - Inventory close reason ++ + public void closeContainer() { + this.containerMenu = this.inventoryMenu; + } +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftHumanEntity.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftHumanEntity.java +index 6d4e0a90c70f7a66450cbb18ebec1d7bf9200af2..5ff159be1a6dfb4b1a5b9aa1e435d294f205215e 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftHumanEntity.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftHumanEntity.java +@@ -387,7 +387,7 @@ public class CraftHumanEntity extends CraftLivingEntity implements HumanEntity { + if (((ServerPlayer) this.getHandle()).connection == null) return; + if (this.getHandle().containerMenu != this.getHandle().inventoryMenu) { + // fire INVENTORY_CLOSE if one already open +- ((ServerPlayer) this.getHandle()).connection.handleContainerClose(new ServerboundContainerClosePacket(this.getHandle().containerMenu.containerId)); ++ ((ServerPlayer) this.getHandle()).connection.handleContainerClose(new ServerboundContainerClosePacket(this.getHandle().containerMenu.containerId), org.bukkit.event.inventory.InventoryCloseEvent.Reason.OPEN_NEW); // Paper - Inventory close reason + } + ServerPlayer player = (ServerPlayer) this.getHandle(); + AbstractContainerMenu container; +@@ -457,8 +457,14 @@ public class CraftHumanEntity extends CraftLivingEntity implements HumanEntity { + + @Override + public void closeInventory() { +- this.getHandle().closeContainer(); ++ // Paper start - Inventory close reason ++ this.getHandle().closeContainer(org.bukkit.event.inventory.InventoryCloseEvent.Reason.PLUGIN); + } ++ @Override ++ public void closeInventory(org.bukkit.event.inventory.InventoryCloseEvent.Reason reason) { ++ getHandle().closeContainer(reason); ++ } ++ // Paper end - Inventory close reason + + @Override + public boolean isBlocking() { +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java +index a4333eae8aa2ae3fefdc8a765a4434c36e123b12..462877cf6f0dce4f86b6a47564affe9beb2559a8 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java +@@ -1290,7 +1290,7 @@ public class CraftPlayer extends CraftHumanEntity implements Player { + + // Close any foreign inventory + if (this.getHandle().containerMenu != this.getHandle().inventoryMenu) { +- this.getHandle().closeContainer(); ++ this.getHandle().closeContainer(org.bukkit.event.inventory.InventoryCloseEvent.Reason.TELEPORT); // Paper - Inventory close reason + } + + // Check if the fromWorld and toWorld are the same. +diff --git a/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java b/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java +index 60a16ac62a27a92b763bf90e72fab4ed01aeb592..41c6a7260317ed575a3320ac36b0f2be22c120aa 100644 +--- a/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java ++++ b/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java +@@ -1281,7 +1281,7 @@ public class CraftEventFactory { + + public static AbstractContainerMenu callInventoryOpenEvent(ServerPlayer player, AbstractContainerMenu container, boolean cancelled) { + if (player.containerMenu != player.inventoryMenu) { // fire INVENTORY_CLOSE if one already open +- player.connection.handleContainerClose(new ServerboundContainerClosePacket(player.containerMenu.containerId)); ++ player.connection.handleContainerClose(new ServerboundContainerClosePacket(player.containerMenu.containerId), InventoryCloseEvent.Reason.OPEN_NEW); // Paper - Inventory close reason + } + + CraftServer server = player.level().getCraftServer(); +@@ -1477,8 +1477,18 @@ public class CraftEventFactory { + return event; + } + ++ // Paper start ++ /** ++ * Incase plugins hooked into this or Spigot adds a new inventory close event. Prefer to pass a reason ++ * @param human ++ */ ++ @Deprecated + public static void handleInventoryCloseEvent(net.minecraft.world.entity.player.Player human) { +- InventoryCloseEvent event = new InventoryCloseEvent(human.containerMenu.getBukkitView()); ++ handleInventoryCloseEvent(human, org.bukkit.event.inventory.InventoryCloseEvent.Reason.UNKNOWN); ++ } ++ public static void handleInventoryCloseEvent(net.minecraft.world.entity.player.Player human, org.bukkit.event.inventory.InventoryCloseEvent.Reason reason) { ++ // Paper end ++ InventoryCloseEvent event = new InventoryCloseEvent(human.containerMenu.getBukkitView(), reason); // Paper + human.level().getCraftServer().getPluginManager().callEvent(event); + human.containerMenu.transferTo(human.inventoryMenu, human.getBukkitEntity()); + } diff --git a/patches/server/0208-InventoryCloseEvent-Reason-API.patch b/patches/server/0208-InventoryCloseEvent-Reason-API.patch deleted file mode 100644 index c932aab48fab..000000000000 --- a/patches/server/0208-InventoryCloseEvent-Reason-API.patch +++ /dev/null @@ -1,212 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Aikar -Date: Tue, 3 Jul 2018 21:56:23 -0400 -Subject: [PATCH] InventoryCloseEvent Reason API - -Allows you to determine why an inventory was closed, enabling plugin developers -to "confirm" things based on if it was player triggered close or not. - -diff --git a/src/main/java/net/minecraft/server/level/ServerLevel.java b/src/main/java/net/minecraft/server/level/ServerLevel.java -index f2e75b36cbceb9cfa0755bd045f23073712d0e8a..480025bb35a838cc05022e3e316f8536c47dd87b 100644 ---- a/src/main/java/net/minecraft/server/level/ServerLevel.java -+++ b/src/main/java/net/minecraft/server/level/ServerLevel.java -@@ -1239,7 +1239,7 @@ public class ServerLevel extends Level implements WorldGenLevel { - for (net.minecraft.world.level.block.entity.BlockEntity tileentity : chunk.getBlockEntities().values()) { - if (tileentity instanceof net.minecraft.world.Container) { - for (org.bukkit.entity.HumanEntity h : Lists.newArrayList(((net.minecraft.world.Container) tileentity).getViewers())) { -- h.closeInventory(); -+ h.closeInventory(org.bukkit.event.inventory.InventoryCloseEvent.Reason.UNLOADED); // Paper - Inventory close reason - } - } - } -@@ -2198,7 +2198,7 @@ public class ServerLevel extends Level implements WorldGenLevel { - // Spigot Start - if (entity.getBukkitEntity() instanceof org.bukkit.inventory.InventoryHolder && (!(entity instanceof ServerPlayer) || entity.getRemovalReason() != Entity.RemovalReason.KILLED)) { // SPIGOT-6876: closeInventory clears death message - for (org.bukkit.entity.HumanEntity h : Lists.newArrayList(((org.bukkit.inventory.InventoryHolder) entity.getBukkitEntity()).getInventory().getViewers())) { -- h.closeInventory(); -+ h.closeInventory(org.bukkit.event.inventory.InventoryCloseEvent.Reason.UNLOADED); // Paper - Inventory close reason - } - } - // Spigot End -diff --git a/src/main/java/net/minecraft/server/level/ServerPlayer.java b/src/main/java/net/minecraft/server/level/ServerPlayer.java -index fb5130b6378554ccb23fb7992e408497ca093ff3..0dee94f1dd27a0d7e709367450c5ef7956e27217 100644 ---- a/src/main/java/net/minecraft/server/level/ServerPlayer.java -+++ b/src/main/java/net/minecraft/server/level/ServerPlayer.java -@@ -705,7 +705,7 @@ public class ServerPlayer extends net.minecraft.world.entity.player.Player { - } - // Paper end - Configurable container update tick rate - if (!this.level().isClientSide && !this.containerMenu.stillValid(this)) { -- this.closeContainer(); -+ this.closeContainer(org.bukkit.event.inventory.InventoryCloseEvent.Reason.CANT_USE); // Paper - Inventory close reason - this.containerMenu = this.inventoryMenu; - } - -@@ -925,7 +925,7 @@ public class ServerPlayer extends net.minecraft.world.entity.player.Player { - - // SPIGOT-943 - only call if they have an inventory open - if (this.containerMenu != this.inventoryMenu) { -- this.closeContainer(); -+ this.closeContainer(org.bukkit.event.inventory.InventoryCloseEvent.Reason.DEATH); // Paper - Inventory close reason - } - - net.kyori.adventure.text.Component deathMessage = event.deathMessage() != null ? event.deathMessage() : net.kyori.adventure.text.Component.empty(); // Paper - Adventure -@@ -1591,7 +1591,7 @@ public class ServerPlayer extends net.minecraft.world.entity.player.Player { - } - // CraftBukkit end - if (this.containerMenu != this.inventoryMenu) { -- this.closeContainer(); -+ this.closeContainer(org.bukkit.event.inventory.InventoryCloseEvent.Reason.OPEN_NEW); // Paper - Inventory close reason - } - - // this.nextContainerCounter(); // CraftBukkit - moved up -@@ -1621,7 +1621,13 @@ public class ServerPlayer extends net.minecraft.world.entity.player.Player { - - @Override - public void closeContainer() { -- CraftEventFactory.handleInventoryCloseEvent(this); // CraftBukkit -+ // Paper start - Inventory close reason -+ this.closeContainer(org.bukkit.event.inventory.InventoryCloseEvent.Reason.UNKNOWN); -+ } -+ @Override -+ public void closeContainer(org.bukkit.event.inventory.InventoryCloseEvent.Reason reason) { -+ CraftEventFactory.handleInventoryCloseEvent(this, reason); // CraftBukkit -+ // Paper end - Inventory close reason - this.connection.send(new ClientboundContainerClosePacket(this.containerMenu.containerId)); - this.doCloseContainer(); - } -diff --git a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java -index 0073c6c5433be3193a01257a26c7035e544f37dd..0321128ab745250e79fa5f66079c9aeb7f394cc0 100644 ---- a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java -+++ b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java -@@ -2619,10 +2619,15 @@ public class ServerGamePacketListenerImpl extends ServerCommonPacketListenerImpl - - @Override - public void handleContainerClose(ServerboundContainerClosePacket packet) { -+ // Paper start - Inventory close reason -+ this.handleContainerClose(packet, org.bukkit.event.inventory.InventoryCloseEvent.Reason.PLAYER); -+ } -+ public void handleContainerClose(ServerboundContainerClosePacket packet, org.bukkit.event.inventory.InventoryCloseEvent.Reason reason) { -+ // Paper end - Inventory close reason - PacketUtils.ensureRunningOnSameThread(packet, this, this.player.serverLevel()); - - if (this.player.isImmobile()) return; // CraftBukkit -- CraftEventFactory.handleInventoryCloseEvent(this.player); // CraftBukkit -+ CraftEventFactory.handleInventoryCloseEvent(this.player, reason); // CraftBukkit // Paper - - this.player.doCloseContainer(); - } -diff --git a/src/main/java/net/minecraft/server/players/PlayerList.java b/src/main/java/net/minecraft/server/players/PlayerList.java -index 26cee48ea3650aaf87fd2ba9c70d4ca9a88e2d87..25dee4848c8b2cff74075c6d26d384e71f706627 100644 ---- a/src/main/java/net/minecraft/server/players/PlayerList.java -+++ b/src/main/java/net/minecraft/server/players/PlayerList.java -@@ -510,7 +510,7 @@ public abstract class PlayerList { - // CraftBukkit start - Quitting must be before we do final save of data, in case plugins need to modify it - // See SPIGOT-5799, SPIGOT-6145 - if (entityplayer.containerMenu != entityplayer.inventoryMenu) { -- entityplayer.closeContainer(); -+ entityplayer.closeContainer(org.bukkit.event.inventory.InventoryCloseEvent.Reason.DISCONNECT); // Paper - Inventory close reason - } - - PlayerQuitEvent playerQuitEvent = new PlayerQuitEvent(entityplayer.getBukkitEntity(), net.kyori.adventure.text.Component.translatable("multiplayer.player.left", net.kyori.adventure.text.format.NamedTextColor.YELLOW, io.papermc.paper.configuration.GlobalConfiguration.get().messages.useDisplayNameInQuitMessage ? entityplayer.getBukkitEntity().displayName() : io.papermc.paper.adventure.PaperAdventure.asAdventure(entityplayer.getDisplayName()))); // Paper - Adventure -diff --git a/src/main/java/net/minecraft/world/entity/player/Player.java b/src/main/java/net/minecraft/world/entity/player/Player.java -index 1b13096da1f0bc49efe25677ec24e6abe7ff2879..b70fbc1a93271bbf28402afbe9c6e538a4b6e9aa 100644 ---- a/src/main/java/net/minecraft/world/entity/player/Player.java -+++ b/src/main/java/net/minecraft/world/entity/player/Player.java -@@ -279,7 +279,7 @@ public abstract class Player extends LivingEntity { - this.updateIsUnderwater(); - super.tick(); - if (!this.level().isClientSide && this.containerMenu != null && !this.containerMenu.stillValid(this)) { -- this.closeContainer(); -+ this.closeContainer(org.bukkit.event.inventory.InventoryCloseEvent.Reason.CANT_USE); // Paper - Inventory close reason - this.containerMenu = this.inventoryMenu; - } - -@@ -497,6 +497,13 @@ public abstract class Player extends LivingEntity { - - } - -+ // Paper start - Inventory close reason; unused code, but to keep signatures aligned -+ public void closeContainer(org.bukkit.event.inventory.InventoryCloseEvent.Reason reason) { -+ closeContainer(); -+ this.containerMenu = this.inventoryMenu; -+ } -+ // Paper end - Inventory close reason -+ - public void closeContainer() { - this.containerMenu = this.inventoryMenu; - } -diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftHumanEntity.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftHumanEntity.java -index e59fee587c2d90df5a6aa7a3df0eefc0cb5165ac..ee7cf7f1d491ffdf26c3f156d299e2f517a05608 100644 ---- a/src/main/java/org/bukkit/craftbukkit/entity/CraftHumanEntity.java -+++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftHumanEntity.java -@@ -386,7 +386,7 @@ public class CraftHumanEntity extends CraftLivingEntity implements HumanEntity { - if (((ServerPlayer) this.getHandle()).connection == null) return; - if (this.getHandle().containerMenu != this.getHandle().inventoryMenu) { - // fire INVENTORY_CLOSE if one already open -- ((ServerPlayer) this.getHandle()).connection.handleContainerClose(new ServerboundContainerClosePacket(this.getHandle().containerMenu.containerId)); -+ ((ServerPlayer) this.getHandle()).connection.handleContainerClose(new ServerboundContainerClosePacket(this.getHandle().containerMenu.containerId), org.bukkit.event.inventory.InventoryCloseEvent.Reason.OPEN_NEW); // Paper - Inventory close reason - } - ServerPlayer player = (ServerPlayer) this.getHandle(); - AbstractContainerMenu container; -@@ -456,8 +456,14 @@ public class CraftHumanEntity extends CraftLivingEntity implements HumanEntity { - - @Override - public void closeInventory() { -- this.getHandle().closeContainer(); -+ // Paper start - Inventory close reason -+ this.getHandle().closeContainer(org.bukkit.event.inventory.InventoryCloseEvent.Reason.PLUGIN); - } -+ @Override -+ public void closeInventory(org.bukkit.event.inventory.InventoryCloseEvent.Reason reason) { -+ getHandle().closeContainer(reason); -+ } -+ // Paper end - Inventory close reason - - @Override - public boolean isBlocking() { -diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java -index 18bcc27d07d7ab4b57148521891670ab61d6432c..35addb8d7620eeeede31c4831983a2c8bfb23ef5 100644 ---- a/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java -+++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java -@@ -1273,7 +1273,7 @@ public class CraftPlayer extends CraftHumanEntity implements Player { - - // Close any foreign inventory - if (this.getHandle().containerMenu != this.getHandle().inventoryMenu) { -- this.getHandle().closeContainer(); -+ this.getHandle().closeContainer(org.bukkit.event.inventory.InventoryCloseEvent.Reason.TELEPORT); // Paper - Inventory close reason - } - - // Check if the fromWorld and toWorld are the same. -diff --git a/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java b/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java -index 7b1f55d02c2c1f3598b7b67c4c3d26c11c7450ae..889a2094850e6c5bda1efeac2e26bf77a007f4f3 100644 ---- a/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java -+++ b/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java -@@ -1283,7 +1283,7 @@ public class CraftEventFactory { - - public static AbstractContainerMenu callInventoryOpenEvent(ServerPlayer player, AbstractContainerMenu container, boolean cancelled) { - if (player.containerMenu != player.inventoryMenu) { // fire INVENTORY_CLOSE if one already open -- player.connection.handleContainerClose(new ServerboundContainerClosePacket(player.containerMenu.containerId)); -+ player.connection.handleContainerClose(new ServerboundContainerClosePacket(player.containerMenu.containerId), InventoryCloseEvent.Reason.OPEN_NEW); // Paper - Inventory close reason - } - - CraftServer server = player.level().getCraftServer(); -@@ -1479,8 +1479,18 @@ public class CraftEventFactory { - return event; - } - -+ // Paper start -+ /** -+ * Incase plugins hooked into this or Spigot adds a new inventory close event. Prefer to pass a reason -+ * @param human -+ */ -+ @Deprecated - public static void handleInventoryCloseEvent(net.minecraft.world.entity.player.Player human) { -- InventoryCloseEvent event = new InventoryCloseEvent(human.containerMenu.getBukkitView()); -+ handleInventoryCloseEvent(human, org.bukkit.event.inventory.InventoryCloseEvent.Reason.UNKNOWN); -+ } -+ public static void handleInventoryCloseEvent(net.minecraft.world.entity.player.Player human, org.bukkit.event.inventory.InventoryCloseEvent.Reason reason) { -+ // Paper end -+ InventoryCloseEvent event = new InventoryCloseEvent(human.containerMenu.getBukkitView(), reason); // Paper - human.level().getCraftServer().getPluginManager().callEvent(event); - human.containerMenu.transferTo(human.inventoryMenu, human.getBukkitEntity()); - } diff --git a/patches/server/0209-Vex-get-setSummoner-API.patch b/patches/server/0208-Vex-get-setSummoner-API.patch similarity index 100% rename from patches/server/0209-Vex-get-setSummoner-API.patch rename to patches/server/0208-Vex-get-setSummoner-API.patch diff --git a/patches/server/0209-add-more-information-to-Entity.toString.patch b/patches/server/0209-add-more-information-to-Entity.toString.patch new file mode 100644 index 000000000000..a7a8976cca3c --- /dev/null +++ b/patches/server/0209-add-more-information-to-Entity.toString.patch @@ -0,0 +1,20 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Aikar +Date: Thu, 19 Jul 2018 01:13:28 -0400 +Subject: [PATCH] add more information to Entity.toString() + +UUID, ticks lived, valid, dead + +diff --git a/src/main/java/net/minecraft/world/entity/Entity.java b/src/main/java/net/minecraft/world/entity/Entity.java +index 011006bc2e88a9fec98796f939c07d884b92126b..79a3d586ddf404c449b7c0aa1996e9b9897b2383 100644 +--- a/src/main/java/net/minecraft/world/entity/Entity.java ++++ b/src/main/java/net/minecraft/world/entity/Entity.java +@@ -3406,7 +3406,7 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess + public String toString() { + String s = this.level() == null ? "~NULL~" : this.level().toString(); + +- return this.removalReason != null ? String.format(Locale.ROOT, "%s['%s'/%d, l='%s', x=%.2f, y=%.2f, z=%.2f, removed=%s]", this.getClass().getSimpleName(), this.getName().getString(), this.id, s, this.getX(), this.getY(), this.getZ(), this.removalReason) : String.format(Locale.ROOT, "%s['%s'/%d, l='%s', x=%.2f, y=%.2f, z=%.2f]", this.getClass().getSimpleName(), this.getName().getString(), this.id, s, this.getX(), this.getY(), this.getZ()); ++ return this.removalReason != null ? String.format(Locale.ROOT, "%s['%s'/%d, uuid='%s', l='%s', x=%.2f, y=%.2f, z=%.2f, cpos=%s, tl=%d, v=%b, removed=%s]", this.getClass().getSimpleName(), this.getName().getString(), this.id, this.uuid, s, this.getX(), this.getY(), this.getZ(), this.chunkPosition(), this.tickCount, this.valid, this.removalReason) : String.format(Locale.ROOT, "%s['%s'/%d, uuid='%s', l='%s', x=%.2f, y=%.2f, z=%.2f, cpos=%s, tl=%d, v=%b]", this.getClass().getSimpleName(), this.getName().getString(), this.id, this.uuid, s, this.getX(), this.getY(), this.getZ(), this.chunkPosition(), this.tickCount, this.valid); // Paper - add more info + } + + public final boolean isInvulnerableToBase(DamageSource damageSource) { diff --git a/patches/server/0210-EnderDragon-Events.patch b/patches/server/0210-EnderDragon-Events.patch new file mode 100644 index 000000000000..f78c44aab2eb --- /dev/null +++ b/patches/server/0210-EnderDragon-Events.patch @@ -0,0 +1,53 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: BillyGalbreath +Date: Sat, 21 Jul 2018 01:51:27 -0500 +Subject: [PATCH] EnderDragon Events + + +diff --git a/src/main/java/net/minecraft/world/entity/boss/enderdragon/phases/DragonSittingFlamingPhase.java b/src/main/java/net/minecraft/world/entity/boss/enderdragon/phases/DragonSittingFlamingPhase.java +index fe625faf0120b72e892542e4bb5ecea4b73becc7..55b66d3e0d7394a017fb1cdd87e45d7f4eb84976 100644 +--- a/src/main/java/net/minecraft/world/entity/boss/enderdragon/phases/DragonSittingFlamingPhase.java ++++ b/src/main/java/net/minecraft/world/entity/boss/enderdragon/phases/DragonSittingFlamingPhase.java +@@ -89,7 +89,13 @@ public class DragonSittingFlamingPhase extends AbstractDragonSittingPhase { + this.flame.setDuration(200); + this.flame.setParticle(ParticleTypes.DRAGON_BREATH); + this.flame.addEffect(new MobEffectInstance(MobEffects.HARM)); ++ if (new com.destroystokyo.paper.event.entity.EnderDragonFlameEvent((org.bukkit.entity.EnderDragon) this.dragon.getBukkitEntity(), (org.bukkit.entity.AreaEffectCloud) this.flame.getBukkitEntity()).callEvent()) { // Paper - EnderDragon Events + world.addFreshEntity(this.flame); ++ // Paper start - EnderDragon Events ++ } else { ++ this.end(); ++ } ++ // Paper end - EnderDragon Events + } + + } +diff --git a/src/main/java/net/minecraft/world/entity/boss/enderdragon/phases/DragonStrafePlayerPhase.java b/src/main/java/net/minecraft/world/entity/boss/enderdragon/phases/DragonStrafePlayerPhase.java +index 68b7bfc139e928192005df8339be371ed8b2a9be..2db75673e525708d04bda9f6c9af80e7f8d361e6 100644 +--- a/src/main/java/net/minecraft/world/entity/boss/enderdragon/phases/DragonStrafePlayerPhase.java ++++ b/src/main/java/net/minecraft/world/entity/boss/enderdragon/phases/DragonStrafePlayerPhase.java +@@ -80,7 +80,9 @@ public class DragonStrafePlayerPhase extends AbstractDragonPhaseInstance { + + DragonFireball dragonFireball = new DragonFireball(world, this.dragon, vec34.normalize()); + dragonFireball.moveTo(o, p, q, 0.0F, 0.0F); ++ if (new com.destroystokyo.paper.event.entity.EnderDragonShootFireballEvent((org.bukkit.entity.EnderDragon) dragon.getBukkitEntity(), (org.bukkit.entity.DragonFireball) dragonFireball.getBukkitEntity()).callEvent()) // Paper - EnderDragon Events + world.addFreshEntity(dragonFireball); ++ else dragonFireball.discard(null); // Paper - EnderDragon Events + this.fireballCharge = 0; + if (this.currentPath != null) { + while (!this.currentPath.isDone()) { +diff --git a/src/main/java/net/minecraft/world/entity/projectile/DragonFireball.java b/src/main/java/net/minecraft/world/entity/projectile/DragonFireball.java +index 5b8f3cf80b4fb425bdd0e3b9a431128536c7f1b1..2eecdcbea3d51b1fb6e0c3db0667464a699ca0df 100644 +--- a/src/main/java/net/minecraft/world/entity/projectile/DragonFireball.java ++++ b/src/main/java/net/minecraft/world/entity/projectile/DragonFireball.java +@@ -62,8 +62,10 @@ public class DragonFireball extends AbstractHurtingProjectile { + } + } + ++ if (new com.destroystokyo.paper.event.entity.EnderDragonFireballHitEvent((org.bukkit.entity.DragonFireball) this.getBukkitEntity(), list.stream().map(LivingEntity::getBukkitLivingEntity).collect(java.util.stream.Collectors.toList()), (org.bukkit.entity.AreaEffectCloud) entityareaeffectcloud.getBukkitEntity()).callEvent()) { // Paper - EnderDragon Events + this.level().levelEvent(2006, this.blockPosition(), this.isSilent() ? -1 : 1); + this.level().addFreshEntity(entityareaeffectcloud); ++ } else entityareaeffectcloud.discard(null); // Paper - EnderDragon Events + this.discard(EntityRemoveEvent.Cause.HIT); // CraftBukkit - add Bukkit remove cause + } + diff --git a/patches/server/0210-add-more-information-to-Entity.toString.patch b/patches/server/0210-add-more-information-to-Entity.toString.patch deleted file mode 100644 index b104bbec151a..000000000000 --- a/patches/server/0210-add-more-information-to-Entity.toString.patch +++ /dev/null @@ -1,20 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Aikar -Date: Thu, 19 Jul 2018 01:13:28 -0400 -Subject: [PATCH] add more information to Entity.toString() - -UUID, ticks lived, valid, dead - -diff --git a/src/main/java/net/minecraft/world/entity/Entity.java b/src/main/java/net/minecraft/world/entity/Entity.java -index a2b727dea997ed0d7b1ef677a94b5957f12d5abb..7fd1a75ba0068ee3ca6c29a550a9a1b33c5cacc5 100644 ---- a/src/main/java/net/minecraft/world/entity/Entity.java -+++ b/src/main/java/net/minecraft/world/entity/Entity.java -@@ -3279,7 +3279,7 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess - public String toString() { - String s = this.level() == null ? "~NULL~" : this.level().toString(); - -- return this.removalReason != null ? String.format(Locale.ROOT, "%s['%s'/%d, l='%s', x=%.2f, y=%.2f, z=%.2f, removed=%s]", this.getClass().getSimpleName(), this.getName().getString(), this.id, s, this.getX(), this.getY(), this.getZ(), this.removalReason) : String.format(Locale.ROOT, "%s['%s'/%d, l='%s', x=%.2f, y=%.2f, z=%.2f]", this.getClass().getSimpleName(), this.getName().getString(), this.id, s, this.getX(), this.getY(), this.getZ()); -+ return this.removalReason != null ? String.format(Locale.ROOT, "%s['%s'/%d, uuid='%s', l='%s', x=%.2f, y=%.2f, z=%.2f, cpos=%s, tl=%d, v=%b, removed=%s]", this.getClass().getSimpleName(), this.getName().getString(), this.id, this.uuid, s, this.getX(), this.getY(), this.getZ(), this.chunkPosition(), this.tickCount, this.valid, this.removalReason) : String.format(Locale.ROOT, "%s['%s'/%d, uuid='%s', l='%s', x=%.2f, y=%.2f, z=%.2f, cpos=%s, tl=%d, v=%b]", this.getClass().getSimpleName(), this.getName().getString(), this.id, this.uuid, s, this.getX(), this.getY(), this.getZ(), this.chunkPosition(), this.tickCount, this.valid); // Paper - add more info - } - - public boolean isInvulnerableTo(DamageSource damageSource) { diff --git a/patches/server/0211-EnderDragon-Events.patch b/patches/server/0211-EnderDragon-Events.patch deleted file mode 100644 index 73e0d7b16911..000000000000 --- a/patches/server/0211-EnderDragon-Events.patch +++ /dev/null @@ -1,53 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: BillyGalbreath -Date: Sat, 21 Jul 2018 01:51:27 -0500 -Subject: [PATCH] EnderDragon Events - - -diff --git a/src/main/java/net/minecraft/world/entity/boss/enderdragon/phases/DragonSittingFlamingPhase.java b/src/main/java/net/minecraft/world/entity/boss/enderdragon/phases/DragonSittingFlamingPhase.java -index 3eaf64a6f66c6a844e30967e6b87432e559a59e7..5c5c71db73a2bfebbb33cebd6325a0f4fef1f239 100644 ---- a/src/main/java/net/minecraft/world/entity/boss/enderdragon/phases/DragonSittingFlamingPhase.java -+++ b/src/main/java/net/minecraft/world/entity/boss/enderdragon/phases/DragonSittingFlamingPhase.java -@@ -88,7 +88,13 @@ public class DragonSittingFlamingPhase extends AbstractDragonSittingPhase { - this.flame.setDuration(200); - this.flame.setParticle(ParticleTypes.DRAGON_BREATH); - this.flame.addEffect(new MobEffectInstance(MobEffects.HARM)); -+ if (new com.destroystokyo.paper.event.entity.EnderDragonFlameEvent((org.bukkit.entity.EnderDragon) this.dragon.getBukkitEntity(), (org.bukkit.entity.AreaEffectCloud) this.flame.getBukkitEntity()).callEvent()) { // Paper - EnderDragon Events - this.dragon.level().addFreshEntity(this.flame); -+ // Paper start - EnderDragon Events -+ } else { -+ this.end(); -+ } -+ // Paper end - EnderDragon Events - } - - } -diff --git a/src/main/java/net/minecraft/world/entity/boss/enderdragon/phases/DragonStrafePlayerPhase.java b/src/main/java/net/minecraft/world/entity/boss/enderdragon/phases/DragonStrafePlayerPhase.java -index e5c896409536b7fb908590d02e40923d5979841f..a28e6b6a50cfd9191732ad2e4aca5f639a1fae75 100644 ---- a/src/main/java/net/minecraft/world/entity/boss/enderdragon/phases/DragonStrafePlayerPhase.java -+++ b/src/main/java/net/minecraft/world/entity/boss/enderdragon/phases/DragonStrafePlayerPhase.java -@@ -79,7 +79,9 @@ public class DragonStrafePlayerPhase extends AbstractDragonPhaseInstance { - - DragonFireball dragonFireball = new DragonFireball(this.dragon.level(), this.dragon, vec34.normalize()); - dragonFireball.moveTo(o, p, q, 0.0F, 0.0F); -+ if (new com.destroystokyo.paper.event.entity.EnderDragonShootFireballEvent((org.bukkit.entity.EnderDragon) dragon.getBukkitEntity(), (org.bukkit.entity.DragonFireball) dragonFireball.getBukkitEntity()).callEvent()) // Paper - EnderDragon Events - this.dragon.level().addFreshEntity(dragonFireball); -+ else dragonFireball.discard(null); // Paper - EnderDragon Events - this.fireballCharge = 0; - if (this.currentPath != null) { - while (!this.currentPath.isDone()) { -diff --git a/src/main/java/net/minecraft/world/entity/projectile/DragonFireball.java b/src/main/java/net/minecraft/world/entity/projectile/DragonFireball.java -index 3c77515f6b7f1ff89325afba214e9a8e5f536158..1dade7a4fbdf190661e4431496349444467509cc 100644 ---- a/src/main/java/net/minecraft/world/entity/projectile/DragonFireball.java -+++ b/src/main/java/net/minecraft/world/entity/projectile/DragonFireball.java -@@ -63,8 +63,10 @@ public class DragonFireball extends AbstractHurtingProjectile { - } - } - -+ if (new com.destroystokyo.paper.event.entity.EnderDragonFireballHitEvent((org.bukkit.entity.DragonFireball) this.getBukkitEntity(), list.stream().map(LivingEntity::getBukkitLivingEntity).collect(java.util.stream.Collectors.toList()), (org.bukkit.entity.AreaEffectCloud) entityareaeffectcloud.getBukkitEntity()).callEvent()) { // Paper - EnderDragon Events - this.level().levelEvent(2006, this.blockPosition(), this.isSilent() ? -1 : 1); - this.level().addFreshEntity(entityareaeffectcloud); -+ } else entityareaeffectcloud.discard(null); // Paper - EnderDragon Events - this.discard(EntityRemoveEvent.Cause.HIT); // CraftBukkit - add Bukkit remove cause - } - diff --git a/patches/server/0211-PlayerElytraBoostEvent.patch b/patches/server/0211-PlayerElytraBoostEvent.patch new file mode 100644 index 000000000000..3042206c2064 --- /dev/null +++ b/patches/server/0211-PlayerElytraBoostEvent.patch @@ -0,0 +1,75 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: BillyGalbreath +Date: Sat, 21 Jul 2018 01:59:59 -0500 +Subject: [PATCH] PlayerElytraBoostEvent + + +diff --git a/src/main/java/net/minecraft/world/entity/projectile/Projectile.java b/src/main/java/net/minecraft/world/entity/projectile/Projectile.java +index fed01aea47090b990939becde837add6c36bf418..f8ae12a3540c4a7aa7e2c1656cbd866e33e11f57 100644 +--- a/src/main/java/net/minecraft/world/entity/projectile/Projectile.java ++++ b/src/main/java/net/minecraft/world/entity/projectile/Projectile.java +@@ -219,11 +219,34 @@ public abstract class Projectile extends Entity implements TraceableEntity { + }); + } + ++ // Paper start - delayed projectile spawning ++ public record Delayed( ++ T projectile, ++ ServerLevel world, ++ ItemStack projectileStack ++ ) { ++ // Taken from net.minecraft.world.entity.projectile.Projectile.spawnProjectile(T, net.minecraft.server.level.ServerLevel, net.minecraft.world.item.ItemStack, java.util.function.Consumer) ++ public boolean attemptSpawn() { ++ if (!world.addFreshEntity(projectile)) return false; ++ projectile.applyOnProjectileSpawned(this.world, this.projectileStack); ++ return true; ++ } ++ ++ public T spawn() { ++ this.attemptSpawn(); ++ return projectile(); ++ } ++ } ++ // Paper end - delayed projectile spawning ++ + public static T spawnProjectile(T projectile, ServerLevel world, ItemStack projectileStack, Consumer beforeSpawn) { ++ // Paper start - delayed projectile spawning ++ return spawnProjectileDelayed(projectile, world, projectileStack, beforeSpawn).spawn(); ++ } ++ public static Delayed spawnProjectileDelayed(T projectile, ServerLevel world, ItemStack projectileStack, Consumer beforeSpawn) { ++ // Paper end - delayed projectile spawning + beforeSpawn.accept(projectile); +- if (world.addFreshEntity(projectile)) // CraftBukkit +- projectile.applyOnProjectileSpawned(world, projectileStack); +- return projectile; ++ return new Delayed<>(projectile, world, projectileStack); // Paper - delayed projectile spawning + } + + public void applyOnProjectileSpawned(ServerLevel world, ItemStack projectileStack) { +diff --git a/src/main/java/net/minecraft/world/item/FireworkRocketItem.java b/src/main/java/net/minecraft/world/item/FireworkRocketItem.java +index 3929cb76f1c98c0a22eb2ab64c2ed09805ffe448..400ad0fa1d07c8b120e3c3b5488dfa315aa2d23f 100644 +--- a/src/main/java/net/minecraft/world/item/FireworkRocketItem.java ++++ b/src/main/java/net/minecraft/world/item/FireworkRocketItem.java +@@ -56,9 +56,19 @@ public class FireworkRocketItem extends Item implements ProjectileItem { + if (user.isFallFlying()) { + ItemStack itemStack = user.getItemInHand(hand); + if (world instanceof ServerLevel serverLevel) { +- Projectile.spawnProjectile(new FireworkRocketEntity(world, itemStack, user), serverLevel, itemStack, f -> f.spawningEntity = user.getUUID()); // Paper - firework api - assign spawning entity uuid +- itemStack.consume(1, user); +- user.awardStat(Stats.ITEM_USED.get(this)); ++ // Paper start - PlayerElytraBoostEvent ++ final Projectile.Delayed delayed = Projectile.spawnProjectileDelayed(new FireworkRocketEntity(world, itemStack, user), serverLevel, itemStack, f -> f.spawningEntity = user.getUUID()); // Paper - firework api - assign spawning entity uuid ++ com.destroystokyo.paper.event.player.PlayerElytraBoostEvent event = new com.destroystokyo.paper.event.player.PlayerElytraBoostEvent((org.bukkit.entity.Player) user.getBukkitEntity(), org.bukkit.craftbukkit.inventory.CraftItemStack.asCraftMirror(itemStack), (org.bukkit.entity.Firework) delayed.projectile().getBukkitEntity(), org.bukkit.craftbukkit.CraftEquipmentSlot.getHand(hand)); ++ if (event.callEvent() && delayed.attemptSpawn()) { ++ user.awardStat(Stats.ITEM_USED.get(this)); // Moved up from below ++ if (event.shouldConsume() && !user.hasInfiniteMaterials()) { ++ itemStack.shrink(1); // Moved up from below ++ } else ((net.minecraft.server.level.ServerPlayer) user).getBukkitEntity().updateInventory(); ++ } else { ++ ((net.minecraft.server.level.ServerPlayer) user).getBukkitEntity().updateInventory(); ++ } ++ // Moved up consume/stat ++ // Paper end - PlayerElytraBoostEvent + } + + return InteractionResult.SUCCESS; diff --git a/patches/server/0212-PlayerElytraBoostEvent.patch b/patches/server/0212-PlayerElytraBoostEvent.patch deleted file mode 100644 index 0581d2cd0a3b..000000000000 --- a/patches/server/0212-PlayerElytraBoostEvent.patch +++ /dev/null @@ -1,33 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: BillyGalbreath -Date: Sat, 21 Jul 2018 01:59:59 -0500 -Subject: [PATCH] PlayerElytraBoostEvent - - -diff --git a/src/main/java/net/minecraft/world/item/FireworkRocketItem.java b/src/main/java/net/minecraft/world/item/FireworkRocketItem.java -index 38b33eb92d21d0099285a304c6e064bbf56db4eb..16da60b7ecf2e0d4a49804dfa1ad47d4ae672571 100644 ---- a/src/main/java/net/minecraft/world/item/FireworkRocketItem.java -+++ b/src/main/java/net/minecraft/world/item/FireworkRocketItem.java -@@ -57,9 +57,19 @@ public class FireworkRocketItem extends Item implements ProjectileItem { - if (!world.isClientSide) { - FireworkRocketEntity fireworkRocketEntity = new FireworkRocketEntity(world, itemStack, user); - fireworkRocketEntity.spawningEntity = user.getUUID(); // Paper -- world.addFreshEntity(fireworkRocketEntity); -- itemStack.consume(1, user); -- user.awardStat(Stats.ITEM_USED.get(this)); -+ // Paper start - PlayerElytraBoostEvent -+ com.destroystokyo.paper.event.player.PlayerElytraBoostEvent event = new com.destroystokyo.paper.event.player.PlayerElytraBoostEvent((org.bukkit.entity.Player) user.getBukkitEntity(), org.bukkit.craftbukkit.inventory.CraftItemStack.asCraftMirror(itemStack), (org.bukkit.entity.Firework) fireworkRocketEntity.getBukkitEntity(), org.bukkit.craftbukkit.CraftEquipmentSlot.getHand(hand)); -+ if (event.callEvent() && world.addFreshEntity(fireworkRocketEntity)) { -+ user.awardStat(Stats.ITEM_USED.get(this)); -+ if (event.shouldConsume() && !user.hasInfiniteMaterials()) { -+ itemStack.shrink(1); -+ } else ((net.minecraft.server.level.ServerPlayer) user).getBukkitEntity().updateInventory(); -+ } else if (user instanceof net.minecraft.server.level.ServerPlayer) { -+ ((net.minecraft.server.level.ServerPlayer) user).getBukkitEntity().updateInventory(); -+ // Paper end - PlayerElytraBoostEvent -+ } -+ -+ // user.awardStat(Stats.ITEM_USED.get(this)); // Paper - PlayerElytraBoostEvent; move up - } - - return InteractionResultHolder.sidedSuccess(user.getItemInHand(hand), world.isClientSide()); diff --git a/patches/server/0212-PlayerLaunchProjectileEvent.patch b/patches/server/0212-PlayerLaunchProjectileEvent.patch new file mode 100644 index 000000000000..b41463a06a15 --- /dev/null +++ b/patches/server/0212-PlayerLaunchProjectileEvent.patch @@ -0,0 +1,377 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: BillyGalbreath +Date: Sat, 21 Jul 2018 03:11:03 -0500 +Subject: [PATCH] PlayerLaunchProjectileEvent + + +diff --git a/src/main/java/net/minecraft/world/entity/projectile/Projectile.java b/src/main/java/net/minecraft/world/entity/projectile/Projectile.java +index f8ae12a3540c4a7aa7e2c1656cbd866e33e11f57..d6dfda0580d919eb8a000113e7f9dd9794117b45 100644 +--- a/src/main/java/net/minecraft/world/entity/projectile/Projectile.java ++++ b/src/main/java/net/minecraft/world/entity/projectile/Projectile.java +@@ -197,7 +197,12 @@ public abstract class Projectile extends Entity implements TraceableEntity { + } + + public static T spawnProjectileFromRotation(Projectile.ProjectileFactory creator, ServerLevel world, ItemStack projectileStack, LivingEntity shooter, float roll, float power, float divergence) { +- return Projectile.spawnProjectile(creator.create(world, shooter, projectileStack), world, projectileStack, (iprojectile) -> { ++ // Paper start - PlayerLaunchProjectileEvent ++ return spawnProjectileFromRotationDelayed(creator, world, projectileStack, shooter, roll, power, divergence).spawn(); ++ } ++ public static Delayed spawnProjectileFromRotationDelayed(Projectile.ProjectileFactory creator, ServerLevel world, ItemStack projectileStack, LivingEntity shooter, float roll, float power, float divergence) { ++ return Projectile.spawnProjectileDelayed(creator.create(world, shooter, projectileStack), world, projectileStack, (iprojectile) -> { ++ // Paper end - PlayerLaunchProjectileEvent + iprojectile.shootFromRotation(shooter, shooter.getXRot(), shooter.getYRot(), roll, power, divergence); + }); + } +diff --git a/src/main/java/net/minecraft/world/item/EggItem.java b/src/main/java/net/minecraft/world/item/EggItem.java +index 8ddc1fa76244265616309322170c3a1b821c5092..3ddd34e5d05fa1355a2affd329d72dea216cd0e4 100644 +--- a/src/main/java/net/minecraft/world/item/EggItem.java ++++ b/src/main/java/net/minecraft/world/item/EggItem.java +@@ -26,7 +26,19 @@ public class EggItem extends Item implements ProjectileItem { + // world.playSound((EntityHuman) null, entityhuman.getX(), entityhuman.getY(), entityhuman.getZ(), SoundEffects.EGG_THROW, SoundCategory.PLAYERS, 0.5F, 0.4F / (world.getRandom().nextFloat() * 0.4F + 0.8F)); // CraftBukkit - moved down + if (world instanceof ServerLevel worldserver) { + // CraftBukkit start +- if (Projectile.spawnProjectileFromRotation(ThrownEgg::new, worldserver, itemstack, user, 0.0F, 1.5F, 1.0F).isRemoved()) { ++ // Paper start - PlayerLaunchProjectileEvent ++ final Projectile.Delayed thrownEgg = Projectile.spawnProjectileFromRotationDelayed(ThrownEgg::new, worldserver, itemstack, user, 0.0F, 1.5F, 1.0F); ++ com.destroystokyo.paper.event.player.PlayerLaunchProjectileEvent event = new com.destroystokyo.paper.event.player.PlayerLaunchProjectileEvent((org.bukkit.entity.Player) user.getBukkitEntity(), org.bukkit.craftbukkit.inventory.CraftItemStack.asCraftMirror(itemstack), (org.bukkit.entity.Projectile) thrownEgg.projectile().getBukkitEntity()); ++ if (event.callEvent() && thrownEgg.attemptSpawn()) { ++ if (event.shouldConsume()) { ++ itemstack.consume(1, user); ++ } else if (user instanceof net.minecraft.server.level.ServerPlayer) { ++ ((net.minecraft.server.level.ServerPlayer) user).getBukkitEntity().updateInventory(); ++ } ++ ++ world.playSound((Player) null, user.getX(), user.getY(), user.getZ(), SoundEvents.EGG_THROW, SoundSource.PLAYERS, 0.5F, 0.4F / (world.getRandom().nextFloat() * 0.4F + 0.8F)); ++ user.awardStat(Stats.ITEM_USED.get(this)); ++ } else { + if (user instanceof net.minecraft.server.level.ServerPlayer) { + ((net.minecraft.server.level.ServerPlayer) user).getBukkitEntity().updateInventory(); + } +@@ -35,9 +47,7 @@ public class EggItem extends Item implements ProjectileItem { + // CraftBukkit end + } + world.playSound((Player) null, user.getX(), user.getY(), user.getZ(), SoundEvents.EGG_THROW, SoundSource.PLAYERS, 0.5F, 0.4F / (world.getRandom().nextFloat() * 0.4F + 0.8F)); +- +- user.awardStat(Stats.ITEM_USED.get(this)); +- itemstack.consume(1, user); ++ // Paper - PlayerLaunchProjectileEvent - moved up + return InteractionResult.SUCCESS; + } + +diff --git a/src/main/java/net/minecraft/world/item/EnderpearlItem.java b/src/main/java/net/minecraft/world/item/EnderpearlItem.java +index b9e24451a8f87479377e3324b3a956c0efc3e53c..b232390d8ee8e449e61c0ea7f3af60df507abb97 100644 +--- a/src/main/java/net/minecraft/world/item/EnderpearlItem.java ++++ b/src/main/java/net/minecraft/world/item/EnderpearlItem.java +@@ -23,7 +23,20 @@ public class EnderpearlItem extends Item { + + if (world instanceof ServerLevel worldserver) { + // CraftBukkit start +- if (Projectile.spawnProjectileFromRotation(ThrownEnderpearl::new, worldserver, itemstack, user, 0.0F, 1.5F, 1.0F).isRemoved()) { ++ // Paper start - PlayerLaunchProjectileEvent ++ final Projectile.Delayed thrownEnderpearl = Projectile.spawnProjectileFromRotationDelayed(ThrownEnderpearl::new, worldserver, itemstack, user, 0.0F, 1.5F, 1.0F); ++ com.destroystokyo.paper.event.player.PlayerLaunchProjectileEvent event = new com.destroystokyo.paper.event.player.PlayerLaunchProjectileEvent((org.bukkit.entity.Player) user.getBukkitEntity(), org.bukkit.craftbukkit.inventory.CraftItemStack.asCraftMirror(itemstack), (org.bukkit.entity.Projectile) thrownEnderpearl.projectile().getBukkitEntity()); ++ if (event.callEvent() && thrownEnderpearl.attemptSpawn()) { ++ if (event.shouldConsume()) { ++ itemstack.consume(1, user); ++ } else if (user instanceof net.minecraft.server.level.ServerPlayer) { ++ ((net.minecraft.server.level.ServerPlayer) user).getBukkitEntity().updateInventory(); ++ } ++ ++ world.playSound((Player) null, user.getX(), user.getY(), user.getZ(), SoundEvents.ENDER_PEARL_THROW, SoundSource.NEUTRAL, 0.5F, 0.4F / (world.getRandom().nextFloat() * 0.4F + 0.8F)); ++ user.awardStat(Stats.ITEM_USED.get(this)); ++ } else { ++ // Paper end - PlayerLaunchProjectileEvent + if (user instanceof net.minecraft.server.level.ServerPlayer) { + ((net.minecraft.server.level.ServerPlayer) user).getBukkitEntity().updateInventory(); + } +@@ -33,8 +46,7 @@ public class EnderpearlItem extends Item { + world.playSound((Player) null, user.getX(), user.getY(), user.getZ(), SoundEvents.ENDER_PEARL_THROW, SoundSource.NEUTRAL, 0.5F, 0.4F / (world.getRandom().nextFloat() * 0.4F + 0.8F)); + // CraftBukkit end + +- user.awardStat(Stats.ITEM_USED.get(this)); +- itemstack.consume(1, user); ++ // Paper - PlayerLaunchProjectileEvent - moved up + return InteractionResult.SUCCESS; + } + } +diff --git a/src/main/java/net/minecraft/world/item/ExperienceBottleItem.java b/src/main/java/net/minecraft/world/item/ExperienceBottleItem.java +index b255e40277585928a767e5efd4b61708e13dab50..dfa3f9534159400e539ee61debedffb5f978aae8 100644 +--- a/src/main/java/net/minecraft/world/item/ExperienceBottleItem.java ++++ b/src/main/java/net/minecraft/world/item/ExperienceBottleItem.java +@@ -21,22 +21,38 @@ public class ExperienceBottleItem extends Item implements ProjectileItem { + @Override + public InteractionResult use(Level world, Player user, InteractionHand hand) { + ItemStack itemStack = user.getItemInHand(hand); +- world.playSound( +- null, +- user.getX(), +- user.getY(), +- user.getZ(), +- SoundEvents.EXPERIENCE_BOTTLE_THROW, +- SoundSource.NEUTRAL, +- 0.5F, +- 0.4F / (world.getRandom().nextFloat() * 0.4F + 0.8F) +- ); ++ // Paper - PlayerLaunchProjectileEvent - moved down + if (world instanceof ServerLevel serverLevel) { +- Projectile.spawnProjectileFromRotation(ThrownExperienceBottle::new, serverLevel, itemStack, user, -20.0F, 0.7F, 1.0F); ++ // Paper start - PlayerLaunchProjectileEvent ++ final Projectile.Delayed thrownExperienceBottle = Projectile.spawnProjectileFromRotationDelayed(ThrownExperienceBottle::new, serverLevel, itemStack, user, -20.0F, 0.7F, 1.0F); ++ com.destroystokyo.paper.event.player.PlayerLaunchProjectileEvent event = new com.destroystokyo.paper.event.player.PlayerLaunchProjectileEvent((org.bukkit.entity.Player) user.getBukkitEntity(), org.bukkit.craftbukkit.inventory.CraftItemStack.asCraftMirror(itemStack), (org.bukkit.entity.Projectile) thrownExperienceBottle.projectile().getBukkitEntity()); ++ if (event.callEvent() && thrownExperienceBottle.attemptSpawn()) { ++ if (event.shouldConsume()) { ++ itemStack.consume(1, user); ++ } else if (user instanceof net.minecraft.server.level.ServerPlayer) { ++ ((net.minecraft.server.level.ServerPlayer) user).getBukkitEntity().updateInventory(); ++ } ++ ++ world.playSound( ++ null, ++ user.getX(), ++ user.getY(), ++ user.getZ(), ++ SoundEvents.EXPERIENCE_BOTTLE_THROW, ++ SoundSource.NEUTRAL, ++ 0.5F, ++ 0.4F / (world.getRandom().nextFloat() * 0.4F + 0.8F) ++ ); ++ } else { ++ if (user instanceof net.minecraft.server.level.ServerPlayer) { ++ ((net.minecraft.server.level.ServerPlayer) user).getBukkitEntity().updateInventory(); ++ } ++ return InteractionResult.FAIL; ++ } ++ // Paper end - PlayerLaunchProjectileEvent + } + +- user.awardStat(Stats.ITEM_USED.get(this)); +- itemStack.consume(1, user); ++ // Paper - PlayerLaunchProjectileEvent - moved up + return InteractionResult.SUCCESS; + } + +diff --git a/src/main/java/net/minecraft/world/item/FireworkRocketItem.java b/src/main/java/net/minecraft/world/item/FireworkRocketItem.java +index 400ad0fa1d07c8b120e3c3b5488dfa315aa2d23f..7e308b364227dedc2d05496f5e0c90573f4a53f7 100644 +--- a/src/main/java/net/minecraft/world/item/FireworkRocketItem.java ++++ b/src/main/java/net/minecraft/world/item/FireworkRocketItem.java +@@ -33,7 +33,7 @@ public class FireworkRocketItem extends Item implements ProjectileItem { + ItemStack itemStack = context.getItemInHand(); + Vec3 vec3 = context.getClickLocation(); + Direction direction = context.getClickedFace(); +- Projectile.spawnProjectile( ++ final Projectile.Delayed fireworkRocketEntity = Projectile.spawnProjectileDelayed( // Paper - PlayerLaunchProjectileEvent + new FireworkRocketEntity( + level, + context.getPlayer(), +@@ -45,7 +45,12 @@ public class FireworkRocketItem extends Item implements ProjectileItem { + serverLevel, + itemStack, f -> f.spawningEntity = context.getPlayer() == null ? null : context.getPlayer().getUUID() // Paper - firework api - assign spawning entity uuid + ); +- itemStack.shrink(1); ++ // Paper start - PlayerLaunchProjectileEvent ++ com.destroystokyo.paper.event.player.PlayerLaunchProjectileEvent event = new com.destroystokyo.paper.event.player.PlayerLaunchProjectileEvent((org.bukkit.entity.Player) context.getPlayer().getBukkitEntity(), org.bukkit.craftbukkit.inventory.CraftItemStack.asCraftMirror(itemStack), (org.bukkit.entity.Firework) fireworkRocketEntity.projectile().getBukkitEntity()); ++ if (!event.callEvent() || fireworkRocketEntity.attemptSpawn()) return InteractionResult.PASS; ++ if (event.shouldConsume() && !context.getPlayer().hasInfiniteMaterials()) itemStack.shrink(1); ++ else if (context.getPlayer() instanceof net.minecraft.server.level.ServerPlayer) ((net.minecraft.server.level.ServerPlayer) context.getPlayer()).getBukkitEntity().updateInventory(); ++ // Paper end - PlayerLaunchProjectileEvent + } + + return InteractionResult.SUCCESS; +diff --git a/src/main/java/net/minecraft/world/item/LingeringPotionItem.java b/src/main/java/net/minecraft/world/item/LingeringPotionItem.java +index 7a5045469bc2d383ed087dcc094b6f97df4ec7ab..fa92a1346825f00a585503d0a0825711fe3f754e 100644 +--- a/src/main/java/net/minecraft/world/item/LingeringPotionItem.java ++++ b/src/main/java/net/minecraft/world/item/LingeringPotionItem.java +@@ -24,6 +24,10 @@ public class LingeringPotionItem extends ThrowablePotionItem { + + @Override + public InteractionResult use(Level world, Player user, InteractionHand hand) { ++ // Paper start - PlayerLaunchProjectileEvent ++ final InteractionResult wrapper = super.use(world, user, hand); ++ if (wrapper instanceof InteractionResult.Fail) return wrapper; ++ // Paper end - PlayerLaunchProjectileEvent + world.playSound( + null, + user.getX(), +@@ -34,6 +38,6 @@ public class LingeringPotionItem extends ThrowablePotionItem { + 0.5F, + 0.4F / (world.getRandom().nextFloat() * 0.4F + 0.8F) + ); +- return super.use(world, user, hand); ++ return wrapper; // Paper - PlayerLaunchProjectileEvent + } + } +diff --git a/src/main/java/net/minecraft/world/item/SnowballItem.java b/src/main/java/net/minecraft/world/item/SnowballItem.java +index ada9bc42a788b5f472324a0765edf5766d729784..57872ebef6beb8cdc03c9f8f19de94652ee19062 100644 +--- a/src/main/java/net/minecraft/world/item/SnowballItem.java ++++ b/src/main/java/net/minecraft/world/item/SnowballItem.java +@@ -26,17 +26,26 @@ public class SnowballItem extends Item implements ProjectileItem { + // CraftBukkit start - moved down + // world.playSound((EntityHuman) null, entityhuman.getX(), entityhuman.getY(), entityhuman.getZ(), SoundEffects.SNOWBALL_THROW, SoundCategory.NEUTRAL, 0.5F, 0.4F / (world.getRandom().nextFloat() * 0.4F + 0.8F)); + if (world instanceof ServerLevel worldserver) { +- if (Projectile.spawnProjectileFromRotation(Snowball::new, worldserver, itemstack, user, 0.0F, 1.5F, 1.0F).isAlive()) { +- itemstack.consume(1, user); ++ // Paper start - PlayerLaunchProjectileEvent ++ final Projectile.Delayed snowball = Projectile.spawnProjectileFromRotationDelayed(Snowball::new, worldserver, itemstack, user, 0.0F, 1.5F, 1.0F); ++ com.destroystokyo.paper.event.player.PlayerLaunchProjectileEvent event = new com.destroystokyo.paper.event.player.PlayerLaunchProjectileEvent((org.bukkit.entity.Player) user.getBukkitEntity(), org.bukkit.craftbukkit.inventory.CraftItemStack.asCraftMirror(itemstack), (org.bukkit.entity.Projectile) snowball.projectile().getBukkitEntity()); ++ if (event.callEvent() && snowball.attemptSpawn()) { ++ user.awardStat(Stats.ITEM_USED.get(this)); ++ if (event.shouldConsume()) { ++ itemstack.consume(1, user); ++ } else if (user instanceof net.minecraft.server.level.ServerPlayer) { ++ ((net.minecraft.server.level.ServerPlayer) user).getBukkitEntity().updateInventory(); ++ } ++ // Paper end - PlayerLaunchProjectileEvent + + world.playSound((Player) null, user.getX(), user.getY(), user.getZ(), SoundEvents.SNOWBALL_THROW, SoundSource.NEUTRAL, 0.5F, 0.4F / (world.getRandom().nextFloat() * 0.4F + 0.8F)); +- } else if (user instanceof net.minecraft.server.level.ServerPlayer) { ++ } else { if (user instanceof net.minecraft.server.level.ServerPlayer) { // Paper - PlayerLaunchProjectileEvent - return fail + ((net.minecraft.server.level.ServerPlayer) user).getBukkitEntity().updateInventory(); +- } ++ } return InteractionResult.FAIL; } // Paper - PlayerLaunchProjectileEvent - return fail + // CraftBukkit end + } + +- user.awardStat(Stats.ITEM_USED.get(this)); ++ // Paper - PlayerLaunchProjectileEvent - moved up + // itemstack.consume(1, entityhuman); // CraftBukkit - moved up + return InteractionResult.SUCCESS; + } +diff --git a/src/main/java/net/minecraft/world/item/SplashPotionItem.java b/src/main/java/net/minecraft/world/item/SplashPotionItem.java +index 6d622d276a632c9c29ab04a01fbe7adbf10c35cd..577e66a7ff64d9569ee2402ecc26b0aa1105fe9a 100644 +--- a/src/main/java/net/minecraft/world/item/SplashPotionItem.java ++++ b/src/main/java/net/minecraft/world/item/SplashPotionItem.java +@@ -14,6 +14,10 @@ public class SplashPotionItem extends ThrowablePotionItem { + + @Override + public InteractionResult use(Level world, Player user, InteractionHand hand) { ++ // Paper start - PlayerLaunchProjectileEvent ++ final InteractionResult wrapper = super.use(world, user, hand); ++ if (wrapper instanceof InteractionResult.Fail) return wrapper; ++ // Paper end - PlayerLaunchProjectileEvent + world.playSound( + null, + user.getX(), +@@ -24,6 +28,6 @@ public class SplashPotionItem extends ThrowablePotionItem { + 0.5F, + 0.4F / (world.getRandom().nextFloat() * 0.4F + 0.8F) + ); +- return super.use(world, user, hand); ++ return wrapper; // Paper - PlayerLaunchProjectileEvent + } + } +diff --git a/src/main/java/net/minecraft/world/item/ThrowablePotionItem.java b/src/main/java/net/minecraft/world/item/ThrowablePotionItem.java +index e3eb733f18c23ae3fcc4c271843d285b217d1a7d..fa9d2ae44fcdd06f8f33cd14ffca422b20a01451 100644 +--- a/src/main/java/net/minecraft/world/item/ThrowablePotionItem.java ++++ b/src/main/java/net/minecraft/world/item/ThrowablePotionItem.java +@@ -20,11 +20,28 @@ public class ThrowablePotionItem extends PotionItem implements ProjectileItem { + public InteractionResult use(Level world, Player user, InteractionHand hand) { + ItemStack itemStack = user.getItemInHand(hand); + if (world instanceof ServerLevel serverLevel) { +- Projectile.spawnProjectileFromRotation(ThrownPotion::new, serverLevel, itemStack, user, -20.0F, 0.5F, 1.0F); ++ // Paper start - PlayerLaunchProjectileEvent ++ final Projectile.Delayed thrownPotion = Projectile.spawnProjectileFromRotationDelayed(ThrownPotion::new, serverLevel, itemStack, user, -20.0F, 0.5F, 1.0F); ++ // Paper start - PlayerLaunchProjectileEvent ++ com.destroystokyo.paper.event.player.PlayerLaunchProjectileEvent event = new com.destroystokyo.paper.event.player.PlayerLaunchProjectileEvent((org.bukkit.entity.Player) user.getBukkitEntity(), org.bukkit.craftbukkit.inventory.CraftItemStack.asCraftMirror(itemStack), (org.bukkit.entity.Projectile) thrownPotion.projectile().getBukkitEntity()); ++ if (event.callEvent() && thrownPotion.attemptSpawn()) { ++ if (event.shouldConsume()) { ++ itemStack.consume(1, user); ++ } else if (user instanceof net.minecraft.server.level.ServerPlayer) { ++ ((net.minecraft.server.level.ServerPlayer) user).getBukkitEntity().updateInventory(); ++ } ++ ++ user.awardStat(Stats.ITEM_USED.get(this)); ++ } else { ++ if (user instanceof net.minecraft.server.level.ServerPlayer) { ++ ((net.minecraft.server.level.ServerPlayer) user).getBukkitEntity().updateInventory(); ++ } ++ return InteractionResult.FAIL; ++ } ++ // Paper end - PlayerLaunchProjectileEvent + } + +- user.awardStat(Stats.ITEM_USED.get(this)); +- itemStack.consume(1, user); ++ // Paper - PlayerLaunchProjectileEvent - move up + return InteractionResult.SUCCESS; + } + +diff --git a/src/main/java/net/minecraft/world/item/TridentItem.java b/src/main/java/net/minecraft/world/item/TridentItem.java +index 22aa715c4ef615c2c9d795cc2e11b7099d5167da..8b9a93ef71164cce8a616735b71d96d37e83b1a8 100644 +--- a/src/main/java/net/minecraft/world/item/TridentItem.java ++++ b/src/main/java/net/minecraft/world/item/TridentItem.java +@@ -87,21 +87,26 @@ public class TridentItem extends Item implements ProjectileItem { + + // itemstack.hurtWithoutBreaking(1, entityhuman); // CraftBukkit - moved down + if (f == 0.0F) { +- ThrownTrident entitythrowntrident = (ThrownTrident) Projectile.spawnProjectileFromRotation(ThrownTrident::new, worldserver, stack, entityhuman, 0.0F, 2.5F, 1.0F); +- // CraftBukkit start +- if (entitythrowntrident.isRemoved()) { ++ // Paper start - PlayerLaunchProjectileEvent ++ Projectile.Delayed tridentDelayed = Projectile.spawnProjectileFromRotationDelayed(ThrownTrident::new, worldserver, stack, entityhuman, 0.0F, 2.5F, 1.0F); ++ // Paper start - PlayerLaunchProjectileEvent ++ com.destroystokyo.paper.event.player.PlayerLaunchProjectileEvent event = new com.destroystokyo.paper.event.player.PlayerLaunchProjectileEvent((org.bukkit.entity.Player) entityhuman.getBukkitEntity(), org.bukkit.craftbukkit.inventory.CraftItemStack.asCraftMirror(stack), (org.bukkit.entity.Projectile) tridentDelayed.projectile().getBukkitEntity()); ++ if (!event.callEvent() || !tridentDelayed.attemptSpawn()) { ++ // CraftBukkit start ++ // Paper end - PlayerLaunchProjectileEvent + if (entityhuman instanceof net.minecraft.server.level.ServerPlayer) { + ((net.minecraft.server.level.ServerPlayer) entityhuman).getBukkitEntity().updateInventory(); + } + return false; + } +- stack.hurtWithoutBreaking(1, entityhuman); ++ ThrownTrident entitythrowntrident = tridentDelayed.projectile(); // Paper - PlayerLaunchProjectileEvent ++ if (event.shouldConsume()) stack.hurtWithoutBreaking(1, entityhuman); // Paper - PlayerLaunchProjectileEvent + entitythrowntrident.pickupItemStack = stack.copy(); // SPIGOT-4511 update since damage call moved + // CraftBukkit end + + if (entityhuman.hasInfiniteMaterials()) { + entitythrowntrident.pickup = AbstractArrow.Pickup.CREATIVE_ONLY; +- } else { ++ } else if (event.shouldConsume()) { // Paper - PlayerLaunchProjectileEvent + entityhuman.getInventory().removeItem(stack); + } + +diff --git a/src/main/java/net/minecraft/world/item/WindChargeItem.java b/src/main/java/net/minecraft/world/item/WindChargeItem.java +index 79fdb78a4c3c7e324f0ed84a3dad9a3a80a6456a..0ce52d239b5bfb63e09cf5df679eee177f7bc5b0 100644 +--- a/src/main/java/net/minecraft/world/item/WindChargeItem.java ++++ b/src/main/java/net/minecraft/world/item/WindChargeItem.java +@@ -25,7 +25,7 @@ public class WindChargeItem extends Item implements ProjectileItem { + public InteractionResult use(Level world, Player user, InteractionHand hand) { + ItemStack itemStack = user.getItemInHand(hand); + if (world instanceof ServerLevel serverLevel) { +- Projectile.spawnProjectileFromRotation( ++ final Projectile.Delayed windCharge = Projectile.spawnProjectileFromRotationDelayed( // Paper - PlayerLaunchProjectileEvent + (world2, shooter, stack) -> new WindCharge(user, world, user.position().x(), user.getEyePosition().y(), user.position().z()), + serverLevel, + itemStack, +@@ -34,6 +34,21 @@ public class WindChargeItem extends Item implements ProjectileItem { + 1.5F, + 1.0F + ); ++ com.destroystokyo.paper.event.player.PlayerLaunchProjectileEvent event = new com.destroystokyo.paper.event.player.PlayerLaunchProjectileEvent((org.bukkit.entity.Player) user.getBukkitEntity(), org.bukkit.craftbukkit.inventory.CraftItemStack.asCraftMirror(itemStack), (org.bukkit.entity.Projectile) windCharge.projectile().getBukkitEntity()); ++ if (!event.callEvent() || !windCharge.attemptSpawn()) { ++ user.containerMenu.sendAllDataToRemote(); ++ if (user instanceof net.minecraft.server.level.ServerPlayer player) { ++ player.connection.send(new net.minecraft.network.protocol.game.ClientboundCooldownPacket(user.getCooldowns().getCooldownGroup(itemStack), 0)); // prevent visual desync of cooldown on the slot ++ } ++ return InteractionResult.FAIL; ++ } ++ ++ user.awardStat(Stats.ITEM_USED.get(this)); ++ if (event.shouldConsume()) itemStack.consume(1, user); ++ else if (!user.hasInfiniteMaterials()) { ++ user.containerMenu.sendAllDataToRemote(); ++ } ++ // Paper end - PlayerLaunchProjectileEvent + } + + world.playSound( +@@ -46,8 +61,6 @@ public class WindChargeItem extends Item implements ProjectileItem { + 0.5F, + 0.4F / (world.getRandom().nextFloat() * 0.4F + 0.8F) + ); +- user.awardStat(Stats.ITEM_USED.get(this)); +- itemStack.consume(1, user); + return InteractionResult.SUCCESS; + } + diff --git a/patches/server/0213-Improve-BlockPosition-inlining.patch b/patches/server/0213-Improve-BlockPosition-inlining.patch new file mode 100644 index 000000000000..e1e44f61f0c1 --- /dev/null +++ b/patches/server/0213-Improve-BlockPosition-inlining.patch @@ -0,0 +1,60 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Techcable +Date: Wed, 30 Nov 2016 20:56:58 -0600 +Subject: [PATCH] Improve BlockPosition inlining + +Normally the JVM can inline virtual getters by having two sets of code, one is the 'optimized' code and the other is the 'deoptimized' code. +If a single type is used 99% of the time, then its worth it to inline, and to revert to 'deoptimized' the 1% of the time we encounter other types. +But if two types are encountered commonly, then the JVM can't inline them both, and the call overhead remains. + +This scenario also occurs with BlockPos and MutableBlockPos. +The variables in BlockPos are final, so MutableBlockPos can't modify them. +MutableBlockPos fixes this by adding custom mutable variables, and overriding the getters to access them. + +This approach with utility methods that operate on MutableBlockPos and BlockPos. +Specific examples are BlockPosition.up(), and World.isValidLocation(). +It makes these simple methods much slower than they need to be. + +This should result in an across the board speedup in anything that accesses blocks or does logic with positions. + +This is based upon conclusions drawn from inspecting the assenmbly generated bythe JIT compiler on my microbenchmarks. +They had 'callq' (invoke) instead of 'mov' (get from memory) instructions. + +diff --git a/src/main/java/net/minecraft/core/Vec3i.java b/src/main/java/net/minecraft/core/Vec3i.java +index 671550477476a7252a52686aa60fe6454eda2055..7d5f99cac756c54e5922bf85d5d359edcc21f1e8 100644 +--- a/src/main/java/net/minecraft/core/Vec3i.java ++++ b/src/main/java/net/minecraft/core/Vec3i.java +@@ -35,12 +35,12 @@ public class Vec3i implements Comparable { + } + + @Override +- public boolean equals(Object object) { ++ public final boolean equals(Object object) { // Paper - Perf: Final for inline + return this == object || object instanceof Vec3i vec3i && this.getX() == vec3i.getX() && this.getY() == vec3i.getY() && this.getZ() == vec3i.getZ(); + } + + @Override +- public int hashCode() { ++ public final int hashCode() { // Paper - Perf: Final for inline + return (this.getY() + this.getZ() * 31) * 31 + this.getX(); + } + +@@ -53,15 +53,15 @@ public class Vec3i implements Comparable { + } + } + +- public int getX() { ++ public final int getX() { // Paper - Perf: Final for inline + return this.x; + } + +- public int getY() { ++ public final int getY() { // Paper - Perf: Final for inline + return this.y; + } + +- public int getZ() { ++ public final int getZ() { // Paper - Perf: Final for inline + return this.z; + } + diff --git a/patches/server/0213-PlayerLaunchProjectileEvent.patch b/patches/server/0213-PlayerLaunchProjectileEvent.patch deleted file mode 100644 index be34c111d31a..000000000000 --- a/patches/server/0213-PlayerLaunchProjectileEvent.patch +++ /dev/null @@ -1,363 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: BillyGalbreath -Date: Sat, 21 Jul 2018 03:11:03 -0500 -Subject: [PATCH] PlayerLaunchProjectileEvent - - -diff --git a/src/main/java/net/minecraft/world/item/EggItem.java b/src/main/java/net/minecraft/world/item/EggItem.java -index be7d1bfb13cf0afb80ba98f3b56602bccebeab64..4ebd634cff286b10868e26eeb3ecf34abdcab22e 100644 ---- a/src/main/java/net/minecraft/world/item/EggItem.java -+++ b/src/main/java/net/minecraft/world/item/EggItem.java -@@ -28,19 +28,31 @@ public class EggItem extends Item implements ProjectileItem { - - entityegg.setItem(itemstack); - entityegg.shootFromRotation(user, user.getXRot(), user.getYRot(), 0.0F, 1.5F, 1.0F); -- // CraftBukkit start -- if (!world.addFreshEntity(entityegg)) { -+ // Paper start - PlayerLaunchProjectileEvent -+ com.destroystokyo.paper.event.player.PlayerLaunchProjectileEvent event = new com.destroystokyo.paper.event.player.PlayerLaunchProjectileEvent((org.bukkit.entity.Player) user.getBukkitEntity(), org.bukkit.craftbukkit.inventory.CraftItemStack.asCraftMirror(itemstack), (org.bukkit.entity.Projectile) entityegg.getBukkitEntity()); -+ if (event.callEvent() && world.addFreshEntity(entityegg)) { -+ if (event.shouldConsume()) { -+ itemstack.consume(1, user); -+ } else if (user instanceof net.minecraft.server.level.ServerPlayer) { -+ ((net.minecraft.server.level.ServerPlayer) user).getBukkitEntity().updateInventory(); -+ } -+ -+ world.playSound((Player) null, user.getX(), user.getY(), user.getZ(), net.minecraft.sounds.SoundEvents.EGG_THROW, net.minecraft.sounds.SoundSource.PLAYERS, 0.5F, 0.4F / (net.minecraft.world.entity.Entity.SHARED_RANDOM.nextFloat() * 0.4F + 0.8F)); -+ user.awardStat(Stats.ITEM_USED.get(this)); -+ } else { - if (user instanceof net.minecraft.server.level.ServerPlayer) { - ((net.minecraft.server.level.ServerPlayer) user).getBukkitEntity().updateInventory(); - } - return InteractionResultHolder.fail(itemstack); - } -- // CraftBukkit end -+ // Paper end - PlayerLaunchProjectileEvent - } - world.playSound((Player) null, user.getX(), user.getY(), user.getZ(), SoundEvents.EGG_THROW, SoundSource.PLAYERS, 0.5F, 0.4F / (world.getRandom().nextFloat() * 0.4F + 0.8F)); - -+ /* // Paper start - PlayerLaunchProjectileEvent; moved up - user.awardStat(Stats.ITEM_USED.get(this)); - itemstack.consume(1, user); -+ */ // Paper end - PlayerLaunchProjectileEvent - return InteractionResultHolder.sidedSuccess(itemstack, world.isClientSide()); - } - -diff --git a/src/main/java/net/minecraft/world/item/EnderpearlItem.java b/src/main/java/net/minecraft/world/item/EnderpearlItem.java -index 48048204b619dd515253c8ffb05a0f7105ef7718..20a91d798d31a71b3c05efa2cc5bda55494e26cc 100644 ---- a/src/main/java/net/minecraft/world/item/EnderpearlItem.java -+++ b/src/main/java/net/minecraft/world/item/EnderpearlItem.java -@@ -25,7 +25,20 @@ public class EnderpearlItem extends Item { - - entityenderpearl.setItem(itemstack); - entityenderpearl.shootFromRotation(user, user.getXRot(), user.getYRot(), 0.0F, 1.5F, 1.0F); -- if (!world.addFreshEntity(entityenderpearl)) { -+ // Paper start - PlayerLaunchProjectileEvent -+ com.destroystokyo.paper.event.player.PlayerLaunchProjectileEvent event = new com.destroystokyo.paper.event.player.PlayerLaunchProjectileEvent((org.bukkit.entity.Player) user.getBukkitEntity(), org.bukkit.craftbukkit.inventory.CraftItemStack.asCraftMirror(itemstack), (org.bukkit.entity.Projectile) entityenderpearl.getBukkitEntity()); -+ if (event.callEvent() && world.addFreshEntity(entityenderpearl)) { -+ if (event.shouldConsume()) { -+ itemstack.consume(1, user); -+ } else if (user instanceof net.minecraft.server.level.ServerPlayer) { -+ ((net.minecraft.server.level.ServerPlayer) user).getBukkitEntity().updateInventory(); -+ } -+ -+ world.playSound((Player) null, user.getX(), user.getY(), user.getZ(), SoundEvents.ENDER_PEARL_THROW, SoundSource.NEUTRAL, 0.5F, 0.4F / (net.minecraft.world.entity.Entity.SHARED_RANDOM.nextFloat() * 0.4F + 0.8F)); -+ user.awardStat(Stats.ITEM_USED.get(this)); -+ user.getCooldowns().addCooldown(this, 20); -+ } else { -+ // Paper end - PlayerLaunchProjectileEvent - if (user instanceof net.minecraft.server.level.ServerPlayer) { - ((net.minecraft.server.level.ServerPlayer) user).getBukkitEntity().updateInventory(); - } -@@ -33,12 +46,14 @@ public class EnderpearlItem extends Item { - } - } - -+ /* // Paper start - PlayerLaunchProjectileEvent; moved up - world.playSound((Player) null, user.getX(), user.getY(), user.getZ(), SoundEvents.ENDER_PEARL_THROW, SoundSource.NEUTRAL, 0.5F, 0.4F / (world.getRandom().nextFloat() * 0.4F + 0.8F)); - user.getCooldowns().addCooldown(this, 20); - // CraftBukkit end - - user.awardStat(Stats.ITEM_USED.get(this)); - itemstack.consume(1, user); -+ */ // Paper end - PlayerLaunchProjectileEvent; moved up - return InteractionResultHolder.sidedSuccess(itemstack, world.isClientSide()); - } - } -diff --git a/src/main/java/net/minecraft/world/item/ExperienceBottleItem.java b/src/main/java/net/minecraft/world/item/ExperienceBottleItem.java -index 686f0fd87e06fa7552d66fd3e38a3e059c22f180..7448309b6dca619b6366aa55fe3a395a830ffbdd 100644 ---- a/src/main/java/net/minecraft/world/item/ExperienceBottleItem.java -+++ b/src/main/java/net/minecraft/world/item/ExperienceBottleItem.java -@@ -20,25 +20,44 @@ public class ExperienceBottleItem extends Item implements ProjectileItem { - @Override - public InteractionResultHolder use(Level world, Player user, InteractionHand hand) { - ItemStack itemStack = user.getItemInHand(hand); -- world.playSound( -- null, -- user.getX(), -- user.getY(), -- user.getZ(), -- SoundEvents.EXPERIENCE_BOTTLE_THROW, -- SoundSource.NEUTRAL, -- 0.5F, -- 0.4F / (world.getRandom().nextFloat() * 0.4F + 0.8F) -- ); -+ // Paper - PlayerLaunchProjectileEvent; moved down - if (!world.isClientSide) { - ThrownExperienceBottle thrownExperienceBottle = new ThrownExperienceBottle(world, user); - thrownExperienceBottle.setItem(itemStack); - thrownExperienceBottle.shootFromRotation(user, user.getXRot(), user.getYRot(), -20.0F, 0.7F, 1.0F); -- world.addFreshEntity(thrownExperienceBottle); -+ // Paper start - PlayerLaunchProjectileEvent -+ com.destroystokyo.paper.event.player.PlayerLaunchProjectileEvent event = new com.destroystokyo.paper.event.player.PlayerLaunchProjectileEvent((org.bukkit.entity.Player) user.getBukkitEntity(), org.bukkit.craftbukkit.inventory.CraftItemStack.asCraftMirror(itemStack), (org.bukkit.entity.Projectile) thrownExperienceBottle.getBukkitEntity()); -+ if (event.callEvent() && world.addFreshEntity(thrownExperienceBottle)) { -+ if (event.shouldConsume()) { -+ itemStack.consume(1, user); -+ } else if (user instanceof net.minecraft.server.level.ServerPlayer) { -+ ((net.minecraft.server.level.ServerPlayer) user).getBukkitEntity().updateInventory(); -+ } -+ -+ world.playSound( -+ null, -+ user.getX(), -+ user.getY(), -+ user.getZ(), -+ SoundEvents.EXPERIENCE_BOTTLE_THROW, -+ SoundSource.NEUTRAL, -+ 0.5F, -+ 0.4F / (world.getRandom().nextFloat() * 0.4F + 0.8F) -+ ); -+ user.awardStat(Stats.ITEM_USED.get(this)); -+ } else { -+ if (user instanceof net.minecraft.server.level.ServerPlayer) { -+ ((net.minecraft.server.level.ServerPlayer) user).getBukkitEntity().updateInventory(); -+ } -+ return InteractionResultHolder.fail(itemStack); -+ } -+ // Paper end - PlayerLaunchProjectileEvent - } - -+ /* // Paper start - PlayerLaunchProjectileEvent; moved up - user.awardStat(Stats.ITEM_USED.get(this)); - itemStack.consume(1, user); -+ */ // Paper end - PlayerLaunchProjectileEvent - return InteractionResultHolder.sidedSuccess(itemStack, world.isClientSide()); - } - -diff --git a/src/main/java/net/minecraft/world/item/FireworkRocketItem.java b/src/main/java/net/minecraft/world/item/FireworkRocketItem.java -index 16da60b7ecf2e0d4a49804dfa1ad47d4ae672571..218f2f085309f04438f8b07bc41cf242583db2dc 100644 ---- a/src/main/java/net/minecraft/world/item/FireworkRocketItem.java -+++ b/src/main/java/net/minecraft/world/item/FireworkRocketItem.java -@@ -43,8 +43,12 @@ public class FireworkRocketItem extends Item implements ProjectileItem { - itemStack - ); - fireworkRocketEntity.spawningEntity = context.getPlayer() == null ? null : context.getPlayer().getUUID(); // Paper -- level.addFreshEntity(fireworkRocketEntity); -- itemStack.shrink(1); -+ // Paper start - PlayerLaunchProjectileEvent -+ com.destroystokyo.paper.event.player.PlayerLaunchProjectileEvent event = new com.destroystokyo.paper.event.player.PlayerLaunchProjectileEvent((org.bukkit.entity.Player) context.getPlayer().getBukkitEntity(), org.bukkit.craftbukkit.inventory.CraftItemStack.asCraftMirror(itemStack), (org.bukkit.entity.Firework) fireworkRocketEntity.getBukkitEntity()); -+ if (!event.callEvent() || !level.addFreshEntity(fireworkRocketEntity)) return InteractionResult.PASS; -+ if (event.shouldConsume() && !context.getPlayer().getAbilities().instabuild) itemStack.shrink(1); -+ else if (context.getPlayer() instanceof net.minecraft.server.level.ServerPlayer) ((net.minecraft.server.level.ServerPlayer) context.getPlayer()).getBukkitEntity().updateInventory(); -+ // Paper end - PlayerLaunchProjectileEvent - } - - return InteractionResult.sidedSuccess(level.isClientSide); -diff --git a/src/main/java/net/minecraft/world/item/LingeringPotionItem.java b/src/main/java/net/minecraft/world/item/LingeringPotionItem.java -index b94888d359ec92bf7ef77b0610f41ba75185a26e..c9b511cc4f2499ca13e3cd8f1cdabdc225f66b9c 100644 ---- a/src/main/java/net/minecraft/world/item/LingeringPotionItem.java -+++ b/src/main/java/net/minecraft/world/item/LingeringPotionItem.java -@@ -24,6 +24,10 @@ public class LingeringPotionItem extends ThrowablePotionItem { - - @Override - public InteractionResultHolder use(Level world, Player user, InteractionHand hand) { -+ // Paper start - PlayerLaunchProjectileEvent -+ InteractionResultHolder wrapper = super.use(world, user, hand); -+ if (wrapper.getResult() != net.minecraft.world.InteractionResult.FAIL) { -+ // Paper end - PlayerLaunchProjectileEvent - world.playSound( - null, - user.getX(), -@@ -34,6 +38,9 @@ public class LingeringPotionItem extends ThrowablePotionItem { - 0.5F, - 0.4F / (world.getRandom().nextFloat() * 0.4F + 0.8F) - ); -- return super.use(world, user, hand); -+ // Paper start - PlayerLaunchProjectileEvent -+ } -+ return wrapper; -+ // Paper end - PlayerLaunchProjectileEvent - } - } -diff --git a/src/main/java/net/minecraft/world/item/SnowballItem.java b/src/main/java/net/minecraft/world/item/SnowballItem.java -index 2ae40936b31012fab0c7c42a7dea13b01ec6068f..32b170551a2f5bdc88d29f4d03750bfe3974e71b 100644 ---- a/src/main/java/net/minecraft/world/item/SnowballItem.java -+++ b/src/main/java/net/minecraft/world/item/SnowballItem.java -@@ -29,17 +29,26 @@ public class SnowballItem extends Item implements ProjectileItem { - - entitysnowball.setItem(itemstack); - entitysnowball.shootFromRotation(user, user.getXRot(), user.getYRot(), 0.0F, 1.5F, 1.0F); -- if (world.addFreshEntity(entitysnowball)) { -- itemstack.consume(1, user); -+ // Paper start - PlayerLaunchProjectileEvent -+ com.destroystokyo.paper.event.player.PlayerLaunchProjectileEvent event = new com.destroystokyo.paper.event.player.PlayerLaunchProjectileEvent((org.bukkit.entity.Player) user.getBukkitEntity(), org.bukkit.craftbukkit.inventory.CraftItemStack.asCraftMirror(itemstack), (org.bukkit.entity.Projectile) entitysnowball.getBukkitEntity()); -+ if (event.callEvent() && world.addFreshEntity(entitysnowball)) { -+ user.awardStat(Stats.ITEM_USED.get(this)); -+ if (event.shouldConsume()) { -+ // Paper end - PlayerLaunchProjectileEvent -+ itemstack.consume(1, user); -+ } else if (user instanceof net.minecraft.server.level.ServerPlayer) { // Paper - PlayerLaunchProjectileEvent -+ ((net.minecraft.server.level.ServerPlayer) user).getBukkitEntity().updateInventory(); // Paper - PlayerLaunchProjectileEvent -+ } - - world.playSound((Player) null, user.getX(), user.getY(), user.getZ(), SoundEvents.SNOWBALL_THROW, SoundSource.NEUTRAL, 0.5F, 0.4F / (world.getRandom().nextFloat() * 0.4F + 0.8F)); -- } else if (user instanceof net.minecraft.server.level.ServerPlayer) { -- ((net.minecraft.server.level.ServerPlayer) user).getBukkitEntity().updateInventory(); -+ } else { // Paper - PlayerLaunchProjectileEvent -+ if (user instanceof net.minecraft.server.level.ServerPlayer) ((net.minecraft.server.level.ServerPlayer) user).getBukkitEntity().updateInventory(); // Paper - PlayerLaunchProjectileEvent -+ return InteractionResultHolder.fail(itemstack); // Paper - PlayerLaunchProjectileEvent - } - } - // CraftBukkit end - -- user.awardStat(Stats.ITEM_USED.get(this)); -+ // user.awardStat(Stats.ITEM_USED.get(this)); // Paper - PlayerLaunchProjectileEvent; moved up - // itemstack.consume(1, entityhuman); // CraftBukkit - moved up - return InteractionResultHolder.sidedSuccess(itemstack, world.isClientSide()); - } -diff --git a/src/main/java/net/minecraft/world/item/SplashPotionItem.java b/src/main/java/net/minecraft/world/item/SplashPotionItem.java -index 935c34ba7eb14348becdd3ac0c29766abf7ca614..73bac60b3bf6d20d415a8250d0426251c0c3265b 100644 ---- a/src/main/java/net/minecraft/world/item/SplashPotionItem.java -+++ b/src/main/java/net/minecraft/world/item/SplashPotionItem.java -@@ -14,6 +14,10 @@ public class SplashPotionItem extends ThrowablePotionItem { - - @Override - public InteractionResultHolder use(Level world, Player user, InteractionHand hand) { -+ // Paper start - PlayerLaunchProjectileEvent -+ InteractionResultHolder wrapper = super.use(world, user, hand); -+ if (wrapper.getResult() != net.minecraft.world.InteractionResult.FAIL) { -+ // Paper end - PlayerLaunchProjectileEvent - world.playSound( - null, - user.getX(), -@@ -24,6 +28,9 @@ public class SplashPotionItem extends ThrowablePotionItem { - 0.5F, - 0.4F / (world.getRandom().nextFloat() * 0.4F + 0.8F) - ); -- return super.use(world, user, hand); -+ // Paper start - PlayerLaunchProjectileEvent -+ } -+ return wrapper; -+ // Paper end - PlayerLaunchProjectileEvent - } - } -diff --git a/src/main/java/net/minecraft/world/item/ThrowablePotionItem.java b/src/main/java/net/minecraft/world/item/ThrowablePotionItem.java -index 12795451393e48f5c9ab4b1dfd9369e1ee6e0367..369955746f4b51f69fa01103e3771dd74fc6c8f0 100644 ---- a/src/main/java/net/minecraft/world/item/ThrowablePotionItem.java -+++ b/src/main/java/net/minecraft/world/item/ThrowablePotionItem.java -@@ -22,11 +22,29 @@ public class ThrowablePotionItem extends PotionItem implements ProjectileItem { - ThrownPotion thrownPotion = new ThrownPotion(world, user); - thrownPotion.setItem(itemStack); - thrownPotion.shootFromRotation(user, user.getXRot(), user.getYRot(), -20.0F, 0.5F, 1.0F); -- world.addFreshEntity(thrownPotion); -+ // Paper start - PlayerLaunchProjectileEvent -+ com.destroystokyo.paper.event.player.PlayerLaunchProjectileEvent event = new com.destroystokyo.paper.event.player.PlayerLaunchProjectileEvent((org.bukkit.entity.Player) user.getBukkitEntity(), org.bukkit.craftbukkit.inventory.CraftItemStack.asCraftMirror(itemStack), (org.bukkit.entity.Projectile) thrownPotion.getBukkitEntity()); -+ if (event.callEvent() && world.addFreshEntity(thrownPotion)) { -+ if (event.shouldConsume()) { -+ itemStack.consume(1, user); -+ } else if (user instanceof net.minecraft.server.level.ServerPlayer) { -+ ((net.minecraft.server.level.ServerPlayer) user).getBukkitEntity().updateInventory(); -+ } -+ -+ user.awardStat(Stats.ITEM_USED.get(this)); -+ } else { -+ if (user instanceof net.minecraft.server.level.ServerPlayer) { -+ ((net.minecraft.server.level.ServerPlayer) user).getBukkitEntity().updateInventory(); -+ } -+ return InteractionResultHolder.fail(itemStack); -+ } -+ // Paper end - PlayerLaunchProjectileEvent - } - -+ /* // Paper start - PlayerLaunchProjectileEvent; moved up - user.awardStat(Stats.ITEM_USED.get(this)); - itemStack.consume(1, user); -+ */ // Paper end - return InteractionResultHolder.sidedSuccess(itemStack, world.isClientSide()); - } - -diff --git a/src/main/java/net/minecraft/world/item/TridentItem.java b/src/main/java/net/minecraft/world/item/TridentItem.java -index 2d34f206b4b02d5f2ee35101233afc6b4f58a579..f1b2d388a1a40a1d909a2e726f32d6c15e1eb0eb 100644 ---- a/src/main/java/net/minecraft/world/item/TridentItem.java -+++ b/src/main/java/net/minecraft/world/item/TridentItem.java -@@ -87,19 +87,24 @@ public class TridentItem extends Item implements ProjectileItem { - } - - // CraftBukkit start -- if (!world.addFreshEntity(entitythrowntrident)) { -+ // Paper start - PlayerLaunchProjectileEvent -+ com.destroystokyo.paper.event.player.PlayerLaunchProjectileEvent event = new com.destroystokyo.paper.event.player.PlayerLaunchProjectileEvent((org.bukkit.entity.Player) entityhuman.getBukkitEntity(), org.bukkit.craftbukkit.inventory.CraftItemStack.asCraftMirror(stack), (org.bukkit.entity.Projectile) entitythrowntrident.getBukkitEntity()); -+ if (!event.callEvent() || !world.addFreshEntity(entitythrowntrident)) { -+ // Paper end - PlayerLaunchProjectileEvent - if (entityhuman instanceof net.minecraft.server.level.ServerPlayer) { - ((net.minecraft.server.level.ServerPlayer) entityhuman).getBukkitEntity().updateInventory(); - } - return; - } - -+ if (event.shouldConsume()) { // Paper - PlayerLaunchProjectileEvent - stack.hurtAndBreak(1, entityhuman, LivingEntity.getSlotForHand(user.getUsedItemHand())); -+ } // Paper - PlayerLaunchProjectileEvent - entitythrowntrident.pickupItemStack = stack.copy(); // SPIGOT-4511 update since damage call moved - // CraftBukkit end - - world.playSound((Player) null, (Entity) entitythrowntrident, (SoundEvent) holder.value(), SoundSource.PLAYERS, 1.0F, 1.0F); -- if (!entityhuman.hasInfiniteMaterials()) { -+ if (event.shouldConsume() && !entityhuman.hasInfiniteMaterials()) { // Paper - PlayerLaunchProjectileEvent - entityhuman.getInventory().removeItem(stack); - } - // CraftBukkit start - SPIGOT-5458 also need in this branch :( -diff --git a/src/main/java/net/minecraft/world/item/WindChargeItem.java b/src/main/java/net/minecraft/world/item/WindChargeItem.java -index 6222e867bb959fab05bcd8f2114ab00b26847c3e..eed0d754e9a3c7c94614e0fd54651500e0612ea8 100644 ---- a/src/main/java/net/minecraft/world/item/WindChargeItem.java -+++ b/src/main/java/net/minecraft/world/item/WindChargeItem.java -@@ -24,10 +24,26 @@ public class WindChargeItem extends Item implements ProjectileItem { - - @Override - public InteractionResultHolder use(Level world, Player user, InteractionHand hand) { -+ ItemStack itemStack = user.getItemInHand(hand); // Paper - PlayerLaunchProjectileEvent; moved from below -+ boolean shouldConsume = true; // Paper - PlayerLaunchProjectileEvent - if (!world.isClientSide()) { - WindCharge windCharge = new WindCharge(user, world, user.position().x(), user.getEyePosition().y(), user.position().z()); - windCharge.shootFromRotation(user, user.getXRot(), user.getYRot(), 0.0F, 1.5F, 1.0F); -- world.addFreshEntity(windCharge); -+ // Paper start - PlayerLaunchProjectileEvent -+ com.destroystokyo.paper.event.player.PlayerLaunchProjectileEvent event = new com.destroystokyo.paper.event.player.PlayerLaunchProjectileEvent((org.bukkit.entity.Player) user.getBukkitEntity(), org.bukkit.craftbukkit.inventory.CraftItemStack.asCraftMirror(itemStack), (org.bukkit.entity.Projectile) windCharge.getBukkitEntity()); -+ if (!event.callEvent() || !world.addFreshEntity(windCharge)) { -+ user.containerMenu.sendAllDataToRemote(); -+ if (user instanceof net.minecraft.server.level.ServerPlayer player) { -+ player.connection.send(new net.minecraft.network.protocol.game.ClientboundCooldownPacket(this, 0)); // prevent visual desync of cooldown on the slot -+ } -+ return InteractionResultHolder.fail(itemStack); -+ } -+ -+ shouldConsume = event.shouldConsume(); -+ if (!shouldConsume && !user.hasInfiniteMaterials()) { -+ user.containerMenu.sendAllDataToRemote(); -+ } -+ // Paper end - PlayerLaunchProjectileEvent - } - - world.playSound( -@@ -40,10 +56,9 @@ public class WindChargeItem extends Item implements ProjectileItem { - 0.5F, - 0.4F / (world.getRandom().nextFloat() * 0.4F + 0.8F) - ); -- ItemStack itemStack = user.getItemInHand(hand); - user.getCooldowns().addCooldown(this, 10); - user.awardStat(Stats.ITEM_USED.get(this)); -- itemStack.consume(1, user); -+ if (shouldConsume) itemStack.consume(1, user); // Paper - PlayerLaunchProjectileEvent - return InteractionResultHolder.sidedSuccess(itemStack, world.isClientSide()); - } - diff --git a/patches/server/0214-Improve-BlockPosition-inlining.patch b/patches/server/0214-Improve-BlockPosition-inlining.patch deleted file mode 100644 index 469c5f739033..000000000000 --- a/patches/server/0214-Improve-BlockPosition-inlining.patch +++ /dev/null @@ -1,60 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Techcable -Date: Wed, 30 Nov 2016 20:56:58 -0600 -Subject: [PATCH] Improve BlockPosition inlining - -Normally the JVM can inline virtual getters by having two sets of code, one is the 'optimized' code and the other is the 'deoptimized' code. -If a single type is used 99% of the time, then its worth it to inline, and to revert to 'deoptimized' the 1% of the time we encounter other types. -But if two types are encountered commonly, then the JVM can't inline them both, and the call overhead remains. - -This scenario also occurs with BlockPos and MutableBlockPos. -The variables in BlockPos are final, so MutableBlockPos can't modify them. -MutableBlockPos fixes this by adding custom mutable variables, and overriding the getters to access them. - -This approach with utility methods that operate on MutableBlockPos and BlockPos. -Specific examples are BlockPosition.up(), and World.isValidLocation(). -It makes these simple methods much slower than they need to be. - -This should result in an across the board speedup in anything that accesses blocks or does logic with positions. - -This is based upon conclusions drawn from inspecting the assenmbly generated bythe JIT compiler on my microbenchmarks. -They had 'callq' (invoke) instead of 'mov' (get from memory) instructions. - -diff --git a/src/main/java/net/minecraft/core/Vec3i.java b/src/main/java/net/minecraft/core/Vec3i.java -index ea4660fe600db94e97a5dd335135f76dd5951468..df4c9b275752ad97a4efe9380ae0d511ee760695 100644 ---- a/src/main/java/net/minecraft/core/Vec3i.java -+++ b/src/main/java/net/minecraft/core/Vec3i.java -@@ -35,12 +35,12 @@ public class Vec3i implements Comparable { - } - - @Override -- public boolean equals(Object object) { -+ public final boolean equals(Object object) { // Paper - Perf: Final for inline - return this == object || object instanceof Vec3i vec3i && this.getX() == vec3i.getX() && this.getY() == vec3i.getY() && this.getZ() == vec3i.getZ(); - } - - @Override -- public int hashCode() { -+ public final int hashCode() { // Paper - Perf: Final for inline - return (this.getY() + this.getZ() * 31) * 31 + this.getX(); - } - -@@ -53,15 +53,15 @@ public class Vec3i implements Comparable { - } - } - -- public int getX() { -+ public final int getX() { // Paper - Perf: Final for inline - return this.x; - } - -- public int getY() { -+ public final int getY() { // Paper - Perf: Final for inline - return this.y; - } - -- public int getZ() { -+ public final int getZ() { // Paper - Perf: Final for inline - return this.z; - } - diff --git a/patches/server/0214-Option-to-prevent-armor-stands-from-doing-entity-loo.patch b/patches/server/0214-Option-to-prevent-armor-stands-from-doing-entity-loo.patch new file mode 100644 index 000000000000..8f032f18fa4d --- /dev/null +++ b/patches/server/0214-Option-to-prevent-armor-stands-from-doing-entity-loo.patch @@ -0,0 +1,36 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Hugo Manrique +Date: Mon, 23 Jul 2018 12:57:39 +0200 +Subject: [PATCH] Option to prevent armor stands from doing entity lookups + + +diff --git a/src/main/java/net/minecraft/world/entity/decoration/ArmorStand.java b/src/main/java/net/minecraft/world/entity/decoration/ArmorStand.java +index aea97a30a9226275f8fbf9cb2c15d5ddf36371ac..e9d6211eb0f955eb95d2f73ad96799ef4740d506 100644 +--- a/src/main/java/net/minecraft/world/entity/decoration/ArmorStand.java ++++ b/src/main/java/net/minecraft/world/entity/decoration/ArmorStand.java +@@ -348,6 +348,7 @@ public class ArmorStand extends LivingEntity { + + @Override + protected void pushEntities() { ++ if (!this.level().paperConfig().entities.armorStands.doCollisionEntityLookups) return; // Paper - Option to prevent armor stands from doing entity lookups + List list = this.level().getEntities((Entity) this, this.getBoundingBox(), ArmorStand.RIDABLE_MINECARTS); + Iterator iterator = list.iterator(); + +diff --git a/src/main/java/net/minecraft/world/level/Level.java b/src/main/java/net/minecraft/world/level/Level.java +index e0986a141384de0ede38c88c2b116e083c63efe4..fb68aff87670f545ae5ba26db9f074a34375031e 100644 +--- a/src/main/java/net/minecraft/world/level/Level.java ++++ b/src/main/java/net/minecraft/world/level/Level.java +@@ -759,6 +759,13 @@ public abstract class Level implements LevelAccessor, AutoCloseable { + // Paper end - Prevent block entity and entity crashes + } + } ++ // Paper start - Option to prevent armor stands from doing entity lookups ++ @Override ++ public boolean noCollision(@Nullable Entity entity, AABB box) { ++ if (entity instanceof net.minecraft.world.entity.decoration.ArmorStand && !entity.level().paperConfig().entities.armorStands.doCollisionEntityLookups) return false; ++ return LevelAccessor.super.noCollision(entity, box); ++ } ++ // Paper end - Option to prevent armor stands from doing entity lookups + + public boolean shouldTickDeath(Entity entity) { + return true; diff --git a/patches/server/0215-Option-to-prevent-armor-stands-from-doing-entity-loo.patch b/patches/server/0215-Option-to-prevent-armor-stands-from-doing-entity-loo.patch deleted file mode 100644 index fbe09d503180..000000000000 --- a/patches/server/0215-Option-to-prevent-armor-stands-from-doing-entity-loo.patch +++ /dev/null @@ -1,36 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Hugo Manrique -Date: Mon, 23 Jul 2018 12:57:39 +0200 -Subject: [PATCH] Option to prevent armor stands from doing entity lookups - - -diff --git a/src/main/java/net/minecraft/world/entity/decoration/ArmorStand.java b/src/main/java/net/minecraft/world/entity/decoration/ArmorStand.java -index dae6835696e90bc5a541cacd37ea7aa88c60f4f4..1057679ceec86898a3e62bd183c6944f561aa7fd 100644 ---- a/src/main/java/net/minecraft/world/entity/decoration/ArmorStand.java -+++ b/src/main/java/net/minecraft/world/entity/decoration/ArmorStand.java -@@ -343,6 +343,7 @@ public class ArmorStand extends LivingEntity { - - @Override - protected void pushEntities() { -+ if (!this.level().paperConfig().entities.armorStands.doCollisionEntityLookups) return; // Paper - Option to prevent armor stands from doing entity lookups - List list = this.level().getEntities((Entity) this, this.getBoundingBox(), ArmorStand.RIDABLE_MINECARTS); - Iterator iterator = list.iterator(); - -diff --git a/src/main/java/net/minecraft/world/level/Level.java b/src/main/java/net/minecraft/world/level/Level.java -index 6944c0b0cfcde9fa4dd78742aee3e3b87d679abf..a9227581ec78a56e96dc3a342006e4a649906326 100644 ---- a/src/main/java/net/minecraft/world/level/Level.java -+++ b/src/main/java/net/minecraft/world/level/Level.java -@@ -764,6 +764,13 @@ public abstract class Level implements LevelAccessor, AutoCloseable { - // Paper end - Prevent block entity and entity crashes - } - } -+ // Paper start - Option to prevent armor stands from doing entity lookups -+ @Override -+ public boolean noCollision(@Nullable Entity entity, AABB box) { -+ if (entity instanceof net.minecraft.world.entity.decoration.ArmorStand && !entity.level().paperConfig().entities.armorStands.doCollisionEntityLookups) return false; -+ return LevelAccessor.super.noCollision(entity, box); -+ } -+ // Paper end - Option to prevent armor stands from doing entity lookups - - public boolean shouldTickDeath(Entity entity) { - return true; diff --git a/patches/server/0215-Vanished-players-don-t-have-rights.patch b/patches/server/0215-Vanished-players-don-t-have-rights.patch new file mode 100644 index 000000000000..4d196bf7d16b --- /dev/null +++ b/patches/server/0215-Vanished-players-don-t-have-rights.patch @@ -0,0 +1,109 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Hugo Manrique +Date: Mon, 23 Jul 2018 14:22:26 +0200 +Subject: [PATCH] Vanished players don't have rights + + +diff --git a/src/main/java/net/minecraft/world/entity/projectile/Projectile.java b/src/main/java/net/minecraft/world/entity/projectile/Projectile.java +index d6dfda0580d919eb8a000113e7f9dd9794117b45..6c7f644738548da30908a08dcfde8142b597c0b7 100644 +--- a/src/main/java/net/minecraft/world/entity/projectile/Projectile.java ++++ b/src/main/java/net/minecraft/world/entity/projectile/Projectile.java +@@ -387,6 +387,15 @@ public abstract class Projectile extends Entity implements TraceableEntity { + } else { + Entity entity1 = this.getOwner(); + ++ // Paper start - Cancel hit for vanished players ++ if (entity1 instanceof net.minecraft.server.level.ServerPlayer && entity instanceof net.minecraft.server.level.ServerPlayer) { ++ org.bukkit.entity.Player collided = (org.bukkit.entity.Player) entity.getBukkitEntity(); ++ org.bukkit.entity.Player shooter = (org.bukkit.entity.Player) entity1.getBukkitEntity(); ++ if (!shooter.canSee(collided)) { ++ return false; ++ } ++ } ++ // Paper end - Cancel hit for vanished players + return entity1 == null || this.leftOwner || !entity1.isPassengerOfSameVehicle(entity); + } + } +diff --git a/src/main/java/net/minecraft/world/item/BlockItem.java b/src/main/java/net/minecraft/world/item/BlockItem.java +index 752929f3bcd6404b08dad1c67e9a0023b671f10d..407f5db0a4b3884440bc49bf4f00d9c035899e86 100644 +--- a/src/main/java/net/minecraft/world/item/BlockItem.java ++++ b/src/main/java/net/minecraft/world/item/BlockItem.java +@@ -168,7 +168,8 @@ public class BlockItem extends Item { + Player entityhuman = context.getPlayer(); + CollisionContext voxelshapecollision = entityhuman == null ? CollisionContext.empty() : CollisionContext.of(entityhuman); + // CraftBukkit start - store default return +- boolean defaultReturn = (!this.mustSurvive() || state.canSurvive(context.getLevel(), context.getClickedPos())) && context.getLevel().isUnobstructed(state, context.getClickedPos(), voxelshapecollision); ++ Level world = context.getLevel(); // Paper - Cancel hit for vanished players ++ boolean defaultReturn = (!this.mustSurvive() || state.canSurvive(context.getLevel(), context.getClickedPos())) && world.checkEntityCollision(state, entityhuman, voxelshapecollision, context.getClickedPos(), true); // Paper - Cancel hit for vanished players + org.bukkit.entity.Player player = (context.getPlayer() instanceof ServerPlayer) ? (org.bukkit.entity.Player) context.getPlayer().getBukkitEntity() : null; + + BlockCanBuildEvent event = new BlockCanBuildEvent(CraftBlock.at(context.getLevel(), context.getClickedPos()), player, CraftBlockData.fromData(state), defaultReturn); +diff --git a/src/main/java/net/minecraft/world/level/Level.java b/src/main/java/net/minecraft/world/level/Level.java +index fb68aff87670f545ae5ba26db9f074a34375031e..924b496aaaa19c7ef69498730725ae9287e46e28 100644 +--- a/src/main/java/net/minecraft/world/level/Level.java ++++ b/src/main/java/net/minecraft/world/level/Level.java +@@ -263,6 +263,45 @@ public abstract class Level implements LevelAccessor, AutoCloseable { + this.tileLimiter = new org.spigotmc.TickLimiter(this.spigotConfig.tileMaxTickTime); + } + ++ // Paper start - Cancel hit for vanished players ++ // ret true if no collision ++ public final boolean checkEntityCollision(BlockState data, Entity source, net.minecraft.world.phys.shapes.CollisionContext voxelshapedcollision, ++ BlockPos position, boolean checkCanSee) { ++ // Copied from IWorldReader#a(IBlockData, BlockPosition, VoxelShapeCollision) & EntityAccess#a(Entity, VoxelShape) ++ net.minecraft.world.phys.shapes.VoxelShape voxelshape = data.getCollisionShape(this, position, voxelshapedcollision); ++ if (voxelshape.isEmpty()) { ++ return true; ++ } ++ ++ voxelshape = voxelshape.move((double) position.getX(), (double) position.getY(), (double) position.getZ()); ++ if (voxelshape.isEmpty()) { ++ return true; ++ } ++ ++ List entities = this.getEntities(null, voxelshape.bounds()); ++ for (int i = 0, len = entities.size(); i < len; ++i) { ++ Entity entity = entities.get(i); ++ ++ if (checkCanSee && source instanceof net.minecraft.server.level.ServerPlayer && entity instanceof net.minecraft.server.level.ServerPlayer ++ && !((net.minecraft.server.level.ServerPlayer) source).getBukkitEntity().canSee(((net.minecraft.server.level.ServerPlayer) entity).getBukkitEntity())) { ++ continue; ++ } ++ ++ // !entity1.dead && entity1.i && (entity == null || !entity1.x(entity)); ++ // elide the last check since vanilla calls with entity = null ++ // only we care about the source for the canSee check ++ if (entity.isRemoved() || !entity.blocksBuilding) { ++ continue; ++ } ++ ++ if (net.minecraft.world.phys.shapes.Shapes.joinIsNotEmpty(voxelshape, net.minecraft.world.phys.shapes.Shapes.create(entity.getBoundingBox()), net.minecraft.world.phys.shapes.BooleanOp.AND)) { ++ return false; ++ } ++ } ++ ++ return true; ++ } ++ // Paper end - Cancel hit for vanished players + @Override + public boolean isClientSide() { + return this.isClientSide; +diff --git a/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java b/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java +index 41c6a7260317ed575a3320ac36b0f2be22c120aa..474f330f381aa74e9f2fd0accdbaf2617ec1c557 100644 +--- a/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java ++++ b/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java +@@ -1327,6 +1327,14 @@ public class CraftEventFactory { + Projectile projectile = (Projectile) entity.getBukkitEntity(); + org.bukkit.entity.Entity collided = position.getEntity().getBukkitEntity(); + com.destroystokyo.paper.event.entity.ProjectileCollideEvent event = new com.destroystokyo.paper.event.entity.ProjectileCollideEvent(projectile, collided); ++ ++ if (projectile.getShooter() instanceof Player && collided instanceof Player) { ++ if (!((Player) projectile.getShooter()).canSee((Player) collided)) { ++ event.setCancelled(true); ++ return event; ++ } ++ } ++ + Bukkit.getPluginManager().callEvent(event); + return event; + } diff --git a/patches/server/0216-Allow-disabling-armor-stand-ticking.patch b/patches/server/0216-Allow-disabling-armor-stand-ticking.patch new file mode 100644 index 000000000000..52031cba6b57 --- /dev/null +++ b/patches/server/0216-Allow-disabling-armor-stand-ticking.patch @@ -0,0 +1,145 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: kashike +Date: Wed, 15 Aug 2018 01:26:09 -0700 +Subject: [PATCH] Allow disabling armor stand ticking + + +diff --git a/src/main/java/net/minecraft/world/entity/decoration/ArmorStand.java b/src/main/java/net/minecraft/world/entity/decoration/ArmorStand.java +index e9d6211eb0f955eb95d2f73ad96799ef4740d506..2caba38a50b7ea535337a3540aa5272d4a9f1878 100644 +--- a/src/main/java/net/minecraft/world/entity/decoration/ArmorStand.java ++++ b/src/main/java/net/minecraft/world/entity/decoration/ArmorStand.java +@@ -108,9 +108,16 @@ public class ArmorStand extends LivingEntity { + public Rotations leftLegPose; + public Rotations rightLegPose; + public boolean canMove = true; // Paper ++ // Paper start - Allow ArmorStands not to tick ++ public boolean canTick = true; ++ public boolean canTickSetByAPI = false; ++ private boolean noTickPoseDirty = false; ++ private boolean noTickEquipmentDirty = false; ++ // Paper end - Allow ArmorStands not to tick + + public ArmorStand(EntityType type, Level world) { + super(type, world); ++ if (world != null) this.canTick = world.paperConfig().entities.armorStands.tick; // Paper - Allow ArmorStands not to tick + this.handItems = NonNullList.withSize(2, ItemStack.EMPTY); + this.armorItems = NonNullList.withSize(4, ItemStack.EMPTY); + this.headPose = ArmorStand.DEFAULT_HEAD_POSE; +@@ -213,6 +220,7 @@ public class ArmorStand extends LivingEntity { + this.onEquipItem(enumitemslot, (ItemStack) this.armorItems.set(enumitemslot.getIndex(), itemstack), itemstack, silent); // CraftBukkit + } + ++ this.noTickEquipmentDirty = true; // Paper - Allow ArmorStands not to tick; Still update equipment + } + + @Override +@@ -248,6 +256,7 @@ public class ArmorStand extends LivingEntity { + } + + nbt.put("Pose", this.writePose()); ++ if (this.canTickSetByAPI) nbt.putBoolean("Paper.CanTickOverride", this.canTick); // Paper - Allow ArmorStands not to tick + } + + @Override +@@ -282,6 +291,12 @@ public class ArmorStand extends LivingEntity { + this.setNoBasePlate(nbt.getBoolean("NoBasePlate")); + this.setMarker(nbt.getBoolean("Marker")); + this.noPhysics = !this.hasPhysics(); ++ // Paper start - Allow ArmorStands not to tick ++ if (nbt.contains("Paper.CanTickOverride")) { ++ this.canTick = nbt.getBoolean("Paper.CanTickOverride"); ++ this.canTickSetByAPI = true; ++ } ++ // Paper end - Allow ArmorStands not to tick + CompoundTag nbttagcompound2 = nbt.getCompound("Pose"); + + this.readPose(nbttagcompound2); +@@ -661,7 +676,29 @@ public class ArmorStand extends LivingEntity { + + @Override + public void tick() { ++ // Paper start - Allow ArmorStands not to tick ++ if (!this.canTick) { ++ if (this.noTickPoseDirty) { ++ this.noTickPoseDirty = false; ++ this.updatePose(); ++ } ++ ++ if (this.noTickEquipmentDirty) { ++ this.noTickEquipmentDirty = false; ++ this.detectEquipmentUpdatesPublic(); ++ } ++ ++ return; ++ } ++ // Paper end - Allow ArmorStands not to tick ++ + super.tick(); ++ // Paper start - Allow ArmorStands not to tick ++ updatePose(); ++ } ++ ++ public void updatePose() { ++ // Paper end - Allow ArmorStands not to tick + Rotations vector3f = (Rotations) this.entityData.get(ArmorStand.DATA_HEAD_POSE); + + if (!this.headPose.equals(vector3f)) { +@@ -796,31 +833,37 @@ public class ArmorStand extends LivingEntity { + public void setHeadPose(Rotations angle) { + this.headPose = angle; + this.entityData.set(ArmorStand.DATA_HEAD_POSE, angle); ++ this.noTickPoseDirty = true; // Paper - Allow updates when not ticking + } + + public void setBodyPose(Rotations angle) { + this.bodyPose = angle; + this.entityData.set(ArmorStand.DATA_BODY_POSE, angle); ++ this.noTickPoseDirty = true; // Paper - Allow updates when not ticking + } + + public void setLeftArmPose(Rotations angle) { + this.leftArmPose = angle; + this.entityData.set(ArmorStand.DATA_LEFT_ARM_POSE, angle); ++ this.noTickPoseDirty = true; // Paper - Allow updates when not ticking + } + + public void setRightArmPose(Rotations angle) { + this.rightArmPose = angle; + this.entityData.set(ArmorStand.DATA_RIGHT_ARM_POSE, angle); ++ this.noTickPoseDirty = true; // Paper - Allow updates when not ticking + } + + public void setLeftLegPose(Rotations angle) { + this.leftLegPose = angle; + this.entityData.set(ArmorStand.DATA_LEFT_LEG_POSE, angle); ++ this.noTickPoseDirty = true; // Paper - Allow updates when not ticking + } + + public void setRightLegPose(Rotations angle) { + this.rightLegPose = angle; + this.entityData.set(ArmorStand.DATA_RIGHT_LEG_POSE, angle); ++ this.noTickPoseDirty = true; // Paper - Allow updates when not ticking + } + + public Rotations getHeadPose() { +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftArmorStand.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftArmorStand.java +index 56fcd9dd40e6a63e1af5fbd470ece0d6100292a2..1bb080a8af45411b68a0f2a3c40718d60fdc9d97 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftArmorStand.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftArmorStand.java +@@ -232,5 +232,16 @@ public class CraftArmorStand extends CraftLivingEntity implements ArmorStand { + public void setCanMove(boolean move) { + getHandle().canMove = move; + } ++ ++ @Override ++ public boolean canTick() { ++ return this.getHandle().canTick; ++ } ++ ++ @Override ++ public void setCanTick(final boolean tick) { ++ this.getHandle().canTick = tick; ++ this.getHandle().canTickSetByAPI = true; ++ } + // Paper end + } diff --git a/patches/server/0216-Vanished-players-don-t-have-rights.patch b/patches/server/0216-Vanished-players-don-t-have-rights.patch deleted file mode 100644 index 5820c721da9c..000000000000 --- a/patches/server/0216-Vanished-players-don-t-have-rights.patch +++ /dev/null @@ -1,109 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Hugo Manrique -Date: Mon, 23 Jul 2018 14:22:26 +0200 -Subject: [PATCH] Vanished players don't have rights - - -diff --git a/src/main/java/net/minecraft/world/entity/projectile/Projectile.java b/src/main/java/net/minecraft/world/entity/projectile/Projectile.java -index a39de724848d6dc796dd99dde5206f20e513fd18..30eb86b52f00cfa61af4f93aca50ffc3547c95e8 100644 ---- a/src/main/java/net/minecraft/world/entity/projectile/Projectile.java -+++ b/src/main/java/net/minecraft/world/entity/projectile/Projectile.java -@@ -288,6 +288,15 @@ public abstract class Projectile extends Entity implements TraceableEntity { - } else { - Entity entity1 = this.getOwner(); - -+ // Paper start - Cancel hit for vanished players -+ if (entity1 instanceof net.minecraft.server.level.ServerPlayer && entity instanceof net.minecraft.server.level.ServerPlayer) { -+ org.bukkit.entity.Player collided = (org.bukkit.entity.Player) entity.getBukkitEntity(); -+ org.bukkit.entity.Player shooter = (org.bukkit.entity.Player) entity1.getBukkitEntity(); -+ if (!shooter.canSee(collided)) { -+ return false; -+ } -+ } -+ // Paper end - Cancel hit for vanished players - return entity1 == null || this.leftOwner || !entity1.isPassengerOfSameVehicle(entity); - } - } -diff --git a/src/main/java/net/minecraft/world/item/BlockItem.java b/src/main/java/net/minecraft/world/item/BlockItem.java -index 6ca74a5cf691ee92c84bd031e875f72440df6b32..cee3f1200af602b5dfd0b27d05eb01826c5bbb1d 100644 ---- a/src/main/java/net/minecraft/world/item/BlockItem.java -+++ b/src/main/java/net/minecraft/world/item/BlockItem.java -@@ -174,7 +174,8 @@ public class BlockItem extends Item { - Player entityhuman = context.getPlayer(); - CollisionContext voxelshapecollision = entityhuman == null ? CollisionContext.empty() : CollisionContext.of(entityhuman); - // CraftBukkit start - store default return -- boolean defaultReturn = (!this.mustSurvive() || state.canSurvive(context.getLevel(), context.getClickedPos())) && context.getLevel().isUnobstructed(state, context.getClickedPos(), voxelshapecollision); -+ Level world = context.getLevel(); // Paper - Cancel hit for vanished players -+ boolean defaultReturn = (!this.mustSurvive() || state.canSurvive(context.getLevel(), context.getClickedPos())) && world.checkEntityCollision(state, entityhuman, voxelshapecollision, context.getClickedPos(), true); // Paper - Cancel hit for vanished players - org.bukkit.entity.Player player = (context.getPlayer() instanceof ServerPlayer) ? (org.bukkit.entity.Player) context.getPlayer().getBukkitEntity() : null; - - BlockCanBuildEvent event = new BlockCanBuildEvent(CraftBlock.at(context.getLevel(), context.getClickedPos()), player, CraftBlockData.fromData(state), defaultReturn); -diff --git a/src/main/java/net/minecraft/world/level/Level.java b/src/main/java/net/minecraft/world/level/Level.java -index a9227581ec78a56e96dc3a342006e4a649906326..5929b450a26e7c3cf63de3dc1d0e67cb781b24c7 100644 ---- a/src/main/java/net/minecraft/world/level/Level.java -+++ b/src/main/java/net/minecraft/world/level/Level.java -@@ -265,6 +265,45 @@ public abstract class Level implements LevelAccessor, AutoCloseable { - this.tileLimiter = new org.spigotmc.TickLimiter(this.spigotConfig.tileMaxTickTime); - } - -+ // Paper start - Cancel hit for vanished players -+ // ret true if no collision -+ public final boolean checkEntityCollision(BlockState data, Entity source, net.minecraft.world.phys.shapes.CollisionContext voxelshapedcollision, -+ BlockPos position, boolean checkCanSee) { -+ // Copied from IWorldReader#a(IBlockData, BlockPosition, VoxelShapeCollision) & EntityAccess#a(Entity, VoxelShape) -+ net.minecraft.world.phys.shapes.VoxelShape voxelshape = data.getCollisionShape(this, position, voxelshapedcollision); -+ if (voxelshape.isEmpty()) { -+ return true; -+ } -+ -+ voxelshape = voxelshape.move((double) position.getX(), (double) position.getY(), (double) position.getZ()); -+ if (voxelshape.isEmpty()) { -+ return true; -+ } -+ -+ List entities = this.getEntities(null, voxelshape.bounds()); -+ for (int i = 0, len = entities.size(); i < len; ++i) { -+ Entity entity = entities.get(i); -+ -+ if (checkCanSee && source instanceof net.minecraft.server.level.ServerPlayer && entity instanceof net.minecraft.server.level.ServerPlayer -+ && !((net.minecraft.server.level.ServerPlayer) source).getBukkitEntity().canSee(((net.minecraft.server.level.ServerPlayer) entity).getBukkitEntity())) { -+ continue; -+ } -+ -+ // !entity1.dead && entity1.i && (entity == null || !entity1.x(entity)); -+ // elide the last check since vanilla calls with entity = null -+ // only we care about the source for the canSee check -+ if (entity.isRemoved() || !entity.blocksBuilding) { -+ continue; -+ } -+ -+ if (net.minecraft.world.phys.shapes.Shapes.joinIsNotEmpty(voxelshape, net.minecraft.world.phys.shapes.Shapes.create(entity.getBoundingBox()), net.minecraft.world.phys.shapes.BooleanOp.AND)) { -+ return false; -+ } -+ } -+ -+ return true; -+ } -+ // Paper end - Cancel hit for vanished players - @Override - public boolean isClientSide() { - return this.isClientSide; -diff --git a/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java b/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java -index 9b6607700ed23b97755a2171a49b22d498a60626..0613bdf3c2325d5cab64783af7211b07fcf5124a 100644 ---- a/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java -+++ b/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java -@@ -1329,6 +1329,14 @@ public class CraftEventFactory { - Projectile projectile = (Projectile) entity.getBukkitEntity(); - org.bukkit.entity.Entity collided = position.getEntity().getBukkitEntity(); - com.destroystokyo.paper.event.entity.ProjectileCollideEvent event = new com.destroystokyo.paper.event.entity.ProjectileCollideEvent(projectile, collided); -+ -+ if (projectile.getShooter() instanceof Player && collided instanceof Player) { -+ if (!((Player) projectile.getShooter()).canSee((Player) collided)) { -+ event.setCancelled(true); -+ return event; -+ } -+ } -+ - Bukkit.getPluginManager().callEvent(event); - return event; - } diff --git a/patches/server/0217-Allow-disabling-armor-stand-ticking.patch b/patches/server/0217-Allow-disabling-armor-stand-ticking.patch deleted file mode 100644 index bf0bbc008ecd..000000000000 --- a/patches/server/0217-Allow-disabling-armor-stand-ticking.patch +++ /dev/null @@ -1,145 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: kashike -Date: Wed, 15 Aug 2018 01:26:09 -0700 -Subject: [PATCH] Allow disabling armor stand ticking - - -diff --git a/src/main/java/net/minecraft/world/entity/decoration/ArmorStand.java b/src/main/java/net/minecraft/world/entity/decoration/ArmorStand.java -index 1057679ceec86898a3e62bd183c6944f561aa7fd..ee3902cbada46ffb78c42dbf6f00c859546c76e1 100644 ---- a/src/main/java/net/minecraft/world/entity/decoration/ArmorStand.java -+++ b/src/main/java/net/minecraft/world/entity/decoration/ArmorStand.java -@@ -96,9 +96,16 @@ public class ArmorStand extends LivingEntity { - public Rotations leftLegPose; - public Rotations rightLegPose; - public boolean canMove = true; // Paper -+ // Paper start - Allow ArmorStands not to tick -+ public boolean canTick = true; -+ public boolean canTickSetByAPI = false; -+ private boolean noTickPoseDirty = false; -+ private boolean noTickEquipmentDirty = false; -+ // Paper end - Allow ArmorStands not to tick - - public ArmorStand(EntityType type, Level world) { - super(type, world); -+ if (world != null) this.canTick = world.paperConfig().entities.armorStands.tick; // Paper - Allow ArmorStands not to tick - this.handItems = NonNullList.withSize(2, ItemStack.EMPTY); - this.armorItems = NonNullList.withSize(4, ItemStack.EMPTY); - this.headPose = ArmorStand.DEFAULT_HEAD_POSE; -@@ -201,6 +208,7 @@ public class ArmorStand extends LivingEntity { - this.onEquipItem(enumitemslot, (ItemStack) this.armorItems.set(enumitemslot.getIndex(), itemstack), itemstack, silent); // CraftBukkit - } - -+ this.noTickEquipmentDirty = true; // Paper - Allow ArmorStands not to tick; Still update equipment - } - - @Override -@@ -243,6 +251,7 @@ public class ArmorStand extends LivingEntity { - } - - nbt.put("Pose", this.writePose()); -+ if (this.canTickSetByAPI) nbt.putBoolean("Paper.CanTickOverride", this.canTick); // Paper - Allow ArmorStands not to tick - } - - @Override -@@ -277,6 +286,12 @@ public class ArmorStand extends LivingEntity { - this.setNoBasePlate(nbt.getBoolean("NoBasePlate")); - this.setMarker(nbt.getBoolean("Marker")); - this.noPhysics = !this.hasPhysics(); -+ // Paper start - Allow ArmorStands not to tick -+ if (nbt.contains("Paper.CanTickOverride")) { -+ this.canTick = nbt.getBoolean("Paper.CanTickOverride"); -+ this.canTickSetByAPI = true; -+ } -+ // Paper end - Allow ArmorStands not to tick - CompoundTag nbttagcompound2 = nbt.getCompound("Pose"); - - this.readPose(nbttagcompound2); -@@ -664,7 +679,29 @@ public class ArmorStand extends LivingEntity { - - @Override - public void tick() { -+ // Paper start - Allow ArmorStands not to tick -+ if (!this.canTick) { -+ if (this.noTickPoseDirty) { -+ this.noTickPoseDirty = false; -+ this.updatePose(); -+ } -+ -+ if (this.noTickEquipmentDirty) { -+ this.noTickEquipmentDirty = false; -+ this.detectEquipmentUpdatesPublic(); -+ } -+ -+ return; -+ } -+ // Paper end - Allow ArmorStands not to tick -+ - super.tick(); -+ // Paper start - Allow ArmorStands not to tick -+ updatePose(); -+ } -+ -+ public void updatePose() { -+ // Paper end - Allow ArmorStands not to tick - Rotations vector3f = (Rotations) this.entityData.get(ArmorStand.DATA_HEAD_POSE); - - if (!this.headPose.equals(vector3f)) { -@@ -799,31 +836,37 @@ public class ArmorStand extends LivingEntity { - public void setHeadPose(Rotations angle) { - this.headPose = angle; - this.entityData.set(ArmorStand.DATA_HEAD_POSE, angle); -+ this.noTickPoseDirty = true; // Paper - Allow updates when not ticking - } - - public void setBodyPose(Rotations angle) { - this.bodyPose = angle; - this.entityData.set(ArmorStand.DATA_BODY_POSE, angle); -+ this.noTickPoseDirty = true; // Paper - Allow updates when not ticking - } - - public void setLeftArmPose(Rotations angle) { - this.leftArmPose = angle; - this.entityData.set(ArmorStand.DATA_LEFT_ARM_POSE, angle); -+ this.noTickPoseDirty = true; // Paper - Allow updates when not ticking - } - - public void setRightArmPose(Rotations angle) { - this.rightArmPose = angle; - this.entityData.set(ArmorStand.DATA_RIGHT_ARM_POSE, angle); -+ this.noTickPoseDirty = true; // Paper - Allow updates when not ticking - } - - public void setLeftLegPose(Rotations angle) { - this.leftLegPose = angle; - this.entityData.set(ArmorStand.DATA_LEFT_LEG_POSE, angle); -+ this.noTickPoseDirty = true; // Paper - Allow updates when not ticking - } - - public void setRightLegPose(Rotations angle) { - this.rightLegPose = angle; - this.entityData.set(ArmorStand.DATA_RIGHT_LEG_POSE, angle); -+ this.noTickPoseDirty = true; // Paper - Allow updates when not ticking - } - - public Rotations getHeadPose() { -diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftArmorStand.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftArmorStand.java -index 52ffc401bbb9fa768534a4b871f9cc7dbebb8b20..9923cea74ba39a774d6b16a225bc3e455e54c418 100644 ---- a/src/main/java/org/bukkit/craftbukkit/entity/CraftArmorStand.java -+++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftArmorStand.java -@@ -232,5 +232,16 @@ public class CraftArmorStand extends CraftLivingEntity implements ArmorStand { - public void setCanMove(boolean move) { - getHandle().canMove = move; - } -+ -+ @Override -+ public boolean canTick() { -+ return this.getHandle().canTick; -+ } -+ -+ @Override -+ public void setCanTick(final boolean tick) { -+ this.getHandle().canTick = tick; -+ this.getHandle().canTickSetByAPI = true; -+ } - // Paper end - } diff --git a/patches/server/0217-SkeletonHorse-Additions.patch b/patches/server/0217-SkeletonHorse-Additions.patch new file mode 100644 index 000000000000..2b721231ef21 --- /dev/null +++ b/patches/server/0217-SkeletonHorse-Additions.patch @@ -0,0 +1,87 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: BillyGalbreath +Date: Fri, 27 Jul 2018 22:36:31 -0500 +Subject: [PATCH] SkeletonHorse Additions + + +diff --git a/src/main/java/net/minecraft/world/entity/animal/horse/SkeletonTrapGoal.java b/src/main/java/net/minecraft/world/entity/animal/horse/SkeletonTrapGoal.java +index 521b09ac14372f524b06ffdce57932d0a590700b..b782fd54d94d1a1704ddc8e7bfda03d3aefbccbe 100644 +--- a/src/main/java/net/minecraft/world/entity/animal/horse/SkeletonTrapGoal.java ++++ b/src/main/java/net/minecraft/world/entity/animal/horse/SkeletonTrapGoal.java +@@ -20,6 +20,7 @@ import net.minecraft.world.item.enchantment.providers.VanillaEnchantmentProvider + public class SkeletonTrapGoal extends Goal { + + private final SkeletonHorse horse; ++ private java.util.List eligiblePlayers; // Paper + + public SkeletonTrapGoal(SkeletonHorse skeletonHorse) { + this.horse = skeletonHorse; +@@ -27,12 +28,13 @@ public class SkeletonTrapGoal extends Goal { + + @Override + public boolean canUse() { +- return this.horse.level().hasNearbyAlivePlayerThatAffectsSpawning(this.horse.getX(), this.horse.getY(), this.horse.getZ(), 10.0D); // Paper - Affects Spawning API ++ return !(eligiblePlayers = this.horse.level().findNearbyBukkitPlayers(this.horse.getX(), this.horse.getY(), this.horse.getZ(), 10.0D, net.minecraft.world.entity.EntitySelector.PLAYER_AFFECTS_SPAWNING)).isEmpty(); // Paper - Affects Spawning API & SkeletonHorseTrapEvent + } + + @Override + public void tick() { + ServerLevel worldserver = (ServerLevel) this.horse.level(); ++ if (!new com.destroystokyo.paper.event.entity.SkeletonHorseTrapEvent((org.bukkit.entity.SkeletonHorse) this.horse.getBukkitEntity(), eligiblePlayers).callEvent()) return; // Paper + DifficultyInstance difficultydamagescaler = worldserver.getCurrentDifficultyAt(this.horse.blockPosition()); + + this.horse.setTrap(false); +diff --git a/src/main/java/net/minecraft/world/level/EntityGetter.java b/src/main/java/net/minecraft/world/level/EntityGetter.java +index fb043d67eaa6336fc0b5d62774b8f1107f9dfa1e..dac8305f1c897e6f82a2dde67c5b1b6b8b649b19 100644 +--- a/src/main/java/net/minecraft/world/level/EntityGetter.java ++++ b/src/main/java/net/minecraft/world/level/EntityGetter.java +@@ -94,6 +94,28 @@ public interface EntityGetter { + return player; + } + ++ // Paper start ++ default List findNearbyBukkitPlayers(double x, double y, double z, double radius, boolean notSpectator) { ++ return findNearbyBukkitPlayers(x, y, z, radius, notSpectator ? EntitySelector.NO_SPECTATORS : net.minecraft.world.entity.EntitySelector.NO_CREATIVE_OR_SPECTATOR); ++ } ++ ++ default List findNearbyBukkitPlayers(double x, double y, double z, double radius, @Nullable Predicate predicate) { ++ com.google.common.collect.ImmutableList.Builder builder = com.google.common.collect.ImmutableList.builder(); ++ ++ for (Player human : this.players()) { ++ if (predicate == null || predicate.test(human)) { ++ double distanceSquared = human.distanceToSqr(x, y, z); ++ ++ if (radius < 0.0D || distanceSquared < radius * radius) { ++ builder.add(human.getBukkitEntity()); ++ } ++ } ++ } ++ ++ return builder.build(); ++ } ++ // Paper end ++ + @Nullable + default Player getNearestPlayer(Entity entity, double maxDistance) { + return this.getNearestPlayer(entity.getX(), entity.getY(), entity.getZ(), maxDistance, false); +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftSkeletonHorse.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftSkeletonHorse.java +index 248e4febbe8fe225920b6504d2c29d295cf09ec6..fbb47491dcc75f8247dee9f123f946f99ef1467f 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftSkeletonHorse.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftSkeletonHorse.java +@@ -44,4 +44,16 @@ public class CraftSkeletonHorse extends CraftAbstractHorse implements SkeletonHo + public void setTrapTime(int trapTime) { + this.getHandle().trapTime = trapTime; + } ++ ++ // Paper start - replaced by above methods ++ @Override ++ public boolean isTrap() { ++ return getHandle().isTrap(); ++ } ++ ++ @Override ++ public void setTrap(boolean trap) { ++ getHandle().setTrap(trap); ++ } ++ // Paper end + } diff --git a/patches/server/0218-Expand-ArmorStand-API.patch b/patches/server/0218-Expand-ArmorStand-API.patch new file mode 100644 index 000000000000..8e53f0be90d2 --- /dev/null +++ b/patches/server/0218-Expand-ArmorStand-API.patch @@ -0,0 +1,169 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: willies952002 +Date: Thu, 26 Jul 2018 02:25:46 -0400 +Subject: [PATCH] Expand ArmorStand API + +Adds the following: +- Add proper methods for getting and setting items in both hands. Deprecates old methods +- Enable/Disable slot interactions +- Allow using degrees for ArmorStand rotations (via new Rotations class) + +== AT == +public net.minecraft.world.entity.decoration.ArmorStand isDisabled(Lnet/minecraft/world/entity/EquipmentSlot;)Z + +Co-authored-by: SoSeDiK + +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftArmorStand.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftArmorStand.java +index 1bb080a8af45411b68a0f2a3c40718d60fdc9d97..c8713200d946b0fdd74b60d0c2c136c8226389e0 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftArmorStand.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftArmorStand.java +@@ -233,6 +233,149 @@ public class CraftArmorStand extends CraftLivingEntity implements ArmorStand { + getHandle().canMove = move; + } + ++ @Override ++ public ItemStack getItem(org.bukkit.inventory.EquipmentSlot slot) { ++ com.google.common.base.Preconditions.checkArgument(slot != null, "slot"); ++ com.google.common.base.Preconditions.checkArgument(slot != EquipmentSlot.BODY, "Cannot get body item"); ++ return getHandle().getItemBySlot(org.bukkit.craftbukkit.CraftEquipmentSlot.getNMS(slot)).asBukkitMirror(); ++ } ++ ++ @Override ++ public void setItem(org.bukkit.inventory.EquipmentSlot slot, ItemStack item) { ++ com.google.common.base.Preconditions.checkArgument(slot != null, "slot"); ++ com.google.common.base.Preconditions.checkArgument(slot != EquipmentSlot.BODY, "Cannot set body item"); ++ switch (slot) { ++ case HAND: ++ getEquipment().setItemInMainHand(item); ++ return; ++ case OFF_HAND: ++ getEquipment().setItemInOffHand(item); ++ return; ++ case FEET: ++ setBoots(item); ++ return; ++ case LEGS: ++ setLeggings(item); ++ return; ++ case CHEST: ++ setChestplate(item); ++ return; ++ case HEAD: ++ setHelmet(item); ++ return; ++ } ++ throw new UnsupportedOperationException(slot.name()); ++ } ++ ++ @Override ++ public java.util.Set getDisabledSlots() { ++ java.util.Set disabled = new java.util.HashSet<>(); ++ for (org.bukkit.inventory.EquipmentSlot slot : org.bukkit.inventory.EquipmentSlot.values()) { ++ if (this.isSlotDisabled(slot)) { ++ disabled.add(slot); ++ } ++ } ++ return disabled; ++ } ++ ++ @Override ++ public void setDisabledSlots(org.bukkit.inventory.EquipmentSlot... slots) { ++ int disabled = 0; ++ for (org.bukkit.inventory.EquipmentSlot slot : slots) { ++ if (slot == org.bukkit.inventory.EquipmentSlot.OFF_HAND) continue; ++ net.minecraft.world.entity.EquipmentSlot nmsSlot = org.bukkit.craftbukkit.CraftEquipmentSlot.getNMS(slot); ++ disabled += (1 << nmsSlot.getFilterBit(0)) + (1 << nmsSlot.getFilterBit(8)) + (1 << nmsSlot.getFilterBit(16)); ++ } ++ getHandle().disabledSlots = disabled; ++ } ++ ++ @Override ++ public void addDisabledSlots(org.bukkit.inventory.EquipmentSlot... slots) { ++ java.util.Set disabled = getDisabledSlots(); ++ java.util.Collections.addAll(disabled, slots); ++ setDisabledSlots(disabled.toArray(new org.bukkit.inventory.EquipmentSlot[0])); ++ } ++ ++ @Override ++ public void removeDisabledSlots(org.bukkit.inventory.EquipmentSlot... slots) { ++ java.util.Set disabled = getDisabledSlots(); ++ for (final org.bukkit.inventory.EquipmentSlot slot : slots) disabled.remove(slot); ++ setDisabledSlots(disabled.toArray(new org.bukkit.inventory.EquipmentSlot[0])); ++ } ++ ++ @Override ++ public boolean isSlotDisabled(org.bukkit.inventory.EquipmentSlot slot) { ++ return getHandle().isDisabled(org.bukkit.craftbukkit.CraftEquipmentSlot.getNMS(slot)); ++ } ++ ++ @Override ++ public io.papermc.paper.math.Rotations getBodyRotations() { ++ return fromNMSRotations(getHandle().bodyPose); ++ } ++ ++ @Override ++ public void setBodyRotations(io.papermc.paper.math.Rotations rotations) { ++ getHandle().setBodyPose(toNMSRotations(rotations)); ++ } ++ ++ @Override ++ public io.papermc.paper.math.Rotations getLeftArmRotations() { ++ return fromNMSRotations(getHandle().leftArmPose); ++ } ++ ++ @Override ++ public void setLeftArmRotations(io.papermc.paper.math.Rotations rotations) { ++ getHandle().setLeftArmPose(toNMSRotations(rotations)); ++ } ++ ++ @Override ++ public io.papermc.paper.math.Rotations getRightArmRotations() { ++ return fromNMSRotations(getHandle().rightArmPose); ++ } ++ ++ @Override ++ public void setRightArmRotations(io.papermc.paper.math.Rotations rotations) { ++ getHandle().setRightArmPose(toNMSRotations(rotations)); ++ } ++ ++ @Override ++ public io.papermc.paper.math.Rotations getLeftLegRotations() { ++ return fromNMSRotations(getHandle().leftLegPose); ++ } ++ ++ @Override ++ public void setLeftLegRotations(io.papermc.paper.math.Rotations rotations) { ++ getHandle().setLeftLegPose(toNMSRotations(rotations)); ++ } ++ ++ @Override ++ public io.papermc.paper.math.Rotations getRightLegRotations() { ++ return fromNMSRotations(getHandle().rightLegPose); ++ } ++ ++ @Override ++ public void setRightLegRotations(io.papermc.paper.math.Rotations rotations) { ++ getHandle().setRightLegPose(toNMSRotations(rotations)); ++ } ++ ++ @Override ++ public io.papermc.paper.math.Rotations getHeadRotations() { ++ return fromNMSRotations(getHandle().headPose); ++ } ++ ++ @Override ++ public void setHeadRotations(io.papermc.paper.math.Rotations rotations) { ++ getHandle().setHeadPose(toNMSRotations(rotations)); ++ } ++ ++ private static io.papermc.paper.math.Rotations fromNMSRotations(Rotations old) { ++ return io.papermc.paper.math.Rotations.ofDegrees(old.getX(), old.getY(), old.getZ()); ++ } ++ ++ private static Rotations toNMSRotations(io.papermc.paper.math.Rotations old) { ++ return new Rotations((float) old.x(), (float) old.y(), (float) old.z()); ++ } ++ + @Override + public boolean canTick() { + return this.getHandle().canTick; diff --git a/patches/server/0218-SkeletonHorse-Additions.patch b/patches/server/0218-SkeletonHorse-Additions.patch deleted file mode 100644 index 3d2b910a9bb2..000000000000 --- a/patches/server/0218-SkeletonHorse-Additions.patch +++ /dev/null @@ -1,87 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: BillyGalbreath -Date: Fri, 27 Jul 2018 22:36:31 -0500 -Subject: [PATCH] SkeletonHorse Additions - - -diff --git a/src/main/java/net/minecraft/world/entity/animal/horse/SkeletonTrapGoal.java b/src/main/java/net/minecraft/world/entity/animal/horse/SkeletonTrapGoal.java -index 3cb84856c10347162a8736ae1ef65165183ec8fe..5042d1d10061d611c6d283a1a1ba9f94c5ba1db5 100644 ---- a/src/main/java/net/minecraft/world/entity/animal/horse/SkeletonTrapGoal.java -+++ b/src/main/java/net/minecraft/world/entity/animal/horse/SkeletonTrapGoal.java -@@ -20,6 +20,7 @@ import net.minecraft.world.item.enchantment.providers.VanillaEnchantmentProvider - public class SkeletonTrapGoal extends Goal { - - private final SkeletonHorse horse; -+ private java.util.List eligiblePlayers; // Paper - - public SkeletonTrapGoal(SkeletonHorse skeletonHorse) { - this.horse = skeletonHorse; -@@ -27,12 +28,13 @@ public class SkeletonTrapGoal extends Goal { - - @Override - public boolean canUse() { -- return this.horse.level().hasNearbyAlivePlayerThatAffectsSpawning(this.horse.getX(), this.horse.getY(), this.horse.getZ(), 10.0D); // Paper - Affects Spawning API -+ return !(eligiblePlayers = this.horse.level().findNearbyBukkitPlayers(this.horse.getX(), this.horse.getY(), this.horse.getZ(), 10.0D, net.minecraft.world.entity.EntitySelector.PLAYER_AFFECTS_SPAWNING)).isEmpty(); // Paper - Affects Spawning API & SkeletonHorseTrapEvent - } - - @Override - public void tick() { - ServerLevel worldserver = (ServerLevel) this.horse.level(); -+ if (!new com.destroystokyo.paper.event.entity.SkeletonHorseTrapEvent((org.bukkit.entity.SkeletonHorse) this.horse.getBukkitEntity(), eligiblePlayers).callEvent()) return; // Paper - DifficultyInstance difficultydamagescaler = worldserver.getCurrentDifficultyAt(this.horse.blockPosition()); - - this.horse.setTrap(false); -diff --git a/src/main/java/net/minecraft/world/level/EntityGetter.java b/src/main/java/net/minecraft/world/level/EntityGetter.java -index 77ae7882a08441d9a80b50492be5e48487a2fdab..d465fb01af4c8610f83ecb9c68b83127cf7e95ae 100644 ---- a/src/main/java/net/minecraft/world/level/EntityGetter.java -+++ b/src/main/java/net/minecraft/world/level/EntityGetter.java -@@ -97,6 +97,28 @@ public interface EntityGetter { - return player; - } - -+ // Paper start -+ default List findNearbyBukkitPlayers(double x, double y, double z, double radius, boolean notSpectator) { -+ return findNearbyBukkitPlayers(x, y, z, radius, notSpectator ? EntitySelector.NO_SPECTATORS : net.minecraft.world.entity.EntitySelector.NO_CREATIVE_OR_SPECTATOR); -+ } -+ -+ default List findNearbyBukkitPlayers(double x, double y, double z, double radius, @Nullable Predicate predicate) { -+ com.google.common.collect.ImmutableList.Builder builder = com.google.common.collect.ImmutableList.builder(); -+ -+ for (Player human : this.players()) { -+ if (predicate == null || predicate.test(human)) { -+ double distanceSquared = human.distanceToSqr(x, y, z); -+ -+ if (radius < 0.0D || distanceSquared < radius * radius) { -+ builder.add(human.getBukkitEntity()); -+ } -+ } -+ } -+ -+ return builder.build(); -+ } -+ // Paper end -+ - @Nullable - default Player getNearestPlayer(Entity entity, double maxDistance) { - return this.getNearestPlayer(entity.getX(), entity.getY(), entity.getZ(), maxDistance, false); -diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftSkeletonHorse.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftSkeletonHorse.java -index 248e4febbe8fe225920b6504d2c29d295cf09ec6..fbb47491dcc75f8247dee9f123f946f99ef1467f 100644 ---- a/src/main/java/org/bukkit/craftbukkit/entity/CraftSkeletonHorse.java -+++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftSkeletonHorse.java -@@ -44,4 +44,16 @@ public class CraftSkeletonHorse extends CraftAbstractHorse implements SkeletonHo - public void setTrapTime(int trapTime) { - this.getHandle().trapTime = trapTime; - } -+ -+ // Paper start - replaced by above methods -+ @Override -+ public boolean isTrap() { -+ return getHandle().isTrap(); -+ } -+ -+ @Override -+ public void setTrap(boolean trap) { -+ getHandle().setTrap(trap); -+ } -+ // Paper end - } diff --git a/patches/server/0219-AnvilDamageEvent.patch b/patches/server/0219-AnvilDamageEvent.patch new file mode 100644 index 000000000000..8e5891eb7c10 --- /dev/null +++ b/patches/server/0219-AnvilDamageEvent.patch @@ -0,0 +1,27 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: BillyGalbreath +Date: Fri, 20 Jul 2018 23:37:03 -0500 +Subject: [PATCH] AnvilDamageEvent + + +diff --git a/src/main/java/net/minecraft/world/inventory/AnvilMenu.java b/src/main/java/net/minecraft/world/inventory/AnvilMenu.java +index ab59f5cdd5ce76a0408f4b6ce907e7be103d7950..126565e673e94b9c66aa4547596bbf198c57c7ad 100644 +--- a/src/main/java/net/minecraft/world/inventory/AnvilMenu.java ++++ b/src/main/java/net/minecraft/world/inventory/AnvilMenu.java +@@ -111,6 +111,16 @@ public class AnvilMenu extends ItemCombinerMenu { + if (!player.hasInfiniteMaterials() && iblockdata.is(BlockTags.ANVIL) && player.getRandom().nextFloat() < 0.12F) { + BlockState iblockdata1 = AnvilBlock.damage(iblockdata); + ++ // Paper start - AnvilDamageEvent ++ com.destroystokyo.paper.event.block.AnvilDamagedEvent event = new com.destroystokyo.paper.event.block.AnvilDamagedEvent(getBukkitView(), iblockdata1 != null ? org.bukkit.craftbukkit.block.data.CraftBlockData.fromData(iblockdata1) : null); ++ if (!event.callEvent()) { ++ return; ++ } else if (event.getDamageState() == com.destroystokyo.paper.event.block.AnvilDamagedEvent.DamageState.BROKEN) { ++ iblockdata1 = null; ++ } else { ++ iblockdata1 = ((org.bukkit.craftbukkit.block.data.CraftBlockData) event.getDamageState().getMaterial().createBlockData()).getState().setValue(AnvilBlock.FACING, iblockdata.getValue(AnvilBlock.FACING)); ++ } ++ // Paper end - AnvilDamageEvent + if (iblockdata1 == null) { + world.removeBlock(blockposition, false); + world.levelEvent(1029, blockposition, 0); diff --git a/patches/server/0219-Expand-ArmorStand-API.patch b/patches/server/0219-Expand-ArmorStand-API.patch deleted file mode 100644 index d68ca34219b0..000000000000 --- a/patches/server/0219-Expand-ArmorStand-API.patch +++ /dev/null @@ -1,169 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: willies952002 -Date: Thu, 26 Jul 2018 02:25:46 -0400 -Subject: [PATCH] Expand ArmorStand API - -Adds the following: -- Add proper methods for getting and setting items in both hands. Deprecates old methods -- Enable/Disable slot interactions -- Allow using degrees for ArmorStand rotations (via new Rotations class) - -== AT == -public net.minecraft.world.entity.decoration.ArmorStand isDisabled(Lnet/minecraft/world/entity/EquipmentSlot;)Z - -Co-authored-by: SoSeDiK - -diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftArmorStand.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftArmorStand.java -index 9923cea74ba39a774d6b16a225bc3e455e54c418..1087840331f68ffe79e79f6493137b2b894832f9 100644 ---- a/src/main/java/org/bukkit/craftbukkit/entity/CraftArmorStand.java -+++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftArmorStand.java -@@ -233,6 +233,149 @@ public class CraftArmorStand extends CraftLivingEntity implements ArmorStand { - getHandle().canMove = move; - } - -+ @Override -+ public ItemStack getItem(org.bukkit.inventory.EquipmentSlot slot) { -+ com.google.common.base.Preconditions.checkArgument(slot != null, "slot"); -+ com.google.common.base.Preconditions.checkArgument(slot != EquipmentSlot.BODY, "Cannot get body item"); -+ return getHandle().getItemBySlot(org.bukkit.craftbukkit.CraftEquipmentSlot.getNMS(slot)).asBukkitMirror(); -+ } -+ -+ @Override -+ public void setItem(org.bukkit.inventory.EquipmentSlot slot, ItemStack item) { -+ com.google.common.base.Preconditions.checkArgument(slot != null, "slot"); -+ com.google.common.base.Preconditions.checkArgument(slot != EquipmentSlot.BODY, "Cannot set body item"); -+ switch (slot) { -+ case HAND: -+ getEquipment().setItemInMainHand(item); -+ return; -+ case OFF_HAND: -+ getEquipment().setItemInOffHand(item); -+ return; -+ case FEET: -+ setBoots(item); -+ return; -+ case LEGS: -+ setLeggings(item); -+ return; -+ case CHEST: -+ setChestplate(item); -+ return; -+ case HEAD: -+ setHelmet(item); -+ return; -+ } -+ throw new UnsupportedOperationException(slot.name()); -+ } -+ -+ @Override -+ public java.util.Set getDisabledSlots() { -+ java.util.Set disabled = new java.util.HashSet<>(); -+ for (org.bukkit.inventory.EquipmentSlot slot : org.bukkit.inventory.EquipmentSlot.values()) { -+ if (this.isSlotDisabled(slot)) { -+ disabled.add(slot); -+ } -+ } -+ return disabled; -+ } -+ -+ @Override -+ public void setDisabledSlots(org.bukkit.inventory.EquipmentSlot... slots) { -+ int disabled = 0; -+ for (org.bukkit.inventory.EquipmentSlot slot : slots) { -+ if (slot == org.bukkit.inventory.EquipmentSlot.OFF_HAND) continue; -+ net.minecraft.world.entity.EquipmentSlot nmsSlot = org.bukkit.craftbukkit.CraftEquipmentSlot.getNMS(slot); -+ disabled += (1 << nmsSlot.getFilterFlag()) + (1 << (nmsSlot.getFilterFlag() + 8)) + (1 << (nmsSlot.getFilterFlag() + 16)); -+ } -+ getHandle().disabledSlots = disabled; -+ } -+ -+ @Override -+ public void addDisabledSlots(org.bukkit.inventory.EquipmentSlot... slots) { -+ java.util.Set disabled = getDisabledSlots(); -+ java.util.Collections.addAll(disabled, slots); -+ setDisabledSlots(disabled.toArray(new org.bukkit.inventory.EquipmentSlot[0])); -+ } -+ -+ @Override -+ public void removeDisabledSlots(org.bukkit.inventory.EquipmentSlot... slots) { -+ java.util.Set disabled = getDisabledSlots(); -+ for (final org.bukkit.inventory.EquipmentSlot slot : slots) disabled.remove(slot); -+ setDisabledSlots(disabled.toArray(new org.bukkit.inventory.EquipmentSlot[0])); -+ } -+ -+ @Override -+ public boolean isSlotDisabled(org.bukkit.inventory.EquipmentSlot slot) { -+ return getHandle().isDisabled(org.bukkit.craftbukkit.CraftEquipmentSlot.getNMS(slot)); -+ } -+ -+ @Override -+ public io.papermc.paper.math.Rotations getBodyRotations() { -+ return fromNMSRotations(getHandle().bodyPose); -+ } -+ -+ @Override -+ public void setBodyRotations(io.papermc.paper.math.Rotations rotations) { -+ getHandle().setBodyPose(toNMSRotations(rotations)); -+ } -+ -+ @Override -+ public io.papermc.paper.math.Rotations getLeftArmRotations() { -+ return fromNMSRotations(getHandle().leftArmPose); -+ } -+ -+ @Override -+ public void setLeftArmRotations(io.papermc.paper.math.Rotations rotations) { -+ getHandle().setLeftArmPose(toNMSRotations(rotations)); -+ } -+ -+ @Override -+ public io.papermc.paper.math.Rotations getRightArmRotations() { -+ return fromNMSRotations(getHandle().rightArmPose); -+ } -+ -+ @Override -+ public void setRightArmRotations(io.papermc.paper.math.Rotations rotations) { -+ getHandle().setRightArmPose(toNMSRotations(rotations)); -+ } -+ -+ @Override -+ public io.papermc.paper.math.Rotations getLeftLegRotations() { -+ return fromNMSRotations(getHandle().leftLegPose); -+ } -+ -+ @Override -+ public void setLeftLegRotations(io.papermc.paper.math.Rotations rotations) { -+ getHandle().setLeftLegPose(toNMSRotations(rotations)); -+ } -+ -+ @Override -+ public io.papermc.paper.math.Rotations getRightLegRotations() { -+ return fromNMSRotations(getHandle().rightLegPose); -+ } -+ -+ @Override -+ public void setRightLegRotations(io.papermc.paper.math.Rotations rotations) { -+ getHandle().setRightLegPose(toNMSRotations(rotations)); -+ } -+ -+ @Override -+ public io.papermc.paper.math.Rotations getHeadRotations() { -+ return fromNMSRotations(getHandle().headPose); -+ } -+ -+ @Override -+ public void setHeadRotations(io.papermc.paper.math.Rotations rotations) { -+ getHandle().setHeadPose(toNMSRotations(rotations)); -+ } -+ -+ private static io.papermc.paper.math.Rotations fromNMSRotations(Rotations old) { -+ return io.papermc.paper.math.Rotations.ofDegrees(old.getX(), old.getY(), old.getZ()); -+ } -+ -+ private static Rotations toNMSRotations(io.papermc.paper.math.Rotations old) { -+ return new Rotations((float) old.x(), (float) old.y(), (float) old.z()); -+ } -+ - @Override - public boolean canTick() { - return this.getHandle().canTick; diff --git a/patches/server/0220-Add-TNTPrimeEvent.patch b/patches/server/0220-Add-TNTPrimeEvent.patch new file mode 100644 index 000000000000..4cf9d4305050 --- /dev/null +++ b/patches/server/0220-Add-TNTPrimeEvent.patch @@ -0,0 +1,117 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Mark Vainomaa +Date: Mon, 16 Jul 2018 00:05:05 +0300 +Subject: [PATCH] Add TNTPrimeEvent + + +diff --git a/src/main/java/net/minecraft/world/entity/boss/enderdragon/EnderDragon.java b/src/main/java/net/minecraft/world/entity/boss/enderdragon/EnderDragon.java +index ba1bb0f82634054e02c5f4bc062c1822a356e2a6..25d2226c2a5dda411a9e35f7a0e3ab183110c227 100644 +--- a/src/main/java/net/minecraft/world/entity/boss/enderdragon/EnderDragon.java ++++ b/src/main/java/net/minecraft/world/entity/boss/enderdragon/EnderDragon.java +@@ -543,6 +543,11 @@ public class EnderDragon extends Mob implements Enemy { + }); + craftBlock.getNMS().spawnAfterBreak((ServerLevel) this.level(), blockposition, ItemStack.EMPTY, false); + } ++ // Paper start - TNTPrimeEvent ++ org.bukkit.block.Block tntBlock = CraftBlock.at(this.level(), blockposition); ++ if (!new com.destroystokyo.paper.event.block.TNTPrimeEvent(tntBlock, com.destroystokyo.paper.event.block.TNTPrimeEvent.PrimeReason.EXPLOSION, explosionSource.getIndirectSourceEntity().getBukkitEntity()).callEvent()) ++ continue; ++ // Paper end - TNTPrimeEvent + nmsBlock.wasExploded((ServerLevel) this.level(), blockposition, this.explosionSource); + + this.level().removeBlock(blockposition, false); +diff --git a/src/main/java/net/minecraft/world/level/block/FireBlock.java b/src/main/java/net/minecraft/world/level/block/FireBlock.java +index 88976aa06028adcb8f0c91e32b794887d0b55308..f44457c0d75efe323cc8242ef5173a3d5067fad0 100644 +--- a/src/main/java/net/minecraft/world/level/block/FireBlock.java ++++ b/src/main/java/net/minecraft/world/level/block/FireBlock.java +@@ -302,12 +302,19 @@ public class FireBlock extends BaseFireBlock { + + world.setBlock(blockposition, this.getStateWithAge(world, blockposition, l), 3); + } else { +- world.removeBlock(blockposition, false); ++ if(iblockdata.getBlock() != Blocks.TNT) world.removeBlock(blockposition, false); // Paper - TNTPrimeEvent; We might be cancelling it below, move the setAir down + } + + Block block = iblockdata.getBlock(); + + if (block instanceof TntBlock) { ++ // Paper start - TNTPrimeEvent ++ org.bukkit.block.Block tntBlock = org.bukkit.craftbukkit.block.CraftBlock.at(world, blockposition); ++ if (!new com.destroystokyo.paper.event.block.TNTPrimeEvent(tntBlock, com.destroystokyo.paper.event.block.TNTPrimeEvent.PrimeReason.FIRE, null).callEvent()) { ++ return; ++ } ++ world.removeBlock(blockposition, false); ++ // Paper end - TNTPrimeEvent + TntBlock.explode(world, blockposition); + } + } +diff --git a/src/main/java/net/minecraft/world/level/block/TntBlock.java b/src/main/java/net/minecraft/world/level/block/TntBlock.java +index 5e14568b325dc805e507d23ae66e789fc35ec3df..d256b0f3998028709334dd6c394d184f2c36efce 100644 +--- a/src/main/java/net/minecraft/world/level/block/TntBlock.java ++++ b/src/main/java/net/minecraft/world/level/block/TntBlock.java +@@ -52,6 +52,12 @@ public class TntBlock extends Block { + protected void onPlace(BlockState state, Level world, BlockPos pos, BlockState oldState, boolean notify) { + if (!oldState.is(state.getBlock())) { + if (world.hasNeighborSignal(pos) && CraftEventFactory.callTNTPrimeEvent(world, pos, PrimeCause.REDSTONE, null, null)) { // CraftBukkit - TNTPrimeEvent ++ // Paper start - TNTPrimeEvent ++ org.bukkit.block.Block tntBlock = org.bukkit.craftbukkit.block.CraftBlock.at(world, pos); ++ if (!new com.destroystokyo.paper.event.block.TNTPrimeEvent(tntBlock, com.destroystokyo.paper.event.block.TNTPrimeEvent.PrimeReason.REDSTONE, null).callEvent()) { ++ return; ++ } ++ // Paper end - TNTPrimeEvent + TntBlock.explode(world, pos); + world.removeBlock(pos, false); + } +@@ -62,6 +68,12 @@ public class TntBlock extends Block { + @Override + protected void neighborChanged(BlockState state, Level world, BlockPos pos, Block sourceBlock, @Nullable Orientation wireOrientation, boolean notify) { + if (world.hasNeighborSignal(pos) && CraftEventFactory.callTNTPrimeEvent(world, pos, PrimeCause.REDSTONE, null, null)) { // CraftBukkit - TNTPrimeEvent ++ // Paper start - TNTPrimeEvent ++ org.bukkit.block.Block tntBlock = org.bukkit.craftbukkit.block.CraftBlock.at(world, pos); ++ if (!new com.destroystokyo.paper.event.block.TNTPrimeEvent(tntBlock, com.destroystokyo.paper.event.block.TNTPrimeEvent.PrimeReason.REDSTONE, null).callEvent()) { ++ return; ++ } ++ // Paper end - TNTPrimeEvent + TntBlock.explode(world, pos); + world.removeBlock(pos, false); + } +@@ -79,6 +91,13 @@ public class TntBlock extends Block { + + @Override + public void wasExploded(ServerLevel world, BlockPos pos, Explosion explosion) { ++ // Paper start - TNTPrimeEvent ++ org.bukkit.block.Block tntBlock = org.bukkit.craftbukkit.block.CraftBlock.at(world, pos); ++ org.bukkit.entity.Entity source = explosion.getDirectSourceEntity() != null ? explosion.getDirectSourceEntity().getBukkitEntity() : null; ++ if (!new com.destroystokyo.paper.event.block.TNTPrimeEvent(tntBlock, com.destroystokyo.paper.event.block.TNTPrimeEvent.PrimeReason.EXPLOSION, source).callEvent()) { ++ return; ++ } ++ // Paper end - TNTPrimeEvent + PrimedTnt entitytntprimed = new PrimedTnt(world, (double) pos.getX() + 0.5D, (double) pos.getY(), (double) pos.getZ() + 0.5D, explosion.getIndirectSourceEntity()); + int i = entitytntprimed.getFuse(); + +@@ -110,6 +129,12 @@ public class TntBlock extends Block { + return InteractionResult.CONSUME; + } + // CraftBukkit end ++ // Paper start - TNTPrimeEvent ++ org.bukkit.block.Block tntBlock = org.bukkit.craftbukkit.block.CraftBlock.at(world, pos); ++ if (!new com.destroystokyo.paper.event.block.TNTPrimeEvent(tntBlock, com.destroystokyo.paper.event.block.TNTPrimeEvent.PrimeReason.ITEM, player.getBukkitEntity()).callEvent()) { ++ return InteractionResult.FAIL; ++ } ++ // Paper end - TNTPrimeEvent + TntBlock.explode(world, pos, player); + world.setBlock(pos, Blocks.AIR.defaultBlockState(), 11); + Item item = stack.getItem(); +@@ -137,6 +162,12 @@ public class TntBlock extends Block { + return; + } + // CraftBukkit end ++ // Paper start - TNTPrimeEvent ++ org.bukkit.block.Block tntBlock = org.bukkit.craftbukkit.block.CraftBlock.at(world, blockposition); ++ if (!new com.destroystokyo.paper.event.block.TNTPrimeEvent(tntBlock, com.destroystokyo.paper.event.block.TNTPrimeEvent.PrimeReason.PROJECTILE, projectile.getBukkitEntity()).callEvent()) { ++ return; ++ } ++ // Paper end - TNTPrimeEvent + TntBlock.explode(world, blockposition, entity instanceof LivingEntity ? (LivingEntity) entity : null); + world.removeBlock(blockposition, false); + } diff --git a/patches/server/0220-AnvilDamageEvent.patch b/patches/server/0220-AnvilDamageEvent.patch deleted file mode 100644 index 7437518ee295..000000000000 --- a/patches/server/0220-AnvilDamageEvent.patch +++ /dev/null @@ -1,27 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: BillyGalbreath -Date: Fri, 20 Jul 2018 23:37:03 -0500 -Subject: [PATCH] AnvilDamageEvent - - -diff --git a/src/main/java/net/minecraft/world/inventory/AnvilMenu.java b/src/main/java/net/minecraft/world/inventory/AnvilMenu.java -index 4a6295c0133606131c2b2b881b4dbe7f1e3e47b0..ffda2c984c5683edb38a56f04c53b0ea339e08fc 100644 ---- a/src/main/java/net/minecraft/world/inventory/AnvilMenu.java -+++ b/src/main/java/net/minecraft/world/inventory/AnvilMenu.java -@@ -110,6 +110,16 @@ public class AnvilMenu extends ItemCombinerMenu { - if (!player.hasInfiniteMaterials() && iblockdata.is(BlockTags.ANVIL) && player.getRandom().nextFloat() < 0.12F) { - BlockState iblockdata1 = AnvilBlock.damage(iblockdata); - -+ // Paper start - AnvilDamageEvent -+ com.destroystokyo.paper.event.block.AnvilDamagedEvent event = new com.destroystokyo.paper.event.block.AnvilDamagedEvent(getBukkitView(), iblockdata1 != null ? org.bukkit.craftbukkit.block.data.CraftBlockData.fromData(iblockdata1) : null); -+ if (!event.callEvent()) { -+ return; -+ } else if (event.getDamageState() == com.destroystokyo.paper.event.block.AnvilDamagedEvent.DamageState.BROKEN) { -+ iblockdata1 = null; -+ } else { -+ iblockdata1 = ((org.bukkit.craftbukkit.block.data.CraftBlockData) event.getDamageState().getMaterial().createBlockData()).getState().setValue(AnvilBlock.FACING, iblockdata.getValue(AnvilBlock.FACING)); -+ } -+ // Paper end - AnvilDamageEvent - if (iblockdata1 == null) { - world.removeBlock(blockposition, false); - world.levelEvent(1029, blockposition, 0); diff --git a/patches/server/0221-Add-TNTPrimeEvent.patch b/patches/server/0221-Add-TNTPrimeEvent.patch deleted file mode 100644 index 1dc0962e1dee..000000000000 --- a/patches/server/0221-Add-TNTPrimeEvent.patch +++ /dev/null @@ -1,117 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Mark Vainomaa -Date: Mon, 16 Jul 2018 00:05:05 +0300 -Subject: [PATCH] Add TNTPrimeEvent - - -diff --git a/src/main/java/net/minecraft/world/entity/boss/enderdragon/EnderDragon.java b/src/main/java/net/minecraft/world/entity/boss/enderdragon/EnderDragon.java -index b02a77b486f8d5eee31850de4a1b033fe6a107c7..a2cde7b1b316e43382cb1639ffccf29d89f5ebfc 100644 ---- a/src/main/java/net/minecraft/world/entity/boss/enderdragon/EnderDragon.java -+++ b/src/main/java/net/minecraft/world/entity/boss/enderdragon/EnderDragon.java -@@ -573,6 +573,11 @@ public class EnderDragon extends Mob implements Enemy { - }); - craftBlock.getNMS().spawnAfterBreak((ServerLevel) this.level(), blockposition, ItemStack.EMPTY, false); - } -+ // Paper start - TNTPrimeEvent -+ org.bukkit.block.Block tntBlock = CraftBlock.at(this.level(), blockposition); -+ if (!new com.destroystokyo.paper.event.block.TNTPrimeEvent(tntBlock, com.destroystokyo.paper.event.block.TNTPrimeEvent.PrimeReason.EXPLOSION, explosionSource.getIndirectSourceEntity().getBukkitEntity()).callEvent()) -+ continue; -+ // Paper end - TNTPrimeEvent - nmsBlock.wasExploded(this.level(), blockposition, this.explosionSource); - - this.level().removeBlock(blockposition, false); -diff --git a/src/main/java/net/minecraft/world/level/block/FireBlock.java b/src/main/java/net/minecraft/world/level/block/FireBlock.java -index b288db03dd5385a8a9cc49a8a7d89a9fab7224a7..c1111bd8065b53cb140e4289cb72985f03e6f549 100644 ---- a/src/main/java/net/minecraft/world/level/block/FireBlock.java -+++ b/src/main/java/net/minecraft/world/level/block/FireBlock.java -@@ -302,12 +302,19 @@ public class FireBlock extends BaseFireBlock { - - world.setBlock(blockposition, this.getStateWithAge(world, blockposition, l), 3); - } else { -- world.removeBlock(blockposition, false); -+ if(iblockdata.getBlock() != Blocks.TNT) world.removeBlock(blockposition, false); // Paper - TNTPrimeEvent; We might be cancelling it below, move the setAir down - } - - Block block = iblockdata.getBlock(); - - if (block instanceof TntBlock) { -+ // Paper start - TNTPrimeEvent -+ org.bukkit.block.Block tntBlock = org.bukkit.craftbukkit.block.CraftBlock.at(world, blockposition); -+ if (!new com.destroystokyo.paper.event.block.TNTPrimeEvent(tntBlock, com.destroystokyo.paper.event.block.TNTPrimeEvent.PrimeReason.FIRE, null).callEvent()) { -+ return; -+ } -+ world.removeBlock(blockposition, false); -+ // Paper end - TNTPrimeEvent - TntBlock.explode(world, blockposition); - } - } -diff --git a/src/main/java/net/minecraft/world/level/block/TntBlock.java b/src/main/java/net/minecraft/world/level/block/TntBlock.java -index d80b4b3b38069016a5238f619fa3b156f576d9ae..4896ddca849646135ae101236e534ab8f59bd617 100644 ---- a/src/main/java/net/minecraft/world/level/block/TntBlock.java -+++ b/src/main/java/net/minecraft/world/level/block/TntBlock.java -@@ -50,6 +50,12 @@ public class TntBlock extends Block { - protected void onPlace(BlockState state, Level world, BlockPos pos, BlockState oldState, boolean notify) { - if (!oldState.is(state.getBlock())) { - if (world.hasNeighborSignal(pos) && CraftEventFactory.callTNTPrimeEvent(world, pos, PrimeCause.REDSTONE, null, null)) { // CraftBukkit - TNTPrimeEvent -+ // Paper start - TNTPrimeEvent -+ org.bukkit.block.Block tntBlock = org.bukkit.craftbukkit.block.CraftBlock.at(world, pos); -+ if (!new com.destroystokyo.paper.event.block.TNTPrimeEvent(tntBlock, com.destroystokyo.paper.event.block.TNTPrimeEvent.PrimeReason.REDSTONE, null).callEvent()) { -+ return; -+ } -+ // Paper end - TNTPrimeEvent - TntBlock.explode(world, pos); - world.removeBlock(pos, false); - } -@@ -60,6 +66,12 @@ public class TntBlock extends Block { - @Override - protected void neighborChanged(BlockState state, Level world, BlockPos pos, Block sourceBlock, BlockPos sourcePos, boolean notify) { - if (world.hasNeighborSignal(pos) && CraftEventFactory.callTNTPrimeEvent(world, pos, PrimeCause.REDSTONE, null, sourcePos)) { // CraftBukkit - TNTPrimeEvent -+ // Paper start - TNTPrimeEvent -+ org.bukkit.block.Block tntBlock = org.bukkit.craftbukkit.block.CraftBlock.at(world, pos); -+ if (!new com.destroystokyo.paper.event.block.TNTPrimeEvent(tntBlock, com.destroystokyo.paper.event.block.TNTPrimeEvent.PrimeReason.REDSTONE, null).callEvent()) { -+ return; -+ } -+ // Paper end - TNTPrimeEvent - TntBlock.explode(world, pos); - world.removeBlock(pos, false); - } -@@ -78,6 +90,13 @@ public class TntBlock extends Block { - @Override - public void wasExploded(Level world, BlockPos pos, Explosion explosion) { - if (!world.isClientSide) { -+ // Paper start - TNTPrimeEvent -+ org.bukkit.block.Block tntBlock = org.bukkit.craftbukkit.block.CraftBlock.at(world, pos); -+ org.bukkit.entity.Entity source = explosion.source != null ? explosion.source.getBukkitEntity() : null; -+ if (!new com.destroystokyo.paper.event.block.TNTPrimeEvent(tntBlock, com.destroystokyo.paper.event.block.TNTPrimeEvent.PrimeReason.EXPLOSION, source).callEvent()) { -+ return; -+ } -+ // Paper end - TNTPrimeEvent - PrimedTnt entitytntprimed = new PrimedTnt(world, (double) pos.getX() + 0.5D, (double) pos.getY(), (double) pos.getZ() + 0.5D, explosion.getIndirectSourceEntity()); - int i = entitytntprimed.getFuse(); - -@@ -110,6 +129,12 @@ public class TntBlock extends Block { - return ItemInteractionResult.CONSUME; - } - // CraftBukkit end -+ // Paper start - TNTPrimeEvent -+ org.bukkit.block.Block tntBlock = org.bukkit.craftbukkit.block.CraftBlock.at(world, pos); -+ if (!new com.destroystokyo.paper.event.block.TNTPrimeEvent(tntBlock, com.destroystokyo.paper.event.block.TNTPrimeEvent.PrimeReason.ITEM, player.getBukkitEntity()).callEvent()) { -+ return ItemInteractionResult.FAIL; -+ } -+ // Paper end - TNTPrimeEvent - TntBlock.explode(world, pos, player); - world.setBlock(pos, Blocks.AIR.defaultBlockState(), 11); - Item item = stack.getItem(); -@@ -137,6 +162,12 @@ public class TntBlock extends Block { - return; - } - // CraftBukkit end -+ // Paper start - TNTPrimeEvent -+ org.bukkit.block.Block tntBlock = org.bukkit.craftbukkit.block.CraftBlock.at(world, blockposition); -+ if (!new com.destroystokyo.paper.event.block.TNTPrimeEvent(tntBlock, com.destroystokyo.paper.event.block.TNTPrimeEvent.PrimeReason.PROJECTILE, projectile.getBukkitEntity()).callEvent()) { -+ return; -+ } -+ // Paper end - TNTPrimeEvent - TntBlock.explode(world, blockposition, entity instanceof LivingEntity ? (LivingEntity) entity : null); - world.removeBlock(blockposition, false); - } diff --git a/patches/server/0221-Break-up-and-make-tab-spam-limits-configurable.patch b/patches/server/0221-Break-up-and-make-tab-spam-limits-configurable.patch new file mode 100644 index 000000000000..92f889e9c052 --- /dev/null +++ b/patches/server/0221-Break-up-and-make-tab-spam-limits-configurable.patch @@ -0,0 +1,52 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Shane Freeder +Date: Sun, 29 Jul 2018 05:02:15 +0100 +Subject: [PATCH] Break up and make tab spam limits configurable + +Due to the changes in 1.13, clients will send a tab completion request +for all bukkit commands in order to factor in the lack of support for +brigadier and provide backwards support in the API. + +Craftbukkit, however; has moved the chat spam limiter to also interact +with the tab completion request, which while good for avoiding abuse, +causes 1.13 clients to easilly be kicked from a server in bukkit due +to this. Removing the spam limit could cause issues for servers, however, +there is no way for servers to manipulate this without blindly cancelling +kick events, which only causes additional complications. This also causes +issues in that the tab spam limit and chat share the same field but different +limits, meaning that a player having typed a long command may be kicked from +the server. + +Splitting the field up and making it configurable allows for server owners +to take the burden of this into their own hand without having to rely on +plugins doing unsafe things. + +diff --git a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java +index f36d72153c1ec0426790ed3033500c3cb766af2d..af519e9914bae40fa1605b0cd4cf02ca1d44f0e8 100644 +--- a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java ++++ b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java +@@ -274,6 +274,7 @@ public class ServerGamePacketListenerImpl extends ServerCommonPacketListenerImpl + private int tickCount; + private int ackBlockChangesUpTo = -1; + private final TickThrottler chatSpamThrottler = new TickThrottler(20, 200); ++ private final TickThrottler tabSpamThrottler = new TickThrottler(io.papermc.paper.configuration.GlobalConfiguration.get().spamLimiter.tabSpamIncrement, io.papermc.paper.configuration.GlobalConfiguration.get().spamLimiter.tabSpamLimit); // Paper - configurable tab spam limits + private final TickThrottler dropSpamThrottler = new TickThrottler(20, 1480); + private double firstGoodX; + private double firstGoodY; +@@ -389,6 +390,7 @@ public class ServerGamePacketListenerImpl extends ServerCommonPacketListenerImpl + + this.keepConnectionAlive(); + this.chatSpamThrottler.tick(); ++ this.tabSpamThrottler.tick(); // Paper - configurable tab spam limits + this.dropSpamThrottler.tick(); + if (this.player.getLastActionTime() > 0L && this.server.getPlayerIdleTimeout() > 0 && Util.getMillis() - this.player.getLastActionTime() > (long) this.server.getPlayerIdleTimeout() * 1000L * 60L) { + this.player.resetLastActionTime(); // CraftBukkit - SPIGOT-854 +@@ -730,7 +732,7 @@ public class ServerGamePacketListenerImpl extends ServerCommonPacketListenerImpl + public void handleCustomCommandSuggestions(ServerboundCommandSuggestionPacket packet) { + // PacketUtils.ensureRunningOnSameThread(packet, this, this.player.serverLevel()); // Paper - AsyncTabCompleteEvent; run this async + // CraftBukkit start +- if (!this.chatSpamThrottler.isIncrementAndUnderThreshold(1, 500) && !this.server.getPlayerList().isOp(this.player.getGameProfile()) && !this.server.isSingleplayerOwner(this.player.getGameProfile())) { ++ if (!this.tabSpamThrottler.isIncrementAndUnderThreshold() && !this.server.getPlayerList().isOp(this.player.getGameProfile()) && !this.server.isSingleplayerOwner(this.player.getGameProfile())) { // Paper - configurable tab spam limits + this.disconnect(Component.translatable("disconnect.spam")); + return; + } diff --git a/patches/server/0222-Break-up-and-make-tab-spam-limits-configurable.patch b/patches/server/0222-Break-up-and-make-tab-spam-limits-configurable.patch deleted file mode 100644 index 504d5c5e397a..000000000000 --- a/patches/server/0222-Break-up-and-make-tab-spam-limits-configurable.patch +++ /dev/null @@ -1,52 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Shane Freeder -Date: Sun, 29 Jul 2018 05:02:15 +0100 -Subject: [PATCH] Break up and make tab spam limits configurable - -Due to the changes in 1.13, clients will send a tab completion request -for all bukkit commands in order to factor in the lack of support for -brigadier and provide backwards support in the API. - -Craftbukkit, however; has moved the chat spam limiter to also interact -with the tab completion request, which while good for avoiding abuse, -causes 1.13 clients to easilly be kicked from a server in bukkit due -to this. Removing the spam limit could cause issues for servers, however, -there is no way for servers to manipulate this without blindly cancelling -kick events, which only causes additional complications. This also causes -issues in that the tab spam limit and chat share the same field but different -limits, meaning that a player having typed a long command may be kicked from -the server. - -Splitting the field up and making it configurable allows for server owners -to take the burden of this into their own hand without having to rely on -plugins doing unsafe things. - -diff --git a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java -index 0321128ab745250e79fa5f66079c9aeb7f394cc0..5ed35d744a87290a03e9bf58143b5650501af0e6 100644 ---- a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java -+++ b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java -@@ -265,6 +265,7 @@ public class ServerGamePacketListenerImpl extends ServerCommonPacketListenerImpl - private int ackBlockChangesUpTo = -1; - // CraftBukkit start - multithreaded fields - private final AtomicInteger chatSpamTickCount = new AtomicInteger(); -+ private final java.util.concurrent.atomic.AtomicInteger tabSpamLimiter = new java.util.concurrent.atomic.AtomicInteger(); // Paper - configurable tab spam limits - // CraftBukkit end - private int dropSpamTickCount; - private double firstGoodX; -@@ -381,6 +382,7 @@ public class ServerGamePacketListenerImpl extends ServerCommonPacketListenerImpl - this.keepConnectionAlive(); - // CraftBukkit start - for (int spam; (spam = this.chatSpamTickCount.get()) > 0 && !this.chatSpamTickCount.compareAndSet(spam, spam - 1); ) ; -+ if (tabSpamLimiter.get() > 0) tabSpamLimiter.getAndDecrement(); // Paper - configurable tab spam limits - /* Use thread-safe field access instead - if (this.chatSpamTickCount > 0) { - --this.chatSpamTickCount; -@@ -719,7 +721,7 @@ public class ServerGamePacketListenerImpl extends ServerCommonPacketListenerImpl - public void handleCustomCommandSuggestions(ServerboundCommandSuggestionPacket packet) { - // PacketUtils.ensureRunningOnSameThread(packet, this, this.player.serverLevel()); // Paper - AsyncTabCompleteEvent; run this async - // CraftBukkit start -- if (this.chatSpamTickCount.addAndGet(1) > 500 && !this.server.getPlayerList().isOp(this.player.getGameProfile())) { -+ if (this.chatSpamTickCount.addAndGet(io.papermc.paper.configuration.GlobalConfiguration.get().spamLimiter.tabSpamIncrement) > io.papermc.paper.configuration.GlobalConfiguration.get().spamLimiter.tabSpamLimit && !this.server.getPlayerList().isOp(this.player.getGameProfile())) { // Paper - configurable tab spam limits - this.disconnect(Component.translatable("disconnect.spam")); - return; - } diff --git a/patches/server/0222-Fix-NBT-type-issues.patch b/patches/server/0222-Fix-NBT-type-issues.patch new file mode 100644 index 000000000000..bae1fe439363 --- /dev/null +++ b/patches/server/0222-Fix-NBT-type-issues.patch @@ -0,0 +1,44 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Aikar +Date: Fri, 3 Aug 2018 00:04:54 -0400 +Subject: [PATCH] Fix NBT type issues + +Addresses two issues: +- MC-135506: Experience should save as Integers +- Allay duplication cooldown is saved and exposed as a long, but loaded as an int + +diff --git a/src/main/java/net/minecraft/world/entity/ExperienceOrb.java b/src/main/java/net/minecraft/world/entity/ExperienceOrb.java +index 74a0bebbf829fdb2bbae87100c4e2523c34f95a0..9d9e3daebc5da0af627c3d3cdb50029aacbc587b 100644 +--- a/src/main/java/net/minecraft/world/entity/ExperienceOrb.java ++++ b/src/main/java/net/minecraft/world/entity/ExperienceOrb.java +@@ -309,7 +309,7 @@ public class ExperienceOrb extends Entity { + public void addAdditionalSaveData(CompoundTag nbt) { + nbt.putShort("Health", (short) this.health); + nbt.putShort("Age", (short) this.age); +- nbt.putShort("Value", (short) this.value); ++ nbt.putInt("Value", this.value); // Paper - save as Integer + nbt.putInt("Count", this.count); + this.savePaperNBT(nbt); // Paper + } +@@ -318,7 +318,7 @@ public class ExperienceOrb extends Entity { + public void readAdditionalSaveData(CompoundTag nbt) { + this.health = nbt.getShort("Health"); + this.age = nbt.getShort("Age"); +- this.value = nbt.getShort("Value"); ++ this.value = nbt.getInt("Value"); // Paper - load as Integer + this.count = Math.max(nbt.getInt("Count"), 1); + this.loadPaperNBT(nbt); // Paper + } +diff --git a/src/main/java/net/minecraft/world/entity/animal/allay/Allay.java b/src/main/java/net/minecraft/world/entity/animal/allay/Allay.java +index c0c054b604cdf87591e4ce7c9f15baa5c942aadc..05c3d43fafc781e2c2d762dd5f509753df8da3b3 100644 +--- a/src/main/java/net/minecraft/world/entity/animal/allay/Allay.java ++++ b/src/main/java/net/minecraft/world/entity/animal/allay/Allay.java +@@ -495,7 +495,7 @@ public class Allay extends PathfinderMob implements InventoryCarrier, VibrationS + }); + } + +- this.duplicationCooldown = (long) nbt.getInt("DuplicationCooldown"); ++ this.duplicationCooldown = nbt.getLong("DuplicationCooldown"); // Paper - Load as long + this.entityData.set(Allay.DATA_CAN_DUPLICATE, nbt.getBoolean("CanDuplicate")); + } + diff --git a/patches/server/0223-Fix-NBT-type-issues.patch b/patches/server/0223-Fix-NBT-type-issues.patch deleted file mode 100644 index 7444db91d20e..000000000000 --- a/patches/server/0223-Fix-NBT-type-issues.patch +++ /dev/null @@ -1,44 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Aikar -Date: Fri, 3 Aug 2018 00:04:54 -0400 -Subject: [PATCH] Fix NBT type issues - -Addresses two issues: -- MC-135506: Experience should save as Integers -- Allay duplication cooldown is saved and exposed as a long, but loaded as an int - -diff --git a/src/main/java/net/minecraft/world/entity/ExperienceOrb.java b/src/main/java/net/minecraft/world/entity/ExperienceOrb.java -index 2d438cd3e69503fdc45a706f25c219af6f7a5db3..0916e24271d07ad5db51c5bc68791722b0f69c2b 100644 ---- a/src/main/java/net/minecraft/world/entity/ExperienceOrb.java -+++ b/src/main/java/net/minecraft/world/entity/ExperienceOrb.java -@@ -305,7 +305,7 @@ public class ExperienceOrb extends Entity { - public void addAdditionalSaveData(CompoundTag nbt) { - nbt.putShort("Health", (short) this.health); - nbt.putShort("Age", (short) this.age); -- nbt.putShort("Value", (short) this.value); -+ nbt.putInt("Value", this.value); // Paper - save as Integer - nbt.putInt("Count", this.count); - this.savePaperNBT(nbt); // Paper - } -@@ -314,7 +314,7 @@ public class ExperienceOrb extends Entity { - public void readAdditionalSaveData(CompoundTag nbt) { - this.health = nbt.getShort("Health"); - this.age = nbt.getShort("Age"); -- this.value = nbt.getShort("Value"); -+ this.value = nbt.getInt("Value"); // Paper - load as Integer - this.count = Math.max(nbt.getInt("Count"), 1); - this.loadPaperNBT(nbt); // Paper - } -diff --git a/src/main/java/net/minecraft/world/entity/animal/allay/Allay.java b/src/main/java/net/minecraft/world/entity/animal/allay/Allay.java -index 56b9123b42d05e0fb20763b8988aa68583a36781..69986f75d3cf729204cca0c7e5428536af31f695 100644 ---- a/src/main/java/net/minecraft/world/entity/animal/allay/Allay.java -+++ b/src/main/java/net/minecraft/world/entity/animal/allay/Allay.java -@@ -489,7 +489,7 @@ public class Allay extends PathfinderMob implements InventoryCarrier, VibrationS - }); - } - -- this.duplicationCooldown = (long) nbt.getInt("DuplicationCooldown"); -+ this.duplicationCooldown = nbt.getLong("DuplicationCooldown"); // Paper - Load as long - this.entityData.set(Allay.DATA_CAN_DUPLICATE, nbt.getBoolean("CanDuplicate")); - } - diff --git a/patches/server/0223-Remove-unnecessary-itemmeta-handling.patch b/patches/server/0223-Remove-unnecessary-itemmeta-handling.patch new file mode 100644 index 000000000000..d573a1e6f964 --- /dev/null +++ b/patches/server/0223-Remove-unnecessary-itemmeta-handling.patch @@ -0,0 +1,30 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Aikar +Date: Tue, 22 Nov 2016 00:40:42 -0500 +Subject: [PATCH] Remove unnecessary itemmeta handling + + +diff --git a/src/main/java/net/minecraft/world/item/ItemStack.java b/src/main/java/net/minecraft/world/item/ItemStack.java +index 1ebaedc9617e5b79458fa119887fd72cb1f39852..957c112b4145fda5078a6f8f1689935fa0290806 100644 +--- a/src/main/java/net/minecraft/world/item/ItemStack.java ++++ b/src/main/java/net/minecraft/world/item/ItemStack.java +@@ -165,7 +165,7 @@ public final class ItemStack implements DataComponentHolder { + + // CraftBukkit start + ItemStack itemstack = new ItemStack(holder, i, datacomponentpatch); +- if (!datacomponentpatch.isEmpty()) { ++ if (false && !datacomponentpatch.isEmpty()) { // Paper - This is no longer needed with raw NBT being handled in metadata + CraftItemStack.setItemMeta(itemstack, CraftItemStack.getItemMeta(itemstack)); + } + return itemstack; +@@ -179,8 +179,8 @@ public final class ItemStack implements DataComponentHolder { + } else { + registryfriendlybytebuf.writeVarInt(itemstack.getCount()); + // Spigot start - filter +- itemstack = itemstack.copy(); +- CraftItemStack.setItemMeta(itemstack, CraftItemStack.getItemMeta(itemstack)); ++ // itemstack = itemstack.copy(); ++ // CraftItemStack.setItemMeta(itemstack, CraftItemStack.getItemMeta(itemstack)); // Paper - This is no longer with raw NBT being handled in metadata + // Spigot end + ITEM_STREAM_CODEC.encode(registryfriendlybytebuf, itemstack.getItemHolder()); // CraftBukkit - decompile error + // Paper start - adventure; conditionally render translatable components diff --git a/patches/server/0224-Add-Debug-Entities-option-to-debug-dupe-uuid-issues.patch b/patches/server/0224-Add-Debug-Entities-option-to-debug-dupe-uuid-issues.patch new file mode 100644 index 000000000000..6cd73f5ba74d --- /dev/null +++ b/patches/server/0224-Add-Debug-Entities-option-to-debug-dupe-uuid-issues.patch @@ -0,0 +1,42 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Aikar +Date: Sat, 21 Jul 2018 08:25:40 -0400 +Subject: [PATCH] Add Debug Entities option to debug dupe uuid issues + + +diff --git a/src/main/java/net/minecraft/server/level/ServerLevel.java b/src/main/java/net/minecraft/server/level/ServerLevel.java +index 75b2caba214c312f9afdd231ee3d75689380a5f3..0635e7ed67d45abb7c419cf4ebda0e64718b630e 100644 +--- a/src/main/java/net/minecraft/server/level/ServerLevel.java ++++ b/src/main/java/net/minecraft/server/level/ServerLevel.java +@@ -1166,6 +1166,12 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe + // CraftBukkit start + private boolean addEntity(Entity entity, CreatureSpawnEvent.SpawnReason spawnReason) { + org.spigotmc.AsyncCatcher.catchOp("entity add"); // Spigot ++ // Paper start - extra debug info ++ if (entity.valid) { ++ MinecraftServer.LOGGER.error("Attempted Double World add on {}", entity, new Throwable()); ++ return true; ++ } ++ // Paper end - extra debug info + if (entity.isRemoved()) { + // WorldServer.LOGGER.warn("Tried to add entity {} but it was marked as removed already", EntityTypes.getKey(entity.getType())); // CraftBukkit + return false; +diff --git a/src/main/java/net/minecraft/world/level/entity/EntityLookup.java b/src/main/java/net/minecraft/world/level/entity/EntityLookup.java +index 38df704dca30ef08f4d0831dc1cc48c6d6f71a4d..ed6aea7a38ef6e80c300ff9b012dcdbc390ad2c7 100644 +--- a/src/main/java/net/minecraft/world/level/entity/EntityLookup.java ++++ b/src/main/java/net/minecraft/world/level/entity/EntityLookup.java +@@ -33,6 +33,14 @@ public class EntityLookup { + UUID uUID = entity.getUUID(); + if (this.byUuid.containsKey(uUID)) { + LOGGER.warn("Duplicate entity UUID {}: {}", uUID, entity); ++ // Paper start - extra debug info ++ if (entity instanceof net.minecraft.world.entity.Entity) { ++ final T old = this.byUuid.get(entity.getUUID()); ++ if (old instanceof net.minecraft.world.entity.Entity oldCast && oldCast.getId() != entity.getId() && oldCast.valid) { ++ LOGGER.error("Overwrote an existing entity {} with {}", oldCast, entity); ++ } ++ } ++ // Paper end - extra debug info + } else { + this.byUuid.put(uUID, entity); + this.byId.put(entity.getId(), entity); diff --git a/patches/server/0224-Remove-unnecessary-itemmeta-handling.patch b/patches/server/0224-Remove-unnecessary-itemmeta-handling.patch deleted file mode 100644 index 787fe866f3bf..000000000000 --- a/patches/server/0224-Remove-unnecessary-itemmeta-handling.patch +++ /dev/null @@ -1,30 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Aikar -Date: Tue, 22 Nov 2016 00:40:42 -0500 -Subject: [PATCH] Remove unnecessary itemmeta handling - - -diff --git a/src/main/java/net/minecraft/world/item/ItemStack.java b/src/main/java/net/minecraft/world/item/ItemStack.java -index 1138d238caa18171b6562cc748c92cec03bfbb97..b0d0e08e81e3b87e5d4faf62e9afe9606c254115 100644 ---- a/src/main/java/net/minecraft/world/item/ItemStack.java -+++ b/src/main/java/net/minecraft/world/item/ItemStack.java -@@ -165,7 +165,7 @@ public final class ItemStack implements DataComponentHolder { - - // CraftBukkit start - ItemStack itemstack = new ItemStack(holder, i, datacomponentpatch); -- if (!datacomponentpatch.isEmpty()) { -+ if (false && !datacomponentpatch.isEmpty()) { // Paper - This is no longer needed with raw NBT being handled in metadata - CraftItemStack.setItemMeta(itemstack, CraftItemStack.getItemMeta(itemstack)); - } - return itemstack; -@@ -179,8 +179,8 @@ public final class ItemStack implements DataComponentHolder { - } else { - registryfriendlybytebuf.writeVarInt(itemstack.getCount()); - // Spigot start - filter -- itemstack = itemstack.copy(); -- CraftItemStack.setItemMeta(itemstack, CraftItemStack.getItemMeta(itemstack)); -+ // itemstack = itemstack.copy(); -+ // CraftItemStack.setItemMeta(itemstack, CraftItemStack.getItemMeta(itemstack)); // Paper - This is no longer with raw NBT being handled in metadata - // Spigot end - ITEM_STREAM_CODEC.encode(registryfriendlybytebuf, itemstack.getItemHolder()); // CraftBukkit - decompile error - // Paper start - adventure; conditionally render translatable components diff --git a/patches/server/0225-Add-Debug-Entities-option-to-debug-dupe-uuid-issues.patch b/patches/server/0225-Add-Debug-Entities-option-to-debug-dupe-uuid-issues.patch deleted file mode 100644 index 07722aef159f..000000000000 --- a/patches/server/0225-Add-Debug-Entities-option-to-debug-dupe-uuid-issues.patch +++ /dev/null @@ -1,42 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Aikar -Date: Sat, 21 Jul 2018 08:25:40 -0400 -Subject: [PATCH] Add Debug Entities option to debug dupe uuid issues - - -diff --git a/src/main/java/net/minecraft/server/level/ServerLevel.java b/src/main/java/net/minecraft/server/level/ServerLevel.java -index 4f777c9d8c3052f68bc0465c8a7386b8fb486c83..e9d08662c065d04a67918f0aa2cd4fde5798f2a6 100644 ---- a/src/main/java/net/minecraft/server/level/ServerLevel.java -+++ b/src/main/java/net/minecraft/server/level/ServerLevel.java -@@ -1201,6 +1201,12 @@ public class ServerLevel extends Level implements WorldGenLevel { - // CraftBukkit start - private boolean addEntity(Entity entity, CreatureSpawnEvent.SpawnReason spawnReason) { - org.spigotmc.AsyncCatcher.catchOp("entity add"); // Spigot -+ // Paper start - extra debug info -+ if (entity.valid) { -+ MinecraftServer.LOGGER.error("Attempted Double World add on {}", entity, new Throwable()); -+ return true; -+ } -+ // Paper end - extra debug info - if (entity.isRemoved()) { - // WorldServer.LOGGER.warn("Tried to add entity {} but it was marked as removed already", EntityTypes.getKey(entity.getType())); // CraftBukkit - return false; -diff --git a/src/main/java/net/minecraft/world/level/entity/EntityLookup.java b/src/main/java/net/minecraft/world/level/entity/EntityLookup.java -index 38df704dca30ef08f4d0831dc1cc48c6d6f71a4d..ed6aea7a38ef6e80c300ff9b012dcdbc390ad2c7 100644 ---- a/src/main/java/net/minecraft/world/level/entity/EntityLookup.java -+++ b/src/main/java/net/minecraft/world/level/entity/EntityLookup.java -@@ -33,6 +33,14 @@ public class EntityLookup { - UUID uUID = entity.getUUID(); - if (this.byUuid.containsKey(uUID)) { - LOGGER.warn("Duplicate entity UUID {}: {}", uUID, entity); -+ // Paper start - extra debug info -+ if (entity instanceof net.minecraft.world.entity.Entity) { -+ final T old = this.byUuid.get(entity.getUUID()); -+ if (old instanceof net.minecraft.world.entity.Entity oldCast && oldCast.getId() != entity.getId() && oldCast.valid) { -+ LOGGER.error("Overwrote an existing entity {} with {}", oldCast, entity); -+ } -+ } -+ // Paper end - extra debug info - } else { - this.byUuid.put(uUID, entity); - this.byId.put(entity.getId(), entity); diff --git a/patches/server/0225-Add-Early-Warning-Feature-to-WatchDog.patch b/patches/server/0225-Add-Early-Warning-Feature-to-WatchDog.patch new file mode 100644 index 000000000000..685cfead736d --- /dev/null +++ b/patches/server/0225-Add-Early-Warning-Feature-to-WatchDog.patch @@ -0,0 +1,159 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: miclebrick +Date: Wed, 8 Aug 2018 15:30:52 -0400 +Subject: [PATCH] Add Early Warning Feature to WatchDog + +Detect when the server has been hung for a long duration, and start printing +thread dumps at an interval until the point of crash. + +This will help diagnose what was going on in that time before the crash. + +diff --git a/src/main/java/net/minecraft/server/MinecraftServer.java b/src/main/java/net/minecraft/server/MinecraftServer.java +index 260d755666efc94e2ea2c8fdb38d7deddda82c08..eb0adba0178a88243946e0c7f39503aa8c7d8feb 100644 +--- a/src/main/java/net/minecraft/server/MinecraftServer.java ++++ b/src/main/java/net/minecraft/server/MinecraftServer.java +@@ -1121,6 +1121,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop 0 && WatchdogThread.monotonicMillis() > this.lastTick + this.timeoutTime && !Boolean.getBoolean("disable.watchdog")) // Paper - Add property to disable ++ // Paper start ++ Logger log = Bukkit.getServer().getLogger(); ++ long currentTime = WatchdogThread.monotonicMillis(); ++ if ( this.lastTick != 0 && this.timeoutTime > 0 && currentTime > this.lastTick + this.earlyWarningEvery && !Boolean.getBoolean("disable.watchdog")) // Paper - Add property to disable + { +- Logger log = Bukkit.getServer().getLogger(); ++ boolean isLongTimeout = currentTime > lastTick + timeoutTime; ++ // Don't spam early warning dumps ++ if ( !isLongTimeout && (earlyWarningEvery <= 0 || !hasStarted || currentTime < lastEarlyWarning + earlyWarningEvery || currentTime < lastTick + earlyWarningDelay)) continue; ++ if ( !isLongTimeout && MinecraftServer.getServer().hasStopped()) continue; // Don't spam early watchdog warnings during shutdown, we'll come back to this... ++ lastEarlyWarning = currentTime; ++ if (isLongTimeout) { ++ // Paper end + log.log( Level.SEVERE, "------------------------------" ); + log.log( Level.SEVERE, "The server has stopped responding! This is (probably) not a Paper bug." ); // Paper + log.log( Level.SEVERE, "If you see a plugin in the Server thread dump below, then please report it to that author" ); +@@ -92,29 +107,45 @@ public class WatchdogThread extends Thread + } + } + // Paper end ++ } else ++ { ++ log.log(Level.SEVERE, "--- DO NOT REPORT THIS TO PAPER - THIS IS NOT A BUG OR A CRASH - " + Bukkit.getServer().getVersion() + " ---"); ++ log.log(Level.SEVERE, "The server has not responded for " + (currentTime - lastTick) / 1000 + " seconds! Creating thread dump"); ++ } ++ // Paper end - Different message for short timeout + log.log( Level.SEVERE, "------------------------------" ); + log.log( Level.SEVERE, "Server thread dump (Look for plugins here before reporting to Paper!):" ); // Paper + WatchdogThread.dumpThread( ManagementFactory.getThreadMXBean().getThreadInfo( MinecraftServer.getServer().serverThread.getId(), Integer.MAX_VALUE ), log ); + log.log( Level.SEVERE, "------------------------------" ); + // ++ // Paper start - Only print full dump on long timeouts ++ if ( isLongTimeout ) ++ { + log.log( Level.SEVERE, "Entire Thread Dump:" ); + ThreadInfo[] threads = ManagementFactory.getThreadMXBean().dumpAllThreads( true, true ); + for ( ThreadInfo thread : threads ) + { + WatchdogThread.dumpThread( thread, log ); + } ++ } else { ++ log.log(Level.SEVERE, "--- DO NOT REPORT THIS TO PAPER - THIS IS NOT A BUG OR A CRASH ---"); ++ } ++ + log.log( Level.SEVERE, "------------------------------" ); + ++ if ( isLongTimeout ) ++ { + if ( this.restart && !MinecraftServer.getServer().hasStopped() ) + { + RestartCommand.restart(); + } + break; ++ } // Paper end + } + + try + { +- sleep( 10000 ); ++ sleep( 1000 ); // Paper - Reduce check time to every second instead of every ten seconds, more consistent and allows for short timeout + } catch ( InterruptedException ex ) + { + this.interrupt(); diff --git a/patches/server/0226-Add-Early-Warning-Feature-to-WatchDog.patch b/patches/server/0226-Add-Early-Warning-Feature-to-WatchDog.patch deleted file mode 100644 index babdb2e00b00..000000000000 --- a/patches/server/0226-Add-Early-Warning-Feature-to-WatchDog.patch +++ /dev/null @@ -1,159 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: miclebrick -Date: Wed, 8 Aug 2018 15:30:52 -0400 -Subject: [PATCH] Add Early Warning Feature to WatchDog - -Detect when the server has been hung for a long duration, and start printing -thread dumps at an interval until the point of crash. - -This will help diagnose what was going on in that time before the crash. - -diff --git a/src/main/java/net/minecraft/server/MinecraftServer.java b/src/main/java/net/minecraft/server/MinecraftServer.java -index cc0968182ab597892dbae8dd9b3e803fb62b7065..2d5ae71c143556a938f078d2fb84cab7bd4f789b 100644 ---- a/src/main/java/net/minecraft/server/MinecraftServer.java -+++ b/src/main/java/net/minecraft/server/MinecraftServer.java -@@ -1107,6 +1107,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop 0 && WatchdogThread.monotonicMillis() > this.lastTick + this.timeoutTime && !Boolean.getBoolean("disable.watchdog")) // Paper - Add property to disable -+ // Paper start -+ Logger log = Bukkit.getServer().getLogger(); -+ long currentTime = WatchdogThread.monotonicMillis(); -+ if ( this.lastTick != 0 && this.timeoutTime > 0 && currentTime > this.lastTick + this.earlyWarningEvery && !Boolean.getBoolean("disable.watchdog")) // Paper - Add property to disable - { -- Logger log = Bukkit.getServer().getLogger(); -+ boolean isLongTimeout = currentTime > lastTick + timeoutTime; -+ // Don't spam early warning dumps -+ if ( !isLongTimeout && (earlyWarningEvery <= 0 || !hasStarted || currentTime < lastEarlyWarning + earlyWarningEvery || currentTime < lastTick + earlyWarningDelay)) continue; -+ if ( !isLongTimeout && MinecraftServer.getServer().hasStopped()) continue; // Don't spam early watchdog warnings during shutdown, we'll come back to this... -+ lastEarlyWarning = currentTime; -+ if (isLongTimeout) { -+ // Paper end - log.log( Level.SEVERE, "------------------------------" ); - log.log( Level.SEVERE, "The server has stopped responding! This is (probably) not a Paper bug." ); // Paper - log.log( Level.SEVERE, "If you see a plugin in the Server thread dump below, then please report it to that author" ); -@@ -92,29 +107,45 @@ public class WatchdogThread extends Thread - } - } - // Paper end -+ } else -+ { -+ log.log(Level.SEVERE, "--- DO NOT REPORT THIS TO PAPER - THIS IS NOT A BUG OR A CRASH - " + Bukkit.getServer().getVersion() + " ---"); -+ log.log(Level.SEVERE, "The server has not responded for " + (currentTime - lastTick) / 1000 + " seconds! Creating thread dump"); -+ } -+ // Paper end - Different message for short timeout - log.log( Level.SEVERE, "------------------------------" ); - log.log( Level.SEVERE, "Server thread dump (Look for plugins here before reporting to Paper!):" ); // Paper - WatchdogThread.dumpThread( ManagementFactory.getThreadMXBean().getThreadInfo( MinecraftServer.getServer().serverThread.getId(), Integer.MAX_VALUE ), log ); - log.log( Level.SEVERE, "------------------------------" ); - // -+ // Paper start - Only print full dump on long timeouts -+ if ( isLongTimeout ) -+ { - log.log( Level.SEVERE, "Entire Thread Dump:" ); - ThreadInfo[] threads = ManagementFactory.getThreadMXBean().dumpAllThreads( true, true ); - for ( ThreadInfo thread : threads ) - { - WatchdogThread.dumpThread( thread, log ); - } -+ } else { -+ log.log(Level.SEVERE, "--- DO NOT REPORT THIS TO PAPER - THIS IS NOT A BUG OR A CRASH ---"); -+ } -+ - log.log( Level.SEVERE, "------------------------------" ); - -+ if ( isLongTimeout ) -+ { - if ( this.restart && !MinecraftServer.getServer().hasStopped() ) - { - RestartCommand.restart(); - } - break; -+ } // Paper end - } - - try - { -- sleep( 10000 ); -+ sleep( 1000 ); // Paper - Reduce check time to every second instead of every ten seconds, more consistent and allows for short timeout - } catch ( InterruptedException ex ) - { - this.interrupt(); diff --git a/patches/server/0226-Use-ConcurrentHashMap-in-JsonList.patch b/patches/server/0226-Use-ConcurrentHashMap-in-JsonList.patch new file mode 100644 index 000000000000..657e75580e8d --- /dev/null +++ b/patches/server/0226-Use-ConcurrentHashMap-in-JsonList.patch @@ -0,0 +1,108 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: egg82 +Date: Tue, 7 Aug 2018 01:24:23 -0600 +Subject: [PATCH] Use ConcurrentHashMap in JsonList + +This is specifically aimed at fixing #471 + +Using a ConcurrentHashMap because thread safety +The performance benefit of Map over ConcurrentMap is negligabe at best in this scenaio, as most operations will be get and not add or remove +Even without considering the use-case the benefits are still negligable + +Original ideas for the system included an expiration policy and/or handler +The simpler solution was to use a computeIfPresent in the get method +This will simultaneously have an O(1) lookup time and automatically expire any values +Since the get method (nor other similar methods) don't seem to have a critical need to flush the map to disk at any of these points further processing is simply wasteful +Meaning the original function expired values unrelated to the current value without actually having any explicit need to + +The h method was heavily modified to be much more efficient in its processing +Also instead of being called on every get, it's now called just before a save +This will eliminate stale values being flushed to disk + +Modified isEmpty to use the isEmpty() method instead of the slightly confusing size() < 1 +The point of this is readability, but does have a side-benefit of a small microptimization + +diff --git a/src/main/java/net/minecraft/server/players/PlayerList.java b/src/main/java/net/minecraft/server/players/PlayerList.java +index c9c3ebcb7239bf01617a89f03cd0ad12dd0b2c34..f2c7fba1f474618eb68a804d2dc7fc8d70c8ff6f 100644 +--- a/src/main/java/net/minecraft/server/players/PlayerList.java ++++ b/src/main/java/net/minecraft/server/players/PlayerList.java +@@ -593,7 +593,7 @@ public abstract class PlayerList { + } else if (!this.isWhiteListed(gameprofile, event)) { // Paper - ProfileWhitelistVerifyEvent + //ichatmutablecomponent = Component.translatable("multiplayer.disconnect.not_whitelisted"); // Paper + //event.disallow(PlayerLoginEvent.Result.KICK_WHITELIST, net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer.legacySection().deserialize(org.spigotmc.SpigotConfig.whitelistMessage)); // Spigot // Paper - Adventure - moved to isWhitelisted +- } else if (this.getIpBans().isBanned(socketaddress) && !this.getIpBans().get(socketaddress).hasExpired()) { ++ } else if (this.getIpBans().isBanned(socketaddress) && getIpBans().get(socketaddress) != null && !this.getIpBans().get(socketaddress).hasExpired()) { // Paper - fix NPE with temp ip bans + IpBanListEntry ipbanentry = this.ipBans.get(socketaddress); + + ichatmutablecomponent = Component.translatable("multiplayer.disconnect.banned_ip.reason", ipbanentry.getReason()); +diff --git a/src/main/java/net/minecraft/server/players/StoredUserList.java b/src/main/java/net/minecraft/server/players/StoredUserList.java +index 7e81907bfdca225413e8191e37969990e6f4cf2c..c038da20b76c0b7b1c18471b20be01e849d29f3a 100644 +--- a/src/main/java/net/minecraft/server/players/StoredUserList.java ++++ b/src/main/java/net/minecraft/server/players/StoredUserList.java +@@ -30,7 +30,7 @@ public abstract class StoredUserList> { + private static final Logger LOGGER = LogUtils.getLogger(); + private static final Gson GSON = (new GsonBuilder()).setPrettyPrinting().create(); + private final File file; +- private final Map map = Maps.newHashMap(); ++ private final Map map = Maps.newConcurrentMap(); // Paper - Use ConcurrentHashMap in JsonList + + public StoredUserList(File file) { + this.file = file; +@@ -53,8 +53,11 @@ public abstract class StoredUserList> { + + @Nullable + public V get(K key) { +- this.removeExpired(); +- return (V) this.map.get(this.getKeyForUser(key)); // CraftBukkit - fix decompile error ++ // Paper start - Use ConcurrentHashMap in JsonList ++ return (V) this.map.computeIfPresent(this.getKeyForUser(key), (k, v) -> { ++ return v.hasExpired() ? null : v; ++ }); ++ // Paper end - Use ConcurrentHashMap in JsonList + } + + public void remove(K key) { +@@ -77,7 +80,7 @@ public abstract class StoredUserList> { + } + + public boolean isEmpty() { +- return this.map.size() < 1; ++ return this.map.isEmpty(); // Paper - Use ConcurrentHashMap in JsonList + } + + protected String getKeyForUser(K profile) { +@@ -90,25 +93,7 @@ public abstract class StoredUserList> { + } + + private void removeExpired() { +- List list = Lists.newArrayList(); +- Iterator iterator = this.map.values().iterator(); +- +- while (iterator.hasNext()) { +- V v0 = (V) iterator.next(); // CraftBukkit - decompile error +- +- if (v0.hasExpired()) { +- list.add(v0.getUser()); +- } +- } +- +- iterator = list.iterator(); +- +- while (iterator.hasNext()) { +- K k0 = (K) iterator.next(); // CraftBukkit - decompile error +- +- this.map.remove(this.getKeyForUser(k0)); +- } +- ++ this.map.values().removeIf(StoredUserEntry::hasExpired); // Paper - Use ConcurrentHashMap in JsonList + } + + protected abstract StoredUserEntry createEntry(JsonObject json); +@@ -118,6 +103,7 @@ public abstract class StoredUserList> { + } + + public void save() throws IOException { ++ this.removeExpired(); // Paper - remove expired values before saving + JsonArray jsonarray = new JsonArray(); + Stream stream = this.map.values().stream().map((jsonlistentry) -> { // CraftBukkit - decompile error + JsonObject jsonobject = new JsonObject(); diff --git a/patches/server/0227-Use-ConcurrentHashMap-in-JsonList.patch b/patches/server/0227-Use-ConcurrentHashMap-in-JsonList.patch deleted file mode 100644 index 35ab668887ef..000000000000 --- a/patches/server/0227-Use-ConcurrentHashMap-in-JsonList.patch +++ /dev/null @@ -1,108 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: egg82 -Date: Tue, 7 Aug 2018 01:24:23 -0600 -Subject: [PATCH] Use ConcurrentHashMap in JsonList - -This is specifically aimed at fixing #471 - -Using a ConcurrentHashMap because thread safety -The performance benefit of Map over ConcurrentMap is negligabe at best in this scenaio, as most operations will be get and not add or remove -Even without considering the use-case the benefits are still negligable - -Original ideas for the system included an expiration policy and/or handler -The simpler solution was to use a computeIfPresent in the get method -This will simultaneously have an O(1) lookup time and automatically expire any values -Since the get method (nor other similar methods) don't seem to have a critical need to flush the map to disk at any of these points further processing is simply wasteful -Meaning the original function expired values unrelated to the current value without actually having any explicit need to - -The h method was heavily modified to be much more efficient in its processing -Also instead of being called on every get, it's now called just before a save -This will eliminate stale values being flushed to disk - -Modified isEmpty to use the isEmpty() method instead of the slightly confusing size() < 1 -The point of this is readability, but does have a side-benefit of a small microptimization - -diff --git a/src/main/java/net/minecraft/server/players/PlayerList.java b/src/main/java/net/minecraft/server/players/PlayerList.java -index 25dee4848c8b2cff74075c6d26d384e71f706627..5fc76fd70f98fe874b38d8da08017fdadbd115e5 100644 ---- a/src/main/java/net/minecraft/server/players/PlayerList.java -+++ b/src/main/java/net/minecraft/server/players/PlayerList.java -@@ -627,7 +627,7 @@ public abstract class PlayerList { - } else if (!this.isWhiteListed(gameprofile, event)) { // Paper - ProfileWhitelistVerifyEvent - //ichatmutablecomponent = Component.translatable("multiplayer.disconnect.not_whitelisted"); // Paper - //event.disallow(PlayerLoginEvent.Result.KICK_WHITELIST, net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer.legacySection().deserialize(org.spigotmc.SpigotConfig.whitelistMessage)); // Spigot // Paper - Adventure - moved to isWhitelisted -- } else if (this.getIpBans().isBanned(socketaddress) && !this.getIpBans().get(socketaddress).hasExpired()) { -+ } else if (this.getIpBans().isBanned(socketaddress) && getIpBans().get(socketaddress) != null && !this.getIpBans().get(socketaddress).hasExpired()) { // Paper - fix NPE with temp ip bans - IpBanListEntry ipbanentry = this.ipBans.get(socketaddress); - - ichatmutablecomponent = Component.translatable("multiplayer.disconnect.banned_ip.reason", ipbanentry.getReason()); -diff --git a/src/main/java/net/minecraft/server/players/StoredUserList.java b/src/main/java/net/minecraft/server/players/StoredUserList.java -index 7e81907bfdca225413e8191e37969990e6f4cf2c..c038da20b76c0b7b1c18471b20be01e849d29f3a 100644 ---- a/src/main/java/net/minecraft/server/players/StoredUserList.java -+++ b/src/main/java/net/minecraft/server/players/StoredUserList.java -@@ -30,7 +30,7 @@ public abstract class StoredUserList> { - private static final Logger LOGGER = LogUtils.getLogger(); - private static final Gson GSON = (new GsonBuilder()).setPrettyPrinting().create(); - private final File file; -- private final Map map = Maps.newHashMap(); -+ private final Map map = Maps.newConcurrentMap(); // Paper - Use ConcurrentHashMap in JsonList - - public StoredUserList(File file) { - this.file = file; -@@ -53,8 +53,11 @@ public abstract class StoredUserList> { - - @Nullable - public V get(K key) { -- this.removeExpired(); -- return (V) this.map.get(this.getKeyForUser(key)); // CraftBukkit - fix decompile error -+ // Paper start - Use ConcurrentHashMap in JsonList -+ return (V) this.map.computeIfPresent(this.getKeyForUser(key), (k, v) -> { -+ return v.hasExpired() ? null : v; -+ }); -+ // Paper end - Use ConcurrentHashMap in JsonList - } - - public void remove(K key) { -@@ -77,7 +80,7 @@ public abstract class StoredUserList> { - } - - public boolean isEmpty() { -- return this.map.size() < 1; -+ return this.map.isEmpty(); // Paper - Use ConcurrentHashMap in JsonList - } - - protected String getKeyForUser(K profile) { -@@ -90,25 +93,7 @@ public abstract class StoredUserList> { - } - - private void removeExpired() { -- List list = Lists.newArrayList(); -- Iterator iterator = this.map.values().iterator(); -- -- while (iterator.hasNext()) { -- V v0 = (V) iterator.next(); // CraftBukkit - decompile error -- -- if (v0.hasExpired()) { -- list.add(v0.getUser()); -- } -- } -- -- iterator = list.iterator(); -- -- while (iterator.hasNext()) { -- K k0 = (K) iterator.next(); // CraftBukkit - decompile error -- -- this.map.remove(this.getKeyForUser(k0)); -- } -- -+ this.map.values().removeIf(StoredUserEntry::hasExpired); // Paper - Use ConcurrentHashMap in JsonList - } - - protected abstract StoredUserEntry createEntry(JsonObject json); -@@ -118,6 +103,7 @@ public abstract class StoredUserList> { - } - - public void save() throws IOException { -+ this.removeExpired(); // Paper - remove expired values before saving - JsonArray jsonarray = new JsonArray(); - Stream stream = this.map.values().stream().map((jsonlistentry) -> { // CraftBukkit - decompile error - JsonObject jsonobject = new JsonObject(); diff --git a/patches/server/0227-Use-a-Queue-for-Queueing-Commands.patch b/patches/server/0227-Use-a-Queue-for-Queueing-Commands.patch new file mode 100644 index 000000000000..8ee1b6a200e3 --- /dev/null +++ b/patches/server/0227-Use-a-Queue-for-Queueing-Commands.patch @@ -0,0 +1,38 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Aikar +Date: Sun, 12 Aug 2018 02:33:39 -0400 +Subject: [PATCH] Use a Queue for Queueing Commands + +Lists are bad as Queues mmmkay. + +diff --git a/src/main/java/net/minecraft/server/dedicated/DedicatedServer.java b/src/main/java/net/minecraft/server/dedicated/DedicatedServer.java +index 5080c8296112aa093ca5450cad1d54c447ed8214..417c8bf97c5f5b7d127ac7d496b86882d75ff354 100644 +--- a/src/main/java/net/minecraft/server/dedicated/DedicatedServer.java ++++ b/src/main/java/net/minecraft/server/dedicated/DedicatedServer.java +@@ -75,7 +75,7 @@ public class DedicatedServer extends MinecraftServer implements ServerInterface + static final Logger LOGGER = LogUtils.getLogger(); + private static final int CONVERSION_RETRY_DELAY_MS = 5000; + private static final int CONVERSION_RETRIES = 2; +- private final List consoleInput = Collections.synchronizedList(Lists.newArrayList()); ++ private final java.util.Queue serverCommandQueue = new java.util.concurrent.ConcurrentLinkedQueue<>(); // Paper - Perf: use a proper queue + @Nullable + private QueryThreadGs4 queryThreadGs4; + // private final RemoteControlCommandListener rconConsoleSource; // CraftBukkit - remove field +@@ -418,12 +418,14 @@ public class DedicatedServer extends MinecraftServer implements ServerInterface + } + + public void handleConsoleInput(String command, CommandSourceStack commandSource) { +- this.consoleInput.add(new ConsoleInput(command, commandSource)); ++ this.serverCommandQueue.add(new ConsoleInput(command, commandSource)); // Paper - Perf: use proper queue + } + + public void handleConsoleInputs() { +- while (!this.consoleInput.isEmpty()) { +- ConsoleInput servercommand = (ConsoleInput) this.consoleInput.remove(0); ++ // Paper start - Perf: use proper queue ++ ConsoleInput servercommand; ++ while ((servercommand = this.serverCommandQueue.poll()) != null) { ++ // Paper end - Perf: use proper queue + + // CraftBukkit start - ServerCommand for preprocessing + ServerCommandEvent event = new ServerCommandEvent(this.console, servercommand.msg); diff --git a/patches/server/0228-Ability-to-get-block-entities-from-a-chunk-without-s.patch b/patches/server/0228-Ability-to-get-block-entities-from-a-chunk-without-s.patch new file mode 100644 index 000000000000..77ed848f4c9d --- /dev/null +++ b/patches/server/0228-Ability-to-get-block-entities-from-a-chunk-without-s.patch @@ -0,0 +1,55 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Aikar +Date: Wed, 15 Aug 2018 01:16:34 -0400 +Subject: [PATCH] Ability to get block entities from a chunk without snapshots + + +diff --git a/src/main/java/org/bukkit/craftbukkit/CraftChunk.java b/src/main/java/org/bukkit/craftbukkit/CraftChunk.java +index 91d2b6eaa2af0abb1bdf11849f0fd59660f765dd..b2d85abb6c9c725955d972cd6895440849213fdf 100644 +--- a/src/main/java/org/bukkit/craftbukkit/CraftChunk.java ++++ b/src/main/java/org/bukkit/craftbukkit/CraftChunk.java +@@ -174,6 +174,13 @@ public class CraftChunk implements Chunk { + + @Override + public BlockState[] getTileEntities() { ++ // Paper start ++ return getTileEntities(true); ++ } ++ ++ @Override ++ public BlockState[] getTileEntities(boolean useSnapshot) { ++ // Paper end + if (!this.isLoaded()) { + this.getWorld().getChunkAt(this.x, this.z); // Transient load for this tick + } +@@ -183,7 +190,29 @@ public class CraftChunk implements Chunk { + BlockState[] entities = new BlockState[chunk.blockEntities.size()]; + + for (BlockPos position : chunk.blockEntities.keySet()) { +- entities[index++] = this.worldServer.getWorld().getBlockAt(position.getX(), position.getY(), position.getZ()).getState(); ++ // Paper start ++ entities[index++] = this.worldServer.getWorld().getBlockAt(position.getX(), position.getY(), position.getZ()).getState(useSnapshot); ++ } ++ ++ return entities; ++ } ++ ++ @Override ++ public Collection getTileEntities(Predicate blockPredicate, boolean useSnapshot) { ++ Preconditions.checkNotNull(blockPredicate, "blockPredicate"); ++ if (!this.isLoaded()) { ++ this.getWorld().getChunkAt(this.x, this.z); // Transient load for this tick ++ } ++ ChunkAccess chunk = this.getHandle(ChunkStatus.FULL); ++ ++ java.util.List entities = new java.util.ArrayList<>(); ++ ++ for (BlockPos position : chunk.blockEntities.keySet()) { ++ Block block = this.worldServer.getWorld().getBlockAt(position.getX(), position.getY(), position.getZ()); ++ if (blockPredicate.test(block)) { ++ entities.add(block.getState(useSnapshot)); ++ } ++ // Paper end + } + + return entities; diff --git a/patches/server/0228-Use-a-Queue-for-Queueing-Commands.patch b/patches/server/0228-Use-a-Queue-for-Queueing-Commands.patch deleted file mode 100644 index eee26554782e..000000000000 --- a/patches/server/0228-Use-a-Queue-for-Queueing-Commands.patch +++ /dev/null @@ -1,39 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Aikar -Date: Sun, 12 Aug 2018 02:33:39 -0400 -Subject: [PATCH] Use a Queue for Queueing Commands - -Lists are bad as Queues mmmkay. - -diff --git a/src/main/java/net/minecraft/server/dedicated/DedicatedServer.java b/src/main/java/net/minecraft/server/dedicated/DedicatedServer.java -index baf93b5d5883d0a5c360f1a475949804b7907636..b15cee6f21ff300b596922a8eed35a5f8a89fe22 100644 ---- a/src/main/java/net/minecraft/server/dedicated/DedicatedServer.java -+++ b/src/main/java/net/minecraft/server/dedicated/DedicatedServer.java -@@ -78,7 +78,7 @@ public class DedicatedServer extends MinecraftServer implements ServerInterface - static final Logger LOGGER = LogUtils.getLogger(); - private static final int CONVERSION_RETRY_DELAY_MS = 5000; - private static final int CONVERSION_RETRIES = 2; -- private final List consoleInput = Collections.synchronizedList(Lists.newArrayList()); -+ private final java.util.Queue serverCommandQueue = new java.util.concurrent.ConcurrentLinkedQueue<>(); // Paper - Perf: use a proper queue - @Nullable - private QueryThreadGs4 queryThreadGs4; - // private final RemoteControlCommandListener rconConsoleSource; // CraftBukkit - remove field -@@ -438,13 +438,15 @@ public class DedicatedServer extends MinecraftServer implements ServerInterface - } - - public void handleConsoleInput(String command, CommandSourceStack commandSource) { -- this.consoleInput.add(new ConsoleInput(command, commandSource)); -+ this.serverCommandQueue.add(new ConsoleInput(command, commandSource)); // Paper - Perf: use proper queue - } - - public void handleConsoleInputs() { - MinecraftTimings.serverCommandTimer.startTiming(); // Spigot -- while (!this.consoleInput.isEmpty()) { -- ConsoleInput servercommand = (ConsoleInput) this.consoleInput.remove(0); -+ // Paper start - Perf: use proper queue -+ ConsoleInput servercommand; -+ while ((servercommand = this.serverCommandQueue.poll()) != null) { -+ // Paper end - Perf: use proper queue - - // CraftBukkit start - ServerCommand for preprocessing - ServerCommandEvent event = new ServerCommandEvent(this.console, servercommand.msg); diff --git a/patches/server/0229-Ability-to-get-block-entities-from-a-chunk-without-s.patch b/patches/server/0229-Ability-to-get-block-entities-from-a-chunk-without-s.patch deleted file mode 100644 index 8ecb7597f58a..000000000000 --- a/patches/server/0229-Ability-to-get-block-entities-from-a-chunk-without-s.patch +++ /dev/null @@ -1,55 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Aikar -Date: Wed, 15 Aug 2018 01:16:34 -0400 -Subject: [PATCH] Ability to get block entities from a chunk without snapshots - - -diff --git a/src/main/java/org/bukkit/craftbukkit/CraftChunk.java b/src/main/java/org/bukkit/craftbukkit/CraftChunk.java -index e37dae711e7059834612ead5f4fcea9f28ad436f..f1d5c2d423dc015cc7720a4544370895f3cc644b 100644 ---- a/src/main/java/org/bukkit/craftbukkit/CraftChunk.java -+++ b/src/main/java/org/bukkit/craftbukkit/CraftChunk.java -@@ -174,6 +174,13 @@ public class CraftChunk implements Chunk { - - @Override - public BlockState[] getTileEntities() { -+ // Paper start -+ return getTileEntities(true); -+ } -+ -+ @Override -+ public BlockState[] getTileEntities(boolean useSnapshot) { -+ // Paper end - if (!this.isLoaded()) { - this.getWorld().getChunkAt(this.x, this.z); // Transient load for this tick - } -@@ -183,7 +190,29 @@ public class CraftChunk implements Chunk { - BlockState[] entities = new BlockState[chunk.blockEntities.size()]; - - for (BlockPos position : chunk.blockEntities.keySet()) { -- entities[index++] = this.worldServer.getWorld().getBlockAt(position.getX(), position.getY(), position.getZ()).getState(); -+ // Paper start -+ entities[index++] = this.worldServer.getWorld().getBlockAt(position.getX(), position.getY(), position.getZ()).getState(useSnapshot); -+ } -+ -+ return entities; -+ } -+ -+ @Override -+ public Collection getTileEntities(Predicate blockPredicate, boolean useSnapshot) { -+ Preconditions.checkNotNull(blockPredicate, "blockPredicate"); -+ if (!this.isLoaded()) { -+ this.getWorld().getChunkAt(this.x, this.z); // Transient load for this tick -+ } -+ ChunkAccess chunk = this.getHandle(ChunkStatus.FULL); -+ -+ java.util.List entities = new java.util.ArrayList<>(); -+ -+ for (BlockPos position : chunk.blockEntities.keySet()) { -+ Block block = this.worldServer.getWorld().getBlockAt(position.getX(), position.getY(), position.getZ()); -+ if (blockPredicate.test(block)) { -+ entities.add(block.getState(useSnapshot)); -+ } -+ // Paper end - } - - return entities; diff --git a/patches/server/0229-Optimize-BlockPosition-helper-methods.patch b/patches/server/0229-Optimize-BlockPosition-helper-methods.patch new file mode 100644 index 000000000000..5c3a739bd054 --- /dev/null +++ b/patches/server/0229-Optimize-BlockPosition-helper-methods.patch @@ -0,0 +1,107 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Spottedleaf +Date: Wed, 15 Aug 2018 12:05:12 -0700 +Subject: [PATCH] Optimize BlockPosition helper methods + + +diff --git a/src/main/java/net/minecraft/core/BlockPos.java b/src/main/java/net/minecraft/core/BlockPos.java +index 83e7c141d947f8f8096fed1da716560494bc5c62..8e00fd1a1fb0d73e800395f4b9fffdbdb6c6b5cb 100644 +--- a/src/main/java/net/minecraft/core/BlockPos.java ++++ b/src/main/java/net/minecraft/core/BlockPos.java +@@ -157,67 +157,84 @@ public class BlockPos extends Vec3i { + + @Override + public BlockPos above() { +- return this.relative(Direction.UP); ++ return new BlockPos(this.getX(), this.getY() + 1, this.getZ()); // Paper - Perf: Optimize BlockPosition + } + + @Override + public BlockPos above(int distance) { +- return this.relative(Direction.UP, distance); ++ return distance == 0 ? this.immutable() : new BlockPos(this.getX(), this.getY() + distance, this.getZ()); // Paper - Perf: Optimize BlockPosition + } + + @Override + public BlockPos below() { +- return this.relative(Direction.DOWN); ++ return new BlockPos(this.getX(), this.getY() - 1, this.getZ()); // Paper - Perf: Optimize BlockPosition + } + + @Override + public BlockPos below(int i) { +- return this.relative(Direction.DOWN, i); ++ return i == 0 ? this.immutable() : new BlockPos(this.getX(), this.getY() - i, this.getZ()); // Paper - Perf: Optimize BlockPosition + } + + @Override + public BlockPos north() { +- return this.relative(Direction.NORTH); ++ return new BlockPos(this.getX(), this.getY(), this.getZ() - 1); // Paper - Perf: Optimize BlockPosition + } + + @Override + public BlockPos north(int distance) { +- return this.relative(Direction.NORTH, distance); ++ return distance == 0 ? this.immutable() : new BlockPos(this.getX(), this.getY(), this.getZ() - distance); // Paper - Perf: Optimize BlockPosition + } + + @Override + public BlockPos south() { +- return this.relative(Direction.SOUTH); ++ return new BlockPos(this.getX(), this.getY(), this.getZ() + 1); // Paper - Perf: Optimize BlockPosition + } + + @Override + public BlockPos south(int distance) { +- return this.relative(Direction.SOUTH, distance); ++ return distance == 0 ? this.immutable() : new BlockPos(this.getX(), this.getY(), this.getZ() + distance); // Paper - Perf: Optimize BlockPosition + } + + @Override + public BlockPos west() { +- return this.relative(Direction.WEST); ++ return new BlockPos(this.getX() - 1, this.getY(), this.getZ()); // Paper - Perf: Optimize BlockPosition + } + + @Override + public BlockPos west(int distance) { +- return this.relative(Direction.WEST, distance); ++ return distance == 0 ? this.immutable() : new BlockPos(this.getX() - distance, this.getY(), this.getZ()); // Paper - Perf: Optimize BlockPosition + } + + @Override + public BlockPos east() { +- return this.relative(Direction.EAST); ++ return new BlockPos(this.getX() + 1, this.getY(), this.getZ()); // Paper - Perf: Optimize BlockPosition + } + + @Override + public BlockPos east(int distance) { +- return this.relative(Direction.EAST, distance); ++ return distance == 0 ? this.immutable() : new BlockPos(this.getX() + distance, this.getY(), this.getZ()); // Paper - Perf: Optimize BlockPosition + } + + @Override + public BlockPos relative(Direction direction) { ++ // Paper start - Perf: Optimize BlockPosition ++ switch(direction) { ++ case UP: ++ return new BlockPos(this.getX(), this.getY() + 1, this.getZ()); ++ case DOWN: ++ return new BlockPos(this.getX(), this.getY() - 1, this.getZ()); ++ case NORTH: ++ return new BlockPos(this.getX(), this.getY(), this.getZ() - 1); ++ case SOUTH: ++ return new BlockPos(this.getX(), this.getY(), this.getZ() + 1); ++ case WEST: ++ return new BlockPos(this.getX() - 1, this.getY(), this.getZ()); ++ case EAST: ++ return new BlockPos(this.getX() + 1, this.getY(), this.getZ()); ++ default: + return new BlockPos(this.getX() + direction.getStepX(), this.getY() + direction.getStepY(), this.getZ() + direction.getStepZ()); ++ } ++ // Paper end - Perf: Optimize BlockPosition + } + + @Override diff --git a/patches/server/0230-Optimize-BlockPosition-helper-methods.patch b/patches/server/0230-Optimize-BlockPosition-helper-methods.patch deleted file mode 100644 index b5729f894928..000000000000 --- a/patches/server/0230-Optimize-BlockPosition-helper-methods.patch +++ /dev/null @@ -1,107 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Spottedleaf -Date: Wed, 15 Aug 2018 12:05:12 -0700 -Subject: [PATCH] Optimize BlockPosition helper methods - - -diff --git a/src/main/java/net/minecraft/core/BlockPos.java b/src/main/java/net/minecraft/core/BlockPos.java -index 8994a381b05dcdd1163d2e7a0b63a8875b6063ed..73d7b5148e3a92c085b08303589827a6f0ae8d07 100644 ---- a/src/main/java/net/minecraft/core/BlockPos.java -+++ b/src/main/java/net/minecraft/core/BlockPos.java -@@ -157,67 +157,84 @@ public class BlockPos extends Vec3i { - - @Override - public BlockPos above() { -- return this.relative(Direction.UP); -+ return new BlockPos(this.getX(), this.getY() + 1, this.getZ()); // Paper - Perf: Optimize BlockPosition - } - - @Override - public BlockPos above(int distance) { -- return this.relative(Direction.UP, distance); -+ return distance == 0 ? this : new BlockPos(this.getX(), this.getY() + distance, this.getZ()); // Paper - Perf: Optimize BlockPosition - } - - @Override - public BlockPos below() { -- return this.relative(Direction.DOWN); -+ return new BlockPos(this.getX(), this.getY() - 1, this.getZ()); // Paper - Perf: Optimize BlockPosition - } - - @Override - public BlockPos below(int i) { -- return this.relative(Direction.DOWN, i); -+ return i == 0 ? this : new BlockPos(this.getX(), this.getY() - i, this.getZ()); // Paper - Perf: Optimize BlockPosition - } - - @Override - public BlockPos north() { -- return this.relative(Direction.NORTH); -+ return new BlockPos(this.getX(), this.getY(), this.getZ() - 1); // Paper - Perf: Optimize BlockPosition - } - - @Override - public BlockPos north(int distance) { -- return this.relative(Direction.NORTH, distance); -+ return distance == 0 ? this : new BlockPos(this.getX(), this.getY(), this.getZ() - distance); // Paper - Perf: Optimize BlockPosition - } - - @Override - public BlockPos south() { -- return this.relative(Direction.SOUTH); -+ return new BlockPos(this.getX(), this.getY(), this.getZ() + 1); // Paper - Perf: Optimize BlockPosition - } - - @Override - public BlockPos south(int distance) { -- return this.relative(Direction.SOUTH, distance); -+ return distance == 0 ? this : new BlockPos(this.getX(), this.getY(), this.getZ() + distance); // Paper - Perf: Optimize BlockPosition - } - - @Override - public BlockPos west() { -- return this.relative(Direction.WEST); -+ return new BlockPos(this.getX() - 1, this.getY(), this.getZ()); // Paper - Perf: Optimize BlockPosition - } - - @Override - public BlockPos west(int distance) { -- return this.relative(Direction.WEST, distance); -+ return distance == 0 ? this : new BlockPos(this.getX() - distance, this.getY(), this.getZ()); // Paper - Perf: Optimize BlockPosition - } - - @Override - public BlockPos east() { -- return this.relative(Direction.EAST); -+ return new BlockPos(this.getX() + 1, this.getY(), this.getZ()); // Paper - Perf: Optimize BlockPosition - } - - @Override - public BlockPos east(int distance) { -- return this.relative(Direction.EAST, distance); -+ return distance == 0 ? this : new BlockPos(this.getX() + distance, this.getY(), this.getZ()); // Paper - Perf: Optimize BlockPosition - } - - @Override - public BlockPos relative(Direction direction) { -+ // Paper start - Perf: Optimize BlockPosition -+ switch(direction) { -+ case UP: -+ return new BlockPos(this.getX(), this.getY() + 1, this.getZ()); -+ case DOWN: -+ return new BlockPos(this.getX(), this.getY() - 1, this.getZ()); -+ case NORTH: -+ return new BlockPos(this.getX(), this.getY(), this.getZ() - 1); -+ case SOUTH: -+ return new BlockPos(this.getX(), this.getY(), this.getZ() + 1); -+ case WEST: -+ return new BlockPos(this.getX() - 1, this.getY(), this.getZ()); -+ case EAST: -+ return new BlockPos(this.getX() + 1, this.getY(), this.getZ()); -+ default: - return new BlockPos(this.getX() + direction.getStepX(), this.getY() + direction.getStepY(), this.getZ() + direction.getStepZ()); -+ } -+ // Paper end - Perf: Optimize BlockPosition - } - - @Override diff --git a/patches/server/0231-Restore-vanilla-default-mob-spawn-range-and-water-an.patch b/patches/server/0230-Restore-vanilla-default-mob-spawn-range-and-water-an.patch similarity index 100% rename from patches/server/0231-Restore-vanilla-default-mob-spawn-range-and-water-an.patch rename to patches/server/0230-Restore-vanilla-default-mob-spawn-range-and-water-an.patch diff --git a/patches/server/0231-Slime-Pathfinder-Events.patch b/patches/server/0231-Slime-Pathfinder-Events.patch new file mode 100644 index 000000000000..b24fcc8b0b9b --- /dev/null +++ b/patches/server/0231-Slime-Pathfinder-Events.patch @@ -0,0 +1,154 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: BillyGalbreath +Date: Fri, 24 Aug 2018 08:18:42 -0500 +Subject: [PATCH] Slime Pathfinder Events + + +diff --git a/src/main/java/net/minecraft/world/entity/monster/Slime.java b/src/main/java/net/minecraft/world/entity/monster/Slime.java +index 131fce812eb0dcdebab02b529ed18e81eb1861eb..26f4db572dc6c25a9815b8f352d8829e252fa1a2 100644 +--- a/src/main/java/net/minecraft/world/entity/monster/Slime.java ++++ b/src/main/java/net/minecraft/world/entity/monster/Slime.java +@@ -119,6 +119,7 @@ public class Slime extends Mob implements Enemy { + @Override + public void addAdditionalSaveData(CompoundTag nbt) { + super.addAdditionalSaveData(nbt); ++ nbt.putBoolean("Paper.canWander", this.canWander); // Paper + nbt.putInt("Size", this.getSize() - 1); + nbt.putBoolean("wasOnGround", this.wasOnGround); + } +@@ -127,6 +128,11 @@ public class Slime extends Mob implements Enemy { + public void readAdditionalSaveData(CompoundTag nbt) { + this.setSize(nbt.getInt("Size") + 1, false); + super.readAdditionalSaveData(nbt); ++ // Paper start ++ if (nbt.contains("Paper.canWander")) { ++ this.canWander = nbt.getBoolean("Paper.canWander"); ++ } ++ // Paper end + this.wasOnGround = nbt.getBoolean("wasOnGround"); + } + +@@ -474,7 +480,7 @@ public class Slime extends Mob implements Enemy { + + @Override + public boolean canUse() { +- return (this.slime.isInWater() || this.slime.isInLava()) && this.slime.getMoveControl() instanceof Slime.SlimeMoveControl; ++ return (this.slime.isInWater() || this.slime.isInLava()) && this.slime.getMoveControl() instanceof Slime.SlimeMoveControl && this.slime.canWander && new com.destroystokyo.paper.event.entity.SlimeSwimEvent((org.bukkit.entity.Slime) this.slime.getBukkitEntity()).callEvent(); // Paper - Slime pathfinder events + } + + @Override +@@ -511,7 +517,15 @@ public class Slime extends Mob implements Enemy { + public boolean canUse() { + LivingEntity entityliving = this.slime.getTarget(); + +- return entityliving == null ? false : (!this.slime.canAttack(entityliving) ? false : this.slime.getMoveControl() instanceof Slime.SlimeMoveControl); ++ // Paper start - Slime pathfinder events ++ if (entityliving == null || !entityliving.isAlive()) { ++ return false; ++ } ++ if (!this.slime.canAttack(entityliving)) { ++ return false; ++ } ++ return this.slime.getMoveControl() instanceof Slime.SlimeMoveControl && this.slime.canWander && new com.destroystokyo.paper.event.entity.SlimeTargetLivingEntityEvent((org.bukkit.entity.Slime) this.slime.getBukkitEntity(), (org.bukkit.entity.LivingEntity) entityliving.getBukkitEntity()).callEvent(); ++ // Paper end - Slime pathfinder events + } + + @Override +@@ -524,7 +538,15 @@ public class Slime extends Mob implements Enemy { + public boolean canContinueToUse() { + LivingEntity entityliving = this.slime.getTarget(); + +- return entityliving == null ? false : (!this.slime.canAttack(entityliving) ? false : --this.growTiredTimer > 0); ++ // Paper start - Slime pathfinder events ++ if (entityliving == null || !entityliving.isAlive()) { ++ return false; ++ } ++ if (!this.slime.canAttack(entityliving)) { ++ return false; ++ } ++ return --this.growTiredTimer > 0 && this.slime.canWander && new com.destroystokyo.paper.event.entity.SlimeTargetLivingEntityEvent((org.bukkit.entity.Slime) this.slime.getBukkitEntity(), (org.bukkit.entity.LivingEntity) entityliving.getBukkitEntity()).callEvent(); ++ // Paper end - Slime pathfinder events + } + + @Override +@@ -547,6 +569,13 @@ public class Slime extends Mob implements Enemy { + } + + } ++ ++ // Paper start - Slime pathfinder events; clear timer and target when goal resets ++ public void stop() { ++ this.growTiredTimer = 0; ++ this.slime.setTarget(null); ++ } ++ // Paper end - Slime pathfinder events + } + + private static class SlimeRandomDirectionGoal extends Goal { +@@ -562,7 +591,7 @@ public class Slime extends Mob implements Enemy { + + @Override + public boolean canUse() { +- return this.slime.getTarget() == null && (this.slime.onGround() || this.slime.isInWater() || this.slime.isInLava() || this.slime.hasEffect(MobEffects.LEVITATION)) && this.slime.getMoveControl() instanceof Slime.SlimeMoveControl; ++ return this.slime.getTarget() == null && (this.slime.onGround() || this.slime.isInWater() || this.slime.isInLava() || this.slime.hasEffect(MobEffects.LEVITATION)) && this.slime.getMoveControl() instanceof Slime.SlimeMoveControl && this.slime.canWander; // Paper - Slime pathfinder events + } + + @Override +@@ -570,6 +599,11 @@ public class Slime extends Mob implements Enemy { + if (--this.nextRandomizeTime <= 0) { + this.nextRandomizeTime = this.adjustedTickDelay(40 + this.slime.getRandom().nextInt(60)); + this.chosenDegrees = (float) this.slime.getRandom().nextInt(360); ++ // Paper start - Slime pathfinder events ++ com.destroystokyo.paper.event.entity.SlimeChangeDirectionEvent event = new com.destroystokyo.paper.event.entity.SlimeChangeDirectionEvent((org.bukkit.entity.Slime) this.slime.getBukkitEntity(), this.chosenDegrees); ++ if (!this.slime.canWander || !event.callEvent()) return; ++ this.chosenDegrees = event.getNewYaw(); ++ // Paper end - Slime pathfinder events + } + + MoveControl controllermove = this.slime.getMoveControl(); +@@ -592,7 +626,7 @@ public class Slime extends Mob implements Enemy { + + @Override + public boolean canUse() { +- return !this.slime.isPassenger(); ++ return !this.slime.isPassenger() && this.slime.canWander && new com.destroystokyo.paper.event.entity.SlimeWanderEvent((org.bukkit.entity.Slime) this.slime.getBukkitEntity()).callEvent(); // Paper - Slime pathfinder events + } + + @Override +@@ -605,4 +639,15 @@ public class Slime extends Mob implements Enemy { + + } + } ++ ++ // Paper start - Slime pathfinder events ++ private boolean canWander = true; ++ public boolean canWander() { ++ return canWander; ++ } ++ ++ public void setWander(boolean canWander) { ++ this.canWander = canWander; ++ } ++ // Paper end - Slime pathfinder events + } +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftSlime.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftSlime.java +index 3d991d9d9388108ec6d137950913209d61d132e7..3d9b7c0e128ea05bec5600c774e9685998b71cac 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftSlime.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftSlime.java +@@ -28,4 +28,16 @@ public class CraftSlime extends CraftMob implements Slime, CraftEnemy { + public String toString() { + return "CraftSlime"; + } ++ ++ // Paper start ++ @Override ++ public boolean canWander() { ++ return getHandle().canWander(); ++ } ++ ++ @Override ++ public void setWander(boolean canWander) { ++ getHandle().setWander(canWander); ++ } ++ // Paper end + } diff --git a/patches/server/0232-Configurable-speed-for-water-flowing-over-lava.patch b/patches/server/0232-Configurable-speed-for-water-flowing-over-lava.patch new file mode 100644 index 000000000000..5b45a51e5ff2 --- /dev/null +++ b/patches/server/0232-Configurable-speed-for-water-flowing-over-lava.patch @@ -0,0 +1,52 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Byteflux +Date: Wed, 8 Aug 2018 16:33:21 -0600 +Subject: [PATCH] Configurable speed for water flowing over lava + + +diff --git a/src/main/java/net/minecraft/world/level/block/LiquidBlock.java b/src/main/java/net/minecraft/world/level/block/LiquidBlock.java +index ac54fd2abb3334c16cba844aee38d7a6797046f3..a2d023ff011f71f80032f02430a53d6a08a23623 100644 +--- a/src/main/java/net/minecraft/world/level/block/LiquidBlock.java ++++ b/src/main/java/net/minecraft/world/level/block/LiquidBlock.java +@@ -141,11 +141,31 @@ public class LiquidBlock extends Block implements BucketPickup { + @Override + protected void onPlace(BlockState state, Level world, BlockPos pos, BlockState oldState, boolean notify) { + if (this.shouldSpreadLiquid(world, pos, state)) { +- world.scheduleTick(pos, state.getFluidState().getType(), this.fluid.getTickDelay(world)); ++ world.scheduleTick(pos, state.getFluidState().getType(), this.getFlowSpeed(world, pos)); // Paper - Configurable speed for water flowing over lava + } + + } + ++ // Paper start - Configurable speed for water flowing over lava ++ public int getFlowSpeed(Level world, BlockPos blockposition) { ++ if (net.minecraft.core.registries.BuiltInRegistries.FLUID.wrapAsHolder(this.fluid).is(FluidTags.WATER)) { ++ if ( ++ isLava(world, blockposition.north(1)) || ++ isLava(world, blockposition.south(1)) || ++ isLava(world, blockposition.west(1)) || ++ isLava(world, blockposition.east(1)) ++ ) { ++ return world.paperConfig().environment.waterOverLavaFlowSpeed; ++ } ++ } ++ return this.fluid.getTickDelay(world); ++ } ++ private static boolean isLava(Level world, BlockPos blockPos) { ++ final FluidState fluidState = world.getFluidIfLoaded(blockPos); ++ return fluidState != null && fluidState.is(FluidTags.LAVA); ++ } ++ // Paper end - Configurable speed for water flowing over lava ++ + @Override + protected BlockState updateShape(BlockState state, LevelReader world, ScheduledTickAccess tickView, BlockPos pos, Direction direction, BlockPos neighborPos, BlockState neighborState, RandomSource random) { + if (state.getFluidState().isSource() || neighborState.getFluidState().isSource()) { +@@ -158,7 +178,7 @@ public class LiquidBlock extends Block implements BucketPickup { + @Override + protected void neighborChanged(BlockState state, Level world, BlockPos pos, Block sourceBlock, @Nullable Orientation wireOrientation, boolean notify) { + if (this.shouldSpreadLiquid(world, pos, state)) { +- world.scheduleTick(pos, state.getFluidState().getType(), this.fluid.getTickDelay(world)); ++ world.scheduleTick(pos, state.getFluidState().getType(), this.getFlowSpeed(world, pos)); // Paper - Configurable speed for water flowing over lava + } + + } diff --git a/patches/server/0232-Slime-Pathfinder-Events.patch b/patches/server/0232-Slime-Pathfinder-Events.patch deleted file mode 100644 index 3cbd43d550f3..000000000000 --- a/patches/server/0232-Slime-Pathfinder-Events.patch +++ /dev/null @@ -1,154 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: BillyGalbreath -Date: Fri, 24 Aug 2018 08:18:42 -0500 -Subject: [PATCH] Slime Pathfinder Events - - -diff --git a/src/main/java/net/minecraft/world/entity/monster/Slime.java b/src/main/java/net/minecraft/world/entity/monster/Slime.java -index b1f7ea02e533660322675e1bddb070f0a41084f2..ef27b0af849f071f79271689783b7a557e6d660a 100644 ---- a/src/main/java/net/minecraft/world/entity/monster/Slime.java -+++ b/src/main/java/net/minecraft/world/entity/monster/Slime.java -@@ -117,6 +117,7 @@ public class Slime extends Mob implements Enemy { - @Override - public void addAdditionalSaveData(CompoundTag nbt) { - super.addAdditionalSaveData(nbt); -+ nbt.putBoolean("Paper.canWander", this.canWander); // Paper - nbt.putInt("Size", this.getSize() - 1); - nbt.putBoolean("wasOnGround", this.wasOnGround); - } -@@ -125,6 +126,11 @@ public class Slime extends Mob implements Enemy { - public void readAdditionalSaveData(CompoundTag nbt) { - this.setSize(nbt.getInt("Size") + 1, false); - super.readAdditionalSaveData(nbt); -+ // Paper start -+ if (nbt.contains("Paper.canWander")) { -+ this.canWander = nbt.getBoolean("Paper.canWander"); -+ } -+ // Paper end - this.wasOnGround = nbt.getBoolean("wasOnGround"); - } - -@@ -483,7 +489,7 @@ public class Slime extends Mob implements Enemy { - - @Override - public boolean canUse() { -- return (this.slime.isInWater() || this.slime.isInLava()) && this.slime.getMoveControl() instanceof Slime.SlimeMoveControl; -+ return (this.slime.isInWater() || this.slime.isInLava()) && this.slime.getMoveControl() instanceof Slime.SlimeMoveControl && this.slime.canWander && new com.destroystokyo.paper.event.entity.SlimeSwimEvent((org.bukkit.entity.Slime) this.slime.getBukkitEntity()).callEvent(); // Paper - Slime pathfinder events - } - - @Override -@@ -520,7 +526,15 @@ public class Slime extends Mob implements Enemy { - public boolean canUse() { - LivingEntity entityliving = this.slime.getTarget(); - -- return entityliving == null ? false : (!this.slime.canAttack(entityliving) ? false : this.slime.getMoveControl() instanceof Slime.SlimeMoveControl); -+ // Paper start - Slime pathfinder events -+ if (entityliving == null || !entityliving.isAlive()) { -+ return false; -+ } -+ if (!this.slime.canAttack(entityliving)) { -+ return false; -+ } -+ return this.slime.getMoveControl() instanceof Slime.SlimeMoveControl && this.slime.canWander && new com.destroystokyo.paper.event.entity.SlimeTargetLivingEntityEvent((org.bukkit.entity.Slime) this.slime.getBukkitEntity(), (org.bukkit.entity.LivingEntity) entityliving.getBukkitEntity()).callEvent(); -+ // Paper end - Slime pathfinder events - } - - @Override -@@ -533,7 +547,15 @@ public class Slime extends Mob implements Enemy { - public boolean canContinueToUse() { - LivingEntity entityliving = this.slime.getTarget(); - -- return entityliving == null ? false : (!this.slime.canAttack(entityliving) ? false : --this.growTiredTimer > 0); -+ // Paper start - Slime pathfinder events -+ if (entityliving == null || !entityliving.isAlive()) { -+ return false; -+ } -+ if (!this.slime.canAttack(entityliving)) { -+ return false; -+ } -+ return --this.growTiredTimer > 0 && this.slime.canWander && new com.destroystokyo.paper.event.entity.SlimeTargetLivingEntityEvent((org.bukkit.entity.Slime) this.slime.getBukkitEntity(), (org.bukkit.entity.LivingEntity) entityliving.getBukkitEntity()).callEvent(); -+ // Paper end - Slime pathfinder events - } - - @Override -@@ -556,6 +578,13 @@ public class Slime extends Mob implements Enemy { - } - - } -+ -+ // Paper start - Slime pathfinder events; clear timer and target when goal resets -+ public void stop() { -+ this.growTiredTimer = 0; -+ this.slime.setTarget(null); -+ } -+ // Paper end - Slime pathfinder events - } - - private static class SlimeRandomDirectionGoal extends Goal { -@@ -571,7 +600,7 @@ public class Slime extends Mob implements Enemy { - - @Override - public boolean canUse() { -- return this.slime.getTarget() == null && (this.slime.onGround() || this.slime.isInWater() || this.slime.isInLava() || this.slime.hasEffect(MobEffects.LEVITATION)) && this.slime.getMoveControl() instanceof Slime.SlimeMoveControl; -+ return this.slime.getTarget() == null && (this.slime.onGround() || this.slime.isInWater() || this.slime.isInLava() || this.slime.hasEffect(MobEffects.LEVITATION)) && this.slime.getMoveControl() instanceof Slime.SlimeMoveControl && this.slime.canWander; // Paper - Slime pathfinder events - } - - @Override -@@ -579,6 +608,11 @@ public class Slime extends Mob implements Enemy { - if (--this.nextRandomizeTime <= 0) { - this.nextRandomizeTime = this.adjustedTickDelay(40 + this.slime.getRandom().nextInt(60)); - this.chosenDegrees = (float) this.slime.getRandom().nextInt(360); -+ // Paper start - Slime pathfinder events -+ com.destroystokyo.paper.event.entity.SlimeChangeDirectionEvent event = new com.destroystokyo.paper.event.entity.SlimeChangeDirectionEvent((org.bukkit.entity.Slime) this.slime.getBukkitEntity(), this.chosenDegrees); -+ if (!this.slime.canWander || !event.callEvent()) return; -+ this.chosenDegrees = event.getNewYaw(); -+ // Paper end - Slime pathfinder events - } - - MoveControl controllermove = this.slime.getMoveControl(); -@@ -601,7 +635,7 @@ public class Slime extends Mob implements Enemy { - - @Override - public boolean canUse() { -- return !this.slime.isPassenger(); -+ return !this.slime.isPassenger() && this.slime.canWander && new com.destroystokyo.paper.event.entity.SlimeWanderEvent((org.bukkit.entity.Slime) this.slime.getBukkitEntity()).callEvent(); // Paper - Slime pathfinder events - } - - @Override -@@ -614,4 +648,15 @@ public class Slime extends Mob implements Enemy { - - } - } -+ -+ // Paper start - Slime pathfinder events -+ private boolean canWander = true; -+ public boolean canWander() { -+ return canWander; -+ } -+ -+ public void setWander(boolean canWander) { -+ this.canWander = canWander; -+ } -+ // Paper end - Slime pathfinder events - } -diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftSlime.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftSlime.java -index 3d991d9d9388108ec6d137950913209d61d132e7..3d9b7c0e128ea05bec5600c774e9685998b71cac 100644 ---- a/src/main/java/org/bukkit/craftbukkit/entity/CraftSlime.java -+++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftSlime.java -@@ -28,4 +28,16 @@ public class CraftSlime extends CraftMob implements Slime, CraftEnemy { - public String toString() { - return "CraftSlime"; - } -+ -+ // Paper start -+ @Override -+ public boolean canWander() { -+ return getHandle().canWander(); -+ } -+ -+ @Override -+ public void setWander(boolean canWander) { -+ getHandle().setWander(canWander); -+ } -+ // Paper end - } diff --git a/patches/server/0233-Configurable-speed-for-water-flowing-over-lava.patch b/patches/server/0233-Configurable-speed-for-water-flowing-over-lava.patch deleted file mode 100644 index fafdd583ab15..000000000000 --- a/patches/server/0233-Configurable-speed-for-water-flowing-over-lava.patch +++ /dev/null @@ -1,52 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Byteflux -Date: Wed, 8 Aug 2018 16:33:21 -0600 -Subject: [PATCH] Configurable speed for water flowing over lava - - -diff --git a/src/main/java/net/minecraft/world/level/block/LiquidBlock.java b/src/main/java/net/minecraft/world/level/block/LiquidBlock.java -index 6d24989965e5215c1e256444a868633cf2772aa3..84623c632d8c2f0fa7ec939c711316d757117d23 100644 ---- a/src/main/java/net/minecraft/world/level/block/LiquidBlock.java -+++ b/src/main/java/net/minecraft/world/level/block/LiquidBlock.java -@@ -138,11 +138,31 @@ public class LiquidBlock extends Block implements BucketPickup { - @Override - protected void onPlace(BlockState state, Level world, BlockPos pos, BlockState oldState, boolean notify) { - if (this.shouldSpreadLiquid(world, pos, state)) { -- world.scheduleTick(pos, state.getFluidState().getType(), this.fluid.getTickDelay(world)); -+ world.scheduleTick(pos, state.getFluidState().getType(), this.getFlowSpeed(world, pos)); // Paper - Configurable speed for water flowing over lava - } - - } - -+ // Paper start - Configurable speed for water flowing over lava -+ public int getFlowSpeed(Level world, BlockPos blockposition) { -+ if (net.minecraft.core.registries.BuiltInRegistries.FLUID.wrapAsHolder(this.fluid).is(FluidTags.WATER)) { -+ if ( -+ isLava(world, blockposition.north(1)) || -+ isLava(world, blockposition.south(1)) || -+ isLava(world, blockposition.west(1)) || -+ isLava(world, blockposition.east(1)) -+ ) { -+ return world.paperConfig().environment.waterOverLavaFlowSpeed; -+ } -+ } -+ return this.fluid.getTickDelay(world); -+ } -+ private static boolean isLava(Level world, BlockPos blockPos) { -+ final FluidState fluidState = world.getFluidIfLoaded(blockPos); -+ return fluidState != null && fluidState.is(FluidTags.LAVA); -+ } -+ // Paper end - Configurable speed for water flowing over lava -+ - @Override - protected BlockState updateShape(BlockState state, Direction direction, BlockState neighborState, LevelAccessor world, BlockPos pos, BlockPos neighborPos) { - if (state.getFluidState().isSource() || neighborState.getFluidState().isSource()) { -@@ -155,7 +175,7 @@ public class LiquidBlock extends Block implements BucketPickup { - @Override - protected void neighborChanged(BlockState state, Level world, BlockPos pos, Block sourceBlock, BlockPos sourcePos, boolean notify) { - if (this.shouldSpreadLiquid(world, pos, state)) { -- world.scheduleTick(pos, state.getFluidState().getType(), this.fluid.getTickDelay(world)); -+ world.scheduleTick(pos, state.getFluidState().getType(), this.getFlowSpeed(world, pos)); // Paper - Configurable speed for water flowing over lava - } - - } diff --git a/patches/server/0233-Optimize-CraftBlockData-Creation.patch b/patches/server/0233-Optimize-CraftBlockData-Creation.patch new file mode 100644 index 000000000000..03c51206ce25 --- /dev/null +++ b/patches/server/0233-Optimize-CraftBlockData-Creation.patch @@ -0,0 +1,49 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: miclebrick +Date: Thu, 23 Aug 2018 11:45:32 -0400 +Subject: [PATCH] Optimize CraftBlockData Creation + +Avoids a hashmap lookup by cacheing a reference to the CraftBlockData +and cloning it when one is needed. + +diff --git a/src/main/java/net/minecraft/world/level/block/state/BlockBehaviour.java b/src/main/java/net/minecraft/world/level/block/state/BlockBehaviour.java +index 0bae4e8d1e9fcc4608b3ef1c981c65f3b03de22b..83a3c877f2969549ea154ad86687e96fdf34d881 100644 +--- a/src/main/java/net/minecraft/world/level/block/state/BlockBehaviour.java ++++ b/src/main/java/net/minecraft/world/level/block/state/BlockBehaviour.java +@@ -862,6 +862,14 @@ public abstract class BlockBehaviour implements FeatureElement { + this.instrument = blockbase_info.instrument; + this.replaceable = blockbase_info.replaceable; + } ++ // Paper start - Perf: impl cached craft block data, lazy load to fix issue with loading at the wrong time ++ private org.bukkit.craftbukkit.block.data.CraftBlockData cachedCraftBlockData; ++ ++ public org.bukkit.craftbukkit.block.data.CraftBlockData createCraftBlockData() { ++ if (cachedCraftBlockData == null) cachedCraftBlockData = org.bukkit.craftbukkit.block.data.CraftBlockData.createData(asState()); ++ return (org.bukkit.craftbukkit.block.data.CraftBlockData) cachedCraftBlockData.clone(); ++ } ++ // Paper end - Perf: impl cached craft block data, lazy load to fix issue with loading at the wrong time + + private boolean calculateSolid() { + if (((Block) this.owner).properties.forceSolidOn) { +diff --git a/src/main/java/org/bukkit/craftbukkit/block/data/CraftBlockData.java b/src/main/java/org/bukkit/craftbukkit/block/data/CraftBlockData.java +index da39a252be9b77c81c07d6b67da9f2380f9445a8..c53dbcfde62ae8e2f019e854c336ce4a60346dc9 100644 +--- a/src/main/java/org/bukkit/craftbukkit/block/data/CraftBlockData.java ++++ b/src/main/java/org/bukkit/craftbukkit/block/data/CraftBlockData.java +@@ -574,7 +574,17 @@ public class CraftBlockData implements BlockData { + return craft; + } + ++ // Paper start - optimize creating BlockData to not need a map lookup ++ static { ++ // Initialize cached data for all IBlockData instances after registration ++ Block.BLOCK_STATE_REGISTRY.iterator().forEachRemaining(net.minecraft.world.level.block.state.BlockState::createCraftBlockData); ++ } + public static CraftBlockData fromData(net.minecraft.world.level.block.state.BlockState data) { ++ return data.createCraftBlockData(); ++ } ++ ++ public static CraftBlockData createData(net.minecraft.world.level.block.state.BlockState data) { ++ // Paper end + return CraftBlockData.MAP.getOrDefault(data.getBlock().getClass(), CraftBlockData::new).apply(data); + } + diff --git a/patches/server/0234-Optimize-CraftBlockData-Creation.patch b/patches/server/0234-Optimize-CraftBlockData-Creation.patch deleted file mode 100644 index 501971aa29fe..000000000000 --- a/patches/server/0234-Optimize-CraftBlockData-Creation.patch +++ /dev/null @@ -1,49 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: miclebrick -Date: Thu, 23 Aug 2018 11:45:32 -0400 -Subject: [PATCH] Optimize CraftBlockData Creation - -Avoids a hashmap lookup by cacheing a reference to the CraftBlockData -and cloning it when one is needed. - -diff --git a/src/main/java/net/minecraft/world/level/block/state/BlockBehaviour.java b/src/main/java/net/minecraft/world/level/block/state/BlockBehaviour.java -index 59fcaca90b67c03e1a6799e58061dbae3b1f1ceb..46dd499c2023ec482ae7204d2894fb4100d9233b 100644 ---- a/src/main/java/net/minecraft/world/level/block/state/BlockBehaviour.java -+++ b/src/main/java/net/minecraft/world/level/block/state/BlockBehaviour.java -@@ -820,6 +820,14 @@ public abstract class BlockBehaviour implements FeatureElement { - this.instrument = blockbase_info.instrument; - this.replaceable = blockbase_info.replaceable; - } -+ // Paper start - Perf: impl cached craft block data, lazy load to fix issue with loading at the wrong time -+ private org.bukkit.craftbukkit.block.data.CraftBlockData cachedCraftBlockData; -+ -+ public org.bukkit.craftbukkit.block.data.CraftBlockData createCraftBlockData() { -+ if (cachedCraftBlockData == null) cachedCraftBlockData = org.bukkit.craftbukkit.block.data.CraftBlockData.createData(asState()); -+ return (org.bukkit.craftbukkit.block.data.CraftBlockData) cachedCraftBlockData.clone(); -+ } -+ // Paper end - Perf: impl cached craft block data, lazy load to fix issue with loading at the wrong time - - private boolean calculateSolid() { - if (((Block) this.owner).properties.forceSolidOn) { -diff --git a/src/main/java/org/bukkit/craftbukkit/block/data/CraftBlockData.java b/src/main/java/org/bukkit/craftbukkit/block/data/CraftBlockData.java -index 6ff5da6fac47f5eb6e574665110c0f2649b842d3..c1c5750dd2e4a9af1a115996a87eaaa1ea552c74 100644 ---- a/src/main/java/org/bukkit/craftbukkit/block/data/CraftBlockData.java -+++ b/src/main/java/org/bukkit/craftbukkit/block/data/CraftBlockData.java -@@ -570,7 +570,17 @@ public class CraftBlockData implements BlockData { - return craft; - } - -+ // Paper start - optimize creating BlockData to not need a map lookup -+ static { -+ // Initialize cached data for all IBlockData instances after registration -+ Block.BLOCK_STATE_REGISTRY.iterator().forEachRemaining(net.minecraft.world.level.block.state.BlockState::createCraftBlockData); -+ } - public static CraftBlockData fromData(net.minecraft.world.level.block.state.BlockState data) { -+ return data.createCraftBlockData(); -+ } -+ -+ public static CraftBlockData createData(net.minecraft.world.level.block.state.BlockState data) { -+ // Paper end - return CraftBlockData.MAP.getOrDefault(data.getBlock().getClass(), CraftBlockData::new).apply(data); - } - diff --git a/patches/server/0234-Optimize-MappedRegistry.patch b/patches/server/0234-Optimize-MappedRegistry.patch new file mode 100644 index 000000000000..967e274d5b6f --- /dev/null +++ b/patches/server/0234-Optimize-MappedRegistry.patch @@ -0,0 +1,30 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Aikar +Date: Sun, 26 Aug 2018 20:49:50 -0400 +Subject: [PATCH] Optimize MappedRegistry + +Use larger initial sizes to increase bucket capacity on the BiMap + +BiMap.get was seen to be using a good bit of CPU time. + +diff --git a/src/main/java/net/minecraft/core/MappedRegistry.java b/src/main/java/net/minecraft/core/MappedRegistry.java +index ecfb5efd6256ab212c28fa5eaa7005d6c7f80125..71e04e5c1bc0722abf8ca2e0738bd60b6d7ae21c 100644 +--- a/src/main/java/net/minecraft/core/MappedRegistry.java ++++ b/src/main/java/net/minecraft/core/MappedRegistry.java +@@ -33,11 +33,11 @@ import net.minecraft.util.RandomSource; + public class MappedRegistry implements WritableRegistry { + private final ResourceKey> key; + private final ObjectList> byId = new ObjectArrayList<>(256); +- private final Reference2IntMap toId = Util.make(new Reference2IntOpenHashMap<>(), map -> map.defaultReturnValue(-1)); +- private final Map> byLocation = new HashMap<>(); +- private final Map, Holder.Reference> byKey = new HashMap<>(); +- private final Map> byValue = new IdentityHashMap<>(); +- private final Map, RegistrationInfo> registrationInfos = new IdentityHashMap<>(); ++ private final Reference2IntMap toId = Util.make(new Reference2IntOpenHashMap<>(2048), map -> map.defaultReturnValue(-1)); // Paper - Perf: Use bigger expected size to reduce collisions ++ private final Map> byLocation = new HashMap<>(2048); // Paper - Perf: Use bigger expected size to reduce collisions ++ private final Map, Holder.Reference> byKey = new HashMap<>(2048); // Paper - Perf: Use bigger expected size to reduce collisions ++ private final Map> byValue = new IdentityHashMap<>(2048); // Paper - Perf: Use bigger expected size to reduce collisions ++ private final Map, RegistrationInfo> registrationInfos = new IdentityHashMap<>(2048); // Paper - Perf: Use bigger expected size to reduce collisions + private Lifecycle registryLifecycle; + private final Map, HolderSet.Named> frozenTags = new IdentityHashMap<>(); + MappedRegistry.TagSet allTags = MappedRegistry.TagSet.unbound(); diff --git a/patches/server/0235-Add-PhantomPreSpawnEvent.patch b/patches/server/0235-Add-PhantomPreSpawnEvent.patch new file mode 100644 index 000000000000..3398693ce432 --- /dev/null +++ b/patches/server/0235-Add-PhantomPreSpawnEvent.patch @@ -0,0 +1,92 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: BillyGalbreath +Date: Sat, 25 Aug 2018 19:56:51 -0500 +Subject: [PATCH] Add PhantomPreSpawnEvent + + +diff --git a/src/main/java/net/minecraft/world/entity/monster/Phantom.java b/src/main/java/net/minecraft/world/entity/monster/Phantom.java +index d9d374e18b1cacf0b04e6e02f3a94b145f4052fb..748f07c7036fe5955d76e28c4e7d23f8c0235d5f 100644 +--- a/src/main/java/net/minecraft/world/entity/monster/Phantom.java ++++ b/src/main/java/net/minecraft/world/entity/monster/Phantom.java +@@ -161,6 +161,11 @@ public class Phantom extends FlyingMob implements Enemy { + } + + this.setPhantomSize(nbt.getInt("Size")); ++ // Paper start ++ if (nbt.hasUUID("Paper.SpawningEntity")) { ++ this.spawningEntity = nbt.getUUID("Paper.SpawningEntity"); ++ } ++ // Paper end + } + + @Override +@@ -170,6 +175,11 @@ public class Phantom extends FlyingMob implements Enemy { + nbt.putInt("AY", this.anchorPoint.getY()); + nbt.putInt("AZ", this.anchorPoint.getZ()); + nbt.putInt("Size", this.getPhantomSize()); ++ // Paper start ++ if (this.spawningEntity != null) { ++ nbt.putUUID("Paper.SpawningEntity", this.spawningEntity); ++ } ++ // Paper end + } + + @Override +@@ -219,6 +229,17 @@ public class Phantom extends FlyingMob implements Enemy { + return predicate.test(world, this, target); + } + ++ // Paper start ++ @Nullable ++ java.util.UUID spawningEntity; ++ ++ @Nullable ++ public java.util.UUID getSpawningEntity() { ++ return this.spawningEntity; ++ } ++ public void setSpawningEntity(java.util.UUID entity) { this.spawningEntity = entity; } ++ // Paper end ++ + private static enum AttackPhase { + + CIRCLE, SWOOP; +diff --git a/src/main/java/net/minecraft/world/level/levelgen/PhantomSpawner.java b/src/main/java/net/minecraft/world/level/levelgen/PhantomSpawner.java +index 1ef81620541c97dce0fca4d85951202e9532e733..499b124f905ffa8e375efa354a3f2240997ddea5 100644 +--- a/src/main/java/net/minecraft/world/level/levelgen/PhantomSpawner.java ++++ b/src/main/java/net/minecraft/world/level/levelgen/PhantomSpawner.java +@@ -69,9 +69,19 @@ public class PhantomSpawner implements CustomSpawner { + int k = 1 + randomsource.nextInt(difficultydamagescaler.getDifficulty().getId() + 1); + + for (int l = 0; l < k; ++l) { ++ // Paper start - PhantomPreSpawnEvent ++ com.destroystokyo.paper.event.entity.PhantomPreSpawnEvent event = new com.destroystokyo.paper.event.entity.PhantomPreSpawnEvent(io.papermc.paper.util.MCUtil.toLocation(world, blockposition1), entityplayer.getBukkitEntity(), org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason.NATURAL); ++ if (!event.callEvent()) { ++ if (event.shouldAbortSpawn()) { ++ break; ++ } ++ continue; ++ } ++ // Paper end - PhantomPreSpawnEvent + Phantom entityphantom = (Phantom) EntityType.PHANTOM.create(world, EntitySpawnReason.NATURAL); + + if (entityphantom != null) { ++ entityphantom.setSpawningEntity(entityplayer.getUUID()); // Paper - PhantomPreSpawnEvent + entityphantom.moveTo(blockposition1, 0.0F, 0.0F); + groupdataentity = entityphantom.finalizeSpawn(world, difficultydamagescaler, EntitySpawnReason.NATURAL, groupdataentity); + world.addFreshEntityWithPassengers(entityphantom, org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason.NATURAL); // CraftBukkit +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftPhantom.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftPhantom.java +index 0359c161448941f1b9fdac545a5c47a68f19b760..305a635b049741ac5e2670060c6818cb2c07e5ab 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftPhantom.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftPhantom.java +@@ -28,4 +28,11 @@ public class CraftPhantom extends CraftFlying implements Phantom, CraftEnemy { + public String toString() { + return "CraftPhantom"; + } ++ ++ // Paper start ++ @Override ++ public java.util.UUID getSpawningEntity() { ++ return getHandle().getSpawningEntity(); ++ } ++ // Paper end + } diff --git a/patches/server/0235-Optimize-MappedRegistry.patch b/patches/server/0235-Optimize-MappedRegistry.patch deleted file mode 100644 index 17c68f31d757..000000000000 --- a/patches/server/0235-Optimize-MappedRegistry.patch +++ /dev/null @@ -1,30 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Aikar -Date: Sun, 26 Aug 2018 20:49:50 -0400 -Subject: [PATCH] Optimize MappedRegistry - -Use larger initial sizes to increase bucket capacity on the BiMap - -BiMap.get was seen to be using a good bit of CPU time. - -diff --git a/src/main/java/net/minecraft/core/MappedRegistry.java b/src/main/java/net/minecraft/core/MappedRegistry.java -index 1dcbde18bd9c462cca48887b904a9c43261e1854..edbbafd1705345282e5e6251eb71bfde5793b7d4 100644 ---- a/src/main/java/net/minecraft/core/MappedRegistry.java -+++ b/src/main/java/net/minecraft/core/MappedRegistry.java -@@ -35,11 +35,11 @@ public class MappedRegistry implements WritableRegistry { - private static final Logger LOGGER = LogUtils.getLogger(); - final ResourceKey> key; - private final ObjectList> byId = new ObjectArrayList<>(256); -- private final Reference2IntMap toId = Util.make(new Reference2IntOpenHashMap<>(), map -> map.defaultReturnValue(-1)); -- private final Map> byLocation = new HashMap<>(); -- private final Map, Holder.Reference> byKey = new HashMap<>(); -- private final Map> byValue = new IdentityHashMap<>(); -- private final Map, RegistrationInfo> registrationInfos = new IdentityHashMap<>(); -+ private final Reference2IntMap toId = Util.make(new Reference2IntOpenHashMap<>(2048), map -> map.defaultReturnValue(-1)); // Paper - Perf: Use bigger expected size to reduce collisions -+ private final Map> byLocation = new HashMap<>(2048); // Paper - Perf: Use bigger expected size to reduce collisions -+ private final Map, Holder.Reference> byKey = new HashMap<>(2048); // Paper - Perf: Use bigger expected size to reduce collisions -+ private final Map> byValue = new IdentityHashMap<>(2048); // Paper - Perf: Use bigger expected size to reduce collisions -+ private final Map, RegistrationInfo> registrationInfos = new IdentityHashMap<>(2048); // Paper - Perf: Use bigger expected size to reduce collisions - private Lifecycle registryLifecycle; - private volatile Map, HolderSet.Named> tags = new IdentityHashMap<>(); - private boolean frozen; diff --git a/patches/server/0236-Add-More-Creeper-API.patch b/patches/server/0236-Add-More-Creeper-API.patch new file mode 100644 index 000000000000..04605aa213e3 --- /dev/null +++ b/patches/server/0236-Add-More-Creeper-API.patch @@ -0,0 +1,60 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: BillyGalbreath +Date: Fri, 24 Aug 2018 11:50:26 -0500 +Subject: [PATCH] Add More Creeper API + + +diff --git a/src/main/java/net/minecraft/world/entity/monster/Creeper.java b/src/main/java/net/minecraft/world/entity/monster/Creeper.java +index 0552cc23391ec305754339d000630ccab0729100..7a18dc59aed5294cd442994aa2d34ea00b877f46 100644 +--- a/src/main/java/net/minecraft/world/entity/monster/Creeper.java ++++ b/src/main/java/net/minecraft/world/entity/monster/Creeper.java +@@ -133,7 +133,7 @@ public class Creeper extends Monster { + } + + if (nbt.getBoolean("ignited")) { +- this.ignite(); ++ this.entityData.set(Creeper.DATA_IS_IGNITED, true); // Paper - set directly to avoid firing event + } + + } +@@ -315,7 +315,18 @@ public class Creeper extends Monster { + } + + public void ignite() { +- this.entityData.set(Creeper.DATA_IS_IGNITED, true); ++ // Paper start - CreeperIgniteEvent ++ setIgnited(true); ++ } ++ ++ public void setIgnited(boolean ignited) { ++ if (isIgnited() != ignited) { ++ com.destroystokyo.paper.event.entity.CreeperIgniteEvent event = new com.destroystokyo.paper.event.entity.CreeperIgniteEvent((org.bukkit.entity.Creeper) getBukkitEntity(), ignited); ++ if (event.callEvent()) { ++ this.entityData.set(Creeper.DATA_IS_IGNITED, event.isIgnited()); ++ } ++ } ++ // Paper end - CreeperIgniteEvent + } + + public boolean canDropMobsSkull() { +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftCreeper.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftCreeper.java +index 15bd44f8bd08b55b4da7fbbfdddc7ec3db454d89..42dd26b9170f7d217d73f725a6b8440b45ac2190 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftCreeper.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftCreeper.java +@@ -101,4 +101,16 @@ public class CraftCreeper extends CraftMonster implements Creeper { + public String toString() { + return "CraftCreeper"; + } ++ ++ // Paper start ++ @Override ++ public void setIgnited(boolean ignited) { ++ getHandle().setIgnited(ignited); ++ } ++ ++ @Override ++ public boolean isIgnited() { ++ return getHandle().isIgnited(); ++ } ++ // Paper end + } diff --git a/patches/server/0236-Add-PhantomPreSpawnEvent.patch b/patches/server/0236-Add-PhantomPreSpawnEvent.patch deleted file mode 100644 index 42c7ae9133a3..000000000000 --- a/patches/server/0236-Add-PhantomPreSpawnEvent.patch +++ /dev/null @@ -1,92 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: BillyGalbreath -Date: Sat, 25 Aug 2018 19:56:51 -0500 -Subject: [PATCH] Add PhantomPreSpawnEvent - - -diff --git a/src/main/java/net/minecraft/world/entity/monster/Phantom.java b/src/main/java/net/minecraft/world/entity/monster/Phantom.java -index 81e75e9d6619f7108fd769e462f24d72cbd5a73c..3c3f70d05fb51b530b792adf84c324840bd03c14 100644 ---- a/src/main/java/net/minecraft/world/entity/monster/Phantom.java -+++ b/src/main/java/net/minecraft/world/entity/monster/Phantom.java -@@ -165,6 +165,11 @@ public class Phantom extends FlyingMob implements Enemy { - } - - this.setPhantomSize(nbt.getInt("Size")); -+ // Paper start -+ if (nbt.hasUUID("Paper.SpawningEntity")) { -+ this.spawningEntity = nbt.getUUID("Paper.SpawningEntity"); -+ } -+ // Paper end - } - - @Override -@@ -174,6 +179,11 @@ public class Phantom extends FlyingMob implements Enemy { - nbt.putInt("AY", this.anchorPoint.getY()); - nbt.putInt("AZ", this.anchorPoint.getZ()); - nbt.putInt("Size", this.getPhantomSize()); -+ // Paper start -+ if (this.spawningEntity != null) { -+ nbt.putUUID("Paper.SpawningEntity", this.spawningEntity); -+ } -+ // Paper end - } - - @Override -@@ -219,6 +229,17 @@ public class Phantom extends FlyingMob implements Enemy { - return entitysize.scale(1.0F + 0.15F * (float) i); - } - -+ // Paper start -+ @Nullable -+ java.util.UUID spawningEntity; -+ -+ @Nullable -+ public java.util.UUID getSpawningEntity() { -+ return this.spawningEntity; -+ } -+ public void setSpawningEntity(java.util.UUID entity) { this.spawningEntity = entity; } -+ // Paper end -+ - private static enum AttackPhase { - - CIRCLE, SWOOP; -diff --git a/src/main/java/net/minecraft/world/level/levelgen/PhantomSpawner.java b/src/main/java/net/minecraft/world/level/levelgen/PhantomSpawner.java -index 9d28e3855a9b150534ef8b6c89e186f5c4c47694..bb7f2d3ff7fc6f5cadb4ab24efb5a3a2f5bdc33f 100644 ---- a/src/main/java/net/minecraft/world/level/levelgen/PhantomSpawner.java -+++ b/src/main/java/net/minecraft/world/level/levelgen/PhantomSpawner.java -@@ -69,9 +69,19 @@ public class PhantomSpawner implements CustomSpawner { - int k = 1 + randomsource.nextInt(difficultydamagescaler.getDifficulty().getId() + 1); - - for (int l = 0; l < k; ++l) { -+ // Paper start - PhantomPreSpawnEvent -+ com.destroystokyo.paper.event.entity.PhantomPreSpawnEvent event = new com.destroystokyo.paper.event.entity.PhantomPreSpawnEvent(io.papermc.paper.util.MCUtil.toLocation(world, blockposition1), entityplayer.getBukkitEntity(), org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason.NATURAL); -+ if (!event.callEvent()) { -+ if (event.shouldAbortSpawn()) { -+ break; -+ } -+ continue; -+ } -+ // Paper end - PhantomPreSpawnEvent - Phantom entityphantom = (Phantom) EntityType.PHANTOM.create(world); - - if (entityphantom != null) { -+ entityphantom.setSpawningEntity(entityplayer.getUUID()); // Paper - PhantomPreSpawnEvent - entityphantom.moveTo(blockposition1, 0.0F, 0.0F); - groupdataentity = entityphantom.finalizeSpawn(world, difficultydamagescaler, MobSpawnType.NATURAL, groupdataentity); - world.addFreshEntityWithPassengers(entityphantom, org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason.NATURAL); // CraftBukkit -diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftPhantom.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftPhantom.java -index 0359c161448941f1b9fdac545a5c47a68f19b760..305a635b049741ac5e2670060c6818cb2c07e5ab 100644 ---- a/src/main/java/org/bukkit/craftbukkit/entity/CraftPhantom.java -+++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftPhantom.java -@@ -28,4 +28,11 @@ public class CraftPhantom extends CraftFlying implements Phantom, CraftEnemy { - public String toString() { - return "CraftPhantom"; - } -+ -+ // Paper start -+ @Override -+ public java.util.UUID getSpawningEntity() { -+ return getHandle().getSpawningEntity(); -+ } -+ // Paper end - } diff --git a/patches/server/0237-Add-More-Creeper-API.patch b/patches/server/0237-Add-More-Creeper-API.patch deleted file mode 100644 index 931f2b615897..000000000000 --- a/patches/server/0237-Add-More-Creeper-API.patch +++ /dev/null @@ -1,60 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: BillyGalbreath -Date: Fri, 24 Aug 2018 11:50:26 -0500 -Subject: [PATCH] Add More Creeper API - - -diff --git a/src/main/java/net/minecraft/world/entity/monster/Creeper.java b/src/main/java/net/minecraft/world/entity/monster/Creeper.java -index 95df4ac539ec284654c53d39955870a46478c27d..9bf11a8b44e696b6587bc775904a836d390e437b 100644 ---- a/src/main/java/net/minecraft/world/entity/monster/Creeper.java -+++ b/src/main/java/net/minecraft/world/entity/monster/Creeper.java -@@ -134,7 +134,7 @@ public class Creeper extends Monster implements PowerableMob { - } - - if (nbt.getBoolean("ignited")) { -- this.ignite(); -+ this.entityData.set(Creeper.DATA_IS_IGNITED, true); // Paper - set directly to avoid firing event - } - - } -@@ -315,7 +315,18 @@ public class Creeper extends Monster implements PowerableMob { - } - - public void ignite() { -- this.entityData.set(Creeper.DATA_IS_IGNITED, true); -+ // Paper start - CreeperIgniteEvent -+ setIgnited(true); -+ } -+ -+ public void setIgnited(boolean ignited) { -+ if (isIgnited() != ignited) { -+ com.destroystokyo.paper.event.entity.CreeperIgniteEvent event = new com.destroystokyo.paper.event.entity.CreeperIgniteEvent((org.bukkit.entity.Creeper) getBukkitEntity(), ignited); -+ if (event.callEvent()) { -+ this.entityData.set(Creeper.DATA_IS_IGNITED, event.isIgnited()); -+ } -+ } -+ // Paper end - CreeperIgniteEvent - } - - public boolean canDropMobsSkull() { -diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftCreeper.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftCreeper.java -index 15bd44f8bd08b55b4da7fbbfdddc7ec3db454d89..42dd26b9170f7d217d73f725a6b8440b45ac2190 100644 ---- a/src/main/java/org/bukkit/craftbukkit/entity/CraftCreeper.java -+++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftCreeper.java -@@ -101,4 +101,16 @@ public class CraftCreeper extends CraftMonster implements Creeper { - public String toString() { - return "CraftCreeper"; - } -+ -+ // Paper start -+ @Override -+ public void setIgnited(boolean ignited) { -+ getHandle().setIgnited(ignited); -+ } -+ -+ @Override -+ public boolean isIgnited() { -+ return getHandle().isIgnited(); -+ } -+ // Paper end - } diff --git a/patches/server/0238-Inventory-removeItemAnySlot.patch b/patches/server/0237-Inventory-removeItemAnySlot.patch similarity index 100% rename from patches/server/0238-Inventory-removeItemAnySlot.patch rename to patches/server/0237-Inventory-removeItemAnySlot.patch diff --git a/patches/server/0238-Make-CraftWorld-loadChunk-int-int-false-load-unconve.patch b/patches/server/0238-Make-CraftWorld-loadChunk-int-int-false-load-unconve.patch new file mode 100644 index 000000000000..bb06c999cf47 --- /dev/null +++ b/patches/server/0238-Make-CraftWorld-loadChunk-int-int-false-load-unconve.patch @@ -0,0 +1,20 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Spottedleaf +Date: Sun, 2 Sep 2018 19:34:33 -0700 +Subject: [PATCH] Make CraftWorld#loadChunk(int, int, false) load unconverted + chunks + + +diff --git a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java +index e9840a1159419593145d166b54e523fd3e6684f0..a8e738941d8cd6373eb0ae5259da8e763a695657 100644 +--- a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java ++++ b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java +@@ -438,7 +438,7 @@ public class CraftWorld extends CraftRegionAccessor implements World { + @Override + public boolean loadChunk(int x, int z, boolean generate) { + org.spigotmc.AsyncCatcher.catchOp("chunk load"); // Spigot +- ChunkAccess chunk = this.world.getChunkSource().getChunk(x, z, generate ? ChunkStatus.FULL : ChunkStatus.EMPTY, true); ++ ChunkAccess chunk = this.world.getChunkSource().getChunk(x, z, generate || isChunkGenerated(x, z) ? ChunkStatus.FULL : ChunkStatus.EMPTY, true); // Paper + + // If generate = false, but the chunk already exists, we will get this back. + if (chunk instanceof ImposterProtoChunk) { diff --git a/patches/server/0239-Add-ray-tracing-methods-to-LivingEntity.patch b/patches/server/0239-Add-ray-tracing-methods-to-LivingEntity.patch new file mode 100644 index 000000000000..4ad440ffa379 --- /dev/null +++ b/patches/server/0239-Add-ray-tracing-methods-to-LivingEntity.patch @@ -0,0 +1,68 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: BillyGalbreath +Date: Mon, 3 Sep 2018 18:20:03 -0500 +Subject: [PATCH] Add ray tracing methods to LivingEntity + + +diff --git a/src/main/java/net/minecraft/world/entity/LivingEntity.java b/src/main/java/net/minecraft/world/entity/LivingEntity.java +index cfff596d720efe5f5ee4ad1990c3ee0fd6e4e836..5b8e0931be2acb1eb4ac6f399ecc0a5ebc5db586 100644 +--- a/src/main/java/net/minecraft/world/entity/LivingEntity.java ++++ b/src/main/java/net/minecraft/world/entity/LivingEntity.java +@@ -4112,6 +4112,19 @@ public abstract class LivingEntity extends Entity implements Attackable { + } + + // Paper start - Make shield blocking delay configurable ++ public HitResult getRayTrace(int maxDistance, ClipContext.Fluid fluidCollisionOption) { ++ if (maxDistance < 1 || maxDistance > 120) { ++ throw new IllegalArgumentException("maxDistance must be between 1-120"); ++ } ++ ++ Vec3 start = new Vec3(getX(), getY() + getEyeHeight(), getZ()); ++ org.bukkit.util.Vector dir = getBukkitEntity().getLocation().getDirection().multiply(maxDistance); ++ Vec3 end = new Vec3(start.x + dir.getX(), start.y + dir.getY(), start.z + dir.getZ()); ++ ClipContext raytrace = new ClipContext(start, end, ClipContext.Block.OUTLINE, fluidCollisionOption, this); ++ ++ return this.level().clip(raytrace); ++ } ++ + public int shieldBlockingDelay = this.level().paperConfig().misc.shieldBlockingDelay; + + public int getShieldBlockingDelay() { +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftLivingEntity.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftLivingEntity.java +index 2033354daafc739a8bd9ddf4a6128d3204d32094..0c7bf4124d67258ebca9b9b73b92c2e0efbdaa86 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftLivingEntity.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftLivingEntity.java +@@ -207,6 +207,33 @@ public class CraftLivingEntity extends CraftEntity implements LivingEntity { + return blocks.get(0); + } + ++ // Paper start ++ @Override ++ public Block getTargetBlock(int maxDistance, com.destroystokyo.paper.block.TargetBlockInfo.FluidMode fluidMode) { ++ return this.getTargetBlockExact(maxDistance, fluidMode.bukkit); ++ } ++ ++ @Override ++ public org.bukkit.block.BlockFace getTargetBlockFace(int maxDistance, com.destroystokyo.paper.block.TargetBlockInfo.FluidMode fluidMode) { ++ return this.getTargetBlockFace(maxDistance, fluidMode.bukkit); ++ } ++ ++ @Override ++ public org.bukkit.block.BlockFace getTargetBlockFace(int maxDistance, org.bukkit.FluidCollisionMode fluidMode) { ++ RayTraceResult result = this.rayTraceBlocks(maxDistance, fluidMode); ++ return result != null ? result.getHitBlockFace() : null; ++ } ++ ++ @Override ++ public com.destroystokyo.paper.block.TargetBlockInfo getTargetBlockInfo(int maxDistance, com.destroystokyo.paper.block.TargetBlockInfo.FluidMode fluidMode) { ++ RayTraceResult result = this.rayTraceBlocks(maxDistance, fluidMode.bukkit); ++ if (result != null && result.getHitBlock() != null && result.getHitBlockFace() != null) { ++ return new com.destroystokyo.paper.block.TargetBlockInfo(result.getHitBlock(), result.getHitBlockFace()); ++ } ++ return null; ++ } ++ // Paper end ++ + @Override + public List getLastTwoTargetBlocks(Set transparent, int maxDistance) { + return this.getLineOfSight(transparent, maxDistance, 2); diff --git a/patches/server/0239-Make-CraftWorld-loadChunk-int-int-false-load-unconve.patch b/patches/server/0239-Make-CraftWorld-loadChunk-int-int-false-load-unconve.patch deleted file mode 100644 index 911ca5db1fb1..000000000000 --- a/patches/server/0239-Make-CraftWorld-loadChunk-int-int-false-load-unconve.patch +++ /dev/null @@ -1,20 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Spottedleaf -Date: Sun, 2 Sep 2018 19:34:33 -0700 -Subject: [PATCH] Make CraftWorld#loadChunk(int, int, false) load unconverted - chunks - - -diff --git a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java -index 281fa67cb5c95e6016c7220c0ef912bbfd28cd9f..333e3109d19c867e8a74f20693952bdb3df804e4 100644 ---- a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java -+++ b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java -@@ -432,7 +432,7 @@ public class CraftWorld extends CraftRegionAccessor implements World { - @Override - public boolean loadChunk(int x, int z, boolean generate) { - org.spigotmc.AsyncCatcher.catchOp("chunk load"); // Spigot -- ChunkAccess chunk = this.world.getChunkSource().getChunk(x, z, generate ? ChunkStatus.FULL : ChunkStatus.EMPTY, true); -+ ChunkAccess chunk = this.world.getChunkSource().getChunk(x, z, generate || isChunkGenerated(x, z) ? ChunkStatus.FULL : ChunkStatus.EMPTY, true); // Paper - - // If generate = false, but the chunk already exists, we will get this back. - if (chunk instanceof ImposterProtoChunk) { diff --git a/patches/server/0240-Add-ray-tracing-methods-to-LivingEntity.patch b/patches/server/0240-Add-ray-tracing-methods-to-LivingEntity.patch deleted file mode 100644 index 0962c2d288b7..000000000000 --- a/patches/server/0240-Add-ray-tracing-methods-to-LivingEntity.patch +++ /dev/null @@ -1,68 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: BillyGalbreath -Date: Mon, 3 Sep 2018 18:20:03 -0500 -Subject: [PATCH] Add ray tracing methods to LivingEntity - - -diff --git a/src/main/java/net/minecraft/world/entity/LivingEntity.java b/src/main/java/net/minecraft/world/entity/LivingEntity.java -index d90e74b7323a60e781d942baffe9b4bdb8ae2943..08f756b4fbb4732d73ca281b7006024b21504880 100644 ---- a/src/main/java/net/minecraft/world/entity/LivingEntity.java -+++ b/src/main/java/net/minecraft/world/entity/LivingEntity.java -@@ -3980,6 +3980,19 @@ public abstract class LivingEntity extends Entity implements Attackable { - } - - // Paper start - Make shield blocking delay configurable -+ public HitResult getRayTrace(int maxDistance, ClipContext.Fluid fluidCollisionOption) { -+ if (maxDistance < 1 || maxDistance > 120) { -+ throw new IllegalArgumentException("maxDistance must be between 1-120"); -+ } -+ -+ Vec3 start = new Vec3(getX(), getY() + getEyeHeight(), getZ()); -+ org.bukkit.util.Vector dir = getBukkitEntity().getLocation().getDirection().multiply(maxDistance); -+ Vec3 end = new Vec3(start.x + dir.getX(), start.y + dir.getY(), start.z + dir.getZ()); -+ ClipContext raytrace = new ClipContext(start, end, ClipContext.Block.OUTLINE, fluidCollisionOption, this); -+ -+ return this.level().clip(raytrace); -+ } -+ - public int shieldBlockingDelay = this.level().paperConfig().misc.shieldBlockingDelay; - - public int getShieldBlockingDelay() { -diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftLivingEntity.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftLivingEntity.java -index 2612e5016646591bb65ac255804b612b348a32fd..c8ac50351b7b1b2f4afc138570b8098a3c0ce1ba 100644 ---- a/src/main/java/org/bukkit/craftbukkit/entity/CraftLivingEntity.java -+++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftLivingEntity.java -@@ -202,6 +202,33 @@ public class CraftLivingEntity extends CraftEntity implements LivingEntity { - return blocks.get(0); - } - -+ // Paper start -+ @Override -+ public Block getTargetBlock(int maxDistance, com.destroystokyo.paper.block.TargetBlockInfo.FluidMode fluidMode) { -+ return this.getTargetBlockExact(maxDistance, fluidMode.bukkit); -+ } -+ -+ @Override -+ public org.bukkit.block.BlockFace getTargetBlockFace(int maxDistance, com.destroystokyo.paper.block.TargetBlockInfo.FluidMode fluidMode) { -+ return this.getTargetBlockFace(maxDistance, fluidMode.bukkit); -+ } -+ -+ @Override -+ public org.bukkit.block.BlockFace getTargetBlockFace(int maxDistance, org.bukkit.FluidCollisionMode fluidMode) { -+ RayTraceResult result = this.rayTraceBlocks(maxDistance, fluidMode); -+ return result != null ? result.getHitBlockFace() : null; -+ } -+ -+ @Override -+ public com.destroystokyo.paper.block.TargetBlockInfo getTargetBlockInfo(int maxDistance, com.destroystokyo.paper.block.TargetBlockInfo.FluidMode fluidMode) { -+ RayTraceResult result = this.rayTraceBlocks(maxDistance, fluidMode.bukkit); -+ if (result != null && result.getHitBlock() != null && result.getHitBlockFace() != null) { -+ return new com.destroystokyo.paper.block.TargetBlockInfo(result.getHitBlock(), result.getHitBlockFace()); -+ } -+ return null; -+ } -+ // Paper end -+ - @Override - public List getLastTwoTargetBlocks(Set transparent, int maxDistance) { - return this.getLineOfSight(transparent, maxDistance, 2); diff --git a/patches/server/0240-Expose-attack-cooldown-methods-for-Player.patch b/patches/server/0240-Expose-attack-cooldown-methods-for-Player.patch new file mode 100644 index 000000000000..348ac73d0c42 --- /dev/null +++ b/patches/server/0240-Expose-attack-cooldown-methods-for-Player.patch @@ -0,0 +1,32 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: BillyGalbreath +Date: Tue, 4 Sep 2018 15:02:00 -0500 +Subject: [PATCH] Expose attack cooldown methods for Player + + +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java +index 462877cf6f0dce4f86b6a47564affe9beb2559a8..e4d840d6335007a6a542240746504bf1f2af332d 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java +@@ -3007,6 +3007,21 @@ public class CraftPlayer extends CraftHumanEntity implements Player { + + return this.adventure$pointers; + } ++ ++ @Override ++ public float getCooldownPeriod() { ++ return getHandle().getCurrentItemAttackStrengthDelay(); ++ } ++ ++ @Override ++ public float getCooledAttackStrength(float adjustTicks) { ++ return getHandle().getAttackStrengthScale(adjustTicks); ++ } ++ ++ @Override ++ public void resetCooldown() { ++ getHandle().resetAttackStrengthTicker(); ++ } + // Paper end + // Spigot start + private final Player.Spigot spigot = new Player.Spigot() diff --git a/patches/server/0241-Expose-attack-cooldown-methods-for-Player.patch b/patches/server/0241-Expose-attack-cooldown-methods-for-Player.patch deleted file mode 100644 index 0d19ca5bf3d6..000000000000 --- a/patches/server/0241-Expose-attack-cooldown-methods-for-Player.patch +++ /dev/null @@ -1,32 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: BillyGalbreath -Date: Tue, 4 Sep 2018 15:02:00 -0500 -Subject: [PATCH] Expose attack cooldown methods for Player - - -diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java -index 12c61db6d4b1284765f9bed3ae26131a118b318e..a7e611aaeb457820ad303b95822d8ea86b060477 100644 ---- a/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java -+++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java -@@ -2980,6 +2980,21 @@ public class CraftPlayer extends CraftHumanEntity implements Player { - - return this.adventure$pointers; - } -+ -+ @Override -+ public float getCooldownPeriod() { -+ return getHandle().getCurrentItemAttackStrengthDelay(); -+ } -+ -+ @Override -+ public float getCooledAttackStrength(float adjustTicks) { -+ return getHandle().getAttackStrengthScale(adjustTicks); -+ } -+ -+ @Override -+ public void resetCooldown() { -+ getHandle().resetAttackStrengthTicker(); -+ } - // Paper end - // Spigot start - private final Player.Spigot spigot = new Player.Spigot() diff --git a/patches/server/0241-Improve-death-events.patch b/patches/server/0241-Improve-death-events.patch new file mode 100644 index 000000000000..0dd870277303 --- /dev/null +++ b/patches/server/0241-Improve-death-events.patch @@ -0,0 +1,508 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Phoenix616 +Date: Tue, 21 Aug 2018 01:39:35 +0100 +Subject: [PATCH] Improve death events + +This adds the ability to cancel the death events and to modify the sound +an entity makes when dying. (In cases were no sound should it will be +called with shouldPlaySound set to false allowing unsilencing of silent +entities) + +It makes handling of entity deaths a lot nicer as you no longer need +to listen on the damage event and calculate if the entity dies yourself +to cancel the death which has the benefit of also receiving the dropped +items and experience which is otherwise only properly possible by using +internal code. + +== AT == +public net.minecraft.world.entity.LivingEntity getDeathSound()Lnet/minecraft/sounds/SoundEvent; +public net.minecraft.world.entity.LivingEntity getSoundVolume()F + +diff --git a/src/main/java/net/minecraft/server/level/ServerPlayer.java b/src/main/java/net/minecraft/server/level/ServerPlayer.java +index 29b836a75b835f0d5233db419fc5ca8dde885fdb..2bd97344502a63173de923542f27759d7e98b6cc 100644 +--- a/src/main/java/net/minecraft/server/level/ServerPlayer.java ++++ b/src/main/java/net/minecraft/server/level/ServerPlayer.java +@@ -296,6 +296,10 @@ public class ServerPlayer extends net.minecraft.world.entity.player.Player { + private int containerCounter; + public boolean wonGame; + private int containerUpdateDelay; // Paper - Configurable container update tick rate ++ // Paper start - cancellable death event ++ public boolean queueHealthUpdatePacket; ++ public net.minecraft.network.protocol.game.ClientboundSetHealthPacket queuedHealthUpdatePacket; ++ // Paper end - cancellable death event + + // CraftBukkit start + public CraftPlayer.TransferCookieConnection transferCookieConnection; +@@ -1154,7 +1158,7 @@ public class ServerPlayer extends net.minecraft.world.entity.player.Player { + + @Override + public void die(DamageSource damageSource) { +- this.gameEvent(GameEvent.ENTITY_DIE); ++ // this.gameEvent(GameEvent.ENTITY_DIE); // Paper - move below event cancellation check + boolean flag = this.serverLevel().getGameRules().getBoolean(GameRules.RULE_SHOWDEATHMESSAGES); + // CraftBukkit start - fire PlayerDeathEvent + if (this.isRemoved()) { +@@ -1182,6 +1186,16 @@ public class ServerPlayer extends net.minecraft.world.entity.player.Player { + String deathmessage = defaultMessage.getString(); + this.keepLevel = keepInventory; // SPIGOT-2222: pre-set keepLevel + org.bukkit.event.entity.PlayerDeathEvent event = CraftEventFactory.callPlayerDeathEvent(this, damageSource, loot, PaperAdventure.asAdventure(defaultMessage), keepInventory); // Paper - Adventure ++ // Paper start - cancellable death event ++ if (event.isCancelled()) { ++ // make compatible with plugins that might have already set the health in an event listener ++ if (this.getHealth() <= 0) { ++ this.setHealth((float) event.getReviveHealth()); ++ } ++ return; ++ } ++ this.gameEvent(GameEvent.ENTITY_DIE); // moved from the top of this method ++ // Paper end + + // SPIGOT-943 - only call if they have an inventory open + if (this.containerMenu != this.inventoryMenu) { +@@ -1331,7 +1345,17 @@ public class ServerPlayer extends net.minecraft.world.entity.player.Player { + } + } + +- return super.hurtServer(world, source, amount); ++ // Paper start - cancellable death events ++ //return super.hurt(source, amount); ++ this.queueHealthUpdatePacket = true; ++ boolean damaged = super.hurtServer(world, source, amount); ++ this.queueHealthUpdatePacket = false; ++ if (this.queuedHealthUpdatePacket != null) { ++ this.connection.send(this.queuedHealthUpdatePacket); ++ this.queuedHealthUpdatePacket = null; ++ } ++ return damaged; ++ // Paper end + } + } + } +diff --git a/src/main/java/net/minecraft/world/entity/LivingEntity.java b/src/main/java/net/minecraft/world/entity/LivingEntity.java +index 5b8e0931be2acb1eb4ac6f399ecc0a5ebc5db586..7a89c25fcd1c76e4b176c257600db89788aa0f21 100644 +--- a/src/main/java/net/minecraft/world/entity/LivingEntity.java ++++ b/src/main/java/net/minecraft/world/entity/LivingEntity.java +@@ -295,6 +295,7 @@ public abstract class LivingEntity extends Entity implements Attackable { + public Set collidableExemptions = new HashSet<>(); + public boolean bukkitPickUpLoot; + public org.bukkit.craftbukkit.entity.CraftLivingEntity getBukkitLivingEntity() { return (org.bukkit.craftbukkit.entity.CraftLivingEntity) super.getBukkitEntity(); } // Paper ++ public boolean silentDeath = false; // Paper - mark entity as dying silently for cancellable death event + + @Override + public float getBukkitYaw() { +@@ -1572,11 +1573,12 @@ public abstract class LivingEntity extends Entity implements Attackable { + + if (this.isDeadOrDying()) { + if (!this.checkTotemDeathProtection(source)) { +- if (flag1) { +- this.makeSound(this.getDeathSound()); +- } ++ // Paper start - moved into CraftEventFactory event caller for cancellable death event ++ this.silentDeath = !flag1; // mark entity as dying silently ++ // Paper end + + this.die(source); ++ this.silentDeath = false; // Paper - cancellable death event - reset to default + } + } else if (flag1) { + this.playHurtSound(source); +@@ -1738,6 +1740,7 @@ public abstract class LivingEntity extends Entity implements Attackable { + Entity entity = damageSource.getEntity(); + LivingEntity entityliving = this.getKillCredit(); + ++ /* // Paper - move down to make death event cancellable - this is the awardKillScore below + if (this.deathScore >= 0 && entityliving != null) { + entityliving.awardKillScore(this, this.deathScore, damageSource); + } +@@ -1749,24 +1752,59 @@ public abstract class LivingEntity extends Entity implements Attackable { + if (!this.level().isClientSide && this.hasCustomName()) { + if (org.spigotmc.SpigotConfig.logNamedDeaths) LivingEntity.LOGGER.info("Named entity {} died: {}", this, this.getCombatTracker().getDeathMessage().getString()); // Spigot + } ++ */ // Paper - move down to make death event cancellable - this is the awardKillScore below + + this.dead = true; +- this.getCombatTracker().recheckStatus(); ++ // Paper - moved into if below + Level world = this.level(); + + if (world instanceof ServerLevel) { + ServerLevel worldserver = (ServerLevel) world; ++ // Paper - move below into if for onKill ++ ++ // Paper start ++ org.bukkit.event.entity.EntityDeathEvent deathEvent = this.dropAllDeathLoot(worldserver, damageSource); ++ if (deathEvent == null || !deathEvent.isCancelled()) { ++ if (this.deathScore >= 0 && entityliving != null) { ++ entityliving.awardKillScore(this, this.deathScore, damageSource); ++ } ++ // Paper start - clear equipment if event is not cancelled ++ if (this instanceof Mob) { ++ for (EquipmentSlot slot : this.clearedEquipmentSlots) { ++ this.setItemSlot(slot, ItemStack.EMPTY); ++ } ++ this.clearedEquipmentSlots.clear(); ++ } ++ // Paper end ++ ++ if (this.isSleeping()) { ++ this.stopSleeping(); ++ } + +- if (entity == null || entity.killedEntity(worldserver, this)) { ++ if (!this.level().isClientSide && this.hasCustomName()) { ++ if (org.spigotmc.SpigotConfig.logNamedDeaths) LivingEntity.LOGGER.info("Named entity {} died: {}", this, this.getCombatTracker().getDeathMessage().getString()); // Spigot ++ } ++ ++ this.getCombatTracker().recheckStatus(); ++ if (entity != null) { ++ entity.killedEntity((ServerLevel) this.level(), this); ++ } + this.gameEvent(GameEvent.ENTITY_DIE); +- this.dropAllDeathLoot(worldserver, damageSource); ++ } else { ++ this.dead = false; ++ this.setHealth((float) deathEvent.getReviveHealth()); ++ } ++ // Paper end + this.createWitherRose(entityliving); + } + ++ // Paper start ++ if (this.dead) { // Paper + this.level().broadcastEntityEvent(this, (byte) 3); +- } + + this.setPose(Pose.DYING); ++ } ++ // Paper end + } + } + +@@ -1776,7 +1814,7 @@ public abstract class LivingEntity extends Entity implements Attackable { + if (world instanceof ServerLevel worldserver) { + boolean flag = false; + +- if (adversary instanceof WitherBoss) { ++ if (this.dead && adversary instanceof WitherBoss) { // Paper + if (worldserver.getGameRules().getBoolean(GameRules.RULE_MOBGRIEFING)) { + BlockPos blockposition = this.blockPosition(); + BlockState iblockdata = Blocks.WITHER_ROSE.defaultBlockState(); +@@ -1805,24 +1843,37 @@ public abstract class LivingEntity extends Entity implements Attackable { + } + } + +- protected void dropAllDeathLoot(ServerLevel world, DamageSource damageSource) { ++ // Paper start ++ protected boolean clearEquipmentSlots = true; ++ protected Set clearedEquipmentSlots = new java.util.HashSet<>(); ++ protected org.bukkit.event.entity.EntityDeathEvent dropAllDeathLoot(ServerLevel world, DamageSource damageSource) { ++ // Paper end + boolean flag = this.lastHurtByPlayerTime > 0; + + this.dropEquipment(world); // CraftBukkit - from below + if (this.shouldDropLoot() && world.getGameRules().getBoolean(GameRules.RULE_DOMOBLOOT)) { + this.dropFromLootTable(world, damageSource, flag); ++ // Paper start ++ final boolean prev = this.clearEquipmentSlots; ++ this.clearEquipmentSlots = false; ++ this.clearedEquipmentSlots.clear(); ++ // Paper end + this.dropCustomDeathLoot(world, damageSource, flag); ++ this.clearEquipmentSlots = prev; // Paper + } + // CraftBukkit start - Call death event +- CraftEventFactory.callEntityDeathEvent(this, damageSource, this.drops); ++ org.bukkit.event.entity.EntityDeathEvent deathEvent = CraftEventFactory.callEntityDeathEvent(this, damageSource, this.drops); // Paper ++ this.postDeathDropItems(deathEvent); // Paper + this.drops = new ArrayList<>(); + // CraftBukkit end + + // this.dropEquipment(worldserver);// CraftBukkit - moved up + this.dropExperience(world, damageSource.getEntity()); ++ return deathEvent; // Paper + } + + protected void dropEquipment(ServerLevel world) {} ++ protected void postDeathDropItems(org.bukkit.event.entity.EntityDeathEvent event) {} // Paper - method for post death logic that cannot be ran before the event is potentially cancelled + + public int getExpReward(ServerLevel worldserver, @Nullable Entity entity) { // CraftBukkit + if (!this.wasExperienceConsumed() && (this.isAlwaysExperienceDropper() || this.lastHurtByPlayerTime > 0 && this.shouldDropExperience() && worldserver.getGameRules().getBoolean(GameRules.RULE_DOMOBLOOT))) { +diff --git a/src/main/java/net/minecraft/world/entity/Mob.java b/src/main/java/net/minecraft/world/entity/Mob.java +index 2b8bfccbf520f9a356f816522ac3a5caa51e44e1..a8ab3c03a6f96658ce2a3f5758225954a36de6a9 100644 +--- a/src/main/java/net/minecraft/world/entity/Mob.java ++++ b/src/main/java/net/minecraft/world/entity/Mob.java +@@ -1117,6 +1117,12 @@ public abstract class Mob extends LivingEntity implements EquipmentUser, Leashab + + } + ++ // Paper start ++ protected boolean shouldSkipLoot(EquipmentSlot slot) { // method to avoid to fallback into the global mob loot logic (i.e fox) ++ return false; ++ } ++ // Paper end ++ + @Override + protected void dropCustomDeathLoot(ServerLevel world, DamageSource source, boolean causedByPlayer) { + super.dropCustomDeathLoot(world, source, causedByPlayer); +@@ -1124,6 +1130,7 @@ public abstract class Mob extends LivingEntity implements EquipmentUser, Leashab + + while (iterator.hasNext()) { + EquipmentSlot enumitemslot = (EquipmentSlot) iterator.next(); ++ if (this.shouldSkipLoot(enumitemslot)) continue; // Paper + ItemStack itemstack = this.getItemBySlot(enumitemslot); + float f = this.getEquipmentDropChance(enumitemslot); + +@@ -1148,7 +1155,13 @@ public abstract class Mob extends LivingEntity implements EquipmentUser, Leashab + } + + this.spawnAtLocation(world, itemstack); ++ if (this.clearEquipmentSlots) { // Paper + this.setItemSlot(enumitemslot, ItemStack.EMPTY); ++ // Paper start ++ } else { ++ this.clearedEquipmentSlots.add(enumitemslot); ++ } ++ // Paper end + } + } + } +diff --git a/src/main/java/net/minecraft/world/entity/animal/Fox.java b/src/main/java/net/minecraft/world/entity/animal/Fox.java +index faffc3a9ed8bc306277cad37bc43af2ef7303493..205aefd38a185fa411ff17cfb0155769de8fc2fd 100644 +--- a/src/main/java/net/minecraft/world/entity/animal/Fox.java ++++ b/src/main/java/net/minecraft/world/entity/animal/Fox.java +@@ -689,16 +689,38 @@ public class Fox extends Animal implements VariantHolder { + return this.getTrustedUUIDs().contains(uuid); + } + ++ // Paper start - handle the bitten item separately like vanilla + @Override +- protected void dropAllDeathLoot(ServerLevel world, DamageSource damageSource) { ++ protected boolean shouldSkipLoot(EquipmentSlot slot) { ++ return slot == EquipmentSlot.MAINHAND; ++ } ++ // Paper end ++ ++ @Override ++ // Paper start - Cancellable death event ++ protected org.bukkit.event.entity.EntityDeathEvent dropAllDeathLoot(ServerLevel world, DamageSource damageSource) { + ItemStack itemstack = this.getItemBySlot(EquipmentSlot.MAINHAND); + +- if (!itemstack.isEmpty()) { ++ boolean releaseMouth = false; ++ if (!itemstack.isEmpty() && world.getGameRules().getBoolean(GameRules.RULE_DOMOBLOOT)) { // Fix MC-153010 + this.spawnAtLocation(world, itemstack); ++ releaseMouth = true; ++ } ++ ++ org.bukkit.event.entity.EntityDeathEvent deathEvent = super.dropAllDeathLoot(world, damageSource); ++ ++ // Below is code to drop ++ ++ if (deathEvent == null || deathEvent.isCancelled()) { ++ return deathEvent; ++ } ++ ++ if (releaseMouth) { ++ // Paper end - Cancellable death event + this.setItemSlot(EquipmentSlot.MAINHAND, ItemStack.EMPTY); + } + +- super.dropAllDeathLoot(world, damageSource); ++ return deathEvent; // Paper - Cancellable death event + } + + public static boolean isPathClear(Fox fox, LivingEntity chasedEntity) { +diff --git a/src/main/java/net/minecraft/world/entity/animal/horse/AbstractChestedHorse.java b/src/main/java/net/minecraft/world/entity/animal/horse/AbstractChestedHorse.java +index dd7ef4c873d5e06eb5be3abc8049bf8acbd5a3fb..112b82ba7709b36e996e2f984c72ce40ca718720 100644 +--- a/src/main/java/net/minecraft/world/entity/animal/horse/AbstractChestedHorse.java ++++ b/src/main/java/net/minecraft/world/entity/animal/horse/AbstractChestedHorse.java +@@ -69,9 +69,16 @@ public abstract class AbstractChestedHorse extends AbstractHorse { + super.dropEquipment(world); + if (this.hasChest()) { + this.spawnAtLocation(world, Blocks.CHEST); ++ //this.setChest(false); // Paper - moved to post death logic ++ } ++ } ++ // Paper start ++ protected void postDeathDropItems(org.bukkit.event.entity.EntityDeathEvent event) { ++ if (this.hasChest() && (event == null || !event.isCancelled())) { + this.setChest(false); + } + } ++ // Paper end + + @Override + public void addAdditionalSaveData(CompoundTag nbt) { +diff --git a/src/main/java/net/minecraft/world/entity/decoration/ArmorStand.java b/src/main/java/net/minecraft/world/entity/decoration/ArmorStand.java +index 2caba38a50b7ea535337a3540aa5272d4a9f1878..e20565cf256aacd012a1722c5ebbf9016bc82e42 100644 +--- a/src/main/java/net/minecraft/world/entity/decoration/ArmorStand.java ++++ b/src/main/java/net/minecraft/world/entity/decoration/ArmorStand.java +@@ -506,8 +506,10 @@ public class ArmorStand extends LivingEntity { + } + // CraftBukkit end + if (source.is(DamageTypeTags.IS_EXPLOSION)) { +- this.brokenByAnything(world, source); +- this.kill(world, source); // CraftBukkit ++ // Paper start - avoid duplicate event call ++ org.bukkit.event.entity.EntityDeathEvent event = this.brokenByAnything(world, source); ++ if (!event.isCancelled()) this.kill(source, false); // CraftBukkit ++ // Paper end + return false; + } else if (source.is(DamageTypeTags.IGNITES_ARMOR_STANDS)) { + if (this.isOnFire()) { +@@ -550,9 +552,9 @@ public class ArmorStand extends LivingEntity { + this.gameEvent(GameEvent.ENTITY_DAMAGE, source.getEntity()); + this.lastHit = i; + } else { +- this.brokenByPlayer(world, source); ++ org.bukkit.event.entity.EntityDeathEvent event = this.brokenByPlayer(world, source); // Paper + this.showBreakingParticles(); +- this.discard(EntityRemoveEvent.Cause.DEATH); // CraftBukkit - SPIGOT-4890: remain as this.discard() since above damagesource method will call death event ++ if (!event.isCancelled()) this.kill(source, false); // Paper - we still need to kill to follow vanilla logic (emit the game event etc...) + } + + return true; +@@ -602,7 +604,10 @@ public class ArmorStand extends LivingEntity { + f1 -= amount; + if (f1 <= 0.5F) { + this.brokenByAnything(world, damageSource); +- this.kill(world, damageSource); // CraftBukkit ++ // Paper start - avoid duplicate event call ++ org.bukkit.event.entity.EntityDeathEvent event = this.brokenByAnything(world, damageSource); ++ if (!event.isCancelled()) this.kill(damageSource, false); // CraftBukkit ++ // Paper end + } else { + this.setHealth(f1); + this.gameEvent(GameEvent.ENTITY_DAMAGE, damageSource.getEntity()); +@@ -610,15 +615,15 @@ public class ArmorStand extends LivingEntity { + + } + +- private void brokenByPlayer(ServerLevel world, DamageSource damageSource) { ++ private org.bukkit.event.entity.EntityDeathEvent brokenByPlayer(ServerLevel world, DamageSource damageSource) { // Paper + ItemStack itemstack = new ItemStack(Items.ARMOR_STAND); + + itemstack.set(DataComponents.CUSTOM_NAME, this.getCustomName()); + this.drops.add(org.bukkit.craftbukkit.inventory.CraftItemStack.asBukkitCopy(itemstack)); // CraftBukkit - add to drops +- this.brokenByAnything(world, damageSource); ++ return this.brokenByAnything(world, damageSource); // Paper + } + +- private void brokenByAnything(ServerLevel world, DamageSource damageSource) { ++ private org.bukkit.event.entity.EntityDeathEvent brokenByAnything(ServerLevel world, DamageSource damageSource) { // Paper + this.playBrokenSound(); + // this.dropAllDeathLoot(worldserver, damagesource); // CraftBukkit - moved down + +@@ -640,7 +645,7 @@ public class ArmorStand extends LivingEntity { + this.armorItems.set(i, ItemStack.EMPTY); + } + } +- this.dropAllDeathLoot(world, damageSource); // CraftBukkit - moved from above ++ return this.dropAllDeathLoot(world, damageSource); // CraftBukkit - moved from above // Paper + + } + +@@ -767,7 +772,15 @@ public class ArmorStand extends LivingEntity { + } + + public void kill(ServerLevel worldserver, DamageSource damageSource) { +- org.bukkit.craftbukkit.event.CraftEventFactory.callEntityDeathEvent(this, (damageSource == null ? this.damageSources().genericKill() : damageSource), this.drops); // CraftBukkit - call event ++ // Paper start - make cancellable ++ this.kill(damageSource, true); ++ } ++ public void kill(DamageSource damageSource, boolean callEvent) { ++ if (callEvent) { ++ org.bukkit.event.entity.EntityDeathEvent event = org.bukkit.craftbukkit.event.CraftEventFactory.callEntityDeathEvent(this, (damageSource == null ? this.damageSources().genericKill() : damageSource), this.drops); // CraftBukkit - call event ++ if (event.isCancelled()) return; ++ } ++ // Paper end + this.remove(Entity.RemovalReason.KILLED, EntityRemoveEvent.Cause.DEATH); // CraftBukkit - add Bukkit remove cause + // CraftBukkit end + this.gameEvent(GameEvent.ENTITY_DIE); +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java +index e4d840d6335007a6a542240746504bf1f2af332d..159bba49095aec77cce5f53d4388a5dff0afcc60 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java +@@ -2544,7 +2544,14 @@ public class CraftPlayer extends CraftHumanEntity implements Player { + @Override + public void sendHealthUpdate() { + FoodData foodData = this.getHandle().getFoodData(); +- this.sendHealthUpdate(this.getScaledHealth(), foodData.getFoodLevel(), foodData.getSaturationLevel()); ++ // Paper start - cancellable death event ++ ClientboundSetHealthPacket packet = new ClientboundSetHealthPacket(this.getScaledHealth(), foodData.getFoodLevel(), foodData.getSaturationLevel()); ++ if (this.getHandle().queueHealthUpdatePacket) { ++ this.getHandle().queuedHealthUpdatePacket = packet; ++ } else { ++ this.getHandle().connection.send(packet); ++ } ++ // Paper end + } + + public void injectScaledMaxHealth(Collection collection, boolean force) { +diff --git a/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java b/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java +index 474f330f381aa74e9f2fd0accdbaf2617ec1c557..199abd66d2113e7bc8c478fe8e4f6657d2e12304 100644 +--- a/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java ++++ b/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java +@@ -902,8 +902,15 @@ public class CraftEventFactory { + CraftDamageSource bukkitDamageSource = new CraftDamageSource(damageSource); + CraftWorld world = (CraftWorld) entity.getWorld(); + EntityDeathEvent event = new EntityDeathEvent(entity, bukkitDamageSource, drops, victim.getExpReward(world.getHandle(), damageSource.getEntity())); ++ populateFields(victim, event); // Paper - make cancellable + Bukkit.getServer().getPluginManager().callEvent(event); + ++ // Paper start - make cancellable ++ if (event.isCancelled()) { ++ return event; ++ } ++ playDeathSound(victim, event); ++ // Paper end + victim.expToDrop = event.getDroppedExp(); + + for (org.bukkit.inventory.ItemStack stack : event.getDrops()) { +@@ -921,7 +928,14 @@ public class CraftEventFactory { + PlayerDeathEvent event = new PlayerDeathEvent(entity, bukkitDamageSource, drops, victim.getExpReward(victim.serverLevel(), damageSource.getEntity()), 0, deathMessage); + event.setKeepInventory(keepInventory); + event.setKeepLevel(victim.keepLevel); // SPIGOT-2222: pre-set keepLevel ++ populateFields(victim, event); // Paper - make cancellable + Bukkit.getServer().getPluginManager().callEvent(event); ++ // Paper start - make cancellable ++ if (event.isCancelled()) { ++ return event; ++ } ++ playDeathSound(victim, event); ++ // Paper end + + victim.keepLevel = event.getKeepLevel(); + victim.newLevel = event.getNewLevel(); +@@ -944,6 +958,31 @@ public class CraftEventFactory { + return event; + } + ++ // Paper start - helper methods for making death event cancellable ++ // Add information to death event ++ private static void populateFields(net.minecraft.world.entity.LivingEntity victim, EntityDeathEvent event) { ++ event.setReviveHealth(event.getEntity().getAttribute(org.bukkit.attribute.Attribute.GENERIC_MAX_HEALTH).getValue()); ++ event.setShouldPlayDeathSound(!victim.silentDeath && !victim.isSilent()); ++ net.minecraft.sounds.SoundEvent soundEffect = victim.getDeathSound(); ++ event.setDeathSound(soundEffect != null ? org.bukkit.craftbukkit.CraftSound.minecraftToBukkit(soundEffect) : null); ++ event.setDeathSoundCategory(org.bukkit.SoundCategory.valueOf(victim.getSoundSource().name())); ++ event.setDeathSoundVolume(victim.getSoundVolume()); ++ event.setDeathSoundPitch(victim.getVoicePitch()); ++ } ++ ++ // Play death sound manually ++ private static void playDeathSound(net.minecraft.world.entity.LivingEntity victim, EntityDeathEvent event) { ++ if (event.shouldPlayDeathSound() && event.getDeathSound() != null && event.getDeathSoundCategory() != null) { ++ net.minecraft.world.entity.player.Player source = victim instanceof net.minecraft.world.entity.player.Player ? (net.minecraft.world.entity.player.Player) victim : null; ++ double x = event.getEntity().getLocation().getX(); ++ double y = event.getEntity().getLocation().getY(); ++ double z = event.getEntity().getLocation().getZ(); ++ net.minecraft.sounds.SoundEvent soundEffect = org.bukkit.craftbukkit.CraftSound.bukkitToMinecraft(event.getDeathSound()); ++ net.minecraft.sounds.SoundSource soundCategory = net.minecraft.sounds.SoundSource.valueOf(event.getDeathSoundCategory().name()); ++ victim.level().playSound(source, x, y, z, soundEffect, soundCategory, event.getDeathSoundVolume(), event.getDeathSoundPitch()); ++ } ++ } ++ // Paper end + /** + * Server methods + */ diff --git a/patches/server/0242-Allow-chests-to-be-placed-with-NBT-data.patch b/patches/server/0242-Allow-chests-to-be-placed-with-NBT-data.patch new file mode 100644 index 000000000000..4b282589d9b7 --- /dev/null +++ b/patches/server/0242-Allow-chests-to-be-placed-with-NBT-data.patch @@ -0,0 +1,31 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: BillyGalbreath +Date: Sat, 8 Sep 2018 18:43:31 -0500 +Subject: [PATCH] Allow chests to be placed with NBT data + + +diff --git a/src/main/java/net/minecraft/world/item/ItemStack.java b/src/main/java/net/minecraft/world/item/ItemStack.java +index 957c112b4145fda5078a6f8f1689935fa0290806..f00b756fe5dad616323e3b11e35e27353f347042 100644 +--- a/src/main/java/net/minecraft/world/item/ItemStack.java ++++ b/src/main/java/net/minecraft/world/item/ItemStack.java +@@ -496,6 +496,7 @@ public final class ItemStack implements DataComponentHolder { + enuminteractionresult = InteractionResult.FAIL; // cancel placement + // PAIL: Remove this when MC-99075 fixed + placeEvent.getPlayer().updateInventory(); ++ world.capturedTileEntities.clear(); // Paper - Allow chests to be placed with NBT data; clear out block entities as chests and such will pop loot + // revert back all captured blocks + world.preventPoiUpdated = true; // CraftBukkit - SPIGOT-5710 + for (BlockState blockstate : blocks) { +diff --git a/src/main/java/net/minecraft/world/level/block/entity/ChestBlockEntity.java b/src/main/java/net/minecraft/world/level/block/entity/ChestBlockEntity.java +index a3cdb4af2b7a3c7f884c8af2cba0d3d5e1c184a6..8847617f6a23e6d2fe9bf7444a2072dc53f741b8 100644 +--- a/src/main/java/net/minecraft/world/level/block/entity/ChestBlockEntity.java ++++ b/src/main/java/net/minecraft/world/level/block/entity/ChestBlockEntity.java +@@ -238,7 +238,7 @@ public class ChestBlockEntity extends RandomizableContainerBlockEntity implement + // CraftBukkit start + @Override + public boolean onlyOpCanSetNbt() { +- return true; ++ return false; // Paper - Allow chests to be placed with NBT data + } + // CraftBukkit end + } diff --git a/patches/server/0242-Improve-death-events.patch b/patches/server/0242-Improve-death-events.patch deleted file mode 100644 index 32eb4bc818c5..000000000000 --- a/patches/server/0242-Improve-death-events.patch +++ /dev/null @@ -1,512 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Phoenix616 -Date: Tue, 21 Aug 2018 01:39:35 +0100 -Subject: [PATCH] Improve death events - -This adds the ability to cancel the death events and to modify the sound -an entity makes when dying. (In cases were no sound should it will be -called with shouldPlaySound set to false allowing unsilencing of silent -entities) - -It makes handling of entity deaths a lot nicer as you no longer need -to listen on the damage event and calculate if the entity dies yourself -to cancel the death which has the benefit of also receiving the dropped -items and experience which is otherwise only properly possible by using -internal code. - -== AT == -public net.minecraft.world.entity.LivingEntity getDeathSound()Lnet/minecraft/sounds/SoundEvent; -public net.minecraft.world.entity.LivingEntity getSoundVolume()F - -diff --git a/src/main/java/net/minecraft/server/level/ServerPlayer.java b/src/main/java/net/minecraft/server/level/ServerPlayer.java -index 0dee94f1dd27a0d7e709367450c5ef7956e27217..fa8640f961b93dc811296131dfda58faa1908add 100644 ---- a/src/main/java/net/minecraft/server/level/ServerPlayer.java -+++ b/src/main/java/net/minecraft/server/level/ServerPlayer.java -@@ -269,6 +269,10 @@ public class ServerPlayer extends net.minecraft.world.entity.player.Player { - private int containerCounter; - public boolean wonGame; - private int containerUpdateDelay; // Paper - Configurable container update tick rate -+ // Paper start - cancellable death event -+ public boolean queueHealthUpdatePacket; -+ public net.minecraft.network.protocol.game.ClientboundSetHealthPacket queuedHealthUpdatePacket; -+ // Paper end - cancellable death event - - // CraftBukkit start - public CraftPlayer.TransferCookieConnection transferCookieConnection; -@@ -894,7 +898,7 @@ public class ServerPlayer extends net.minecraft.world.entity.player.Player { - - @Override - public void die(DamageSource damageSource) { -- this.gameEvent(GameEvent.ENTITY_DIE); -+ // this.gameEvent(GameEvent.ENTITY_DIE); // Paper - move below event cancellation check - boolean flag = this.level().getGameRules().getBoolean(GameRules.RULE_SHOWDEATHMESSAGES); - // CraftBukkit start - fire PlayerDeathEvent - if (this.isRemoved()) { -@@ -922,6 +926,16 @@ public class ServerPlayer extends net.minecraft.world.entity.player.Player { - String deathmessage = defaultMessage.getString(); - this.keepLevel = keepInventory; // SPIGOT-2222: pre-set keepLevel - org.bukkit.event.entity.PlayerDeathEvent event = CraftEventFactory.callPlayerDeathEvent(this, damageSource, loot, PaperAdventure.asAdventure(defaultMessage), keepInventory); // Paper - Adventure -+ // Paper start - cancellable death event -+ if (event.isCancelled()) { -+ // make compatible with plugins that might have already set the health in an event listener -+ if (this.getHealth() <= 0) { -+ this.setHealth((float) event.getReviveHealth()); -+ } -+ return; -+ } -+ this.gameEvent(GameEvent.ENTITY_DIE); // moved from the top of this method -+ // Paper end - - // SPIGOT-943 - only call if they have an inventory open - if (this.containerMenu != this.inventoryMenu) { -@@ -1070,8 +1084,17 @@ public class ServerPlayer extends net.minecraft.world.entity.player.Player { - } - } - } -- -- return super.hurt(source, amount); -+ // Paper start - cancellable death events -+ //return super.hurt(source, amount); -+ this.queueHealthUpdatePacket = true; -+ boolean damaged = super.hurt(source, amount); -+ this.queueHealthUpdatePacket = false; -+ if (this.queuedHealthUpdatePacket != null) { -+ this.connection.send(this.queuedHealthUpdatePacket); -+ this.queuedHealthUpdatePacket = null; -+ } -+ return damaged; -+ // Paper end - } - } - } -diff --git a/src/main/java/net/minecraft/world/entity/LivingEntity.java b/src/main/java/net/minecraft/world/entity/LivingEntity.java -index 39dff0a38b53624c935f27cc86ff036c831f407f..bdee5725029eda3a0e7bee407286480c0bb47db1 100644 ---- a/src/main/java/net/minecraft/world/entity/LivingEntity.java -+++ b/src/main/java/net/minecraft/world/entity/LivingEntity.java -@@ -283,6 +283,7 @@ public abstract class LivingEntity extends Entity implements Attackable { - public Set collidableExemptions = new HashSet<>(); - public boolean bukkitPickUpLoot; - public org.bukkit.craftbukkit.entity.CraftLivingEntity getBukkitLivingEntity() { return (org.bukkit.craftbukkit.entity.CraftLivingEntity) super.getBukkitEntity(); } // Paper -+ public boolean silentDeath = false; // Paper - mark entity as dying silently for cancellable death event - - @Override - public float getBukkitYaw() { -@@ -1537,11 +1538,12 @@ public abstract class LivingEntity extends Entity implements Attackable { - - if (this.isDeadOrDying()) { - if (!this.checkTotemDeathProtection(source)) { -- if (flag1) { -- this.makeSound(this.getDeathSound()); -- } -+ // Paper start - moved into CraftEventFactory event caller for cancellable death event -+ this.silentDeath = !flag1; // mark entity as dying silently -+ // Paper end - - this.die(source); -+ this.silentDeath = false; // Paper - cancellable death event - reset to default - } - } else if (flag1) { - this.playHurtSound(source); -@@ -1700,6 +1702,7 @@ public abstract class LivingEntity extends Entity implements Attackable { - Entity entity = damageSource.getEntity(); - LivingEntity entityliving = this.getKillCredit(); - -+ /* // Paper - move down to make death event cancellable - this is the awardKillScore below - if (this.deathScore >= 0 && entityliving != null) { - entityliving.awardKillScore(this, this.deathScore, damageSource); - } -@@ -1711,24 +1714,59 @@ public abstract class LivingEntity extends Entity implements Attackable { - if (!this.level().isClientSide && this.hasCustomName()) { - if (org.spigotmc.SpigotConfig.logNamedDeaths) LivingEntity.LOGGER.info("Named entity {} died: {}", this, this.getCombatTracker().getDeathMessage().getString()); // Spigot - } -+ */ // Paper - move down to make death event cancellable - this is the awardKillScore below - - this.dead = true; -- this.getCombatTracker().recheckStatus(); -+ // Paper - moved into if below - Level world = this.level(); - - if (world instanceof ServerLevel) { - ServerLevel worldserver = (ServerLevel) world; -+ // Paper - move below into if for onKill -+ -+ // Paper start -+ org.bukkit.event.entity.EntityDeathEvent deathEvent = this.dropAllDeathLoot(worldserver, damageSource); -+ if (deathEvent == null || !deathEvent.isCancelled()) { -+ if (this.deathScore >= 0 && entityliving != null) { -+ entityliving.awardKillScore(this, this.deathScore, damageSource); -+ } -+ // Paper start - clear equipment if event is not cancelled -+ if (this instanceof Mob) { -+ for (EquipmentSlot slot : this.clearedEquipmentSlots) { -+ this.setItemSlot(slot, ItemStack.EMPTY); -+ } -+ this.clearedEquipmentSlots.clear(); -+ } -+ // Paper end -+ -+ if (this.isSleeping()) { -+ this.stopSleeping(); -+ } - -- if (entity == null || entity.killedEntity(worldserver, this)) { -+ if (!this.level().isClientSide && this.hasCustomName()) { -+ if (org.spigotmc.SpigotConfig.logNamedDeaths) LivingEntity.LOGGER.info("Named entity {} died: {}", this, this.getCombatTracker().getDeathMessage().getString()); // Spigot -+ } -+ -+ this.getCombatTracker().recheckStatus(); -+ if (entity != null) { -+ entity.killedEntity((ServerLevel) this.level(), this); -+ } - this.gameEvent(GameEvent.ENTITY_DIE); -- this.dropAllDeathLoot(worldserver, damageSource); -+ } else { -+ this.dead = false; -+ this.setHealth((float) deathEvent.getReviveHealth()); -+ } -+ // Paper end - this.createWitherRose(entityliving); - } - -+ // Paper start -+ if (this.dead) { // Paper - this.level().broadcastEntityEvent(this, (byte) 3); -- } - - this.setPose(Pose.DYING); -+ } -+ // Paper end - } - } - -@@ -1736,7 +1774,7 @@ public abstract class LivingEntity extends Entity implements Attackable { - if (!this.level().isClientSide) { - boolean flag = false; - -- if (adversary instanceof WitherBoss) { -+ if (this.dead && adversary instanceof WitherBoss) { // Paper - if (this.level().getGameRules().getBoolean(GameRules.RULE_MOBGRIEFING)) { - BlockPos blockposition = this.blockPosition(); - BlockState iblockdata = Blocks.WITHER_ROSE.defaultBlockState(); -@@ -1765,24 +1803,37 @@ public abstract class LivingEntity extends Entity implements Attackable { - } - } - -- protected void dropAllDeathLoot(ServerLevel world, DamageSource damageSource) { -+ // Paper start -+ protected boolean clearEquipmentSlots = true; -+ protected Set clearedEquipmentSlots = new java.util.HashSet<>(); -+ protected org.bukkit.event.entity.EntityDeathEvent dropAllDeathLoot(ServerLevel world, DamageSource damageSource) { -+ // Paper end - boolean flag = this.lastHurtByPlayerTime > 0; - - this.dropEquipment(); // CraftBukkit - from below - if (this.shouldDropLoot() && world.getGameRules().getBoolean(GameRules.RULE_DOMOBLOOT)) { - this.dropFromLootTable(damageSource, flag); -+ // Paper start -+ final boolean prev = this.clearEquipmentSlots; -+ this.clearEquipmentSlots = false; -+ this.clearedEquipmentSlots.clear(); -+ // Paper end - this.dropCustomDeathLoot(world, damageSource, flag); -+ this.clearEquipmentSlots = prev; // Paper - } - // CraftBukkit start - Call death event -- CraftEventFactory.callEntityDeathEvent(this, damageSource, this.drops); -+ org.bukkit.event.entity.EntityDeathEvent deathEvent = CraftEventFactory.callEntityDeathEvent(this, damageSource, this.drops); // Paper -+ this.postDeathDropItems(deathEvent); // Paper - this.drops = new ArrayList<>(); - // CraftBukkit end - - // this.dropEquipment();// CraftBukkit - moved up - this.dropExperience(damageSource.getEntity()); -+ return deathEvent; // Paper - } - - protected void dropEquipment() {} -+ protected void postDeathDropItems(org.bukkit.event.entity.EntityDeathEvent event) {} // Paper - method for post death logic that cannot be ran before the event is potentially cancelled - - public int getExpReward(@Nullable Entity entity) { // CraftBukkit - Level world = this.level(); -diff --git a/src/main/java/net/minecraft/world/entity/Mob.java b/src/main/java/net/minecraft/world/entity/Mob.java -index 25a71cc5ca8cf8a5070cd24eb56fe0d79e765669..b46572f6e3b52f498b395d3b8c5def2aa799ff03 100644 ---- a/src/main/java/net/minecraft/world/entity/Mob.java -+++ b/src/main/java/net/minecraft/world/entity/Mob.java -@@ -1123,6 +1123,12 @@ public abstract class Mob extends LivingEntity implements EquipmentUser, Leashab - - } - -+ // Paper start -+ protected boolean shouldSkipLoot(EquipmentSlot slot) { // method to avoid to fallback into the global mob loot logic (i.e fox) -+ return false; -+ } -+ // Paper end -+ - @Override - protected void dropCustomDeathLoot(ServerLevel world, DamageSource source, boolean causedByPlayer) { - super.dropCustomDeathLoot(world, source, causedByPlayer); -@@ -1131,6 +1137,7 @@ public abstract class Mob extends LivingEntity implements EquipmentUser, Leashab - - for (int j = 0; j < i; ++j) { - EquipmentSlot enumitemslot = aenumitemslot[j]; -+ if (this.shouldSkipLoot(enumitemslot)) continue; // Paper - ItemStack itemstack = this.getItemBySlot(enumitemslot); - float f = this.getEquipmentDropChance(enumitemslot); - -@@ -1155,7 +1162,13 @@ public abstract class Mob extends LivingEntity implements EquipmentUser, Leashab - } - - this.spawnAtLocation(itemstack); -+ if (this.clearEquipmentSlots) { // Paper - this.setItemSlot(enumitemslot, ItemStack.EMPTY); -+ // Paper start -+ } else { -+ this.clearedEquipmentSlots.add(enumitemslot); -+ } -+ // Paper end - } - } - } -diff --git a/src/main/java/net/minecraft/world/entity/animal/Fox.java b/src/main/java/net/minecraft/world/entity/animal/Fox.java -index a6788da1505f9e119c03b94488f5e006da13e918..e46c8231ee318eda0512afbb6343b426b4838643 100644 ---- a/src/main/java/net/minecraft/world/entity/animal/Fox.java -+++ b/src/main/java/net/minecraft/world/entity/animal/Fox.java -@@ -704,16 +704,38 @@ public class Fox extends Animal implements VariantHolder { - return this.getTrustedUUIDs().contains(uuid); - } - -+ // Paper start - handle the bitten item separately like vanilla - @Override -- protected void dropAllDeathLoot(ServerLevel world, DamageSource damageSource) { -+ protected boolean shouldSkipLoot(EquipmentSlot slot) { -+ return slot == EquipmentSlot.MAINHAND; -+ } -+ // Paper end -+ -+ @Override -+ // Paper start - Cancellable death event -+ protected org.bukkit.event.entity.EntityDeathEvent dropAllDeathLoot(ServerLevel world, DamageSource damageSource) { - ItemStack itemstack = this.getItemBySlot(EquipmentSlot.MAINHAND); - -- if (!itemstack.isEmpty()) { -+ boolean releaseMouth = false; -+ if (!itemstack.isEmpty() && this.level().getGameRules().getBoolean(GameRules.RULE_DOMOBLOOT)) { // Fix MC-153010 - this.spawnAtLocation(itemstack); -+ releaseMouth = true; -+ } -+ -+ org.bukkit.event.entity.EntityDeathEvent deathEvent = super.dropAllDeathLoot(world, damageSource); -+ -+ // Below is code to drop -+ -+ if (deathEvent == null || deathEvent.isCancelled()) { -+ return deathEvent; -+ } -+ -+ if (releaseMouth) { -+ // Paper end - Cancellable death event - this.setItemSlot(EquipmentSlot.MAINHAND, ItemStack.EMPTY); - } - -- super.dropAllDeathLoot(world, damageSource); -+ return deathEvent; // Paper - Cancellable death event - } - - public static boolean isPathClear(Fox fox, LivingEntity chasedEntity) { -diff --git a/src/main/java/net/minecraft/world/entity/animal/horse/AbstractChestedHorse.java b/src/main/java/net/minecraft/world/entity/animal/horse/AbstractChestedHorse.java -index 767817fb1418958c89d0db9da4ae7eb8a5a16076..5654c614f07f07ff642ba4851b0cb6fa185924ae 100644 ---- a/src/main/java/net/minecraft/world/entity/animal/horse/AbstractChestedHorse.java -+++ b/src/main/java/net/minecraft/world/entity/animal/horse/AbstractChestedHorse.java -@@ -71,9 +71,17 @@ public abstract class AbstractChestedHorse extends AbstractHorse { - this.spawnAtLocation(Blocks.CHEST); - } - -+ //this.setChest(false); // Paper - moved to post death logic -+ } -+ } -+ -+ // Paper start -+ protected void postDeathDropItems(org.bukkit.event.entity.EntityDeathEvent event) { -+ if (this.hasChest() && (event == null || !event.isCancelled())) { - this.setChest(false); - } - } -+ // Paper end - - @Override - public void addAdditionalSaveData(CompoundTag nbt) { -diff --git a/src/main/java/net/minecraft/world/entity/decoration/ArmorStand.java b/src/main/java/net/minecraft/world/entity/decoration/ArmorStand.java -index ee3902cbada46ffb78c42dbf6f00c859546c76e1..92bb0c63330ad3a4cb13b2dc655020714e9b1ffd 100644 ---- a/src/main/java/net/minecraft/world/entity/decoration/ArmorStand.java -+++ b/src/main/java/net/minecraft/world/entity/decoration/ArmorStand.java -@@ -505,8 +505,10 @@ public class ArmorStand extends LivingEntity { - } - // CraftBukkit end - if (source.is(DamageTypeTags.IS_EXPLOSION)) { -- this.brokenByAnything(worldserver, source); -- this.kill(source); // CraftBukkit -+ // Paper start - avoid duplicate event call -+ org.bukkit.event.entity.EntityDeathEvent event = this.brokenByAnything(worldserver, source); -+ if (!event.isCancelled()) this.kill(source, false); // CraftBukkit -+ // Paper end - return false; - } else if (source.is(DamageTypeTags.IGNITES_ARMOR_STANDS)) { - if (this.isOnFire()) { -@@ -549,9 +551,9 @@ public class ArmorStand extends LivingEntity { - this.gameEvent(GameEvent.ENTITY_DAMAGE, source.getEntity()); - this.lastHit = i; - } else { -- this.brokenByPlayer(worldserver, source); -+ org.bukkit.event.entity.EntityDeathEvent event = this.brokenByPlayer(worldserver, source); // Paper - this.showBreakingParticles(); -- this.discard(EntityRemoveEvent.Cause.DEATH); // CraftBukkit - SPIGOT-4890: remain as this.discard() since above damagesource method will call death event -+ if (!event.isCancelled()) this.kill(source, false); // Paper - we still need to kill to follow vanilla logic (emit the game event etc...) - } - - return true; -@@ -604,8 +606,10 @@ public class ArmorStand extends LivingEntity { - - f1 -= amount; - if (f1 <= 0.5F) { -- this.brokenByAnything(world, damageSource); -- this.kill(damageSource); // CraftBukkit -+ // Paper start - avoid duplicate event call -+ org.bukkit.event.entity.EntityDeathEvent event = this.brokenByAnything(world, damageSource); -+ if (!event.isCancelled()) this.kill(damageSource, false); // CraftBukkit -+ // Paper end - } else { - this.setHealth(f1); - this.gameEvent(GameEvent.ENTITY_DAMAGE, damageSource.getEntity()); -@@ -613,15 +617,15 @@ public class ArmorStand extends LivingEntity { - - } - -- private void brokenByPlayer(ServerLevel world, DamageSource damageSource) { -+ private org.bukkit.event.entity.EntityDeathEvent brokenByPlayer(ServerLevel world, DamageSource damageSource) { // Paper - ItemStack itemstack = new ItemStack(Items.ARMOR_STAND); - - itemstack.set(DataComponents.CUSTOM_NAME, this.getCustomName()); - this.drops.add(org.bukkit.craftbukkit.inventory.CraftItemStack.asBukkitCopy(itemstack)); // CraftBukkit - add to drops -- this.brokenByAnything(world, damageSource); -+ return this.brokenByAnything(world, damageSource); // Paper - } - -- private void brokenByAnything(ServerLevel world, DamageSource damageSource) { -+ private org.bukkit.event.entity.EntityDeathEvent brokenByAnything(ServerLevel world, DamageSource damageSource) { // Paper - this.playBrokenSound(); - // this.dropAllDeathLoot(worldserver, damagesource); // CraftBukkit - moved down - -@@ -643,7 +647,7 @@ public class ArmorStand extends LivingEntity { - this.armorItems.set(i, ItemStack.EMPTY); - } - } -- this.dropAllDeathLoot(world, damageSource); // CraftBukkit - moved from above -+ return this.dropAllDeathLoot(world, damageSource); // CraftBukkit - moved from above // Paper - - } - -@@ -770,7 +774,15 @@ public class ArmorStand extends LivingEntity { - } - - public void kill(DamageSource damageSource) { -- org.bukkit.craftbukkit.event.CraftEventFactory.callEntityDeathEvent(this, (damageSource == null ? this.damageSources().genericKill() : damageSource), this.drops); // CraftBukkit - call event -+ // Paper start - make cancellable -+ this.kill(damageSource, true); -+ } -+ public void kill(DamageSource damageSource, boolean callEvent) { -+ if (callEvent) { -+ org.bukkit.event.entity.EntityDeathEvent event = org.bukkit.craftbukkit.event.CraftEventFactory.callEntityDeathEvent(this, (damageSource == null ? this.damageSources().genericKill() : damageSource), this.drops); // CraftBukkit - call event -+ if (event.isCancelled()) return; -+ } -+ // Paper end - this.remove(Entity.RemovalReason.KILLED, EntityRemoveEvent.Cause.DEATH); // CraftBukkit - add Bukkit remove cause - // CraftBukkit end - this.gameEvent(GameEvent.ENTITY_DIE); -diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java -index a7e611aaeb457820ad303b95822d8ea86b060477..004ac565d4124f6059d530034cf0c9a28f0be467 100644 ---- a/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java -+++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java -@@ -2517,7 +2517,14 @@ public class CraftPlayer extends CraftHumanEntity implements Player { - @Override - public void sendHealthUpdate() { - FoodData foodData = this.getHandle().getFoodData(); -- this.sendHealthUpdate(this.getScaledHealth(), foodData.getFoodLevel(), foodData.getSaturationLevel()); -+ // Paper start - cancellable death event -+ ClientboundSetHealthPacket packet = new ClientboundSetHealthPacket(this.getScaledHealth(), foodData.getFoodLevel(), foodData.getSaturationLevel()); -+ if (this.getHandle().queueHealthUpdatePacket) { -+ this.getHandle().queuedHealthUpdatePacket = packet; -+ } else { -+ this.getHandle().connection.send(packet); -+ } -+ // Paper end - } - - public void injectScaledMaxHealth(Collection collection, boolean force) { -diff --git a/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java b/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java -index 0613bdf3c2325d5cab64783af7211b07fcf5124a..6a018f9c289a539b07855d75e4cc2d3c2828ded1 100644 ---- a/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java -+++ b/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java -@@ -903,9 +903,16 @@ public class CraftEventFactory { - CraftLivingEntity entity = (CraftLivingEntity) victim.getBukkitEntity(); - CraftDamageSource bukkitDamageSource = new CraftDamageSource(damageSource); - EntityDeathEvent event = new EntityDeathEvent(entity, bukkitDamageSource, drops, victim.getExpReward(damageSource.getEntity())); -+ populateFields(victim, event); // Paper - make cancellable - CraftWorld world = (CraftWorld) entity.getWorld(); - Bukkit.getServer().getPluginManager().callEvent(event); - -+ // Paper start - make cancellable -+ if (event.isCancelled()) { -+ return event; -+ } -+ playDeathSound(victim, event); -+ // Paper end - victim.expToDrop = event.getDroppedExp(); - - for (org.bukkit.inventory.ItemStack stack : event.getDrops()) { -@@ -923,7 +930,14 @@ public class CraftEventFactory { - PlayerDeathEvent event = new PlayerDeathEvent(entity, bukkitDamageSource, drops, victim.getExpReward(damageSource.getEntity()), 0, deathMessage); - event.setKeepInventory(keepInventory); - event.setKeepLevel(victim.keepLevel); // SPIGOT-2222: pre-set keepLevel -+ populateFields(victim, event); // Paper - make cancellable - Bukkit.getServer().getPluginManager().callEvent(event); -+ // Paper start - make cancellable -+ if (event.isCancelled()) { -+ return event; -+ } -+ playDeathSound(victim, event); -+ // Paper end - - victim.keepLevel = event.getKeepLevel(); - victim.newLevel = event.getNewLevel(); -@@ -946,6 +960,31 @@ public class CraftEventFactory { - return event; - } - -+ // Paper start - helper methods for making death event cancellable -+ // Add information to death event -+ private static void populateFields(net.minecraft.world.entity.LivingEntity victim, EntityDeathEvent event) { -+ event.setReviveHealth(event.getEntity().getAttribute(org.bukkit.attribute.Attribute.GENERIC_MAX_HEALTH).getValue()); -+ event.setShouldPlayDeathSound(!victim.silentDeath && !victim.isSilent()); -+ net.minecraft.sounds.SoundEvent soundEffect = victim.getDeathSound(); -+ event.setDeathSound(soundEffect != null ? org.bukkit.craftbukkit.CraftSound.minecraftToBukkit(soundEffect) : null); -+ event.setDeathSoundCategory(org.bukkit.SoundCategory.valueOf(victim.getSoundSource().name())); -+ event.setDeathSoundVolume(victim.getSoundVolume()); -+ event.setDeathSoundPitch(victim.getVoicePitch()); -+ } -+ -+ // Play death sound manually -+ private static void playDeathSound(net.minecraft.world.entity.LivingEntity victim, EntityDeathEvent event) { -+ if (event.shouldPlayDeathSound() && event.getDeathSound() != null && event.getDeathSoundCategory() != null) { -+ net.minecraft.world.entity.player.Player source = victim instanceof net.minecraft.world.entity.player.Player ? (net.minecraft.world.entity.player.Player) victim : null; -+ double x = event.getEntity().getLocation().getX(); -+ double y = event.getEntity().getLocation().getY(); -+ double z = event.getEntity().getLocation().getZ(); -+ net.minecraft.sounds.SoundEvent soundEffect = org.bukkit.craftbukkit.CraftSound.bukkitToMinecraft(event.getDeathSound()); -+ net.minecraft.sounds.SoundSource soundCategory = net.minecraft.sounds.SoundSource.valueOf(event.getDeathSoundCategory().name()); -+ victim.level().playSound(source, x, y, z, soundEffect, soundCategory, event.getDeathSoundVolume(), event.getDeathSoundPitch()); -+ } -+ } -+ // Paper end - /** - * Server methods - */ diff --git a/patches/server/0243-Allow-chests-to-be-placed-with-NBT-data.patch b/patches/server/0243-Allow-chests-to-be-placed-with-NBT-data.patch deleted file mode 100644 index 92d7a5d3fce5..000000000000 --- a/patches/server/0243-Allow-chests-to-be-placed-with-NBT-data.patch +++ /dev/null @@ -1,31 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: BillyGalbreath -Date: Sat, 8 Sep 2018 18:43:31 -0500 -Subject: [PATCH] Allow chests to be placed with NBT data - - -diff --git a/src/main/java/net/minecraft/world/item/ItemStack.java b/src/main/java/net/minecraft/world/item/ItemStack.java -index b0d0e08e81e3b87e5d4faf62e9afe9606c254115..2ee949e1b0015c62499c557d5e359df4b9de6027 100644 ---- a/src/main/java/net/minecraft/world/item/ItemStack.java -+++ b/src/main/java/net/minecraft/world/item/ItemStack.java -@@ -490,6 +490,7 @@ public final class ItemStack implements DataComponentHolder { - enuminteractionresult = InteractionResult.FAIL; // cancel placement - // PAIL: Remove this when MC-99075 fixed - placeEvent.getPlayer().updateInventory(); -+ world.capturedTileEntities.clear(); // Paper - Allow chests to be placed with NBT data; clear out block entities as chests and such will pop loot - // revert back all captured blocks - world.preventPoiUpdated = true; // CraftBukkit - SPIGOT-5710 - for (BlockState blockstate : blocks) { -diff --git a/src/main/java/net/minecraft/world/level/block/entity/ChestBlockEntity.java b/src/main/java/net/minecraft/world/level/block/entity/ChestBlockEntity.java -index 9d6262e286a00f840d88d6eb6bfdb304467466e3..b88aa184cd06a0485146f58a5b61a56a50911209 100644 ---- a/src/main/java/net/minecraft/world/level/block/entity/ChestBlockEntity.java -+++ b/src/main/java/net/minecraft/world/level/block/entity/ChestBlockEntity.java -@@ -238,7 +238,7 @@ public class ChestBlockEntity extends RandomizableContainerBlockEntity implement - // CraftBukkit start - @Override - public boolean onlyOpCanSetNbt() { -- return true; -+ return false; // Paper - Allow chests to be placed with NBT data - } - // CraftBukkit end - } diff --git a/patches/server/0243-Mob-Pathfinding-API.patch b/patches/server/0243-Mob-Pathfinding-API.patch new file mode 100644 index 000000000000..638eb9310c7d --- /dev/null +++ b/patches/server/0243-Mob-Pathfinding-API.patch @@ -0,0 +1,209 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Aikar +Date: Sun, 9 Sep 2018 13:30:00 -0400 +Subject: [PATCH] Mob Pathfinding API + +Implements Pathfinding API for mobs + +== AT == +public net.minecraft.world.entity.ai.navigation.PathNavigation pathFinder +public net.minecraft.world.level.pathfinder.PathFinder nodeEvaluator +public net.minecraft.world.level.pathfinder.Path nodes + +diff --git a/src/main/java/com/destroystokyo/paper/entity/PaperPathfinder.java b/src/main/java/com/destroystokyo/paper/entity/PaperPathfinder.java +new file mode 100644 +index 0000000000000000000000000000000000000000..946cbc955683e81933e0f3c0db5f8cc4ec437a76 +--- /dev/null ++++ b/src/main/java/com/destroystokyo/paper/entity/PaperPathfinder.java +@@ -0,0 +1,148 @@ ++package com.destroystokyo.paper.entity; ++ ++import org.apache.commons.lang.Validate; ++import org.bukkit.Location; ++import org.bukkit.craftbukkit.entity.CraftLivingEntity; ++import org.bukkit.entity.LivingEntity; ++import org.bukkit.entity.Mob; ++import javax.annotation.Nonnull; ++import javax.annotation.Nullable; ++import net.minecraft.world.level.pathfinder.Node; ++import net.minecraft.world.level.pathfinder.Path; ++import java.util.ArrayList; ++import java.util.List; ++ ++public class PaperPathfinder implements com.destroystokyo.paper.entity.Pathfinder { ++ ++ private net.minecraft.world.entity.Mob entity; ++ ++ public PaperPathfinder(net.minecraft.world.entity.Mob entity) { ++ this.entity = entity; ++ } ++ ++ @Override ++ public Mob getEntity() { ++ return (Mob) entity.getBukkitEntity(); ++ } ++ ++ public void setHandle(net.minecraft.world.entity.Mob entity) { ++ this.entity = entity; ++ } ++ ++ @Override ++ public void stopPathfinding() { ++ entity.getNavigation().stop(); ++ } ++ ++ @Override ++ public boolean hasPath() { ++ return entity.getNavigation().getPath() != null && !entity.getNavigation().getPath().isDone(); ++ } ++ ++ @Nullable ++ @Override ++ public PathResult getCurrentPath() { ++ Path path = entity.getNavigation().getPath(); ++ return path != null && !path.isDone() ? new PaperPathResult(path) : null; ++ } ++ ++ @Nullable ++ @Override ++ public PathResult findPath(Location loc) { ++ Validate.notNull(loc, "Location can not be null"); ++ Path path = entity.getNavigation().createPath(loc.getX(), loc.getY(), loc.getZ(), 0); ++ return path != null ? new PaperPathResult(path) : null; ++ } ++ ++ @Nullable ++ @Override ++ public PathResult findPath(LivingEntity target) { ++ Validate.notNull(target, "Target can not be null"); ++ Path path = entity.getNavigation().createPath(((CraftLivingEntity) target).getHandle(), 0); ++ return path != null ? new PaperPathResult(path) : null; ++ } ++ ++ @Override ++ public boolean moveTo(@Nonnull PathResult path, double speed) { ++ Validate.notNull(path, "PathResult can not be null"); ++ Path pathEntity = ((PaperPathResult) path).path; ++ return entity.getNavigation().moveTo(pathEntity, speed); ++ } ++ ++ @Override ++ public boolean canOpenDoors() { ++ return entity.getNavigation().pathFinder.nodeEvaluator.canOpenDoors(); ++ } ++ ++ @Override ++ public void setCanOpenDoors(boolean canOpenDoors) { ++ entity.getNavigation().pathFinder.nodeEvaluator.setCanOpenDoors(canOpenDoors); ++ } ++ ++ @Override ++ public boolean canPassDoors() { ++ return entity.getNavigation().pathFinder.nodeEvaluator.canPassDoors(); ++ } ++ ++ @Override ++ public void setCanPassDoors(boolean canPassDoors) { ++ entity.getNavigation().pathFinder.nodeEvaluator.setCanPassDoors(canPassDoors); ++ } ++ ++ @Override ++ public boolean canFloat() { ++ return entity.getNavigation().pathFinder.nodeEvaluator.canFloat(); ++ } ++ ++ @Override ++ public void setCanFloat(boolean canFloat) { ++ entity.getNavigation().pathFinder.nodeEvaluator.setCanFloat(canFloat); ++ } ++ ++ public class PaperPathResult implements com.destroystokyo.paper.entity.PaperPathfinder.PathResult { ++ ++ private final Path path; ++ PaperPathResult(Path path) { ++ this.path = path; ++ } ++ ++ @Nullable ++ @Override ++ public Location getFinalPoint() { ++ Node point = path.getEndNode(); ++ return point != null ? toLoc(point) : null; ++ } ++ ++ @Override ++ public boolean canReachFinalPoint() { ++ return path.canReach(); ++ } ++ ++ @Override ++ public List getPoints() { ++ List points = new ArrayList<>(); ++ for (Node point : path.nodes) { ++ points.add(toLoc(point)); ++ } ++ return points; ++ } ++ ++ @Override ++ public int getNextPointIndex() { ++ return path.getNextNodeIndex(); ++ } ++ ++ @Nullable ++ @Override ++ public Location getNextPoint() { ++ if (!path.hasNext()) { ++ return null; ++ } ++ return toLoc(path.nodes.get(path.getNextNodeIndex())); ++ } ++ } ++ ++ private Location toLoc(Node point) { ++ return new Location(entity.level().getWorld(), point.x, point.y, point.z); ++ } ++} +diff --git a/src/main/java/net/minecraft/world/level/pathfinder/Path.java b/src/main/java/net/minecraft/world/level/pathfinder/Path.java +index f6419f3b345e9e21a05b315aa4669090d7da4194..d9d0fff9962131808d54cca20f209df50b8e4af1 100644 +--- a/src/main/java/net/minecraft/world/level/pathfinder/Path.java ++++ b/src/main/java/net/minecraft/world/level/pathfinder/Path.java +@@ -18,6 +18,7 @@ public class Path { + private final BlockPos target; + private final float distToTarget; + private final boolean reached; ++ public boolean hasNext() { return getNextNodeIndex() < this.nodes.size(); } // Paper - Mob Pathfinding API + + public Path(List nodes, BlockPos target, boolean reachesTarget) { + this.nodes = nodes; +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftMob.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftMob.java +index 31f7b19b8978d941df801c97ad8989ef18d9fe2d..26c9bc53fe50636ca1eb32144c648f382d4172ff 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftMob.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftMob.java +@@ -14,8 +14,11 @@ import org.bukkit.loot.LootTable; + public abstract class CraftMob extends CraftLivingEntity implements Mob { + public CraftMob(CraftServer server, net.minecraft.world.entity.Mob entity) { + super(server, entity); ++ paperPathfinder = new com.destroystokyo.paper.entity.PaperPathfinder(entity); // Paper - Mob Pathfinding API + } + ++ private final com.destroystokyo.paper.entity.PaperPathfinder paperPathfinder; // Paper - Mob Pathfinding API ++ @Override public com.destroystokyo.paper.entity.Pathfinder getPathfinder() { return paperPathfinder; } // Paper - Mob Pathfinding API + @Override + public void setTarget(LivingEntity target) { + Preconditions.checkState(!this.getHandle().generation, "Cannot set target during world generation"); +@@ -56,6 +59,14 @@ public abstract class CraftMob extends CraftLivingEntity implements Mob { + return (net.minecraft.world.entity.Mob) this.entity; + } + ++ // Paper start - Mob Pathfinding API ++ @Override ++ public void setHandle(net.minecraft.world.entity.Entity entity) { ++ super.setHandle(entity); ++ paperPathfinder.setHandle(getHandle()); ++ } ++ // Paper end - Mob Pathfinding API ++ + @Override + public String toString() { + return "CraftMob"; diff --git a/patches/server/0244-Mob-Pathfinding-API.patch b/patches/server/0244-Mob-Pathfinding-API.patch deleted file mode 100644 index 427d08a15744..000000000000 --- a/patches/server/0244-Mob-Pathfinding-API.patch +++ /dev/null @@ -1,209 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Aikar -Date: Sun, 9 Sep 2018 13:30:00 -0400 -Subject: [PATCH] Mob Pathfinding API - -Implements Pathfinding API for mobs - -== AT == -public net.minecraft.world.entity.ai.navigation.PathNavigation pathFinder -public net.minecraft.world.level.pathfinder.PathFinder nodeEvaluator -public net.minecraft.world.level.pathfinder.Path nodes - -diff --git a/src/main/java/com/destroystokyo/paper/entity/PaperPathfinder.java b/src/main/java/com/destroystokyo/paper/entity/PaperPathfinder.java -new file mode 100644 -index 0000000000000000000000000000000000000000..946cbc955683e81933e0f3c0db5f8cc4ec437a76 ---- /dev/null -+++ b/src/main/java/com/destroystokyo/paper/entity/PaperPathfinder.java -@@ -0,0 +1,148 @@ -+package com.destroystokyo.paper.entity; -+ -+import org.apache.commons.lang.Validate; -+import org.bukkit.Location; -+import org.bukkit.craftbukkit.entity.CraftLivingEntity; -+import org.bukkit.entity.LivingEntity; -+import org.bukkit.entity.Mob; -+import javax.annotation.Nonnull; -+import javax.annotation.Nullable; -+import net.minecraft.world.level.pathfinder.Node; -+import net.minecraft.world.level.pathfinder.Path; -+import java.util.ArrayList; -+import java.util.List; -+ -+public class PaperPathfinder implements com.destroystokyo.paper.entity.Pathfinder { -+ -+ private net.minecraft.world.entity.Mob entity; -+ -+ public PaperPathfinder(net.minecraft.world.entity.Mob entity) { -+ this.entity = entity; -+ } -+ -+ @Override -+ public Mob getEntity() { -+ return (Mob) entity.getBukkitEntity(); -+ } -+ -+ public void setHandle(net.minecraft.world.entity.Mob entity) { -+ this.entity = entity; -+ } -+ -+ @Override -+ public void stopPathfinding() { -+ entity.getNavigation().stop(); -+ } -+ -+ @Override -+ public boolean hasPath() { -+ return entity.getNavigation().getPath() != null && !entity.getNavigation().getPath().isDone(); -+ } -+ -+ @Nullable -+ @Override -+ public PathResult getCurrentPath() { -+ Path path = entity.getNavigation().getPath(); -+ return path != null && !path.isDone() ? new PaperPathResult(path) : null; -+ } -+ -+ @Nullable -+ @Override -+ public PathResult findPath(Location loc) { -+ Validate.notNull(loc, "Location can not be null"); -+ Path path = entity.getNavigation().createPath(loc.getX(), loc.getY(), loc.getZ(), 0); -+ return path != null ? new PaperPathResult(path) : null; -+ } -+ -+ @Nullable -+ @Override -+ public PathResult findPath(LivingEntity target) { -+ Validate.notNull(target, "Target can not be null"); -+ Path path = entity.getNavigation().createPath(((CraftLivingEntity) target).getHandle(), 0); -+ return path != null ? new PaperPathResult(path) : null; -+ } -+ -+ @Override -+ public boolean moveTo(@Nonnull PathResult path, double speed) { -+ Validate.notNull(path, "PathResult can not be null"); -+ Path pathEntity = ((PaperPathResult) path).path; -+ return entity.getNavigation().moveTo(pathEntity, speed); -+ } -+ -+ @Override -+ public boolean canOpenDoors() { -+ return entity.getNavigation().pathFinder.nodeEvaluator.canOpenDoors(); -+ } -+ -+ @Override -+ public void setCanOpenDoors(boolean canOpenDoors) { -+ entity.getNavigation().pathFinder.nodeEvaluator.setCanOpenDoors(canOpenDoors); -+ } -+ -+ @Override -+ public boolean canPassDoors() { -+ return entity.getNavigation().pathFinder.nodeEvaluator.canPassDoors(); -+ } -+ -+ @Override -+ public void setCanPassDoors(boolean canPassDoors) { -+ entity.getNavigation().pathFinder.nodeEvaluator.setCanPassDoors(canPassDoors); -+ } -+ -+ @Override -+ public boolean canFloat() { -+ return entity.getNavigation().pathFinder.nodeEvaluator.canFloat(); -+ } -+ -+ @Override -+ public void setCanFloat(boolean canFloat) { -+ entity.getNavigation().pathFinder.nodeEvaluator.setCanFloat(canFloat); -+ } -+ -+ public class PaperPathResult implements com.destroystokyo.paper.entity.PaperPathfinder.PathResult { -+ -+ private final Path path; -+ PaperPathResult(Path path) { -+ this.path = path; -+ } -+ -+ @Nullable -+ @Override -+ public Location getFinalPoint() { -+ Node point = path.getEndNode(); -+ return point != null ? toLoc(point) : null; -+ } -+ -+ @Override -+ public boolean canReachFinalPoint() { -+ return path.canReach(); -+ } -+ -+ @Override -+ public List getPoints() { -+ List points = new ArrayList<>(); -+ for (Node point : path.nodes) { -+ points.add(toLoc(point)); -+ } -+ return points; -+ } -+ -+ @Override -+ public int getNextPointIndex() { -+ return path.getNextNodeIndex(); -+ } -+ -+ @Nullable -+ @Override -+ public Location getNextPoint() { -+ if (!path.hasNext()) { -+ return null; -+ } -+ return toLoc(path.nodes.get(path.getNextNodeIndex())); -+ } -+ } -+ -+ private Location toLoc(Node point) { -+ return new Location(entity.level().getWorld(), point.x, point.y, point.z); -+ } -+} -diff --git a/src/main/java/net/minecraft/world/level/pathfinder/Path.java b/src/main/java/net/minecraft/world/level/pathfinder/Path.java -index f6419f3b345e9e21a05b315aa4669090d7da4194..d9d0fff9962131808d54cca20f209df50b8e4af1 100644 ---- a/src/main/java/net/minecraft/world/level/pathfinder/Path.java -+++ b/src/main/java/net/minecraft/world/level/pathfinder/Path.java -@@ -18,6 +18,7 @@ public class Path { - private final BlockPos target; - private final float distToTarget; - private final boolean reached; -+ public boolean hasNext() { return getNextNodeIndex() < this.nodes.size(); } // Paper - Mob Pathfinding API - - public Path(List nodes, BlockPos target, boolean reachesTarget) { - this.nodes = nodes; -diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftMob.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftMob.java -index 58e690a91aea9ea294f8e4ec9861aa92bc6060a0..d597eea5d5c2f223e87bff06f292619657596f1f 100644 ---- a/src/main/java/org/bukkit/craftbukkit/entity/CraftMob.java -+++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftMob.java -@@ -13,8 +13,11 @@ import org.bukkit.loot.LootTable; - public abstract class CraftMob extends CraftLivingEntity implements Mob { - public CraftMob(CraftServer server, net.minecraft.world.entity.Mob entity) { - super(server, entity); -+ paperPathfinder = new com.destroystokyo.paper.entity.PaperPathfinder(entity); // Paper - Mob Pathfinding API - } - -+ private final com.destroystokyo.paper.entity.PaperPathfinder paperPathfinder; // Paper - Mob Pathfinding API -+ @Override public com.destroystokyo.paper.entity.Pathfinder getPathfinder() { return paperPathfinder; } // Paper - Mob Pathfinding API - @Override - public void setTarget(LivingEntity target) { - Preconditions.checkState(!this.getHandle().generation, "Cannot set target during world generation"); -@@ -55,6 +58,14 @@ public abstract class CraftMob extends CraftLivingEntity implements Mob { - return (net.minecraft.world.entity.Mob) this.entity; - } - -+ // Paper start - Mob Pathfinding API -+ @Override -+ public void setHandle(net.minecraft.world.entity.Entity entity) { -+ super.setHandle(entity); -+ paperPathfinder.setHandle(getHandle()); -+ } -+ // Paper end - Mob Pathfinding API -+ - @Override - public String toString() { - return "CraftMob"; diff --git a/patches/server/0244-Prevent-various-interactions-from-causing-chunk-load.patch b/patches/server/0244-Prevent-various-interactions-from-causing-chunk-load.patch new file mode 100644 index 000000000000..a2789a6dfadf --- /dev/null +++ b/patches/server/0244-Prevent-various-interactions-from-causing-chunk-load.patch @@ -0,0 +1,99 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Aikar +Date: Mon, 10 Sep 2018 23:56:36 -0400 +Subject: [PATCH] Prevent various interactions from causing chunk loads + +Co-authored-by: Shane Freeder + +diff --git a/src/main/java/net/minecraft/world/entity/ai/goal/RemoveBlockGoal.java b/src/main/java/net/minecraft/world/entity/ai/goal/RemoveBlockGoal.java +index 90f61511429799b1e852326d0d014551e5c35d5d..9d245d08be61d7edee9138196ae3bf52023e3993 100644 +--- a/src/main/java/net/minecraft/world/entity/ai/goal/RemoveBlockGoal.java ++++ b/src/main/java/net/minecraft/world/entity/ai/goal/RemoveBlockGoal.java +@@ -127,7 +127,9 @@ public class RemoveBlockGoal extends MoveToBlockGoal { + + @Nullable + private BlockPos getPosWithBlock(BlockPos pos, BlockGetter world) { +- if (world.getBlockState(pos).is(this.blockToRemove)) { ++ net.minecraft.world.level.block.state.BlockState block = world.getBlockStateIfLoaded(pos); // Paper - Prevent AI rules from loading chunks ++ if (block == null) return null; // Paper - Prevent AI rules from loading chunks ++ if (block.is(this.blockToRemove)) { // Paper - Prevent AI rules from loading chunks + return pos; + } else { + BlockPos[] ablockposition = new BlockPos[]{pos.below(), pos.west(), pos.east(), pos.north(), pos.south(), pos.below().below()}; +@@ -137,7 +139,8 @@ public class RemoveBlockGoal extends MoveToBlockGoal { + for (int j = 0; j < i; ++j) { + BlockPos blockposition1 = ablockposition1[j]; + +- if (world.getBlockState(blockposition1).is(this.blockToRemove)) { ++ net.minecraft.world.level.block.state.BlockState block2 = world.getBlockStateIfLoaded(blockposition1); // Paper - Prevent AI rules from loading chunks ++ if (block2 != null && block2.is(this.blockToRemove)) { // Paper - Prevent AI rules from loading chunks + return blockposition1; + } + } +@@ -148,7 +151,7 @@ public class RemoveBlockGoal extends MoveToBlockGoal { + + @Override + protected boolean isValidTarget(LevelReader world, BlockPos pos) { +- ChunkAccess ichunkaccess = world.getChunk(SectionPos.blockToSectionCoord(pos.getX()), SectionPos.blockToSectionCoord(pos.getZ()), ChunkStatus.FULL, false); ++ ChunkAccess ichunkaccess = world.getChunkIfLoadedImmediately(pos.getX() >> 4, pos.getZ() >> 4); // Paper - Prevent AI rules from loading chunks + + return ichunkaccess == null ? false : ichunkaccess.getBlockState(pos).is(this.blockToRemove) && ichunkaccess.getBlockState(pos.above()).isAir() && ichunkaccess.getBlockState(pos.above(2)).isAir(); + } +diff --git a/src/main/java/net/minecraft/world/entity/monster/EnderMan.java b/src/main/java/net/minecraft/world/entity/monster/EnderMan.java +index 5f1bbb4302013c2c1788db6b64eafba2a11a373a..512de8e79a842d4389e8528983b94af4843ffd11 100644 +--- a/src/main/java/net/minecraft/world/entity/monster/EnderMan.java ++++ b/src/main/java/net/minecraft/world/entity/monster/EnderMan.java +@@ -489,7 +489,8 @@ public class EnderMan extends Monster implements NeutralMob { + int j = Mth.floor(this.enderman.getY() + randomsource.nextDouble() * 2.0D); + int k = Mth.floor(this.enderman.getZ() - 1.0D + randomsource.nextDouble() * 2.0D); + BlockPos blockposition = new BlockPos(i, j, k); +- BlockState iblockdata = world.getBlockState(blockposition); ++ BlockState iblockdata = world.getBlockStateIfLoaded(blockposition); // Paper - Prevent endermen from loading chunks ++ if (iblockdata == null) return; // Paper - Prevent endermen from loading chunks + BlockPos blockposition1 = blockposition.below(); + BlockState iblockdata1 = world.getBlockState(blockposition1); + BlockState iblockdata2 = this.enderman.getCarriedBlock(); +@@ -533,7 +534,8 @@ public class EnderMan extends Monster implements NeutralMob { + int j = Mth.floor(this.enderman.getY() + randomsource.nextDouble() * 3.0D); + int k = Mth.floor(this.enderman.getZ() - 2.0D + randomsource.nextDouble() * 4.0D); + BlockPos blockposition = new BlockPos(i, j, k); +- BlockState iblockdata = world.getBlockState(blockposition); ++ BlockState iblockdata = world.getBlockStateIfLoaded(blockposition); // Paper - Prevent endermen from loading chunks ++ if (iblockdata == null) return; // Paper - Prevent endermen from loading chunks + Vec3 vec3d = new Vec3((double) this.enderman.getBlockX() + 0.5D, (double) j + 0.5D, (double) this.enderman.getBlockZ() + 0.5D); + Vec3 vec3d1 = new Vec3((double) i + 0.5D, (double) j + 0.5D, (double) k + 0.5D); + BlockHitResult movingobjectpositionblock = world.clip(new ClipContext(vec3d, vec3d1, ClipContext.Block.OUTLINE, ClipContext.Fluid.NONE, this.enderman)); +diff --git a/src/main/java/net/minecraft/world/item/component/LodestoneTracker.java b/src/main/java/net/minecraft/world/item/component/LodestoneTracker.java +index c02a2f9e1b4e727b1deeb73377e1f7193f5ee072..cdd1f6939ce33e62f6609f7eb3a5dff59bf12675 100644 +--- a/src/main/java/net/minecraft/world/item/component/LodestoneTracker.java ++++ b/src/main/java/net/minecraft/world/item/component/LodestoneTracker.java +@@ -29,7 +29,7 @@ public record LodestoneTracker(Optional target, boolean tracked) { + return this; + } else { + BlockPos blockPos = this.target.get().pos(); +- return world.isInWorldBounds(blockPos) && world.getPoiManager().existsAtPosition(PoiTypes.LODESTONE, blockPos) ++ return world.isInWorldBounds(blockPos) && (!world.hasChunkAt(blockPos) || world.getPoiManager().existsAtPosition(PoiTypes.LODESTONE, blockPos)) // Paper - Prevent compass from loading chunks + ? this + : new LodestoneTracker(Optional.empty(), true); + } +diff --git a/src/main/java/net/minecraft/world/level/BlockGetter.java b/src/main/java/net/minecraft/world/level/BlockGetter.java +index 6850ac324ee4d202f112dbd057ea1bde9de17ea9..f39ccc0d2a4eea4e1e0b15608780c7c4a749e672 100644 +--- a/src/main/java/net/minecraft/world/level/BlockGetter.java ++++ b/src/main/java/net/minecraft/world/level/BlockGetter.java +@@ -71,7 +71,15 @@ public interface BlockGetter extends LevelHeightAccessor { + + // CraftBukkit start - moved block handling into separate method for use by Block#rayTrace + default BlockHitResult clip(ClipContext raytrace1, BlockPos blockposition) { +- BlockState iblockdata = this.getBlockState(blockposition); ++ // Paper start - Prevent raytrace from loading chunks ++ BlockState iblockdata = this.getBlockStateIfLoaded(blockposition); ++ if (iblockdata == null) { ++ // copied the last function parameter (listed below) ++ Vec3 vec3d = raytrace1.getFrom().subtract(raytrace1.getTo()); ++ ++ return BlockHitResult.miss(raytrace1.getTo(), Direction.getApproximateNearest(vec3d.x, vec3d.y, vec3d.z), BlockPos.containing(raytrace1.getTo())); ++ } ++ // Paper end - Prevent raytrace from loading chunks + FluidState fluid = this.getFluidState(blockposition); + Vec3 vec3d = raytrace1.getFrom(); + Vec3 vec3d1 = raytrace1.getTo(); diff --git a/patches/server/0245-Prevent-mob-spawning-from-loading-generating-chunks.patch b/patches/server/0245-Prevent-mob-spawning-from-loading-generating-chunks.patch new file mode 100644 index 000000000000..e9d396eb027e --- /dev/null +++ b/patches/server/0245-Prevent-mob-spawning-from-loading-generating-chunks.patch @@ -0,0 +1,32 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Aikar +Date: Wed, 12 Sep 2018 21:12:57 -0400 +Subject: [PATCH] Prevent mob spawning from loading/generating chunks + +also prevents if out of world border bounds + +diff --git a/src/main/java/net/minecraft/world/level/NaturalSpawner.java b/src/main/java/net/minecraft/world/level/NaturalSpawner.java +index 5a0ea71dc39c582ef6c843dc0532adb04ba120ce..c826bd20d1ba32ebb9069737ede88e122294ea7a 100644 +--- a/src/main/java/net/minecraft/world/level/NaturalSpawner.java ++++ b/src/main/java/net/minecraft/world/level/NaturalSpawner.java +@@ -185,9 +185,9 @@ public final class NaturalSpawner { + StructureManager structuremanager = world.structureManager(); + ChunkGenerator chunkgenerator = world.getChunkSource().getGenerator(); + int i = pos.getY(); +- BlockState iblockdata = chunk.getBlockState(pos); ++ BlockState iblockdata = world.getBlockStateIfLoadedAndInBounds(pos); // Paper - don't load chunks for mob spawn + +- if (!iblockdata.isRedstoneConductor(chunk, pos)) { ++ if (iblockdata != null && !iblockdata.isRedstoneConductor(chunk, pos)) { // Paper - don't load chunks for mob spawn + BlockPos.MutableBlockPos blockposition_mutableblockposition = new BlockPos.MutableBlockPos(); + int j = 0; + int k = 0; +@@ -216,7 +216,7 @@ public final class NaturalSpawner { + if (entityhuman != null) { + double d2 = entityhuman.distanceToSqr(d0, (double) i, d1); + +- if (NaturalSpawner.isRightDistanceToPlayerAndSpawnPoint(world, chunk, blockposition_mutableblockposition, d2)) { ++ if (world.isLoadedAndInBounds(blockposition_mutableblockposition) && NaturalSpawner.isRightDistanceToPlayerAndSpawnPoint(world, chunk, blockposition_mutableblockposition, d2)) { // Paper - don't load chunks for mob spawn + if (biomesettingsmobs_c == null) { + Optional optional = NaturalSpawner.getRandomSpawnMobAt(world, structuremanager, chunkgenerator, group, world.random, blockposition_mutableblockposition); + diff --git a/patches/server/0245-Prevent-various-interactions-from-causing-chunk-load.patch b/patches/server/0245-Prevent-various-interactions-from-causing-chunk-load.patch deleted file mode 100644 index 01a574e73c4a..000000000000 --- a/patches/server/0245-Prevent-various-interactions-from-causing-chunk-load.patch +++ /dev/null @@ -1,99 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Aikar -Date: Mon, 10 Sep 2018 23:56:36 -0400 -Subject: [PATCH] Prevent various interactions from causing chunk loads - -Co-authored-by: Shane Freeder - -diff --git a/src/main/java/net/minecraft/world/entity/ai/goal/RemoveBlockGoal.java b/src/main/java/net/minecraft/world/entity/ai/goal/RemoveBlockGoal.java -index da5373d8eb4643414a0f2c699044fde93715c258..6634228ef002cbef67980272a26be4a75c954116 100644 ---- a/src/main/java/net/minecraft/world/entity/ai/goal/RemoveBlockGoal.java -+++ b/src/main/java/net/minecraft/world/entity/ai/goal/RemoveBlockGoal.java -@@ -126,7 +126,9 @@ public class RemoveBlockGoal extends MoveToBlockGoal { - - @Nullable - private BlockPos getPosWithBlock(BlockPos pos, BlockGetter world) { -- if (world.getBlockState(pos).is(this.blockToRemove)) { -+ net.minecraft.world.level.block.state.BlockState block = world.getBlockStateIfLoaded(pos); // Paper - Prevent AI rules from loading chunks -+ if (block == null) return null; // Paper - Prevent AI rules from loading chunks -+ if (block.is(this.blockToRemove)) { // Paper - Prevent AI rules from loading chunks - return pos; - } else { - BlockPos[] ablockposition = new BlockPos[]{pos.below(), pos.west(), pos.east(), pos.north(), pos.south(), pos.below().below()}; -@@ -136,7 +138,8 @@ public class RemoveBlockGoal extends MoveToBlockGoal { - for (int j = 0; j < i; ++j) { - BlockPos blockposition1 = ablockposition1[j]; - -- if (world.getBlockState(blockposition1).is(this.blockToRemove)) { -+ net.minecraft.world.level.block.state.BlockState block2 = world.getBlockStateIfLoaded(blockposition1); // Paper - Prevent AI rules from loading chunks -+ if (block2 != null && block2.is(this.blockToRemove)) { // Paper - Prevent AI rules from loading chunks - return blockposition1; - } - } -@@ -147,7 +150,7 @@ public class RemoveBlockGoal extends MoveToBlockGoal { - - @Override - protected boolean isValidTarget(LevelReader world, BlockPos pos) { -- ChunkAccess ichunkaccess = world.getChunk(SectionPos.blockToSectionCoord(pos.getX()), SectionPos.blockToSectionCoord(pos.getZ()), ChunkStatus.FULL, false); -+ ChunkAccess ichunkaccess = world.getChunkIfLoadedImmediately(pos.getX() >> 4, pos.getZ() >> 4); // Paper - Prevent AI rules from loading chunks - - return ichunkaccess == null ? false : ichunkaccess.getBlockState(pos).is(this.blockToRemove) && ichunkaccess.getBlockState(pos.above()).isAir() && ichunkaccess.getBlockState(pos.above(2)).isAir(); - } -diff --git a/src/main/java/net/minecraft/world/entity/monster/EnderMan.java b/src/main/java/net/minecraft/world/entity/monster/EnderMan.java -index 70888dd25b6a1d1ab7702d73a64a47eebafe76fe..0214e8bbcaefdd92ee3719d9a570f9d256ee29ba 100644 ---- a/src/main/java/net/minecraft/world/entity/monster/EnderMan.java -+++ b/src/main/java/net/minecraft/world/entity/monster/EnderMan.java -@@ -502,7 +502,8 @@ public class EnderMan extends Monster implements NeutralMob { - int j = Mth.floor(this.enderman.getY() + randomsource.nextDouble() * 2.0D); - int k = Mth.floor(this.enderman.getZ() - 1.0D + randomsource.nextDouble() * 2.0D); - BlockPos blockposition = new BlockPos(i, j, k); -- BlockState iblockdata = world.getBlockState(blockposition); -+ BlockState iblockdata = world.getBlockStateIfLoaded(blockposition); // Paper - Prevent endermen from loading chunks -+ if (iblockdata == null) return; // Paper - Prevent endermen from loading chunks - BlockPos blockposition1 = blockposition.below(); - BlockState iblockdata1 = world.getBlockState(blockposition1); - BlockState iblockdata2 = this.enderman.getCarriedBlock(); -@@ -546,7 +547,8 @@ public class EnderMan extends Monster implements NeutralMob { - int j = Mth.floor(this.enderman.getY() + randomsource.nextDouble() * 3.0D); - int k = Mth.floor(this.enderman.getZ() - 2.0D + randomsource.nextDouble() * 4.0D); - BlockPos blockposition = new BlockPos(i, j, k); -- BlockState iblockdata = world.getBlockState(blockposition); -+ BlockState iblockdata = world.getBlockStateIfLoaded(blockposition); // Paper - Prevent endermen from loading chunks -+ if (iblockdata == null) return; // Paper - Prevent endermen from loading chunks - Vec3 vec3d = new Vec3((double) this.enderman.getBlockX() + 0.5D, (double) j + 0.5D, (double) this.enderman.getBlockZ() + 0.5D); - Vec3 vec3d1 = new Vec3((double) i + 0.5D, (double) j + 0.5D, (double) k + 0.5D); - BlockHitResult movingobjectpositionblock = world.clip(new ClipContext(vec3d, vec3d1, ClipContext.Block.OUTLINE, ClipContext.Fluid.NONE, this.enderman)); -diff --git a/src/main/java/net/minecraft/world/item/component/LodestoneTracker.java b/src/main/java/net/minecraft/world/item/component/LodestoneTracker.java -index c02a2f9e1b4e727b1deeb73377e1f7193f5ee072..cdd1f6939ce33e62f6609f7eb3a5dff59bf12675 100644 ---- a/src/main/java/net/minecraft/world/item/component/LodestoneTracker.java -+++ b/src/main/java/net/minecraft/world/item/component/LodestoneTracker.java -@@ -29,7 +29,7 @@ public record LodestoneTracker(Optional target, boolean tracked) { - return this; - } else { - BlockPos blockPos = this.target.get().pos(); -- return world.isInWorldBounds(blockPos) && world.getPoiManager().existsAtPosition(PoiTypes.LODESTONE, blockPos) -+ return world.isInWorldBounds(blockPos) && (!world.hasChunkAt(blockPos) || world.getPoiManager().existsAtPosition(PoiTypes.LODESTONE, blockPos)) // Paper - Prevent compass from loading chunks - ? this - : new LodestoneTracker(Optional.empty(), true); - } -diff --git a/src/main/java/net/minecraft/world/level/BlockGetter.java b/src/main/java/net/minecraft/world/level/BlockGetter.java -index d6d8bbc98fc71997cb52521d59ebb59d727d3c22..c3760e0c8ac0b3ea200f4e1c237e250137a78caf 100644 ---- a/src/main/java/net/minecraft/world/level/BlockGetter.java -+++ b/src/main/java/net/minecraft/world/level/BlockGetter.java -@@ -70,7 +70,15 @@ public interface BlockGetter extends LevelHeightAccessor { - - // CraftBukkit start - moved block handling into separate method for use by Block#rayTrace - default BlockHitResult clip(ClipContext raytrace1, BlockPos blockposition) { -- BlockState iblockdata = this.getBlockState(blockposition); -+ // Paper start - Prevent raytrace from loading chunks -+ BlockState iblockdata = this.getBlockStateIfLoaded(blockposition); -+ if (iblockdata == null) { -+ // copied the last function parameter (listed below) -+ Vec3 vec3d = raytrace1.getFrom().subtract(raytrace1.getTo()); -+ -+ return BlockHitResult.miss(raytrace1.getTo(), Direction.getNearest(vec3d.x, vec3d.y, vec3d.z), BlockPos.containing(raytrace1.getTo())); -+ } -+ // Paper end - Prevent raytrace from loading chunks - FluidState fluid = this.getFluidState(blockposition); - Vec3 vec3d = raytrace1.getFrom(); - Vec3 vec3d1 = raytrace1.getTo(); diff --git a/patches/server/0246-Implement-furnace-cook-speed-multiplier-API.patch b/patches/server/0246-Implement-furnace-cook-speed-multiplier-API.patch new file mode 100644 index 000000000000..3a04c8b1ea0e --- /dev/null +++ b/patches/server/0246-Implement-furnace-cook-speed-multiplier-API.patch @@ -0,0 +1,139 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Tassu +Date: Thu, 13 Sep 2018 08:45:21 +0300 +Subject: [PATCH] Implement furnace cook speed multiplier API + +Fixed an issue where a furnace's cook-speed multiplier rounds down +to the nearest Integer when updating its current cook time. + +== AT == +public net.minecraft.world.level.block.entity.AbstractFurnaceBlockEntity getTotalCookTime(Lnet/minecraft/server/level/ServerLevel;Lnet/minecraft/world/level/block/entity/AbstractFurnaceBlockEntity;)I + +Co-authored-by: Eric Su + +diff --git a/src/main/java/net/minecraft/world/level/block/entity/AbstractFurnaceBlockEntity.java b/src/main/java/net/minecraft/world/level/block/entity/AbstractFurnaceBlockEntity.java +index 119ea31f6e15185b6d6171053f790e39c24f6823..187f380eae3948eb5e37e8703db6ea785aaf833d 100644 +--- a/src/main/java/net/minecraft/world/level/block/entity/AbstractFurnaceBlockEntity.java ++++ b/src/main/java/net/minecraft/world/level/block/entity/AbstractFurnaceBlockEntity.java +@@ -74,11 +74,13 @@ public abstract class AbstractFurnaceBlockEntity extends BaseContainerBlockEntit + protected NonNullList items; + public int litTime; + int litDuration; ++ public double cookSpeedMultiplier = 1.0; // Paper - cook speed multiplier API + public int cookingProgress; + public int cookingTotalTime; + protected final ContainerData dataAccess; + public final Reference2IntOpenHashMap>> recipesUsed; + private final RecipeManager.CachedCheck quickCheck; ++ public final RecipeType recipeType; // Paper - cook speed multiplier API + + protected AbstractFurnaceBlockEntity(BlockEntityType blockEntityType, BlockPos pos, BlockState state, RecipeType recipeType) { + super(blockEntityType, pos, state); +@@ -126,6 +128,7 @@ public abstract class AbstractFurnaceBlockEntity extends BaseContainerBlockEntit + }; + this.recipesUsed = new Reference2IntOpenHashMap(); + this.quickCheck = RecipeManager.createCheck((RecipeType) recipeType); // CraftBukkit - decompile error // Eclipse fail ++ this.recipeType = recipeType; // Paper - cook speed multiplier API + } + + // CraftBukkit start - add fields and methods +@@ -180,6 +183,11 @@ public abstract class AbstractFurnaceBlockEntity extends BaseContainerBlockEntit + this.recipesUsed.put(ResourceKey.create(Registries.RECIPE, ResourceLocation.parse(s)), nbttagcompound1.getInt(s)); + } + ++ // Paper start - cook speed multiplier API ++ if (nbt.contains("Paper.CookSpeedMultiplier")) { ++ this.cookSpeedMultiplier = nbt.getDouble("Paper.CookSpeedMultiplier"); ++ } ++ // Paper end - cook speed multiplier API + } + + @Override +@@ -188,6 +196,7 @@ public abstract class AbstractFurnaceBlockEntity extends BaseContainerBlockEntit + nbt.putShort("BurnTime", (short) this.litTime); + nbt.putShort("CookTime", (short) this.cookingProgress); + nbt.putShort("CookTimeTotal", (short) this.cookingTotalTime); ++ nbt.putDouble("Paper.CookSpeedMultiplier", this.cookSpeedMultiplier); // Paper - cook speed multiplier API + ContainerHelper.saveAllItems(nbt, this.items, registries); + CompoundTag nbttagcompound1 = new CompoundTag(); + +@@ -263,7 +272,7 @@ public abstract class AbstractFurnaceBlockEntity extends BaseContainerBlockEntit + CraftItemStack source = CraftItemStack.asCraftMirror(blockEntity.items.get(0)); + CookingRecipe recipe = (CookingRecipe) recipeholder.toBukkitRecipe(); + +- FurnaceStartSmeltEvent event = new FurnaceStartSmeltEvent(CraftBlock.at(world, pos), source, recipe); ++ FurnaceStartSmeltEvent event = new FurnaceStartSmeltEvent(CraftBlock.at(world, pos), source, recipe, AbstractFurnaceBlockEntity.getTotalCookTime(world, blockEntity, blockEntity.recipeType, blockEntity.cookSpeedMultiplier)); // Paper - cook speed multiplier API + world.getCraftServer().getPluginManager().callEvent(event); + + blockEntity.cookingTotalTime = event.getTotalCookTime(); +@@ -271,9 +280,9 @@ public abstract class AbstractFurnaceBlockEntity extends BaseContainerBlockEntit + // CraftBukkit end + + ++blockEntity.cookingProgress; +- if (blockEntity.cookingProgress == blockEntity.cookingTotalTime) { ++ if (blockEntity.cookingProgress >= blockEntity.cookingTotalTime) { // Paper - cook speed multiplier API + blockEntity.cookingProgress = 0; +- blockEntity.cookingTotalTime = AbstractFurnaceBlockEntity.getTotalCookTime(world, blockEntity); ++ blockEntity.cookingTotalTime = AbstractFurnaceBlockEntity.getTotalCookTime(world, blockEntity, blockEntity.recipeType, blockEntity.cookSpeedMultiplier); // Paper - cook speed multiplier API + if (AbstractFurnaceBlockEntity.burn(blockEntity.level, blockEntity.worldPosition, world.registryAccess(), recipeholder, singlerecipeinput, blockEntity.items, i)) { // CraftBukkit + blockEntity.setRecipeUsed(recipeholder); + } +@@ -367,13 +376,14 @@ public abstract class AbstractFurnaceBlockEntity extends BaseContainerBlockEntit + return fuelRegistry.burnDuration(stack); + } + +- public static int getTotalCookTime(ServerLevel world, AbstractFurnaceBlockEntity furnace) { +- if (world == null) return 200; // CraftBukkit - SPIGOT-4302 ++ public static int getTotalCookTime(@Nullable ServerLevel world, AbstractFurnaceBlockEntity furnace, RecipeType recipeType, double cookSpeedMultiplier) { // Paper - cook speed multiplier API + SingleRecipeInput singlerecipeinput = new SingleRecipeInput(furnace.getItem(0)); + +- return (Integer) furnace.quickCheck.getRecipeFor(singlerecipeinput, world).map((recipeholder) -> { +- return ((AbstractCookingRecipe) recipeholder.value()).cookingTime(); +- }).orElse(200); ++ // Paper start - cook speed multiplier API ++ /* Scale the recipe's cooking time to the current cookSpeedMultiplier */ ++ int cookTime = world != null ? furnace.quickCheck.getRecipeFor(singlerecipeinput, world).map(holder -> holder.value().cookingTime()).orElse(200) : (net.minecraft.server.MinecraftServer.getServer().getRecipeManager().getRecipeFor(recipeType, singlerecipeinput, world /* passing a null level here is safe. world is only used for map extending recipes which won't happen here */).map(holder -> holder.value().cookingTime()).orElse(200)); ++ return (int) Math.ceil (cookTime / cookSpeedMultiplier); ++ // Paper end - cook speed multiplier API + } + + @Override +@@ -419,12 +429,11 @@ public abstract class AbstractFurnaceBlockEntity extends BaseContainerBlockEntit + if (world instanceof ServerLevel) { + ServerLevel worldserver = (ServerLevel) world; + +- this.cookingTotalTime = AbstractFurnaceBlockEntity.getTotalCookTime(worldserver, this); ++ this.cookingTotalTime = AbstractFurnaceBlockEntity.getTotalCookTime(worldserver, this, this.recipeType, this.cookSpeedMultiplier); // Paper - cook speed multiplier API + this.cookingProgress = 0; + this.setChanged(); + } + } +- + } + + @Override +diff --git a/src/main/java/org/bukkit/craftbukkit/block/CraftFurnace.java b/src/main/java/org/bukkit/craftbukkit/block/CraftFurnace.java +index 7ed43bc29a4bc0f6db2cabd3cd4c8489ed81ee81..7b5f35779ac63b5f9b3a88cc4dcde38147fea2b7 100644 +--- a/src/main/java/org/bukkit/craftbukkit/block/CraftFurnace.java ++++ b/src/main/java/org/bukkit/craftbukkit/block/CraftFurnace.java +@@ -88,4 +88,20 @@ public abstract class CraftFurnace extends + + @Override + public abstract CraftFurnace copy(Location location); ++ ++ // Paper start - cook speed multiplier API ++ @Override ++ public double getCookSpeedMultiplier() { ++ return this.getSnapshot().cookSpeedMultiplier; ++ } ++ ++ @Override ++ public void setCookSpeedMultiplier(double multiplier) { ++ com.google.common.base.Preconditions.checkArgument(multiplier >= 0, "Furnace speed multiplier cannot be negative"); ++ com.google.common.base.Preconditions.checkArgument(multiplier <= 200, "Furnace speed multiplier cannot more than 200"); ++ T snapshot = this.getSnapshot(); ++ snapshot.cookSpeedMultiplier = multiplier; ++ snapshot.cookingTotalTime = AbstractFurnaceBlockEntity.getTotalCookTime(this.isPlaced() ? this.world.getHandle() : null, snapshot, snapshot.recipeType, snapshot.cookSpeedMultiplier); // Update the snapshot's current total cook time to scale with the newly set multiplier ++ } ++ // Paper end + } diff --git a/patches/server/0246-Prevent-mob-spawning-from-loading-generating-chunks.patch b/patches/server/0246-Prevent-mob-spawning-from-loading-generating-chunks.patch deleted file mode 100644 index 1ca48ef5a81d..000000000000 --- a/patches/server/0246-Prevent-mob-spawning-from-loading-generating-chunks.patch +++ /dev/null @@ -1,32 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Aikar -Date: Wed, 12 Sep 2018 21:12:57 -0400 -Subject: [PATCH] Prevent mob spawning from loading/generating chunks - -also prevents if out of world border bounds - -diff --git a/src/main/java/net/minecraft/world/level/NaturalSpawner.java b/src/main/java/net/minecraft/world/level/NaturalSpawner.java -index bce78beaadbfd0e400457bd14bcf6538be702879..41eef8bfd1572aecaf086bfbec300abeae2df794 100644 ---- a/src/main/java/net/minecraft/world/level/NaturalSpawner.java -+++ b/src/main/java/net/minecraft/world/level/NaturalSpawner.java -@@ -165,9 +165,9 @@ public final class NaturalSpawner { - StructureManager structuremanager = world.structureManager(); - ChunkGenerator chunkgenerator = world.getChunkSource().getGenerator(); - int i = pos.getY(); -- BlockState iblockdata = chunk.getBlockState(pos); -+ BlockState iblockdata = world.getBlockStateIfLoadedAndInBounds(pos); // Paper - don't load chunks for mob spawn - -- if (!iblockdata.isRedstoneConductor(chunk, pos)) { -+ if (iblockdata != null && !iblockdata.isRedstoneConductor(chunk, pos)) { // Paper - don't load chunks for mob spawn - BlockPos.MutableBlockPos blockposition_mutableblockposition = new BlockPos.MutableBlockPos(); - int j = 0; - int k = 0; -@@ -196,7 +196,7 @@ public final class NaturalSpawner { - if (entityhuman != null) { - double d2 = entityhuman.distanceToSqr(d0, (double) i, d1); - -- if (NaturalSpawner.isRightDistanceToPlayerAndSpawnPoint(world, chunk, blockposition_mutableblockposition, d2)) { -+ if (world.isLoadedAndInBounds(blockposition_mutableblockposition) && NaturalSpawner.isRightDistanceToPlayerAndSpawnPoint(world, chunk, blockposition_mutableblockposition, d2)) { // Paper - don't load chunks for mob spawn - if (biomesettingsmobs_c == null) { - Optional optional = NaturalSpawner.getRandomSpawnMobAt(world, structuremanager, chunkgenerator, group, world.random, blockposition_mutableblockposition); - diff --git a/patches/server/0247-Honor-EntityAgeable.ageLock.patch b/patches/server/0247-Honor-EntityAgeable.ageLock.patch new file mode 100644 index 000000000000..aaaf81b8948e --- /dev/null +++ b/patches/server/0247-Honor-EntityAgeable.ageLock.patch @@ -0,0 +1,38 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: BillyGalbreath +Date: Sun, 23 Sep 2018 20:59:53 -0500 +Subject: [PATCH] Honor EntityAgeable.ageLock + + +diff --git a/src/main/java/net/minecraft/world/entity/AgeableMob.java b/src/main/java/net/minecraft/world/entity/AgeableMob.java +index d7020068a9ea3e5e1cacfe53ef19a88139687205..119856b22df5bbcd4e5bf5f95645156f774c6168 100644 +--- a/src/main/java/net/minecraft/world/entity/AgeableMob.java ++++ b/src/main/java/net/minecraft/world/entity/AgeableMob.java +@@ -86,6 +86,7 @@ public abstract class AgeableMob extends PathfinderMob { + } + + public void ageUp(int age, boolean overGrow) { ++ if (this.ageLocked) return; // Paper - Honor ageLock + int j = this.getAge(); + int k = j; + +diff --git a/src/main/java/net/minecraft/world/level/block/entity/BeehiveBlockEntity.java b/src/main/java/net/minecraft/world/level/block/entity/BeehiveBlockEntity.java +index f3687ecd2d757b22b224346bbb8342d83221efdb..0fab8826e14af9184f07bc1262555a71effcd84b 100644 +--- a/src/main/java/net/minecraft/world/level/block/entity/BeehiveBlockEntity.java ++++ b/src/main/java/net/minecraft/world/level/block/entity/BeehiveBlockEntity.java +@@ -447,6 +447,7 @@ public class BeehiveBlockEntity extends BlockEntity { + } + + private static void setBeeReleaseData(int ticksInHive, Bee beeEntity) { ++ if (!beeEntity.ageLocked) { // Paper - Honor ageLock + int j = beeEntity.getAge(); + + if (j < 0) { +@@ -456,6 +457,7 @@ public class BeehiveBlockEntity extends BlockEntity { + } + + beeEntity.setInLoveTime(Math.max(0, beeEntity.getInLoveTime() - ticksInHive)); ++ } // Paper - Honor ageLock + } + } + diff --git a/patches/server/0247-Implement-furnace-cook-speed-multiplier-API.patch b/patches/server/0247-Implement-furnace-cook-speed-multiplier-API.patch deleted file mode 100644 index 24067a522fce..000000000000 --- a/patches/server/0247-Implement-furnace-cook-speed-multiplier-API.patch +++ /dev/null @@ -1,133 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Tassu -Date: Thu, 13 Sep 2018 08:45:21 +0300 -Subject: [PATCH] Implement furnace cook speed multiplier API - -Fixed an issue where a furnace's cook-speed multiplier rounds down -to the nearest Integer when updating its current cook time. - -Co-authored-by: Eric Su - -diff --git a/src/main/java/net/minecraft/world/level/block/entity/AbstractFurnaceBlockEntity.java b/src/main/java/net/minecraft/world/level/block/entity/AbstractFurnaceBlockEntity.java -index e2a587ca5b732c62c4956e6f39ad795cd1411cc4..5ea2b05961590732a43bb5a1abf00bf8a00c72c2 100644 ---- a/src/main/java/net/minecraft/world/level/block/entity/AbstractFurnaceBlockEntity.java -+++ b/src/main/java/net/minecraft/world/level/block/entity/AbstractFurnaceBlockEntity.java -@@ -79,6 +79,7 @@ public abstract class AbstractFurnaceBlockEntity extends BaseContainerBlockEntit - protected NonNullList items; - public int litTime; - int litDuration; -+ public double cookSpeedMultiplier = 1.0; // Paper - cook speed multiplier API - public int cookingProgress; - public int cookingTotalTime; - @Nullable -@@ -86,6 +87,7 @@ public abstract class AbstractFurnaceBlockEntity extends BaseContainerBlockEntit - protected final ContainerData dataAccess; - public final Object2IntOpenHashMap recipesUsed; - private final RecipeManager.CachedCheck quickCheck; -+ public final RecipeType recipeType; // Paper - cook speed multiplier API - - protected AbstractFurnaceBlockEntity(BlockEntityType blockEntityType, BlockPos pos, BlockState state, RecipeType recipeType) { - super(blockEntityType, pos, state); -@@ -132,6 +134,7 @@ public abstract class AbstractFurnaceBlockEntity extends BaseContainerBlockEntit - }; - this.recipesUsed = new Object2IntOpenHashMap(); - this.quickCheck = RecipeManager.createCheck((RecipeType) recipeType); // CraftBukkit - decompile error // Eclipse fail -+ this.recipeType = recipeType; // Paper - cook speed multiplier API - } - - public static void invalidateCache() { -@@ -295,6 +298,11 @@ public abstract class AbstractFurnaceBlockEntity extends BaseContainerBlockEntit - this.recipesUsed.put(ResourceLocation.parse(s), nbttagcompound1.getInt(s)); - } - -+ // Paper start - cook speed multiplier API -+ if (nbt.contains("Paper.CookSpeedMultiplier")) { -+ this.cookSpeedMultiplier = nbt.getDouble("Paper.CookSpeedMultiplier"); -+ } -+ // Paper end - cook speed multiplier API - } - - @Override -@@ -303,6 +311,7 @@ public abstract class AbstractFurnaceBlockEntity extends BaseContainerBlockEntit - nbt.putShort("BurnTime", (short) this.litTime); - nbt.putShort("CookTime", (short) this.cookingProgress); - nbt.putShort("CookTimeTotal", (short) this.cookingTotalTime); -+ nbt.putDouble("Paper.CookSpeedMultiplier", this.cookSpeedMultiplier); // Paper - cook speed multiplier API - ContainerHelper.saveAllItems(nbt, this.items, registryLookup); - CompoundTag nbttagcompound1 = new CompoundTag(); - -@@ -375,7 +384,7 @@ public abstract class AbstractFurnaceBlockEntity extends BaseContainerBlockEntit - CraftItemStack source = CraftItemStack.asCraftMirror(blockEntity.items.get(0)); - CookingRecipe recipe = (CookingRecipe) recipeholder.toBukkitRecipe(); - -- FurnaceStartSmeltEvent event = new FurnaceStartSmeltEvent(CraftBlock.at(world, pos), source, recipe); -+ FurnaceStartSmeltEvent event = new FurnaceStartSmeltEvent(CraftBlock.at(world, pos), source, recipe, AbstractFurnaceBlockEntity.getTotalCookTime(world, blockEntity.recipeType, blockEntity, blockEntity.cookSpeedMultiplier)); // Paper - cook speed multiplier API - world.getCraftServer().getPluginManager().callEvent(event); - - blockEntity.cookingTotalTime = event.getTotalCookTime(); -@@ -383,9 +392,9 @@ public abstract class AbstractFurnaceBlockEntity extends BaseContainerBlockEntit - // CraftBukkit end - - ++blockEntity.cookingProgress; -- if (blockEntity.cookingProgress == blockEntity.cookingTotalTime) { -+ if (blockEntity.cookingProgress >= blockEntity.cookingTotalTime) { // Paper - cook speed multiplier API - blockEntity.cookingProgress = 0; -- blockEntity.cookingTotalTime = AbstractFurnaceBlockEntity.getTotalCookTime(world, blockEntity); -+ blockEntity.cookingTotalTime = AbstractFurnaceBlockEntity.getTotalCookTime(world, blockEntity.recipeType, blockEntity, blockEntity.cookSpeedMultiplier); // Paper - cook speed multiplier API - if (AbstractFurnaceBlockEntity.burn(blockEntity.level, blockEntity.worldPosition, world.registryAccess(), recipeholder, blockEntity.items, i)) { // CraftBukkit - blockEntity.setRecipeUsed(recipeholder); - } -@@ -485,13 +494,14 @@ public abstract class AbstractFurnaceBlockEntity extends BaseContainerBlockEntit - } - } - -- private static int getTotalCookTime(Level world, AbstractFurnaceBlockEntity furnace) { -- if (world == null) return 200; // CraftBukkit - SPIGOT-4302 -+ public static int getTotalCookTime(@Nullable Level world, RecipeType recipeType, AbstractFurnaceBlockEntity furnace, double cookSpeedMultiplier) { // Paper - cook speed multiplier API - SingleRecipeInput singlerecipeinput = new SingleRecipeInput(furnace.getItem(0)); - -- return (Integer) furnace.quickCheck.getRecipeFor(singlerecipeinput, world).map((recipeholder) -> { -- return ((AbstractCookingRecipe) recipeholder.value()).getCookingTime(); -- }).orElse(200); -+ // Paper start - cook speed multiplier API -+ /* Scale the recipe's cooking time to the current cookSpeedMultiplier */ -+ int cookTime = world != null ? furnace.quickCheck.getRecipeFor(singlerecipeinput, world).map(holder -> holder.value().getCookingTime()).orElse(200) : (net.minecraft.server.MinecraftServer.getServer().getRecipeManager().getRecipeFor(recipeType, singlerecipeinput, world /* passing a null level here is safe. world is only used for map extending recipes which won't happen here */).map(holder -> holder.value().getCookingTime()).orElse(200)); -+ return (int) Math.ceil (cookTime / cookSpeedMultiplier); -+ // Paper end - cook speed multiplier API - } - - public static boolean isFuel(ItemStack stack) { -@@ -536,7 +546,7 @@ public abstract class AbstractFurnaceBlockEntity extends BaseContainerBlockEntit - this.items.set(slot, stack); - stack.limitSize(this.getMaxStackSize(stack)); - if (slot == 0 && !flag) { -- this.cookingTotalTime = AbstractFurnaceBlockEntity.getTotalCookTime(this.level, this); -+ this.cookingTotalTime = AbstractFurnaceBlockEntity.getTotalCookTime(this.level, this.recipeType, this, this.cookSpeedMultiplier); // Paper - cook speed multiplier API - this.cookingProgress = 0; - this.setChanged(); - } -diff --git a/src/main/java/org/bukkit/craftbukkit/block/CraftFurnace.java b/src/main/java/org/bukkit/craftbukkit/block/CraftFurnace.java -index 46a1c96efc5ffb5c8d6c20af758bdca5bb4a5049..ddbbf977c8f536a156ff6b2462353f7be5ab5742 100644 ---- a/src/main/java/org/bukkit/craftbukkit/block/CraftFurnace.java -+++ b/src/main/java/org/bukkit/craftbukkit/block/CraftFurnace.java -@@ -89,4 +89,20 @@ public abstract class CraftFurnace extends - - @Override - public abstract CraftFurnace copy(Location location); -+ -+ // Paper start - cook speed multiplier API -+ @Override -+ public double getCookSpeedMultiplier() { -+ return this.getSnapshot().cookSpeedMultiplier; -+ } -+ -+ @Override -+ public void setCookSpeedMultiplier(double multiplier) { -+ com.google.common.base.Preconditions.checkArgument(multiplier >= 0, "Furnace speed multiplier cannot be negative"); -+ com.google.common.base.Preconditions.checkArgument(multiplier <= 200, "Furnace speed multiplier cannot more than 200"); -+ T snapshot = this.getSnapshot(); -+ snapshot.cookSpeedMultiplier = multiplier; -+ snapshot.cookingTotalTime = AbstractFurnaceBlockEntity.getTotalCookTime(this.isPlaced() ? this.world.getHandle() : null, snapshot.recipeType, snapshot, snapshot.cookSpeedMultiplier); // Update the snapshot's current total cook time to scale with the newly set multiplier -+ } -+ // Paper end - } diff --git a/patches/server/0249-Configurable-connection-throttle-kick-message.patch b/patches/server/0248-Configurable-connection-throttle-kick-message.patch similarity index 100% rename from patches/server/0249-Configurable-connection-throttle-kick-message.patch rename to patches/server/0248-Configurable-connection-throttle-kick-message.patch diff --git a/patches/server/0248-Honor-EntityAgeable.ageLock.patch b/patches/server/0248-Honor-EntityAgeable.ageLock.patch deleted file mode 100644 index 1522ccc88216..000000000000 --- a/patches/server/0248-Honor-EntityAgeable.ageLock.patch +++ /dev/null @@ -1,38 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: BillyGalbreath -Date: Sun, 23 Sep 2018 20:59:53 -0500 -Subject: [PATCH] Honor EntityAgeable.ageLock - - -diff --git a/src/main/java/net/minecraft/world/entity/AgeableMob.java b/src/main/java/net/minecraft/world/entity/AgeableMob.java -index f07cf6d91e0cbad80c3c630c0d505820e701ce81..3dc3609d13a7b823d15384d1c385b68eeb933d26 100644 ---- a/src/main/java/net/minecraft/world/entity/AgeableMob.java -+++ b/src/main/java/net/minecraft/world/entity/AgeableMob.java -@@ -85,6 +85,7 @@ public abstract class AgeableMob extends PathfinderMob { - } - - public void ageUp(int age, boolean overGrow) { -+ if (this.ageLocked) return; // Paper - Honor ageLock - int j = this.getAge(); - int k = j; - -diff --git a/src/main/java/net/minecraft/world/level/block/entity/BeehiveBlockEntity.java b/src/main/java/net/minecraft/world/level/block/entity/BeehiveBlockEntity.java -index 4ee7a4e5637fe36eb50e8ec9186d72d1253bfd98..b345403cdf5b2828f99708fef65136594a3331c3 100644 ---- a/src/main/java/net/minecraft/world/level/block/entity/BeehiveBlockEntity.java -+++ b/src/main/java/net/minecraft/world/level/block/entity/BeehiveBlockEntity.java -@@ -446,6 +446,7 @@ public class BeehiveBlockEntity extends BlockEntity { - } - - private static void setBeeReleaseData(int ticksInHive, Bee beeEntity) { -+ if (!beeEntity.ageLocked) { // Paper - Honor ageLock - int j = beeEntity.getAge(); - - if (j < 0) { -@@ -455,6 +456,7 @@ public class BeehiveBlockEntity extends BlockEntity { - } - - beeEntity.setInLoveTime(Math.max(0, beeEntity.getInLoveTime() - ticksInHive)); -+ } // Paper - Honor ageLock - } - } - diff --git a/patches/server/0249-Prevent-chunk-loading-from-Fluid-Flowing.patch b/patches/server/0249-Prevent-chunk-loading-from-Fluid-Flowing.patch new file mode 100644 index 000000000000..8426fd78fd9a --- /dev/null +++ b/patches/server/0249-Prevent-chunk-loading-from-Fluid-Flowing.patch @@ -0,0 +1,88 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Aikar +Date: Mon, 10 Sep 2018 23:36:16 -0400 +Subject: [PATCH] Prevent chunk loading from Fluid Flowing + + +diff --git a/src/main/java/net/minecraft/world/level/material/FlowingFluid.java b/src/main/java/net/minecraft/world/level/material/FlowingFluid.java +index 2053d1e0b7577ec99958bae828b4c219b835edfc..83dc8bcd9e2b8ecbd32225e4e10aec392ef28325 100644 +--- a/src/main/java/net/minecraft/world/level/material/FlowingFluid.java ++++ b/src/main/java/net/minecraft/world/level/material/FlowingFluid.java +@@ -184,6 +184,8 @@ public abstract class FlowingFluid extends Fluid { + Direction enumdirection = (Direction) entry.getKey(); + FluidState fluid1 = (FluidState) entry.getValue(); + BlockPos blockposition1 = pos.relative(enumdirection); ++ final BlockState blockStateIfLoaded = world.getBlockStateIfLoaded(blockposition1); // Paper - Prevent chunk loading from fluid flowing ++ if (blockStateIfLoaded == null) continue; // Paper - Prevent chunk loading from fluid flowing + + // CraftBukkit start + org.bukkit.block.Block source = CraftBlock.at(world, pos); +@@ -194,7 +196,7 @@ public abstract class FlowingFluid extends Fluid { + continue; + } + // CraftBukkit end +- this.spreadTo(world, blockposition1, world.getBlockState(blockposition1), enumdirection, fluid1); ++ this.spreadTo(world, blockposition1, blockStateIfLoaded, enumdirection, fluid1); // Paper - Prevent chunk loading from fluid flowing + } + + } +@@ -209,7 +211,8 @@ public abstract class FlowingFluid extends Fluid { + while (iterator.hasNext()) { + Direction enumdirection = (Direction) iterator.next(); + BlockPos.MutableBlockPos blockposition_mutableblockposition1 = blockposition_mutableblockposition.setWithOffset(pos, enumdirection); +- BlockState iblockdata1 = world.getBlockState(blockposition_mutableblockposition1); ++ BlockState iblockdata1 = world.getBlockStateIfLoaded(blockposition_mutableblockposition1); // Paper - Prevent chunk loading from fluid flowing ++ if (iblockdata1 == null) continue; // Paper - Prevent chunk loading from fluid flowing + FluidState fluid = iblockdata1.getFluidState(); + + if (fluid.getType().isSame(this) && FlowingFluid.canPassThroughWall(enumdirection, world, pos, state, blockposition_mutableblockposition1, iblockdata1)) { +@@ -332,7 +335,8 @@ public abstract class FlowingFluid extends Fluid { + + if (enumdirection1 != direction) { + BlockPos blockposition1 = pos.relative(enumdirection1); +- BlockState iblockdata1 = spreadCache.getBlockState(blockposition1); ++ BlockState iblockdata1 = spreadCache.getBlockStateIfLoaded(blockposition1); // Paper - Prevent chunk loading from fluid flowing ++ if (iblockdata1 == null) continue; // Paper - Prevent chunk loading from fluid flowing + FluidState fluid = iblockdata1.getFluidState(); + + if (this.canPassThrough(world, this.getFlowing(), pos, state, enumdirection1, blockposition1, iblockdata1, fluid)) { +@@ -398,7 +402,8 @@ public abstract class FlowingFluid extends Fluid { + while (iterator.hasNext()) { + Direction enumdirection = (Direction) iterator.next(); + BlockPos blockposition1 = pos.relative(enumdirection); +- BlockState iblockdata1 = world.getBlockState(blockposition1); ++ BlockState iblockdata1 = world.getBlockStateIfLoaded(blockposition1); // Paper - Prevent chunk loading from fluid flowing ++ if (iblockdata1 == null) continue; // Paper - Prevent chunk loading from fluid flowing + FluidState fluid = iblockdata1.getFluidState(); + + if (this.canMaybePassThrough(world, pos, state, enumdirection, blockposition1, iblockdata1, fluid)) { +@@ -564,11 +569,26 @@ public abstract class FlowingFluid extends Fluid { + public BlockState getBlockState(BlockPos pos) { + return this.getBlockState(pos, this.getCacheKey(pos)); + } ++ // Paper start - Prevent chunk loading from fluid flowing ++ public @javax.annotation.Nullable BlockState getBlockStateIfLoaded(BlockPos pos) { ++ return this.getBlockState(pos, this.getCacheKey(pos), false); ++ } ++ // Paper end - Prevent chunk loading from fluid flowing + + private BlockState getBlockState(BlockPos pos, short packed) { +- return (BlockState) this.stateCache.computeIfAbsent(packed, (short1) -> { +- return this.level.getBlockState(pos); +- }); ++ // Paper start - Prevent chunk loading from fluid flowing ++ return getBlockState(pos, packed, true); ++ } ++ private @javax.annotation.Nullable BlockState getBlockState(BlockPos pos, short packed, boolean load) { ++ BlockState blockState = this.stateCache.get(packed); ++ if (blockState == null) { ++ blockState = load ? level.getBlockState(pos) : level.getBlockStateIfLoaded(pos); ++ if (blockState != null) { ++ this.stateCache.put(packed, blockState); ++ } ++ } ++ return blockState; ++ // Paper end - Prevent chunk loading from fluid flowing + } + + public boolean isHole(BlockPos pos) { diff --git a/patches/server/0250-PreSpawnerSpawnEvent.patch b/patches/server/0250-PreSpawnerSpawnEvent.patch new file mode 100644 index 000000000000..d10c136bb81d --- /dev/null +++ b/patches/server/0250-PreSpawnerSpawnEvent.patch @@ -0,0 +1,27 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Phoenix616 +Date: Tue, 18 Sep 2018 23:53:23 +0100 +Subject: [PATCH] PreSpawnerSpawnEvent + +This adds a separate event before an entity is spawned by a spawner +which contains the location of the spawner too similarly to how the +SpawnerSpawnEvent gets called instead of the CreatureSpawnEvent for +spawners. + +diff --git a/src/main/java/net/minecraft/world/level/BaseSpawner.java b/src/main/java/net/minecraft/world/level/BaseSpawner.java +index bb3f3bec350dda43dbf5eda0a8c8057a413694b2..261de9ea37d22023da6a306b58b1b62a54dc03da 100644 +--- a/src/main/java/net/minecraft/world/level/BaseSpawner.java ++++ b/src/main/java/net/minecraft/world/level/BaseSpawner.java +@@ -133,10 +133,10 @@ public abstract class BaseSpawner { + continue; + } + // Paper start - PreCreatureSpawnEvent +- com.destroystokyo.paper.event.entity.PreCreatureSpawnEvent event = new com.destroystokyo.paper.event.entity.PreCreatureSpawnEvent( ++ com.destroystokyo.paper.event.entity.PreSpawnerSpawnEvent event = new com.destroystokyo.paper.event.entity.PreSpawnerSpawnEvent( + io.papermc.paper.util.MCUtil.toLocation(world, d0, d1, d2), + org.bukkit.craftbukkit.entity.CraftEntityType.minecraftToBukkit(optional.get()), +- org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason.SPAWNER ++ io.papermc.paper.util.MCUtil.toLocation(world, pos) + ); + if (!event.callEvent()) { + flag = true; diff --git a/patches/server/0250-Prevent-chunk-loading-from-Fluid-Flowing.patch b/patches/server/0250-Prevent-chunk-loading-from-Fluid-Flowing.patch deleted file mode 100644 index 50c82e5d54ce..000000000000 --- a/patches/server/0250-Prevent-chunk-loading-from-Fluid-Flowing.patch +++ /dev/null @@ -1,75 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Aikar -Date: Mon, 10 Sep 2018 23:36:16 -0400 -Subject: [PATCH] Prevent chunk loading from Fluid Flowing - - -diff --git a/src/main/java/net/minecraft/world/level/material/FlowingFluid.java b/src/main/java/net/minecraft/world/level/material/FlowingFluid.java -index b67bc6d6a02fdac377f32a766fd8cc2c5fc43488..3a2ae2bca410708736da64560e74b8010444f2dc 100644 ---- a/src/main/java/net/minecraft/world/level/material/FlowingFluid.java -+++ b/src/main/java/net/minecraft/world/level/material/FlowingFluid.java -@@ -176,7 +176,8 @@ public abstract class FlowingFluid extends Fluid { - Direction enumdirection = (Direction) entry.getKey(); - FluidState fluid1 = (FluidState) entry.getValue(); - BlockPos blockposition1 = pos.relative(enumdirection); -- BlockState iblockdata1 = world.getBlockState(blockposition1); -+ BlockState iblockdata1 = world.getBlockStateIfLoaded(blockposition1); // Paper - Prevent chunk loading from fluid flowing -+ if (iblockdata1 == null) continue; // Paper - Prevent chunk loading from fluid flowing - - if (this.canSpreadTo(world, pos, blockState, enumdirection, blockposition1, iblockdata1, world.getFluidState(blockposition1), fluid1.getType())) { - // CraftBukkit start -@@ -203,7 +204,8 @@ public abstract class FlowingFluid extends Fluid { - while (iterator.hasNext()) { - Direction enumdirection = (Direction) iterator.next(); - BlockPos blockposition1 = pos.relative(enumdirection); -- BlockState iblockdata1 = world.getBlockState(blockposition1); -+ BlockState iblockdata1 = world.getBlockStateIfLoaded(blockposition1); // Paper - Prevent chunk loading from fluid flowing -+ if (iblockdata1 == null) continue; // Paper - Prevent chunk loading from fluid flowing - FluidState fluid = iblockdata1.getFluidState(); - - if (fluid.getType().isSame(this) && this.canPassThroughWall(enumdirection, world, pos, state, blockposition1, iblockdata1)) { -@@ -320,11 +322,18 @@ public abstract class FlowingFluid extends Fluid { - if (enumdirection1 != direction) { - BlockPos blockposition2 = pos.relative(enumdirection1); - short short0 = FlowingFluid.getCacheKey(fromPos, blockposition2); -- Pair pair = (Pair) stateCache.computeIfAbsent(short0, (short1) -> { -- BlockState iblockdata1 = world.getBlockState(blockposition2); -+ // Paper start - Prevent chunk loading from fluid flowing -+ Pair pair = stateCache.get(short0); -+ if (pair == null) { -+ BlockState iblockdatax = world.getBlockStateIfLoaded(blockposition2); -+ if (iblockdatax == null) { -+ continue; -+ } - -- return Pair.of(iblockdata1, iblockdata1.getFluidState()); -- }); -+ pair = Pair.of(iblockdatax, iblockdatax.getFluidState()); -+ stateCache.put(short0, pair); -+ } -+ // Paper end - Prevent chunk loading from fluid flowing - BlockState iblockdata1 = (BlockState) pair.getFirst(); - FluidState fluid = (FluidState) pair.getSecond(); - -@@ -396,11 +405,16 @@ public abstract class FlowingFluid extends Fluid { - Direction enumdirection = (Direction) iterator.next(); - BlockPos blockposition1 = pos.relative(enumdirection); - short short0 = FlowingFluid.getCacheKey(pos, blockposition1); -- Pair pair = (Pair) short2objectmap.computeIfAbsent(short0, (short1) -> { -- BlockState iblockdata1 = world.getBlockState(blockposition1); -- -- return Pair.of(iblockdata1, iblockdata1.getFluidState()); -- }); -+ // Paper start - Prevent chunk loading from fluid flowing -+ Pair pair = (Pair) short2objectmap.get(short0); -+ if (pair == null) { -+ BlockState iblockdatax = world.getBlockStateIfLoaded(blockposition1); -+ if (iblockdatax == null) continue; -+ -+ pair = Pair.of(iblockdatax, iblockdatax.getFluidState()); -+ short2objectmap.put(short0, pair); -+ } -+ // Paper end - Prevent chunk loading from fluid flowing - BlockState iblockdata1 = (BlockState) pair.getFirst(); - FluidState fluid = (FluidState) pair.getSecond(); - FluidState fluid1 = this.getNewLiquid(world, blockposition1, iblockdata1); diff --git a/patches/server/0251-Add-LivingEntity-getTargetEntity.patch b/patches/server/0251-Add-LivingEntity-getTargetEntity.patch new file mode 100644 index 000000000000..8f5a3aa5fa8d --- /dev/null +++ b/patches/server/0251-Add-LivingEntity-getTargetEntity.patch @@ -0,0 +1,93 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: BillyGalbreath +Date: Sat, 22 Sep 2018 00:33:08 -0500 +Subject: [PATCH] Add LivingEntity#getTargetEntity + + +diff --git a/src/main/java/net/minecraft/world/entity/LivingEntity.java b/src/main/java/net/minecraft/world/entity/LivingEntity.java +index 7a89c25fcd1c76e4b176c257600db89788aa0f21..3bcf2ba5f065d946ded4020b9882bc4e19af0e08 100644 +--- a/src/main/java/net/minecraft/world/entity/LivingEntity.java ++++ b/src/main/java/net/minecraft/world/entity/LivingEntity.java +@@ -4176,6 +4176,38 @@ public abstract class LivingEntity extends Entity implements Attackable { + return this.level().clip(raytrace); + } + ++ public @Nullable net.minecraft.world.phys.EntityHitResult getTargetEntity(int maxDistance) { ++ if (maxDistance < 1 || maxDistance > 120) { ++ throw new IllegalArgumentException("maxDistance must be between 1-120"); ++ } ++ ++ Vec3 start = this.getEyePosition(1.0F); ++ Vec3 direction = this.getLookAngle(); ++ Vec3 end = start.add(direction.x * maxDistance, direction.y * maxDistance, direction.z * maxDistance); ++ ++ List entityList = this.level().getEntities(this, getBoundingBox().expandTowards(direction.x * maxDistance, direction.y * maxDistance, direction.z * maxDistance).inflate(1.0D, 1.0D, 1.0D), EntitySelector.NO_SPECTATORS.and(Entity::isPickable)); ++ ++ double distance = 0.0D; ++ net.minecraft.world.phys.EntityHitResult result = null; ++ ++ for (Entity entity : entityList) { ++ final double inflationAmount = (double) entity.getPickRadius(); ++ AABB aabb = entity.getBoundingBox().inflate(inflationAmount, inflationAmount, inflationAmount); ++ Optional rayTraceResult = aabb.clip(start, end); ++ ++ if (rayTraceResult.isPresent()) { ++ Vec3 rayTrace = rayTraceResult.get(); ++ double distanceTo = start.distanceToSqr(rayTrace); ++ if (distanceTo < distance || distance == 0.0D) { ++ result = new net.minecraft.world.phys.EntityHitResult(entity, rayTrace); ++ distance = distanceTo; ++ } ++ } ++ } ++ ++ return result; ++ } ++ + public int shieldBlockingDelay = this.level().paperConfig().misc.shieldBlockingDelay; + + public int getShieldBlockingDelay() { +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftLivingEntity.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftLivingEntity.java +index 0c7bf4124d67258ebca9b9b73b92c2e0efbdaa86..c4c83b4e17aac23794fdb51acfba4e7ef8451e2c 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftLivingEntity.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftLivingEntity.java +@@ -232,6 +232,39 @@ public class CraftLivingEntity extends CraftEntity implements LivingEntity { + } + return null; + } ++ ++ public Entity getTargetEntity(int maxDistance, boolean ignoreBlocks) { ++ net.minecraft.world.phys.EntityHitResult rayTrace = rayTraceEntity(maxDistance, ignoreBlocks); ++ return rayTrace == null ? null : rayTrace.getEntity().getBukkitEntity(); ++ } ++ ++ public com.destroystokyo.paper.entity.TargetEntityInfo getTargetEntityInfo(int maxDistance, boolean ignoreBlocks) { ++ net.minecraft.world.phys.EntityHitResult rayTrace = rayTraceEntity(maxDistance, ignoreBlocks); ++ return rayTrace == null ? null : new com.destroystokyo.paper.entity.TargetEntityInfo(rayTrace.getEntity().getBukkitEntity(), new org.bukkit.util.Vector(rayTrace.getLocation().x, rayTrace.getLocation().y, rayTrace.getLocation().z)); ++ } ++ ++ @Override ++ public RayTraceResult rayTraceEntities(int maxDistance, boolean ignoreBlocks) { ++ net.minecraft.world.phys.EntityHitResult rayTrace = this.rayTraceEntity(maxDistance, ignoreBlocks); ++ return rayTrace == null ? null : new org.bukkit.util.RayTraceResult(org.bukkit.craftbukkit.util.CraftVector.toBukkit(rayTrace.getLocation()), rayTrace.getEntity().getBukkitEntity()); ++ } ++ ++ public net.minecraft.world.phys.EntityHitResult rayTraceEntity(int maxDistance, boolean ignoreBlocks) { ++ net.minecraft.world.phys.EntityHitResult rayTrace = getHandle().getTargetEntity(maxDistance); ++ if (rayTrace == null) { ++ return null; ++ } ++ if (!ignoreBlocks) { ++ net.minecraft.world.phys.HitResult rayTraceBlocks = getHandle().getRayTrace(maxDistance, net.minecraft.world.level.ClipContext.Fluid.NONE); ++ if (rayTraceBlocks != null) { ++ net.minecraft.world.phys.Vec3 eye = getHandle().getEyePosition(1.0F); ++ if (eye.distanceToSqr(rayTraceBlocks.getLocation()) <= eye.distanceToSqr(rayTrace.getLocation())) { ++ return null; ++ } ++ } ++ } ++ return rayTrace; ++ } + // Paper end + + @Override diff --git a/patches/server/0251-PreSpawnerSpawnEvent.patch b/patches/server/0251-PreSpawnerSpawnEvent.patch deleted file mode 100644 index 3512bab510e8..000000000000 --- a/patches/server/0251-PreSpawnerSpawnEvent.patch +++ /dev/null @@ -1,27 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Phoenix616 -Date: Tue, 18 Sep 2018 23:53:23 +0100 -Subject: [PATCH] PreSpawnerSpawnEvent - -This adds a separate event before an entity is spawned by a spawner -which contains the location of the spawner too similarly to how the -SpawnerSpawnEvent gets called instead of the CreatureSpawnEvent for -spawners. - -diff --git a/src/main/java/net/minecraft/world/level/BaseSpawner.java b/src/main/java/net/minecraft/world/level/BaseSpawner.java -index d13abdcc7a54bdecf853c883911ef535733610b4..ee897b8c9462dbb3d7be9a2994753155065ce205 100644 ---- a/src/main/java/net/minecraft/world/level/BaseSpawner.java -+++ b/src/main/java/net/minecraft/world/level/BaseSpawner.java -@@ -133,10 +133,10 @@ public abstract class BaseSpawner { - continue; - } - // Paper start - PreCreatureSpawnEvent -- com.destroystokyo.paper.event.entity.PreCreatureSpawnEvent event = new com.destroystokyo.paper.event.entity.PreCreatureSpawnEvent( -+ com.destroystokyo.paper.event.entity.PreSpawnerSpawnEvent event = new com.destroystokyo.paper.event.entity.PreSpawnerSpawnEvent( - io.papermc.paper.util.MCUtil.toLocation(world, d0, d1, d2), - org.bukkit.craftbukkit.entity.CraftEntityType.minecraftToBukkit(optional.get()), -- org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason.SPAWNER -+ io.papermc.paper.util.MCUtil.toLocation(world, pos) - ); - if (!event.callEvent()) { - flag = true; diff --git a/patches/server/0252-Add-LivingEntity-getTargetEntity.patch b/patches/server/0252-Add-LivingEntity-getTargetEntity.patch deleted file mode 100644 index 7b66e0ad7524..000000000000 --- a/patches/server/0252-Add-LivingEntity-getTargetEntity.patch +++ /dev/null @@ -1,108 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: BillyGalbreath -Date: Sat, 22 Sep 2018 00:33:08 -0500 -Subject: [PATCH] Add LivingEntity#getTargetEntity - - -diff --git a/src/main/java/net/minecraft/world/entity/LivingEntity.java b/src/main/java/net/minecraft/world/entity/LivingEntity.java -index 64aa52c2d1fe50d304d75ebb197e8a834016c3cf..7bc0a66602d77902d83d6ca515da48e3453de900 100644 ---- a/src/main/java/net/minecraft/world/entity/LivingEntity.java -+++ b/src/main/java/net/minecraft/world/entity/LivingEntity.java -@@ -127,6 +127,7 @@ import net.minecraft.world.level.storage.loot.LootTable; - import net.minecraft.world.level.storage.loot.parameters.LootContextParamSets; - import net.minecraft.world.level.storage.loot.parameters.LootContextParams; - import net.minecraft.world.phys.AABB; -+import net.minecraft.world.phys.EntityHitResult; - import net.minecraft.world.phys.HitResult; - import net.minecraft.world.phys.Vec3; - import net.minecraft.world.scores.PlayerTeam; -@@ -4044,6 +4045,38 @@ public abstract class LivingEntity extends Entity implements Attackable { - return this.level().clip(raytrace); - } - -+ public @Nullable EntityHitResult getTargetEntity(int maxDistance) { -+ if (maxDistance < 1 || maxDistance > 120) { -+ throw new IllegalArgumentException("maxDistance must be between 1-120"); -+ } -+ -+ Vec3 start = this.getEyePosition(1.0F); -+ Vec3 direction = this.getLookAngle(); -+ Vec3 end = start.add(direction.x * maxDistance, direction.y * maxDistance, direction.z * maxDistance); -+ -+ List entityList = this.level().getEntities(this, getBoundingBox().expandTowards(direction.x * maxDistance, direction.y * maxDistance, direction.z * maxDistance).inflate(1.0D, 1.0D, 1.0D), EntitySelector.NO_SPECTATORS.and(Entity::isPickable)); -+ -+ double distance = 0.0D; -+ EntityHitResult result = null; -+ -+ for (Entity entity : entityList) { -+ final double inflationAmount = (double) entity.getPickRadius(); -+ AABB aabb = entity.getBoundingBox().inflate(inflationAmount, inflationAmount, inflationAmount); -+ Optional rayTraceResult = aabb.clip(start, end); -+ -+ if (rayTraceResult.isPresent()) { -+ Vec3 rayTrace = rayTraceResult.get(); -+ double distanceTo = start.distanceToSqr(rayTrace); -+ if (distanceTo < distance || distance == 0.0D) { -+ result = new EntityHitResult(entity, rayTrace); -+ distance = distanceTo; -+ } -+ } -+ } -+ -+ return result; -+ } -+ - public int shieldBlockingDelay = this.level().paperConfig().misc.shieldBlockingDelay; - - public int getShieldBlockingDelay() { -diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftLivingEntity.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftLivingEntity.java -index c8ac50351b7b1b2f4afc138570b8098a3c0ce1ba..c0684f1864ece26b4f337ac615db04f615957c13 100644 ---- a/src/main/java/org/bukkit/craftbukkit/entity/CraftLivingEntity.java -+++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftLivingEntity.java -@@ -1,5 +1,6 @@ - package org.bukkit.craftbukkit.entity; - -+import com.destroystokyo.paper.entity.TargetEntityInfo; - import com.google.common.base.Preconditions; - import com.google.common.collect.Sets; - import java.util.ArrayList; -@@ -227,6 +228,39 @@ public class CraftLivingEntity extends CraftEntity implements LivingEntity { - } - return null; - } -+ -+ public Entity getTargetEntity(int maxDistance, boolean ignoreBlocks) { -+ net.minecraft.world.phys.EntityHitResult rayTrace = rayTraceEntity(maxDistance, ignoreBlocks); -+ return rayTrace == null ? null : rayTrace.getEntity().getBukkitEntity(); -+ } -+ -+ public TargetEntityInfo getTargetEntityInfo(int maxDistance, boolean ignoreBlocks) { -+ net.minecraft.world.phys.EntityHitResult rayTrace = rayTraceEntity(maxDistance, ignoreBlocks); -+ return rayTrace == null ? null : new TargetEntityInfo(rayTrace.getEntity().getBukkitEntity(), new org.bukkit.util.Vector(rayTrace.getLocation().x, rayTrace.getLocation().y, rayTrace.getLocation().z)); -+ } -+ -+ @Override -+ public RayTraceResult rayTraceEntities(int maxDistance, boolean ignoreBlocks) { -+ net.minecraft.world.phys.EntityHitResult rayTrace = this.rayTraceEntity(maxDistance, ignoreBlocks); -+ return rayTrace == null ? null : new org.bukkit.util.RayTraceResult(org.bukkit.craftbukkit.util.CraftVector.toBukkit(rayTrace.getLocation()), rayTrace.getEntity().getBukkitEntity()); -+ } -+ -+ public net.minecraft.world.phys.EntityHitResult rayTraceEntity(int maxDistance, boolean ignoreBlocks) { -+ net.minecraft.world.phys.EntityHitResult rayTrace = getHandle().getTargetEntity(maxDistance); -+ if (rayTrace == null) { -+ return null; -+ } -+ if (!ignoreBlocks) { -+ net.minecraft.world.phys.HitResult rayTraceBlocks = getHandle().getRayTrace(maxDistance, net.minecraft.world.level.ClipContext.Fluid.NONE); -+ if (rayTraceBlocks != null) { -+ net.minecraft.world.phys.Vec3 eye = getHandle().getEyePosition(1.0F); -+ if (eye.distanceToSqr(rayTraceBlocks.getLocation()) <= eye.distanceToSqr(rayTrace.getLocation())) { -+ return null; -+ } -+ } -+ } -+ return rayTrace; -+ } - // Paper end - - @Override diff --git a/patches/server/0252-Add-sun-related-API.patch b/patches/server/0252-Add-sun-related-API.patch new file mode 100644 index 000000000000..2c323e679fb2 --- /dev/null +++ b/patches/server/0252-Add-sun-related-API.patch @@ -0,0 +1,42 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: BillyGalbreath +Date: Sun, 7 Oct 2018 00:54:21 -0500 +Subject: [PATCH] Add sun related API + +== AT == +public net.minecraft.world.entity.Mob isSunBurnTick()Z + +diff --git a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java +index a8e738941d8cd6373eb0ae5259da8e763a695657..d5f8c07480b1dc6b6f97e8ebc74b50493f011f45 100644 +--- a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java ++++ b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java +@@ -762,6 +762,13 @@ public class CraftWorld extends CraftRegionAccessor implements World { + } + } + ++ // Paper start ++ @Override ++ public boolean isDayTime() { ++ return getHandle().isDay(); ++ } ++ // Paper end ++ + @Override + public long getGameTime() { + return this.world.levelData.getGameTime(); +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftMob.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftMob.java +index 26c9bc53fe50636ca1eb32144c648f382d4172ff..c67673772c876dab7c015dcdfb75b297d3c4fbad 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftMob.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftMob.java +@@ -91,4 +91,11 @@ public abstract class CraftMob extends CraftLivingEntity implements Mob { + public long getSeed() { + return this.getHandle().lootTableSeed; + } ++ ++ // Paper start ++ @Override ++ public boolean isInDaylight() { ++ return getHandle().isSunBurnTick(); ++ } ++ // Paper end + } diff --git a/patches/server/0253-Add-sun-related-API.patch b/patches/server/0253-Add-sun-related-API.patch deleted file mode 100644 index 01cd62209b1e..000000000000 --- a/patches/server/0253-Add-sun-related-API.patch +++ /dev/null @@ -1,42 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: BillyGalbreath -Date: Sun, 7 Oct 2018 00:54:21 -0500 -Subject: [PATCH] Add sun related API - -== AT == -public net.minecraft.world.entity.Mob isSunBurnTick()Z - -diff --git a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java -index 333e3109d19c867e8a74f20693952bdb3df804e4..20bb365b188c7081123db87186f0e1a999758817 100644 ---- a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java -+++ b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java -@@ -756,6 +756,13 @@ public class CraftWorld extends CraftRegionAccessor implements World { - } - } - -+ // Paper start -+ @Override -+ public boolean isDayTime() { -+ return getHandle().isDay(); -+ } -+ // Paper end -+ - @Override - public long getGameTime() { - return this.world.levelData.getGameTime(); -diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftMob.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftMob.java -index d597eea5d5c2f223e87bff06f292619657596f1f..2a8596e4f9d7be966c18e867c2c7b5bfbea9742c 100644 ---- a/src/main/java/org/bukkit/craftbukkit/entity/CraftMob.java -+++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftMob.java -@@ -90,4 +90,11 @@ public abstract class CraftMob extends CraftLivingEntity implements Mob { - public long getSeed() { - return this.getHandle().lootTableSeed; - } -+ -+ // Paper start -+ @Override -+ public boolean isInDaylight() { -+ return getHandle().isSunBurnTick(); -+ } -+ // Paper end - } diff --git a/patches/server/0253-Turtle-API.patch b/patches/server/0253-Turtle-API.patch new file mode 100644 index 000000000000..bf96743cf230 --- /dev/null +++ b/patches/server/0253-Turtle-API.patch @@ -0,0 +1,83 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: BillyGalbreath +Date: Sat, 29 Sep 2018 16:08:23 -0500 +Subject: [PATCH] Turtle API + +== AT == +public net.minecraft.world.entity.animal.Turtle getHomePos()Lnet/minecraft/core/BlockPos; +public net.minecraft.world.entity.animal.Turtle setHasEgg(Z)V +public net.minecraft.world.entity.animal.Turtle isGoingHome()Z +public net.minecraft.world.entity.animal.Turtle setGoingHome(Z)V +public net.minecraft.world.entity.animal.Turtle isTravelling()Z +public net.minecraft.world.entity.animal.Turtle setTravelling(Z)V + +diff --git a/src/main/java/net/minecraft/world/entity/animal/Turtle.java b/src/main/java/net/minecraft/world/entity/animal/Turtle.java +index a2c5042e99a8f4cd45d502320cf1c06e8af0bd0e..ed7f5eb9b3b700c2f817d61ee0bf8a6952731510 100644 +--- a/src/main/java/net/minecraft/world/entity/animal/Turtle.java ++++ b/src/main/java/net/minecraft/world/entity/animal/Turtle.java +@@ -494,14 +494,17 @@ public class Turtle extends Animal { + + if (!this.turtle.isInWater() && this.isReachedTarget()) { + if (this.turtle.layEggCounter < 1) { +- this.turtle.setLayingEgg(true); ++ this.turtle.setLayingEgg(new com.destroystokyo.paper.event.entity.TurtleStartDiggingEvent((org.bukkit.entity.Turtle) this.turtle.getBukkitEntity(), io.papermc.paper.util.MCUtil.toLocation(this.turtle.level(), this.blockPos)).callEvent()); // Paper - Turtle API + } else if (this.turtle.layEggCounter > this.adjustedTickDelay(200)) { + Level world = this.turtle.level(); + +- if (org.bukkit.craftbukkit.event.CraftEventFactory.callEntityChangeBlockEvent(this.turtle, this.blockPos.above(), (BlockState) Blocks.TURTLE_EGG.defaultBlockState().setValue(TurtleEggBlock.EGGS, this.turtle.random.nextInt(4) + 1))) { // CraftBukkit ++ // Paper start - Turtle API ++ int eggCount = this.turtle.random.nextInt(4) + 1; ++ com.destroystokyo.paper.event.entity.TurtleLayEggEvent layEggEvent = new com.destroystokyo.paper.event.entity.TurtleLayEggEvent((org.bukkit.entity.Turtle) this.turtle.getBukkitEntity(), io.papermc.paper.util.MCUtil.toLocation(this.turtle.level(), this.blockPos.above()), eggCount); ++ if (layEggEvent.callEvent() && org.bukkit.craftbukkit.event.CraftEventFactory.callEntityChangeBlockEvent(this.turtle, this.blockPos.above(), Blocks.TURTLE_EGG.defaultBlockState().setValue(TurtleEggBlock.EGGS, layEggEvent.getEggCount()))) { + world.playSound((Player) null, blockposition, SoundEvents.TURTLE_LAY_EGG, SoundSource.BLOCKS, 0.3F, 0.9F + world.random.nextFloat() * 0.2F); + BlockPos blockposition1 = this.blockPos.above(); +- BlockState iblockdata = (BlockState) Blocks.TURTLE_EGG.defaultBlockState().setValue(TurtleEggBlock.EGGS, this.turtle.random.nextInt(4) + 1); ++ BlockState iblockdata = (BlockState) Blocks.TURTLE_EGG.defaultBlockState().setValue(TurtleEggBlock.EGGS, layEggEvent.getEggCount()); // Paper + + world.setBlock(blockposition1, iblockdata, 3); + world.gameEvent((Holder) GameEvent.BLOCK_PLACE, blockposition1, GameEvent.Context.of(this.turtle, iblockdata)); +@@ -571,7 +574,7 @@ public class Turtle extends Animal { + + @Override + public boolean canUse() { +- return this.turtle.isBaby() ? false : (this.turtle.hasEgg() ? true : (this.turtle.getRandom().nextInt(reducedTickDelay(700)) != 0 ? false : !this.turtle.getHomePos().closerToCenterThan(this.turtle.position(), 64.0D))); ++ return this.turtle.isBaby() ? false : (this.turtle.hasEgg() ? true : (this.turtle.getRandom().nextInt(reducedTickDelay(700)) != 0 ? false : !this.turtle.getHomePos().closerToCenterThan(this.turtle.position(), 64.0D))) && new com.destroystokyo.paper.event.entity.TurtleGoHomeEvent((org.bukkit.entity.Turtle) this.turtle.getBukkitEntity()).callEvent(); // Paper - Turtle API + } + + @Override +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftTurtle.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftTurtle.java +index fac0317ff945db991e761ac8973f0d3d41ade26b..d44e6f4bb682d18c1497eee9fb2802f2bda6e840 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftTurtle.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftTurtle.java +@@ -28,4 +28,31 @@ public class CraftTurtle extends CraftAnimals implements Turtle { + public boolean isLayingEgg() { + return this.getHandle().isLayingEgg(); + } ++ ++ // Paper start ++ @Override ++ public org.bukkit.Location getHome() { ++ return io.papermc.paper.util.MCUtil.toLocation(this.getHandle().level(), this.getHandle().getHomePos()); ++ } ++ ++ @Override ++ public void setHome(org.bukkit.Location location) { ++ this.getHandle().setHomePos(io.papermc.paper.util.MCUtil.toBlockPosition(location)); ++ } ++ ++ @Override ++ public boolean isGoingHome() { ++ return this.getHandle().isGoingHome(); ++ } ++ ++ @Override ++ public boolean isDigging() { ++ return this.getHandle().isLayingEgg(); ++ } ++ ++ @Override ++ public void setHasEgg(boolean hasEgg) { ++ this.getHandle().setHasEgg(hasEgg); ++ } ++ // Paper end + } diff --git a/patches/server/0254-Call-player-spectator-target-events-and-improve-impl.patch b/patches/server/0254-Call-player-spectator-target-events-and-improve-impl.patch new file mode 100644 index 000000000000..e23eee6ec8d3 --- /dev/null +++ b/patches/server/0254-Call-player-spectator-target-events-and-improve-impl.patch @@ -0,0 +1,46 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Caleb Bassham +Date: Fri, 28 Sep 2018 02:32:19 -0500 +Subject: [PATCH] Call player spectator target events and improve + implementation + +Use a proper teleport for teleporting to entities in different +worlds. + +Implementation improvements authored by Spottedleaf +Validate that the target entity is valid and deny spectate +requests from frozen players. + +Also, make sure the entity is spawned to the client before +sending the camera packet. If the entity isn't spawned clientside +when it receives the camera packet, then the client will not +spectate the target entity. + +Co-authored-by: Spottedleaf + +diff --git a/src/main/java/net/minecraft/server/level/ServerPlayer.java b/src/main/java/net/minecraft/server/level/ServerPlayer.java +index 2bd97344502a63173de923542f27759d7e98b6cc..c533098740bbf5d8e27011fa9593a0fa274e6600 100644 +--- a/src/main/java/net/minecraft/server/level/ServerPlayer.java ++++ b/src/main/java/net/minecraft/server/level/ServerPlayer.java +@@ -2441,6 +2441,21 @@ public class ServerPlayer extends net.minecraft.world.entity.player.Player { + + this.camera = (Entity) (entity == null ? this : entity); + if (entity1 != this.camera) { ++ // Paper start - Add PlayerStartSpectatingEntityEvent and PlayerStopSpectatingEntity ++ if (this.camera == this) { ++ com.destroystokyo.paper.event.player.PlayerStopSpectatingEntityEvent playerStopSpectatingEntityEvent = new com.destroystokyo.paper.event.player.PlayerStopSpectatingEntityEvent(this.getBukkitEntity(), entity1.getBukkitEntity()); ++ if (!playerStopSpectatingEntityEvent.callEvent()) { ++ this.camera = entity1; // rollback camera entity again ++ return; ++ } ++ } else { ++ com.destroystokyo.paper.event.player.PlayerStartSpectatingEntityEvent playerStartSpectatingEntityEvent = new com.destroystokyo.paper.event.player.PlayerStartSpectatingEntityEvent(this.getBukkitEntity(), entity1.getBukkitEntity(), entity.getBukkitEntity()); ++ if (!playerStartSpectatingEntityEvent.callEvent()) { ++ this.camera = entity1; // rollback camera entity again ++ return; ++ } ++ } ++ // Paper end - Add PlayerStartSpectatingEntityEvent and PlayerStopSpectatingEntity + Level world = this.camera.level(); + + if (world instanceof ServerLevel) { diff --git a/patches/server/0254-Turtle-API.patch b/patches/server/0254-Turtle-API.patch deleted file mode 100644 index efd6b79262ac..000000000000 --- a/patches/server/0254-Turtle-API.patch +++ /dev/null @@ -1,83 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: BillyGalbreath -Date: Sat, 29 Sep 2018 16:08:23 -0500 -Subject: [PATCH] Turtle API - -== AT == -public net.minecraft.world.entity.animal.Turtle getHomePos()Lnet/minecraft/core/BlockPos; -public net.minecraft.world.entity.animal.Turtle setHasEgg(Z)V -public net.minecraft.world.entity.animal.Turtle isGoingHome()Z -public net.minecraft.world.entity.animal.Turtle setGoingHome(Z)V -public net.minecraft.world.entity.animal.Turtle isTravelling()Z -public net.minecraft.world.entity.animal.Turtle setTravelling(Z)V - -diff --git a/src/main/java/net/minecraft/world/entity/animal/Turtle.java b/src/main/java/net/minecraft/world/entity/animal/Turtle.java -index 6f90ee749aed98b97868aa40fc233d164ddc2ef6..34e6bf677a9f4548f3febe6d57e6af9602530271 100644 ---- a/src/main/java/net/minecraft/world/entity/animal/Turtle.java -+++ b/src/main/java/net/minecraft/world/entity/animal/Turtle.java -@@ -487,14 +487,17 @@ public class Turtle extends Animal { - - if (!this.turtle.isInWater() && this.isReachedTarget()) { - if (this.turtle.layEggCounter < 1) { -- this.turtle.setLayingEgg(true); -+ this.turtle.setLayingEgg(new com.destroystokyo.paper.event.entity.TurtleStartDiggingEvent((org.bukkit.entity.Turtle) this.turtle.getBukkitEntity(), io.papermc.paper.util.MCUtil.toLocation(this.turtle.level(), this.blockPos)).callEvent()); // Paper - Turtle API - } else if (this.turtle.layEggCounter > this.adjustedTickDelay(200)) { - Level world = this.turtle.level(); - -- if (org.bukkit.craftbukkit.event.CraftEventFactory.callEntityChangeBlockEvent(this.turtle, this.blockPos.above(), (BlockState) Blocks.TURTLE_EGG.defaultBlockState().setValue(TurtleEggBlock.EGGS, this.turtle.random.nextInt(4) + 1))) { // CraftBukkit -+ // Paper start - Turtle API -+ int eggCount = this.turtle.random.nextInt(4) + 1; -+ com.destroystokyo.paper.event.entity.TurtleLayEggEvent layEggEvent = new com.destroystokyo.paper.event.entity.TurtleLayEggEvent((org.bukkit.entity.Turtle) this.turtle.getBukkitEntity(), io.papermc.paper.util.MCUtil.toLocation(this.turtle.level(), this.blockPos.above()), eggCount); -+ if (layEggEvent.callEvent() && org.bukkit.craftbukkit.event.CraftEventFactory.callEntityChangeBlockEvent(this.turtle, this.blockPos.above(), Blocks.TURTLE_EGG.defaultBlockState().setValue(TurtleEggBlock.EGGS, layEggEvent.getEggCount()))) { - world.playSound((Player) null, blockposition, SoundEvents.TURTLE_LAY_EGG, SoundSource.BLOCKS, 0.3F, 0.9F + world.random.nextFloat() * 0.2F); - BlockPos blockposition1 = this.blockPos.above(); -- BlockState iblockdata = (BlockState) Blocks.TURTLE_EGG.defaultBlockState().setValue(TurtleEggBlock.EGGS, this.turtle.random.nextInt(4) + 1); -+ BlockState iblockdata = (BlockState) Blocks.TURTLE_EGG.defaultBlockState().setValue(TurtleEggBlock.EGGS, layEggEvent.getEggCount()); // Paper - - world.setBlock(blockposition1, iblockdata, 3); - world.gameEvent((Holder) GameEvent.BLOCK_PLACE, blockposition1, GameEvent.Context.of(this.turtle, iblockdata)); -@@ -564,7 +567,7 @@ public class Turtle extends Animal { - - @Override - public boolean canUse() { -- return this.turtle.isBaby() ? false : (this.turtle.hasEgg() ? true : (this.turtle.getRandom().nextInt(reducedTickDelay(700)) != 0 ? false : !this.turtle.getHomePos().closerToCenterThan(this.turtle.position(), 64.0D))); -+ return this.turtle.isBaby() ? false : (this.turtle.hasEgg() ? true : (this.turtle.getRandom().nextInt(reducedTickDelay(700)) != 0 ? false : !this.turtle.getHomePos().closerToCenterThan(this.turtle.position(), 64.0D))) && new com.destroystokyo.paper.event.entity.TurtleGoHomeEvent((org.bukkit.entity.Turtle) this.turtle.getBukkitEntity()).callEvent(); // Paper - Turtle API - } - - @Override -diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftTurtle.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftTurtle.java -index fac0317ff945db991e761ac8973f0d3d41ade26b..d44e6f4bb682d18c1497eee9fb2802f2bda6e840 100644 ---- a/src/main/java/org/bukkit/craftbukkit/entity/CraftTurtle.java -+++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftTurtle.java -@@ -28,4 +28,31 @@ public class CraftTurtle extends CraftAnimals implements Turtle { - public boolean isLayingEgg() { - return this.getHandle().isLayingEgg(); - } -+ -+ // Paper start -+ @Override -+ public org.bukkit.Location getHome() { -+ return io.papermc.paper.util.MCUtil.toLocation(this.getHandle().level(), this.getHandle().getHomePos()); -+ } -+ -+ @Override -+ public void setHome(org.bukkit.Location location) { -+ this.getHandle().setHomePos(io.papermc.paper.util.MCUtil.toBlockPosition(location)); -+ } -+ -+ @Override -+ public boolean isGoingHome() { -+ return this.getHandle().isGoingHome(); -+ } -+ -+ @Override -+ public boolean isDigging() { -+ return this.getHandle().isLayingEgg(); -+ } -+ -+ @Override -+ public void setHasEgg(boolean hasEgg) { -+ this.getHandle().setHasEgg(hasEgg); -+ } -+ // Paper end - } diff --git a/patches/server/0255-Add-more-Witch-API.patch b/patches/server/0255-Add-more-Witch-API.patch new file mode 100644 index 000000000000..c250557de6fe --- /dev/null +++ b/patches/server/0255-Add-more-Witch-API.patch @@ -0,0 +1,101 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: BillyGalbreath +Date: Fri, 12 Oct 2018 14:10:46 -0500 +Subject: [PATCH] Add more Witch API + +== AT == +public net.minecraft.world.entity.monster.Witch usingTime + +diff --git a/src/main/java/net/minecraft/world/entity/monster/Witch.java b/src/main/java/net/minecraft/world/entity/monster/Witch.java +index 120f0c729c48ddfa598472029cdfbab3dc6db50f..a03fa8a3e648532a7ffaaf523ca87c13e8af4c0a 100644 +--- a/src/main/java/net/minecraft/world/entity/monster/Witch.java ++++ b/src/main/java/net/minecraft/world/entity/monster/Witch.java +@@ -152,21 +152,7 @@ public class Witch extends Raider implements RangedAttackMob { + } + + if (holder != null) { +- // Paper start +- ItemStack potion = PotionContents.createItemStack(Items.POTION, holder); +- potion = org.bukkit.craftbukkit.event.CraftEventFactory.handleWitchReadyPotionEvent(this, potion); +- this.setItemSlot(EquipmentSlot.MAINHAND, potion); +- // Paper end +- this.usingTime = this.getMainHandItem().getUseDuration(this); +- this.setUsingItem(true); +- if (!this.isSilent()) { +- this.level().playSound((Player) null, this.getX(), this.getY(), this.getZ(), SoundEvents.WITCH_DRINK, this.getSoundSource(), 1.0F, 0.8F + this.random.nextFloat() * 0.4F); +- } +- +- AttributeInstance attributemodifiable = this.getAttribute(Attributes.MOVEMENT_SPEED); +- +- attributemodifiable.removeModifier(Witch.SPEED_MODIFIER_DRINKING_ID); +- attributemodifiable.addTransientModifier(Witch.SPEED_MODIFIER_DRINKING); ++ this.setDrinkingPotion(PotionContents.createItemStack(Items.POTION, holder)); // Paper - logic moved into setDrinkingPotion, copy exact impl into the method and then comment out + } + } + +@@ -178,6 +164,23 @@ public class Witch extends Raider implements RangedAttackMob { + super.aiStep(); + } + ++ // Paper start - moved to its own method ++ public void setDrinkingPotion(ItemStack potion) { ++ potion = org.bukkit.craftbukkit.event.CraftEventFactory.handleWitchReadyPotionEvent(this, potion); ++ this.setItemSlot(EquipmentSlot.MAINHAND, potion); ++ this.usingTime = this.getMainHandItem().getUseDuration(this); ++ this.setUsingItem(true); ++ if (!this.isSilent()) { ++ this.level().playSound((Player) null, this.getX(), this.getY(), this.getZ(), SoundEvents.WITCH_DRINK, this.getSoundSource(), 1.0F, 0.8F + this.random.nextFloat() * 0.4F); ++ } ++ ++ AttributeInstance attributemodifiable = this.getAttribute(Attributes.MOVEMENT_SPEED); ++ ++ attributemodifiable.removeModifier(Witch.SPEED_MODIFIER_DRINKING_ID); ++ attributemodifiable.addTransientModifier(Witch.SPEED_MODIFIER_DRINKING); ++ } ++ // Paper end ++ + @Override + public SoundEvent getCelebrateSound() { + return SoundEvents.WITCH_CELEBRATE; +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftWitch.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftWitch.java +index 524b5ba5995affc09eedf9a85d22e8b0b4efc156..4b3d783cabcb2de1a67d7fbfb6f525bfb493aed1 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftWitch.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftWitch.java +@@ -2,6 +2,13 @@ package org.bukkit.craftbukkit.entity; + + import org.bukkit.craftbukkit.CraftServer; + import org.bukkit.entity.Witch; ++// Paper start ++import com.destroystokyo.paper.entity.CraftRangedEntity; ++import com.google.common.base.Preconditions; ++import org.bukkit.Material; ++import org.bukkit.craftbukkit.inventory.CraftItemStack; ++import org.bukkit.inventory.ItemStack; ++// Paper end + + public class CraftWitch extends CraftRaider implements Witch, com.destroystokyo.paper.entity.CraftRangedEntity { // Paper + public CraftWitch(CraftServer server, net.minecraft.world.entity.monster.Witch entity) { +@@ -22,4 +29,23 @@ public class CraftWitch extends CraftRaider implements Witch, com.destroystokyo. + public boolean isDrinkingPotion() { + return this.getHandle().isDrinkingPotion(); + } ++ // Paper start ++ public int getPotionUseTimeLeft() { ++ return getHandle().usingTime; ++ } ++ ++ @Override ++ public void setPotionUseTimeLeft(int ticks) { ++ getHandle().usingTime = ticks; ++ } ++ ++ public ItemStack getDrinkingPotion() { ++ return CraftItemStack.asCraftMirror(getHandle().getMainHandItem()); ++ } ++ ++ public void setDrinkingPotion(ItemStack potion) { ++ Preconditions.checkArgument(potion == null || potion.getType().isEmpty() || potion.getType() == Material.POTION, "must be potion, air, or null"); ++ getHandle().setDrinkingPotion(CraftItemStack.asNMSCopy(potion)); ++ } ++ // Paper end + } diff --git a/patches/server/0255-Call-player-spectator-target-events-and-improve-impl.patch b/patches/server/0255-Call-player-spectator-target-events-and-improve-impl.patch deleted file mode 100644 index b29358d325b3..000000000000 --- a/patches/server/0255-Call-player-spectator-target-events-and-improve-impl.patch +++ /dev/null @@ -1,46 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Caleb Bassham -Date: Fri, 28 Sep 2018 02:32:19 -0500 -Subject: [PATCH] Call player spectator target events and improve - implementation - -Use a proper teleport for teleporting to entities in different -worlds. - -Implementation improvements authored by Spottedleaf -Validate that the target entity is valid and deny spectate -requests from frozen players. - -Also, make sure the entity is spawned to the client before -sending the camera packet. If the entity isn't spawned clientside -when it receives the camera packet, then the client will not -spectate the target entity. - -Co-authored-by: Spottedleaf - -diff --git a/src/main/java/net/minecraft/server/level/ServerPlayer.java b/src/main/java/net/minecraft/server/level/ServerPlayer.java -index fa8640f961b93dc811296131dfda58faa1908add..15328d344a26f5c40011ee6ba0bc54dd5ab0b87b 100644 ---- a/src/main/java/net/minecraft/server/level/ServerPlayer.java -+++ b/src/main/java/net/minecraft/server/level/ServerPlayer.java -@@ -2182,6 +2182,21 @@ public class ServerPlayer extends net.minecraft.world.entity.player.Player { - - this.camera = (Entity) (entity == null ? this : entity); - if (entity1 != this.camera) { -+ // Paper start - Add PlayerStartSpectatingEntityEvent and PlayerStopSpectatingEntity -+ if (this.camera == this) { -+ com.destroystokyo.paper.event.player.PlayerStopSpectatingEntityEvent playerStopSpectatingEntityEvent = new com.destroystokyo.paper.event.player.PlayerStopSpectatingEntityEvent(this.getBukkitEntity(), entity1.getBukkitEntity()); -+ if (!playerStopSpectatingEntityEvent.callEvent()) { -+ this.camera = entity1; // rollback camera entity again -+ return; -+ } -+ } else { -+ com.destroystokyo.paper.event.player.PlayerStartSpectatingEntityEvent playerStartSpectatingEntityEvent = new com.destroystokyo.paper.event.player.PlayerStartSpectatingEntityEvent(this.getBukkitEntity(), entity1.getBukkitEntity(), entity.getBukkitEntity()); -+ if (!playerStartSpectatingEntityEvent.callEvent()) { -+ this.camera = entity1; // rollback camera entity again -+ return; -+ } -+ } -+ // Paper end - Add PlayerStartSpectatingEntityEvent and PlayerStopSpectatingEntity - Level world = this.camera.level(); - - if (world instanceof ServerLevel) { diff --git a/patches/server/0256-Add-more-Witch-API.patch b/patches/server/0256-Add-more-Witch-API.patch deleted file mode 100644 index ead3a185c684..000000000000 --- a/patches/server/0256-Add-more-Witch-API.patch +++ /dev/null @@ -1,101 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: BillyGalbreath -Date: Fri, 12 Oct 2018 14:10:46 -0500 -Subject: [PATCH] Add more Witch API - -== AT == -public net.minecraft.world.entity.monster.Witch usingTime - -diff --git a/src/main/java/net/minecraft/world/entity/monster/Witch.java b/src/main/java/net/minecraft/world/entity/monster/Witch.java -index f6d01d21745391595d61b191832be4c28a3e58cb..b8ff1e3d280171378fe383bcc7c6a855d20ae5d1 100644 ---- a/src/main/java/net/minecraft/world/entity/monster/Witch.java -+++ b/src/main/java/net/minecraft/world/entity/monster/Witch.java -@@ -151,21 +151,7 @@ public class Witch extends Raider implements RangedAttackMob { - } - - if (holder != null) { -- // Paper start -- ItemStack potion = PotionContents.createItemStack(Items.POTION, holder); -- potion = org.bukkit.craftbukkit.event.CraftEventFactory.handleWitchReadyPotionEvent(this, potion); -- this.setItemSlot(EquipmentSlot.MAINHAND, potion); -- // Paper end -- this.usingTime = this.getMainHandItem().getUseDuration(this); -- this.setUsingItem(true); -- if (!this.isSilent()) { -- this.level().playSound((Player) null, this.getX(), this.getY(), this.getZ(), SoundEvents.WITCH_DRINK, this.getSoundSource(), 1.0F, 0.8F + this.random.nextFloat() * 0.4F); -- } -- -- AttributeInstance attributemodifiable = this.getAttribute(Attributes.MOVEMENT_SPEED); -- -- attributemodifiable.removeModifier(Witch.SPEED_MODIFIER_DRINKING_ID); -- attributemodifiable.addTransientModifier(Witch.SPEED_MODIFIER_DRINKING); -+ this.setDrinkingPotion(PotionContents.createItemStack(Items.POTION, holder)); // Paper - logic moved into setDrinkingPotion, copy exact impl into the method and then comment out - } - } - -@@ -177,6 +163,23 @@ public class Witch extends Raider implements RangedAttackMob { - super.aiStep(); - } - -+ // Paper start - moved to its own method -+ public void setDrinkingPotion(ItemStack potion) { -+ potion = org.bukkit.craftbukkit.event.CraftEventFactory.handleWitchReadyPotionEvent(this, potion); -+ this.setItemSlot(EquipmentSlot.MAINHAND, potion); -+ this.usingTime = this.getMainHandItem().getUseDuration(this); -+ this.setUsingItem(true); -+ if (!this.isSilent()) { -+ this.level().playSound((Player) null, this.getX(), this.getY(), this.getZ(), SoundEvents.WITCH_DRINK, this.getSoundSource(), 1.0F, 0.8F + this.random.nextFloat() * 0.4F); -+ } -+ -+ AttributeInstance attributemodifiable = this.getAttribute(Attributes.MOVEMENT_SPEED); -+ -+ attributemodifiable.removeModifier(Witch.SPEED_MODIFIER_DRINKING_ID); -+ attributemodifiable.addTransientModifier(Witch.SPEED_MODIFIER_DRINKING); -+ } -+ // Paper end -+ - @Override - public SoundEvent getCelebrateSound() { - return SoundEvents.WITCH_CELEBRATE; -diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftWitch.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftWitch.java -index 524b5ba5995affc09eedf9a85d22e8b0b4efc156..4b3d783cabcb2de1a67d7fbfb6f525bfb493aed1 100644 ---- a/src/main/java/org/bukkit/craftbukkit/entity/CraftWitch.java -+++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftWitch.java -@@ -2,6 +2,13 @@ package org.bukkit.craftbukkit.entity; - - import org.bukkit.craftbukkit.CraftServer; - import org.bukkit.entity.Witch; -+// Paper start -+import com.destroystokyo.paper.entity.CraftRangedEntity; -+import com.google.common.base.Preconditions; -+import org.bukkit.Material; -+import org.bukkit.craftbukkit.inventory.CraftItemStack; -+import org.bukkit.inventory.ItemStack; -+// Paper end - - public class CraftWitch extends CraftRaider implements Witch, com.destroystokyo.paper.entity.CraftRangedEntity { // Paper - public CraftWitch(CraftServer server, net.minecraft.world.entity.monster.Witch entity) { -@@ -22,4 +29,23 @@ public class CraftWitch extends CraftRaider implements Witch, com.destroystokyo. - public boolean isDrinkingPotion() { - return this.getHandle().isDrinkingPotion(); - } -+ // Paper start -+ public int getPotionUseTimeLeft() { -+ return getHandle().usingTime; -+ } -+ -+ @Override -+ public void setPotionUseTimeLeft(int ticks) { -+ getHandle().usingTime = ticks; -+ } -+ -+ public ItemStack getDrinkingPotion() { -+ return CraftItemStack.asCraftMirror(getHandle().getMainHandItem()); -+ } -+ -+ public void setDrinkingPotion(ItemStack potion) { -+ Preconditions.checkArgument(potion == null || potion.getType().isEmpty() || potion.getType() == Material.POTION, "must be potion, air, or null"); -+ getHandle().setDrinkingPotion(CraftItemStack.asNMSCopy(potion)); -+ } -+ // Paper end - } diff --git a/patches/server/0256-Check-Drowned-for-Villager-Aggression-Config.patch b/patches/server/0256-Check-Drowned-for-Villager-Aggression-Config.patch new file mode 100644 index 000000000000..34b444c1096e --- /dev/null +++ b/patches/server/0256-Check-Drowned-for-Villager-Aggression-Config.patch @@ -0,0 +1,19 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: BillyGalbreath +Date: Wed, 10 Oct 2018 21:22:44 -0500 +Subject: [PATCH] Check Drowned for Villager Aggression Config + + +diff --git a/src/main/java/net/minecraft/world/entity/monster/Drowned.java b/src/main/java/net/minecraft/world/entity/monster/Drowned.java +index 95e0a5d464398c17dfaa2f919a17f63c63925551..2e73917ce9270de7483bb1d4e9bf312a31ec9b1e 100644 +--- a/src/main/java/net/minecraft/world/entity/monster/Drowned.java ++++ b/src/main/java/net/minecraft/world/entity/monster/Drowned.java +@@ -82,7 +82,7 @@ public class Drowned extends Zombie implements RangedAttackMob { + this.goalSelector.addGoal(7, new RandomStrollGoal(this, 1.0)); + this.targetSelector.addGoal(1, new HurtByTargetGoal(this, Drowned.class).setAlertOthers(ZombifiedPiglin.class)); + this.targetSelector.addGoal(2, new NearestAttackableTargetGoal<>(this, Player.class, 10, true, false, (target, world) -> this.okTarget(target))); +- this.targetSelector.addGoal(3, new NearestAttackableTargetGoal<>(this, AbstractVillager.class, false)); ++ if (this.level().spigotConfig.zombieAggressiveTowardsVillager) this.targetSelector.addGoal(3, new NearestAttackableTargetGoal<>(this, AbstractVillager.class, false)); // Paper - Check drowned for villager aggression config + this.targetSelector.addGoal(3, new NearestAttackableTargetGoal<>(this, IronGolem.class, true)); + this.targetSelector.addGoal(3, new NearestAttackableTargetGoal<>(this, Axolotl.class, true, false)); + this.targetSelector.addGoal(5, new NearestAttackableTargetGoal<>(this, Turtle.class, 10, true, false, Turtle.BABY_ON_LAND_SELECTOR)); diff --git a/patches/server/0257-Add-option-to-prevent-players-from-moving-into-unloa.patch b/patches/server/0257-Add-option-to-prevent-players-from-moving-into-unloa.patch new file mode 100644 index 000000000000..a382aa3965a7 --- /dev/null +++ b/patches/server/0257-Add-option-to-prevent-players-from-moving-into-unloa.patch @@ -0,0 +1,67 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Gabriele C +Date: Mon, 22 Oct 2018 17:34:10 +0200 +Subject: [PATCH] Add option to prevent players from moving into unloaded + chunks #1551 + + +diff --git a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java +index af519e9914bae40fa1605b0cd4cf02ca1d44f0e8..f57c133de5e974d2c86145f697d3636347a29d65 100644 +--- a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java ++++ b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java +@@ -498,9 +498,9 @@ public class ServerGamePacketListenerImpl extends ServerCommonPacketListenerImpl + double d0 = entity.getX(); + double d1 = entity.getY(); + double d2 = entity.getZ(); +- double d3 = ServerGamePacketListenerImpl.clampHorizontal(packet.getX()); +- double d4 = ServerGamePacketListenerImpl.clampVertical(packet.getY()); +- double d5 = ServerGamePacketListenerImpl.clampHorizontal(packet.getZ()); ++ double d3 = ServerGamePacketListenerImpl.clampHorizontal(packet.getX()); final double toX = d3; // Paper - OBFHELPER ++ double d4 = ServerGamePacketListenerImpl.clampVertical(packet.getY()); final double toY = d4; // Paper - OBFHELPER ++ double d5 = ServerGamePacketListenerImpl.clampHorizontal(packet.getZ()); final double toZ = d5; // Paper - OBFHELPER + float f = Mth.wrapDegrees(packet.getYRot()); + float f1 = Mth.wrapDegrees(packet.getXRot()); + double d6 = d3 - this.vehicleFirstGoodX; +@@ -534,6 +534,16 @@ public class ServerGamePacketListenerImpl extends ServerCommonPacketListenerImpl + } + speed *= 2f; // TODO: Get the speed of the vehicle instead of the player + ++ // Paper start - Prevent moving into unloaded chunks ++ if (this.player.level().paperConfig().chunks.preventMovingIntoUnloadedChunks && ( ++ !worldserver.areChunksLoadedForMove(this.player.getBoundingBox().expandTowards(new Vec3(toX, toY, toZ).subtract(this.player.position()))) || ++ !worldserver.areChunksLoadedForMove(entity.getBoundingBox().expandTowards(new Vec3(toX, toY, toZ).subtract(entity.position()))) ++ )) { ++ this.connection.send(new ClientboundMoveVehiclePacket(entity)); ++ return; ++ } ++ // Paper end - Prevent moving into unloaded chunks ++ + if (d10 - d9 > Math.max(100.0D, Math.pow((double) (org.spigotmc.SpigotConfig.movedTooQuicklyMultiplier * (float) i * speed), 2)) && !this.isSingleplayerOwner()) { + // CraftBukkit end + ServerGamePacketListenerImpl.LOGGER.warn("{} (vehicle of {}) moved too quickly! {},{},{}", new Object[]{entity.getName().getString(), this.player.getName().getString(), d6, d7, d8}); +@@ -1167,9 +1177,9 @@ public class ServerGamePacketListenerImpl extends ServerCommonPacketListenerImpl + } + + if (!this.updateAwaitingTeleport()) { +- double d0 = ServerGamePacketListenerImpl.clampHorizontal(packet.getX(this.player.getX())); +- double d1 = ServerGamePacketListenerImpl.clampVertical(packet.getY(this.player.getY())); +- double d2 = ServerGamePacketListenerImpl.clampHorizontal(packet.getZ(this.player.getZ())); ++ double d0 = ServerGamePacketListenerImpl.clampHorizontal(packet.getX(this.player.getX())); final double toX = d0; // Paper - OBFHELPER ++ double d1 = ServerGamePacketListenerImpl.clampVertical(packet.getY(this.player.getY())); final double toY = d1; // Paper - OBFHELPER ++ double d2 = ServerGamePacketListenerImpl.clampHorizontal(packet.getZ(this.player.getZ())); final double toZ = d2; // Paper - OBFHELPER + float f = Mth.wrapDegrees(packet.getYRot(this.player.getYRot())); + float f1 = Mth.wrapDegrees(packet.getXRot(this.player.getXRot())); + +@@ -1227,6 +1237,12 @@ public class ServerGamePacketListenerImpl extends ServerCommonPacketListenerImpl + } else { + speed = this.player.getAbilities().walkingSpeed * 10f; + } ++ // Paper start - Prevent moving into unloaded chunks ++ if (this.player.level().paperConfig().chunks.preventMovingIntoUnloadedChunks && (this.player.getX() != toX || this.player.getZ() != toZ) && !worldserver.areChunksLoadedForMove(this.player.getBoundingBox().expandTowards(new Vec3(toX, toY, toZ).subtract(this.player.position())))) { ++ this.internalTeleport(PositionMoveRotation.of(this.player), Collections.emptySet()); ++ return; ++ } ++ // Paper end - Prevent moving into unloaded chunks + + if (this.shouldCheckPlayerMovement(flag)) { + float f2 = flag ? 300.0F : 100.0F; diff --git a/patches/server/0257-Check-Drowned-for-Villager-Aggression-Config.patch b/patches/server/0257-Check-Drowned-for-Villager-Aggression-Config.patch deleted file mode 100644 index efafeb919ce5..000000000000 --- a/patches/server/0257-Check-Drowned-for-Villager-Aggression-Config.patch +++ /dev/null @@ -1,19 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: BillyGalbreath -Date: Wed, 10 Oct 2018 21:22:44 -0500 -Subject: [PATCH] Check Drowned for Villager Aggression Config - - -diff --git a/src/main/java/net/minecraft/world/entity/monster/Drowned.java b/src/main/java/net/minecraft/world/entity/monster/Drowned.java -index d4b3ce2bb2021625c90a3f51c6f9da6056b2e2ff..cff1b5e0e3fd32d82157d5f13d83d4abdfad7378 100644 ---- a/src/main/java/net/minecraft/world/entity/monster/Drowned.java -+++ b/src/main/java/net/minecraft/world/entity/monster/Drowned.java -@@ -81,7 +81,7 @@ public class Drowned extends Zombie implements RangedAttackMob { - this.goalSelector.addGoal(7, new RandomStrollGoal(this, 1.0D)); - this.targetSelector.addGoal(1, (new HurtByTargetGoal(this, new Class[]{Drowned.class})).setAlertOthers(ZombifiedPiglin.class)); - this.targetSelector.addGoal(2, new NearestAttackableTargetGoal<>(this, Player.class, 10, true, false, this::okTarget)); -- this.targetSelector.addGoal(3, new NearestAttackableTargetGoal<>(this, AbstractVillager.class, false)); -+ if (this.level().spigotConfig.zombieAggressiveTowardsVillager) this.targetSelector.addGoal(3, new NearestAttackableTargetGoal<>(this, AbstractVillager.class, false)); // Paper - Check drowned for villager aggression config - this.targetSelector.addGoal(3, new NearestAttackableTargetGoal<>(this, IronGolem.class, true)); - this.targetSelector.addGoal(3, new NearestAttackableTargetGoal<>(this, Axolotl.class, true, false)); - this.targetSelector.addGoal(5, new NearestAttackableTargetGoal<>(this, Turtle.class, 10, true, false, Turtle.BABY_ON_LAND_SELECTOR)); diff --git a/patches/server/0258-Add-option-to-prevent-players-from-moving-into-unloa.patch b/patches/server/0258-Add-option-to-prevent-players-from-moving-into-unloa.patch deleted file mode 100644 index c62badf3b150..000000000000 --- a/patches/server/0258-Add-option-to-prevent-players-from-moving-into-unloa.patch +++ /dev/null @@ -1,67 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Gabriele C -Date: Mon, 22 Oct 2018 17:34:10 +0200 -Subject: [PATCH] Add option to prevent players from moving into unloaded - chunks #1551 - - -diff --git a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java -index 5ed35d744a87290a03e9bf58143b5650501af0e6..6cf68d7173bc23c39261856f11cbd42de387bd60 100644 ---- a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java -+++ b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java -@@ -494,9 +494,9 @@ public class ServerGamePacketListenerImpl extends ServerCommonPacketListenerImpl - double d0 = entity.getX(); - double d1 = entity.getY(); - double d2 = entity.getZ(); -- double d3 = ServerGamePacketListenerImpl.clampHorizontal(packet.getX()); -- double d4 = ServerGamePacketListenerImpl.clampVertical(packet.getY()); -- double d5 = ServerGamePacketListenerImpl.clampHorizontal(packet.getZ()); -+ double d3 = ServerGamePacketListenerImpl.clampHorizontal(packet.getX()); final double toX = d3; // Paper - OBFHELPER -+ double d4 = ServerGamePacketListenerImpl.clampVertical(packet.getY()); final double toY = d4; // Paper - OBFHELPER -+ double d5 = ServerGamePacketListenerImpl.clampHorizontal(packet.getZ()); final double toZ = d5; // Paper - OBFHELPER - float f = Mth.wrapDegrees(packet.getYRot()); - float f1 = Mth.wrapDegrees(packet.getXRot()); - double d6 = d3 - this.vehicleFirstGoodX; -@@ -530,6 +530,16 @@ public class ServerGamePacketListenerImpl extends ServerCommonPacketListenerImpl - } - speed *= 2f; // TODO: Get the speed of the vehicle instead of the player - -+ // Paper start - Prevent moving into unloaded chunks -+ if (this.player.level().paperConfig().chunks.preventMovingIntoUnloadedChunks && ( -+ !worldserver.areChunksLoadedForMove(this.player.getBoundingBox().expandTowards(new Vec3(toX, toY, toZ).subtract(this.player.position()))) || -+ !worldserver.areChunksLoadedForMove(entity.getBoundingBox().expandTowards(new Vec3(toX, toY, toZ).subtract(entity.position()))) -+ )) { -+ this.connection.send(new ClientboundMoveVehiclePacket(entity)); -+ return; -+ } -+ // Paper end - Prevent moving into unloaded chunks -+ - if (d10 - d9 > Math.max(100.0D, Math.pow((double) (org.spigotmc.SpigotConfig.movedTooQuicklyMultiplier * (float) i * speed), 2)) && !this.isSingleplayerOwner()) { - // CraftBukkit end - ServerGamePacketListenerImpl.LOGGER.warn("{} (vehicle of {}) moved too quickly! {},{},{}", new Object[]{entity.getName().getString(), this.player.getName().getString(), d6, d7, d8}); -@@ -1157,9 +1167,9 @@ public class ServerGamePacketListenerImpl extends ServerCommonPacketListenerImpl - } - - if (!this.updateAwaitingTeleport()) { -- double d0 = ServerGamePacketListenerImpl.clampHorizontal(packet.getX(this.player.getX())); -- double d1 = ServerGamePacketListenerImpl.clampVertical(packet.getY(this.player.getY())); -- double d2 = ServerGamePacketListenerImpl.clampHorizontal(packet.getZ(this.player.getZ())); -+ double d0 = ServerGamePacketListenerImpl.clampHorizontal(packet.getX(this.player.getX())); final double toX = d0; // Paper - OBFHELPER -+ double d1 = ServerGamePacketListenerImpl.clampVertical(packet.getY(this.player.getY())); final double toY = d1; // Paper - OBFHELPER -+ double d2 = ServerGamePacketListenerImpl.clampHorizontal(packet.getZ(this.player.getZ())); final double toZ = d2; // Paper - OBFHELPER - float f = Mth.wrapDegrees(packet.getYRot(this.player.getYRot())); - float f1 = Mth.wrapDegrees(packet.getXRot(this.player.getXRot())); - -@@ -1217,6 +1227,12 @@ public class ServerGamePacketListenerImpl extends ServerCommonPacketListenerImpl - } else { - speed = this.player.getAbilities().walkingSpeed * 10f; - } -+ // Paper start - Prevent moving into unloaded chunks -+ if (this.player.level().paperConfig().chunks.preventMovingIntoUnloadedChunks && (this.player.getX() != toX || this.player.getZ() != toZ) && !worldserver.areChunksLoadedForMove(this.player.getBoundingBox().expandTowards(new Vec3(toX, toY, toZ).subtract(this.player.position())))) { -+ this.internalTeleport(this.player.getX(), this.player.getY(), this.player.getZ(), this.player.getYRot(), this.player.getXRot(), Collections.emptySet()); -+ return; -+ } -+ // Paper end - Prevent moving into unloaded chunks - - if (!this.player.isChangingDimension() && (!this.player.level().getGameRules().getBoolean(GameRules.RULE_DISABLE_ELYTRA_MOVEMENT_CHECK) || !flag)) { - float f2 = flag ? 300.0F : 100.0F; diff --git a/patches/server/0258-Reset-players-airTicks-on-respawn.patch b/patches/server/0258-Reset-players-airTicks-on-respawn.patch new file mode 100644 index 000000000000..601a3944c079 --- /dev/null +++ b/patches/server/0258-Reset-players-airTicks-on-respawn.patch @@ -0,0 +1,18 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: GreenMeanie +Date: Sat, 20 Oct 2018 22:34:02 -0400 +Subject: [PATCH] Reset players airTicks on respawn + + +diff --git a/src/main/java/net/minecraft/server/level/ServerPlayer.java b/src/main/java/net/minecraft/server/level/ServerPlayer.java +index c533098740bbf5d8e27011fa9593a0fa274e6600..cef054ba95ed7d2b0e2ee575edae3e94b77f58b6 100644 +--- a/src/main/java/net/minecraft/server/level/ServerPlayer.java ++++ b/src/main/java/net/minecraft/server/level/ServerPlayer.java +@@ -3052,6 +3052,7 @@ public class ServerPlayer extends net.minecraft.world.entity.player.Player { + + this.setHealth(this.getMaxHealth()); + this.stopUsingItem(); // CraftBukkit - SPIGOT-6682: Clear active item on reset ++ this.setAirSupply(this.getMaxAirSupply()); // Paper - Reset players airTicks on respawn + this.setRemainingFireTicks(0); + this.fallDistance = 0; + this.foodData = new FoodData(); diff --git a/patches/server/0260-Don-t-sleep-after-profile-lookups-if-not-needed.patch b/patches/server/0259-Don-t-sleep-after-profile-lookups-if-not-needed.patch similarity index 100% rename from patches/server/0260-Don-t-sleep-after-profile-lookups-if-not-needed.patch rename to patches/server/0259-Don-t-sleep-after-profile-lookups-if-not-needed.patch diff --git a/patches/server/0259-Reset-players-airTicks-on-respawn.patch b/patches/server/0259-Reset-players-airTicks-on-respawn.patch deleted file mode 100644 index 0669db4de0c0..000000000000 --- a/patches/server/0259-Reset-players-airTicks-on-respawn.patch +++ /dev/null @@ -1,18 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: GreenMeanie -Date: Sat, 20 Oct 2018 22:34:02 -0400 -Subject: [PATCH] Reset players airTicks on respawn - - -diff --git a/src/main/java/net/minecraft/server/level/ServerPlayer.java b/src/main/java/net/minecraft/server/level/ServerPlayer.java -index 15328d344a26f5c40011ee6ba0bc54dd5ab0b87b..7cca5c778f9d20cfa6cb543c9afcf74779aaa355 100644 ---- a/src/main/java/net/minecraft/server/level/ServerPlayer.java -+++ b/src/main/java/net/minecraft/server/level/ServerPlayer.java -@@ -2703,6 +2703,7 @@ public class ServerPlayer extends net.minecraft.world.entity.player.Player { - - this.setHealth(this.getMaxHealth()); - this.stopUsingItem(); // CraftBukkit - SPIGOT-6682: Clear active item on reset -+ this.setAirSupply(this.getMaxAirSupply()); // Paper - Reset players airTicks on respawn - this.setRemainingFireTicks(0); - this.fallDistance = 0; - this.foodData = new FoodData(this); diff --git a/patches/server/0260-Improve-Server-Thread-Pool-and-Thread-Priorities.patch b/patches/server/0260-Improve-Server-Thread-Pool-and-Thread-Priorities.patch new file mode 100644 index 000000000000..71b311db27f3 --- /dev/null +++ b/patches/server/0260-Improve-Server-Thread-Pool-and-Thread-Priorities.patch @@ -0,0 +1,107 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Aikar +Date: Tue, 23 Oct 2018 23:14:38 -0400 +Subject: [PATCH] Improve Server Thread Pool and Thread Priorities + +Use a simple executor since Fork join is a much more complex pool +type and we are not using its capabilities. + +Set thread priorities so main thread has above normal priority over +server threads + +Allow usage of a single thread executor by not using ForkJoin so single core CPU's +and reduce worldgen thread worker count for low core count CPUs. + +== AT == +public net.minecraft.Util onThreadException(Ljava/lang/Thread;Ljava/lang/Throwable;)V + +Co-authored-by: Spottedleaf + +diff --git a/src/main/java/io/papermc/paper/util/ServerWorkerThread.java b/src/main/java/io/papermc/paper/util/ServerWorkerThread.java +new file mode 100644 +index 0000000000000000000000000000000000000000..b60f59cf5cc8eb84a6055b7861857dece7f2501b +--- /dev/null ++++ b/src/main/java/io/papermc/paper/util/ServerWorkerThread.java +@@ -0,0 +1,14 @@ ++package io.papermc.paper.util; ++ ++import java.util.concurrent.atomic.AtomicInteger; ++import net.minecraft.Util; ++ ++public class ServerWorkerThread extends Thread { ++ private static final AtomicInteger threadId = new AtomicInteger(1); ++ public ServerWorkerThread(Runnable target, String poolName, int prioritityModifier) { ++ super(target, "Worker-" + poolName + "-" + threadId.getAndIncrement()); ++ setPriority(Thread.NORM_PRIORITY+prioritityModifier); // Deprioritize over main ++ this.setDaemon(true); ++ this.setUncaughtExceptionHandler(Util::onThreadException); ++ } ++} +diff --git a/src/main/java/net/minecraft/Util.java b/src/main/java/net/minecraft/Util.java +index 8cac2075077b1d9c2b01e09c99780ff9e204abb2..bf2833c92eca6491699b4a89410e4e46b5bbe4d1 100644 +--- a/src/main/java/net/minecraft/Util.java ++++ b/src/main/java/net/minecraft/Util.java +@@ -92,7 +92,7 @@ public class Util { + private static final int DEFAULT_MAX_THREADS = 255; + private static final int DEFAULT_SAFE_FILE_OPERATION_RETRIES = 10; + private static final String MAX_THREADS_SYSTEM_PROPERTY = "max.bg.threads"; +- private static final TracingExecutor BACKGROUND_EXECUTOR = makeExecutor("Main"); ++ private static final TracingExecutor BACKGROUND_EXECUTOR = makeExecutor("Main", -1); // Paper - Perf: add priority + private static final TracingExecutor IO_POOL = makeIoExecutor("IO-Worker-", false); + private static final TracingExecutor DOWNLOAD_POOL = makeIoExecutor("Download-", true); + // Paper start - don't submit BLOCKING PROFILE LOOKUPS to the world gen thread +@@ -163,15 +163,28 @@ public class Util { + return FILENAME_DATE_TIME_FORMATTER.format(ZonedDateTime.now()); + } + +- private static TracingExecutor makeExecutor(String name) { +- int i = Mth.clamp(Runtime.getRuntime().availableProcessors() - 1, 1, getMaxThreads()); ++ private static TracingExecutor makeExecutor(String s, final int priorityModifier) { // Paper - Perf: add priority ++ // Paper start - Perf: use simpler thread pool that allows 1 thread and reduce worldgen thread worker count for low core count CPUs ++ int cpus = Runtime.getRuntime().availableProcessors() / 2; ++ int i; ++ if (cpus <= 4) { ++ i = cpus <= 2 ? 1 : 2; ++ } else if (cpus <= 8) { ++ // [5, 8] ++ i = Math.max(3, cpus - 2); ++ } else { ++ i = cpus * 2 / 3; ++ } ++ i = Math.min(8, i); ++ i = Integer.getInteger("Paper.WorkerThreadCount", i); ++ + ExecutorService executorService; + if (i <= 0) { + executorService = MoreExecutors.newDirectExecutorService(); + } else { +- AtomicInteger atomicInteger = new AtomicInteger(1); +- executorService = new ForkJoinPool(i, pool -> { +- final String string2 = "Worker-" + name + "-" + atomicInteger.getAndIncrement(); ++ executorService = new java.util.concurrent.ThreadPoolExecutor(i, i,0L, TimeUnit.MILLISECONDS, new java.util.concurrent.LinkedBlockingQueue<>(), target -> new io.papermc.paper.util.ServerWorkerThread(target, s, priorityModifier)); ++ } ++ /* final String string2 = "Worker-" + name + "-" + atomicInteger.getAndIncrement(); + ForkJoinWorkerThread forkJoinWorkerThread = new ForkJoinWorkerThread(pool) { + @Override + protected void onStart() { +@@ -193,7 +206,7 @@ public class Util { + forkJoinWorkerThread.setName(string2); + return forkJoinWorkerThread; + }, Util::onThreadException, true); +- } ++ }*/ + + return new TracingExecutor(executorService); + } +diff --git a/src/main/java/net/minecraft/server/MinecraftServer.java b/src/main/java/net/minecraft/server/MinecraftServer.java +index eb0adba0178a88243946e0c7f39503aa8c7d8feb..3669a2943b01e0e9add41df1ff38afd1cd40e96b 100644 +--- a/src/main/java/net/minecraft/server/MinecraftServer.java ++++ b/src/main/java/net/minecraft/server/MinecraftServer.java +@@ -332,6 +332,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop { + MinecraftServer.LOGGER.error("Uncaught exception in server thread", throwable); + }); ++ thread.setPriority(Thread.NORM_PRIORITY+2); // Paper - Perf: Boost priority + if (Runtime.getRuntime().availableProcessors() > 4) { + thread.setPriority(8); + } diff --git a/patches/server/0261-Improve-Server-Thread-Pool-and-Thread-Priorities.patch b/patches/server/0261-Improve-Server-Thread-Pool-and-Thread-Priorities.patch deleted file mode 100644 index 588ca7240f46..000000000000 --- a/patches/server/0261-Improve-Server-Thread-Pool-and-Thread-Priorities.patch +++ /dev/null @@ -1,105 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Aikar -Date: Tue, 23 Oct 2018 23:14:38 -0400 -Subject: [PATCH] Improve Server Thread Pool and Thread Priorities - -Use a simple executor since Fork join is a much more complex pool -type and we are not using its capabilities. - -Set thread priorities so main thread has above normal priority over -server threads - -Allow usage of a single thread executor by not using ForkJoin so single core CPU's -and reduce worldgen thread worker count for low core count CPUs. - -== AT == -public net.minecraft.Util onThreadException(Ljava/lang/Thread;Ljava/lang/Throwable;)V - -Co-authored-by: Spottedleaf - -diff --git a/src/main/java/io/papermc/paper/util/ServerWorkerThread.java b/src/main/java/io/papermc/paper/util/ServerWorkerThread.java -new file mode 100644 -index 0000000000000000000000000000000000000000..b60f59cf5cc8eb84a6055b7861857dece7f2501b ---- /dev/null -+++ b/src/main/java/io/papermc/paper/util/ServerWorkerThread.java -@@ -0,0 +1,14 @@ -+package io.papermc.paper.util; -+ -+import java.util.concurrent.atomic.AtomicInteger; -+import net.minecraft.Util; -+ -+public class ServerWorkerThread extends Thread { -+ private static final AtomicInteger threadId = new AtomicInteger(1); -+ public ServerWorkerThread(Runnable target, String poolName, int prioritityModifier) { -+ super(target, "Worker-" + poolName + "-" + threadId.getAndIncrement()); -+ setPriority(Thread.NORM_PRIORITY+prioritityModifier); // Deprioritize over main -+ this.setDaemon(true); -+ this.setUncaughtExceptionHandler(Util::onThreadException); -+ } -+} -diff --git a/src/main/java/net/minecraft/Util.java b/src/main/java/net/minecraft/Util.java -index 54562fa04d14a937451ea7aa9d80194f2c31b471..4cf88f6d815d60cfbf8e4ecf9d96d0cfadd0620b 100644 ---- a/src/main/java/net/minecraft/Util.java -+++ b/src/main/java/net/minecraft/Util.java -@@ -89,7 +89,7 @@ public class Util { - private static final int DEFAULT_MAX_THREADS = 255; - private static final int DEFAULT_SAFE_FILE_OPERATION_RETRIES = 10; - private static final String MAX_THREADS_SYSTEM_PROPERTY = "max.bg.threads"; -- private static final ExecutorService BACKGROUND_EXECUTOR = makeExecutor("Main"); -+ private static final ExecutorService BACKGROUND_EXECUTOR = makeExecutor("Main", -1); // Paper - Perf: add priority - private static final ExecutorService IO_POOL = makeIoExecutor("IO-Worker-", false); - private static final ExecutorService DOWNLOAD_POOL = makeIoExecutor("Download-", true); - // Paper start - don't submit BLOCKING PROFILE LOOKUPS to the world gen thread -@@ -160,15 +160,27 @@ public class Util { - return FILENAME_DATE_TIME_FORMATTER.format(ZonedDateTime.now()); - } - -- private static ExecutorService makeExecutor(String name) { -- int i = Mth.clamp(Runtime.getRuntime().availableProcessors() - 1, 1, getMaxThreads()); -+ private static ExecutorService makeExecutor(String s, int priorityModifier) { // Paper - Perf: add priority -+ // Paper start - Perf: use simpler thread pool that allows 1 thread and reduce worldgen thread worker count for low core count CPUs -+ int cpus = Runtime.getRuntime().availableProcessors() / 2; -+ int i; -+ if (cpus <= 4) { -+ i = cpus <= 2 ? 1 : 2; -+ } else if (cpus <= 8) { -+ // [5, 8] -+ i = Math.max(3, cpus - 2); -+ } else { -+ i = cpus * 2 / 3; -+ } -+ i = Math.min(8, i); -+ i = Integer.getInteger("Paper.WorkerThreadCount", i); - ExecutorService executorService; - if (i <= 0) { - executorService = MoreExecutors.newDirectExecutorService(); - } else { -- AtomicInteger atomicInteger = new AtomicInteger(1); -- executorService = new ForkJoinPool(i, pool -> { -- ForkJoinWorkerThread forkJoinWorkerThread = new ForkJoinWorkerThread(pool) { -+ executorService = new java.util.concurrent.ThreadPoolExecutor(i, i,0L, TimeUnit.MILLISECONDS, new java.util.concurrent.LinkedBlockingQueue<>(), target -> new io.papermc.paper.util.ServerWorkerThread(target, s, priorityModifier)); -+ } -+ /* - @Override - protected void onTermination(Throwable throwable) { - if (throwable != null) { -@@ -184,6 +196,7 @@ public class Util { - return forkJoinWorkerThread; - }, Util::onThreadException, true); - } -+ }*/ // Paper end - Perf: use simpler thread pool - - return executorService; - } -diff --git a/src/main/java/net/minecraft/server/MinecraftServer.java b/src/main/java/net/minecraft/server/MinecraftServer.java -index 2d5ae71c143556a938f078d2fb84cab7bd4f789b..f3f3f80f14bc1df13b80033aa143fcccab6f3a31 100644 ---- a/src/main/java/net/minecraft/server/MinecraftServer.java -+++ b/src/main/java/net/minecraft/server/MinecraftServer.java -@@ -322,6 +322,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop { - MinecraftServer.LOGGER.error("Uncaught exception in server thread", throwable); - }); -+ thread.setPriority(Thread.NORM_PRIORITY+2); // Paper - Perf: Boost priority - if (Runtime.getRuntime().availableProcessors() > 4) { - thread.setPriority(8); - } diff --git a/patches/server/0261-Optimize-World-Time-Updates.patch b/patches/server/0261-Optimize-World-Time-Updates.patch new file mode 100644 index 000000000000..15bcb789439e --- /dev/null +++ b/patches/server/0261-Optimize-World-Time-Updates.patch @@ -0,0 +1,40 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Aikar +Date: Fri, 2 Nov 2018 23:11:51 -0400 +Subject: [PATCH] Optimize World Time Updates + +Splits time updates into incremental updates as well as does +the updates per world, so that we can re-use the same packet +object for every player unless they have per-player time enabled. + +diff --git a/src/main/java/net/minecraft/server/MinecraftServer.java b/src/main/java/net/minecraft/server/MinecraftServer.java +index 3669a2943b01e0e9add41df1ff38afd1cd40e96b..a4c897000a5e70e64b5fe4306581b04d8d38bce2 100644 +--- a/src/main/java/net/minecraft/server/MinecraftServer.java ++++ b/src/main/java/net/minecraft/server/MinecraftServer.java +@@ -1604,10 +1604,22 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop -Date: Fri, 2 Nov 2018 23:11:51 -0400 -Subject: [PATCH] Optimize World Time Updates - -Splits time updates into incremental updates as well as does -the updates per world, so that we can re-use the same packet -object for every player unless they have per-player time enabled. - -diff --git a/src/main/java/net/minecraft/server/MinecraftServer.java b/src/main/java/net/minecraft/server/MinecraftServer.java -index f3f3f80f14bc1df13b80033aa143fcccab6f3a31..699191356a9d873fa6bbe04ea3e5db91eb2db41d 100644 ---- a/src/main/java/net/minecraft/server/MinecraftServer.java -+++ b/src/main/java/net/minecraft/server/MinecraftServer.java -@@ -1560,12 +1560,24 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop +Date: Sat, 10 Nov 2018 05:15:21 +0000 +Subject: [PATCH] Fix SpongeAbsortEvent handling + +Only process drops when the block is actually going to be removed + +diff --git a/src/main/java/net/minecraft/world/level/block/SpongeBlock.java b/src/main/java/net/minecraft/world/level/block/SpongeBlock.java +index 569bdef574680319b4fd6569ef05b2b3c04c739d..59cf905b1b5686f6f4f2bad94730ffa69d3a2834 100644 +--- a/src/main/java/net/minecraft/world/level/block/SpongeBlock.java ++++ b/src/main/java/net/minecraft/world/level/block/SpongeBlock.java +@@ -137,7 +137,11 @@ public class SpongeBlock extends Block { + } else if (iblockdata.is(Blocks.KELP) || iblockdata.is(Blocks.KELP_PLANT) || iblockdata.is(Blocks.SEAGRASS) || iblockdata.is(Blocks.TALL_SEAGRASS)) { + BlockEntity tileentity = iblockdata.hasBlockEntity() ? world.getBlockEntity(blockposition1) : null; + ++ // Paper start - Fix SpongeAbsortEvent handling ++ if (block.getHandle().isAir()) { + dropResources(iblockdata, world, blockposition1, tileentity); ++ } ++ // Paper end - Fix SpongeAbsortEvent handling + } + } + world.setBlock(blockposition1, block.getHandle(), block.getFlag()); diff --git a/patches/server/0264-Don-t-allow-digging-into-unloaded-chunks.patch b/patches/server/0264-Don-t-allow-digging-into-unloaded-chunks.patch new file mode 100644 index 000000000000..3c5725587c12 --- /dev/null +++ b/patches/server/0264-Don-t-allow-digging-into-unloaded-chunks.patch @@ -0,0 +1,77 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Shane Freeder +Date: Sun, 11 Nov 2018 21:01:09 +0000 +Subject: [PATCH] Don't allow digging into unloaded chunks + + +diff --git a/src/main/java/net/minecraft/server/level/ServerPlayerGameMode.java b/src/main/java/net/minecraft/server/level/ServerPlayerGameMode.java +index 4c8189a2a7edea824545a24dccb376b8eceac001..4623c8acd125dff4919c4e2045b848310d785da5 100644 +--- a/src/main/java/net/minecraft/server/level/ServerPlayerGameMode.java ++++ b/src/main/java/net/minecraft/server/level/ServerPlayerGameMode.java +@@ -122,8 +122,8 @@ public class ServerPlayerGameMode { + BlockState iblockdata; + + if (this.hasDelayedDestroy) { +- iblockdata = this.level.getBlockState(this.delayedDestroyPos); +- if (iblockdata.isAir()) { ++ iblockdata = this.level.getBlockStateIfLoaded(this.delayedDestroyPos); // Paper - Don't allow digging into unloaded chunks ++ if (iblockdata == null || iblockdata.isAir()) { // Paper - Don't allow digging into unloaded chunks + this.hasDelayedDestroy = false; + } else { + float f = this.incrementDestroyProgress(iblockdata, this.delayedDestroyPos, this.delayedTickStart); +@@ -134,7 +134,13 @@ public class ServerPlayerGameMode { + } + } + } else if (this.isDestroyingBlock) { +- iblockdata = this.level.getBlockState(this.destroyPos); ++ // Paper start - Don't allow digging into unloaded chunks; don't want to do same logic as above, return instead ++ iblockdata = this.level.getBlockStateIfLoaded(this.destroyPos); ++ if (iblockdata == null) { ++ this.isDestroyingBlock = false; ++ return; ++ } ++ // Paper end - Don't allow digging into unloaded chunks + if (iblockdata.isAir()) { + this.level.destroyBlockProgress(this.player.getId(), this.destroyPos, -1); + this.lastSentState = -1; +@@ -163,6 +169,7 @@ public class ServerPlayerGameMode { + + public void handleBlockBreakAction(BlockPos pos, ServerboundPlayerActionPacket.Action action, Direction direction, int worldHeight, int sequence) { + if (!this.player.canInteractWithBlock(pos, 1.0D)) { ++ if (true) return; // Paper - Don't allow digging into unloaded chunks; Don't notify if unreasonably far away + this.debugLogging(pos, false, sequence, "too far"); + } else if (pos.getY() > worldHeight) { + this.player.connection.send(new ClientboundBlockUpdatePacket(pos, this.level.getBlockState(pos))); +@@ -305,10 +312,12 @@ public class ServerPlayerGameMode { + this.debugLogging(pos, true, sequence, "stopped destroying"); + } else if (action == ServerboundPlayerActionPacket.Action.ABORT_DESTROY_BLOCK) { + this.isDestroyingBlock = false; +- if (!Objects.equals(this.destroyPos, pos)) { ++ if (!Objects.equals(this.destroyPos, pos) && !BlockPos.ZERO.equals(this.destroyPos)) { // Paper + ServerPlayerGameMode.LOGGER.debug("Mismatch in destroy block pos: {} {}", this.destroyPos, pos); // CraftBukkit - SPIGOT-5457 sent by client when interact event cancelled +- this.level.destroyBlockProgress(this.player.getId(), this.destroyPos, -1); +- this.debugLogging(pos, true, sequence, "aborted mismatched destroying"); ++ BlockState type = this.level.getBlockStateIfLoaded(this.destroyPos); // Paper - don't load unloaded chunks for stale records here ++ if (type != null) this.level.destroyBlockProgress(this.player.getId(), this.destroyPos, -1); ++ if (type != null) this.debugLogging(pos, true, sequence, "aborted mismatched destroying"); ++ this.destroyPos = BlockPos.ZERO; // Paper + } + + this.level.destroyBlockProgress(this.player.getId(), pos, -1); +diff --git a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java +index f57c133de5e974d2c86145f697d3636347a29d65..c3e281754a1cd9d5ce5f4aab36822f9f5d34be39 100644 +--- a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java ++++ b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java +@@ -1606,6 +1606,12 @@ public class ServerGamePacketListenerImpl extends ServerCommonPacketListenerImpl + case START_DESTROY_BLOCK: + case ABORT_DESTROY_BLOCK: + case STOP_DESTROY_BLOCK: ++ // Paper start - Don't allow digging into unloaded chunks ++ if (this.player.level().getChunkIfLoadedImmediately(blockposition.getX() >> 4, blockposition.getZ() >> 4) == null) { ++ this.player.connection.ackBlockChangesUpTo(packet.getSequence()); ++ return; ++ } ++ // Paper end - Don't allow digging into unloaded chunks + this.player.gameMode.handleBlockBreakAction(blockposition, packetplayinblockdig_enumplayerdigtype, packet.getDirection(), this.player.level().getMaxY(), packet.getSequence()); + this.player.connection.ackBlockChangesUpTo(packet.getSequence()); + return; diff --git a/patches/server/0264-Fix-SpongeAbsortEvent-handling.patch b/patches/server/0264-Fix-SpongeAbsortEvent-handling.patch deleted file mode 100644 index be21ac2045e4..000000000000 --- a/patches/server/0264-Fix-SpongeAbsortEvent-handling.patch +++ /dev/null @@ -1,23 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Shane Freeder -Date: Sat, 10 Nov 2018 05:15:21 +0000 -Subject: [PATCH] Fix SpongeAbsortEvent handling - -Only process drops when the block is actually going to be removed - -diff --git a/src/main/java/net/minecraft/world/level/block/SpongeBlock.java b/src/main/java/net/minecraft/world/level/block/SpongeBlock.java -index 2e54730deee3149517a535f15ef6c0628ba659c2..902825ec9ea05f4418b45f56a008d73f217bd178 100644 ---- a/src/main/java/net/minecraft/world/level/block/SpongeBlock.java -+++ b/src/main/java/net/minecraft/world/level/block/SpongeBlock.java -@@ -134,7 +134,11 @@ public class SpongeBlock extends Block { - } else if (iblockdata.is(Blocks.KELP) || iblockdata.is(Blocks.KELP_PLANT) || iblockdata.is(Blocks.SEAGRASS) || iblockdata.is(Blocks.TALL_SEAGRASS)) { - BlockEntity tileentity = iblockdata.hasBlockEntity() ? world.getBlockEntity(blockposition1) : null; - -+ // Paper start - Fix SpongeAbsortEvent handling -+ if (block.getHandle().isAir()) { - dropResources(iblockdata, world, blockposition1, tileentity); -+ } -+ // Paper end - Fix SpongeAbsortEvent handling - } - } - world.setBlock(blockposition1, block.getHandle(), block.getFlag()); diff --git a/patches/server/0265-Don-t-allow-digging-into-unloaded-chunks.patch b/patches/server/0265-Don-t-allow-digging-into-unloaded-chunks.patch deleted file mode 100644 index f65e5077e72d..000000000000 --- a/patches/server/0265-Don-t-allow-digging-into-unloaded-chunks.patch +++ /dev/null @@ -1,77 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Shane Freeder -Date: Sun, 11 Nov 2018 21:01:09 +0000 -Subject: [PATCH] Don't allow digging into unloaded chunks - - -diff --git a/src/main/java/net/minecraft/server/level/ServerPlayerGameMode.java b/src/main/java/net/minecraft/server/level/ServerPlayerGameMode.java -index a5b0efd6142075ca1ecb604afbc1d0162199e7a4..da9e864520150acd8027545672aa476be414bb4d 100644 ---- a/src/main/java/net/minecraft/server/level/ServerPlayerGameMode.java -+++ b/src/main/java/net/minecraft/server/level/ServerPlayerGameMode.java -@@ -124,8 +124,8 @@ public class ServerPlayerGameMode { - BlockState iblockdata; - - if (this.hasDelayedDestroy) { -- iblockdata = this.level.getBlockState(this.delayedDestroyPos); -- if (iblockdata.isAir()) { -+ iblockdata = this.level.getBlockStateIfLoaded(this.delayedDestroyPos); // Paper - Don't allow digging into unloaded chunks -+ if (iblockdata == null || iblockdata.isAir()) { // Paper - Don't allow digging into unloaded chunks - this.hasDelayedDestroy = false; - } else { - float f = this.incrementDestroyProgress(iblockdata, this.delayedDestroyPos, this.delayedTickStart); -@@ -136,7 +136,13 @@ public class ServerPlayerGameMode { - } - } - } else if (this.isDestroyingBlock) { -- iblockdata = this.level.getBlockState(this.destroyPos); -+ // Paper start - Don't allow digging into unloaded chunks; don't want to do same logic as above, return instead -+ iblockdata = this.level.getBlockStateIfLoaded(this.destroyPos); -+ if (iblockdata == null) { -+ this.isDestroyingBlock = false; -+ return; -+ } -+ // Paper end - Don't allow digging into unloaded chunks - if (iblockdata.isAir()) { - this.level.destroyBlockProgress(this.player.getId(), this.destroyPos, -1); - this.lastSentState = -1; -@@ -165,6 +171,7 @@ public class ServerPlayerGameMode { - - public void handleBlockBreakAction(BlockPos pos, ServerboundPlayerActionPacket.Action action, Direction direction, int worldHeight, int sequence) { - if (!this.player.canInteractWithBlock(pos, 1.0D)) { -+ if (true) return; // Paper - Don't allow digging into unloaded chunks; Don't notify if unreasonably far away - this.debugLogging(pos, false, sequence, "too far"); - } else if (pos.getY() >= worldHeight) { - this.player.connection.send(new ClientboundBlockUpdatePacket(pos, this.level.getBlockState(pos))); -@@ -307,10 +314,12 @@ public class ServerPlayerGameMode { - this.debugLogging(pos, true, sequence, "stopped destroying"); - } else if (action == ServerboundPlayerActionPacket.Action.ABORT_DESTROY_BLOCK) { - this.isDestroyingBlock = false; -- if (!Objects.equals(this.destroyPos, pos)) { -+ if (!Objects.equals(this.destroyPos, pos) && !BlockPos.ZERO.equals(this.destroyPos)) { // Paper - ServerPlayerGameMode.LOGGER.debug("Mismatch in destroy block pos: {} {}", this.destroyPos, pos); // CraftBukkit - SPIGOT-5457 sent by client when interact event cancelled -- this.level.destroyBlockProgress(this.player.getId(), this.destroyPos, -1); -- this.debugLogging(pos, true, sequence, "aborted mismatched destroying"); -+ BlockState type = this.level.getBlockStateIfLoaded(this.destroyPos); // Paper - don't load unloaded chunks for stale records here -+ if (type != null) this.level.destroyBlockProgress(this.player.getId(), this.destroyPos, -1); -+ if (type != null) this.debugLogging(pos, true, sequence, "aborted mismatched destroying"); -+ this.destroyPos = BlockPos.ZERO; // Paper - } - - this.level.destroyBlockProgress(this.player.getId(), pos, -1); -diff --git a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java -index 6cf68d7173bc23c39261856f11cbd42de387bd60..4942aa857ecc5762c9b0b1662eefaeae4daab80d 100644 ---- a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java -+++ b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java -@@ -1595,6 +1595,12 @@ public class ServerGamePacketListenerImpl extends ServerCommonPacketListenerImpl - case START_DESTROY_BLOCK: - case ABORT_DESTROY_BLOCK: - case STOP_DESTROY_BLOCK: -+ // Paper start - Don't allow digging into unloaded chunks -+ if (this.player.level().getChunkIfLoadedImmediately(blockposition.getX() >> 4, blockposition.getZ() >> 4) == null) { -+ this.player.connection.ackBlockChangesUpTo(packet.getSequence()); -+ return; -+ } -+ // Paper end - Don't allow digging into unloaded chunks - this.player.gameMode.handleBlockBreakAction(blockposition, packetplayinblockdig_enumplayerdigtype, packet.getDirection(), this.player.level().getMaxBuildHeight(), packet.getSequence()); - this.player.connection.ackBlockChangesUpTo(packet.getSequence()); - return; diff --git a/patches/server/0265-Make-the-default-permission-message-configurable.patch b/patches/server/0265-Make-the-default-permission-message-configurable.patch new file mode 100644 index 000000000000..6aedb7eff830 --- /dev/null +++ b/patches/server/0265-Make-the-default-permission-message-configurable.patch @@ -0,0 +1,40 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Shane Freeder +Date: Sun, 18 Nov 2018 19:49:56 +0000 +Subject: [PATCH] Make the default permission message configurable + + +diff --git a/src/main/java/io/papermc/paper/command/PaperCommand.java b/src/main/java/io/papermc/paper/command/PaperCommand.java +index 5b070d158760789bbcaa984426a55d20767abe4a..e1820a339452cd3388dd7cbb928c5f58779a77b6 100644 +--- a/src/main/java/io/papermc/paper/command/PaperCommand.java ++++ b/src/main/java/io/papermc/paper/command/PaperCommand.java +@@ -73,7 +73,7 @@ public final class PaperCommand extends Command { + if (sender.hasPermission(BASE_PERM + permission) || sender.hasPermission("bukkit.command.paper")) { + return true; + } +- sender.sendMessage(text("I'm sorry, but you do not have permission to perform this command. Please contact the server administrators if you believe that this is in error.", RED)); ++ sender.sendMessage(Bukkit.permissionMessage()); + return false; + } + +diff --git a/src/main/java/org/bukkit/craftbukkit/CraftServer.java b/src/main/java/org/bukkit/craftbukkit/CraftServer.java +index 6d08b753c04a78e4ec07eaf8ff6c3b9daaf91f64..e7dbb7a6e0e6bb9c42e72531f4cf337abb7090ee 100644 +--- a/src/main/java/org/bukkit/craftbukkit/CraftServer.java ++++ b/src/main/java/org/bukkit/craftbukkit/CraftServer.java +@@ -2890,6 +2890,16 @@ public final class CraftServer implements Server { + return io.papermc.paper.configuration.GlobalConfiguration.get().commands.suggestPlayerNamesWhenNullTabCompletions; + } + ++ @Override ++ public String getPermissionMessage() { ++ return net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer.legacyAmpersand().serialize(io.papermc.paper.configuration.GlobalConfiguration.get().messages.noPermission); ++ } ++ ++ @Override ++ public net.kyori.adventure.text.Component permissionMessage() { ++ return io.papermc.paper.configuration.GlobalConfiguration.get().messages.noPermission; ++ } ++ + @Override + public com.destroystokyo.paper.profile.PlayerProfile createProfile(@Nonnull UUID uuid) { + return createProfile(uuid, null); diff --git a/patches/server/0266-Make-the-default-permission-message-configurable.patch b/patches/server/0266-Make-the-default-permission-message-configurable.patch deleted file mode 100644 index a1ea906d4dcb..000000000000 --- a/patches/server/0266-Make-the-default-permission-message-configurable.patch +++ /dev/null @@ -1,40 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Shane Freeder -Date: Sun, 18 Nov 2018 19:49:56 +0000 -Subject: [PATCH] Make the default permission message configurable - - -diff --git a/src/main/java/io/papermc/paper/command/PaperCommand.java b/src/main/java/io/papermc/paper/command/PaperCommand.java -index 5b070d158760789bbcaa984426a55d20767abe4a..e1820a339452cd3388dd7cbb928c5f58779a77b6 100644 ---- a/src/main/java/io/papermc/paper/command/PaperCommand.java -+++ b/src/main/java/io/papermc/paper/command/PaperCommand.java -@@ -73,7 +73,7 @@ public final class PaperCommand extends Command { - if (sender.hasPermission(BASE_PERM + permission) || sender.hasPermission("bukkit.command.paper")) { - return true; - } -- sender.sendMessage(text("I'm sorry, but you do not have permission to perform this command. Please contact the server administrators if you believe that this is in error.", RED)); -+ sender.sendMessage(Bukkit.permissionMessage()); - return false; - } - -diff --git a/src/main/java/org/bukkit/craftbukkit/CraftServer.java b/src/main/java/org/bukkit/craftbukkit/CraftServer.java -index 954b3725d4f702f284cd8712305a3f97fb90b9c1..7407d9f3480e0e44be44c84831864e511dfdebc2 100644 ---- a/src/main/java/org/bukkit/craftbukkit/CraftServer.java -+++ b/src/main/java/org/bukkit/craftbukkit/CraftServer.java -@@ -2874,6 +2874,16 @@ public final class CraftServer implements Server { - return io.papermc.paper.configuration.GlobalConfiguration.get().commands.suggestPlayerNamesWhenNullTabCompletions; - } - -+ @Override -+ public String getPermissionMessage() { -+ return net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer.legacyAmpersand().serialize(io.papermc.paper.configuration.GlobalConfiguration.get().messages.noPermission); -+ } -+ -+ @Override -+ public net.kyori.adventure.text.Component permissionMessage() { -+ return io.papermc.paper.configuration.GlobalConfiguration.get().messages.noPermission; -+ } -+ - @Override - public com.destroystokyo.paper.profile.PlayerProfile createProfile(@Nonnull UUID uuid) { - return createProfile(uuid, null); diff --git a/patches/server/0266-force-entity-dismount-during-teleportation.patch b/patches/server/0266-force-entity-dismount-during-teleportation.patch new file mode 100644 index 000000000000..e01ab14535bc --- /dev/null +++ b/patches/server/0266-force-entity-dismount-during-teleportation.patch @@ -0,0 +1,166 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Shane Freeder +Date: Thu, 15 Nov 2018 13:38:37 +0000 +Subject: [PATCH] force entity dismount during teleportation + +Entities must be dismounted before teleportation in order to avoid +multiple issues in the server with regards to teleportation, shamefully, +too many plugins rely on the events firing, which means that not firing +these events caues more issues than it solves; + +In order to counteract this, Entity dismount/exit vehicle events have +been modified to supress cancellation (and has a method to allow plugins +to check if this has been set), noting that cancellation will be silently +surpressed given that plugins are not expecting this event to not be cancellable. + +This is a far from ideal scenario, however: given the current state of this +event and other alternatives causing issues elsewhere, I believe that +this is going to be the best soultion all around. + +Improvements/suggestions welcome! + +diff --git a/src/main/java/net/minecraft/server/level/ServerPlayer.java b/src/main/java/net/minecraft/server/level/ServerPlayer.java +index cef054ba95ed7d2b0e2ee575edae3e94b77f58b6..8d958ac09bd9484d879eee6acb6aaea20f4f8339 100644 +--- a/src/main/java/net/minecraft/server/level/ServerPlayer.java ++++ b/src/main/java/net/minecraft/server/level/ServerPlayer.java +@@ -2826,9 +2826,15 @@ public class ServerPlayer extends net.minecraft.world.entity.player.Player { + + @Override + public void stopRiding() { ++ // Paper start - Force entity dismount during teleportation ++ this.stopRiding(false); ++ } ++ @Override ++ public void stopRiding(boolean suppressCancellation) { ++ // Paper end - Force entity dismount during teleportation + Entity entity = this.getVehicle(); + +- super.stopRiding(); ++ super.stopRiding(suppressCancellation); // Paper - Force entity dismount during teleportation + if (entity instanceof LivingEntity entityliving) { + Iterator iterator = entityliving.getActiveEffects().iterator(); + +diff --git a/src/main/java/net/minecraft/world/entity/Entity.java b/src/main/java/net/minecraft/world/entity/Entity.java +index 79a3d586ddf404c449b7c0aa1996e9b9897b2383..5d551a50e1043e369ebf3ddfe181be1e24cfd068 100644 +--- a/src/main/java/net/minecraft/world/entity/Entity.java ++++ b/src/main/java/net/minecraft/world/entity/Entity.java +@@ -2821,17 +2821,28 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess + } + + public void removeVehicle() { ++ // Paper start - Force entity dismount during teleportation ++ this.removeVehicle(false); ++ } ++ public void removeVehicle(boolean suppressCancellation) { ++ // Paper end - Force entity dismount during teleportation + if (this.vehicle != null) { + Entity entity = this.vehicle; + + this.vehicle = null; +- if (!entity.removePassenger(this)) this.vehicle = entity; // CraftBukkit ++ if (!entity.removePassenger(this, suppressCancellation)) this.vehicle = entity; // CraftBukkit // Paper - Force entity dismount during teleportation + } + + } + + public void stopRiding() { +- this.removeVehicle(); ++ // Paper start - Force entity dismount during teleportation ++ this.stopRiding(false); ++ } ++ ++ public void stopRiding(boolean suppressCancellation) { ++ this.removeVehicle(suppressCancellation); ++ // Paper end - Force entity dismount during teleportation + } + + protected void addPassenger(Entity passenger) { +@@ -2856,7 +2867,10 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess + } + } + +- protected boolean removePassenger(Entity entity) { // CraftBukkit ++ // Paper start - Force entity dismount during teleportation ++ protected boolean removePassenger(Entity entity) { return removePassenger(entity, false);} ++ protected boolean removePassenger(Entity entity, boolean suppressCancellation) { // CraftBukkit ++ // Paper end - Force entity dismount during teleportation + if (entity.getVehicle() == this) { + throw new IllegalStateException("Use x.stopRiding(y), not y.removePassenger(x)"); + } else { +@@ -2866,7 +2880,7 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess + if (this.getBukkitEntity() instanceof Vehicle && entity.getBukkitEntity() instanceof LivingEntity) { + VehicleExitEvent event = new VehicleExitEvent( + (Vehicle) this.getBukkitEntity(), +- (LivingEntity) entity.getBukkitEntity() ++ (LivingEntity) entity.getBukkitEntity(), !suppressCancellation // Paper - Force entity dismount during teleportation + ); + // Suppress during worldgen + if (this.valid) { +@@ -2879,7 +2893,7 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess + } + } + +- EntityDismountEvent event = new EntityDismountEvent(entity.getBukkitEntity(), this.getBukkitEntity()); ++ EntityDismountEvent event = new EntityDismountEvent(entity.getBukkitEntity(), this.getBukkitEntity(), !suppressCancellation); // Paper - Force entity dismount during teleportation + // Suppress during worldgen + if (this.valid) { + Bukkit.getPluginManager().callEvent(event); +diff --git a/src/main/java/net/minecraft/world/entity/LivingEntity.java b/src/main/java/net/minecraft/world/entity/LivingEntity.java +index 3bcf2ba5f065d946ded4020b9882bc4e19af0e08..63d1c5e8441eb5a32fc298ff2d2f3157cbd19557 100644 +--- a/src/main/java/net/minecraft/world/entity/LivingEntity.java ++++ b/src/main/java/net/minecraft/world/entity/LivingEntity.java +@@ -3756,9 +3756,15 @@ public abstract class LivingEntity extends Entity implements Attackable { + + @Override + public void stopRiding() { ++ // Paper start - Force entity dismount during teleportation ++ this.stopRiding(false); ++ } ++ @Override ++ public void stopRiding(boolean suppressCancellation) { ++ // Paper end - Force entity dismount during teleportation + Entity entity = this.getVehicle(); + +- super.stopRiding(); ++ super.stopRiding(suppressCancellation); // Paper - Force entity dismount during teleportation + if (entity != null && entity != this.getVehicle() && !this.level().isClientSide) { + this.dismountVehicle(entity); + } +diff --git a/src/main/java/net/minecraft/world/entity/monster/Shulker.java b/src/main/java/net/minecraft/world/entity/monster/Shulker.java +index 04e973430bf5706d5264423c24d73b903bc3f0aa..dc1870baf172982ebb34eccd4ee79497f48f8050 100644 +--- a/src/main/java/net/minecraft/world/entity/monster/Shulker.java ++++ b/src/main/java/net/minecraft/world/entity/monster/Shulker.java +@@ -289,7 +289,13 @@ public class Shulker extends AbstractGolem implements VariantHolder +Date: Sun, 7 Oct 2018 04:29:59 -0500 +Subject: [PATCH] Add more Zombie API + +== AT == +public net.minecraft.world.entity.monster.Zombie isSunSensitive()Z + +diff --git a/src/main/java/net/minecraft/world/entity/monster/Zombie.java b/src/main/java/net/minecraft/world/entity/monster/Zombie.java +index 73c4585870b7af409f84474f126a58497ed0495f..d3d1e170380e7674c9ac13b06186eb563a58cd64 100644 +--- a/src/main/java/net/minecraft/world/entity/monster/Zombie.java ++++ b/src/main/java/net/minecraft/world/entity/monster/Zombie.java +@@ -99,6 +99,7 @@ public class Zombie extends Monster { + private int inWaterTime; + public int conversionTime; + private int lastTick = MinecraftServer.currentTick; // CraftBukkit - add field ++ private boolean shouldBurnInDay = true; // Paper - Add more Zombie API + + public Zombie(EntityType type, Level world) { + super(type, world); +@@ -265,6 +266,12 @@ public class Zombie extends Monster { + super.aiStep(); + } + ++ // Paper start - Add more Zombie API ++ public void stopDrowning() { ++ this.conversionTime = -1; ++ this.getEntityData().set(Zombie.DATA_DROWNED_CONVERSION_ID, false); ++ } ++ // Paper end - Add more Zombie API + public void startUnderWaterConversion(int ticksUntilWaterConversion) { + this.lastTick = MinecraftServer.currentTick; // CraftBukkit + this.conversionTime = ticksUntilWaterConversion; +@@ -316,9 +323,15 @@ public class Zombie extends Monster { + } + + public boolean isSunSensitive() { +- return true; ++ return this.shouldBurnInDay; // Paper - Add more Zombie API + } + ++ // Paper start - Add more Zombie API ++ public void setShouldBurnInDay(boolean shouldBurnInDay) { ++ this.shouldBurnInDay = shouldBurnInDay; ++ } ++ // Paper end - Add more Zombie API ++ + @Override + public boolean hurtServer(ServerLevel world, DamageSource source, float amount) { + if (!super.hurtServer(world, source, amount)) { +@@ -447,6 +460,7 @@ public class Zombie extends Monster { + nbt.putBoolean("CanBreakDoors", this.canBreakDoors()); + nbt.putInt("InWaterTime", this.isInWater() ? this.inWaterTime : -1); + nbt.putInt("DrownedConversionTime", this.isUnderWaterConverting() ? this.conversionTime : -1); ++ nbt.putBoolean("Paper.ShouldBurnInDay", this.shouldBurnInDay); // Paper - Add more Zombie API + } + + @Override +@@ -458,6 +472,11 @@ public class Zombie extends Monster { + if (nbt.contains("DrownedConversionTime", 99) && nbt.getInt("DrownedConversionTime") > -1) { + this.startUnderWaterConversion(nbt.getInt("DrownedConversionTime")); + } ++ // Paper start - Add more Zombie API ++ if (nbt.contains("Paper.ShouldBurnInDay")) { ++ this.shouldBurnInDay = nbt.getBoolean("Paper.ShouldBurnInDay"); ++ } ++ // Paper end - Add more Zombie API + + } + +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftZombie.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftZombie.java +index 99dcaa827831a40ea46453f502d8b6ccb107f0ad..4412c913123f7521f449c98b60378e8d3b1671ce 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftZombie.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftZombie.java +@@ -87,6 +87,42 @@ public class CraftZombie extends CraftMonster implements Zombie { + @Override + public void setAgeLock(boolean b) { + } ++ // Paper start ++ @Override ++ public boolean isDrowning() { ++ return getHandle().isUnderWaterConverting(); ++ } ++ ++ @Override ++ public void startDrowning(int drownedConversionTime) { ++ getHandle().startUnderWaterConversion(drownedConversionTime); ++ } ++ ++ @Override ++ public void stopDrowning() { ++ getHandle().stopDrowning(); ++ } ++ ++ @Override ++ public boolean shouldBurnInDay() { ++ return getHandle().isSunSensitive(); ++ } ++ ++ @Override ++ public boolean isArmsRaised() { ++ return getHandle().isAggressive(); ++ } ++ ++ @Override ++ public void setArmsRaised(final boolean raised) { ++ getHandle().setAggressive(raised); ++ } ++ ++ @Override ++ public void setShouldBurnInDay(boolean shouldBurnInDay) { ++ getHandle().setShouldBurnInDay(shouldBurnInDay); ++ } ++ // Paper end + + @Override + public boolean getAgeLock() { diff --git a/patches/server/0267-force-entity-dismount-during-teleportation.patch b/patches/server/0267-force-entity-dismount-during-teleportation.patch deleted file mode 100644 index a91a738571ab..000000000000 --- a/patches/server/0267-force-entity-dismount-during-teleportation.patch +++ /dev/null @@ -1,166 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Shane Freeder -Date: Thu, 15 Nov 2018 13:38:37 +0000 -Subject: [PATCH] force entity dismount during teleportation - -Entities must be dismounted before teleportation in order to avoid -multiple issues in the server with regards to teleportation, shamefully, -too many plugins rely on the events firing, which means that not firing -these events caues more issues than it solves; - -In order to counteract this, Entity dismount/exit vehicle events have -been modified to supress cancellation (and has a method to allow plugins -to check if this has been set), noting that cancellation will be silently -surpressed given that plugins are not expecting this event to not be cancellable. - -This is a far from ideal scenario, however: given the current state of this -event and other alternatives causing issues elsewhere, I believe that -this is going to be the best soultion all around. - -Improvements/suggestions welcome! - -diff --git a/src/main/java/net/minecraft/server/level/ServerPlayer.java b/src/main/java/net/minecraft/server/level/ServerPlayer.java -index 7cca5c778f9d20cfa6cb543c9afcf74779aaa355..c2571855ca6a8ecd144b8fbb97d601d1808e8d61 100644 ---- a/src/main/java/net/minecraft/server/level/ServerPlayer.java -+++ b/src/main/java/net/minecraft/server/level/ServerPlayer.java -@@ -2522,9 +2522,15 @@ public class ServerPlayer extends net.minecraft.world.entity.player.Player { - - @Override - public void stopRiding() { -+ // Paper start - Force entity dismount during teleportation -+ this.stopRiding(false); -+ } -+ @Override -+ public void stopRiding(boolean suppressCancellation) { -+ // Paper end - Force entity dismount during teleportation - Entity entity = this.getVehicle(); - -- super.stopRiding(); -+ super.stopRiding(suppressCancellation); // Paper - Force entity dismount during teleportation - if (entity instanceof LivingEntity entityliving) { - Iterator iterator = entityliving.getActiveEffects().iterator(); - -diff --git a/src/main/java/net/minecraft/world/entity/Entity.java b/src/main/java/net/minecraft/world/entity/Entity.java -index 7fd1a75ba0068ee3ca6c29a550a9a1b33c5cacc5..f330ddca00ed11bf76ae825820423b94920013b9 100644 ---- a/src/main/java/net/minecraft/world/entity/Entity.java -+++ b/src/main/java/net/minecraft/world/entity/Entity.java -@@ -2704,17 +2704,28 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess - } - - public void removeVehicle() { -+ // Paper start - Force entity dismount during teleportation -+ this.removeVehicle(false); -+ } -+ public void removeVehicle(boolean suppressCancellation) { -+ // Paper end - Force entity dismount during teleportation - if (this.vehicle != null) { - Entity entity = this.vehicle; - - this.vehicle = null; -- if (!entity.removePassenger(this)) this.vehicle = entity; // CraftBukkit -+ if (!entity.removePassenger(this, suppressCancellation)) this.vehicle = entity; // CraftBukkit // Paper - Force entity dismount during teleportation - } - - } - - public void stopRiding() { -- this.removeVehicle(); -+ // Paper start - Force entity dismount during teleportation -+ this.stopRiding(false); -+ } -+ -+ public void stopRiding(boolean suppressCancellation) { -+ this.removeVehicle(suppressCancellation); -+ // Paper end - Force entity dismount during teleportation - } - - protected void addPassenger(Entity passenger) { -@@ -2739,7 +2750,10 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess - } - } - -- protected boolean removePassenger(Entity entity) { // CraftBukkit -+ // Paper start - Force entity dismount during teleportation -+ protected boolean removePassenger(Entity entity) { return removePassenger(entity, false);} -+ protected boolean removePassenger(Entity entity, boolean suppressCancellation) { // CraftBukkit -+ // Paper end - Force entity dismount during teleportation - if (entity.getVehicle() == this) { - throw new IllegalStateException("Use x.stopRiding(y), not y.removePassenger(x)"); - } else { -@@ -2749,7 +2763,7 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess - if (this.getBukkitEntity() instanceof Vehicle && entity.getBukkitEntity() instanceof LivingEntity) { - VehicleExitEvent event = new VehicleExitEvent( - (Vehicle) this.getBukkitEntity(), -- (LivingEntity) entity.getBukkitEntity() -+ (LivingEntity) entity.getBukkitEntity(), !suppressCancellation // Paper - Force entity dismount during teleportation - ); - // Suppress during worldgen - if (this.valid) { -@@ -2762,7 +2776,7 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess - } - } - -- EntityDismountEvent event = new EntityDismountEvent(entity.getBukkitEntity(), this.getBukkitEntity()); -+ EntityDismountEvent event = new EntityDismountEvent(entity.getBukkitEntity(), this.getBukkitEntity(), !suppressCancellation); // Paper - Force entity dismount during teleportation - // Suppress during worldgen - if (this.valid) { - Bukkit.getPluginManager().callEvent(event); -diff --git a/src/main/java/net/minecraft/world/entity/LivingEntity.java b/src/main/java/net/minecraft/world/entity/LivingEntity.java -index 689aaf4ceedc598fe71db726215cceae6cc97296..fad0445628499ac14cd9d8ab7f618c490885e798 100644 ---- a/src/main/java/net/minecraft/world/entity/LivingEntity.java -+++ b/src/main/java/net/minecraft/world/entity/LivingEntity.java -@@ -3619,9 +3619,15 @@ public abstract class LivingEntity extends Entity implements Attackable { - - @Override - public void stopRiding() { -+ // Paper start - Force entity dismount during teleportation -+ this.stopRiding(false); -+ } -+ @Override -+ public void stopRiding(boolean suppressCancellation) { -+ // Paper end - Force entity dismount during teleportation - Entity entity = this.getVehicle(); - -- super.stopRiding(); -+ super.stopRiding(suppressCancellation); // Paper - Force entity dismount during teleportation - if (entity != null && entity != this.getVehicle() && !this.level().isClientSide) { - this.dismountVehicle(entity); - } -diff --git a/src/main/java/net/minecraft/world/entity/monster/Shulker.java b/src/main/java/net/minecraft/world/entity/monster/Shulker.java -index 882236c8ebad90ed2adc873de4dda3b7f3f869d9..632b74e84d6ee58da8806e30b75e16fb864afa64 100644 ---- a/src/main/java/net/minecraft/world/entity/monster/Shulker.java -+++ b/src/main/java/net/minecraft/world/entity/monster/Shulker.java -@@ -288,7 +288,13 @@ public class Shulker extends AbstractGolem implements VariantHolder -Date: Sun, 7 Oct 2018 04:29:59 -0500 -Subject: [PATCH] Add more Zombie API - -== AT == -public net.minecraft.world.entity.monster.Zombie isSunSensitive()Z - -diff --git a/src/main/java/net/minecraft/world/entity/monster/Zombie.java b/src/main/java/net/minecraft/world/entity/monster/Zombie.java -index d97c3c139f10a45febc0cfb1057ff6e33266228e..d981f8679149669f6ca4ea950d744149974532b2 100644 ---- a/src/main/java/net/minecraft/world/entity/monster/Zombie.java -+++ b/src/main/java/net/minecraft/world/entity/monster/Zombie.java -@@ -97,6 +97,7 @@ public class Zombie extends Monster { - private int inWaterTime; - public int conversionTime; - private int lastTick = MinecraftServer.currentTick; // CraftBukkit - add field -+ private boolean shouldBurnInDay = true; // Paper - Add more Zombie API - - public Zombie(EntityType type, Level world) { - super(type, world); -@@ -267,6 +268,12 @@ public class Zombie extends Monster { - super.aiStep(); - } - -+ // Paper start - Add more Zombie API -+ public void stopDrowning() { -+ this.conversionTime = -1; -+ this.getEntityData().set(Zombie.DATA_DROWNED_CONVERSION_ID, false); -+ } -+ // Paper end - Add more Zombie API - public void startUnderWaterConversion(int ticksUntilWaterConversion) { - this.lastTick = MinecraftServer.currentTick; // CraftBukkit - this.conversionTime = ticksUntilWaterConversion; -@@ -296,9 +303,15 @@ public class Zombie extends Monster { - } - - public boolean isSunSensitive() { -- return true; -+ return this.shouldBurnInDay; // Paper - Add more Zombie API - } - -+ // Paper start - Add more Zombie API -+ public void setShouldBurnInDay(boolean shouldBurnInDay) { -+ this.shouldBurnInDay = shouldBurnInDay; -+ } -+ // Paper end - Add more Zombie API -+ - @Override - public boolean hurt(DamageSource source, float amount) { - if (!super.hurt(source, amount)) { -@@ -417,6 +430,7 @@ public class Zombie extends Monster { - nbt.putBoolean("CanBreakDoors", this.canBreakDoors()); - nbt.putInt("InWaterTime", this.isInWater() ? this.inWaterTime : -1); - nbt.putInt("DrownedConversionTime", this.isUnderWaterConverting() ? this.conversionTime : -1); -+ nbt.putBoolean("Paper.ShouldBurnInDay", this.shouldBurnInDay); // Paper - Add more Zombie API - } - - @Override -@@ -428,6 +442,11 @@ public class Zombie extends Monster { - if (nbt.contains("DrownedConversionTime", 99) && nbt.getInt("DrownedConversionTime") > -1) { - this.startUnderWaterConversion(nbt.getInt("DrownedConversionTime")); - } -+ // Paper start - Add more Zombie API -+ if (nbt.contains("Paper.ShouldBurnInDay")) { -+ this.shouldBurnInDay = nbt.getBoolean("Paper.ShouldBurnInDay"); -+ } -+ // Paper end - Add more Zombie API - - } - -diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftZombie.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftZombie.java -index 99dcaa827831a40ea46453f502d8b6ccb107f0ad..4412c913123f7521f449c98b60378e8d3b1671ce 100644 ---- a/src/main/java/org/bukkit/craftbukkit/entity/CraftZombie.java -+++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftZombie.java -@@ -87,6 +87,42 @@ public class CraftZombie extends CraftMonster implements Zombie { - @Override - public void setAgeLock(boolean b) { - } -+ // Paper start -+ @Override -+ public boolean isDrowning() { -+ return getHandle().isUnderWaterConverting(); -+ } -+ -+ @Override -+ public void startDrowning(int drownedConversionTime) { -+ getHandle().startUnderWaterConversion(drownedConversionTime); -+ } -+ -+ @Override -+ public void stopDrowning() { -+ getHandle().stopDrowning(); -+ } -+ -+ @Override -+ public boolean shouldBurnInDay() { -+ return getHandle().isSunSensitive(); -+ } -+ -+ @Override -+ public boolean isArmsRaised() { -+ return getHandle().isAggressive(); -+ } -+ -+ @Override -+ public void setArmsRaised(final boolean raised) { -+ getHandle().setAggressive(raised); -+ } -+ -+ @Override -+ public void setShouldBurnInDay(boolean shouldBurnInDay) { -+ getHandle().setShouldBurnInDay(shouldBurnInDay); -+ } -+ // Paper end - - @Override - public boolean getAgeLock() { diff --git a/patches/server/0268-Book-size-limits.patch b/patches/server/0268-Book-size-limits.patch new file mode 100644 index 000000000000..8e4d723b1263 --- /dev/null +++ b/patches/server/0268-Book-size-limits.patch @@ -0,0 +1,56 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Aikar +Date: Fri, 16 Nov 2018 23:08:50 -0500 +Subject: [PATCH] Book size limits + +Puts some limits on the size of books. + +diff --git a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java +index c3e281754a1cd9d5ce5f4aab36822f9f5d34be39..542899c382309986f3aaa16bf534b677aaac82f5 100644 +--- a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java ++++ b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java +@@ -1056,6 +1056,44 @@ public class ServerGamePacketListenerImpl extends ServerCommonPacketListenerImpl + + @Override + public void handleEditBook(ServerboundEditBookPacket packet) { ++ // Paper start - Book size limits ++ final io.papermc.paper.configuration.type.number.IntOr.Disabled pageMax = io.papermc.paper.configuration.GlobalConfiguration.get().itemValidation.bookSize.pageMax; ++ if (!this.cserver.isPrimaryThread() && pageMax.enabled()) { ++ final List pageList = packet.pages(); ++ long byteTotal = 0; ++ final int maxBookPageSize = pageMax.intValue(); ++ final double multiplier = Math.clamp(io.papermc.paper.configuration.GlobalConfiguration.get().itemValidation.bookSize.totalMultiplier, 0.3D, 1D); ++ long byteAllowed = maxBookPageSize; ++ for (final String page : pageList) { ++ final int byteLength = page.getBytes(java.nio.charset.StandardCharsets.UTF_8).length; ++ byteTotal += byteLength; ++ final int length = page.length(); ++ int multiByteCharacters = 0; ++ if (byteLength != length) { ++ // Count the number of multi byte characters ++ for (final char c : page.toCharArray()) { ++ if (c > 127) { ++ multiByteCharacters++; ++ } ++ } ++ } ++ ++ // Allow pages with fewer characters to consume less of the allowed byte quota ++ byteAllowed += maxBookPageSize * Math.clamp((double) length / 255D, 0.1D, 1) * multiplier; ++ ++ if (multiByteCharacters > 1) { ++ // Penalize multibyte characters ++ byteAllowed -= multiByteCharacters; ++ } ++ } ++ ++ if (byteTotal > byteAllowed) { ++ ServerGamePacketListenerImpl.LOGGER.warn("{} tried to send a book too large. Book size: {} - Allowed: {} - Pages: {}", this.player.getScoreboardName(), byteTotal, byteAllowed, pageList.size()); ++ this.disconnect(Component.literal("Book too large!")); ++ return; ++ } ++ } ++ // Paper end - Book size limits + // CraftBukkit start + if (this.lastBookTick + 20 > MinecraftServer.currentTick) { + this.disconnect(Component.literal("Book edited too quickly!")); diff --git a/patches/server/0269-Add-PlayerConnectionCloseEvent.patch b/patches/server/0269-Add-PlayerConnectionCloseEvent.patch new file mode 100644 index 000000000000..30540debeaa6 --- /dev/null +++ b/patches/server/0269-Add-PlayerConnectionCloseEvent.patch @@ -0,0 +1,83 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Spottedleaf +Date: Sun, 7 Oct 2018 12:05:28 -0700 +Subject: [PATCH] Add PlayerConnectionCloseEvent + +This event is invoked when a player has disconnected. It is guaranteed that, +if the server is in online-mode, that the provided uuid and username have been +validated. + +The event is invoked for players who have not yet logged into the world, whereas +PlayerQuitEvent is only invoked on players who have logged into the world. + +The event is invoked for players who have already logged into the world, +although whether or not the player exists in the world at the time of +firing is undefined. (That is, whether the plugin can retrieve a Player object +using the event parameters is undefined). However, it is guaranteed that this +event is invoked AFTER PlayerQuitEvent, if the player has already logged into +the world. + +This event is guaranteed to never fire unless AsyncPlayerPreLoginEvent has +been called beforehand, and this event may not be called in parallel with +AsyncPlayerPreLoginEvent for the same connection. + +Cancelling the AsyncPlayerPreLoginEvent guarantees the corresponding +PlayerConnectionCloseEvent is never called. + +The event may be invoked asynchronously or synchronously. As it stands, +it is never invoked asynchronously. However, plugins should check +Event#isAsynchronous to be future-proof. + +On purpose, the deprecated PlayerPreLoginEvent event is left out of the +API spec for this event. Plugins should not be using that event, and +how PlayerPreLoginEvent interacts with PlayerConnectionCloseEvent +is undefined. + +== AT == +public net.minecraft.server.network.ServerLoginPacketListenerImpl$State +public net.minecraft.server.network.ServerLoginPacketListenerImpl state + +diff --git a/src/main/java/net/minecraft/network/Connection.java b/src/main/java/net/minecraft/network/Connection.java +index c45b8b2c89ffec7bd6a6875963c61f11185d3ee1..38947f40864f3661df2eb4baa0aef5740b82f9d9 100644 +--- a/src/main/java/net/minecraft/network/Connection.java ++++ b/src/main/java/net/minecraft/network/Connection.java +@@ -692,6 +692,26 @@ public class Connection extends SimpleChannelInboundHandler> { + packetlistener1.onDisconnect(disconnectiondetails); + } + this.pendingActions.clear(); // Free up packet queue. ++ // Paper start - Add PlayerConnectionCloseEvent ++ final PacketListener packetListener = this.getPacketListener(); ++ if (packetListener instanceof net.minecraft.server.network.ServerCommonPacketListenerImpl commonPacketListener) { ++ /* Player was logged in, either game listener or configuration listener */ ++ final com.mojang.authlib.GameProfile profile = commonPacketListener.getOwner(); ++ new com.destroystokyo.paper.event.player.PlayerConnectionCloseEvent(profile.getId(), ++ profile.getName(), ((InetSocketAddress) this.address).getAddress(), false).callEvent(); ++ } else if (packetListener instanceof net.minecraft.server.network.ServerLoginPacketListenerImpl loginListener) { ++ /* Player is login stage */ ++ switch (loginListener.state) { ++ case VERIFYING: ++ case WAITING_FOR_DUPE_DISCONNECT: ++ case PROTOCOL_SWITCHING: ++ case ACCEPTED: ++ final com.mojang.authlib.GameProfile profile = loginListener.authenticatedProfile; /* Should be non-null at this stage */ ++ new com.destroystokyo.paper.event.player.PlayerConnectionCloseEvent(profile.getId(), profile.getName(), ++ ((InetSocketAddress) this.address).getAddress(), false).callEvent(); ++ } ++ } ++ // Paper end - Add PlayerConnectionCloseEvent + + } + } +diff --git a/src/main/java/net/minecraft/server/network/ServerLoginPacketListenerImpl.java b/src/main/java/net/minecraft/server/network/ServerLoginPacketListenerImpl.java +index bab8c53041afb9606db55923e5466eab25640226..fd9e6781a18d41ca3982788711749d11575566d0 100644 +--- a/src/main/java/net/minecraft/server/network/ServerLoginPacketListenerImpl.java ++++ b/src/main/java/net/minecraft/server/network/ServerLoginPacketListenerImpl.java +@@ -86,7 +86,7 @@ public class ServerLoginPacketListenerImpl implements ServerLoginPacketListener, + @Nullable + String requestedUsername; + @Nullable +- private GameProfile authenticatedProfile; ++ public GameProfile authenticatedProfile; // Paper - public + private final String serverId; + private final boolean transferred; + private ServerPlayer player; // CraftBukkit diff --git a/patches/server/0269-Book-size-limits.patch b/patches/server/0269-Book-size-limits.patch deleted file mode 100644 index 964cf927a74f..000000000000 --- a/patches/server/0269-Book-size-limits.patch +++ /dev/null @@ -1,72 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Aikar -Date: Fri, 16 Nov 2018 23:08:50 -0500 -Subject: [PATCH] Book size limits - -Puts some limits on the size of books. - -diff --git a/src/main/java/net/minecraft/network/protocol/game/ServerboundEditBookPacket.java b/src/main/java/net/minecraft/network/protocol/game/ServerboundEditBookPacket.java -index ed61767a64cdce37dc7c226ebd0d693a60de24a9..f634a830a2b58a419e84f969bd53eeae4f4513bb 100644 ---- a/src/main/java/net/minecraft/network/protocol/game/ServerboundEditBookPacket.java -+++ b/src/main/java/net/minecraft/network/protocol/game/ServerboundEditBookPacket.java -@@ -16,9 +16,9 @@ public record ServerboundEditBookPacket(int slot, List pages, Optional STREAM_CODEC = StreamCodec.composite( - ByteBufCodecs.VAR_INT, - ServerboundEditBookPacket::slot, -- ByteBufCodecs.stringUtf8(8192).apply(ByteBufCodecs.list(200)), -+ ByteBufCodecs.stringUtf8(net.minecraft.world.item.component.WritableBookContent.PAGE_EDIT_LENGTH).apply(ByteBufCodecs.list(net.minecraft.world.item.component.WritableBookContent.MAX_PAGES)), // Paper - limit books - ServerboundEditBookPacket::pages, -- ByteBufCodecs.stringUtf8(128).apply(ByteBufCodecs::optional), -+ ByteBufCodecs.stringUtf8(net.minecraft.world.item.component.WrittenBookContent.TITLE_MAX_LENGTH).apply(ByteBufCodecs::optional), // Paper - limit books - ServerboundEditBookPacket::title, - ServerboundEditBookPacket::new - ); -diff --git a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java -index 4942aa857ecc5762c9b0b1662eefaeae4daab80d..577f0e290f66afd2ded18714fb8b5f62a7a9aa71 100644 ---- a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java -+++ b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java -@@ -1043,6 +1043,44 @@ public class ServerGamePacketListenerImpl extends ServerCommonPacketListenerImpl - - @Override - public void handleEditBook(ServerboundEditBookPacket packet) { -+ // Paper start - Book size limits -+ final io.papermc.paper.configuration.type.number.IntOr.Disabled pageMax = io.papermc.paper.configuration.GlobalConfiguration.get().itemValidation.bookSize.pageMax; -+ if (!this.cserver.isPrimaryThread() && pageMax.enabled()) { -+ final List pageList = packet.pages(); -+ long byteTotal = 0; -+ final int maxBookPageSize = pageMax.intValue(); -+ final double multiplier = Math.clamp(io.papermc.paper.configuration.GlobalConfiguration.get().itemValidation.bookSize.totalMultiplier, 0.3D, 1D); -+ long byteAllowed = maxBookPageSize; -+ for (final String page : pageList) { -+ final int byteLength = page.getBytes(java.nio.charset.StandardCharsets.UTF_8).length; -+ byteTotal += byteLength; -+ final int length = page.length(); -+ int multiByteCharacters = 0; -+ if (byteLength != length) { -+ // Count the number of multi byte characters -+ for (final char c : page.toCharArray()) { -+ if (c > 127) { -+ multiByteCharacters++; -+ } -+ } -+ } -+ -+ // Allow pages with fewer characters to consume less of the allowed byte quota -+ byteAllowed += maxBookPageSize * Math.clamp((double) length / 255D, 0.1D, 1) * multiplier; -+ -+ if (multiByteCharacters > 1) { -+ // Penalize multibyte characters -+ byteAllowed -= multiByteCharacters; -+ } -+ } -+ -+ if (byteTotal > byteAllowed) { -+ ServerGamePacketListenerImpl.LOGGER.warn("{} tried to send a book too large. Book size: {} - Allowed: {} - Pages: {}", this.player.getScoreboardName(), byteTotal, byteAllowed, pageList.size()); -+ this.disconnect(Component.literal("Book too large!")); -+ return; -+ } -+ } -+ // Paper end - Book size limits - // CraftBukkit start - if (this.lastBookTick + 20 > MinecraftServer.currentTick) { - this.disconnect(Component.literal("Book edited too quickly!")); diff --git a/patches/server/0270-Add-PlayerConnectionCloseEvent.patch b/patches/server/0270-Add-PlayerConnectionCloseEvent.patch deleted file mode 100644 index fd1a1d334b90..000000000000 --- a/patches/server/0270-Add-PlayerConnectionCloseEvent.patch +++ /dev/null @@ -1,83 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Spottedleaf -Date: Sun, 7 Oct 2018 12:05:28 -0700 -Subject: [PATCH] Add PlayerConnectionCloseEvent - -This event is invoked when a player has disconnected. It is guaranteed that, -if the server is in online-mode, that the provided uuid and username have been -validated. - -The event is invoked for players who have not yet logged into the world, whereas -PlayerQuitEvent is only invoked on players who have logged into the world. - -The event is invoked for players who have already logged into the world, -although whether or not the player exists in the world at the time of -firing is undefined. (That is, whether the plugin can retrieve a Player object -using the event parameters is undefined). However, it is guaranteed that this -event is invoked AFTER PlayerQuitEvent, if the player has already logged into -the world. - -This event is guaranteed to never fire unless AsyncPlayerPreLoginEvent has -been called beforehand, and this event may not be called in parallel with -AsyncPlayerPreLoginEvent for the same connection. - -Cancelling the AsyncPlayerPreLoginEvent guarantees the corresponding -PlayerConnectionCloseEvent is never called. - -The event may be invoked asynchronously or synchronously. As it stands, -it is never invoked asynchronously. However, plugins should check -Event#isAsynchronous to be future-proof. - -On purpose, the deprecated PlayerPreLoginEvent event is left out of the -API spec for this event. Plugins should not be using that event, and -how PlayerPreLoginEvent interacts with PlayerConnectionCloseEvent -is undefined. - -== AT == -public net.minecraft.server.network.ServerLoginPacketListenerImpl$State -public net.minecraft.server.network.ServerLoginPacketListenerImpl state - -diff --git a/src/main/java/net/minecraft/network/Connection.java b/src/main/java/net/minecraft/network/Connection.java -index c45b8b2c89ffec7bd6a6875963c61f11185d3ee1..38947f40864f3661df2eb4baa0aef5740b82f9d9 100644 ---- a/src/main/java/net/minecraft/network/Connection.java -+++ b/src/main/java/net/minecraft/network/Connection.java -@@ -692,6 +692,26 @@ public class Connection extends SimpleChannelInboundHandler> { - packetlistener1.onDisconnect(disconnectiondetails); - } - this.pendingActions.clear(); // Free up packet queue. -+ // Paper start - Add PlayerConnectionCloseEvent -+ final PacketListener packetListener = this.getPacketListener(); -+ if (packetListener instanceof net.minecraft.server.network.ServerCommonPacketListenerImpl commonPacketListener) { -+ /* Player was logged in, either game listener or configuration listener */ -+ final com.mojang.authlib.GameProfile profile = commonPacketListener.getOwner(); -+ new com.destroystokyo.paper.event.player.PlayerConnectionCloseEvent(profile.getId(), -+ profile.getName(), ((InetSocketAddress) this.address).getAddress(), false).callEvent(); -+ } else if (packetListener instanceof net.minecraft.server.network.ServerLoginPacketListenerImpl loginListener) { -+ /* Player is login stage */ -+ switch (loginListener.state) { -+ case VERIFYING: -+ case WAITING_FOR_DUPE_DISCONNECT: -+ case PROTOCOL_SWITCHING: -+ case ACCEPTED: -+ final com.mojang.authlib.GameProfile profile = loginListener.authenticatedProfile; /* Should be non-null at this stage */ -+ new com.destroystokyo.paper.event.player.PlayerConnectionCloseEvent(profile.getId(), profile.getName(), -+ ((InetSocketAddress) this.address).getAddress(), false).callEvent(); -+ } -+ } -+ // Paper end - Add PlayerConnectionCloseEvent - - } - } -diff --git a/src/main/java/net/minecraft/server/network/ServerLoginPacketListenerImpl.java b/src/main/java/net/minecraft/server/network/ServerLoginPacketListenerImpl.java -index 636b8aef2348fa4cfe63a9b7d77a64b14dc7a42c..de25b9ea8aa4b7d6fd3fed12cdd16be9ddfcbfff 100644 ---- a/src/main/java/net/minecraft/server/network/ServerLoginPacketListenerImpl.java -+++ b/src/main/java/net/minecraft/server/network/ServerLoginPacketListenerImpl.java -@@ -86,7 +86,7 @@ public class ServerLoginPacketListenerImpl implements ServerLoginPacketListener, - @Nullable - String requestedUsername; - @Nullable -- private GameProfile authenticatedProfile; -+ public GameProfile authenticatedProfile; // Paper - public - private final String serverId; - private final boolean transferred; - private ServerPlayer player; // CraftBukkit diff --git a/patches/server/0270-Replace-OfflinePlayer-getLastPlayed.patch b/patches/server/0270-Replace-OfflinePlayer-getLastPlayed.patch new file mode 100644 index 000000000000..ccda77389871 --- /dev/null +++ b/patches/server/0270-Replace-OfflinePlayer-getLastPlayed.patch @@ -0,0 +1,164 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Zach Brown +Date: Wed, 2 Jan 2019 00:35:43 -0600 +Subject: [PATCH] Replace OfflinePlayer#getLastPlayed + +Currently OfflinePlayer#getLastPlayed could more accurately be described +as "OfflinePlayer#getLastTimeTheirDataWasSaved". + +The API doc says it should return the last time the server "witnessed" +the player, whilst also saying it should return the last time they +logged in. The current implementation does neither. + +Given this interesting contradiction in the API documentation and the +current defacto implementation, I've elected to deprecate (with no +intent to remove) and replace it with two new methods, clearly named and +documented as to their purpose. + +diff --git a/src/main/java/net/minecraft/server/level/ServerPlayer.java b/src/main/java/net/minecraft/server/level/ServerPlayer.java +index 8d958ac09bd9484d879eee6acb6aaea20f4f8339..3e0ddfe11d74ce47a023837b2ee472f0bc4c48c3 100644 +--- a/src/main/java/net/minecraft/server/level/ServerPlayer.java ++++ b/src/main/java/net/minecraft/server/level/ServerPlayer.java +@@ -296,6 +296,7 @@ public class ServerPlayer extends net.minecraft.world.entity.player.Player { + private int containerCounter; + public boolean wonGame; + private int containerUpdateDelay; // Paper - Configurable container update tick rate ++ public long loginTime; // Paper - Replace OfflinePlayer#getLastPlayed + // Paper start - cancellable death event + public boolean queueHealthUpdatePacket; + public net.minecraft.network.protocol.game.ClientboundSetHealthPacket queuedHealthUpdatePacket; +diff --git a/src/main/java/net/minecraft/server/players/PlayerList.java b/src/main/java/net/minecraft/server/players/PlayerList.java +index f2c7fba1f474618eb68a804d2dc7fc8d70c8ff6f..f56ef720a74611ffef314553f4f785ea5cf086ac 100644 +--- a/src/main/java/net/minecraft/server/players/PlayerList.java ++++ b/src/main/java/net/minecraft/server/players/PlayerList.java +@@ -182,6 +182,7 @@ public abstract class PlayerList { + + public void placeNewPlayer(Connection connection, ServerPlayer player, CommonListenerCookie clientData) { + player.isRealPlayer = true; // Paper ++ player.loginTime = System.currentTimeMillis(); // Paper - Replace OfflinePlayer#getLastPlayed + GameProfile gameprofile = player.getGameProfile(); + GameProfileCache usercache = this.server.getProfileCache(); + // Optional optional; // CraftBukkit - decompile error +diff --git a/src/main/java/org/bukkit/craftbukkit/CraftOfflinePlayer.java b/src/main/java/org/bukkit/craftbukkit/CraftOfflinePlayer.java +index f6b2ca92fd3510a76cbf56d0ea55aa6caaf12ba1..e0d342a0ddd140b342f7af138c71596c6d718788 100644 +--- a/src/main/java/org/bukkit/craftbukkit/CraftOfflinePlayer.java ++++ b/src/main/java/org/bukkit/craftbukkit/CraftOfflinePlayer.java +@@ -262,6 +262,61 @@ public class CraftOfflinePlayer implements OfflinePlayer, ConfigurationSerializa + return this.getData() != null; + } + ++ // Paper start ++ @Override ++ public long getLastLogin() { ++ Player player = getPlayer(); ++ if (player != null) return player.getLastLogin(); ++ ++ CompoundTag data = getPaperData(); ++ ++ if (data != null) { ++ if (data.contains("LastLogin")) { ++ return data.getLong("LastLogin"); ++ } else { ++ // if the player file cannot provide accurate data, this is probably the closest we can approximate ++ File file = getDataFile(); ++ return file.lastModified(); ++ } ++ } else { ++ return 0; ++ } ++ } ++ ++ @Override ++ public long getLastSeen() { ++ Player player = getPlayer(); ++ if (player != null) return player.getLastSeen(); ++ ++ CompoundTag data = getPaperData(); ++ ++ if (data != null) { ++ if (data.contains("LastSeen")) { ++ return data.getLong("LastSeen"); ++ } else { ++ // if the player file cannot provide accurate data, this is probably the closest we can approximate ++ File file = getDataFile(); ++ return file.lastModified(); ++ } ++ } else { ++ return 0; ++ } ++ } ++ ++ private CompoundTag getPaperData() { ++ CompoundTag result = getData(); ++ ++ if (result != null) { ++ if (!result.contains("Paper")) { ++ result.put("Paper", new CompoundTag()); ++ } ++ result = result.getCompound("Paper"); ++ } ++ ++ return result; ++ } ++ // Paper end ++ + @Override + public Location getLastDeathLocation() { + if (this.getData().contains("LastDeathLocation", 10)) { +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java +index 159bba49095aec77cce5f53d4388a5dff0afcc60..f351250b8e8b66bcf72230bbc76835fcb45817e1 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java +@@ -215,6 +215,7 @@ public class CraftPlayer extends CraftHumanEntity implements Player { + private BorderChangeListener clientWorldBorderListener = this.createWorldBorderListener(); + public org.bukkit.event.player.PlayerResourcePackStatusEvent.Status resourcePackStatus; // Paper - more resource pack API + private static final boolean DISABLE_CHANNEL_LIMIT = System.getProperty("paper.disableChannelLimit") != null; // Paper - add a flag to disable the channel limit ++ private long lastSaveTime; // Paper - getLastPlayed replacement API + + public CraftPlayer(CraftServer server, ServerPlayer entity) { + super(server, entity); +@@ -2076,6 +2077,18 @@ public class CraftPlayer extends CraftHumanEntity implements Player { + this.firstPlayed = firstPlayed; + } + ++ // Paper start - getLastPlayed replacement API ++ @Override ++ public long getLastLogin() { ++ return this.getHandle().loginTime; ++ } ++ ++ @Override ++ public long getLastSeen() { ++ return this.isOnline() ? System.currentTimeMillis() : this.lastSaveTime; ++ } ++ // Paper end - getLastPlayed replacement API ++ + public void readExtraData(CompoundTag nbttagcompound) { + this.hasPlayedBefore = true; + if (nbttagcompound.contains("bukkit")) { +@@ -2098,6 +2111,8 @@ public class CraftPlayer extends CraftHumanEntity implements Player { + } + + public void setExtraData(CompoundTag nbttagcompound) { ++ this.lastSaveTime = System.currentTimeMillis(); // Paper ++ + if (!nbttagcompound.contains("bukkit")) { + nbttagcompound.put("bukkit", new CompoundTag()); + } +@@ -2112,6 +2127,16 @@ public class CraftPlayer extends CraftHumanEntity implements Player { + data.putLong("firstPlayed", this.getFirstPlayed()); + data.putLong("lastPlayed", System.currentTimeMillis()); + data.putString("lastKnownName", handle.getScoreboardName()); ++ ++ // Paper start - persist for use in offline save data ++ if (!nbttagcompound.contains("Paper")) { ++ nbttagcompound.put("Paper", new CompoundTag()); ++ } ++ ++ CompoundTag paper = nbttagcompound.getCompound("Paper"); ++ paper.putLong("LastLogin", handle.loginTime); ++ paper.putLong("LastSeen", System.currentTimeMillis()); ++ // Paper end + } + + @Override diff --git a/patches/server/0271-Replace-OfflinePlayer-getLastPlayed.patch b/patches/server/0271-Replace-OfflinePlayer-getLastPlayed.patch deleted file mode 100644 index be356006816b..000000000000 --- a/patches/server/0271-Replace-OfflinePlayer-getLastPlayed.patch +++ /dev/null @@ -1,164 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Zach Brown -Date: Wed, 2 Jan 2019 00:35:43 -0600 -Subject: [PATCH] Replace OfflinePlayer#getLastPlayed - -Currently OfflinePlayer#getLastPlayed could more accurately be described -as "OfflinePlayer#getLastTimeTheirDataWasSaved". - -The API doc says it should return the last time the server "witnessed" -the player, whilst also saying it should return the last time they -logged in. The current implementation does neither. - -Given this interesting contradiction in the API documentation and the -current defacto implementation, I've elected to deprecate (with no -intent to remove) and replace it with two new methods, clearly named and -documented as to their purpose. - -diff --git a/src/main/java/net/minecraft/server/level/ServerPlayer.java b/src/main/java/net/minecraft/server/level/ServerPlayer.java -index c2571855ca6a8ecd144b8fbb97d601d1808e8d61..bd130d3a4d0cfe431be627c3f4d85bb394fe099b 100644 ---- a/src/main/java/net/minecraft/server/level/ServerPlayer.java -+++ b/src/main/java/net/minecraft/server/level/ServerPlayer.java -@@ -269,6 +269,7 @@ public class ServerPlayer extends net.minecraft.world.entity.player.Player { - private int containerCounter; - public boolean wonGame; - private int containerUpdateDelay; // Paper - Configurable container update tick rate -+ public long loginTime; // Paper - Replace OfflinePlayer#getLastPlayed - // Paper start - cancellable death event - public boolean queueHealthUpdatePacket; - public net.minecraft.network.protocol.game.ClientboundSetHealthPacket queuedHealthUpdatePacket; -diff --git a/src/main/java/net/minecraft/server/players/PlayerList.java b/src/main/java/net/minecraft/server/players/PlayerList.java -index 5fc76fd70f98fe874b38d8da08017fdadbd115e5..cdd1d8222ad1796abd0858b9ed0e2ddc9be83c93 100644 ---- a/src/main/java/net/minecraft/server/players/PlayerList.java -+++ b/src/main/java/net/minecraft/server/players/PlayerList.java -@@ -182,6 +182,7 @@ public abstract class PlayerList { - - public void placeNewPlayer(Connection connection, ServerPlayer player, CommonListenerCookie clientData) { - player.isRealPlayer = true; // Paper -+ player.loginTime = System.currentTimeMillis(); // Paper - Replace OfflinePlayer#getLastPlayed - GameProfile gameprofile = player.getGameProfile(); - GameProfileCache usercache = this.server.getProfileCache(); - // Optional optional; // CraftBukkit - decompile error -diff --git a/src/main/java/org/bukkit/craftbukkit/CraftOfflinePlayer.java b/src/main/java/org/bukkit/craftbukkit/CraftOfflinePlayer.java -index 461656e1cb095243bfe7a9ee2906e5b00574ae78..411b280ac3e27e72091db813c0c9b69b62df6097 100644 ---- a/src/main/java/org/bukkit/craftbukkit/CraftOfflinePlayer.java -+++ b/src/main/java/org/bukkit/craftbukkit/CraftOfflinePlayer.java -@@ -262,6 +262,61 @@ public class CraftOfflinePlayer implements OfflinePlayer, ConfigurationSerializa - return this.getData() != null; - } - -+ // Paper start -+ @Override -+ public long getLastLogin() { -+ Player player = getPlayer(); -+ if (player != null) return player.getLastLogin(); -+ -+ CompoundTag data = getPaperData(); -+ -+ if (data != null) { -+ if (data.contains("LastLogin")) { -+ return data.getLong("LastLogin"); -+ } else { -+ // if the player file cannot provide accurate data, this is probably the closest we can approximate -+ File file = getDataFile(); -+ return file.lastModified(); -+ } -+ } else { -+ return 0; -+ } -+ } -+ -+ @Override -+ public long getLastSeen() { -+ Player player = getPlayer(); -+ if (player != null) return player.getLastSeen(); -+ -+ CompoundTag data = getPaperData(); -+ -+ if (data != null) { -+ if (data.contains("LastSeen")) { -+ return data.getLong("LastSeen"); -+ } else { -+ // if the player file cannot provide accurate data, this is probably the closest we can approximate -+ File file = getDataFile(); -+ return file.lastModified(); -+ } -+ } else { -+ return 0; -+ } -+ } -+ -+ private CompoundTag getPaperData() { -+ CompoundTag result = getData(); -+ -+ if (result != null) { -+ if (!result.contains("Paper")) { -+ result.put("Paper", new CompoundTag()); -+ } -+ result = result.getCompound("Paper"); -+ } -+ -+ return result; -+ } -+ // Paper end -+ - @Override - public Location getLastDeathLocation() { - if (this.getData().contains("LastDeathLocation", 10)) { -diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java -index 004ac565d4124f6059d530034cf0c9a28f0be467..6a3bf3f34ff36e0b11bb3c250074f672b1e81b4f 100644 ---- a/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java -+++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java -@@ -210,6 +210,7 @@ public class CraftPlayer extends CraftHumanEntity implements Player { - private BorderChangeListener clientWorldBorderListener = this.createWorldBorderListener(); - public org.bukkit.event.player.PlayerResourcePackStatusEvent.Status resourcePackStatus; // Paper - more resource pack API - private static final boolean DISABLE_CHANNEL_LIMIT = System.getProperty("paper.disableChannelLimit") != null; // Paper - add a flag to disable the channel limit -+ private long lastSaveTime; // Paper - getLastPlayed replacement API - - public CraftPlayer(CraftServer server, ServerPlayer entity) { - super(server, entity); -@@ -2049,6 +2050,18 @@ public class CraftPlayer extends CraftHumanEntity implements Player { - this.firstPlayed = firstPlayed; - } - -+ // Paper start - getLastPlayed replacement API -+ @Override -+ public long getLastLogin() { -+ return this.getHandle().loginTime; -+ } -+ -+ @Override -+ public long getLastSeen() { -+ return this.isOnline() ? System.currentTimeMillis() : this.lastSaveTime; -+ } -+ // Paper end - getLastPlayed replacement API -+ - public void readExtraData(CompoundTag nbttagcompound) { - this.hasPlayedBefore = true; - if (nbttagcompound.contains("bukkit")) { -@@ -2071,6 +2084,8 @@ public class CraftPlayer extends CraftHumanEntity implements Player { - } - - public void setExtraData(CompoundTag nbttagcompound) { -+ this.lastSaveTime = System.currentTimeMillis(); // Paper -+ - if (!nbttagcompound.contains("bukkit")) { - nbttagcompound.put("bukkit", new CompoundTag()); - } -@@ -2085,6 +2100,16 @@ public class CraftPlayer extends CraftHumanEntity implements Player { - data.putLong("firstPlayed", this.getFirstPlayed()); - data.putLong("lastPlayed", System.currentTimeMillis()); - data.putString("lastKnownName", handle.getScoreboardName()); -+ -+ // Paper start - persist for use in offline save data -+ if (!nbttagcompound.contains("Paper")) { -+ nbttagcompound.put("Paper", new CompoundTag()); -+ } -+ -+ CompoundTag paper = nbttagcompound.getCompound("Paper"); -+ paper.putLong("LastLogin", handle.loginTime); -+ paper.putLong("LastSeen", System.currentTimeMillis()); -+ // Paper end - } - - @Override diff --git a/patches/server/0271-Workaround-for-vehicle-tracking-issue-on-disconnect.patch b/patches/server/0271-Workaround-for-vehicle-tracking-issue-on-disconnect.patch new file mode 100644 index 000000000000..8be3eeac381a --- /dev/null +++ b/patches/server/0271-Workaround-for-vehicle-tracking-issue-on-disconnect.patch @@ -0,0 +1,24 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: connorhartley +Date: Mon, 7 Jan 2019 14:43:48 -0600 +Subject: [PATCH] Workaround for vehicle tracking issue on disconnect + + +diff --git a/src/main/java/net/minecraft/server/level/ServerPlayer.java b/src/main/java/net/minecraft/server/level/ServerPlayer.java +index 3e0ddfe11d74ce47a023837b2ee472f0bc4c48c3..9cfd0b457f6c462921667b9439a7b3e32d019758 100644 +--- a/src/main/java/net/minecraft/server/level/ServerPlayer.java ++++ b/src/main/java/net/minecraft/server/level/ServerPlayer.java +@@ -2078,6 +2078,13 @@ public class ServerPlayer extends net.minecraft.world.entity.player.Player { + public void disconnect() { + this.disconnected = true; + this.ejectPassengers(); ++ ++ // Paper start - Workaround vehicle not tracking the passenger disconnection dismount ++ if (this.isPassenger() && this.getVehicle() instanceof ServerPlayer) { ++ this.stopRiding(); ++ } ++ // Paper end - Workaround vehicle not tracking the passenger disconnection dismount ++ + if (this.isSleeping()) { + this.stopSleepInBed(true, false); + } diff --git a/patches/server/0272-Dont-block-Player-remove-if-the-handle-is-a-custom-p.patch b/patches/server/0272-Dont-block-Player-remove-if-the-handle-is-a-custom-p.patch new file mode 100644 index 000000000000..12aeb87aa360 --- /dev/null +++ b/patches/server/0272-Dont-block-Player-remove-if-the-handle-is-a-custom-p.patch @@ -0,0 +1,26 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Zach Brown +Date: Mon, 4 Feb 2019 23:33:24 -0500 +Subject: [PATCH] Dont block Player#remove if the handle is a custom player + +Upstream throws UOE if you try to call remove on a Player. +We just add a check to ensure that the CraftPlayer's handle +is a ServerPlayer + +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java +index f351250b8e8b66bcf72230bbc76835fcb45817e1..faab55435a0c4fc6ff9d117f29a2401677b9f828 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java +@@ -229,8 +229,12 @@ public class CraftPlayer extends CraftHumanEntity implements Player { + + @Override + public void remove() { ++ if (this.getHandle().getClass().equals(ServerPlayer.class)) { // special case for NMS plugins inheriting + // Will lead to an inconsistent player state if we remove the player as any other entity. + throw new UnsupportedOperationException(String.format("Cannot remove player %s, use Player#kickPlayer(String) instead.", this.getName())); ++ } else { ++ super.remove(); ++ } + } + + @Override diff --git a/patches/server/0272-Workaround-for-vehicle-tracking-issue-on-disconnect.patch b/patches/server/0272-Workaround-for-vehicle-tracking-issue-on-disconnect.patch deleted file mode 100644 index a8477b17860f..000000000000 --- a/patches/server/0272-Workaround-for-vehicle-tracking-issue-on-disconnect.patch +++ /dev/null @@ -1,24 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: connorhartley -Date: Mon, 7 Jan 2019 14:43:48 -0600 -Subject: [PATCH] Workaround for vehicle tracking issue on disconnect - - -diff --git a/src/main/java/net/minecraft/server/level/ServerPlayer.java b/src/main/java/net/minecraft/server/level/ServerPlayer.java -index bd130d3a4d0cfe431be627c3f4d85bb394fe099b..ad2b8ea068469f2b0597c0e0436ad3c94dcb62ea 100644 ---- a/src/main/java/net/minecraft/server/level/ServerPlayer.java -+++ b/src/main/java/net/minecraft/server/level/ServerPlayer.java -@@ -1833,6 +1833,13 @@ public class ServerPlayer extends net.minecraft.world.entity.player.Player { - public void disconnect() { - this.disconnected = true; - this.ejectPassengers(); -+ -+ // Paper start - Workaround vehicle not tracking the passenger disconnection dismount -+ if (this.isPassenger() && this.getVehicle() instanceof ServerPlayer) { -+ this.stopRiding(); -+ } -+ // Paper end - Workaround vehicle not tracking the passenger disconnection dismount -+ - if (this.isSleeping()) { - this.stopSleepInBed(true, false); - } diff --git a/patches/server/0273-BlockDestroyEvent.patch b/patches/server/0273-BlockDestroyEvent.patch new file mode 100644 index 000000000000..6e560f06f5b0 --- /dev/null +++ b/patches/server/0273-BlockDestroyEvent.patch @@ -0,0 +1,53 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Aikar +Date: Wed, 6 Feb 2019 00:20:33 -0500 +Subject: [PATCH] BlockDestroyEvent + +Adds an event for when the server is going to destroy a current block, +potentially causing it to drop. This event can be cancelled to avoid +the block destruction, such as preventing signs from popping when +floating in the air. + +This can replace many uses of BlockPhysicsEvent + +diff --git a/src/main/java/net/minecraft/world/level/Level.java b/src/main/java/net/minecraft/world/level/Level.java +index 924b496aaaa19c7ef69498730725ae9287e46e28..0f4b9b5d3e34b5e08f9ca2f78c5e8bcec9f5a85e 100644 +--- a/src/main/java/net/minecraft/world/level/Level.java ++++ b/src/main/java/net/minecraft/world/level/Level.java +@@ -24,6 +24,7 @@ import net.minecraft.core.registries.Registries; + import net.minecraft.network.protocol.Packet; + import net.minecraft.resources.ResourceKey; + import net.minecraft.resources.ResourceLocation; ++import io.papermc.paper.util.MCUtil; + import net.minecraft.server.MinecraftServer; + import net.minecraft.server.level.FullChunkStatus; + import net.minecraft.server.level.ServerLevel; +@@ -574,9 +575,26 @@ public abstract class Level implements LevelAccessor, AutoCloseable { + return false; + } else { + FluidState fluid = this.getFluidState(pos); ++ // Paper start - BlockDestroyEvent; while the above setAir method is named same and looks very similar ++ // they are NOT used with same intent and the above should not fire this event. The above method is more of a BlockSetToAirEvent, ++ // it doesn't imply destruction of a block that plays a sound effect / drops an item. ++ boolean playEffect = true; ++ BlockState effectType = iblockdata; ++ int xp = iblockdata.getBlock().getExpDrop(iblockdata, (ServerLevel) this, pos, ItemStack.EMPTY, true); ++ if (com.destroystokyo.paper.event.block.BlockDestroyEvent.getHandlerList().getRegisteredListeners().length > 0) { ++ com.destroystokyo.paper.event.block.BlockDestroyEvent event = new com.destroystokyo.paper.event.block.BlockDestroyEvent(org.bukkit.craftbukkit.block.CraftBlock.at(this, pos), fluid.createLegacyBlock().createCraftBlockData(), effectType.createCraftBlockData(), xp, drop); ++ if (!event.callEvent()) { ++ return false; ++ } ++ effectType = ((CraftBlockData) event.getEffectBlock()).getState(); ++ playEffect = event.playEffect(); ++ drop = event.willDrop(); ++ xp = event.getExpToDrop(); ++ } ++ // Paper end - BlockDestroyEvent + +- if (!(iblockdata.getBlock() instanceof BaseFireBlock)) { +- this.levelEvent(2001, pos, Block.getId(iblockdata)); ++ if (playEffect && !(effectType.getBlock() instanceof BaseFireBlock)) { // Paper - BlockDestroyEvent ++ this.levelEvent(2001, pos, Block.getId(effectType)); // Paper - BlockDestroyEvent + } + + if (drop) { diff --git a/patches/server/0273-Dont-block-Player-remove-if-the-handle-is-a-custom-p.patch b/patches/server/0273-Dont-block-Player-remove-if-the-handle-is-a-custom-p.patch deleted file mode 100644 index 4869ad5e17f1..000000000000 --- a/patches/server/0273-Dont-block-Player-remove-if-the-handle-is-a-custom-p.patch +++ /dev/null @@ -1,26 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Zach Brown -Date: Mon, 4 Feb 2019 23:33:24 -0500 -Subject: [PATCH] Dont block Player#remove if the handle is a custom player - -Upstream throws UOE if you try to call remove on a Player. -We just add a check to ensure that the CraftPlayer's handle -is a ServerPlayer - -diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java -index 6a3bf3f34ff36e0b11bb3c250074f672b1e81b4f..d0af4b838bd43ef2388e918ac14e7acec71b63ef 100644 ---- a/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java -+++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java -@@ -224,8 +224,12 @@ public class CraftPlayer extends CraftHumanEntity implements Player { - - @Override - public void remove() { -+ if (this.getHandle().getClass().equals(ServerPlayer.class)) { // special case for NMS plugins inheriting - // Will lead to an inconsistent player state if we remove the player as any other entity. - throw new UnsupportedOperationException(String.format("Cannot remove player %s, use Player#kickPlayer(String) instead.", this.getName())); -+ } else { -+ super.remove(); -+ } - } - - @Override diff --git a/patches/server/0274-Async-command-map-building.patch b/patches/server/0274-Async-command-map-building.patch new file mode 100644 index 000000000000..4de7f1039f52 --- /dev/null +++ b/patches/server/0274-Async-command-map-building.patch @@ -0,0 +1,66 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Callahan +Date: Wed, 8 Apr 2020 02:42:14 -0500 +Subject: [PATCH] Async command map building + +This adds a custom pool inorder to make sure that they are closed +without much though, as it doesn't matter if the client is not sent +commands if the server is restarting. Using the default async pool caused issues to arise +due to the shutdown logic generally being much later. + +diff --git a/src/main/java/net/minecraft/commands/Commands.java b/src/main/java/net/minecraft/commands/Commands.java +index 2008fd542eaf1c2fac776ae1751c227a3b6dde4b..e812cc865baaa1ee03872f7969ee98600b82483b 100644 +--- a/src/main/java/net/minecraft/commands/Commands.java ++++ b/src/main/java/net/minecraft/commands/Commands.java +@@ -455,6 +455,24 @@ public class Commands { + if ( org.spigotmc.SpigotConfig.tabComplete < 0 ) return; // Spigot + // CraftBukkit start + // Register Vanilla commands into builtRoot as before ++ // Paper start - Perf: Async command map building ++ COMMAND_SENDING_POOL.execute(() -> { ++ this.sendAsync(player); ++ }); ++ } ++ ++ public static final java.util.concurrent.ThreadPoolExecutor COMMAND_SENDING_POOL = new java.util.concurrent.ThreadPoolExecutor( ++ 0, 2, 60L, java.util.concurrent.TimeUnit.SECONDS, ++ new java.util.concurrent.LinkedBlockingQueue<>(), ++ new com.google.common.util.concurrent.ThreadFactoryBuilder() ++ .setNameFormat("Paper Async Command Builder Thread Pool - %1$d") ++ .setUncaughtExceptionHandler(new net.minecraft.DefaultUncaughtExceptionHandlerWithName(net.minecraft.server.MinecraftServer.LOGGER)) ++ .build(), ++ new java.util.concurrent.ThreadPoolExecutor.DiscardPolicy() ++ ); ++ ++ private void sendAsync(ServerPlayer player) { ++ // Paper end - Perf: Async command map building + Map, CommandNode> map = Maps.newIdentityHashMap(); // Use identity to prevent aliasing issues + RootCommandNode vanillaRoot = new RootCommandNode(); + +@@ -472,7 +490,14 @@ public class Commands { + for (CommandNode node : rootcommandnode.getChildren()) { + bukkit.add(node.getName()); + } ++ // Paper start - Perf: Async command map building ++ net.minecraft.server.MinecraftServer.getServer().execute(() -> { ++ runSync(player, bukkit, rootcommandnode); ++ }); ++ } + ++ private void runSync(ServerPlayer player, Collection bukkit, RootCommandNode rootcommandnode) { ++ // Paper end - Perf: Async command map building + PlayerCommandSendEvent event = new PlayerCommandSendEvent(player.getBukkitEntity(), new LinkedHashSet<>(bukkit)); + event.getPlayer().getServer().getPluginManager().callEvent(event); + +diff --git a/src/main/java/net/minecraft/server/MinecraftServer.java b/src/main/java/net/minecraft/server/MinecraftServer.java +index a4c897000a5e70e64b5fe4306581b04d8d38bce2..8f427475f7418bbfb8121dbd3e25e7827775ea41 100644 +--- a/src/main/java/net/minecraft/server/MinecraftServer.java ++++ b/src/main/java/net/minecraft/server/MinecraftServer.java +@@ -945,6 +945,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop -Date: Wed, 6 Feb 2019 00:20:33 -0500 -Subject: [PATCH] BlockDestroyEvent - -Adds an event for when the server is going to destroy a current block, -potentially causing it to drop. This event can be cancelled to avoid -the block destruction, such as preventing signs from popping when -floating in the air. - -This can replace many uses of BlockPhysicsEvent - -diff --git a/src/main/java/net/minecraft/world/level/Level.java b/src/main/java/net/minecraft/world/level/Level.java -index 5929b450a26e7c3cf63de3dc1d0e67cb781b24c7..4c4449f7dee8695a362f83b9356e5754244fff18 100644 ---- a/src/main/java/net/minecraft/world/level/Level.java -+++ b/src/main/java/net/minecraft/world/level/Level.java -@@ -25,6 +25,7 @@ import net.minecraft.core.registries.Registries; - import net.minecraft.network.protocol.Packet; - import net.minecraft.resources.ResourceKey; - import net.minecraft.resources.ResourceLocation; -+import io.papermc.paper.util.MCUtil; - import net.minecraft.server.MinecraftServer; - import net.minecraft.server.level.FullChunkStatus; - import net.minecraft.server.level.ServerLevel; -@@ -576,9 +577,26 @@ public abstract class Level implements LevelAccessor, AutoCloseable { - return false; - } else { - FluidState fluid = this.getFluidState(pos); -+ // Paper start - BlockDestroyEvent; while the above setAir method is named same and looks very similar -+ // they are NOT used with same intent and the above should not fire this event. The above method is more of a BlockSetToAirEvent, -+ // it doesn't imply destruction of a block that plays a sound effect / drops an item. -+ boolean playEffect = true; -+ BlockState effectType = iblockdata; -+ int xp = iblockdata.getBlock().getExpDrop(iblockdata, (ServerLevel) this, pos, ItemStack.EMPTY, true); -+ if (com.destroystokyo.paper.event.block.BlockDestroyEvent.getHandlerList().getRegisteredListeners().length > 0) { -+ com.destroystokyo.paper.event.block.BlockDestroyEvent event = new com.destroystokyo.paper.event.block.BlockDestroyEvent(org.bukkit.craftbukkit.block.CraftBlock.at(this, pos), fluid.createLegacyBlock().createCraftBlockData(), effectType.createCraftBlockData(), xp, drop); -+ if (!event.callEvent()) { -+ return false; -+ } -+ effectType = ((CraftBlockData) event.getEffectBlock()).getState(); -+ playEffect = event.playEffect(); -+ drop = event.willDrop(); -+ xp = event.getExpToDrop(); -+ } -+ // Paper end - BlockDestroyEvent - -- if (!(iblockdata.getBlock() instanceof BaseFireBlock)) { -- this.levelEvent(2001, pos, Block.getId(iblockdata)); -+ if (playEffect && !(effectType.getBlock() instanceof BaseFireBlock)) { // Paper - BlockDestroyEvent -+ this.levelEvent(2001, pos, Block.getId(effectType)); // Paper - BlockDestroyEvent - } - - if (drop) { diff --git a/patches/server/0275-Async-command-map-building.patch b/patches/server/0275-Async-command-map-building.patch deleted file mode 100644 index b202de9d2432..000000000000 --- a/patches/server/0275-Async-command-map-building.patch +++ /dev/null @@ -1,66 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Callahan -Date: Wed, 8 Apr 2020 02:42:14 -0500 -Subject: [PATCH] Async command map building - -This adds a custom pool inorder to make sure that they are closed -without much though, as it doesn't matter if the client is not sent -commands if the server is restarting. Using the default async pool caused issues to arise -due to the shutdown logic generally being much later. - -diff --git a/src/main/java/net/minecraft/commands/Commands.java b/src/main/java/net/minecraft/commands/Commands.java -index 72756ef14b8ec8afd80313b9f6aaf76722cb18cf..a05aea8561ac102476ee1b3068942b095950a86a 100644 ---- a/src/main/java/net/minecraft/commands/Commands.java -+++ b/src/main/java/net/minecraft/commands/Commands.java -@@ -450,6 +450,24 @@ public class Commands { - if ( org.spigotmc.SpigotConfig.tabComplete < 0 ) return; // Spigot - // CraftBukkit start - // Register Vanilla commands into builtRoot as before -+ // Paper start - Perf: Async command map building -+ COMMAND_SENDING_POOL.execute(() -> { -+ this.sendAsync(player); -+ }); -+ } -+ -+ public static final java.util.concurrent.ThreadPoolExecutor COMMAND_SENDING_POOL = new java.util.concurrent.ThreadPoolExecutor( -+ 0, 2, 60L, java.util.concurrent.TimeUnit.SECONDS, -+ new java.util.concurrent.LinkedBlockingQueue<>(), -+ new com.google.common.util.concurrent.ThreadFactoryBuilder() -+ .setNameFormat("Paper Async Command Builder Thread Pool - %1$d") -+ .setUncaughtExceptionHandler(new net.minecraft.DefaultUncaughtExceptionHandlerWithName(net.minecraft.server.MinecraftServer.LOGGER)) -+ .build(), -+ new java.util.concurrent.ThreadPoolExecutor.DiscardPolicy() -+ ); -+ -+ private void sendAsync(ServerPlayer player) { -+ // Paper end - Perf: Async command map building - Map, CommandNode> map = Maps.newIdentityHashMap(); // Use identity to prevent aliasing issues - RootCommandNode vanillaRoot = new RootCommandNode(); - -@@ -467,7 +485,14 @@ public class Commands { - for (CommandNode node : rootcommandnode.getChildren()) { - bukkit.add(node.getName()); - } -+ // Paper start - Perf: Async command map building -+ net.minecraft.server.MinecraftServer.getServer().execute(() -> { -+ runSync(player, bukkit, rootcommandnode); -+ }); -+ } - -+ private void runSync(ServerPlayer player, Collection bukkit, RootCommandNode rootcommandnode) { -+ // Paper end - Perf: Async command map building - PlayerCommandSendEvent event = new PlayerCommandSendEvent(player.getBukkitEntity(), new LinkedHashSet<>(bukkit)); - event.getPlayer().getServer().getPluginManager().callEvent(event); - -diff --git a/src/main/java/net/minecraft/server/MinecraftServer.java b/src/main/java/net/minecraft/server/MinecraftServer.java -index 699191356a9d873fa6bbe04ea3e5db91eb2db41d..8ecfd7a3daa99dabe796d28d27790fb8b45c628b 100644 ---- a/src/main/java/net/minecraft/server/MinecraftServer.java -+++ b/src/main/java/net/minecraft/server/MinecraftServer.java -@@ -930,6 +930,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop +Date: Sun, 19 Apr 2020 18:15:29 -0400 +Subject: [PATCH] Brigadier Mojang API + +Adds AsyncPlayerSendCommandsEvent + - Allows modifying on a per command basis what command data they see. + +Adds CommandRegisteredEvent + - Allows manipulating the CommandNode to add more children/metadata for the client + +diff --git a/src/main/java/com/mojang/brigadier/exceptions/CommandSyntaxException.java b/src/main/java/com/mojang/brigadier/exceptions/CommandSyntaxException.java +index 3370731ee064d2693b972a0765c13dd4fd69f66a..09d486a05179b9d878e1c33725b4e614c3544da9 100644 +--- a/src/main/java/com/mojang/brigadier/exceptions/CommandSyntaxException.java ++++ b/src/main/java/com/mojang/brigadier/exceptions/CommandSyntaxException.java +@@ -5,7 +5,7 @@ package com.mojang.brigadier.exceptions; + + import com.mojang.brigadier.Message; + +-public class CommandSyntaxException extends Exception { ++public class CommandSyntaxException extends Exception implements net.kyori.adventure.util.ComponentMessageThrowable { // Paper - Brigadier API + public static final int CONTEXT_AMOUNT = 10; + public static boolean ENABLE_COMMAND_STACK_TRACES = true; + public static BuiltInExceptionProvider BUILT_IN_EXCEPTIONS = new BuiltInExceptions(); +@@ -73,4 +73,11 @@ public class CommandSyntaxException extends Exception { + public int getCursor() { + return cursor; + } ++ ++ // Paper start - Brigadier API ++ @Override ++ public @org.jetbrains.annotations.Nullable net.kyori.adventure.text.Component componentMessage() { ++ return io.papermc.paper.brigadier.PaperBrigadier.componentFromMessage(this.message); ++ } ++ // Paper end - Brigadier API + } +diff --git a/src/main/java/com/mojang/brigadier/tree/CommandNode.java b/src/main/java/com/mojang/brigadier/tree/CommandNode.java +index da6250df1c5f3385b683cffde47754bca4606f5e..14ccd0c8f721e9be7dca8a5dcb8ef95b5cd82731 100644 +--- a/src/main/java/com/mojang/brigadier/tree/CommandNode.java ++++ b/src/main/java/com/mojang/brigadier/tree/CommandNode.java +@@ -34,6 +34,7 @@ public abstract class CommandNode implements Comparable> { + private final RedirectModifier modifier; + private final boolean forks; + private Command command; ++ public CommandNode clientNode; // Paper - Brigadier API + // CraftBukkit start + public void removeCommand(String name) { + this.children.remove(name); +diff --git a/src/main/java/net/minecraft/commands/CommandSourceStack.java b/src/main/java/net/minecraft/commands/CommandSourceStack.java +index 4d5f1dd1c3bd742b1bc5e3914101a699041caa7e..5316f148f3f9128690f019d544e462b042d8d797 100644 +--- a/src/main/java/net/minecraft/commands/CommandSourceStack.java ++++ b/src/main/java/net/minecraft/commands/CommandSourceStack.java +@@ -47,7 +47,7 @@ import net.minecraft.world.phys.Vec2; + import net.minecraft.world.phys.Vec3; + import com.mojang.brigadier.tree.CommandNode; // CraftBukkit + +-public class CommandSourceStack implements ExecutionCommandSource, SharedSuggestionProvider { ++public class CommandSourceStack implements ExecutionCommandSource, SharedSuggestionProvider, com.destroystokyo.paper.brigadier.BukkitBrigadierCommandSource { // Paper - Brigadier API + + public static final SimpleCommandExceptionType ERROR_NOT_PLAYER = new SimpleCommandExceptionType(Component.translatable("permissions.requires.player")); + public static final SimpleCommandExceptionType ERROR_NOT_ENTITY = new SimpleCommandExceptionType(Component.translatable("permissions.requires.entity")); +@@ -172,6 +172,26 @@ public class CommandSourceStack implements ExecutionCommandSource(player.getBukkitEntity(), (RootCommandNode) rootcommandnode, false).callEvent(); // Paper - Brigadier API + net.minecraft.server.MinecraftServer.getServer().execute(() -> { + runSync(player, bukkit, rootcommandnode); + }); +@@ -498,6 +499,7 @@ public class Commands { + + private void runSync(ServerPlayer player, Collection bukkit, RootCommandNode rootcommandnode) { + // Paper end - Perf: Async command map building ++ new com.destroystokyo.paper.event.brigadier.AsyncPlayerSendCommandsEvent(player.getBukkitEntity(), (RootCommandNode) rootcommandnode, true).callEvent(); // Paper - Brigadier API + PlayerCommandSendEvent event = new PlayerCommandSendEvent(player.getBukkitEntity(), new LinkedHashSet<>(bukkit)); + event.getPlayer().getServer().getPluginManager().callEvent(event); + +@@ -516,6 +518,11 @@ public class Commands { + + while (iterator.hasNext()) { + CommandNode commandnode2 = (CommandNode) iterator.next(); ++ // Paper start - Brigadier API ++ if (commandnode2.clientNode != null) { ++ commandnode2 = commandnode2.clientNode; ++ } ++ // Paper end - Brigadier API + if ( !org.spigotmc.SpigotConfig.sendNamespaced && commandnode2.getName().contains( ":" ) ) continue; // Spigot + + if (commandnode2.canUse(source)) { +diff --git a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java +index 542899c382309986f3aaa16bf534b677aaac82f5..b234957d0563f1ccf533f927e60d8b6b77c33a25 100644 +--- a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java ++++ b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java +@@ -780,19 +780,34 @@ public class ServerGamePacketListenerImpl extends ServerCommonPacketListenerImpl + builder.suggest(completion.suggestion(), PaperAdventure.asVanilla(completion.tooltip())); + } + } +- this.connection.send(new ClientboundCommandSuggestionsPacket(packet.getId(), builder.buildFuture().join())); ++ // Paper start - Brigadier API ++ com.mojang.brigadier.suggestion.Suggestions suggestions = builder.buildFuture().join(); ++ com.destroystokyo.paper.event.brigadier.AsyncPlayerSendSuggestionsEvent suggestEvent = new com.destroystokyo.paper.event.brigadier.AsyncPlayerSendSuggestionsEvent(this.getCraftPlayer(), suggestions, packet.getCommand()); ++ suggestEvent.setCancelled(suggestions.isEmpty()); ++ if (suggestEvent.callEvent()) { ++ this.connection.send(new ClientboundCommandSuggestionsPacket(packet.getId(), limitTo(suggestEvent.getSuggestions(), ServerGamePacketListenerImpl.MAX_COMMAND_SUGGESTIONS))); ++ } ++ // Paper end - Brigadier API + } + } ++ // Paper start - brig API ++ private static Suggestions limitTo(final Suggestions suggestions, final int size) { ++ return suggestions.getList().size() <= size ? suggestions : new Suggestions(suggestions.getRange(), suggestions.getList().subList(0, size)); ++ } ++ // Paper end - brig API + + private void sendServerSuggestions(final ServerboundCommandSuggestionPacket packet, final StringReader stringreader) { + // Paper end - AsyncTabCompleteEvent + ParseResults parseresults = this.server.getCommands().getDispatcher().parse(stringreader, this.player.createCommandSourceStack()); + + this.server.getCommands().getDispatcher().getCompletionSuggestions(parseresults).thenAccept((suggestions) -> { +- if (suggestions.isEmpty()) return; // CraftBukkit - don't send through empty suggestions - prevents [] from showing for plugins with nothing more to offer +- Suggestions suggestions1 = suggestions.getList().size() <= 1000 ? suggestions : new Suggestions(suggestions.getRange(), suggestions.getList().subList(0, 1000)); +- +- this.send(new ClientboundCommandSuggestionsPacket(packet.getId(), suggestions1)); ++ // Paper start - Brigadier API ++ com.destroystokyo.paper.event.brigadier.AsyncPlayerSendSuggestionsEvent suggestEvent = new com.destroystokyo.paper.event.brigadier.AsyncPlayerSendSuggestionsEvent(this.getCraftPlayer(), suggestions, packet.getCommand()); ++ suggestEvent.setCancelled(suggestions.isEmpty()); ++ if (suggestEvent.callEvent()) { ++ this.send(new ClientboundCommandSuggestionsPacket(packet.getId(), limitTo(suggestEvent.getSuggestions(), ServerGamePacketListenerImpl.MAX_COMMAND_SUGGESTIONS))); ++ } ++ // Paper end - Brigadier API + }); + } + +diff --git a/src/main/java/org/bukkit/craftbukkit/command/BukkitCommandWrapper.java b/src/main/java/org/bukkit/craftbukkit/command/BukkitCommandWrapper.java +index 83d81b9371902b0302d13e53b31c15fac4e67966..d113e54a30db16e2ad955170df6030d15de530d6 100644 +--- a/src/main/java/org/bukkit/craftbukkit/command/BukkitCommandWrapper.java ++++ b/src/main/java/org/bukkit/craftbukkit/command/BukkitCommandWrapper.java +@@ -20,7 +20,7 @@ import org.bukkit.command.CommandException; + import org.bukkit.command.CommandSender; + import org.bukkit.craftbukkit.CraftServer; + +-public class BukkitCommandWrapper implements com.mojang.brigadier.Command, Predicate, SuggestionProvider { ++public class BukkitCommandWrapper implements com.mojang.brigadier.Command, Predicate, SuggestionProvider, com.destroystokyo.paper.brigadier.BukkitBrigadierCommand { // Paper + + private final CraftServer server; + private final Command command; +@@ -31,10 +31,24 @@ public class BukkitCommandWrapper implements com.mojang.brigadier.Command register(CommandDispatcher dispatcher, String label) { +- return dispatcher.register( +- LiteralArgumentBuilder.literal(label).requires(this).executes(this) +- .then(RequiredArgumentBuilder.argument("args", StringArgumentType.greedyString()).suggests(this).executes(this)) +- ); ++ // Paper start - Expose Brigadier to Paper-MojangAPI ++ com.mojang.brigadier.tree.RootCommandNode root = dispatcher.getRoot(); ++ LiteralCommandNode literal = LiteralArgumentBuilder.literal(label).requires(this).executes(this).build(); ++ LiteralCommandNode defaultNode = literal; ++ com.mojang.brigadier.tree.ArgumentCommandNode defaultArgs = RequiredArgumentBuilder.argument("args", StringArgumentType.greedyString()).suggests(this).executes(this).build(); ++ literal.addChild(defaultArgs); ++ com.destroystokyo.paper.event.brigadier.CommandRegisteredEvent event = new com.destroystokyo.paper.event.brigadier.CommandRegisteredEvent<>(label, this, this.command, root, literal, defaultArgs); ++ if (!event.callEvent()) { ++ return null; ++ } ++ literal = event.getLiteral(); ++ if (event.isRawCommand()) { ++ defaultNode.clientNode = literal; ++ literal = defaultNode; ++ } ++ root.addChild(literal); ++ return literal; ++ // Paper end + } + + @Override diff --git a/patches/server/0276-Brigadier-Mojang-API.patch b/patches/server/0276-Brigadier-Mojang-API.patch deleted file mode 100644 index 6556c6eccc29..000000000000 --- a/patches/server/0276-Brigadier-Mojang-API.patch +++ /dev/null @@ -1,206 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Aikar -Date: Sun, 19 Apr 2020 18:15:29 -0400 -Subject: [PATCH] Brigadier Mojang API - -Adds AsyncPlayerSendCommandsEvent - - Allows modifying on a per command basis what command data they see. - -Adds CommandRegisteredEvent - - Allows manipulating the CommandNode to add more children/metadata for the client - -diff --git a/src/main/java/com/mojang/brigadier/exceptions/CommandSyntaxException.java b/src/main/java/com/mojang/brigadier/exceptions/CommandSyntaxException.java -index 3370731ee064d2693b972a0765c13dd4fd69f66a..09d486a05179b9d878e1c33725b4e614c3544da9 100644 ---- a/src/main/java/com/mojang/brigadier/exceptions/CommandSyntaxException.java -+++ b/src/main/java/com/mojang/brigadier/exceptions/CommandSyntaxException.java -@@ -5,7 +5,7 @@ package com.mojang.brigadier.exceptions; - - import com.mojang.brigadier.Message; - --public class CommandSyntaxException extends Exception { -+public class CommandSyntaxException extends Exception implements net.kyori.adventure.util.ComponentMessageThrowable { // Paper - Brigadier API - public static final int CONTEXT_AMOUNT = 10; - public static boolean ENABLE_COMMAND_STACK_TRACES = true; - public static BuiltInExceptionProvider BUILT_IN_EXCEPTIONS = new BuiltInExceptions(); -@@ -73,4 +73,11 @@ public class CommandSyntaxException extends Exception { - public int getCursor() { - return cursor; - } -+ -+ // Paper start - Brigadier API -+ @Override -+ public @org.jetbrains.annotations.Nullable net.kyori.adventure.text.Component componentMessage() { -+ return io.papermc.paper.brigadier.PaperBrigadier.componentFromMessage(this.message); -+ } -+ // Paper end - Brigadier API - } -diff --git a/src/main/java/com/mojang/brigadier/tree/CommandNode.java b/src/main/java/com/mojang/brigadier/tree/CommandNode.java -index da6250df1c5f3385b683cffde47754bca4606f5e..14ccd0c8f721e9be7dca8a5dcb8ef95b5cd82731 100644 ---- a/src/main/java/com/mojang/brigadier/tree/CommandNode.java -+++ b/src/main/java/com/mojang/brigadier/tree/CommandNode.java -@@ -34,6 +34,7 @@ public abstract class CommandNode implements Comparable> { - private final RedirectModifier modifier; - private final boolean forks; - private Command command; -+ public CommandNode clientNode; // Paper - Brigadier API - // CraftBukkit start - public void removeCommand(String name) { - this.children.remove(name); -diff --git a/src/main/java/net/minecraft/commands/CommandSourceStack.java b/src/main/java/net/minecraft/commands/CommandSourceStack.java -index d9fc3c25bef251df6a53ee47ec224b07240a931c..2a22827f44dd0d524c22264447959a6979e9f0de 100644 ---- a/src/main/java/net/minecraft/commands/CommandSourceStack.java -+++ b/src/main/java/net/minecraft/commands/CommandSourceStack.java -@@ -45,7 +45,7 @@ import net.minecraft.world.phys.Vec2; - import net.minecraft.world.phys.Vec3; - import com.mojang.brigadier.tree.CommandNode; // CraftBukkit - --public class CommandSourceStack implements ExecutionCommandSource, SharedSuggestionProvider { -+public class CommandSourceStack implements ExecutionCommandSource, SharedSuggestionProvider, com.destroystokyo.paper.brigadier.BukkitBrigadierCommandSource { // Paper - Brigadier API - - public static final SimpleCommandExceptionType ERROR_NOT_PLAYER = new SimpleCommandExceptionType(Component.translatable("permissions.requires.player")); - public static final SimpleCommandExceptionType ERROR_NOT_ENTITY = new SimpleCommandExceptionType(Component.translatable("permissions.requires.entity")); -@@ -170,6 +170,26 @@ public class CommandSourceStack implements ExecutionCommandSource(player.getBukkitEntity(), (RootCommandNode) rootcommandnode, false).callEvent(); // Paper - Brigadier API - net.minecraft.server.MinecraftServer.getServer().execute(() -> { - runSync(player, bukkit, rootcommandnode); - }); -@@ -493,6 +494,7 @@ public class Commands { - - private void runSync(ServerPlayer player, Collection bukkit, RootCommandNode rootcommandnode) { - // Paper end - Perf: Async command map building -+ new com.destroystokyo.paper.event.brigadier.AsyncPlayerSendCommandsEvent(player.getBukkitEntity(), (RootCommandNode) rootcommandnode, true).callEvent(); // Paper - Brigadier API - PlayerCommandSendEvent event = new PlayerCommandSendEvent(player.getBukkitEntity(), new LinkedHashSet<>(bukkit)); - event.getPlayer().getServer().getPluginManager().callEvent(event); - -@@ -511,6 +513,11 @@ public class Commands { - - while (iterator.hasNext()) { - CommandNode commandnode2 = (CommandNode) iterator.next(); -+ // Paper start - Brigadier API -+ if (commandnode2.clientNode != null) { -+ commandnode2 = commandnode2.clientNode; -+ } -+ // Paper end - Brigadier API - if ( !org.spigotmc.SpigotConfig.sendNamespaced && commandnode2.getName().contains( ":" ) ) continue; // Spigot - - if (commandnode2.canUse(source)) { -diff --git a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java -index 577f0e290f66afd2ded18714fb8b5f62a7a9aa71..e786d4b940a6fcd6d5ce66c5e13f52ff001b8367 100644 ---- a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java -+++ b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java -@@ -769,19 +769,34 @@ public class ServerGamePacketListenerImpl extends ServerCommonPacketListenerImpl - builder.suggest(completion.suggestion(), PaperAdventure.asVanilla(completion.tooltip())); - } - } -- this.connection.send(new ClientboundCommandSuggestionsPacket(packet.getId(), builder.buildFuture().join())); -+ // Paper start - Brigadier API -+ com.mojang.brigadier.suggestion.Suggestions suggestions = builder.buildFuture().join(); -+ com.destroystokyo.paper.event.brigadier.AsyncPlayerSendSuggestionsEvent suggestEvent = new com.destroystokyo.paper.event.brigadier.AsyncPlayerSendSuggestionsEvent(this.getCraftPlayer(), suggestions, packet.getCommand()); -+ suggestEvent.setCancelled(suggestions.isEmpty()); -+ if (suggestEvent.callEvent()) { -+ this.connection.send(new ClientboundCommandSuggestionsPacket(packet.getId(), limitTo(suggestEvent.getSuggestions(), ServerGamePacketListenerImpl.MAX_COMMAND_SUGGESTIONS))); -+ } -+ // Paper end - Brigadier API - } - } -+ // Paper start - brig API -+ private static Suggestions limitTo(final Suggestions suggestions, final int size) { -+ return suggestions.getList().size() <= size ? suggestions : new Suggestions(suggestions.getRange(), suggestions.getList().subList(0, size)); -+ } -+ // Paper end - brig API - - private void sendServerSuggestions(final ServerboundCommandSuggestionPacket packet, final StringReader stringreader) { - // Paper end - AsyncTabCompleteEvent - ParseResults parseresults = this.server.getCommands().getDispatcher().parse(stringreader, this.player.createCommandSourceStack()); - - this.server.getCommands().getDispatcher().getCompletionSuggestions(parseresults).thenAccept((suggestions) -> { -- if (suggestions.isEmpty()) return; // CraftBukkit - don't send through empty suggestions - prevents [] from showing for plugins with nothing more to offer -- Suggestions suggestions1 = suggestions.getList().size() <= 1000 ? suggestions : new Suggestions(suggestions.getRange(), suggestions.getList().subList(0, 1000)); -- -- this.send(new ClientboundCommandSuggestionsPacket(packet.getId(), suggestions1)); -+ // Paper start - Brigadier API -+ com.destroystokyo.paper.event.brigadier.AsyncPlayerSendSuggestionsEvent suggestEvent = new com.destroystokyo.paper.event.brigadier.AsyncPlayerSendSuggestionsEvent(this.getCraftPlayer(), suggestions, packet.getCommand()); -+ suggestEvent.setCancelled(suggestions.isEmpty()); -+ if (suggestEvent.callEvent()) { -+ this.send(new ClientboundCommandSuggestionsPacket(packet.getId(), limitTo(suggestEvent.getSuggestions(), ServerGamePacketListenerImpl.MAX_COMMAND_SUGGESTIONS))); -+ } -+ // Paper end - Brigadier API - }); - } - -diff --git a/src/main/java/org/bukkit/craftbukkit/command/BukkitCommandWrapper.java b/src/main/java/org/bukkit/craftbukkit/command/BukkitCommandWrapper.java -index 83d81b9371902b0302d13e53b31c15fac4e67966..d113e54a30db16e2ad955170df6030d15de530d6 100644 ---- a/src/main/java/org/bukkit/craftbukkit/command/BukkitCommandWrapper.java -+++ b/src/main/java/org/bukkit/craftbukkit/command/BukkitCommandWrapper.java -@@ -20,7 +20,7 @@ import org.bukkit.command.CommandException; - import org.bukkit.command.CommandSender; - import org.bukkit.craftbukkit.CraftServer; - --public class BukkitCommandWrapper implements com.mojang.brigadier.Command, Predicate, SuggestionProvider { -+public class BukkitCommandWrapper implements com.mojang.brigadier.Command, Predicate, SuggestionProvider, com.destroystokyo.paper.brigadier.BukkitBrigadierCommand { // Paper - - private final CraftServer server; - private final Command command; -@@ -31,10 +31,24 @@ public class BukkitCommandWrapper implements com.mojang.brigadier.Command register(CommandDispatcher dispatcher, String label) { -- return dispatcher.register( -- LiteralArgumentBuilder.literal(label).requires(this).executes(this) -- .then(RequiredArgumentBuilder.argument("args", StringArgumentType.greedyString()).suggests(this).executes(this)) -- ); -+ // Paper start - Expose Brigadier to Paper-MojangAPI -+ com.mojang.brigadier.tree.RootCommandNode root = dispatcher.getRoot(); -+ LiteralCommandNode literal = LiteralArgumentBuilder.literal(label).requires(this).executes(this).build(); -+ LiteralCommandNode defaultNode = literal; -+ com.mojang.brigadier.tree.ArgumentCommandNode defaultArgs = RequiredArgumentBuilder.argument("args", StringArgumentType.greedyString()).suggests(this).executes(this).build(); -+ literal.addChild(defaultArgs); -+ com.destroystokyo.paper.event.brigadier.CommandRegisteredEvent event = new com.destroystokyo.paper.event.brigadier.CommandRegisteredEvent<>(label, this, this.command, root, literal, defaultArgs); -+ if (!event.callEvent()) { -+ return null; -+ } -+ literal = event.getLiteral(); -+ if (event.isRawCommand()) { -+ defaultNode.clientNode = literal; -+ literal = defaultNode; -+ } -+ root.addChild(literal); -+ return literal; -+ // Paper end - } - - @Override diff --git a/patches/server/0276-Limit-Client-Sign-length-more.patch b/patches/server/0276-Limit-Client-Sign-length-more.patch new file mode 100644 index 000000000000..adfa39c69870 --- /dev/null +++ b/patches/server/0276-Limit-Client-Sign-length-more.patch @@ -0,0 +1,56 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Aikar +Date: Wed, 27 Feb 2019 22:18:40 -0500 +Subject: [PATCH] Limit Client Sign length more + +modified clients can send more data from the client +to the server and it would get stored on the sign as sent. + +Mojang has a limit of 384 which is much higher than reasonable. + +the client can barely render around 16 characters as-is, but formatting +codes can get it to be more than 16 actual length. + +Set a limit of 80 which should give an average of 16 characters 2 +sets of legacy formatting codes which should be plenty for all uses. + +This does not strip any existing data from the NBT as plugins +may use this for storing data out of the rendered area. + +it only impacts data sent from the client. + +Set -DPaper.maxSignLength=XX to change limit or -1 to disable + +diff --git a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java +index b234957d0563f1ccf533f927e60d8b6b77c33a25..8a58c1bdda065edd7b8560cd43e805de3fe0b178 100644 +--- a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java ++++ b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java +@@ -308,6 +308,7 @@ public class ServerGamePacketListenerImpl extends ServerCommonPacketListenerImpl + private final MessageSignatureCache messageSignatureCache = MessageSignatureCache.createDefault(); + private final FutureChain chatMessageChain; + private boolean waitingForSwitchToConfig; ++ private static final int MAX_SIGN_LINE_LENGTH = Integer.getInteger("Paper.maxSignLength", 80); // Paper - Limit client sign length + + public ServerGamePacketListenerImpl(MinecraftServer server, Connection connection, ServerPlayer player, CommonListenerCookie clientData) { + super(server, connection, clientData, player); // CraftBukkit +@@ -3201,7 +3202,19 @@ public class ServerGamePacketListenerImpl extends ServerCommonPacketListenerImpl + + @Override + public void handleSignUpdate(ServerboundSignUpdatePacket packet) { +- List list = (List) Stream.of(packet.getLines()).map(ChatFormatting::stripFormatting).collect(Collectors.toList()); ++ // Paper start - Limit client sign length ++ String[] lines = packet.getLines(); ++ for (int i = 0; i < lines.length; ++i) { ++ if (MAX_SIGN_LINE_LENGTH > 0 && lines[i].length() > MAX_SIGN_LINE_LENGTH) { ++ // This handles multibyte characters as 1 ++ int offset = lines[i].codePoints().limit(MAX_SIGN_LINE_LENGTH).map(Character::charCount).sum(); ++ if (offset < lines[i].length()) { ++ lines[i] = lines[i].substring(0, offset); // this will break any filtering, but filtering is NYI as of 1.17 ++ } ++ } ++ } ++ List list = (List) Stream.of(lines).map(ChatFormatting::stripFormatting).collect(Collectors.toList()); ++ // Paper end - Limit client sign length + + this.filterTextPacket(list).thenAcceptAsync((list1) -> { + this.updateSignText(packet, list1); diff --git a/patches/server/0277-Call-WhitelistToggleEvent-when-whitelist-is-toggled.patch b/patches/server/0277-Call-WhitelistToggleEvent-when-whitelist-is-toggled.patch new file mode 100644 index 000000000000..b24ebf201842 --- /dev/null +++ b/patches/server/0277-Call-WhitelistToggleEvent-when-whitelist-is-toggled.patch @@ -0,0 +1,18 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Mark Vainomaa +Date: Wed, 13 Mar 2019 20:08:09 +0200 +Subject: [PATCH] Call WhitelistToggleEvent when whitelist is toggled + + +diff --git a/src/main/java/net/minecraft/server/players/PlayerList.java b/src/main/java/net/minecraft/server/players/PlayerList.java +index f56ef720a74611ffef314553f4f785ea5cf086ac..c75bc7427bd818e9d23ca0af2b08cb160f3c432e 100644 +--- a/src/main/java/net/minecraft/server/players/PlayerList.java ++++ b/src/main/java/net/minecraft/server/players/PlayerList.java +@@ -1095,6 +1095,7 @@ public abstract class PlayerList { + } + + public void setUsingWhiteList(boolean whitelistEnabled) { ++ new com.destroystokyo.paper.event.server.WhitelistToggleEvent(whitelistEnabled).callEvent(); // Paper - WhitelistToggleEvent + this.doWhiteList = whitelistEnabled; + } + diff --git a/patches/server/0278-Fixes-and-additions-to-the-spawn-reason-API.patch b/patches/server/0278-Fixes-and-additions-to-the-spawn-reason-API.patch new file mode 100644 index 000000000000..cc586b335c34 --- /dev/null +++ b/patches/server/0278-Fixes-and-additions-to-the-spawn-reason-API.patch @@ -0,0 +1,289 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Aikar +Date: Sun, 24 Mar 2019 00:24:52 -0400 +Subject: [PATCH] Fixes and additions to the spawn reason API + +Expose an entities spawn reason on the entity. +Pre existing entities will return NATURAL if it was a non +persistenting Living Entity, SPAWNER for spawners, +or DEFAULT since data was not stored. + +Additionally, add missing spawn reasons. + +Co-authored-by: Jake Potrebic +Co-authored-by: Doc + +diff --git a/src/main/java/net/minecraft/server/commands/SummonCommand.java b/src/main/java/net/minecraft/server/commands/SummonCommand.java +index f55832ce841621daab4d3a910650ab6562cefcda..f635da34335cd2901adf975fcd74c5c6f9785836 100644 +--- a/src/main/java/net/minecraft/server/commands/SummonCommand.java ++++ b/src/main/java/net/minecraft/server/commands/SummonCommand.java +@@ -57,6 +57,7 @@ public class SummonCommand { + ServerLevel worldserver = source.getLevel(); + Entity entity = EntityType.loadEntityRecursive(nbttagcompound1, worldserver, EntitySpawnReason.COMMAND, (entity1) -> { + entity1.moveTo(pos.x, pos.y, pos.z, entity1.getYRot(), entity1.getXRot()); ++ entity1.spawnReason = org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason.COMMAND; // Paper - Entity#getEntitySpawnReason + return entity1; + }); + +diff --git a/src/main/java/net/minecraft/server/level/ServerLevel.java b/src/main/java/net/minecraft/server/level/ServerLevel.java +index 0635e7ed67d45abb7c419cf4ebda0e64718b630e..91e462544b82e0fe41851fa0fa3f6d6f08ac490d 100644 +--- a/src/main/java/net/minecraft/server/level/ServerLevel.java ++++ b/src/main/java/net/minecraft/server/level/ServerLevel.java +@@ -1172,6 +1172,7 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe + return true; + } + // Paper end - extra debug info ++ if (entity.spawnReason == null) entity.spawnReason = spawnReason; // Paper - Entity#getEntitySpawnReason + if (entity.isRemoved()) { + // WorldServer.LOGGER.warn("Tried to add entity {} but it was marked as removed already", EntityTypes.getKey(entity.getType())); // CraftBukkit + return false; +diff --git a/src/main/java/net/minecraft/server/level/ServerPlayer.java b/src/main/java/net/minecraft/server/level/ServerPlayer.java +index 9cfd0b457f6c462921667b9439a7b3e32d019758..62412b37d4f7d37b3fec0966ab700c2ae4d8cada 100644 +--- a/src/main/java/net/minecraft/server/level/ServerPlayer.java ++++ b/src/main/java/net/minecraft/server/level/ServerPlayer.java +@@ -716,7 +716,7 @@ public class ServerPlayer extends net.minecraft.world.entity.player.Player { + ServerLevel worldserver = (ServerLevel) world; + CompoundTag nbttagcompound = ((CompoundTag) nbt.get()).getCompound("RootVehicle"); + Entity entity = EntityType.loadEntityRecursive(nbttagcompound.getCompound("Entity"), worldserver, EntitySpawnReason.LOAD, (entity1) -> { +- return !worldserver.addWithUUID(entity1) ? null : entity1; ++ return !worldserver.addWithUUID(entity1, org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason.MOUNT) ? null : entity1; // CraftBukkit - decompile error // Paper - Entity#getEntitySpawnReason + }); + + if (entity == null) { +diff --git a/src/main/java/net/minecraft/server/players/PlayerList.java b/src/main/java/net/minecraft/server/players/PlayerList.java +index c75bc7427bd818e9d23ca0af2b08cb160f3c432e..fd1fe9a72a1d4e87b97a34fc79ab1429d31207e5 100644 +--- a/src/main/java/net/minecraft/server/players/PlayerList.java ++++ b/src/main/java/net/minecraft/server/players/PlayerList.java +@@ -223,6 +223,11 @@ public abstract class PlayerList { + worldserver1 = worldserver; + } + ++ // Paper start - Entity#getEntitySpawnReason ++ if (optional.isEmpty()) { ++ player.spawnReason = org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason.DEFAULT; // set Player SpawnReason to DEFAULT on first login ++ } ++ // Paper end - Entity#getEntitySpawnReason + player.setServerLevel(worldserver1); + String s1 = connection.getLoggableAddress(this.server.logIPs()); + +diff --git a/src/main/java/net/minecraft/world/entity/Entity.java b/src/main/java/net/minecraft/world/entity/Entity.java +index 5d551a50e1043e369ebf3ddfe181be1e24cfd068..463d34e7b54efd503c4879d1386b2439474863dd 100644 +--- a/src/main/java/net/minecraft/world/entity/Entity.java ++++ b/src/main/java/net/minecraft/world/entity/Entity.java +@@ -255,6 +255,7 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess + } + } + // Paper end - Share random for entities to make them more random ++ public org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason spawnReason; // Paper - Entity#getEntitySpawnReason + + private CraftEntity bukkitEntity; + +@@ -2390,6 +2391,9 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess + } + nbttagcompound.put("Paper.Origin", this.newDoubleList(origin.getX(), origin.getY(), origin.getZ())); + } ++ if (spawnReason != null) { ++ nbttagcompound.putString("Paper.SpawnReason", spawnReason.name()); ++ } + // Save entity's from mob spawner status + if (spawnedViaMobSpawner) { + nbttagcompound.putBoolean("Paper.FromMobSpawner", true); +@@ -2537,6 +2541,26 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess + } + + spawnedViaMobSpawner = nbt.getBoolean("Paper.FromMobSpawner"); // Restore entity's from mob spawner status ++ if (nbt.contains("Paper.SpawnReason")) { ++ String spawnReasonName = nbt.getString("Paper.SpawnReason"); ++ try { ++ spawnReason = org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason.valueOf(spawnReasonName); ++ } catch (Exception ignored) { ++ LOGGER.error("Unknown SpawnReason " + spawnReasonName + " for " + this); ++ } ++ } ++ if (spawnReason == null) { ++ if (spawnedViaMobSpawner) { ++ spawnReason = org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason.SPAWNER; ++ } else if (this instanceof Mob && (this instanceof net.minecraft.world.entity.animal.Animal || this instanceof net.minecraft.world.entity.animal.AbstractFish) && !((Mob) this).removeWhenFarAway(0.0)) { ++ if (!nbt.getBoolean("PersistenceRequired")) { ++ spawnReason = org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason.NATURAL; ++ } ++ } ++ } ++ if (spawnReason == null) { ++ spawnReason = org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason.DEFAULT; ++ } + // Paper end + + } catch (Throwable throwable) { +diff --git a/src/main/java/net/minecraft/world/entity/EntityType.java b/src/main/java/net/minecraft/world/entity/EntityType.java +index 8f4ec4f0ea7ff2f9a952120785aea65f6559f897..989b766bc166141a391e0a7c1a5eb815e20acfac 100644 +--- a/src/main/java/net/minecraft/world/entity/EntityType.java ++++ b/src/main/java/net/minecraft/world/entity/EntityType.java +@@ -433,7 +433,7 @@ public class EntityType implements FeatureElement, EntityTypeT + @Nullable + public T spawn(ServerLevel world, @Nullable ItemStack stack, @Nullable Player player, BlockPos pos, EntitySpawnReason spawnReason, boolean alignPosition, boolean invertY) { + // CraftBukkit start +- return this.spawn(world, stack, player, pos, spawnReason, alignPosition, invertY, org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason.SPAWNER_EGG); ++ return this.spawn(world, stack, player, pos, spawnReason, alignPosition, invertY, spawnReason == EntitySpawnReason.DISPENSER ? org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason.DISPENSE_EGG : org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason.SPAWNER_EGG); // Paper - use correct spawn reason for dispenser spawn eggs + } + + @Nullable +diff --git a/src/main/java/net/minecraft/world/entity/OminousItemSpawner.java b/src/main/java/net/minecraft/world/entity/OminousItemSpawner.java +index 1fc9f883305e150f4bdf478bf0f43e301460cbf2..7b75dbb79e0d2a92cc10838a6e4da973e69125fb 100644 +--- a/src/main/java/net/minecraft/world/entity/OminousItemSpawner.java ++++ b/src/main/java/net/minecraft/world/entity/OminousItemSpawner.java +@@ -76,7 +76,7 @@ public class OminousItemSpawner extends Entity { + entity = this.spawnProjectile(serverLevel, projectileItem, itemStack); + } else { + entity = new ItemEntity(serverLevel, this.getX(), this.getY(), this.getZ(), itemStack); +- serverLevel.addFreshEntity(entity); ++ serverLevel.addFreshEntity(entity, org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason.OMINOUS_ITEM_SPAWNER); // Paper - fixes and addition to spawn reason API + } + + serverLevel.levelEvent(3021, this.blockPosition(), 1); +@@ -90,7 +90,7 @@ public class OminousItemSpawner extends Entity { + ProjectileItem.DispenseConfig dispenseConfig = item.createDispenseConfig(); + dispenseConfig.overrideDispenseEvent().ifPresent(dispenseEvent -> world.levelEvent(dispenseEvent, this.blockPosition(), 0)); + Direction direction = Direction.DOWN; +- Projectile projectile = Projectile.spawnProjectileUsingShoot( ++ Projectile projectile = Projectile.spawnProjectileUsingShootDelayed( // Paper - fixes and addition to spawn reason API + item.asProjectile(world, this.position(), stack, direction), + world, + stack, +@@ -99,7 +99,7 @@ public class OminousItemSpawner extends Entity { + (double)direction.getStepZ(), + dispenseConfig.power(), + dispenseConfig.uncertainty() +- ); ++ ).spawn(org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason.OMINOUS_ITEM_SPAWNER); // Paper - fixes and addition to spawn reason API + projectile.setOwner(this); + return projectile; + } +diff --git a/src/main/java/net/minecraft/world/entity/projectile/DragonFireball.java b/src/main/java/net/minecraft/world/entity/projectile/DragonFireball.java +index 2eecdcbea3d51b1fb6e0c3db0667464a699ca0df..c68ddccd5fbe27f6a62cedbdc2337f1b6e4d3273 100644 +--- a/src/main/java/net/minecraft/world/entity/projectile/DragonFireball.java ++++ b/src/main/java/net/minecraft/world/entity/projectile/DragonFireball.java +@@ -64,7 +64,7 @@ public class DragonFireball extends AbstractHurtingProjectile { + + if (new com.destroystokyo.paper.event.entity.EnderDragonFireballHitEvent((org.bukkit.entity.DragonFireball) this.getBukkitEntity(), list.stream().map(LivingEntity::getBukkitLivingEntity).collect(java.util.stream.Collectors.toList()), (org.bukkit.entity.AreaEffectCloud) entityareaeffectcloud.getBukkitEntity()).callEvent()) { // Paper - EnderDragon Events + this.level().levelEvent(2006, this.blockPosition(), this.isSilent() ? -1 : 1); +- this.level().addFreshEntity(entityareaeffectcloud); ++ this.level().addFreshEntity(entityareaeffectcloud, org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason.EXPLOSION); // Paper - use correct spawn reason + } else entityareaeffectcloud.discard(null); // Paper - EnderDragon Events + this.discard(EntityRemoveEvent.Cause.HIT); // CraftBukkit - add Bukkit remove cause + } +diff --git a/src/main/java/net/minecraft/world/entity/projectile/Projectile.java b/src/main/java/net/minecraft/world/entity/projectile/Projectile.java +index 6c7f644738548da30908a08dcfde8142b597c0b7..80637835a35dbfd7c7458b246ddee9d836e5101a 100644 +--- a/src/main/java/net/minecraft/world/entity/projectile/Projectile.java ++++ b/src/main/java/net/minecraft/world/entity/projectile/Projectile.java +@@ -214,7 +214,12 @@ public abstract class Projectile extends Entity implements TraceableEntity { + } + + public static T spawnProjectileUsingShoot(T projectile, ServerLevel world, ItemStack projectileStack, double velocityX, double velocityY, double velocityZ, float power, float divergence) { +- return Projectile.spawnProjectile(projectile, world, projectileStack, (iprojectile) -> { ++ // Paper start - fixes and addition to spawn reason API ++ return spawnProjectileUsingShootDelayed(projectile, world, projectileStack, velocityX, velocityY, velocityZ, power, divergence).spawn(); ++ } ++ public static Delayed spawnProjectileUsingShootDelayed(T projectile, ServerLevel world, ItemStack projectileStack, double velocityX, double velocityY, double velocityZ, float power, float divergence) { ++ return Projectile.spawnProjectileDelayed(projectile, world, projectileStack, (iprojectile) -> { ++ // Paper end - fixes and addition to spawn reason API + projectile.shoot(velocityX, velocityY, velocityZ, power, divergence); + }); + } +@@ -241,6 +246,17 @@ public abstract class Projectile extends Entity implements TraceableEntity { + this.attemptSpawn(); + return projectile(); + } ++ ++ public boolean attemptSpawn(final org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason reason) { ++ if (!world.addFreshEntity(projectile, reason)) return false; ++ projectile.applyOnProjectileSpawned(this.world, this.projectileStack); ++ return true; ++ } ++ ++ public T spawn(final org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason reason) { ++ this.attemptSpawn(reason); ++ return projectile(); ++ } + } + // Paper end - delayed projectile spawning + +diff --git a/src/main/java/net/minecraft/world/level/BaseSpawner.java b/src/main/java/net/minecraft/world/level/BaseSpawner.java +index 261de9ea37d22023da6a306b58b1b62a54dc03da..299137519dba07c8b2cb35437742b5d40eb9e5d2 100644 +--- a/src/main/java/net/minecraft/world/level/BaseSpawner.java ++++ b/src/main/java/net/minecraft/world/level/BaseSpawner.java +@@ -191,6 +191,7 @@ public abstract class BaseSpawner { + } + + entity.spawnedViaMobSpawner = true; // Paper ++ entity.spawnReason = org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason.SPAWNER; // Paper - Entity#getEntitySpawnReason + flag = true; // Paper + // CraftBukkit start + if (org.bukkit.craftbukkit.event.CraftEventFactory.callSpawnerSpawnEvent(entity, pos).isCancelled()) { +diff --git a/src/main/java/net/minecraft/world/level/block/FrogspawnBlock.java b/src/main/java/net/minecraft/world/level/block/FrogspawnBlock.java +index 524f94a00f7b76a3847b68f0ab0cf82cc524b01e..aee71779f31def5f1ef7438cf06219d1de7092ec 100644 +--- a/src/main/java/net/minecraft/world/level/block/FrogspawnBlock.java ++++ b/src/main/java/net/minecraft/world/level/block/FrogspawnBlock.java +@@ -121,7 +121,7 @@ public class FrogspawnBlock extends Block { + int k = random.nextInt(1, 361); + tadpole.moveTo(d, (double)pos.getY() - 0.5, e, (float)k, 0.0F); + tadpole.setPersistenceRequired(); +- world.addFreshEntity(tadpole); ++ world.addFreshEntity(tadpole, org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason.EGG); // Paper - use correct spawn reason + } + } + } +diff --git a/src/main/java/net/minecraft/world/level/block/SnifferEggBlock.java b/src/main/java/net/minecraft/world/level/block/SnifferEggBlock.java +index fc26713a869d0c1fad1634ef4a6c6282fef5aa31..4f8224841865f956aaa969ab7f543c80b3c24319 100644 +--- a/src/main/java/net/minecraft/world/level/block/SnifferEggBlock.java ++++ b/src/main/java/net/minecraft/world/level/block/SnifferEggBlock.java +@@ -74,7 +74,7 @@ public class SnifferEggBlock extends Block { + Vec3 vec3 = pos.getCenter(); + sniffer.setBaby(true); + sniffer.moveTo(vec3.x(), vec3.y(), vec3.z(), Mth.wrapDegrees(world.random.nextFloat() * 360.0F), 0.0F); +- world.addFreshEntity(sniffer); ++ world.addFreshEntity(sniffer, org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason.EGG); // Paper - use correct spawn reason + } + } + } +diff --git a/src/main/java/net/minecraft/world/level/block/entity/SculkShriekerBlockEntity.java b/src/main/java/net/minecraft/world/level/block/entity/SculkShriekerBlockEntity.java +index dc6b18527222d23e9719e2210b684a1aa797c70d..ea53c25c350c0cf8e0360ea409cd1f69a62054a8 100644 +--- a/src/main/java/net/minecraft/world/level/block/entity/SculkShriekerBlockEntity.java ++++ b/src/main/java/net/minecraft/world/level/block/entity/SculkShriekerBlockEntity.java +@@ -189,7 +189,7 @@ public class SculkShriekerBlockEntity extends BlockEntity implements GameEventLi + + private boolean trySummonWarden(ServerLevel world) { + return this.warningLevel >= 4 +- && SpawnUtil.trySpawnMob(EntityType.WARDEN, EntitySpawnReason.TRIGGERED, world, this.getBlockPos(), 20, 5, 6, SpawnUtil.Strategy.ON_TOP_OF_COLLIDER) ++ && SpawnUtil.trySpawnMob(EntityType.WARDEN, EntitySpawnReason.TRIGGERED, world, this.getBlockPos(), 20, 5, 6, SpawnUtil.Strategy.ON_TOP_OF_COLLIDER, org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason.NATURAL, null) // Paper - Entity#getEntitySpawnReason + .isPresent(); + } + +diff --git a/src/main/java/net/minecraft/world/level/block/entity/trialspawner/TrialSpawner.java b/src/main/java/net/minecraft/world/level/block/entity/trialspawner/TrialSpawner.java +index f0163b7fa8b27823db9df5b8d2b6adcb63023164..de9d00ec932c8d07b28dd68ac9b8e6d5b94f7a8d 100644 +--- a/src/main/java/net/minecraft/world/level/block/entity/trialspawner/TrialSpawner.java ++++ b/src/main/java/net/minecraft/world/level/block/entity/trialspawner/TrialSpawner.java +@@ -231,6 +231,7 @@ public final class TrialSpawner { + } + + entity.spawnedViaMobSpawner = true; // Paper ++ entity.spawnReason = org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason.TRIAL_SPAWNER; // Paper - Entity#getEntitySpawnReason + // CraftBukkit start + if (org.bukkit.craftbukkit.event.CraftEventFactory.callTrialSpawnerSpawnEvent(entity, pos).isCancelled()) { + return Optional.empty(); +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java +index 0e5cc680ee2418ec2af5fc3e215618ad4e768ed0..7fb04535b8c0744d0fa4b0c0a933234134486956 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java +@@ -1019,4 +1019,11 @@ public abstract class CraftEntity implements org.bukkit.entity.Entity { + return this.getHandle().spawnedViaMobSpawner; + } + // Paper end - Entity#fromMobSpawner ++ ++ // Paper start - entity spawn reason API ++ @Override ++ public org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason getEntitySpawnReason() { ++ return getHandle().spawnReason; ++ } ++ // Paper end - entity spawn reason API + } diff --git a/patches/server/0278-Limit-Client-Sign-length-more.patch b/patches/server/0278-Limit-Client-Sign-length-more.patch deleted file mode 100644 index a5c3397ebe62..000000000000 --- a/patches/server/0278-Limit-Client-Sign-length-more.patch +++ /dev/null @@ -1,56 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Aikar -Date: Wed, 27 Feb 2019 22:18:40 -0500 -Subject: [PATCH] Limit Client Sign length more - -modified clients can send more data from the client -to the server and it would get stored on the sign as sent. - -Mojang has a limit of 384 which is much higher than reasonable. - -the client can barely render around 16 characters as-is, but formatting -codes can get it to be more than 16 actual length. - -Set a limit of 80 which should give an average of 16 characters 2 -sets of legacy formatting codes which should be plenty for all uses. - -This does not strip any existing data from the NBT as plugins -may use this for storing data out of the rendered area. - -it only impacts data sent from the client. - -Set -DPaper.maxSignLength=XX to change limit or -1 to disable - -diff --git a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java -index e786d4b940a6fcd6d5ce66c5e13f52ff001b8367..0ed2d0f5ec9d107e8049aa9e803479ffd341639f 100644 ---- a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java -+++ b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java -@@ -299,6 +299,7 @@ public class ServerGamePacketListenerImpl extends ServerCommonPacketListenerImpl - private final MessageSignatureCache messageSignatureCache = MessageSignatureCache.createDefault(); - private final FutureChain chatMessageChain; - private boolean waitingForSwitchToConfig; -+ private static final int MAX_SIGN_LINE_LENGTH = Integer.getInteger("Paper.maxSignLength", 80); // Paper - Limit client sign length - - public ServerGamePacketListenerImpl(MinecraftServer server, Connection connection, ServerPlayer player, CommonListenerCookie clientData) { - super(server, connection, clientData, player); // CraftBukkit -@@ -3149,7 +3150,19 @@ public class ServerGamePacketListenerImpl extends ServerCommonPacketListenerImpl - - @Override - public void handleSignUpdate(ServerboundSignUpdatePacket packet) { -- List list = (List) Stream.of(packet.getLines()).map(ChatFormatting::stripFormatting).collect(Collectors.toList()); -+ // Paper start - Limit client sign length -+ String[] lines = packet.getLines(); -+ for (int i = 0; i < lines.length; ++i) { -+ if (MAX_SIGN_LINE_LENGTH > 0 && lines[i].length() > MAX_SIGN_LINE_LENGTH) { -+ // This handles multibyte characters as 1 -+ int offset = lines[i].codePoints().limit(MAX_SIGN_LINE_LENGTH).map(Character::charCount).sum(); -+ if (offset < lines[i].length()) { -+ lines[i] = lines[i].substring(0, offset); // this will break any filtering, but filtering is NYI as of 1.17 -+ } -+ } -+ } -+ List list = (List) Stream.of(lines).map(ChatFormatting::stripFormatting).collect(Collectors.toList()); -+ // Paper end - Limit client sign length - - this.filterTextPacket(list).thenAcceptAsync((list1) -> { - this.updateSignText(packet, list1); diff --git a/patches/server/0279-Call-WhitelistToggleEvent-when-whitelist-is-toggled.patch b/patches/server/0279-Call-WhitelistToggleEvent-when-whitelist-is-toggled.patch deleted file mode 100644 index 36ca904431c7..000000000000 --- a/patches/server/0279-Call-WhitelistToggleEvent-when-whitelist-is-toggled.patch +++ /dev/null @@ -1,18 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Mark Vainomaa -Date: Wed, 13 Mar 2019 20:08:09 +0200 -Subject: [PATCH] Call WhitelistToggleEvent when whitelist is toggled - - -diff --git a/src/main/java/net/minecraft/server/players/PlayerList.java b/src/main/java/net/minecraft/server/players/PlayerList.java -index 7f8081446704ea9642275cb2bc139fed174a2f1f..0bac75f111398fd22df978a09dcd4cdc22998894 100644 ---- a/src/main/java/net/minecraft/server/players/PlayerList.java -+++ b/src/main/java/net/minecraft/server/players/PlayerList.java -@@ -1128,6 +1128,7 @@ public abstract class PlayerList { - } - - public void setUsingWhiteList(boolean whitelistEnabled) { -+ new com.destroystokyo.paper.event.server.WhitelistToggleEvent(whitelistEnabled).callEvent(); // Paper - WhitelistToggleEvent - this.doWhiteList = whitelistEnabled; - } - diff --git a/patches/server/0281-Fire-event-on-GS4-query.patch b/patches/server/0279-Fire-event-on-GS4-query.patch similarity index 100% rename from patches/server/0281-Fire-event-on-GS4-query.patch rename to patches/server/0279-Fire-event-on-GS4-query.patch diff --git a/patches/server/0280-Add-PlayerPostRespawnEvent.patch b/patches/server/0280-Add-PlayerPostRespawnEvent.patch new file mode 100644 index 000000000000..a887fd99185f --- /dev/null +++ b/patches/server/0280-Add-PlayerPostRespawnEvent.patch @@ -0,0 +1,65 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: MisterVector +Date: Fri, 26 Oct 2018 21:31:00 -0700 +Subject: [PATCH] Add PlayerPostRespawnEvent + + +diff --git a/src/main/java/net/minecraft/server/players/PlayerList.java b/src/main/java/net/minecraft/server/players/PlayerList.java +index fd1fe9a72a1d4e87b97a34fc79ab1429d31207e5..2cfc2213e3036585dc4723eecf747e1c37d53b72 100644 +--- a/src/main/java/net/minecraft/server/players/PlayerList.java ++++ b/src/main/java/net/minecraft/server/players/PlayerList.java +@@ -704,6 +704,10 @@ public abstract class PlayerList { + + entityplayer1.addTag(s); + } ++ // Paper start - Add PlayerPostRespawnEvent ++ boolean isBedSpawn = false; ++ boolean isRespawn = false; ++ // Paper end - Add PlayerPostRespawnEvent + + // CraftBukkit start - fire PlayerRespawnEvent + TeleportTransition teleporttransition; +@@ -711,11 +715,16 @@ public abstract class PlayerList { + teleporttransition = entityplayer.findRespawnPositionAndUseSpawnBlock(!flag, TeleportTransition.DO_NOTHING, reason); + + if (!flag) entityplayer.reset(); // SPIGOT-4785 ++ // Paper start - Add PlayerPostRespawnEvent ++ if (teleporttransition == null) return entityplayer; // Early exit, mirrors belows early return for disconnected players in respawn event ++ isRespawn = true; ++ location = CraftLocation.toBukkit(teleporttransition.position(), teleporttransition.newLevel().getWorld(), teleporttransition.yRot(), teleporttransition.xRot()); ++ // Paper end - Add PlayerPostRespawnEvent + } else { + teleporttransition = new TeleportTransition(((CraftWorld) location.getWorld()).getHandle(), CraftLocation.toVec3D(location), Vec3.ZERO, location.getYaw(), location.getPitch(), TeleportTransition.DO_NOTHING); + } + // Spigot Start +- if (teleporttransition == null) { ++ if (teleporttransition == null) { // Paper - Add PlayerPostRespawnEvent - diff on change - spigot early returns if respawn pos is null, that is how they handle disconnected player in respawn event + return entityplayer; + } + // Spigot End +@@ -763,6 +772,11 @@ public abstract class PlayerList { + if (iblockdata.is(Blocks.RESPAWN_ANCHOR)) { + entityplayer1.connection.send(new ClientboundSoundPacket(SoundEvents.RESPAWN_ANCHOR_DEPLETE, SoundSource.BLOCKS, (double) blockposition.getX(), (double) blockposition.getY(), (double) blockposition.getZ(), 1.0F, 1.0F, worldserver.getRandom().nextLong())); + } ++ // Paper start - Add PlayerPostRespawnEvent ++ if (iblockdata.is(net.minecraft.tags.BlockTags.BEDS) && !teleporttransition.missingRespawnBlock()) { ++ isBedSpawn = true; ++ } ++ // Paper end - Add PlayerPostRespawnEvent + } + // Added from changeDimension + this.sendAllPlayerInfo(entityplayer); // Update health, etc... +@@ -784,6 +798,13 @@ public abstract class PlayerList { + if (entityplayer.connection.isDisconnected()) { + this.save(entityplayer); + } ++ ++ // Paper start - Add PlayerPostRespawnEvent ++ if (isRespawn) { ++ cserver.getPluginManager().callEvent(new com.destroystokyo.paper.event.player.PlayerPostRespawnEvent(entityplayer.getBukkitEntity(), location, isBedSpawn)); ++ } ++ // Paper end - Add PlayerPostRespawnEvent ++ + // CraftBukkit end + + return entityplayer1; diff --git a/patches/server/0280-Fixes-and-additions-to-the-spawn-reason-API.patch b/patches/server/0280-Fixes-and-additions-to-the-spawn-reason-API.patch deleted file mode 100644 index a8fda4a8d70e..000000000000 --- a/patches/server/0280-Fixes-and-additions-to-the-spawn-reason-API.patch +++ /dev/null @@ -1,231 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Aikar -Date: Sun, 24 Mar 2019 00:24:52 -0400 -Subject: [PATCH] Fixes and additions to the spawn reason API - -Expose an entities spawn reason on the entity. -Pre existing entities will return NATURAL if it was a non -persistenting Living Entity, SPAWNER for spawners, -or DEFAULT since data was not stored. - -Additionally, add missing spawn reasons. - -Co-authored-by: Jake Potrebic -Co-authored-by: Doc - -diff --git a/src/main/java/net/minecraft/server/commands/SummonCommand.java b/src/main/java/net/minecraft/server/commands/SummonCommand.java -index bf72cf288ade52ee8cc41ca978f368b3ad575951..798999be50d26be357ef3c6d5b9383ce4d1048c1 100644 ---- a/src/main/java/net/minecraft/server/commands/SummonCommand.java -+++ b/src/main/java/net/minecraft/server/commands/SummonCommand.java -@@ -57,6 +57,7 @@ public class SummonCommand { - ServerLevel worldserver = source.getLevel(); - Entity entity = EntityType.loadEntityRecursive(nbttagcompound1, worldserver, (entity1) -> { - entity1.moveTo(pos.x, pos.y, pos.z, entity1.getYRot(), entity1.getXRot()); -+ entity1.spawnReason = org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason.COMMAND; // Paper - Entity#getEntitySpawnReason - return entity1; - }); - -diff --git a/src/main/java/net/minecraft/server/level/ServerLevel.java b/src/main/java/net/minecraft/server/level/ServerLevel.java -index e9d08662c065d04a67918f0aa2cd4fde5798f2a6..a6a21def1ae0d35fa106da6242c49a0928e4eda5 100644 ---- a/src/main/java/net/minecraft/server/level/ServerLevel.java -+++ b/src/main/java/net/minecraft/server/level/ServerLevel.java -@@ -1207,6 +1207,7 @@ public class ServerLevel extends Level implements WorldGenLevel { - return true; - } - // Paper end - extra debug info -+ if (entity.spawnReason == null) entity.spawnReason = spawnReason; // Paper - Entity#getEntitySpawnReason - if (entity.isRemoved()) { - // WorldServer.LOGGER.warn("Tried to add entity {} but it was marked as removed already", EntityTypes.getKey(entity.getType())); // CraftBukkit - return false; -diff --git a/src/main/java/net/minecraft/server/players/PlayerList.java b/src/main/java/net/minecraft/server/players/PlayerList.java -index 2f1d075b8dbcf173c51f3e6396ccbc61b64f75df..79203d0e5cdb86d9e2fb22cdaeb8cf3a93e43dcc 100644 ---- a/src/main/java/net/minecraft/server/players/PlayerList.java -+++ b/src/main/java/net/minecraft/server/players/PlayerList.java -@@ -223,6 +223,11 @@ public abstract class PlayerList { - worldserver1 = worldserver; - } - -+ // Paper start - Entity#getEntitySpawnReason -+ if (optional.isEmpty()) { -+ player.spawnReason = org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason.DEFAULT; // set Player SpawnReason to DEFAULT on first login -+ } -+ // Paper end - Entity#getEntitySpawnReason - player.setServerLevel(worldserver1); - String s1 = connection.getLoggableAddress(this.server.logIPs()); - -@@ -348,7 +353,7 @@ public abstract class PlayerList { - CompoundTag nbttagcompound = ((CompoundTag) optional.get()).getCompound("RootVehicle"); - ServerLevel finalWorldServer = worldserver1; // CraftBukkit - decompile error - Entity entity = EntityType.loadEntityRecursive(nbttagcompound.getCompound("Entity"), worldserver1, (entity1) -> { -- return !finalWorldServer.addWithUUID(entity1) ? null : entity1; // CraftBukkit - decompile error -+ return !finalWorldServer.addWithUUID(entity1, org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason.MOUNT) ? null : entity1; // CraftBukkit - decompile error // Paper - Entity#getEntitySpawnReason - }); - - if (entity != null) { -diff --git a/src/main/java/net/minecraft/world/entity/Entity.java b/src/main/java/net/minecraft/world/entity/Entity.java -index f330ddca00ed11bf76ae825820423b94920013b9..9d56aff2766b684f0fb20e93d504de1a7a564b11 100644 ---- a/src/main/java/net/minecraft/world/entity/Entity.java -+++ b/src/main/java/net/minecraft/world/entity/Entity.java -@@ -247,6 +247,7 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess - } - } - // Paper end - Share random for entities to make them more random -+ public org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason spawnReason; // Paper - Entity#getEntitySpawnReason - - private CraftEntity bukkitEntity; - -@@ -2274,6 +2275,9 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess - } - nbttagcompound.put("Paper.Origin", this.newDoubleList(origin.getX(), origin.getY(), origin.getZ())); - } -+ if (spawnReason != null) { -+ nbttagcompound.putString("Paper.SpawnReason", spawnReason.name()); -+ } - // Save entity's from mob spawner status - if (spawnedViaMobSpawner) { - nbttagcompound.putBoolean("Paper.FromMobSpawner", true); -@@ -2420,6 +2424,26 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess - } - - spawnedViaMobSpawner = nbt.getBoolean("Paper.FromMobSpawner"); // Restore entity's from mob spawner status -+ if (nbt.contains("Paper.SpawnReason")) { -+ String spawnReasonName = nbt.getString("Paper.SpawnReason"); -+ try { -+ spawnReason = org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason.valueOf(spawnReasonName); -+ } catch (Exception ignored) { -+ LOGGER.error("Unknown SpawnReason " + spawnReasonName + " for " + this); -+ } -+ } -+ if (spawnReason == null) { -+ if (spawnedViaMobSpawner) { -+ spawnReason = org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason.SPAWNER; -+ } else if (this instanceof Mob && (this instanceof net.minecraft.world.entity.animal.Animal || this instanceof net.minecraft.world.entity.animal.AbstractFish) && !((Mob) this).removeWhenFarAway(0.0)) { -+ if (!nbt.getBoolean("PersistenceRequired")) { -+ spawnReason = org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason.NATURAL; -+ } -+ } -+ } -+ if (spawnReason == null) { -+ spawnReason = org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason.DEFAULT; -+ } - // Paper end - - } catch (Throwable throwable) { -diff --git a/src/main/java/net/minecraft/world/entity/EntityType.java b/src/main/java/net/minecraft/world/entity/EntityType.java -index 69a661f01e43d17262fd2845dde5528416bbe456..c0062c8f83641ff30e79a309c0bb9930ba4b422a 100644 ---- a/src/main/java/net/minecraft/world/entity/EntityType.java -+++ b/src/main/java/net/minecraft/world/entity/EntityType.java -@@ -366,7 +366,7 @@ public class EntityType implements FeatureElement, EntityTypeT - @Nullable - public T spawn(ServerLevel world, @Nullable ItemStack stack, @Nullable Player player, BlockPos pos, MobSpawnType spawnReason, boolean alignPosition, boolean invertY) { - // CraftBukkit start -- return this.spawn(world, stack, player, pos, spawnReason, alignPosition, invertY, org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason.SPAWNER_EGG); -+ return this.spawn(world, stack, player, pos, spawnReason, alignPosition, invertY, spawnReason == MobSpawnType.DISPENSER ? org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason.DISPENSE_EGG : org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason.SPAWNER_EGG); // Paper - use correct spawn reason for dispenser spawn eggs - } - - @Nullable -diff --git a/src/main/java/net/minecraft/world/entity/OminousItemSpawner.java b/src/main/java/net/minecraft/world/entity/OminousItemSpawner.java -index 146cbec9e64b6c77759aadd0d0c4e989018e9aef..4c4545b3732d4c08afdb7bc1913169a96e82825c 100644 ---- a/src/main/java/net/minecraft/world/entity/OminousItemSpawner.java -+++ b/src/main/java/net/minecraft/world/entity/OminousItemSpawner.java -@@ -89,7 +89,7 @@ public class OminousItemSpawner extends Entity { - entity = new ItemEntity(level, this.getX(), this.getY(), this.getZ(), itemStack); - } - -- level.addFreshEntity(entity); -+ level.addFreshEntity(entity, org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason.OMINOUS_ITEM_SPAWNER); // Paper - Fixes and additions to the SpawnReason API - level.levelEvent(3021, this.blockPosition(), 1); - level.gameEvent(entity, GameEvent.ENTITY_PLACE, this.position()); - this.setItem(ItemStack.EMPTY); -diff --git a/src/main/java/net/minecraft/world/entity/projectile/DragonFireball.java b/src/main/java/net/minecraft/world/entity/projectile/DragonFireball.java -index 1dade7a4fbdf190661e4431496349444467509cc..3e869620db35d38db39fbeed715b898ef9d2743c 100644 ---- a/src/main/java/net/minecraft/world/entity/projectile/DragonFireball.java -+++ b/src/main/java/net/minecraft/world/entity/projectile/DragonFireball.java -@@ -65,7 +65,7 @@ public class DragonFireball extends AbstractHurtingProjectile { - - if (new com.destroystokyo.paper.event.entity.EnderDragonFireballHitEvent((org.bukkit.entity.DragonFireball) this.getBukkitEntity(), list.stream().map(LivingEntity::getBukkitLivingEntity).collect(java.util.stream.Collectors.toList()), (org.bukkit.entity.AreaEffectCloud) entityareaeffectcloud.getBukkitEntity()).callEvent()) { // Paper - EnderDragon Events - this.level().levelEvent(2006, this.blockPosition(), this.isSilent() ? -1 : 1); -- this.level().addFreshEntity(entityareaeffectcloud); -+ this.level().addFreshEntity(entityareaeffectcloud, org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason.EXPLOSION); // Paper - use correct spawn reason - } else entityareaeffectcloud.discard(null); // Paper - EnderDragon Events - this.discard(EntityRemoveEvent.Cause.HIT); // CraftBukkit - add Bukkit remove cause - } -diff --git a/src/main/java/net/minecraft/world/level/BaseSpawner.java b/src/main/java/net/minecraft/world/level/BaseSpawner.java -index ee897b8c9462dbb3d7be9a2994753155065ce205..1d0964a7f544735a0213d5c7832c71f53db139a9 100644 ---- a/src/main/java/net/minecraft/world/level/BaseSpawner.java -+++ b/src/main/java/net/minecraft/world/level/BaseSpawner.java -@@ -191,6 +191,7 @@ public abstract class BaseSpawner { - } - - entity.spawnedViaMobSpawner = true; // Paper -+ entity.spawnReason = org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason.SPAWNER; // Paper - Entity#getEntitySpawnReason - flag = true; // Paper - // CraftBukkit start - if (org.bukkit.craftbukkit.event.CraftEventFactory.callSpawnerSpawnEvent(entity, pos).isCancelled()) { -diff --git a/src/main/java/net/minecraft/world/level/block/FrogspawnBlock.java b/src/main/java/net/minecraft/world/level/block/FrogspawnBlock.java -index 211b7809f099678bc3bd64bd29fd9c4d19e3ab0d..b968129b9a93fdf771caba5f768456070543ba6a 100644 ---- a/src/main/java/net/minecraft/world/level/block/FrogspawnBlock.java -+++ b/src/main/java/net/minecraft/world/level/block/FrogspawnBlock.java -@@ -111,7 +111,7 @@ public class FrogspawnBlock extends Block { - int k = random.nextInt(1, 361); - tadpole.moveTo(d, (double)pos.getY() - 0.5, e, (float)k, 0.0F); - tadpole.setPersistenceRequired(); -- world.addFreshEntity(tadpole); -+ world.addFreshEntity(tadpole, org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason.EGG); // Paper - use correct spawn reason - } - } - } -diff --git a/src/main/java/net/minecraft/world/level/block/SnifferEggBlock.java b/src/main/java/net/minecraft/world/level/block/SnifferEggBlock.java -index 1eda49c9ce8ee009cb08b18f02f59b37c2118628..14a5cd54820243f4ca59857b0d67c1e86457d590 100644 ---- a/src/main/java/net/minecraft/world/level/block/SnifferEggBlock.java -+++ b/src/main/java/net/minecraft/world/level/block/SnifferEggBlock.java -@@ -73,7 +73,7 @@ public class SnifferEggBlock extends Block { - Vec3 vec3 = pos.getCenter(); - sniffer.setBaby(true); - sniffer.moveTo(vec3.x(), vec3.y(), vec3.z(), Mth.wrapDegrees(world.random.nextFloat() * 360.0F), 0.0F); -- world.addFreshEntity(sniffer); -+ world.addFreshEntity(sniffer, org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason.EGG); // Paper - use correct spawn reason - } - } - } -diff --git a/src/main/java/net/minecraft/world/level/block/entity/SculkShriekerBlockEntity.java b/src/main/java/net/minecraft/world/level/block/entity/SculkShriekerBlockEntity.java -index dbc0b69603dcffbf3d41d79719aa0f2b7da4a131..dd86f5ec5b2051aeea4e19ff97146362b1e8d019 100644 ---- a/src/main/java/net/minecraft/world/level/block/entity/SculkShriekerBlockEntity.java -+++ b/src/main/java/net/minecraft/world/level/block/entity/SculkShriekerBlockEntity.java -@@ -189,7 +189,7 @@ public class SculkShriekerBlockEntity extends BlockEntity implements GameEventLi - - private boolean trySummonWarden(ServerLevel world) { - return this.warningLevel >= 4 -- && SpawnUtil.trySpawnMob(EntityType.WARDEN, MobSpawnType.TRIGGERED, world, this.getBlockPos(), 20, 5, 6, SpawnUtil.Strategy.ON_TOP_OF_COLLIDER) -+ && SpawnUtil.trySpawnMob(EntityType.WARDEN, MobSpawnType.TRIGGERED, world, this.getBlockPos(), 20, 5, 6, SpawnUtil.Strategy.ON_TOP_OF_COLLIDER, org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason.NATURAL, null) // Paper - Entity#getEntitySpawnReason - .isPresent(); - } - -diff --git a/src/main/java/net/minecraft/world/level/block/entity/trialspawner/TrialSpawner.java b/src/main/java/net/minecraft/world/level/block/entity/trialspawner/TrialSpawner.java -index c3f6522a7dc0777c5b3fa2bac63a8901f96d9f38..9b336f651ead8111a0b2c0fedad9331f594b2990 100644 ---- a/src/main/java/net/minecraft/world/level/block/entity/trialspawner/TrialSpawner.java -+++ b/src/main/java/net/minecraft/world/level/block/entity/trialspawner/TrialSpawner.java -@@ -231,6 +231,7 @@ public final class TrialSpawner { - } - - entity.spawnedViaMobSpawner = true; // Paper -+ entity.spawnReason = org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason.TRIAL_SPAWNER; // Paper - Entity#getEntitySpawnReason - // CraftBukkit start - if (org.bukkit.craftbukkit.event.CraftEventFactory.callTrialSpawnerSpawnEvent(entity, pos).isCancelled()) { - return Optional.empty(); -diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java -index bddf98bdf60473eb1d2e533cf533ed7eee797aaa..ce70c8fddbe63d0af2b1f988ce9a2b40c5d48066 100644 ---- a/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java -+++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java -@@ -1018,4 +1018,11 @@ public abstract class CraftEntity implements org.bukkit.entity.Entity { - return this.getHandle().spawnedViaMobSpawner; - } - // Paper end - Entity#fromMobSpawner -+ -+ // Paper start - entity spawn reason API -+ @Override -+ public org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason getEntitySpawnReason() { -+ return getHandle().spawnReason; -+ } -+ // Paper end - entity spawn reason API - } diff --git a/patches/server/0281-Server-Tick-Events.patch b/patches/server/0281-Server-Tick-Events.patch new file mode 100644 index 000000000000..5e040767919f --- /dev/null +++ b/patches/server/0281-Server-Tick-Events.patch @@ -0,0 +1,31 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Aikar +Date: Wed, 27 Mar 2019 22:48:45 -0400 +Subject: [PATCH] Server Tick Events + +Fires event at start and end of a server tick + +diff --git a/src/main/java/net/minecraft/server/MinecraftServer.java b/src/main/java/net/minecraft/server/MinecraftServer.java +index 8f427475f7418bbfb8121dbd3e25e7827775ea41..447b8f9ede3f57c6c5f968a0d25153c5c8770c5a 100644 +--- a/src/main/java/net/minecraft/server/MinecraftServer.java ++++ b/src/main/java/net/minecraft/server/MinecraftServer.java +@@ -1482,6 +1482,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop -Date: Fri, 26 Oct 2018 21:31:00 -0700 -Subject: [PATCH] Add PlayerPostRespawnEvent - - -diff --git a/src/main/java/net/minecraft/server/players/PlayerList.java b/src/main/java/net/minecraft/server/players/PlayerList.java -index 79203d0e5cdb86d9e2fb22cdaeb8cf3a93e43dcc..8cd80ea83ddcfd5052c8d8c19d3edb42538d1e15 100644 ---- a/src/main/java/net/minecraft/server/players/PlayerList.java -+++ b/src/main/java/net/minecraft/server/players/PlayerList.java -@@ -738,6 +738,10 @@ public abstract class PlayerList { - - entityplayer1.addTag(s); - } -+ // Paper start - Add PlayerPostRespawnEvent -+ boolean isBedSpawn = false; -+ boolean isRespawn = false; -+ // Paper end - Add PlayerPostRespawnEvent - - // CraftBukkit start - fire PlayerRespawnEvent - DimensionTransition dimensiontransition; -@@ -745,11 +749,16 @@ public abstract class PlayerList { - dimensiontransition = entityplayer.findRespawnPositionAndUseSpawnBlock(flag, DimensionTransition.DO_NOTHING, reason); - - if (!flag) entityplayer.reset(); // SPIGOT-4785 -+ // Paper start - Add PlayerPostRespawnEvent -+ if (dimensiontransition == null) return entityplayer; // Early exit, mirrors belows early return for disconnected players in respawn event -+ isRespawn = true; -+ location = CraftLocation.toBukkit(dimensiontransition.pos(), dimensiontransition.newLevel().getWorld(), dimensiontransition.yRot(), dimensiontransition.xRot()); -+ // Paper end - Add PlayerPostRespawnEvent - } else { - dimensiontransition = new DimensionTransition(((CraftWorld) location.getWorld()).getHandle(), CraftLocation.toVec3D(location), Vec3.ZERO, location.getYaw(), location.getPitch(), DimensionTransition.DO_NOTHING); - } - // Spigot Start -- if (dimensiontransition == null) { -+ if (dimensiontransition == null) { // Paper - Add PlayerPostRespawnEvent - diff on change - spigot early returns if respawn pos is null, that is how they handle disconnected player in respawn event - return entityplayer; - } - // Spigot End -@@ -795,6 +804,11 @@ public abstract class PlayerList { - if (iblockdata.is(Blocks.RESPAWN_ANCHOR)) { - entityplayer1.connection.send(new ClientboundSoundPacket(SoundEvents.RESPAWN_ANCHOR_DEPLETE, SoundSource.BLOCKS, (double) blockposition.getX(), (double) blockposition.getY(), (double) blockposition.getZ(), 1.0F, 1.0F, worldserver.getRandom().nextLong())); - } -+ // Paper start - Add PlayerPostRespawnEvent -+ if (iblockdata.is(net.minecraft.tags.BlockTags.BEDS) && !dimensiontransition.missingRespawnBlock()) { -+ isBedSpawn = true; -+ } -+ // Paper end - Add PlayerPostRespawnEvent - } - // Added from changeDimension - this.sendAllPlayerInfo(entityplayer); // Update health, etc... -@@ -816,6 +830,13 @@ public abstract class PlayerList { - if (entityplayer.connection.isDisconnected()) { - this.save(entityplayer); - } -+ -+ // Paper start - Add PlayerPostRespawnEvent -+ if (isRespawn) { -+ cserver.getPluginManager().callEvent(new com.destroystokyo.paper.event.player.PlayerPostRespawnEvent(entityplayer.getBukkitEntity(), location, isBedSpawn)); -+ } -+ // Paper end - Add PlayerPostRespawnEvent -+ - // CraftBukkit end - - return entityplayer1; diff --git a/patches/server/0282-PlayerDeathEvent-getItemsToKeep.patch b/patches/server/0282-PlayerDeathEvent-getItemsToKeep.patch new file mode 100644 index 000000000000..dd5b9f834ccc --- /dev/null +++ b/patches/server/0282-PlayerDeathEvent-getItemsToKeep.patch @@ -0,0 +1,77 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Aikar +Date: Wed, 27 Mar 2019 23:01:33 -0400 +Subject: [PATCH] PlayerDeathEvent#getItemsToKeep + +Exposes a mutable array on items a player should keep on death + +Example Usage: https://gist.github.com/aikar/5bb202de6057a051a950ce1f29feb0b4 + +== AT == +public net.minecraft.world.entity.player.Inventory compartments + +diff --git a/src/main/java/net/minecraft/server/level/ServerPlayer.java b/src/main/java/net/minecraft/server/level/ServerPlayer.java +index 62412b37d4f7d37b3fec0966ab700c2ae4d8cada..c5eb4766d95b1ed91be8e8631200f56096a17766 100644 +--- a/src/main/java/net/minecraft/server/level/ServerPlayer.java ++++ b/src/main/java/net/minecraft/server/level/ServerPlayer.java +@@ -1157,6 +1157,46 @@ public class ServerPlayer extends net.minecraft.world.entity.player.Player { + }); + } + ++ // Paper start - PlayerDeathEvent#getItemsToKeep ++ private static void processKeep(org.bukkit.event.entity.PlayerDeathEvent event, NonNullList inv) { ++ List itemsToKeep = event.getItemsToKeep(); ++ if (inv == null) { ++ // remainder of items left in toKeep - plugin added stuff on death that wasn't in the initial loot? ++ if (!itemsToKeep.isEmpty()) { ++ for (org.bukkit.inventory.ItemStack itemStack : itemsToKeep) { ++ event.getEntity().getInventory().addItem(itemStack); ++ } ++ } ++ ++ return; ++ } ++ ++ for (int i = 0; i < inv.size(); ++i) { ++ ItemStack item = inv.get(i); ++ if (EnchantmentHelper.has(item, EnchantmentEffectComponents.PREVENT_EQUIPMENT_DROP) || itemsToKeep.isEmpty() || item.isEmpty()) { ++ inv.set(i, ItemStack.EMPTY); ++ continue; ++ } ++ ++ final org.bukkit.inventory.ItemStack bukkitStack = item.getBukkitStack(); ++ boolean keep = false; ++ final Iterator iterator = itemsToKeep.iterator(); ++ while (iterator.hasNext()) { ++ final org.bukkit.inventory.ItemStack itemStack = iterator.next(); ++ if (bukkitStack.equals(itemStack)) { ++ iterator.remove(); ++ keep = true; ++ break; ++ } ++ } ++ ++ if (!keep) { ++ inv.set(i, ItemStack.EMPTY); ++ } ++ } ++ } ++ // Paper end - PlayerDeathEvent#getItemsToKeep ++ + @Override + public void die(DamageSource damageSource) { + // this.gameEvent(GameEvent.ENTITY_DIE); // Paper - move below event cancellation check +@@ -1241,7 +1281,12 @@ public class ServerPlayer extends net.minecraft.world.entity.player.Player { + this.dropExperience(this.serverLevel(), damageSource.getEntity()); + // we clean the player's inventory after the EntityDeathEvent is called so plugins can get the exact state of the inventory. + if (!event.getKeepInventory()) { +- this.getInventory().clearContent(); ++ // Paper start - PlayerDeathEvent#getItemsToKeep ++ for (NonNullList inv : this.getInventory().compartments) { ++ processKeep(event, inv); ++ } ++ processKeep(event, null); ++ // Paper end - PlayerDeathEvent#getItemsToKeep + } + + this.setCamera(this); // Remove spectated target diff --git a/patches/server/0283-Optimize-Captured-BlockEntity-Lookup.patch b/patches/server/0283-Optimize-Captured-BlockEntity-Lookup.patch new file mode 100644 index 000000000000..4dedbb8d9c01 --- /dev/null +++ b/patches/server/0283-Optimize-Captured-BlockEntity-Lookup.patch @@ -0,0 +1,30 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Aikar +Date: Sat, 6 Apr 2019 10:16:48 -0400 +Subject: [PATCH] Optimize Captured BlockEntity Lookup + +upstream was doing a containsKey/get pattern, and always doing it at that. +that scenario is only even valid if were in the middle of a block place. + +Optimize to check if the captured list even has values in it, and also to +just do a get call since the value can never be null. + +diff --git a/src/main/java/net/minecraft/world/level/Level.java b/src/main/java/net/minecraft/world/level/Level.java +index 0f4b9b5d3e34b5e08f9ca2f78c5e8bcec9f5a85e..1e22ee380237a33c506316e3cfe3f6efb7f9ae4a 100644 +--- a/src/main/java/net/minecraft/world/level/Level.java ++++ b/src/main/java/net/minecraft/world/level/Level.java +@@ -865,9 +865,12 @@ public abstract class Level implements LevelAccessor, AutoCloseable { + + @Nullable + public BlockEntity getBlockEntity(BlockPos blockposition, boolean validate) { +- if (this.capturedTileEntities.containsKey(blockposition)) { +- return this.capturedTileEntities.get(blockposition); ++ // Paper start - Perf: Optimize capturedTileEntities lookup ++ net.minecraft.world.level.block.entity.BlockEntity blockEntity; ++ if (!this.capturedTileEntities.isEmpty() && (blockEntity = this.capturedTileEntities.get(blockposition)) != null) { ++ return blockEntity; + } ++ // Paper end - Perf: Optimize capturedTileEntities lookup + // CraftBukkit end + return this.isOutsideBuildHeight(blockposition) ? null : (!this.isClientSide && Thread.currentThread() != this.thread ? null : this.getChunkAt(blockposition).getBlockEntity(blockposition, LevelChunk.EntityCreationType.IMMEDIATE)); + } diff --git a/patches/server/0283-Server-Tick-Events.patch b/patches/server/0283-Server-Tick-Events.patch deleted file mode 100644 index 5c5d419332e1..000000000000 --- a/patches/server/0283-Server-Tick-Events.patch +++ /dev/null @@ -1,31 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Aikar -Date: Wed, 27 Mar 2019 22:48:45 -0400 -Subject: [PATCH] Server Tick Events - -Fires event at start and end of a server tick - -diff --git a/src/main/java/net/minecraft/server/MinecraftServer.java b/src/main/java/net/minecraft/server/MinecraftServer.java -index 8ecfd7a3daa99dabe796d28d27790fb8b45c628b..ddc3da84c5a55d2cd977fcdb18121351606a6b3c 100644 ---- a/src/main/java/net/minecraft/server/MinecraftServer.java -+++ b/src/main/java/net/minecraft/server/MinecraftServer.java -@@ -1433,6 +1433,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop +Date: Fri, 19 Apr 2019 12:41:13 -0500 +Subject: [PATCH] Mob Spawner API Enhancements + +== AT == +public net.minecraft.world.level.BaseSpawner isNearPlayer(Lnet/minecraft/world/level/Level;Lnet/minecraft/core/BlockPos;)Z +public net.minecraft.world.level.BaseSpawner delay(Lnet/minecraft/world/level/Level;Lnet/minecraft/core/BlockPos;)V +public net.minecraft.world.level.BaseSpawner setNextSpawnData(Lnet/minecraft/world/level/Level;Lnet/minecraft/core/BlockPos;Lnet/minecraft/world/level/SpawnData;)V + +diff --git a/src/main/java/net/minecraft/world/level/BaseSpawner.java b/src/main/java/net/minecraft/world/level/BaseSpawner.java +index 299137519dba07c8b2cb35437742b5d40eb9e5d2..56dbe701a93eb9f1309bec92e5d3b310860a4dc5 100644 +--- a/src/main/java/net/minecraft/world/level/BaseSpawner.java ++++ b/src/main/java/net/minecraft/world/level/BaseSpawner.java +@@ -237,7 +237,13 @@ public abstract class BaseSpawner { + } + + public void load(@Nullable Level world, BlockPos pos, CompoundTag nbt) { ++ // Paper start - use larger int if set ++ if (nbt.contains("Paper.Delay")) { ++ this.spawnDelay = nbt.getInt("Paper.Delay"); ++ } else { + this.spawnDelay = nbt.getShort("Delay"); ++ } ++ // Paper end + boolean flag = nbt.contains("SpawnData", 10); + + if (flag) { +@@ -260,9 +266,15 @@ public abstract class BaseSpawner { + this.spawnPotentials = SimpleWeightedRandomList.single(this.nextSpawnData != null ? this.nextSpawnData : new SpawnData()); + } + ++ // Paper start - use ints if set ++ if (nbt.contains("Paper.MinSpawnDelay", net.minecraft.nbt.Tag.TAG_ANY_NUMERIC)) { ++ this.minSpawnDelay = nbt.getInt("Paper.MinSpawnDelay"); ++ this.maxSpawnDelay = nbt.getInt("Paper.MaxSpawnDelay"); ++ this.spawnCount = nbt.getShort("SpawnCount"); ++ } else // Paper end + if (nbt.contains("MinSpawnDelay", 99)) { +- this.minSpawnDelay = nbt.getShort("MinSpawnDelay"); +- this.maxSpawnDelay = nbt.getShort("MaxSpawnDelay"); ++ this.minSpawnDelay = nbt.getInt("MinSpawnDelay"); // Paper - short -> int ++ this.maxSpawnDelay = nbt.getInt("MaxSpawnDelay"); // Paper - short -> int + this.spawnCount = nbt.getShort("SpawnCount"); + } + +@@ -279,9 +291,20 @@ public abstract class BaseSpawner { + } + + public CompoundTag save(CompoundTag nbt) { +- nbt.putShort("Delay", (short) this.spawnDelay); +- nbt.putShort("MinSpawnDelay", (short) this.minSpawnDelay); +- nbt.putShort("MaxSpawnDelay", (short) this.maxSpawnDelay); ++ // Paper start ++ if (spawnDelay > Short.MAX_VALUE) { ++ nbt.putInt("Paper.Delay", this.spawnDelay); ++ } ++ nbt.putShort("Delay", (short) Math.min(Short.MAX_VALUE, this.spawnDelay)); ++ ++ if (minSpawnDelay > Short.MAX_VALUE || maxSpawnDelay > Short.MAX_VALUE) { ++ nbt.putInt("Paper.MinSpawnDelay", this.minSpawnDelay); ++ nbt.putInt("Paper.MaxSpawnDelay", this.maxSpawnDelay); ++ } ++ ++ nbt.putShort("MinSpawnDelay", (short) Math.min(Short.MAX_VALUE, this.minSpawnDelay)); ++ nbt.putShort("MaxSpawnDelay", (short) Math.min(Short.MAX_VALUE, this.maxSpawnDelay)); ++ // Paper end + nbt.putShort("SpawnCount", (short) this.spawnCount); + nbt.putShort("MaxNearbyEntities", (short) this.maxNearbyEntities); + nbt.putShort("RequiredPlayerRange", (short) this.requiredPlayerRange); +diff --git a/src/main/java/org/bukkit/craftbukkit/block/CraftCreatureSpawner.java b/src/main/java/org/bukkit/craftbukkit/block/CraftCreatureSpawner.java +index 9b2b6697d0b64da2bc99dc646f552c2689d5a1fc..146dde200845abcbe11015dda2c826a1aa711e42 100644 +--- a/src/main/java/org/bukkit/craftbukkit/block/CraftCreatureSpawner.java ++++ b/src/main/java/org/bukkit/craftbukkit/block/CraftCreatureSpawner.java +@@ -29,7 +29,7 @@ import org.bukkit.craftbukkit.entity.CraftEntityType; + import org.bukkit.entity.EntitySnapshot; + import org.bukkit.entity.EntityType; + +-public class CraftCreatureSpawner extends CraftBlockEntityState implements CreatureSpawner { ++public class CraftCreatureSpawner extends CraftBlockEntityState implements CreatureSpawner, org.bukkit.craftbukkit.spawner.PaperSharedSpawnerLogic { // Paper - more spawner API + + public CraftCreatureSpawner(World world, SpawnerBlockEntity tileEntity) { + super(world, tileEntity); +@@ -291,4 +291,38 @@ public class CraftCreatureSpawner extends CraftBlockEntityState(nms.slotDropChances().entrySet().stream().collect(Collectors.toMap((entry) -> CraftEquipmentSlot.getSlot(entry.getKey()), Map.Entry::getValue))) + )).orElse(null); + } ++ ++ // Paper start - more spawner API ++ @Override ++ public boolean isActivated() { ++ requirePlaced(); ++ return org.bukkit.craftbukkit.spawner.PaperSharedSpawnerLogic.super.isActivated(); ++ } ++ ++ @Override ++ public void resetTimer() { ++ requirePlaced(); ++ org.bukkit.craftbukkit.spawner.PaperSharedSpawnerLogic.super.resetTimer(); ++ } ++ ++ @Override ++ public void setNextSpawnData(final SpawnData spawnData) { ++ this.getSpawner().setNextSpawnData(this.isPlaced() ? this.getInternalWorld() : null, this.getInternalPosition(), spawnData); ++ } ++ ++ @Override ++ public BaseSpawner getSpawner() { ++ return this.getSnapshot().getSpawner(); ++ } ++ ++ @Override ++ public net.minecraft.core.BlockPos getInternalPosition() { ++ return this.getPosition(); ++ } ++ ++ @Override ++ public net.minecraft.world.level.Level getInternalWorld() { ++ return this.world.getHandle(); ++ } ++ // Paper end - more spawner API + } +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftMinecartMobSpawner.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftMinecartMobSpawner.java +index 72e34dbfadcebb26a0707ce095b0d270f4d1d97c..e8ece01669373ecf6552d33b2ed72668524e2650 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftMinecartMobSpawner.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftMinecartMobSpawner.java +@@ -16,7 +16,7 @@ import org.bukkit.entity.EntitySnapshot; + import org.bukkit.entity.EntityType; + import org.bukkit.entity.minecart.SpawnerMinecart; + +-final class CraftMinecartMobSpawner extends CraftMinecart implements SpawnerMinecart { ++final class CraftMinecartMobSpawner extends CraftMinecart implements SpawnerMinecart, org.bukkit.craftbukkit.spawner.PaperSharedSpawnerLogic { // Paper - more spawner API + CraftMinecartMobSpawner(CraftServer server, MinecartSpawner entity) { + super(server, entity); + } +@@ -171,4 +171,21 @@ final class CraftMinecartMobSpawner extends CraftMinecart implements SpawnerMine + public String toString() { + return "CraftMinecartMobSpawner"; + } ++ ++ // Paper start - more spawner API ++ @Override ++ public net.minecraft.world.level.BaseSpawner getSpawner() { ++ return this.getHandle().getSpawner(); ++ } ++ ++ @Override ++ public net.minecraft.world.level.Level getInternalWorld() { ++ return this.getHandle().level(); ++ } ++ ++ @Override ++ public net.minecraft.core.BlockPos getInternalPosition() { ++ return this.getHandle().blockPosition(); ++ } ++ // Paper end - more spawner API + } +diff --git a/src/main/java/org/bukkit/craftbukkit/spawner/PaperSharedSpawnerLogic.java b/src/main/java/org/bukkit/craftbukkit/spawner/PaperSharedSpawnerLogic.java +new file mode 100644 +index 0000000000000000000000000000000000000000..b1d08dc4c4257a6f5cd70dfdddade58ff7eedb4b +--- /dev/null ++++ b/src/main/java/org/bukkit/craftbukkit/spawner/PaperSharedSpawnerLogic.java +@@ -0,0 +1,56 @@ ++package org.bukkit.craftbukkit.spawner; ++ ++import com.google.common.base.Preconditions; ++import java.util.Optional; ++import net.minecraft.core.BlockPos; ++import net.minecraft.core.registries.BuiltInRegistries; ++import net.minecraft.nbt.CompoundTag; ++import net.minecraft.world.entity.Entity; ++import net.minecraft.world.entity.EntityType; ++import net.minecraft.world.level.BaseSpawner; ++import net.minecraft.world.level.Level; ++import net.minecraft.world.level.SpawnData; ++import org.bukkit.craftbukkit.inventory.CraftItemStack; ++import org.bukkit.inventory.ItemStack; ++import org.bukkit.spawner.Spawner; ++ ++/** ++ * A common parent interface for both the {@link org.bukkit.craftbukkit.block.CraftCreatureSpawner} and minecart mob spawner. ++ */ ++public interface PaperSharedSpawnerLogic extends Spawner { ++ ++ BaseSpawner getSpawner(); ++ ++ Level getInternalWorld(); ++ ++ BlockPos getInternalPosition(); ++ ++ default boolean isActivated() { ++ return this.getSpawner().isNearPlayer(this.getInternalWorld(), this.getInternalPosition()); ++ } ++ ++ default void resetTimer() { ++ this.getSpawner().delay(this.getInternalWorld(), this.getInternalPosition()); ++ } ++ ++ default void setNextSpawnData(SpawnData spawnData) { ++ this.getSpawner().setNextSpawnData(this.getInternalWorld(), this.getInternalPosition(), spawnData); ++ } ++ ++ default void setSpawnedItem(final ItemStack itemStack) { ++ Preconditions.checkArgument(itemStack != null && !itemStack.getType().isAir(), "spawners cannot spawn air"); ++ ++ final net.minecraft.world.item.ItemStack item = CraftItemStack.asNMSCopy(itemStack); ++ final CompoundTag entity = new CompoundTag(); ++ entity.putString(Entity.ID_TAG, BuiltInRegistries.ENTITY_TYPE.getKey(EntityType.ITEM).toString()); ++ entity.put("Item", item.save(this.getInternalWorld().registryAccess())); ++ ++ this.setNextSpawnData( ++ new net.minecraft.world.level.SpawnData( ++ entity, ++ java.util.Optional.empty(), ++ Optional.ofNullable(this.getSpawner().nextSpawnData).flatMap(SpawnData::equipment) ++ ) ++ ); ++ } ++} diff --git a/patches/server/0284-PlayerDeathEvent-getItemsToKeep.patch b/patches/server/0284-PlayerDeathEvent-getItemsToKeep.patch deleted file mode 100644 index 4284b5ff3b28..000000000000 --- a/patches/server/0284-PlayerDeathEvent-getItemsToKeep.patch +++ /dev/null @@ -1,77 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Aikar -Date: Wed, 27 Mar 2019 23:01:33 -0400 -Subject: [PATCH] PlayerDeathEvent#getItemsToKeep - -Exposes a mutable array on items a player should keep on death - -Example Usage: https://gist.github.com/aikar/5bb202de6057a051a950ce1f29feb0b4 - -== AT == -public net.minecraft.world.entity.player.Inventory compartments - -diff --git a/src/main/java/net/minecraft/server/level/ServerPlayer.java b/src/main/java/net/minecraft/server/level/ServerPlayer.java -index ad2b8ea068469f2b0597c0e0436ad3c94dcb62ea..cba123f40d72ebd39305a76694be508510997d1e 100644 ---- a/src/main/java/net/minecraft/server/level/ServerPlayer.java -+++ b/src/main/java/net/minecraft/server/level/ServerPlayer.java -@@ -897,6 +897,46 @@ public class ServerPlayer extends net.minecraft.world.entity.player.Player { - }); - } - -+ // Paper start - PlayerDeathEvent#getItemsToKeep -+ private static void processKeep(org.bukkit.event.entity.PlayerDeathEvent event, NonNullList inv) { -+ List itemsToKeep = event.getItemsToKeep(); -+ if (inv == null) { -+ // remainder of items left in toKeep - plugin added stuff on death that wasn't in the initial loot? -+ if (!itemsToKeep.isEmpty()) { -+ for (org.bukkit.inventory.ItemStack itemStack : itemsToKeep) { -+ event.getEntity().getInventory().addItem(itemStack); -+ } -+ } -+ -+ return; -+ } -+ -+ for (int i = 0; i < inv.size(); ++i) { -+ ItemStack item = inv.get(i); -+ if (EnchantmentHelper.has(item, EnchantmentEffectComponents.PREVENT_EQUIPMENT_DROP) || itemsToKeep.isEmpty() || item.isEmpty()) { -+ inv.set(i, ItemStack.EMPTY); -+ continue; -+ } -+ -+ final org.bukkit.inventory.ItemStack bukkitStack = item.getBukkitStack(); -+ boolean keep = false; -+ final Iterator iterator = itemsToKeep.iterator(); -+ while (iterator.hasNext()) { -+ final org.bukkit.inventory.ItemStack itemStack = iterator.next(); -+ if (bukkitStack.equals(itemStack)) { -+ iterator.remove(); -+ keep = true; -+ break; -+ } -+ } -+ -+ if (!keep) { -+ inv.set(i, ItemStack.EMPTY); -+ } -+ } -+ } -+ // Paper end - PlayerDeathEvent#getItemsToKeep -+ - @Override - public void die(DamageSource damageSource) { - // this.gameEvent(GameEvent.ENTITY_DIE); // Paper - move below event cancellation check -@@ -981,7 +1021,12 @@ public class ServerPlayer extends net.minecraft.world.entity.player.Player { - this.dropExperience(damageSource.getEntity()); - // we clean the player's inventory after the EntityDeathEvent is called so plugins can get the exact state of the inventory. - if (!event.getKeepInventory()) { -- this.getInventory().clearContent(); -+ // Paper start - PlayerDeathEvent#getItemsToKeep -+ for (NonNullList inv : this.getInventory().compartments) { -+ processKeep(event, inv); -+ } -+ processKeep(event, null); -+ // Paper end - PlayerDeathEvent#getItemsToKeep - } - - this.setCamera(this); // Remove spectated target diff --git a/patches/server/0285-Fix-CB-call-to-changed-postToMainThread-method.patch b/patches/server/0285-Fix-CB-call-to-changed-postToMainThread-method.patch new file mode 100644 index 000000000000..9c45be140fc5 --- /dev/null +++ b/patches/server/0285-Fix-CB-call-to-changed-postToMainThread-method.patch @@ -0,0 +1,19 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Shane Freeder +Date: Fri, 10 May 2019 18:38:19 +0100 +Subject: [PATCH] Fix CB call to changed postToMainThread method + + +diff --git a/src/main/java/net/minecraft/server/network/ServerCommonPacketListenerImpl.java b/src/main/java/net/minecraft/server/network/ServerCommonPacketListenerImpl.java +index b36528ad18c8603994c6a821b19b99581717b44c..b9fbaddcc8239bf737fdea51790f678306e511eb 100644 +--- a/src/main/java/net/minecraft/server/network/ServerCommonPacketListenerImpl.java ++++ b/src/main/java/net/minecraft/server/network/ServerCommonPacketListenerImpl.java +@@ -378,7 +378,7 @@ public abstract class ServerCommonPacketListenerImpl implements ServerCommonPack + + Objects.requireNonNull(this.connection); + // CraftBukkit - Don't wait +- minecraftserver.wrapRunnable(networkmanager::handleDisconnection); ++ minecraftserver.scheduleOnMain(networkmanager::handleDisconnection); // Paper + } + + protected boolean isSingleplayerOwner() { diff --git a/patches/server/0285-Optimize-Captured-BlockEntity-Lookup.patch b/patches/server/0285-Optimize-Captured-BlockEntity-Lookup.patch deleted file mode 100644 index 98f12d68db5c..000000000000 --- a/patches/server/0285-Optimize-Captured-BlockEntity-Lookup.patch +++ /dev/null @@ -1,30 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Aikar -Date: Sat, 6 Apr 2019 10:16:48 -0400 -Subject: [PATCH] Optimize Captured BlockEntity Lookup - -upstream was doing a containsKey/get pattern, and always doing it at that. -that scenario is only even valid if were in the middle of a block place. - -Optimize to check if the captured list even has values in it, and also to -just do a get call since the value can never be null. - -diff --git a/src/main/java/net/minecraft/world/level/Level.java b/src/main/java/net/minecraft/world/level/Level.java -index e414d417dc9226339f0d6c32406772a2eda92940..ba914a54d549b45bdbede2a9850aab34c3640c59 100644 ---- a/src/main/java/net/minecraft/world/level/Level.java -+++ b/src/main/java/net/minecraft/world/level/Level.java -@@ -929,9 +929,12 @@ public abstract class Level implements LevelAccessor, AutoCloseable { - - @Nullable - public BlockEntity getBlockEntity(BlockPos blockposition, boolean validate) { -- if (this.capturedTileEntities.containsKey(blockposition)) { -- return this.capturedTileEntities.get(blockposition); -+ // Paper start - Perf: Optimize capturedTileEntities lookup -+ net.minecraft.world.level.block.entity.BlockEntity blockEntity; -+ if (!this.capturedTileEntities.isEmpty() && (blockEntity = this.capturedTileEntities.get(blockposition)) != null) { -+ return blockEntity; - } -+ // Paper end - Perf: Optimize capturedTileEntities lookup - // CraftBukkit end - return this.isOutsideBuildHeight(blockposition) ? null : (!this.isClientSide && Thread.currentThread() != this.thread ? null : this.getChunkAt(blockposition).getBlockEntity(blockposition, LevelChunk.EntityCreationType.IMMEDIATE)); - } diff --git a/patches/server/0286-Fix-sounds-when-item-frames-are-modified-MC-123450.patch b/patches/server/0286-Fix-sounds-when-item-frames-are-modified-MC-123450.patch new file mode 100644 index 000000000000..d0f13edcc480 --- /dev/null +++ b/patches/server/0286-Fix-sounds-when-item-frames-are-modified-MC-123450.patch @@ -0,0 +1,20 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Phoenix616 +Date: Sat, 27 Apr 2019 20:00:43 +0100 +Subject: [PATCH] Fix sounds when item frames are modified (MC-123450) + +This also fixes the adding sound playing when the item frame direction is changed. + +diff --git a/src/main/java/net/minecraft/world/entity/decoration/ItemFrame.java b/src/main/java/net/minecraft/world/entity/decoration/ItemFrame.java +index 7dfbcb2ed831cff5d002d79fb81514e6abf587a4..c0deabf9d86d086fbd8cbaac5a02badf5c01c870 100644 +--- a/src/main/java/net/minecraft/world/entity/decoration/ItemFrame.java ++++ b/src/main/java/net/minecraft/world/entity/decoration/ItemFrame.java +@@ -290,7 +290,7 @@ public class ItemFrame extends HangingEntity { + + this.onItemChanged(itemstack); + this.getEntityData().set(ItemFrame.DATA_ITEM, itemstack); +- if (!itemstack.isEmpty() && playSound) { // CraftBukkit ++ if (!itemstack.isEmpty() && flag && playSound) { // CraftBukkit // Paper - only play sound when update flag is set + this.playSound(this.getAddItemSound(), 1.0F, 1.0F); + } + diff --git a/patches/server/0286-Mob-Spawner-API-Enhancements.patch b/patches/server/0286-Mob-Spawner-API-Enhancements.patch deleted file mode 100644 index 9f35b91c5a99..000000000000 --- a/patches/server/0286-Mob-Spawner-API-Enhancements.patch +++ /dev/null @@ -1,219 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: William Blake Galbreath -Date: Fri, 19 Apr 2019 12:41:13 -0500 -Subject: [PATCH] Mob Spawner API Enhancements - -== AT == -public net.minecraft.world.level.BaseSpawner isNearPlayer(Lnet/minecraft/world/level/Level;Lnet/minecraft/core/BlockPos;)Z -public net.minecraft.world.level.BaseSpawner delay(Lnet/minecraft/world/level/Level;Lnet/minecraft/core/BlockPos;)V -public net.minecraft.world.level.BaseSpawner setNextSpawnData(Lnet/minecraft/world/level/Level;Lnet/minecraft/core/BlockPos;Lnet/minecraft/world/level/SpawnData;)V - -diff --git a/src/main/java/net/minecraft/world/level/BaseSpawner.java b/src/main/java/net/minecraft/world/level/BaseSpawner.java -index 1d0964a7f544735a0213d5c7832c71f53db139a9..b90127f9f805fdb5bb43a4b8ad2b10499b0b6b78 100644 ---- a/src/main/java/net/minecraft/world/level/BaseSpawner.java -+++ b/src/main/java/net/minecraft/world/level/BaseSpawner.java -@@ -237,7 +237,13 @@ public abstract class BaseSpawner { - } - - public void load(@Nullable Level world, BlockPos pos, CompoundTag nbt) { -+ // Paper start - use larger int if set -+ if (nbt.contains("Paper.Delay")) { -+ this.spawnDelay = nbt.getInt("Paper.Delay"); -+ } else { - this.spawnDelay = nbt.getShort("Delay"); -+ } -+ // Paper end - boolean flag = nbt.contains("SpawnData", 10); - - if (flag) { -@@ -260,9 +266,15 @@ public abstract class BaseSpawner { - this.spawnPotentials = SimpleWeightedRandomList.single(this.nextSpawnData != null ? this.nextSpawnData : new SpawnData()); - } - -+ // Paper start - use ints if set -+ if (nbt.contains("Paper.MinSpawnDelay", net.minecraft.nbt.Tag.TAG_ANY_NUMERIC)) { -+ this.minSpawnDelay = nbt.getInt("Paper.MinSpawnDelay"); -+ this.maxSpawnDelay = nbt.getInt("Paper.MaxSpawnDelay"); -+ this.spawnCount = nbt.getShort("SpawnCount"); -+ } else // Paper end - if (nbt.contains("MinSpawnDelay", 99)) { -- this.minSpawnDelay = nbt.getShort("MinSpawnDelay"); -- this.maxSpawnDelay = nbt.getShort("MaxSpawnDelay"); -+ this.minSpawnDelay = nbt.getInt("MinSpawnDelay"); // Paper - short -> int -+ this.maxSpawnDelay = nbt.getInt("MaxSpawnDelay"); // Paper - short -> int - this.spawnCount = nbt.getShort("SpawnCount"); - } - -@@ -279,9 +291,20 @@ public abstract class BaseSpawner { - } - - public CompoundTag save(CompoundTag nbt) { -- nbt.putShort("Delay", (short) this.spawnDelay); -- nbt.putShort("MinSpawnDelay", (short) this.minSpawnDelay); -- nbt.putShort("MaxSpawnDelay", (short) this.maxSpawnDelay); -+ // Paper start -+ if (spawnDelay > Short.MAX_VALUE) { -+ nbt.putInt("Paper.Delay", this.spawnDelay); -+ } -+ nbt.putShort("Delay", (short) Math.min(Short.MAX_VALUE, this.spawnDelay)); -+ -+ if (minSpawnDelay > Short.MAX_VALUE || maxSpawnDelay > Short.MAX_VALUE) { -+ nbt.putInt("Paper.MinSpawnDelay", this.minSpawnDelay); -+ nbt.putInt("Paper.MaxSpawnDelay", this.maxSpawnDelay); -+ } -+ -+ nbt.putShort("MinSpawnDelay", (short) Math.min(Short.MAX_VALUE, this.minSpawnDelay)); -+ nbt.putShort("MaxSpawnDelay", (short) Math.min(Short.MAX_VALUE, this.maxSpawnDelay)); -+ // Paper end - nbt.putShort("SpawnCount", (short) this.spawnCount); - nbt.putShort("MaxNearbyEntities", (short) this.maxNearbyEntities); - nbt.putShort("RequiredPlayerRange", (short) this.requiredPlayerRange); -diff --git a/src/main/java/org/bukkit/craftbukkit/block/CraftCreatureSpawner.java b/src/main/java/org/bukkit/craftbukkit/block/CraftCreatureSpawner.java -index 9b2b6697d0b64da2bc99dc646f552c2689d5a1fc..146dde200845abcbe11015dda2c826a1aa711e42 100644 ---- a/src/main/java/org/bukkit/craftbukkit/block/CraftCreatureSpawner.java -+++ b/src/main/java/org/bukkit/craftbukkit/block/CraftCreatureSpawner.java -@@ -29,7 +29,7 @@ import org.bukkit.craftbukkit.entity.CraftEntityType; - import org.bukkit.entity.EntitySnapshot; - import org.bukkit.entity.EntityType; - --public class CraftCreatureSpawner extends CraftBlockEntityState implements CreatureSpawner { -+public class CraftCreatureSpawner extends CraftBlockEntityState implements CreatureSpawner, org.bukkit.craftbukkit.spawner.PaperSharedSpawnerLogic { // Paper - more spawner API - - public CraftCreatureSpawner(World world, SpawnerBlockEntity tileEntity) { - super(world, tileEntity); -@@ -291,4 +291,38 @@ public class CraftCreatureSpawner extends CraftBlockEntityState(nms.slotDropChances().entrySet().stream().collect(Collectors.toMap((entry) -> CraftEquipmentSlot.getSlot(entry.getKey()), Map.Entry::getValue))) - )).orElse(null); - } -+ -+ // Paper start - more spawner API -+ @Override -+ public boolean isActivated() { -+ requirePlaced(); -+ return org.bukkit.craftbukkit.spawner.PaperSharedSpawnerLogic.super.isActivated(); -+ } -+ -+ @Override -+ public void resetTimer() { -+ requirePlaced(); -+ org.bukkit.craftbukkit.spawner.PaperSharedSpawnerLogic.super.resetTimer(); -+ } -+ -+ @Override -+ public void setNextSpawnData(final SpawnData spawnData) { -+ this.getSpawner().setNextSpawnData(this.isPlaced() ? this.getInternalWorld() : null, this.getInternalPosition(), spawnData); -+ } -+ -+ @Override -+ public BaseSpawner getSpawner() { -+ return this.getSnapshot().getSpawner(); -+ } -+ -+ @Override -+ public net.minecraft.core.BlockPos getInternalPosition() { -+ return this.getPosition(); -+ } -+ -+ @Override -+ public net.minecraft.world.level.Level getInternalWorld() { -+ return this.world.getHandle(); -+ } -+ // Paper end - more spawner API - } -diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftMinecartMobSpawner.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftMinecartMobSpawner.java -index 72e34dbfadcebb26a0707ce095b0d270f4d1d97c..e8ece01669373ecf6552d33b2ed72668524e2650 100644 ---- a/src/main/java/org/bukkit/craftbukkit/entity/CraftMinecartMobSpawner.java -+++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftMinecartMobSpawner.java -@@ -16,7 +16,7 @@ import org.bukkit.entity.EntitySnapshot; - import org.bukkit.entity.EntityType; - import org.bukkit.entity.minecart.SpawnerMinecart; - --final class CraftMinecartMobSpawner extends CraftMinecart implements SpawnerMinecart { -+final class CraftMinecartMobSpawner extends CraftMinecart implements SpawnerMinecart, org.bukkit.craftbukkit.spawner.PaperSharedSpawnerLogic { // Paper - more spawner API - CraftMinecartMobSpawner(CraftServer server, MinecartSpawner entity) { - super(server, entity); - } -@@ -171,4 +171,21 @@ final class CraftMinecartMobSpawner extends CraftMinecart implements SpawnerMine - public String toString() { - return "CraftMinecartMobSpawner"; - } -+ -+ // Paper start - more spawner API -+ @Override -+ public net.minecraft.world.level.BaseSpawner getSpawner() { -+ return this.getHandle().getSpawner(); -+ } -+ -+ @Override -+ public net.minecraft.world.level.Level getInternalWorld() { -+ return this.getHandle().level(); -+ } -+ -+ @Override -+ public net.minecraft.core.BlockPos getInternalPosition() { -+ return this.getHandle().blockPosition(); -+ } -+ // Paper end - more spawner API - } -diff --git a/src/main/java/org/bukkit/craftbukkit/spawner/PaperSharedSpawnerLogic.java b/src/main/java/org/bukkit/craftbukkit/spawner/PaperSharedSpawnerLogic.java -new file mode 100644 -index 0000000000000000000000000000000000000000..b1d08dc4c4257a6f5cd70dfdddade58ff7eedb4b ---- /dev/null -+++ b/src/main/java/org/bukkit/craftbukkit/spawner/PaperSharedSpawnerLogic.java -@@ -0,0 +1,56 @@ -+package org.bukkit.craftbukkit.spawner; -+ -+import com.google.common.base.Preconditions; -+import java.util.Optional; -+import net.minecraft.core.BlockPos; -+import net.minecraft.core.registries.BuiltInRegistries; -+import net.minecraft.nbt.CompoundTag; -+import net.minecraft.world.entity.Entity; -+import net.minecraft.world.entity.EntityType; -+import net.minecraft.world.level.BaseSpawner; -+import net.minecraft.world.level.Level; -+import net.minecraft.world.level.SpawnData; -+import org.bukkit.craftbukkit.inventory.CraftItemStack; -+import org.bukkit.inventory.ItemStack; -+import org.bukkit.spawner.Spawner; -+ -+/** -+ * A common parent interface for both the {@link org.bukkit.craftbukkit.block.CraftCreatureSpawner} and minecart mob spawner. -+ */ -+public interface PaperSharedSpawnerLogic extends Spawner { -+ -+ BaseSpawner getSpawner(); -+ -+ Level getInternalWorld(); -+ -+ BlockPos getInternalPosition(); -+ -+ default boolean isActivated() { -+ return this.getSpawner().isNearPlayer(this.getInternalWorld(), this.getInternalPosition()); -+ } -+ -+ default void resetTimer() { -+ this.getSpawner().delay(this.getInternalWorld(), this.getInternalPosition()); -+ } -+ -+ default void setNextSpawnData(SpawnData spawnData) { -+ this.getSpawner().setNextSpawnData(this.getInternalWorld(), this.getInternalPosition(), spawnData); -+ } -+ -+ default void setSpawnedItem(final ItemStack itemStack) { -+ Preconditions.checkArgument(itemStack != null && !itemStack.getType().isAir(), "spawners cannot spawn air"); -+ -+ final net.minecraft.world.item.ItemStack item = CraftItemStack.asNMSCopy(itemStack); -+ final CompoundTag entity = new CompoundTag(); -+ entity.putString(Entity.ID_TAG, BuiltInRegistries.ENTITY_TYPE.getKey(EntityType.ITEM).toString()); -+ entity.put("Item", item.save(this.getInternalWorld().registryAccess())); -+ -+ this.setNextSpawnData( -+ new net.minecraft.world.level.SpawnData( -+ entity, -+ java.util.Optional.empty(), -+ Optional.ofNullable(this.getSpawner().nextSpawnData).flatMap(SpawnData::equipment) -+ ) -+ ); -+ } -+} diff --git a/patches/server/0287-Fix-CB-call-to-changed-postToMainThread-method.patch b/patches/server/0287-Fix-CB-call-to-changed-postToMainThread-method.patch deleted file mode 100644 index b135e5922198..000000000000 --- a/patches/server/0287-Fix-CB-call-to-changed-postToMainThread-method.patch +++ /dev/null @@ -1,19 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Shane Freeder -Date: Fri, 10 May 2019 18:38:19 +0100 -Subject: [PATCH] Fix CB call to changed postToMainThread method - - -diff --git a/src/main/java/net/minecraft/server/network/ServerCommonPacketListenerImpl.java b/src/main/java/net/minecraft/server/network/ServerCommonPacketListenerImpl.java -index 9b1df397e1d2d8ca04b34012808be2110526f401..4f62abfe62c951b7a1df36ece34747100d6a4ff7 100644 ---- a/src/main/java/net/minecraft/server/network/ServerCommonPacketListenerImpl.java -+++ b/src/main/java/net/minecraft/server/network/ServerCommonPacketListenerImpl.java -@@ -371,7 +371,7 @@ public abstract class ServerCommonPacketListenerImpl implements ServerCommonPack - - Objects.requireNonNull(this.connection); - // CraftBukkit - Don't wait -- minecraftserver.wrapRunnable(networkmanager::handleDisconnection); -+ minecraftserver.scheduleOnMain(networkmanager::handleDisconnection); // Paper - } - - protected boolean isSingleplayerOwner() { diff --git a/patches/server/0287-Implement-CraftBlockSoundGroup.patch b/patches/server/0287-Implement-CraftBlockSoundGroup.patch new file mode 100644 index 000000000000..84bba66813f3 --- /dev/null +++ b/patches/server/0287-Implement-CraftBlockSoundGroup.patch @@ -0,0 +1,72 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: simpleauthority +Date: Tue, 28 May 2019 03:48:51 -0700 +Subject: [PATCH] Implement CraftBlockSoundGroup + + +diff --git a/src/main/java/com/destroystokyo/paper/block/CraftBlockSoundGroup.java b/src/main/java/com/destroystokyo/paper/block/CraftBlockSoundGroup.java +new file mode 100644 +index 0000000000000000000000000000000000000000..3913c407a3bfa7dfa4a5e374a5e792962fcdafe6 +--- /dev/null ++++ b/src/main/java/com/destroystokyo/paper/block/CraftBlockSoundGroup.java +@@ -0,0 +1,39 @@ ++package com.destroystokyo.paper.block; ++ ++import net.minecraft.world.level.block.SoundType; ++import org.bukkit.Sound; ++import org.bukkit.craftbukkit.CraftSound; ++ ++@Deprecated(forRemoval = true) ++public class CraftBlockSoundGroup implements BlockSoundGroup { ++ private final SoundType soundEffectType; ++ ++ public CraftBlockSoundGroup(SoundType soundEffectType) { ++ this.soundEffectType = soundEffectType; ++ } ++ ++ @Override ++ public Sound getBreakSound() { ++ return CraftSound.minecraftToBukkit(soundEffectType.getBreakSound()); ++ } ++ ++ @Override ++ public Sound getStepSound() { ++ return CraftSound.minecraftToBukkit(soundEffectType.getStepSound()); ++ } ++ ++ @Override ++ public Sound getPlaceSound() { ++ return CraftSound.minecraftToBukkit(soundEffectType.getPlaceSound()); ++ } ++ ++ @Override ++ public Sound getHitSound() { ++ return CraftSound.minecraftToBukkit(soundEffectType.getHitSound()); ++ } ++ ++ @Override ++ public Sound getFallSound() { ++ return CraftSound.minecraftToBukkit(soundEffectType.getFallSound()); ++ } ++} +diff --git a/src/main/java/org/bukkit/craftbukkit/block/CraftBlock.java b/src/main/java/org/bukkit/craftbukkit/block/CraftBlock.java +index e163ff416f19766132d73fff2c8eb7f3f098f8c6..e5ac2d79b21db4e9a92b739e39d41a22deee175b 100644 +--- a/src/main/java/org/bukkit/craftbukkit/block/CraftBlock.java ++++ b/src/main/java/org/bukkit/craftbukkit/block/CraftBlock.java +@@ -622,4 +622,16 @@ public class CraftBlock implements Block { + public String getTranslationKey() { + return this.getNMS().getBlock().getDescriptionId(); + } ++ ++ // Paper start ++ @Override ++ public com.destroystokyo.paper.block.BlockSoundGroup getSoundGroup() { ++ return new com.destroystokyo.paper.block.CraftBlockSoundGroup(getNMS().getBlock().defaultBlockState().getSoundType()); ++ } ++ ++ @Override ++ public org.bukkit.SoundGroup getBlockSoundGroup() { ++ return org.bukkit.craftbukkit.CraftSoundGroup.getSoundGroup(this.getNMS().getSoundType()); ++ } ++ // Paper end + } diff --git a/patches/server/0288-Expose-the-internal-current-tick.patch b/patches/server/0288-Expose-the-internal-current-tick.patch new file mode 100644 index 000000000000..f9e040e4f267 --- /dev/null +++ b/patches/server/0288-Expose-the-internal-current-tick.patch @@ -0,0 +1,21 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: William Blake Galbreath +Date: Sat, 20 Apr 2019 19:47:34 -0500 +Subject: [PATCH] Expose the internal current tick + + +diff --git a/src/main/java/org/bukkit/craftbukkit/CraftServer.java b/src/main/java/org/bukkit/craftbukkit/CraftServer.java +index e7dbb7a6e0e6bb9c42e72531f4cf337abb7090ee..90dc5b52c07b40c317e13eebebf84dfe9fefde27 100644 +--- a/src/main/java/org/bukkit/craftbukkit/CraftServer.java ++++ b/src/main/java/org/bukkit/craftbukkit/CraftServer.java +@@ -2933,5 +2933,10 @@ public final class CraftServer implements Server { + profile.getGameProfile().getProperties().putAll(((CraftPlayer) player).getHandle().getGameProfile().getProperties()); + return profile; + } ++ ++ @Override ++ public int getCurrentTick() { ++ return net.minecraft.server.MinecraftServer.currentTick; ++ } + // Paper end + } diff --git a/patches/server/0288-Fix-sounds-when-item-frames-are-modified-MC-123450.patch b/patches/server/0288-Fix-sounds-when-item-frames-are-modified-MC-123450.patch deleted file mode 100644 index 83a7d7d541cc..000000000000 --- a/patches/server/0288-Fix-sounds-when-item-frames-are-modified-MC-123450.patch +++ /dev/null @@ -1,20 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Phoenix616 -Date: Sat, 27 Apr 2019 20:00:43 +0100 -Subject: [PATCH] Fix sounds when item frames are modified (MC-123450) - -This also fixes the adding sound playing when the item frame direction is changed. - -diff --git a/src/main/java/net/minecraft/world/entity/decoration/ItemFrame.java b/src/main/java/net/minecraft/world/entity/decoration/ItemFrame.java -index 84dcd662981b1eeb03128e7717f6af44c2b9cff6..fdb6898519acfb27baf25d8bbad2013956c1361f 100644 ---- a/src/main/java/net/minecraft/world/entity/decoration/ItemFrame.java -+++ b/src/main/java/net/minecraft/world/entity/decoration/ItemFrame.java -@@ -278,7 +278,7 @@ public class ItemFrame extends HangingEntity { - - this.onItemChanged(itemstack); - this.getEntityData().set(ItemFrame.DATA_ITEM, itemstack); -- if (!itemstack.isEmpty() && playSound) { // CraftBukkit -+ if (!itemstack.isEmpty() && flag && playSound) { // CraftBukkit // Paper - only play sound when update flag is set - this.playSound(this.getAddItemSound(), 1.0F, 1.0F); - } - diff --git a/patches/server/0289-Implement-CraftBlockSoundGroup.patch b/patches/server/0289-Implement-CraftBlockSoundGroup.patch deleted file mode 100644 index 9bf16806b3a6..000000000000 --- a/patches/server/0289-Implement-CraftBlockSoundGroup.patch +++ /dev/null @@ -1,72 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: simpleauthority -Date: Tue, 28 May 2019 03:48:51 -0700 -Subject: [PATCH] Implement CraftBlockSoundGroup - - -diff --git a/src/main/java/com/destroystokyo/paper/block/CraftBlockSoundGroup.java b/src/main/java/com/destroystokyo/paper/block/CraftBlockSoundGroup.java -new file mode 100644 -index 0000000000000000000000000000000000000000..3913c407a3bfa7dfa4a5e374a5e792962fcdafe6 ---- /dev/null -+++ b/src/main/java/com/destroystokyo/paper/block/CraftBlockSoundGroup.java -@@ -0,0 +1,39 @@ -+package com.destroystokyo.paper.block; -+ -+import net.minecraft.world.level.block.SoundType; -+import org.bukkit.Sound; -+import org.bukkit.craftbukkit.CraftSound; -+ -+@Deprecated(forRemoval = true) -+public class CraftBlockSoundGroup implements BlockSoundGroup { -+ private final SoundType soundEffectType; -+ -+ public CraftBlockSoundGroup(SoundType soundEffectType) { -+ this.soundEffectType = soundEffectType; -+ } -+ -+ @Override -+ public Sound getBreakSound() { -+ return CraftSound.minecraftToBukkit(soundEffectType.getBreakSound()); -+ } -+ -+ @Override -+ public Sound getStepSound() { -+ return CraftSound.minecraftToBukkit(soundEffectType.getStepSound()); -+ } -+ -+ @Override -+ public Sound getPlaceSound() { -+ return CraftSound.minecraftToBukkit(soundEffectType.getPlaceSound()); -+ } -+ -+ @Override -+ public Sound getHitSound() { -+ return CraftSound.minecraftToBukkit(soundEffectType.getHitSound()); -+ } -+ -+ @Override -+ public Sound getFallSound() { -+ return CraftSound.minecraftToBukkit(soundEffectType.getFallSound()); -+ } -+} -diff --git a/src/main/java/org/bukkit/craftbukkit/block/CraftBlock.java b/src/main/java/org/bukkit/craftbukkit/block/CraftBlock.java -index 013298c424025cd88f15d61e50d196f70fa4c58b..d4e14ac1514e2d8b87b4667a91c90eded3ba6636 100644 ---- a/src/main/java/org/bukkit/craftbukkit/block/CraftBlock.java -+++ b/src/main/java/org/bukkit/craftbukkit/block/CraftBlock.java -@@ -622,4 +622,16 @@ public class CraftBlock implements Block { - public String getTranslationKey() { - return this.getNMS().getBlock().getDescriptionId(); - } -+ -+ // Paper start -+ @Override -+ public com.destroystokyo.paper.block.BlockSoundGroup getSoundGroup() { -+ return new com.destroystokyo.paper.block.CraftBlockSoundGroup(getNMS().getBlock().defaultBlockState().getSoundType()); -+ } -+ -+ @Override -+ public org.bukkit.SoundGroup getBlockSoundGroup() { -+ return org.bukkit.craftbukkit.CraftSoundGroup.getSoundGroup(this.getNMS().getSoundType()); -+ } -+ // Paper end - } diff --git a/patches/server/0289-Show-blockstate-location-if-we-failed-to-read-it.patch b/patches/server/0289-Show-blockstate-location-if-we-failed-to-read-it.patch new file mode 100644 index 000000000000..55deba82c560 --- /dev/null +++ b/patches/server/0289-Show-blockstate-location-if-we-failed-to-read-it.patch @@ -0,0 +1,33 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Spottedleaf +Date: Sat, 15 Jun 2019 10:28:25 -0700 +Subject: [PATCH] Show blockstate location if we failed to read it + + +diff --git a/src/main/java/org/bukkit/craftbukkit/block/CraftBlockEntityState.java b/src/main/java/org/bukkit/craftbukkit/block/CraftBlockEntityState.java +index 80418a05d53516d2c539383aa9994099c634b905..80bd6e8a6dadb74356a9fa9aa394efbd31c49c37 100644 +--- a/src/main/java/org/bukkit/craftbukkit/block/CraftBlockEntityState.java ++++ b/src/main/java/org/bukkit/craftbukkit/block/CraftBlockEntityState.java +@@ -32,6 +32,7 @@ public class CraftBlockEntityState extends CraftBlockStat + + this.tileEntity = tileEntity; + ++ try { // Paper - Show blockstate location if we failed to read it + // Paper start + this.snapshotDisabled = DISABLE_SNAPSHOT; + if (DISABLE_SNAPSHOT) { +@@ -44,6 +45,14 @@ public class CraftBlockEntityState extends CraftBlockStat + this.load(this.snapshot); + } + // Paper end ++ // Paper start - Show blockstate location if we failed to read it ++ } catch (Throwable thr) { ++ if (thr instanceof ThreadDeath) { ++ throw (ThreadDeath)thr; ++ } ++ throw new RuntimeException("Failed to read BlockState at: world: " + this.getWorld().getName() + " location: (" + this.getX() + ", " + this.getY() + ", " + this.getZ() + ")", thr); ++ } ++ // Paper end - Show blockstate location if we failed to read it + } + + protected CraftBlockEntityState(CraftBlockEntityState state, Location location) { diff --git a/patches/server/0290-Expose-the-internal-current-tick.patch b/patches/server/0290-Expose-the-internal-current-tick.patch deleted file mode 100644 index 48e52995c458..000000000000 --- a/patches/server/0290-Expose-the-internal-current-tick.patch +++ /dev/null @@ -1,21 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: William Blake Galbreath -Date: Sat, 20 Apr 2019 19:47:34 -0500 -Subject: [PATCH] Expose the internal current tick - - -diff --git a/src/main/java/org/bukkit/craftbukkit/CraftServer.java b/src/main/java/org/bukkit/craftbukkit/CraftServer.java -index 7407d9f3480e0e44be44c84831864e511dfdebc2..9d07873c4a17549452ac8d9b6ac14d88cde185bc 100644 ---- a/src/main/java/org/bukkit/craftbukkit/CraftServer.java -+++ b/src/main/java/org/bukkit/craftbukkit/CraftServer.java -@@ -2917,5 +2917,10 @@ public final class CraftServer implements Server { - profile.getGameProfile().getProperties().putAll(((CraftPlayer) player).getHandle().getGameProfile().getProperties()); - return profile; - } -+ -+ @Override -+ public int getCurrentTick() { -+ return net.minecraft.server.MinecraftServer.currentTick; -+ } - // Paper end - } diff --git a/patches/server/0290-Only-count-Natural-Spawned-mobs-towards-natural-spaw.patch b/patches/server/0290-Only-count-Natural-Spawned-mobs-towards-natural-spaw.patch new file mode 100644 index 000000000000..9b920bb3ec8a --- /dev/null +++ b/patches/server/0290-Only-count-Natural-Spawned-mobs-towards-natural-spaw.patch @@ -0,0 +1,36 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Aikar +Date: Sun, 24 Mar 2019 01:01:32 -0400 +Subject: [PATCH] Only count Natural Spawned mobs towards natural spawn mob + limit + +This resolves the super common complaint about mobs not spawning. + +This was ultimately a flaw in the vanilla count algorithim that allows +spawners and other misc mobs to count against the mob limit, which are +not bounded, and can prevent the entire world from spawning new. + +I believe Bukkits changes around persistence may of actually made it +worse than vanilla. + +This should fully solve all of the issues around it so that only natural +influences natural spawns. + +diff --git a/src/main/java/net/minecraft/world/level/NaturalSpawner.java b/src/main/java/net/minecraft/world/level/NaturalSpawner.java +index c826bd20d1ba32ebb9069737ede88e122294ea7a..606a60fe273974b71ed2bd40be819d848627e777 100644 +--- a/src/main/java/net/minecraft/world/level/NaturalSpawner.java ++++ b/src/main/java/net/minecraft/world/level/NaturalSpawner.java +@@ -87,6 +87,13 @@ public final class NaturalSpawner { + MobCategory enumcreaturetype = entity.getType().getCategory(); + + if (enumcreaturetype != MobCategory.MISC) { ++ // Paper start - Only count natural spawns ++ if (!entity.level().paperConfig().entities.spawning.countAllMobsForSpawning && ++ !(entity.spawnReason == org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason.NATURAL || ++ entity.spawnReason == org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason.CHUNK_GEN)) { ++ continue; ++ } ++ // Paper end - Only count natural spawns + BlockPos blockposition = entity.blockPosition(); + + chunkSource.query(ChunkPos.asLong(blockposition), (chunk) -> { diff --git a/patches/server/0291-Configurable-projectile-relative-velocity.patch b/patches/server/0291-Configurable-projectile-relative-velocity.patch new file mode 100644 index 000000000000..93e1cc2d7d26 --- /dev/null +++ b/patches/server/0291-Configurable-projectile-relative-velocity.patch @@ -0,0 +1,43 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Lucavon +Date: Tue, 23 Jul 2019 20:29:20 -0500 +Subject: [PATCH] Configurable projectile relative velocity + +This patch adds an option "disable relative projectile velocity", which, when +enabled, will cause projectiles to ignore the shooter's current velocity, +like they did in Minecraft 1.8 and prior. +If a player is falling, for example, their shooting range will be drastically +reduced, as a downwards velocity is applied to the projectile. This prevents +players from saving themselves from falling off floating islands, for example, +as a thrown ender pearl will not make it back to the island, while it would +have in 1.8. + +While this could easily be done with plugins, too, there are multiple problems: +P1) If multiple plugins cancel the velocity by subtracting the shooter's velocity +from the projectile's velocity, the projectile's velocity would be different. +As there's no way to detect whether the projectile's velocity has already been +adjusted to ignore the player's velocity, plugins can't not do it if it's not +necessary. +P2) I've noticed some inconsistencies, e.g. weird velocity when shooting while +using an elytra. Checking for those inconsistencies is possible, but not as +efficient as just not applying the velocity in the first place. +P3) Solutions for 1) and especially 2) might not be future-proof, while this +server-internal fix makes this change future-proof. + +diff --git a/src/main/java/net/minecraft/world/entity/projectile/Projectile.java b/src/main/java/net/minecraft/world/entity/projectile/Projectile.java +index 80637835a35dbfd7c7458b246ddee9d836e5101a..0e4ba92d97998937ccb83ebd60aaad3fb68f7546 100644 +--- a/src/main/java/net/minecraft/world/entity/projectile/Projectile.java ++++ b/src/main/java/net/minecraft/world/entity/projectile/Projectile.java +@@ -192,8 +192,11 @@ public abstract class Projectile extends Entity implements TraceableEntity { + + this.shoot((double) f5, (double) f6, (double) f7, speed, divergence); + Vec3 vec3d = shooter.getKnownMovement(); +- ++ // Paper start - allow disabling relative velocity ++ if (!shooter.level().paperConfig().misc.disableRelativeProjectileVelocity) { + this.setDeltaMovement(this.getDeltaMovement().add(vec3d.x, shooter.onGround() ? 0.0D : vec3d.y, vec3d.z)); ++ } ++ // Paper end - allow disabling relative velocity + } + + public static T spawnProjectileFromRotation(Projectile.ProjectileFactory creator, ServerLevel world, ItemStack projectileStack, LivingEntity shooter, float roll, float power, float divergence) { diff --git a/patches/server/0291-Show-blockstate-location-if-we-failed-to-read-it.patch b/patches/server/0291-Show-blockstate-location-if-we-failed-to-read-it.patch deleted file mode 100644 index c12cf5a95dbe..000000000000 --- a/patches/server/0291-Show-blockstate-location-if-we-failed-to-read-it.patch +++ /dev/null @@ -1,33 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Spottedleaf -Date: Sat, 15 Jun 2019 10:28:25 -0700 -Subject: [PATCH] Show blockstate location if we failed to read it - - -diff --git a/src/main/java/org/bukkit/craftbukkit/block/CraftBlockEntityState.java b/src/main/java/org/bukkit/craftbukkit/block/CraftBlockEntityState.java -index 1ca25dfdb9ec5c3a4d26b71484e1326e1f50b40d..302955eed07d3af91f90875583c70a236bbe11b2 100644 ---- a/src/main/java/org/bukkit/craftbukkit/block/CraftBlockEntityState.java -+++ b/src/main/java/org/bukkit/craftbukkit/block/CraftBlockEntityState.java -@@ -32,6 +32,7 @@ public class CraftBlockEntityState extends CraftBlockStat - - this.tileEntity = tileEntity; - -+ try { // Paper - Show blockstate location if we failed to read it - // Paper start - this.snapshotDisabled = DISABLE_SNAPSHOT; - if (DISABLE_SNAPSHOT) { -@@ -44,6 +45,14 @@ public class CraftBlockEntityState extends CraftBlockStat - this.load(this.snapshot); - } - // Paper end -+ // Paper start - Show blockstate location if we failed to read it -+ } catch (Throwable thr) { -+ if (thr instanceof ThreadDeath) { -+ throw (ThreadDeath)thr; -+ } -+ throw new RuntimeException("Failed to read BlockState at: world: " + this.getWorld().getName() + " location: (" + this.getX() + ", " + this.getY() + ", " + this.getZ() + ")", thr); -+ } -+ // Paper end - Show blockstate location if we failed to read it - } - - protected CraftBlockEntityState(CraftBlockEntityState state, Location location) { diff --git a/patches/server/0292-Only-count-Natural-Spawned-mobs-towards-natural-spaw.patch b/patches/server/0292-Only-count-Natural-Spawned-mobs-towards-natural-spaw.patch deleted file mode 100644 index cb2e502f7154..000000000000 --- a/patches/server/0292-Only-count-Natural-Spawned-mobs-towards-natural-spaw.patch +++ /dev/null @@ -1,36 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Aikar -Date: Sun, 24 Mar 2019 01:01:32 -0400 -Subject: [PATCH] Only count Natural Spawned mobs towards natural spawn mob - limit - -This resolves the super common complaint about mobs not spawning. - -This was ultimately a flaw in the vanilla count algorithim that allows -spawners and other misc mobs to count against the mob limit, which are -not bounded, and can prevent the entire world from spawning new. - -I believe Bukkits changes around persistence may of actually made it -worse than vanilla. - -This should fully solve all of the issues around it so that only natural -influences natural spawns. - -diff --git a/src/main/java/net/minecraft/world/level/NaturalSpawner.java b/src/main/java/net/minecraft/world/level/NaturalSpawner.java -index 41eef8bfd1572aecaf086bfbec300abeae2df794..58ea6a1f95a09c22125a8262b1b221004ebce0e4 100644 ---- a/src/main/java/net/minecraft/world/level/NaturalSpawner.java -+++ b/src/main/java/net/minecraft/world/level/NaturalSpawner.java -@@ -83,6 +83,13 @@ public final class NaturalSpawner { - MobCategory enumcreaturetype = entity.getType().getCategory(); - - if (enumcreaturetype != MobCategory.MISC) { -+ // Paper start - Only count natural spawns -+ if (!entity.level().paperConfig().entities.spawning.countAllMobsForSpawning && -+ !(entity.spawnReason == org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason.NATURAL || -+ entity.spawnReason == org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason.CHUNK_GEN)) { -+ continue; -+ } -+ // Paper end - Only count natural spawns - BlockPos blockposition = entity.blockPosition(); - - chunkSource.query(ChunkPos.asLong(blockposition), (chunk) -> { diff --git a/patches/server/0292-offset-item-frame-ticking.patch b/patches/server/0292-offset-item-frame-ticking.patch new file mode 100644 index 000000000000..f8748a09cc34 --- /dev/null +++ b/patches/server/0292-offset-item-frame-ticking.patch @@ -0,0 +1,19 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: kickash32 +Date: Tue, 30 Jul 2019 03:17:16 +0500 +Subject: [PATCH] offset item frame ticking + + +diff --git a/src/main/java/net/minecraft/world/entity/decoration/BlockAttachedEntity.java b/src/main/java/net/minecraft/world/entity/decoration/BlockAttachedEntity.java +index cd0a2270b58b6c0dba67270e7e1d1ba253eb7953..608acb4f3cb8f9a0dc10b819ed4bf577c229dd2c 100644 +--- a/src/main/java/net/minecraft/world/entity/decoration/BlockAttachedEntity.java ++++ b/src/main/java/net/minecraft/world/entity/decoration/BlockAttachedEntity.java +@@ -29,7 +29,7 @@ import org.bukkit.event.hanging.HangingBreakEvent; + public abstract class BlockAttachedEntity extends Entity { + + private static final Logger LOGGER = LogUtils.getLogger(); +- private int checkInterval; ++ private int checkInterval; { this.checkInterval = this.getId() % this.level().spigotConfig.hangingTickFrequency; } // Paper - Perf: offset item frame ticking + protected BlockPos pos; + + protected BlockAttachedEntity(EntityType type, Level world) { diff --git a/patches/server/0293-Configurable-projectile-relative-velocity.patch b/patches/server/0293-Configurable-projectile-relative-velocity.patch deleted file mode 100644 index 29613c9f0028..000000000000 --- a/patches/server/0293-Configurable-projectile-relative-velocity.patch +++ /dev/null @@ -1,43 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Lucavon -Date: Tue, 23 Jul 2019 20:29:20 -0500 -Subject: [PATCH] Configurable projectile relative velocity - -This patch adds an option "disable relative projectile velocity", which, when -enabled, will cause projectiles to ignore the shooter's current velocity, -like they did in Minecraft 1.8 and prior. -If a player is falling, for example, their shooting range will be drastically -reduced, as a downwards velocity is applied to the projectile. This prevents -players from saving themselves from falling off floating islands, for example, -as a thrown ender pearl will not make it back to the island, while it would -have in 1.8. - -While this could easily be done with plugins, too, there are multiple problems: -P1) If multiple plugins cancel the velocity by subtracting the shooter's velocity -from the projectile's velocity, the projectile's velocity would be different. -As there's no way to detect whether the projectile's velocity has already been -adjusted to ignore the player's velocity, plugins can't not do it if it's not -necessary. -P2) I've noticed some inconsistencies, e.g. weird velocity when shooting while -using an elytra. Checking for those inconsistencies is possible, but not as -efficient as just not applying the velocity in the first place. -P3) Solutions for 1) and especially 2) might not be future-proof, while this -server-internal fix makes this change future-proof. - -diff --git a/src/main/java/net/minecraft/world/entity/projectile/Projectile.java b/src/main/java/net/minecraft/world/entity/projectile/Projectile.java -index 30eb86b52f00cfa61af4f93aca50ffc3547c95e8..d27e17ebf25cd842a943cf82bde05b2248c74414 100644 ---- a/src/main/java/net/minecraft/world/entity/projectile/Projectile.java -+++ b/src/main/java/net/minecraft/world/entity/projectile/Projectile.java -@@ -182,8 +182,11 @@ public abstract class Projectile extends Entity implements TraceableEntity { - - this.shoot((double) f5, (double) f6, (double) f7, speed, divergence); - Vec3 vec3d = shooter.getKnownMovement(); -- -+ // Paper start - allow disabling relative velocity -+ if (!shooter.level().paperConfig().misc.disableRelativeProjectileVelocity) { - this.setDeltaMovement(this.getDeltaMovement().add(vec3d.x, shooter.onGround() ? 0.0D : vec3d.y, vec3d.z)); -+ } -+ // Paper end - allow disabling relative velocity - } - - // CraftBukkit start - call projectile hit event diff --git a/patches/server/0293-Prevent-consuming-the-wrong-itemstack.patch b/patches/server/0293-Prevent-consuming-the-wrong-itemstack.patch new file mode 100644 index 000000000000..ec94a92f4daa --- /dev/null +++ b/patches/server/0293-Prevent-consuming-the-wrong-itemstack.patch @@ -0,0 +1,45 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: kickash32 +Date: Mon, 19 Aug 2019 19:42:35 +0500 +Subject: [PATCH] Prevent consuming the wrong itemstack + + +diff --git a/src/main/java/net/minecraft/world/entity/LivingEntity.java b/src/main/java/net/minecraft/world/entity/LivingEntity.java +index 63d1c5e8441eb5a32fc298ff2d2f3157cbd19557..ebfea3adbedd2695f645421019a276efbc73ee63 100644 +--- a/src/main/java/net/minecraft/world/entity/LivingEntity.java ++++ b/src/main/java/net/minecraft/world/entity/LivingEntity.java +@@ -3993,9 +3993,14 @@ public abstract class LivingEntity extends Entity implements Attackable { + } + + public void startUsingItem(InteractionHand hand) { ++ // Paper start - Prevent consuming the wrong itemstack ++ this.startUsingItem(hand, false); ++ } ++ public void startUsingItem(InteractionHand hand, boolean forceUpdate) { ++ // Paper end - Prevent consuming the wrong itemstack + ItemStack itemstack = this.getItemInHand(hand); + +- if (!itemstack.isEmpty() && !this.isUsingItem()) { ++ if (!itemstack.isEmpty() && !this.isUsingItem() || forceUpdate) { // Paper - Prevent consuming the wrong itemstack + this.useItem = itemstack; + this.useItemRemaining = itemstack.getUseDuration(this); + if (!this.level().isClientSide) { +@@ -4066,6 +4071,7 @@ public abstract class LivingEntity extends Entity implements Attackable { + this.releaseUsingItem(); + } else { + if (!this.useItem.isEmpty() && this.isUsingItem()) { ++ this.startUsingItem(this.getUsedItemHand(), true); // Paper - Prevent consuming the wrong itemstack + // CraftBukkit start - fire PlayerItemConsumeEvent + ItemStack itemstack; + PlayerItemConsumeEvent event = null; // Paper +@@ -4103,8 +4109,8 @@ public abstract class LivingEntity extends Entity implements Attackable { + } + + this.stopUsingItem(); +- // Paper start - if the replacement is anything but the default, update the client inventory +- if (this instanceof ServerPlayer && !com.google.common.base.Objects.equal(defaultReplacement, itemstack)) { ++ // Paper start ++ if (this instanceof ServerPlayer) { + ((ServerPlayer) this).getBukkitEntity().updateInventory(); + } + // Paper end diff --git a/patches/server/0294-Dont-send-unnecessary-sign-update.patch b/patches/server/0294-Dont-send-unnecessary-sign-update.patch new file mode 100644 index 000000000000..cccd627a78f7 --- /dev/null +++ b/patches/server/0294-Dont-send-unnecessary-sign-update.patch @@ -0,0 +1,18 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Nassim Jahnke +Date: Sat, 11 Sep 2021 11:56:51 +0200 +Subject: [PATCH] Dont send unnecessary sign update + + +diff --git a/src/main/java/net/minecraft/world/level/block/entity/SignBlockEntity.java b/src/main/java/net/minecraft/world/level/block/entity/SignBlockEntity.java +index 469d0046ba98289e98aa0f3f66e3ed27026a98a2..3339130f3a02e6e395bcd8e3047b35f6f4eca9a9 100644 +--- a/src/main/java/net/minecraft/world/level/block/entity/SignBlockEntity.java ++++ b/src/main/java/net/minecraft/world/level/block/entity/SignBlockEntity.java +@@ -185,6 +185,7 @@ public class SignBlockEntity extends BlockEntity { + this.level.sendBlockUpdated(this.getBlockPos(), this.getBlockState(), this.getBlockState(), 3); + } else { + SignBlockEntity.LOGGER.warn("Player {} just tried to change non-editable sign", player.getName().getString()); ++ if (player.distanceToSqr(this.getBlockPos().getX(), this.getBlockPos().getY(), this.getBlockPos().getZ()) < 32 * 32) // Paper - Dont send far away sign update + ((ServerPlayer) player).connection.send(this.getUpdatePacket()); // CraftBukkit + } + } diff --git a/patches/server/0294-offset-item-frame-ticking.patch b/patches/server/0294-offset-item-frame-ticking.patch deleted file mode 100644 index 3db1ffe3056a..000000000000 --- a/patches/server/0294-offset-item-frame-ticking.patch +++ /dev/null @@ -1,19 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: kickash32 -Date: Tue, 30 Jul 2019 03:17:16 +0500 -Subject: [PATCH] offset item frame ticking - - -diff --git a/src/main/java/net/minecraft/world/entity/decoration/BlockAttachedEntity.java b/src/main/java/net/minecraft/world/entity/decoration/BlockAttachedEntity.java -index e4eece7bbd14514ec60da26a8744672baa8956f9..7bc612890f941177da11da0ce047d5a74d8ebb33 100644 ---- a/src/main/java/net/minecraft/world/entity/decoration/BlockAttachedEntity.java -+++ b/src/main/java/net/minecraft/world/entity/decoration/BlockAttachedEntity.java -@@ -26,7 +26,7 @@ import org.bukkit.event.hanging.HangingBreakEvent; - public abstract class BlockAttachedEntity extends Entity { - - private static final Logger LOGGER = LogUtils.getLogger(); -- private int checkInterval; -+ private int checkInterval; { this.checkInterval = this.getId() % this.level().spigotConfig.hangingTickFrequency; } // Paper - Perf: offset item frame ticking - protected BlockPos pos; - - protected BlockAttachedEntity(EntityType type, Level world) { diff --git a/patches/server/0295-Add-option-to-disable-pillager-patrols.patch b/patches/server/0295-Add-option-to-disable-pillager-patrols.patch new file mode 100644 index 000000000000..dd3c57ff12bf --- /dev/null +++ b/patches/server/0295-Add-option-to-disable-pillager-patrols.patch @@ -0,0 +1,18 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: William Blake Galbreath +Date: Wed, 9 Oct 2019 21:46:15 -0500 +Subject: [PATCH] Add option to disable pillager patrols + + +diff --git a/src/main/java/net/minecraft/world/level/levelgen/PatrolSpawner.java b/src/main/java/net/minecraft/world/level/levelgen/PatrolSpawner.java +index 04e108633068c2a1a88a35995c2999701ef7bc3b..8837db46ced2412942949a3c6f81ec7d9a0bacda 100644 +--- a/src/main/java/net/minecraft/world/level/levelgen/PatrolSpawner.java ++++ b/src/main/java/net/minecraft/world/level/levelgen/PatrolSpawner.java +@@ -24,6 +24,7 @@ public class PatrolSpawner implements CustomSpawner { + + @Override + public int tick(ServerLevel world, boolean spawnMonsters, boolean spawnAnimals) { ++ if (world.paperConfig().entities.behavior.pillagerPatrols.disable) return 0; // Paper - Add option to disable pillager patrols + if (!spawnMonsters) { + return 0; + } else if (!world.getGameRules().getBoolean(GameRules.RULE_DO_PATROL_SPAWNING)) { diff --git a/patches/server/0295-Prevent-consuming-the-wrong-itemstack.patch b/patches/server/0295-Prevent-consuming-the-wrong-itemstack.patch deleted file mode 100644 index c0dbff9a6b8f..000000000000 --- a/patches/server/0295-Prevent-consuming-the-wrong-itemstack.patch +++ /dev/null @@ -1,45 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: kickash32 -Date: Mon, 19 Aug 2019 19:42:35 +0500 -Subject: [PATCH] Prevent consuming the wrong itemstack - - -diff --git a/src/main/java/net/minecraft/world/entity/LivingEntity.java b/src/main/java/net/minecraft/world/entity/LivingEntity.java -index a23baa42fd3bb09f619fd6528a8a4f97d189cde6..8ee6d25c3860fe7f2e5039c74c736d82eefda237 100644 ---- a/src/main/java/net/minecraft/world/entity/LivingEntity.java -+++ b/src/main/java/net/minecraft/world/entity/LivingEntity.java -@@ -3855,9 +3855,14 @@ public abstract class LivingEntity extends Entity implements Attackable { - } - - public void startUsingItem(InteractionHand hand) { -+ // Paper start - Prevent consuming the wrong itemstack -+ this.startUsingItem(hand, false); -+ } -+ public void startUsingItem(InteractionHand hand, boolean forceUpdate) { -+ // Paper end - Prevent consuming the wrong itemstack - ItemStack itemstack = this.getItemInHand(hand); - -- if (!itemstack.isEmpty() && !this.isUsingItem()) { -+ if (!itemstack.isEmpty() && !this.isUsingItem() || forceUpdate) { // Paper - Prevent consuming the wrong itemstack - this.useItem = itemstack; - this.useItemRemaining = itemstack.getUseDuration(this); - if (!this.level().isClientSide) { -@@ -3942,6 +3947,7 @@ public abstract class LivingEntity extends Entity implements Attackable { - this.releaseUsingItem(); - } else { - if (!this.useItem.isEmpty() && this.isUsingItem()) { -+ this.startUsingItem(this.getUsedItemHand(), true); // Paper - Prevent consuming the wrong itemstack - this.triggerItemUseEffects(this.useItem, 16); - // CraftBukkit start - fire PlayerItemConsumeEvent - ItemStack itemstack; -@@ -3979,8 +3985,8 @@ public abstract class LivingEntity extends Entity implements Attackable { - } - - this.stopUsingItem(); -- // Paper start - if the replacement is anything but the default, update the client inventory -- if (this instanceof ServerPlayer && !com.google.common.base.Objects.equal(defaultReplacement, itemstack)) { -+ // Paper start -+ if (this instanceof ServerPlayer) { - ((ServerPlayer) this).getBukkitEntity().updateInventory(); - } - // Paper end diff --git a/patches/server/0296-Dont-send-unnecessary-sign-update.patch b/patches/server/0296-Dont-send-unnecessary-sign-update.patch deleted file mode 100644 index afca8562dbfb..000000000000 --- a/patches/server/0296-Dont-send-unnecessary-sign-update.patch +++ /dev/null @@ -1,18 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Nassim Jahnke -Date: Sat, 11 Sep 2021 11:56:51 +0200 -Subject: [PATCH] Dont send unnecessary sign update - - -diff --git a/src/main/java/net/minecraft/world/level/block/entity/SignBlockEntity.java b/src/main/java/net/minecraft/world/level/block/entity/SignBlockEntity.java -index 394d0b03f18d55b43f091a9ae1a47e06a6fa4c0d..87e272cfb145c37d26b0bf56f97ec784a9bfd98e 100644 ---- a/src/main/java/net/minecraft/world/level/block/entity/SignBlockEntity.java -+++ b/src/main/java/net/minecraft/world/level/block/entity/SignBlockEntity.java -@@ -185,6 +185,7 @@ public class SignBlockEntity extends BlockEntity implements CommandSource { // C - this.level.sendBlockUpdated(this.getBlockPos(), this.getBlockState(), this.getBlockState(), 3); - } else { - SignBlockEntity.LOGGER.warn("Player {} just tried to change non-editable sign", player.getName().getString()); -+ if (player.distanceToSqr(this.getBlockPos().getX(), this.getBlockPos().getY(), this.getBlockPos().getZ()) < 32 * 32) // Paper - Dont send far away sign update - ((ServerPlayer) player).connection.send(this.getUpdatePacket()); // CraftBukkit - } - } diff --git a/patches/server/0298-Prevent-sync-chunk-loads-when-villagers-try-to-find-.patch b/patches/server/0296-Prevent-sync-chunk-loads-when-villagers-try-to-find-.patch similarity index 100% rename from patches/server/0298-Prevent-sync-chunk-loads-when-villagers-try-to-find-.patch rename to patches/server/0296-Prevent-sync-chunk-loads-when-villagers-try-to-find-.patch diff --git a/patches/server/0297-Add-option-to-disable-pillager-patrols.patch b/patches/server/0297-Add-option-to-disable-pillager-patrols.patch deleted file mode 100644 index 3021a1f6c83a..000000000000 --- a/patches/server/0297-Add-option-to-disable-pillager-patrols.patch +++ /dev/null @@ -1,18 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: William Blake Galbreath -Date: Wed, 9 Oct 2019 21:46:15 -0500 -Subject: [PATCH] Add option to disable pillager patrols - - -diff --git a/src/main/java/net/minecraft/world/level/levelgen/PatrolSpawner.java b/src/main/java/net/minecraft/world/level/levelgen/PatrolSpawner.java -index 7522c40ab352d3b3ce1540f043b4b5f0d411060b..7b5db53d4cf97e41175896de47303526198fb8f6 100644 ---- a/src/main/java/net/minecraft/world/level/levelgen/PatrolSpawner.java -+++ b/src/main/java/net/minecraft/world/level/levelgen/PatrolSpawner.java -@@ -24,6 +24,7 @@ public class PatrolSpawner implements CustomSpawner { - - @Override - public int tick(ServerLevel world, boolean spawnMonsters, boolean spawnAnimals) { -+ if (world.paperConfig().entities.behavior.pillagerPatrols.disable) return 0; // Paper - Add option to disable pillager patrols - if (!spawnMonsters) { - return 0; - } else if (!world.getGameRules().getBoolean(GameRules.RULE_DO_PATROL_SPAWNING)) { diff --git a/patches/server/0297-Duplicate-UUID-Resolve-Option.patch b/patches/server/0297-Duplicate-UUID-Resolve-Option.patch new file mode 100644 index 000000000000..b42bece1fca0 --- /dev/null +++ b/patches/server/0297-Duplicate-UUID-Resolve-Option.patch @@ -0,0 +1,90 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Aikar +Date: Sat, 21 Jul 2018 14:27:34 -0400 +Subject: [PATCH] Duplicate UUID Resolve Option + +Due to a bug in https://github.com/PaperMC/Paper/commit/2e29af3df05ec0a383f48be549d1c03200756d24 +which was added all the way back in March of 2016, it was unknown (potentially not at the time) +that an entity might actually change the seed of the random object. + +At some point, EntitySquid did start setting the seed. Due to this shared random, this caused +every entity to use a Random object with a predictable seed. + +This has caused entities to potentially generate with the same UUID.... + +Over the years, servers have had entities disappear, but no sign of trouble +because CraftBukkit removed the log lines indicating that something was wrong. + +We have fixed the root issue causing duplicate UUID's, however we now have chunk +files full of entities that have the same UUID as another entity! + +When these chunks load, the 2nd entity will not be added to the world correctly. + +If that chunk loads in a different order in the future, then it will reverse and the +missing one is now the one added to the world and not the other. This results in very +inconsistent entity behavior. + +This change allows you to recover any duplicate entity by generating a new UUID for it. +This also lets you delete them instead if you don't want to risk having new entities added to +the world that you previously did not see. + +But for those who are ok with leaving this inconsistent behavior, you may use WARN or NOTHING options. + +It is recommended you regenerate the entities, as these were legit entities, and deserve your love. + +diff --git a/src/main/java/net/minecraft/world/level/chunk/status/ChunkStatusTasks.java b/src/main/java/net/minecraft/world/level/chunk/status/ChunkStatusTasks.java +index 412caefe776df6c8e931f6a1a48ea2525ec6610e..aff4c3d63a97d5bbde004a616f7e14fca59b5ab9 100644 +--- a/src/main/java/net/minecraft/world/level/chunk/status/ChunkStatusTasks.java ++++ b/src/main/java/net/minecraft/world/level/chunk/status/ChunkStatusTasks.java +@@ -178,10 +178,51 @@ public class ChunkStatusTasks { + entity.discard(null); // CraftBukkit - add Bukkit remove cause + needsRemoval = true; + } ++ checkDupeUUID(world, entity); // Paper - duplicate uuid resolving + return !needsRemoval; + })); + // CraftBukkit end + } + + } ++ ++ // Paper start - duplicate uuid resolving ++ // rets true if to prevent the entity from being added ++ public static boolean checkDupeUUID(ServerLevel level, net.minecraft.world.entity.Entity entity) { ++ io.papermc.paper.configuration.WorldConfiguration.Entities.Spawning.DuplicateUUID.DuplicateUUIDMode mode = level.paperConfig().entities.spawning.duplicateUuid.mode; ++ if (mode != io.papermc.paper.configuration.WorldConfiguration.Entities.Spawning.DuplicateUUID.DuplicateUUIDMode.WARN ++ && mode != io.papermc.paper.configuration.WorldConfiguration.Entities.Spawning.DuplicateUUID.DuplicateUUIDMode.DELETE ++ && mode != io.papermc.paper.configuration.WorldConfiguration.Entities.Spawning.DuplicateUUID.DuplicateUUIDMode.SAFE_REGEN) { ++ return false; ++ } ++ net.minecraft.world.entity.Entity other = level.getEntity(entity.getUUID()); ++ ++ if (other == null || other == entity) { ++ return false; ++ } ++ ++ if (mode == io.papermc.paper.configuration.WorldConfiguration.Entities.Spawning.DuplicateUUID.DuplicateUUIDMode.SAFE_REGEN && other != null && !other.isRemoved() ++ && Objects.equals(other.getEncodeId(), entity.getEncodeId()) ++ && entity.getBukkitEntity().getLocation().distance(other.getBukkitEntity().getLocation()) < level.paperConfig().entities.spawning.duplicateUuid.safeRegenDeleteRange ++ ) { ++ entity.discard(null); ++ return true; ++ } ++ if (!other.isRemoved()) { ++ switch (mode) { ++ case SAFE_REGEN: { ++ entity.setUUID(java.util.UUID.randomUUID()); ++ break; ++ } ++ case DELETE: { ++ entity.discard(org.bukkit.event.entity.EntityRemoveEvent.Cause.DISCARD); ++ return true; ++ } ++ default: ++ break; ++ } ++ } ++ return false; ++ } ++ // Paper end - duplicate uuid resolving + } diff --git a/patches/server/0298-PlayerDeathEvent-shouldDropExperience.patch b/patches/server/0298-PlayerDeathEvent-shouldDropExperience.patch new file mode 100644 index 000000000000..22cdda6c7901 --- /dev/null +++ b/patches/server/0298-PlayerDeathEvent-shouldDropExperience.patch @@ -0,0 +1,19 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Shane Freeder +Date: Tue, 24 Dec 2019 00:35:42 +0000 +Subject: [PATCH] PlayerDeathEvent#shouldDropExperience + + +diff --git a/src/main/java/net/minecraft/server/level/ServerPlayer.java b/src/main/java/net/minecraft/server/level/ServerPlayer.java +index c5eb4766d95b1ed91be8e8631200f56096a17766..099a9aa7e64da22e720fa527cac8750991ee8562 100644 +--- a/src/main/java/net/minecraft/server/level/ServerPlayer.java ++++ b/src/main/java/net/minecraft/server/level/ServerPlayer.java +@@ -1278,7 +1278,7 @@ public class ServerPlayer extends net.minecraft.world.entity.player.Player { + this.tellNeutralMobsThatIDied(); + } + // SPIGOT-5478 must be called manually now +- this.dropExperience(this.serverLevel(), damageSource.getEntity()); ++ if (event.shouldDropExperience()) this.dropExperience(this.serverLevel(), damageSource.getEntity()); // Paper - tie to event + // we clean the player's inventory after the EntityDeathEvent is called so plugins can get the exact state of the inventory. + if (!event.getKeepInventory()) { + // Paper start - PlayerDeathEvent#getItemsToKeep diff --git a/patches/server/0299-MC-145656-Fix-Follow-Range-Initial-Target.patch b/patches/server/0299-MC-145656-Fix-Follow-Range-Initial-Target.patch deleted file mode 100644 index 528cf153ea7a..000000000000 --- a/patches/server/0299-MC-145656-Fix-Follow-Range-Initial-Target.patch +++ /dev/null @@ -1,50 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: William Blake Galbreath -Date: Wed, 18 Dec 2019 22:21:35 -0600 -Subject: [PATCH] MC-145656 Fix Follow Range Initial Target - - -diff --git a/src/main/java/net/minecraft/world/entity/ai/goal/target/NearestAttackableTargetGoal.java b/src/main/java/net/minecraft/world/entity/ai/goal/target/NearestAttackableTargetGoal.java -index 0dad5be671f990d0edf0a155e2534b3812438902..175ba1184fc997f562f0834b172e17dc1b5b3027 100644 ---- a/src/main/java/net/minecraft/world/entity/ai/goal/target/NearestAttackableTargetGoal.java -+++ b/src/main/java/net/minecraft/world/entity/ai/goal/target/NearestAttackableTargetGoal.java -@@ -38,6 +38,7 @@ public class NearestAttackableTargetGoal extends TargetG - this.randomInterval = reducedTickDelay(reciprocalChance); - this.setFlags(EnumSet.of(Goal.Flag.TARGET)); - this.targetConditions = TargetingConditions.forCombat().range(this.getFollowDistance()).selector(targetPredicate); -+ if (mob.level().paperConfig().entities.entitiesTargetWithFollowRange) this.targetConditions.useFollowRange(); // Paper - Fix MC-145656 - } - - @Override -diff --git a/src/main/java/net/minecraft/world/entity/ai/targeting/TargetingConditions.java b/src/main/java/net/minecraft/world/entity/ai/targeting/TargetingConditions.java -index bc4b9a46056e83762e29bb04485ad7c754a20336..aecb0ad814586bfc5e56755ee14379a69388b38c 100644 ---- a/src/main/java/net/minecraft/world/entity/ai/targeting/TargetingConditions.java -+++ b/src/main/java/net/minecraft/world/entity/ai/targeting/TargetingConditions.java -@@ -77,7 +77,7 @@ public class TargetingConditions { - - if (this.range > 0.0) { - double d = this.testInvisible ? targetEntity.getVisibilityPercent(baseEntity) : 1.0; -- double e = Math.max(this.range * d, 2.0); -+ double e = Math.max((this.useFollowRange ? this.getFollowRange(baseEntity) : this.range) * d, 2.0); // Paper - Fix MC-145656 - double f = baseEntity.distanceToSqr(targetEntity.getX(), targetEntity.getY(), targetEntity.getZ()); - if (f > e * e) { - return false; -@@ -92,4 +92,18 @@ public class TargetingConditions { - return true; - } - } -+ -+ // Paper start - Fix MC-145656 -+ private boolean useFollowRange = false; -+ -+ public TargetingConditions useFollowRange() { -+ this.useFollowRange = true; -+ return this; -+ } -+ -+ private double getFollowRange(LivingEntity entityliving) { -+ net.minecraft.world.entity.ai.attributes.AttributeInstance attributeinstance = entityliving.getAttribute(net.minecraft.world.entity.ai.attributes.Attributes.FOLLOW_RANGE); -+ return attributeinstance == null ? 16.0D : attributeinstance.getValue(); -+ } -+ // Paper end - Fix MC-145656 - } diff --git a/patches/server/0299-Prevent-bees-loading-chunks-checking-hive-position.patch b/patches/server/0299-Prevent-bees-loading-chunks-checking-hive-position.patch new file mode 100644 index 000000000000..ce7ab4dd4d0e --- /dev/null +++ b/patches/server/0299-Prevent-bees-loading-chunks-checking-hive-position.patch @@ -0,0 +1,24 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: William Blake Galbreath +Date: Sun, 5 Jan 2020 17:24:34 -0600 +Subject: [PATCH] Prevent bees loading chunks checking hive position + + +diff --git a/src/main/java/net/minecraft/world/entity/animal/Bee.java b/src/main/java/net/minecraft/world/entity/animal/Bee.java +index b1a72ed404f4bc31f32dd771dd320b7c783c9651..310a2aa23365f9a8a8b7b6fa2323aed792f95adb 100644 +--- a/src/main/java/net/minecraft/world/entity/animal/Bee.java ++++ b/src/main/java/net/minecraft/world/entity/animal/Bee.java +@@ -514,7 +514,12 @@ public class Bee extends Animal implements NeutralMob, FlyingAnimal { + + @Nullable + BeehiveBlockEntity getBeehiveBlockEntity() { +- return this.hivePos == null ? null : (this.isTooFarAway(this.hivePos) ? null : (BeehiveBlockEntity) this.level().getBlockEntity(this.hivePos, BlockEntityType.BEEHIVE).orElse(null)); // CraftBukkit - decompile error ++ // Paper start - move over logic to accommodate isTooFarAway with chunk load check ++ if (this.hivePos != null && !this.isTooFarAway(this.hivePos) && this.level().getChunkIfLoadedImmediately(this.hivePos.getX() >> 4, this.hivePos.getZ() >> 4) != null) { ++ return (BeehiveBlockEntity) this.level().getBlockEntity(this.hivePos, BlockEntityType.BEEHIVE).orElse(null); ++ } ++ return null; ++ // Paper end + } + + boolean isHiveValid() { diff --git a/patches/server/0300-Don-t-load-Chunks-from-Hoppers-and-other-things.patch b/patches/server/0300-Don-t-load-Chunks-from-Hoppers-and-other-things.patch new file mode 100644 index 000000000000..cf3afd704034 --- /dev/null +++ b/patches/server/0300-Don-t-load-Chunks-from-Hoppers-and-other-things.patch @@ -0,0 +1,32 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Aikar +Date: Thu, 3 Nov 2016 20:28:12 -0400 +Subject: [PATCH] Don't load Chunks from Hoppers and other things + +Hoppers call this to I guess "get the primary side" of a double sided chest. + +If the double sided chest crosses chunk lines, it causes the chunk to load. +This will end up causing sync chunk loads, which will unload with Chunk GC, +only to be reloaded again the next tick. + +This of course is undesirable, so just return the loaded side as "primary" +and treat it as a single chest if the other sides are unloaded + +diff --git a/src/main/java/net/minecraft/world/level/block/DoubleBlockCombiner.java b/src/main/java/net/minecraft/world/level/block/DoubleBlockCombiner.java +index cccdaa29ea97ec1d9e79f5a4811884d54e641677..b5025914678484cc615dc1982762d5d7f55a4557 100644 +--- a/src/main/java/net/minecraft/world/level/block/DoubleBlockCombiner.java ++++ b/src/main/java/net/minecraft/world/level/block/DoubleBlockCombiner.java +@@ -34,7 +34,12 @@ public class DoubleBlockCombiner { + return new DoubleBlockCombiner.NeighborCombineResult.Single<>(blockEntity); + } else { + BlockPos blockPos = pos.relative(directionMapper.apply(state)); +- BlockState blockState = world.getBlockState(blockPos); ++ // Paper start - Don't load Chunks from Hoppers and other things ++ BlockState blockState = world.getBlockStateIfLoaded(blockPos); ++ if (blockState == null) { ++ return new DoubleBlockCombiner.NeighborCombineResult.Single<>(blockEntity); ++ } ++ // Paper end - Don't load Chunks from Hoppers and other things + if (blockState.is(state.getBlock())) { + DoubleBlockCombiner.BlockType blockType2 = typeMapper.apply(blockState); + if (blockType2 != DoubleBlockCombiner.BlockType.SINGLE diff --git a/patches/server/0300-Duplicate-UUID-Resolve-Option.patch b/patches/server/0300-Duplicate-UUID-Resolve-Option.patch deleted file mode 100644 index 0139752e8eee..000000000000 --- a/patches/server/0300-Duplicate-UUID-Resolve-Option.patch +++ /dev/null @@ -1,90 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Aikar -Date: Sat, 21 Jul 2018 14:27:34 -0400 -Subject: [PATCH] Duplicate UUID Resolve Option - -Due to a bug in https://github.com/PaperMC/Paper/commit/2e29af3df05ec0a383f48be549d1c03200756d24 -which was added all the way back in March of 2016, it was unknown (potentially not at the time) -that an entity might actually change the seed of the random object. - -At some point, EntitySquid did start setting the seed. Due to this shared random, this caused -every entity to use a Random object with a predictable seed. - -This has caused entities to potentially generate with the same UUID.... - -Over the years, servers have had entities disappear, but no sign of trouble -because CraftBukkit removed the log lines indicating that something was wrong. - -We have fixed the root issue causing duplicate UUID's, however we now have chunk -files full of entities that have the same UUID as another entity! - -When these chunks load, the 2nd entity will not be added to the world correctly. - -If that chunk loads in a different order in the future, then it will reverse and the -missing one is now the one added to the world and not the other. This results in very -inconsistent entity behavior. - -This change allows you to recover any duplicate entity by generating a new UUID for it. -This also lets you delete them instead if you don't want to risk having new entities added to -the world that you previously did not see. - -But for those who are ok with leaving this inconsistent behavior, you may use WARN or NOTHING options. - -It is recommended you regenerate the entities, as these were legit entities, and deserve your love. - -diff --git a/src/main/java/net/minecraft/world/level/chunk/status/ChunkStatusTasks.java b/src/main/java/net/minecraft/world/level/chunk/status/ChunkStatusTasks.java -index 82fc5133325a127890984d51c1381883759eb444..ae16cf5c803caae636860dd9b1a83abe479ca5a4 100644 ---- a/src/main/java/net/minecraft/world/level/chunk/status/ChunkStatusTasks.java -+++ b/src/main/java/net/minecraft/world/level/chunk/status/ChunkStatusTasks.java -@@ -189,10 +189,51 @@ public class ChunkStatusTasks { - entity.discard(null); // CraftBukkit - add Bukkit remove cause - needsRemoval = true; - } -+ checkDupeUUID(world, entity); // Paper - duplicate uuid resolving - return !needsRemoval; - })); - // CraftBukkit end - } - - } -+ -+ // Paper start - duplicate uuid resolving -+ // rets true if to prevent the entity from being added -+ public static boolean checkDupeUUID(ServerLevel level, net.minecraft.world.entity.Entity entity) { -+ io.papermc.paper.configuration.WorldConfiguration.Entities.Spawning.DuplicateUUID.DuplicateUUIDMode mode = level.paperConfig().entities.spawning.duplicateUuid.mode; -+ if (mode != io.papermc.paper.configuration.WorldConfiguration.Entities.Spawning.DuplicateUUID.DuplicateUUIDMode.WARN -+ && mode != io.papermc.paper.configuration.WorldConfiguration.Entities.Spawning.DuplicateUUID.DuplicateUUIDMode.DELETE -+ && mode != io.papermc.paper.configuration.WorldConfiguration.Entities.Spawning.DuplicateUUID.DuplicateUUIDMode.SAFE_REGEN) { -+ return false; -+ } -+ net.minecraft.world.entity.Entity other = level.getEntity(entity.getUUID()); -+ -+ if (other == null || other == entity) { -+ return false; -+ } -+ -+ if (mode == io.papermc.paper.configuration.WorldConfiguration.Entities.Spawning.DuplicateUUID.DuplicateUUIDMode.SAFE_REGEN && other != null && !other.isRemoved() -+ && Objects.equals(other.getEncodeId(), entity.getEncodeId()) -+ && entity.getBukkitEntity().getLocation().distance(other.getBukkitEntity().getLocation()) < level.paperConfig().entities.spawning.duplicateUuid.safeRegenDeleteRange -+ ) { -+ entity.discard(null); -+ return true; -+ } -+ if (!other.isRemoved()) { -+ switch (mode) { -+ case SAFE_REGEN: { -+ entity.setUUID(java.util.UUID.randomUUID()); -+ break; -+ } -+ case DELETE: { -+ entity.discard(org.bukkit.event.entity.EntityRemoveEvent.Cause.DISCARD); -+ return true; -+ } -+ default: -+ break; -+ } -+ } -+ return false; -+ } -+ // Paper end - duplicate uuid resolving - } diff --git a/patches/server/0301-Optimise-EntityGetter-getPlayerByUUID.patch b/patches/server/0301-Optimise-EntityGetter-getPlayerByUUID.patch new file mode 100644 index 000000000000..26d9943b72f3 --- /dev/null +++ b/patches/server/0301-Optimise-EntityGetter-getPlayerByUUID.patch @@ -0,0 +1,27 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Spottedleaf +Date: Sat, 11 Jan 2020 21:50:56 -0800 +Subject: [PATCH] Optimise EntityGetter#getPlayerByUUID + +Use the PlayerList map instead of iterating over all players + +diff --git a/src/main/java/net/minecraft/server/level/ServerLevel.java b/src/main/java/net/minecraft/server/level/ServerLevel.java +index 91e462544b82e0fe41851fa0fa3f6d6f08ac490d..800dd7338d77289b7d0e6e126342eaea184d3e43 100644 +--- a/src/main/java/net/minecraft/server/level/ServerLevel.java ++++ b/src/main/java/net/minecraft/server/level/ServerLevel.java +@@ -331,6 +331,15 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe + } + // Paper end + ++ // Paper start - optimise getPlayerByUUID ++ @Nullable ++ @Override ++ public Player getPlayerByUUID(UUID uuid) { ++ final Player player = this.getServer().getPlayerList().getPlayer(uuid); ++ return player != null && player.level() == this ? player : null; ++ } ++ // Paper end - optimise getPlayerByUUID ++ + // Add env and gen to constructor, IWorldDataServer -> WorldDataServer + public ServerLevel(MinecraftServer minecraftserver, Executor executor, LevelStorageSource.LevelStorageAccess convertable_conversionsession, PrimaryLevelData iworlddataserver, ResourceKey resourcekey, LevelStem worlddimension, ChunkProgressListener worldloadlistener, boolean flag, long i, List list, boolean flag1, @Nullable RandomSequences randomsequences, org.bukkit.World.Environment env, org.bukkit.generator.ChunkGenerator gen, org.bukkit.generator.BiomeProvider biomeProvider) { + super(iworlddataserver, resourcekey, minecraftserver.registryAccess(), worlddimension.type(), false, flag, i, minecraftserver.getMaxChainedNeighborUpdates(), gen, biomeProvider, env, spigotConfig -> minecraftserver.paperConfigurations.createWorldConfig(io.papermc.paper.configuration.PaperConfigurations.createWorldContextMap(convertable_conversionsession.levelDirectory.path(), iworlddataserver.getLevelName(), resourcekey.location(), spigotConfig, minecraftserver.registryAccess(), iworlddataserver.getGameRules()))); // Paper - create paper world configs diff --git a/patches/server/0301-PlayerDeathEvent-shouldDropExperience.patch b/patches/server/0301-PlayerDeathEvent-shouldDropExperience.patch deleted file mode 100644 index b48dcea61d16..000000000000 --- a/patches/server/0301-PlayerDeathEvent-shouldDropExperience.patch +++ /dev/null @@ -1,19 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Shane Freeder -Date: Tue, 24 Dec 2019 00:35:42 +0000 -Subject: [PATCH] PlayerDeathEvent#shouldDropExperience - - -diff --git a/src/main/java/net/minecraft/server/level/ServerPlayer.java b/src/main/java/net/minecraft/server/level/ServerPlayer.java -index cba123f40d72ebd39305a76694be508510997d1e..4ccb5ba03d19223d25aee4cebd4b8bec2838a3e0 100644 ---- a/src/main/java/net/minecraft/server/level/ServerPlayer.java -+++ b/src/main/java/net/minecraft/server/level/ServerPlayer.java -@@ -1018,7 +1018,7 @@ public class ServerPlayer extends net.minecraft.world.entity.player.Player { - this.tellNeutralMobsThatIDied(); - } - // SPIGOT-5478 must be called manually now -- this.dropExperience(damageSource.getEntity()); -+ if (event.shouldDropExperience()) this.dropExperience(damageSource.getEntity()); // Paper - tie to event - // we clean the player's inventory after the EntityDeathEvent is called so plugins can get the exact state of the inventory. - if (!event.getKeepInventory()) { - // Paper start - PlayerDeathEvent#getItemsToKeep diff --git a/patches/server/0302-Fix-item-EAR-ticks.patch b/patches/server/0302-Fix-item-EAR-ticks.patch new file mode 100644 index 000000000000..8fd577191b39 --- /dev/null +++ b/patches/server/0302-Fix-item-EAR-ticks.patch @@ -0,0 +1,42 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Nassim Jahnke +Date: Wed, 30 Oct 2024 13:51:54 +0100 +Subject: [PATCH] Fix item EAR ticks + +Item entities only have their gravity ticked every 4 ticks when on ground. +Fix that and also remove Spigot's arbitrary tick skipping. It's a terribly +cheap way of getting extra performance that doesn't really work at all. + +diff --git a/src/main/java/net/minecraft/world/entity/item/ItemEntity.java b/src/main/java/net/minecraft/world/entity/item/ItemEntity.java +index 75ebf09777e19645eee296a9edabac39c858ffb9..c21fa55c62d97d9511e41a1e313e904330a6eee6 100644 +--- a/src/main/java/net/minecraft/world/entity/item/ItemEntity.java ++++ b/src/main/java/net/minecraft/world/entity/item/ItemEntity.java +@@ -175,7 +175,7 @@ public class ItemEntity extends Entity implements TraceableEntity { + } + } + +- if (!this.onGround() || this.getDeltaMovement().horizontalDistanceSqr() > 9.999999747378752E-6D || (this.tickCount + this.getId()) % 4 == 0) { ++ if (!this.onGround() || this.getDeltaMovement().horizontalDistanceSqr() > 9.999999747378752E-6D || (this.tickCount + this.getId()) % 4 == 0) { // Paper - Diff on change; ActivationRange immunity + this.move(MoverType.SELF, this.getDeltaMovement()); + this.applyEffectsFromBlocks(); + float f = 0.98F; +diff --git a/src/main/java/org/spigotmc/ActivationRange.java b/src/main/java/org/spigotmc/ActivationRange.java +index dd1c5bc7522a4710cbfdd4764f6431e1e28d63cc..05ad15fc40ccb7feed5c51ad0ad0a98bd0d02af6 100644 +--- a/src/main/java/org/spigotmc/ActivationRange.java ++++ b/src/main/java/org/spigotmc/ActivationRange.java +@@ -251,12 +251,11 @@ public class ActivationRange + entity.activatedTick = MinecraftServer.currentTick + 20; + } + isActive = true; ++ } else if (entity instanceof net.minecraft.world.entity.item.ItemEntity && (entity.tickCount + entity.getId()) % 4 == 0) { // Paper - Needed for item gravity, see ItemEntity tick ++ isActive = true; + } +- // Add a little performance juice to active entities. Skip 1/4 if not immune. +- } else if ( !entity.defaultActivationState && entity.tickCount % 4 == 0 && !ActivationRange.checkEntityImmunities( entity ) ) +- { +- isActive = false; + } ++ // Paper - remove dumb tick skipping for active entities + return isActive; + } + } diff --git a/patches/server/0302-Prevent-bees-loading-chunks-checking-hive-position.patch b/patches/server/0302-Prevent-bees-loading-chunks-checking-hive-position.patch deleted file mode 100644 index e933516d42fc..000000000000 --- a/patches/server/0302-Prevent-bees-loading-chunks-checking-hive-position.patch +++ /dev/null @@ -1,18 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: William Blake Galbreath -Date: Sun, 5 Jan 2020 17:24:34 -0600 -Subject: [PATCH] Prevent bees loading chunks checking hive position - - -diff --git a/src/main/java/net/minecraft/world/entity/animal/Bee.java b/src/main/java/net/minecraft/world/entity/animal/Bee.java -index 048d03c8ef3ef865641b2276477cf84e8d4397a1..58536ee8707c5ad0625cae2f26a58cf03b3f85d7 100644 ---- a/src/main/java/net/minecraft/world/entity/animal/Bee.java -+++ b/src/main/java/net/minecraft/world/entity/animal/Bee.java -@@ -510,6 +510,7 @@ public class Bee extends Animal implements NeutralMob, FlyingAnimal { - } else if (this.isTooFarAway(this.hivePos)) { - return false; - } else { -+ if (this.level().getChunkIfLoadedImmediately(this.hivePos.getX() >> 4, this.hivePos.getZ() >> 4) == null) return true; // Paper - just assume the hive is still there, no need to load the chunk(s) - BlockEntity tileentity = this.level().getBlockEntity(this.hivePos); - - return tileentity != null && tileentity.getType() == BlockEntityType.BEEHIVE; diff --git a/patches/server/0303-Don-t-load-Chunks-from-Hoppers-and-other-things.patch b/patches/server/0303-Don-t-load-Chunks-from-Hoppers-and-other-things.patch deleted file mode 100644 index 3b189be4e438..000000000000 --- a/patches/server/0303-Don-t-load-Chunks-from-Hoppers-and-other-things.patch +++ /dev/null @@ -1,32 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Aikar -Date: Thu, 3 Nov 2016 20:28:12 -0400 -Subject: [PATCH] Don't load Chunks from Hoppers and other things - -Hoppers call this to I guess "get the primary side" of a double sided chest. - -If the double sided chest crosses chunk lines, it causes the chunk to load. -This will end up causing sync chunk loads, which will unload with Chunk GC, -only to be reloaded again the next tick. - -This of course is undesirable, so just return the loaded side as "primary" -and treat it as a single chest if the other sides are unloaded - -diff --git a/src/main/java/net/minecraft/world/level/block/DoubleBlockCombiner.java b/src/main/java/net/minecraft/world/level/block/DoubleBlockCombiner.java -index 963092cd5a4e756ad6a471379a81d8996cc2b091..20c9921339ec6b5127fbadcedc19577f3906074d 100644 ---- a/src/main/java/net/minecraft/world/level/block/DoubleBlockCombiner.java -+++ b/src/main/java/net/minecraft/world/level/block/DoubleBlockCombiner.java -@@ -34,7 +34,12 @@ public class DoubleBlockCombiner { - return new DoubleBlockCombiner.NeighborCombineResult.Single<>(blockEntity); - } else { - BlockPos blockPos = pos.relative(directionMapper.apply(state)); -- BlockState blockState = world.getBlockState(blockPos); -+ // Paper start - Don't load Chunks from Hoppers and other things -+ BlockState blockState = world.getBlockStateIfLoaded(blockPos); -+ if (blockState == null) { -+ return new DoubleBlockCombiner.NeighborCombineResult.Single<>(blockEntity); -+ } -+ // Paper end - Don't load Chunks from Hoppers and other things - if (blockState.is(state.getBlock())) { - DoubleBlockCombiner.BlockType blockType2 = typeMapper.apply(blockState); - if (blockType2 != DoubleBlockCombiner.BlockType.SINGLE diff --git a/patches/server/0303-Optimize-call-to-getFluid-for-explosions.patch b/patches/server/0303-Optimize-call-to-getFluid-for-explosions.patch new file mode 100644 index 000000000000..b6eb87bb5e74 --- /dev/null +++ b/patches/server/0303-Optimize-call-to-getFluid-for-explosions.patch @@ -0,0 +1,19 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: BrodyBeckwith +Date: Tue, 14 Jan 2020 17:49:03 -0500 +Subject: [PATCH] Optimize call to getFluid for explosions + + +diff --git a/src/main/java/net/minecraft/world/level/ServerExplosion.java b/src/main/java/net/minecraft/world/level/ServerExplosion.java +index d1878f597c3d8119e9b248f4fe8af435ce8c9710..d3d74a7f306d88b0d5cd83893b3ee5e750fd4caf 100644 +--- a/src/main/java/net/minecraft/world/level/ServerExplosion.java ++++ b/src/main/java/net/minecraft/world/level/ServerExplosion.java +@@ -150,7 +150,7 @@ public class ServerExplosion implements Explosion { + for (float f1 = 0.3F; f > 0.0F; f -= 0.22500001F) { + BlockPos blockposition = BlockPos.containing(d4, d5, d6); + BlockState iblockdata = this.level.getBlockState(blockposition); +- FluidState fluid = this.level.getFluidState(blockposition); ++ FluidState fluid = iblockdata.getFluidState(); // Paper - Perf: Optimize call to getFluid for explosions + + if (!this.level.isInWorldBounds(blockposition)) { + break; diff --git a/patches/server/0304-Guard-against-serializing-mismatching-chunk-coordina.patch b/patches/server/0304-Guard-against-serializing-mismatching-chunk-coordina.patch new file mode 100644 index 000000000000..26436c7223e1 --- /dev/null +++ b/patches/server/0304-Guard-against-serializing-mismatching-chunk-coordina.patch @@ -0,0 +1,63 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Spottedleaf +Date: Fri, 27 Dec 2019 09:42:26 -0800 +Subject: [PATCH] Guard against serializing mismatching chunk coordinate + +Should help if something dumb happens + +diff --git a/src/main/java/net/minecraft/world/level/chunk/storage/ChunkStorage.java b/src/main/java/net/minecraft/world/level/chunk/storage/ChunkStorage.java +index 329e7eb1c3e92445b3fae9e1e79d0497b7e3e3f2..909acbf5b8c6edcb4529647c11464d911d6f8c15 100644 +--- a/src/main/java/net/minecraft/world/level/chunk/storage/ChunkStorage.java ++++ b/src/main/java/net/minecraft/world/level/chunk/storage/ChunkStorage.java +@@ -172,8 +172,19 @@ public class ChunkStorage implements AutoCloseable { + } + + public CompletableFuture write(ChunkPos chunkPos, Supplier nbtSupplier) { ++ // Paper start - guard against possible chunk pos desync ++ final Supplier guardedPosCheck = () -> { ++ CompoundTag nbt = nbtSupplier.get(); ++ if (nbt != null && !chunkPos.equals(SerializableChunkData.getChunkCoordinate(nbt))) { ++ final String world = (ChunkStorage.this instanceof net.minecraft.server.level.ChunkMap) ? ((net.minecraft.server.level.ChunkMap) ChunkStorage.this).level.getWorld().getName() : null; ++ throw new IllegalArgumentException("Chunk coordinate and serialized data do not have matching coordinates, trying to serialize coordinate " + chunkPos ++ + " but compound says coordinate is " + SerializableChunkData.getChunkCoordinate(nbt) + (world == null ? " for an unknown world" : (" for world: " + world))); ++ } ++ return nbt; ++ }; ++ // Paper end - guard against possible chunk pos desync + this.handleLegacyStructureIndex(chunkPos); +- return this.worker.store(chunkPos, nbtSupplier); ++ return this.worker.store(chunkPos, guardedPosCheck); // Paper - guard against possible chunk pos desync + } + + protected void handleLegacyStructureIndex(ChunkPos chunkPos) { +diff --git a/src/main/java/net/minecraft/world/level/chunk/storage/SerializableChunkData.java b/src/main/java/net/minecraft/world/level/chunk/storage/SerializableChunkData.java +index 4367ccc628bb4f404d6a081083002518442f462b..92ad7704a77c024afe090488764eaf59a4f7505f 100644 +--- a/src/main/java/net/minecraft/world/level/chunk/storage/SerializableChunkData.java ++++ b/src/main/java/net/minecraft/world/level/chunk/storage/SerializableChunkData.java +@@ -91,13 +91,25 @@ public record SerializableChunkData(Registry biomeRegistry, ChunkPos chun + public static final String SECTIONS_TAG = "sections"; + public static final String BLOCK_LIGHT_TAG = "BlockLight"; + public static final String SKY_LIGHT_TAG = "SkyLight"; ++ // Paper start - guard against serializing mismatching coordinates ++ // TODO Note: This needs to be re-checked each update ++ public static ChunkPos getChunkCoordinate(final CompoundTag chunkData) { ++ final int dataVersion = ChunkStorage.getVersion(chunkData); ++ if (dataVersion < 2842) { // Level tag is removed after this version ++ final CompoundTag levelData = chunkData.getCompound("Level"); ++ return new ChunkPos(levelData.getInt("xPos"), levelData.getInt("zPos")); ++ } else { ++ return new ChunkPos(chunkData.getInt("xPos"), chunkData.getInt("zPos")); ++ } ++ } ++ // Paper end - guard against serializing mismatching coordinates + + @Nullable + public static SerializableChunkData parse(LevelHeightAccessor world, RegistryAccess registryManager, CompoundTag nbt) { + if (!nbt.contains("Status", 8)) { + return null; + } else { +- ChunkPos chunkcoordintpair = new ChunkPos(nbt.getInt("xPos"), nbt.getInt("zPos")); ++ ChunkPos chunkcoordintpair = new ChunkPos(nbt.getInt("xPos"), nbt.getInt("zPos")); // Paper - guard against serializing mismatching coordinates; diff on change, see ChunkSerializer#getChunkCoordinate + long i = nbt.getLong("LastUpdate"); + long j = nbt.getLong("InhabitedTime"); + ChunkStatus chunkstatus = ChunkStatus.byName(nbt.getString("Status")); diff --git a/patches/server/0304-Optimise-EntityGetter-getPlayerByUUID.patch b/patches/server/0304-Optimise-EntityGetter-getPlayerByUUID.patch deleted file mode 100644 index 6ffcd9f3c3bf..000000000000 --- a/patches/server/0304-Optimise-EntityGetter-getPlayerByUUID.patch +++ /dev/null @@ -1,27 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Spottedleaf -Date: Sat, 11 Jan 2020 21:50:56 -0800 -Subject: [PATCH] Optimise EntityGetter#getPlayerByUUID - -Use the PlayerList map instead of iterating over all players - -diff --git a/src/main/java/net/minecraft/server/level/ServerLevel.java b/src/main/java/net/minecraft/server/level/ServerLevel.java -index a6a21def1ae0d35fa106da6242c49a0928e4eda5..c85d5b941b5cb1c37cd2619d419006c5956f6eaf 100644 ---- a/src/main/java/net/minecraft/server/level/ServerLevel.java -+++ b/src/main/java/net/minecraft/server/level/ServerLevel.java -@@ -330,6 +330,15 @@ public class ServerLevel extends Level implements WorldGenLevel { - } - // Paper end - -+ // Paper start - optimise getPlayerByUUID -+ @Nullable -+ @Override -+ public Player getPlayerByUUID(UUID uuid) { -+ final Player player = this.getServer().getPlayerList().getPlayer(uuid); -+ return player != null && player.level() == this ? player : null; -+ } -+ // Paper end - optimise getPlayerByUUID -+ - // Add env and gen to constructor, IWorldDataServer -> WorldDataServer - public ServerLevel(MinecraftServer minecraftserver, Executor executor, LevelStorageSource.LevelStorageAccess convertable_conversionsession, PrimaryLevelData iworlddataserver, ResourceKey resourcekey, LevelStem worlddimension, ChunkProgressListener worldloadlistener, boolean flag, long i, List list, boolean flag1, @Nullable RandomSequences randomsequences, org.bukkit.World.Environment env, org.bukkit.generator.ChunkGenerator gen, org.bukkit.generator.BiomeProvider biomeProvider) { - // IRegistryCustom.Dimension iregistrycustom_dimension = minecraftserver.registryAccess(); // CraftBukkit - decompile error diff --git a/patches/server/0305-Alternative-item-despawn-rate.patch b/patches/server/0305-Alternative-item-despawn-rate.patch new file mode 100644 index 000000000000..ba7e9db85de8 --- /dev/null +++ b/patches/server/0305-Alternative-item-despawn-rate.patch @@ -0,0 +1,63 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: kickash32 +Date: Mon, 3 Jun 2019 02:02:39 -0400 +Subject: [PATCH] Alternative item-despawn-rate + +Co-authored-by: Noah van der Aa + +diff --git a/src/main/java/net/minecraft/world/entity/item/ItemEntity.java b/src/main/java/net/minecraft/world/entity/item/ItemEntity.java +index c21fa55c62d97d9511e41a1e313e904330a6eee6..9974aec00935a1c3068eceee6d7042f14f15ac56 100644 +--- a/src/main/java/net/minecraft/world/entity/item/ItemEntity.java ++++ b/src/main/java/net/minecraft/world/entity/item/ItemEntity.java +@@ -62,6 +62,7 @@ public class ItemEntity extends Entity implements TraceableEntity { + public final float bobOffs; + private int lastTick = MinecraftServer.currentTick - 1; // CraftBukkit + public boolean canMobPickup = true; // Paper - Item#canEntityPickup ++ private int despawnRate = -1; // Paper - Alternative item-despawn-rate + + public ItemEntity(EntityType type, Level world) { + super(type, world); +@@ -216,7 +217,7 @@ public class ItemEntity extends Entity implements TraceableEntity { + } + } + +- if (!this.level().isClientSide && this.age >= this.level().spigotConfig.itemDespawnRate) { // Spigot ++ if (!this.level().isClientSide && this.age >= this.despawnRate) { // Spigot // Paper - Alternative item-despawn-rate + // CraftBukkit start - fire ItemDespawnEvent + if (CraftEventFactory.callItemDespawnEvent(this).isCancelled()) { + this.age = 0; +@@ -239,7 +240,7 @@ public class ItemEntity extends Entity implements TraceableEntity { + this.lastTick = MinecraftServer.currentTick; + // CraftBukkit end + +- if (!this.level().isClientSide && this.age >= this.level().spigotConfig.itemDespawnRate) { // Spigot ++ if (!this.level().isClientSide && this.age >= this.despawnRate) { // Spigot // Paper - Alternative item-despawn-rate + // CraftBukkit start - fire ItemDespawnEvent + if (org.bukkit.craftbukkit.event.CraftEventFactory.callItemDespawnEvent(this).isCancelled()) { + this.age = 0; +@@ -297,7 +298,7 @@ public class ItemEntity extends Entity implements TraceableEntity { + private boolean isMergable() { + ItemStack itemstack = this.getItem(); + +- return this.isAlive() && this.pickupDelay != 32767 && this.age != -32768 && this.age < 6000 && itemstack.getCount() < itemstack.getMaxStackSize(); ++ return this.isAlive() && this.pickupDelay != 32767 && this.age != -32768 && this.age < this.despawnRate && itemstack.getCount() < itemstack.getMaxStackSize(); // Paper - Alternative item-despawn-rate + } + + private void tryToMerge(ItemEntity other) { +@@ -558,6 +559,7 @@ public class ItemEntity extends Entity implements TraceableEntity { + + public void setItem(ItemStack stack) { + this.getEntityData().set(ItemEntity.DATA_ITEM, stack); ++ this.despawnRate = this.level().paperConfig().entities.spawning.altItemDespawnRate.enabled ? this.level().paperConfig().entities.spawning.altItemDespawnRate.items.getOrDefault(stack.getItem(), this.level().spigotConfig.itemDespawnRate) : this.level().spigotConfig.itemDespawnRate; // Paper - Alternative item-despawn-rate + } + + @Override +@@ -612,7 +614,7 @@ public class ItemEntity extends Entity implements TraceableEntity { + + public void makeFakeItem() { + this.setNeverPickUp(); +- this.age = this.level().spigotConfig.itemDespawnRate - 1; // Spigot ++ this.age = this.despawnRate - 1; // Spigot // Paper - Alternative item-despawn-rate + } + + public static float getSpin(float f, float f1) { diff --git a/patches/server/0305-Fix-items-not-falling-correctly.patch b/patches/server/0305-Fix-items-not-falling-correctly.patch deleted file mode 100644 index 5be02c3b7bd1..000000000000 --- a/patches/server/0305-Fix-items-not-falling-correctly.patch +++ /dev/null @@ -1,42 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: AJMFactsheets -Date: Fri, 17 Jan 2020 17:17:54 -0600 -Subject: [PATCH] Fix items not falling correctly - -Since 1.14, Mojang has added an optimization which skips checking if -an item should fall every fourth tick. - -However, Spigot's entity activation range class also has an -optimization which skips ticking active entities every fourth tick. -This can result in a state where an item will never properly fall -due to its move method never being called. - -This patch resolves the conflict by offsetting checking Spigot's entity -activation range check from an item's move method. - -diff --git a/src/main/java/net/minecraft/world/entity/item/ItemEntity.java b/src/main/java/net/minecraft/world/entity/item/ItemEntity.java -index f9dfd6e7b610cfee75524a525ab0e72bed5522da..e110296a95441a13ec431d897796326b0277edb1 100644 ---- a/src/main/java/net/minecraft/world/entity/item/ItemEntity.java -+++ b/src/main/java/net/minecraft/world/entity/item/ItemEntity.java -@@ -175,7 +175,7 @@ public class ItemEntity extends Entity implements TraceableEntity { - } - } - -- if (!this.onGround() || this.getDeltaMovement().horizontalDistanceSqr() > 9.999999747378752E-6D || (this.tickCount + this.getId()) % 4 == 0) { -+ if (!this.onGround() || this.getDeltaMovement().horizontalDistanceSqr() > 9.999999747378752E-6D || (this.tickCount + this.getId()) % 4 == 0) { // Paper - Diff on change - this.move(MoverType.SELF, this.getDeltaMovement()); - float f = 0.98F; - -diff --git a/src/main/java/org/spigotmc/ActivationRange.java b/src/main/java/org/spigotmc/ActivationRange.java -index 6d51464f6368151e8acc532414ee223714584e96..9fb9fa62c32445ac3c3883a6433759c86dcfc428 100644 ---- a/src/main/java/org/spigotmc/ActivationRange.java -+++ b/src/main/java/org/spigotmc/ActivationRange.java -@@ -256,7 +256,7 @@ public class ActivationRange - isActive = true; - } - // Add a little performance juice to active entities. Skip 1/4 if not immune. -- } else if ( !entity.defaultActivationState && entity.tickCount % 4 == 0 && !ActivationRange.checkEntityImmunities( entity ) ) -+ } else if ( !entity.defaultActivationState && (entity.tickCount + entity.getId()) % 4 == 0 && !ActivationRange.checkEntityImmunities( entity ) ) // Paper - Ensure checking item movement is offset from Spigot's entity activation range check - { - isActive = false; - } diff --git a/patches/server/0306-Optimize-call-to-getFluid-for-explosions.patch b/patches/server/0306-Optimize-call-to-getFluid-for-explosions.patch deleted file mode 100644 index 7a9152c9ec21..000000000000 --- a/patches/server/0306-Optimize-call-to-getFluid-for-explosions.patch +++ /dev/null @@ -1,19 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: BrodyBeckwith -Date: Tue, 14 Jan 2020 17:49:03 -0500 -Subject: [PATCH] Optimize call to getFluid for explosions - - -diff --git a/src/main/java/net/minecraft/world/level/Explosion.java b/src/main/java/net/minecraft/world/level/Explosion.java -index ce8ac06b47e81161ad5ff6cc865ad6d3d59807c1..d4fe425954d36f0baddb256e3c83009a84084b72 100644 ---- a/src/main/java/net/minecraft/world/level/Explosion.java -+++ b/src/main/java/net/minecraft/world/level/Explosion.java -@@ -194,7 +194,7 @@ public class Explosion { - for (float f1 = 0.3F; f > 0.0F; f -= 0.22500001F) { - BlockPos blockposition = BlockPos.containing(d4, d5, d6); - BlockState iblockdata = this.level.getBlockState(blockposition); -- FluidState fluid = this.level.getFluidState(blockposition); -+ FluidState fluid = iblockdata.getFluidState(); // Paper - Perf: Optimize call to getFluid for explosions - - if (!this.level.isInWorldBounds(blockposition)) { - break; diff --git a/patches/server/0306-Tracking-Range-Improvements.patch b/patches/server/0306-Tracking-Range-Improvements.patch new file mode 100644 index 000000000000..c22d82fbf9a9 --- /dev/null +++ b/patches/server/0306-Tracking-Range-Improvements.patch @@ -0,0 +1,78 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: kickash32 +Date: Sat, 21 Dec 2019 15:22:09 -0500 +Subject: [PATCH] Tracking Range Improvements + +Sets tracking range of watermobs to animals instead of misc and simplifies code + +Also ignores Enderdragon, defaulting it to Mojang's setting + +diff --git a/src/main/java/net/minecraft/server/level/ChunkMap.java b/src/main/java/net/minecraft/server/level/ChunkMap.java +index 985ba48a5ac027d3c3dcd9b710b53748508966fb..5f85d7f7ec57fc1b0375e62a8e8e3e8783f34193 100644 +--- a/src/main/java/net/minecraft/server/level/ChunkMap.java ++++ b/src/main/java/net/minecraft/server/level/ChunkMap.java +@@ -1598,6 +1598,7 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider + while (iterator.hasNext()) { + Entity entity = (Entity) iterator.next(); + int j = entity.getType().clientTrackingRange() * 16; ++ j = org.spigotmc.TrackingRange.getEntityTrackingRange(entity, j); // Paper + + if (j > i) { + i = j; +diff --git a/src/main/java/org/spigotmc/TrackingRange.java b/src/main/java/org/spigotmc/TrackingRange.java +index 73f9563551632a5369ba55e8fe9211afc325e869..bb06f89a29f30144e7e2113e088a503db006a83c 100644 +--- a/src/main/java/org/spigotmc/TrackingRange.java ++++ b/src/main/java/org/spigotmc/TrackingRange.java +@@ -7,7 +7,6 @@ import net.minecraft.world.entity.ExperienceOrb; + import net.minecraft.world.entity.decoration.ItemFrame; + import net.minecraft.world.entity.decoration.Painting; + import net.minecraft.world.entity.item.ItemEntity; +-import net.minecraft.world.entity.monster.Ghast; + + public class TrackingRange + { +@@ -30,22 +29,21 @@ public class TrackingRange + if ( entity instanceof ServerPlayer ) + { + return config.playerTrackingRange; +- } else if ( entity.activationType == ActivationRange.ActivationType.MONSTER || entity.activationType == ActivationRange.ActivationType.RAIDER ) +- { +- return config.monsterTrackingRange; +- } else if ( entity instanceof Ghast ) +- { +- if ( config.monsterTrackingRange > config.monsterActivationRange ) +- { ++ // Paper start - Simplify and set water mobs to animal tracking range ++ } ++ switch (entity.activationType) { ++ case RAIDER: ++ case MONSTER: ++ case FLYING_MONSTER: + return config.monsterTrackingRange; +- } else +- { +- return config.monsterActivationRange; +- } +- } else if ( entity.activationType == ActivationRange.ActivationType.ANIMAL ) +- { +- return config.animalTrackingRange; +- } else if ( entity instanceof ItemFrame || entity instanceof Painting || entity instanceof ItemEntity || entity instanceof ExperienceOrb ) ++ case WATER: ++ case VILLAGER: ++ case ANIMAL: ++ return config.animalTrackingRange; ++ case MISC: ++ } ++ if ( entity instanceof ItemFrame || entity instanceof Painting || entity instanceof ItemEntity || entity instanceof ExperienceOrb ) ++ // Paper end + { + return config.miscTrackingRange; + } else if ( entity instanceof Display ) +@@ -53,6 +51,7 @@ public class TrackingRange + return config.displayTrackingRange; + } else + { ++ if (entity instanceof net.minecraft.world.entity.boss.enderdragon.EnderDragon) return ((net.minecraft.server.level.ServerLevel)(entity.getCommandSenderWorld())).getChunkSource().chunkMap.serverViewDistance; // Paper - enderdragon is exempt + return config.otherTrackingRange; + } + } diff --git a/patches/server/0307-Bees-get-gravity-in-void.-Fixes-MC-167279.patch b/patches/server/0307-Bees-get-gravity-in-void.-Fixes-MC-167279.patch new file mode 100644 index 000000000000..cf391a4f9f7f --- /dev/null +++ b/patches/server/0307-Bees-get-gravity-in-void.-Fixes-MC-167279.patch @@ -0,0 +1,34 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: William Blake Galbreath +Date: Sun, 26 Jan 2020 16:30:19 -0600 +Subject: [PATCH] Bees get gravity in void. Fixes MC-167279 + + +diff --git a/src/main/java/net/minecraft/world/entity/animal/Bee.java b/src/main/java/net/minecraft/world/entity/animal/Bee.java +index 310a2aa23365f9a8a8b7b6fa2323aed792f95adb..adff3bec90786b87323653cf4f94a38c7b9ef7ff 100644 +--- a/src/main/java/net/minecraft/world/entity/animal/Bee.java ++++ b/src/main/java/net/minecraft/world/entity/animal/Bee.java +@@ -150,7 +150,22 @@ public class Bee extends Animal implements NeutralMob, FlyingAnimal { + public Bee(EntityType type, Level world) { + super(type, world); + this.remainingCooldownBeforeLocatingNewFlower = Mth.nextInt(this.random, 20, 60); +- this.moveControl = new FlyingMoveControl(this, 20, true); ++ // Paper start - Fix MC-167279 ++ class BeeFlyingMoveControl extends FlyingMoveControl { ++ public BeeFlyingMoveControl(final Mob entity, final int maxPitchChange, final boolean noGravity) { ++ super(entity, maxPitchChange, noGravity); ++ } ++ ++ @Override ++ public void tick() { ++ if (this.mob.getY() <= Bee.this.level().getMinY()) { ++ this.mob.setNoGravity(false); ++ } ++ super.tick(); ++ } ++ } ++ this.moveControl = new BeeFlyingMoveControl(this, 20, true); ++ // Paper end - Fix MC-167279 + this.lookControl = new Bee.BeeLookControl(this); + this.setPathfindingMalus(PathType.DANGER_FIRE, -1.0F); + this.setPathfindingMalus(PathType.WATER, -1.0F); diff --git a/patches/server/0307-Guard-against-serializing-mismatching-chunk-coordina.patch b/patches/server/0307-Guard-against-serializing-mismatching-chunk-coordina.patch deleted file mode 100644 index 6752b44617db..000000000000 --- a/patches/server/0307-Guard-against-serializing-mismatching-chunk-coordina.patch +++ /dev/null @@ -1,52 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Spottedleaf -Date: Fri, 27 Dec 2019 09:42:26 -0800 -Subject: [PATCH] Guard against serializing mismatching chunk coordinate - -Should help if something dumb happens - -diff --git a/src/main/java/net/minecraft/world/level/chunk/storage/ChunkSerializer.java b/src/main/java/net/minecraft/world/level/chunk/storage/ChunkSerializer.java -index 47f5f3d58bb3bf85cf35f9baae77df7fab5c844f..0dd6f1bce4913cb84ba21a20df5573bab3a64645 100644 ---- a/src/main/java/net/minecraft/world/level/chunk/storage/ChunkSerializer.java -+++ b/src/main/java/net/minecraft/world/level/chunk/storage/ChunkSerializer.java -@@ -88,8 +88,21 @@ public class ChunkSerializer { - - public ChunkSerializer() {} - -+ // Paper start - guard against serializing mismatching coordinates -+ // TODO Note: This needs to be re-checked each update -+ public static ChunkPos getChunkCoordinate(final CompoundTag chunkData) { -+ final int dataVersion = ChunkStorage.getVersion(chunkData); -+ if (dataVersion < 2842) { // Level tag is removed after this version -+ final CompoundTag levelData = chunkData.getCompound("Level"); -+ return new ChunkPos(levelData.getInt("xPos"), levelData.getInt("zPos")); -+ } else { -+ return new ChunkPos(chunkData.getInt("xPos"), chunkData.getInt("zPos")); -+ } -+ } -+ // Paper end - guard against serializing mismatching coordinates -+ - public static ProtoChunk read(ServerLevel world, PoiManager poiStorage, RegionStorageInfo key, ChunkPos chunkPos, CompoundTag nbt) { -- ChunkPos chunkcoordintpair1 = new ChunkPos(nbt.getInt("xPos"), nbt.getInt("zPos")); -+ ChunkPos chunkcoordintpair1 = new ChunkPos(nbt.getInt("xPos"), nbt.getInt("zPos")); // Paper - guard against serializing mismatching coordinates; diff on change, see ChunkSerializer#getChunkCoordinate - - if (!Objects.equals(chunkPos, chunkcoordintpair1)) { - ChunkSerializer.LOGGER.error("Chunk file at {} is in the wrong location; relocating. (Expected {}, got {})", new Object[]{chunkPos, chunkPos, chunkcoordintpair1}); -diff --git a/src/main/java/net/minecraft/world/level/chunk/storage/ChunkStorage.java b/src/main/java/net/minecraft/world/level/chunk/storage/ChunkStorage.java -index 3e194944e50f8395074d68d4abe4c084c3fadcc1..9aa9ab894080a5819fc45698771afd034906d36a 100644 ---- a/src/main/java/net/minecraft/world/level/chunk/storage/ChunkStorage.java -+++ b/src/main/java/net/minecraft/world/level/chunk/storage/ChunkStorage.java -@@ -172,6 +172,13 @@ public class ChunkStorage implements AutoCloseable { - } - - public CompletableFuture write(ChunkPos chunkPos, CompoundTag nbt) { -+ // Paper start - guard against serializing mismatching coordinates -+ if (nbt != null && !chunkPos.equals(ChunkSerializer.getChunkCoordinate(nbt))) { -+ final String world = (this instanceof net.minecraft.server.level.ChunkMap) ? ((net.minecraft.server.level.ChunkMap) this).level.getWorld().getName() : null; -+ throw new IllegalArgumentException("Chunk coordinate and serialized data do not have matching coordinates, trying to serialize coordinate " + chunkPos -+ + " but compound says coordinate is " + ChunkSerializer.getChunkCoordinate(nbt) + (world == null ? " for an unknown world" : (" for world: " + world))); -+ } -+ // Paper end - guard against serializing mismatching coordinates - this.handleLegacyStructureIndex(chunkPos); - return this.worker.store(chunkPos, nbt); - } diff --git a/patches/server/0308-Alternative-item-despawn-rate.patch b/patches/server/0308-Alternative-item-despawn-rate.patch deleted file mode 100644 index d4e1a80c7c9d..000000000000 --- a/patches/server/0308-Alternative-item-despawn-rate.patch +++ /dev/null @@ -1,63 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: kickash32 -Date: Mon, 3 Jun 2019 02:02:39 -0400 -Subject: [PATCH] Alternative item-despawn-rate - -Co-authored-by: Noah van der Aa - -diff --git a/src/main/java/net/minecraft/world/entity/item/ItemEntity.java b/src/main/java/net/minecraft/world/entity/item/ItemEntity.java -index e110296a95441a13ec431d897796326b0277edb1..c724359995d65c88e7f365eea55f3e4382a46ddd 100644 ---- a/src/main/java/net/minecraft/world/entity/item/ItemEntity.java -+++ b/src/main/java/net/minecraft/world/entity/item/ItemEntity.java -@@ -62,6 +62,7 @@ public class ItemEntity extends Entity implements TraceableEntity { - public final float bobOffs; - private int lastTick = MinecraftServer.currentTick - 1; // CraftBukkit - public boolean canMobPickup = true; // Paper - Item#canEntityPickup -+ private int despawnRate = -1; // Paper - Alternative item-despawn-rate - - public ItemEntity(EntityType type, Level world) { - super(type, world); -@@ -215,7 +216,7 @@ public class ItemEntity extends Entity implements TraceableEntity { - } - } - -- if (!this.level().isClientSide && this.age >= this.level().spigotConfig.itemDespawnRate) { // Spigot -+ if (!this.level().isClientSide && this.age >= this.despawnRate) { // Spigot // Paper - Alternative item-despawn-rate - // CraftBukkit start - fire ItemDespawnEvent - if (CraftEventFactory.callItemDespawnEvent(this).isCancelled()) { - this.age = 0; -@@ -238,7 +239,7 @@ public class ItemEntity extends Entity implements TraceableEntity { - this.lastTick = MinecraftServer.currentTick; - // CraftBukkit end - -- if (!this.level().isClientSide && this.age >= this.level().spigotConfig.itemDespawnRate) { // Spigot -+ if (!this.level().isClientSide && this.age >= this.despawnRate) { // Spigot // Paper - Alternative item-despawn-rate - // CraftBukkit start - fire ItemDespawnEvent - if (org.bukkit.craftbukkit.event.CraftEventFactory.callItemDespawnEvent(this).isCancelled()) { - this.age = 0; -@@ -294,7 +295,7 @@ public class ItemEntity extends Entity implements TraceableEntity { - private boolean isMergable() { - ItemStack itemstack = this.getItem(); - -- return this.isAlive() && this.pickupDelay != 32767 && this.age != -32768 && this.age < 6000 && itemstack.getCount() < itemstack.getMaxStackSize(); -+ return this.isAlive() && this.pickupDelay != 32767 && this.age != -32768 && this.age < this.despawnRate && itemstack.getCount() < itemstack.getMaxStackSize(); // Paper - Alternative item-despawn-rate - } - - private void tryToMerge(ItemEntity other) { -@@ -542,6 +543,7 @@ public class ItemEntity extends Entity implements TraceableEntity { - - public void setItem(ItemStack stack) { - this.getEntityData().set(ItemEntity.DATA_ITEM, stack); -+ this.despawnRate = this.level().paperConfig().entities.spawning.altItemDespawnRate.enabled ? this.level().paperConfig().entities.spawning.altItemDespawnRate.items.getOrDefault(stack.getItem(), this.level().spigotConfig.itemDespawnRate) : this.level().spigotConfig.itemDespawnRate; // Paper - Alternative item-despawn-rate - } - - @Override -@@ -596,7 +598,7 @@ public class ItemEntity extends Entity implements TraceableEntity { - - public void makeFakeItem() { - this.setNeverPickUp(); -- this.age = this.level().spigotConfig.itemDespawnRate - 1; // Spigot -+ this.age = this.despawnRate - 1; // Spigot // Paper - Alternative item-despawn-rate - } - - public float getSpin(float tickDelta) { diff --git a/patches/server/0308-Improve-Block-breakNaturally-API.patch b/patches/server/0308-Improve-Block-breakNaturally-API.patch new file mode 100644 index 000000000000..55dcdabf6445 --- /dev/null +++ b/patches/server/0308-Improve-Block-breakNaturally-API.patch @@ -0,0 +1,95 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Jake Potrebic +Date: Thu, 2 Jan 2020 12:25:07 -0600 +Subject: [PATCH] Improve Block#breakNaturally API + +Adds bool parameter to play world effect on block break + +Adds bool parameter to drop xp from blocks + +Fixes fluid-logged blocks not leaving fluid behind if +broken + +Handles special cases for ice and turtle eggs + +== AT == +public net.minecraft.world.level.block.TurtleEggBlock decreaseEggs(Lnet/minecraft/world/level/Level;Lnet/minecraft/core/BlockPos;Lnet/minecraft/world/level/block/state/BlockState;)V + +Co-authored-by: William Blake Galbreath + +diff --git a/src/main/java/net/minecraft/world/level/block/IceBlock.java b/src/main/java/net/minecraft/world/level/block/IceBlock.java +index 6a4cc64068f67ee18a9e505888edb3874597b98a..067a2d969ca0979ae67b600e303deec93eda0251 100644 +--- a/src/main/java/net/minecraft/world/level/block/IceBlock.java ++++ b/src/main/java/net/minecraft/world/level/block/IceBlock.java +@@ -36,6 +36,11 @@ public class IceBlock extends HalfTransparentBlock { + @Override + public void playerDestroy(Level world, Player player, BlockPos pos, BlockState state, @Nullable BlockEntity blockEntity, ItemStack tool) { + super.playerDestroy(world, player, pos, state, blockEntity, tool); ++ // Paper start - Improve Block#breakNaturally API ++ this.afterDestroy(world, pos, tool); ++ } ++ public void afterDestroy(Level world, BlockPos pos, ItemStack tool) { ++ // Paper end - Improve Block#breakNaturally API + if (!EnchantmentHelper.hasTag(tool, EnchantmentTags.PREVENTS_ICE_MELTING)) { + if (world.dimensionType().ultraWarm()) { + world.removeBlock(pos, false); +diff --git a/src/main/java/org/bukkit/craftbukkit/block/CraftBlock.java b/src/main/java/org/bukkit/craftbukkit/block/CraftBlock.java +index e5ac2d79b21db4e9a92b739e39d41a22deee175b..aae00320ab8003420bae5de7df47f553b62c5aab 100644 +--- a/src/main/java/org/bukkit/craftbukkit/block/CraftBlock.java ++++ b/src/main/java/org/bukkit/craftbukkit/block/CraftBlock.java +@@ -452,6 +452,18 @@ public class CraftBlock implements Block { + + @Override + public boolean breakNaturally(ItemStack item) { ++ // Paper start ++ return this.breakNaturally(item, false); ++ } ++ ++ @Override ++ public boolean breakNaturally(boolean triggerEffect, boolean dropExperience) { ++ return this.breakNaturally(null, triggerEffect, dropExperience); ++ } ++ ++ @Override ++ public boolean breakNaturally(ItemStack item, boolean triggerEffect, boolean dropExperience) { ++ // Paper end + // Order matters here, need to drop before setting to air so skulls can get their data + net.minecraft.world.level.block.state.BlockState iblockdata = this.getNMS(); + net.minecraft.world.level.block.Block block = iblockdata.getBlock(); +@@ -461,11 +473,35 @@ public class CraftBlock implements Block { + // Modelled off EntityHuman#hasBlock + if (block != Blocks.AIR && (item == null || !iblockdata.requiresCorrectToolForDrops() || nmsItem.isCorrectToolForDrops(iblockdata))) { + net.minecraft.world.level.block.Block.dropResources(iblockdata, this.world.getMinecraftWorld(), this.position, this.world.getBlockEntity(this.position), null, nmsItem); ++ // Paper start - improve Block#breanNaturally ++ if (triggerEffect) { ++ if (iblockdata.getBlock() instanceof net.minecraft.world.level.block.BaseFireBlock) { ++ this.world.levelEvent(net.minecraft.world.level.block.LevelEvent.SOUND_EXTINGUISH_FIRE, this.position, 0); ++ } else { ++ this.world.levelEvent(net.minecraft.world.level.block.LevelEvent.PARTICLES_DESTROY_BLOCK, this.position, net.minecraft.world.level.block.Block.getId(iblockdata)); ++ } ++ } ++ if (dropExperience) block.popExperience(this.world.getMinecraftWorld(), this.position, block.getExpDrop(iblockdata, this.world.getMinecraftWorld(), this.position, nmsItem, true)); ++ // Paper end + result = true; + } + + // SPIGOT-6778: Directly call setBlock instead of setTypeAndData, so that the tile entiy is not removed and custom remove logic is run. +- return this.world.setBlock(this.position, Blocks.AIR.defaultBlockState(), 3) && result; ++ // Paper start - improve breakNaturally ++ boolean destroyed = this.world.removeBlock(this.position, false); ++ if (destroyed) { ++ block.destroy(this.world, this.position, iblockdata); ++ } ++ if (result) { ++ // special cases ++ if (block instanceof net.minecraft.world.level.block.IceBlock iceBlock) { ++ iceBlock.afterDestroy(this.world.getMinecraftWorld(), this.position, nmsItem); ++ } else if (block instanceof net.minecraft.world.level.block.TurtleEggBlock turtleEggBlock) { ++ turtleEggBlock.decreaseEggs(this.world.getMinecraftWorld(), this.position, iblockdata); ++ } ++ } ++ return destroyed && result; ++ // Paper end + } + + @Override diff --git a/patches/server/0309-Optimise-getChunkAt-calls-for-loaded-chunks.patch b/patches/server/0309-Optimise-getChunkAt-calls-for-loaded-chunks.patch new file mode 100644 index 000000000000..314e598d7be4 --- /dev/null +++ b/patches/server/0309-Optimise-getChunkAt-calls-for-loaded-chunks.patch @@ -0,0 +1,60 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Spottedleaf +Date: Sat, 25 Jan 2020 17:04:35 -0800 +Subject: [PATCH] Optimise getChunkAt calls for loaded chunks + +bypass the need to get a player chunk, then get the either, +then unwrap it... + +diff --git a/src/main/java/net/minecraft/server/level/ServerChunkCache.java b/src/main/java/net/minecraft/server/level/ServerChunkCache.java +index d4eb7608a3e40d2da4c427e9b3a2ce916be86df1..43e01db5314452c194d2809fdc6a71c6fb42d8d2 100644 +--- a/src/main/java/net/minecraft/server/level/ServerChunkCache.java ++++ b/src/main/java/net/minecraft/server/level/ServerChunkCache.java +@@ -191,6 +191,12 @@ public class ServerChunkCache extends ChunkSource { + return this.getChunk(x, z, leastStatus, create); + }, this.mainThreadProcessor).join(); + } else { ++ // Paper start - Perf: Optimise getChunkAt calls for loaded chunks ++ LevelChunk ifLoaded = this.getChunkAtIfLoadedMainThread(x, z); ++ if (ifLoaded != null) { ++ return ifLoaded; ++ } ++ // Paper end - Perf: Optimise getChunkAt calls for loaded chunks + ProfilerFiller gameprofilerfiller = Profiler.get(); + + gameprofilerfiller.incrementCounter("getChunk"); +@@ -230,33 +236,7 @@ public class ServerChunkCache extends ChunkSource { + if (Thread.currentThread() != this.mainThread) { + return null; + } else { +- Profiler.get().incrementCounter("getChunkNow"); +- long k = ChunkPos.asLong(chunkX, chunkZ); +- +- ChunkAccess ichunkaccess; +- +- for (int l = 0; l < 4; ++l) { +- if (k == this.lastChunkPos[l] && this.lastChunkStatus[l] == ChunkStatus.FULL) { +- ichunkaccess = this.lastChunk[l]; +- return ichunkaccess instanceof LevelChunk ? (LevelChunk) ichunkaccess : null; +- } +- } +- +- ChunkHolder playerchunk = this.getVisibleChunkIfPresent(k); +- +- if (playerchunk == null) { +- return null; +- } else { +- ichunkaccess = playerchunk.getChunkIfPresent(ChunkStatus.FULL); +- if (ichunkaccess != null) { +- this.storeInCache(k, ichunkaccess, ChunkStatus.FULL); +- if (ichunkaccess instanceof LevelChunk) { +- return (LevelChunk) ichunkaccess; +- } +- } +- +- return null; +- } ++ return this.getChunkAtIfLoadedMainThread(chunkX, chunkZ); // Paper - Perf: Optimise getChunkAt calls for loaded chunks + } + } + diff --git a/patches/server/0309-Tracking-Range-Improvements.patch b/patches/server/0309-Tracking-Range-Improvements.patch deleted file mode 100644 index 845b77df2ee9..000000000000 --- a/patches/server/0309-Tracking-Range-Improvements.patch +++ /dev/null @@ -1,78 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: kickash32 -Date: Sat, 21 Dec 2019 15:22:09 -0500 -Subject: [PATCH] Tracking Range Improvements - -Sets tracking range of watermobs to animals instead of misc and simplifies code - -Also ignores Enderdragon, defaulting it to Mojang's setting - -diff --git a/src/main/java/net/minecraft/server/level/ChunkMap.java b/src/main/java/net/minecraft/server/level/ChunkMap.java -index 5573a5d0d0e10e3c584e821d3e8e7ba64a41a627..ee54706b36bd227edacea2a1b6099009bd652039 100644 ---- a/src/main/java/net/minecraft/server/level/ChunkMap.java -+++ b/src/main/java/net/minecraft/server/level/ChunkMap.java -@@ -1545,6 +1545,7 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider - while (iterator.hasNext()) { - Entity entity = (Entity) iterator.next(); - int j = entity.getType().clientTrackingRange() * 16; -+ j = org.spigotmc.TrackingRange.getEntityTrackingRange(entity, j); // Paper - - if (j > i) { - i = j; -diff --git a/src/main/java/org/spigotmc/TrackingRange.java b/src/main/java/org/spigotmc/TrackingRange.java -index 73f9563551632a5369ba55e8fe9211afc325e869..bb06f89a29f30144e7e2113e088a503db006a83c 100644 ---- a/src/main/java/org/spigotmc/TrackingRange.java -+++ b/src/main/java/org/spigotmc/TrackingRange.java -@@ -7,7 +7,6 @@ import net.minecraft.world.entity.ExperienceOrb; - import net.minecraft.world.entity.decoration.ItemFrame; - import net.minecraft.world.entity.decoration.Painting; - import net.minecraft.world.entity.item.ItemEntity; --import net.minecraft.world.entity.monster.Ghast; - - public class TrackingRange - { -@@ -30,22 +29,21 @@ public class TrackingRange - if ( entity instanceof ServerPlayer ) - { - return config.playerTrackingRange; -- } else if ( entity.activationType == ActivationRange.ActivationType.MONSTER || entity.activationType == ActivationRange.ActivationType.RAIDER ) -- { -- return config.monsterTrackingRange; -- } else if ( entity instanceof Ghast ) -- { -- if ( config.monsterTrackingRange > config.monsterActivationRange ) -- { -+ // Paper start - Simplify and set water mobs to animal tracking range -+ } -+ switch (entity.activationType) { -+ case RAIDER: -+ case MONSTER: -+ case FLYING_MONSTER: - return config.monsterTrackingRange; -- } else -- { -- return config.monsterActivationRange; -- } -- } else if ( entity.activationType == ActivationRange.ActivationType.ANIMAL ) -- { -- return config.animalTrackingRange; -- } else if ( entity instanceof ItemFrame || entity instanceof Painting || entity instanceof ItemEntity || entity instanceof ExperienceOrb ) -+ case WATER: -+ case VILLAGER: -+ case ANIMAL: -+ return config.animalTrackingRange; -+ case MISC: -+ } -+ if ( entity instanceof ItemFrame || entity instanceof Painting || entity instanceof ItemEntity || entity instanceof ExperienceOrb ) -+ // Paper end - { - return config.miscTrackingRange; - } else if ( entity instanceof Display ) -@@ -53,6 +51,7 @@ public class TrackingRange - return config.displayTrackingRange; - } else - { -+ if (entity instanceof net.minecraft.world.entity.boss.enderdragon.EnderDragon) return ((net.minecraft.server.level.ServerLevel)(entity.getCommandSenderWorld())).getChunkSource().chunkMap.serverViewDistance; // Paper - enderdragon is exempt - return config.otherTrackingRange; - } - } diff --git a/patches/server/0310-Add-debug-for-sync-chunk-loads.patch b/patches/server/0310-Add-debug-for-sync-chunk-loads.patch new file mode 100644 index 000000000000..7dc3e61f5196 --- /dev/null +++ b/patches/server/0310-Add-debug-for-sync-chunk-loads.patch @@ -0,0 +1,333 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Spottedleaf +Date: Fri, 19 Jul 2019 03:29:14 -0700 +Subject: [PATCH] Add debug for sync chunk loads + +This patch adds a tool to find calls to getChunkAt which would load +chunks, however it must be enabled by setting the startup flag +-Dpaper.debug-sync-loads=true + +- To get a debug log for sync loads, the command is + /paper syncloadinfo +- To clear clear the currently stored sync load info, use + /paper syncloadinfo clear + +diff --git a/src/main/java/com/destroystokyo/paper/io/SyncLoadFinder.java b/src/main/java/com/destroystokyo/paper/io/SyncLoadFinder.java +new file mode 100644 +index 0000000000000000000000000000000000000000..605a4a83d0a098a9977da00c710e798396dc5256 +--- /dev/null ++++ b/src/main/java/com/destroystokyo/paper/io/SyncLoadFinder.java +@@ -0,0 +1,177 @@ ++package com.destroystokyo.paper.io; ++ ++import com.google.gson.JsonArray; ++import com.google.gson.JsonObject; ++import com.mojang.datafixers.util.Pair; ++import it.unimi.dsi.fastutil.longs.Long2IntMap; ++import it.unimi.dsi.fastutil.longs.Long2IntOpenHashMap; ++import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap; ++ ++import java.util.ArrayList; ++import java.util.List; ++import java.util.Map; ++import java.util.WeakHashMap; ++import net.minecraft.world.level.ChunkPos; ++import net.minecraft.world.level.Level; ++ ++public class SyncLoadFinder { ++ ++ public static final boolean ENABLED = Boolean.getBoolean("paper.debug-sync-loads"); ++ ++ private static final WeakHashMap> SYNC_LOADS = new WeakHashMap<>(); ++ ++ private static final class SyncLoadInformation { ++ ++ public int times; ++ ++ public final Long2IntOpenHashMap coordinateTimes = new Long2IntOpenHashMap(); ++ } ++ ++ public static void clear() { ++ SYNC_LOADS.clear(); ++ } ++ ++ public static void logSyncLoad(final Level world, final int chunkX, final int chunkZ) { ++ if (!ENABLED) { ++ return; ++ } ++ ++ final ThrowableWithEquals stacktrace = new ThrowableWithEquals(Thread.currentThread().getStackTrace()); ++ ++ SYNC_LOADS.compute(world, (final Level keyInMap, Object2ObjectOpenHashMap map) -> { ++ if (map == null) { ++ map = new Object2ObjectOpenHashMap<>(); ++ } ++ ++ map.compute(stacktrace, (ThrowableWithEquals keyInMap0, SyncLoadInformation valueInMap) -> { ++ if (valueInMap == null) { ++ valueInMap = new SyncLoadInformation(); ++ } ++ ++ ++valueInMap.times; ++ ++ valueInMap.coordinateTimes.compute(ChunkPos.asLong(chunkX, chunkZ), (Long keyInMap1, Integer valueInMap1) -> { ++ return valueInMap1 == null ? Integer.valueOf(1) : Integer.valueOf(valueInMap1.intValue() + 1); ++ }); ++ ++ return valueInMap; ++ }); ++ ++ return map; ++ }); ++ } ++ ++ public static JsonObject serialize() { ++ final JsonObject ret = new JsonObject(); ++ ++ final JsonArray worldsData = new JsonArray(); ++ ++ for (final Map.Entry> entry : SYNC_LOADS.entrySet()) { ++ final Level world = entry.getKey(); ++ ++ final JsonObject worldData = new JsonObject(); ++ ++ worldData.addProperty("name", world.getWorld().getName()); ++ ++ final List> data = new ArrayList<>(); ++ ++ entry.getValue().forEach((ThrowableWithEquals stacktrace, SyncLoadInformation times) -> { ++ data.add(new Pair<>(stacktrace, times)); ++ }); ++ ++ data.sort((Pair pair1, Pair pair2) -> { ++ return Integer.compare(pair2.getSecond().times, pair1.getSecond().times); // reverse order ++ }); ++ ++ final JsonArray stacktraces = new JsonArray(); ++ ++ for (Pair pair : data) { ++ final JsonObject stacktrace = new JsonObject(); ++ ++ stacktrace.addProperty("times", pair.getSecond().times); ++ ++ final JsonArray traces = new JsonArray(); ++ ++ for (StackTraceElement element : pair.getFirst().stacktrace) { ++ traces.add(String.valueOf(element)); ++ } ++ ++ stacktrace.add("stacktrace", traces); ++ ++ final JsonArray coordinates = new JsonArray(); ++ ++ for (Long2IntMap.Entry coordinate : pair.getSecond().coordinateTimes.long2IntEntrySet()) { ++ final long key = coordinate.getLongKey(); ++ final int times = coordinate.getIntValue(); ++ final ChunkPos chunkPos = new ChunkPos(key); ++ coordinates.add("(" + chunkPos.x + "," + chunkPos.z + "): " + times); ++ } ++ ++ stacktrace.add("coordinates", coordinates); ++ ++ stacktraces.add(stacktrace); ++ } ++ ++ ++ worldData.add("stacktraces", stacktraces); ++ worldsData.add(worldData); ++ } ++ ++ ret.add("worlds", worldsData); ++ ++ return ret; ++ } ++ ++ static final class ThrowableWithEquals { ++ ++ private final StackTraceElement[] stacktrace; ++ private final int hash; ++ ++ public ThrowableWithEquals(final StackTraceElement[] stacktrace) { ++ this.stacktrace = stacktrace; ++ this.hash = ThrowableWithEquals.hash(stacktrace); ++ } ++ ++ public static int hash(final StackTraceElement[] stacktrace) { ++ int hash = 0; ++ ++ for (int i = 0; i < stacktrace.length; ++i) { ++ hash *= 31; ++ hash += stacktrace[i].hashCode(); ++ } ++ ++ return hash; ++ } ++ ++ @Override ++ public int hashCode() { ++ return this.hash; ++ } ++ ++ @Override ++ public boolean equals(final Object obj) { ++ if (obj == null || obj.getClass() != this.getClass()) { ++ return false; ++ } ++ ++ final ThrowableWithEquals other = (ThrowableWithEquals)obj; ++ final StackTraceElement[] otherStackTrace = other.stacktrace; ++ ++ if (this.stacktrace.length != otherStackTrace.length || this.hash != other.hash) { ++ return false; ++ } ++ ++ if (this == obj) { ++ return true; ++ } ++ ++ for (int i = 0; i < this.stacktrace.length; ++i) { ++ if (!this.stacktrace[i].equals(otherStackTrace[i])) { ++ return false; ++ } ++ } ++ ++ return true; ++ } ++ } ++} +diff --git a/src/main/java/io/papermc/paper/command/PaperCommand.java b/src/main/java/io/papermc/paper/command/PaperCommand.java +index e1820a339452cd3388dd7cbb928c5f58779a77b6..3010d57efcc97fb409bfe43b1fc9af198c099a67 100644 +--- a/src/main/java/io/papermc/paper/command/PaperCommand.java ++++ b/src/main/java/io/papermc/paper/command/PaperCommand.java +@@ -38,6 +38,7 @@ public final class PaperCommand extends Command { + commands.put(Set.of("reload"), new ReloadCommand()); + commands.put(Set.of("version"), new VersionCommand()); + commands.put(Set.of("dumpplugins"), new DumpPluginsCommand()); ++ commands.put(Set.of("syncloadinfo"), new SyncLoadInfoCommand()); + + return commands.entrySet().stream() + .flatMap(entry -> entry.getKey().stream().map(s -> Map.entry(s, entry.getValue()))) +diff --git a/src/main/java/io/papermc/paper/command/subcommands/SyncLoadInfoCommand.java b/src/main/java/io/papermc/paper/command/subcommands/SyncLoadInfoCommand.java +new file mode 100644 +index 0000000000000000000000000000000000000000..95d6022c9cfb2e36ec5a71be6e34354027c2ec08 +--- /dev/null ++++ b/src/main/java/io/papermc/paper/command/subcommands/SyncLoadInfoCommand.java +@@ -0,0 +1,88 @@ ++package io.papermc.paper.command.subcommands; ++ ++import com.destroystokyo.paper.io.SyncLoadFinder; ++import com.google.gson.JsonObject; ++import com.google.gson.internal.Streams; ++import com.google.gson.stream.JsonWriter; ++import io.papermc.paper.command.CommandUtil; ++import io.papermc.paper.command.PaperSubcommand; ++import java.io.File; ++import java.io.FileOutputStream; ++import java.io.PrintStream; ++import java.io.StringWriter; ++import java.nio.charset.StandardCharsets; ++import java.time.LocalDateTime; ++import java.time.format.DateTimeFormatter; ++import java.util.List; ++ ++import net.kyori.adventure.text.event.ClickEvent; ++import net.kyori.adventure.text.event.HoverEvent; ++import net.minecraft.server.MinecraftServer; ++import org.bukkit.command.CommandSender; ++import org.checkerframework.checker.nullness.qual.NonNull; ++import org.checkerframework.framework.qual.DefaultQualifier; ++ ++import static net.kyori.adventure.text.Component.text; ++import static net.kyori.adventure.text.format.NamedTextColor.GRAY; ++import static net.kyori.adventure.text.format.NamedTextColor.GREEN; ++import static net.kyori.adventure.text.format.NamedTextColor.RED; ++import static net.kyori.adventure.text.format.NamedTextColor.WHITE; ++ ++@DefaultQualifier(NonNull.class) ++public final class SyncLoadInfoCommand implements PaperSubcommand { ++ @Override ++ public boolean execute(final CommandSender sender, final String subCommand, final String[] args) { ++ this.doSyncLoadInfo(sender, args); ++ return true; ++ } ++ ++ @Override ++ public List tabComplete(final CommandSender sender, final String subCommand, final String[] args) { ++ return CommandUtil.getListMatchingLast(sender, args, "clear"); ++ } ++ ++ private static final DateTimeFormatter FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd_HH.mm.ss"); ++ ++ private void doSyncLoadInfo(final CommandSender sender, final String[] args) { ++ if (!SyncLoadFinder.ENABLED) { ++ String systemFlag = "-Dpaper.debug-sync-loads=true"; ++ sender.sendMessage(text().color(RED).append(text("This command requires the server startup flag '")).append( ++ text(systemFlag, WHITE).clickEvent(ClickEvent.copyToClipboard(systemFlag)) ++ .hoverEvent(HoverEvent.showText(text("Click to copy the system flag")))).append( ++ text("' to be set."))); ++ return; ++ } ++ ++ if (args.length > 0 && args[0].equals("clear")) { ++ SyncLoadFinder.clear(); ++ sender.sendMessage(text("Sync load data cleared.", GRAY)); ++ return; ++ } ++ ++ File file = new File(new File(new File("."), "debug"), ++ "sync-load-info-" + FORMATTER.format(LocalDateTime.now()) + ".txt"); ++ file.getParentFile().mkdirs(); ++ sender.sendMessage(text("Writing sync load info to " + file, GREEN)); ++ ++ ++ try { ++ final JsonObject data = SyncLoadFinder.serialize(); ++ ++ StringWriter stringWriter = new StringWriter(); ++ JsonWriter jsonWriter = new JsonWriter(stringWriter); ++ jsonWriter.setIndent(" "); ++ jsonWriter.setLenient(false); ++ Streams.write(data, jsonWriter); ++ ++ try ( ++ PrintStream out = new PrintStream(new FileOutputStream(file), false, StandardCharsets.UTF_8) ++ ) { ++ out.print(stringWriter); ++ } ++ sender.sendMessage(text("Successfully written sync load information!", GREEN)); ++ } catch (Throwable thr) { ++ sender.sendMessage(text("Failed to write sync load information! See the console for more info.", RED)); ++ MinecraftServer.LOGGER.warn("Error occurred while dumping sync chunk load info", thr); ++ } ++ } ++} +diff --git a/src/main/java/net/minecraft/server/level/ServerChunkCache.java b/src/main/java/net/minecraft/server/level/ServerChunkCache.java +index 43e01db5314452c194d2809fdc6a71c6fb42d8d2..76cb7ffad02dcc27966ca13da6552edbb696e41f 100644 +--- a/src/main/java/net/minecraft/server/level/ServerChunkCache.java ++++ b/src/main/java/net/minecraft/server/level/ServerChunkCache.java +@@ -218,6 +218,7 @@ public class ServerChunkCache extends ChunkSource { + + Objects.requireNonNull(completablefuture); + chunkproviderserver_b.managedBlock(completablefuture::isDone); ++ // com.destroystokyo.paper.io.SyncLoadFinder.logSyncLoad(this.level, x, z); // Paper - Add debug for sync chunk loads + ChunkResult chunkresult = (ChunkResult) completablefuture.join(); + ChunkAccess ichunkaccess1 = (ChunkAccess) chunkresult.orElse(null); // CraftBukkit - decompile error + +diff --git a/src/main/java/net/minecraft/server/level/ServerLevel.java b/src/main/java/net/minecraft/server/level/ServerLevel.java +index 800dd7338d77289b7d0e6e126342eaea184d3e43..ecb9f6f0c9b24313b9e55b3a4c4f4bba40bc8fb3 100644 +--- a/src/main/java/net/minecraft/server/level/ServerLevel.java ++++ b/src/main/java/net/minecraft/server/level/ServerLevel.java +@@ -410,6 +410,13 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe + this.getCraftServer().addWorld(this.getWorld()); // CraftBukkit + } + ++ // Paper start ++ @Override ++ public boolean hasChunk(int chunkX, int chunkZ) { ++ return this.getChunkSource().getChunkAtIfLoadedImmediately(chunkX, chunkZ) != null; ++ } ++ // Paper end ++ + /** @deprecated */ + @Deprecated + @VisibleForTesting diff --git a/patches/server/0310-Bees-get-gravity-in-void.-Fixes-MC-167279.patch b/patches/server/0310-Bees-get-gravity-in-void.-Fixes-MC-167279.patch deleted file mode 100644 index 9ea731b674e7..000000000000 --- a/patches/server/0310-Bees-get-gravity-in-void.-Fixes-MC-167279.patch +++ /dev/null @@ -1,34 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: William Blake Galbreath -Date: Sun, 26 Jan 2020 16:30:19 -0600 -Subject: [PATCH] Bees get gravity in void. Fixes MC-167279 - - -diff --git a/src/main/java/net/minecraft/world/entity/animal/Bee.java b/src/main/java/net/minecraft/world/entity/animal/Bee.java -index 58536ee8707c5ad0625cae2f26a58cf03b3f85d7..4134ee48909110f8c338f5d553d4cc1e9e31aaba 100644 ---- a/src/main/java/net/minecraft/world/entity/animal/Bee.java -+++ b/src/main/java/net/minecraft/world/entity/animal/Bee.java -@@ -144,7 +144,22 @@ public class Bee extends Animal implements NeutralMob, FlyingAnimal { - public Bee(EntityType type, Level world) { - super(type, world); - this.remainingCooldownBeforeLocatingNewFlower = Mth.nextInt(this.random, 20, 60); -- this.moveControl = new FlyingMoveControl(this, 20, true); -+ // Paper start - Fix MC-167279 -+ class BeeFlyingMoveControl extends FlyingMoveControl { -+ public BeeFlyingMoveControl(final Mob entity, final int maxPitchChange, final boolean noGravity) { -+ super(entity, maxPitchChange, noGravity); -+ } -+ -+ @Override -+ public void tick() { -+ if (this.mob.getY() <= Bee.this.level().getMinBuildHeight()) { -+ this.mob.setNoGravity(false); -+ } -+ super.tick(); -+ } -+ } -+ this.moveControl = new BeeFlyingMoveControl(this, 20, true); -+ // Paper end - Fix MC-167279 - this.lookControl = new Bee.BeeLookControl(this); - this.setPathfindingMalus(PathType.DANGER_FIRE, -1.0F); - this.setPathfindingMalus(PathType.WATER, -1.0F); diff --git a/patches/server/0311-Improve-Block-breakNaturally-API.patch b/patches/server/0311-Improve-Block-breakNaturally-API.patch deleted file mode 100644 index b478ee5bb4fa..000000000000 --- a/patches/server/0311-Improve-Block-breakNaturally-API.patch +++ /dev/null @@ -1,95 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Jake Potrebic -Date: Thu, 2 Jan 2020 12:25:07 -0600 -Subject: [PATCH] Improve Block#breakNaturally API - -Adds bool parameter to play world effect on block break - -Adds bool parameter to drop xp from blocks - -Fixes fluid-logged blocks not leaving fluid behind if -broken - -Handles special cases for ice and turtle eggs - -== AT == -public net.minecraft.world.level.block.TurtleEggBlock decreaseEggs(Lnet/minecraft/world/level/Level;Lnet/minecraft/core/BlockPos;Lnet/minecraft/world/level/block/state/BlockState;)V - -Co-authored-by: William Blake Galbreath - -diff --git a/src/main/java/net/minecraft/world/level/block/IceBlock.java b/src/main/java/net/minecraft/world/level/block/IceBlock.java -index 363dd6ab9c7b650913795ef350374d5c4e7e4925..e862814c1e54776f11050623b52476accc2f1f79 100644 ---- a/src/main/java/net/minecraft/world/level/block/IceBlock.java -+++ b/src/main/java/net/minecraft/world/level/block/IceBlock.java -@@ -35,6 +35,11 @@ public class IceBlock extends HalfTransparentBlock { - @Override - public void playerDestroy(Level world, Player player, BlockPos pos, BlockState state, @Nullable BlockEntity blockEntity, ItemStack tool) { - super.playerDestroy(world, player, pos, state, blockEntity, tool); -+ // Paper start - Improve Block#breakNaturally API -+ this.afterDestroy(world, pos, tool); -+ } -+ public void afterDestroy(Level world, BlockPos pos, ItemStack tool) { -+ // Paper end - Improve Block#breakNaturally API - if (!EnchantmentHelper.hasTag(tool, EnchantmentTags.PREVENTS_ICE_MELTING)) { - if (world.dimensionType().ultraWarm()) { - world.removeBlock(pos, false); -diff --git a/src/main/java/org/bukkit/craftbukkit/block/CraftBlock.java b/src/main/java/org/bukkit/craftbukkit/block/CraftBlock.java -index d4e14ac1514e2d8b87b4667a91c90eded3ba6636..f041b5d80bff9c022b007e04ef1558e9116acc6b 100644 ---- a/src/main/java/org/bukkit/craftbukkit/block/CraftBlock.java -+++ b/src/main/java/org/bukkit/craftbukkit/block/CraftBlock.java -@@ -452,6 +452,18 @@ public class CraftBlock implements Block { - - @Override - public boolean breakNaturally(ItemStack item) { -+ // Paper start -+ return this.breakNaturally(item, false); -+ } -+ -+ @Override -+ public boolean breakNaturally(boolean triggerEffect, boolean dropExperience) { -+ return this.breakNaturally(null, triggerEffect, dropExperience); -+ } -+ -+ @Override -+ public boolean breakNaturally(ItemStack item, boolean triggerEffect, boolean dropExperience) { -+ // Paper end - // Order matters here, need to drop before setting to air so skulls can get their data - net.minecraft.world.level.block.state.BlockState iblockdata = this.getNMS(); - net.minecraft.world.level.block.Block block = iblockdata.getBlock(); -@@ -461,11 +473,35 @@ public class CraftBlock implements Block { - // Modelled off EntityHuman#hasBlock - if (block != Blocks.AIR && (item == null || !iblockdata.requiresCorrectToolForDrops() || nmsItem.isCorrectToolForDrops(iblockdata))) { - net.minecraft.world.level.block.Block.dropResources(iblockdata, this.world.getMinecraftWorld(), this.position, this.world.getBlockEntity(this.position), null, nmsItem); -+ // Paper start - improve Block#breanNaturally -+ if (triggerEffect) { -+ if (iblockdata.getBlock() instanceof net.minecraft.world.level.block.BaseFireBlock) { -+ this.world.levelEvent(net.minecraft.world.level.block.LevelEvent.SOUND_EXTINGUISH_FIRE, this.position, 0); -+ } else { -+ this.world.levelEvent(net.minecraft.world.level.block.LevelEvent.PARTICLES_DESTROY_BLOCK, this.position, net.minecraft.world.level.block.Block.getId(iblockdata)); -+ } -+ } -+ if (dropExperience) block.popExperience(this.world.getMinecraftWorld(), this.position, block.getExpDrop(iblockdata, this.world.getMinecraftWorld(), this.position, nmsItem, true)); -+ // Paper end - result = true; - } - - // SPIGOT-6778: Directly call setBlock instead of setTypeAndData, so that the tile entiy is not removed and custom remove logic is run. -- return this.world.setBlock(this.position, Blocks.AIR.defaultBlockState(), 3) && result; -+ // Paper start - improve breakNaturally -+ boolean destroyed = this.world.removeBlock(this.position, false); -+ if (destroyed) { -+ block.destroy(this.world, this.position, iblockdata); -+ } -+ if (result) { -+ // special cases -+ if (block instanceof net.minecraft.world.level.block.IceBlock iceBlock) { -+ iceBlock.afterDestroy(this.world.getMinecraftWorld(), this.position, nmsItem); -+ } else if (block instanceof net.minecraft.world.level.block.TurtleEggBlock turtleEggBlock) { -+ turtleEggBlock.decreaseEggs(this.world.getMinecraftWorld(), this.position, iblockdata); -+ } -+ } -+ return destroyed && result; -+ // Paper end - } - - @Override diff --git a/patches/server/0311-Improve-java-version-check.patch b/patches/server/0311-Improve-java-version-check.patch new file mode 100644 index 000000000000..90af2b3b154a --- /dev/null +++ b/patches/server/0311-Improve-java-version-check.patch @@ -0,0 +1,36 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Nassim Jahnke +Date: Wed, 16 Mar 2022 13:58:16 +0100 +Subject: [PATCH] Improve java version check + +Co-Authored-By: MiniDigger + +diff --git a/src/main/java/org/bukkit/craftbukkit/Main.java b/src/main/java/org/bukkit/craftbukkit/Main.java +index 1efb42f7ad357f9b4185dea79106ccd38c9f5325..c618934b5cf66d9625c7be2ac114f1a1ca629d33 100644 +--- a/src/main/java/org/bukkit/craftbukkit/Main.java ++++ b/src/main/java/org/bukkit/craftbukkit/Main.java +@@ -203,11 +203,20 @@ public class Main { + return; + } + +- float javaVersion = Float.parseFloat(System.getProperty("java.class.version")); +- if (javaVersion > 67.0) { +- System.err.println("Unsupported Java detected (" + javaVersion + "). Only up to Java 23 is supported."); +- return; ++ // Paper start - Improve java version check ++ boolean skip = Boolean.getBoolean("Paper.IgnoreJavaVersion"); ++ String javaVersionName = System.getProperty("java.version"); ++ // J2SE SDK/JRE Version String Naming Convention ++ boolean isPreRelease = javaVersionName.contains("-"); ++ if (isPreRelease) { ++ if (!skip) { ++ System.err.println("Unsupported Java detected (" + javaVersionName + "). You are running an unsupported, non official, version. Only general availability versions of Java are supported. Please update your Java version. See https://docs.papermc.io/paper/faq#unsupported-java-detected-what-do-i-do for more information."); ++ return; ++ } ++ ++ System.err.println("Unsupported Java detected ("+ javaVersionName + "), but the check was skipped. Proceed with caution! "); + } ++ // Paper end - Improve java version check + + try { + // Paper start - Handled by TerminalConsoleAppender diff --git a/patches/server/0312-Add-ThrownEggHatchEvent.patch b/patches/server/0312-Add-ThrownEggHatchEvent.patch new file mode 100644 index 000000000000..4f5c62f3e678 --- /dev/null +++ b/patches/server/0312-Add-ThrownEggHatchEvent.patch @@ -0,0 +1,26 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: William Blake Galbreath +Date: Sun, 9 Feb 2020 00:19:05 -0600 +Subject: [PATCH] Add ThrownEggHatchEvent + +Adds a new event similar to PlayerEggThrowEvent, but without the Player requirement +(dispensers can throw eggs to hatch them, too). + +diff --git a/src/main/java/net/minecraft/world/entity/projectile/ThrownEgg.java b/src/main/java/net/minecraft/world/entity/projectile/ThrownEgg.java +index eb197a719177dcc1605ee0ff3c471ba91783f040..155c2bbd35adacb7c3668fbe81a7c454e5102c8b 100644 +--- a/src/main/java/net/minecraft/world/entity/projectile/ThrownEgg.java ++++ b/src/main/java/net/minecraft/world/entity/projectile/ThrownEgg.java +@@ -88,6 +88,13 @@ public class ThrownEgg extends ThrowableItemProjectile { + } + } + // CraftBukkit end ++ // Paper start - Add ThrownEggHatchEvent ++ com.destroystokyo.paper.event.entity.ThrownEggHatchEvent event = new com.destroystokyo.paper.event.entity.ThrownEggHatchEvent((org.bukkit.entity.Egg) getBukkitEntity(), hatching, b0, hatchingType); ++ event.callEvent(); ++ hatching = event.isHatching(); ++ b0 = hatching ? event.getNumHatches() : 0; // If hatching is set to false, ensure child count is 0 ++ hatchingType = event.getHatchingType(); ++ // Paper end - Add ThrownEggHatchEvent + + for (int i = 0; i < b0; ++i) { + Entity entitychicken = this.level().getWorld().makeEntity(new org.bukkit.Location(this.level().getWorld(), this.getX(), this.getY(), this.getZ(), this.getYRot(), 0.0F), hatchingType.getEntityClass()); // CraftBukkit diff --git a/patches/server/0312-Optimise-getChunkAt-calls-for-loaded-chunks.patch b/patches/server/0312-Optimise-getChunkAt-calls-for-loaded-chunks.patch deleted file mode 100644 index d1fed5285fcf..000000000000 --- a/patches/server/0312-Optimise-getChunkAt-calls-for-loaded-chunks.patch +++ /dev/null @@ -1,60 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Spottedleaf -Date: Sat, 25 Jan 2020 17:04:35 -0800 -Subject: [PATCH] Optimise getChunkAt calls for loaded chunks - -bypass the need to get a player chunk, then get the either, -then unwrap it... - -diff --git a/src/main/java/net/minecraft/server/level/ServerChunkCache.java b/src/main/java/net/minecraft/server/level/ServerChunkCache.java -index a939bad7da9c852827a2d67d9ace5d0df4911a31..1049452baaf0381ffbf15b30da956c2a1994da35 100644 ---- a/src/main/java/net/minecraft/server/level/ServerChunkCache.java -+++ b/src/main/java/net/minecraft/server/level/ServerChunkCache.java -@@ -179,6 +179,12 @@ public class ServerChunkCache extends ChunkSource { - return this.getChunk(x, z, leastStatus, create); - }, this.mainThreadProcessor).join(); - } else { -+ // Paper start - Perf: Optimise getChunkAt calls for loaded chunks -+ LevelChunk ifLoaded = this.getChunkAtIfLoadedMainThread(x, z); -+ if (ifLoaded != null) { -+ return ifLoaded; -+ } -+ // Paper end - Perf: Optimise getChunkAt calls for loaded chunks - ProfilerFiller gameprofilerfiller = this.level.getProfiler(); - - gameprofilerfiller.incrementCounter("getChunk"); -@@ -222,33 +228,7 @@ public class ServerChunkCache extends ChunkSource { - if (Thread.currentThread() != this.mainThread) { - return null; - } else { -- this.level.getProfiler().incrementCounter("getChunkNow"); -- long k = ChunkPos.asLong(chunkX, chunkZ); -- -- ChunkAccess ichunkaccess; -- -- for (int l = 0; l < 4; ++l) { -- if (k == this.lastChunkPos[l] && this.lastChunkStatus[l] == ChunkStatus.FULL) { -- ichunkaccess = this.lastChunk[l]; -- return ichunkaccess instanceof LevelChunk ? (LevelChunk) ichunkaccess : null; -- } -- } -- -- ChunkHolder playerchunk = this.getVisibleChunkIfPresent(k); -- -- if (playerchunk == null) { -- return null; -- } else { -- ichunkaccess = playerchunk.getChunkIfPresent(ChunkStatus.FULL); -- if (ichunkaccess != null) { -- this.storeInCache(k, ichunkaccess, ChunkStatus.FULL); -- if (ichunkaccess instanceof LevelChunk) { -- return (LevelChunk) ichunkaccess; -- } -- } -- -- return null; -- } -+ return this.getChunkAtIfLoadedMainThread(chunkX, chunkZ); // Paper - Perf: Optimise getChunkAt calls for loaded chunks - } - } - diff --git a/patches/server/0313-Add-debug-for-sync-chunk-loads.patch b/patches/server/0313-Add-debug-for-sync-chunk-loads.patch deleted file mode 100644 index bf5532d58e85..000000000000 --- a/patches/server/0313-Add-debug-for-sync-chunk-loads.patch +++ /dev/null @@ -1,333 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Spottedleaf -Date: Fri, 19 Jul 2019 03:29:14 -0700 -Subject: [PATCH] Add debug for sync chunk loads - -This patch adds a tool to find calls to getChunkAt which would load -chunks, however it must be enabled by setting the startup flag --Dpaper.debug-sync-loads=true - -- To get a debug log for sync loads, the command is - /paper syncloadinfo -- To clear clear the currently stored sync load info, use - /paper syncloadinfo clear - -diff --git a/src/main/java/com/destroystokyo/paper/io/SyncLoadFinder.java b/src/main/java/com/destroystokyo/paper/io/SyncLoadFinder.java -new file mode 100644 -index 0000000000000000000000000000000000000000..605a4a83d0a098a9977da00c710e798396dc5256 ---- /dev/null -+++ b/src/main/java/com/destroystokyo/paper/io/SyncLoadFinder.java -@@ -0,0 +1,177 @@ -+package com.destroystokyo.paper.io; -+ -+import com.google.gson.JsonArray; -+import com.google.gson.JsonObject; -+import com.mojang.datafixers.util.Pair; -+import it.unimi.dsi.fastutil.longs.Long2IntMap; -+import it.unimi.dsi.fastutil.longs.Long2IntOpenHashMap; -+import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap; -+ -+import java.util.ArrayList; -+import java.util.List; -+import java.util.Map; -+import java.util.WeakHashMap; -+import net.minecraft.world.level.ChunkPos; -+import net.minecraft.world.level.Level; -+ -+public class SyncLoadFinder { -+ -+ public static final boolean ENABLED = Boolean.getBoolean("paper.debug-sync-loads"); -+ -+ private static final WeakHashMap> SYNC_LOADS = new WeakHashMap<>(); -+ -+ private static final class SyncLoadInformation { -+ -+ public int times; -+ -+ public final Long2IntOpenHashMap coordinateTimes = new Long2IntOpenHashMap(); -+ } -+ -+ public static void clear() { -+ SYNC_LOADS.clear(); -+ } -+ -+ public static void logSyncLoad(final Level world, final int chunkX, final int chunkZ) { -+ if (!ENABLED) { -+ return; -+ } -+ -+ final ThrowableWithEquals stacktrace = new ThrowableWithEquals(Thread.currentThread().getStackTrace()); -+ -+ SYNC_LOADS.compute(world, (final Level keyInMap, Object2ObjectOpenHashMap map) -> { -+ if (map == null) { -+ map = new Object2ObjectOpenHashMap<>(); -+ } -+ -+ map.compute(stacktrace, (ThrowableWithEquals keyInMap0, SyncLoadInformation valueInMap) -> { -+ if (valueInMap == null) { -+ valueInMap = new SyncLoadInformation(); -+ } -+ -+ ++valueInMap.times; -+ -+ valueInMap.coordinateTimes.compute(ChunkPos.asLong(chunkX, chunkZ), (Long keyInMap1, Integer valueInMap1) -> { -+ return valueInMap1 == null ? Integer.valueOf(1) : Integer.valueOf(valueInMap1.intValue() + 1); -+ }); -+ -+ return valueInMap; -+ }); -+ -+ return map; -+ }); -+ } -+ -+ public static JsonObject serialize() { -+ final JsonObject ret = new JsonObject(); -+ -+ final JsonArray worldsData = new JsonArray(); -+ -+ for (final Map.Entry> entry : SYNC_LOADS.entrySet()) { -+ final Level world = entry.getKey(); -+ -+ final JsonObject worldData = new JsonObject(); -+ -+ worldData.addProperty("name", world.getWorld().getName()); -+ -+ final List> data = new ArrayList<>(); -+ -+ entry.getValue().forEach((ThrowableWithEquals stacktrace, SyncLoadInformation times) -> { -+ data.add(new Pair<>(stacktrace, times)); -+ }); -+ -+ data.sort((Pair pair1, Pair pair2) -> { -+ return Integer.compare(pair2.getSecond().times, pair1.getSecond().times); // reverse order -+ }); -+ -+ final JsonArray stacktraces = new JsonArray(); -+ -+ for (Pair pair : data) { -+ final JsonObject stacktrace = new JsonObject(); -+ -+ stacktrace.addProperty("times", pair.getSecond().times); -+ -+ final JsonArray traces = new JsonArray(); -+ -+ for (StackTraceElement element : pair.getFirst().stacktrace) { -+ traces.add(String.valueOf(element)); -+ } -+ -+ stacktrace.add("stacktrace", traces); -+ -+ final JsonArray coordinates = new JsonArray(); -+ -+ for (Long2IntMap.Entry coordinate : pair.getSecond().coordinateTimes.long2IntEntrySet()) { -+ final long key = coordinate.getLongKey(); -+ final int times = coordinate.getIntValue(); -+ final ChunkPos chunkPos = new ChunkPos(key); -+ coordinates.add("(" + chunkPos.x + "," + chunkPos.z + "): " + times); -+ } -+ -+ stacktrace.add("coordinates", coordinates); -+ -+ stacktraces.add(stacktrace); -+ } -+ -+ -+ worldData.add("stacktraces", stacktraces); -+ worldsData.add(worldData); -+ } -+ -+ ret.add("worlds", worldsData); -+ -+ return ret; -+ } -+ -+ static final class ThrowableWithEquals { -+ -+ private final StackTraceElement[] stacktrace; -+ private final int hash; -+ -+ public ThrowableWithEquals(final StackTraceElement[] stacktrace) { -+ this.stacktrace = stacktrace; -+ this.hash = ThrowableWithEquals.hash(stacktrace); -+ } -+ -+ public static int hash(final StackTraceElement[] stacktrace) { -+ int hash = 0; -+ -+ for (int i = 0; i < stacktrace.length; ++i) { -+ hash *= 31; -+ hash += stacktrace[i].hashCode(); -+ } -+ -+ return hash; -+ } -+ -+ @Override -+ public int hashCode() { -+ return this.hash; -+ } -+ -+ @Override -+ public boolean equals(final Object obj) { -+ if (obj == null || obj.getClass() != this.getClass()) { -+ return false; -+ } -+ -+ final ThrowableWithEquals other = (ThrowableWithEquals)obj; -+ final StackTraceElement[] otherStackTrace = other.stacktrace; -+ -+ if (this.stacktrace.length != otherStackTrace.length || this.hash != other.hash) { -+ return false; -+ } -+ -+ if (this == obj) { -+ return true; -+ } -+ -+ for (int i = 0; i < this.stacktrace.length; ++i) { -+ if (!this.stacktrace[i].equals(otherStackTrace[i])) { -+ return false; -+ } -+ } -+ -+ return true; -+ } -+ } -+} -diff --git a/src/main/java/io/papermc/paper/command/PaperCommand.java b/src/main/java/io/papermc/paper/command/PaperCommand.java -index e1820a339452cd3388dd7cbb928c5f58779a77b6..3010d57efcc97fb409bfe43b1fc9af198c099a67 100644 ---- a/src/main/java/io/papermc/paper/command/PaperCommand.java -+++ b/src/main/java/io/papermc/paper/command/PaperCommand.java -@@ -38,6 +38,7 @@ public final class PaperCommand extends Command { - commands.put(Set.of("reload"), new ReloadCommand()); - commands.put(Set.of("version"), new VersionCommand()); - commands.put(Set.of("dumpplugins"), new DumpPluginsCommand()); -+ commands.put(Set.of("syncloadinfo"), new SyncLoadInfoCommand()); - - return commands.entrySet().stream() - .flatMap(entry -> entry.getKey().stream().map(s -> Map.entry(s, entry.getValue()))) -diff --git a/src/main/java/io/papermc/paper/command/subcommands/SyncLoadInfoCommand.java b/src/main/java/io/papermc/paper/command/subcommands/SyncLoadInfoCommand.java -new file mode 100644 -index 0000000000000000000000000000000000000000..95d6022c9cfb2e36ec5a71be6e34354027c2ec08 ---- /dev/null -+++ b/src/main/java/io/papermc/paper/command/subcommands/SyncLoadInfoCommand.java -@@ -0,0 +1,88 @@ -+package io.papermc.paper.command.subcommands; -+ -+import com.destroystokyo.paper.io.SyncLoadFinder; -+import com.google.gson.JsonObject; -+import com.google.gson.internal.Streams; -+import com.google.gson.stream.JsonWriter; -+import io.papermc.paper.command.CommandUtil; -+import io.papermc.paper.command.PaperSubcommand; -+import java.io.File; -+import java.io.FileOutputStream; -+import java.io.PrintStream; -+import java.io.StringWriter; -+import java.nio.charset.StandardCharsets; -+import java.time.LocalDateTime; -+import java.time.format.DateTimeFormatter; -+import java.util.List; -+ -+import net.kyori.adventure.text.event.ClickEvent; -+import net.kyori.adventure.text.event.HoverEvent; -+import net.minecraft.server.MinecraftServer; -+import org.bukkit.command.CommandSender; -+import org.checkerframework.checker.nullness.qual.NonNull; -+import org.checkerframework.framework.qual.DefaultQualifier; -+ -+import static net.kyori.adventure.text.Component.text; -+import static net.kyori.adventure.text.format.NamedTextColor.GRAY; -+import static net.kyori.adventure.text.format.NamedTextColor.GREEN; -+import static net.kyori.adventure.text.format.NamedTextColor.RED; -+import static net.kyori.adventure.text.format.NamedTextColor.WHITE; -+ -+@DefaultQualifier(NonNull.class) -+public final class SyncLoadInfoCommand implements PaperSubcommand { -+ @Override -+ public boolean execute(final CommandSender sender, final String subCommand, final String[] args) { -+ this.doSyncLoadInfo(sender, args); -+ return true; -+ } -+ -+ @Override -+ public List tabComplete(final CommandSender sender, final String subCommand, final String[] args) { -+ return CommandUtil.getListMatchingLast(sender, args, "clear"); -+ } -+ -+ private static final DateTimeFormatter FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd_HH.mm.ss"); -+ -+ private void doSyncLoadInfo(final CommandSender sender, final String[] args) { -+ if (!SyncLoadFinder.ENABLED) { -+ String systemFlag = "-Dpaper.debug-sync-loads=true"; -+ sender.sendMessage(text().color(RED).append(text("This command requires the server startup flag '")).append( -+ text(systemFlag, WHITE).clickEvent(ClickEvent.copyToClipboard(systemFlag)) -+ .hoverEvent(HoverEvent.showText(text("Click to copy the system flag")))).append( -+ text("' to be set."))); -+ return; -+ } -+ -+ if (args.length > 0 && args[0].equals("clear")) { -+ SyncLoadFinder.clear(); -+ sender.sendMessage(text("Sync load data cleared.", GRAY)); -+ return; -+ } -+ -+ File file = new File(new File(new File("."), "debug"), -+ "sync-load-info-" + FORMATTER.format(LocalDateTime.now()) + ".txt"); -+ file.getParentFile().mkdirs(); -+ sender.sendMessage(text("Writing sync load info to " + file, GREEN)); -+ -+ -+ try { -+ final JsonObject data = SyncLoadFinder.serialize(); -+ -+ StringWriter stringWriter = new StringWriter(); -+ JsonWriter jsonWriter = new JsonWriter(stringWriter); -+ jsonWriter.setIndent(" "); -+ jsonWriter.setLenient(false); -+ Streams.write(data, jsonWriter); -+ -+ try ( -+ PrintStream out = new PrintStream(new FileOutputStream(file), false, StandardCharsets.UTF_8) -+ ) { -+ out.print(stringWriter); -+ } -+ sender.sendMessage(text("Successfully written sync load information!", GREEN)); -+ } catch (Throwable thr) { -+ sender.sendMessage(text("Failed to write sync load information! See the console for more info.", RED)); -+ MinecraftServer.LOGGER.warn("Error occurred while dumping sync chunk load info", thr); -+ } -+ } -+} -diff --git a/src/main/java/net/minecraft/server/level/ServerChunkCache.java b/src/main/java/net/minecraft/server/level/ServerChunkCache.java -index 1049452baaf0381ffbf15b30da956c2a1994da35..60f678c26fb5144386d8697ddfd5b6d841563b6f 100644 ---- a/src/main/java/net/minecraft/server/level/ServerChunkCache.java -+++ b/src/main/java/net/minecraft/server/level/ServerChunkCache.java -@@ -206,6 +206,7 @@ public class ServerChunkCache extends ChunkSource { - - Objects.requireNonNull(completablefuture); - if (!completablefuture.isDone()) { // Paper -+ com.destroystokyo.paper.io.SyncLoadFinder.logSyncLoad(this.level, x, z); // Paper - Add debug for sync chunk loads - this.level.timings.syncChunkLoad.startTiming(); // Paper - chunkproviderserver_b.managedBlock(completablefuture::isDone); - this.level.timings.syncChunkLoad.stopTiming(); // Paper -diff --git a/src/main/java/net/minecraft/server/level/ServerLevel.java b/src/main/java/net/minecraft/server/level/ServerLevel.java -index c85d5b941b5cb1c37cd2619d419006c5956f6eaf..f1fa4cb11f69e248dd55b8aa69f5d07268f182a1 100644 ---- a/src/main/java/net/minecraft/server/level/ServerLevel.java -+++ b/src/main/java/net/minecraft/server/level/ServerLevel.java -@@ -422,6 +422,13 @@ public class ServerLevel extends Level implements WorldGenLevel { - this.getCraftServer().addWorld(this.getWorld()); // CraftBukkit - } - -+ // Paper start -+ @Override -+ public boolean hasChunk(int chunkX, int chunkZ) { -+ return this.getChunkSource().getChunkAtIfLoadedImmediately(chunkX, chunkZ) != null; -+ } -+ // Paper end -+ - /** @deprecated */ - @Deprecated - @VisibleForTesting diff --git a/patches/server/0313-Entity-Jump-API.patch b/patches/server/0313-Entity-Jump-API.patch new file mode 100644 index 000000000000..66167bd061bd --- /dev/null +++ b/patches/server/0313-Entity-Jump-API.patch @@ -0,0 +1,76 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: BillyGalbreath +Date: Sat, 8 Feb 2020 23:26:11 -0600 +Subject: [PATCH] Entity Jump API + +== AT == +public net.minecraft.world.entity.LivingEntity jumping + +diff --git a/src/main/java/net/minecraft/world/entity/LivingEntity.java b/src/main/java/net/minecraft/world/entity/LivingEntity.java +index ebfea3adbedd2695f645421019a276efbc73ee63..724fe482f1711008dc43fef96c4512a18ed54a48 100644 +--- a/src/main/java/net/minecraft/world/entity/LivingEntity.java ++++ b/src/main/java/net/minecraft/world/entity/LivingEntity.java +@@ -3537,8 +3537,10 @@ public abstract class LivingEntity extends Entity implements Attackable { + } else if (this.isInLava() && (!this.onGround() || d3 > d4)) { + this.jumpInLiquid(FluidTags.LAVA); + } else if ((this.onGround() || flag && d3 <= d4) && this.noJumpDelay == 0) { ++ if (new com.destroystokyo.paper.event.entity.EntityJumpEvent(getBukkitLivingEntity()).callEvent()) { // Paper - Entity Jump API + this.jumpFromGround(); + this.noJumpDelay = 10; ++ } else { this.setJumping(false); } // Paper - Entity Jump API; setJumping(false) stops a potential loop + } + } else { + this.noJumpDelay = 0; +diff --git a/src/main/java/net/minecraft/world/entity/animal/Panda.java b/src/main/java/net/minecraft/world/entity/animal/Panda.java +index 0b0887b7d2c69138617bee6ec3145f0a83b8734d..705c26ceff9371b09311bd7fa796c0efde7ebfee 100644 +--- a/src/main/java/net/minecraft/world/entity/animal/Panda.java ++++ b/src/main/java/net/minecraft/world/entity/animal/Panda.java +@@ -530,7 +530,9 @@ public class Panda extends Animal { + Panda entitypanda = (Panda) iterator.next(); + + if (!entitypanda.isBaby() && entitypanda.onGround() && !entitypanda.isInWater() && entitypanda.canPerformAction()) { ++ if (new com.destroystokyo.paper.event.entity.EntityJumpEvent(getBukkitLivingEntity()).callEvent()) { // Paper - Entity Jump API + entitypanda.jumpFromGround(); ++ } else { this.setJumping(false); } // Paper - Entity Jump API; setJumping(false) stops a potential loop + } + } + +diff --git a/src/main/java/net/minecraft/world/entity/monster/Ravager.java b/src/main/java/net/minecraft/world/entity/monster/Ravager.java +index 32973d9c7bf4c4105d7bd82d3723e0b70cd79644..c661ae4e5c07494c7de852cc8d01f0f9839c1590 100644 +--- a/src/main/java/net/minecraft/world/entity/monster/Ravager.java ++++ b/src/main/java/net/minecraft/world/entity/monster/Ravager.java +@@ -174,7 +174,9 @@ public class Ravager extends Raider { + } + + if (!flag && this.onGround()) { ++ if (new com.destroystokyo.paper.event.entity.EntityJumpEvent(getBukkitLivingEntity()).callEvent()) { // Paper - Entity Jump API + this.jumpFromGround(); ++ } else { this.setJumping(false); } // Paper - Entity Jump API; setJumping(false) stops a potential loop + } + } + } +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftLivingEntity.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftLivingEntity.java +index c4c83b4e17aac23794fdb51acfba4e7ef8451e2c..81034513faa3bd4f306430bc48532ba671a7b94b 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftLivingEntity.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftLivingEntity.java +@@ -993,4 +993,20 @@ public class CraftLivingEntity extends CraftEntity implements LivingEntity { + return org.bukkit.craftbukkit.CraftEquipmentSlot.getHand(this.getHandle().getUsedItemHand()); + } + // Paper end - active item API ++ ++ // Paper start - entity jump API ++ @Override ++ public boolean isJumping() { ++ return getHandle().jumping; ++ } ++ ++ @Override ++ public void setJumping(boolean jumping) { ++ getHandle().setJumping(jumping); ++ if (jumping && getHandle() instanceof Mob) { ++ // this is needed to actually make a mob jump ++ ((Mob) getHandle()).getJumpControl().jump(); ++ } ++ } ++ // Paper end - entity jump API + } diff --git a/patches/server/0314-Add-option-to-nerf-pigmen-from-nether-portals.patch b/patches/server/0314-Add-option-to-nerf-pigmen-from-nether-portals.patch new file mode 100644 index 000000000000..81ec1db92d1f --- /dev/null +++ b/patches/server/0314-Add-option-to-nerf-pigmen-from-nether-portals.patch @@ -0,0 +1,51 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: William Blake Galbreath +Date: Fri, 7 Feb 2020 14:36:56 -0600 +Subject: [PATCH] Add option to nerf pigmen from nether portals + + +diff --git a/src/main/java/net/minecraft/world/entity/Entity.java b/src/main/java/net/minecraft/world/entity/Entity.java +index 463d34e7b54efd503c4879d1386b2439474863dd..fa80b6220aba144521dc0a2a35914c10046c1963 100644 +--- a/src/main/java/net/minecraft/world/entity/Entity.java ++++ b/src/main/java/net/minecraft/world/entity/Entity.java +@@ -406,6 +406,7 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess + public void inactiveTick() { } + // Spigot end + protected int numCollisions = 0; // Paper - Cap entity collisions ++ public boolean fromNetherPortal; // Paper - Add option to nerf pigmen from nether portals + public boolean spawnedViaMobSpawner; // Paper - Yes this name is similar to above, upstream took the better one + // Paper start - Entity origin API + @javax.annotation.Nullable +@@ -2398,6 +2399,9 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess + if (spawnedViaMobSpawner) { + nbttagcompound.putBoolean("Paper.FromMobSpawner", true); + } ++ if (fromNetherPortal) { ++ nbttagcompound.putBoolean("Paper.FromNetherPortal", true); ++ } + // Paper end + return nbttagcompound; + } catch (Throwable throwable) { +@@ -2541,6 +2545,7 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess + } + + spawnedViaMobSpawner = nbt.getBoolean("Paper.FromMobSpawner"); // Restore entity's from mob spawner status ++ fromNetherPortal = nbt.getBoolean("Paper.FromNetherPortal"); + if (nbt.contains("Paper.SpawnReason")) { + String spawnReasonName = nbt.getString("Paper.SpawnReason"); + try { +diff --git a/src/main/java/net/minecraft/world/level/block/NetherPortalBlock.java b/src/main/java/net/minecraft/world/level/block/NetherPortalBlock.java +index 5d4c0d7fec42bf843b11875f7a09bcb9279b3b54..dea13596eb8a3b7bc69c19545b2227b4c45ed241 100644 +--- a/src/main/java/net/minecraft/world/level/block/NetherPortalBlock.java ++++ b/src/main/java/net/minecraft/world/level/block/NetherPortalBlock.java +@@ -89,6 +89,10 @@ public class NetherPortalBlock extends Block implements Portal { + + if (entity != null) { + entity.setPortalCooldown(); ++ // Paper start - Add option to nerf pigmen from nether portals ++ entity.fromNetherPortal = true; ++ if (world.paperConfig().entities.behavior.nerfPigmenFromNetherPortals) ((net.minecraft.world.entity.Mob) entity).aware = false; ++ // Paper end - Add option to nerf pigmen from nether portals + Entity entity1 = entity.getVehicle(); + + if (entity1 != null) { diff --git a/patches/server/0314-Improve-java-version-check.patch b/patches/server/0314-Improve-java-version-check.patch deleted file mode 100644 index 7814ffea5aa6..000000000000 --- a/patches/server/0314-Improve-java-version-check.patch +++ /dev/null @@ -1,36 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Nassim Jahnke -Date: Wed, 16 Mar 2022 13:58:16 +0100 -Subject: [PATCH] Improve java version check - -Co-Authored-By: MiniDigger - -diff --git a/src/main/java/org/bukkit/craftbukkit/Main.java b/src/main/java/org/bukkit/craftbukkit/Main.java -index bcc9d3b1503b1be1841c9ab40e879a1cbb0549f2..224b7f5394a568a3982f1e9a554e72af969fbe43 100644 ---- a/src/main/java/org/bukkit/craftbukkit/Main.java -+++ b/src/main/java/org/bukkit/craftbukkit/Main.java -@@ -203,11 +203,20 @@ public class Main { - return; - } - -- float javaVersion = Float.parseFloat(System.getProperty("java.class.version")); -- if (javaVersion > 67.0) { -- System.err.println("Unsupported Java detected (" + javaVersion + "). Only up to Java 23 is supported."); -- return; -+ // Paper start - Improve java version check -+ boolean skip = Boolean.getBoolean("Paper.IgnoreJavaVersion"); -+ String javaVersionName = System.getProperty("java.version"); -+ // J2SE SDK/JRE Version String Naming Convention -+ boolean isPreRelease = javaVersionName.contains("-"); -+ if (isPreRelease) { -+ if (!skip) { -+ System.err.println("Unsupported Java detected (" + javaVersionName + "). You are running an unsupported, non official, version. Only general availability versions of Java are supported. Please update your Java version. See https://docs.papermc.io/paper/faq#unsupported-java-detected-what-do-i-do for more information."); -+ return; -+ } -+ -+ System.err.println("Unsupported Java detected ("+ javaVersionName + "), but the check was skipped. Proceed with caution! "); - } -+ // Paper end - Improve java version check - - try { - // Paper start - Handled by TerminalConsoleAppender diff --git a/patches/server/0315-Add-ThrownEggHatchEvent.patch b/patches/server/0315-Add-ThrownEggHatchEvent.patch deleted file mode 100644 index 21fd12f38043..000000000000 --- a/patches/server/0315-Add-ThrownEggHatchEvent.patch +++ /dev/null @@ -1,26 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: William Blake Galbreath -Date: Sun, 9 Feb 2020 00:19:05 -0600 -Subject: [PATCH] Add ThrownEggHatchEvent - -Adds a new event similar to PlayerEggThrowEvent, but without the Player requirement -(dispensers can throw eggs to hatch them, too). - -diff --git a/src/main/java/net/minecraft/world/entity/projectile/ThrownEgg.java b/src/main/java/net/minecraft/world/entity/projectile/ThrownEgg.java -index 62850e899955732afdd255ea1e55fc84b7c6c96b..dbd60cc8c39f5d2d4c77e2de4f2567e7fa456cd2 100644 ---- a/src/main/java/net/minecraft/world/entity/projectile/ThrownEgg.java -+++ b/src/main/java/net/minecraft/world/entity/projectile/ThrownEgg.java -@@ -86,6 +86,13 @@ public class ThrownEgg extends ThrowableItemProjectile { - } - } - // CraftBukkit end -+ // Paper start - Add ThrownEggHatchEvent -+ com.destroystokyo.paper.event.entity.ThrownEggHatchEvent event = new com.destroystokyo.paper.event.entity.ThrownEggHatchEvent((org.bukkit.entity.Egg) getBukkitEntity(), hatching, b0, hatchingType); -+ event.callEvent(); -+ hatching = event.isHatching(); -+ b0 = hatching ? event.getNumHatches() : 0; // If hatching is set to false, ensure child count is 0 -+ hatchingType = event.getHatchingType(); -+ // Paper end - Add ThrownEggHatchEvent - - for (int i = 0; i < b0; ++i) { - Entity entitychicken = this.level().getWorld().makeEntity(new org.bukkit.Location(this.level().getWorld(), this.getX(), this.getY(), this.getZ(), this.getYRot(), 0.0F), hatchingType.getEntityClass()); // CraftBukkit diff --git a/patches/server/0318-Make-the-GUI-graph-fancier.patch b/patches/server/0315-Make-the-GUI-graph-fancier.patch similarity index 100% rename from patches/server/0318-Make-the-GUI-graph-fancier.patch rename to patches/server/0315-Make-the-GUI-graph-fancier.patch diff --git a/patches/server/0316-Entity-Jump-API.patch b/patches/server/0316-Entity-Jump-API.patch deleted file mode 100644 index 9e2cba6a0a81..000000000000 --- a/patches/server/0316-Entity-Jump-API.patch +++ /dev/null @@ -1,76 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: BillyGalbreath -Date: Sat, 8 Feb 2020 23:26:11 -0600 -Subject: [PATCH] Entity Jump API - -== AT == -public net.minecraft.world.entity.LivingEntity jumping - -diff --git a/src/main/java/net/minecraft/world/entity/LivingEntity.java b/src/main/java/net/minecraft/world/entity/LivingEntity.java -index 8ee6d25c3860fe7f2e5039c74c736d82eefda237..4c32b26e29ca3db0a2f62052e14bcc3e4c1cdea5 100644 ---- a/src/main/java/net/minecraft/world/entity/LivingEntity.java -+++ b/src/main/java/net/minecraft/world/entity/LivingEntity.java -@@ -3433,8 +3433,10 @@ public abstract class LivingEntity extends Entity implements Attackable { - } else if (this.isInLava() && (!this.onGround() || d3 > d4)) { - this.jumpInLiquid(FluidTags.LAVA); - } else if ((this.onGround() || flag && d3 <= d4) && this.noJumpDelay == 0) { -+ if (new com.destroystokyo.paper.event.entity.EntityJumpEvent(getBukkitLivingEntity()).callEvent()) { // Paper - Entity Jump API - this.jumpFromGround(); - this.noJumpDelay = 10; -+ } else { this.setJumping(false); } // Paper - Entity Jump API; setJumping(false) stops a potential loop - } - } else { - this.noJumpDelay = 0; -diff --git a/src/main/java/net/minecraft/world/entity/animal/Panda.java b/src/main/java/net/minecraft/world/entity/animal/Panda.java -index 228cfb77e12ed5979e422dc5dbb5e8dcf363b509..8df42121aa22ec9f95a1b8627b64b0ff71e36314 100644 ---- a/src/main/java/net/minecraft/world/entity/animal/Panda.java -+++ b/src/main/java/net/minecraft/world/entity/animal/Panda.java -@@ -541,7 +541,9 @@ public class Panda extends Animal { - Panda entitypanda = (Panda) iterator.next(); - - if (!entitypanda.isBaby() && entitypanda.onGround() && !entitypanda.isInWater() && entitypanda.canPerformAction()) { -+ if (new com.destroystokyo.paper.event.entity.EntityJumpEvent(getBukkitLivingEntity()).callEvent()) { // Paper - Entity Jump API - entitypanda.jumpFromGround(); -+ } else { this.setJumping(false); } // Paper - Entity Jump API; setJumping(false) stops a potential loop - } - } - -diff --git a/src/main/java/net/minecraft/world/entity/monster/Ravager.java b/src/main/java/net/minecraft/world/entity/monster/Ravager.java -index 4d91bc4b90a208fec789325dbcec8cc56d1a2a8b..aa4111eef22546f8aa630228c51ef5761c9b373b 100644 ---- a/src/main/java/net/minecraft/world/entity/monster/Ravager.java -+++ b/src/main/java/net/minecraft/world/entity/monster/Ravager.java -@@ -160,7 +160,9 @@ public class Ravager extends Raider { - } - - if (!flag && this.onGround()) { -+ if (new com.destroystokyo.paper.event.entity.EntityJumpEvent(getBukkitLivingEntity()).callEvent()) { // Paper - Entity Jump API - this.jumpFromGround(); -+ } else { this.setJumping(false); } // Paper - Entity Jump API; setJumping(false) stops a potential loop - } - } - -diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftLivingEntity.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftLivingEntity.java -index c0684f1864ece26b4f337ac615db04f615957c13..05ba1654ec02ff2b518251c128661e3d8dfa4c6d 100644 ---- a/src/main/java/org/bukkit/craftbukkit/entity/CraftLivingEntity.java -+++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftLivingEntity.java -@@ -974,4 +974,20 @@ public class CraftLivingEntity extends CraftEntity implements LivingEntity { - return org.bukkit.craftbukkit.CraftEquipmentSlot.getHand(this.getHandle().getUsedItemHand()); - } - // Paper end - active item API -+ -+ // Paper start - entity jump API -+ @Override -+ public boolean isJumping() { -+ return getHandle().jumping; -+ } -+ -+ @Override -+ public void setJumping(boolean jumping) { -+ getHandle().setJumping(jumping); -+ if (jumping && getHandle() instanceof Mob) { -+ // this is needed to actually make a mob jump -+ ((Mob) getHandle()).getJumpControl().jump(); -+ } -+ } -+ // Paper end - entity jump API - } diff --git a/patches/server/0316-add-hand-to-BlockMultiPlaceEvent.patch b/patches/server/0316-add-hand-to-BlockMultiPlaceEvent.patch new file mode 100644 index 000000000000..f5cef45e8c2c --- /dev/null +++ b/patches/server/0316-add-hand-to-BlockMultiPlaceEvent.patch @@ -0,0 +1,30 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Trigary +Date: Sun, 1 Mar 2020 22:43:24 +0100 +Subject: [PATCH] add hand to BlockMultiPlaceEvent + + +diff --git a/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java b/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java +index 199abd66d2113e7bc8c478fe8e4f6657d2e12304..0627e3be754dbf8201f3212cf823e2f8cb77cdeb 100644 +--- a/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java ++++ b/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java +@@ -411,13 +411,18 @@ public class CraftEventFactory { + } + + org.bukkit.inventory.ItemStack item; ++ // Paper start - add hand to BlockMultiPlaceEvent ++ EquipmentSlot equipmentSlot; + if (hand == InteractionHand.MAIN_HAND) { + item = player.getInventory().getItemInMainHand(); ++ equipmentSlot = EquipmentSlot.HAND; + } else { + item = player.getInventory().getItemInOffHand(); ++ equipmentSlot = EquipmentSlot.OFF_HAND; + } + +- BlockMultiPlaceEvent event = new BlockMultiPlaceEvent(blockStates, blockClicked, item, player, canBuild); ++ BlockMultiPlaceEvent event = new BlockMultiPlaceEvent(blockStates, blockClicked, item, player, canBuild, equipmentSlot); ++ // Paper end + craftServer.getPluginManager().callEvent(event); + + return event; diff --git a/patches/server/0317-Add-option-to-nerf-pigmen-from-nether-portals.patch b/patches/server/0317-Add-option-to-nerf-pigmen-from-nether-portals.patch deleted file mode 100644 index 18fff797e7a3..000000000000 --- a/patches/server/0317-Add-option-to-nerf-pigmen-from-nether-portals.patch +++ /dev/null @@ -1,49 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: William Blake Galbreath -Date: Fri, 7 Feb 2020 14:36:56 -0600 -Subject: [PATCH] Add option to nerf pigmen from nether portals - - -diff --git a/src/main/java/net/minecraft/world/entity/Entity.java b/src/main/java/net/minecraft/world/entity/Entity.java -index 9d56aff2766b684f0fb20e93d504de1a7a564b11..94242c19740ae6ab2c86e3949bab6cee631b938f 100644 ---- a/src/main/java/net/minecraft/world/entity/Entity.java -+++ b/src/main/java/net/minecraft/world/entity/Entity.java -@@ -403,6 +403,7 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess - public void inactiveTick() { } - // Spigot end - protected int numCollisions = 0; // Paper - Cap entity collisions -+ public boolean fromNetherPortal; // Paper - Add option to nerf pigmen from nether portals - public boolean spawnedViaMobSpawner; // Paper - Yes this name is similar to above, upstream took the better one - // Paper start - Entity origin API - @javax.annotation.Nullable -@@ -2282,6 +2283,9 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess - if (spawnedViaMobSpawner) { - nbttagcompound.putBoolean("Paper.FromMobSpawner", true); - } -+ if (fromNetherPortal) { -+ nbttagcompound.putBoolean("Paper.FromNetherPortal", true); -+ } - // Paper end - return nbttagcompound; - } catch (Throwable throwable) { -@@ -2424,6 +2428,7 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess - } - - spawnedViaMobSpawner = nbt.getBoolean("Paper.FromMobSpawner"); // Restore entity's from mob spawner status -+ fromNetherPortal = nbt.getBoolean("Paper.FromNetherPortal"); - if (nbt.contains("Paper.SpawnReason")) { - String spawnReasonName = nbt.getString("Paper.SpawnReason"); - try { -diff --git a/src/main/java/net/minecraft/world/level/block/NetherPortalBlock.java b/src/main/java/net/minecraft/world/level/block/NetherPortalBlock.java -index 8072e67f7b2f5944670159d3de1b01090bd1019d..a4e1c878dc7677e8ccc7c568ab455e85513d86cb 100644 ---- a/src/main/java/net/minecraft/world/level/block/NetherPortalBlock.java -+++ b/src/main/java/net/minecraft/world/level/block/NetherPortalBlock.java -@@ -88,6 +88,8 @@ public class NetherPortalBlock extends Block implements Portal { - - if (entity != null) { - entity.setPortalCooldown(); -+ entity.fromNetherPortal = true; // Paper - Add option to nerf pigmen from nether portals -+ if (world.paperConfig().entities.behavior.nerfPigmenFromNetherPortals) ((net.minecraft.world.entity.Mob) entity).aware = false; // Paper - Add option to nerf pigmen from nether portals - } - } - } diff --git a/patches/server/0317-Validate-tripwire-hook-placement-before-update.patch b/patches/server/0317-Validate-tripwire-hook-placement-before-update.patch new file mode 100644 index 000000000000..b607c1afe96f --- /dev/null +++ b/patches/server/0317-Validate-tripwire-hook-placement-before-update.patch @@ -0,0 +1,18 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Shane Freeder +Date: Sat, 7 Mar 2020 00:07:51 +0000 +Subject: [PATCH] Validate tripwire hook placement before update + + +diff --git a/src/main/java/net/minecraft/world/level/block/TripWireHookBlock.java b/src/main/java/net/minecraft/world/level/block/TripWireHookBlock.java +index 1240f18b3d40f8bde2346c893304ae340a727e24..c2589f42c467ca672417c24076313da51bb2dcbb 100644 +--- a/src/main/java/net/minecraft/world/level/block/TripWireHookBlock.java ++++ b/src/main/java/net/minecraft/world/level/block/TripWireHookBlock.java +@@ -191,6 +191,7 @@ public class TripWireHookBlock extends Block { + + TripWireHookBlock.emitState(world, pos, flag4, flag5, flag2, flag3); + if (!flag) { ++ if (world.getBlockState(pos).getBlock() == Blocks.TRIPWIRE_HOOK) // Paper - Validate tripwire hook placement before update + world.setBlock(pos, (BlockState) iblockdata3.setValue(TripWireHookBlock.FACING, enumdirection), 3); + if (flag1) { + TripWireHookBlock.notifyNeighbors(block, world, pos, enumdirection); diff --git a/patches/server/0318-Add-option-to-allow-iron-golems-to-spawn-in-air.patch b/patches/server/0318-Add-option-to-allow-iron-golems-to-spawn-in-air.patch new file mode 100644 index 000000000000..8991de1b700c --- /dev/null +++ b/patches/server/0318-Add-option-to-allow-iron-golems-to-spawn-in-air.patch @@ -0,0 +1,19 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: William Blake Galbreath +Date: Sat, 13 Apr 2019 16:50:58 -0500 +Subject: [PATCH] Add option to allow iron golems to spawn in air + + +diff --git a/src/main/java/net/minecraft/world/entity/animal/IronGolem.java b/src/main/java/net/minecraft/world/entity/animal/IronGolem.java +index dac63f51fc23cc7f35ee299d1e04c96c210db52b..e07b79ef172095c1800c88342b3ac8dc7703aea2 100644 +--- a/src/main/java/net/minecraft/world/entity/animal/IronGolem.java ++++ b/src/main/java/net/minecraft/world/entity/animal/IronGolem.java +@@ -319,7 +319,7 @@ public class IronGolem extends AbstractGolem implements NeutralMob { + BlockPos blockposition1 = blockposition.below(); + BlockState iblockdata = world.getBlockState(blockposition1); + +- if (!iblockdata.entityCanStandOn(world, blockposition1, this)) { ++ if (!iblockdata.entityCanStandOn(world, blockposition1, this) && !this.level().paperConfig().entities.spawning.ironGolemsCanSpawnInAir) { // Paper - Add option to allow iron golems to spawn in air + return false; + } else { + for (int i = 1; i < 3; ++i) { diff --git a/patches/server/0319-Configurable-chance-of-villager-zombie-infection.patch b/patches/server/0319-Configurable-chance-of-villager-zombie-infection.patch new file mode 100644 index 000000000000..e26452bb88e3 --- /dev/null +++ b/patches/server/0319-Configurable-chance-of-villager-zombie-infection.patch @@ -0,0 +1,26 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Zero +Date: Sat, 22 Feb 2020 16:10:31 -0500 +Subject: [PATCH] Configurable chance of villager zombie infection + +This allows you to solve an issue in vanilla behavior where: +* On easy difficulty your villagers will NEVER get infected, meaning they will always die. +* On normal difficulty they will have a 50% of getting infected or dying. + +diff --git a/src/main/java/net/minecraft/world/entity/monster/Zombie.java b/src/main/java/net/minecraft/world/entity/monster/Zombie.java +index d3d1e170380e7674c9ac13b06186eb563a58cd64..5b8ac7113eb8a57fe07bfaacc1b1320383a56d06 100644 +--- a/src/main/java/net/minecraft/world/entity/monster/Zombie.java ++++ b/src/main/java/net/minecraft/world/entity/monster/Zombie.java +@@ -484,10 +484,8 @@ public class Zombie extends Monster { + public boolean killedEntity(ServerLevel world, LivingEntity other) { + boolean flag = super.killedEntity(world, other); + +- if ((world.getDifficulty() == Difficulty.NORMAL || world.getDifficulty() == Difficulty.HARD) && other instanceof Villager entityvillager) { +- if (world.getDifficulty() != Difficulty.HARD && this.random.nextBoolean()) { +- return flag; +- } ++ final double fallbackChance = world.getDifficulty() == Difficulty.HARD ? 100d : world.getDifficulty() == Difficulty.NORMAL ? 50d : 0d; // Paper - Configurable chance of villager zombie infection ++ if (this.random.nextDouble() * 100 < world.paperConfig().entities.behavior.zombieVillagerInfectionChance.or(fallbackChance) && other instanceof Villager entityvillager) { // Paper - Configurable chance of villager zombie infection + + if (this.convertVillagerToZombieVillager(world, entityvillager)) { + flag = false; diff --git a/patches/server/0319-add-hand-to-BlockMultiPlaceEvent.patch b/patches/server/0319-add-hand-to-BlockMultiPlaceEvent.patch deleted file mode 100644 index 078a0ba255de..000000000000 --- a/patches/server/0319-add-hand-to-BlockMultiPlaceEvent.patch +++ /dev/null @@ -1,30 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Trigary -Date: Sun, 1 Mar 2020 22:43:24 +0100 -Subject: [PATCH] add hand to BlockMultiPlaceEvent - - -diff --git a/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java b/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java -index 8172a75aef7c3dcdd92817f148466a4cad4c78ed..235ac5c12dab593da3a40e348a010ff626ce74a3 100644 ---- a/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java -+++ b/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java -@@ -412,13 +412,18 @@ public class CraftEventFactory { - } - - org.bukkit.inventory.ItemStack item; -+ // Paper start - add hand to BlockMultiPlaceEvent -+ EquipmentSlot equipmentSlot; - if (hand == InteractionHand.MAIN_HAND) { - item = player.getInventory().getItemInMainHand(); -+ equipmentSlot = EquipmentSlot.HAND; - } else { - item = player.getInventory().getItemInOffHand(); -+ equipmentSlot = EquipmentSlot.OFF_HAND; - } - -- BlockMultiPlaceEvent event = new BlockMultiPlaceEvent(blockStates, blockClicked, item, player, canBuild); -+ BlockMultiPlaceEvent event = new BlockMultiPlaceEvent(blockStates, blockClicked, item, player, canBuild, equipmentSlot); -+ // Paper end - craftServer.getPluginManager().callEvent(event); - - return event; diff --git a/patches/server/0320-Optimise-Chunk-getFluid.patch b/patches/server/0320-Optimise-Chunk-getFluid.patch new file mode 100644 index 000000000000..b81a71067cd9 --- /dev/null +++ b/patches/server/0320-Optimise-Chunk-getFluid.patch @@ -0,0 +1,61 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Spottedleaf +Date: Tue, 14 Jan 2020 14:59:08 -0800 +Subject: [PATCH] Optimise Chunk#getFluid + +Removing the try catch and generally reducing ops should make it +faster on its own, however removing the try catch makes it +easier to inline due to code size + +diff --git a/src/main/java/net/minecraft/world/level/chunk/LevelChunk.java b/src/main/java/net/minecraft/world/level/chunk/LevelChunk.java +index a1b6c13d496519ef6ce240036cec6642626903b9..d4bd4cbc5c4773659662a6d7a09b21a99463c1fb 100644 +--- a/src/main/java/net/minecraft/world/level/chunk/LevelChunk.java ++++ b/src/main/java/net/minecraft/world/level/chunk/LevelChunk.java +@@ -293,18 +293,20 @@ public class LevelChunk extends ChunkAccess { + } + + public FluidState getFluidState(int x, int y, int z) { +- try { +- int l = this.getSectionIndex(y); +- +- if (l >= 0 && l < this.sections.length) { +- LevelChunkSection chunksection = this.sections[l]; ++ // Paper start - Perf: Optimise Chunk#getFluid ++ // try { // Remove try catch ++ int index = this.getSectionIndex(y); ++ if (index >= 0 && index < this.sections.length) { ++ LevelChunkSection chunksection = this.sections[index]; + + if (!chunksection.hasOnlyAir()) { +- return chunksection.getFluidState(x & 15, y & 15, z & 15); ++ return chunksection.states.get((y & 15) << 8 | (z & 15) << 4 | x & 15).getFluidState(); ++ // Paper end - Perf: Optimise Chunk#getFluid + } + } + + return Fluids.EMPTY.defaultFluidState(); ++ /* // Paper - Perf: Optimise Chunk#getFluid + } catch (Throwable throwable) { + CrashReport crashreport = CrashReport.forThrowable(throwable, "Getting fluid state"); + CrashReportCategory crashreportsystemdetails = crashreport.addCategory("Block being got"); +@@ -314,6 +316,7 @@ public class LevelChunk extends ChunkAccess { + }); + throw new ReportedException(crashreport); + } ++ */ // Paper - Perf: Optimise Chunk#getFluid + } + + // CraftBukkit start +diff --git a/src/main/java/net/minecraft/world/level/chunk/LevelChunkSection.java b/src/main/java/net/minecraft/world/level/chunk/LevelChunkSection.java +index 771529ba28a16664ad19ed9c0f4bf95eeb7da76b..52f44f14bbda60fe771c351e01e6ff470d7371e6 100644 +--- a/src/main/java/net/minecraft/world/level/chunk/LevelChunkSection.java ++++ b/src/main/java/net/minecraft/world/level/chunk/LevelChunkSection.java +@@ -49,7 +49,7 @@ public class LevelChunkSection { + } + + public FluidState getFluidState(int x, int y, int z) { +- return ((BlockState) this.states.get(x, y, z)).getFluidState(); ++ return this.states.get(x, y, z).getFluidState(); // Paper - Perf: Optimise Chunk#getFluid; diff on change - we expect this to be effectively just getType(x, y, z).getFluid(). If this changes we need to check other patches that use IBlockData#getFluid. + } + + public void acquire() { diff --git a/patches/server/0320-Validate-tripwire-hook-placement-before-update.patch b/patches/server/0320-Validate-tripwire-hook-placement-before-update.patch deleted file mode 100644 index bc1125b169f9..000000000000 --- a/patches/server/0320-Validate-tripwire-hook-placement-before-update.patch +++ /dev/null @@ -1,18 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Shane Freeder -Date: Sat, 7 Mar 2020 00:07:51 +0000 -Subject: [PATCH] Validate tripwire hook placement before update - - -diff --git a/src/main/java/net/minecraft/world/level/block/TripWireHookBlock.java b/src/main/java/net/minecraft/world/level/block/TripWireHookBlock.java -index bc4d324f086d815c139408629a561ea4d94c839b..8614fad5b3df7a6030384b108b1689bf6b9f1209 100644 ---- a/src/main/java/net/minecraft/world/level/block/TripWireHookBlock.java -+++ b/src/main/java/net/minecraft/world/level/block/TripWireHookBlock.java -@@ -189,6 +189,7 @@ public class TripWireHookBlock extends Block { - - TripWireHookBlock.emitState(world, pos, flag4, flag5, flag2, flag3); - if (!flag) { -+ if (world.getBlockState(pos).getBlock() == Blocks.TRIPWIRE_HOOK) // Paper - Validate tripwire hook placement before update - world.setBlock(pos, (BlockState) iblockdata3.setValue(TripWireHookBlock.FACING, enumdirection), 3); - if (flag1) { - TripWireHookBlock.notifyNeighbors(block, world, pos, enumdirection); diff --git a/patches/server/0321-Add-option-to-allow-iron-golems-to-spawn-in-air.patch b/patches/server/0321-Add-option-to-allow-iron-golems-to-spawn-in-air.patch deleted file mode 100644 index 75d1d9c02bdc..000000000000 --- a/patches/server/0321-Add-option-to-allow-iron-golems-to-spawn-in-air.patch +++ /dev/null @@ -1,19 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: William Blake Galbreath -Date: Sat, 13 Apr 2019 16:50:58 -0500 -Subject: [PATCH] Add option to allow iron golems to spawn in air - - -diff --git a/src/main/java/net/minecraft/world/entity/animal/IronGolem.java b/src/main/java/net/minecraft/world/entity/animal/IronGolem.java -index 615be0b85fb3d28a044c6bae6a0fe93ec4fca061..1807da10d07d1f6e4ddbc0fa1b8da34a688d67c3 100644 ---- a/src/main/java/net/minecraft/world/entity/animal/IronGolem.java -+++ b/src/main/java/net/minecraft/world/entity/animal/IronGolem.java -@@ -325,7 +325,7 @@ public class IronGolem extends AbstractGolem implements NeutralMob { - BlockPos blockposition1 = blockposition.below(); - BlockState iblockdata = world.getBlockState(blockposition1); - -- if (!iblockdata.entityCanStandOn(world, blockposition1, this)) { -+ if (!iblockdata.entityCanStandOn(world, blockposition1, this) && !this.level().paperConfig().entities.spawning.ironGolemsCanSpawnInAir) { // Paper - Add option to allow iron golems to spawn in air - return false; - } else { - for (int i = 1; i < 3; ++i) { diff --git a/patches/server/0324-Set-spigots-verbose-world-setting-to-false-by-def.patch b/patches/server/0321-Set-spigots-verbose-world-setting-to-false-by-def.patch similarity index 100% rename from patches/server/0324-Set-spigots-verbose-world-setting-to-false-by-def.patch rename to patches/server/0321-Set-spigots-verbose-world-setting-to-false-by-def.patch diff --git a/patches/server/0322-Add-tick-times-API-and-mspt-command.patch b/patches/server/0322-Add-tick-times-API-and-mspt-command.patch new file mode 100644 index 000000000000..cbef13696103 --- /dev/null +++ b/patches/server/0322-Add-tick-times-API-and-mspt-command.patch @@ -0,0 +1,208 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: William Blake Galbreath +Date: Sun, 5 Apr 2020 22:23:14 -0500 +Subject: [PATCH] Add tick times API and /mspt command + + +diff --git a/src/main/java/io/papermc/paper/command/MSPTCommand.java b/src/main/java/io/papermc/paper/command/MSPTCommand.java +new file mode 100644 +index 0000000000000000000000000000000000000000..8b5293b0c696ef21d0101493ffa41b60bf0bc86b +--- /dev/null ++++ b/src/main/java/io/papermc/paper/command/MSPTCommand.java +@@ -0,0 +1,102 @@ ++package io.papermc.paper.command; ++ ++import net.kyori.adventure.text.Component; ++import net.minecraft.server.MinecraftServer; ++import org.bukkit.Location; ++import org.bukkit.command.Command; ++import org.bukkit.command.CommandSender; ++ ++import java.text.DecimalFormat; ++import java.util.ArrayList; ++import java.util.Arrays; ++import java.util.Collections; ++import java.util.List; ++import org.checkerframework.checker.nullness.qual.NonNull; ++import org.checkerframework.framework.qual.DefaultQualifier; ++ ++import static net.kyori.adventure.text.Component.text; ++import static net.kyori.adventure.text.format.NamedTextColor.GOLD; ++import static net.kyori.adventure.text.format.NamedTextColor.GRAY; ++import static net.kyori.adventure.text.format.NamedTextColor.GREEN; ++import static net.kyori.adventure.text.format.NamedTextColor.RED; ++import static net.kyori.adventure.text.format.NamedTextColor.YELLOW; ++ ++@DefaultQualifier(NonNull.class) ++public final class MSPTCommand extends Command { ++ private static final DecimalFormat DF = new DecimalFormat("########0.0"); ++ private static final Component SLASH = text("/"); ++ ++ public MSPTCommand(final String name) { ++ super(name); ++ this.description = "View server tick times"; ++ this.usageMessage = "/mspt"; ++ this.setPermission("bukkit.command.mspt"); ++ } ++ ++ @Override ++ public List tabComplete(CommandSender sender, String alias, String[] args, Location location) throws IllegalArgumentException { ++ return Collections.emptyList(); ++ } ++ ++ @Override ++ public boolean execute(CommandSender sender, String commandLabel, String[] args) { ++ if (!testPermission(sender)) return true; ++ ++ MinecraftServer server = MinecraftServer.getServer(); ++ ++ List times = new ArrayList<>(); ++ times.addAll(eval(server.tickTimes5s.getTimes())); ++ times.addAll(eval(server.tickTimes10s.getTimes())); ++ times.addAll(eval(server.tickTimes60s.getTimes())); ++ ++ sender.sendMessage(text().content("Server tick times ").color(GOLD) ++ .append(text().color(YELLOW) ++ .append( ++ text("("), ++ text("avg", GRAY), ++ text("/"), ++ text("min", GRAY), ++ text("/"), ++ text("max", GRAY), ++ text(")") ++ ) ++ ).append( ++ text(" from last 5s"), ++ text(",", GRAY), ++ text(" 10s"), ++ text(",", GRAY), ++ text(" 1m"), ++ text(":", YELLOW) ++ ) ++ ); ++ sender.sendMessage(text().content("â—´ ").color(GOLD) ++ .append(text().color(GRAY) ++ .append( ++ times.get(0), SLASH, times.get(1), SLASH, times.get(2), text(", ", YELLOW), ++ times.get(3), SLASH, times.get(4), SLASH, times.get(5), text(", ", YELLOW), ++ times.get(6), SLASH, times.get(7), SLASH, times.get(8) ++ ) ++ ) ++ ); ++ return true; ++ } ++ ++ private static List eval(long[] times) { ++ long min = Integer.MAX_VALUE; ++ long max = 0L; ++ long total = 0L; ++ for (long value : times) { ++ if (value > 0L && value < min) min = value; ++ if (value > max) max = value; ++ total += value; ++ } ++ double avgD = ((double) total / (double) times.length) * 1.0E-6D; ++ double minD = ((double) min) * 1.0E-6D; ++ double maxD = ((double) max) * 1.0E-6D; ++ return Arrays.asList(getColor(avgD), getColor(minD), getColor(maxD)); ++ } ++ ++ private static Component getColor(double avg) { ++ return text(DF.format(avg), avg >= 50 ? RED : avg >= 40 ? YELLOW : GREEN); ++ } ++} +diff --git a/src/main/java/io/papermc/paper/command/PaperCommands.java b/src/main/java/io/papermc/paper/command/PaperCommands.java +index 72f2e81b9905a0d57ed8e2a88578f62d5235c456..7b58b2d6297800c2dcdbf7539e5ab8e7703f39f1 100644 +--- a/src/main/java/io/papermc/paper/command/PaperCommands.java ++++ b/src/main/java/io/papermc/paper/command/PaperCommands.java +@@ -18,6 +18,7 @@ public final class PaperCommands { + static { + COMMANDS.put("paper", new PaperCommand("paper")); + COMMANDS.put("callback", new CallbackCommand("callback")); ++ COMMANDS.put("mspt", new MSPTCommand("mspt")); + } + + public static void registerCommands(final MinecraftServer server) { +diff --git a/src/main/java/net/minecraft/server/MinecraftServer.java b/src/main/java/net/minecraft/server/MinecraftServer.java +index 447b8f9ede3f57c6c5f968a0d25153c5c8770c5a..652626c30a5e25ada797ec01273f1e016798aae1 100644 +--- a/src/main/java/net/minecraft/server/MinecraftServer.java ++++ b/src/main/java/net/minecraft/server/MinecraftServer.java +@@ -263,6 +263,11 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop -Date: Sat, 22 Feb 2020 16:10:31 -0500 -Subject: [PATCH] Configurable chance of villager zombie infection - -This allows you to solve an issue in vanilla behavior where: -* On easy difficulty your villagers will NEVER get infected, meaning they will always die. -* On normal difficulty they will have a 50% of getting infected or dying. - -diff --git a/src/main/java/net/minecraft/world/entity/monster/Zombie.java b/src/main/java/net/minecraft/world/entity/monster/Zombie.java -index d981f8679149669f6ca4ea950d744149974532b2..e2a3978899497b6622829d6577cfaa723092da9d 100644 ---- a/src/main/java/net/minecraft/world/entity/monster/Zombie.java -+++ b/src/main/java/net/minecraft/world/entity/monster/Zombie.java -@@ -454,10 +454,8 @@ public class Zombie extends Monster { - public boolean killedEntity(ServerLevel world, LivingEntity other) { - boolean flag = super.killedEntity(world, other); - -- if ((world.getDifficulty() == Difficulty.NORMAL || world.getDifficulty() == Difficulty.HARD) && other instanceof Villager entityvillager) { -- if (world.getDifficulty() != Difficulty.HARD && this.random.nextBoolean()) { -- return flag; -- } -+ final double fallbackChance = world.getDifficulty() == Difficulty.HARD ? 100d : world.getDifficulty() == Difficulty.NORMAL ? 50d : 0d; // Paper - Configurable chance of villager zombie infection -+ if (this.random.nextDouble() * 100 < world.paperConfig().entities.behavior.zombieVillagerInfectionChance.or(fallbackChance) && other instanceof Villager entityvillager) { // Paper - Configurable chance of villager zombie infection - // CraftBukkit start - flag = Zombie.zombifyVillager(world, entityvillager, this.blockPosition(), this.isSilent(), CreatureSpawnEvent.SpawnReason.INFECTION) == null; - } diff --git a/patches/server/0323-Expose-MinecraftServer-isRunning.patch b/patches/server/0323-Expose-MinecraftServer-isRunning.patch new file mode 100644 index 000000000000..1f82adb8b4d2 --- /dev/null +++ b/patches/server/0323-Expose-MinecraftServer-isRunning.patch @@ -0,0 +1,22 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: JRoy +Date: Fri, 10 Apr 2020 21:24:12 -0400 +Subject: [PATCH] Expose MinecraftServer#isRunning + +This allows for plugins to detect if the server is actually turning off in onDisable rather than just plugins reloading. + +diff --git a/src/main/java/org/bukkit/craftbukkit/CraftServer.java b/src/main/java/org/bukkit/craftbukkit/CraftServer.java +index ff6bac3e53795c427719900428a56a0e505f6ec2..c40331c2be3a3cfb156c3faa5f6a9fd3cb237077 100644 +--- a/src/main/java/org/bukkit/craftbukkit/CraftServer.java ++++ b/src/main/java/org/bukkit/craftbukkit/CraftServer.java +@@ -2950,5 +2950,10 @@ public final class CraftServer implements Server { + public int getCurrentTick() { + return net.minecraft.server.MinecraftServer.currentTick; + } ++ ++ @Override ++ public boolean isStopping() { ++ return net.minecraft.server.MinecraftServer.getServer().hasStopped(); ++ } + // Paper end + } diff --git a/patches/server/0323-Optimise-Chunk-getFluid.patch b/patches/server/0323-Optimise-Chunk-getFluid.patch deleted file mode 100644 index 8142952ce69e..000000000000 --- a/patches/server/0323-Optimise-Chunk-getFluid.patch +++ /dev/null @@ -1,61 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Spottedleaf -Date: Tue, 14 Jan 2020 14:59:08 -0800 -Subject: [PATCH] Optimise Chunk#getFluid - -Removing the try catch and generally reducing ops should make it -faster on its own, however removing the try catch makes it -easier to inline due to code size - -diff --git a/src/main/java/net/minecraft/world/level/chunk/LevelChunk.java b/src/main/java/net/minecraft/world/level/chunk/LevelChunk.java -index 6775217c065026aa61fce56a17c93010209b6941..59f9ff720e92c69e11afe7f6ccecd81b0e54a74d 100644 ---- a/src/main/java/net/minecraft/world/level/chunk/LevelChunk.java -+++ b/src/main/java/net/minecraft/world/level/chunk/LevelChunk.java -@@ -270,18 +270,20 @@ public class LevelChunk extends ChunkAccess { - } - - public FluidState getFluidState(int x, int y, int z) { -- try { -- int l = this.getSectionIndex(y); -- -- if (l >= 0 && l < this.sections.length) { -- LevelChunkSection chunksection = this.sections[l]; -+ // Paper start - Perf: Optimise Chunk#getFluid -+ // try { // Remove try catch -+ int index = this.getSectionIndex(y); -+ if (index >= 0 && index < this.sections.length) { -+ LevelChunkSection chunksection = this.sections[index]; - - if (!chunksection.hasOnlyAir()) { -- return chunksection.getFluidState(x & 15, y & 15, z & 15); -+ return chunksection.states.get((y & 15) << 8 | (z & 15) << 4 | x & 15).getFluidState(); -+ // Paper end - Perf: Optimise Chunk#getFluid - } - } - - return Fluids.EMPTY.defaultFluidState(); -+ /* // Paper - Perf: Optimise Chunk#getFluid - } catch (Throwable throwable) { - CrashReport crashreport = CrashReport.forThrowable(throwable, "Getting fluid state"); - CrashReportCategory crashreportsystemdetails = crashreport.addCategory("Block being got"); -@@ -291,6 +293,7 @@ public class LevelChunk extends ChunkAccess { - }); - throw new ReportedException(crashreport); - } -+ */ // Paper - Perf: Optimise Chunk#getFluid - } - - // CraftBukkit start -diff --git a/src/main/java/net/minecraft/world/level/chunk/LevelChunkSection.java b/src/main/java/net/minecraft/world/level/chunk/LevelChunkSection.java -index 2c153af611399e884752f8256bee4fe32de5c572..90d1c3e23e753c29660f7d993b3c90ac022941c3 100644 ---- a/src/main/java/net/minecraft/world/level/chunk/LevelChunkSection.java -+++ b/src/main/java/net/minecraft/world/level/chunk/LevelChunkSection.java -@@ -43,7 +43,7 @@ public class LevelChunkSection { - } - - public FluidState getFluidState(int x, int y, int z) { -- return ((BlockState) this.states.get(x, y, z)).getFluidState(); -+ return this.states.get(x, y, z).getFluidState(); // Paper - Perf: Optimise Chunk#getFluid; diff on change - we expect this to be effectively just getType(x, y, z).getFluid(). If this changes we need to check other patches that use IBlockData#getFluid. - } - - public void acquire() { diff --git a/patches/server/0324-Add-Raw-Byte-ItemStack-Serialization.patch b/patches/server/0324-Add-Raw-Byte-ItemStack-Serialization.patch new file mode 100644 index 000000000000..3895429c4425 --- /dev/null +++ b/patches/server/0324-Add-Raw-Byte-ItemStack-Serialization.patch @@ -0,0 +1,65 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Mariell Hoversholm +Date: Thu, 30 Apr 2020 16:56:54 +0200 +Subject: [PATCH] Add Raw Byte ItemStack Serialization + +Serializes using NBT which is safer for server data migrations than bukkits format. + +diff --git a/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java b/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java +index 2b60572307e3ec23e21e09c34a04de9a1c57f136..150ab63f231fc3c39661ab876a8c90d608ee8568 100644 +--- a/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java ++++ b/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java +@@ -469,6 +469,53 @@ public final class CraftMagicNumbers implements UnsafeValues { + public com.destroystokyo.paper.util.VersionFetcher getVersionFetcher() { + return new com.destroystokyo.paper.PaperVersionFetcher(); + } ++ ++ @Override ++ public byte[] serializeItem(ItemStack item) { ++ Preconditions.checkNotNull(item, "null cannot be serialized"); ++ Preconditions.checkArgument(item.getType() != Material.AIR, "air cannot be serialized"); ++ ++ return serializeNbtToBytes((net.minecraft.nbt.CompoundTag) (item instanceof CraftItemStack ? ((CraftItemStack) item).handle : CraftItemStack.asNMSCopy(item)).save(MinecraftServer.getServer().registryAccess())); ++ } ++ ++ @Override ++ public ItemStack deserializeItem(byte[] data) { ++ Preconditions.checkNotNull(data, "null cannot be deserialized"); ++ Preconditions.checkArgument(data.length > 0, "cannot deserialize nothing"); ++ ++ net.minecraft.nbt.CompoundTag compound = deserializeNbtFromBytes(data); ++ final int dataVersion = compound.getInt("DataVersion"); ++ compound = (net.minecraft.nbt.CompoundTag) MinecraftServer.getServer().fixerUpper.update(References.ITEM_STACK, new Dynamic<>(NbtOps.INSTANCE, compound), dataVersion, this.getDataVersion()).getValue(); ++ return CraftItemStack.asCraftMirror(net.minecraft.world.item.ItemStack.parse(MinecraftServer.getServer().registryAccess(), compound).orElseThrow()); ++ } ++ ++ private byte[] serializeNbtToBytes(CompoundTag compound) { ++ compound.putInt("DataVersion", getDataVersion()); ++ java.io.ByteArrayOutputStream outputStream = new java.io.ByteArrayOutputStream(); ++ try { ++ net.minecraft.nbt.NbtIo.writeCompressed( ++ compound, ++ outputStream ++ ); ++ } catch (IOException ex) { ++ throw new RuntimeException(ex); ++ } ++ return outputStream.toByteArray(); ++ } ++ ++ private net.minecraft.nbt.CompoundTag deserializeNbtFromBytes(byte[] data) { ++ net.minecraft.nbt.CompoundTag compound; ++ try { ++ compound = net.minecraft.nbt.NbtIo.readCompressed( ++ new java.io.ByteArrayInputStream(data), net.minecraft.nbt.NbtAccounter.unlimitedHeap() ++ ); ++ } catch (IOException ex) { ++ throw new RuntimeException(ex); ++ } ++ int dataVersion = compound.getInt("DataVersion"); ++ Preconditions.checkArgument(dataVersion <= getDataVersion(), "Newer version! Server downgrades are not supported!"); ++ return compound; ++ } + // Paper end + + /** diff --git a/patches/server/0325-Add-tick-times-API-and-mspt-command.patch b/patches/server/0325-Add-tick-times-API-and-mspt-command.patch deleted file mode 100644 index b8fd4a8f65e7..000000000000 --- a/patches/server/0325-Add-tick-times-API-and-mspt-command.patch +++ /dev/null @@ -1,206 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: William Blake Galbreath -Date: Sun, 5 Apr 2020 22:23:14 -0500 -Subject: [PATCH] Add tick times API and /mspt command - - -diff --git a/src/main/java/io/papermc/paper/command/MSPTCommand.java b/src/main/java/io/papermc/paper/command/MSPTCommand.java -new file mode 100644 -index 0000000000000000000000000000000000000000..8b5293b0c696ef21d0101493ffa41b60bf0bc86b ---- /dev/null -+++ b/src/main/java/io/papermc/paper/command/MSPTCommand.java -@@ -0,0 +1,102 @@ -+package io.papermc.paper.command; -+ -+import net.kyori.adventure.text.Component; -+import net.minecraft.server.MinecraftServer; -+import org.bukkit.Location; -+import org.bukkit.command.Command; -+import org.bukkit.command.CommandSender; -+ -+import java.text.DecimalFormat; -+import java.util.ArrayList; -+import java.util.Arrays; -+import java.util.Collections; -+import java.util.List; -+import org.checkerframework.checker.nullness.qual.NonNull; -+import org.checkerframework.framework.qual.DefaultQualifier; -+ -+import static net.kyori.adventure.text.Component.text; -+import static net.kyori.adventure.text.format.NamedTextColor.GOLD; -+import static net.kyori.adventure.text.format.NamedTextColor.GRAY; -+import static net.kyori.adventure.text.format.NamedTextColor.GREEN; -+import static net.kyori.adventure.text.format.NamedTextColor.RED; -+import static net.kyori.adventure.text.format.NamedTextColor.YELLOW; -+ -+@DefaultQualifier(NonNull.class) -+public final class MSPTCommand extends Command { -+ private static final DecimalFormat DF = new DecimalFormat("########0.0"); -+ private static final Component SLASH = text("/"); -+ -+ public MSPTCommand(final String name) { -+ super(name); -+ this.description = "View server tick times"; -+ this.usageMessage = "/mspt"; -+ this.setPermission("bukkit.command.mspt"); -+ } -+ -+ @Override -+ public List tabComplete(CommandSender sender, String alias, String[] args, Location location) throws IllegalArgumentException { -+ return Collections.emptyList(); -+ } -+ -+ @Override -+ public boolean execute(CommandSender sender, String commandLabel, String[] args) { -+ if (!testPermission(sender)) return true; -+ -+ MinecraftServer server = MinecraftServer.getServer(); -+ -+ List times = new ArrayList<>(); -+ times.addAll(eval(server.tickTimes5s.getTimes())); -+ times.addAll(eval(server.tickTimes10s.getTimes())); -+ times.addAll(eval(server.tickTimes60s.getTimes())); -+ -+ sender.sendMessage(text().content("Server tick times ").color(GOLD) -+ .append(text().color(YELLOW) -+ .append( -+ text("("), -+ text("avg", GRAY), -+ text("/"), -+ text("min", GRAY), -+ text("/"), -+ text("max", GRAY), -+ text(")") -+ ) -+ ).append( -+ text(" from last 5s"), -+ text(",", GRAY), -+ text(" 10s"), -+ text(",", GRAY), -+ text(" 1m"), -+ text(":", YELLOW) -+ ) -+ ); -+ sender.sendMessage(text().content("â—´ ").color(GOLD) -+ .append(text().color(GRAY) -+ .append( -+ times.get(0), SLASH, times.get(1), SLASH, times.get(2), text(", ", YELLOW), -+ times.get(3), SLASH, times.get(4), SLASH, times.get(5), text(", ", YELLOW), -+ times.get(6), SLASH, times.get(7), SLASH, times.get(8) -+ ) -+ ) -+ ); -+ return true; -+ } -+ -+ private static List eval(long[] times) { -+ long min = Integer.MAX_VALUE; -+ long max = 0L; -+ long total = 0L; -+ for (long value : times) { -+ if (value > 0L && value < min) min = value; -+ if (value > max) max = value; -+ total += value; -+ } -+ double avgD = ((double) total / (double) times.length) * 1.0E-6D; -+ double minD = ((double) min) * 1.0E-6D; -+ double maxD = ((double) max) * 1.0E-6D; -+ return Arrays.asList(getColor(avgD), getColor(minD), getColor(maxD)); -+ } -+ -+ private static Component getColor(double avg) { -+ return text(DF.format(avg), avg >= 50 ? RED : avg >= 40 ? YELLOW : GREEN); -+ } -+} -diff --git a/src/main/java/io/papermc/paper/command/PaperCommands.java b/src/main/java/io/papermc/paper/command/PaperCommands.java -index 72f2e81b9905a0d57ed8e2a88578f62d5235c456..7b58b2d6297800c2dcdbf7539e5ab8e7703f39f1 100644 ---- a/src/main/java/io/papermc/paper/command/PaperCommands.java -+++ b/src/main/java/io/papermc/paper/command/PaperCommands.java -@@ -18,6 +18,7 @@ public final class PaperCommands { - static { - COMMANDS.put("paper", new PaperCommand("paper")); - COMMANDS.put("callback", new CallbackCommand("callback")); -+ COMMANDS.put("mspt", new MSPTCommand("mspt")); - } - - public static void registerCommands(final MinecraftServer server) { -diff --git a/src/main/java/net/minecraft/server/MinecraftServer.java b/src/main/java/net/minecraft/server/MinecraftServer.java -index ddc3da84c5a55d2cd977fcdb18121351606a6b3c..53bb62c1dcb487be915759d22e06aea80be54f36 100644 ---- a/src/main/java/net/minecraft/server/MinecraftServer.java -+++ b/src/main/java/net/minecraft/server/MinecraftServer.java -@@ -258,6 +258,11 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop +Date: Sat, 1 Feb 2020 16:50:39 +0100 +Subject: [PATCH] Pillager patrol spawn settings and per player options + +This adds config options for defining the spawn chance, spawn delay and +spawn start day as well as toggles for handling the spawn delay and +start day per player. (Based on the time played statistic) +When not per player it will use the Vanilla mechanic of one delay per +world and the world age for the start day. + +diff --git a/src/main/java/net/minecraft/server/level/ServerPlayer.java b/src/main/java/net/minecraft/server/level/ServerPlayer.java +index 099a9aa7e64da22e720fa527cac8750991ee8562..ea8279637a2af4d18c9decbf3342b058edeffd73 100644 +--- a/src/main/java/net/minecraft/server/level/ServerPlayer.java ++++ b/src/main/java/net/minecraft/server/level/ServerPlayer.java +@@ -297,6 +297,7 @@ public class ServerPlayer extends net.minecraft.world.entity.player.Player { + public boolean wonGame; + private int containerUpdateDelay; // Paper - Configurable container update tick rate + public long loginTime; // Paper - Replace OfflinePlayer#getLastPlayed ++ public int patrolSpawnDelay; // Paper - Pillager patrol spawn settings and per player options + // Paper start - cancellable death event + public boolean queueHealthUpdatePacket; + public net.minecraft.network.protocol.game.ClientboundSetHealthPacket queuedHealthUpdatePacket; +diff --git a/src/main/java/net/minecraft/world/level/levelgen/PatrolSpawner.java b/src/main/java/net/minecraft/world/level/levelgen/PatrolSpawner.java +index 8837db46ced2412942949a3c6f81ec7d9a0bacda..e93ef232b0426a1095dad05fc4acb2a74db5b689 100644 +--- a/src/main/java/net/minecraft/world/level/levelgen/PatrolSpawner.java ++++ b/src/main/java/net/minecraft/world/level/levelgen/PatrolSpawner.java +@@ -24,7 +24,7 @@ public class PatrolSpawner implements CustomSpawner { + + @Override + public int tick(ServerLevel world, boolean spawnMonsters, boolean spawnAnimals) { +- if (world.paperConfig().entities.behavior.pillagerPatrols.disable) return 0; // Paper - Add option to disable pillager patrols ++ if (world.paperConfig().entities.behavior.pillagerPatrols.disable || world.paperConfig().entities.behavior.pillagerPatrols.spawnChance == 0) return 0; // Paper - Add option to disable pillager patrols & Pillager patrol spawn settings and per player options + if (!spawnMonsters) { + return 0; + } else if (!world.getGameRules().getBoolean(GameRules.RULE_DO_PATROL_SPAWNING)) { +@@ -32,23 +32,51 @@ public class PatrolSpawner implements CustomSpawner { + } else { + RandomSource randomsource = world.random; + +- --this.nextTick; +- if (this.nextTick > 0) { ++ // Paper start - Pillager patrol spawn settings and per player options ++ // Random player selection moved up for per player spawning and configuration ++ int j = world.players().size(); ++ if (j < 1) { + return 0; ++ } ++ ++ net.minecraft.server.level.ServerPlayer entityhuman = world.players().get(randomsource.nextInt(j)); ++ if (entityhuman.isSpectator()) { ++ return 0; ++ } ++ ++ int patrolSpawnDelay; ++ if (world.paperConfig().entities.behavior.pillagerPatrols.spawnDelay.perPlayer) { ++ --entityhuman.patrolSpawnDelay; ++ patrolSpawnDelay = entityhuman.patrolSpawnDelay; + } else { +- this.nextTick += 12000 + randomsource.nextInt(1200); +- long i = world.getDayTime() / 24000L; ++ this.nextTick--; ++ patrolSpawnDelay = this.nextTick; ++ } ++ ++ if (patrolSpawnDelay > 0) { ++ return 0; ++ } else { ++ long days; ++ if (world.paperConfig().entities.behavior.pillagerPatrols.start.perPlayer) { ++ days = entityhuman.getStats().getValue(net.minecraft.stats.Stats.CUSTOM.get(net.minecraft.stats.Stats.PLAY_TIME)) / 24000L; // PLAY_ONE_MINUTE is actually counting in ticks, a misnomer by Mojang ++ } else { ++ days = world.getDayTime() / 24000L; ++ } ++ if (world.paperConfig().entities.behavior.pillagerPatrols.spawnDelay.perPlayer) { ++ entityhuman.patrolSpawnDelay += world.paperConfig().entities.behavior.pillagerPatrols.spawnDelay.ticks + randomsource.nextInt(1200); ++ } else { ++ this.nextTick += world.paperConfig().entities.behavior.pillagerPatrols.spawnDelay.ticks + randomsource.nextInt(1200); ++ } + +- if (i >= 5L && world.isDay()) { +- if (randomsource.nextInt(5) != 0) { ++ if (days >= world.paperConfig().entities.behavior.pillagerPatrols.start.day && world.isDay()) { ++ if (randomsource.nextDouble() >= world.paperConfig().entities.behavior.pillagerPatrols.spawnChance) { ++ // Paper end - Pillager patrol spawn settings and per player options + return 0; + } else { +- int j = world.players().size(); + + if (j < 1) { + return 0; + } else { +- Player entityhuman = (Player) world.players().get(randomsource.nextInt(j)); + + if (entityhuman.isSpectator()) { + return 0; diff --git a/patches/server/0326-Expose-MinecraftServer-isRunning.patch b/patches/server/0326-Expose-MinecraftServer-isRunning.patch deleted file mode 100644 index 4ee3eec40836..000000000000 --- a/patches/server/0326-Expose-MinecraftServer-isRunning.patch +++ /dev/null @@ -1,22 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: JRoy -Date: Fri, 10 Apr 2020 21:24:12 -0400 -Subject: [PATCH] Expose MinecraftServer#isRunning - -This allows for plugins to detect if the server is actually turning off in onDisable rather than just plugins reloading. - -diff --git a/src/main/java/org/bukkit/craftbukkit/CraftServer.java b/src/main/java/org/bukkit/craftbukkit/CraftServer.java -index 0078d999c132e56c6af242718fc1b983caa8b147..7226c506c902fc94f27cc5d444e25882138759c2 100644 ---- a/src/main/java/org/bukkit/craftbukkit/CraftServer.java -+++ b/src/main/java/org/bukkit/craftbukkit/CraftServer.java -@@ -2932,5 +2932,10 @@ public final class CraftServer implements Server { - public int getCurrentTick() { - return net.minecraft.server.MinecraftServer.currentTick; - } -+ -+ @Override -+ public boolean isStopping() { -+ return net.minecraft.server.MinecraftServer.getServer().hasStopped(); -+ } - // Paper end - } diff --git a/patches/server/0326-Remote-Connections-shouldn-t-hold-up-shutdown.patch b/patches/server/0326-Remote-Connections-shouldn-t-hold-up-shutdown.patch new file mode 100644 index 000000000000..b9679f9f481d --- /dev/null +++ b/patches/server/0326-Remote-Connections-shouldn-t-hold-up-shutdown.patch @@ -0,0 +1,44 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Aikar +Date: Tue, 31 Mar 2020 03:50:42 -0400 +Subject: [PATCH] Remote Connections shouldn't hold up shutdown + +Bugs in the connection logic appears to leave stale connections even, preventing shutdown + +diff --git a/src/main/java/net/minecraft/server/dedicated/DedicatedServer.java b/src/main/java/net/minecraft/server/dedicated/DedicatedServer.java +index 417c8bf97c5f5b7d127ac7d496b86882d75ff354..fe1975675189c6d1a63c42b7959fa40b5ac95ef8 100644 +--- a/src/main/java/net/minecraft/server/dedicated/DedicatedServer.java ++++ b/src/main/java/net/minecraft/server/dedicated/DedicatedServer.java +@@ -396,11 +396,11 @@ public class DedicatedServer extends MinecraftServer implements ServerInterface + } + + if (this.rconThread != null) { +- this.rconThread.stop(); ++ this.rconThread.stopNonBlocking(); // Paper - don't wait for remote connections + } + + if (this.queryThreadGs4 != null) { +- this.queryThreadGs4.stop(); ++ // this.remoteStatusListener.stop(); // Paper - don't wait for remote connections + } + + System.exit(0); // CraftBukkit +diff --git a/src/main/java/net/minecraft/server/rcon/thread/RconThread.java b/src/main/java/net/minecraft/server/rcon/thread/RconThread.java +index 594fbb033b63b8c9fb8752b1fcc78f8e9f7a2a83..c12d7db2b048a327c0e8f398848cd3a9bce0ebce 100644 +--- a/src/main/java/net/minecraft/server/rcon/thread/RconThread.java ++++ b/src/main/java/net/minecraft/server/rcon/thread/RconThread.java +@@ -104,6 +104,14 @@ public class RconThread extends GenericThread { + + this.clients.clear(); + } ++ // Paper start - don't wait for remote connections ++ public void stopNonBlocking() { ++ this.running = false; ++ for (RconClient client : this.clients) { ++ client.running = false; ++ } ++ } ++ // Paper end - don't wait for remote connections + + private void closeSocket(ServerSocket socket) { + LOGGER.debug("closeSocket: {}", socket); diff --git a/patches/server/0327-Add-Raw-Byte-ItemStack-Serialization.patch b/patches/server/0327-Add-Raw-Byte-ItemStack-Serialization.patch deleted file mode 100644 index dfefad8f18c0..000000000000 --- a/patches/server/0327-Add-Raw-Byte-ItemStack-Serialization.patch +++ /dev/null @@ -1,65 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Mariell Hoversholm -Date: Thu, 30 Apr 2020 16:56:54 +0200 -Subject: [PATCH] Add Raw Byte ItemStack Serialization - -Serializes using NBT which is safer for server data migrations than bukkits format. - -diff --git a/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java b/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java -index 432f019afff36aa6143c052f7387a6c275a09de8..8b11f5f8cec74c57d614d73233a449c97cd56d18 100644 ---- a/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java -+++ b/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java -@@ -464,6 +464,53 @@ public final class CraftMagicNumbers implements UnsafeValues { - public com.destroystokyo.paper.util.VersionFetcher getVersionFetcher() { - return new com.destroystokyo.paper.PaperVersionFetcher(); - } -+ -+ @Override -+ public byte[] serializeItem(ItemStack item) { -+ Preconditions.checkNotNull(item, "null cannot be serialized"); -+ Preconditions.checkArgument(item.getType() != Material.AIR, "air cannot be serialized"); -+ -+ return serializeNbtToBytes((net.minecraft.nbt.CompoundTag) (item instanceof CraftItemStack ? ((CraftItemStack) item).handle : CraftItemStack.asNMSCopy(item)).save(MinecraftServer.getServer().registryAccess())); -+ } -+ -+ @Override -+ public ItemStack deserializeItem(byte[] data) { -+ Preconditions.checkNotNull(data, "null cannot be deserialized"); -+ Preconditions.checkArgument(data.length > 0, "cannot deserialize nothing"); -+ -+ net.minecraft.nbt.CompoundTag compound = deserializeNbtFromBytes(data); -+ final int dataVersion = compound.getInt("DataVersion"); -+ compound = (net.minecraft.nbt.CompoundTag) MinecraftServer.getServer().fixerUpper.update(References.ITEM_STACK, new Dynamic<>(NbtOps.INSTANCE, compound), dataVersion, this.getDataVersion()).getValue(); -+ return CraftItemStack.asCraftMirror(net.minecraft.world.item.ItemStack.parse(MinecraftServer.getServer().registryAccess(), compound).orElseThrow()); -+ } -+ -+ private byte[] serializeNbtToBytes(CompoundTag compound) { -+ compound.putInt("DataVersion", getDataVersion()); -+ java.io.ByteArrayOutputStream outputStream = new java.io.ByteArrayOutputStream(); -+ try { -+ net.minecraft.nbt.NbtIo.writeCompressed( -+ compound, -+ outputStream -+ ); -+ } catch (IOException ex) { -+ throw new RuntimeException(ex); -+ } -+ return outputStream.toByteArray(); -+ } -+ -+ private net.minecraft.nbt.CompoundTag deserializeNbtFromBytes(byte[] data) { -+ net.minecraft.nbt.CompoundTag compound; -+ try { -+ compound = net.minecraft.nbt.NbtIo.readCompressed( -+ new java.io.ByteArrayInputStream(data), net.minecraft.nbt.NbtAccounter.unlimitedHeap() -+ ); -+ } catch (IOException ex) { -+ throw new RuntimeException(ex); -+ } -+ int dataVersion = compound.getInt("DataVersion"); -+ Preconditions.checkArgument(dataVersion <= getDataVersion(), "Newer version! Server downgrades are not supported!"); -+ return compound; -+ } - // Paper end - - @Override diff --git a/patches/server/0327-Do-not-allow-Vexes-to-load-chunks.patch b/patches/server/0327-Do-not-allow-Vexes-to-load-chunks.patch new file mode 100644 index 000000000000..0bc83a134492 --- /dev/null +++ b/patches/server/0327-Do-not-allow-Vexes-to-load-chunks.patch @@ -0,0 +1,22 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: chickeneer +Date: Tue, 17 Mar 2020 14:18:50 -0500 +Subject: [PATCH] Do not allow Vexes to load chunks + + +diff --git a/src/main/java/net/minecraft/world/entity/monster/Vex.java b/src/main/java/net/minecraft/world/entity/monster/Vex.java +index 1a5a61f3c6587992acadc83d5dff97ae2fe53235..183a33b7d666d652b455baa7e8339e9c4a870a58 100644 +--- a/src/main/java/net/minecraft/world/entity/monster/Vex.java ++++ b/src/main/java/net/minecraft/world/entity/monster/Vex.java +@@ -354,7 +354,10 @@ public class Vex extends Monster implements TraceableEntity { + for (int i = 0; i < 3; ++i) { + BlockPos blockposition1 = blockposition.offset(Vex.this.random.nextInt(15) - 7, Vex.this.random.nextInt(11) - 5, Vex.this.random.nextInt(15) - 7); + +- if (Vex.this.level().isEmptyBlock(blockposition1)) { ++ // Paper start - Don't load chunks ++ final net.minecraft.world.level.block.state.BlockState blockState = Vex.this.level().getBlockStateIfLoaded(blockposition1); ++ if (blockState != null && blockState.isAir()) { ++ // Paper end - Don't load chunks + Vex.this.moveControl.setWantedPosition((double) blockposition1.getX() + 0.5D, (double) blockposition1.getY() + 0.5D, (double) blockposition1.getZ() + 0.5D, 0.25D); + if (Vex.this.getTarget() == null) { + Vex.this.getLookControl().setLookAt((double) blockposition1.getX() + 0.5D, (double) blockposition1.getY() + 0.5D, (double) blockposition1.getZ() + 0.5D, 180.0F, 20.0F); diff --git a/patches/server/0328-Pillager-patrol-spawn-settings-and-per-player-option.patch b/patches/server/0328-Pillager-patrol-spawn-settings-and-per-player-option.patch deleted file mode 100644 index d6517e07b1e5..000000000000 --- a/patches/server/0328-Pillager-patrol-spawn-settings-and-per-player-option.patch +++ /dev/null @@ -1,96 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Phoenix616 -Date: Sat, 1 Feb 2020 16:50:39 +0100 -Subject: [PATCH] Pillager patrol spawn settings and per player options - -This adds config options for defining the spawn chance, spawn delay and -spawn start day as well as toggles for handling the spawn delay and -start day per player. (Based on the time played statistic) -When not per player it will use the Vanilla mechanic of one delay per -world and the world age for the start day. - -diff --git a/src/main/java/net/minecraft/server/level/ServerPlayer.java b/src/main/java/net/minecraft/server/level/ServerPlayer.java -index 4ccb5ba03d19223d25aee4cebd4b8bec2838a3e0..d4bda65d10bee880a341196364db63523f36e380 100644 ---- a/src/main/java/net/minecraft/server/level/ServerPlayer.java -+++ b/src/main/java/net/minecraft/server/level/ServerPlayer.java -@@ -270,6 +270,7 @@ public class ServerPlayer extends net.minecraft.world.entity.player.Player { - public boolean wonGame; - private int containerUpdateDelay; // Paper - Configurable container update tick rate - public long loginTime; // Paper - Replace OfflinePlayer#getLastPlayed -+ public int patrolSpawnDelay; // Paper - Pillager patrol spawn settings and per player options - // Paper start - cancellable death event - public boolean queueHealthUpdatePacket; - public net.minecraft.network.protocol.game.ClientboundSetHealthPacket queuedHealthUpdatePacket; -diff --git a/src/main/java/net/minecraft/world/level/levelgen/PatrolSpawner.java b/src/main/java/net/minecraft/world/level/levelgen/PatrolSpawner.java -index 7b5db53d4cf97e41175896de47303526198fb8f6..1741360aa3f2409b1a8ddf1d4602ffe57651a586 100644 ---- a/src/main/java/net/minecraft/world/level/levelgen/PatrolSpawner.java -+++ b/src/main/java/net/minecraft/world/level/levelgen/PatrolSpawner.java -@@ -24,7 +24,7 @@ public class PatrolSpawner implements CustomSpawner { - - @Override - public int tick(ServerLevel world, boolean spawnMonsters, boolean spawnAnimals) { -- if (world.paperConfig().entities.behavior.pillagerPatrols.disable) return 0; // Paper - Add option to disable pillager patrols -+ if (world.paperConfig().entities.behavior.pillagerPatrols.disable || world.paperConfig().entities.behavior.pillagerPatrols.spawnChance == 0) return 0; // Paper - Add option to disable pillager patrols & Pillager patrol spawn settings and per player options - if (!spawnMonsters) { - return 0; - } else if (!world.getGameRules().getBoolean(GameRules.RULE_DO_PATROL_SPAWNING)) { -@@ -32,23 +32,51 @@ public class PatrolSpawner implements CustomSpawner { - } else { - RandomSource randomsource = world.random; - -- --this.nextTick; -- if (this.nextTick > 0) { -+ // Paper start - Pillager patrol spawn settings and per player options -+ // Random player selection moved up for per player spawning and configuration -+ int j = world.players().size(); -+ if (j < 1) { - return 0; -+ } -+ -+ net.minecraft.server.level.ServerPlayer entityhuman = world.players().get(randomsource.nextInt(j)); -+ if (entityhuman.isSpectator()) { -+ return 0; -+ } -+ -+ int patrolSpawnDelay; -+ if (world.paperConfig().entities.behavior.pillagerPatrols.spawnDelay.perPlayer) { -+ --entityhuman.patrolSpawnDelay; -+ patrolSpawnDelay = entityhuman.patrolSpawnDelay; - } else { -- this.nextTick += 12000 + randomsource.nextInt(1200); -- long i = world.getDayTime() / 24000L; -+ this.nextTick--; -+ patrolSpawnDelay = this.nextTick; -+ } -+ -+ if (patrolSpawnDelay > 0) { -+ return 0; -+ } else { -+ long days; -+ if (world.paperConfig().entities.behavior.pillagerPatrols.start.perPlayer) { -+ days = entityhuman.getStats().getValue(net.minecraft.stats.Stats.CUSTOM.get(net.minecraft.stats.Stats.PLAY_TIME)) / 24000L; // PLAY_ONE_MINUTE is actually counting in ticks, a misnomer by Mojang -+ } else { -+ days = world.getDayTime() / 24000L; -+ } -+ if (world.paperConfig().entities.behavior.pillagerPatrols.spawnDelay.perPlayer) { -+ entityhuman.patrolSpawnDelay += world.paperConfig().entities.behavior.pillagerPatrols.spawnDelay.ticks + randomsource.nextInt(1200); -+ } else { -+ this.nextTick += world.paperConfig().entities.behavior.pillagerPatrols.spawnDelay.ticks + randomsource.nextInt(1200); -+ } - -- if (i >= 5L && world.isDay()) { -- if (randomsource.nextInt(5) != 0) { -+ if (days >= world.paperConfig().entities.behavior.pillagerPatrols.start.day && world.isDay()) { -+ if (randomsource.nextDouble() >= world.paperConfig().entities.behavior.pillagerPatrols.spawnChance) { -+ // Paper end - Pillager patrol spawn settings and per player options - return 0; - } else { -- int j = world.players().size(); - - if (j < 1) { - return 0; - } else { -- Player entityhuman = (Player) world.players().get(randomsource.nextInt(j)); - - if (entityhuman.isSpectator()) { - return 0; diff --git a/patches/server/0328-Prevent-Double-PlayerChunkMap-adds-crashing-server.patch b/patches/server/0328-Prevent-Double-PlayerChunkMap-adds-crashing-server.patch new file mode 100644 index 000000000000..f3de36db4bd6 --- /dev/null +++ b/patches/server/0328-Prevent-Double-PlayerChunkMap-adds-crashing-server.patch @@ -0,0 +1,47 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Aikar +Date: Thu, 2 Apr 2020 01:42:39 -0400 +Subject: [PATCH] Prevent Double PlayerChunkMap adds crashing server + +Suspected case would be around the technique used in .stopRiding +Stack will identify any causer of this and warn instead of crashing. + +diff --git a/src/main/java/net/minecraft/server/level/ChunkMap.java b/src/main/java/net/minecraft/server/level/ChunkMap.java +index 5f85d7f7ec57fc1b0375e62a8e8e3e8783f34193..396310a51480cf0d1ea4c0959d3f8e4ed77b99e3 100644 +--- a/src/main/java/net/minecraft/server/level/ChunkMap.java ++++ b/src/main/java/net/minecraft/server/level/ChunkMap.java +@@ -1298,6 +1298,13 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider + + public void addEntity(Entity entity) { + org.spigotmc.AsyncCatcher.catchOp("entity track"); // Spigot ++ // Paper start - ignore and warn about illegal addEntity calls instead of crashing server ++ if (!entity.valid || entity.level() != this.level || this.entityMap.containsKey(entity.getId())) { ++ LOGGER.error("Illegal ChunkMap::addEntity for world " + this.level.getWorld().getName() ++ + ": " + entity + (this.entityMap.containsKey(entity.getId()) ? " ALREADY CONTAINED (This would have crashed your server)" : ""), new Throwable()); ++ return; ++ } ++ // Paper end - ignore and warn about illegal addEntity calls instead of crashing server + if (!(entity instanceof EnderDragonPart)) { + EntityType entitytypes = entity.getType(); + int i = entitytypes.clientTrackingRange() * 16; +diff --git a/src/main/java/net/minecraft/server/level/ServerLevel.java b/src/main/java/net/minecraft/server/level/ServerLevel.java +index ecb9f6f0c9b24313b9e55b3a4c4f4bba40bc8fb3..0995a3a274df988a5c63c813de8213019a7c47c4 100644 +--- a/src/main/java/net/minecraft/server/level/ServerLevel.java ++++ b/src/main/java/net/minecraft/server/level/ServerLevel.java +@@ -2202,7 +2202,7 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe + + public void onTrackingStart(Entity entity) { + org.spigotmc.AsyncCatcher.catchOp("entity register"); // Spigot +- ServerLevel.this.getChunkSource().addEntity(entity); ++ // ServerLevel.this.getChunkSource().addEntity(entity); // Paper - ignore and warn about illegal addEntity calls instead of crashing server; moved down below valid=true + if (entity instanceof ServerPlayer entityplayer) { + ServerLevel.this.players.add(entityplayer); + ServerLevel.this.updateSleepingPlayerList(); +@@ -2232,6 +2232,7 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe + entity.updateDynamicGameEventListener(DynamicGameEventListener::add); + entity.inWorld = true; // CraftBukkit - Mark entity as in world + entity.valid = true; // CraftBukkit ++ ServerLevel.this.getChunkSource().addEntity(entity); // Paper - ignore and warn about illegal addEntity calls instead of crashing server + // Paper start - Entity origin API + if (entity.getOriginVector() == null) { + entity.setOrigin(entity.getBukkitEntity().getLocation()); diff --git a/patches/server/0329-Don-t-tick-dead-players.patch b/patches/server/0329-Don-t-tick-dead-players.patch new file mode 100644 index 000000000000..511d5ca03749 --- /dev/null +++ b/patches/server/0329-Don-t-tick-dead-players.patch @@ -0,0 +1,21 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Aikar +Date: Thu, 2 Apr 2020 17:16:48 -0400 +Subject: [PATCH] Don't tick dead players + +Causes sync chunk loads and who knows what all else. +This is safe because Spectators are skipped in unloaded chunks too in vanilla. + +diff --git a/src/main/java/net/minecraft/server/level/ServerPlayer.java b/src/main/java/net/minecraft/server/level/ServerPlayer.java +index ea8279637a2af4d18c9decbf3342b058edeffd73..965ca28a8877f5e541741c45bace7075d15a77d7 100644 +--- a/src/main/java/net/minecraft/server/level/ServerPlayer.java ++++ b/src/main/java/net/minecraft/server/level/ServerPlayer.java +@@ -993,7 +993,7 @@ public class ServerPlayer extends net.minecraft.world.entity.player.Player { + + public void doTick() { + try { +- if (!this.isSpectator() || !this.touchingUnloadedChunk()) { ++ if (valid && !this.isSpectator() || !this.touchingUnloadedChunk()) { // Paper - don't tick dead players that are not in the world currently (pending respawn) + super.tick(); + } + diff --git a/patches/server/0329-Remote-Connections-shouldn-t-hold-up-shutdown.patch b/patches/server/0329-Remote-Connections-shouldn-t-hold-up-shutdown.patch deleted file mode 100644 index fb2f7c6706c8..000000000000 --- a/patches/server/0329-Remote-Connections-shouldn-t-hold-up-shutdown.patch +++ /dev/null @@ -1,44 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Aikar -Date: Tue, 31 Mar 2020 03:50:42 -0400 -Subject: [PATCH] Remote Connections shouldn't hold up shutdown - -Bugs in the connection logic appears to leave stale connections even, preventing shutdown - -diff --git a/src/main/java/net/minecraft/server/dedicated/DedicatedServer.java b/src/main/java/net/minecraft/server/dedicated/DedicatedServer.java -index b15cee6f21ff300b596922a8eed35a5f8a89fe22..d6dc8c983d26ce89f17a990be4284fdc78ad164b 100644 ---- a/src/main/java/net/minecraft/server/dedicated/DedicatedServer.java -+++ b/src/main/java/net/minecraft/server/dedicated/DedicatedServer.java -@@ -416,11 +416,11 @@ public class DedicatedServer extends MinecraftServer implements ServerInterface - } - - if (this.rconThread != null) { -- this.rconThread.stop(); -+ this.rconThread.stopNonBlocking(); // Paper - don't wait for remote connections - } - - if (this.queryThreadGs4 != null) { -- this.queryThreadGs4.stop(); -+ // this.remoteStatusListener.stop(); // Paper - don't wait for remote connections - } - - System.exit(0); // CraftBukkit -diff --git a/src/main/java/net/minecraft/server/rcon/thread/RconThread.java b/src/main/java/net/minecraft/server/rcon/thread/RconThread.java -index 594fbb033b63b8c9fb8752b1fcc78f8e9f7a2a83..c12d7db2b048a327c0e8f398848cd3a9bce0ebce 100644 ---- a/src/main/java/net/minecraft/server/rcon/thread/RconThread.java -+++ b/src/main/java/net/minecraft/server/rcon/thread/RconThread.java -@@ -104,6 +104,14 @@ public class RconThread extends GenericThread { - - this.clients.clear(); - } -+ // Paper start - don't wait for remote connections -+ public void stopNonBlocking() { -+ this.running = false; -+ for (RconClient client : this.clients) { -+ client.running = false; -+ } -+ } -+ // Paper end - don't wait for remote connections - - private void closeSocket(ServerSocket socket) { - LOGGER.debug("closeSocket: {}", socket); diff --git a/patches/server/0330-Dead-Player-s-shouldn-t-be-able-to-move.patch b/patches/server/0330-Dead-Player-s-shouldn-t-be-able-to-move.patch new file mode 100644 index 000000000000..068a5a06f9b9 --- /dev/null +++ b/patches/server/0330-Dead-Player-s-shouldn-t-be-able-to-move.patch @@ -0,0 +1,21 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Aikar +Date: Thu, 2 Apr 2020 19:31:16 -0400 +Subject: [PATCH] Dead Player's shouldn't be able to move + +This fixes a lot of game state issues where packets were delayed for processing +due to 1.15's new queue but processed while dead. + +diff --git a/src/main/java/net/minecraft/world/entity/player/Player.java b/src/main/java/net/minecraft/world/entity/player/Player.java +index 4d8929a1f5390af10fbde1dcc13c0136b0a3a745..917ac21794f1aabc6e95ab2fff2ea7547b9778a8 100644 +--- a/src/main/java/net/minecraft/world/entity/player/Player.java ++++ b/src/main/java/net/minecraft/world/entity/player/Player.java +@@ -1143,7 +1143,7 @@ public abstract class Player extends LivingEntity { + + @Override + protected boolean isImmobile() { +- return super.isImmobile() || this.isSleeping(); ++ return super.isImmobile() || this.isSleeping() || this.isRemoved() || !valid; // Paper - player's who are dead or not in a world shouldn't move... + } + + @Override diff --git a/patches/server/0330-Do-not-allow-bees-to-load-chunks-for-beehives.patch b/patches/server/0330-Do-not-allow-bees-to-load-chunks-for-beehives.patch deleted file mode 100644 index f361af6e505e..000000000000 --- a/patches/server/0330-Do-not-allow-bees-to-load-chunks-for-beehives.patch +++ /dev/null @@ -1,58 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: chickeneer -Date: Tue, 17 Mar 2020 14:18:50 -0500 -Subject: [PATCH] Do not allow bees to load chunks for beehives - - -diff --git a/src/main/java/net/minecraft/world/entity/animal/Bee.java b/src/main/java/net/minecraft/world/entity/animal/Bee.java -index 4134ee48909110f8c338f5d553d4cc1e9e31aaba..615b57fac9def18d9dcaefcfe397c74c11cac627 100644 ---- a/src/main/java/net/minecraft/world/entity/animal/Bee.java -+++ b/src/main/java/net/minecraft/world/entity/animal/Bee.java -@@ -421,6 +421,7 @@ public class Bee extends Animal implements NeutralMob, FlyingAnimal { - if (this.hivePos == null) { - return false; - } else { -+ if (!this.level().isLoadedAndInBounds(this.hivePos)) return false; // Paper - Do not allow bees to load chunks for beehives - BlockEntity tileentity = this.level().getBlockEntity(this.hivePos); - - return tileentity instanceof BeehiveBlockEntity && ((BeehiveBlockEntity) tileentity).isFireNearby(); -@@ -454,6 +455,7 @@ public class Bee extends Animal implements NeutralMob, FlyingAnimal { - } - - private boolean doesHiveHaveSpace(BlockPos pos) { -+ if (!this.level().isLoadedAndInBounds(pos)) return false; // Paper - Do not allow bees to load chunks for beehives - BlockEntity tileentity = this.level().getBlockEntity(pos); - - return tileentity instanceof BeehiveBlockEntity ? !((BeehiveBlockEntity) tileentity).isFull() : false; -@@ -924,6 +926,7 @@ public class Bee extends Animal implements NeutralMob, FlyingAnimal { - @Override - public boolean canBeeUse() { - if (Bee.this.hasHive() && Bee.this.wantsToEnterHive() && Bee.this.hivePos.closerToCenterThan(Bee.this.position(), 2.0D)) { -+ if (!Bee.this.level().isLoadedAndInBounds(Bee.this.hivePos)) return false; // Paper - Do not allow bees to load chunks for beehives - BlockEntity tileentity = Bee.this.level().getBlockEntity(Bee.this.hivePos); - - if (tileentity instanceof BeehiveBlockEntity) { -@@ -947,6 +950,7 @@ public class Bee extends Animal implements NeutralMob, FlyingAnimal { - - @Override - public void start() { -+ if (!Bee.this.level().isLoadedAndInBounds(Bee.this.hivePos)) return; // Paper - Do not allow bees to load chunks for beehives - BlockEntity tileentity = Bee.this.level().getBlockEntity(Bee.this.hivePos); - - if (tileentity instanceof BeehiveBlockEntity tileentitybeehive) { -diff --git a/src/main/java/net/minecraft/world/entity/monster/Vex.java b/src/main/java/net/minecraft/world/entity/monster/Vex.java -index d6f73c719d58970c6d13340f78c3303916b46546..2985296a9a034e535157f55e322fc8c107827752 100644 ---- a/src/main/java/net/minecraft/world/entity/monster/Vex.java -+++ b/src/main/java/net/minecraft/world/entity/monster/Vex.java -@@ -356,7 +356,10 @@ public class Vex extends Monster implements TraceableEntity { - for (int i = 0; i < 3; ++i) { - BlockPos blockposition1 = blockposition.offset(Vex.this.random.nextInt(15) - 7, Vex.this.random.nextInt(11) - 5, Vex.this.random.nextInt(15) - 7); - -- if (Vex.this.level().isEmptyBlock(blockposition1)) { -+ // Paper start - Don't load chunks -+ final net.minecraft.world.level.block.state.BlockState blockState = Vex.this.level().getBlockStateIfLoaded(blockposition1); -+ if (blockState != null && blockState.isAir()) { -+ // Paper end - Don't load chunks - Vex.this.moveControl.setWantedPosition((double) blockposition1.getX() + 0.5D, (double) blockposition1.getY() + 0.5D, (double) blockposition1.getZ() + 0.5D, 0.25D); - if (Vex.this.getTarget() == null) { - Vex.this.getLookControl().setLookAt((double) blockposition1.getX() + 0.5D, (double) blockposition1.getY() + 0.5D, (double) blockposition1.getZ() + 0.5D, 180.0F, 20.0F); diff --git a/patches/server/0331-Don-t-move-existing-players-to-world-spawn.patch b/patches/server/0331-Don-t-move-existing-players-to-world-spawn.patch new file mode 100644 index 000000000000..4c5196324898 --- /dev/null +++ b/patches/server/0331-Don-t-move-existing-players-to-world-spawn.patch @@ -0,0 +1,48 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Aikar +Date: Thu, 9 Apr 2020 21:20:33 -0400 +Subject: [PATCH] Don't move existing players to world spawn + +This can cause a nasty server lag the spawn chunks are not kept loaded +or they aren't finished loading yet, or if the world spawn radius is +larger than the keep loaded range. + +By skipping this, we avoid potential for a large spike on server start. + +== AT == +public net.minecraft.server.level.ServerPlayer fudgeSpawnLocation(Lnet/minecraft/server/level/ServerLevel;)V + +diff --git a/src/main/java/net/minecraft/server/level/ServerPlayer.java b/src/main/java/net/minecraft/server/level/ServerPlayer.java +index 965ca28a8877f5e541741c45bace7075d15a77d7..adbc8e74f0b454403bc682de11bd0342e9bb6c2c 100644 +--- a/src/main/java/net/minecraft/server/level/ServerPlayer.java ++++ b/src/main/java/net/minecraft/server/level/ServerPlayer.java +@@ -419,7 +419,7 @@ public class ServerPlayer extends net.minecraft.world.entity.player.Player { + this.server = server; + this.stats = server.getPlayerList().getPlayerStats(this); + this.advancements = server.getPlayerList().getPlayerAdvancements(this); +- this.moveTo(this.adjustSpawnLocation(world, world.getSharedSpawnPos()).getBottomCenter(), 0.0F, 0.0F); ++ // this.moveTo(this.adjustSpawnLocation(world, world.getSharedSpawnPos()).getBottomCenter(), 0.0F, 0.0F); // Paper - Don't move existing players to world spawn + this.updateOptions(clientOptions); + this.object = null; + +@@ -856,7 +856,7 @@ public class ServerPlayer extends net.minecraft.world.entity.player.Player { + position = Vec3.atCenterOf(world.getSharedSpawnPos()); + } + this.setLevel(world); +- this.setPos(position); ++ this.setPosRaw(position.x(), position.y(), position.z()); // Paper - don't register to chunks yet + } + this.gameMode.setLevel((ServerLevel) world); + } +diff --git a/src/main/java/net/minecraft/server/players/PlayerList.java b/src/main/java/net/minecraft/server/players/PlayerList.java +index 2cfc2213e3036585dc4723eecf747e1c37d53b72..ad4f5a4e00b9b517841acd0b4ff5088e45451bf4 100644 +--- a/src/main/java/net/minecraft/server/players/PlayerList.java ++++ b/src/main/java/net/minecraft/server/players/PlayerList.java +@@ -226,6 +226,7 @@ public abstract class PlayerList { + // Paper start - Entity#getEntitySpawnReason + if (optional.isEmpty()) { + player.spawnReason = org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason.DEFAULT; // set Player SpawnReason to DEFAULT on first login ++ player.moveTo(player.adjustSpawnLocation(worldserver1, worldserver1.getSharedSpawnPos()).getBottomCenter(), 0.0F, 0.0F); + } + // Paper end - Entity#getEntitySpawnReason + player.setServerLevel(worldserver1); diff --git a/patches/server/0331-Prevent-Double-PlayerChunkMap-adds-crashing-server.patch b/patches/server/0331-Prevent-Double-PlayerChunkMap-adds-crashing-server.patch deleted file mode 100644 index e7c3500d183d..000000000000 --- a/patches/server/0331-Prevent-Double-PlayerChunkMap-adds-crashing-server.patch +++ /dev/null @@ -1,47 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Aikar -Date: Thu, 2 Apr 2020 01:42:39 -0400 -Subject: [PATCH] Prevent Double PlayerChunkMap adds crashing server - -Suspected case would be around the technique used in .stopRiding -Stack will identify any causer of this and warn instead of crashing. - -diff --git a/src/main/java/net/minecraft/server/level/ChunkMap.java b/src/main/java/net/minecraft/server/level/ChunkMap.java -index ee54706b36bd227edacea2a1b6099009bd652039..8206ec366b429858d9582e437781191e5aa0fa02 100644 ---- a/src/main/java/net/minecraft/server/level/ChunkMap.java -+++ b/src/main/java/net/minecraft/server/level/ChunkMap.java -@@ -1241,6 +1241,13 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider - - public void addEntity(Entity entity) { - org.spigotmc.AsyncCatcher.catchOp("entity track"); // Spigot -+ // Paper start - ignore and warn about illegal addEntity calls instead of crashing server -+ if (!entity.valid || entity.level() != this.level || this.entityMap.containsKey(entity.getId())) { -+ LOGGER.error("Illegal ChunkMap::addEntity for world " + this.level.getWorld().getName() -+ + ": " + entity + (this.entityMap.containsKey(entity.getId()) ? " ALREADY CONTAINED (This would have crashed your server)" : ""), new Throwable()); -+ return; -+ } -+ // Paper end - ignore and warn about illegal addEntity calls instead of crashing server - if (!(entity instanceof EnderDragonPart)) { - EntityType entitytypes = entity.getType(); - int i = entitytypes.clientTrackingRange() * 16; -diff --git a/src/main/java/net/minecraft/server/level/ServerLevel.java b/src/main/java/net/minecraft/server/level/ServerLevel.java -index b465f05d78e79ffbf70114b18204d85d32761c67..5b89d834a7c01530807e61ea25af2b01f004ce86 100644 ---- a/src/main/java/net/minecraft/server/level/ServerLevel.java -+++ b/src/main/java/net/minecraft/server/level/ServerLevel.java -@@ -2151,7 +2151,7 @@ public class ServerLevel extends Level implements WorldGenLevel { - - public void onTrackingStart(Entity entity) { - org.spigotmc.AsyncCatcher.catchOp("entity register"); // Spigot -- ServerLevel.this.getChunkSource().addEntity(entity); -+ // ServerLevel.this.getChunkSource().addEntity(entity); // Paper - ignore and warn about illegal addEntity calls instead of crashing server; moved down below valid=true - if (entity instanceof ServerPlayer entityplayer) { - ServerLevel.this.players.add(entityplayer); - ServerLevel.this.updateSleepingPlayerList(); -@@ -2181,6 +2181,7 @@ public class ServerLevel extends Level implements WorldGenLevel { - entity.updateDynamicGameEventListener(DynamicGameEventListener::add); - entity.inWorld = true; // CraftBukkit - Mark entity as in world - entity.valid = true; // CraftBukkit -+ ServerLevel.this.getChunkSource().addEntity(entity); // Paper - ignore and warn about illegal addEntity calls instead of crashing server - // Paper start - Entity origin API - if (entity.getOriginVector() == null) { - entity.setOrigin(entity.getBukkitEntity().getLocation()); diff --git a/patches/server/0332-Don-t-tick-dead-players.patch b/patches/server/0332-Don-t-tick-dead-players.patch deleted file mode 100644 index 0633c8502c6d..000000000000 --- a/patches/server/0332-Don-t-tick-dead-players.patch +++ /dev/null @@ -1,21 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Aikar -Date: Thu, 2 Apr 2020 17:16:48 -0400 -Subject: [PATCH] Don't tick dead players - -Causes sync chunk loads and who knows what all else. -This is safe because Spectators are skipped in unloaded chunks too in vanilla. - -diff --git a/src/main/java/net/minecraft/server/level/ServerPlayer.java b/src/main/java/net/minecraft/server/level/ServerPlayer.java -index d4bda65d10bee880a341196364db63523f36e380..47982f87ce93658c73ee3d7a5f7dd680e2f9aa36 100644 ---- a/src/main/java/net/minecraft/server/level/ServerPlayer.java -+++ b/src/main/java/net/minecraft/server/level/ServerPlayer.java -@@ -765,7 +765,7 @@ public class ServerPlayer extends net.minecraft.world.entity.player.Player { - - public void doTick() { - try { -- if (!this.isSpectator() || !this.touchingUnloadedChunk()) { -+ if (valid && !this.isSpectator() || !this.touchingUnloadedChunk()) { // Paper - don't tick dead players that are not in the world currently (pending respawn) - super.tick(); - } - diff --git a/patches/server/0332-Optimize-Pathfinding.patch b/patches/server/0332-Optimize-Pathfinding.patch new file mode 100644 index 000000000000..76c7c6e42333 --- /dev/null +++ b/patches/server/0332-Optimize-Pathfinding.patch @@ -0,0 +1,47 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Aikar +Date: Thu, 3 Mar 2016 02:02:07 -0600 +Subject: [PATCH] Optimize Pathfinding + +Prevents pathfinding from spamming failures for things such as +arrow attacks. + +diff --git a/src/main/java/net/minecraft/world/entity/ai/navigation/PathNavigation.java b/src/main/java/net/minecraft/world/entity/ai/navigation/PathNavigation.java +index 436812c3bfe53358b4d76bb72d777d6661bb6d60..48c0de870a5bbf647309e69361dfb10ab56c65ab 100644 +--- a/src/main/java/net/minecraft/world/entity/ai/navigation/PathNavigation.java ++++ b/src/main/java/net/minecraft/world/entity/ai/navigation/PathNavigation.java +@@ -209,13 +209,33 @@ public abstract class PathNavigation { + return this.moveTo(this.createPath(x, y, z, 1), speed); + } + ++ // Paper start - Perf: Optimise pathfinding ++ private int lastFailure = 0; ++ private int pathfindFailures = 0; ++ // Paper end - Perf: Optimise pathfinding ++ + public boolean moveTo(double x, double y, double z, int distance, double speed) { + return this.moveTo(this.createPath(x, y, z, distance), speed); + } + + public boolean moveTo(Entity entity, double speed) { ++ // Paper start - Perf: Optimise pathfinding ++ if (this.pathfindFailures > 10 && this.path == null && net.minecraft.server.MinecraftServer.currentTick < this.lastFailure + 40) { ++ return false; ++ } ++ // Paper end - Perf: Optimise pathfinding + Path path = this.createPath(entity, 1); +- return path != null && this.moveTo(path, speed); ++ // Paper start - Perf: Optimise pathfinding ++ if (path != null && this.moveTo(path, speed)) { ++ this.lastFailure = 0; ++ this.pathfindFailures = 0; ++ return true; ++ } else { ++ this.pathfindFailures++; ++ this.lastFailure = net.minecraft.server.MinecraftServer.currentTick; ++ return false; ++ } ++ // Paper end - Perf: Optimise pathfinding + } + + public boolean moveTo(@Nullable Path path, double speed) { diff --git a/patches/server/0333-Dead-Player-s-shouldn-t-be-able-to-move.patch b/patches/server/0333-Dead-Player-s-shouldn-t-be-able-to-move.patch deleted file mode 100644 index d10a5a50c4a5..000000000000 --- a/patches/server/0333-Dead-Player-s-shouldn-t-be-able-to-move.patch +++ /dev/null @@ -1,21 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Aikar -Date: Thu, 2 Apr 2020 19:31:16 -0400 -Subject: [PATCH] Dead Player's shouldn't be able to move - -This fixes a lot of game state issues where packets were delayed for processing -due to 1.15's new queue but processed while dead. - -diff --git a/src/main/java/net/minecraft/world/entity/player/Player.java b/src/main/java/net/minecraft/world/entity/player/Player.java -index fa906334a1c569748d3f2dc073ec03a85bd09d3b..cb89b020d93ac838843ec2cbad562326a1e4257b 100644 ---- a/src/main/java/net/minecraft/world/entity/player/Player.java -+++ b/src/main/java/net/minecraft/world/entity/player/Player.java -@@ -1173,7 +1173,7 @@ public abstract class Player extends LivingEntity { - - @Override - protected boolean isImmobile() { -- return super.isImmobile() || this.isSleeping(); -+ return super.isImmobile() || this.isSleeping() || this.isRemoved() || !valid; // Paper - player's who are dead or not in a world shouldn't move... - } - - @Override diff --git a/patches/server/0336-Reduce-Either-Optional-allocation.patch b/patches/server/0333-Reduce-Either-Optional-allocation.patch similarity index 100% rename from patches/server/0336-Reduce-Either-Optional-allocation.patch rename to patches/server/0333-Reduce-Either-Optional-allocation.patch diff --git a/patches/server/0334-Don-t-move-existing-players-to-world-spawn.patch b/patches/server/0334-Don-t-move-existing-players-to-world-spawn.patch deleted file mode 100644 index 4c59557ecb19..000000000000 --- a/patches/server/0334-Don-t-move-existing-players-to-world-spawn.patch +++ /dev/null @@ -1,48 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Aikar -Date: Thu, 9 Apr 2020 21:20:33 -0400 -Subject: [PATCH] Don't move existing players to world spawn - -This can cause a nasty server lag the spawn chunks are not kept loaded -or they aren't finished loading yet, or if the world spawn radius is -larger than the keep loaded range. - -By skipping this, we avoid potential for a large spike on server start. - -== AT == -public net.minecraft.server.level.ServerPlayer fudgeSpawnLocation(Lnet/minecraft/server/level/ServerLevel;)V - -diff --git a/src/main/java/net/minecraft/server/level/ServerPlayer.java b/src/main/java/net/minecraft/server/level/ServerPlayer.java -index 47982f87ce93658c73ee3d7a5f7dd680e2f9aa36..f8d4bd18f98ed914e116d0cc4a80140e4e8d759f 100644 ---- a/src/main/java/net/minecraft/server/level/ServerPlayer.java -+++ b/src/main/java/net/minecraft/server/level/ServerPlayer.java -@@ -358,7 +358,7 @@ public class ServerPlayer extends net.minecraft.world.entity.player.Player { - this.server = server; - this.stats = server.getPlayerList().getPlayerStats(this); - this.advancements = server.getPlayerList().getPlayerAdvancements(this); -- this.moveTo(this.adjustSpawnLocation(world, world.getSharedSpawnPos()).getBottomCenter(), 0.0F, 0.0F); -+ // this.moveTo(this.adjustSpawnLocation(world, world.getSharedSpawnPos()).getBottomCenter(), 0.0F, 0.0F); // Paper - Don't move existing players to world spawn - this.updateOptions(clientOptions); - this.object = null; - -@@ -628,7 +628,7 @@ public class ServerPlayer extends net.minecraft.world.entity.player.Player { - position = Vec3.atCenterOf(world.getSharedSpawnPos()); - } - this.setLevel(world); -- this.setPos(position); -+ this.setPosRaw(position.x(), position.y(), position.z()); // Paper - don't register to chunks yet - } - this.gameMode.setLevel((ServerLevel) world); - } -diff --git a/src/main/java/net/minecraft/server/players/PlayerList.java b/src/main/java/net/minecraft/server/players/PlayerList.java -index 8cd80ea83ddcfd5052c8d8c19d3edb42538d1e15..2cbdcdf0349e7efa797802d0d339d158153690af 100644 ---- a/src/main/java/net/minecraft/server/players/PlayerList.java -+++ b/src/main/java/net/minecraft/server/players/PlayerList.java -@@ -226,6 +226,7 @@ public abstract class PlayerList { - // Paper start - Entity#getEntitySpawnReason - if (optional.isEmpty()) { - player.spawnReason = org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason.DEFAULT; // set Player SpawnReason to DEFAULT on first login -+ player.moveTo(player.adjustSpawnLocation(worldserver1, worldserver1.getSharedSpawnPos()).getBottomCenter(), 0.0F, 0.0F); - } - // Paper end - Entity#getEntitySpawnReason - player.setServerLevel(worldserver1); diff --git a/patches/server/0337-Reduce-memory-footprint-of-CompoundTag.patch b/patches/server/0334-Reduce-memory-footprint-of-CompoundTag.patch similarity index 100% rename from patches/server/0337-Reduce-memory-footprint-of-CompoundTag.patch rename to patches/server/0334-Reduce-memory-footprint-of-CompoundTag.patch diff --git a/patches/server/0335-Optimize-Pathfinding.patch b/patches/server/0335-Optimize-Pathfinding.patch deleted file mode 100644 index 281988ce371e..000000000000 --- a/patches/server/0335-Optimize-Pathfinding.patch +++ /dev/null @@ -1,47 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Aikar -Date: Thu, 3 Mar 2016 02:02:07 -0600 -Subject: [PATCH] Optimize Pathfinding - -Prevents pathfinding from spamming failures for things such as -arrow attacks. - -diff --git a/src/main/java/net/minecraft/world/entity/ai/navigation/PathNavigation.java b/src/main/java/net/minecraft/world/entity/ai/navigation/PathNavigation.java -index 188904c9f0f81db1d63eec953d6746f2dc23dc81..2e9991e6b3c05584002744a2ee2579b1dba218b2 100644 ---- a/src/main/java/net/minecraft/world/entity/ai/navigation/PathNavigation.java -+++ b/src/main/java/net/minecraft/world/entity/ai/navigation/PathNavigation.java -@@ -192,13 +192,33 @@ public abstract class PathNavigation { - return this.moveTo(this.createPath(x, y, z, 1), speed); - } - -+ // Paper start - Perf: Optimise pathfinding -+ private int lastFailure = 0; -+ private int pathfindFailures = 0; -+ // Paper end - Perf: Optimise pathfinding -+ - public boolean moveTo(double x, double y, double z, int distance, double speed) { - return this.moveTo(this.createPath(x, y, z, distance), speed); - } - - public boolean moveTo(Entity entity, double speed) { -+ // Paper start - Perf: Optimise pathfinding -+ if (this.pathfindFailures > 10 && this.path == null && net.minecraft.server.MinecraftServer.currentTick < this.lastFailure + 40) { -+ return false; -+ } -+ // Paper end - Perf: Optimise pathfinding - Path path = this.createPath(entity, 1); -- return path != null && this.moveTo(path, speed); -+ // Paper start - Perf: Optimise pathfinding -+ if (path != null && this.moveTo(path, speed)) { -+ this.lastFailure = 0; -+ this.pathfindFailures = 0; -+ return true; -+ } else { -+ this.pathfindFailures++; -+ this.lastFailure = net.minecraft.server.MinecraftServer.currentTick; -+ return false; -+ } -+ // Paper end - Perf: Optimise pathfinding - } - - public boolean moveTo(@Nullable Path path, double speed) { diff --git a/patches/server/0335-Prevent-opening-inventories-when-frozen.patch b/patches/server/0335-Prevent-opening-inventories-when-frozen.patch new file mode 100644 index 000000000000..de2065861e64 --- /dev/null +++ b/patches/server/0335-Prevent-opening-inventories-when-frozen.patch @@ -0,0 +1,50 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Shane Freeder +Date: Mon, 13 Apr 2020 07:31:44 +0100 +Subject: [PATCH] Prevent opening inventories when frozen + + +diff --git a/src/main/java/net/minecraft/server/level/ServerPlayer.java b/src/main/java/net/minecraft/server/level/ServerPlayer.java +index adbc8e74f0b454403bc682de11bd0342e9bb6c2c..363175d3325c012f31ba84060bb0bfac694f6ab8 100644 +--- a/src/main/java/net/minecraft/server/level/ServerPlayer.java ++++ b/src/main/java/net/minecraft/server/level/ServerPlayer.java +@@ -938,7 +938,7 @@ public class ServerPlayer extends net.minecraft.world.entity.player.Player { + containerUpdateDelay = this.level().paperConfig().tickRates.containerUpdate; + } + // Paper end - Configurable container update tick rate +- if (!this.level().isClientSide && !this.containerMenu.stillValid(this)) { ++ if (!this.level().isClientSide && this.containerMenu != this.inventoryMenu && (this.isImmobile() || !this.containerMenu.stillValid(this))) { // Paper - Prevent opening inventories when frozen + this.closeContainer(org.bukkit.event.inventory.InventoryCloseEvent.Reason.CANT_USE); // Paper - Inventory close reason + this.containerMenu = this.inventoryMenu; + } +@@ -1903,7 +1903,7 @@ public class ServerPlayer extends net.minecraft.world.entity.player.Player { + } else { + // CraftBukkit start + this.containerMenu = container; +- this.connection.send(new ClientboundOpenScreenPacket(container.containerId, container.getType(), container.getTitle())); ++ if (!this.isImmobile()) this.connection.send(new ClientboundOpenScreenPacket(container.containerId, container.getType(), container.getTitle())); // Paper - Prevent opening inventories when frozen + // CraftBukkit end + this.initMenu(container); + return OptionalInt.of(this.containerCounter); +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftHumanEntity.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftHumanEntity.java +index 5ff159be1a6dfb4b1a5b9aa1e435d294f205215e..6ec6b80d224e2402054afb85b78c793942a58bbf 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftHumanEntity.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftHumanEntity.java +@@ -335,7 +335,7 @@ public class CraftHumanEntity extends CraftLivingEntity implements HumanEntity { + if (adventure$title == null) adventure$title = net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer.legacySection().deserialize(container.getBukkitView().getTitle()); // Paper + + //player.connection.send(new ClientboundOpenScreenPacket(container.containerId, windowType, CraftChatMessage.fromString(title)[0])); // Paper - comment +- player.connection.send(new ClientboundOpenScreenPacket(container.containerId, windowType, io.papermc.paper.adventure.PaperAdventure.asVanilla(adventure$title))); // Paper ++ if (!player.isImmobile()) player.connection.send(new ClientboundOpenScreenPacket(container.containerId, windowType, io.papermc.paper.adventure.PaperAdventure.asVanilla(adventure$title))); // Paper - Prevent opening inventories when frozen + player.containerMenu = container; + player.initMenu(container); + } +@@ -410,7 +410,7 @@ public class CraftHumanEntity extends CraftLivingEntity implements HumanEntity { + net.kyori.adventure.text.Component adventure$title = inventory.title(); // Paper + if (adventure$title == null) adventure$title = net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer.legacySection().deserialize(inventory.getTitle()); // Paper + //player.connection.send(new ClientboundOpenScreenPacket(container.containerId, windowType, CraftChatMessage.fromString(title)[0])); // Paper - comment +- player.connection.send(new ClientboundOpenScreenPacket(container.containerId, windowType, io.papermc.paper.adventure.PaperAdventure.asVanilla(adventure$title))); // Paper ++ if (!player.isImmobile()) player.connection.send(new ClientboundOpenScreenPacket(container.containerId, windowType, io.papermc.paper.adventure.PaperAdventure.asVanilla(adventure$title))); // Paper - Prevent opening inventories when frozen + player.containerMenu = container; + player.initMenu(container); + } diff --git a/patches/server/0336-Don-t-run-entity-collision-code-if-not-needed.patch b/patches/server/0336-Don-t-run-entity-collision-code-if-not-needed.patch new file mode 100644 index 000000000000..543230b8509a --- /dev/null +++ b/patches/server/0336-Don-t-run-entity-collision-code-if-not-needed.patch @@ -0,0 +1,43 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Spottedleaf +Date: Wed, 15 Apr 2020 17:56:07 -0700 +Subject: [PATCH] Don't run entity collision code if not needed + +Will not run if: +Max entity cramming is disabled and the max collisions per entity is less than or equal to 0. +Entity#isPushable() returns false, meaning all entities will not be able to collide with this +entity anyways. +The entity's current team collision rule causes them to NEVER collide. + +Co-authored-by: Owen1212055 <23108066+Owen1212055@users.noreply.github.com> + +diff --git a/src/main/java/net/minecraft/world/entity/LivingEntity.java b/src/main/java/net/minecraft/world/entity/LivingEntity.java +index 724fe482f1711008dc43fef96c4512a18ed54a48..4fb99e3ee14d2e7fe2720e25af1c890004b0c250 100644 +--- a/src/main/java/net/minecraft/world/entity/LivingEntity.java ++++ b/src/main/java/net/minecraft/world/entity/LivingEntity.java +@@ -3681,10 +3681,24 @@ public abstract class LivingEntity extends Entity implements Attackable { + if (!(world instanceof ServerLevel worldserver)) { + this.level().getEntities(EntityTypeTest.forClass(net.minecraft.world.entity.player.Player.class), this.getBoundingBox(), EntitySelector.pushableBy(this)).forEach(this::doPush); + } else { ++ // Paper start - don't run getEntities if we're not going to use its result ++ if (!this.isPushable()) { ++ return; ++ } ++ net.minecraft.world.scores.Team team = this.getTeam(); ++ if (team != null && team.getCollisionRule() == net.minecraft.world.scores.Team.CollisionRule.NEVER) { ++ return; ++ } ++ ++ int i = worldserver.getGameRules().getInt(GameRules.RULE_MAX_ENTITY_CRAMMING); ++ if (i <= 0 && this.level().paperConfig().collisions.maxEntityCollisions <= 0) { ++ return; ++ } ++ // Paper end - don't run getEntities if we're not going to use its result + List list = this.level().getEntities((Entity) this, this.getBoundingBox(), EntitySelector.pushableBy(this)); + + if (!list.isEmpty()) { +- int i = worldserver.getGameRules().getInt(GameRules.RULE_MAX_ENTITY_CRAMMING); ++ // Paper - don't run getEntities if we're not going to use its result; moved up + + if (i > 0 && list.size() > i - 1 && this.random.nextInt(4) == 0) { + int j = 0; diff --git a/patches/server/0337-Implement-Player-Client-Options-API.patch b/patches/server/0337-Implement-Player-Client-Options-API.patch new file mode 100644 index 000000000000..da2d0fcb26f3 --- /dev/null +++ b/patches/server/0337-Implement-Player-Client-Options-API.patch @@ -0,0 +1,194 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: MiniDigger +Date: Mon, 20 Jan 2020 21:38:15 +0100 +Subject: [PATCH] Implement Player Client Options API + +== AT == +public net.minecraft.world.entity.player.Player DATA_PLAYER_MODE_CUSTOMISATION + +diff --git a/src/main/java/com/destroystokyo/paper/PaperSkinParts.java b/src/main/java/com/destroystokyo/paper/PaperSkinParts.java +new file mode 100644 +index 0000000000000000000000000000000000000000..b6f4400df3d8ec7e06a996de54f8cabba57885e1 +--- /dev/null ++++ b/src/main/java/com/destroystokyo/paper/PaperSkinParts.java +@@ -0,0 +1,74 @@ ++package com.destroystokyo.paper; ++ ++import com.google.common.base.Objects; ++ ++import java.util.StringJoiner; ++ ++public class PaperSkinParts implements SkinParts { ++ ++ private final int raw; ++ ++ public PaperSkinParts(int raw) { ++ this.raw = raw; ++ } ++ ++ public boolean hasCapeEnabled() { ++ return (raw & 1) == 1; ++ } ++ ++ public boolean hasJacketEnabled() { ++ return (raw >> 1 & 1) == 1; ++ } ++ ++ public boolean hasLeftSleeveEnabled() { ++ return (raw >> 2 & 1) == 1; ++ } ++ ++ public boolean hasRightSleeveEnabled() { ++ return (raw >> 3 & 1) == 1; ++ } ++ ++ public boolean hasLeftPantsEnabled() { ++ return (raw >> 4 & 1) == 1; ++ } ++ ++ public boolean hasRightPantsEnabled() { ++ return (raw >> 5 & 1) == 1; ++ } ++ ++ public boolean hasHatsEnabled() { ++ return (raw >> 6 & 1) == 1; ++ } ++ ++ @Override ++ public int getRaw() { ++ return raw; ++ } ++ ++ @Override ++ public boolean equals(Object o) { ++ if (this == o) return true; ++ if (o == null || getClass() != o.getClass()) return false; ++ PaperSkinParts that = (PaperSkinParts) o; ++ return raw == that.raw; ++ } ++ ++ @Override ++ public int hashCode() { ++ return Objects.hashCode(raw); ++ } ++ ++ @Override ++ public String toString() { ++ return new StringJoiner(", ", PaperSkinParts.class.getSimpleName() + "[", "]") ++ .add("raw=" + raw) ++ .add("cape=" + hasCapeEnabled()) ++ .add("jacket=" + hasJacketEnabled()) ++ .add("leftSleeve=" + hasLeftSleeveEnabled()) ++ .add("rightSleeve=" + hasRightSleeveEnabled()) ++ .add("leftPants=" + hasLeftPantsEnabled()) ++ .add("rightPants=" + hasRightPantsEnabled()) ++ .add("hats=" + hasHatsEnabled()) ++ .toString(); ++ } ++} +diff --git a/src/main/java/net/minecraft/server/level/ServerPlayer.java b/src/main/java/net/minecraft/server/level/ServerPlayer.java +index 363175d3325c012f31ba84060bb0bfac694f6ab8..9911e231ad021286f2da90057b06874f7b4e3b4d 100644 +--- a/src/main/java/net/minecraft/server/level/ServerPlayer.java ++++ b/src/main/java/net/minecraft/server/level/ServerPlayer.java +@@ -420,7 +420,7 @@ public class ServerPlayer extends net.minecraft.world.entity.player.Player { + this.stats = server.getPlayerList().getPlayerStats(this); + this.advancements = server.getPlayerList().getPlayerAdvancements(this); + // this.moveTo(this.adjustSpawnLocation(world, world.getSharedSpawnPos()).getBottomCenter(), 0.0F, 0.0F); // Paper - Don't move existing players to world spawn +- this.updateOptions(clientOptions); ++ this.updateOptionsNoEvents(clientOptions); // Paper - don't call options events on login + this.object = null; + + // CraftBukkit start +@@ -2404,7 +2404,23 @@ public class ServerPlayer extends net.minecraft.world.entity.player.Player { + } + } + ++ // Paper start - Client option API ++ private java.util.Map, ?> getClientOptionMap(String locale, int viewDistance, com.destroystokyo.paper.ClientOption.ChatVisibility chatVisibility, boolean chatColors, com.destroystokyo.paper.PaperSkinParts skinParts, org.bukkit.inventory.MainHand mainHand, boolean allowsServerListing, boolean textFilteringEnabled) { ++ java.util.Map, Object> map = new java.util.HashMap<>(); ++ map.put(com.destroystokyo.paper.ClientOption.LOCALE, locale); ++ map.put(com.destroystokyo.paper.ClientOption.VIEW_DISTANCE, viewDistance); ++ map.put(com.destroystokyo.paper.ClientOption.CHAT_VISIBILITY, chatVisibility); ++ map.put(com.destroystokyo.paper.ClientOption.CHAT_COLORS_ENABLED, chatColors); ++ map.put(com.destroystokyo.paper.ClientOption.SKIN_PARTS, skinParts); ++ map.put(com.destroystokyo.paper.ClientOption.MAIN_HAND, mainHand); ++ map.put(com.destroystokyo.paper.ClientOption.ALLOW_SERVER_LISTINGS, allowsServerListing); ++ map.put(com.destroystokyo.paper.ClientOption.TEXT_FILTERING_ENABLED, textFilteringEnabled); ++ return map; ++ } ++ // Paper end ++ + public void updateOptions(ClientInformation clientOptions) { ++ new com.destroystokyo.paper.event.player.PlayerClientOptionsChangeEvent(getBukkitEntity(), getClientOptionMap(clientOptions.language(), clientOptions.viewDistance(), com.destroystokyo.paper.ClientOption.ChatVisibility.valueOf(clientOptions.chatVisibility().name()), clientOptions.chatColors(), new com.destroystokyo.paper.PaperSkinParts(clientOptions.modelCustomisation()), clientOptions.mainHand() == HumanoidArm.LEFT ? MainHand.LEFT : MainHand.RIGHT, clientOptions.allowsListing(), clientOptions.textFilteringEnabled())).callEvent(); // Paper - settings event + // CraftBukkit start + if (this.getMainArm() != clientOptions.mainHand()) { + PlayerChangedMainHandEvent event = new PlayerChangedMainHandEvent(this.getBukkitEntity(), this.getMainArm() == HumanoidArm.LEFT ? MainHand.LEFT : MainHand.RIGHT); +@@ -2415,6 +2431,11 @@ public class ServerPlayer extends net.minecraft.world.entity.player.Player { + this.server.server.getPluginManager().callEvent(event); + } + // CraftBukkit end ++ // Paper start - don't call options events on login ++ this.updateOptionsNoEvents(clientOptions); ++ } ++ public void updateOptionsNoEvents(ClientInformation clientOptions) { ++ // Paper end + this.language = clientOptions.language(); + this.adventure$locale = java.util.Objects.requireNonNullElse(net.kyori.adventure.translation.Translator.parseLocale(this.language), java.util.Locale.US); // Paper + this.requestedViewDistance = clientOptions.viewDistance(); +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java +index faab55435a0c4fc6ff9d117f29a2401677b9f828..69108fd869c7c929fd7971abea520d5ab9063f69 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java +@@ -670,6 +670,28 @@ public class CraftPlayer extends CraftHumanEntity implements Player { + connection.disconnect(message == null ? net.kyori.adventure.text.Component.empty() : message); + } + } ++ ++ @Override ++ public T getClientOption(com.destroystokyo.paper.ClientOption type) { ++ if (com.destroystokyo.paper.ClientOption.SKIN_PARTS == type) { ++ return type.getType().cast(new com.destroystokyo.paper.PaperSkinParts(getHandle().getEntityData().get(net.minecraft.world.entity.player.Player.DATA_PLAYER_MODE_CUSTOMISATION))); ++ } else if (com.destroystokyo.paper.ClientOption.CHAT_COLORS_ENABLED == type) { ++ return type.getType().cast(getHandle().canChatInColor()); ++ } else if (com.destroystokyo.paper.ClientOption.CHAT_VISIBILITY == type) { ++ return type.getType().cast(getHandle().getChatVisibility() == null ? com.destroystokyo.paper.ClientOption.ChatVisibility.UNKNOWN : com.destroystokyo.paper.ClientOption.ChatVisibility.valueOf(getHandle().getChatVisibility().name())); ++ } else if (com.destroystokyo.paper.ClientOption.LOCALE == type) { ++ return type.getType().cast(getLocale()); ++ } else if (com.destroystokyo.paper.ClientOption.MAIN_HAND == type) { ++ return type.getType().cast(getMainHand()); ++ } else if (com.destroystokyo.paper.ClientOption.VIEW_DISTANCE == type) { ++ return type.getType().cast(getClientViewDistance()); ++ } else if (com.destroystokyo.paper.ClientOption.ALLOW_SERVER_LISTINGS == type) { ++ return type.getType().cast(getHandle().allowsListing()); ++ } else if (com.destroystokyo.paper.ClientOption.TEXT_FILTERING_ENABLED == type) { ++ return type.getType().cast(getHandle().isTextFilteringEnabled()); ++ } ++ throw new RuntimeException("Unknown settings type"); ++ } + // Paper end + + @Override +diff --git a/src/test/java/io/papermc/paper/world/TranslationKeyTest.java b/src/test/java/io/papermc/paper/world/TranslationKeyTest.java +new file mode 100644 +index 0000000000000000000000000000000000000000..7f8b6462d2a1bbd39a870d2543bebc135f7eb45b +--- /dev/null ++++ b/src/test/java/io/papermc/paper/world/TranslationKeyTest.java +@@ -0,0 +1,18 @@ ++package io.papermc.paper.world; ++ ++import com.destroystokyo.paper.ClientOption; ++import net.minecraft.world.entity.player.ChatVisiblity; ++import org.bukkit.Difficulty; ++import org.junit.jupiter.api.Assertions; ++import org.junit.jupiter.api.Test; ++ ++public class TranslationKeyTest { ++ ++ @Test ++ public void testChatVisibilityKeys() { ++ for (ClientOption.ChatVisibility chatVisibility : ClientOption.ChatVisibility.values()) { ++ if (chatVisibility == ClientOption.ChatVisibility.UNKNOWN) continue; ++ Assertions.assertEquals(ChatVisiblity.valueOf(chatVisibility.name()).getKey(), chatVisibility.translationKey(), chatVisibility + "'s translation key doesn't match"); ++ } ++ } ++} diff --git a/patches/server/0338-Don-t-crash-if-player-is-attempted-to-be-removed-fro.patch b/patches/server/0338-Don-t-crash-if-player-is-attempted-to-be-removed-fro.patch new file mode 100644 index 000000000000..0db5556d5a10 --- /dev/null +++ b/patches/server/0338-Don-t-crash-if-player-is-attempted-to-be-removed-fro.patch @@ -0,0 +1,23 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Aikar +Date: Sat, 18 Apr 2020 15:59:41 -0400 +Subject: [PATCH] Don't crash if player is attempted to be removed from + untracked chunk. + +I suspect it deals with teleporting as it uses players current x/y/z + +diff --git a/src/main/java/net/minecraft/server/level/DistanceManager.java b/src/main/java/net/minecraft/server/level/DistanceManager.java +index 6c2339d6a93172e25040c4868a3a47473a1f5336..f7c2c03749d6be25bf33afd61e1da120770b3432 100644 +--- a/src/main/java/net/minecraft/server/level/DistanceManager.java ++++ b/src/main/java/net/minecraft/server/level/DistanceManager.java +@@ -281,8 +281,8 @@ public abstract class DistanceManager { + ObjectSet objectset = (ObjectSet) this.playersPerChunk.get(i); + if (objectset == null) return; // CraftBukkit - SPIGOT-6208 + +- objectset.remove(player); +- if (objectset.isEmpty()) { ++ if (objectset != null) objectset.remove(player); // Paper - some state corruption happens here, don't crash, clean up gracefully ++ if (objectset == null || objectset.isEmpty()) { // Paper + this.playersPerChunk.remove(i); + this.naturalSpawnChunkCounter.update(i, Integer.MAX_VALUE, false); + this.playerTicketManager.update(i, Integer.MAX_VALUE, false); diff --git a/patches/server/0338-Prevent-opening-inventories-when-frozen.patch b/patches/server/0338-Prevent-opening-inventories-when-frozen.patch deleted file mode 100644 index 3f6f9857e63f..000000000000 --- a/patches/server/0338-Prevent-opening-inventories-when-frozen.patch +++ /dev/null @@ -1,50 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Shane Freeder -Date: Mon, 13 Apr 2020 07:31:44 +0100 -Subject: [PATCH] Prevent opening inventories when frozen - - -diff --git a/src/main/java/net/minecraft/server/level/ServerPlayer.java b/src/main/java/net/minecraft/server/level/ServerPlayer.java -index f8d4bd18f98ed914e116d0cc4a80140e4e8d759f..7daa310dd5d3eb1befb9983ce85e0354771af71d 100644 ---- a/src/main/java/net/minecraft/server/level/ServerPlayer.java -+++ b/src/main/java/net/minecraft/server/level/ServerPlayer.java -@@ -710,7 +710,7 @@ public class ServerPlayer extends net.minecraft.world.entity.player.Player { - containerUpdateDelay = this.level().paperConfig().tickRates.containerUpdate; - } - // Paper end - Configurable container update tick rate -- if (!this.level().isClientSide && !this.containerMenu.stillValid(this)) { -+ if (!this.level().isClientSide && this.containerMenu != this.inventoryMenu && (this.isImmobile() || !this.containerMenu.stillValid(this))) { // Paper - Prevent opening inventories when frozen - this.closeContainer(org.bukkit.event.inventory.InventoryCloseEvent.Reason.CANT_USE); // Paper - Inventory close reason - this.containerMenu = this.inventoryMenu; - } -@@ -1634,7 +1634,7 @@ public class ServerPlayer extends net.minecraft.world.entity.player.Player { - } else { - // CraftBukkit start - this.containerMenu = container; -- this.connection.send(new ClientboundOpenScreenPacket(container.containerId, container.getType(), container.getTitle())); -+ if (!this.isImmobile()) this.connection.send(new ClientboundOpenScreenPacket(container.containerId, container.getType(), container.getTitle())); // Paper - Prevent opening inventories when frozen - // CraftBukkit end - this.initMenu(container); - return OptionalInt.of(this.containerCounter); -diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftHumanEntity.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftHumanEntity.java -index ee7cf7f1d491ffdf26c3f156d299e2f517a05608..ba63c58d40cb3b8655fdb8177c423c67ac7cc3ef 100644 ---- a/src/main/java/org/bukkit/craftbukkit/entity/CraftHumanEntity.java -+++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftHumanEntity.java -@@ -334,7 +334,7 @@ public class CraftHumanEntity extends CraftLivingEntity implements HumanEntity { - if (adventure$title == null) adventure$title = net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer.legacySection().deserialize(container.getBukkitView().getTitle()); // Paper - - //player.connection.send(new ClientboundOpenScreenPacket(container.containerId, windowType, CraftChatMessage.fromString(title)[0])); // Paper - comment -- player.connection.send(new ClientboundOpenScreenPacket(container.containerId, windowType, io.papermc.paper.adventure.PaperAdventure.asVanilla(adventure$title))); // Paper -+ if (!player.isImmobile()) player.connection.send(new ClientboundOpenScreenPacket(container.containerId, windowType, io.papermc.paper.adventure.PaperAdventure.asVanilla(adventure$title))); // Paper - Prevent opening inventories when frozen - player.containerMenu = container; - player.initMenu(container); - } -@@ -409,7 +409,7 @@ public class CraftHumanEntity extends CraftLivingEntity implements HumanEntity { - net.kyori.adventure.text.Component adventure$title = inventory.title(); // Paper - if (adventure$title == null) adventure$title = net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer.legacySection().deserialize(inventory.getTitle()); // Paper - //player.connection.send(new ClientboundOpenScreenPacket(container.containerId, windowType, CraftChatMessage.fromString(title)[0])); // Paper - comment -- player.connection.send(new ClientboundOpenScreenPacket(container.containerId, windowType, io.papermc.paper.adventure.PaperAdventure.asVanilla(adventure$title))); // Paper -+ if (!player.isImmobile()) player.connection.send(new ClientboundOpenScreenPacket(container.containerId, windowType, io.papermc.paper.adventure.PaperAdventure.asVanilla(adventure$title))); // Paper - Prevent opening inventories when frozen - player.containerMenu = container; - player.initMenu(container); - } diff --git a/patches/server/0339-Don-t-run-entity-collision-code-if-not-needed.patch b/patches/server/0339-Don-t-run-entity-collision-code-if-not-needed.patch deleted file mode 100644 index 4e134e507c1a..000000000000 --- a/patches/server/0339-Don-t-run-entity-collision-code-if-not-needed.patch +++ /dev/null @@ -1,43 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Spottedleaf -Date: Wed, 15 Apr 2020 17:56:07 -0700 -Subject: [PATCH] Don't run entity collision code if not needed - -Will not run if: -Max entity cramming is disabled and the max collisions per entity is less than or equal to 0. -Entity#isPushable() returns false, meaning all entities will not be able to collide with this -entity anyways. -The entity's current team collision rule causes them to NEVER collide. - -Co-authored-by: Owen1212055 <23108066+Owen1212055@users.noreply.github.com> - -diff --git a/src/main/java/net/minecraft/world/entity/LivingEntity.java b/src/main/java/net/minecraft/world/entity/LivingEntity.java -index 4c32b26e29ca3db0a2f62052e14bcc3e4c1cdea5..cf18f17f181bc94a2b5f4ac6926c2388ec3178c8 100644 ---- a/src/main/java/net/minecraft/world/entity/LivingEntity.java -+++ b/src/main/java/net/minecraft/world/entity/LivingEntity.java -@@ -3544,10 +3544,24 @@ public abstract class LivingEntity extends Entity implements Attackable { - if (this.level().isClientSide()) { - this.level().getEntities(EntityTypeTest.forClass(net.minecraft.world.entity.player.Player.class), this.getBoundingBox(), EntitySelector.pushableBy(this)).forEach(this::doPush); - } else { -+ // Paper start - don't run getEntities if we're not going to use its result -+ if (!this.isPushable()) { -+ return; -+ } -+ net.minecraft.world.scores.Team team = this.getTeam(); -+ if (team != null && team.getCollisionRule() == net.minecraft.world.scores.Team.CollisionRule.NEVER) { -+ return; -+ } -+ -+ int i = this.level().getGameRules().getInt(GameRules.RULE_MAX_ENTITY_CRAMMING); -+ if (i <= 0 && this.level().paperConfig().collisions.maxEntityCollisions <= 0) { -+ return; -+ } -+ // Paper end - don't run getEntities if we're not going to use its result - List list = this.level().getEntities((Entity) this, this.getBoundingBox(), EntitySelector.pushableBy(this)); - - if (!list.isEmpty()) { -- int i = this.level().getGameRules().getInt(GameRules.RULE_MAX_ENTITY_CRAMMING); -+ // Paper - don't run getEntities if we're not going to use its result; moved up - - if (i > 0 && list.size() > i - 1 && this.random.nextInt(4) == 0) { - int j = 0; diff --git a/patches/server/0339-Fire-PlayerJoinEvent-when-Player-is-actually-ready.patch b/patches/server/0339-Fire-PlayerJoinEvent-when-Player-is-actually-ready.patch new file mode 100644 index 000000000000..463ceefd14b1 --- /dev/null +++ b/patches/server/0339-Fire-PlayerJoinEvent-when-Player-is-actually-ready.patch @@ -0,0 +1,93 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Aikar +Date: Sun, 19 Apr 2020 00:05:46 -0400 +Subject: [PATCH] Fire PlayerJoinEvent when Player is actually ready + +For years, plugin developers have had to delay many things they do +inside of the PlayerJoinEvent by 1 tick to make it actually work. + +This all boiled down to 1 reason why: The event fired before the +player was fully ready and joined to the world! + +Additionally, if that player logged out on a vehicle, the event +fired before the vehicle was even loaded, so that plugins had no +access to the vehicle during this event either. + +This change finally fixes this issue, fully preparing the player +into the world as a fully ready entity, vehicle included. + +There should be no plugins that break because of this change, but might +improve consistency with other plugins instead. + +For example, if 2 plugins listens to this event, and the first one +teleported the player in the event, then the 2nd plugin actually +would be getting a valid player! + +This was very non deterministic. This change will ensure every plugin +receives a deterministic result, and should no longer require 1 tick +delays anymore. + +== AT == +public net.minecraft.server.level.ChunkMap addEntity(Lnet/minecraft/world/entity/Entity;)V + +diff --git a/src/main/java/net/minecraft/server/level/ChunkMap.java b/src/main/java/net/minecraft/server/level/ChunkMap.java +index 396310a51480cf0d1ea4c0959d3f8e4ed77b99e3..0f8fc275af95750871aa6917aa12053f75c112f7 100644 +--- a/src/main/java/net/minecraft/server/level/ChunkMap.java ++++ b/src/main/java/net/minecraft/server/level/ChunkMap.java +@@ -1305,6 +1305,7 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider + return; + } + // Paper end - ignore and warn about illegal addEntity calls instead of crashing server ++ if (entity instanceof ServerPlayer && ((ServerPlayer) entity).supressTrackerForLogin) return; // Paper - Fire PlayerJoinEvent when Player is actually ready; Delay adding to tracker until after list packets + if (!(entity instanceof EnderDragonPart)) { + EntityType entitytypes = entity.getType(); + int i = entitytypes.clientTrackingRange() * 16; +diff --git a/src/main/java/net/minecraft/server/level/ServerPlayer.java b/src/main/java/net/minecraft/server/level/ServerPlayer.java +index 9911e231ad021286f2da90057b06874f7b4e3b4d..807068fc6065f71961d34cb4f18b6eb39ae49637 100644 +--- a/src/main/java/net/minecraft/server/level/ServerPlayer.java ++++ b/src/main/java/net/minecraft/server/level/ServerPlayer.java +@@ -317,6 +317,7 @@ public class ServerPlayer extends net.minecraft.world.entity.player.Player { + public double maxHealthCache; + public boolean joining = true; + public boolean sentListPacket = false; ++ public boolean supressTrackerForLogin = false; // Paper - Fire PlayerJoinEvent when Player is actually ready + public String kickLeaveMessage = null; // SPIGOT-3034: Forward leave message to PlayerQuitEvent + // CraftBukkit end + public boolean isRealPlayer; // Paper +diff --git a/src/main/java/net/minecraft/server/players/PlayerList.java b/src/main/java/net/minecraft/server/players/PlayerList.java +index ad4f5a4e00b9b517841acd0b4ff5088e45451bf4..4ff14dc6996634b0fcd365f76055023601ad2be0 100644 +--- a/src/main/java/net/minecraft/server/players/PlayerList.java ++++ b/src/main/java/net/minecraft/server/players/PlayerList.java +@@ -299,6 +299,13 @@ public abstract class PlayerList { + this.playersByUUID.put(player.getUUID(), player); + // this.broadcastAll(ClientboundPlayerInfoUpdatePacket.createPlayerInitializing(List.of(entityplayer))); // CraftBukkit - replaced with loop below + ++ // Paper start - Fire PlayerJoinEvent when Player is actually ready; correctly register player BEFORE PlayerJoinEvent, so the entity is valid and doesn't require tick delay hacks ++ player.supressTrackerForLogin = true; ++ worldserver1.addNewPlayer(player); ++ this.server.getCustomBossEvents().onPlayerConnect(player); // see commented out section below worldserver.addPlayerJoin(entityplayer); ++ player.loadAndSpawnEnderpearls(optional); ++ player.loadAndSpawnParentVehicle(optional); ++ // Paper end - Fire PlayerJoinEvent when Player is actually ready + // CraftBukkit start + CraftPlayer bukkitPlayer = player.getBukkitEntity(); + +@@ -337,6 +344,8 @@ public abstract class PlayerList { + player.connection.send(ClientboundPlayerInfoUpdatePacket.createPlayerInitializing(List.of(entityplayer1))); + } + player.sentListPacket = true; ++ player.supressTrackerForLogin = false; // Paper - Fire PlayerJoinEvent when Player is actually ready ++ ((ServerLevel)player.level()).getChunkSource().chunkMap.addEntity(player); // Paper - Fire PlayerJoinEvent when Player is actually ready; track entity now + // CraftBukkit end + + player.refreshEntityData(player); // CraftBukkit - BungeeCord#2321, send complete data to self on spawn +@@ -352,8 +361,7 @@ public abstract class PlayerList { + worldserver1 = player.serverLevel(); // CraftBukkit - Update in case join event changed it + // CraftBukkit end + this.sendActivePlayerEffects(player); +- player.loadAndSpawnEnderpearls(optional); +- player.loadAndSpawnParentVehicle(optional); ++ // Paper - move loading pearls / parent vehicle up + player.initInventoryMenu(); + // CraftBukkit - Moved from above, added world + // Paper start - Configurable player collision; Add to collideRule team if needed diff --git a/patches/server/0340-Implement-Player-Client-Options-API.patch b/patches/server/0340-Implement-Player-Client-Options-API.patch deleted file mode 100644 index d190884bb0d1..000000000000 --- a/patches/server/0340-Implement-Player-Client-Options-API.patch +++ /dev/null @@ -1,194 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: MiniDigger -Date: Mon, 20 Jan 2020 21:38:15 +0100 -Subject: [PATCH] Implement Player Client Options API - -== AT == -public net.minecraft.world.entity.player.Player DATA_PLAYER_MODE_CUSTOMISATION - -diff --git a/src/main/java/com/destroystokyo/paper/PaperSkinParts.java b/src/main/java/com/destroystokyo/paper/PaperSkinParts.java -new file mode 100644 -index 0000000000000000000000000000000000000000..b6f4400df3d8ec7e06a996de54f8cabba57885e1 ---- /dev/null -+++ b/src/main/java/com/destroystokyo/paper/PaperSkinParts.java -@@ -0,0 +1,74 @@ -+package com.destroystokyo.paper; -+ -+import com.google.common.base.Objects; -+ -+import java.util.StringJoiner; -+ -+public class PaperSkinParts implements SkinParts { -+ -+ private final int raw; -+ -+ public PaperSkinParts(int raw) { -+ this.raw = raw; -+ } -+ -+ public boolean hasCapeEnabled() { -+ return (raw & 1) == 1; -+ } -+ -+ public boolean hasJacketEnabled() { -+ return (raw >> 1 & 1) == 1; -+ } -+ -+ public boolean hasLeftSleeveEnabled() { -+ return (raw >> 2 & 1) == 1; -+ } -+ -+ public boolean hasRightSleeveEnabled() { -+ return (raw >> 3 & 1) == 1; -+ } -+ -+ public boolean hasLeftPantsEnabled() { -+ return (raw >> 4 & 1) == 1; -+ } -+ -+ public boolean hasRightPantsEnabled() { -+ return (raw >> 5 & 1) == 1; -+ } -+ -+ public boolean hasHatsEnabled() { -+ return (raw >> 6 & 1) == 1; -+ } -+ -+ @Override -+ public int getRaw() { -+ return raw; -+ } -+ -+ @Override -+ public boolean equals(Object o) { -+ if (this == o) return true; -+ if (o == null || getClass() != o.getClass()) return false; -+ PaperSkinParts that = (PaperSkinParts) o; -+ return raw == that.raw; -+ } -+ -+ @Override -+ public int hashCode() { -+ return Objects.hashCode(raw); -+ } -+ -+ @Override -+ public String toString() { -+ return new StringJoiner(", ", PaperSkinParts.class.getSimpleName() + "[", "]") -+ .add("raw=" + raw) -+ .add("cape=" + hasCapeEnabled()) -+ .add("jacket=" + hasJacketEnabled()) -+ .add("leftSleeve=" + hasLeftSleeveEnabled()) -+ .add("rightSleeve=" + hasRightSleeveEnabled()) -+ .add("leftPants=" + hasLeftPantsEnabled()) -+ .add("rightPants=" + hasRightPantsEnabled()) -+ .add("hats=" + hasHatsEnabled()) -+ .toString(); -+ } -+} -diff --git a/src/main/java/net/minecraft/server/level/ServerPlayer.java b/src/main/java/net/minecraft/server/level/ServerPlayer.java -index 7daa310dd5d3eb1befb9983ce85e0354771af71d..8938f90c53de8aef71aa70522a66a69edb467e73 100644 ---- a/src/main/java/net/minecraft/server/level/ServerPlayer.java -+++ b/src/main/java/net/minecraft/server/level/ServerPlayer.java -@@ -359,7 +359,7 @@ public class ServerPlayer extends net.minecraft.world.entity.player.Player { - this.stats = server.getPlayerList().getPlayerStats(this); - this.advancements = server.getPlayerList().getPlayerAdvancements(this); - // this.moveTo(this.adjustSpawnLocation(world, world.getSharedSpawnPos()).getBottomCenter(), 0.0F, 0.0F); // Paper - Don't move existing players to world spawn -- this.updateOptions(clientOptions); -+ this.updateOptionsNoEvents(clientOptions); // Paper - don't call options events on login - this.object = null; - - // CraftBukkit start -@@ -2146,7 +2146,23 @@ public class ServerPlayer extends net.minecraft.world.entity.player.Player { - } - } - -+ // Paper start - Client option API -+ private java.util.Map, ?> getClientOptionMap(String locale, int viewDistance, com.destroystokyo.paper.ClientOption.ChatVisibility chatVisibility, boolean chatColors, com.destroystokyo.paper.PaperSkinParts skinParts, org.bukkit.inventory.MainHand mainHand, boolean allowsServerListing, boolean textFilteringEnabled) { -+ java.util.Map, Object> map = new java.util.HashMap<>(); -+ map.put(com.destroystokyo.paper.ClientOption.LOCALE, locale); -+ map.put(com.destroystokyo.paper.ClientOption.VIEW_DISTANCE, viewDistance); -+ map.put(com.destroystokyo.paper.ClientOption.CHAT_VISIBILITY, chatVisibility); -+ map.put(com.destroystokyo.paper.ClientOption.CHAT_COLORS_ENABLED, chatColors); -+ map.put(com.destroystokyo.paper.ClientOption.SKIN_PARTS, skinParts); -+ map.put(com.destroystokyo.paper.ClientOption.MAIN_HAND, mainHand); -+ map.put(com.destroystokyo.paper.ClientOption.ALLOW_SERVER_LISTINGS, allowsServerListing); -+ map.put(com.destroystokyo.paper.ClientOption.TEXT_FILTERING_ENABLED, textFilteringEnabled); -+ return map; -+ } -+ // Paper end -+ - public void updateOptions(ClientInformation clientOptions) { -+ new com.destroystokyo.paper.event.player.PlayerClientOptionsChangeEvent(getBukkitEntity(), getClientOptionMap(clientOptions.language(), clientOptions.viewDistance(), com.destroystokyo.paper.ClientOption.ChatVisibility.valueOf(clientOptions.chatVisibility().name()), clientOptions.chatColors(), new com.destroystokyo.paper.PaperSkinParts(clientOptions.modelCustomisation()), clientOptions.mainHand() == HumanoidArm.LEFT ? MainHand.LEFT : MainHand.RIGHT, clientOptions.allowsListing(), clientOptions.textFilteringEnabled())).callEvent(); // Paper - settings event - // CraftBukkit start - if (this.getMainArm() != clientOptions.mainHand()) { - PlayerChangedMainHandEvent event = new PlayerChangedMainHandEvent(this.getBukkitEntity(), this.getMainArm() == HumanoidArm.LEFT ? MainHand.LEFT : MainHand.RIGHT); -@@ -2157,6 +2173,11 @@ public class ServerPlayer extends net.minecraft.world.entity.player.Player { - this.server.server.getPluginManager().callEvent(event); - } - // CraftBukkit end -+ // Paper start - don't call options events on login -+ this.updateOptionsNoEvents(clientOptions); -+ } -+ public void updateOptionsNoEvents(ClientInformation clientOptions) { -+ // Paper end - this.language = clientOptions.language(); - this.adventure$locale = java.util.Objects.requireNonNullElse(net.kyori.adventure.translation.Translator.parseLocale(this.language), java.util.Locale.US); // Paper - this.requestedViewDistance = clientOptions.viewDistance(); -diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java -index 1959e2af13daa06293c10ea2a4f68e319fac26ad..068ff2c228308ec87fcc6d1352bd63b91bd34a93 100644 ---- a/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java -+++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java -@@ -653,6 +653,28 @@ public class CraftPlayer extends CraftHumanEntity implements Player { - connection.disconnect(message == null ? net.kyori.adventure.text.Component.empty() : message); - } - } -+ -+ @Override -+ public T getClientOption(com.destroystokyo.paper.ClientOption type) { -+ if (com.destroystokyo.paper.ClientOption.SKIN_PARTS == type) { -+ return type.getType().cast(new com.destroystokyo.paper.PaperSkinParts(getHandle().getEntityData().get(net.minecraft.world.entity.player.Player.DATA_PLAYER_MODE_CUSTOMISATION))); -+ } else if (com.destroystokyo.paper.ClientOption.CHAT_COLORS_ENABLED == type) { -+ return type.getType().cast(getHandle().canChatInColor()); -+ } else if (com.destroystokyo.paper.ClientOption.CHAT_VISIBILITY == type) { -+ return type.getType().cast(getHandle().getChatVisibility() == null ? com.destroystokyo.paper.ClientOption.ChatVisibility.UNKNOWN : com.destroystokyo.paper.ClientOption.ChatVisibility.valueOf(getHandle().getChatVisibility().name())); -+ } else if (com.destroystokyo.paper.ClientOption.LOCALE == type) { -+ return type.getType().cast(getLocale()); -+ } else if (com.destroystokyo.paper.ClientOption.MAIN_HAND == type) { -+ return type.getType().cast(getMainHand()); -+ } else if (com.destroystokyo.paper.ClientOption.VIEW_DISTANCE == type) { -+ return type.getType().cast(getClientViewDistance()); -+ } else if (com.destroystokyo.paper.ClientOption.ALLOW_SERVER_LISTINGS == type) { -+ return type.getType().cast(getHandle().allowsListing()); -+ } else if (com.destroystokyo.paper.ClientOption.TEXT_FILTERING_ENABLED == type) { -+ return type.getType().cast(getHandle().isTextFilteringEnabled()); -+ } -+ throw new RuntimeException("Unknown settings type"); -+ } - // Paper end - - @Override -diff --git a/src/test/java/io/papermc/paper/world/TranslationKeyTest.java b/src/test/java/io/papermc/paper/world/TranslationKeyTest.java -new file mode 100644 -index 0000000000000000000000000000000000000000..7f8b6462d2a1bbd39a870d2543bebc135f7eb45b ---- /dev/null -+++ b/src/test/java/io/papermc/paper/world/TranslationKeyTest.java -@@ -0,0 +1,18 @@ -+package io.papermc.paper.world; -+ -+import com.destroystokyo.paper.ClientOption; -+import net.minecraft.world.entity.player.ChatVisiblity; -+import org.bukkit.Difficulty; -+import org.junit.jupiter.api.Assertions; -+import org.junit.jupiter.api.Test; -+ -+public class TranslationKeyTest { -+ -+ @Test -+ public void testChatVisibilityKeys() { -+ for (ClientOption.ChatVisibility chatVisibility : ClientOption.ChatVisibility.values()) { -+ if (chatVisibility == ClientOption.ChatVisibility.UNKNOWN) continue; -+ Assertions.assertEquals(ChatVisiblity.valueOf(chatVisibility.name()).getKey(), chatVisibility.translationKey(), chatVisibility + "'s translation key doesn't match"); -+ } -+ } -+} diff --git a/patches/server/0340-Move-player-to-spawn-point-if-spawn-in-unloaded-worl.patch b/patches/server/0340-Move-player-to-spawn-point-if-spawn-in-unloaded-worl.patch new file mode 100644 index 000000000000..3dae78af463b --- /dev/null +++ b/patches/server/0340-Move-player-to-spawn-point-if-spawn-in-unloaded-worl.patch @@ -0,0 +1,119 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: 2277 <38501234+2277@users.noreply.github.com> +Date: Tue, 31 Mar 2020 10:33:55 +0100 +Subject: [PATCH] Move player to spawn point if spawn in unloaded world + +If the playerdata contains an invalid world (missing, unloaded, invalid, +etc.), spawn the player at the spawn point of the main world. + +Co-authored-by: Wyatt Childers +Co-authored-by: Jake Potrebic + +diff --git a/src/main/java/net/minecraft/server/players/PlayerList.java b/src/main/java/net/minecraft/server/players/PlayerList.java +index 4ff14dc6996634b0fcd365f76055023601ad2be0..530369764cad77466995f8f65070eec6a5de74f2 100644 +--- a/src/main/java/net/minecraft/server/players/PlayerList.java ++++ b/src/main/java/net/minecraft/server/players/PlayerList.java +@@ -197,6 +197,7 @@ public abstract class PlayerList { + } + + Optional optional = this.load(player); // CraftBukkit - decompile error ++ ResourceKey resourcekey = null; // Paper + // CraftBukkit start - Better rename detection + if (optional.isPresent()) { + CompoundTag nbttagcompound = optional.get(); +@@ -206,19 +207,47 @@ public abstract class PlayerList { + } + } + // CraftBukkit end +- ResourceKey resourcekey = (ResourceKey) optional.flatMap((nbttagcompound) -> { ++ // Paper start - move logic in Entity to here, to use bukkit supplied world UUID & reset to main world spawn if no valid world is found ++ boolean[] invalidPlayerWorld = {false}; ++ bukkitData: if (optional.isPresent()) { ++ // The main way for bukkit worlds to store the world is the world UUID despite mojang adding custom worlds ++ final org.bukkit.World bWorld; ++ if (optional.get().contains("WorldUUIDMost") && optional.get().contains("WorldUUIDLeast")) { ++ bWorld = org.bukkit.Bukkit.getServer().getWorld(new UUID(optional.get().getLong("WorldUUIDMost"), optional.get().getLong("WorldUUIDLeast"))); ++ } else if (optional.get().contains("world", net.minecraft.nbt.Tag.TAG_STRING)) { // Paper - legacy bukkit world name ++ bWorld = org.bukkit.Bukkit.getServer().getWorld(optional.get().getString("world")); ++ } else { ++ break bukkitData; // if neither of the bukkit data points exist, proceed to the vanilla migration section ++ } ++ if (bWorld != null) { ++ resourcekey = ((CraftWorld) bWorld).getHandle().dimension(); ++ } else { ++ resourcekey = Level.OVERWORLD; ++ invalidPlayerWorld[0] = true; ++ } ++ } ++ if (resourcekey == null) { // only run the vanilla logic if we haven't found a world from the bukkit data ++ // Below is the vanilla way of getting the dimension, this is for migration from vanilla servers ++ resourcekey = optional.flatMap((nbttagcompound) -> { ++ // Paper end + DataResult> dataresult = DimensionType.parseLegacy(new Dynamic(NbtOps.INSTANCE, nbttagcompound.get("Dimension"))); // CraftBukkit - decompile error + Logger logger = PlayerList.LOGGER; + + Objects.requireNonNull(logger); +- return dataresult.resultOrPartial(logger::error); +- }).orElse(player.serverLevel().dimension()); // CraftBukkit - SPIGOT-7507: If no dimension, fall back to existing dimension loaded from "WorldUUID", which in turn defaults to World.OVERWORLD ++ // Paper start - reset to main world spawn if no valid world is found ++ final Optional> result = dataresult.resultOrPartial(logger::error); ++ invalidPlayerWorld[0] = result.isEmpty(); ++ return result; ++ }).orElse(Level.OVERWORLD); // Paper - revert to vanilla default main world, this isn't an "invalid world" since no player data existed ++ } ++ // Paper end + ServerLevel worldserver = this.server.getLevel(resourcekey); + ServerLevel worldserver1; + + if (worldserver == null) { + PlayerList.LOGGER.warn("Unknown respawn dimension {}, defaulting to overworld", resourcekey); + worldserver1 = this.server.overworld(); ++ invalidPlayerWorld[0] = true; // Paper - reset to main world if no world with parsed value is found + } else { + worldserver1 = worldserver; + } +@@ -226,6 +255,10 @@ public abstract class PlayerList { + // Paper start - Entity#getEntitySpawnReason + if (optional.isEmpty()) { + player.spawnReason = org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason.DEFAULT; // set Player SpawnReason to DEFAULT on first login ++ // Paper start - reset to main world spawn if first spawn or invalid world ++ } ++ if (optional.isEmpty() || invalidPlayerWorld[0]) { ++ // Paper end - reset to main world spawn if first spawn or invalid world + player.moveTo(player.adjustSpawnLocation(worldserver1, worldserver1.getSharedSpawnPos()).getBottomCenter(), 0.0F, 0.0F); + } + // Paper end - Entity#getEntitySpawnReason +diff --git a/src/main/java/net/minecraft/world/entity/Entity.java b/src/main/java/net/minecraft/world/entity/Entity.java +index fa80b6220aba144521dc0a2a35914c10046c1963..5b8e264098f1b713de15f714bae59d3efda365cf 100644 +--- a/src/main/java/net/minecraft/world/entity/Entity.java ++++ b/src/main/java/net/minecraft/world/entity/Entity.java +@@ -2502,27 +2502,8 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess + } + // CraftBukkit end + +- // CraftBukkit start - Reset world +- if (this instanceof ServerPlayer) { +- Server server = Bukkit.getServer(); +- org.bukkit.World bworld = null; +- +- // TODO: Remove World related checks, replaced with WorldUID +- String worldName = nbt.getString("world"); +- +- if (nbt.contains("WorldUUIDMost") && nbt.contains("WorldUUIDLeast")) { +- UUID uid = new UUID(nbt.getLong("WorldUUIDMost"), nbt.getLong("WorldUUIDLeast")); +- bworld = server.getWorld(uid); +- } else { +- bworld = server.getWorld(worldName); +- } +- +- if (bworld == null) { +- bworld = ((org.bukkit.craftbukkit.CraftServer) server).getServer().getLevel(Level.OVERWORLD).getWorld(); +- } +- +- ((ServerPlayer) this).setLevel(bworld == null ? null : ((CraftWorld) bworld).getHandle()); +- } ++ // CraftBukkit start ++ // Paper - move world parsing/loading to PlayerList#placeNewPlayer + this.getBukkitEntity().readBukkitValues(nbt); + if (nbt.contains("Bukkit.invisible")) { + boolean bukkitInvisible = nbt.getBoolean("Bukkit.invisible"); diff --git a/patches/server/0341-Add-PlayerAttackEntityCooldownResetEvent.patch b/patches/server/0341-Add-PlayerAttackEntityCooldownResetEvent.patch new file mode 100644 index 000000000000..620e81e21006 --- /dev/null +++ b/patches/server/0341-Add-PlayerAttackEntityCooldownResetEvent.patch @@ -0,0 +1,29 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: nossr50 +Date: Thu, 26 Mar 2020 19:44:50 -0700 +Subject: [PATCH] Add PlayerAttackEntityCooldownResetEvent + + +diff --git a/src/main/java/net/minecraft/world/entity/LivingEntity.java b/src/main/java/net/minecraft/world/entity/LivingEntity.java +index 4fb99e3ee14d2e7fe2720e25af1c890004b0c250..25a2085ed68d393afbb658b63a1cd39af98f39fa 100644 +--- a/src/main/java/net/minecraft/world/entity/LivingEntity.java ++++ b/src/main/java/net/minecraft/world/entity/LivingEntity.java +@@ -2352,7 +2352,17 @@ public abstract class LivingEntity extends Entity implements Attackable { + } + + if (damagesource.getEntity() instanceof net.minecraft.world.entity.player.Player) { +- ((net.minecraft.world.entity.player.Player) damagesource.getEntity()).resetAttackStrengthTicker(); // Moved from EntityHuman in order to make the cooldown reset get called after the damage event is fired ++ // Paper start - PlayerAttackEntityCooldownResetEvent ++ //((net.minecraft.world.entity.player.Player) damagesource.getEntity()).resetAttackStrengthTicker(); // Moved from EntityHuman in order to make the cooldown reset get called after the damage event is fired ++ if (damagesource.getEntity() instanceof ServerPlayer) { ++ ServerPlayer player = (ServerPlayer) damagesource.getEntity(); ++ if (new com.destroystokyo.paper.event.player.PlayerAttackEntityCooldownResetEvent(player.getBukkitEntity(), this.getBukkitEntity(), player.getAttackStrengthScale(0F)).callEvent()) { ++ player.resetAttackStrengthTicker(); ++ } ++ } else { ++ ((net.minecraft.world.entity.player.Player) damagesource.getEntity()).resetAttackStrengthTicker(); ++ } ++ // Paper end - PlayerAttackEntityCooldownResetEvent + } + + // Resistance diff --git a/patches/server/0341-Don-t-crash-if-player-is-attempted-to-be-removed-fro.patch b/patches/server/0341-Don-t-crash-if-player-is-attempted-to-be-removed-fro.patch deleted file mode 100644 index 9d20799a092c..000000000000 --- a/patches/server/0341-Don-t-crash-if-player-is-attempted-to-be-removed-fro.patch +++ /dev/null @@ -1,23 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Aikar -Date: Sat, 18 Apr 2020 15:59:41 -0400 -Subject: [PATCH] Don't crash if player is attempted to be removed from - untracked chunk. - -I suspect it deals with teleporting as it uses players current x/y/z - -diff --git a/src/main/java/net/minecraft/server/level/DistanceManager.java b/src/main/java/net/minecraft/server/level/DistanceManager.java -index dfa0456f352ce25bc4edd1b0f04ca5a14434d7fa..1e7b440cc2c1bf53210069b38286f67a7b97041b 100644 ---- a/src/main/java/net/minecraft/server/level/DistanceManager.java -+++ b/src/main/java/net/minecraft/server/level/DistanceManager.java -@@ -275,8 +275,8 @@ public abstract class DistanceManager { - ObjectSet objectset = (ObjectSet) this.playersPerChunk.get(i); - if (objectset == null) return; // CraftBukkit - SPIGOT-6208 - -- objectset.remove(player); -- if (objectset.isEmpty()) { -+ if (objectset != null) objectset.remove(player); // Paper - some state corruption happens here, don't crash, clean up gracefully -+ if (objectset == null || objectset.isEmpty()) { // Paper - this.playersPerChunk.remove(i); - this.naturalSpawnChunkCounter.update(i, Integer.MAX_VALUE, false); - this.playerTicketManager.update(i, Integer.MAX_VALUE, false); diff --git a/patches/server/0342-Don-t-fire-BlockFade-on-worldgen-threads.patch b/patches/server/0342-Don-t-fire-BlockFade-on-worldgen-threads.patch new file mode 100644 index 000000000000..cafa4896dfb7 --- /dev/null +++ b/patches/server/0342-Don-t-fire-BlockFade-on-worldgen-threads.patch @@ -0,0 +1,27 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Aikar +Date: Thu, 23 Apr 2020 01:36:39 -0400 +Subject: [PATCH] Don't fire BlockFade on worldgen threads + + +diff --git a/src/main/java/net/minecraft/world/level/block/FireBlock.java b/src/main/java/net/minecraft/world/level/block/FireBlock.java +index f44457c0d75efe323cc8242ef5173a3d5067fad0..065d6164b5c9d65d20e7790c607d77e9ad70dfef 100644 +--- a/src/main/java/net/minecraft/world/level/block/FireBlock.java ++++ b/src/main/java/net/minecraft/world/level/block/FireBlock.java +@@ -108,6 +108,7 @@ public class FireBlock extends BaseFireBlock { + @Override + protected BlockState updateShape(BlockState state, LevelReader world, ScheduledTickAccess tickView, BlockPos pos, Direction direction, BlockPos neighborPos, BlockState neighborState, RandomSource random) { + // CraftBukkit start ++ if (!(world instanceof ServerLevel)) return this.canSurvive(state, world, pos) ? (BlockState) this.getStateWithAge(world, pos, (Integer) state.getValue(FireBlock.AGE)) : Blocks.AIR.defaultBlockState(); // Paper - don't fire events in world generation + if (!this.canSurvive(state, world, pos)) { + // Suppress during worldgen + if (!(world instanceof Level world1)) { +@@ -123,7 +124,7 @@ public class FireBlock extends BaseFireBlock { + return blockState.getHandle(); + } + } +- return this.getStateWithAge(world, pos, (Integer) state.getValue(FireBlock.AGE)); ++ return this.getStateWithAge(world, pos, (Integer) state.getValue(FireBlock.AGE)); // Paper - don't fire events in world generation; diff on change, see "don't fire events in world generation" + // CraftBukkit end + } + diff --git a/patches/server/0342-Fire-PlayerJoinEvent-when-Player-is-actually-ready.patch b/patches/server/0342-Fire-PlayerJoinEvent-when-Player-is-actually-ready.patch deleted file mode 100644 index fda85df8ca64..000000000000 --- a/patches/server/0342-Fire-PlayerJoinEvent-when-Player-is-actually-ready.patch +++ /dev/null @@ -1,105 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Aikar -Date: Sun, 19 Apr 2020 00:05:46 -0400 -Subject: [PATCH] Fire PlayerJoinEvent when Player is actually ready - -For years, plugin developers have had to delay many things they do -inside of the PlayerJoinEvent by 1 tick to make it actually work. - -This all boiled down to 1 reason why: The event fired before the -player was fully ready and joined to the world! - -Additionally, if that player logged out on a vehicle, the event -fired before the vehicle was even loaded, so that plugins had no -access to the vehicle during this event either. - -This change finally fixes this issue, fully preparing the player -into the world as a fully ready entity, vehicle included. - -There should be no plugins that break because of this change, but might -improve consistency with other plugins instead. - -For example, if 2 plugins listens to this event, and the first one -teleported the player in the event, then the 2nd plugin actually -would be getting a valid player! - -This was very non deterministic. This change will ensure every plugin -receives a deterministic result, and should no longer require 1 tick -delays anymore. - -== AT == -public net.minecraft.server.level.ChunkMap addEntity(Lnet/minecraft/world/entity/Entity;)V - -diff --git a/src/main/java/net/minecraft/server/level/ChunkMap.java b/src/main/java/net/minecraft/server/level/ChunkMap.java -index 8206ec366b429858d9582e437781191e5aa0fa02..d91279f3bd009e1542e73354aadd6a16c80965e2 100644 ---- a/src/main/java/net/minecraft/server/level/ChunkMap.java -+++ b/src/main/java/net/minecraft/server/level/ChunkMap.java -@@ -1248,6 +1248,7 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider - return; - } - // Paper end - ignore and warn about illegal addEntity calls instead of crashing server -+ if (entity instanceof ServerPlayer && ((ServerPlayer) entity).supressTrackerForLogin) return; // Paper - Fire PlayerJoinEvent when Player is actually ready; Delay adding to tracker until after list packets - if (!(entity instanceof EnderDragonPart)) { - EntityType entitytypes = entity.getType(); - int i = entitytypes.clientTrackingRange() * 16; -diff --git a/src/main/java/net/minecraft/server/level/ServerPlayer.java b/src/main/java/net/minecraft/server/level/ServerPlayer.java -index 8938f90c53de8aef71aa70522a66a69edb467e73..e6e7dc17d1196a8211a565355f34b5dcfd478852 100644 ---- a/src/main/java/net/minecraft/server/level/ServerPlayer.java -+++ b/src/main/java/net/minecraft/server/level/ServerPlayer.java -@@ -289,6 +289,7 @@ public class ServerPlayer extends net.minecraft.world.entity.player.Player { - public double maxHealthCache; - public boolean joining = true; - public boolean sentListPacket = false; -+ public boolean supressTrackerForLogin = false; // Paper - Fire PlayerJoinEvent when Player is actually ready - public String kickLeaveMessage = null; // SPIGOT-3034: Forward leave message to PlayerQuitEvent - // CraftBukkit end - public boolean isRealPlayer; // Paper -diff --git a/src/main/java/net/minecraft/server/players/PlayerList.java b/src/main/java/net/minecraft/server/players/PlayerList.java -index 2cbdcdf0349e7efa797802d0d339d158153690af..5390ce62ec8afd24d2e028ea04d2ef3029a125f9 100644 ---- a/src/main/java/net/minecraft/server/players/PlayerList.java -+++ b/src/main/java/net/minecraft/server/players/PlayerList.java -@@ -297,6 +297,12 @@ public abstract class PlayerList { - this.playersByUUID.put(player.getUUID(), player); - // this.broadcastAll(ClientboundPlayerInfoUpdatePacket.createPlayerInitializing(List.of(entityplayer))); // CraftBukkit - replaced with loop below - -+ // Paper start - Fire PlayerJoinEvent when Player is actually ready; correctly register player BEFORE PlayerJoinEvent, so the entity is valid and doesn't require tick delay hacks -+ player.supressTrackerForLogin = true; -+ worldserver1.addNewPlayer(player); -+ this.server.getCustomBossEvents().onPlayerConnect(player); // see commented out section below worldserver.addPlayerJoin(entityplayer); -+ this.mountSavedVehicle(player, worldserver1, optional); -+ // Paper end - Fire PlayerJoinEvent when Player is actually ready - // CraftBukkit start - CraftPlayer bukkitPlayer = player.getBukkitEntity(); - -@@ -335,6 +341,8 @@ public abstract class PlayerList { - player.connection.send(ClientboundPlayerInfoUpdatePacket.createPlayerInitializing(List.of(entityplayer1))); - } - player.sentListPacket = true; -+ player.supressTrackerForLogin = false; // Paper - Fire PlayerJoinEvent when Player is actually ready -+ ((ServerLevel)player.level()).getChunkSource().chunkMap.addEntity(player); // Paper - Fire PlayerJoinEvent when Player is actually ready; track entity now - // CraftBukkit end - - player.refreshEntityData(player); // CraftBukkit - BungeeCord#2321, send complete data to self on spawn -@@ -350,6 +358,11 @@ public abstract class PlayerList { - worldserver1 = player.serverLevel(); // CraftBukkit - Update in case join event changed it - // CraftBukkit end - this.sendActivePlayerEffects(player); -+ // Paper start - Fire PlayerJoinEvent when Player is actually ready; move vehicle into method so it can be called above - short circuit around that code -+ this.onPlayerJoinFinish(player, worldserver1, s1); -+ } -+ private void mountSavedVehicle(ServerPlayer player, ServerLevel worldserver1, Optional optional) { -+ // Paper end - Fire PlayerJoinEvent when Player is actually ready - if (optional.isPresent() && ((CompoundTag) optional.get()).contains("RootVehicle", 10)) { - CompoundTag nbttagcompound = ((CompoundTag) optional.get()).getCompound("RootVehicle"); - ServerLevel finalWorldServer = worldserver1; // CraftBukkit - decompile error -@@ -396,6 +409,10 @@ public abstract class PlayerList { - } - } - -+ // Paper start - Fire PlayerJoinEvent when Player is actually ready -+ } -+ public void onPlayerJoinFinish(ServerPlayer player, ServerLevel worldserver1, String s1) { -+ // Paper end - Fire PlayerJoinEvent when Player is actually ready - player.initInventoryMenu(); - // CraftBukkit - Moved from above, added world - // Paper start - Configurable player collision; Add to collideRule team if needed diff --git a/patches/server/0343-Add-phantom-creative-and-insomniac-controls.patch b/patches/server/0343-Add-phantom-creative-and-insomniac-controls.patch new file mode 100644 index 000000000000..eed8a10b78ae --- /dev/null +++ b/patches/server/0343-Add-phantom-creative-and-insomniac-controls.patch @@ -0,0 +1,43 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: William Blake Galbreath +Date: Sat, 25 Apr 2020 15:13:41 -0500 +Subject: [PATCH] Add phantom creative and insomniac controls + + +diff --git a/src/main/java/net/minecraft/world/entity/EntitySelector.java b/src/main/java/net/minecraft/world/entity/EntitySelector.java +index 3a4c1d4afddd7d8d1f43554a7a08855686cadf56..b8d57e25851dd7da905100dfd4022e4b99fd7f02 100644 +--- a/src/main/java/net/minecraft/world/entity/EntitySelector.java ++++ b/src/main/java/net/minecraft/world/entity/EntitySelector.java +@@ -27,6 +27,7 @@ public final class EntitySelector { + }; + public static final Predicate CAN_BE_COLLIDED_WITH = EntitySelector.NO_SPECTATORS.and(Entity::canBeCollidedWith); + public static final Predicate CAN_BE_PICKED = EntitySelector.NO_SPECTATORS.and(Entity::isPickable); ++ public static Predicate IS_INSOMNIAC = (player) -> net.minecraft.util.Mth.clamp(((net.minecraft.server.level.ServerPlayer) player).getStats().getValue(net.minecraft.stats.Stats.CUSTOM.get(net.minecraft.stats.Stats.TIME_SINCE_REST)), 1, Integer.MAX_VALUE) >= 72000; // Paper - Add phantom creative and insomniac controls + + private EntitySelector() {} + // Paper start - Affects Spawning API +diff --git a/src/main/java/net/minecraft/world/entity/monster/Phantom.java b/src/main/java/net/minecraft/world/entity/monster/Phantom.java +index 748f07c7036fe5955d76e28c4e7d23f8c0235d5f..d0820b3a561db7e1e5667594b2b6ea13214dfa58 100644 +--- a/src/main/java/net/minecraft/world/entity/monster/Phantom.java ++++ b/src/main/java/net/minecraft/world/entity/monster/Phantom.java +@@ -550,6 +550,7 @@ public class Phantom extends FlyingMob implements Enemy { + Player entityhuman = (Player) iterator.next(); + + if (Phantom.this.canAttack(worldserver, entityhuman, TargetingConditions.DEFAULT)) { ++ if (!level().paperConfig().entities.behavior.phantomsOnlyAttackInsomniacs || EntitySelector.IS_INSOMNIAC.test(entityhuman)) // Paper - Add phantom creative and insomniac controls + Phantom.this.setTarget(entityhuman, org.bukkit.event.entity.EntityTargetEvent.TargetReason.CLOSEST_PLAYER, true); // CraftBukkit - reason + return true; + } +diff --git a/src/main/java/net/minecraft/world/level/levelgen/PhantomSpawner.java b/src/main/java/net/minecraft/world/level/levelgen/PhantomSpawner.java +index 499b124f905ffa8e375efa354a3f2240997ddea5..7d407a7597f3ae576ac7e94bc2eb96d9dcd78dd3 100644 +--- a/src/main/java/net/minecraft/world/level/levelgen/PhantomSpawner.java ++++ b/src/main/java/net/minecraft/world/level/levelgen/PhantomSpawner.java +@@ -48,7 +48,7 @@ public class PhantomSpawner implements CustomSpawner { + while (iterator.hasNext()) { + ServerPlayer entityplayer = (ServerPlayer) iterator.next(); + +- if (!entityplayer.isSpectator()) { ++ if (!entityplayer.isSpectator() && (!world.paperConfig().entities.behavior.phantomsDoNotSpawnOnCreativePlayers || !entityplayer.isCreative())) { // Paper - Add phantom creative and insomniac controls + BlockPos blockposition = entityplayer.blockPosition(); + + if (!world.dimensionType().hasSkyLight() || blockposition.getY() >= world.getSeaLevel() && world.canSeeSky(blockposition)) { diff --git a/patches/server/0343-Move-player-to-spawn-point-if-spawn-in-unloaded-worl.patch b/patches/server/0343-Move-player-to-spawn-point-if-spawn-in-unloaded-worl.patch deleted file mode 100644 index b739652ade17..000000000000 --- a/patches/server/0343-Move-player-to-spawn-point-if-spawn-in-unloaded-worl.patch +++ /dev/null @@ -1,119 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: 2277 <38501234+2277@users.noreply.github.com> -Date: Tue, 31 Mar 2020 10:33:55 +0100 -Subject: [PATCH] Move player to spawn point if spawn in unloaded world - -If the playerdata contains an invalid world (missing, unloaded, invalid, -etc.), spawn the player at the spawn point of the main world. - -Co-authored-by: Wyatt Childers -Co-authored-by: Jake Potrebic - -diff --git a/src/main/java/net/minecraft/server/players/PlayerList.java b/src/main/java/net/minecraft/server/players/PlayerList.java -index 5390ce62ec8afd24d2e028ea04d2ef3029a125f9..6f587c4bb4704d93552ba61335d87b1852fc169c 100644 ---- a/src/main/java/net/minecraft/server/players/PlayerList.java -+++ b/src/main/java/net/minecraft/server/players/PlayerList.java -@@ -197,6 +197,7 @@ public abstract class PlayerList { - } - - Optional optional = this.load(player); // CraftBukkit - decompile error -+ ResourceKey resourcekey = null; // Paper - // CraftBukkit start - Better rename detection - if (optional.isPresent()) { - CompoundTag nbttagcompound = optional.get(); -@@ -206,19 +207,47 @@ public abstract class PlayerList { - } - } - // CraftBukkit end -- ResourceKey resourcekey = (ResourceKey) optional.flatMap((nbttagcompound) -> { -+ // Paper start - move logic in Entity to here, to use bukkit supplied world UUID & reset to main world spawn if no valid world is found -+ boolean[] invalidPlayerWorld = {false}; -+ bukkitData: if (optional.isPresent()) { -+ // The main way for bukkit worlds to store the world is the world UUID despite mojang adding custom worlds -+ final org.bukkit.World bWorld; -+ if (optional.get().contains("WorldUUIDMost") && optional.get().contains("WorldUUIDLeast")) { -+ bWorld = org.bukkit.Bukkit.getServer().getWorld(new UUID(optional.get().getLong("WorldUUIDMost"), optional.get().getLong("WorldUUIDLeast"))); -+ } else if (optional.get().contains("world", net.minecraft.nbt.Tag.TAG_STRING)) { // Paper - legacy bukkit world name -+ bWorld = org.bukkit.Bukkit.getServer().getWorld(optional.get().getString("world")); -+ } else { -+ break bukkitData; // if neither of the bukkit data points exist, proceed to the vanilla migration section -+ } -+ if (bWorld != null) { -+ resourcekey = ((CraftWorld) bWorld).getHandle().dimension(); -+ } else { -+ resourcekey = Level.OVERWORLD; -+ invalidPlayerWorld[0] = true; -+ } -+ } -+ if (resourcekey == null) { // only run the vanilla logic if we haven't found a world from the bukkit data -+ // Below is the vanilla way of getting the dimension, this is for migration from vanilla servers -+ resourcekey = optional.flatMap((nbttagcompound) -> { -+ // Paper end - DataResult> dataresult = DimensionType.parseLegacy(new Dynamic(NbtOps.INSTANCE, nbttagcompound.get("Dimension"))); // CraftBukkit - decompile error - Logger logger = PlayerList.LOGGER; - - Objects.requireNonNull(logger); -- return dataresult.resultOrPartial(logger::error); -- }).orElse(player.serverLevel().dimension()); // CraftBukkit - SPIGOT-7507: If no dimension, fall back to existing dimension loaded from "WorldUUID", which in turn defaults to World.OVERWORLD -+ // Paper start - reset to main world spawn if no valid world is found -+ final Optional> result = dataresult.resultOrPartial(logger::error); -+ invalidPlayerWorld[0] = result.isEmpty(); -+ return result; -+ }).orElse(Level.OVERWORLD); // Paper - revert to vanilla default main world, this isn't an "invalid world" since no player data existed -+ } -+ // Paper end - ServerLevel worldserver = this.server.getLevel(resourcekey); - ServerLevel worldserver1; - - if (worldserver == null) { - PlayerList.LOGGER.warn("Unknown respawn dimension {}, defaulting to overworld", resourcekey); - worldserver1 = this.server.overworld(); -+ invalidPlayerWorld[0] = true; // Paper - reset to main world if no world with parsed value is found - } else { - worldserver1 = worldserver; - } -@@ -226,6 +255,10 @@ public abstract class PlayerList { - // Paper start - Entity#getEntitySpawnReason - if (optional.isEmpty()) { - player.spawnReason = org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason.DEFAULT; // set Player SpawnReason to DEFAULT on first login -+ // Paper start - reset to main world spawn if first spawn or invalid world -+ } -+ if (optional.isEmpty() || invalidPlayerWorld[0]) { -+ // Paper end - reset to main world spawn if first spawn or invalid world - player.moveTo(player.adjustSpawnLocation(worldserver1, worldserver1.getSharedSpawnPos()).getBottomCenter(), 0.0F, 0.0F); - } - // Paper end - Entity#getEntitySpawnReason -diff --git a/src/main/java/net/minecraft/world/entity/Entity.java b/src/main/java/net/minecraft/world/entity/Entity.java -index 94242c19740ae6ab2c86e3949bab6cee631b938f..d80fd4e2f41583f83c9527ccf4ce80afe851276a 100644 ---- a/src/main/java/net/minecraft/world/entity/Entity.java -+++ b/src/main/java/net/minecraft/world/entity/Entity.java -@@ -2385,27 +2385,8 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess - } - // CraftBukkit end - -- // CraftBukkit start - Reset world -- if (this instanceof ServerPlayer) { -- Server server = Bukkit.getServer(); -- org.bukkit.World bworld = null; -- -- // TODO: Remove World related checks, replaced with WorldUID -- String worldName = nbt.getString("world"); -- -- if (nbt.contains("WorldUUIDMost") && nbt.contains("WorldUUIDLeast")) { -- UUID uid = new UUID(nbt.getLong("WorldUUIDMost"), nbt.getLong("WorldUUIDLeast")); -- bworld = server.getWorld(uid); -- } else { -- bworld = server.getWorld(worldName); -- } -- -- if (bworld == null) { -- bworld = ((org.bukkit.craftbukkit.CraftServer) server).getServer().getLevel(Level.OVERWORLD).getWorld(); -- } -- -- ((ServerPlayer) this).setLevel(bworld == null ? null : ((CraftWorld) bworld).getHandle()); -- } -+ // CraftBukkit start -+ // Paper - move world parsing/loading to PlayerList#placeNewPlayer - this.getBukkitEntity().readBukkitValues(nbt); - if (nbt.contains("Bukkit.invisible")) { - boolean bukkitInvisible = nbt.getBoolean("Bukkit.invisible"); diff --git a/patches/server/0344-Add-PlayerAttackEntityCooldownResetEvent.patch b/patches/server/0344-Add-PlayerAttackEntityCooldownResetEvent.patch deleted file mode 100644 index 3348155d8568..000000000000 --- a/patches/server/0344-Add-PlayerAttackEntityCooldownResetEvent.patch +++ /dev/null @@ -1,29 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: nossr50 -Date: Thu, 26 Mar 2020 19:44:50 -0700 -Subject: [PATCH] Add PlayerAttackEntityCooldownResetEvent - - -diff --git a/src/main/java/net/minecraft/world/entity/LivingEntity.java b/src/main/java/net/minecraft/world/entity/LivingEntity.java -index cf18f17f181bc94a2b5f4ac6926c2388ec3178c8..42c4adabe451cd32aa362075395a9fcc384ea788 100644 ---- a/src/main/java/net/minecraft/world/entity/LivingEntity.java -+++ b/src/main/java/net/minecraft/world/entity/LivingEntity.java -@@ -2277,7 +2277,17 @@ public abstract class LivingEntity extends Entity implements Attackable { - } - - if (damagesource.getEntity() instanceof net.minecraft.world.entity.player.Player) { -- ((net.minecraft.world.entity.player.Player) damagesource.getEntity()).resetAttackStrengthTicker(); // Moved from EntityHuman in order to make the cooldown reset get called after the damage event is fired -+ // Paper start - PlayerAttackEntityCooldownResetEvent -+ //((net.minecraft.world.entity.player.Player) damagesource.getEntity()).resetAttackStrengthTicker(); // Moved from EntityHuman in order to make the cooldown reset get called after the damage event is fired -+ if (damagesource.getEntity() instanceof ServerPlayer) { -+ ServerPlayer player = (ServerPlayer) damagesource.getEntity(); -+ if (new com.destroystokyo.paper.event.player.PlayerAttackEntityCooldownResetEvent(player.getBukkitEntity(), this.getBukkitEntity(), player.getAttackStrengthScale(0F)).callEvent()) { -+ player.resetAttackStrengthTicker(); -+ } -+ } else { -+ ((net.minecraft.world.entity.player.Player) damagesource.getEntity()).resetAttackStrengthTicker(); -+ } -+ // Paper end - PlayerAttackEntityCooldownResetEvent - } - - // Resistance diff --git a/patches/server/0344-Fix-item-duplication-and-teleport-issues.patch b/patches/server/0344-Fix-item-duplication-and-teleport-issues.patch new file mode 100644 index 000000000000..d818b547f11b --- /dev/null +++ b/patches/server/0344-Fix-item-duplication-and-teleport-issues.patch @@ -0,0 +1,155 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Aikar +Date: Sat, 25 Apr 2020 06:46:35 -0400 +Subject: [PATCH] Fix item duplication and teleport issues + +This notably fixes the newest "Donkey Dupe", but also fixes a lot +of dupe bugs in general around nether portals and entity world transfer + +We also fix item duplication generically by anytime we clone an item +to drop it on the ground, destroy the source item. + +This avoid an itemstack ever existing twice in the world state pre +clean up stage. + +So even if something NEW comes up, it would be impossible to drop the +same item twice because the source was destroyed. + +diff --git a/src/main/java/net/minecraft/world/entity/Entity.java b/src/main/java/net/minecraft/world/entity/Entity.java +index 5b8e264098f1b713de15f714bae59d3efda365cf..d5f96ed753e8298085e40c6181285cd6ea838ca2 100644 +--- a/src/main/java/net/minecraft/world/entity/Entity.java ++++ b/src/main/java/net/minecraft/world/entity/Entity.java +@@ -2630,11 +2630,12 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess + } else { + // CraftBukkit start - Capture drops for death event + if (this instanceof net.minecraft.world.entity.LivingEntity && !((net.minecraft.world.entity.LivingEntity) this).forceDrops) { +- ((net.minecraft.world.entity.LivingEntity) this).drops.add(org.bukkit.craftbukkit.inventory.CraftItemStack.asBukkitCopy(stack)); ++ ((net.minecraft.world.entity.LivingEntity) this).drops.add(org.bukkit.craftbukkit.inventory.CraftItemStack.asCraftMirror(stack)); // Paper - mirror so we can destroy it later + return null; + } + // CraftBukkit end +- ItemEntity entityitem = new ItemEntity(world, this.getX(), this.getY() + (double) yOffset, this.getZ(), stack); ++ ItemEntity entityitem = new ItemEntity(world, this.getX(), this.getY() + (double) yOffset, this.getZ(), stack.copy()); // Paper - copy so we can destroy original ++ stack.setCount(0); // Paper - destroy this item - if this ever leaks due to game bugs, ensure it doesn't dupe + + entityitem.setDefaultPickUpDelay(); + // CraftBukkit start +@@ -3462,6 +3463,12 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess + public Entity teleport(TeleportTransition teleportTarget) { + Level world = this.level(); + ++ // Paper start - Fix item duplication and teleport issues ++ if (!this.isAlive() || !this.valid) { ++ LOGGER.warn("Illegal Entity Teleport " + this + " to " + teleportTarget.newLevel() + ":" + teleportTarget.position(), new Throwable()); ++ return null; ++ } ++ // Paper end - Fix item duplication and teleport issues + if (world instanceof ServerLevel worldserver) { + if (!this.isRemoved()) { + // CraftBukkit start +@@ -3550,6 +3557,11 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess + gameprofilerfiller.pop(); + return null; + } else { ++ // Paper start - Fix item duplication and teleport issues ++ if (this instanceof Leashable leashable) { ++ leashable.dropLeash(true, true); // Paper drop lead ++ } ++ // Paper end - Fix item duplication and teleport issues + entity.restoreFrom(this); + this.removeAfterChangingDimensions(); + // CraftBukkit start - Forward the CraftEntity to the new entity +@@ -3662,6 +3674,7 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess + } + + public boolean canTeleport(Level from, Level to) { ++ if (!this.isAlive() || !this.valid) return false; // Paper - Fix item duplication and teleport issues + if (from.dimension() == Level.END && to.dimension() == Level.OVERWORLD) { + Iterator iterator = this.getPassengers().iterator(); + +diff --git a/src/main/java/net/minecraft/world/entity/LivingEntity.java b/src/main/java/net/minecraft/world/entity/LivingEntity.java +index 25a2085ed68d393afbb658b63a1cd39af98f39fa..718ccabb46d4520ba363d21a33e06eb2c9ff62ee 100644 +--- a/src/main/java/net/minecraft/world/entity/LivingEntity.java ++++ b/src/main/java/net/minecraft/world/entity/LivingEntity.java +@@ -1765,9 +1765,9 @@ public abstract class LivingEntity extends Entity implements Attackable { + // Paper start + org.bukkit.event.entity.EntityDeathEvent deathEvent = this.dropAllDeathLoot(worldserver, damageSource); + if (deathEvent == null || !deathEvent.isCancelled()) { +- if (this.deathScore >= 0 && entityliving != null) { +- entityliving.awardKillScore(this, this.deathScore, damageSource); +- } ++ // if (this.deathScore >= 0 && entityliving != null) { // Paper - Fix item duplication and teleport issues; moved to be run earlier in #dropAllDeathLoot before destroying the drop items in CraftEventFactory#callEntityDeathEvent ++ // entityliving.awardKillScore(this, this.deathScore, damageSource); ++ // } + // Paper start - clear equipment if event is not cancelled + if (this instanceof Mob) { + for (EquipmentSlot slot : this.clearedEquipmentSlots) { +@@ -1861,8 +1861,13 @@ public abstract class LivingEntity extends Entity implements Attackable { + this.dropCustomDeathLoot(world, damageSource, flag); + this.clearEquipmentSlots = prev; // Paper + } +- // CraftBukkit start - Call death event +- org.bukkit.event.entity.EntityDeathEvent deathEvent = CraftEventFactory.callEntityDeathEvent(this, damageSource, this.drops); // Paper ++ // CraftBukkit start - Call death event // Paper start - call advancement triggers with correct entity equipment ++ org.bukkit.event.entity.EntityDeathEvent deathEvent = CraftEventFactory.callEntityDeathEvent(this, damageSource, this.drops, () -> { ++ final LivingEntity entityliving = this.getKillCredit(); ++ if (this.deathScore >= 0 && entityliving != null) { ++ entityliving.awardKillScore(this, this.deathScore, damageSource); ++ } ++ }); // Paper end + this.postDeathDropItems(deathEvent); // Paper + this.drops = new ArrayList<>(); + // CraftBukkit end +diff --git a/src/main/java/net/minecraft/world/entity/decoration/ArmorStand.java b/src/main/java/net/minecraft/world/entity/decoration/ArmorStand.java +index e20565cf256aacd012a1722c5ebbf9016bc82e42..59fbfe8de2dc5ec020dd61a5e446b0b6f67d76e4 100644 +--- a/src/main/java/net/minecraft/world/entity/decoration/ArmorStand.java ++++ b/src/main/java/net/minecraft/world/entity/decoration/ArmorStand.java +@@ -633,7 +633,7 @@ public class ArmorStand extends LivingEntity { + for (i = 0; i < this.handItems.size(); ++i) { + itemstack = (ItemStack) this.handItems.get(i); + if (!itemstack.isEmpty()) { +- this.drops.add(org.bukkit.craftbukkit.inventory.CraftItemStack.asBukkitCopy(itemstack)); // CraftBukkit - add to drops ++ this.drops.add(org.bukkit.craftbukkit.inventory.CraftItemStack.asCraftMirror(itemstack)); // CraftBukkit - add to drops // Paper - mirror so we can destroy it later - though this call site was safe + this.handItems.set(i, ItemStack.EMPTY); + } + } +@@ -641,7 +641,7 @@ public class ArmorStand extends LivingEntity { + for (i = 0; i < this.armorItems.size(); ++i) { + itemstack = (ItemStack) this.armorItems.get(i); + if (!itemstack.isEmpty()) { +- this.drops.add(org.bukkit.craftbukkit.inventory.CraftItemStack.asBukkitCopy(itemstack)); // CraftBukkit - add to drops ++ this.drops.add(org.bukkit.craftbukkit.inventory.CraftItemStack.asCraftMirror(itemstack)); // CraftBukkit - add to drops // Paper - mirror so we can destroy it later - though this call site was safe + this.armorItems.set(i, ItemStack.EMPTY); + } + } +diff --git a/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java b/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java +index 0627e3be754dbf8201f3212cf823e2f8cb77cdeb..6b094f8bdf1f25d5c2e108eb88d95aed4a41f6f3 100644 +--- a/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java ++++ b/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java +@@ -903,6 +903,11 @@ public class CraftEventFactory { + } + + public static EntityDeathEvent callEntityDeathEvent(net.minecraft.world.entity.LivingEntity victim, DamageSource damageSource, List drops) { ++ // Paper start ++ return CraftEventFactory.callEntityDeathEvent(victim, damageSource, drops, com.google.common.util.concurrent.Runnables.doNothing()); ++ } ++ public static EntityDeathEvent callEntityDeathEvent(net.minecraft.world.entity.LivingEntity victim, DamageSource damageSource, List drops, Runnable lootCheck) { ++ // Paper end + CraftLivingEntity entity = (CraftLivingEntity) victim.getBukkitEntity(); + CraftDamageSource bukkitDamageSource = new CraftDamageSource(damageSource); + CraftWorld world = (CraftWorld) entity.getWorld(); +@@ -917,11 +922,13 @@ public class CraftEventFactory { + playDeathSound(victim, event); + // Paper end + victim.expToDrop = event.getDroppedExp(); ++ lootCheck.run(); // Paper - advancement triggers before destroying items + + for (org.bukkit.inventory.ItemStack stack : event.getDrops()) { + if (stack == null || stack.getType() == Material.AIR || stack.getAmount() == 0) continue; + +- world.dropItem(entity.getLocation(), stack); ++ world.dropItem(entity.getLocation(), stack); // Paper - note: dropItem already clones due to this being bukkit -> NMS ++ if (stack instanceof CraftItemStack) stack.setAmount(0); // Paper - destroy this item - if this ever leaks due to game bugs, ensure it doesn't dupe, but don't nuke bukkit stacks of manually added items + } + + return event; diff --git a/patches/server/0345-Don-t-fire-BlockFade-on-worldgen-threads.patch b/patches/server/0345-Don-t-fire-BlockFade-on-worldgen-threads.patch deleted file mode 100644 index a909ceca1272..000000000000 --- a/patches/server/0345-Don-t-fire-BlockFade-on-worldgen-threads.patch +++ /dev/null @@ -1,27 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Aikar -Date: Thu, 23 Apr 2020 01:36:39 -0400 -Subject: [PATCH] Don't fire BlockFade on worldgen threads - - -diff --git a/src/main/java/net/minecraft/world/level/block/FireBlock.java b/src/main/java/net/minecraft/world/level/block/FireBlock.java -index c1111bd8065b53cb140e4289cb72985f03e6f549..9db6df5f28be559a324ead2fcfbe189eac076e2e 100644 ---- a/src/main/java/net/minecraft/world/level/block/FireBlock.java -+++ b/src/main/java/net/minecraft/world/level/block/FireBlock.java -@@ -108,6 +108,7 @@ public class FireBlock extends BaseFireBlock { - @Override - protected BlockState updateShape(BlockState state, Direction direction, BlockState neighborState, LevelAccessor world, BlockPos pos, BlockPos neighborPos) { - // CraftBukkit start -+ if (!(world instanceof ServerLevel)) return this.canSurvive(state, world, pos) ? (BlockState) this.getStateWithAge(world, pos, (Integer) state.getValue(FireBlock.AGE)) : Blocks.AIR.defaultBlockState(); // Paper - don't fire events in world generation - if (!this.canSurvive(state, world, pos)) { - // Suppress during worldgen - if (!(world instanceof Level)) { -@@ -123,7 +124,7 @@ public class FireBlock extends BaseFireBlock { - return blockState.getHandle(); - } - } -- return this.getStateWithAge(world, pos, (Integer) state.getValue(FireBlock.AGE)); -+ return this.getStateWithAge(world, pos, (Integer) state.getValue(FireBlock.AGE)); // Paper - don't fire events in world generation; diff on change, see "don't fire events in world generation" - // CraftBukkit end - } - diff --git a/patches/server/0345-Villager-Restocks-API.patch b/patches/server/0345-Villager-Restocks-API.patch new file mode 100644 index 000000000000..4ad766705c0e --- /dev/null +++ b/patches/server/0345-Villager-Restocks-API.patch @@ -0,0 +1,31 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: zbk +Date: Sun, 26 Apr 2020 23:49:01 -0400 +Subject: [PATCH] Villager Restocks API + +== AT == +public net.minecraft.world.entity.npc.Villager numberOfRestocksToday + +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftVillager.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftVillager.java +index 1a7b666f9795e72022a60d14bfd405ae3165e292..ded319d10a5907b96abfd5620036b66a9aa3b3f7 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftVillager.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftVillager.java +@@ -90,6 +90,18 @@ public class CraftVillager extends CraftAbstractVillager implements Villager { + this.getHandle().setVillagerXp(experience); + } + ++ // Paper start ++ @Override ++ public int getRestocksToday() { ++ return getHandle().numberOfRestocksToday; ++ } ++ ++ @Override ++ public void setRestocksToday(int restocksToday) { ++ getHandle().numberOfRestocksToday = restocksToday; ++ } ++ // Paper end ++ + @Override + public boolean sleep(Location location) { + Preconditions.checkArgument(location != null, "Location cannot be null"); diff --git a/patches/server/0346-Add-phantom-creative-and-insomniac-controls.patch b/patches/server/0346-Add-phantom-creative-and-insomniac-controls.patch deleted file mode 100644 index 0eea572994f7..000000000000 --- a/patches/server/0346-Add-phantom-creative-and-insomniac-controls.patch +++ /dev/null @@ -1,43 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: William Blake Galbreath -Date: Sat, 25 Apr 2020 15:13:41 -0500 -Subject: [PATCH] Add phantom creative and insomniac controls - - -diff --git a/src/main/java/net/minecraft/world/entity/EntitySelector.java b/src/main/java/net/minecraft/world/entity/EntitySelector.java -index f7014bf5faae03a04c31cbdaeb27188c47156c2d..eb425fac573881f3aad09ae75a32184fb0e325a6 100644 ---- a/src/main/java/net/minecraft/world/entity/EntitySelector.java -+++ b/src/main/java/net/minecraft/world/entity/EntitySelector.java -@@ -28,6 +28,7 @@ public final class EntitySelector { - return !entity.isSpectator(); - }; - public static final Predicate CAN_BE_COLLIDED_WITH = EntitySelector.NO_SPECTATORS.and(Entity::canBeCollidedWith); -+ public static Predicate IS_INSOMNIAC = (player) -> net.minecraft.util.Mth.clamp(((net.minecraft.server.level.ServerPlayer) player).getStats().getValue(net.minecraft.stats.Stats.CUSTOM.get(net.minecraft.stats.Stats.TIME_SINCE_REST)), 1, Integer.MAX_VALUE) >= 72000; // Paper - Add phantom creative and insomniac controls - - private EntitySelector() {} - // Paper start - Affects Spawning API -diff --git a/src/main/java/net/minecraft/world/entity/monster/Phantom.java b/src/main/java/net/minecraft/world/entity/monster/Phantom.java -index 3c3f70d05fb51b530b792adf84c324840bd03c14..4b3bec32921feb1dcf71abf5e8d34fcbbc59baf5 100644 ---- a/src/main/java/net/minecraft/world/entity/monster/Phantom.java -+++ b/src/main/java/net/minecraft/world/entity/monster/Phantom.java -@@ -549,6 +549,7 @@ public class Phantom extends FlyingMob implements Enemy { - Player entityhuman = (Player) iterator.next(); - - if (Phantom.this.canAttack(entityhuman, TargetingConditions.DEFAULT)) { -+ if (!level().paperConfig().entities.behavior.phantomsOnlyAttackInsomniacs || EntitySelector.IS_INSOMNIAC.test(entityhuman)) // Paper - Add phantom creative and insomniac controls - Phantom.this.setTarget(entityhuman, org.bukkit.event.entity.EntityTargetEvent.TargetReason.CLOSEST_PLAYER, true); // CraftBukkit - reason - return true; - } -diff --git a/src/main/java/net/minecraft/world/level/levelgen/PhantomSpawner.java b/src/main/java/net/minecraft/world/level/levelgen/PhantomSpawner.java -index bb7f2d3ff7fc6f5cadb4ab24efb5a3a2f5bdc33f..f74d41e57570a40cd5ce4da3076f3210b6594a63 100644 ---- a/src/main/java/net/minecraft/world/level/levelgen/PhantomSpawner.java -+++ b/src/main/java/net/minecraft/world/level/levelgen/PhantomSpawner.java -@@ -48,7 +48,7 @@ public class PhantomSpawner implements CustomSpawner { - while (iterator.hasNext()) { - ServerPlayer entityplayer = (ServerPlayer) iterator.next(); - -- if (!entityplayer.isSpectator()) { -+ if (!entityplayer.isSpectator() && (!world.paperConfig().entities.behavior.phantomsDoNotSpawnOnCreativePlayers || !entityplayer.isCreative())) { // Paper - Add phantom creative and insomniac controls - BlockPos blockposition = entityplayer.blockPosition(); - - if (!world.dimensionType().hasSkyLight() || blockposition.getY() >= world.getSeaLevel() && world.canSeeSky(blockposition)) { diff --git a/patches/server/0346-Validate-PickItem-Packet-and-kick-for-invalid.patch b/patches/server/0346-Validate-PickItem-Packet-and-kick-for-invalid.patch new file mode 100644 index 000000000000..6b1b7c3df379 --- /dev/null +++ b/patches/server/0346-Validate-PickItem-Packet-and-kick-for-invalid.patch @@ -0,0 +1,26 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Aikar +Date: Sat, 2 May 2020 03:09:46 -0400 +Subject: [PATCH] Validate PickItem Packet and kick for invalid + + +diff --git a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java +index 8a58c1bdda065edd7b8560cd43e805de3fe0b178..59c12f6d5d96835b4b37ed5a761f25f8f147c54a 100644 +--- a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java ++++ b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java +@@ -906,7 +906,14 @@ public class ServerGamePacketListenerImpl extends ServerCommonPacketListenerImpl + @Override + public void handlePickItem(ServerboundPickItemPacket packet) { + PacketUtils.ensureRunningOnSameThread(packet, this, this.player.serverLevel()); +- this.player.getInventory().pickSlot(packet.getSlot()); ++ // Paper start - validate pick item position ++ if (!(packet.getSlot() >= 0 && packet.getSlot() < this.player.getInventory().items.size())) { ++ ServerGamePacketListenerImpl.LOGGER.warn("{} tried to set an invalid carried item", this.player.getName().getString()); ++ this.disconnect(Component.literal("Invalid hotbar selection (Hacking?)")); ++ return; ++ } ++ this.player.getInventory().pickSlot(packet.getSlot()); // Paper - Diff above if changed ++ // Paper end - validate pick item position + int i = this.player.getInventory().selected; + + this.player.connection.send(this.player.getInventory().createInventoryUpdatePacket(i)); diff --git a/patches/server/0347-Fix-item-duplication-and-teleport-issues.patch b/patches/server/0347-Fix-item-duplication-and-teleport-issues.patch deleted file mode 100644 index 86f3c094a3fa..000000000000 --- a/patches/server/0347-Fix-item-duplication-and-teleport-issues.patch +++ /dev/null @@ -1,156 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Aikar -Date: Sat, 25 Apr 2020 06:46:35 -0400 -Subject: [PATCH] Fix item duplication and teleport issues - -This notably fixes the newest "Donkey Dupe", but also fixes a lot -of dupe bugs in general around nether portals and entity world transfer - -We also fix item duplication generically by anytime we clone an item -to drop it on the ground, destroy the source item. - -This avoid an itemstack ever existing twice in the world state pre -clean up stage. - -So even if something NEW comes up, it would be impossible to drop the -same item twice because the source was destroyed. - -diff --git a/src/main/java/net/minecraft/world/entity/Entity.java b/src/main/java/net/minecraft/world/entity/Entity.java -index d80fd4e2f41583f83c9527ccf4ce80afe851276a..10015beb7a2de890fe27bffde8f55c1dd78ce344 100644 ---- a/src/main/java/net/minecraft/world/entity/Entity.java -+++ b/src/main/java/net/minecraft/world/entity/Entity.java -@@ -2515,11 +2515,12 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess - } else { - // CraftBukkit start - Capture drops for death event - if (this instanceof net.minecraft.world.entity.LivingEntity && !((net.minecraft.world.entity.LivingEntity) this).forceDrops) { -- ((net.minecraft.world.entity.LivingEntity) this).drops.add(org.bukkit.craftbukkit.inventory.CraftItemStack.asBukkitCopy(stack)); -+ ((net.minecraft.world.entity.LivingEntity) this).drops.add(org.bukkit.craftbukkit.inventory.CraftItemStack.asCraftMirror(stack)); // Paper - mirror so we can destroy it later - return null; - } - // CraftBukkit end -- ItemEntity entityitem = new ItemEntity(this.level(), this.getX(), this.getY() + (double) yOffset, this.getZ(), stack); -+ ItemEntity entityitem = new ItemEntity(this.level(), this.getX(), this.getY() + (double) yOffset, this.getZ(), stack.copy()); // Paper - copy so we can destroy original -+ stack.setCount(0); // Paper - destroy this item - if this ever leaks due to game bugs, ensure it doesn't dupe - - entityitem.setDefaultPickUpDelay(); - // CraftBukkit start -@@ -3335,6 +3336,12 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess - public Entity changeDimension(DimensionTransition teleportTarget) { - Level world = this.level(); - -+ // Paper start - Fix item duplication and teleport issues -+ if (!this.isAlive() || !this.valid) { -+ LOGGER.warn("Illegal Entity Teleport " + this + " to " + teleportTarget.newLevel() + ":" + teleportTarget.pos(), new Throwable()); -+ return null; -+ } -+ // Paper end - Fix item duplication and teleport issues - if (world instanceof ServerLevel worldserver) { - if (!this.isRemoved()) { - // CraftBukkit start -@@ -3377,6 +3384,11 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess - - if (entity2 != null) { - if (this != entity2) { -+ // Paper start - Fix item duplication and teleport issues -+ if (this instanceof Leashable leashable) { -+ leashable.dropLeash(true, true); // Paper drop lead -+ } -+ // Paper end - Fix item duplication and teleport issues - entity2.restoreFrom(this); - this.removeAfterChangingDimensions(); - // CraftBukkit start - Forward the CraftEntity to the new entity -@@ -3452,7 +3464,7 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess - } - - public boolean canChangeDimensions(Level from, Level to) { -- return true; -+ return this.isAlive() && this.valid; // Paper - Fix item duplication and teleport issues - } - - public float getBlockExplosionResistance(Explosion explosion, BlockGetter world, BlockPos pos, BlockState blockState, FluidState fluidState, float max) { -diff --git a/src/main/java/net/minecraft/world/entity/LivingEntity.java b/src/main/java/net/minecraft/world/entity/LivingEntity.java -index 42c4adabe451cd32aa362075395a9fcc384ea788..8920099eb488c37b036b7bd97fbd4f7db505c77c 100644 ---- a/src/main/java/net/minecraft/world/entity/LivingEntity.java -+++ b/src/main/java/net/minecraft/world/entity/LivingEntity.java -@@ -1728,9 +1728,9 @@ public abstract class LivingEntity extends Entity implements Attackable { - // Paper start - org.bukkit.event.entity.EntityDeathEvent deathEvent = this.dropAllDeathLoot(worldserver, damageSource); - if (deathEvent == null || !deathEvent.isCancelled()) { -- if (this.deathScore >= 0 && entityliving != null) { -- entityliving.awardKillScore(this, this.deathScore, damageSource); -- } -+ // if (this.deathScore >= 0 && entityliving != null) { // Paper - Fix item duplication and teleport issues; moved to be run earlier in #dropAllDeathLoot before destroying the drop items in CraftEventFactory#callEntityDeathEvent -+ // entityliving.awardKillScore(this, this.deathScore, damageSource); -+ // } - // Paper start - clear equipment if event is not cancelled - if (this instanceof Mob) { - for (EquipmentSlot slot : this.clearedEquipmentSlots) { -@@ -1822,8 +1822,13 @@ public abstract class LivingEntity extends Entity implements Attackable { - this.dropCustomDeathLoot(world, damageSource, flag); - this.clearEquipmentSlots = prev; // Paper - } -- // CraftBukkit start - Call death event -- org.bukkit.event.entity.EntityDeathEvent deathEvent = CraftEventFactory.callEntityDeathEvent(this, damageSource, this.drops); // Paper -+ // CraftBukkit start - Call death event // Paper start - call advancement triggers with correct entity equipment -+ org.bukkit.event.entity.EntityDeathEvent deathEvent = CraftEventFactory.callEntityDeathEvent(this, damageSource, this.drops, () -> { -+ final LivingEntity entityliving = this.getKillCredit(); -+ if (this.deathScore >= 0 && entityliving != null) { -+ entityliving.awardKillScore(this, this.deathScore, damageSource); -+ } -+ }); // Paper end - this.postDeathDropItems(deathEvent); // Paper - this.drops = new ArrayList<>(); - // CraftBukkit end -diff --git a/src/main/java/net/minecraft/world/entity/decoration/ArmorStand.java b/src/main/java/net/minecraft/world/entity/decoration/ArmorStand.java -index 92bb0c63330ad3a4cb13b2dc655020714e9b1ffd..cc1189c2d7dc57ba8f29aad4ba5d2a07362bcd5b 100644 ---- a/src/main/java/net/minecraft/world/entity/decoration/ArmorStand.java -+++ b/src/main/java/net/minecraft/world/entity/decoration/ArmorStand.java -@@ -635,7 +635,7 @@ public class ArmorStand extends LivingEntity { - for (i = 0; i < this.handItems.size(); ++i) { - itemstack = (ItemStack) this.handItems.get(i); - if (!itemstack.isEmpty()) { -- this.drops.add(org.bukkit.craftbukkit.inventory.CraftItemStack.asBukkitCopy(itemstack)); // CraftBukkit - add to drops -+ this.drops.add(org.bukkit.craftbukkit.inventory.CraftItemStack.asCraftMirror(itemstack)); // CraftBukkit - add to drops // Paper - mirror so we can destroy it later - though this call site was safe - this.handItems.set(i, ItemStack.EMPTY); - } - } -@@ -643,7 +643,7 @@ public class ArmorStand extends LivingEntity { - for (i = 0; i < this.armorItems.size(); ++i) { - itemstack = (ItemStack) this.armorItems.get(i); - if (!itemstack.isEmpty()) { -- this.drops.add(org.bukkit.craftbukkit.inventory.CraftItemStack.asBukkitCopy(itemstack)); // CraftBukkit - add to drops -+ this.drops.add(org.bukkit.craftbukkit.inventory.CraftItemStack.asCraftMirror(itemstack)); // CraftBukkit - add to drops // Paper - mirror so we can destroy it later - though this call site was safe - this.armorItems.set(i, ItemStack.EMPTY); - } - } -diff --git a/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java b/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java -index 235ac5c12dab593da3a40e348a010ff626ce74a3..26f8a8cb18205bfb9fe9dc557097946987ddcb18 100644 ---- a/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java -+++ b/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java -@@ -905,6 +905,11 @@ public class CraftEventFactory { - } - - public static EntityDeathEvent callEntityDeathEvent(net.minecraft.world.entity.LivingEntity victim, DamageSource damageSource, List drops) { -+ // Paper start -+ return CraftEventFactory.callEntityDeathEvent(victim, damageSource, drops, com.google.common.util.concurrent.Runnables.doNothing()); -+ } -+ public static EntityDeathEvent callEntityDeathEvent(net.minecraft.world.entity.LivingEntity victim, DamageSource damageSource, List drops, Runnable lootCheck) { -+ // Paper end - CraftLivingEntity entity = (CraftLivingEntity) victim.getBukkitEntity(); - CraftDamageSource bukkitDamageSource = new CraftDamageSource(damageSource); - EntityDeathEvent event = new EntityDeathEvent(entity, bukkitDamageSource, drops, victim.getExpReward(damageSource.getEntity())); -@@ -919,11 +924,13 @@ public class CraftEventFactory { - playDeathSound(victim, event); - // Paper end - victim.expToDrop = event.getDroppedExp(); -+ lootCheck.run(); // Paper - advancement triggers before destroying items - - for (org.bukkit.inventory.ItemStack stack : event.getDrops()) { - if (stack == null || stack.getType() == Material.AIR || stack.getAmount() == 0) continue; - -- world.dropItem(entity.getLocation(), stack); -+ world.dropItem(entity.getLocation(), stack); // Paper - note: dropItem already clones due to this being bukkit -> NMS -+ if (stack instanceof CraftItemStack) stack.setAmount(0); // Paper - destroy this item - if this ever leaks due to game bugs, ensure it doesn't dupe, but don't nuke bukkit stacks of manually added items - } - - return event; diff --git a/patches/server/0347-Set-cap-on-JDK-per-thread-native-byte-buffer-cache.patch b/patches/server/0347-Set-cap-on-JDK-per-thread-native-byte-buffer-cache.patch new file mode 100644 index 000000000000..e325a1d81095 --- /dev/null +++ b/patches/server/0347-Set-cap-on-JDK-per-thread-native-byte-buffer-cache.patch @@ -0,0 +1,30 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Aikar +Date: Mon, 4 May 2020 01:08:56 -0400 +Subject: [PATCH] Set cap on JDK per-thread native byte buffer cache + +See: https://www.evanjones.ca/java-bytebuffer-leak.html + +This is potentially a source of lots of native memory usage. + +We are clearly seeing native usage upwards to 1-4GB which doesn't make sense. + +Region File usage fixed in previous patch should of tecnically only been somewhat +temporary until GC finally gets it some time later, but between all the various +plugins doing IO on various threads, this hidden detail of the JDK could be +keeping long lived large direct buffers in cache. + +Set system properly at server startup if not set already to help protect from this. + +diff --git a/src/main/java/org/bukkit/craftbukkit/Main.java b/src/main/java/org/bukkit/craftbukkit/Main.java +index c618934b5cf66d9625c7be2ac114f1a1ca629d33..124aeebbbae7dc8cea1260bf3134a339c2e152ed 100644 +--- a/src/main/java/org/bukkit/craftbukkit/Main.java ++++ b/src/main/java/org/bukkit/craftbukkit/Main.java +@@ -27,6 +27,7 @@ public class Main { + } + // Paper end + // Todo: Installation script ++ if (System.getProperty("jdk.nio.maxCachedBufferSize") == null) System.setProperty("jdk.nio.maxCachedBufferSize", "262144"); // Paper - cap per-thread NIO cache size; https://www.evanjones.ca/java-bytebuffer-leak.html + OptionParser parser = new OptionParser() { + { + this.acceptsAll(Main.asList("?", "help"), "Show the help"); diff --git a/patches/server/0348-Villager-Restocks-API.patch b/patches/server/0348-Villager-Restocks-API.patch deleted file mode 100644 index 1f7c26de9d24..000000000000 --- a/patches/server/0348-Villager-Restocks-API.patch +++ /dev/null @@ -1,31 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: zbk -Date: Sun, 26 Apr 2020 23:49:01 -0400 -Subject: [PATCH] Villager Restocks API - -== AT == -public net.minecraft.world.entity.npc.Villager numberOfRestocksToday - -diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftVillager.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftVillager.java -index 1b97755b42aaa6cc27b79f0b6369955e9a17c4d4..957c9ec21c7a9888b3038402b0111c68f816f968 100644 ---- a/src/main/java/org/bukkit/craftbukkit/entity/CraftVillager.java -+++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftVillager.java -@@ -89,6 +89,18 @@ public class CraftVillager extends CraftAbstractVillager implements Villager { - this.getHandle().setVillagerXp(experience); - } - -+ // Paper start -+ @Override -+ public int getRestocksToday() { -+ return getHandle().numberOfRestocksToday; -+ } -+ -+ @Override -+ public void setRestocksToday(int restocksToday) { -+ getHandle().numberOfRestocksToday = restocksToday; -+ } -+ // Paper end -+ - @Override - public boolean sleep(Location location) { - Preconditions.checkArgument(location != null, "Location cannot be null"); diff --git a/patches/server/0348-misc-debugging-dumps.patch b/patches/server/0348-misc-debugging-dumps.patch new file mode 100644 index 000000000000..64ee1d9744f0 --- /dev/null +++ b/patches/server/0348-misc-debugging-dumps.patch @@ -0,0 +1,118 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Shane Freeder +Date: Thu, 18 Feb 2021 20:23:28 +0000 +Subject: [PATCH] misc debugging dumps + + +diff --git a/src/main/java/io/papermc/paper/util/TraceUtil.java b/src/main/java/io/papermc/paper/util/TraceUtil.java +new file mode 100644 +index 0000000000000000000000000000000000000000..479bb92d159f33c54c2d9c39d8a63aa9e74d95b9 +--- /dev/null ++++ b/src/main/java/io/papermc/paper/util/TraceUtil.java +@@ -0,0 +1,25 @@ ++package io.papermc.paper.util; ++ ++import org.bukkit.Bukkit; ++ ++public final class TraceUtil { ++ ++ public static void dumpTraceForThread(Thread thread, String reason) { ++ Bukkit.getLogger().warning(thread.getName() + ": " + reason); ++ StackTraceElement[] trace = StacktraceDeobfuscator.INSTANCE.deobfuscateStacktrace(thread.getStackTrace()); ++ for (StackTraceElement traceElement : trace) { ++ Bukkit.getLogger().warning("\tat " + traceElement); ++ } ++ } ++ ++ public static void dumpTraceForThread(String reason) { ++ final Throwable thr = new Throwable(reason); ++ StacktraceDeobfuscator.INSTANCE.deobfuscateThrowable(thr); ++ thr.printStackTrace(); ++ } ++ ++ public static void printStackTrace(Throwable thr) { ++ StacktraceDeobfuscator.INSTANCE.deobfuscateThrowable(thr); ++ thr.printStackTrace(); ++ } ++} +diff --git a/src/main/java/net/minecraft/commands/Commands.java b/src/main/java/net/minecraft/commands/Commands.java +index c847fbdb6f52386570eb4c070fcc01d39cc52151..a7eb2a37a81a414dcb19319c075faefe0382aeba 100644 +--- a/src/main/java/net/minecraft/commands/Commands.java ++++ b/src/main/java/net/minecraft/commands/Commands.java +@@ -345,7 +345,7 @@ public class Commands { + } catch (Exception exception) { + MutableComponent ichatmutablecomponent = Component.literal(exception.getMessage() == null ? exception.getClass().getName() : exception.getMessage()); + +- if (Commands.LOGGER.isDebugEnabled()) { ++ if (commandlistenerwrapper.getServer().isDebugging() || Commands.LOGGER.isDebugEnabled()) { // Paper - Debugging + Commands.LOGGER.error("Command exception: /{}", s, exception); + StackTraceElement[] astacktraceelement = exception.getStackTrace(); + +diff --git a/src/main/java/net/minecraft/server/MinecraftServer.java b/src/main/java/net/minecraft/server/MinecraftServer.java +index 652626c30a5e25ada797ec01273f1e016798aae1..909302aa0afc082a2d0bf55fd31ff9c510e8c3c5 100644 +--- a/src/main/java/net/minecraft/server/MinecraftServer.java ++++ b/src/main/java/net/minecraft/server/MinecraftServer.java +@@ -930,6 +930,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop +Date: Tue, 3 Mar 2020 05:26:40 +0000 +Subject: [PATCH] Prevent teleporting dead entities + + +diff --git a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java +index 59c12f6d5d96835b4b37ed5a761f25f8f147c54a..6c9f7a4d3c7551157d22f17e8a66ada2c50c0550 100644 +--- a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java ++++ b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java +@@ -1571,6 +1571,13 @@ public class ServerGamePacketListenerImpl extends ServerCommonPacketListenerImpl + } + + public void internalTeleport(PositionMoveRotation positionmoverotation, Set set) { ++ // Paper start - Prevent teleporting dead entities ++ if (player.isRemoved()) { ++ LOGGER.info("Attempt to teleport removed player {} restricted", player.getScoreboardName()); ++ if (server.isDebugging()) io.papermc.paper.util.TraceUtil.dumpTraceForThread("Attempt to teleport removed player"); ++ return; ++ } ++ // Paper end - Prevent teleporting dead entities + if (Float.isNaN(positionmoverotation.yRot())) { + positionmoverotation = new PositionMoveRotation(positionmoverotation.position(), positionmoverotation.deltaMovement(), 0, positionmoverotation.xRot()); + } diff --git a/patches/server/0349-Validate-PickItem-Packet-and-kick-for-invalid.patch b/patches/server/0349-Validate-PickItem-Packet-and-kick-for-invalid.patch deleted file mode 100644 index 774f59a11577..000000000000 --- a/patches/server/0349-Validate-PickItem-Packet-and-kick-for-invalid.patch +++ /dev/null @@ -1,26 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Aikar -Date: Sat, 2 May 2020 03:09:46 -0400 -Subject: [PATCH] Validate PickItem Packet and kick for invalid - - -diff --git a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java -index 0ed2d0f5ec9d107e8049aa9e803479ffd341639f..119b9b2c45e3321b4197f3e6a34037e3fa99622d 100644 ---- a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java -+++ b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java -@@ -895,7 +895,14 @@ public class ServerGamePacketListenerImpl extends ServerCommonPacketListenerImpl - @Override - public void handlePickItem(ServerboundPickItemPacket packet) { - PacketUtils.ensureRunningOnSameThread(packet, this, this.player.serverLevel()); -- this.player.getInventory().pickSlot(packet.getSlot()); -+ // Paper start - validate pick item position -+ if (!(packet.getSlot() >= 0 && packet.getSlot() < this.player.getInventory().items.size())) { -+ ServerGamePacketListenerImpl.LOGGER.warn("{} tried to set an invalid carried item", this.player.getName().getString()); -+ this.disconnect(Component.literal("Invalid hotbar selection (Hacking?)")); -+ return; -+ } -+ this.player.getInventory().pickSlot(packet.getSlot()); // Paper - Diff above if changed -+ // Paper end - validate pick item position - this.player.connection.send(new ClientboundContainerSetSlotPacket(-2, 0, this.player.getInventory().selected, this.player.getInventory().getItem(this.player.getInventory().selected))); - this.player.connection.send(new ClientboundContainerSetSlotPacket(-2, 0, packet.getSlot(), this.player.getInventory().getItem(packet.getSlot()))); - this.player.connection.send(new ClientboundSetCarriedItemPacket(this.player.getInventory().selected)); diff --git a/patches/server/0350-Implement-Mob-Goal-API.patch b/patches/server/0350-Implement-Mob-Goal-API.patch new file mode 100644 index 000000000000..b1e4b0cf0d27 --- /dev/null +++ b/patches/server/0350-Implement-Mob-Goal-API.patch @@ -0,0 +1,802 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: MiniDigger +Date: Fri, 3 Jan 2020 16:26:19 +0100 +Subject: [PATCH] Implement Mob Goal API + + +diff --git a/build.gradle.kts b/build.gradle.kts +index da2f9c5afb2994f403a1128af0f7acbd6b73b862..38585b7f0b8e1e287b37820924a1b0d464fe9e99 100644 +--- a/build.gradle.kts ++++ b/build.gradle.kts +@@ -41,6 +41,7 @@ dependencies { + runtimeOnly("org.apache.maven.resolver:maven-resolver-connector-basic:1.9.18") + runtimeOnly("org.apache.maven.resolver:maven-resolver-transport-http:1.9.18") + ++ testImplementation("io.github.classgraph:classgraph:4.8.47") // Paper - mob goal test + testImplementation("org.junit.jupiter:junit-jupiter:5.10.2") + testImplementation("org.junit.platform:junit-platform-suite-engine:1.10.0") + testImplementation("org.hamcrest:hamcrest:2.2") +diff --git a/src/main/java/com/destroystokyo/paper/entity/ai/MobGoalHelper.java b/src/main/java/com/destroystokyo/paper/entity/ai/MobGoalHelper.java +new file mode 100644 +index 0000000000000000000000000000000000000000..05e793e722bbb367ca64cd7f26156fa3dabb8474 +--- /dev/null ++++ b/src/main/java/com/destroystokyo/paper/entity/ai/MobGoalHelper.java +@@ -0,0 +1,380 @@ ++package com.destroystokyo.paper.entity.ai; ++ ++import com.destroystokyo.paper.entity.RangedEntity; ++import com.google.common.collect.BiMap; ++import com.google.common.collect.HashBiMap; ++import io.papermc.paper.util.ObfHelper; ++import java.lang.reflect.Constructor; ++import java.util.EnumSet; ++import java.util.HashMap; ++import java.util.HashSet; ++import java.util.Map; ++import java.util.Set; ++import net.minecraft.world.entity.FlyingMob; ++import net.minecraft.world.entity.PathfinderMob; ++import net.minecraft.world.entity.TamableAnimal; ++import net.minecraft.world.entity.ai.goal.Goal; ++import net.minecraft.world.entity.ambient.AmbientCreature; ++import net.minecraft.world.entity.animal.AbstractFish; ++import net.minecraft.world.entity.animal.AbstractGolem; ++import net.minecraft.world.entity.animal.AbstractSchoolingFish; ++import net.minecraft.world.entity.animal.Animal; ++import net.minecraft.world.entity.animal.Pufferfish; ++import net.minecraft.world.entity.animal.ShoulderRidingEntity; ++import net.minecraft.world.entity.animal.SnowGolem; ++import net.minecraft.world.entity.animal.WaterAnimal; ++import net.minecraft.world.entity.animal.camel.Camel; ++import net.minecraft.world.entity.animal.horse.AbstractChestedHorse; ++import net.minecraft.world.entity.boss.wither.WitherBoss; ++import net.minecraft.world.entity.monster.AbstractIllager; ++import net.minecraft.world.entity.monster.EnderMan; ++import net.minecraft.world.entity.monster.PatrollingMonster; ++import net.minecraft.world.entity.monster.RangedAttackMob; ++import net.minecraft.world.entity.monster.SpellcasterIllager; ++import net.minecraft.world.entity.monster.ZombifiedPiglin; ++import net.minecraft.world.entity.monster.breeze.Breeze; ++import net.minecraft.world.entity.monster.piglin.AbstractPiglin; ++import org.bukkit.NamespacedKey; ++import org.bukkit.entity.AbstractHorse; ++import org.bukkit.entity.AbstractSkeleton; ++import org.bukkit.entity.AbstractVillager; ++import org.bukkit.entity.Ageable; ++import org.bukkit.entity.Ambient; ++import org.bukkit.entity.Animals; ++import org.bukkit.entity.Bat; ++import org.bukkit.entity.Bee; ++import org.bukkit.entity.Blaze; ++import org.bukkit.entity.Cat; ++import org.bukkit.entity.CaveSpider; ++import org.bukkit.entity.ChestedHorse; ++import org.bukkit.entity.Chicken; ++import org.bukkit.entity.Cod; ++import org.bukkit.entity.Cow; ++import org.bukkit.entity.Creature; ++import org.bukkit.entity.Creeper; ++import org.bukkit.entity.Dolphin; ++import org.bukkit.entity.Donkey; ++import org.bukkit.entity.Drowned; ++import org.bukkit.entity.ElderGuardian; ++import org.bukkit.entity.EnderDragon; ++import org.bukkit.entity.Enderman; ++import org.bukkit.entity.Endermite; ++import org.bukkit.entity.Evoker; ++import org.bukkit.entity.Fish; ++import org.bukkit.entity.Flying; ++import org.bukkit.entity.Fox; ++import org.bukkit.entity.Ghast; ++import org.bukkit.entity.Giant; ++import org.bukkit.entity.Golem; ++import org.bukkit.entity.Guardian; ++import org.bukkit.entity.Hoglin; ++import org.bukkit.entity.Horse; ++import org.bukkit.entity.Husk; ++import org.bukkit.entity.Illager; ++import org.bukkit.entity.Illusioner; ++import org.bukkit.entity.IronGolem; ++import org.bukkit.entity.Llama; ++import org.bukkit.entity.MagmaCube; ++import org.bukkit.entity.Mob; ++import org.bukkit.entity.Monster; ++import org.bukkit.entity.Mule; ++import org.bukkit.entity.MushroomCow; ++import org.bukkit.entity.Ocelot; ++import org.bukkit.entity.Panda; ++import org.bukkit.entity.Parrot; ++import org.bukkit.entity.Phantom; ++import org.bukkit.entity.Pig; ++import org.bukkit.entity.PigZombie; ++import org.bukkit.entity.Piglin; ++import org.bukkit.entity.PiglinAbstract; ++import org.bukkit.entity.PiglinBrute; ++import org.bukkit.entity.Pillager; ++import org.bukkit.entity.PolarBear; ++import org.bukkit.entity.PufferFish; ++import org.bukkit.entity.Rabbit; ++import org.bukkit.entity.Raider; ++import org.bukkit.entity.Ravager; ++import org.bukkit.entity.Salmon; ++import org.bukkit.entity.Sheep; ++import org.bukkit.entity.Shulker; ++import org.bukkit.entity.Silverfish; ++import org.bukkit.entity.Skeleton; ++import org.bukkit.entity.SkeletonHorse; ++import org.bukkit.entity.Slime; ++import org.bukkit.entity.Snowman; ++import org.bukkit.entity.Spellcaster; ++import org.bukkit.entity.Spider; ++import org.bukkit.entity.Squid; ++import org.bukkit.entity.Stray; ++import org.bukkit.entity.Strider; ++import org.bukkit.entity.Tameable; ++import org.bukkit.entity.TraderLlama; ++import org.bukkit.entity.TropicalFish; ++import org.bukkit.entity.Turtle; ++import org.bukkit.entity.Vex; ++import org.bukkit.entity.Villager; ++import org.bukkit.entity.Vindicator; ++import org.bukkit.entity.WanderingTrader; ++import org.bukkit.entity.WaterMob; ++import org.bukkit.entity.Witch; ++import org.bukkit.entity.Wither; ++import org.bukkit.entity.WitherSkeleton; ++import org.bukkit.entity.Wolf; ++import org.bukkit.entity.Zoglin; ++import org.bukkit.entity.Zombie; ++import org.bukkit.entity.ZombieHorse; ++import org.bukkit.entity.ZombieVillager; ++ ++public class MobGoalHelper { ++ ++ private static final BiMap deobfuscationMap = HashBiMap.create(); ++ private static final Map, Class> entityClassCache = new HashMap<>(); ++ private static final Map, Class> bukkitMap = new HashMap<>(); ++ ++ static final Set ignored = new HashSet<>(); ++ ++ static { ++ // TODO these kinda should be checked on each release, in case obfuscation changes ++ deobfuscationMap.put("abstract_skeleton_1", "abstract_skeleton_melee"); ++ ++ ignored.add("goal_selector_1"); ++ ignored.add("goal_selector_2"); ++ ignored.add("selector_1"); ++ ignored.add("selector_2"); ++ ignored.add("wrapped"); ++ ++ bukkitMap.put(net.minecraft.world.entity.Mob.class, Mob.class); ++ bukkitMap.put(net.minecraft.world.entity.AgeableMob.class, Ageable.class); ++ bukkitMap.put(AmbientCreature.class, Ambient.class); ++ bukkitMap.put(Animal.class, Animals.class); ++ bukkitMap.put(net.minecraft.world.entity.ambient.Bat.class, Bat.class); ++ bukkitMap.put(net.minecraft.world.entity.animal.Bee.class, Bee.class); ++ bukkitMap.put(net.minecraft.world.entity.monster.Blaze.class, Blaze.class); ++ bukkitMap.put(net.minecraft.world.entity.animal.Cat.class, Cat.class); ++ bukkitMap.put(net.minecraft.world.entity.monster.CaveSpider.class, CaveSpider.class); ++ bukkitMap.put(net.minecraft.world.entity.animal.Chicken.class, Chicken.class); ++ bukkitMap.put(net.minecraft.world.entity.animal.Cod.class, Cod.class); ++ bukkitMap.put(net.minecraft.world.entity.animal.Cow.class, Cow.class); ++ bukkitMap.put(PathfinderMob.class, Creature.class); ++ bukkitMap.put(net.minecraft.world.entity.monster.Creeper.class, Creeper.class); ++ bukkitMap.put(net.minecraft.world.entity.animal.Dolphin.class, Dolphin.class); ++ bukkitMap.put(net.minecraft.world.entity.monster.Drowned.class, Drowned.class); ++ bukkitMap.put(net.minecraft.world.entity.boss.enderdragon.EnderDragon.class, EnderDragon.class); ++ bukkitMap.put(EnderMan.class, Enderman.class); ++ bukkitMap.put(net.minecraft.world.entity.monster.Endermite.class, Endermite.class); ++ bukkitMap.put(net.minecraft.world.entity.monster.Evoker.class, Evoker.class); ++ bukkitMap.put(AbstractFish.class, Fish.class); ++ bukkitMap.put(AbstractSchoolingFish.class, Fish.class); // close enough ++ bukkitMap.put(FlyingMob.class, Flying.class); ++ bukkitMap.put(net.minecraft.world.entity.animal.Fox.class, Fox.class); ++ bukkitMap.put(net.minecraft.world.entity.monster.Ghast.class, Ghast.class); ++ bukkitMap.put(net.minecraft.world.entity.monster.Giant.class, Giant.class); ++ bukkitMap.put(AbstractGolem.class, Golem.class); ++ bukkitMap.put(net.minecraft.world.entity.monster.Guardian.class, Guardian.class); ++ bukkitMap.put(net.minecraft.world.entity.monster.ElderGuardian.class, ElderGuardian.class); ++ bukkitMap.put(net.minecraft.world.entity.animal.horse.Horse.class, Horse.class); ++ bukkitMap.put(net.minecraft.world.entity.animal.horse.AbstractHorse.class, AbstractHorse.class); ++ bukkitMap.put(AbstractChestedHorse.class, ChestedHorse.class); ++ bukkitMap.put(net.minecraft.world.entity.animal.horse.Donkey.class, Donkey.class); ++ bukkitMap.put(net.minecraft.world.entity.animal.horse.Mule.class, Mule.class); ++ bukkitMap.put(net.minecraft.world.entity.animal.horse.SkeletonHorse.class, SkeletonHorse.class); ++ bukkitMap.put(net.minecraft.world.entity.animal.horse.ZombieHorse.class, ZombieHorse.class); ++ bukkitMap.put(Camel.class, org.bukkit.entity.Camel.class); ++ bukkitMap.put(AbstractIllager.class, Illager.class); ++ bukkitMap.put(net.minecraft.world.entity.monster.Illusioner.class, Illusioner.class); ++ bukkitMap.put(SpellcasterIllager.class, Spellcaster.class); ++ bukkitMap.put(net.minecraft.world.entity.animal.IronGolem.class, IronGolem.class); ++ bukkitMap.put(net.minecraft.world.entity.animal.horse.Llama.class, Llama.class); ++ bukkitMap.put(net.minecraft.world.entity.animal.horse.TraderLlama.class, TraderLlama.class); ++ bukkitMap.put(net.minecraft.world.entity.monster.MagmaCube.class, MagmaCube.class); ++ bukkitMap.put(net.minecraft.world.entity.monster.Monster.class, Monster.class); ++ bukkitMap.put(PatrollingMonster.class, Raider.class); // close enough ++ bukkitMap.put(net.minecraft.world.entity.animal.MushroomCow.class, MushroomCow.class); ++ bukkitMap.put(net.minecraft.world.entity.animal.Ocelot.class, Ocelot.class); ++ bukkitMap.put(net.minecraft.world.entity.animal.Panda.class, Panda.class); ++ bukkitMap.put(net.minecraft.world.entity.animal.Parrot.class, Parrot.class); ++ bukkitMap.put(ShoulderRidingEntity.class, Parrot.class); // close enough ++ bukkitMap.put(net.minecraft.world.entity.monster.Phantom.class, Phantom.class); ++ bukkitMap.put(net.minecraft.world.entity.animal.Pig.class, Pig.class); ++ bukkitMap.put(ZombifiedPiglin.class, PigZombie.class); ++ bukkitMap.put(net.minecraft.world.entity.monster.Pillager.class, Pillager.class); ++ bukkitMap.put(net.minecraft.world.entity.animal.PolarBear.class, PolarBear.class); ++ bukkitMap.put(Pufferfish.class, PufferFish.class); ++ bukkitMap.put(net.minecraft.world.entity.animal.Rabbit.class, Rabbit.class); ++ bukkitMap.put(net.minecraft.world.entity.raid.Raider.class, Raider.class); ++ bukkitMap.put(net.minecraft.world.entity.monster.Ravager.class, Ravager.class); ++ bukkitMap.put(net.minecraft.world.entity.animal.Salmon.class, Salmon.class); ++ bukkitMap.put(net.minecraft.world.entity.animal.Sheep.class, Sheep.class); ++ bukkitMap.put(net.minecraft.world.entity.monster.Shulker.class, Shulker.class); ++ bukkitMap.put(net.minecraft.world.entity.monster.Silverfish.class, Silverfish.class); ++ bukkitMap.put(net.minecraft.world.entity.monster.Skeleton.class, Skeleton.class); ++ bukkitMap.put(net.minecraft.world.entity.monster.AbstractSkeleton.class, AbstractSkeleton.class); ++ bukkitMap.put(net.minecraft.world.entity.monster.Stray.class, Stray.class); ++ bukkitMap.put(net.minecraft.world.entity.monster.WitherSkeleton.class, WitherSkeleton.class); ++ bukkitMap.put(net.minecraft.world.entity.monster.Slime.class, Slime.class); ++ bukkitMap.put(SnowGolem.class, Snowman.class); ++ bukkitMap.put(net.minecraft.world.entity.monster.Spider.class, Spider.class); ++ bukkitMap.put(net.minecraft.world.entity.animal.Squid.class, Squid.class); ++ bukkitMap.put(TamableAnimal.class, Tameable.class); ++ bukkitMap.put(net.minecraft.world.entity.animal.TropicalFish.class, TropicalFish.class); ++ bukkitMap.put(net.minecraft.world.entity.animal.Turtle.class, Turtle.class); ++ bukkitMap.put(net.minecraft.world.entity.monster.Vex.class, Vex.class); ++ bukkitMap.put(net.minecraft.world.entity.npc.Villager.class, Villager.class); ++ bukkitMap.put(net.minecraft.world.entity.npc.AbstractVillager.class, AbstractVillager.class); ++ bukkitMap.put(net.minecraft.world.entity.npc.WanderingTrader.class, WanderingTrader.class); ++ bukkitMap.put(net.minecraft.world.entity.monster.Vindicator.class, Vindicator.class); ++ bukkitMap.put(WaterAnimal.class, WaterMob.class); ++ bukkitMap.put(net.minecraft.world.entity.monster.Witch.class, Witch.class); ++ bukkitMap.put(WitherBoss.class, Wither.class); ++ bukkitMap.put(net.minecraft.world.entity.animal.Wolf.class, Wolf.class); ++ bukkitMap.put(net.minecraft.world.entity.monster.Zombie.class, Zombie.class); ++ bukkitMap.put(net.minecraft.world.entity.monster.Husk.class, Husk.class); ++ bukkitMap.put(net.minecraft.world.entity.monster.ZombieVillager.class, ZombieVillager.class); ++ bukkitMap.put(net.minecraft.world.entity.monster.hoglin.Hoglin.class, Hoglin.class); ++ bukkitMap.put(net.minecraft.world.entity.monster.piglin.Piglin.class, Piglin.class); ++ bukkitMap.put(AbstractPiglin.class, PiglinAbstract.class); ++ bukkitMap.put(net.minecraft.world.entity.monster.piglin.PiglinBrute.class, PiglinBrute.class); ++ bukkitMap.put(net.minecraft.world.entity.monster.Strider.class, Strider.class); ++ bukkitMap.put(net.minecraft.world.entity.monster.Zoglin.class, Zoglin.class); ++ bukkitMap.put(net.minecraft.world.entity.GlowSquid.class, org.bukkit.entity.GlowSquid.class); ++ bukkitMap.put(net.minecraft.world.entity.animal.axolotl.Axolotl.class, org.bukkit.entity.Axolotl.class); ++ bukkitMap.put(net.minecraft.world.entity.animal.goat.Goat.class, org.bukkit.entity.Goat.class); ++ bukkitMap.put(net.minecraft.world.entity.animal.frog.Frog.class, org.bukkit.entity.Frog.class); ++ bukkitMap.put(net.minecraft.world.entity.animal.frog.Tadpole.class, org.bukkit.entity.Tadpole.class); ++ bukkitMap.put(net.minecraft.world.entity.monster.warden.Warden.class, org.bukkit.entity.Warden.class); ++ bukkitMap.put(net.minecraft.world.entity.animal.allay.Allay.class, org.bukkit.entity.Allay.class); ++ bukkitMap.put(net.minecraft.world.entity.animal.sniffer.Sniffer.class, org.bukkit.entity.Sniffer.class); ++ bukkitMap.put(Breeze.class, org.bukkit.entity.Breeze.class); ++ bukkitMap.put(net.minecraft.world.entity.animal.armadillo.Armadillo.class, org.bukkit.entity.Armadillo.class); ++ bukkitMap.put(net.minecraft.world.entity.monster.Bogged.class, org.bukkit.entity.Bogged.class); ++ bukkitMap.put(net.minecraft.world.entity.monster.creaking.Creaking.class, org.bukkit.entity.Creaking.class); ++ bukkitMap.put(net.minecraft.world.entity.monster.creaking.CreakingTransient.class, org.bukkit.entity.CreakingTransient.class); ++ bukkitMap.put(net.minecraft.world.entity.animal.AgeableWaterCreature.class, org.bukkit.entity.Squid.class); // close enough ++ } ++ ++ public static String getUsableName(Class clazz) { ++ String name = io.papermc.paper.util.MappingEnvironment.reobf() ? ObfHelper.INSTANCE.deobfClassName(clazz.getName()) : clazz.getName(); ++ name = name.substring(name.lastIndexOf(".") + 1); ++ boolean flag = false; ++ // inner classes ++ if (name.contains("$")) { ++ String cut = name.substring(name.indexOf("$") + 1); ++ if (cut.length() <= 2) { ++ name = name.replace("Entity", ""); ++ name = name.replace("$", "_"); ++ flag = true; ++ } else { ++ // mapped, wooo ++ name = cut; ++ } ++ } ++ name = name.replace("PathfinderGoal", ""); ++ name = name.replace("TargetGoal", ""); ++ name = name.replace("Goal", ""); ++ StringBuilder sb = new StringBuilder(); ++ for (char c : name.toCharArray()) { ++ if (c >= 'A' && c <= 'Z') { ++ sb.append("_"); ++ sb.append(Character.toLowerCase(c)); ++ } else { ++ sb.append(c); ++ } ++ } ++ name = sb.toString(); ++ name = name.replaceFirst("_", ""); ++ ++ if (flag && !deobfuscationMap.containsKey(name.toLowerCase(java.util.Locale.ROOT)) && !ignored.contains(name)) { ++ System.out.println("need to map " + clazz.getName() + " (" + name.toLowerCase(java.util.Locale.ROOT) + ")"); ++ } ++ ++ // did we rename this key? ++ return deobfuscationMap.getOrDefault(name, name); ++ } ++ ++ public static EnumSet vanillaToPaper(Goal goal) { ++ EnumSet goals = EnumSet.noneOf(GoalType.class); ++ for (GoalType type : GoalType.values()) { ++ if (goal.getFlags().hasElement(paperToVanilla(type))) { ++ goals.add(type); ++ } ++ } ++ return goals; ++ } ++ ++ public static GoalType vanillaToPaper(Goal.Flag type) { ++ switch (type) { ++ case MOVE: ++ return GoalType.MOVE; ++ case LOOK: ++ return GoalType.LOOK; ++ case JUMP: ++ return GoalType.JUMP; ++ case UNKNOWN_BEHAVIOR: ++ return GoalType.UNKNOWN_BEHAVIOR; ++ case TARGET: ++ return GoalType.TARGET; ++ default: ++ throw new IllegalArgumentException("Unknown vanilla mob goal type " + type.name()); ++ } ++ } ++ ++ public static EnumSet paperToVanilla(EnumSet types) { ++ EnumSet goals = EnumSet.noneOf(Goal.Flag.class); ++ for (GoalType type : types) { ++ goals.add(paperToVanilla(type)); ++ } ++ return goals; ++ } ++ ++ public static Goal.Flag paperToVanilla(GoalType type) { ++ switch (type) { ++ case MOVE: ++ return Goal.Flag.MOVE; ++ case LOOK: ++ return Goal.Flag.LOOK; ++ case JUMP: ++ return Goal.Flag.JUMP; ++ case UNKNOWN_BEHAVIOR: ++ return Goal.Flag.UNKNOWN_BEHAVIOR; ++ case TARGET: ++ return Goal.Flag.TARGET; ++ default: ++ throw new IllegalArgumentException("Unknown paper mob goal type " + type.name()); ++ } ++ } ++ ++ public static GoalKey getKey(Class goalClass) { ++ String name = getUsableName(goalClass); ++ if (ignored.contains(name)) { ++ //noinspection unchecked ++ return (GoalKey) GoalKey.of(Mob.class, NamespacedKey.minecraft(name)); ++ } ++ return GoalKey.of(getEntity(goalClass), NamespacedKey.minecraft(name)); ++ } ++ ++ public static Class getEntity(Class goalClass) { ++ //noinspection unchecked ++ return (Class) entityClassCache.computeIfAbsent(goalClass, key -> { ++ for (Constructor ctor : key.getDeclaredConstructors()) { ++ for (int i = 0; i < ctor.getParameterCount(); i++) { ++ Class param = ctor.getParameterTypes()[i]; ++ if (net.minecraft.world.entity.Mob.class.isAssignableFrom(param)) { ++ //noinspection unchecked ++ return toBukkitClass((Class) param); ++ } else if (RangedAttackMob.class.isAssignableFrom(param)) { ++ return RangedEntity.class; ++ } ++ } ++ } ++ throw new RuntimeException("Can't figure out applicable entity for mob goal " + goalClass); // maybe just return EntityInsentient? ++ }); ++ } ++ ++ public static Class toBukkitClass(Class nmsClass) { ++ Class bukkitClass = bukkitMap.get(nmsClass); ++ if (bukkitClass == null) { ++ throw new RuntimeException("Can't figure out applicable bukkit entity for nms entity " + nmsClass); // maybe just return Mob? ++ } ++ return bukkitClass; ++ } ++} +diff --git a/src/main/java/com/destroystokyo/paper/entity/ai/PaperCustomGoal.java b/src/main/java/com/destroystokyo/paper/entity/ai/PaperCustomGoal.java +new file mode 100644 +index 0000000000000000000000000000000000000000..26c745dd9ccdfdd5c5039f2acc5201b9b91fb274 +--- /dev/null ++++ b/src/main/java/com/destroystokyo/paper/entity/ai/PaperCustomGoal.java +@@ -0,0 +1,53 @@ ++package com.destroystokyo.paper.entity.ai; ++ ++import org.bukkit.entity.Mob; ++ ++/** ++ * Wraps api in vanilla ++ */ ++public class PaperCustomGoal extends net.minecraft.world.entity.ai.goal.Goal { ++ ++ private final Goal handle; ++ ++ public PaperCustomGoal(Goal handle) { ++ this.handle = handle; ++ ++ this.setFlags(MobGoalHelper.paperToVanilla(handle.getTypes())); ++ if (this.getFlags().size() == 0) { ++ this.getFlags().addUnchecked(Flag.UNKNOWN_BEHAVIOR); ++ } ++ } ++ ++ @Override ++ public boolean canUse() { ++ return handle.shouldActivate(); ++ } ++ ++ @Override ++ public boolean canContinueToUse() { ++ return handle.shouldStayActive(); ++ } ++ ++ @Override ++ public void start() { ++ handle.start(); ++ } ++ ++ @Override ++ public void stop() { ++ handle.stop(); ++ } ++ ++ @Override ++ public void tick() { ++ handle.tick(); ++ } ++ ++ public Goal getHandle() { ++ return handle; ++ } ++ ++ public GoalKey getKey() { ++ return handle.getKey(); ++ } ++} +diff --git a/src/main/java/com/destroystokyo/paper/entity/ai/PaperMobGoals.java b/src/main/java/com/destroystokyo/paper/entity/ai/PaperMobGoals.java +new file mode 100644 +index 0000000000000000000000000000000000000000..e8a427ea777af040d0e2b9cc0ba2a80b9176d026 +--- /dev/null ++++ b/src/main/java/com/destroystokyo/paper/entity/ai/PaperMobGoals.java +@@ -0,0 +1,226 @@ ++package com.destroystokyo.paper.entity.ai; ++ ++import java.util.Collection; ++import java.util.EnumSet; ++import java.util.HashSet; ++import java.util.LinkedList; ++import java.util.List; ++import java.util.Set; ++import net.minecraft.world.entity.ai.goal.GoalSelector; ++import net.minecraft.world.entity.ai.goal.WrappedGoal; ++import org.bukkit.craftbukkit.entity.CraftMob; ++import org.bukkit.entity.Mob; ++import org.jspecify.annotations.NullMarked; ++ ++@NullMarked ++public class PaperMobGoals implements MobGoals { ++ ++ @Override ++ public void addGoal(T mob, int priority, Goal goal) { ++ CraftMob craftMob = (CraftMob) mob; ++ net.minecraft.world.entity.ai.goal.Goal mojangGoal; ++ ++ if (goal instanceof PaperVanillaGoal vanillaGoal) { ++ mojangGoal = vanillaGoal.getHandle(); ++ } else { ++ mojangGoal = new PaperCustomGoal<>(goal); ++ } ++ ++ getHandle(craftMob, goal.getTypes()).addGoal(priority, mojangGoal); ++ } ++ ++ @Override ++ public void removeGoal(T mob, Goal goal) { ++ CraftMob craftMob = (CraftMob) mob; ++ if (goal instanceof PaperCustomGoal) { ++ getHandle(craftMob, goal.getTypes()).removeGoal((net.minecraft.world.entity.ai.goal.Goal) goal); ++ } else if (goal instanceof PaperVanillaGoal) { ++ getHandle(craftMob, goal.getTypes()).removeGoal(((PaperVanillaGoal) goal).getHandle()); ++ } else { ++ List toRemove = new LinkedList<>(); ++ for (WrappedGoal item : getHandle(craftMob, goal.getTypes()).getAvailableGoals()) { ++ if (item.getGoal() instanceof PaperCustomGoal) { ++ //noinspection unchecked ++ if (((PaperCustomGoal) item.getGoal()).getHandle() == goal) { ++ toRemove.add(item.getGoal()); ++ } ++ } ++ } ++ ++ for (net.minecraft.world.entity.ai.goal.Goal g : toRemove) { ++ getHandle(craftMob, goal.getTypes()).removeGoal(g); ++ } ++ } ++ } ++ ++ @Override ++ public void removeAllGoals(T mob) { ++ for (GoalType type : GoalType.values()) { ++ removeAllGoals(mob, type); ++ } ++ } ++ ++ @Override ++ public void removeAllGoals(T mob, GoalType type) { ++ for (Goal goal : getAllGoals(mob, type)) { ++ removeGoal(mob, goal); ++ } ++ } ++ ++ @Override ++ public void removeGoal(T mob, GoalKey key) { ++ for (Goal goal : getGoals(mob, key)) { ++ removeGoal(mob, goal); ++ } ++ } ++ ++ @Override ++ public boolean hasGoal(T mob, GoalKey key) { ++ for (Goal g : getAllGoals(mob)) { ++ if (g.getKey().equals(key)) { ++ return true; ++ } ++ } ++ return false; ++ } ++ ++ @Override ++ public Goal getGoal(T mob, GoalKey key) { ++ for (Goal g : getAllGoals(mob)) { ++ if (g.getKey().equals(key)) { ++ return g; ++ } ++ } ++ return null; ++ } ++ ++ @Override ++ public Collection> getGoals(T mob, GoalKey key) { ++ Set> goals = new HashSet<>(); ++ for (Goal g : getAllGoals(mob)) { ++ if (g.getKey().equals(key)) { ++ goals.add(g); ++ } ++ } ++ return goals; ++ } ++ ++ @Override ++ public Collection> getAllGoals(T mob) { ++ Set> goals = new HashSet<>(); ++ for (GoalType type : GoalType.values()) { ++ goals.addAll(getAllGoals(mob, type)); ++ } ++ return goals; ++ } ++ ++ @Override ++ public Collection> getAllGoals(T mob, GoalType type) { ++ CraftMob craftMob = (CraftMob) mob; ++ Set> goals = new HashSet<>(); ++ for (WrappedGoal item : getHandle(craftMob, type).getAvailableGoals()) { ++ if (!item.getGoal().getFlags().hasElement(MobGoalHelper.paperToVanilla(type))) { ++ continue; ++ } ++ ++ if (item.getGoal() instanceof PaperCustomGoal) { ++ //noinspection unchecked ++ goals.add(((PaperCustomGoal) item.getGoal()).getHandle()); ++ } else { ++ goals.add(item.getGoal().asPaperVanillaGoal()); ++ } ++ } ++ return goals; ++ } ++ ++ @Override ++ public Collection> getAllGoalsWithout(T mob, GoalType type) { ++ CraftMob craftMob = (CraftMob) mob; ++ Set> goals = new HashSet<>(); ++ for (GoalType internalType : GoalType.values()) { ++ if (internalType == type) { ++ continue; ++ } ++ for (WrappedGoal item : getHandle(craftMob, internalType).getAvailableGoals()) { ++ if (item.getGoal().getFlags().hasElement(MobGoalHelper.paperToVanilla(type))) { ++ continue; ++ } ++ ++ if (item.getGoal() instanceof PaperCustomGoal) { ++ //noinspection unchecked ++ goals.add(((PaperCustomGoal) item.getGoal()).getHandle()); ++ } else { ++ goals.add(item.getGoal().asPaperVanillaGoal()); ++ } ++ } ++ } ++ return goals; ++ } ++ ++ @Override ++ public Collection> getRunningGoals(T mob) { ++ Set> goals = new HashSet<>(); ++ for (GoalType type : GoalType.values()) { ++ goals.addAll(getRunningGoals(mob, type)); ++ } ++ return goals; ++ } ++ ++ @Override ++ public Collection> getRunningGoals(T mob, GoalType type) { ++ CraftMob craftMob = (CraftMob) mob; ++ Set> goals = new HashSet<>(); ++ getHandle(craftMob, type).getAvailableGoals() ++ .stream().filter(WrappedGoal::isRunning) ++ .filter(item -> item.getGoal().getFlags().hasElement(MobGoalHelper.paperToVanilla(type))) ++ .forEach(item -> { ++ if (item.getGoal() instanceof PaperCustomGoal) { ++ //noinspection unchecked ++ goals.add(((PaperCustomGoal) item.getGoal()).getHandle()); ++ } else { ++ goals.add(item.getGoal().asPaperVanillaGoal()); ++ } ++ }); ++ return goals; ++ } ++ ++ @Override ++ public Collection> getRunningGoalsWithout(T mob, GoalType type) { ++ CraftMob craftMob = (CraftMob) mob; ++ Set> goals = new HashSet<>(); ++ for (GoalType internalType : GoalType.values()) { ++ if (internalType == type) { ++ continue; ++ } ++ getHandle(craftMob, internalType).getAvailableGoals() ++ .stream() ++ .filter(WrappedGoal::isRunning) ++ .filter(item -> !item.getGoal().getFlags().hasElement(MobGoalHelper.paperToVanilla(type))) ++ .forEach(item -> { ++ if (item.getGoal() instanceof PaperCustomGoal) { ++ //noinspection unchecked ++ goals.add(((PaperCustomGoal) item.getGoal()).getHandle()); ++ } else { ++ goals.add(item.getGoal().asPaperVanillaGoal()); ++ } ++ }); ++ } ++ return goals; ++ } ++ ++ private GoalSelector getHandle(CraftMob mob, EnumSet types) { ++ if (types.contains(GoalType.TARGET)) { ++ return mob.getHandle().targetSelector; ++ } else { ++ return mob.getHandle().goalSelector; ++ } ++ } ++ ++ private GoalSelector getHandle(CraftMob mob, GoalType type) { ++ if (type == GoalType.TARGET) { ++ return mob.getHandle().targetSelector; ++ } else { ++ return mob.getHandle().goalSelector; ++ } ++ } ++} +diff --git a/src/main/java/com/destroystokyo/paper/entity/ai/PaperVanillaGoal.java b/src/main/java/com/destroystokyo/paper/entity/ai/PaperVanillaGoal.java +new file mode 100644 +index 0000000000000000000000000000000000000000..b5c594a5499556ad452d9939c75e150af8252e90 +--- /dev/null ++++ b/src/main/java/com/destroystokyo/paper/entity/ai/PaperVanillaGoal.java +@@ -0,0 +1,61 @@ ++package com.destroystokyo.paper.entity.ai; ++ ++import java.util.EnumSet; ++import net.minecraft.world.entity.ai.goal.Goal; ++import org.bukkit.entity.Mob; ++ ++/** ++ * Wraps vanilla in api ++ */ ++public class PaperVanillaGoal implements VanillaGoal { ++ ++ private final Goal handle; ++ private final GoalKey key; ++ ++ private final EnumSet types; ++ ++ public PaperVanillaGoal(Goal handle) { ++ this.handle = handle; ++ this.key = MobGoalHelper.getKey(handle.getClass()); ++ this.types = MobGoalHelper.vanillaToPaper(handle); ++ } ++ ++ public Goal getHandle() { ++ return handle; ++ } ++ ++ @Override ++ public boolean shouldActivate() { ++ return handle.canUse(); ++ } ++ ++ @Override ++ public boolean shouldStayActive() { ++ return handle.canContinueToUse(); ++ } ++ ++ @Override ++ public void start() { ++ handle.start(); ++ } ++ ++ @Override ++ public void stop() { ++ handle.stop(); ++ } ++ ++ @Override ++ public void tick() { ++ handle.tick(); ++ } ++ ++ @Override ++ public GoalKey getKey() { ++ return key; ++ } ++ ++ @Override ++ public EnumSet getTypes() { ++ return types; ++ } ++} +diff --git a/src/main/java/net/minecraft/world/entity/ai/goal/Goal.java b/src/main/java/net/minecraft/world/entity/ai/goal/Goal.java +index a8d6d7054110b5d95830296699f004418dae10db..acc25b08ed3b9f978229fa017d23f9fa0da519e3 100644 +--- a/src/main/java/net/minecraft/world/entity/ai/goal/Goal.java ++++ b/src/main/java/net/minecraft/world/entity/ai/goal/Goal.java +@@ -62,7 +62,19 @@ public abstract class Goal { + return (ServerLevel)world; + } + ++ // Paper start - Mob goal api ++ private com.destroystokyo.paper.entity.ai.PaperVanillaGoal vanillaGoal; ++ public com.destroystokyo.paper.entity.ai.Goal asPaperVanillaGoal() { ++ if(this.vanillaGoal == null) { ++ this.vanillaGoal = new com.destroystokyo.paper.entity.ai.PaperVanillaGoal<>(this); ++ } ++ //noinspection unchecked ++ return (com.destroystokyo.paper.entity.ai.Goal) this.vanillaGoal; ++ } ++ // Paper end - Mob goal api ++ + public static enum Flag { ++ UNKNOWN_BEHAVIOR, // Paper - add UNKNOWN_BEHAVIOR + MOVE, + LOOK, + JUMP, +diff --git a/src/main/java/org/bukkit/craftbukkit/CraftServer.java b/src/main/java/org/bukkit/craftbukkit/CraftServer.java +index 6b042419af11730b0159c030ea1881dcfbb37dcd..8339da59b7a73411f45b77f6eb93e8ad65a1ef48 100644 +--- a/src/main/java/org/bukkit/craftbukkit/CraftServer.java ++++ b/src/main/java/org/bukkit/craftbukkit/CraftServer.java +@@ -2956,5 +2956,11 @@ public final class CraftServer implements Server { + public boolean isStopping() { + return net.minecraft.server.MinecraftServer.getServer().hasStopped(); + } ++ ++ private com.destroystokyo.paper.entity.ai.MobGoals mobGoals = new com.destroystokyo.paper.entity.ai.PaperMobGoals(); ++ @Override ++ public com.destroystokyo.paper.entity.ai.MobGoals getMobGoals() { ++ return mobGoals; ++ } + // Paper end + } diff --git a/patches/server/0350-Set-cap-on-JDK-per-thread-native-byte-buffer-cache.patch b/patches/server/0350-Set-cap-on-JDK-per-thread-native-byte-buffer-cache.patch deleted file mode 100644 index 46f51a9f451e..000000000000 --- a/patches/server/0350-Set-cap-on-JDK-per-thread-native-byte-buffer-cache.patch +++ /dev/null @@ -1,30 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Aikar -Date: Mon, 4 May 2020 01:08:56 -0400 -Subject: [PATCH] Set cap on JDK per-thread native byte buffer cache - -See: https://www.evanjones.ca/java-bytebuffer-leak.html - -This is potentially a source of lots of native memory usage. - -We are clearly seeing native usage upwards to 1-4GB which doesn't make sense. - -Region File usage fixed in previous patch should of tecnically only been somewhat -temporary until GC finally gets it some time later, but between all the various -plugins doing IO on various threads, this hidden detail of the JDK could be -keeping long lived large direct buffers in cache. - -Set system properly at server startup if not set already to help protect from this. - -diff --git a/src/main/java/org/bukkit/craftbukkit/Main.java b/src/main/java/org/bukkit/craftbukkit/Main.java -index ed167d0d399924d54d9ff99c10ab8ee093efc149..168cbb239ac5d632908f2b0aca82cbcfdc35651f 100644 ---- a/src/main/java/org/bukkit/craftbukkit/Main.java -+++ b/src/main/java/org/bukkit/craftbukkit/Main.java -@@ -27,6 +27,7 @@ public class Main { - } - // Paper end - // Todo: Installation script -+ if (System.getProperty("jdk.nio.maxCachedBufferSize") == null) System.setProperty("jdk.nio.maxCachedBufferSize", "262144"); // Paper - cap per-thread NIO cache size; https://www.evanjones.ca/java-bytebuffer-leak.html - OptionParser parser = new OptionParser() { - { - this.acceptsAll(Main.asList("?", "help"), "Show the help"); diff --git a/patches/server/0351-Add-villager-reputation-API.patch b/patches/server/0351-Add-villager-reputation-API.patch new file mode 100644 index 000000000000..f08d60eeaede --- /dev/null +++ b/patches/server/0351-Add-villager-reputation-API.patch @@ -0,0 +1,122 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Mariell Hoversholm +Date: Wed, 22 Apr 2020 23:29:20 +0200 +Subject: [PATCH] Add villager reputation API + +== AT == +public net.minecraft.world.entity.ai.gossip.GossipContainer$EntityGossips +public net.minecraft.world.entity.ai.gossip.GossipContainer$EntityGossips ()V +public net.minecraft.world.entity.ai.gossip.GossipContainer gossips + +diff --git a/src/main/java/net/minecraft/world/entity/ai/gossip/GossipContainer.java b/src/main/java/net/minecraft/world/entity/ai/gossip/GossipContainer.java +index f06a5f0d9c5c877ddf963254d3124f5fe2d67282..aa32804bc9affe9a615d3ffaa513f6f09aab3f32 100644 +--- a/src/main/java/net/minecraft/world/entity/ai/gossip/GossipContainer.java ++++ b/src/main/java/net/minecraft/world/entity/ai/gossip/GossipContainer.java +@@ -216,6 +216,43 @@ public class GossipContainer { + public void remove(GossipType gossipType) { + this.entries.removeInt(gossipType); + } ++ ++ // Paper start - Add villager reputation API ++ private static final GossipType[] TYPES = GossipType.values(); ++ public com.destroystokyo.paper.entity.villager.Reputation getPaperReputation() { ++ Map map = new java.util.EnumMap<>(com.destroystokyo.paper.entity.villager.ReputationType.class); ++ for (Object2IntMap.Entry type : this.entries.object2IntEntrySet()) { ++ map.put(toApi(type.getKey()), type.getIntValue()); ++ } ++ ++ return new com.destroystokyo.paper.entity.villager.Reputation(map); ++ } ++ ++ public void assignFromPaperReputation(com.destroystokyo.paper.entity.villager.Reputation rep) { ++ for (GossipType type : TYPES) { ++ com.destroystokyo.paper.entity.villager.ReputationType api = toApi(type); ++ ++ if (rep.hasReputationSet(api)) { ++ int reputation = rep.getReputation(api); ++ if (reputation == 0) { ++ this.entries.removeInt(type); ++ } else { ++ this.entries.put(type, reputation); ++ } ++ } ++ } ++ } ++ ++ private static com.destroystokyo.paper.entity.villager.ReputationType toApi(GossipType type) { ++ return switch (type) { ++ case MAJOR_NEGATIVE -> com.destroystokyo.paper.entity.villager.ReputationType.MAJOR_NEGATIVE; ++ case MINOR_NEGATIVE -> com.destroystokyo.paper.entity.villager.ReputationType.MINOR_NEGATIVE; ++ case MINOR_POSITIVE -> com.destroystokyo.paper.entity.villager.ReputationType.MINOR_POSITIVE; ++ case MAJOR_POSITIVE -> com.destroystokyo.paper.entity.villager.ReputationType.MAJOR_POSITIVE; ++ case TRADING -> com.destroystokyo.paper.entity.villager.ReputationType.TRADING; ++ }; ++ } ++ // Paper end - Add villager reputation API + } + + static record GossipEntry(UUID target, GossipType type, int value) { +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftVillager.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftVillager.java +index ded319d10a5907b96abfd5620036b66a9aa3b3f7..45c44c46edee9f46b8e197f1f54ea2779bf1184c 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftVillager.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftVillager.java +@@ -21,6 +21,13 @@ import org.bukkit.entity.ZombieVillager; + import org.bukkit.event.entity.CreatureSpawnEvent; + import org.bukkit.event.entity.EntityTransformEvent; + ++// Paper start ++import com.destroystokyo.paper.entity.villager.Reputation; ++import com.google.common.collect.Maps; ++import java.util.Map; ++import java.util.UUID; ++// Paper end ++ + public class CraftVillager extends CraftAbstractVillager implements Villager { + + public CraftVillager(CraftServer server, net.minecraft.world.entity.npc.Villager entity) { +@@ -299,4 +306,45 @@ public class CraftVillager extends CraftAbstractVillager implements Villager { + return this.getKey().hashCode(); + } + } ++ ++ // Paper start - Add villager reputation API ++ @Override ++ public Reputation getReputation(UUID uniqueId) { ++ net.minecraft.world.entity.ai.gossip.GossipContainer.EntityGossips rep = getHandle().getGossips().gossips.get(uniqueId); ++ if (rep == null) { ++ return new Reputation(new java.util.EnumMap<>(com.destroystokyo.paper.entity.villager.ReputationType.class)); ++ } ++ ++ return rep.getPaperReputation(); ++ } ++ ++ @Override ++ public Map getReputations() { ++ return getHandle().getGossips().gossips.entrySet() ++ .stream() ++ .collect(java.util.stream.Collectors.toMap(Map.Entry::getKey, entry -> entry.getValue().getPaperReputation())); ++ } ++ ++ @Override ++ public void setReputation(UUID uniqueId, Reputation reputation) { ++ net.minecraft.world.entity.ai.gossip.GossipContainer.EntityGossips nmsReputation = ++ getHandle().getGossips().gossips.computeIfAbsent( ++ uniqueId, ++ key -> new net.minecraft.world.entity.ai.gossip.GossipContainer.EntityGossips() ++ ); ++ nmsReputation.assignFromPaperReputation(reputation); ++ } ++ ++ @Override ++ public void setReputations(Map reputations) { ++ for (Map.Entry entry : reputations.entrySet()) { ++ setReputation(entry.getKey(), entry.getValue()); ++ } ++ } ++ ++ @Override ++ public void clearReputations() { ++ getHandle().getGossips().gossips.clear(); ++ } ++ // Paper end + } diff --git a/patches/server/0351-misc-debugging-dumps.patch b/patches/server/0351-misc-debugging-dumps.patch deleted file mode 100644 index b339217a999b..000000000000 --- a/patches/server/0351-misc-debugging-dumps.patch +++ /dev/null @@ -1,118 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Shane Freeder -Date: Thu, 18 Feb 2021 20:23:28 +0000 -Subject: [PATCH] misc debugging dumps - - -diff --git a/src/main/java/io/papermc/paper/util/TraceUtil.java b/src/main/java/io/papermc/paper/util/TraceUtil.java -new file mode 100644 -index 0000000000000000000000000000000000000000..479bb92d159f33c54c2d9c39d8a63aa9e74d95b9 ---- /dev/null -+++ b/src/main/java/io/papermc/paper/util/TraceUtil.java -@@ -0,0 +1,25 @@ -+package io.papermc.paper.util; -+ -+import org.bukkit.Bukkit; -+ -+public final class TraceUtil { -+ -+ public static void dumpTraceForThread(Thread thread, String reason) { -+ Bukkit.getLogger().warning(thread.getName() + ": " + reason); -+ StackTraceElement[] trace = StacktraceDeobfuscator.INSTANCE.deobfuscateStacktrace(thread.getStackTrace()); -+ for (StackTraceElement traceElement : trace) { -+ Bukkit.getLogger().warning("\tat " + traceElement); -+ } -+ } -+ -+ public static void dumpTraceForThread(String reason) { -+ final Throwable thr = new Throwable(reason); -+ StacktraceDeobfuscator.INSTANCE.deobfuscateThrowable(thr); -+ thr.printStackTrace(); -+ } -+ -+ public static void printStackTrace(Throwable thr) { -+ StacktraceDeobfuscator.INSTANCE.deobfuscateThrowable(thr); -+ thr.printStackTrace(); -+ } -+} -diff --git a/src/main/java/net/minecraft/commands/Commands.java b/src/main/java/net/minecraft/commands/Commands.java -index e25fc35716aff1d1805884b18f67b0eb33d8c05c..8ca9ac8eff9d605baa878ca24e165ac5642bf160 100644 ---- a/src/main/java/net/minecraft/commands/Commands.java -+++ b/src/main/java/net/minecraft/commands/Commands.java -@@ -340,7 +340,7 @@ public class Commands { - } catch (Exception exception) { - MutableComponent ichatmutablecomponent = Component.literal(exception.getMessage() == null ? exception.getClass().getName() : exception.getMessage()); - -- if (Commands.LOGGER.isDebugEnabled()) { -+ if (commandlistenerwrapper.getServer().isDebugging() || Commands.LOGGER.isDebugEnabled()) { // Paper - Debugging - Commands.LOGGER.error("Command exception: /{}", s, exception); - StackTraceElement[] astacktraceelement = exception.getStackTrace(); - -diff --git a/src/main/java/net/minecraft/server/MinecraftServer.java b/src/main/java/net/minecraft/server/MinecraftServer.java -index 53bb62c1dcb487be915759d22e06aea80be54f36..8b17df3d18fe9acc1a7b10c6809886da0143f8c5 100644 ---- a/src/main/java/net/minecraft/server/MinecraftServer.java -+++ b/src/main/java/net/minecraft/server/MinecraftServer.java -@@ -915,6 +915,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop +Date: Fri, 10 Nov 2017 23:03:12 -0500 +Subject: [PATCH] ExperienceOrb merging/stacking API and fixes + +Adds an option for maximum exp value when merging orbs + +Adds ExperienceOrbMergeEvent +Fired when the server is about to merge 2 experience orbs +as entities. Plugins can cancel it if they want to ensure experience orbs do not lose important +metadata such as spawn reason, or conditionally move data from source to target. + +Fixes an issue where the stacked count was not taking into account +for mending repairs and when merging with spigot's merge-on-spawn +logic + +== AT == +public net.minecraft.world.entity.ExperienceOrb count + +Co-authored-by: Aikar +Co-authored-by: Jake Potrebic + +diff --git a/src/main/java/net/minecraft/world/entity/ExperienceOrb.java b/src/main/java/net/minecraft/world/entity/ExperienceOrb.java +index 9d9e3daebc5da0af627c3d3cdb50029aacbc587b..3a7af27bb1ce0cbe56bd3760cd400083daf98d4c 100644 +--- a/src/main/java/net/minecraft/world/entity/ExperienceOrb.java ++++ b/src/main/java/net/minecraft/world/entity/ExperienceOrb.java +@@ -245,6 +245,7 @@ public class ExperienceOrb extends Entity { + } + + private static boolean tryMergeToExisting(ServerLevel world, Vec3 pos, int amount) { ++ // Paper - TODO some other event for this kind of merge + AABB axisalignedbb = AABB.ofSize(pos, 1.0D, 1.0D, 1.0D); + int j = world.getRandom().nextInt(40); + List list = world.getEntities(EntityTypeTest.forClass(ExperienceOrb.class), axisalignedbb, (entityexperienceorb) -> { +@@ -271,6 +272,11 @@ public class ExperienceOrb extends Entity { + } + + private void merge(ExperienceOrb other) { ++ // Paper start - call orb merge event ++ if (!new com.destroystokyo.paper.event.entity.ExperienceOrbMergeEvent((org.bukkit.entity.ExperienceOrb) this.getBukkitEntity(), (org.bukkit.entity.ExperienceOrb) other.getBukkitEntity()).callEvent()) { ++ return; ++ } ++ // Paper end - call orb merge event + this.count += other.count; + this.age = Math.min(this.age, other.age); + other.discard(EntityRemoveEvent.Cause.MERGE); // CraftBukkit - add Bukkit remove cause +@@ -364,7 +370,7 @@ public class ExperienceOrb extends Entity { + int l = amount - k * amount / j; + + if (l > 0) { +- this.value = l; // CraftBukkit - update exp value of orb for PlayerItemMendEvent calls ++ // this.value = l; // CraftBukkit - update exp value of orb for PlayerItemMendEvent calls // Paper - the value field should not be mutated here because it doesn't take "count" into account + return this.repairPlayerItems(player, l); + } + } +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftExperienceOrb.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftExperienceOrb.java +index 5a7d314ec0562e472f5dc45924a7b24841cff126..650e4a01cecc4cc08e7ff9ebcc4c367084351f21 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftExperienceOrb.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftExperienceOrb.java +@@ -18,6 +18,18 @@ public class CraftExperienceOrb extends CraftEntity implements ExperienceOrb { + this.getHandle().value = value; + } + ++ // Paper start - expose count ++ @Override ++ public int getCount() { ++ return this.getHandle().count; ++ } ++ ++ @Override ++ public void setCount(final int count) { ++ this.getHandle().count = count; ++ } ++ // Paper end ++ + // Paper start + public java.util.UUID getTriggerEntityId() { + return getHandle().triggerEntityId; +diff --git a/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java b/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java +index 6b094f8bdf1f25d5c2e108eb88d95aed4a41f6f3..cb708c5ce9e81005d638f03e398e7f0f2b9f7521 100644 +--- a/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java ++++ b/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java +@@ -712,15 +712,29 @@ public class CraftEventFactory { + if (entity instanceof net.minecraft.world.entity.ExperienceOrb xp) { + double radius = world.spigotConfig.expMerge; + if (radius > 0) { ++ // Paper start - Maximum exp value when merging; Whole section has been tweaked, see comments for specifics ++ final long maxValue = world.paperConfig().entities.behavior.experienceMergeMaxValue; ++ final boolean mergeUnconditionally = maxValue <= 0; ++ if (mergeUnconditionally || xp.value < maxValue) { // Paper - Skip iteration if unnecessary ++ + List entities = world.getEntities(entity, entity.getBoundingBox().inflate(radius, radius, radius)); + for (Entity e : entities) { + if (e instanceof net.minecraft.world.entity.ExperienceOrb loopItem) { +- if (!loopItem.isRemoved()) { ++ // Paper start ++ if (!loopItem.isRemoved() && xp.count == loopItem.count && (mergeUnconditionally || loopItem.value < maxValue) && new com.destroystokyo.paper.event.entity.ExperienceOrbMergeEvent((org.bukkit.entity.ExperienceOrb) entity.getBukkitEntity(), (org.bukkit.entity.ExperienceOrb) loopItem.getBukkitEntity()).callEvent()) { // Paper - ExperienceOrbMergeEvent ++ long newTotal = (long)xp.value + (long)loopItem.value; ++ if ((int) newTotal < 0) continue; // Overflow ++ if (!mergeUnconditionally && newTotal > maxValue) { ++ loopItem.value = (int) (newTotal - maxValue); ++ xp.value = (int) maxValue; ++ } else { + xp.value += loopItem.value; + loopItem.discard(null); // Add Bukkit remove cause ++ } // Paper end - Maximum exp value when merging + } + } + } ++ } // Paper end - End iteration skip check - All tweaking ends here + } + } + // Spigot end diff --git a/patches/server/0352-Prevent-teleporting-dead-entities.patch b/patches/server/0352-Prevent-teleporting-dead-entities.patch deleted file mode 100644 index 361f87db5607..000000000000 --- a/patches/server/0352-Prevent-teleporting-dead-entities.patch +++ /dev/null @@ -1,24 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Shane Freeder -Date: Tue, 3 Mar 2020 05:26:40 +0000 -Subject: [PATCH] Prevent teleporting dead entities - - -diff --git a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java -index 119b9b2c45e3321b4197f3e6a34037e3fa99622d..c6bcde25b476ef2362f469bd7cba82ce97cb300c 100644 ---- a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java -+++ b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java -@@ -1552,6 +1552,13 @@ public class ServerGamePacketListenerImpl extends ServerCommonPacketListenerImpl - } - - public void internalTeleport(double d0, double d1, double d2, float f, float f1, Set set) { // Paper -+ // Paper start - Prevent teleporting dead entities -+ if (player.isRemoved()) { -+ LOGGER.info("Attempt to teleport removed player {} restricted", player.getScoreboardName()); -+ if (server.isDebugging()) io.papermc.paper.util.TraceUtil.dumpTraceForThread("Attempt to teleport removed player"); -+ return; -+ } -+ // Paper end - Prevent teleporting dead entities - // CraftBukkit start - if (Float.isNaN(f)) { - f = 0; diff --git a/patches/server/0353-Fix-PotionEffect-ignores-icon-flag.patch b/patches/server/0353-Fix-PotionEffect-ignores-icon-flag.patch new file mode 100644 index 000000000000..9dff9c741c77 --- /dev/null +++ b/patches/server/0353-Fix-PotionEffect-ignores-icon-flag.patch @@ -0,0 +1,60 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: kickash32 +Date: Fri, 8 May 2020 00:49:18 -0400 +Subject: [PATCH] Fix PotionEffect ignores icon flag + +Co-authored-by: Tamion <70228790+notTamion@users.noreply.github.com> + +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftLivingEntity.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftLivingEntity.java +index 81034513faa3bd4f306430bc48532ba671a7b94b..35130dc4e20ef644b5764091fcbccda2e4da780b 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftLivingEntity.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftLivingEntity.java +@@ -494,7 +494,7 @@ public class CraftLivingEntity extends CraftEntity implements LivingEntity { + + @Override + public boolean addPotionEffect(PotionEffect effect, boolean force) { +- this.getHandle().addEffect(new MobEffectInstance(CraftPotionEffectType.bukkitToMinecraftHolder(effect.getType()), effect.getDuration(), effect.getAmplifier(), effect.isAmbient(), effect.hasParticles()), EntityPotionEffectEvent.Cause.PLUGIN); ++ this.getHandle().addEffect(org.bukkit.craftbukkit.potion.CraftPotionUtil.fromBukkit(effect), EntityPotionEffectEvent.Cause.PLUGIN); // Paper - Don't ignore icon + return true; + } + +@@ -515,7 +515,7 @@ public class CraftLivingEntity extends CraftEntity implements LivingEntity { + @Override + public PotionEffect getPotionEffect(PotionEffectType type) { + MobEffectInstance handle = this.getHandle().getEffect(CraftPotionEffectType.bukkitToMinecraftHolder(type)); +- return (handle == null) ? null : new PotionEffect(CraftPotionEffectType.minecraftHolderToBukkit(handle.getEffect()), handle.getDuration(), handle.getAmplifier(), handle.isAmbient(), handle.isVisible()); ++ return (handle == null) ? null : org.bukkit.craftbukkit.potion.CraftPotionUtil.toBukkit(handle); // Paper + } + + @Override +@@ -527,7 +527,7 @@ public class CraftLivingEntity extends CraftEntity implements LivingEntity { + public Collection getActivePotionEffects() { + List effects = new ArrayList(); + for (MobEffectInstance handle : this.getHandle().activeEffects.values()) { +- effects.add(new PotionEffect(CraftPotionEffectType.minecraftHolderToBukkit(handle.getEffect()), handle.getDuration(), handle.getAmplifier(), handle.isAmbient(), handle.isVisible())); ++ effects.add(org.bukkit.craftbukkit.potion.CraftPotionUtil.toBukkit(handle)); // Paper + } + return effects; + } +diff --git a/src/main/java/org/bukkit/craftbukkit/potion/CraftPotionUtil.java b/src/main/java/org/bukkit/craftbukkit/potion/CraftPotionUtil.java +index b7525f6ddc8f315ec9830b6b8f225d4839d306ad..01af4db5d0f17ea2943e5c464d4122a358503bc1 100644 +--- a/src/main/java/org/bukkit/craftbukkit/potion/CraftPotionUtil.java ++++ b/src/main/java/org/bukkit/craftbukkit/potion/CraftPotionUtil.java +@@ -78,7 +78,7 @@ public class CraftPotionUtil { + + public static MobEffectInstance fromBukkit(PotionEffect effect) { + Holder type = CraftPotionEffectType.bukkitToMinecraftHolder(effect.getType()); +- return new MobEffectInstance(type, effect.getDuration(), effect.getAmplifier(), effect.isAmbient(), effect.hasParticles()); ++ return new MobEffectInstance(type, effect.getDuration(), effect.getAmplifier(), effect.isAmbient(), effect.hasParticles(), effect.hasIcon()); // Paper + } + + public static PotionEffect toBukkit(MobEffectInstance effect) { +@@ -87,7 +87,7 @@ public class CraftPotionUtil { + int duration = effect.getDuration(); + boolean ambient = effect.isAmbient(); + boolean particles = effect.isVisible(); +- return new PotionEffect(type, duration, amp, ambient, particles); ++ return new PotionEffect(type, duration, amp, ambient, particles, effect.showIcon()); // Paper + } + + public static boolean equals(Holder mobEffect, PotionEffectType type) { diff --git a/patches/server/0353-Implement-Mob-Goal-API.patch b/patches/server/0353-Implement-Mob-Goal-API.patch deleted file mode 100644 index 5706a5b6b878..000000000000 --- a/patches/server/0353-Implement-Mob-Goal-API.patch +++ /dev/null @@ -1,799 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: MiniDigger -Date: Fri, 3 Jan 2020 16:26:19 +0100 -Subject: [PATCH] Implement Mob Goal API - - -diff --git a/build.gradle.kts b/build.gradle.kts -index 0c349354ba76dfd2c5f16fb232263b18e77a9a40..6c3ed9e685473d7f555ae0e34fb9d4f3873f109a 100644 ---- a/build.gradle.kts -+++ b/build.gradle.kts -@@ -41,6 +41,7 @@ dependencies { - runtimeOnly("org.apache.maven.resolver:maven-resolver-connector-basic:1.9.18") - runtimeOnly("org.apache.maven.resolver:maven-resolver-transport-http:1.9.18") - -+ testImplementation("io.github.classgraph:classgraph:4.8.47") // Paper - mob goal test - testImplementation("org.junit.jupiter:junit-jupiter:5.10.2") - testImplementation("org.junit.platform:junit-platform-suite-engine:1.10.0") - testImplementation("org.hamcrest:hamcrest:2.2") -diff --git a/src/main/java/com/destroystokyo/paper/entity/ai/MobGoalHelper.java b/src/main/java/com/destroystokyo/paper/entity/ai/MobGoalHelper.java -new file mode 100644 -index 0000000000000000000000000000000000000000..c99eafab2103c7f5bca7ffba68a10bd853df055f ---- /dev/null -+++ b/src/main/java/com/destroystokyo/paper/entity/ai/MobGoalHelper.java -@@ -0,0 +1,377 @@ -+package com.destroystokyo.paper.entity.ai; -+ -+import com.destroystokyo.paper.entity.RangedEntity; -+import com.google.common.collect.BiMap; -+import com.google.common.collect.HashBiMap; -+import io.papermc.paper.util.ObfHelper; -+import java.lang.reflect.Constructor; -+import java.util.EnumSet; -+import java.util.HashMap; -+import java.util.HashSet; -+import java.util.Map; -+import java.util.Set; -+import net.minecraft.world.entity.FlyingMob; -+import net.minecraft.world.entity.PathfinderMob; -+import net.minecraft.world.entity.TamableAnimal; -+import net.minecraft.world.entity.ai.goal.Goal; -+import net.minecraft.world.entity.ambient.AmbientCreature; -+import net.minecraft.world.entity.animal.AbstractFish; -+import net.minecraft.world.entity.animal.AbstractGolem; -+import net.minecraft.world.entity.animal.AbstractSchoolingFish; -+import net.minecraft.world.entity.animal.Animal; -+import net.minecraft.world.entity.animal.Pufferfish; -+import net.minecraft.world.entity.animal.ShoulderRidingEntity; -+import net.minecraft.world.entity.animal.SnowGolem; -+import net.minecraft.world.entity.animal.WaterAnimal; -+import net.minecraft.world.entity.animal.camel.Camel; -+import net.minecraft.world.entity.animal.horse.AbstractChestedHorse; -+import net.minecraft.world.entity.boss.wither.WitherBoss; -+import net.minecraft.world.entity.monster.AbstractIllager; -+import net.minecraft.world.entity.monster.EnderMan; -+import net.minecraft.world.entity.monster.PatrollingMonster; -+import net.minecraft.world.entity.monster.RangedAttackMob; -+import net.minecraft.world.entity.monster.SpellcasterIllager; -+import net.minecraft.world.entity.monster.ZombifiedPiglin; -+import net.minecraft.world.entity.monster.breeze.Breeze; -+import net.minecraft.world.entity.monster.piglin.AbstractPiglin; -+import org.bukkit.NamespacedKey; -+import org.bukkit.entity.AbstractHorse; -+import org.bukkit.entity.AbstractSkeleton; -+import org.bukkit.entity.AbstractVillager; -+import org.bukkit.entity.Ageable; -+import org.bukkit.entity.Ambient; -+import org.bukkit.entity.Animals; -+import org.bukkit.entity.Bat; -+import org.bukkit.entity.Bee; -+import org.bukkit.entity.Blaze; -+import org.bukkit.entity.Cat; -+import org.bukkit.entity.CaveSpider; -+import org.bukkit.entity.ChestedHorse; -+import org.bukkit.entity.Chicken; -+import org.bukkit.entity.Cod; -+import org.bukkit.entity.Cow; -+import org.bukkit.entity.Creature; -+import org.bukkit.entity.Creeper; -+import org.bukkit.entity.Dolphin; -+import org.bukkit.entity.Donkey; -+import org.bukkit.entity.Drowned; -+import org.bukkit.entity.ElderGuardian; -+import org.bukkit.entity.EnderDragon; -+import org.bukkit.entity.Enderman; -+import org.bukkit.entity.Endermite; -+import org.bukkit.entity.Evoker; -+import org.bukkit.entity.Fish; -+import org.bukkit.entity.Flying; -+import org.bukkit.entity.Fox; -+import org.bukkit.entity.Ghast; -+import org.bukkit.entity.Giant; -+import org.bukkit.entity.Golem; -+import org.bukkit.entity.Guardian; -+import org.bukkit.entity.Hoglin; -+import org.bukkit.entity.Horse; -+import org.bukkit.entity.Husk; -+import org.bukkit.entity.Illager; -+import org.bukkit.entity.Illusioner; -+import org.bukkit.entity.IronGolem; -+import org.bukkit.entity.Llama; -+import org.bukkit.entity.MagmaCube; -+import org.bukkit.entity.Mob; -+import org.bukkit.entity.Monster; -+import org.bukkit.entity.Mule; -+import org.bukkit.entity.MushroomCow; -+import org.bukkit.entity.Ocelot; -+import org.bukkit.entity.Panda; -+import org.bukkit.entity.Parrot; -+import org.bukkit.entity.Phantom; -+import org.bukkit.entity.Pig; -+import org.bukkit.entity.PigZombie; -+import org.bukkit.entity.Piglin; -+import org.bukkit.entity.PiglinAbstract; -+import org.bukkit.entity.PiglinBrute; -+import org.bukkit.entity.Pillager; -+import org.bukkit.entity.PolarBear; -+import org.bukkit.entity.PufferFish; -+import org.bukkit.entity.Rabbit; -+import org.bukkit.entity.Raider; -+import org.bukkit.entity.Ravager; -+import org.bukkit.entity.Salmon; -+import org.bukkit.entity.Sheep; -+import org.bukkit.entity.Shulker; -+import org.bukkit.entity.Silverfish; -+import org.bukkit.entity.Skeleton; -+import org.bukkit.entity.SkeletonHorse; -+import org.bukkit.entity.Slime; -+import org.bukkit.entity.Snowman; -+import org.bukkit.entity.Spellcaster; -+import org.bukkit.entity.Spider; -+import org.bukkit.entity.Squid; -+import org.bukkit.entity.Stray; -+import org.bukkit.entity.Strider; -+import org.bukkit.entity.Tameable; -+import org.bukkit.entity.TraderLlama; -+import org.bukkit.entity.TropicalFish; -+import org.bukkit.entity.Turtle; -+import org.bukkit.entity.Vex; -+import org.bukkit.entity.Villager; -+import org.bukkit.entity.Vindicator; -+import org.bukkit.entity.WanderingTrader; -+import org.bukkit.entity.WaterMob; -+import org.bukkit.entity.Witch; -+import org.bukkit.entity.Wither; -+import org.bukkit.entity.WitherSkeleton; -+import org.bukkit.entity.Wolf; -+import org.bukkit.entity.Zoglin; -+import org.bukkit.entity.Zombie; -+import org.bukkit.entity.ZombieHorse; -+import org.bukkit.entity.ZombieVillager; -+ -+public class MobGoalHelper { -+ -+ private static final BiMap deobfuscationMap = HashBiMap.create(); -+ private static final Map, Class> entityClassCache = new HashMap<>(); -+ private static final Map, Class> bukkitMap = new HashMap<>(); -+ -+ static final Set ignored = new HashSet<>(); -+ -+ static { -+ // TODO these kinda should be checked on each release, in case obfuscation changes -+ deobfuscationMap.put("abstract_skeleton_1", "abstract_skeleton_melee"); -+ -+ ignored.add("goal_selector_1"); -+ ignored.add("goal_selector_2"); -+ ignored.add("selector_1"); -+ ignored.add("selector_2"); -+ ignored.add("wrapped"); -+ -+ bukkitMap.put(net.minecraft.world.entity.Mob.class, Mob.class); -+ bukkitMap.put(net.minecraft.world.entity.AgeableMob.class, Ageable.class); -+ bukkitMap.put(AmbientCreature.class, Ambient.class); -+ bukkitMap.put(Animal.class, Animals.class); -+ bukkitMap.put(net.minecraft.world.entity.ambient.Bat.class, Bat.class); -+ bukkitMap.put(net.minecraft.world.entity.animal.Bee.class, Bee.class); -+ bukkitMap.put(net.minecraft.world.entity.monster.Blaze.class, Blaze.class); -+ bukkitMap.put(net.minecraft.world.entity.animal.Cat.class, Cat.class); -+ bukkitMap.put(net.minecraft.world.entity.monster.CaveSpider.class, CaveSpider.class); -+ bukkitMap.put(net.minecraft.world.entity.animal.Chicken.class, Chicken.class); -+ bukkitMap.put(net.minecraft.world.entity.animal.Cod.class, Cod.class); -+ bukkitMap.put(net.minecraft.world.entity.animal.Cow.class, Cow.class); -+ bukkitMap.put(PathfinderMob.class, Creature.class); -+ bukkitMap.put(net.minecraft.world.entity.monster.Creeper.class, Creeper.class); -+ bukkitMap.put(net.minecraft.world.entity.animal.Dolphin.class, Dolphin.class); -+ bukkitMap.put(net.minecraft.world.entity.monster.Drowned.class, Drowned.class); -+ bukkitMap.put(net.minecraft.world.entity.boss.enderdragon.EnderDragon.class, EnderDragon.class); -+ bukkitMap.put(EnderMan.class, Enderman.class); -+ bukkitMap.put(net.minecraft.world.entity.monster.Endermite.class, Endermite.class); -+ bukkitMap.put(net.minecraft.world.entity.monster.Evoker.class, Evoker.class); -+ bukkitMap.put(AbstractFish.class, Fish.class); -+ bukkitMap.put(AbstractSchoolingFish.class, Fish.class); // close enough -+ bukkitMap.put(FlyingMob.class, Flying.class); -+ bukkitMap.put(net.minecraft.world.entity.animal.Fox.class, Fox.class); -+ bukkitMap.put(net.minecraft.world.entity.monster.Ghast.class, Ghast.class); -+ bukkitMap.put(net.minecraft.world.entity.monster.Giant.class, Giant.class); -+ bukkitMap.put(AbstractGolem.class, Golem.class); -+ bukkitMap.put(net.minecraft.world.entity.monster.Guardian.class, Guardian.class); -+ bukkitMap.put(net.minecraft.world.entity.monster.ElderGuardian.class, ElderGuardian.class); -+ bukkitMap.put(net.minecraft.world.entity.animal.horse.Horse.class, Horse.class); -+ bukkitMap.put(net.minecraft.world.entity.animal.horse.AbstractHorse.class, AbstractHorse.class); -+ bukkitMap.put(AbstractChestedHorse.class, ChestedHorse.class); -+ bukkitMap.put(net.minecraft.world.entity.animal.horse.Donkey.class, Donkey.class); -+ bukkitMap.put(net.minecraft.world.entity.animal.horse.Mule.class, Mule.class); -+ bukkitMap.put(net.minecraft.world.entity.animal.horse.SkeletonHorse.class, SkeletonHorse.class); -+ bukkitMap.put(net.minecraft.world.entity.animal.horse.ZombieHorse.class, ZombieHorse.class); -+ bukkitMap.put(Camel.class, org.bukkit.entity.Camel.class); -+ bukkitMap.put(AbstractIllager.class, Illager.class); -+ bukkitMap.put(net.minecraft.world.entity.monster.Illusioner.class, Illusioner.class); -+ bukkitMap.put(SpellcasterIllager.class, Spellcaster.class); -+ bukkitMap.put(net.minecraft.world.entity.animal.IronGolem.class, IronGolem.class); -+ bukkitMap.put(net.minecraft.world.entity.animal.horse.Llama.class, Llama.class); -+ bukkitMap.put(net.minecraft.world.entity.animal.horse.TraderLlama.class, TraderLlama.class); -+ bukkitMap.put(net.minecraft.world.entity.monster.MagmaCube.class, MagmaCube.class); -+ bukkitMap.put(net.minecraft.world.entity.monster.Monster.class, Monster.class); -+ bukkitMap.put(PatrollingMonster.class, Raider.class); // close enough -+ bukkitMap.put(net.minecraft.world.entity.animal.MushroomCow.class, MushroomCow.class); -+ bukkitMap.put(net.minecraft.world.entity.animal.Ocelot.class, Ocelot.class); -+ bukkitMap.put(net.minecraft.world.entity.animal.Panda.class, Panda.class); -+ bukkitMap.put(net.minecraft.world.entity.animal.Parrot.class, Parrot.class); -+ bukkitMap.put(ShoulderRidingEntity.class, Parrot.class); // close enough -+ bukkitMap.put(net.minecraft.world.entity.monster.Phantom.class, Phantom.class); -+ bukkitMap.put(net.minecraft.world.entity.animal.Pig.class, Pig.class); -+ bukkitMap.put(ZombifiedPiglin.class, PigZombie.class); -+ bukkitMap.put(net.minecraft.world.entity.monster.Pillager.class, Pillager.class); -+ bukkitMap.put(net.minecraft.world.entity.animal.PolarBear.class, PolarBear.class); -+ bukkitMap.put(Pufferfish.class, PufferFish.class); -+ bukkitMap.put(net.minecraft.world.entity.animal.Rabbit.class, Rabbit.class); -+ bukkitMap.put(net.minecraft.world.entity.raid.Raider.class, Raider.class); -+ bukkitMap.put(net.minecraft.world.entity.monster.Ravager.class, Ravager.class); -+ bukkitMap.put(net.minecraft.world.entity.animal.Salmon.class, Salmon.class); -+ bukkitMap.put(net.minecraft.world.entity.animal.Sheep.class, Sheep.class); -+ bukkitMap.put(net.minecraft.world.entity.monster.Shulker.class, Shulker.class); -+ bukkitMap.put(net.minecraft.world.entity.monster.Silverfish.class, Silverfish.class); -+ bukkitMap.put(net.minecraft.world.entity.monster.Skeleton.class, Skeleton.class); -+ bukkitMap.put(net.minecraft.world.entity.monster.AbstractSkeleton.class, AbstractSkeleton.class); -+ bukkitMap.put(net.minecraft.world.entity.monster.Stray.class, Stray.class); -+ bukkitMap.put(net.minecraft.world.entity.monster.WitherSkeleton.class, WitherSkeleton.class); -+ bukkitMap.put(net.minecraft.world.entity.monster.Slime.class, Slime.class); -+ bukkitMap.put(SnowGolem.class, Snowman.class); -+ bukkitMap.put(net.minecraft.world.entity.monster.Spider.class, Spider.class); -+ bukkitMap.put(net.minecraft.world.entity.animal.Squid.class, Squid.class); -+ bukkitMap.put(TamableAnimal.class, Tameable.class); -+ bukkitMap.put(net.minecraft.world.entity.animal.TropicalFish.class, TropicalFish.class); -+ bukkitMap.put(net.minecraft.world.entity.animal.Turtle.class, Turtle.class); -+ bukkitMap.put(net.minecraft.world.entity.monster.Vex.class, Vex.class); -+ bukkitMap.put(net.minecraft.world.entity.npc.Villager.class, Villager.class); -+ bukkitMap.put(net.minecraft.world.entity.npc.AbstractVillager.class, AbstractVillager.class); -+ bukkitMap.put(net.minecraft.world.entity.npc.WanderingTrader.class, WanderingTrader.class); -+ bukkitMap.put(net.minecraft.world.entity.monster.Vindicator.class, Vindicator.class); -+ bukkitMap.put(WaterAnimal.class, WaterMob.class); -+ bukkitMap.put(net.minecraft.world.entity.monster.Witch.class, Witch.class); -+ bukkitMap.put(WitherBoss.class, Wither.class); -+ bukkitMap.put(net.minecraft.world.entity.animal.Wolf.class, Wolf.class); -+ bukkitMap.put(net.minecraft.world.entity.monster.Zombie.class, Zombie.class); -+ bukkitMap.put(net.minecraft.world.entity.monster.Husk.class, Husk.class); -+ bukkitMap.put(net.minecraft.world.entity.monster.ZombieVillager.class, ZombieVillager.class); -+ bukkitMap.put(net.minecraft.world.entity.monster.hoglin.Hoglin.class, Hoglin.class); -+ bukkitMap.put(net.minecraft.world.entity.monster.piglin.Piglin.class, Piglin.class); -+ bukkitMap.put(AbstractPiglin.class, PiglinAbstract.class); -+ bukkitMap.put(net.minecraft.world.entity.monster.piglin.PiglinBrute.class, PiglinBrute.class); -+ bukkitMap.put(net.minecraft.world.entity.monster.Strider.class, Strider.class); -+ bukkitMap.put(net.minecraft.world.entity.monster.Zoglin.class, Zoglin.class); -+ bukkitMap.put(net.minecraft.world.entity.GlowSquid.class, org.bukkit.entity.GlowSquid.class); -+ bukkitMap.put(net.minecraft.world.entity.animal.axolotl.Axolotl.class, org.bukkit.entity.Axolotl.class); -+ bukkitMap.put(net.minecraft.world.entity.animal.goat.Goat.class, org.bukkit.entity.Goat.class); -+ bukkitMap.put(net.minecraft.world.entity.animal.frog.Frog.class, org.bukkit.entity.Frog.class); -+ bukkitMap.put(net.minecraft.world.entity.animal.frog.Tadpole.class, org.bukkit.entity.Tadpole.class); -+ bukkitMap.put(net.minecraft.world.entity.monster.warden.Warden.class, org.bukkit.entity.Warden.class); -+ bukkitMap.put(net.minecraft.world.entity.animal.allay.Allay.class, org.bukkit.entity.Allay.class); -+ bukkitMap.put(net.minecraft.world.entity.animal.sniffer.Sniffer.class, org.bukkit.entity.Sniffer.class); -+ bukkitMap.put(Breeze.class, org.bukkit.entity.Breeze.class); -+ bukkitMap.put(net.minecraft.world.entity.animal.armadillo.Armadillo.class, org.bukkit.entity.Armadillo.class); -+ bukkitMap.put(net.minecraft.world.entity.monster.Bogged.class, org.bukkit.entity.Bogged.class); -+ } -+ -+ public static String getUsableName(Class clazz) { -+ String name = io.papermc.paper.util.MappingEnvironment.reobf() ? ObfHelper.INSTANCE.deobfClassName(clazz.getName()) : clazz.getName(); -+ name = name.substring(name.lastIndexOf(".") + 1); -+ boolean flag = false; -+ // inner classes -+ if (name.contains("$")) { -+ String cut = name.substring(name.indexOf("$") + 1); -+ if (cut.length() <= 2) { -+ name = name.replace("Entity", ""); -+ name = name.replace("$", "_"); -+ flag = true; -+ } else { -+ // mapped, wooo -+ name = cut; -+ } -+ } -+ name = name.replace("PathfinderGoal", ""); -+ name = name.replace("TargetGoal", ""); -+ name = name.replace("Goal", ""); -+ StringBuilder sb = new StringBuilder(); -+ for (char c : name.toCharArray()) { -+ if (c >= 'A' && c <= 'Z') { -+ sb.append("_"); -+ sb.append(Character.toLowerCase(c)); -+ } else { -+ sb.append(c); -+ } -+ } -+ name = sb.toString(); -+ name = name.replaceFirst("_", ""); -+ -+ if (flag && !deobfuscationMap.containsKey(name.toLowerCase(java.util.Locale.ROOT)) && !ignored.contains(name)) { -+ System.out.println("need to map " + clazz.getName() + " (" + name.toLowerCase(java.util.Locale.ROOT) + ")"); -+ } -+ -+ // did we rename this key? -+ return deobfuscationMap.getOrDefault(name, name); -+ } -+ -+ public static EnumSet vanillaToPaper(Goal goal) { -+ EnumSet goals = EnumSet.noneOf(GoalType.class); -+ for (GoalType type : GoalType.values()) { -+ if (goal.getFlags().hasElement(paperToVanilla(type))) { -+ goals.add(type); -+ } -+ } -+ return goals; -+ } -+ -+ public static GoalType vanillaToPaper(Goal.Flag type) { -+ switch (type) { -+ case MOVE: -+ return GoalType.MOVE; -+ case LOOK: -+ return GoalType.LOOK; -+ case JUMP: -+ return GoalType.JUMP; -+ case UNKNOWN_BEHAVIOR: -+ return GoalType.UNKNOWN_BEHAVIOR; -+ case TARGET: -+ return GoalType.TARGET; -+ default: -+ throw new IllegalArgumentException("Unknown vanilla mob goal type " + type.name()); -+ } -+ } -+ -+ public static EnumSet paperToVanilla(EnumSet types) { -+ EnumSet goals = EnumSet.noneOf(Goal.Flag.class); -+ for (GoalType type : types) { -+ goals.add(paperToVanilla(type)); -+ } -+ return goals; -+ } -+ -+ public static Goal.Flag paperToVanilla(GoalType type) { -+ switch (type) { -+ case MOVE: -+ return Goal.Flag.MOVE; -+ case LOOK: -+ return Goal.Flag.LOOK; -+ case JUMP: -+ return Goal.Flag.JUMP; -+ case UNKNOWN_BEHAVIOR: -+ return Goal.Flag.UNKNOWN_BEHAVIOR; -+ case TARGET: -+ return Goal.Flag.TARGET; -+ default: -+ throw new IllegalArgumentException("Unknown paper mob goal type " + type.name()); -+ } -+ } -+ -+ public static GoalKey getKey(Class goalClass) { -+ String name = getUsableName(goalClass); -+ if (ignored.contains(name)) { -+ //noinspection unchecked -+ return (GoalKey) GoalKey.of(Mob.class, NamespacedKey.minecraft(name)); -+ } -+ return GoalKey.of(getEntity(goalClass), NamespacedKey.minecraft(name)); -+ } -+ -+ public static Class getEntity(Class goalClass) { -+ //noinspection unchecked -+ return (Class) entityClassCache.computeIfAbsent(goalClass, key -> { -+ for (Constructor ctor : key.getDeclaredConstructors()) { -+ for (int i = 0; i < ctor.getParameterCount(); i++) { -+ Class param = ctor.getParameterTypes()[i]; -+ if (net.minecraft.world.entity.Mob.class.isAssignableFrom(param)) { -+ //noinspection unchecked -+ return toBukkitClass((Class) param); -+ } else if (RangedAttackMob.class.isAssignableFrom(param)) { -+ return RangedEntity.class; -+ } -+ } -+ } -+ throw new RuntimeException("Can't figure out applicable entity for mob goal " + goalClass); // maybe just return EntityInsentient? -+ }); -+ } -+ -+ public static Class toBukkitClass(Class nmsClass) { -+ Class bukkitClass = bukkitMap.get(nmsClass); -+ if (bukkitClass == null) { -+ throw new RuntimeException("Can't figure out applicable bukkit entity for nms entity " + nmsClass); // maybe just return Mob? -+ } -+ return bukkitClass; -+ } -+} -diff --git a/src/main/java/com/destroystokyo/paper/entity/ai/PaperCustomGoal.java b/src/main/java/com/destroystokyo/paper/entity/ai/PaperCustomGoal.java -new file mode 100644 -index 0000000000000000000000000000000000000000..26c745dd9ccdfdd5c5039f2acc5201b9b91fb274 ---- /dev/null -+++ b/src/main/java/com/destroystokyo/paper/entity/ai/PaperCustomGoal.java -@@ -0,0 +1,53 @@ -+package com.destroystokyo.paper.entity.ai; -+ -+import org.bukkit.entity.Mob; -+ -+/** -+ * Wraps api in vanilla -+ */ -+public class PaperCustomGoal extends net.minecraft.world.entity.ai.goal.Goal { -+ -+ private final Goal handle; -+ -+ public PaperCustomGoal(Goal handle) { -+ this.handle = handle; -+ -+ this.setFlags(MobGoalHelper.paperToVanilla(handle.getTypes())); -+ if (this.getFlags().size() == 0) { -+ this.getFlags().addUnchecked(Flag.UNKNOWN_BEHAVIOR); -+ } -+ } -+ -+ @Override -+ public boolean canUse() { -+ return handle.shouldActivate(); -+ } -+ -+ @Override -+ public boolean canContinueToUse() { -+ return handle.shouldStayActive(); -+ } -+ -+ @Override -+ public void start() { -+ handle.start(); -+ } -+ -+ @Override -+ public void stop() { -+ handle.stop(); -+ } -+ -+ @Override -+ public void tick() { -+ handle.tick(); -+ } -+ -+ public Goal getHandle() { -+ return handle; -+ } -+ -+ public GoalKey getKey() { -+ return handle.getKey(); -+ } -+} -diff --git a/src/main/java/com/destroystokyo/paper/entity/ai/PaperMobGoals.java b/src/main/java/com/destroystokyo/paper/entity/ai/PaperMobGoals.java -new file mode 100644 -index 0000000000000000000000000000000000000000..e8a427ea777af040d0e2b9cc0ba2a80b9176d026 ---- /dev/null -+++ b/src/main/java/com/destroystokyo/paper/entity/ai/PaperMobGoals.java -@@ -0,0 +1,226 @@ -+package com.destroystokyo.paper.entity.ai; -+ -+import java.util.Collection; -+import java.util.EnumSet; -+import java.util.HashSet; -+import java.util.LinkedList; -+import java.util.List; -+import java.util.Set; -+import net.minecraft.world.entity.ai.goal.GoalSelector; -+import net.minecraft.world.entity.ai.goal.WrappedGoal; -+import org.bukkit.craftbukkit.entity.CraftMob; -+import org.bukkit.entity.Mob; -+import org.jspecify.annotations.NullMarked; -+ -+@NullMarked -+public class PaperMobGoals implements MobGoals { -+ -+ @Override -+ public void addGoal(T mob, int priority, Goal goal) { -+ CraftMob craftMob = (CraftMob) mob; -+ net.minecraft.world.entity.ai.goal.Goal mojangGoal; -+ -+ if (goal instanceof PaperVanillaGoal vanillaGoal) { -+ mojangGoal = vanillaGoal.getHandle(); -+ } else { -+ mojangGoal = new PaperCustomGoal<>(goal); -+ } -+ -+ getHandle(craftMob, goal.getTypes()).addGoal(priority, mojangGoal); -+ } -+ -+ @Override -+ public void removeGoal(T mob, Goal goal) { -+ CraftMob craftMob = (CraftMob) mob; -+ if (goal instanceof PaperCustomGoal) { -+ getHandle(craftMob, goal.getTypes()).removeGoal((net.minecraft.world.entity.ai.goal.Goal) goal); -+ } else if (goal instanceof PaperVanillaGoal) { -+ getHandle(craftMob, goal.getTypes()).removeGoal(((PaperVanillaGoal) goal).getHandle()); -+ } else { -+ List toRemove = new LinkedList<>(); -+ for (WrappedGoal item : getHandle(craftMob, goal.getTypes()).getAvailableGoals()) { -+ if (item.getGoal() instanceof PaperCustomGoal) { -+ //noinspection unchecked -+ if (((PaperCustomGoal) item.getGoal()).getHandle() == goal) { -+ toRemove.add(item.getGoal()); -+ } -+ } -+ } -+ -+ for (net.minecraft.world.entity.ai.goal.Goal g : toRemove) { -+ getHandle(craftMob, goal.getTypes()).removeGoal(g); -+ } -+ } -+ } -+ -+ @Override -+ public void removeAllGoals(T mob) { -+ for (GoalType type : GoalType.values()) { -+ removeAllGoals(mob, type); -+ } -+ } -+ -+ @Override -+ public void removeAllGoals(T mob, GoalType type) { -+ for (Goal goal : getAllGoals(mob, type)) { -+ removeGoal(mob, goal); -+ } -+ } -+ -+ @Override -+ public void removeGoal(T mob, GoalKey key) { -+ for (Goal goal : getGoals(mob, key)) { -+ removeGoal(mob, goal); -+ } -+ } -+ -+ @Override -+ public boolean hasGoal(T mob, GoalKey key) { -+ for (Goal g : getAllGoals(mob)) { -+ if (g.getKey().equals(key)) { -+ return true; -+ } -+ } -+ return false; -+ } -+ -+ @Override -+ public Goal getGoal(T mob, GoalKey key) { -+ for (Goal g : getAllGoals(mob)) { -+ if (g.getKey().equals(key)) { -+ return g; -+ } -+ } -+ return null; -+ } -+ -+ @Override -+ public Collection> getGoals(T mob, GoalKey key) { -+ Set> goals = new HashSet<>(); -+ for (Goal g : getAllGoals(mob)) { -+ if (g.getKey().equals(key)) { -+ goals.add(g); -+ } -+ } -+ return goals; -+ } -+ -+ @Override -+ public Collection> getAllGoals(T mob) { -+ Set> goals = new HashSet<>(); -+ for (GoalType type : GoalType.values()) { -+ goals.addAll(getAllGoals(mob, type)); -+ } -+ return goals; -+ } -+ -+ @Override -+ public Collection> getAllGoals(T mob, GoalType type) { -+ CraftMob craftMob = (CraftMob) mob; -+ Set> goals = new HashSet<>(); -+ for (WrappedGoal item : getHandle(craftMob, type).getAvailableGoals()) { -+ if (!item.getGoal().getFlags().hasElement(MobGoalHelper.paperToVanilla(type))) { -+ continue; -+ } -+ -+ if (item.getGoal() instanceof PaperCustomGoal) { -+ //noinspection unchecked -+ goals.add(((PaperCustomGoal) item.getGoal()).getHandle()); -+ } else { -+ goals.add(item.getGoal().asPaperVanillaGoal()); -+ } -+ } -+ return goals; -+ } -+ -+ @Override -+ public Collection> getAllGoalsWithout(T mob, GoalType type) { -+ CraftMob craftMob = (CraftMob) mob; -+ Set> goals = new HashSet<>(); -+ for (GoalType internalType : GoalType.values()) { -+ if (internalType == type) { -+ continue; -+ } -+ for (WrappedGoal item : getHandle(craftMob, internalType).getAvailableGoals()) { -+ if (item.getGoal().getFlags().hasElement(MobGoalHelper.paperToVanilla(type))) { -+ continue; -+ } -+ -+ if (item.getGoal() instanceof PaperCustomGoal) { -+ //noinspection unchecked -+ goals.add(((PaperCustomGoal) item.getGoal()).getHandle()); -+ } else { -+ goals.add(item.getGoal().asPaperVanillaGoal()); -+ } -+ } -+ } -+ return goals; -+ } -+ -+ @Override -+ public Collection> getRunningGoals(T mob) { -+ Set> goals = new HashSet<>(); -+ for (GoalType type : GoalType.values()) { -+ goals.addAll(getRunningGoals(mob, type)); -+ } -+ return goals; -+ } -+ -+ @Override -+ public Collection> getRunningGoals(T mob, GoalType type) { -+ CraftMob craftMob = (CraftMob) mob; -+ Set> goals = new HashSet<>(); -+ getHandle(craftMob, type).getAvailableGoals() -+ .stream().filter(WrappedGoal::isRunning) -+ .filter(item -> item.getGoal().getFlags().hasElement(MobGoalHelper.paperToVanilla(type))) -+ .forEach(item -> { -+ if (item.getGoal() instanceof PaperCustomGoal) { -+ //noinspection unchecked -+ goals.add(((PaperCustomGoal) item.getGoal()).getHandle()); -+ } else { -+ goals.add(item.getGoal().asPaperVanillaGoal()); -+ } -+ }); -+ return goals; -+ } -+ -+ @Override -+ public Collection> getRunningGoalsWithout(T mob, GoalType type) { -+ CraftMob craftMob = (CraftMob) mob; -+ Set> goals = new HashSet<>(); -+ for (GoalType internalType : GoalType.values()) { -+ if (internalType == type) { -+ continue; -+ } -+ getHandle(craftMob, internalType).getAvailableGoals() -+ .stream() -+ .filter(WrappedGoal::isRunning) -+ .filter(item -> !item.getGoal().getFlags().hasElement(MobGoalHelper.paperToVanilla(type))) -+ .forEach(item -> { -+ if (item.getGoal() instanceof PaperCustomGoal) { -+ //noinspection unchecked -+ goals.add(((PaperCustomGoal) item.getGoal()).getHandle()); -+ } else { -+ goals.add(item.getGoal().asPaperVanillaGoal()); -+ } -+ }); -+ } -+ return goals; -+ } -+ -+ private GoalSelector getHandle(CraftMob mob, EnumSet types) { -+ if (types.contains(GoalType.TARGET)) { -+ return mob.getHandle().targetSelector; -+ } else { -+ return mob.getHandle().goalSelector; -+ } -+ } -+ -+ private GoalSelector getHandle(CraftMob mob, GoalType type) { -+ if (type == GoalType.TARGET) { -+ return mob.getHandle().targetSelector; -+ } else { -+ return mob.getHandle().goalSelector; -+ } -+ } -+} -diff --git a/src/main/java/com/destroystokyo/paper/entity/ai/PaperVanillaGoal.java b/src/main/java/com/destroystokyo/paper/entity/ai/PaperVanillaGoal.java -new file mode 100644 -index 0000000000000000000000000000000000000000..b5c594a5499556ad452d9939c75e150af8252e90 ---- /dev/null -+++ b/src/main/java/com/destroystokyo/paper/entity/ai/PaperVanillaGoal.java -@@ -0,0 +1,61 @@ -+package com.destroystokyo.paper.entity.ai; -+ -+import java.util.EnumSet; -+import net.minecraft.world.entity.ai.goal.Goal; -+import org.bukkit.entity.Mob; -+ -+/** -+ * Wraps vanilla in api -+ */ -+public class PaperVanillaGoal implements VanillaGoal { -+ -+ private final Goal handle; -+ private final GoalKey key; -+ -+ private final EnumSet types; -+ -+ public PaperVanillaGoal(Goal handle) { -+ this.handle = handle; -+ this.key = MobGoalHelper.getKey(handle.getClass()); -+ this.types = MobGoalHelper.vanillaToPaper(handle); -+ } -+ -+ public Goal getHandle() { -+ return handle; -+ } -+ -+ @Override -+ public boolean shouldActivate() { -+ return handle.canUse(); -+ } -+ -+ @Override -+ public boolean shouldStayActive() { -+ return handle.canContinueToUse(); -+ } -+ -+ @Override -+ public void start() { -+ handle.start(); -+ } -+ -+ @Override -+ public void stop() { -+ handle.stop(); -+ } -+ -+ @Override -+ public void tick() { -+ handle.tick(); -+ } -+ -+ @Override -+ public GoalKey getKey() { -+ return key; -+ } -+ -+ @Override -+ public EnumSet getTypes() { -+ return types; -+ } -+} -diff --git a/src/main/java/net/minecraft/world/entity/ai/goal/Goal.java b/src/main/java/net/minecraft/world/entity/ai/goal/Goal.java -index 6667ecc4b7eded4e20a415cef1e1b1179e6710b8..16f9a98b8a939e5ca7e2dc04f87134a7ed66736b 100644 ---- a/src/main/java/net/minecraft/world/entity/ai/goal/Goal.java -+++ b/src/main/java/net/minecraft/world/entity/ai/goal/Goal.java -@@ -51,7 +51,19 @@ public abstract class Goal { - return Mth.positiveCeilDiv(serverTicks, 2); - } - -+ // Paper start - Mob goal api -+ private com.destroystokyo.paper.entity.ai.PaperVanillaGoal vanillaGoal; -+ public com.destroystokyo.paper.entity.ai.Goal asPaperVanillaGoal() { -+ if(this.vanillaGoal == null) { -+ this.vanillaGoal = new com.destroystokyo.paper.entity.ai.PaperVanillaGoal<>(this); -+ } -+ //noinspection unchecked -+ return (com.destroystokyo.paper.entity.ai.Goal) this.vanillaGoal; -+ } -+ // Paper end - Mob goal api -+ - public static enum Flag { -+ UNKNOWN_BEHAVIOR, // Paper - add UNKNOWN_BEHAVIOR - MOVE, - LOOK, - JUMP, -diff --git a/src/main/java/org/bukkit/craftbukkit/CraftServer.java b/src/main/java/org/bukkit/craftbukkit/CraftServer.java -index 75ea1d68c4be6f73ad05cd53b4e4c0182832395c..2b3eaeea881b12fe7e4c5150815ad00fe9f026e0 100644 ---- a/src/main/java/org/bukkit/craftbukkit/CraftServer.java -+++ b/src/main/java/org/bukkit/craftbukkit/CraftServer.java -@@ -2938,5 +2938,11 @@ public final class CraftServer implements Server { - public boolean isStopping() { - return net.minecraft.server.MinecraftServer.getServer().hasStopped(); - } -+ -+ private com.destroystokyo.paper.entity.ai.MobGoals mobGoals = new com.destroystokyo.paper.entity.ai.PaperMobGoals(); -+ @Override -+ public com.destroystokyo.paper.entity.ai.MobGoals getMobGoals() { -+ return mobGoals; -+ } - // Paper end - } diff --git a/patches/server/0354-Add-villager-reputation-API.patch b/patches/server/0354-Add-villager-reputation-API.patch deleted file mode 100644 index cf69cd346a19..000000000000 --- a/patches/server/0354-Add-villager-reputation-API.patch +++ /dev/null @@ -1,122 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Mariell Hoversholm -Date: Wed, 22 Apr 2020 23:29:20 +0200 -Subject: [PATCH] Add villager reputation API - -== AT == -public net.minecraft.world.entity.ai.gossip.GossipContainer$EntityGossips -public net.minecraft.world.entity.ai.gossip.GossipContainer$EntityGossips ()V -public net.minecraft.world.entity.ai.gossip.GossipContainer gossips - -diff --git a/src/main/java/net/minecraft/world/entity/ai/gossip/GossipContainer.java b/src/main/java/net/minecraft/world/entity/ai/gossip/GossipContainer.java -index f06a5f0d9c5c877ddf963254d3124f5fe2d67282..aa32804bc9affe9a615d3ffaa513f6f09aab3f32 100644 ---- a/src/main/java/net/minecraft/world/entity/ai/gossip/GossipContainer.java -+++ b/src/main/java/net/minecraft/world/entity/ai/gossip/GossipContainer.java -@@ -216,6 +216,43 @@ public class GossipContainer { - public void remove(GossipType gossipType) { - this.entries.removeInt(gossipType); - } -+ -+ // Paper start - Add villager reputation API -+ private static final GossipType[] TYPES = GossipType.values(); -+ public com.destroystokyo.paper.entity.villager.Reputation getPaperReputation() { -+ Map map = new java.util.EnumMap<>(com.destroystokyo.paper.entity.villager.ReputationType.class); -+ for (Object2IntMap.Entry type : this.entries.object2IntEntrySet()) { -+ map.put(toApi(type.getKey()), type.getIntValue()); -+ } -+ -+ return new com.destroystokyo.paper.entity.villager.Reputation(map); -+ } -+ -+ public void assignFromPaperReputation(com.destroystokyo.paper.entity.villager.Reputation rep) { -+ for (GossipType type : TYPES) { -+ com.destroystokyo.paper.entity.villager.ReputationType api = toApi(type); -+ -+ if (rep.hasReputationSet(api)) { -+ int reputation = rep.getReputation(api); -+ if (reputation == 0) { -+ this.entries.removeInt(type); -+ } else { -+ this.entries.put(type, reputation); -+ } -+ } -+ } -+ } -+ -+ private static com.destroystokyo.paper.entity.villager.ReputationType toApi(GossipType type) { -+ return switch (type) { -+ case MAJOR_NEGATIVE -> com.destroystokyo.paper.entity.villager.ReputationType.MAJOR_NEGATIVE; -+ case MINOR_NEGATIVE -> com.destroystokyo.paper.entity.villager.ReputationType.MINOR_NEGATIVE; -+ case MINOR_POSITIVE -> com.destroystokyo.paper.entity.villager.ReputationType.MINOR_POSITIVE; -+ case MAJOR_POSITIVE -> com.destroystokyo.paper.entity.villager.ReputationType.MAJOR_POSITIVE; -+ case TRADING -> com.destroystokyo.paper.entity.villager.ReputationType.TRADING; -+ }; -+ } -+ // Paper end - Add villager reputation API - } - - static record GossipEntry(UUID target, GossipType type, int value) { -diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftVillager.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftVillager.java -index 957c9ec21c7a9888b3038402b0111c68f816f968..52312bec840322d32ea845f0bd64eb3ca1380854 100644 ---- a/src/main/java/org/bukkit/craftbukkit/entity/CraftVillager.java -+++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftVillager.java -@@ -20,6 +20,13 @@ import org.bukkit.entity.Villager; - import org.bukkit.entity.ZombieVillager; - import org.bukkit.event.entity.CreatureSpawnEvent; - -+// Paper start -+import com.destroystokyo.paper.entity.villager.Reputation; -+import com.google.common.collect.Maps; -+import java.util.Map; -+import java.util.UUID; -+// Paper end -+ - public class CraftVillager extends CraftAbstractVillager implements Villager { - - public CraftVillager(CraftServer server, net.minecraft.world.entity.npc.Villager entity) { -@@ -298,4 +305,45 @@ public class CraftVillager extends CraftAbstractVillager implements Villager { - return this.getKey().hashCode(); - } - } -+ -+ // Paper start - Add villager reputation API -+ @Override -+ public Reputation getReputation(UUID uniqueId) { -+ net.minecraft.world.entity.ai.gossip.GossipContainer.EntityGossips rep = getHandle().getGossips().gossips.get(uniqueId); -+ if (rep == null) { -+ return new Reputation(new java.util.EnumMap<>(com.destroystokyo.paper.entity.villager.ReputationType.class)); -+ } -+ -+ return rep.getPaperReputation(); -+ } -+ -+ @Override -+ public Map getReputations() { -+ return getHandle().getGossips().gossips.entrySet() -+ .stream() -+ .collect(java.util.stream.Collectors.toMap(Map.Entry::getKey, entry -> entry.getValue().getPaperReputation())); -+ } -+ -+ @Override -+ public void setReputation(UUID uniqueId, Reputation reputation) { -+ net.minecraft.world.entity.ai.gossip.GossipContainer.EntityGossips nmsReputation = -+ getHandle().getGossips().gossips.computeIfAbsent( -+ uniqueId, -+ key -> new net.minecraft.world.entity.ai.gossip.GossipContainer.EntityGossips() -+ ); -+ nmsReputation.assignFromPaperReputation(reputation); -+ } -+ -+ @Override -+ public void setReputations(Map reputations) { -+ for (Map.Entry entry : reputations.entrySet()) { -+ setReputation(entry.getKey(), entry.getValue()); -+ } -+ } -+ -+ @Override -+ public void clearReputations() { -+ getHandle().getGossips().gossips.clear(); -+ } -+ // Paper end - } diff --git a/patches/server/0354-Potential-bed-API.patch b/patches/server/0354-Potential-bed-API.patch new file mode 100644 index 000000000000..e4dbfa304f45 --- /dev/null +++ b/patches/server/0354-Potential-bed-API.patch @@ -0,0 +1,36 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: JRoy +Date: Sun, 10 May 2020 23:06:30 -0400 +Subject: [PATCH] Potential bed API + +Adds a new method to fetch the location of a player's bed without generating any sync loads. + +getPotentialBedLocation - Gets the last known location of a player's bed. This does not preform any check if the bed is still valid and does not load any chunks. + +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftHumanEntity.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftHumanEntity.java +index 6ec6b80d224e2402054afb85b78c793942a58bbf..b2ab430392fc0f214ba8c5383e3f3ad5c548bda2 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftHumanEntity.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftHumanEntity.java +@@ -130,6 +130,22 @@ public class CraftHumanEntity extends CraftLivingEntity implements HumanEntity { + return this.getHandle().sleepCounter; + } + ++ // Paper start - Potential bed api ++ @Override ++ public Location getPotentialBedLocation() { ++ ServerPlayer handle = (ServerPlayer) getHandle(); ++ BlockPos bed = handle.getRespawnPosition(); ++ if (bed == null) { ++ return null; ++ } ++ ++ net.minecraft.server.level.ServerLevel worldServer = handle.server.getLevel(handle.getRespawnDimension()); ++ if (worldServer == null) { ++ return null; ++ } ++ return new Location(worldServer.getWorld(), bed.getX(), bed.getY(), bed.getZ()); ++ } ++ // Paper end + @Override + public boolean sleep(Location location, boolean force) { + Preconditions.checkArgument(location != null, "Location cannot be null"); diff --git a/patches/server/0355-ExperienceOrb-merging-stacking-API-and-fixes.patch b/patches/server/0355-ExperienceOrb-merging-stacking-API-and-fixes.patch deleted file mode 100644 index 369ade07202d..000000000000 --- a/patches/server/0355-ExperienceOrb-merging-stacking-API-and-fixes.patch +++ /dev/null @@ -1,113 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: BillyGalbreath -Date: Fri, 10 Nov 2017 23:03:12 -0500 -Subject: [PATCH] ExperienceOrb merging/stacking API and fixes - -Adds an option for maximum exp value when merging orbs - -Adds ExperienceOrbMergeEvent -Fired when the server is about to merge 2 experience orbs -as entities. Plugins can cancel it if they want to ensure experience orbs do not lose important -metadata such as spawn reason, or conditionally move data from source to target. - -Fixes an issue where the stacked count was not taking into account -for mending repairs and when merging with spigot's merge-on-spawn -logic - -== AT == -public net.minecraft.world.entity.ExperienceOrb count - -Co-authored-by: Aikar -Co-authored-by: Jake Potrebic - -diff --git a/src/main/java/net/minecraft/world/entity/ExperienceOrb.java b/src/main/java/net/minecraft/world/entity/ExperienceOrb.java -index 0916e24271d07ad5db51c5bc68791722b0f69c2b..a758b2456acac23095fe4619ae10300a034cb460 100644 ---- a/src/main/java/net/minecraft/world/entity/ExperienceOrb.java -+++ b/src/main/java/net/minecraft/world/entity/ExperienceOrb.java -@@ -244,6 +244,7 @@ public class ExperienceOrb extends Entity { - } - - private static boolean tryMergeToExisting(ServerLevel world, Vec3 pos, int amount) { -+ // Paper - TODO some other event for this kind of merge - AABB axisalignedbb = AABB.ofSize(pos, 1.0D, 1.0D, 1.0D); - int j = world.getRandom().nextInt(40); - List list = world.getEntities(EntityTypeTest.forClass(ExperienceOrb.class), axisalignedbb, (entityexperienceorb) -> { -@@ -270,6 +271,11 @@ public class ExperienceOrb extends Entity { - } - - private void merge(ExperienceOrb other) { -+ // Paper start - call orb merge event -+ if (!new com.destroystokyo.paper.event.entity.ExperienceOrbMergeEvent((org.bukkit.entity.ExperienceOrb) this.getBukkitEntity(), (org.bukkit.entity.ExperienceOrb) other.getBukkitEntity()).callEvent()) { -+ return; -+ } -+ // Paper end - call orb merge event - this.count += other.count; - this.age = Math.min(this.age, other.age); - other.discard(EntityRemoveEvent.Cause.MERGE); // CraftBukkit - add Bukkit remove cause -@@ -360,7 +366,7 @@ public class ExperienceOrb extends Entity { - int l = amount - k * amount / j; - - if (l > 0) { -- this.value = l; // CraftBukkit - update exp value of orb for PlayerItemMendEvent calls -+ // this.value = l; // CraftBukkit - update exp value of orb for PlayerItemMendEvent calls // Paper - the value field should not be mutated here because it doesn't take "count" into account - return this.repairPlayerItems(player, l); - } - } -diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftExperienceOrb.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftExperienceOrb.java -index 5a7d314ec0562e472f5dc45924a7b24841cff126..650e4a01cecc4cc08e7ff9ebcc4c367084351f21 100644 ---- a/src/main/java/org/bukkit/craftbukkit/entity/CraftExperienceOrb.java -+++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftExperienceOrb.java -@@ -18,6 +18,18 @@ public class CraftExperienceOrb extends CraftEntity implements ExperienceOrb { - this.getHandle().value = value; - } - -+ // Paper start - expose count -+ @Override -+ public int getCount() { -+ return this.getHandle().count; -+ } -+ -+ @Override -+ public void setCount(final int count) { -+ this.getHandle().count = count; -+ } -+ // Paper end -+ - // Paper start - public java.util.UUID getTriggerEntityId() { - return getHandle().triggerEntityId; -diff --git a/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java b/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java -index 26f8a8cb18205bfb9fe9dc557097946987ddcb18..f3ec3d14e66b9c1dff32088d4aef57e21265048c 100644 ---- a/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java -+++ b/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java -@@ -714,15 +714,29 @@ public class CraftEventFactory { - if (entity instanceof net.minecraft.world.entity.ExperienceOrb xp) { - double radius = world.spigotConfig.expMerge; - if (radius > 0) { -+ // Paper start - Maximum exp value when merging; Whole section has been tweaked, see comments for specifics -+ final long maxValue = world.paperConfig().entities.behavior.experienceMergeMaxValue; -+ final boolean mergeUnconditionally = maxValue <= 0; -+ if (mergeUnconditionally || xp.value < maxValue) { // Paper - Skip iteration if unnecessary -+ - List entities = world.getEntities(entity, entity.getBoundingBox().inflate(radius, radius, radius)); - for (Entity e : entities) { - if (e instanceof net.minecraft.world.entity.ExperienceOrb loopItem) { -- if (!loopItem.isRemoved()) { -+ // Paper start -+ if (!loopItem.isRemoved() && xp.count == loopItem.count && (mergeUnconditionally || loopItem.value < maxValue) && new com.destroystokyo.paper.event.entity.ExperienceOrbMergeEvent((org.bukkit.entity.ExperienceOrb) entity.getBukkitEntity(), (org.bukkit.entity.ExperienceOrb) loopItem.getBukkitEntity()).callEvent()) { // Paper - ExperienceOrbMergeEvent -+ long newTotal = (long)xp.value + (long)loopItem.value; -+ if ((int) newTotal < 0) continue; // Overflow -+ if (!mergeUnconditionally && newTotal > maxValue) { -+ loopItem.value = (int) (newTotal - maxValue); -+ xp.value = (int) maxValue; -+ } else { - xp.value += loopItem.value; - loopItem.discard(null); // Add Bukkit remove cause -+ } // Paper end - Maximum exp value when merging - } - } - } -+ } // Paper end - End iteration skip check - All tweaking ends here - } - } - // Spigot end diff --git a/patches/server/0355-Wait-for-Async-Tasks-during-shutdown.patch b/patches/server/0355-Wait-for-Async-Tasks-during-shutdown.patch new file mode 100644 index 000000000000..1dd08c6eab3b --- /dev/null +++ b/patches/server/0355-Wait-for-Async-Tasks-during-shutdown.patch @@ -0,0 +1,60 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Aikar +Date: Sun, 10 May 2020 22:16:17 -0400 +Subject: [PATCH] Wait for Async Tasks during shutdown + +Server.reload() had this logic to give time for tasks to shutdown, +however shutdown did not... + +Adds a 5 second grace period for any async tasks to finish and warns +if any are still running after that delay just as reload does. + +diff --git a/src/main/java/net/minecraft/server/MinecraftServer.java b/src/main/java/net/minecraft/server/MinecraftServer.java +index 909302aa0afc082a2d0bf55fd31ff9c510e8c3c5..03d6045fd5721b32852a357f1f2e3d2e16efb85b 100644 +--- a/src/main/java/net/minecraft/server/MinecraftServer.java ++++ b/src/main/java/net/minecraft/server/MinecraftServer.java +@@ -956,6 +956,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop 0) { ++ try { ++ Thread.sleep(100); ++ } catch (InterruptedException e) {} ++ pollCount++; ++ } ++ ++ List overdueWorkers = getScheduler().getActiveWorkers(); ++ for (BukkitWorker worker : overdueWorkers) { ++ Plugin plugin = worker.getOwner(); ++ getLogger().log(Level.SEVERE, String.format( ++ "Nag author(s): '%s' of '%s' about the following: %s", ++ plugin.getPluginMeta().getAuthors(), ++ plugin.getPluginMeta().getDisplayName(), ++ "This plugin is not properly shutting down its async tasks when it is being shut down. This task may throw errors during the final shutdown logs and might not complete before process dies." ++ )); ++ if (console.isDebugging()) io.papermc.paper.util.TraceUtil.dumpTraceForThread(worker.getThread(), "still running"); // Paper - Debugging ++ } ++ } ++ // Paper end - Wait for Async Tasks during shutdown ++ + @Override + public void reloadData() { + ReloadCommand.reload(this.console); diff --git a/patches/server/0356-Ensure-EntityRaider-respects-game-and-entity-rules-f.patch b/patches/server/0356-Ensure-EntityRaider-respects-game-and-entity-rules-f.patch new file mode 100644 index 000000000000..1a72e117f560 --- /dev/null +++ b/patches/server/0356-Ensure-EntityRaider-respects-game-and-entity-rules-f.patch @@ -0,0 +1,19 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: kickash32 +Date: Sat, 9 May 2020 02:01:48 -0400 +Subject: [PATCH] Ensure EntityRaider respects game and entity rules for + picking up items + + +diff --git a/src/main/java/net/minecraft/world/entity/raid/Raider.java b/src/main/java/net/minecraft/world/entity/raid/Raider.java +index 96c6a2a6cb015d4ac90f98ce48a4c345ab72fd7a..6ac2351b3476aa04872196836ce00c622adab315 100644 +--- a/src/main/java/net/minecraft/world/entity/raid/Raider.java ++++ b/src/main/java/net/minecraft/world/entity/raid/Raider.java +@@ -338,6 +338,7 @@ public abstract class Raider extends PatrollingMonster { + } + + private boolean cannotPickUpBanner() { ++ if (!getServerLevel(this.mob).getGameRules().getBoolean(net.minecraft.world.level.GameRules.RULE_MOBGRIEFING) || !this.mob.canPickUpLoot()) return false; // Paper - respect game and entity rules for picking up items + if (!this.mob.hasActiveRaid()) { + return true; + } else if (this.mob.getCurrentRaid().isOver()) { diff --git a/patches/server/0356-Fix-PotionEffect-ignores-icon-flag.patch b/patches/server/0356-Fix-PotionEffect-ignores-icon-flag.patch deleted file mode 100644 index 28f3888cb2b5..000000000000 --- a/patches/server/0356-Fix-PotionEffect-ignores-icon-flag.patch +++ /dev/null @@ -1,60 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: kickash32 -Date: Fri, 8 May 2020 00:49:18 -0400 -Subject: [PATCH] Fix PotionEffect ignores icon flag - -Co-authored-by: Tamion <70228790+notTamion@users.noreply.github.com> - -diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftLivingEntity.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftLivingEntity.java -index 05ba1654ec02ff2b518251c128661e3d8dfa4c6d..733c69a2cfa60fb8c920400e3d9acfc2465090e5 100644 ---- a/src/main/java/org/bukkit/craftbukkit/entity/CraftLivingEntity.java -+++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftLivingEntity.java -@@ -485,7 +485,7 @@ public class CraftLivingEntity extends CraftEntity implements LivingEntity { - - @Override - public boolean addPotionEffect(PotionEffect effect, boolean force) { -- this.getHandle().addEffect(new MobEffectInstance(CraftPotionEffectType.bukkitToMinecraftHolder(effect.getType()), effect.getDuration(), effect.getAmplifier(), effect.isAmbient(), effect.hasParticles()), EntityPotionEffectEvent.Cause.PLUGIN); -+ this.getHandle().addEffect(org.bukkit.craftbukkit.potion.CraftPotionUtil.fromBukkit(effect), EntityPotionEffectEvent.Cause.PLUGIN); // Paper - Don't ignore icon - return true; - } - -@@ -506,7 +506,7 @@ public class CraftLivingEntity extends CraftEntity implements LivingEntity { - @Override - public PotionEffect getPotionEffect(PotionEffectType type) { - MobEffectInstance handle = this.getHandle().getEffect(CraftPotionEffectType.bukkitToMinecraftHolder(type)); -- return (handle == null) ? null : new PotionEffect(CraftPotionEffectType.minecraftHolderToBukkit(handle.getEffect()), handle.getDuration(), handle.getAmplifier(), handle.isAmbient(), handle.isVisible()); -+ return (handle == null) ? null : org.bukkit.craftbukkit.potion.CraftPotionUtil.toBukkit(handle); // Paper - } - - @Override -@@ -518,7 +518,7 @@ public class CraftLivingEntity extends CraftEntity implements LivingEntity { - public Collection getActivePotionEffects() { - List effects = new ArrayList(); - for (MobEffectInstance handle : this.getHandle().activeEffects.values()) { -- effects.add(new PotionEffect(CraftPotionEffectType.minecraftHolderToBukkit(handle.getEffect()), handle.getDuration(), handle.getAmplifier(), handle.isAmbient(), handle.isVisible())); -+ effects.add(org.bukkit.craftbukkit.potion.CraftPotionUtil.toBukkit(handle)); // Paper - } - return effects; - } -diff --git a/src/main/java/org/bukkit/craftbukkit/potion/CraftPotionUtil.java b/src/main/java/org/bukkit/craftbukkit/potion/CraftPotionUtil.java -index b7525f6ddc8f315ec9830b6b8f225d4839d306ad..01af4db5d0f17ea2943e5c464d4122a358503bc1 100644 ---- a/src/main/java/org/bukkit/craftbukkit/potion/CraftPotionUtil.java -+++ b/src/main/java/org/bukkit/craftbukkit/potion/CraftPotionUtil.java -@@ -78,7 +78,7 @@ public class CraftPotionUtil { - - public static MobEffectInstance fromBukkit(PotionEffect effect) { - Holder type = CraftPotionEffectType.bukkitToMinecraftHolder(effect.getType()); -- return new MobEffectInstance(type, effect.getDuration(), effect.getAmplifier(), effect.isAmbient(), effect.hasParticles()); -+ return new MobEffectInstance(type, effect.getDuration(), effect.getAmplifier(), effect.isAmbient(), effect.hasParticles(), effect.hasIcon()); // Paper - } - - public static PotionEffect toBukkit(MobEffectInstance effect) { -@@ -87,7 +87,7 @@ public class CraftPotionUtil { - int duration = effect.getDuration(); - boolean ambient = effect.isAmbient(); - boolean particles = effect.isVisible(); -- return new PotionEffect(type, duration, amp, ambient, particles); -+ return new PotionEffect(type, duration, amp, ambient, particles, effect.showIcon()); // Paper - } - - public static boolean equals(Holder mobEffect, PotionEffectType type) { diff --git a/patches/server/0360-Add-option-for-console-having-all-permissions.patch b/patches/server/0357-Add-option-for-console-having-all-permissions.patch similarity index 100% rename from patches/server/0360-Add-option-for-console-having-all-permissions.patch rename to patches/server/0357-Add-option-for-console-having-all-permissions.patch diff --git a/patches/server/0357-Potential-bed-API.patch b/patches/server/0357-Potential-bed-API.patch deleted file mode 100644 index 899b989746eb..000000000000 --- a/patches/server/0357-Potential-bed-API.patch +++ /dev/null @@ -1,44 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: JRoy -Date: Sun, 10 May 2020 23:06:30 -0400 -Subject: [PATCH] Potential bed API - -Adds a new method to fetch the location of a player's bed without generating any sync loads. - -getPotentialBedLocation - Gets the last known location of a player's bed. This does not preform any check if the bed is still valid and does not load any chunks. - -diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftHumanEntity.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftHumanEntity.java -index ba63c58d40cb3b8655fdb8177c423c67ac7cc3ef..c17dd4205983855615289cf0a5619056d237f325 100644 ---- a/src/main/java/org/bukkit/craftbukkit/entity/CraftHumanEntity.java -+++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftHumanEntity.java -@@ -12,6 +12,7 @@ import net.minecraft.nbt.CompoundTag; - import net.minecraft.network.chat.Component; - import net.minecraft.network.protocol.game.ClientboundOpenScreenPacket; - import net.minecraft.network.protocol.game.ServerboundContainerClosePacket; -+import net.minecraft.server.level.ServerLevel; - import net.minecraft.server.level.ServerPlayer; - import net.minecraft.world.MenuProvider; - import net.minecraft.world.entity.Entity; -@@ -129,6 +130,22 @@ public class CraftHumanEntity extends CraftLivingEntity implements HumanEntity { - return this.getHandle().sleepCounter; - } - -+ // Paper start - Potential bed api -+ @Override -+ public Location getPotentialBedLocation() { -+ ServerPlayer handle = (ServerPlayer) getHandle(); -+ BlockPos bed = handle.getRespawnPosition(); -+ if (bed == null) { -+ return null; -+ } -+ -+ ServerLevel worldServer = handle.server.getLevel(handle.getRespawnDimension()); -+ if (worldServer == null) { -+ return null; -+ } -+ return new Location(worldServer.getWorld(), bed.getX(), bed.getY(), bed.getZ()); -+ } -+ // Paper end - @Override - public boolean sleep(Location location, boolean force) { - Preconditions.checkArgument(location != null, "Location cannot be null"); diff --git a/patches/server/0361-Fix-villager-trading-demand-MC-163962.patch b/patches/server/0358-Fix-villager-trading-demand-MC-163962.patch similarity index 100% rename from patches/server/0361-Fix-villager-trading-demand-MC-163962.patch rename to patches/server/0358-Fix-villager-trading-demand-MC-163962.patch diff --git a/patches/server/0358-Wait-for-Async-Tasks-during-shutdown.patch b/patches/server/0358-Wait-for-Async-Tasks-during-shutdown.patch deleted file mode 100644 index fb2ca753f18f..000000000000 --- a/patches/server/0358-Wait-for-Async-Tasks-during-shutdown.patch +++ /dev/null @@ -1,60 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Aikar -Date: Sun, 10 May 2020 22:16:17 -0400 -Subject: [PATCH] Wait for Async Tasks during shutdown - -Server.reload() had this logic to give time for tasks to shutdown, -however shutdown did not... - -Adds a 5 second grace period for any async tasks to finish and warns -if any are still running after that delay just as reload does. - -diff --git a/src/main/java/net/minecraft/server/MinecraftServer.java b/src/main/java/net/minecraft/server/MinecraftServer.java -index 8b17df3d18fe9acc1a7b10c6809886da0143f8c5..0370d26bd1e1d2fe1d640b052aca8a9c05dcb9dc 100644 ---- a/src/main/java/net/minecraft/server/MinecraftServer.java -+++ b/src/main/java/net/minecraft/server/MinecraftServer.java -@@ -942,6 +942,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop 0) { -+ try { -+ Thread.sleep(100); -+ } catch (InterruptedException e) {} -+ pollCount++; -+ } -+ -+ List overdueWorkers = getScheduler().getActiveWorkers(); -+ for (BukkitWorker worker : overdueWorkers) { -+ Plugin plugin = worker.getOwner(); -+ getLogger().log(Level.SEVERE, String.format( -+ "Nag author(s): '%s' of '%s' about the following: %s", -+ plugin.getPluginMeta().getAuthors(), -+ plugin.getPluginMeta().getDisplayName(), -+ "This plugin is not properly shutting down its async tasks when it is being shut down. This task may throw errors during the final shutdown logs and might not complete before process dies." -+ )); -+ if (console.isDebugging()) io.papermc.paper.util.TraceUtil.dumpTraceForThread(worker.getThread(), "still running"); // Paper - Debugging -+ } -+ } -+ // Paper end - Wait for Async Tasks during shutdown -+ - @Override - public void reloadData() { - ReloadCommand.reload(this.console); diff --git a/patches/server/0359-Ensure-EntityRaider-respects-game-and-entity-rules-f.patch b/patches/server/0359-Ensure-EntityRaider-respects-game-and-entity-rules-f.patch deleted file mode 100644 index dfe8e5260b42..000000000000 --- a/patches/server/0359-Ensure-EntityRaider-respects-game-and-entity-rules-f.patch +++ /dev/null @@ -1,19 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: kickash32 -Date: Sat, 9 May 2020 02:01:48 -0400 -Subject: [PATCH] Ensure EntityRaider respects game and entity rules for - picking up items - - -diff --git a/src/main/java/net/minecraft/world/entity/raid/Raider.java b/src/main/java/net/minecraft/world/entity/raid/Raider.java -index 6fb56c826f0eaf76ab7896f784084b4fb1b3d105..f9bd6f5e54bdb4e2fe4cc73e54961721f440ef07 100644 ---- a/src/main/java/net/minecraft/world/entity/raid/Raider.java -+++ b/src/main/java/net/minecraft/world/entity/raid/Raider.java -@@ -292,6 +292,7 @@ public abstract class Raider extends PatrollingMonster { - - @Override - public boolean canUse() { -+ if (!this.mob.level().getGameRules().getBoolean(net.minecraft.world.level.GameRules.RULE_MOBGRIEFING) || !this.mob.canPickUpLoot()) return false; // Paper - respect game and entity rules for picking up items - Raid raid = this.mob.getCurrentRaid(); - - if (this.mob.hasActiveRaid() && !this.mob.getCurrentRaid().isOver() && this.mob.canBeLeader() && !ItemStack.matches(this.mob.getItemBySlot(EquipmentSlot.HEAD), Raid.getLeaderBannerInstance(this.mob.registryAccess().lookupOrThrow(Registries.BANNER_PATTERN)))) { diff --git a/patches/server/0359-Maps-shouldn-t-load-chunks.patch b/patches/server/0359-Maps-shouldn-t-load-chunks.patch new file mode 100644 index 000000000000..450ca270e142 --- /dev/null +++ b/patches/server/0359-Maps-shouldn-t-load-chunks.patch @@ -0,0 +1,31 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Phoenix616 +Date: Sun, 7 Jun 2020 21:43:42 +0100 +Subject: [PATCH] Maps shouldn't load chunks + +Previously maps would load all chunks in a certain radius depending on + their scale when trying to update their content. This would result in + main thread chunk loads when they weren't really necessary, especially + on low view distances or "slow" async chunk loads after teleports or + other prioritisation. + + This changes it to only try to render already loaded chunks based on + the assumption that the chunks around the player will get loaded + eventually anyways and that maps will get checked for update every + five ticks that movement occur in anyways. + +diff --git a/src/main/java/net/minecraft/world/item/MapItem.java b/src/main/java/net/minecraft/world/item/MapItem.java +index 2715c89fb9ea098d8c77b124be1be634835ecb93..af78d15aead7a2dc8304c541e37e306215b761be 100644 +--- a/src/main/java/net/minecraft/world/item/MapItem.java ++++ b/src/main/java/net/minecraft/world/item/MapItem.java +@@ -97,8 +97,8 @@ public class MapItem extends Item { + int r = (j / i + o - 64) * i; + int s = (k / i + p - 64) * i; + Multiset multiset = LinkedHashMultiset.create(); +- LevelChunk levelChunk = world.getChunk(SectionPos.blockToSectionCoord(r), SectionPos.blockToSectionCoord(s)); +- if (!levelChunk.isEmpty()) { ++ LevelChunk levelChunk = world.getChunkIfLoaded(SectionPos.blockToSectionCoord(r), SectionPos.blockToSectionCoord(s)); // Paper - Maps shouldn't load chunks ++ if (levelChunk != null && !levelChunk.isEmpty()) { // Paper - Maps shouldn't load chunks + int t = 0; + double e = 0.0; + if (world.dimensionType().hasCeiling()) { diff --git a/patches/server/0360-Use-seed-based-lookup-for-Treasure-Maps-Fixes-lag-fr.patch b/patches/server/0360-Use-seed-based-lookup-for-Treasure-Maps-Fixes-lag-fr.patch new file mode 100644 index 000000000000..fa9145353c8b --- /dev/null +++ b/patches/server/0360-Use-seed-based-lookup-for-Treasure-Maps-Fixes-lag-fr.patch @@ -0,0 +1,20 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Aikar +Date: Sun, 7 Jun 2020 19:25:13 -0400 +Subject: [PATCH] Use seed based lookup for Treasure Maps - Fixes lag from + carto/sunken maps + + +diff --git a/src/main/java/net/minecraft/world/item/MapItem.java b/src/main/java/net/minecraft/world/item/MapItem.java +index af78d15aead7a2dc8304c541e37e306215b761be..571f2540a1e9422025efe651167e26b44b437daa 100644 +--- a/src/main/java/net/minecraft/world/item/MapItem.java ++++ b/src/main/java/net/minecraft/world/item/MapItem.java +@@ -205,7 +205,7 @@ public class MapItem extends Item { + + for (int n = 0; n < 128; n++) { + for (int o = 0; o < 128; o++) { +- Holder holder = world.getBiome(mutableBlockPos.set((l + o) * i, 0, (m + n) * i)); ++ Holder holder = world.getUncachedNoiseBiome((l + o) * i, 0, (m + n) * i); // Paper - Perf: Use seed based lookup for treasure maps + bls[n * 128 + o] = holder.is(BiomeTags.WATER_ON_MAP_OUTLINES); + } + } diff --git a/patches/server/0361-Fix-CraftScheduler-runTaskTimerAsynchronously-Plugin.patch b/patches/server/0361-Fix-CraftScheduler-runTaskTimerAsynchronously-Plugin.patch new file mode 100644 index 000000000000..72996a4bdcd3 --- /dev/null +++ b/patches/server/0361-Fix-CraftScheduler-runTaskTimerAsynchronously-Plugin.patch @@ -0,0 +1,21 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: ossi +Date: Fri, 12 Jun 2020 01:38:06 +0300 +Subject: [PATCH] Fix CraftScheduler#runTaskTimerAsynchronously(Plugin, + Consumer, long, long) scheduling a non-repeating task instead of + a repeating one. + + +diff --git a/src/main/java/org/bukkit/craftbukkit/scheduler/CraftScheduler.java b/src/main/java/org/bukkit/craftbukkit/scheduler/CraftScheduler.java +index 7de4db0099b380c81d6a809a298d580f0f6e4acc..a700dac93499650fdaa0af06ff77607ffa4dbbb2 100644 +--- a/src/main/java/org/bukkit/craftbukkit/scheduler/CraftScheduler.java ++++ b/src/main/java/org/bukkit/craftbukkit/scheduler/CraftScheduler.java +@@ -196,7 +196,7 @@ public class CraftScheduler implements BukkitScheduler { + + @Override + public void runTaskTimerAsynchronously(Plugin plugin, Consumer task, long delay, long period) throws IllegalArgumentException { +- this.runTaskTimerAsynchronously(plugin, (Object) task, delay, CraftTask.NO_REPEATING); ++ this.runTaskTimerAsynchronously(plugin, (Object) task, delay, period); // Paper + } + + @Override diff --git a/patches/server/0362-Fix-piston-physics-inconsistency-MC-188840.patch b/patches/server/0362-Fix-piston-physics-inconsistency-MC-188840.patch new file mode 100644 index 000000000000..aca7ba66dff4 --- /dev/null +++ b/patches/server/0362-Fix-piston-physics-inconsistency-MC-188840.patch @@ -0,0 +1,79 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Spottedleaf +Date: Thu, 11 Jun 2020 17:29:42 -0700 +Subject: [PATCH] Fix piston physics inconsistency - MC-188840 + +Pistons invoke physics when they move blocks. The physics can cause +tnt blocks to ignite. However, pistons (when storing the blocks they "moved") +don't actually go back to the world state sometimes to check if something +like that happened. As a result they end up moving the tnt like it was +never ignited. This resulted in the ability to create machines +that can duplicate tnt, called "world eaters". +This patch makes the piston logic retrieve the block state from the world +prevent this from occuring. + +This patch also sets the moved pos to air immediately after creating +the moving piston TE. This prevents the block from being updated from +other physics calls by the piston. + +Tested against the following tnt duper design: +https://www.youtube.com/watch?v=mS7xxNGhjxs + +This patch also affects every type of machine that utilises +this mechanic. For example, dead coral is removed by a physics +update when being moved while it is attached to slimeblocks. + +Standard piston machines that don't destroy or modify the +blocks they move by physics updates should be entirely +unaffected. + +This patch fixes https://bugs.mojang.com/browse/MC-188840 + +This patch also fixes rail duping and carpet duping. + +diff --git a/src/main/java/net/minecraft/world/level/block/piston/PistonBaseBlock.java b/src/main/java/net/minecraft/world/level/block/piston/PistonBaseBlock.java +index 0b80940ed23a35e0412957a50d04907b676b0aca..0c6b517196d48ba4384eac240b7e580adfdbc4d4 100644 +--- a/src/main/java/net/minecraft/world/level/block/piston/PistonBaseBlock.java ++++ b/src/main/java/net/minecraft/world/level/block/piston/PistonBaseBlock.java +@@ -419,13 +419,25 @@ public class PistonBaseBlock extends DirectionalBlock { + BlockState iblockdata2; + + for (j = list.size() - 1; j >= 0; --j) { +- blockposition3 = (BlockPos) list.get(j); +- iblockdata1 = world.getBlockState(blockposition3); ++ // Paper start - fix a variety of piston desync dupes ++ boolean allowDesync = io.papermc.paper.configuration.GlobalConfiguration.get().unsupportedSettings.allowPistonDuplication; ++ BlockPos oldPos = blockposition3 = (BlockPos) list.get(j); ++ iblockdata1 = allowDesync ? world.getBlockState(oldPos) : null; ++ // Paper end - fix a variety of piston desync dupes + blockposition3 = blockposition3.relative(enumdirection1); + map.remove(blockposition3); + iblockdata2 = (BlockState) Blocks.MOVING_PISTON.defaultBlockState().setValue(PistonBaseBlock.FACING, dir); + world.setBlock(blockposition3, iblockdata2, 68); +- world.setBlockEntity(MovingPistonBlock.newMovingBlockEntity(blockposition3, iblockdata2, (BlockState) list1.get(j), dir, extend, false)); ++ // Paper start - fix a variety of piston desync dupes ++ if (!allowDesync) { ++ iblockdata1 = world.getBlockState(oldPos); ++ map.replace(oldPos, iblockdata1); ++ } ++ world.setBlockEntity(MovingPistonBlock.newMovingBlockEntity(blockposition3, iblockdata2, allowDesync ? (BlockState) list1.get(j) : iblockdata1, dir, extend, false)); ++ if (!allowDesync) { ++ world.setBlock(oldPos, Blocks.AIR.defaultBlockState(), Block.UPDATE_CLIENTS | Block.UPDATE_KNOWN_SHAPE | Block.UPDATE_MOVE_BY_PISTON | 1024); // set air to prevent later physics updates from seeing this block ++ } ++ // Paper end - fix a variety of piston desync dupes + aiblockdata[i++] = iblockdata1; + } + +diff --git a/src/main/java/net/minecraft/world/level/block/piston/PistonMovingBlockEntity.java b/src/main/java/net/minecraft/world/level/block/piston/PistonMovingBlockEntity.java +index 53d0057e123540162852cb10fba1b7f7ac8388ad..46afba838cf12eeb1bbccaa260131a76f090364b 100644 +--- a/src/main/java/net/minecraft/world/level/block/piston/PistonMovingBlockEntity.java ++++ b/src/main/java/net/minecraft/world/level/block/piston/PistonMovingBlockEntity.java +@@ -306,7 +306,7 @@ public class PistonMovingBlockEntity extends BlockEntity { + if (world.getBlockState(pos).is(Blocks.MOVING_PISTON)) { + BlockState blockState = Block.updateFromNeighbourShapes(blockEntity.movedState, world, pos); + if (blockState.isAir()) { +- world.setBlock(pos, blockEntity.movedState, 84); ++ world.setBlock(pos, blockEntity.movedState, io.papermc.paper.configuration.GlobalConfiguration.get().unsupportedSettings.allowPistonDuplication ? 84 : (84 | Block.UPDATE_CLIENTS)); // Paper - fix a variety of piston desync dupes; force notify (flag 2), it's possible the set type by the piston block (which doesn't notify) set this block to air + Block.updateOrDestroy(blockEntity.movedState, blockState, world, pos, 3); + } else { + if (blockState.hasProperty(BlockStateProperties.WATERLOGGED) && blockState.getValue(BlockStateProperties.WATERLOGGED)) { diff --git a/patches/server/0362-Maps-shouldn-t-load-chunks.patch b/patches/server/0362-Maps-shouldn-t-load-chunks.patch deleted file mode 100644 index 3bc3299f812f..000000000000 --- a/patches/server/0362-Maps-shouldn-t-load-chunks.patch +++ /dev/null @@ -1,31 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Phoenix616 -Date: Sun, 7 Jun 2020 21:43:42 +0100 -Subject: [PATCH] Maps shouldn't load chunks - -Previously maps would load all chunks in a certain radius depending on - their scale when trying to update their content. This would result in - main thread chunk loads when they weren't really necessary, especially - on low view distances or "slow" async chunk loads after teleports or - other prioritisation. - - This changes it to only try to render already loaded chunks based on - the assumption that the chunks around the player will get loaded - eventually anyways and that maps will get checked for update every - five ticks that movement occur in anyways. - -diff --git a/src/main/java/net/minecraft/world/item/MapItem.java b/src/main/java/net/minecraft/world/item/MapItem.java -index 4e6d42051de14a1a62184fbb586ea38fb31261b3..36de7c63370c529579d31ba1d77ec12b754ef313 100644 ---- a/src/main/java/net/minecraft/world/item/MapItem.java -+++ b/src/main/java/net/minecraft/world/item/MapItem.java -@@ -98,8 +98,8 @@ public class MapItem extends ComplexItem { - int r = (j / i + o - 64) * i; - int s = (k / i + p - 64) * i; - Multiset multiset = LinkedHashMultiset.create(); -- LevelChunk levelChunk = world.getChunk(SectionPos.blockToSectionCoord(r), SectionPos.blockToSectionCoord(s)); -- if (!levelChunk.isEmpty()) { -+ LevelChunk levelChunk = world.getChunkIfLoaded(SectionPos.blockToSectionCoord(r), SectionPos.blockToSectionCoord(s)); // Paper - Maps shouldn't load chunks -+ if (levelChunk != null && !levelChunk.isEmpty()) { // Paper - Maps shouldn't load chunks - int t = 0; - double e = 0.0; - if (world.dimensionType().hasCeiling()) { diff --git a/patches/server/0366-Fix-missing-chunks-due-to-integer-overflow.patch b/patches/server/0363-Fix-missing-chunks-due-to-integer-overflow.patch similarity index 100% rename from patches/server/0366-Fix-missing-chunks-due-to-integer-overflow.patch rename to patches/server/0363-Fix-missing-chunks-due-to-integer-overflow.patch diff --git a/patches/server/0363-Use-seed-based-lookup-for-Treasure-Maps-Fixes-lag-fr.patch b/patches/server/0363-Use-seed-based-lookup-for-Treasure-Maps-Fixes-lag-fr.patch deleted file mode 100644 index 27672e59d75d..000000000000 --- a/patches/server/0363-Use-seed-based-lookup-for-Treasure-Maps-Fixes-lag-fr.patch +++ /dev/null @@ -1,20 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Aikar -Date: Sun, 7 Jun 2020 19:25:13 -0400 -Subject: [PATCH] Use seed based lookup for Treasure Maps - Fixes lag from - carto/sunken maps - - -diff --git a/src/main/java/net/minecraft/world/item/MapItem.java b/src/main/java/net/minecraft/world/item/MapItem.java -index 36de7c63370c529579d31ba1d77ec12b754ef313..ce461b1a8d7fab87ae28e30205f6fab67f1808b6 100644 ---- a/src/main/java/net/minecraft/world/item/MapItem.java -+++ b/src/main/java/net/minecraft/world/item/MapItem.java -@@ -206,7 +206,7 @@ public class MapItem extends ComplexItem { - - for (int n = 0; n < 128; n++) { - for (int o = 0; o < 128; o++) { -- Holder holder = world.getBiome(mutableBlockPos.set((l + o) * i, 0, (m + n) * i)); -+ Holder holder = world.getUncachedNoiseBiome((l + o) * i, 0, (m + n) * i); // Paper - Perf: Use seed based lookup for treasure maps - bls[n * 128 + o] = holder.is(BiomeTags.WATER_ON_MAP_OUTLINES); - } - } diff --git a/patches/server/0364-Fix-CraftScheduler-runTaskTimerAsynchronously-Plugin.patch b/patches/server/0364-Fix-CraftScheduler-runTaskTimerAsynchronously-Plugin.patch deleted file mode 100644 index f3c979276054..000000000000 --- a/patches/server/0364-Fix-CraftScheduler-runTaskTimerAsynchronously-Plugin.patch +++ /dev/null @@ -1,21 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: ossi -Date: Fri, 12 Jun 2020 01:38:06 +0300 -Subject: [PATCH] Fix CraftScheduler#runTaskTimerAsynchronously(Plugin, - Consumer, long, long) scheduling a non-repeating task instead of - a repeating one. - - -diff --git a/src/main/java/org/bukkit/craftbukkit/scheduler/CraftScheduler.java b/src/main/java/org/bukkit/craftbukkit/scheduler/CraftScheduler.java -index 02835e4f0a0b262af27acff0939c981cae728db4..d7c2d8993a172e343669228cf24a58d8992a1c10 100644 ---- a/src/main/java/org/bukkit/craftbukkit/scheduler/CraftScheduler.java -+++ b/src/main/java/org/bukkit/craftbukkit/scheduler/CraftScheduler.java -@@ -197,7 +197,7 @@ public class CraftScheduler implements BukkitScheduler { - - @Override - public void runTaskTimerAsynchronously(Plugin plugin, Consumer task, long delay, long period) throws IllegalArgumentException { -- this.runTaskTimerAsynchronously(plugin, (Object) task, delay, CraftTask.NO_REPEATING); -+ this.runTaskTimerAsynchronously(plugin, (Object) task, delay, period); // Paper - } - - @Override diff --git a/patches/server/0364-Prevent-position-desync-causing-tp-exploit.patch b/patches/server/0364-Prevent-position-desync-causing-tp-exploit.patch new file mode 100644 index 000000000000..8b73d196cfd9 --- /dev/null +++ b/patches/server/0364-Prevent-position-desync-causing-tp-exploit.patch @@ -0,0 +1,30 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Spottedleaf +Date: Fri, 12 Jun 2020 16:51:39 -0700 +Subject: [PATCH] Prevent position desync causing tp exploit + +Caused the server to revert to the player's overworld coordinates +after teleporting into the end. + +Sidenote: The underlying issue is that the move call can teleport +entities and do other things like kill the entity. In the future, +to fix all exploits derieved from this usually unexpected +behaviour, we need to move all of this dangerous logic outside +of the move call and into an appropriate place in the tick method. + +diff --git a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java +index 6c9f7a4d3c7551157d22f17e8a66ada2c50c0550..12827fdd39bb7571739efa482ceb1e32f64ea982 100644 +--- a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java ++++ b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java +@@ -1359,6 +1359,11 @@ public class ServerGamePacketListenerImpl extends ServerCommonPacketListenerImpl + + this.player.move(MoverType.PLAYER, new Vec3(d6, d7, d8)); + this.player.onGround = packet.isOnGround(); // CraftBukkit - SPIGOT-5810, SPIGOT-5835, SPIGOT-6828: reset by this.player.move ++ // Paper start - prevent position desync ++ if (this.awaitingPositionFromClient != null) { ++ return; // ... thanks Mojang for letting move calls teleport across dimensions. ++ } ++ // Paper end - prevent position desync + double d11 = d7; + + d6 = d0 - this.player.getX(); diff --git a/patches/server/0365-Fix-piston-physics-inconsistency-MC-188840.patch b/patches/server/0365-Fix-piston-physics-inconsistency-MC-188840.patch deleted file mode 100644 index 44f85960c4ed..000000000000 --- a/patches/server/0365-Fix-piston-physics-inconsistency-MC-188840.patch +++ /dev/null @@ -1,80 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Spottedleaf -Date: Thu, 11 Jun 2020 17:29:42 -0700 -Subject: [PATCH] Fix piston physics inconsistency - MC-188840 - -Pistons invoke physics when they move blocks. The physics can cause -tnt blocks to ignite. However, pistons (when storing the blocks they "moved") -don't actually go back to the world state sometimes to check if something -like that happened. As a result they end up moving the tnt like it was -never ignited. This resulted in the ability to create machines -that can duplicate tnt, called "world eaters". -This patch makes the piston logic retrieve the block state from the world -prevent this from occuring. - -This patch also sets the moved pos to air immediately after creating -the moving piston TE. This prevents the block from being updated from -other physics calls by the piston. - -Tested against the following tnt duper design: -https://www.youtube.com/watch?v=mS7xxNGhjxs - -This patch also affects every type of machine that utilises -this mechanic. For example, dead coral is removed by a physics -update when being moved while it is attached to slimeblocks. - -Standard piston machines that don't destroy or modify the -blocks they move by physics updates should be entirely -unaffected. - -This patch fixes https://bugs.mojang.com/browse/MC-188840 - -This patch also fixes rail duping and carpet duping. - -diff --git a/src/main/java/net/minecraft/world/level/block/piston/PistonBaseBlock.java b/src/main/java/net/minecraft/world/level/block/piston/PistonBaseBlock.java -index 530b6af1ca2e0ac81c0a8a55dbc7cf5c796c93ce..4aa34b7df734bb755906b228e0df9eb629569ea0 100644 ---- a/src/main/java/net/minecraft/world/level/block/piston/PistonBaseBlock.java -+++ b/src/main/java/net/minecraft/world/level/block/piston/PistonBaseBlock.java -@@ -414,14 +414,26 @@ public class PistonBaseBlock extends DirectionalBlock { - } - - for (j = list.size() - 1; j >= 0; --j) { -- blockposition3 = (BlockPos) list.get(j); -- iblockdata1 = world.getBlockState(blockposition3); -+ // Paper start - fix a variety of piston desync dupes -+ boolean allowDesync = io.papermc.paper.configuration.GlobalConfiguration.get().unsupportedSettings.allowPistonDuplication; -+ BlockPos oldPos = blockposition3 = (BlockPos) list.get(j); -+ iblockdata1 = allowDesync ? world.getBlockState(oldPos) : null; -+ // Paper end - fix a variety of piston desync dupes - blockposition3 = blockposition3.relative(enumdirection1); - map.remove(blockposition3); - BlockState iblockdata2 = (BlockState) Blocks.MOVING_PISTON.defaultBlockState().setValue(PistonBaseBlock.FACING, dir); - - world.setBlock(blockposition3, iblockdata2, 68); -- world.setBlockEntity(MovingPistonBlock.newMovingBlockEntity(blockposition3, iblockdata2, (BlockState) list1.get(j), dir, retract, false)); -+ // Paper start - fix a variety of piston desync dupes -+ if (!allowDesync) { -+ iblockdata1 = world.getBlockState(oldPos); -+ map.replace(oldPos, iblockdata1); -+ } -+ world.setBlockEntity(MovingPistonBlock.newMovingBlockEntity(blockposition3, iblockdata2, allowDesync ? list1.get(j) : iblockdata1, dir, retract, false)); -+ if (!allowDesync) { -+ world.setBlock(oldPos, Blocks.AIR.defaultBlockState(), Block.UPDATE_CLIENTS | Block.UPDATE_KNOWN_SHAPE | Block.UPDATE_MOVE_BY_PISTON | 1024); // set air to prevent later physics updates from seeing this block -+ } -+ // Paper end - fix a variety of piston desync dupes - aiblockdata[i++] = iblockdata1; - } - -diff --git a/src/main/java/net/minecraft/world/level/block/piston/PistonMovingBlockEntity.java b/src/main/java/net/minecraft/world/level/block/piston/PistonMovingBlockEntity.java -index 8ea7d7da26e7d5644a8ee1a8d9e4c828c6b70a00..d7b963571c900f0f68005d6954bcd9ef1d9e0b7c 100644 ---- a/src/main/java/net/minecraft/world/level/block/piston/PistonMovingBlockEntity.java -+++ b/src/main/java/net/minecraft/world/level/block/piston/PistonMovingBlockEntity.java -@@ -297,7 +297,7 @@ public class PistonMovingBlockEntity extends BlockEntity { - if (world.getBlockState(pos).is(Blocks.MOVING_PISTON)) { - BlockState blockState = Block.updateFromNeighbourShapes(blockEntity.movedState, world, pos); - if (blockState.isAir()) { -- world.setBlock(pos, blockEntity.movedState, 84); -+ world.setBlock(pos, blockEntity.movedState, io.papermc.paper.configuration.GlobalConfiguration.get().unsupportedSettings.allowPistonDuplication ? 84 : (84 | Block.UPDATE_CLIENTS)); // Paper - fix a variety of piston desync dupes; force notify (flag 2), it's possible the set type by the piston block (which doesn't notify) set this block to air - Block.updateOrDestroy(blockEntity.movedState, blockState, world, pos, 3); - } else { - if (blockState.hasProperty(BlockStateProperties.WATERLOGGED) && blockState.getValue(BlockStateProperties.WATERLOGGED)) { diff --git a/patches/server/0368-Inventory-getHolder-method-without-block-snapshot.patch b/patches/server/0365-Inventory-getHolder-method-without-block-snapshot.patch similarity index 100% rename from patches/server/0368-Inventory-getHolder-method-without-block-snapshot.patch rename to patches/server/0365-Inventory-getHolder-method-without-block-snapshot.patch diff --git a/patches/server/0366-Add-PlayerRecipeBookClickEvent.patch b/patches/server/0366-Add-PlayerRecipeBookClickEvent.patch new file mode 100644 index 000000000000..8083914a426a --- /dev/null +++ b/patches/server/0366-Add-PlayerRecipeBookClickEvent.patch @@ -0,0 +1,65 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: JRoy +Date: Fri, 5 Jun 2020 18:24:06 -0400 +Subject: [PATCH] Add PlayerRecipeBookClickEvent + + +diff --git a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java +index 12827fdd39bb7571739efa482ceb1e32f64ea982..300f543913979427b28578e5bb3270b20065098c 100644 +--- a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java ++++ b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java +@@ -199,6 +199,7 @@ import net.minecraft.world.phys.Vec3; + import net.minecraft.world.phys.shapes.BooleanOp; + import net.minecraft.world.phys.shapes.Shapes; + import net.minecraft.world.phys.shapes.VoxelShape; ++import org.bukkit.NamespacedKey; + import org.slf4j.Logger; + + // CraftBukkit start +@@ -3090,21 +3091,41 @@ public class ServerGamePacketListenerImpl extends ServerCommonPacketListenerImpl + ServerGamePacketListenerImpl.LOGGER.debug("Player {} tried to place impossible recipe {}", this.player, recipeholder.id().location()); + return; + } ++ // Paper start - Add PlayerRecipeBookClickEvent ++ NamespacedKey recipeName = org.bukkit.craftbukkit.util.CraftNamespacedKey.fromMinecraft(recipeholder.id().location()); ++ boolean makeAll = packet.useMaxItems(); ++ com.destroystokyo.paper.event.player.PlayerRecipeBookClickEvent paperEvent = new com.destroystokyo.paper.event.player.PlayerRecipeBookClickEvent( ++ this.player.getBukkitEntity(), recipeName, makeAll ++ ); ++ if (!paperEvent.callEvent()) { ++ return; ++ } ++ recipeName = paperEvent.getRecipe(); ++ makeAll = paperEvent.isMakeAll(); ++ if (org.bukkit.event.player.PlayerRecipeBookClickEvent.getHandlerList().getRegisteredListeners().length > 0) { ++ // Paper end - Add PlayerRecipeBookClickEvent + + // CraftBukkit start - implement PlayerRecipeBookClickEvent +- org.bukkit.inventory.Recipe recipe = recipeholder.toBukkitRecipe(); ++ org.bukkit.inventory.Recipe recipe = this.cserver.getRecipe(recipeName); // Paper - Add PlayerRecipeBookClickEvent - forward to legacy event + if (recipe == null) { + return; + } +- org.bukkit.event.player.PlayerRecipeBookClickEvent event = CraftEventFactory.callRecipeBookClickEvent(this.player, recipe, packet.useMaxItems()); ++ // Paper start - Add PlayerRecipeBookClickEvent - forward to legacy event ++ org.bukkit.event.player.PlayerRecipeBookClickEvent event = CraftEventFactory.callRecipeBookClickEvent(this.player, recipe, makeAll); ++ recipeName = ((org.bukkit.Keyed) event.getRecipe()).getKey(); ++ makeAll = event.isShiftClick(); ++ } ++ if (!(this.player.containerMenu instanceof RecipeBookMenu)) { ++ return; ++ } ++ // Paper end - Add PlayerRecipeBookClickEvent - forward to legacy event + + // Cast to keyed should be safe as the recipe will never be a MerchantRecipe. +- recipeholder = this.server.getRecipeManager().byKey(CraftRecipe.toMinecraft(((org.bukkit.Keyed) event.getRecipe()).getKey())).orElse(null); ++ recipeholder = this.server.getRecipeManager().byKey(net.minecraft.resources.ResourceKey.create(net.minecraft.core.registries.Registries.RECIPE, org.bukkit.craftbukkit.util.CraftNamespacedKey.toMinecraft(recipeName))).orElse(null); // Paper - Add PlayerRecipeBookClickEvent - forward to legacy event + if (recipeholder == null) { + return; + } +- +- RecipeBookMenu.PostPlaceAction containerrecipebook_a = containerrecipebook.handlePlacement(event.isShiftClick(), this.player.isCreative(), recipeholder, this.player.serverLevel(), this.player.getInventory()); ++ RecipeBookMenu.PostPlaceAction containerrecipebook_a = containerrecipebook.handlePlacement(makeAll, this.player.isCreative(), recipeholder, this.player.serverLevel(), this.player.getInventory()); + // CraftBukkit end + + if (containerrecipebook_a == RecipeBookMenu.PostPlaceAction.PLACE_GHOST_RECIPE) { diff --git a/patches/server/0367-Hide-sync-chunk-writes-behind-flag.patch b/patches/server/0367-Hide-sync-chunk-writes-behind-flag.patch new file mode 100644 index 000000000000..c0da32d0ed51 --- /dev/null +++ b/patches/server/0367-Hide-sync-chunk-writes-behind-flag.patch @@ -0,0 +1,23 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Spottedleaf +Date: Fri, 26 Jun 2020 22:35:08 -0700 +Subject: [PATCH] Hide sync chunk writes behind flag + +Syncing writes on each write call has terrible performance +on harddrives. + +-DPaper.enable-sync-chunk-writes=true to enable + +diff --git a/src/main/java/net/minecraft/server/dedicated/DedicatedServerProperties.java b/src/main/java/net/minecraft/server/dedicated/DedicatedServerProperties.java +index 47835226b61b726c750fe192fd94d3f8ba47565c..52e61f75f922a075ccc745198f4ba6ad8fa58ea2 100644 +--- a/src/main/java/net/minecraft/server/dedicated/DedicatedServerProperties.java ++++ b/src/main/java/net/minecraft/server/dedicated/DedicatedServerProperties.java +@@ -146,7 +146,7 @@ public class DedicatedServerProperties extends Settings { + return Mth.clamp(integer, 1, 29999984); + }, 29999984); +- this.syncChunkWrites = this.get("sync-chunk-writes", true); ++ this.syncChunkWrites = this.get("sync-chunk-writes", true) && Boolean.getBoolean("Paper.enable-sync-chunk-writes"); // Paper - Hide sync chunk writes behind flag + this.regionFileComression = this.get("region-file-compression", "deflate"); + this.enableJmxMonitoring = this.get("enable-jmx-monitoring", false); + this.enableStatus = this.get("enable-status", true); diff --git a/patches/server/0367-Prevent-position-desync-causing-tp-exploit.patch b/patches/server/0367-Prevent-position-desync-causing-tp-exploit.patch deleted file mode 100644 index 0bd37ce94ec9..000000000000 --- a/patches/server/0367-Prevent-position-desync-causing-tp-exploit.patch +++ /dev/null @@ -1,30 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Spottedleaf -Date: Fri, 12 Jun 2020 16:51:39 -0700 -Subject: [PATCH] Prevent position desync causing tp exploit - -Caused the server to revert to the player's overworld coordinates -after teleporting into the end. - -Sidenote: The underlying issue is that the move call can teleport -entities and do other things like kill the entity. In the future, -to fix all exploits derieved from this usually unexpected -behaviour, we need to move all of this dangerous logic outside -of the move call and into an appropriate place in the tick method. - -diff --git a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java -index c6bcde25b476ef2362f469bd7cba82ce97cb300c..3455781a47fbececab33406e829ceb392c72a113 100644 ---- a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java -+++ b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java -@@ -1349,6 +1349,11 @@ public class ServerGamePacketListenerImpl extends ServerCommonPacketListenerImpl - - this.player.move(MoverType.PLAYER, new Vec3(d6, d7, d8)); - this.player.onGround = packet.isOnGround(); // CraftBukkit - SPIGOT-5810, SPIGOT-5835, SPIGOT-6828: reset by this.player.move -+ // Paper start - prevent position desync -+ if (this.awaitingPositionFromClient != null) { -+ return; // ... thanks Mojang for letting move calls teleport across dimensions. -+ } -+ // Paper end - prevent position desync - double d11 = d7; - - d6 = d0 - this.player.getX(); diff --git a/patches/server/0368-Add-permission-for-command-blocks.patch b/patches/server/0368-Add-permission-for-command-blocks.patch new file mode 100644 index 000000000000..0a7fb386cc21 --- /dev/null +++ b/patches/server/0368-Add-permission-for-command-blocks.patch @@ -0,0 +1,79 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Mariell Hoversholm +Date: Sat, 16 May 2020 10:05:30 +0200 +Subject: [PATCH] Add permission for command blocks + + +diff --git a/src/main/java/net/minecraft/server/level/ServerPlayerGameMode.java b/src/main/java/net/minecraft/server/level/ServerPlayerGameMode.java +index 4623c8acd125dff4919c4e2045b848310d785da5..86e4559da2344f228ef4d1c4ac3c115fa0266d23 100644 +--- a/src/main/java/net/minecraft/server/level/ServerPlayerGameMode.java ++++ b/src/main/java/net/minecraft/server/level/ServerPlayerGameMode.java +@@ -403,7 +403,7 @@ public class ServerPlayerGameMode { + BlockEntity tileentity = this.level.getBlockEntity(pos); + Block block = iblockdata.getBlock(); + +- if (block instanceof GameMasterBlock && !this.player.canUseGameMasterBlocks()) { ++ if (block instanceof GameMasterBlock && !this.player.canUseGameMasterBlocks() && !(block instanceof net.minecraft.world.level.block.CommandBlock && (this.player.isCreative() && this.player.getBukkitEntity().hasPermission("minecraft.commandblock")))) { // Paper - command block permission + this.level.sendBlockUpdated(pos, iblockdata, iblockdata, 3); + return false; + } else if (this.player.blockActionRestricted(this.level, pos, this.gameModeForPlayer)) { +diff --git a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java +index 300f543913979427b28578e5bb3270b20065098c..0884f71d3264c2a09d2a0958d4751962e4156526 100644 +--- a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java ++++ b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java +@@ -818,7 +818,7 @@ public class ServerGamePacketListenerImpl extends ServerCommonPacketListenerImpl + PacketUtils.ensureRunningOnSameThread(packet, this, this.player.serverLevel()); + if (!this.server.isCommandBlockEnabled()) { + this.player.sendSystemMessage(Component.translatable("advMode.notEnabled")); +- } else if (!this.player.canUseGameMasterBlocks()) { ++ } else if (!this.player.canUseGameMasterBlocks() && (!this.player.isCreative() || !this.player.getBukkitEntity().hasPermission("minecraft.commandblock"))) { // Paper - command block permission + this.player.sendSystemMessage(Component.translatable("advMode.notAllowed")); + } else { + BaseCommandBlock commandblocklistenerabstract = null; +@@ -885,7 +885,7 @@ public class ServerGamePacketListenerImpl extends ServerCommonPacketListenerImpl + PacketUtils.ensureRunningOnSameThread(packet, this, this.player.serverLevel()); + if (!this.server.isCommandBlockEnabled()) { + this.player.sendSystemMessage(Component.translatable("advMode.notEnabled")); +- } else if (!this.player.canUseGameMasterBlocks()) { ++ } else if (!this.player.canUseGameMasterBlocks() && (!this.player.isCreative() || !this.player.getBukkitEntity().hasPermission("minecraft.commandblock"))) { // Paper - command block permission + this.player.sendSystemMessage(Component.translatable("advMode.notAllowed")); + } else { + BaseCommandBlock commandblocklistenerabstract = packet.getCommandBlock(this.player.level()); +diff --git a/src/main/java/net/minecraft/world/level/BaseCommandBlock.java b/src/main/java/net/minecraft/world/level/BaseCommandBlock.java +index 0898dfd42de0c25938d9a25c18f43d5b1a16aa6c..a0e59b236dff1f861a0e987764a77ee203504412 100644 +--- a/src/main/java/net/minecraft/world/level/BaseCommandBlock.java ++++ b/src/main/java/net/minecraft/world/level/BaseCommandBlock.java +@@ -204,7 +204,7 @@ public abstract class BaseCommandBlock implements CommandSource { + } + + public InteractionResult usedBy(Player player) { +- if (!player.canUseGameMasterBlocks()) { ++ if (!player.canUseGameMasterBlocks() && (!player.isCreative() || !player.getBukkitEntity().hasPermission("minecraft.commandblock"))) { // Paper - command block permission + return InteractionResult.PASS; + } else { + if (player.getCommandSenderWorld().isClientSide) { +diff --git a/src/main/java/net/minecraft/world/level/block/CommandBlock.java b/src/main/java/net/minecraft/world/level/block/CommandBlock.java +index 68b44cd7c115c0f42992815171197e6bff50f8c6..fb5777b6a729a465c2482c5f89ced2bc79b425bc 100644 +--- a/src/main/java/net/minecraft/world/level/block/CommandBlock.java ++++ b/src/main/java/net/minecraft/world/level/block/CommandBlock.java +@@ -152,7 +152,7 @@ public class CommandBlock extends BaseEntityBlock implements GameMasterBlock { + protected InteractionResult useWithoutItem(BlockState state, Level world, BlockPos pos, Player player, BlockHitResult hit) { + BlockEntity tileentity = world.getBlockEntity(pos); + +- if (tileentity instanceof CommandBlockEntity && player.canUseGameMasterBlocks()) { ++ if (tileentity instanceof CommandBlockEntity && (player.canUseGameMasterBlocks() || (player.isCreative() && player.getBukkitEntity().hasPermission("minecraft.commandblock")))) { // Paper - command block permission + player.openCommandBlock((CommandBlockEntity) tileentity); + return InteractionResult.SUCCESS; + } else { +diff --git a/src/main/java/org/bukkit/craftbukkit/util/permissions/CraftDefaultPermissions.java b/src/main/java/org/bukkit/craftbukkit/util/permissions/CraftDefaultPermissions.java +index 245ad120a36b6defca7e6889faae1ca5fc33d0c7..e0e61115ada9a49d4c528c5d4e02a1ca571d9531 100644 +--- a/src/main/java/org/bukkit/craftbukkit/util/permissions/CraftDefaultPermissions.java ++++ b/src/main/java/org/bukkit/craftbukkit/util/permissions/CraftDefaultPermissions.java +@@ -16,6 +16,7 @@ public final class CraftDefaultPermissions { + DefaultPermissions.registerPermission(CraftDefaultPermissions.ROOT + ".nbt.copy", "Gives the user the ability to copy NBT in creative", org.bukkit.permissions.PermissionDefault.TRUE, parent); + DefaultPermissions.registerPermission(CraftDefaultPermissions.ROOT + ".debugstick", "Gives the user the ability to use the debug stick in creative", org.bukkit.permissions.PermissionDefault.OP, parent); + DefaultPermissions.registerPermission(CraftDefaultPermissions.ROOT + ".debugstick.always", "Gives the user the ability to use the debug stick in all game modes", org.bukkit.permissions.PermissionDefault.FALSE/* , parent */); // Paper - should not have this parent, as it's not a "vanilla" utility ++ DefaultPermissions.registerPermission(CraftDefaultPermissions.ROOT + ".commandblock", "Gives the user the ability to use command blocks.", org.bukkit.permissions.PermissionDefault.OP, parent); // Paper + // Spigot end + parent.recalculatePermissibles(); + } diff --git a/patches/server/0369-Add-PlayerRecipeBookClickEvent.patch b/patches/server/0369-Add-PlayerRecipeBookClickEvent.patch deleted file mode 100644 index add0eaf3d46f..000000000000 --- a/patches/server/0369-Add-PlayerRecipeBookClickEvent.patch +++ /dev/null @@ -1,55 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: JRoy -Date: Fri, 5 Jun 2020 18:24:06 -0400 -Subject: [PATCH] Add PlayerRecipeBookClickEvent - - -diff --git a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java -index 3455781a47fbececab33406e829ceb392c72a113..b4af560246c1fe102bae9ec9383cbef385bb1887 100644 ---- a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java -+++ b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java -@@ -3056,16 +3056,40 @@ public class ServerGamePacketListenerImpl extends ServerCommonPacketListenerImpl - if (!this.player.containerMenu.stillValid(this.player)) { - ServerGamePacketListenerImpl.LOGGER.debug("Player {} interacted with invalid menu {}", this.player, this.player.containerMenu); - } else { -+ // Paper start - Add PlayerRecipeBookClickEvent -+ ResourceLocation recipeName = packet.getRecipe(); -+ boolean makeAll = packet.isShiftDown(); -+ com.destroystokyo.paper.event.player.PlayerRecipeBookClickEvent paperEvent = new com.destroystokyo.paper.event.player.PlayerRecipeBookClickEvent( -+ this.player.getBukkitEntity(), org.bukkit.craftbukkit.util.CraftNamespacedKey.fromMinecraft(recipeName), makeAll -+ ); -+ if (!paperEvent.callEvent()) { -+ return; -+ } -+ recipeName = CraftNamespacedKey.toMinecraft(paperEvent.getRecipe()); -+ makeAll = paperEvent.isMakeAll(); -+ if (org.bukkit.event.player.PlayerRecipeBookClickEvent.getHandlerList().getRegisteredListeners().length > 0) { -+ // Paper end - Add PlayerRecipeBookClickEvent - // CraftBukkit start - implement PlayerRecipeBookClickEvent -- org.bukkit.inventory.Recipe recipe = this.cserver.getRecipe(CraftNamespacedKey.fromMinecraft(packet.getRecipe())); -+ org.bukkit.inventory.Recipe recipe = this.cserver.getRecipe(CraftNamespacedKey.fromMinecraft(recipeName)); // Paper - if (recipe == null) { - return; - } -- org.bukkit.event.player.PlayerRecipeBookClickEvent event = CraftEventFactory.callRecipeBookClickEvent(this.player, recipe, packet.isShiftDown()); -+ // Paper start - Add PlayerRecipeBookClickEvent -+ org.bukkit.event.player.PlayerRecipeBookClickEvent event = CraftEventFactory.callRecipeBookClickEvent(this.player, recipe, makeAll); -+ recipeName = CraftNamespacedKey.toMinecraft(((org.bukkit.Keyed) event.getRecipe()).getKey()); -+ makeAll = event.isShiftClick(); -+ } -+ if (!(this.player.containerMenu instanceof RecipeBookMenu recipeBookMenu)) { -+ return; -+ } -+ // Paper end - Add PlayerRecipeBookClickEvent - - // Cast to keyed should be safe as the recipe will never be a MerchantRecipe. -- this.server.getRecipeManager().byKey(CraftNamespacedKey.toMinecraft(((org.bukkit.Keyed) event.getRecipe()).getKey())).ifPresent((recipeholder) -> { -- ((RecipeBookMenu) this.player.containerMenu).handlePlacement(event.isShiftClick(), recipeholder, this.player); -+ // Paper start - Add PlayerRecipeBookClickEvent -+ final boolean finalMakeAll = makeAll; -+ this.server.getRecipeManager().byKey(recipeName).ifPresent((recipeholder) -> { -+ recipeBookMenu.handlePlacement(finalMakeAll, recipeholder, this.player); -+ // Paper end - Add PlayerRecipeBookClickEvent - }); - // CraftBukkit end - } diff --git a/patches/server/0369-Ensure-Entity-position-and-AABB-are-never-invalid.patch b/patches/server/0369-Ensure-Entity-position-and-AABB-are-never-invalid.patch new file mode 100644 index 000000000000..09ccee390d5d --- /dev/null +++ b/patches/server/0369-Ensure-Entity-position-and-AABB-are-never-invalid.patch @@ -0,0 +1,65 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Aikar +Date: Sun, 10 May 2020 22:12:46 -0400 +Subject: [PATCH] Ensure Entity position and AABB are never invalid + +Co-authored-by: Spottedleaf + +diff --git a/src/main/java/net/minecraft/world/entity/Entity.java b/src/main/java/net/minecraft/world/entity/Entity.java +index d5f96ed753e8298085e40c6181285cd6ea838ca2..8993c16254ac05d509bd8ac1cd21e7cc7c4097b8 100644 +--- a/src/main/java/net/minecraft/world/entity/Entity.java ++++ b/src/main/java/net/minecraft/world/entity/Entity.java +@@ -677,8 +677,8 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess + } + + public void setPos(double x, double y, double z) { +- this.setPosRaw(x, y, z); +- this.setBoundingBox(this.makeBoundingBox()); ++ this.setPosRaw(x, y, z, true); // Paper - Block invalid positions and bounding box; force update ++ // this.setBoundingBox(this.makeBoundingBox()); // Paper - Block invalid positions and bounding box; move into setPosRaw + } + + protected AABB makeBoundingBox() { +@@ -4412,7 +4412,29 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess + return this.getZ((2.0D * this.random.nextDouble() - 1.0D) * widthScale); + } + ++ // Paper start - Block invalid positions and bounding box ++ public static boolean checkPosition(Entity entity, double newX, double newY, double newZ) { ++ if (Double.isFinite(newX) && Double.isFinite(newY) && Double.isFinite(newZ)) { ++ return true; ++ } ++ ++ String entityInfo; ++ try { ++ entityInfo = entity.toString(); ++ } catch (Exception ex) { ++ entityInfo = "[Entity info unavailable] "; ++ } ++ LOGGER.error("New entity position is invalid! Tried to set invalid position ({},{},{}) for entity {} located at {}, entity info: {}", newX, newY, newZ, entity.getClass().getName(), entity.position, entityInfo, new Throwable()); ++ return false; ++ } + public final void setPosRaw(double x, double y, double z) { ++ this.setPosRaw(x, y, z, false); ++ } ++ public final void setPosRaw(double x, double y, double z, boolean forceBoundingBoxUpdate) { ++ if (!checkPosition(this, x, y, z)) { ++ return; ++ } ++ // Paper end - Block invalid positions and bounding box + if (this.position.x != x || this.position.y != y || this.position.z != z) { + this.position = new Vec3(x, y, z); + int i = Mth.floor(x); +@@ -4430,6 +4452,12 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess + this.levelCallback.onMove(); + } + ++ // Paper start - Block invalid positions and bounding box; don't allow desync of pos and AABB ++ // hanging has its own special logic ++ if (!(this instanceof net.minecraft.world.entity.decoration.HangingEntity) && (forceBoundingBoxUpdate || this.position.x != x || this.position.y != y || this.position.z != z)) { ++ this.setBoundingBox(this.makeBoundingBox()); ++ } ++ // Paper end - Block invalid positions and bounding box + } + + public void checkDespawn() {} diff --git a/patches/server/0370-Fix-Per-World-Difficulty-Remembering-Difficulty.patch b/patches/server/0370-Fix-Per-World-Difficulty-Remembering-Difficulty.patch new file mode 100644 index 000000000000..95cf927bee75 --- /dev/null +++ b/patches/server/0370-Fix-Per-World-Difficulty-Remembering-Difficulty.patch @@ -0,0 +1,118 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Aikar +Date: Sun, 28 Jun 2020 03:59:10 -0400 +Subject: [PATCH] Fix Per World Difficulty / Remembering Difficulty + +Fixes per world difficulty with /difficulty command and also +makes it so that the server keeps the last difficulty used instead +of restoring the server.properties every single load. + +diff --git a/src/main/java/net/minecraft/server/MinecraftServer.java b/src/main/java/net/minecraft/server/MinecraftServer.java +index 03d6045fd5721b32852a357f1f2e3d2e16efb85b..fce70aa2b0d92c6291720b75a07a6472eb55855b 100644 +--- a/src/main/java/net/minecraft/server/MinecraftServer.java ++++ b/src/main/java/net/minecraft/server/MinecraftServer.java +@@ -851,7 +851,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop { + return Component.translatable("commands.difficulty.success", difficulty.getDisplayName()); + }, true); +diff --git a/src/main/java/net/minecraft/server/dedicated/DedicatedServer.java b/src/main/java/net/minecraft/server/dedicated/DedicatedServer.java +index fe1975675189c6d1a63c42b7959fa40b5ac95ef8..9a3e73a5c206b78dfcf6f41a47b614342e52acc8 100644 +--- a/src/main/java/net/minecraft/server/dedicated/DedicatedServer.java ++++ b/src/main/java/net/minecraft/server/dedicated/DedicatedServer.java +@@ -337,7 +337,7 @@ public class DedicatedServer extends MinecraftServer implements ServerInterface + + @Override + public void forceDifficulty() { +- this.setDifficulty(this.getProperties().difficulty, true); ++ // this.setDifficulty(this.getProperties().difficulty, true); // Paper - per level difficulty; Don't overwrite level.dat's difficulty, keep current + } + + @Override +diff --git a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java +index 0884f71d3264c2a09d2a0958d4751962e4156526..8bb1b0e7caa938170169379ce22f21c1c772db60 100644 +--- a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java ++++ b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java +@@ -3308,7 +3308,7 @@ public class ServerGamePacketListenerImpl extends ServerCommonPacketListenerImpl + public void handleChangeDifficulty(ServerboundChangeDifficultyPacket packet) { + PacketUtils.ensureRunningOnSameThread(packet, this, this.player.serverLevel()); + if (this.player.hasPermissions(2) || this.isSingleplayerOwner()) { +- this.server.setDifficulty(packet.getDifficulty(), false); ++ // this.server.setDifficulty(packet.getDifficulty(), false); // Paper - per level difficulty; don't allow clients to change this + } + } + +diff --git a/src/main/java/org/bukkit/craftbukkit/CraftServer.java b/src/main/java/org/bukkit/craftbukkit/CraftServer.java +index d1be64bec1985ff04bf61ed65f18b043e771657c..c52d550ebc7eb9aca2c2e109b2982cad7cb9ac4f 100644 +--- a/src/main/java/org/bukkit/craftbukkit/CraftServer.java ++++ b/src/main/java/org/bukkit/craftbukkit/CraftServer.java +@@ -1000,8 +1000,8 @@ public final class CraftServer implements Server { + org.spigotmc.SpigotConfig.init((File) this.console.options.valueOf("spigot-settings")); // Spigot + this.console.paperConfigurations.reloadConfigs(this.console); + for (ServerLevel world : this.console.getAllLevels()) { +- world.serverLevelData.setDifficulty(config.difficulty); +- world.setSpawnSettings(config.spawnMonsters); ++ // world.serverLevelData.setDifficulty(config.difficulty); // Paper - per level difficulty ++ world.setSpawnSettings(world.serverLevelData.getDifficulty() != Difficulty.PEACEFUL && config.spawnMonsters); // Paper - per level difficulty (from MinecraftServer#setDifficulty(ServerLevel, Difficulty, boolean)) + + for (SpawnCategory spawnCategory : SpawnCategory.values()) { + if (CraftSpawnCategory.isValidForLimits(spawnCategory)) { +diff --git a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java +index d5f8c07480b1dc6b6f97e8ebc74b50493f011f45..84028e959f697fade97640b9b35e5433fcf894ce 100644 +--- a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java ++++ b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java +@@ -1177,7 +1177,7 @@ public class CraftWorld extends CraftRegionAccessor implements World { + + @Override + public void setDifficulty(Difficulty difficulty) { +- this.getHandle().serverLevelData.setDifficulty(net.minecraft.world.Difficulty.byId(difficulty.getValue())); ++ this.getHandle().getServer().setDifficulty(this.getHandle(), net.minecraft.world.Difficulty.byId(difficulty.getValue()), true); // Paper - per level difficulty; don't skip other difficulty-changing logic + } + + @Override diff --git a/patches/server/0370-Hide-sync-chunk-writes-behind-flag.patch b/patches/server/0370-Hide-sync-chunk-writes-behind-flag.patch deleted file mode 100644 index e8889b31d8b0..000000000000 --- a/patches/server/0370-Hide-sync-chunk-writes-behind-flag.patch +++ /dev/null @@ -1,23 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Spottedleaf -Date: Fri, 26 Jun 2020 22:35:08 -0700 -Subject: [PATCH] Hide sync chunk writes behind flag - -Syncing writes on each write call has terrible performance -on harddrives. - --DPaper.enable-sync-chunk-writes=true to enable - -diff --git a/src/main/java/net/minecraft/server/dedicated/DedicatedServerProperties.java b/src/main/java/net/minecraft/server/dedicated/DedicatedServerProperties.java -index eb27ef574445e1311b763d84aa1b37128baa75f7..d6431376184e5650b370cbab204e28bc31f4dac6 100644 ---- a/src/main/java/net/minecraft/server/dedicated/DedicatedServerProperties.java -+++ b/src/main/java/net/minecraft/server/dedicated/DedicatedServerProperties.java -@@ -146,7 +146,7 @@ public class DedicatedServerProperties extends Settings { - return Mth.clamp(integer, 1, 29999984); - }, 29999984); -- this.syncChunkWrites = this.get("sync-chunk-writes", true); -+ this.syncChunkWrites = this.get("sync-chunk-writes", true) && Boolean.getBoolean("Paper.enable-sync-chunk-writes"); // Paper - Hide sync chunk writes behind flag - this.regionFileComression = this.get("region-file-compression", "deflate"); - this.enableJmxMonitoring = this.get("enable-jmx-monitoring", false); - this.enableStatus = this.get("enable-status", true); diff --git a/patches/server/0371-Add-permission-for-command-blocks.patch b/patches/server/0371-Add-permission-for-command-blocks.patch deleted file mode 100644 index 84caef8385ad..000000000000 --- a/patches/server/0371-Add-permission-for-command-blocks.patch +++ /dev/null @@ -1,79 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Mariell Hoversholm -Date: Sat, 16 May 2020 10:05:30 +0200 -Subject: [PATCH] Add permission for command blocks - - -diff --git a/src/main/java/net/minecraft/server/level/ServerPlayerGameMode.java b/src/main/java/net/minecraft/server/level/ServerPlayerGameMode.java -index da9e864520150acd8027545672aa476be414bb4d..d4bd44210d58b30696feeea48e1909472a546702 100644 ---- a/src/main/java/net/minecraft/server/level/ServerPlayerGameMode.java -+++ b/src/main/java/net/minecraft/server/level/ServerPlayerGameMode.java -@@ -405,7 +405,7 @@ public class ServerPlayerGameMode { - BlockEntity tileentity = this.level.getBlockEntity(pos); - Block block = iblockdata.getBlock(); - -- if (block instanceof GameMasterBlock && !this.player.canUseGameMasterBlocks()) { -+ if (block instanceof GameMasterBlock && !this.player.canUseGameMasterBlocks() && !(block instanceof net.minecraft.world.level.block.CommandBlock && (this.player.isCreative() && this.player.getBukkitEntity().hasPermission("minecraft.commandblock")))) { // Paper - command block permission - this.level.sendBlockUpdated(pos, iblockdata, iblockdata, 3); - return false; - } else if (this.player.blockActionRestricted(this.level, pos, this.gameModeForPlayer)) { -diff --git a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java -index b4af560246c1fe102bae9ec9383cbef385bb1887..e242ca5ec8c50bfd81b8ab1429e6e4e441025a75 100644 ---- a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java -+++ b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java -@@ -806,7 +806,7 @@ public class ServerGamePacketListenerImpl extends ServerCommonPacketListenerImpl - PacketUtils.ensureRunningOnSameThread(packet, this, this.player.serverLevel()); - if (!this.server.isCommandBlockEnabled()) { - this.player.sendSystemMessage(Component.translatable("advMode.notEnabled")); -- } else if (!this.player.canUseGameMasterBlocks()) { -+ } else if (!this.player.canUseGameMasterBlocks() && (!this.player.isCreative() || !this.player.getBukkitEntity().hasPermission("minecraft.commandblock"))) { // Paper - command block permission - this.player.sendSystemMessage(Component.translatable("advMode.notAllowed")); - } else { - BaseCommandBlock commandblocklistenerabstract = null; -@@ -873,7 +873,7 @@ public class ServerGamePacketListenerImpl extends ServerCommonPacketListenerImpl - PacketUtils.ensureRunningOnSameThread(packet, this, this.player.serverLevel()); - if (!this.server.isCommandBlockEnabled()) { - this.player.sendSystemMessage(Component.translatable("advMode.notEnabled")); -- } else if (!this.player.canUseGameMasterBlocks()) { -+ } else if (!this.player.canUseGameMasterBlocks() && (!this.player.isCreative() || !this.player.getBukkitEntity().hasPermission("minecraft.commandblock"))) { // Paper - command block permission - this.player.sendSystemMessage(Component.translatable("advMode.notAllowed")); - } else { - BaseCommandBlock commandblocklistenerabstract = packet.getCommandBlock(this.player.level()); -diff --git a/src/main/java/net/minecraft/world/level/BaseCommandBlock.java b/src/main/java/net/minecraft/world/level/BaseCommandBlock.java -index efd6fcb0528d3c903e720edeb2f704347b34c18d..8c2dcc4134d96351cee75773214f3f47e71533e9 100644 ---- a/src/main/java/net/minecraft/world/level/BaseCommandBlock.java -+++ b/src/main/java/net/minecraft/world/level/BaseCommandBlock.java -@@ -204,7 +204,7 @@ public abstract class BaseCommandBlock implements CommandSource { - } - - public InteractionResult usedBy(Player player) { -- if (!player.canUseGameMasterBlocks()) { -+ if (!player.canUseGameMasterBlocks() && (!player.isCreative() || !player.getBukkitEntity().hasPermission("minecraft.commandblock"))) { // Paper - command block permission - return InteractionResult.PASS; - } else { - if (player.getCommandSenderWorld().isClientSide) { -diff --git a/src/main/java/net/minecraft/world/level/block/CommandBlock.java b/src/main/java/net/minecraft/world/level/block/CommandBlock.java -index 72165318bcbd680d8dc69f3467cb78a439c9672e..eb88b8b6d83482ce7144622f32923ebeafd1fb7b 100644 ---- a/src/main/java/net/minecraft/world/level/block/CommandBlock.java -+++ b/src/main/java/net/minecraft/world/level/block/CommandBlock.java -@@ -141,7 +141,7 @@ public class CommandBlock extends BaseEntityBlock implements GameMasterBlock { - protected InteractionResult useWithoutItem(BlockState state, Level world, BlockPos pos, Player player, BlockHitResult hit) { - BlockEntity tileentity = world.getBlockEntity(pos); - -- if (tileentity instanceof CommandBlockEntity && player.canUseGameMasterBlocks()) { -+ if (tileentity instanceof CommandBlockEntity && (player.canUseGameMasterBlocks() || (player.isCreative() && player.getBukkitEntity().hasPermission("minecraft.commandblock")))) { // Paper - command block permission - player.openCommandBlock((CommandBlockEntity) tileentity); - return InteractionResult.sidedSuccess(world.isClientSide); - } else { -diff --git a/src/main/java/org/bukkit/craftbukkit/util/permissions/CraftDefaultPermissions.java b/src/main/java/org/bukkit/craftbukkit/util/permissions/CraftDefaultPermissions.java -index 245ad120a36b6defca7e6889faae1ca5fc33d0c7..e0e61115ada9a49d4c528c5d4e02a1ca571d9531 100644 ---- a/src/main/java/org/bukkit/craftbukkit/util/permissions/CraftDefaultPermissions.java -+++ b/src/main/java/org/bukkit/craftbukkit/util/permissions/CraftDefaultPermissions.java -@@ -16,6 +16,7 @@ public final class CraftDefaultPermissions { - DefaultPermissions.registerPermission(CraftDefaultPermissions.ROOT + ".nbt.copy", "Gives the user the ability to copy NBT in creative", org.bukkit.permissions.PermissionDefault.TRUE, parent); - DefaultPermissions.registerPermission(CraftDefaultPermissions.ROOT + ".debugstick", "Gives the user the ability to use the debug stick in creative", org.bukkit.permissions.PermissionDefault.OP, parent); - DefaultPermissions.registerPermission(CraftDefaultPermissions.ROOT + ".debugstick.always", "Gives the user the ability to use the debug stick in all game modes", org.bukkit.permissions.PermissionDefault.FALSE/* , parent */); // Paper - should not have this parent, as it's not a "vanilla" utility -+ DefaultPermissions.registerPermission(CraftDefaultPermissions.ROOT + ".commandblock", "Gives the user the ability to use command blocks.", org.bukkit.permissions.PermissionDefault.OP, parent); // Paper - // Spigot end - parent.recalculatePermissibles(); - } diff --git a/patches/server/0371-Paper-dumpitem-command.patch b/patches/server/0371-Paper-dumpitem-command.patch new file mode 100644 index 000000000000..3c8440021538 --- /dev/null +++ b/patches/server/0371-Paper-dumpitem-command.patch @@ -0,0 +1,158 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Aikar +Date: Sun, 28 Jun 2020 19:27:20 -0400 +Subject: [PATCH] Paper dumpitem command + +Let's you quickly view the item in your hands NBT data + +diff --git a/src/main/java/io/papermc/paper/command/PaperCommand.java b/src/main/java/io/papermc/paper/command/PaperCommand.java +index 3010d57efcc97fb409bfe43b1fc9af198c099a67..cdad0fd5257ae842f83b9c1c98b4565b468d4f54 100644 +--- a/src/main/java/io/papermc/paper/command/PaperCommand.java ++++ b/src/main/java/io/papermc/paper/command/PaperCommand.java +@@ -39,6 +39,7 @@ public final class PaperCommand extends Command { + commands.put(Set.of("version"), new VersionCommand()); + commands.put(Set.of("dumpplugins"), new DumpPluginsCommand()); + commands.put(Set.of("syncloadinfo"), new SyncLoadInfoCommand()); ++ commands.put(Set.of("dumpitem"), new DumpItemCommand()); + + return commands.entrySet().stream() + .flatMap(entry -> entry.getKey().stream().map(s -> Map.entry(s, entry.getValue()))) +diff --git a/src/main/java/io/papermc/paper/command/subcommands/DumpItemCommand.java b/src/main/java/io/papermc/paper/command/subcommands/DumpItemCommand.java +new file mode 100644 +index 0000000000000000000000000000000000000000..b4b90c1bda72f845756b46e2316d952361989697 +--- /dev/null ++++ b/src/main/java/io/papermc/paper/command/subcommands/DumpItemCommand.java +@@ -0,0 +1,133 @@ ++package io.papermc.paper.command.subcommands; ++ ++import io.papermc.paper.adventure.PaperAdventure; ++import io.papermc.paper.command.CommandUtil; ++import io.papermc.paper.command.PaperSubcommand; ++import java.util.ArrayList; ++import java.util.Collections; ++import java.util.IdentityHashMap; ++import java.util.List; ++import java.util.Map; ++import java.util.Optional; ++import java.util.Set; ++import java.util.function.Consumer; ++import net.kyori.adventure.text.Component; ++import net.kyori.adventure.text.ComponentLike; ++import net.kyori.adventure.text.JoinConfiguration; ++import net.kyori.adventure.text.TextComponent; ++import net.minecraft.core.Registry; ++import net.minecraft.core.RegistryAccess; ++import net.minecraft.core.component.DataComponentMap; ++import net.minecraft.core.component.DataComponentPatch; ++import net.minecraft.core.component.DataComponentType; ++import net.minecraft.core.component.TypedDataComponent; ++import net.minecraft.core.registries.Registries; ++import net.minecraft.nbt.NbtOps; ++import net.minecraft.nbt.NbtUtils; ++import net.minecraft.nbt.SnbtPrinterTagVisitor; ++import net.minecraft.nbt.Tag; ++import net.minecraft.resources.RegistryOps; ++import net.minecraft.world.item.ItemStack; ++import org.bukkit.command.CommandSender; ++import org.bukkit.craftbukkit.CraftServer; ++import org.bukkit.craftbukkit.inventory.CraftItemStack; ++import org.bukkit.entity.Player; ++import org.checkerframework.checker.nullness.qual.NonNull; ++import org.checkerframework.checker.nullness.qual.Nullable; ++import org.checkerframework.framework.qual.DefaultQualifier; ++ ++import static net.kyori.adventure.text.Component.join; ++import static net.kyori.adventure.text.Component.text; ++import static net.kyori.adventure.text.Component.textOfChildren; ++import static net.kyori.adventure.text.event.ClickEvent.copyToClipboard; ++import static net.kyori.adventure.text.format.NamedTextColor.AQUA; ++import static net.kyori.adventure.text.format.NamedTextColor.GRAY; ++import static net.kyori.adventure.text.format.NamedTextColor.RED; ++import static net.kyori.adventure.text.format.NamedTextColor.WHITE; ++import static net.kyori.adventure.text.format.NamedTextColor.YELLOW; ++import static net.kyori.adventure.text.format.TextColor.color; ++import static net.kyori.adventure.text.format.TextDecoration.ITALIC; ++ ++@DefaultQualifier(NonNull.class) ++public final class DumpItemCommand implements PaperSubcommand { ++ @Override ++ public boolean execute(final CommandSender sender, final String subCommand, final String[] args) { ++ this.doDumpItem(sender, args.length > 0 && "all".equals(args[0])); ++ return true; ++ } ++ ++ @SuppressWarnings({"unchecked", "OptionalAssignedToNull", "rawtypes"}) ++ private void doDumpItem(final CommandSender sender, final boolean includeAllComponents) { ++ if (!(sender instanceof final Player player)) { ++ sender.sendMessage("Only players can use this command"); ++ return; ++ } ++ final ItemStack itemStack = CraftItemStack.asNMSCopy(player.getInventory().getItemInMainHand()); ++ final TextComponent.Builder visualOutput = Component.text(); ++ final StringBuilder itemCommandBuilder = new StringBuilder(); ++ final String itemName = itemStack.getItemHolder().unwrapKey().orElseThrow().location().toString(); ++ itemCommandBuilder.append(itemName); ++ visualOutput.append(text(itemName, YELLOW)); // item type ++ final Set> referencedComponentTypes = Collections.newSetFromMap(new IdentityHashMap<>()); ++ final DataComponentPatch patch = itemStack.getComponentsPatch(); ++ referencedComponentTypes.addAll(patch.entrySet().stream().map(Map.Entry::getKey).toList()); ++ final DataComponentMap prototype = itemStack.getItem().components(); ++ if (includeAllComponents) { ++ referencedComponentTypes.addAll(prototype.keySet()); ++ } ++ ++ final RegistryAccess.Frozen access = ((CraftServer) sender.getServer()).getServer().registryAccess(); ++ final RegistryOps ops = access.createSerializationContext(NbtOps.INSTANCE); ++ final Registry> registry = access.lookupOrThrow(Registries.DATA_COMPONENT_TYPE); ++ final List componentComponents = new ArrayList<>(); ++ final List commandComponents = new ArrayList<>(); ++ for (final DataComponentType type : referencedComponentTypes) { ++ final String path = registry.getResourceKey(type).orElseThrow().location().getPath(); ++ final @Nullable Optional patchedValue = patch.get(type); ++ final @Nullable TypedDataComponent prototypeValue = prototype.getTyped(type); ++ if (patchedValue != null) { ++ if (patchedValue.isEmpty()) { ++ componentComponents.add(text().append(text('!', RED), text(path, AQUA))); ++ commandComponents.add("!" + path); ++ } else { ++ final Tag serialized = (Tag) ((DataComponentType) type).codecOrThrow().encodeStart(ops, patchedValue.get()).getOrThrow(); ++ writeComponentValue(componentComponents::add, commandComponents::add, path, serialized); ++ } ++ } else if (includeAllComponents && prototypeValue != null) { ++ final Tag serialized = prototypeValue.encodeValue(ops).getOrThrow(); ++ writeComponentValue(componentComponents::add, commandComponents::add, path, serialized); ++ } ++ } ++ if (!componentComponents.isEmpty()) { ++ visualOutput.append( ++ text("[", color(0x8910CE)), ++ join(JoinConfiguration.separator(text(",", GRAY)), componentComponents), ++ text("]", color(0x8910CE)) ++ ); ++ itemCommandBuilder ++ .append("[") ++ .append(String.join(",", commandComponents)) ++ .append("]"); ++ } ++ player.sendMessage(visualOutput.build().compact()); ++ final Component copyMsg = text("Click to copy item definition to clipboard for use with /give", GRAY, ITALIC); ++ sender.sendMessage(copyMsg.clickEvent(copyToClipboard(itemCommandBuilder.toString()))); ++ } ++ ++ private static void writeComponentValue(final Consumer visualOutput, final Consumer commandOutput, final String path, final Tag serialized) { ++ visualOutput.accept(textOfChildren( ++ text(path, color(0xFF7FD7)), ++ text("=", WHITE), ++ PaperAdventure.asAdventure(NbtUtils.toPrettyComponent(serialized)) ++ )); ++ commandOutput.accept(path + "=" + new SnbtPrinterTagVisitor("", 0, new ArrayList<>()).visit(serialized)); ++ } ++ ++ @Override ++ public List tabComplete(final CommandSender sender, final String subCommand, final String[] args) { ++ if (args.length == 1) { ++ return CommandUtil.getListMatchingLast(sender, args, "all"); ++ } ++ return Collections.emptyList(); ++ } ++} diff --git a/patches/server/0372-Ensure-Entity-position-and-AABB-are-never-invalid.patch b/patches/server/0372-Ensure-Entity-position-and-AABB-are-never-invalid.patch deleted file mode 100644 index 63a1e5a41101..000000000000 --- a/patches/server/0372-Ensure-Entity-position-and-AABB-are-never-invalid.patch +++ /dev/null @@ -1,65 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Aikar -Date: Sun, 10 May 2020 22:12:46 -0400 -Subject: [PATCH] Ensure Entity position and AABB are never invalid - -Co-authored-by: Spottedleaf - -diff --git a/src/main/java/net/minecraft/world/entity/Entity.java b/src/main/java/net/minecraft/world/entity/Entity.java -index 10015beb7a2de890fe27bffde8f55c1dd78ce344..9d08d0610be1741b4cd18a1454e538f46928fd94 100644 ---- a/src/main/java/net/minecraft/world/entity/Entity.java -+++ b/src/main/java/net/minecraft/world/entity/Entity.java -@@ -669,8 +669,8 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess - } - - public void setPos(double x, double y, double z) { -- this.setPosRaw(x, y, z); -- this.setBoundingBox(this.makeBoundingBox()); -+ this.setPosRaw(x, y, z, true); // Paper - Block invalid positions and bounding box; force update -+ // this.setBoundingBox(this.makeBoundingBox()); // Paper - Block invalid positions and bounding box; move into setPosRaw - } - - protected AABB makeBoundingBox() { -@@ -4223,7 +4223,29 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess - return this.getZ((2.0D * this.random.nextDouble() - 1.0D) * widthScale); - } - -+ // Paper start - Block invalid positions and bounding box -+ public static boolean checkPosition(Entity entity, double newX, double newY, double newZ) { -+ if (Double.isFinite(newX) && Double.isFinite(newY) && Double.isFinite(newZ)) { -+ return true; -+ } -+ -+ String entityInfo; -+ try { -+ entityInfo = entity.toString(); -+ } catch (Exception ex) { -+ entityInfo = "[Entity info unavailable] "; -+ } -+ LOGGER.error("New entity position is invalid! Tried to set invalid position ({},{},{}) for entity {} located at {}, entity info: {}", newX, newY, newZ, entity.getClass().getName(), entity.position, entityInfo, new Throwable()); -+ return false; -+ } - public final void setPosRaw(double x, double y, double z) { -+ this.setPosRaw(x, y, z, false); -+ } -+ public final void setPosRaw(double x, double y, double z, boolean forceBoundingBoxUpdate) { -+ if (!checkPosition(this, x, y, z)) { -+ return; -+ } -+ // Paper end - Block invalid positions and bounding box - if (this.position.x != x || this.position.y != y || this.position.z != z) { - this.position = new Vec3(x, y, z); - int i = Mth.floor(x); -@@ -4241,6 +4263,12 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess - this.levelCallback.onMove(); - } - -+ // Paper start - Block invalid positions and bounding box; don't allow desync of pos and AABB -+ // hanging has its own special logic -+ if (!(this instanceof net.minecraft.world.entity.decoration.HangingEntity) && (forceBoundingBoxUpdate || this.position.x != x || this.position.y != y || this.position.z != z)) { -+ this.setBoundingBox(this.makeBoundingBox()); -+ } -+ // Paper end - Block invalid positions and bounding box - } - - public void checkDespawn() {} diff --git a/patches/server/0372-Improve-Legacy-Component-serialization-size.patch b/patches/server/0372-Improve-Legacy-Component-serialization-size.patch new file mode 100644 index 000000000000..e52764c13c93 --- /dev/null +++ b/patches/server/0372-Improve-Legacy-Component-serialization-size.patch @@ -0,0 +1,56 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Aikar +Date: Sun, 28 Jun 2020 19:08:41 -0400 +Subject: [PATCH] Improve Legacy Component serialization size + +Don't constantly send format: false for all formatting options when parent already +has it false + +diff --git a/src/main/java/org/bukkit/craftbukkit/util/CraftChatMessage.java b/src/main/java/org/bukkit/craftbukkit/util/CraftChatMessage.java +index 95444fd9fecc5bda5462ca8dfeca82c5318f0895..fd697475e2df1cef8260b47dabb491beb0bd1a8e 100644 +--- a/src/main/java/org/bukkit/craftbukkit/util/CraftChatMessage.java ++++ b/src/main/java/org/bukkit/craftbukkit/util/CraftChatMessage.java +@@ -48,6 +48,7 @@ public final class CraftChatMessage { + // Separate pattern with no group 3, new lines are part of previous string + private static final Pattern INCREMENTAL_PATTERN_KEEP_NEWLINES = Pattern.compile("(" + String.valueOf(org.bukkit.ChatColor.COLOR_CHAR) + "[0-9a-fk-orx])|((?:(?:https?):\\/\\/)?(?:[-\\w_\\.]{2,}\\.[a-z]{2,4}.*?(?=[\\.\\?!,;:]?(?:[" + String.valueOf(org.bukkit.ChatColor.COLOR_CHAR) + " ]|$))))", Pattern.CASE_INSENSITIVE); + // ChatColor.b does not explicitly reset, its more of empty ++ private static final Style EMPTY = Style.EMPTY.withItalic(false); // Paper - Improve Legacy Component serialization size + private static final Style RESET = Style.EMPTY.withBold(false).withItalic(false).withUnderlined(false).withStrikethrough(false).withObfuscated(false); + + private final List list = new ArrayList(); +@@ -69,6 +70,7 @@ public final class CraftChatMessage { + Matcher matcher = (keepNewlines ? StringMessage.INCREMENTAL_PATTERN_KEEP_NEWLINES : StringMessage.INCREMENTAL_PATTERN).matcher(message); + String match = null; + boolean needsAdd = false; ++ boolean hasReset = false; // Paper - Improve Legacy Component serialization size + while (matcher.find()) { + int groupId = 0; + while ((match = matcher.group(++groupId)) == null) { +@@ -114,7 +116,26 @@ public final class CraftChatMessage { + throw new AssertionError("Unexpected message format"); + } + } else { // Color resets formatting +- this.modifier = StringMessage.RESET.withColor(format); ++ // Paper start - Improve Legacy Component serialization size ++ Style previous = modifier; ++ modifier = (!hasReset ? RESET : EMPTY).withColor(format); ++ hasReset = true; ++ if (previous.isBold()) { ++ modifier = modifier.withBold(false); ++ } ++ if (previous.isItalic()) { ++ modifier = modifier.withItalic(false); ++ } ++ if (previous.isObfuscated()) { ++ modifier = modifier.withObfuscated(false); ++ } ++ if (previous.isStrikethrough()) { ++ modifier = modifier.withStrikethrough(false); ++ } ++ if (previous.isUnderlined()) { ++ modifier = modifier.withUnderlined(false); ++ } ++ // Paper end - Improve Legacy Component serialization size + } + needsAdd = true; + break; diff --git a/patches/server/0373-Add-Plugin-Tickets-to-API-Chunk-Methods.patch b/patches/server/0373-Add-Plugin-Tickets-to-API-Chunk-Methods.patch new file mode 100644 index 000000000000..81762652ca54 --- /dev/null +++ b/patches/server/0373-Add-Plugin-Tickets-to-API-Chunk-Methods.patch @@ -0,0 +1,103 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Aikar +Date: Tue, 9 Jun 2020 03:33:03 -0400 +Subject: [PATCH] Add Plugin Tickets to API Chunk Methods + +Like previous versions, plugins loading chunks kept them loaded until +they garbage collected to avoid constant spamming of chunk loads + +This adds tickets to a few more places so that they can be unloaded. + +Additionally, this drops their ticket level to BORDER so they wont be ticking +so they will just sit inactive instead. + +Using .loadChunk to keep a chunk ticking was a horrible idea for upstream +when we have TWO methods that are able to do that already in the API. + +Also reduce their collection count down to a maximum of 1 second. Barely +anyone knows what chunk-gc is in bukkit.yml as its less relevant now, and +since this wasn't spigot behavior, this is safe to mostly ignore (unless someone +wants it to collect even faster, they can restore that setting back to 1 instead of 20+) + +Not adding it to .getType() though to keep behavior consistent with vanilla for performance reasons. + +diff --git a/src/main/java/org/bukkit/craftbukkit/CraftServer.java b/src/main/java/org/bukkit/craftbukkit/CraftServer.java +index c52d550ebc7eb9aca2c2e109b2982cad7cb9ac4f..134cd194962be898253e034b19524fad0d48ada5 100644 +--- a/src/main/java/org/bukkit/craftbukkit/CraftServer.java ++++ b/src/main/java/org/bukkit/craftbukkit/CraftServer.java +@@ -382,7 +382,7 @@ public final class CraftServer implements Server { + this.overrideSpawnLimits(); + console.autosavePeriod = this.configuration.getInt("ticks-per.autosave"); + this.warningState = WarningState.value(this.configuration.getString("settings.deprecated-verbose")); +- TicketType.PLUGIN.timeout = this.configuration.getInt("chunk-gc.period-in-ticks"); ++ TicketType.PLUGIN.timeout = Math.min(20, this.configuration.getInt("chunk-gc.period-in-ticks")); // Paper - cap plugin loads to 1 second + this.minimumAPI = ApiVersion.getOrCreateVersion(this.configuration.getString("settings.minimum-api")); + this.loadIcon(); + this.loadCompatibilities(); +@@ -978,7 +978,7 @@ public final class CraftServer implements Server { + this.console.setMotd(config.motd); + this.overrideSpawnLimits(); + this.warningState = WarningState.value(this.configuration.getString("settings.deprecated-verbose")); +- TicketType.PLUGIN.timeout = this.configuration.getInt("chunk-gc.period-in-ticks"); ++ TicketType.PLUGIN.timeout = Math.min(20, configuration.getInt("chunk-gc.period-in-ticks")); // Paper - cap plugin loads to 1 second + this.minimumAPI = ApiVersion.getOrCreateVersion(this.configuration.getString("settings.minimum-api")); + this.printSaveWarning = false; + this.console.autosavePeriod = this.configuration.getInt("ticks-per.autosave"); +diff --git a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java +index 84028e959f697fade97640b9b35e5433fcf894ce..55daddd0d80a18a3c044f48a47a0a13ef3a68943 100644 +--- a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java ++++ b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java +@@ -270,7 +270,13 @@ public class CraftWorld extends CraftRegionAccessor implements World { + + @Override + public Chunk getChunkAt(int x, int z) { +- net.minecraft.world.level.chunk.LevelChunk chunk = (net.minecraft.world.level.chunk.LevelChunk) this.world.getChunk(x, z, ChunkStatus.FULL, true); ++ // Paper start - add ticket to hold chunk for a little while longer if plugin accesses it ++ net.minecraft.world.level.chunk.LevelChunk chunk = this.world.getChunkSource().getChunkAtIfLoadedImmediately(x, z); ++ if (chunk == null) { ++ this.addTicket(x, z); ++ chunk = this.world.getChunkSource().getChunk(x, z, true); ++ } ++ // Paper end + return new CraftChunk(chunk); + } + +@@ -284,6 +290,12 @@ public class CraftWorld extends CraftRegionAccessor implements World { + return new CraftChunk(this.getHandle(), x, z); + } + ++ // Paper start ++ private void addTicket(int x, int z) { ++ io.papermc.paper.util.MCUtil.MAIN_EXECUTOR.execute(() -> this.world.getChunkSource().addRegionTicket(TicketType.PLUGIN, new ChunkPos(x, z), 0, Unit.INSTANCE)); // Paper ++ } ++ // Paper end ++ + @Override + public Chunk getChunkAt(Block block) { + Preconditions.checkArgument(block != null, "null block"); +@@ -335,7 +347,7 @@ public class CraftWorld extends CraftRegionAccessor implements World { + public boolean unloadChunkRequest(int x, int z) { + org.spigotmc.AsyncCatcher.catchOp("chunk unload"); // Spigot + if (this.isChunkLoaded(x, z)) { +- this.world.getChunkSource().removeRegionTicket(TicketType.PLUGIN, new ChunkPos(x, z), 1, Unit.INSTANCE); ++ this.world.getChunkSource().removeRegionTicket(TicketType.PLUGIN, new ChunkPos(x, z), 0, Unit.INSTANCE); // Paper + } + + return true; +@@ -447,7 +459,7 @@ public class CraftWorld extends CraftRegionAccessor implements World { + } + + if (chunk instanceof net.minecraft.world.level.chunk.LevelChunk) { +- this.world.getChunkSource().addRegionTicket(TicketType.PLUGIN, new ChunkPos(x, z), 1, Unit.INSTANCE); ++ this.world.getChunkSource().addRegionTicket(TicketType.PLUGIN, new ChunkPos(x, z), 0, Unit.INSTANCE); // Paper + return true; + } + +@@ -2256,6 +2268,7 @@ public class CraftWorld extends CraftRegionAccessor implements World { + ca.spottedleaf.moonrise.common.util.ChunkSystem.scheduleChunkLoad(this.getHandle(), x, z, gen, ChunkStatus.FULL, true, priority, (c) -> { + net.minecraft.server.MinecraftServer.getServer().scheduleOnMain(() -> { + net.minecraft.world.level.chunk.LevelChunk chunk = (net.minecraft.world.level.chunk.LevelChunk)c; ++ if (chunk != null) this.addTicket(x, z); // Paper + ret.complete(chunk == null ? null : new CraftChunk(chunk)); + }); + }); diff --git a/patches/server/0373-Fix-Per-World-Difficulty-Remembering-Difficulty.patch b/patches/server/0373-Fix-Per-World-Difficulty-Remembering-Difficulty.patch deleted file mode 100644 index 3fec1cd01408..000000000000 --- a/patches/server/0373-Fix-Per-World-Difficulty-Remembering-Difficulty.patch +++ /dev/null @@ -1,118 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Aikar -Date: Sun, 28 Jun 2020 03:59:10 -0400 -Subject: [PATCH] Fix Per World Difficulty / Remembering Difficulty - -Fixes per world difficulty with /difficulty command and also -makes it so that the server keeps the last difficulty used instead -of restoring the server.properties every single load. - -diff --git a/src/main/java/net/minecraft/server/MinecraftServer.java b/src/main/java/net/minecraft/server/MinecraftServer.java -index 0370d26bd1e1d2fe1d640b052aca8a9c05dcb9dc..dd5f74fe4c8ca81b1d1102805ea8acb9087ba91e 100644 ---- a/src/main/java/net/minecraft/server/MinecraftServer.java -+++ b/src/main/java/net/minecraft/server/MinecraftServer.java -@@ -836,7 +836,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop { - return Component.translatable("commands.difficulty.success", difficulty.getDisplayName()); - }, true); -diff --git a/src/main/java/net/minecraft/server/dedicated/DedicatedServer.java b/src/main/java/net/minecraft/server/dedicated/DedicatedServer.java -index d6dc8c983d26ce89f17a990be4284fdc78ad164b..2b1d7a2360a9ee7bca9d93a2dc8c61d1648a8348 100644 ---- a/src/main/java/net/minecraft/server/dedicated/DedicatedServer.java -+++ b/src/main/java/net/minecraft/server/dedicated/DedicatedServer.java -@@ -350,7 +350,7 @@ public class DedicatedServer extends MinecraftServer implements ServerInterface - - @Override - public void forceDifficulty() { -- this.setDifficulty(this.getProperties().difficulty, true); -+ // this.setDifficulty(this.getProperties().difficulty, true); // Paper - per level difficulty; Don't overwrite level.dat's difficulty, keep current - } - - @Override -diff --git a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java -index e242ca5ec8c50bfd81b8ab1429e6e4e441025a75..d2eb8c619905d87a38820a4c1179ad93093c0ac3 100644 ---- a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java -+++ b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java -@@ -3259,7 +3259,7 @@ public class ServerGamePacketListenerImpl extends ServerCommonPacketListenerImpl - public void handleChangeDifficulty(ServerboundChangeDifficultyPacket packet) { - PacketUtils.ensureRunningOnSameThread(packet, this, this.player.serverLevel()); - if (this.player.hasPermissions(2) || this.isSingleplayerOwner()) { -- this.server.setDifficulty(packet.getDifficulty(), false); -+ // this.server.setDifficulty(packet.getDifficulty(), false); // Paper - per level difficulty; don't allow clients to change this - } - } - -diff --git a/src/main/java/org/bukkit/craftbukkit/CraftServer.java b/src/main/java/org/bukkit/craftbukkit/CraftServer.java -index e56f4b254af45311acb0b55195dcbd9f372cae98..ac3931f7d992d3675403e3b525f3cd9928cf7ed4 100644 ---- a/src/main/java/org/bukkit/craftbukkit/CraftServer.java -+++ b/src/main/java/org/bukkit/craftbukkit/CraftServer.java -@@ -998,8 +998,8 @@ public final class CraftServer implements Server { - org.spigotmc.SpigotConfig.init((File) this.console.options.valueOf("spigot-settings")); // Spigot - this.console.paperConfigurations.reloadConfigs(this.console); - for (ServerLevel world : this.console.getAllLevels()) { -- world.serverLevelData.setDifficulty(config.difficulty); -- world.setSpawnSettings(config.spawnMonsters, config.spawnAnimals); -+ // world.serverLevelData.setDifficulty(config.difficulty); // Paper - per level difficulty -+ world.setSpawnSettings(world.serverLevelData.getDifficulty() != Difficulty.PEACEFUL && config.spawnMonsters, config.spawnAnimals); // Paper - per level difficulty (from MinecraftServer#setDifficulty(ServerLevel, Difficulty, boolean)) - - for (SpawnCategory spawnCategory : SpawnCategory.values()) { - if (CraftSpawnCategory.isValidForLimits(spawnCategory)) { -diff --git a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java -index 2d6b24220057ea9a510dc86161cbaf909c061699..16e1ae8aaad143ee86f850e654de696dbbb84f30 100644 ---- a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java -+++ b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java -@@ -1167,7 +1167,7 @@ public class CraftWorld extends CraftRegionAccessor implements World { - - @Override - public void setDifficulty(Difficulty difficulty) { -- this.getHandle().serverLevelData.setDifficulty(net.minecraft.world.Difficulty.byId(difficulty.getValue())); -+ this.getHandle().getServer().setDifficulty(this.getHandle(), net.minecraft.world.Difficulty.byId(difficulty.getValue()), true); // Paper - per level difficulty; don't skip other difficulty-changing logic - } - - @Override diff --git a/patches/server/0377-Add-BlockStateMeta-clearBlockState.patch b/patches/server/0374-Add-BlockStateMeta-clearBlockState.patch similarity index 100% rename from patches/server/0377-Add-BlockStateMeta-clearBlockState.patch rename to patches/server/0374-Add-BlockStateMeta-clearBlockState.patch diff --git a/patches/server/0374-Paper-dumpitem-command.patch b/patches/server/0374-Paper-dumpitem-command.patch deleted file mode 100644 index 088efe087c0e..000000000000 --- a/patches/server/0374-Paper-dumpitem-command.patch +++ /dev/null @@ -1,158 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Aikar -Date: Sun, 28 Jun 2020 19:27:20 -0400 -Subject: [PATCH] Paper dumpitem command - -Let's you quickly view the item in your hands NBT data - -diff --git a/src/main/java/io/papermc/paper/command/PaperCommand.java b/src/main/java/io/papermc/paper/command/PaperCommand.java -index 3010d57efcc97fb409bfe43b1fc9af198c099a67..cdad0fd5257ae842f83b9c1c98b4565b468d4f54 100644 ---- a/src/main/java/io/papermc/paper/command/PaperCommand.java -+++ b/src/main/java/io/papermc/paper/command/PaperCommand.java -@@ -39,6 +39,7 @@ public final class PaperCommand extends Command { - commands.put(Set.of("version"), new VersionCommand()); - commands.put(Set.of("dumpplugins"), new DumpPluginsCommand()); - commands.put(Set.of("syncloadinfo"), new SyncLoadInfoCommand()); -+ commands.put(Set.of("dumpitem"), new DumpItemCommand()); - - return commands.entrySet().stream() - .flatMap(entry -> entry.getKey().stream().map(s -> Map.entry(s, entry.getValue()))) -diff --git a/src/main/java/io/papermc/paper/command/subcommands/DumpItemCommand.java b/src/main/java/io/papermc/paper/command/subcommands/DumpItemCommand.java -new file mode 100644 -index 0000000000000000000000000000000000000000..e993177b052c76cb3f9c44edb598ebb4be858393 ---- /dev/null -+++ b/src/main/java/io/papermc/paper/command/subcommands/DumpItemCommand.java -@@ -0,0 +1,133 @@ -+package io.papermc.paper.command.subcommands; -+ -+import io.papermc.paper.adventure.PaperAdventure; -+import io.papermc.paper.command.CommandUtil; -+import io.papermc.paper.command.PaperSubcommand; -+import java.util.ArrayList; -+import java.util.Collections; -+import java.util.IdentityHashMap; -+import java.util.List; -+import java.util.Map; -+import java.util.Optional; -+import java.util.Set; -+import java.util.function.Consumer; -+import net.kyori.adventure.text.Component; -+import net.kyori.adventure.text.ComponentLike; -+import net.kyori.adventure.text.JoinConfiguration; -+import net.kyori.adventure.text.TextComponent; -+import net.minecraft.core.Registry; -+import net.minecraft.core.RegistryAccess; -+import net.minecraft.core.component.DataComponentMap; -+import net.minecraft.core.component.DataComponentPatch; -+import net.minecraft.core.component.DataComponentType; -+import net.minecraft.core.component.TypedDataComponent; -+import net.minecraft.core.registries.Registries; -+import net.minecraft.nbt.NbtOps; -+import net.minecraft.nbt.NbtUtils; -+import net.minecraft.nbt.SnbtPrinterTagVisitor; -+import net.minecraft.nbt.Tag; -+import net.minecraft.resources.RegistryOps; -+import net.minecraft.world.item.ItemStack; -+import org.bukkit.command.CommandSender; -+import org.bukkit.craftbukkit.CraftServer; -+import org.bukkit.craftbukkit.inventory.CraftItemStack; -+import org.bukkit.entity.Player; -+import org.checkerframework.checker.nullness.qual.NonNull; -+import org.checkerframework.checker.nullness.qual.Nullable; -+import org.checkerframework.framework.qual.DefaultQualifier; -+ -+import static net.kyori.adventure.text.Component.join; -+import static net.kyori.adventure.text.Component.text; -+import static net.kyori.adventure.text.Component.textOfChildren; -+import static net.kyori.adventure.text.event.ClickEvent.copyToClipboard; -+import static net.kyori.adventure.text.format.NamedTextColor.AQUA; -+import static net.kyori.adventure.text.format.NamedTextColor.GRAY; -+import static net.kyori.adventure.text.format.NamedTextColor.RED; -+import static net.kyori.adventure.text.format.NamedTextColor.WHITE; -+import static net.kyori.adventure.text.format.NamedTextColor.YELLOW; -+import static net.kyori.adventure.text.format.TextColor.color; -+import static net.kyori.adventure.text.format.TextDecoration.ITALIC; -+ -+@DefaultQualifier(NonNull.class) -+public final class DumpItemCommand implements PaperSubcommand { -+ @Override -+ public boolean execute(final CommandSender sender, final String subCommand, final String[] args) { -+ this.doDumpItem(sender, args.length > 0 && "all".equals(args[0])); -+ return true; -+ } -+ -+ @SuppressWarnings({"unchecked", "OptionalAssignedToNull", "rawtypes"}) -+ private void doDumpItem(final CommandSender sender, final boolean includeAllComponents) { -+ if (!(sender instanceof final Player player)) { -+ sender.sendMessage("Only players can use this command"); -+ return; -+ } -+ final ItemStack itemStack = CraftItemStack.asNMSCopy(player.getInventory().getItemInMainHand()); -+ final TextComponent.Builder visualOutput = Component.text(); -+ final StringBuilder itemCommandBuilder = new StringBuilder(); -+ final String itemName = itemStack.getItemHolder().unwrapKey().orElseThrow().location().toString(); -+ itemCommandBuilder.append(itemName); -+ visualOutput.append(text(itemName, YELLOW)); // item type -+ final Set> referencedComponentTypes = Collections.newSetFromMap(new IdentityHashMap<>()); -+ final DataComponentPatch patch = itemStack.getComponentsPatch(); -+ referencedComponentTypes.addAll(patch.entrySet().stream().map(Map.Entry::getKey).toList()); -+ final DataComponentMap prototype = itemStack.getItem().components(); -+ if (includeAllComponents) { -+ referencedComponentTypes.addAll(prototype.keySet()); -+ } -+ -+ final RegistryAccess.Frozen access = ((CraftServer) sender.getServer()).getServer().registryAccess(); -+ final RegistryOps ops = access.createSerializationContext(NbtOps.INSTANCE); -+ final Registry> registry = access.registryOrThrow(Registries.DATA_COMPONENT_TYPE); -+ final List componentComponents = new ArrayList<>(); -+ final List commandComponents = new ArrayList<>(); -+ for (final DataComponentType type : referencedComponentTypes) { -+ final String path = registry.getResourceKey(type).orElseThrow().location().getPath(); -+ final @Nullable Optional patchedValue = patch.get(type); -+ final @Nullable TypedDataComponent prototypeValue = prototype.getTyped(type); -+ if (patchedValue != null) { -+ if (patchedValue.isEmpty()) { -+ componentComponents.add(text().append(text('!', RED), text(path, AQUA))); -+ commandComponents.add("!" + path); -+ } else { -+ final Tag serialized = (Tag) ((DataComponentType) type).codecOrThrow().encodeStart(ops, patchedValue.get()).getOrThrow(); -+ writeComponentValue(componentComponents::add, commandComponents::add, path, serialized); -+ } -+ } else if (includeAllComponents && prototypeValue != null) { -+ final Tag serialized = prototypeValue.encodeValue(ops).getOrThrow(); -+ writeComponentValue(componentComponents::add, commandComponents::add, path, serialized); -+ } -+ } -+ if (!componentComponents.isEmpty()) { -+ visualOutput.append( -+ text("[", color(0x8910CE)), -+ join(JoinConfiguration.separator(text(",", GRAY)), componentComponents), -+ text("]", color(0x8910CE)) -+ ); -+ itemCommandBuilder -+ .append("[") -+ .append(String.join(",", commandComponents)) -+ .append("]"); -+ } -+ player.sendMessage(visualOutput.build().compact()); -+ final Component copyMsg = text("Click to copy item definition to clipboard for use with /give", GRAY, ITALIC); -+ sender.sendMessage(copyMsg.clickEvent(copyToClipboard(itemCommandBuilder.toString()))); -+ } -+ -+ private static void writeComponentValue(final Consumer visualOutput, final Consumer commandOutput, final String path, final Tag serialized) { -+ visualOutput.accept(textOfChildren( -+ text(path, color(0xFF7FD7)), -+ text("=", WHITE), -+ PaperAdventure.asAdventure(NbtUtils.toPrettyComponent(serialized)) -+ )); -+ commandOutput.accept(path + "=" + new SnbtPrinterTagVisitor("", 0, new ArrayList<>()).visit(serialized)); -+ } -+ -+ @Override -+ public List tabComplete(final CommandSender sender, final String subCommand, final String[] args) { -+ if (args.length == 1) { -+ return CommandUtil.getListMatchingLast(sender, args, "all"); -+ } -+ return Collections.emptyList(); -+ } -+} diff --git a/patches/server/0375-Convert-legacy-attributes-in-Item-Meta.patch b/patches/server/0375-Convert-legacy-attributes-in-Item-Meta.patch new file mode 100644 index 000000000000..87efb884b240 --- /dev/null +++ b/patches/server/0375-Convert-legacy-attributes-in-Item-Meta.patch @@ -0,0 +1,44 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Aikar +Date: Wed, 1 Jul 2020 04:50:22 -0400 +Subject: [PATCH] Convert legacy attributes in Item Meta + + +diff --git a/src/main/java/org/bukkit/craftbukkit/attribute/CraftAttributeMap.java b/src/main/java/org/bukkit/craftbukkit/attribute/CraftAttributeMap.java +index de40e522960469b98f987bd688489740446d9f85..5678d2007d5adf45dec0638c5dd848b601801814 100644 +--- a/src/main/java/org/bukkit/craftbukkit/attribute/CraftAttributeMap.java ++++ b/src/main/java/org/bukkit/craftbukkit/attribute/CraftAttributeMap.java +@@ -9,6 +9,20 @@ import org.bukkit.attribute.AttributeInstance; + public class CraftAttributeMap implements Attributable { + + private final AttributeMap handle; ++ // Paper start - convert legacy attributes ++ private static final com.google.common.collect.ImmutableMap legacyNMS = com.google.common.collect.ImmutableMap.builder().put("generic.maxHealth", "generic.max_health").put("Max Health", "generic.max_health").put("zombie.spawnReinforcements", "zombie.spawn_reinforcements").put("Spawn Reinforcements Chance", "zombie.spawn_reinforcements").put("horse.jumpStrength", "horse.jump_strength").put("Jump Strength", "horse.jump_strength").put("generic.followRange", "generic.follow_range").put("Follow Range", "generic.follow_range").put("generic.knockbackResistance", "generic.knockback_resistance").put("Knockback Resistance", "generic.knockback_resistance").put("generic.movementSpeed", "generic.movement_speed").put("Movement Speed", "generic.movement_speed").put("generic.flyingSpeed", "generic.flying_speed").put("Flying Speed", "generic.flying_speed").put("generic.attackDamage", "generic.attack_damage").put("generic.attackKnockback", "generic.attack_knockback").put("generic.attackSpeed", "generic.attack_speed").put("generic.armorToughness", "generic.armor_toughness").build(); ++ ++ public static String convertIfNeeded(String nms) { ++ if (nms == null) { ++ return null; ++ } ++ nms = legacyNMS.getOrDefault(nms, nms); ++ if (!nms.toLowerCase(java.util.Locale.ROOT).equals(nms) || nms.indexOf(' ') != -1) { ++ return null; ++ } ++ return nms; ++ } ++ // Paper end + + public CraftAttributeMap(AttributeMap handle) { + this.handle = handle; +diff --git a/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaItem.java b/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaItem.java +index b2683d6efd53b03d7043098b19a1504885a5b3c7..af78e73755743fb2db7a99b834affc963b44bc10 100644 +--- a/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaItem.java ++++ b/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaItem.java +@@ -810,7 +810,7 @@ class CraftMetaItem implements ItemMeta, Damageable, Repairable, BlockDataMeta { + + AttributeModifier attribMod = CraftAttributeInstance.convert(nmsModifier); + +- String attributeName = entry.getString(CraftMetaItem.ATTRIBUTES_IDENTIFIER.NBT); ++ String attributeName = org.bukkit.craftbukkit.attribute.CraftAttributeMap.convertIfNeeded(entry.getString(CraftMetaItem.ATTRIBUTES_IDENTIFIER.NBT)); // Paper + if (attributeName == null || attributeName.isEmpty()) { + continue; + } diff --git a/patches/server/0375-Improve-Legacy-Component-serialization-size.patch b/patches/server/0375-Improve-Legacy-Component-serialization-size.patch deleted file mode 100644 index 5a1da5d1f07c..000000000000 --- a/patches/server/0375-Improve-Legacy-Component-serialization-size.patch +++ /dev/null @@ -1,56 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Aikar -Date: Sun, 28 Jun 2020 19:08:41 -0400 -Subject: [PATCH] Improve Legacy Component serialization size - -Don't constantly send format: false for all formatting options when parent already -has it false - -diff --git a/src/main/java/org/bukkit/craftbukkit/util/CraftChatMessage.java b/src/main/java/org/bukkit/craftbukkit/util/CraftChatMessage.java -index 1b552b3f05ac7fc44450de4b1ec78907a0f62424..3a550f2bd1366ae89c69a0527404b0449e6d9cf7 100644 ---- a/src/main/java/org/bukkit/craftbukkit/util/CraftChatMessage.java -+++ b/src/main/java/org/bukkit/craftbukkit/util/CraftChatMessage.java -@@ -48,6 +48,7 @@ public final class CraftChatMessage { - // Separate pattern with no group 3, new lines are part of previous string - private static final Pattern INCREMENTAL_PATTERN_KEEP_NEWLINES = Pattern.compile("(" + String.valueOf(org.bukkit.ChatColor.COLOR_CHAR) + "[0-9a-fk-orx])|((?:(?:https?):\\/\\/)?(?:[-\\w_\\.]{2,}\\.[a-z]{2,4}.*?(?=[\\.\\?!,;:]?(?:[" + String.valueOf(org.bukkit.ChatColor.COLOR_CHAR) + " ]|$))))", Pattern.CASE_INSENSITIVE); - // ChatColor.b does not explicitly reset, its more of empty -+ private static final Style EMPTY = Style.EMPTY.withItalic(false); // Paper - Improve Legacy Component serialization size - private static final Style RESET = Style.EMPTY.withBold(false).withItalic(false).withUnderlined(false).withStrikethrough(false).withObfuscated(false); - - private final List list = new ArrayList(); -@@ -69,6 +70,7 @@ public final class CraftChatMessage { - Matcher matcher = (keepNewlines ? StringMessage.INCREMENTAL_PATTERN_KEEP_NEWLINES : StringMessage.INCREMENTAL_PATTERN).matcher(message); - String match = null; - boolean needsAdd = false; -+ boolean hasReset = false; // Paper - Improve Legacy Component serialization size - while (matcher.find()) { - int groupId = 0; - while ((match = matcher.group(++groupId)) == null) { -@@ -114,7 +116,26 @@ public final class CraftChatMessage { - throw new AssertionError("Unexpected message format"); - } - } else { // Color resets formatting -- this.modifier = StringMessage.RESET.withColor(format); -+ // Paper start - Improve Legacy Component serialization size -+ Style previous = modifier; -+ modifier = (!hasReset ? RESET : EMPTY).withColor(format); -+ hasReset = true; -+ if (previous.isBold()) { -+ modifier = modifier.withBold(false); -+ } -+ if (previous.isItalic()) { -+ modifier = modifier.withItalic(false); -+ } -+ if (previous.isObfuscated()) { -+ modifier = modifier.withObfuscated(false); -+ } -+ if (previous.isStrikethrough()) { -+ modifier = modifier.withStrikethrough(false); -+ } -+ if (previous.isUnderlined()) { -+ modifier = modifier.withUnderlined(false); -+ } -+ // Paper end - Improve Legacy Component serialization size - } - needsAdd = true; - break; diff --git a/patches/server/0376-Add-Plugin-Tickets-to-API-Chunk-Methods.patch b/patches/server/0376-Add-Plugin-Tickets-to-API-Chunk-Methods.patch deleted file mode 100644 index 57c7d2ca2136..000000000000 --- a/patches/server/0376-Add-Plugin-Tickets-to-API-Chunk-Methods.patch +++ /dev/null @@ -1,103 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Aikar -Date: Tue, 9 Jun 2020 03:33:03 -0400 -Subject: [PATCH] Add Plugin Tickets to API Chunk Methods - -Like previous versions, plugins loading chunks kept them loaded until -they garbage collected to avoid constant spamming of chunk loads - -This adds tickets to a few more places so that they can be unloaded. - -Additionally, this drops their ticket level to BORDER so they wont be ticking -so they will just sit inactive instead. - -Using .loadChunk to keep a chunk ticking was a horrible idea for upstream -when we have TWO methods that are able to do that already in the API. - -Also reduce their collection count down to a maximum of 1 second. Barely -anyone knows what chunk-gc is in bukkit.yml as its less relevant now, and -since this wasn't spigot behavior, this is safe to mostly ignore (unless someone -wants it to collect even faster, they can restore that setting back to 1 instead of 20+) - -Not adding it to .getType() though to keep behavior consistent with vanilla for performance reasons. - -diff --git a/src/main/java/org/bukkit/craftbukkit/CraftServer.java b/src/main/java/org/bukkit/craftbukkit/CraftServer.java -index ac3931f7d992d3675403e3b525f3cd9928cf7ed4..42ec4aed187b41729a3c985ae440097db0388d3c 100644 ---- a/src/main/java/org/bukkit/craftbukkit/CraftServer.java -+++ b/src/main/java/org/bukkit/craftbukkit/CraftServer.java -@@ -380,7 +380,7 @@ public final class CraftServer implements Server { - this.overrideSpawnLimits(); - console.autosavePeriod = this.configuration.getInt("ticks-per.autosave"); - this.warningState = WarningState.value(this.configuration.getString("settings.deprecated-verbose")); -- TicketType.PLUGIN.timeout = this.configuration.getInt("chunk-gc.period-in-ticks"); -+ TicketType.PLUGIN.timeout = Math.min(20, this.configuration.getInt("chunk-gc.period-in-ticks")); // Paper - cap plugin loads to 1 second - this.minimumAPI = ApiVersion.getOrCreateVersion(this.configuration.getString("settings.minimum-api")); - this.loadIcon(); - this.loadCompatibilities(); -@@ -976,7 +976,7 @@ public final class CraftServer implements Server { - this.console.setMotd(config.motd); - this.overrideSpawnLimits(); - this.warningState = WarningState.value(this.configuration.getString("settings.deprecated-verbose")); -- TicketType.PLUGIN.timeout = this.configuration.getInt("chunk-gc.period-in-ticks"); -+ TicketType.PLUGIN.timeout = Math.min(20, configuration.getInt("chunk-gc.period-in-ticks")); // Paper - cap plugin loads to 1 second - this.minimumAPI = ApiVersion.getOrCreateVersion(this.configuration.getString("settings.minimum-api")); - this.printSaveWarning = false; - this.console.autosavePeriod = this.configuration.getInt("ticks-per.autosave"); -diff --git a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java -index 16e1ae8aaad143ee86f850e654de696dbbb84f30..520867fa3d49e421ef5290976a9eed8f58ff7590 100644 ---- a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java -+++ b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java -@@ -266,7 +266,13 @@ public class CraftWorld extends CraftRegionAccessor implements World { - - @Override - public Chunk getChunkAt(int x, int z) { -- net.minecraft.world.level.chunk.LevelChunk chunk = (net.minecraft.world.level.chunk.LevelChunk) this.world.getChunk(x, z, ChunkStatus.FULL, true); -+ // Paper start - add ticket to hold chunk for a little while longer if plugin accesses it -+ net.minecraft.world.level.chunk.LevelChunk chunk = this.world.getChunkSource().getChunkAtIfLoadedImmediately(x, z); -+ if (chunk == null) { -+ this.addTicket(x, z); -+ chunk = this.world.getChunkSource().getChunk(x, z, true); -+ } -+ // Paper end - return new CraftChunk(chunk); - } - -@@ -280,6 +286,12 @@ public class CraftWorld extends CraftRegionAccessor implements World { - return new CraftChunk(this.getHandle(), x, z); - } - -+ // Paper start -+ private void addTicket(int x, int z) { -+ io.papermc.paper.util.MCUtil.MAIN_EXECUTOR.execute(() -> this.world.getChunkSource().addRegionTicket(TicketType.PLUGIN, new ChunkPos(x, z), 0, Unit.INSTANCE)); // Paper -+ } -+ // Paper end -+ - @Override - public Chunk getChunkAt(Block block) { - Preconditions.checkArgument(block != null, "null block"); -@@ -331,7 +343,7 @@ public class CraftWorld extends CraftRegionAccessor implements World { - public boolean unloadChunkRequest(int x, int z) { - org.spigotmc.AsyncCatcher.catchOp("chunk unload"); // Spigot - if (this.isChunkLoaded(x, z)) { -- this.world.getChunkSource().removeRegionTicket(TicketType.PLUGIN, new ChunkPos(x, z), 1, Unit.INSTANCE); -+ this.world.getChunkSource().removeRegionTicket(TicketType.PLUGIN, new ChunkPos(x, z), 0, Unit.INSTANCE); // Paper - } - - return true; -@@ -441,7 +453,7 @@ public class CraftWorld extends CraftRegionAccessor implements World { - } - - if (chunk instanceof net.minecraft.world.level.chunk.LevelChunk) { -- this.world.getChunkSource().addRegionTicket(TicketType.PLUGIN, new ChunkPos(x, z), 1, Unit.INSTANCE); -+ this.world.getChunkSource().addRegionTicket(TicketType.PLUGIN, new ChunkPos(x, z), 0, Unit.INSTANCE); // Paper - return true; - } - -@@ -2243,6 +2255,7 @@ public class CraftWorld extends CraftRegionAccessor implements World { - ca.spottedleaf.moonrise.common.util.ChunkSystem.scheduleChunkLoad(this.getHandle(), x, z, gen, ChunkStatus.FULL, true, priority, (c) -> { - net.minecraft.server.MinecraftServer.getServer().scheduleOnMain(() -> { - net.minecraft.world.level.chunk.LevelChunk chunk = (net.minecraft.world.level.chunk.LevelChunk)c; -+ if (chunk != null) this.addTicket(x, z); // Paper - ret.complete(chunk == null ? null : new CraftChunk(chunk)); - }); - }); diff --git a/patches/server/0376-Do-not-accept-invalid-client-settings.patch b/patches/server/0376-Do-not-accept-invalid-client-settings.patch new file mode 100644 index 000000000000..ba6f03a6bacb --- /dev/null +++ b/patches/server/0376-Do-not-accept-invalid-client-settings.patch @@ -0,0 +1,24 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Spottedleaf +Date: Sat, 7 May 2022 14:58:53 -0700 +Subject: [PATCH] Do not accept invalid client settings + + +diff --git a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java +index 8bb1b0e7caa938170169379ce22f21c1c772db60..00ce20359f6d73acf3f1016806e5f4f3b6d01bcd 100644 +--- a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java ++++ b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java +@@ -3300,6 +3300,13 @@ public class ServerGamePacketListenerImpl extends ServerCommonPacketListenerImpl + @Override + public void handleClientInformation(ServerboundClientInformationPacket packet) { + PacketUtils.ensureRunningOnSameThread(packet, this, this.player.serverLevel()); ++ // Paper start - do not accept invalid information ++ if (packet.information().viewDistance() < 0) { ++ LOGGER.warn("Disconnecting " + this.player.getScoreboardName() + " for invalid view distance: " + packet.information().viewDistance()); ++ this.disconnect(Component.literal("Invalid client settings"), org.bukkit.event.player.PlayerKickEvent.Cause.ILLEGAL_ACTION); ++ return; ++ } ++ // Paper end - do not accept invalid information + this.player.updateOptions(packet.information()); + this.connection.channel.attr(io.papermc.paper.adventure.PaperAdventure.LOCALE_ATTRIBUTE).set(net.kyori.adventure.translation.Translator.parseLocale(packet.information().language())); // Paper + } diff --git a/patches/server/0377-Improve-fix-EntityTargetLivingEntityEvent.patch b/patches/server/0377-Improve-fix-EntityTargetLivingEntityEvent.patch new file mode 100644 index 000000000000..2777b41e1167 --- /dev/null +++ b/patches/server/0377-Improve-fix-EntityTargetLivingEntityEvent.patch @@ -0,0 +1,35 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Jake Potrebic +Date: Fri, 9 Dec 2022 03:10:23 -0800 +Subject: [PATCH] Improve/fix EntityTargetLivingEntityEvent + + +diff --git a/src/main/java/net/minecraft/world/entity/ai/behavior/StopAttackingIfTargetInvalid.java b/src/main/java/net/minecraft/world/entity/ai/behavior/StopAttackingIfTargetInvalid.java +index 70df68169b17fa5f732e4c73a86376ba6eb9ca13..b31f6ccc95132a7dd022b454d28814b684d99d4c 100644 +--- a/src/main/java/net/minecraft/world/entity/ai/behavior/StopAttackingIfTargetInvalid.java ++++ b/src/main/java/net/minecraft/world/entity/ai/behavior/StopAttackingIfTargetInvalid.java +@@ -46,9 +46,22 @@ public class StopAttackingIfTargetInvalid { + if (entityinsentient.canAttack(entityliving) && (!shouldForgetIfTargetUnreachable || !StopAttackingIfTargetInvalid.isTiredOfTryingToReachTarget(entityinsentient, behaviorbuilder_b.tryGet(memoryaccessor1))) && entityliving.isAlive() && entityliving.level() == entityinsentient.level() && !condition.test(worldserver, entityliving)) { + return true; + } else { ++ // Paper start - better track target change reason ++ final EntityTargetEvent.TargetReason reason; ++ if (!entityinsentient.canAttack(entityliving)) { ++ reason = EntityTargetEvent.TargetReason.TARGET_INVALID; ++ } else if (shouldForgetIfTargetUnreachable && StopAttackingIfTargetInvalid.isTiredOfTryingToReachTarget(entityinsentient, behaviorbuilder_b.tryGet(memoryaccessor1))) { ++ reason = EntityTargetEvent.TargetReason.FORGOT_TARGET; ++ } else if (!entityliving.isAlive()) { ++ reason = EntityTargetEvent.TargetReason.TARGET_DIED; ++ } else if (entityliving.level() != entityinsentient.level()) { ++ reason = EntityTargetEvent.TargetReason.TARGET_OTHER_LEVEL; ++ } else { ++ reason = EntityTargetEvent.TargetReason.TARGET_INVALID; ++ } ++ // Paper end + // CraftBukkit start +- LivingEntity old = entityinsentient.getBrain().getMemory(MemoryModuleType.ATTACK_TARGET).orElse(null); +- EntityTargetEvent event = CraftEventFactory.callEntityTargetLivingEvent(entityinsentient, null, (old != null && !old.isAlive()) ? EntityTargetEvent.TargetReason.TARGET_DIED : EntityTargetEvent.TargetReason.FORGOT_TARGET); ++ EntityTargetEvent event = CraftEventFactory.callEntityTargetLivingEvent(entityinsentient, null, reason); // Paper + if (event.isCancelled()) { + return false; + } diff --git a/patches/server/0378-Add-entity-liquid-API.patch b/patches/server/0378-Add-entity-liquid-API.patch new file mode 100644 index 000000000000..849eeec01f02 --- /dev/null +++ b/patches/server/0378-Add-entity-liquid-API.patch @@ -0,0 +1,55 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: William Blake Galbreath +Date: Thu, 2 Jul 2020 18:11:43 -0500 +Subject: [PATCH] Add entity liquid API + +== AT == +public net.minecraft.world.entity.Entity isInRain()Z +public net.minecraft.world.entity.Entity isInBubbleColumn()Z + +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java +index 7fb04535b8c0744d0fa4b0c0a933234134486956..b001284e368f64871824c1f961999bb3f726de57 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java +@@ -1026,4 +1026,41 @@ public abstract class CraftEntity implements org.bukkit.entity.Entity { + return getHandle().spawnReason; + } + // Paper end - entity spawn reason API ++ ++ // Paper start - entity liquid API ++ @Override ++ public boolean isUnderWater() { ++ return getHandle().isUnderWater(); ++ } ++ ++ @Override ++ public boolean isInRain() { ++ return getHandle().isInRain(); ++ } ++ ++ @Override ++ public boolean isInBubbleColumn() { ++ return getHandle().isInBubbleColumn(); ++ } ++ ++ @Override ++ public boolean isInWaterOrRain() { ++ return getHandle().isInWaterOrRain(); ++ } ++ ++ @Override ++ public boolean isInWaterOrBubbleColumn() { ++ return getHandle().isInWaterOrBubble(); ++ } ++ ++ @Override ++ public boolean isInWaterOrRainOrBubbleColumn() { ++ return getHandle().isInWaterRainOrBubble(); ++ } ++ ++ @Override ++ public boolean isInLava() { ++ return getHandle().isInLava(); ++ } ++ // Paper end - entity liquid API + } diff --git a/patches/server/0378-Convert-legacy-attributes-in-Item-Meta.patch b/patches/server/0378-Convert-legacy-attributes-in-Item-Meta.patch deleted file mode 100644 index 76437f1a9bc3..000000000000 --- a/patches/server/0378-Convert-legacy-attributes-in-Item-Meta.patch +++ /dev/null @@ -1,44 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Aikar -Date: Wed, 1 Jul 2020 04:50:22 -0400 -Subject: [PATCH] Convert legacy attributes in Item Meta - - -diff --git a/src/main/java/org/bukkit/craftbukkit/attribute/CraftAttributeMap.java b/src/main/java/org/bukkit/craftbukkit/attribute/CraftAttributeMap.java -index de40e522960469b98f987bd688489740446d9f85..5678d2007d5adf45dec0638c5dd848b601801814 100644 ---- a/src/main/java/org/bukkit/craftbukkit/attribute/CraftAttributeMap.java -+++ b/src/main/java/org/bukkit/craftbukkit/attribute/CraftAttributeMap.java -@@ -9,6 +9,20 @@ import org.bukkit.attribute.AttributeInstance; - public class CraftAttributeMap implements Attributable { - - private final AttributeMap handle; -+ // Paper start - convert legacy attributes -+ private static final com.google.common.collect.ImmutableMap legacyNMS = com.google.common.collect.ImmutableMap.builder().put("generic.maxHealth", "generic.max_health").put("Max Health", "generic.max_health").put("zombie.spawnReinforcements", "zombie.spawn_reinforcements").put("Spawn Reinforcements Chance", "zombie.spawn_reinforcements").put("horse.jumpStrength", "horse.jump_strength").put("Jump Strength", "horse.jump_strength").put("generic.followRange", "generic.follow_range").put("Follow Range", "generic.follow_range").put("generic.knockbackResistance", "generic.knockback_resistance").put("Knockback Resistance", "generic.knockback_resistance").put("generic.movementSpeed", "generic.movement_speed").put("Movement Speed", "generic.movement_speed").put("generic.flyingSpeed", "generic.flying_speed").put("Flying Speed", "generic.flying_speed").put("generic.attackDamage", "generic.attack_damage").put("generic.attackKnockback", "generic.attack_knockback").put("generic.attackSpeed", "generic.attack_speed").put("generic.armorToughness", "generic.armor_toughness").build(); -+ -+ public static String convertIfNeeded(String nms) { -+ if (nms == null) { -+ return null; -+ } -+ nms = legacyNMS.getOrDefault(nms, nms); -+ if (!nms.toLowerCase(java.util.Locale.ROOT).equals(nms) || nms.indexOf(' ') != -1) { -+ return null; -+ } -+ return nms; -+ } -+ // Paper end - - public CraftAttributeMap(AttributeMap handle) { - this.handle = handle; -diff --git a/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaItem.java b/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaItem.java -index 97c40582bca095532fff9a81515f38ea4ac527e0..5c76ba7f9ceb285d27e18369172612205be96224 100644 ---- a/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaItem.java -+++ b/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaItem.java -@@ -702,7 +702,7 @@ class CraftMetaItem implements ItemMeta, Damageable, Repairable, BlockDataMeta { - - AttributeModifier attribMod = CraftAttributeInstance.convert(nmsModifier); - -- String attributeName = entry.getString(CraftMetaItem.ATTRIBUTES_IDENTIFIER.NBT); -+ String attributeName = org.bukkit.craftbukkit.attribute.CraftAttributeMap.convertIfNeeded(entry.getString(CraftMetaItem.ATTRIBUTES_IDENTIFIER.NBT)); // Paper - if (attributeName == null || attributeName.isEmpty()) { - continue; - } diff --git a/patches/server/0379-Add-PrepareResultEvent.patch b/patches/server/0379-Add-PrepareResultEvent.patch new file mode 100644 index 000000000000..9a7c7568127c --- /dev/null +++ b/patches/server/0379-Add-PrepareResultEvent.patch @@ -0,0 +1,165 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: William Blake Galbreath +Date: Fri, 3 Jul 2020 11:58:56 -0500 +Subject: [PATCH] Add PrepareResultEvent + +Adds a new event for all crafting stations that generate a result slot item + +Anvil, Grindstone and Smithing now extend this event + +diff --git a/src/main/java/net/minecraft/world/inventory/AnvilMenu.java b/src/main/java/net/minecraft/world/inventory/AnvilMenu.java +index 126565e673e94b9c66aa4547596bbf198c57c7ad..cc5aae32f34305965847ade8b530272b1126b5c9 100644 +--- a/src/main/java/net/minecraft/world/inventory/AnvilMenu.java ++++ b/src/main/java/net/minecraft/world/inventory/AnvilMenu.java +@@ -333,6 +333,7 @@ public class AnvilMenu extends ItemCombinerMenu { + } + + this.createResult(); ++ org.bukkit.craftbukkit.event.CraftEventFactory.callPrepareResultEvent(this, RESULT_SLOT); // Paper - Add PrepareResultEvent + return true; + } else { + return false; +diff --git a/src/main/java/net/minecraft/world/inventory/CartographyTableMenu.java b/src/main/java/net/minecraft/world/inventory/CartographyTableMenu.java +index 5b31d09b158c694b3e54ab3a228817accaa00a0c..8b72d1fd551dcb113f4137aee441a9531c00288a 100644 +--- a/src/main/java/net/minecraft/world/inventory/CartographyTableMenu.java ++++ b/src/main/java/net/minecraft/world/inventory/CartographyTableMenu.java +@@ -140,6 +140,7 @@ public class CartographyTableMenu extends AbstractContainerMenu { + this.setupResultSlot(itemstack, itemstack1, itemstack2); + } + ++ org.bukkit.craftbukkit.event.CraftEventFactory.callPrepareResultEvent(this, RESULT_SLOT); // Paper - Add PrepareResultEvent + } + + private void setupResultSlot(ItemStack map, ItemStack item, ItemStack oldResult) { +diff --git a/src/main/java/net/minecraft/world/inventory/GrindstoneMenu.java b/src/main/java/net/minecraft/world/inventory/GrindstoneMenu.java +index 13bc52bc856031c930370828b0381e43920aabd2..8e58dbb4b110338dfe8add26697d0b1c9ec5fa9c 100644 +--- a/src/main/java/net/minecraft/world/inventory/GrindstoneMenu.java ++++ b/src/main/java/net/minecraft/world/inventory/GrindstoneMenu.java +@@ -148,6 +148,7 @@ public class GrindstoneMenu extends AbstractContainerMenu { + super.slotsChanged(inventory); + if (inventory == this.repairSlots) { + this.createResult(); ++ org.bukkit.craftbukkit.event.CraftEventFactory.callPrepareResultEvent(this, RESULT_SLOT); // Paper - Add PrepareResultEvent + } + + } +diff --git a/src/main/java/net/minecraft/world/inventory/ItemCombinerMenu.java b/src/main/java/net/minecraft/world/inventory/ItemCombinerMenu.java +index 594a387d7e3a67da02ea54b4f259426df2eb21a2..4734dd90f2a66e1ac7a64b35ecd62a630108cf07 100644 +--- a/src/main/java/net/minecraft/world/inventory/ItemCombinerMenu.java ++++ b/src/main/java/net/minecraft/world/inventory/ItemCombinerMenu.java +@@ -96,6 +96,7 @@ public abstract class ItemCombinerMenu extends AbstractContainerMenu { + super.slotsChanged(inventory); + if (inventory == this.inputSlots) { + this.createResult(); ++ org.bukkit.craftbukkit.event.CraftEventFactory.callPrepareResultEvent(this, this instanceof SmithingMenu ? 3 : 2); // Paper - Add PrepareResultEvent + } + + } +diff --git a/src/main/java/net/minecraft/world/inventory/LoomMenu.java b/src/main/java/net/minecraft/world/inventory/LoomMenu.java +index d988747ccb81ca74db8ce14fea49d13c9c1b4476..58470d8e9a4ceb1eca05b342481ed8260588e225 100644 +--- a/src/main/java/net/minecraft/world/inventory/LoomMenu.java ++++ b/src/main/java/net/minecraft/world/inventory/LoomMenu.java +@@ -237,7 +237,8 @@ public class LoomMenu extends AbstractContainerMenu { + this.resultSlot.set(ItemStack.EMPTY); + } + +- this.broadcastChanges(); ++ // this.broadcastChanges(); // Paper - Add PrepareResultEvent; done below ++ org.bukkit.craftbukkit.event.CraftEventFactory.callPrepareResultEvent(this, 3); // Paper - Add PrepareResultEvent + } else { + this.resultSlot.set(ItemStack.EMPTY); + this.selectablePatterns = List.of(); +diff --git a/src/main/java/net/minecraft/world/inventory/SmithingMenu.java b/src/main/java/net/minecraft/world/inventory/SmithingMenu.java +index 4b0d07a22cb922aa5ef0c99279663158a5918f1f..89d2f26504bb072c2d75af4ec600ca123e10a600 100644 +--- a/src/main/java/net/minecraft/world/inventory/SmithingMenu.java ++++ b/src/main/java/net/minecraft/world/inventory/SmithingMenu.java +@@ -115,6 +115,7 @@ public class SmithingMenu extends ItemCombinerMenu { + this.hasRecipeError.set(flag ? 1 : 0); + } + ++ org.bukkit.craftbukkit.event.CraftEventFactory.callPrepareResultEvent(this, RESULT_SLOT); // Paper - Add PrepareResultEvent + } + + @Override +diff --git a/src/main/java/net/minecraft/world/inventory/StonecutterMenu.java b/src/main/java/net/minecraft/world/inventory/StonecutterMenu.java +index 0fb0ee5b22dc96c48d68e4391fd71b03d4e799f0..97837d1baf6b929a50e5562ef466050e70c2c8b1 100644 +--- a/src/main/java/net/minecraft/world/inventory/StonecutterMenu.java ++++ b/src/main/java/net/minecraft/world/inventory/StonecutterMenu.java +@@ -170,6 +170,7 @@ public class StonecutterMenu extends AbstractContainerMenu { + this.setupRecipeList(itemstack); + } + ++ org.bukkit.craftbukkit.event.CraftEventFactory.callPrepareResultEvent(this, RESULT_SLOT); // Paper - Add PrepareResultEvent + } + + private void setupRecipeList(ItemStack stack) { +diff --git a/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java b/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java +index cb708c5ce9e81005d638f03e398e7f0f2b9f7521..876644a1b5d119c3420e3042b576e34878797d67 100644 +--- a/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java ++++ b/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java +@@ -1679,6 +1679,12 @@ public class CraftEventFactory { + } + + public static PrepareAnvilEvent callPrepareAnvilEvent(AnvilView view, ItemStack item) { ++ // Paper start - Add PrepareResultEvent ++ if (true) { ++ view.getTopInventory().setItem(net.minecraft.world.inventory.AnvilMenu.RESULT_SLOT, CraftItemStack.asCraftMirror(item)); ++ return null; // verify nothing uses return - disable event: handled below in PrepareResult ++ } ++ // Paper end - Add PrepareResultEvent + PrepareAnvilEvent event = new PrepareAnvilEvent(view, CraftItemStack.asCraftMirror(item).clone()); + event.getView().getPlayer().getServer().getPluginManager().callEvent(event); + event.getInventory().setItem(2, event.getResult()); +@@ -1686,6 +1692,12 @@ public class CraftEventFactory { + } + + public static PrepareGrindstoneEvent callPrepareGrindstoneEvent(InventoryView view, ItemStack item) { ++ // Paper start - Add PrepareResultEvent ++ if (true) { ++ view.getTopInventory().setItem(net.minecraft.world.inventory.GrindstoneMenu.RESULT_SLOT, CraftItemStack.asCraftMirror(item)); ++ return null; // verify nothing uses return - disable event: handled below in PrepareResult ++ } ++ // Paper end - Add PrepareResultEvent + PrepareGrindstoneEvent event = new PrepareGrindstoneEvent(view, CraftItemStack.asCraftMirror(item).clone()); + event.getView().getPlayer().getServer().getPluginManager().callEvent(event); + event.getInventory().setItem(2, event.getResult()); +@@ -1693,12 +1705,39 @@ public class CraftEventFactory { + } + + public static PrepareSmithingEvent callPrepareSmithingEvent(InventoryView view, ItemStack item) { ++ // Paper start - Add PrepareResultEvent ++ if (true) { ++ view.getTopInventory().setItem(net.minecraft.world.inventory.SmithingMenu.RESULT_SLOT, CraftItemStack.asCraftMirror(item)); ++ return null; // verify nothing uses return - disable event: handled below in PrepareResult ++ } ++ // Paper end - Add PrepareResultEvent + PrepareSmithingEvent event = new PrepareSmithingEvent(view, CraftItemStack.asCraftMirror(item).clone()); + event.getView().getPlayer().getServer().getPluginManager().callEvent(event); + event.getInventory().setResult(event.getResult()); + return event; + } + ++ // Paper start - Add PrepareResultEvent ++ public static void callPrepareResultEvent(AbstractContainerMenu container, int resultSlot) { ++ final com.destroystokyo.paper.event.inventory.PrepareResultEvent event; ++ InventoryView view = container.getBukkitView(); ++ org.bukkit.inventory.ItemStack origItem = view.getTopInventory().getItem(resultSlot); ++ CraftItemStack result = origItem != null ? CraftItemStack.asCraftCopy(origItem) : null; ++ if (view.getTopInventory() instanceof org.bukkit.inventory.AnvilInventory && view instanceof AnvilView anvilView) { ++ event = new PrepareAnvilEvent(anvilView, result); ++ } else if (view.getTopInventory() instanceof org.bukkit.inventory.GrindstoneInventory) { ++ event = new PrepareGrindstoneEvent(view, result); ++ } else if (view.getTopInventory() instanceof org.bukkit.inventory.SmithingInventory) { ++ event = new PrepareSmithingEvent(view, result); ++ } else { ++ event = new com.destroystokyo.paper.event.inventory.PrepareResultEvent(view, result); ++ } ++ event.callEvent(); ++ event.getInventory().setItem(resultSlot, event.getResult()); ++ container.broadcastChanges();; ++ } ++ // Paper end - Add PrepareResultEvent ++ + /** + * Mob spawner event. + */ diff --git a/patches/server/0379-Do-not-accept-invalid-client-settings.patch b/patches/server/0379-Do-not-accept-invalid-client-settings.patch deleted file mode 100644 index e0e0f8912eb7..000000000000 --- a/patches/server/0379-Do-not-accept-invalid-client-settings.patch +++ /dev/null @@ -1,24 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Spottedleaf -Date: Sat, 7 May 2022 14:58:53 -0700 -Subject: [PATCH] Do not accept invalid client settings - - -diff --git a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java -index d2eb8c619905d87a38820a4c1179ad93093c0ac3..90e5f263dcb8033dae3ca32cbf5dbd332a109282 100644 ---- a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java -+++ b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java -@@ -3251,6 +3251,13 @@ public class ServerGamePacketListenerImpl extends ServerCommonPacketListenerImpl - @Override - public void handleClientInformation(ServerboundClientInformationPacket packet) { - PacketUtils.ensureRunningOnSameThread(packet, this, this.player.serverLevel()); -+ // Paper start - do not accept invalid information -+ if (packet.information().viewDistance() < 0) { -+ LOGGER.warn("Disconnecting " + this.player.getScoreboardName() + " for invalid view distance: " + packet.information().viewDistance()); -+ this.disconnect(Component.literal("Invalid client settings"), org.bukkit.event.player.PlayerKickEvent.Cause.ILLEGAL_ACTION); -+ return; -+ } -+ // Paper end - do not accept invalid information - this.player.updateOptions(packet.information()); - this.connection.channel.attr(io.papermc.paper.adventure.PaperAdventure.LOCALE_ATTRIBUTE).set(net.kyori.adventure.translation.Translator.parseLocale(packet.information().language())); // Paper - } diff --git a/patches/server/0380-Don-t-check-chunk-for-portal-on-world-gen-entity-add.patch b/patches/server/0380-Don-t-check-chunk-for-portal-on-world-gen-entity-add.patch new file mode 100644 index 000000000000..c46cb6a91928 --- /dev/null +++ b/patches/server/0380-Don-t-check-chunk-for-portal-on-world-gen-entity-add.patch @@ -0,0 +1,19 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Aikar +Date: Sun, 5 Jul 2020 14:59:31 -0400 +Subject: [PATCH] Don't check chunk for portal on world gen entity add + + +diff --git a/src/main/java/net/minecraft/world/entity/LivingEntity.java b/src/main/java/net/minecraft/world/entity/LivingEntity.java +index 718ccabb46d4520ba363d21a33e06eb2c9ff62ee..7e3670bee52407d768d1f7c0be66d7c0d0f72c46 100644 +--- a/src/main/java/net/minecraft/world/entity/LivingEntity.java ++++ b/src/main/java/net/minecraft/world/entity/LivingEntity.java +@@ -3796,7 +3796,7 @@ public abstract class LivingEntity extends Entity implements Attackable { + Entity entity = this.getVehicle(); + + super.stopRiding(suppressCancellation); // Paper - Force entity dismount during teleportation +- if (entity != null && entity != this.getVehicle() && !this.level().isClientSide) { ++ if (entity != null && entity != this.getVehicle() && !this.level().isClientSide && entity.valid) { // Paper - don't process on world gen + this.dismountVehicle(entity); + } + diff --git a/patches/server/0380-Improve-fix-EntityTargetLivingEntityEvent.patch b/patches/server/0380-Improve-fix-EntityTargetLivingEntityEvent.patch deleted file mode 100644 index 29703174a113..000000000000 --- a/patches/server/0380-Improve-fix-EntityTargetLivingEntityEvent.patch +++ /dev/null @@ -1,35 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Jake Potrebic -Date: Fri, 9 Dec 2022 03:10:23 -0800 -Subject: [PATCH] Improve/fix EntityTargetLivingEntityEvent - - -diff --git a/src/main/java/net/minecraft/world/entity/ai/behavior/StopAttackingIfTargetInvalid.java b/src/main/java/net/minecraft/world/entity/ai/behavior/StopAttackingIfTargetInvalid.java -index cc7d161b53a2295fa1745254eafb8a70c7b6c7b2..508d4f391fe563453d7bf6782b3082741c358006 100644 ---- a/src/main/java/net/minecraft/world/entity/ai/behavior/StopAttackingIfTargetInvalid.java -+++ b/src/main/java/net/minecraft/world/entity/ai/behavior/StopAttackingIfTargetInvalid.java -@@ -47,9 +47,22 @@ public class StopAttackingIfTargetInvalid { - if (entityinsentient.canAttack(entityliving) && (!shouldForgetIfTargetUnreachable || !StopAttackingIfTargetInvalid.isTiredOfTryingToReachTarget(entityinsentient, behaviorbuilder_b.tryGet(memoryaccessor1))) && entityliving.isAlive() && entityliving.level() == entityinsentient.level() && !alternativeCondition.test(entityliving)) { - return true; - } else { -+ // Paper start - better track target change reason -+ final EntityTargetEvent.TargetReason reason; -+ if (!entityinsentient.canAttack(entityliving)) { -+ reason = EntityTargetEvent.TargetReason.TARGET_INVALID; -+ } else if (shouldForgetIfTargetUnreachable && StopAttackingIfTargetInvalid.isTiredOfTryingToReachTarget(entityinsentient, behaviorbuilder_b.tryGet(memoryaccessor1))) { -+ reason = EntityTargetEvent.TargetReason.FORGOT_TARGET; -+ } else if (!entityliving.isAlive()) { -+ reason = EntityTargetEvent.TargetReason.TARGET_DIED; -+ } else if (entityliving.level() != entityinsentient.level()) { -+ reason = EntityTargetEvent.TargetReason.TARGET_OTHER_LEVEL; -+ } else { -+ reason = EntityTargetEvent.TargetReason.TARGET_INVALID; -+ } -+ // Paper end - // CraftBukkit start -- LivingEntity old = entityinsentient.getBrain().getMemory(MemoryModuleType.ATTACK_TARGET).orElse(null); -- EntityTargetEvent event = CraftEventFactory.callEntityTargetLivingEvent(entityinsentient, null, (old != null && !old.isAlive()) ? EntityTargetEvent.TargetReason.TARGET_DIED : EntityTargetEvent.TargetReason.FORGOT_TARGET); -+ EntityTargetEvent event = CraftEventFactory.callEntityTargetLivingEvent(entityinsentient, null, reason); // Paper - if (event.isCancelled()) { - return false; - } diff --git a/patches/server/0381-Add-entity-liquid-API.patch b/patches/server/0381-Add-entity-liquid-API.patch deleted file mode 100644 index 51765a7f3a58..000000000000 --- a/patches/server/0381-Add-entity-liquid-API.patch +++ /dev/null @@ -1,55 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: William Blake Galbreath -Date: Thu, 2 Jul 2020 18:11:43 -0500 -Subject: [PATCH] Add entity liquid API - -== AT == -public net.minecraft.world.entity.Entity isInRain()Z -public net.minecraft.world.entity.Entity isInBubbleColumn()Z - -diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java -index ce70c8fddbe63d0af2b1f988ce9a2b40c5d48066..34321f095e12ea0cca34ff1ec00819c6350205a8 100644 ---- a/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java -+++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java -@@ -1025,4 +1025,41 @@ public abstract class CraftEntity implements org.bukkit.entity.Entity { - return getHandle().spawnReason; - } - // Paper end - entity spawn reason API -+ -+ // Paper start - entity liquid API -+ @Override -+ public boolean isUnderWater() { -+ return getHandle().isUnderWater(); -+ } -+ -+ @Override -+ public boolean isInRain() { -+ return getHandle().isInRain(); -+ } -+ -+ @Override -+ public boolean isInBubbleColumn() { -+ return getHandle().isInBubbleColumn(); -+ } -+ -+ @Override -+ public boolean isInWaterOrRain() { -+ return getHandle().isInWaterOrRain(); -+ } -+ -+ @Override -+ public boolean isInWaterOrBubbleColumn() { -+ return getHandle().isInWaterOrBubble(); -+ } -+ -+ @Override -+ public boolean isInWaterOrRainOrBubbleColumn() { -+ return getHandle().isInWaterRainOrBubble(); -+ } -+ -+ @Override -+ public boolean isInLava() { -+ return getHandle().isInLava(); -+ } -+ // Paper end - entity liquid API - } diff --git a/patches/server/0381-Fix-arrows-never-despawning-MC-125757.patch b/patches/server/0381-Fix-arrows-never-despawning-MC-125757.patch new file mode 100644 index 000000000000..ed2c8a340960 --- /dev/null +++ b/patches/server/0381-Fix-arrows-never-despawning-MC-125757.patch @@ -0,0 +1,22 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: William Blake Galbreath +Date: Wed, 8 Jul 2020 11:24:30 -0500 +Subject: [PATCH] Fix arrows never despawning MC-125757 + +This forces the despawn counter to start ticking regardless of +state after the arrow has been alive for 200 ticks (10 seconds) +instead of getting stuck in a never despawn state (bubble columns, +etc). + +diff --git a/src/main/java/net/minecraft/world/entity/projectile/AbstractArrow.java b/src/main/java/net/minecraft/world/entity/projectile/AbstractArrow.java +index 78053060dbff7f8a859f9fecb356491f497eed7e..bc167c21f82ad09952f6cdbf1016523062890f8b 100644 +--- a/src/main/java/net/minecraft/world/entity/projectile/AbstractArrow.java ++++ b/src/main/java/net/minecraft/world/entity/projectile/AbstractArrow.java +@@ -242,6 +242,7 @@ public abstract class AbstractArrow extends Projectile { + } + + } else { ++ if (tickCount > 200) this.tickDespawn(); // Paper - tick despawnCounter regardless after 10 seconds + this.inGroundTime = 0; + Vec3 vec3d2 = this.position(); + diff --git a/patches/server/0382-Add-PrepareResultEvent.patch b/patches/server/0382-Add-PrepareResultEvent.patch deleted file mode 100644 index 985e40f2aeb9..000000000000 --- a/patches/server/0382-Add-PrepareResultEvent.patch +++ /dev/null @@ -1,165 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: William Blake Galbreath -Date: Fri, 3 Jul 2020 11:58:56 -0500 -Subject: [PATCH] Add PrepareResultEvent - -Adds a new event for all crafting stations that generate a result slot item - -Anvil, Grindstone and Smithing now extend this event - -diff --git a/src/main/java/net/minecraft/world/inventory/AnvilMenu.java b/src/main/java/net/minecraft/world/inventory/AnvilMenu.java -index ffda2c984c5683edb38a56f04c53b0ea339e08fc..d685511104ac552dfc9ae2111e1bfb60fa812102 100644 ---- a/src/main/java/net/minecraft/world/inventory/AnvilMenu.java -+++ b/src/main/java/net/minecraft/world/inventory/AnvilMenu.java -@@ -327,6 +327,7 @@ public class AnvilMenu extends ItemCombinerMenu { - } - - this.createResult(); -+ org.bukkit.craftbukkit.event.CraftEventFactory.callPrepareResultEvent(this, RESULT_SLOT); // Paper - Add PrepareResultEvent - return true; - } else { - return false; -diff --git a/src/main/java/net/minecraft/world/inventory/CartographyTableMenu.java b/src/main/java/net/minecraft/world/inventory/CartographyTableMenu.java -index 11d7bf4f90083991cfc8c6c5f9a1e8ad6a162843..c52c4c4210bc6ae082443318d9795c48c816aba6 100644 ---- a/src/main/java/net/minecraft/world/inventory/CartographyTableMenu.java -+++ b/src/main/java/net/minecraft/world/inventory/CartographyTableMenu.java -@@ -152,6 +152,7 @@ public class CartographyTableMenu extends AbstractContainerMenu { - this.setupResultSlot(itemstack, itemstack1, itemstack2); - } - -+ org.bukkit.craftbukkit.event.CraftEventFactory.callPrepareResultEvent(this, RESULT_SLOT); // Paper - Add PrepareResultEvent - } - - private void setupResultSlot(ItemStack map, ItemStack item, ItemStack oldResult) { -diff --git a/src/main/java/net/minecraft/world/inventory/GrindstoneMenu.java b/src/main/java/net/minecraft/world/inventory/GrindstoneMenu.java -index 637d77d6b07ff9ee5ac1cb0470cbefcba5c7495e..15ec798e149d80aace186f84b9236ddeaba690c3 100644 ---- a/src/main/java/net/minecraft/world/inventory/GrindstoneMenu.java -+++ b/src/main/java/net/minecraft/world/inventory/GrindstoneMenu.java -@@ -160,6 +160,7 @@ public class GrindstoneMenu extends AbstractContainerMenu { - super.slotsChanged(inventory); - if (inventory == this.repairSlots) { - this.createResult(); -+ org.bukkit.craftbukkit.event.CraftEventFactory.callPrepareResultEvent(this, RESULT_SLOT); // Paper - Add PrepareResultEvent - } - - } -diff --git a/src/main/java/net/minecraft/world/inventory/ItemCombinerMenu.java b/src/main/java/net/minecraft/world/inventory/ItemCombinerMenu.java -index 62432c347b86fc79ab529a7dde66bef32d0424dd..be840717e180b6b5abd14db6cc9263349737f9a3 100644 ---- a/src/main/java/net/minecraft/world/inventory/ItemCombinerMenu.java -+++ b/src/main/java/net/minecraft/world/inventory/ItemCombinerMenu.java -@@ -110,6 +110,7 @@ public abstract class ItemCombinerMenu extends AbstractContainerMenu { - super.slotsChanged(inventory); - if (inventory == this.inputSlots) { - this.createResult(); -+ org.bukkit.craftbukkit.event.CraftEventFactory.callPrepareResultEvent(this, this instanceof SmithingMenu ? 3 : 2); // Paper - Add PrepareResultEvent - } - - } -diff --git a/src/main/java/net/minecraft/world/inventory/LoomMenu.java b/src/main/java/net/minecraft/world/inventory/LoomMenu.java -index 69ed0753c224cb7746762b4b94c4d79d608951b8..2de558dd205a1078fdcac1bce256d059b9bf5d5f 100644 ---- a/src/main/java/net/minecraft/world/inventory/LoomMenu.java -+++ b/src/main/java/net/minecraft/world/inventory/LoomMenu.java -@@ -249,7 +249,8 @@ public class LoomMenu extends AbstractContainerMenu { - this.resultSlot.set(ItemStack.EMPTY); - } - -- this.broadcastChanges(); -+ // this.broadcastChanges(); // Paper - Add PrepareResultEvent; done below -+ org.bukkit.craftbukkit.event.CraftEventFactory.callPrepareResultEvent(this, 3); // Paper - Add PrepareResultEvent - } else { - this.resultSlot.set(ItemStack.EMPTY); - this.selectablePatterns = List.of(); -diff --git a/src/main/java/net/minecraft/world/inventory/SmithingMenu.java b/src/main/java/net/minecraft/world/inventory/SmithingMenu.java -index 0735705def4f9505b7f16df2497cc78bbf5a8373..86e51fcce767d265ee0d3beb611be2119085830b 100644 ---- a/src/main/java/net/minecraft/world/inventory/SmithingMenu.java -+++ b/src/main/java/net/minecraft/world/inventory/SmithingMenu.java -@@ -121,6 +121,7 @@ public class SmithingMenu extends ItemCombinerMenu { - } - } - -+ org.bukkit.craftbukkit.event.CraftEventFactory.callPrepareResultEvent(this, RESULT_SLOT); // Paper - Add PrepareResultEvent - } - - @Override -diff --git a/src/main/java/net/minecraft/world/inventory/StonecutterMenu.java b/src/main/java/net/minecraft/world/inventory/StonecutterMenu.java -index 30ea1f9e97db86a2ad7baeea4f5a76c821874daa..5b4f03128499b0c1a4b8c5f5ccd17e4bdb391e81 100644 ---- a/src/main/java/net/minecraft/world/inventory/StonecutterMenu.java -+++ b/src/main/java/net/minecraft/world/inventory/StonecutterMenu.java -@@ -182,6 +182,7 @@ public class StonecutterMenu extends AbstractContainerMenu { - this.setupRecipeList(inventory, itemstack); - } - -+ org.bukkit.craftbukkit.event.CraftEventFactory.callPrepareResultEvent(this, RESULT_SLOT); // Paper - Add PrepareResultEvent - } - - private static SingleRecipeInput createRecipeInput(Container inventory) { -diff --git a/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java b/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java -index f3ec3d14e66b9c1dff32088d4aef57e21265048c..f633d08b35bf9018367a449cc000c6c6ca5a44cd 100644 ---- a/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java -+++ b/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java -@@ -1681,6 +1681,12 @@ public class CraftEventFactory { - } - - public static PrepareAnvilEvent callPrepareAnvilEvent(AnvilView view, ItemStack item) { -+ // Paper start - Add PrepareResultEvent -+ if (true) { -+ view.getTopInventory().setItem(net.minecraft.world.inventory.AnvilMenu.RESULT_SLOT, CraftItemStack.asCraftMirror(item)); -+ return null; // verify nothing uses return - disable event: handled below in PrepareResult -+ } -+ // Paper end - Add PrepareResultEvent - PrepareAnvilEvent event = new PrepareAnvilEvent(view, CraftItemStack.asCraftMirror(item).clone()); - event.getView().getPlayer().getServer().getPluginManager().callEvent(event); - event.getInventory().setItem(2, event.getResult()); -@@ -1688,6 +1694,12 @@ public class CraftEventFactory { - } - - public static PrepareGrindstoneEvent callPrepareGrindstoneEvent(InventoryView view, ItemStack item) { -+ // Paper start - Add PrepareResultEvent -+ if (true) { -+ view.getTopInventory().setItem(net.minecraft.world.inventory.GrindstoneMenu.RESULT_SLOT, CraftItemStack.asCraftMirror(item)); -+ return null; // verify nothing uses return - disable event: handled below in PrepareResult -+ } -+ // Paper end - Add PrepareResultEvent - PrepareGrindstoneEvent event = new PrepareGrindstoneEvent(view, CraftItemStack.asCraftMirror(item).clone()); - event.getView().getPlayer().getServer().getPluginManager().callEvent(event); - event.getInventory().setItem(2, event.getResult()); -@@ -1695,12 +1707,39 @@ public class CraftEventFactory { - } - - public static PrepareSmithingEvent callPrepareSmithingEvent(InventoryView view, ItemStack item) { -+ // Paper start - Add PrepareResultEvent -+ if (true) { -+ view.getTopInventory().setItem(net.minecraft.world.inventory.SmithingMenu.RESULT_SLOT, CraftItemStack.asCraftMirror(item)); -+ return null; // verify nothing uses return - disable event: handled below in PrepareResult -+ } -+ // Paper end - Add PrepareResultEvent - PrepareSmithingEvent event = new PrepareSmithingEvent(view, CraftItemStack.asCraftMirror(item).clone()); - event.getView().getPlayer().getServer().getPluginManager().callEvent(event); - event.getInventory().setResult(event.getResult()); - return event; - } - -+ // Paper start - Add PrepareResultEvent -+ public static void callPrepareResultEvent(AbstractContainerMenu container, int resultSlot) { -+ final com.destroystokyo.paper.event.inventory.PrepareResultEvent event; -+ InventoryView view = container.getBukkitView(); -+ org.bukkit.inventory.ItemStack origItem = view.getTopInventory().getItem(resultSlot); -+ CraftItemStack result = origItem != null ? CraftItemStack.asCraftCopy(origItem) : null; -+ if (view.getTopInventory() instanceof org.bukkit.inventory.AnvilInventory && view instanceof AnvilView anvilView) { -+ event = new PrepareAnvilEvent(anvilView, result); -+ } else if (view.getTopInventory() instanceof org.bukkit.inventory.GrindstoneInventory) { -+ event = new PrepareGrindstoneEvent(view, result); -+ } else if (view.getTopInventory() instanceof org.bukkit.inventory.SmithingInventory) { -+ event = new PrepareSmithingEvent(view, result); -+ } else { -+ event = new com.destroystokyo.paper.event.inventory.PrepareResultEvent(view, result); -+ } -+ event.callEvent(); -+ event.getInventory().setItem(resultSlot, event.getResult()); -+ container.broadcastChanges();; -+ } -+ // Paper end - Add PrepareResultEvent -+ - /** - * Mob spawner event. - */ diff --git a/patches/server/0382-Thread-Safe-Vanilla-Command-permission-checking.patch b/patches/server/0382-Thread-Safe-Vanilla-Command-permission-checking.patch new file mode 100644 index 000000000000..475b27747071 --- /dev/null +++ b/patches/server/0382-Thread-Safe-Vanilla-Command-permission-checking.patch @@ -0,0 +1,53 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Aikar +Date: Sat, 11 Jul 2020 03:54:28 -0400 +Subject: [PATCH] Thread Safe Vanilla Command permission checking + +Datapacks check this on load and are built concurrently. This was breaking them badly due +to race conditions. + +Plus, .canUse we want to be safe for async anyways. + +diff --git a/src/main/java/com/mojang/brigadier/tree/CommandNode.java b/src/main/java/com/mojang/brigadier/tree/CommandNode.java +index 14ccd0c8f721e9be7dca8a5dcb8ef95b5cd82731..1f4963bf4681a771130abc1da179819626ecfc1f 100644 +--- a/src/main/java/com/mojang/brigadier/tree/CommandNode.java ++++ b/src/main/java/com/mojang/brigadier/tree/CommandNode.java +@@ -75,10 +75,10 @@ public abstract class CommandNode implements Comparable> { + public synchronized boolean canUse(final S source) { + if (source instanceof CommandSourceStack) { + try { +- ((CommandSourceStack) source).currentCommand = this; ++ ((CommandSourceStack) source).currentCommand.put(Thread.currentThread(), this); // Paper - Thread Safe Vanilla Command permission checking + return this.requirement.test(source); + } finally { +- ((CommandSourceStack) source).currentCommand = null; ++ ((CommandSourceStack) source).currentCommand.remove(Thread.currentThread()); // Paper - Thread Safe Vanilla Command permission checking + } + } + // CraftBukkit end +diff --git a/src/main/java/net/minecraft/commands/CommandSourceStack.java b/src/main/java/net/minecraft/commands/CommandSourceStack.java +index 5316f148f3f9128690f019d544e462b042d8d797..f31c5d665678c3163ed4469f8e9d395b890c1bbe 100644 +--- a/src/main/java/net/minecraft/commands/CommandSourceStack.java ++++ b/src/main/java/net/minecraft/commands/CommandSourceStack.java +@@ -66,7 +66,7 @@ public class CommandSourceStack implements ExecutionCommandSource currentCommand = new java.util.concurrent.ConcurrentHashMap<>(); // CraftBukkit // Paper - Thread Safe Vanilla Command permission checking + public boolean bypassSelectorPermissions = false; // Paper - add bypass for selector permissions + + public CommandSourceStack(CommandSource output, Vec3 pos, Vec2 rot, ServerLevel world, int level, String name, Component displayName, MinecraftServer server, @Nullable Entity entity) { +@@ -195,9 +195,11 @@ public class CommandSourceStack implements ExecutionCommandSource -Date: Sun, 5 Jul 2020 14:59:31 -0400 -Subject: [PATCH] Don't check chunk for portal on world gen entity add - - -diff --git a/src/main/java/net/minecraft/world/entity/LivingEntity.java b/src/main/java/net/minecraft/world/entity/LivingEntity.java -index 8920099eb488c37b036b7bd97fbd4f7db505c77c..906e8589483b93bcdb55677f7f942f6c7a89caf8 100644 ---- a/src/main/java/net/minecraft/world/entity/LivingEntity.java -+++ b/src/main/java/net/minecraft/world/entity/LivingEntity.java -@@ -3659,7 +3659,7 @@ public abstract class LivingEntity extends Entity implements Attackable { - Entity entity = this.getVehicle(); - - super.stopRiding(suppressCancellation); // Paper - Force entity dismount during teleportation -- if (entity != null && entity != this.getVehicle() && !this.level().isClientSide) { -+ if (entity != null && entity != this.getVehicle() && !this.level().isClientSide && entity.valid) { // Paper - don't process on world gen - this.dismountVehicle(entity); - } - diff --git a/patches/server/0383-Fix-SPIGOT-5824-Bukkit-world-container-is-not-used.patch b/patches/server/0383-Fix-SPIGOT-5824-Bukkit-world-container-is-not-used.patch new file mode 100644 index 000000000000..bd520de15101 --- /dev/null +++ b/patches/server/0383-Fix-SPIGOT-5824-Bukkit-world-container-is-not-used.patch @@ -0,0 +1,50 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: William Blake Galbreath +Date: Fri, 10 Jul 2020 13:12:33 -0500 +Subject: [PATCH] Fix SPIGOT-5824 Bukkit world-container is not used + + +diff --git a/src/main/java/net/minecraft/server/Main.java b/src/main/java/net/minecraft/server/Main.java +index a9dadd106ff0ac0f788c16048a73dfc3320b4944..f6020adb0e72b129be154a905941af923c5aadd0 100644 +--- a/src/main/java/net/minecraft/server/Main.java ++++ b/src/main/java/net/minecraft/server/Main.java +@@ -167,8 +167,17 @@ public class Main { + return; + } + +- File file = (File) optionset.valueOf("universe"); // CraftBukkit +- Services services = Services.create(new com.destroystokyo.paper.profile.PaperAuthenticationService(Proxy.NO_PROXY), file, optionset); // Paper - pass OptionSet to load paper config files; override authentication service ++ // Paper start - fix SPIGOT-5824 ++ File file; ++ File userCacheFile = new File(Services.USERID_CACHE_FILE); ++ if (optionset.has("universe")) { ++ file = (File) optionset.valueOf("universe"); // CraftBukkit ++ userCacheFile = new File(file, Services.USERID_CACHE_FILE); ++ } else { ++ file = new File(bukkitConfiguration.getString("settings.world-container", ".")); ++ } ++ // Paper end - fix SPIGOT-5824 ++ Services services = Services.create(new com.destroystokyo.paper.profile.PaperAuthenticationService(Proxy.NO_PROXY), file, userCacheFile, optionset); // Paper - pass OptionSet to load paper config files; override authentication service; fix world-container + // CraftBukkit start + String s = (String) Optional.ofNullable((String) optionset.valueOf("world")).orElse(dedicatedserversettings.getProperties().levelName); + LevelStorageSource convertable = LevelStorageSource.createDefault(file.toPath()); +diff --git a/src/main/java/net/minecraft/server/Services.java b/src/main/java/net/minecraft/server/Services.java +index 5928e5f1934b8e247ba516595018ed5c633d3b5d..33e3815a0c979609d4c7ab83ad91e87ac07a556d 100644 +--- a/src/main/java/net/minecraft/server/Services.java ++++ b/src/main/java/net/minecraft/server/Services.java +@@ -24,12 +24,12 @@ public record Services( + return java.util.Objects.requireNonNull(this.paperConfigurations); + } + // Paper end - add paper configuration files +- private static final String USERID_CACHE_FILE = "usercache.json"; ++ public static final String USERID_CACHE_FILE = "usercache.json"; // Paper - private -> public + +- public static Services create(YggdrasilAuthenticationService authenticationService, File rootDirectory, joptsimple.OptionSet optionSet) throws Exception { // Paper - add optionset to load paper config files ++ public static Services create(YggdrasilAuthenticationService authenticationService, File rootDirectory, File userCacheFile, joptsimple.OptionSet optionSet) throws Exception { // Paper - add optionset to load paper config files; add userCacheFile parameter + MinecraftSessionService minecraftSessionService = authenticationService.createMinecraftSessionService(); + GameProfileRepository gameProfileRepository = authenticationService.createProfileRepository(); +- GameProfileCache gameProfileCache = new GameProfileCache(gameProfileRepository, new File(rootDirectory, "usercache.json")); ++ GameProfileCache gameProfileCache = new GameProfileCache(gameProfileRepository, userCacheFile); // Paper - use specified user cache file + // Paper start - load paper config files from cli options + final java.nio.file.Path legacyConfigPath = ((File) optionSet.valueOf("paper-settings")).toPath(); + final java.nio.file.Path configDirPath = ((File) optionSet.valueOf("paper-settings-directory")).toPath(); diff --git a/patches/server/0384-Fix-SPIGOT-5885-Unable-to-disable-advancements.patch b/patches/server/0384-Fix-SPIGOT-5885-Unable-to-disable-advancements.patch new file mode 100644 index 000000000000..0bd1007497d6 --- /dev/null +++ b/patches/server/0384-Fix-SPIGOT-5885-Unable-to-disable-advancements.patch @@ -0,0 +1,18 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: William Blake Galbreath +Date: Fri, 10 Jul 2020 12:38:12 -0500 +Subject: [PATCH] Fix SPIGOT-5885 Unable to disable advancements + + +diff --git a/src/main/java/net/minecraft/server/Main.java b/src/main/java/net/minecraft/server/Main.java +index f6020adb0e72b129be154a905941af923c5aadd0..59d623dd45c395552ffd99e6fa744c0f71d94998 100644 +--- a/src/main/java/net/minecraft/server/Main.java ++++ b/src/main/java/net/minecraft/server/Main.java +@@ -167,6 +167,7 @@ public class Main { + return; + } + ++ org.spigotmc.SpigotConfig.disabledAdvancements = spigotConfiguration.getStringList("advancements.disabled"); // Paper - fix SPIGOT-5885, must be set early in init + // Paper start - fix SPIGOT-5824 + File file; + File userCacheFile = new File(Services.USERID_CACHE_FILE); diff --git a/patches/server/0384-Fix-arrows-never-despawning-MC-125757.patch b/patches/server/0384-Fix-arrows-never-despawning-MC-125757.patch deleted file mode 100644 index dbcb4ce450dd..000000000000 --- a/patches/server/0384-Fix-arrows-never-despawning-MC-125757.patch +++ /dev/null @@ -1,22 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: William Blake Galbreath -Date: Wed, 8 Jul 2020 11:24:30 -0500 -Subject: [PATCH] Fix arrows never despawning MC-125757 - -This forces the despawn counter to start ticking regardless of -state after the arrow has been alive for 200 ticks (10 seconds) -instead of getting stuck in a never despawn state (bubble columns, -etc). - -diff --git a/src/main/java/net/minecraft/world/entity/projectile/AbstractArrow.java b/src/main/java/net/minecraft/world/entity/projectile/AbstractArrow.java -index 6c79997ba46e641de5aa12ff8a3d790d04a5a475..746bb8a36bd6c6ef953289576af499caad588d79 100644 ---- a/src/main/java/net/minecraft/world/entity/projectile/AbstractArrow.java -+++ b/src/main/java/net/minecraft/world/entity/projectile/AbstractArrow.java -@@ -238,6 +238,7 @@ public abstract class AbstractArrow extends Projectile { - - ++this.inGroundTime; - } else { -+ if (tickCount > 200) this.tickDespawn(); // Paper - tick despawnCounter regardless after 10 seconds - this.inGroundTime = 0; - Vec3 vec3d2 = this.position(); - diff --git a/patches/server/0385-Fix-AdvancementDataPlayer-leak-due-from-quitting-ear.patch b/patches/server/0385-Fix-AdvancementDataPlayer-leak-due-from-quitting-ear.patch new file mode 100644 index 000000000000..ef009ef8234f --- /dev/null +++ b/patches/server/0385-Fix-AdvancementDataPlayer-leak-due-from-quitting-ear.patch @@ -0,0 +1,65 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Spottedleaf +Date: Mon, 13 Jul 2020 06:22:54 -0700 +Subject: [PATCH] Fix AdvancementDataPlayer leak due from quitting early in + login + +Move the criterion storage to the AdvancementDataPlayer object +itself, so the criterion object stores no references - and thus +needs no cleanup. + +diff --git a/src/main/java/net/minecraft/advancements/critereon/SimpleCriterionTrigger.java b/src/main/java/net/minecraft/advancements/critereon/SimpleCriterionTrigger.java +index 9a387d5dc0925304d4163e3caa22206aaa68e3b7..f43053ba082f9772b6ec02828fa2d6f387c32d26 100644 +--- a/src/main/java/net/minecraft/advancements/critereon/SimpleCriterionTrigger.java ++++ b/src/main/java/net/minecraft/advancements/critereon/SimpleCriterionTrigger.java +@@ -15,32 +15,32 @@ import net.minecraft.server.level.ServerPlayer; + import net.minecraft.world.level.storage.loot.LootContext; + + public abstract class SimpleCriterionTrigger implements CriterionTrigger { +- private final Map>> players = Maps.newIdentityHashMap(); ++ // private final Map>> players = Maps.newIdentityHashMap(); // Paper - fix AdvancementDataPlayer leak; moved into AdvancementDataPlayer to fix memory leak + + @Override + public final void addPlayerListener(PlayerAdvancements manager, CriterionTrigger.Listener conditions) { +- this.players.computeIfAbsent(manager, managerx -> Sets.newHashSet()).add(conditions); ++ manager.criterionData.computeIfAbsent(this, managerx -> Sets.newHashSet()).add(conditions); // Paper - fix AdvancementDataPlayer leak + } + + @Override + public final void removePlayerListener(PlayerAdvancements manager, CriterionTrigger.Listener conditions) { +- Set> set = this.players.get(manager); ++ Set> set = (Set) manager.criterionData.get(this); // Paper - fix AdvancementDataPlayer leak + if (set != null) { + set.remove(conditions); + if (set.isEmpty()) { +- this.players.remove(manager); ++ manager.criterionData.remove(this); // Paper - fix AdvancementDataPlayer leak + } + } + } + + @Override + public final void removePlayerListeners(PlayerAdvancements tracker) { +- this.players.remove(tracker); ++ tracker.criterionData.remove(this); // Paper - fix AdvancementDataPlayer leak + } + + protected void trigger(ServerPlayer player, Predicate predicate) { + PlayerAdvancements playerAdvancements = player.getAdvancements(); +- Set> set = this.players.get(playerAdvancements); ++ Set> set = (Set) playerAdvancements.criterionData.get(this); // Paper - fix AdvancementDataPlayer leak + if (set != null && !set.isEmpty()) { + LootContext lootContext = EntityPredicate.createContext(player, player); + List> list = null; +diff --git a/src/main/java/net/minecraft/server/PlayerAdvancements.java b/src/main/java/net/minecraft/server/PlayerAdvancements.java +index 0542b61053ed7039e54856eab86ba5842403e4fc..0fe941b0802f2966ad55509baac124f56ecef999 100644 +--- a/src/main/java/net/minecraft/server/PlayerAdvancements.java ++++ b/src/main/java/net/minecraft/server/PlayerAdvancements.java +@@ -63,6 +63,7 @@ public class PlayerAdvancements { + private AdvancementHolder lastSelectedTab; + private boolean isFirstPacket = true; + private final Codec codec; ++ public final Map, Set>> criterionData = new java.util.IdentityHashMap<>(); // Paper - fix advancement data player leakage + + public PlayerAdvancements(DataFixer dataFixer, PlayerList playerManager, ServerAdvancementManager advancementLoader, Path filePath, ServerPlayer owner) { + this.playerList = playerManager; diff --git a/patches/server/0385-Thread-Safe-Vanilla-Command-permission-checking.patch b/patches/server/0385-Thread-Safe-Vanilla-Command-permission-checking.patch deleted file mode 100644 index a210e43dce33..000000000000 --- a/patches/server/0385-Thread-Safe-Vanilla-Command-permission-checking.patch +++ /dev/null @@ -1,53 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Aikar -Date: Sat, 11 Jul 2020 03:54:28 -0400 -Subject: [PATCH] Thread Safe Vanilla Command permission checking - -Datapacks check this on load and are built concurrently. This was breaking them badly due -to race conditions. - -Plus, .canUse we want to be safe for async anyways. - -diff --git a/src/main/java/com/mojang/brigadier/tree/CommandNode.java b/src/main/java/com/mojang/brigadier/tree/CommandNode.java -index 14ccd0c8f721e9be7dca8a5dcb8ef95b5cd82731..1f4963bf4681a771130abc1da179819626ecfc1f 100644 ---- a/src/main/java/com/mojang/brigadier/tree/CommandNode.java -+++ b/src/main/java/com/mojang/brigadier/tree/CommandNode.java -@@ -75,10 +75,10 @@ public abstract class CommandNode implements Comparable> { - public synchronized boolean canUse(final S source) { - if (source instanceof CommandSourceStack) { - try { -- ((CommandSourceStack) source).currentCommand = this; -+ ((CommandSourceStack) source).currentCommand.put(Thread.currentThread(), this); // Paper - Thread Safe Vanilla Command permission checking - return this.requirement.test(source); - } finally { -- ((CommandSourceStack) source).currentCommand = null; -+ ((CommandSourceStack) source).currentCommand.remove(Thread.currentThread()); // Paper - Thread Safe Vanilla Command permission checking - } - } - // CraftBukkit end -diff --git a/src/main/java/net/minecraft/commands/CommandSourceStack.java b/src/main/java/net/minecraft/commands/CommandSourceStack.java -index 2a22827f44dd0d524c22264447959a6979e9f0de..f3c83bb20a73b489f1fb6bacb69388902b1b6fe7 100644 ---- a/src/main/java/net/minecraft/commands/CommandSourceStack.java -+++ b/src/main/java/net/minecraft/commands/CommandSourceStack.java -@@ -64,7 +64,7 @@ public class CommandSourceStack implements ExecutionCommandSource currentCommand = new java.util.concurrent.ConcurrentHashMap<>(); // CraftBukkit // Paper - Thread Safe Vanilla Command permission checking - public boolean bypassSelectorPermissions = false; // Paper - add bypass for selector permissions - - public CommandSourceStack(CommandSource output, Vec3 pos, Vec2 rot, ServerLevel world, int level, String name, Component displayName, MinecraftServer server, @Nullable Entity entity) { -@@ -193,9 +193,11 @@ public class CommandSourceStack implements ExecutionCommandSource -Date: Fri, 10 Jul 2020 13:12:33 -0500 -Subject: [PATCH] Fix SPIGOT-5824 Bukkit world-container is not used - - -diff --git a/src/main/java/net/minecraft/server/Main.java b/src/main/java/net/minecraft/server/Main.java -index 993296f9c2457809bd6b844c309895f417eb42a5..7dfcb8c6583d1c13fe688b5e17edb9d1c6935547 100644 ---- a/src/main/java/net/minecraft/server/Main.java -+++ b/src/main/java/net/minecraft/server/Main.java -@@ -167,8 +167,17 @@ public class Main { - return; - } - -- File file = (File) optionset.valueOf("universe"); // CraftBukkit -- Services services = Services.create(new com.destroystokyo.paper.profile.PaperAuthenticationService(Proxy.NO_PROXY), file, optionset); // Paper - pass OptionSet to load paper config files; override authentication service -+ // Paper start - fix SPIGOT-5824 -+ File file; -+ File userCacheFile = new File(Services.USERID_CACHE_FILE); -+ if (optionset.has("universe")) { -+ file = (File) optionset.valueOf("universe"); // CraftBukkit -+ userCacheFile = new File(file, Services.USERID_CACHE_FILE); -+ } else { -+ file = new File(bukkitConfiguration.getString("settings.world-container", ".")); -+ } -+ // Paper end - fix SPIGOT-5824 -+ Services services = Services.create(new com.destroystokyo.paper.profile.PaperAuthenticationService(Proxy.NO_PROXY), file, userCacheFile, optionset); // Paper - pass OptionSet to load paper config files; override authentication service; fix world-container - // CraftBukkit start - String s = (String) Optional.ofNullable((String) optionset.valueOf("world")).orElse(dedicatedserversettings.getProperties().levelName); - LevelStorageSource convertable = LevelStorageSource.createDefault(file.toPath()); -diff --git a/src/main/java/net/minecraft/server/Services.java b/src/main/java/net/minecraft/server/Services.java -index 5928e5f1934b8e247ba516595018ed5c633d3b5d..33e3815a0c979609d4c7ab83ad91e87ac07a556d 100644 ---- a/src/main/java/net/minecraft/server/Services.java -+++ b/src/main/java/net/minecraft/server/Services.java -@@ -24,12 +24,12 @@ public record Services( - return java.util.Objects.requireNonNull(this.paperConfigurations); - } - // Paper end - add paper configuration files -- private static final String USERID_CACHE_FILE = "usercache.json"; -+ public static final String USERID_CACHE_FILE = "usercache.json"; // Paper - private -> public - -- public static Services create(YggdrasilAuthenticationService authenticationService, File rootDirectory, joptsimple.OptionSet optionSet) throws Exception { // Paper - add optionset to load paper config files -+ public static Services create(YggdrasilAuthenticationService authenticationService, File rootDirectory, File userCacheFile, joptsimple.OptionSet optionSet) throws Exception { // Paper - add optionset to load paper config files; add userCacheFile parameter - MinecraftSessionService minecraftSessionService = authenticationService.createMinecraftSessionService(); - GameProfileRepository gameProfileRepository = authenticationService.createProfileRepository(); -- GameProfileCache gameProfileCache = new GameProfileCache(gameProfileRepository, new File(rootDirectory, "usercache.json")); -+ GameProfileCache gameProfileCache = new GameProfileCache(gameProfileRepository, userCacheFile); // Paper - use specified user cache file - // Paper start - load paper config files from cli options - final java.nio.file.Path legacyConfigPath = ((File) optionSet.valueOf("paper-settings")).toPath(); - final java.nio.file.Path configDirPath = ((File) optionSet.valueOf("paper-settings-directory")).toPath(); diff --git a/patches/server/0389-Optimize-NetworkManager-Exception-Handling.patch b/patches/server/0386-Optimize-NetworkManager-Exception-Handling.patch similarity index 100% rename from patches/server/0389-Optimize-NetworkManager-Exception-Handling.patch rename to patches/server/0386-Optimize-NetworkManager-Exception-Handling.patch diff --git a/patches/server/0387-Fix-SPIGOT-5885-Unable-to-disable-advancements.patch b/patches/server/0387-Fix-SPIGOT-5885-Unable-to-disable-advancements.patch deleted file mode 100644 index df26348fac4c..000000000000 --- a/patches/server/0387-Fix-SPIGOT-5885-Unable-to-disable-advancements.patch +++ /dev/null @@ -1,18 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: William Blake Galbreath -Date: Fri, 10 Jul 2020 12:38:12 -0500 -Subject: [PATCH] Fix SPIGOT-5885 Unable to disable advancements - - -diff --git a/src/main/java/net/minecraft/server/Main.java b/src/main/java/net/minecraft/server/Main.java -index 7dfcb8c6583d1c13fe688b5e17edb9d1c6935547..ded1b7ab4ca1ae3a2d799fe31d05bd6a0c27dcb7 100644 ---- a/src/main/java/net/minecraft/server/Main.java -+++ b/src/main/java/net/minecraft/server/Main.java -@@ -167,6 +167,7 @@ public class Main { - return; - } - -+ org.spigotmc.SpigotConfig.disabledAdvancements = spigotConfiguration.getStringList("advancements.disabled"); // Paper - fix SPIGOT-5885, must be set early in init - // Paper start - fix SPIGOT-5824 - File file; - File userCacheFile = new File(Services.USERID_CACHE_FILE); diff --git a/patches/server/0387-Fix-some-rails-connecting-improperly.patch b/patches/server/0387-Fix-some-rails-connecting-improperly.patch new file mode 100644 index 000000000000..c22d4e1fdf89 --- /dev/null +++ b/patches/server/0387-Fix-some-rails-connecting-improperly.patch @@ -0,0 +1,84 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Spottedleaf +Date: Fri, 24 Jul 2020 15:56:05 -0700 +Subject: [PATCH] Fix some rails connecting improperly + + +diff --git a/src/main/java/net/minecraft/world/level/block/BaseRailBlock.java b/src/main/java/net/minecraft/world/level/block/BaseRailBlock.java +index 4a5badc4bb1e2c29016735e9df93c7ac4d3f363d..f9a55f76fed8609bca167b2ea37464e8079de0c0 100644 +--- a/src/main/java/net/minecraft/world/level/block/BaseRailBlock.java ++++ b/src/main/java/net/minecraft/world/level/block/BaseRailBlock.java +@@ -71,6 +71,7 @@ public abstract class BaseRailBlock extends Block implements SimpleWaterloggedBl + state = this.updateDir(world, pos, state, true); + if (this.isStraight) { + world.neighborChanged(state, pos, this, null, notify); ++ state = world.getBlockState(pos); // Paper - Fix some rails connecting improperly + } + + return state; +diff --git a/src/main/java/net/minecraft/world/level/block/DetectorRailBlock.java b/src/main/java/net/minecraft/world/level/block/DetectorRailBlock.java +index 3de30845aae143e5be5db921004142792522607d..1e2f56b5c40c3dc72bc38354160f8e7de1f4f5cf 100644 +--- a/src/main/java/net/minecraft/world/level/block/DetectorRailBlock.java ++++ b/src/main/java/net/minecraft/world/level/block/DetectorRailBlock.java +@@ -78,6 +78,7 @@ public class DetectorRailBlock extends BaseRailBlock { + + private void checkPressed(Level world, BlockPos pos, BlockState state) { + if (this.canSurvive(state, world, pos)) { ++ if (state.getBlock() != this) { return; } // Paper - Fix some rails connecting improperly + boolean flag = (Boolean) state.getValue(DetectorRailBlock.POWERED); + boolean flag1 = false; + List list = this.getInteractingMinecartOfType(world, pos, AbstractMinecart.class, (entity) -> { +diff --git a/src/main/java/net/minecraft/world/level/block/RailState.java b/src/main/java/net/minecraft/world/level/block/RailState.java +index 6caafa73b03ed55c95303e49735eaf3dfd34903a..aa7ebaccad8dc555d9e1dee300e75fcd968a3608 100644 +--- a/src/main/java/net/minecraft/world/level/block/RailState.java ++++ b/src/main/java/net/minecraft/world/level/block/RailState.java +@@ -17,6 +17,12 @@ public class RailState { + private final boolean isStraight; + private final List connections = Lists.newArrayList(); + ++ // Paper start - Fix some rails connecting improperly ++ public boolean isValid() { ++ return this.level.getBlockState(this.pos).getBlock() == this.state.getBlock(); ++ } ++ // Paper end - Fix some rails connecting improperly ++ + public RailState(Level world, BlockPos pos, BlockState state) { + this.level = world; + this.pos = pos; +@@ -141,6 +147,11 @@ public class RailState { + } + + private void connectTo(RailState placementHelper) { ++ // Paper start - Fix some rails connecting improperly ++ if (!this.isValid() || !placementHelper.isValid()) { ++ return; ++ } ++ // Paper end - Fix some rails connecting improperly + this.connections.add(placementHelper.pos); + BlockPos blockPos = this.pos.north(); + BlockPos blockPos2 = this.pos.south(); +@@ -331,10 +342,15 @@ public class RailState { + this.state = this.state.setValue(this.block.getShapeProperty(), railShape2); + if (forceUpdate || this.level.getBlockState(this.pos) != this.state) { + this.level.setBlock(this.pos, this.state, 3); ++ // Paper start - Fix some rails connecting improperly ++ if (!this.isValid()) { ++ return this; ++ } ++ // Paper end - Fix some rails connecting improperly + + for (int i = 0; i < this.connections.size(); i++) { + RailState railState = this.getRail(this.connections.get(i)); +- if (railState != null) { ++ if (railState != null && railState.isValid()) { // Paper - Fix some rails connecting improperly + railState.removeSoftConnections(); + if (railState.canConnectTo(this)) { + railState.connectTo(this); +@@ -347,6 +363,6 @@ public class RailState { + } + + public BlockState getState() { +- return this.state; ++ return this.level.getBlockState(this.pos); // Paper - Fix some rails connecting improperly + } + } diff --git a/patches/server/0388-Fix-AdvancementDataPlayer-leak-due-from-quitting-ear.patch b/patches/server/0388-Fix-AdvancementDataPlayer-leak-due-from-quitting-ear.patch deleted file mode 100644 index 5f0bca47b3eb..000000000000 --- a/patches/server/0388-Fix-AdvancementDataPlayer-leak-due-from-quitting-ear.patch +++ /dev/null @@ -1,65 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Spottedleaf -Date: Mon, 13 Jul 2020 06:22:54 -0700 -Subject: [PATCH] Fix AdvancementDataPlayer leak due from quitting early in - login - -Move the criterion storage to the AdvancementDataPlayer object -itself, so the criterion object stores no references - and thus -needs no cleanup. - -diff --git a/src/main/java/net/minecraft/advancements/critereon/SimpleCriterionTrigger.java b/src/main/java/net/minecraft/advancements/critereon/SimpleCriterionTrigger.java -index 9a387d5dc0925304d4163e3caa22206aaa68e3b7..f43053ba082f9772b6ec02828fa2d6f387c32d26 100644 ---- a/src/main/java/net/minecraft/advancements/critereon/SimpleCriterionTrigger.java -+++ b/src/main/java/net/minecraft/advancements/critereon/SimpleCriterionTrigger.java -@@ -15,32 +15,32 @@ import net.minecraft.server.level.ServerPlayer; - import net.minecraft.world.level.storage.loot.LootContext; - - public abstract class SimpleCriterionTrigger implements CriterionTrigger { -- private final Map>> players = Maps.newIdentityHashMap(); -+ // private final Map>> players = Maps.newIdentityHashMap(); // Paper - fix AdvancementDataPlayer leak; moved into AdvancementDataPlayer to fix memory leak - - @Override - public final void addPlayerListener(PlayerAdvancements manager, CriterionTrigger.Listener conditions) { -- this.players.computeIfAbsent(manager, managerx -> Sets.newHashSet()).add(conditions); -+ manager.criterionData.computeIfAbsent(this, managerx -> Sets.newHashSet()).add(conditions); // Paper - fix AdvancementDataPlayer leak - } - - @Override - public final void removePlayerListener(PlayerAdvancements manager, CriterionTrigger.Listener conditions) { -- Set> set = this.players.get(manager); -+ Set> set = (Set) manager.criterionData.get(this); // Paper - fix AdvancementDataPlayer leak - if (set != null) { - set.remove(conditions); - if (set.isEmpty()) { -- this.players.remove(manager); -+ manager.criterionData.remove(this); // Paper - fix AdvancementDataPlayer leak - } - } - } - - @Override - public final void removePlayerListeners(PlayerAdvancements tracker) { -- this.players.remove(tracker); -+ tracker.criterionData.remove(this); // Paper - fix AdvancementDataPlayer leak - } - - protected void trigger(ServerPlayer player, Predicate predicate) { - PlayerAdvancements playerAdvancements = player.getAdvancements(); -- Set> set = this.players.get(playerAdvancements); -+ Set> set = (Set) playerAdvancements.criterionData.get(this); // Paper - fix AdvancementDataPlayer leak - if (set != null && !set.isEmpty()) { - LootContext lootContext = EntityPredicate.createContext(player, player); - List> list = null; -diff --git a/src/main/java/net/minecraft/server/PlayerAdvancements.java b/src/main/java/net/minecraft/server/PlayerAdvancements.java -index 4c85abf29441645039b6a554a50e1d3274229de6..d85fb1e2ea0eaef81e9039b47d18f83507e05a59 100644 ---- a/src/main/java/net/minecraft/server/PlayerAdvancements.java -+++ b/src/main/java/net/minecraft/server/PlayerAdvancements.java -@@ -63,6 +63,7 @@ public class PlayerAdvancements { - private AdvancementHolder lastSelectedTab; - private boolean isFirstPacket = true; - private final Codec codec; -+ public final Map, Set>> criterionData = new java.util.IdentityHashMap<>(); // Paper - fix advancement data player leakage - - public PlayerAdvancements(DataFixer dataFixer, PlayerList playerManager, ServerAdvancementManager advancementLoader, Path filePath, ServerPlayer owner) { - this.playerList = playerManager; diff --git a/patches/server/0391-Fix-regex-mistake-in-CB-NBT-int-deserialization.patch b/patches/server/0388-Fix-regex-mistake-in-CB-NBT-int-deserialization.patch similarity index 100% rename from patches/server/0391-Fix-regex-mistake-in-CB-NBT-int-deserialization.patch rename to patches/server/0388-Fix-regex-mistake-in-CB-NBT-int-deserialization.patch diff --git a/patches/server/0389-Brand-support.patch b/patches/server/0389-Brand-support.patch new file mode 100644 index 000000000000..917f8102d4c8 --- /dev/null +++ b/patches/server/0389-Brand-support.patch @@ -0,0 +1,76 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: DigitalRegent +Date: Sat, 11 Apr 2020 13:10:58 +0200 +Subject: [PATCH] Brand support + + +diff --git a/src/main/java/net/minecraft/server/level/ServerPlayer.java b/src/main/java/net/minecraft/server/level/ServerPlayer.java +index 807068fc6065f71961d34cb4f18b6eb39ae49637..c468947990cf05e554006e51d87b72fad7c28e55 100644 +--- a/src/main/java/net/minecraft/server/level/ServerPlayer.java ++++ b/src/main/java/net/minecraft/server/level/ServerPlayer.java +@@ -322,6 +322,7 @@ public class ServerPlayer extends net.minecraft.world.entity.player.Player { + // CraftBukkit end + public boolean isRealPlayer; // Paper + public com.destroystokyo.paper.event.entity.PlayerNaturallySpawnCreaturesEvent playerNaturallySpawnedEvent; // Paper - PlayerNaturallySpawnCreaturesEvent ++ public @Nullable String clientBrandName = null; // Paper - Brand support + + public ServerPlayer(MinecraftServer server, ServerLevel world, GameProfile profile, ClientInformation clientOptions) { + super(world, world.getSharedSpawnPos(), world.getSharedSpawnAngle(), profile); +diff --git a/src/main/java/net/minecraft/server/network/ServerCommonPacketListenerImpl.java b/src/main/java/net/minecraft/server/network/ServerCommonPacketListenerImpl.java +index b9fbaddcc8239bf737fdea51790f678306e511eb..9a8b08d4b70b8890961e4af7ce6e870aa1c7c810 100644 +--- a/src/main/java/net/minecraft/server/network/ServerCommonPacketListenerImpl.java ++++ b/src/main/java/net/minecraft/server/network/ServerCommonPacketListenerImpl.java +@@ -84,6 +84,7 @@ public abstract class ServerCommonPacketListenerImpl implements ServerCommonPack + private volatile boolean suspendFlushingOnServerThread = false; + public final java.util.Map packCallbacks = new java.util.concurrent.ConcurrentHashMap<>(); // Paper - adventure resource pack callbacks + private static final long KEEPALIVE_LIMIT = Long.getLong("paper.playerconnection.keepalive", 30) * 1000; // Paper - provide property to set keepalive limit ++ protected static final ResourceLocation MINECRAFT_BRAND = ResourceLocation.withDefaultNamespace("brand"); // Paper - Brand support + + public ServerCommonPacketListenerImpl(MinecraftServer minecraftserver, Connection networkmanager, CommonListenerCookie commonlistenercookie, ServerPlayer player) { // CraftBukkit + this.server = minecraftserver; +@@ -155,6 +156,11 @@ public abstract class ServerCommonPacketListenerImpl implements ServerCommonPack + + @Override + public void handleCustomPayload(ServerboundCustomPayloadPacket packet) { ++ // Paper start - Brand support ++ if (packet.payload() instanceof net.minecraft.network.protocol.common.custom.BrandPayload brandPayload) { ++ this.player.clientBrandName = brandPayload.brand(); ++ } ++ // Paper end - Brand support + if (!(packet.payload() instanceof DiscardedPayload)) { + return; + } +@@ -186,6 +192,15 @@ public abstract class ServerCommonPacketListenerImpl implements ServerCommonPack + try { + byte[] data = new byte[payload.readableBytes()]; + payload.readBytes(data); ++ // Paper start - Brand support; Retain this incase upstream decides to 'break' the new mechanism in favour of backwards compat... ++ if (identifier.equals(MINECRAFT_BRAND)) { ++ try { ++ this.player.clientBrandName = new net.minecraft.network.FriendlyByteBuf(io.netty.buffer.Unpooled.copiedBuffer(data)).readUtf(256); ++ } catch (StringIndexOutOfBoundsException ex) { ++ this.player.clientBrandName = "illegal"; ++ } ++ } ++ // Paper end - Brand support + this.cserver.getMessenger().dispatchIncomingMessage(this.player.getBukkitEntity(), identifier.toString(), data); + } catch (Exception ex) { + ServerGamePacketListenerImpl.LOGGER.error("Couldn\'t dispatch custom payload", ex); +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java +index 69108fd869c7c929fd7971abea520d5ab9063f69..3191c66c9e5b9c5fcfd07716733aaa51612d507f 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java +@@ -3166,6 +3166,13 @@ public class CraftPlayer extends CraftHumanEntity implements Player { + // Paper end + }; + ++ // Paper start - brand support ++ @Override ++ public String getClientBrandName() { ++ return getHandle().clientBrandName; ++ } ++ // Paper end ++ + public Player.Spigot spigot() + { + return this.spigot; diff --git a/patches/server/0390-Add-playPickupItemAnimation-to-LivingEntity.patch b/patches/server/0390-Add-playPickupItemAnimation-to-LivingEntity.patch new file mode 100644 index 000000000000..64c59dce175c --- /dev/null +++ b/patches/server/0390-Add-playPickupItemAnimation-to-LivingEntity.patch @@ -0,0 +1,22 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: William Blake Galbreath +Date: Sun, 23 Aug 2020 19:36:22 +0200 +Subject: [PATCH] Add playPickupItemAnimation to LivingEntity + + +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftLivingEntity.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftLivingEntity.java +index 35130dc4e20ef644b5764091fcbccda2e4da780b..5beee554567d36f2a3b871cf6ec3ecb3d2353592 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftLivingEntity.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftLivingEntity.java +@@ -1009,4 +1009,11 @@ public class CraftLivingEntity extends CraftEntity implements LivingEntity { + } + } + // Paper end - entity jump API ++ ++ // Paper start - pickup animation API ++ @Override ++ public void playPickupItemAnimation(final org.bukkit.entity.Item item, final int quantity) { ++ this.getHandle().take(((CraftItem) item).getHandle(), quantity); ++ } ++ // Paper end - pickup animation API + } diff --git a/patches/server/0390-Fix-some-rails-connecting-improperly.patch b/patches/server/0390-Fix-some-rails-connecting-improperly.patch deleted file mode 100644 index 4f5cd8653888..000000000000 --- a/patches/server/0390-Fix-some-rails-connecting-improperly.patch +++ /dev/null @@ -1,84 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Spottedleaf -Date: Fri, 24 Jul 2020 15:56:05 -0700 -Subject: [PATCH] Fix some rails connecting improperly - - -diff --git a/src/main/java/net/minecraft/world/level/block/BaseRailBlock.java b/src/main/java/net/minecraft/world/level/block/BaseRailBlock.java -index f9b6db516fa0788a6d4d40fa7ea2039cd3282b24..0961f6f5e627debe0bf02670d922e6d3388ba631 100644 ---- a/src/main/java/net/minecraft/world/level/block/BaseRailBlock.java -+++ b/src/main/java/net/minecraft/world/level/block/BaseRailBlock.java -@@ -68,6 +68,7 @@ public abstract class BaseRailBlock extends Block implements SimpleWaterloggedBl - state = this.updateDir(world, pos, state, true); - if (this.isStraight) { - world.neighborChanged(state, pos, this, pos, notify); -+ state = world.getBlockState(pos); // Paper - Fix some rails connecting improperly - } - - return state; -diff --git a/src/main/java/net/minecraft/world/level/block/DetectorRailBlock.java b/src/main/java/net/minecraft/world/level/block/DetectorRailBlock.java -index e5534fd35a8f9736cc25471e9f0feaa8da88c85d..55a97da8786ec0ae98abe56876c00f4678ba0007 100644 ---- a/src/main/java/net/minecraft/world/level/block/DetectorRailBlock.java -+++ b/src/main/java/net/minecraft/world/level/block/DetectorRailBlock.java -@@ -77,6 +77,7 @@ public class DetectorRailBlock extends BaseRailBlock { - - private void checkPressed(Level world, BlockPos pos, BlockState state) { - if (this.canSurvive(state, world, pos)) { -+ if (state.getBlock() != this) { return; } // Paper - Fix some rails connecting improperly - boolean flag = (Boolean) state.getValue(DetectorRailBlock.POWERED); - boolean flag1 = false; - List list = this.getInteractingMinecartOfType(world, pos, AbstractMinecart.class, (entity) -> { -diff --git a/src/main/java/net/minecraft/world/level/block/RailState.java b/src/main/java/net/minecraft/world/level/block/RailState.java -index 6caafa73b03ed55c95303e49735eaf3dfd34903a..aa7ebaccad8dc555d9e1dee300e75fcd968a3608 100644 ---- a/src/main/java/net/minecraft/world/level/block/RailState.java -+++ b/src/main/java/net/minecraft/world/level/block/RailState.java -@@ -17,6 +17,12 @@ public class RailState { - private final boolean isStraight; - private final List connections = Lists.newArrayList(); - -+ // Paper start - Fix some rails connecting improperly -+ public boolean isValid() { -+ return this.level.getBlockState(this.pos).getBlock() == this.state.getBlock(); -+ } -+ // Paper end - Fix some rails connecting improperly -+ - public RailState(Level world, BlockPos pos, BlockState state) { - this.level = world; - this.pos = pos; -@@ -141,6 +147,11 @@ public class RailState { - } - - private void connectTo(RailState placementHelper) { -+ // Paper start - Fix some rails connecting improperly -+ if (!this.isValid() || !placementHelper.isValid()) { -+ return; -+ } -+ // Paper end - Fix some rails connecting improperly - this.connections.add(placementHelper.pos); - BlockPos blockPos = this.pos.north(); - BlockPos blockPos2 = this.pos.south(); -@@ -331,10 +342,15 @@ public class RailState { - this.state = this.state.setValue(this.block.getShapeProperty(), railShape2); - if (forceUpdate || this.level.getBlockState(this.pos) != this.state) { - this.level.setBlock(this.pos, this.state, 3); -+ // Paper start - Fix some rails connecting improperly -+ if (!this.isValid()) { -+ return this; -+ } -+ // Paper end - Fix some rails connecting improperly - - for (int i = 0; i < this.connections.size(); i++) { - RailState railState = this.getRail(this.connections.get(i)); -- if (railState != null) { -+ if (railState != null && railState.isValid()) { // Paper - Fix some rails connecting improperly - railState.removeSoftConnections(); - if (railState.canConnectTo(this)) { - railState.connectTo(this); -@@ -347,6 +363,6 @@ public class RailState { - } - - public BlockState getState() { -- return this.state; -+ return this.level.getBlockState(this.pos); // Paper - Fix some rails connecting improperly - } - } diff --git a/patches/server/0394-Don-t-require-FACING-data.patch b/patches/server/0391-Don-t-require-FACING-data.patch similarity index 100% rename from patches/server/0394-Don-t-require-FACING-data.patch rename to patches/server/0391-Don-t-require-FACING-data.patch diff --git a/patches/server/0392-Brand-support.patch b/patches/server/0392-Brand-support.patch deleted file mode 100644 index af42b533fd93..000000000000 --- a/patches/server/0392-Brand-support.patch +++ /dev/null @@ -1,76 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: DigitalRegent -Date: Sat, 11 Apr 2020 13:10:58 +0200 -Subject: [PATCH] Brand support - - -diff --git a/src/main/java/net/minecraft/server/level/ServerPlayer.java b/src/main/java/net/minecraft/server/level/ServerPlayer.java -index e6e7dc17d1196a8211a565355f34b5dcfd478852..45181bc9c422507682d479e4d43177ecd3253971 100644 ---- a/src/main/java/net/minecraft/server/level/ServerPlayer.java -+++ b/src/main/java/net/minecraft/server/level/ServerPlayer.java -@@ -294,6 +294,7 @@ public class ServerPlayer extends net.minecraft.world.entity.player.Player { - // CraftBukkit end - public boolean isRealPlayer; // Paper - public com.destroystokyo.paper.event.entity.PlayerNaturallySpawnCreaturesEvent playerNaturallySpawnedEvent; // Paper - PlayerNaturallySpawnCreaturesEvent -+ public @Nullable String clientBrandName = null; // Paper - Brand support - - public ServerPlayer(MinecraftServer server, ServerLevel world, GameProfile profile, ClientInformation clientOptions) { - super(world, world.getSharedSpawnPos(), world.getSharedSpawnAngle(), profile); -diff --git a/src/main/java/net/minecraft/server/network/ServerCommonPacketListenerImpl.java b/src/main/java/net/minecraft/server/network/ServerCommonPacketListenerImpl.java -index 4f62abfe62c951b7a1df36ece34747100d6a4ff7..48085b2e7197dc44e76b812bdd514af729e21e83 100644 ---- a/src/main/java/net/minecraft/server/network/ServerCommonPacketListenerImpl.java -+++ b/src/main/java/net/minecraft/server/network/ServerCommonPacketListenerImpl.java -@@ -83,6 +83,7 @@ public abstract class ServerCommonPacketListenerImpl implements ServerCommonPack - private volatile boolean suspendFlushingOnServerThread = false; - public final java.util.Map packCallbacks = new java.util.concurrent.ConcurrentHashMap<>(); // Paper - adventure resource pack callbacks - private static final long KEEPALIVE_LIMIT = Long.getLong("paper.playerconnection.keepalive", 30) * 1000; // Paper - provide property to set keepalive limit -+ protected static final ResourceLocation MINECRAFT_BRAND = ResourceLocation.withDefaultNamespace("brand"); // Paper - Brand support - - public ServerCommonPacketListenerImpl(MinecraftServer minecraftserver, Connection networkmanager, CommonListenerCookie commonlistenercookie, ServerPlayer player) { // CraftBukkit - this.server = minecraftserver; -@@ -148,6 +149,11 @@ public abstract class ServerCommonPacketListenerImpl implements ServerCommonPack - - @Override - public void handleCustomPayload(ServerboundCustomPayloadPacket packet) { -+ // Paper start - Brand support -+ if (packet.payload() instanceof net.minecraft.network.protocol.common.custom.BrandPayload brandPayload) { -+ this.player.clientBrandName = brandPayload.brand(); -+ } -+ // Paper end - Brand support - if (!(packet.payload() instanceof DiscardedPayload)) { - return; - } -@@ -179,6 +185,15 @@ public abstract class ServerCommonPacketListenerImpl implements ServerCommonPack - try { - byte[] data = new byte[payload.readableBytes()]; - payload.readBytes(data); -+ // Paper start - Brand support; Retain this incase upstream decides to 'break' the new mechanism in favour of backwards compat... -+ if (identifier.equals(MINECRAFT_BRAND)) { -+ try { -+ this.player.clientBrandName = new net.minecraft.network.FriendlyByteBuf(io.netty.buffer.Unpooled.copiedBuffer(data)).readUtf(256); -+ } catch (StringIndexOutOfBoundsException ex) { -+ this.player.clientBrandName = "illegal"; -+ } -+ } -+ // Paper end - Brand support - this.cserver.getMessenger().dispatchIncomingMessage(this.player.getBukkitEntity(), identifier.toString(), data); - } catch (Exception ex) { - ServerGamePacketListenerImpl.LOGGER.error("Couldn\'t dispatch custom payload", ex); -diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java -index 068ff2c228308ec87fcc6d1352bd63b91bd34a93..854533854dfba24b59a15265ac759331e3ddfc74 100644 ---- a/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java -+++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java -@@ -3139,6 +3139,13 @@ public class CraftPlayer extends CraftHumanEntity implements Player { - // Paper end - }; - -+ // Paper start - brand support -+ @Override -+ public String getClientBrandName() { -+ return getHandle().clientBrandName; -+ } -+ // Paper end -+ - public Player.Spigot spigot() - { - return this.spigot; diff --git a/patches/server/0392-Fix-SpawnChangeEvent-not-firing-for-all-use-cases.patch b/patches/server/0392-Fix-SpawnChangeEvent-not-firing-for-all-use-cases.patch new file mode 100644 index 000000000000..b664948634d2 --- /dev/null +++ b/patches/server/0392-Fix-SpawnChangeEvent-not-firing-for-all-use-cases.patch @@ -0,0 +1,43 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: William Blake Galbreath +Date: Sat, 22 Aug 2020 23:36:21 +0200 +Subject: [PATCH] Fix SpawnChangeEvent not firing for all use-cases + + +diff --git a/src/main/java/net/minecraft/server/level/ServerLevel.java b/src/main/java/net/minecraft/server/level/ServerLevel.java +index 0995a3a274df988a5c63c813de8213019a7c47c4..17725a575a29aa8076582e1b8c644ffd928cfaae 100644 +--- a/src/main/java/net/minecraft/server/level/ServerLevel.java ++++ b/src/main/java/net/minecraft/server/level/ServerLevel.java +@@ -1700,7 +1700,9 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe + float f1 = this.levelData.getSpawnAngle(); + + if (!blockposition1.equals(pos) || f1 != angle) { ++ org.bukkit.Location prevSpawnLoc = this.getWorld().getSpawnLocation(); // Paper - Call SpawnChangeEvent + this.levelData.setSpawn(pos, angle); ++ new org.bukkit.event.world.SpawnChangeEvent(this.getWorld(), prevSpawnLoc).callEvent(); // Paper - Call SpawnChangeEvent + this.getServer().getPlayerList().broadcastAll(new ClientboundSetDefaultSpawnPositionPacket(pos, angle)); + } + +diff --git a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java +index 55daddd0d80a18a3c044f48a47a0a13ef3a68943..b0fbb828306194fd91f0e62244d5db6404b4f10a 100644 +--- a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java ++++ b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java +@@ -250,12 +250,14 @@ public class CraftWorld extends CraftRegionAccessor implements World { + @Override + public boolean setSpawnLocation(int x, int y, int z, float angle) { + try { +- Location previousLocation = this.getSpawnLocation(); +- this.world.levelData.setSpawn(new BlockPos(x, y, z), angle); ++ // Location previousLocation = this.getSpawnLocation(); // Paper - Call SpawnChangeEvent; moved to nms.ServerLevel ++ this.world.setDefaultSpawnPos(new BlockPos(x, y, z), angle); // Paper - use ServerLevel#setDefaultSpawnPos + ++ // Paper start - Call SpawnChangeEvent; move to nms.ServerLevel + // Notify anyone who's listening. +- SpawnChangeEvent event = new SpawnChangeEvent(this, previousLocation); +- this.server.getPluginManager().callEvent(event); ++ // SpawnChangeEvent event = new SpawnChangeEvent(this, previousLocation); ++ // server.getPluginManager().callEvent(event); ++ // Paper end - Call SpawnChangeEvent + + return true; + } catch (Exception e) { diff --git a/patches/server/0393-Add-moon-phase-API.patch b/patches/server/0393-Add-moon-phase-API.patch new file mode 100644 index 000000000000..2fcd34c334fd --- /dev/null +++ b/patches/server/0393-Add-moon-phase-API.patch @@ -0,0 +1,22 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: BillyGalbreath +Date: Sun, 23 Aug 2020 16:32:11 +0200 +Subject: [PATCH] Add moon phase API + + +diff --git a/src/main/java/org/bukkit/craftbukkit/CraftRegionAccessor.java b/src/main/java/org/bukkit/craftbukkit/CraftRegionAccessor.java +index 92a0c2049fa1df76cedfe7dfbd6e5fe402859f4b..15da29058f80a2d7cf2be26c48421c1746815a10 100644 +--- a/src/main/java/org/bukkit/craftbukkit/CraftRegionAccessor.java ++++ b/src/main/java/org/bukkit/craftbukkit/CraftRegionAccessor.java +@@ -515,4 +515,11 @@ public abstract class CraftRegionAccessor implements RegionAccessor { + + throw new IllegalArgumentException("Cannot spawn an entity for " + clazz.getName()); + } ++ ++ // Paper start ++ @Override ++ public io.papermc.paper.world.MoonPhase getMoonPhase() { ++ return io.papermc.paper.world.MoonPhase.getPhase(this.getHandle().dayTime() / 24000L); ++ } ++ // Paper end + } diff --git a/patches/server/0393-Add-playPickupItemAnimation-to-LivingEntity.patch b/patches/server/0393-Add-playPickupItemAnimation-to-LivingEntity.patch deleted file mode 100644 index e66408f81493..000000000000 --- a/patches/server/0393-Add-playPickupItemAnimation-to-LivingEntity.patch +++ /dev/null @@ -1,22 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: William Blake Galbreath -Date: Sun, 23 Aug 2020 19:36:22 +0200 -Subject: [PATCH] Add playPickupItemAnimation to LivingEntity - - -diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftLivingEntity.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftLivingEntity.java -index 733c69a2cfa60fb8c920400e3d9acfc2465090e5..fad7d8130f6db70c7bfca9d02027d8a41f5309c1 100644 ---- a/src/main/java/org/bukkit/craftbukkit/entity/CraftLivingEntity.java -+++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftLivingEntity.java -@@ -990,4 +990,11 @@ public class CraftLivingEntity extends CraftEntity implements LivingEntity { - } - } - // Paper end - entity jump API -+ -+ // Paper start - pickup animation API -+ @Override -+ public void playPickupItemAnimation(final org.bukkit.entity.Item item, final int quantity) { -+ this.getHandle().take(((CraftItem) item).getHandle(), quantity); -+ } -+ // Paper end - pickup animation API - } diff --git a/patches/server/0394-Do-not-let-the-server-load-chunks-from-newer-version.patch b/patches/server/0394-Do-not-let-the-server-load-chunks-from-newer-version.patch new file mode 100644 index 000000000000..3bdfea31f7f8 --- /dev/null +++ b/patches/server/0394-Do-not-let-the-server-load-chunks-from-newer-version.patch @@ -0,0 +1,40 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Zach Brown +Date: Tue, 23 Jul 2019 20:44:47 -0500 +Subject: [PATCH] Do not let the server load chunks from newer versions + +If the server attempts to load a chunk generated by a newer version of +the game, immediately stop the server to prevent data corruption. + +You can override this functionality at your own peril. + +diff --git a/src/main/java/net/minecraft/world/level/chunk/storage/SerializableChunkData.java b/src/main/java/net/minecraft/world/level/chunk/storage/SerializableChunkData.java +index 92ad7704a77c024afe090488764eaf59a4f7505f..d7a204216332ccbd6bece23bd507be0366ea4d61 100644 +--- a/src/main/java/net/minecraft/world/level/chunk/storage/SerializableChunkData.java ++++ b/src/main/java/net/minecraft/world/level/chunk/storage/SerializableChunkData.java +@@ -104,11 +104,25 @@ public record SerializableChunkData(Registry biomeRegistry, ChunkPos chun + } + // Paper end - guard against serializing mismatching coordinates + ++ // Paper start - Do not let the server load chunks from newer versions ++ private static final int CURRENT_DATA_VERSION = net.minecraft.SharedConstants.getCurrentVersion().getDataVersion().getVersion(); ++ private static final boolean JUST_CORRUPT_IT = Boolean.getBoolean("Paper.ignoreWorldDataVersion"); ++ // Paper end - Do not let the server load chunks from newer versions ++ + @Nullable + public static SerializableChunkData parse(LevelHeightAccessor world, RegistryAccess registryManager, CompoundTag nbt) { + if (!nbt.contains("Status", 8)) { + return null; + } else { ++ // Paper start - Do not let the server load chunks from newer versions ++ if (nbt.contains("DataVersion", net.minecraft.nbt.Tag.TAG_ANY_NUMERIC)) { ++ final int dataVersion = nbt.getInt("DataVersion"); ++ if (!JUST_CORRUPT_IT && dataVersion > CURRENT_DATA_VERSION) { ++ new RuntimeException("Server attempted to load chunk saved with newer version of minecraft! " + dataVersion + " > " + CURRENT_DATA_VERSION).printStackTrace(); ++ System.exit(1); ++ } ++ } ++ // Paper end - Do not let the server load chunks from newer versions + ChunkPos chunkcoordintpair = new ChunkPos(nbt.getInt("xPos"), nbt.getInt("zPos")); // Paper - guard against serializing mismatching coordinates; diff on change, see ChunkSerializer#getChunkCoordinate + long i = nbt.getLong("LastUpdate"); + long j = nbt.getLong("InhabitedTime"); diff --git a/patches/server/0395-Fix-SpawnChangeEvent-not-firing-for-all-use-cases.patch b/patches/server/0395-Fix-SpawnChangeEvent-not-firing-for-all-use-cases.patch deleted file mode 100644 index b202f0e93428..000000000000 --- a/patches/server/0395-Fix-SpawnChangeEvent-not-firing-for-all-use-cases.patch +++ /dev/null @@ -1,43 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: William Blake Galbreath -Date: Sat, 22 Aug 2020 23:36:21 +0200 -Subject: [PATCH] Fix SpawnChangeEvent not firing for all use-cases - - -diff --git a/src/main/java/net/minecraft/server/level/ServerLevel.java b/src/main/java/net/minecraft/server/level/ServerLevel.java -index 5b89d834a7c01530807e61ea25af2b01f004ce86..50d2e628590e8563d2ef5b987f764cf1785e3b50 100644 ---- a/src/main/java/net/minecraft/server/level/ServerLevel.java -+++ b/src/main/java/net/minecraft/server/level/ServerLevel.java -@@ -1668,7 +1668,9 @@ public class ServerLevel extends Level implements WorldGenLevel { - float f1 = this.levelData.getSpawnAngle(); - - if (!blockposition1.equals(pos) || f1 != angle) { -+ org.bukkit.Location prevSpawnLoc = this.getWorld().getSpawnLocation(); // Paper - Call SpawnChangeEvent - this.levelData.setSpawn(pos, angle); -+ new org.bukkit.event.world.SpawnChangeEvent(this.getWorld(), prevSpawnLoc).callEvent(); // Paper - Call SpawnChangeEvent - this.getServer().getPlayerList().broadcastAll(new ClientboundSetDefaultSpawnPositionPacket(pos, angle)); - } - -diff --git a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java -index c35fd2fcb6ced6a16816637353d03c88f1d49d1b..99fccedaab2600881683140e10ee17377375b911 100644 ---- a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java -+++ b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java -@@ -246,12 +246,14 @@ public class CraftWorld extends CraftRegionAccessor implements World { - @Override - public boolean setSpawnLocation(int x, int y, int z, float angle) { - try { -- Location previousLocation = this.getSpawnLocation(); -- this.world.levelData.setSpawn(new BlockPos(x, y, z), angle); -+ // Location previousLocation = this.getSpawnLocation(); // Paper - Call SpawnChangeEvent; moved to nms.ServerLevel -+ this.world.setDefaultSpawnPos(new BlockPos(x, y, z), angle); // Paper - use ServerLevel#setDefaultSpawnPos - -+ // Paper start - Call SpawnChangeEvent; move to nms.ServerLevel - // Notify anyone who's listening. -- SpawnChangeEvent event = new SpawnChangeEvent(this, previousLocation); -- this.server.getPluginManager().callEvent(event); -+ // SpawnChangeEvent event = new SpawnChangeEvent(this, previousLocation); -+ // server.getPluginManager().callEvent(event); -+ // Paper end - Call SpawnChangeEvent - - return true; - } catch (Exception e) { diff --git a/patches/server/0395-Prevent-headless-pistons-from-being-created.patch b/patches/server/0395-Prevent-headless-pistons-from-being-created.patch new file mode 100644 index 000000000000..1cbd0b1c6c33 --- /dev/null +++ b/patches/server/0395-Prevent-headless-pistons-from-being-created.patch @@ -0,0 +1,27 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: commandblockguy +Date: Fri, 14 Aug 2020 14:44:14 -0500 +Subject: [PATCH] Prevent headless pistons from being created + +Prevent headless pistons from being created by explosions or tree/mushroom growth. + +diff --git a/src/main/java/net/minecraft/world/level/ServerExplosion.java b/src/main/java/net/minecraft/world/level/ServerExplosion.java +index d3d74a7f306d88b0d5cd83893b3ee5e750fd4caf..86656de31b1e33381eddd3ef210122118b31e620 100644 +--- a/src/main/java/net/minecraft/world/level/ServerExplosion.java ++++ b/src/main/java/net/minecraft/world/level/ServerExplosion.java +@@ -164,6 +164,15 @@ public class ServerExplosion implements Explosion { + + if (f > 0.0F && this.damageCalculator.shouldBlockExplode(this, this.level, blockposition, iblockdata, f)) { + set.add(blockposition); ++ // Paper start - prevent headless pistons from forming ++ if (!io.papermc.paper.configuration.GlobalConfiguration.get().unsupportedSettings.allowHeadlessPistons && iblockdata.getBlock() == Blocks.MOVING_PISTON) { ++ net.minecraft.world.level.block.entity.BlockEntity extension = this.level.getBlockEntity(blockposition); ++ if (extension instanceof net.minecraft.world.level.block.piston.PistonMovingBlockEntity blockEntity && blockEntity.isSourcePiston()) { ++ net.minecraft.core.Direction direction = iblockdata.getValue(net.minecraft.world.level.block.piston.PistonHeadBlock.FACING); ++ set.add(blockposition.relative(direction.getOpposite())); ++ } ++ } ++ // Paper end - prevent headless pistons from forming + } + + d4 += d0 * 0.30000001192092896D; diff --git a/patches/server/0396-Add-BellRingEvent.patch b/patches/server/0396-Add-BellRingEvent.patch new file mode 100644 index 000000000000..32c63afd3744 --- /dev/null +++ b/patches/server/0396-Add-BellRingEvent.patch @@ -0,0 +1,25 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Eearslya Sleiarion +Date: Sun, 23 Aug 2020 13:04:02 +0200 +Subject: [PATCH] Add BellRingEvent + +Add a new event, BellRingEvent, to trigger whenever a player rings a +village bell. Passes along the bell block and the player who rang it. + +diff --git a/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java b/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java +index 876644a1b5d119c3420e3042b576e34878797d67..629936b6266dadcac9d9a1fd584e7ba6b67a0002 100644 +--- a/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java ++++ b/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java +@@ -377,10 +377,11 @@ public class CraftEventFactory { + return tradeSelectEvent; + } + ++ @SuppressWarnings("deprecation") // Paper use deprecated event to maintain compat (it extends modern event) + public static boolean handleBellRingEvent(Level world, BlockPos position, Direction direction, Entity entity) { + Block block = CraftBlock.at(world, position); + BlockFace bukkitDirection = CraftBlock.notchToBlockFace(direction); +- BellRingEvent event = new BellRingEvent(block, bukkitDirection, (entity != null) ? entity.getBukkitEntity() : null); ++ BellRingEvent event = new io.papermc.paper.event.block.BellRingEvent(block, bukkitDirection, (entity != null) ? entity.getBukkitEntity() : null); // Paper - deprecated BellRingEvent + Bukkit.getPluginManager().callEvent(event); + return !event.isCancelled(); + } diff --git a/patches/server/0396-Add-moon-phase-API.patch b/patches/server/0396-Add-moon-phase-API.patch deleted file mode 100644 index 539ccca6de1b..000000000000 --- a/patches/server/0396-Add-moon-phase-API.patch +++ /dev/null @@ -1,22 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: BillyGalbreath -Date: Sun, 23 Aug 2020 16:32:11 +0200 -Subject: [PATCH] Add moon phase API - - -diff --git a/src/main/java/org/bukkit/craftbukkit/CraftRegionAccessor.java b/src/main/java/org/bukkit/craftbukkit/CraftRegionAccessor.java -index 454f3a08c4d319f3d3fe2b1209c4d8b9fa9d4d08..1963e826548c5a8859c50f57654784c3aef50e44 100644 ---- a/src/main/java/org/bukkit/craftbukkit/CraftRegionAccessor.java -+++ b/src/main/java/org/bukkit/craftbukkit/CraftRegionAccessor.java -@@ -509,4 +509,11 @@ public abstract class CraftRegionAccessor implements RegionAccessor { - - throw new IllegalArgumentException("Cannot spawn an entity for " + clazz.getName()); - } -+ -+ // Paper start -+ @Override -+ public io.papermc.paper.world.MoonPhase getMoonPhase() { -+ return io.papermc.paper.world.MoonPhase.getPhase(this.getHandle().dayTime() / 24000L); -+ } -+ // Paper end - } diff --git a/patches/server/0397-Add-zombie-targets-turtle-egg-config.patch b/patches/server/0397-Add-zombie-targets-turtle-egg-config.patch new file mode 100644 index 000000000000..73a1b707b4c6 --- /dev/null +++ b/patches/server/0397-Add-zombie-targets-turtle-egg-config.patch @@ -0,0 +1,19 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: BillyGalbreath +Date: Sun, 23 Aug 2020 15:47:34 +0200 +Subject: [PATCH] Add zombie targets turtle egg config + + +diff --git a/src/main/java/net/minecraft/world/entity/monster/Zombie.java b/src/main/java/net/minecraft/world/entity/monster/Zombie.java +index 5b8ac7113eb8a57fe07bfaacc1b1320383a56d06..c182bdcc5da5652f8b34b4cb8d28651cf79009fe 100644 +--- a/src/main/java/net/minecraft/world/entity/monster/Zombie.java ++++ b/src/main/java/net/minecraft/world/entity/monster/Zombie.java +@@ -112,7 +112,7 @@ public class Zombie extends Monster { + + @Override + protected void registerGoals() { +- this.goalSelector.addGoal(4, new Zombie.ZombieAttackTurtleEggGoal(this, 1.0D, 3)); ++ if (this.level().paperConfig().entities.behavior.zombiesTargetTurtleEggs) this.goalSelector.addGoal(4, new Zombie.ZombieAttackTurtleEggGoal(this, 1.0D, 3)); // Paper - Add zombie targets turtle egg config + this.goalSelector.addGoal(8, new LookAtPlayerGoal(this, Player.class, 8.0F)); + this.goalSelector.addGoal(8, new RandomLookAroundGoal(this)); + this.addBehaviourGoals(); diff --git a/patches/server/0397-Do-not-let-the-server-load-chunks-from-newer-version.patch b/patches/server/0397-Do-not-let-the-server-load-chunks-from-newer-version.patch deleted file mode 100644 index 9ada63fcdfd2..000000000000 --- a/patches/server/0397-Do-not-let-the-server-load-chunks-from-newer-version.patch +++ /dev/null @@ -1,41 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Zach Brown -Date: Tue, 23 Jul 2019 20:44:47 -0500 -Subject: [PATCH] Do not let the server load chunks from newer versions - -If the server attempts to load a chunk generated by a newer version of -the game, immediately stop the server to prevent data corruption. - -You can override this functionality at your own peril. - -diff --git a/src/main/java/net/minecraft/world/level/chunk/storage/ChunkSerializer.java b/src/main/java/net/minecraft/world/level/chunk/storage/ChunkSerializer.java -index 0dd6f1bce4913cb84ba21a20df5573bab3a64645..5e1a68e3a920aab10a459b9b54f6abd59e7855e0 100644 ---- a/src/main/java/net/minecraft/world/level/chunk/storage/ChunkSerializer.java -+++ b/src/main/java/net/minecraft/world/level/chunk/storage/ChunkSerializer.java -@@ -86,6 +86,10 @@ public class ChunkSerializer { - public static final String BLOCK_LIGHT_TAG = "BlockLight"; - public static final String SKY_LIGHT_TAG = "SkyLight"; - -+ // Paper start - Do not let the server load chunks from newer versions -+ private static final int CURRENT_DATA_VERSION = net.minecraft.SharedConstants.getCurrentVersion().getDataVersion().getVersion(); -+ private static final boolean JUST_CORRUPT_IT = Boolean.getBoolean("Paper.ignoreWorldDataVersion"); -+ // Paper end - Do not let the server load chunks from newer versions - public ChunkSerializer() {} - - // Paper start - guard against serializing mismatching coordinates -@@ -102,6 +106,15 @@ public class ChunkSerializer { - // Paper end - guard against serializing mismatching coordinates - - public static ProtoChunk read(ServerLevel world, PoiManager poiStorage, RegionStorageInfo key, ChunkPos chunkPos, CompoundTag nbt) { -+ // Paper start - Do not let the server load chunks from newer versions -+ if (nbt.contains("DataVersion", net.minecraft.nbt.Tag.TAG_ANY_NUMERIC)) { -+ final int dataVersion = nbt.getInt("DataVersion"); -+ if (!JUST_CORRUPT_IT && dataVersion > CURRENT_DATA_VERSION) { -+ new RuntimeException("Server attempted to load chunk saved with newer version of minecraft! " + dataVersion + " > " + CURRENT_DATA_VERSION).printStackTrace(); -+ System.exit(1); -+ } -+ } -+ // Paper end - Do not let the server load chunks from newer versions - ChunkPos chunkcoordintpair1 = new ChunkPos(nbt.getInt("xPos"), nbt.getInt("zPos")); // Paper - guard against serializing mismatching coordinates; diff on change, see ChunkSerializer#getChunkCoordinate - - if (!Objects.equals(chunkPos, chunkcoordintpair1)) { diff --git a/patches/server/0401-Buffer-joins-to-world.patch b/patches/server/0398-Buffer-joins-to-world.patch similarity index 100% rename from patches/server/0401-Buffer-joins-to-world.patch rename to patches/server/0398-Buffer-joins-to-world.patch diff --git a/patches/server/0398-Prevent-headless-pistons-from-being-created.patch b/patches/server/0398-Prevent-headless-pistons-from-being-created.patch deleted file mode 100644 index 9b5071ea2f2b..000000000000 --- a/patches/server/0398-Prevent-headless-pistons-from-being-created.patch +++ /dev/null @@ -1,27 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: commandblockguy -Date: Fri, 14 Aug 2020 14:44:14 -0500 -Subject: [PATCH] Prevent headless pistons from being created - -Prevent headless pistons from being created by explosions or tree/mushroom growth. - -diff --git a/src/main/java/net/minecraft/world/level/Explosion.java b/src/main/java/net/minecraft/world/level/Explosion.java -index d4fe425954d36f0baddb256e3c83009a84084b72..edcc9d53ad81e2b2444335ac79abde5a4e2d9d47 100644 ---- a/src/main/java/net/minecraft/world/level/Explosion.java -+++ b/src/main/java/net/minecraft/world/level/Explosion.java -@@ -208,6 +208,15 @@ public class Explosion { - - if (f > 0.0F && this.damageCalculator.shouldBlockExplode(this, this.level, blockposition, iblockdata, f)) { - set.add(blockposition); -+ // Paper start - prevent headless pistons from forming -+ if (!io.papermc.paper.configuration.GlobalConfiguration.get().unsupportedSettings.allowHeadlessPistons && iblockdata.getBlock() == Blocks.MOVING_PISTON) { -+ net.minecraft.world.level.block.entity.BlockEntity extension = this.level.getBlockEntity(blockposition); -+ if (extension instanceof net.minecraft.world.level.block.piston.PistonMovingBlockEntity blockEntity && blockEntity.isSourcePiston()) { -+ net.minecraft.core.Direction direction = iblockdata.getValue(net.minecraft.world.level.block.piston.PistonHeadBlock.FACING); -+ set.add(blockposition.relative(direction.getOpposite())); -+ } -+ } -+ // Paper end - prevent headless pistons from forming - } - - d4 += d0 * 0.30000001192092896D; diff --git a/patches/server/0399-Add-BellRingEvent.patch b/patches/server/0399-Add-BellRingEvent.patch deleted file mode 100644 index 6a7c834c6a71..000000000000 --- a/patches/server/0399-Add-BellRingEvent.patch +++ /dev/null @@ -1,25 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Eearslya Sleiarion -Date: Sun, 23 Aug 2020 13:04:02 +0200 -Subject: [PATCH] Add BellRingEvent - -Add a new event, BellRingEvent, to trigger whenever a player rings a -village bell. Passes along the bell block and the player who rang it. - -diff --git a/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java b/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java -index f633d08b35bf9018367a449cc000c6c6ca5a44cd..dbe7931dd7eb43f9381463d3a57ba1eb53820985 100644 ---- a/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java -+++ b/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java -@@ -378,10 +378,11 @@ public class CraftEventFactory { - return tradeSelectEvent; - } - -+ @SuppressWarnings("deprecation") // Paper use deprecated event to maintain compat (it extends modern event) - public static boolean handleBellRingEvent(Level world, BlockPos position, Direction direction, Entity entity) { - Block block = CraftBlock.at(world, position); - BlockFace bukkitDirection = CraftBlock.notchToBlockFace(direction); -- BellRingEvent event = new BellRingEvent(block, bukkitDirection, (entity != null) ? entity.getBukkitEntity() : null); -+ BellRingEvent event = new io.papermc.paper.event.block.BellRingEvent(block, bukkitDirection, (entity != null) ? entity.getBukkitEntity() : null); // Paper - deprecated BellRingEvent - Bukkit.getPluginManager().callEvent(event); - return !event.isCancelled(); - } diff --git a/patches/server/0399-Fix-hex-colors-not-working-in-some-kick-messages.patch b/patches/server/0399-Fix-hex-colors-not-working-in-some-kick-messages.patch new file mode 100644 index 000000000000..1cafcc355b50 --- /dev/null +++ b/patches/server/0399-Fix-hex-colors-not-working-in-some-kick-messages.patch @@ -0,0 +1,43 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: JRoy +Date: Thu, 27 Aug 2020 16:57:25 -0400 +Subject: [PATCH] Fix hex colors not working in some kick messages + + +diff --git a/src/main/java/net/minecraft/server/network/ServerHandshakePacketListenerImpl.java b/src/main/java/net/minecraft/server/network/ServerHandshakePacketListenerImpl.java +index 946b423d2184f903dc29c923d7dbe05aaa469c09..0c1bdf2329936ce479a2cc53e8a46bd2ad685ec1 100644 +--- a/src/main/java/net/minecraft/server/network/ServerHandshakePacketListenerImpl.java ++++ b/src/main/java/net/minecraft/server/network/ServerHandshakePacketListenerImpl.java +@@ -113,14 +113,16 @@ public class ServerHandshakePacketListenerImpl implements ServerHandshakePacketL + } + // CraftBukkit end + if (packet.protocolVersion() != SharedConstants.getCurrentVersion().getProtocolVersion()) { +- MutableComponent ichatmutablecomponent; ++ net.kyori.adventure.text.Component adventureComponent; // Paper - Fix hex colors not working in some kick messages + + if (packet.protocolVersion() < SharedConstants.getCurrentVersion().getProtocolVersion()) { // Spigot - SPIGOT-7546: Handle version check correctly for outdated client message +- ichatmutablecomponent = Component.literal( java.text.MessageFormat.format( org.spigotmc.SpigotConfig.outdatedClientMessage.replaceAll("'", "''"), SharedConstants.getCurrentVersion().getName() ) ); // Spigot ++ adventureComponent = net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer.legacySection().deserialize(java.text.MessageFormat.format(org.spigotmc.SpigotConfig.outdatedClientMessage.replaceAll("'", "''"), SharedConstants.getCurrentVersion().getName())); // Spigot // Paper - Fix hex colors not working in some kick messages + } else { +- ichatmutablecomponent = Component.literal( java.text.MessageFormat.format( org.spigotmc.SpigotConfig.outdatedServerMessage.replaceAll("'", "''"), SharedConstants.getCurrentVersion().getName() ) ); // Spigot ++ adventureComponent = net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer.legacySection().deserialize(java.text.MessageFormat.format(org.spigotmc.SpigotConfig.outdatedServerMessage.replaceAll("'", "''"), SharedConstants.getCurrentVersion().getName())); // Spigot // Paper - Fix hex colors not working in some kick messages + } + ++ Component ichatmutablecomponent = io.papermc.paper.adventure.PaperAdventure.asVanilla(adventureComponent); // Paper - Fix hex colors not working in some kick messages ++ + this.connection.send(new ClientboundLoginDisconnectPacket(ichatmutablecomponent)); + this.connection.disconnect((Component) ichatmutablecomponent); + } else { +diff --git a/src/main/java/net/minecraft/server/network/ServerLoginPacketListenerImpl.java b/src/main/java/net/minecraft/server/network/ServerLoginPacketListenerImpl.java +index fd9e6781a18d41ca3982788711749d11575566d0..9d5723cdfdbf6257a71e57842aea9ba317fc049a 100644 +--- a/src/main/java/net/minecraft/server/network/ServerLoginPacketListenerImpl.java ++++ b/src/main/java/net/minecraft/server/network/ServerLoginPacketListenerImpl.java +@@ -133,7 +133,7 @@ public class ServerLoginPacketListenerImpl implements ServerLoginPacketListener, + // CraftBukkit start + @Deprecated + public void disconnect(String s) { +- this.disconnect(Component.literal(s)); ++ this.disconnect(io.papermc.paper.adventure.PaperAdventure.asVanilla(net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer.legacySection().deserialize(s))); // Paper - Fix hex colors not working in some kick messages + } + // CraftBukkit end + diff --git a/patches/server/0403-Add-more-Evoker-API.patch b/patches/server/0400-Add-more-Evoker-API.patch similarity index 100% rename from patches/server/0403-Add-more-Evoker-API.patch rename to patches/server/0400-Add-more-Evoker-API.patch diff --git a/patches/server/0400-Add-zombie-targets-turtle-egg-config.patch b/patches/server/0400-Add-zombie-targets-turtle-egg-config.patch deleted file mode 100644 index 56529e1fd417..000000000000 --- a/patches/server/0400-Add-zombie-targets-turtle-egg-config.patch +++ /dev/null @@ -1,19 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: BillyGalbreath -Date: Sun, 23 Aug 2020 15:47:34 +0200 -Subject: [PATCH] Add zombie targets turtle egg config - - -diff --git a/src/main/java/net/minecraft/world/entity/monster/Zombie.java b/src/main/java/net/minecraft/world/entity/monster/Zombie.java -index e2a3978899497b6622829d6577cfaa723092da9d..78254df3e5fbcb0a90c2f9eb9c9343792238f685 100644 ---- a/src/main/java/net/minecraft/world/entity/monster/Zombie.java -+++ b/src/main/java/net/minecraft/world/entity/monster/Zombie.java -@@ -110,7 +110,7 @@ public class Zombie extends Monster { - - @Override - protected void registerGoals() { -- this.goalSelector.addGoal(4, new Zombie.ZombieAttackTurtleEggGoal(this, 1.0D, 3)); -+ if (this.level().paperConfig().entities.behavior.zombiesTargetTurtleEggs) this.goalSelector.addGoal(4, new Zombie.ZombieAttackTurtleEggGoal(this, 1.0D, 3)); // Paper - Add zombie targets turtle egg config - this.goalSelector.addGoal(8, new LookAtPlayerGoal(this, Player.class, 8.0F)); - this.goalSelector.addGoal(8, new RandomLookAroundGoal(this)); - this.addBehaviourGoals(); diff --git a/patches/server/0401-Add-methods-to-get-translation-keys.patch b/patches/server/0401-Add-methods-to-get-translation-keys.patch new file mode 100644 index 000000000000..8ff2f02eb993 --- /dev/null +++ b/patches/server/0401-Add-methods-to-get-translation-keys.patch @@ -0,0 +1,202 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Jake Potrebic +Date: Tue, 11 Aug 2020 19:16:09 +0200 +Subject: [PATCH] Add methods to get translation keys + +== AT == +public org.bukkit.craftbukkit.inventory.CraftMetaFirework +public org.bukkit.craftbukkit.inventory.CraftMetaFirework power +public org.bukkit.craftbukkit.inventory.CraftMetaFirework getNBT(Lorg/bukkit/FireworkEffect$Type;)Lnet/minecraft/world/item/component/FireworkExplosion$Shape; + +Co-authored-by: MeFisto94 + +diff --git a/src/main/java/org/bukkit/craftbukkit/block/CraftBlock.java b/src/main/java/org/bukkit/craftbukkit/block/CraftBlock.java +index aae00320ab8003420bae5de7df47f553b62c5aab..3fa3de9a89550ec2fcb8ca663742826c0c3136b6 100644 +--- a/src/main/java/org/bukkit/craftbukkit/block/CraftBlock.java ++++ b/src/main/java/org/bukkit/craftbukkit/block/CraftBlock.java +@@ -669,5 +669,10 @@ public class CraftBlock implements Block { + public org.bukkit.SoundGroup getBlockSoundGroup() { + return org.bukkit.craftbukkit.CraftSoundGroup.getSoundGroup(this.getNMS().getSoundType()); + } ++ ++ @Override ++ public String translationKey() { ++ return this.getNMS().getBlock().getDescriptionId(); ++ } + // Paper end + } +diff --git a/src/main/java/org/bukkit/craftbukkit/block/CraftBlockType.java b/src/main/java/org/bukkit/craftbukkit/block/CraftBlockType.java +index 338a8f4acf413ef24fedab60c19c7a51a0ea19a6..2d8a509446c0ed0d7358f10f67ef29c4df683696 100644 +--- a/src/main/java/org/bukkit/craftbukkit/block/CraftBlockType.java ++++ b/src/main/java/org/bukkit/craftbukkit/block/CraftBlockType.java +@@ -234,4 +234,11 @@ public class CraftBlockType implements BlockType.Typed, + public Material asMaterial() { + return Registry.MATERIAL.get(this.key); + } ++ ++ // Paper start - add Translatable ++ @Override ++ public String translationKey() { ++ return this.block.getDescriptionId(); ++ } ++ // Paper end - add Translatable + } +diff --git a/src/main/java/org/bukkit/craftbukkit/enchantments/CraftEnchantment.java b/src/main/java/org/bukkit/craftbukkit/enchantments/CraftEnchantment.java +index f73017bff613bd62b86c974b29576e241c24c927..59c9c970b83f62245d860994c4ac0c21dcc15398 100644 +--- a/src/main/java/org/bukkit/craftbukkit/enchantments/CraftEnchantment.java ++++ b/src/main/java/org/bukkit/craftbukkit/enchantments/CraftEnchantment.java +@@ -152,6 +152,17 @@ public class CraftEnchantment extends Enchantment implements Handleable implements ItemType.Typed, Han + public Material asMaterial() { + return Registry.MATERIAL.get(this.key); + } ++ ++ // Paper start - add Translatable ++ @Override ++ public String translationKey() { ++ return this.item.getDescriptionId(); ++ } ++ // Paper end - add Translatable + } +diff --git a/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaFirework.java b/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaFirework.java +index 4921fc085c9d60c74028ef390325e26c598e8df1..4941e0afff8df5f10f06c715b54bf58eb86051c5 100644 +--- a/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaFirework.java ++++ b/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaFirework.java +@@ -123,7 +123,7 @@ class CraftMetaFirework extends CraftMetaItem implements FireworkMeta { + return new FireworkExplosion(CraftMetaFirework.getNBT(effect.getType()), colors, fadeColors, effect.hasTrail(), effect.hasFlicker()); + } + +- static FireworkExplosion.Shape getNBT(Type type) { ++ public static FireworkExplosion.Shape getNBT(Type type) { // Paper - package-private -> public + switch (type) { + case BALL: + return FireworkExplosion.Shape.SMALL_BALL; +diff --git a/src/test/java/io/papermc/paper/world/TranslationKeyTest.java b/src/test/java/io/papermc/paper/world/TranslationKeyTest.java +index 7f8b6462d2a1bbd39a870d2543bebc135f7eb45b..e8d5c3eb8ad98b2082bdd33d96bfb1d7124ea186 100644 +--- a/src/test/java/io/papermc/paper/world/TranslationKeyTest.java ++++ b/src/test/java/io/papermc/paper/world/TranslationKeyTest.java +@@ -1,11 +1,32 @@ + package io.papermc.paper.world; + + import com.destroystokyo.paper.ClientOption; ++import java.util.Locale; ++import java.util.Map; ++import net.minecraft.core.registries.Registries; ++import net.minecraft.network.chat.contents.TranslatableContents; ++import net.minecraft.resources.ResourceKey; ++import net.minecraft.resources.ResourceLocation; + import net.minecraft.world.entity.player.ChatVisiblity; ++import net.minecraft.world.flag.FeatureFlags; ++import net.minecraft.world.level.GameRules; ++import net.minecraft.world.level.GameType; ++import net.minecraft.world.level.biome.Biome; + import org.bukkit.Difficulty; ++import org.bukkit.FireworkEffect; ++import org.bukkit.GameMode; ++import org.bukkit.GameRule; ++import org.bukkit.MusicInstrument; ++import org.bukkit.attribute.Attribute; ++import org.bukkit.craftbukkit.CraftWorld; ++import org.bukkit.craftbukkit.util.CraftNamespacedKey; ++import org.bukkit.support.RegistryHelper; ++import org.bukkit.support.environment.AllFeatures; + import org.junit.jupiter.api.Assertions; ++import org.junit.jupiter.api.Disabled; + import org.junit.jupiter.api.Test; + ++@AllFeatures + public class TranslationKeyTest { + + @Test +@@ -15,4 +36,70 @@ public class TranslationKeyTest { + Assertions.assertEquals(ChatVisiblity.valueOf(chatVisibility.name()).getKey(), chatVisibility.translationKey(), chatVisibility + "'s translation key doesn't match"); + } + } ++ ++ @Test ++ public void testDifficultyKeys() { ++ for (Difficulty bukkitDifficulty : Difficulty.values()) { ++ Assertions.assertEquals(((TranslatableContents) net.minecraft.world.Difficulty.byId(bukkitDifficulty.ordinal()).getDisplayName().getContents()).getKey(), bukkitDifficulty.translationKey(), bukkitDifficulty + "'s translation key doesn't match"); ++ } ++ } ++ ++ @Test ++ public void testGameruleKeys() { ++ final Map> gameRules = CraftWorld.getGameRulesNMS(new GameRules(FeatureFlags.REGISTRY.allFlags())); ++ for (GameRule rule : GameRule.values()) { ++ Assertions.assertEquals(gameRules.get(rule.getName()).getDescriptionId(), rule.translationKey(), rule.getName() + "'s translation doesn't match"); ++ } ++ } ++ ++ @Test ++ public void testAttributeKeys() { ++ for (Attribute attribute : Attribute.values()) { ++ Assertions.assertEquals(org.bukkit.craftbukkit.attribute.CraftAttribute.bukkitToMinecraft(attribute).getDescriptionId(), attribute.translationKey(), "translation key mismatch for " + attribute); ++ } ++ } ++ ++ @Test ++ public void testFireworkEffectType() { ++ for (final FireworkEffect.Type type : FireworkEffect.Type.values()) { ++ final net.minecraft.world.item.component.FireworkExplosion.Shape nmsType = org.bukkit.craftbukkit.inventory.CraftMetaFirework.getNBT(type); ++ Assertions.assertTrue(nmsType.getName().getContents() instanceof TranslatableContents, "contents aren't translatable"); ++ Assertions.assertEquals(((TranslatableContents) nmsType.getName().getContents()).getKey(), type.translationKey(), "translation key mismatch for " + type); ++ } ++ } ++ ++ @Test ++ @Disabled // TODO fix ++ public void testCreativeCategory() { ++ // for (CreativeModeTab tab : CreativeModeTabs.tabs()) { ++ // CreativeCategory category = Objects.requireNonNull(CraftCreativeCategory.fromNMS(tab)); ++ // Assertions.assertEquals("translation key mismatch for " + category, ((TranslatableContents) tab.getDisplayName().getContents()).getKey(), category.translationKey()); ++ // } ++ } ++ ++ @Test ++ public void testGameMode() { ++ for (GameType nms : GameType.values()) { ++ GameMode bukkit = GameMode.getByValue(nms.getId()); ++ Assertions.assertNotNull(bukkit); ++ Assertions.assertEquals(((TranslatableContents) nms.getLongDisplayName().getContents()).getKey(), bukkit.translationKey(), "translation key mismatch for " + bukkit); ++ } ++ } ++ ++ @Test ++ public void testBiome() { ++ for (Map.Entry, Biome> nms : RegistryHelper.getBiomes().entrySet()) { ++ org.bukkit.block.Biome bukkit = org.bukkit.block.Biome.valueOf(nms.getKey().location().getPath().toUpperCase(Locale.ROOT)); ++ Assertions.assertEquals(nms.getKey().location().toLanguageKey("biome"), bukkit.translationKey(), "translation key mismatch for " + bukkit); ++ } ++ } ++ ++ @Test ++ public void testMusicInstrument() { ++ for (final ResourceLocation nms : RegistryHelper.getRegistry().lookupOrThrow(Registries.INSTRUMENT).keySet()) { ++ final MusicInstrument bukkit = MusicInstrument.getByKey(CraftNamespacedKey.fromMinecraft(nms)); ++ Assertions.assertNotNull(bukkit, "Missing bukkit instrument for " + nms); ++ Assertions.assertEquals(nms.toLanguageKey("instrument"), bukkit.translationKey(), "translation key mismatch for " + bukkit); ++ } ++ } + } diff --git a/patches/server/0402-Create-HoverEvent-from-ItemStack-Entity.patch b/patches/server/0402-Create-HoverEvent-from-ItemStack-Entity.patch new file mode 100644 index 000000000000..66d23bfd33c1 --- /dev/null +++ b/patches/server/0402-Create-HoverEvent-from-ItemStack-Entity.patch @@ -0,0 +1,55 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: ysl3000 +Date: Mon, 6 Jul 2020 22:18:04 +0200 +Subject: [PATCH] Create HoverEvent from ItemStack Entity + + +diff --git a/src/main/java/org/bukkit/craftbukkit/inventory/CraftItemFactory.java b/src/main/java/org/bukkit/craftbukkit/inventory/CraftItemFactory.java +index b407968b111ff9cb9f428319d211e5d9c4c99138..cab7a3d21699605cb7fc480830d7529f70e69e88 100644 +--- a/src/main/java/org/bukkit/craftbukkit/inventory/CraftItemFactory.java ++++ b/src/main/java/org/bukkit/craftbukkit/inventory/CraftItemFactory.java +@@ -249,4 +249,44 @@ public final class CraftItemFactory implements ItemFactory { + return nms != null ? nms.getItem().getName(nms).getString() : null; + } + // Paper end - add getI18NDisplayName ++ ++ // Paper start - bungee hover events ++ @Override ++ public net.md_5.bungee.api.chat.hover.content.Content hoverContentOf(ItemStack itemStack) { ++ throw new UnsupportedOperationException("BungeeCord Chat API does not support data components"); ++ /* ++ net.md_5.bungee.api.chat.ItemTag itemTag = net.md_5.bungee.api.chat.ItemTag.ofNbt(CraftItemStack.asNMSCopy(itemStack).getOrCreateTag().toString()); ++ return new net.md_5.bungee.api.chat.hover.content.Item( ++ itemStack.getType().getKey().toString(), ++ itemStack.getAmount(), ++ itemTag); ++ */ ++ } ++ ++ @Override ++ public net.md_5.bungee.api.chat.hover.content.Content hoverContentOf(org.bukkit.entity.Entity entity) { ++ return hoverContentOf(entity, org.apache.commons.lang3.StringUtils.isBlank(entity.getCustomName()) ? null : new net.md_5.bungee.api.chat.TextComponent(entity.getCustomName())); ++ } ++ ++ @Override ++ public net.md_5.bungee.api.chat.hover.content.Content hoverContentOf(org.bukkit.entity.Entity entity, String customName) { ++ return hoverContentOf(entity, org.apache.commons.lang3.StringUtils.isBlank(customName) ? null : new net.md_5.bungee.api.chat.TextComponent(customName)); ++ } ++ ++ @Override ++ public net.md_5.bungee.api.chat.hover.content.Content hoverContentOf(org.bukkit.entity.Entity entity, net.md_5.bungee.api.chat.BaseComponent customName) { ++ return new net.md_5.bungee.api.chat.hover.content.Entity( ++ entity.getType().getKey().toString(), ++ entity.getUniqueId().toString(), ++ customName); ++ } ++ ++ @Override ++ public net.md_5.bungee.api.chat.hover.content.Content hoverContentOf(org.bukkit.entity.Entity entity, net.md_5.bungee.api.chat.BaseComponent[] customName) { ++ return new net.md_5.bungee.api.chat.hover.content.Entity( ++ entity.getType().getKey().toString(), ++ entity.getUniqueId().toString(), ++ new net.md_5.bungee.api.chat.TextComponent(customName)); ++ } ++ // Paper end - bungee hover events + } diff --git a/patches/server/0402-Fix-hex-colors-not-working-in-some-kick-messages.patch b/patches/server/0402-Fix-hex-colors-not-working-in-some-kick-messages.patch deleted file mode 100644 index 6124f3252909..000000000000 --- a/patches/server/0402-Fix-hex-colors-not-working-in-some-kick-messages.patch +++ /dev/null @@ -1,43 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: JRoy -Date: Thu, 27 Aug 2020 16:57:25 -0400 -Subject: [PATCH] Fix hex colors not working in some kick messages - - -diff --git a/src/main/java/net/minecraft/server/network/ServerHandshakePacketListenerImpl.java b/src/main/java/net/minecraft/server/network/ServerHandshakePacketListenerImpl.java -index 946b423d2184f903dc29c923d7dbe05aaa469c09..0c1bdf2329936ce479a2cc53e8a46bd2ad685ec1 100644 ---- a/src/main/java/net/minecraft/server/network/ServerHandshakePacketListenerImpl.java -+++ b/src/main/java/net/minecraft/server/network/ServerHandshakePacketListenerImpl.java -@@ -113,14 +113,16 @@ public class ServerHandshakePacketListenerImpl implements ServerHandshakePacketL - } - // CraftBukkit end - if (packet.protocolVersion() != SharedConstants.getCurrentVersion().getProtocolVersion()) { -- MutableComponent ichatmutablecomponent; -+ net.kyori.adventure.text.Component adventureComponent; // Paper - Fix hex colors not working in some kick messages - - if (packet.protocolVersion() < SharedConstants.getCurrentVersion().getProtocolVersion()) { // Spigot - SPIGOT-7546: Handle version check correctly for outdated client message -- ichatmutablecomponent = Component.literal( java.text.MessageFormat.format( org.spigotmc.SpigotConfig.outdatedClientMessage.replaceAll("'", "''"), SharedConstants.getCurrentVersion().getName() ) ); // Spigot -+ adventureComponent = net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer.legacySection().deserialize(java.text.MessageFormat.format(org.spigotmc.SpigotConfig.outdatedClientMessage.replaceAll("'", "''"), SharedConstants.getCurrentVersion().getName())); // Spigot // Paper - Fix hex colors not working in some kick messages - } else { -- ichatmutablecomponent = Component.literal( java.text.MessageFormat.format( org.spigotmc.SpigotConfig.outdatedServerMessage.replaceAll("'", "''"), SharedConstants.getCurrentVersion().getName() ) ); // Spigot -+ adventureComponent = net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer.legacySection().deserialize(java.text.MessageFormat.format(org.spigotmc.SpigotConfig.outdatedServerMessage.replaceAll("'", "''"), SharedConstants.getCurrentVersion().getName())); // Spigot // Paper - Fix hex colors not working in some kick messages - } - -+ Component ichatmutablecomponent = io.papermc.paper.adventure.PaperAdventure.asVanilla(adventureComponent); // Paper - Fix hex colors not working in some kick messages -+ - this.connection.send(new ClientboundLoginDisconnectPacket(ichatmutablecomponent)); - this.connection.disconnect((Component) ichatmutablecomponent); - } else { -diff --git a/src/main/java/net/minecraft/server/network/ServerLoginPacketListenerImpl.java b/src/main/java/net/minecraft/server/network/ServerLoginPacketListenerImpl.java -index de25b9ea8aa4b7d6fd3fed12cdd16be9ddfcbfff..d2d153e587e624025ef01fbe3dcfa4bf06f1a06b 100644 ---- a/src/main/java/net/minecraft/server/network/ServerLoginPacketListenerImpl.java -+++ b/src/main/java/net/minecraft/server/network/ServerLoginPacketListenerImpl.java -@@ -133,7 +133,7 @@ public class ServerLoginPacketListenerImpl implements ServerLoginPacketListener, - // CraftBukkit start - @Deprecated - public void disconnect(String s) { -- this.disconnect(Component.literal(s)); -+ this.disconnect(io.papermc.paper.adventure.PaperAdventure.asVanilla(net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer.legacySection().deserialize(s))); // Paper - Fix hex colors not working in some kick messages - } - // CraftBukkit end - diff --git a/patches/server/0403-Cache-block-data-strings.patch b/patches/server/0403-Cache-block-data-strings.patch new file mode 100644 index 000000000000..4c26cbbfcd70 --- /dev/null +++ b/patches/server/0403-Cache-block-data-strings.patch @@ -0,0 +1,70 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: miclebrick +Date: Thu, 6 Dec 2018 19:52:50 -0500 +Subject: [PATCH] Cache block data strings + + +diff --git a/src/main/java/net/minecraft/server/MinecraftServer.java b/src/main/java/net/minecraft/server/MinecraftServer.java +index fce70aa2b0d92c6291720b75a07a6472eb55855b..b9acf51e30839447cb7fc48e202321ed4bb2b7ea 100644 +--- a/src/main/java/net/minecraft/server/MinecraftServer.java ++++ b/src/main/java/net/minecraft/server/MinecraftServer.java +@@ -2184,6 +2184,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop>, Enum[]> ENUM_VALUES = new HashMap<>(); ++ private static final Map>, Enum[]> ENUM_VALUES = new java.util.concurrent.ConcurrentHashMap<>(); // Paper - cache block data strings; make thread safe + + /** + * Convert an NMS Enum (usually a BlockStateEnum) to its appropriate Bukkit +@@ -543,7 +543,38 @@ public class CraftBlockData implements BlockData { + Preconditions.checkState(CraftBlockData.MAP.put(nms, bukkit) == null, "Duplicate mapping %s->%s", nms, bukkit); + } + ++ // Paper start - cache block data strings ++ private static Map stringDataCache = new java.util.concurrent.ConcurrentHashMap<>(); ++ ++ static { ++ // cache all of the default states at startup, will not cache ones with the custom states inside of the ++ // brackets in a different order, though ++ reloadCache(); ++ } ++ ++ public static void reloadCache() { ++ stringDataCache.clear(); ++ Block.BLOCK_STATE_REGISTRY.forEach(blockData -> stringDataCache.put(blockData.toString(), blockData.createCraftBlockData())); ++ } ++ // Paper end - cache block data strings ++ + public static CraftBlockData newData(BlockType blockType, String data) { ++ ++ // Paper start - cache block data strings ++ if (blockType != null) { ++ Block block = CraftBlockType.bukkitToMinecraftNew(blockType); ++ if (block != null) { ++ net.minecraft.resources.ResourceLocation key = BuiltInRegistries.BLOCK.getKey(block); ++ data = data == null ? key.toString() : key + data; ++ } ++ } ++ ++ CraftBlockData cached = stringDataCache.computeIfAbsent(data, s -> createNewData(null, s)); ++ return (CraftBlockData) cached.clone(); ++ } ++ ++ private static CraftBlockData createNewData(BlockType blockType, String data) { ++ // Paper end - cache block data strings + net.minecraft.world.level.block.state.BlockState blockData; + Block block = blockType == null ? null : ((CraftBlockType) blockType).getHandle(); + Map, Comparable> parsed = null; diff --git a/patches/server/0404-Add-methods-to-get-translation-keys.patch b/patches/server/0404-Add-methods-to-get-translation-keys.patch deleted file mode 100644 index 00eaa98c22c9..000000000000 --- a/patches/server/0404-Add-methods-to-get-translation-keys.patch +++ /dev/null @@ -1,198 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Jake Potrebic -Date: Tue, 11 Aug 2020 19:16:09 +0200 -Subject: [PATCH] Add methods to get translation keys - -== AT == -public org.bukkit.craftbukkit.inventory.CraftMetaFirework -public org.bukkit.craftbukkit.inventory.CraftMetaFirework power -public org.bukkit.craftbukkit.inventory.CraftMetaFirework getNBT(Lorg/bukkit/FireworkEffect$Type;)Lnet/minecraft/world/item/component/FireworkExplosion$Shape; - -Co-authored-by: MeFisto94 - -diff --git a/src/main/java/org/bukkit/craftbukkit/block/CraftBlock.java b/src/main/java/org/bukkit/craftbukkit/block/CraftBlock.java -index f041b5d80bff9c022b007e04ef1558e9116acc6b..a586442422a2b2c06b785af0d261d3e19eb1d59b 100644 ---- a/src/main/java/org/bukkit/craftbukkit/block/CraftBlock.java -+++ b/src/main/java/org/bukkit/craftbukkit/block/CraftBlock.java -@@ -669,5 +669,10 @@ public class CraftBlock implements Block { - public org.bukkit.SoundGroup getBlockSoundGroup() { - return org.bukkit.craftbukkit.CraftSoundGroup.getSoundGroup(this.getNMS().getSoundType()); - } -+ -+ @Override -+ public String translationKey() { -+ return this.getNMS().getBlock().getDescriptionId(); -+ } - // Paper end - } -diff --git a/src/main/java/org/bukkit/craftbukkit/block/CraftBlockType.java b/src/main/java/org/bukkit/craftbukkit/block/CraftBlockType.java -index 338a8f4acf413ef24fedab60c19c7a51a0ea19a6..2d8a509446c0ed0d7358f10f67ef29c4df683696 100644 ---- a/src/main/java/org/bukkit/craftbukkit/block/CraftBlockType.java -+++ b/src/main/java/org/bukkit/craftbukkit/block/CraftBlockType.java -@@ -234,4 +234,11 @@ public class CraftBlockType implements BlockType.Typed, - public Material asMaterial() { - return Registry.MATERIAL.get(this.key); - } -+ -+ // Paper start - add Translatable -+ @Override -+ public String translationKey() { -+ return this.block.getDescriptionId(); -+ } -+ // Paper end - add Translatable - } -diff --git a/src/main/java/org/bukkit/craftbukkit/enchantments/CraftEnchantment.java b/src/main/java/org/bukkit/craftbukkit/enchantments/CraftEnchantment.java -index f73017bff613bd62b86c974b29576e241c24c927..59c9c970b83f62245d860994c4ac0c21dcc15398 100644 ---- a/src/main/java/org/bukkit/craftbukkit/enchantments/CraftEnchantment.java -+++ b/src/main/java/org/bukkit/craftbukkit/enchantments/CraftEnchantment.java -@@ -152,6 +152,17 @@ public class CraftEnchantment extends Enchantment implements Handleable implements ItemType.Typed, Han - public Material asMaterial() { - return Registry.MATERIAL.get(this.key); - } -+ -+ // Paper start - add Translatable -+ @Override -+ public String translationKey() { -+ return this.item.getDescriptionId(); -+ } -+ // Paper end - add Translatable - } -diff --git a/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaFirework.java b/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaFirework.java -index 4921fc085c9d60c74028ef390325e26c598e8df1..4941e0afff8df5f10f06c715b54bf58eb86051c5 100644 ---- a/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaFirework.java -+++ b/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaFirework.java -@@ -123,7 +123,7 @@ class CraftMetaFirework extends CraftMetaItem implements FireworkMeta { - return new FireworkExplosion(CraftMetaFirework.getNBT(effect.getType()), colors, fadeColors, effect.hasTrail(), effect.hasFlicker()); - } - -- static FireworkExplosion.Shape getNBT(Type type) { -+ public static FireworkExplosion.Shape getNBT(Type type) { // Paper - package-private -> public - switch (type) { - case BALL: - return FireworkExplosion.Shape.SMALL_BALL; -diff --git a/src/test/java/io/papermc/paper/world/TranslationKeyTest.java b/src/test/java/io/papermc/paper/world/TranslationKeyTest.java -index 7f8b6462d2a1bbd39a870d2543bebc135f7eb45b..4001c73e833ebf17baa22463dd197cee8ad67266 100644 ---- a/src/test/java/io/papermc/paper/world/TranslationKeyTest.java -+++ b/src/test/java/io/papermc/paper/world/TranslationKeyTest.java -@@ -1,11 +1,29 @@ - package io.papermc.paper.world; - - import com.destroystokyo.paper.ClientOption; -+import java.util.Locale; -+import java.util.Map; -+import net.minecraft.core.registries.BuiltInRegistries; -+import net.minecraft.network.chat.contents.TranslatableContents; -+import net.minecraft.resources.ResourceKey; -+import net.minecraft.resources.ResourceLocation; - import net.minecraft.world.entity.player.ChatVisiblity; -+import net.minecraft.world.level.GameType; -+import net.minecraft.world.level.biome.Biome; - import org.bukkit.Difficulty; -+import org.bukkit.FireworkEffect; -+import org.bukkit.GameMode; -+import org.bukkit.GameRule; -+import org.bukkit.MusicInstrument; -+import org.bukkit.attribute.Attribute; -+import org.bukkit.craftbukkit.util.CraftNamespacedKey; -+import org.bukkit.support.RegistryHelper; -+import org.bukkit.support.environment.AllFeatures; - import org.junit.jupiter.api.Assertions; -+import org.junit.jupiter.api.Disabled; - import org.junit.jupiter.api.Test; - -+@AllFeatures - public class TranslationKeyTest { - - @Test -@@ -15,4 +33,69 @@ public class TranslationKeyTest { - Assertions.assertEquals(ChatVisiblity.valueOf(chatVisibility.name()).getKey(), chatVisibility.translationKey(), chatVisibility + "'s translation key doesn't match"); - } - } -+ -+ @Test -+ public void testDifficultyKeys() { -+ for (Difficulty bukkitDifficulty : Difficulty.values()) { -+ Assertions.assertEquals(((TranslatableContents) net.minecraft.world.Difficulty.byId(bukkitDifficulty.ordinal()).getDisplayName().getContents()).getKey(), bukkitDifficulty.translationKey(), bukkitDifficulty + "'s translation key doesn't match"); -+ } -+ } -+ -+ @Test -+ public void testGameruleKeys() { -+ for (GameRule rule : GameRule.values()) { -+ Assertions.assertEquals(org.bukkit.craftbukkit.CraftWorld.getGameRulesNMS().get(rule.getName()).getDescriptionId(), rule.translationKey(), rule.getName() + "'s translation doesn't match"); -+ } -+ } -+ -+ @Test -+ public void testAttributeKeys() { -+ for (Attribute attribute : Attribute.values()) { -+ Assertions.assertEquals(org.bukkit.craftbukkit.attribute.CraftAttribute.bukkitToMinecraft(attribute).getDescriptionId(), attribute.translationKey(), "translation key mismatch for " + attribute); -+ } -+ } -+ -+ @Test -+ public void testFireworkEffectType() { -+ for (final FireworkEffect.Type type : FireworkEffect.Type.values()) { -+ final net.minecraft.world.item.component.FireworkExplosion.Shape nmsType = org.bukkit.craftbukkit.inventory.CraftMetaFirework.getNBT(type); -+ Assertions.assertTrue(nmsType.getName().getContents() instanceof TranslatableContents, "contents aren't translatable"); -+ Assertions.assertEquals(((TranslatableContents) nmsType.getName().getContents()).getKey(), type.translationKey(), "translation key mismatch for " + type); -+ } -+ } -+ -+ @Test -+ @Disabled // TODO fix -+ public void testCreativeCategory() { -+ // for (CreativeModeTab tab : CreativeModeTabs.tabs()) { -+ // CreativeCategory category = Objects.requireNonNull(CraftCreativeCategory.fromNMS(tab)); -+ // Assertions.assertEquals("translation key mismatch for " + category, ((TranslatableContents) tab.getDisplayName().getContents()).getKey(), category.translationKey()); -+ // } -+ } -+ -+ @Test -+ public void testGameMode() { -+ for (GameType nms : GameType.values()) { -+ GameMode bukkit = GameMode.getByValue(nms.getId()); -+ Assertions.assertNotNull(bukkit); -+ Assertions.assertEquals(((TranslatableContents) nms.getLongDisplayName().getContents()).getKey(), bukkit.translationKey(), "translation key mismatch for " + bukkit); -+ } -+ } -+ -+ @Test -+ public void testBiome() { -+ for (Map.Entry, Biome> nms : RegistryHelper.getBiomes().entrySet()) { -+ org.bukkit.block.Biome bukkit = org.bukkit.block.Biome.valueOf(nms.getKey().location().getPath().toUpperCase(Locale.ROOT)); -+ Assertions.assertEquals(nms.getKey().location().toLanguageKey("biome"), bukkit.translationKey(), "translation key mismatch for " + bukkit); -+ } -+ } -+ -+ @Test -+ public void testMusicInstrument() { -+ for (final ResourceLocation nms : BuiltInRegistries.INSTRUMENT.keySet()) { -+ final MusicInstrument bukkit = MusicInstrument.getByKey(CraftNamespacedKey.fromMinecraft(nms)); -+ Assertions.assertNotNull(bukkit, "Missing bukkit instrument for " + nms); -+ Assertions.assertEquals(nms.toLanguageKey("instrument"), bukkit.translationKey(), "translation key mismatch for " + bukkit); -+ } -+ } - } diff --git a/patches/server/0404-Fix-Entity-Teleportation-and-cancel-velocity-if-tele.patch b/patches/server/0404-Fix-Entity-Teleportation-and-cancel-velocity-if-tele.patch new file mode 100644 index 000000000000..4545d0671c03 --- /dev/null +++ b/patches/server/0404-Fix-Entity-Teleportation-and-cancel-velocity-if-tele.patch @@ -0,0 +1,98 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Aikar +Date: Tue, 25 Aug 2020 20:45:36 -0400 +Subject: [PATCH] Fix Entity Teleportation and cancel velocity if teleported + +Uses correct setPositionRotation for Entity teleporting instead of setLocation +as this is how Vanilla teleports entities. + +Cancel any pending motion when teleported. + +diff --git a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java +index 00ce20359f6d73acf3f1016806e5f4f3b6d01bcd..1754e413dc1b7a18b3fc0b981eae11283b2106e5 100644 +--- a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java ++++ b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java +@@ -685,7 +685,7 @@ public class ServerGamePacketListenerImpl extends ServerCommonPacketListenerImpl + return; + } + +- this.player.absMoveTo(this.awaitingPositionFromClient.x, this.awaitingPositionFromClient.y, this.awaitingPositionFromClient.z, this.player.getYRot(), this.player.getXRot()); ++ this.player.moveTo(this.awaitingPositionFromClient.x, this.awaitingPositionFromClient.y, this.awaitingPositionFromClient.z, this.player.getYRot(), this.player.getXRot()); // Paper - Fix Entity Teleportation and cancel velocity if teleported + this.lastGoodX = this.awaitingPositionFromClient.x; + this.lastGoodY = this.awaitingPositionFromClient.y; + this.lastGoodZ = this.awaitingPositionFromClient.z; +diff --git a/src/main/java/net/minecraft/world/entity/Entity.java b/src/main/java/net/minecraft/world/entity/Entity.java +index 8993c16254ac05d509bd8ac1cd21e7cc7c4097b8..2c9e57436469f94beb45f656a1df71aba7b1e408 100644 +--- a/src/main/java/net/minecraft/world/entity/Entity.java ++++ b/src/main/java/net/minecraft/world/entity/Entity.java +@@ -179,6 +179,7 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess + + // CraftBukkit start + private static final int CURRENT_LEVEL = 2; ++ public boolean preserveMotion = true; // Paper - Fix Entity Teleportation and cancel velocity if teleported; keep initial motion on first setPositionRotation + static boolean isLevelAtLeast(CompoundTag tag, int level) { + return tag.contains("Bukkit.updateLevel") && tag.getInt("Bukkit.updateLevel") >= level; + } +@@ -1943,6 +1944,13 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess + } + + public void moveTo(double x, double y, double z, float yaw, float pitch) { ++ // Paper start - Fix Entity Teleportation and cancel velocity if teleported ++ if (!preserveMotion) { ++ this.deltaMovement = Vec3.ZERO; ++ } else { ++ this.preserveMotion = false; ++ } ++ // Paper end - Fix Entity Teleportation and cancel velocity if teleported + this.setPosRaw(x, y, z); + this.setYRot(yaw); + this.setXRot(pitch); +diff --git a/src/main/java/net/minecraft/world/entity/boss/enderdragon/phases/DragonStrafePlayerPhase.java b/src/main/java/net/minecraft/world/entity/boss/enderdragon/phases/DragonStrafePlayerPhase.java +index 2db75673e525708d04bda9f6c9af80e7f8d361e6..be2b5f56f6f29c82c4fa9df33287d0ddb589f383 100644 +--- a/src/main/java/net/minecraft/world/entity/boss/enderdragon/phases/DragonStrafePlayerPhase.java ++++ b/src/main/java/net/minecraft/world/entity/boss/enderdragon/phases/DragonStrafePlayerPhase.java +@@ -79,6 +79,7 @@ public class DragonStrafePlayerPhase extends AbstractDragonPhaseInstance { + } + + DragonFireball dragonFireball = new DragonFireball(world, this.dragon, vec34.normalize()); ++ dragonFireball.preserveMotion = true; // Paper - Fix Entity Teleportation and cancel velocity if teleported + dragonFireball.moveTo(o, p, q, 0.0F, 0.0F); + if (new com.destroystokyo.paper.event.entity.EnderDragonShootFireballEvent((org.bukkit.entity.EnderDragon) dragon.getBukkitEntity(), (org.bukkit.entity.DragonFireball) dragonFireball.getBukkitEntity()).callEvent()) // Paper - EnderDragon Events + world.addFreshEntity(dragonFireball); +diff --git a/src/main/java/net/minecraft/world/level/BaseSpawner.java b/src/main/java/net/minecraft/world/level/BaseSpawner.java +index 56dbe701a93eb9f1309bec92e5d3b310860a4dc5..1b6ec72f59504d2f420d6d5dcbed4d3b9115e3d8 100644 +--- a/src/main/java/net/minecraft/world/level/BaseSpawner.java ++++ b/src/main/java/net/minecraft/world/level/BaseSpawner.java +@@ -164,6 +164,7 @@ public abstract class BaseSpawner { + return; + } + ++ entity.preserveMotion = true; // Paper - Fix Entity Teleportation and cancel velocity if teleported; preserve entity motion from tag + entity.moveTo(entity.getX(), entity.getY(), entity.getZ(), randomsource.nextFloat() * 360.0F, 0.0F); + if (entity instanceof Mob) { + Mob entityinsentient = (Mob) entity; +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java +index b001284e368f64871824c1f961999bb3f726de57..abecb0235eee3e62a84f15f2ef100efcf3fcbf2a 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java +@@ -241,7 +241,7 @@ public abstract class CraftEntity implements org.bukkit.entity.Entity { + } + + // entity.setLocation() throws no event, and so cannot be cancelled +- this.entity.absMoveTo(location.getX(), location.getY(), location.getZ(), location.getYaw(), location.getPitch()); ++ entity.moveTo(location.getX(), location.getY(), location.getZ(), location.getYaw(), location.getPitch()); // Paper - use proper moveTo, as per vanilla teleporting + // SPIGOT-619: Force sync head rotation also + this.entity.setYHeadRot(location.getYaw()); + +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftLivingEntity.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftLivingEntity.java +index 5beee554567d36f2a3b871cf6ec3ecb3d2353592..db15382f35464de43a2fe0a41aca070a8c834777 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftLivingEntity.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftLivingEntity.java +@@ -603,6 +603,7 @@ public class CraftLivingEntity extends CraftEntity implements LivingEntity { + } + + ((AbstractHurtingProjectile) launch).projectileSource = this; ++ launch.preserveMotion = true; // Paper - Fix Entity Teleportation and cancel velocity if teleported + launch.moveTo(location.getX(), location.getY(), location.getZ(), location.getYaw(), location.getPitch()); + } else if (LlamaSpit.class.isAssignableFrom(projectile)) { + Location location = this.getEyeLocation(); diff --git a/patches/server/0405-Add-additional-open-container-api-to-HumanEntity.patch b/patches/server/0405-Add-additional-open-container-api-to-HumanEntity.patch new file mode 100644 index 000000000000..4f9cf8d9d9a1 --- /dev/null +++ b/patches/server/0405-Add-additional-open-container-api-to-HumanEntity.patch @@ -0,0 +1,83 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: JRoy +Date: Wed, 26 Aug 2020 02:12:31 -0400 +Subject: [PATCH] Add additional open container api to HumanEntity + +== AT == +public net/minecraft/world/level/block/state/BlockBehaviour getMenuProvider(Lnet/minecraft/world/level/block/state/BlockState;Lnet/minecraft/world/level/Level;Lnet/minecraft/core/BlockPos;)Lnet/minecraft/world/MenuProvider; + +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftHumanEntity.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftHumanEntity.java +index b2ab430392fc0f214ba8c5383e3f3ad5c548bda2..9022555db0df8c269fc039c895422cf36c08097e 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftHumanEntity.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftHumanEntity.java +@@ -471,6 +471,70 @@ public class CraftHumanEntity extends CraftLivingEntity implements HumanEntity { + return this.getHandle().containerMenu.getBukkitView(); + } + ++ // Paper start - Add additional containers ++ @Override ++ public InventoryView openAnvil(Location location, boolean force) { ++ return this.openInventory(location, force, Material.ANVIL); ++ } ++ ++ @Override ++ public InventoryView openCartographyTable(Location location, boolean force) { ++ return this.openInventory(location, force, Material.CARTOGRAPHY_TABLE); ++ } ++ ++ @Override ++ public InventoryView openGrindstone(Location location, boolean force) { ++ return this.openInventory(location, force, Material.GRINDSTONE); ++ } ++ ++ @Override ++ public InventoryView openLoom(Location location, boolean force) { ++ return this.openInventory(location, force, Material.LOOM); ++ } ++ ++ @Override ++ public InventoryView openSmithingTable(Location location, boolean force) { ++ return this.openInventory(location, force, Material.SMITHING_TABLE); ++ } ++ ++ @Override ++ public InventoryView openStonecutter(Location location, boolean force) { ++ return this.openInventory(location, force, Material.STONECUTTER); ++ } ++ ++ private InventoryView openInventory(Location location, boolean force, Material material) { ++ org.spigotmc.AsyncCatcher.catchOp("open" + material); ++ if (location == null) { ++ location = this.getLocation(); ++ } ++ if (!force) { ++ Block block = location.getBlock(); ++ if (block.getType() != material) { ++ return null; ++ } ++ } ++ net.minecraft.world.level.block.Block block; ++ if (material == Material.ANVIL) { ++ block = Blocks.ANVIL; ++ } else if (material == Material.CARTOGRAPHY_TABLE) { ++ block = Blocks.CARTOGRAPHY_TABLE; ++ } else if (material == Material.GRINDSTONE) { ++ block = Blocks.GRINDSTONE; ++ } else if (material == Material.LOOM) { ++ block = Blocks.LOOM; ++ } else if (material == Material.SMITHING_TABLE) { ++ block = Blocks.SMITHING_TABLE; ++ } else if (material == Material.STONECUTTER) { ++ block = Blocks.STONECUTTER; ++ } else { ++ throw new IllegalArgumentException("Unsupported inventory type: " + material); ++ } ++ this.getHandle().openMenu(block.getMenuProvider(null, this.getHandle().level(), new BlockPos(location.getBlockX(), location.getBlockY(), location.getBlockZ()))); ++ this.getHandle().containerMenu.checkReachable = !force; ++ return this.getHandle().containerMenu.getBukkitView(); ++ } ++ // Paper end ++ + @Override + public void closeInventory() { + // Paper start - Inventory close reason diff --git a/patches/server/0405-Create-HoverEvent-from-ItemStack-Entity.patch b/patches/server/0405-Create-HoverEvent-from-ItemStack-Entity.patch deleted file mode 100644 index 081d6c07e520..000000000000 --- a/patches/server/0405-Create-HoverEvent-from-ItemStack-Entity.patch +++ /dev/null @@ -1,55 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: ysl3000 -Date: Mon, 6 Jul 2020 22:18:04 +0200 -Subject: [PATCH] Create HoverEvent from ItemStack Entity - - -diff --git a/src/main/java/org/bukkit/craftbukkit/inventory/CraftItemFactory.java b/src/main/java/org/bukkit/craftbukkit/inventory/CraftItemFactory.java -index fd0df053cd5e8802991e665185e7f90f8001d80c..eabb8b42b890224dd19b879ff276e9908674310d 100644 ---- a/src/main/java/org/bukkit/craftbukkit/inventory/CraftItemFactory.java -+++ b/src/main/java/org/bukkit/craftbukkit/inventory/CraftItemFactory.java -@@ -246,4 +246,44 @@ public final class CraftItemFactory implements ItemFactory { - return nms != null ? net.minecraft.locale.Language.getInstance().getOrDefault(nms.getItem().getDescriptionId(nms)) : null; - } - // Paper end - add getI18NDisplayName -+ -+ // Paper start - bungee hover events -+ @Override -+ public net.md_5.bungee.api.chat.hover.content.Content hoverContentOf(ItemStack itemStack) { -+ throw new UnsupportedOperationException("BungeeCord Chat API does not support data components"); -+ /* -+ net.md_5.bungee.api.chat.ItemTag itemTag = net.md_5.bungee.api.chat.ItemTag.ofNbt(CraftItemStack.asNMSCopy(itemStack).getOrCreateTag().toString()); -+ return new net.md_5.bungee.api.chat.hover.content.Item( -+ itemStack.getType().getKey().toString(), -+ itemStack.getAmount(), -+ itemTag); -+ */ -+ } -+ -+ @Override -+ public net.md_5.bungee.api.chat.hover.content.Content hoverContentOf(org.bukkit.entity.Entity entity) { -+ return hoverContentOf(entity, org.apache.commons.lang3.StringUtils.isBlank(entity.getCustomName()) ? null : new net.md_5.bungee.api.chat.TextComponent(entity.getCustomName())); -+ } -+ -+ @Override -+ public net.md_5.bungee.api.chat.hover.content.Content hoverContentOf(org.bukkit.entity.Entity entity, String customName) { -+ return hoverContentOf(entity, org.apache.commons.lang3.StringUtils.isBlank(customName) ? null : new net.md_5.bungee.api.chat.TextComponent(customName)); -+ } -+ -+ @Override -+ public net.md_5.bungee.api.chat.hover.content.Content hoverContentOf(org.bukkit.entity.Entity entity, net.md_5.bungee.api.chat.BaseComponent customName) { -+ return new net.md_5.bungee.api.chat.hover.content.Entity( -+ entity.getType().getKey().toString(), -+ entity.getUniqueId().toString(), -+ customName); -+ } -+ -+ @Override -+ public net.md_5.bungee.api.chat.hover.content.Content hoverContentOf(org.bukkit.entity.Entity entity, net.md_5.bungee.api.chat.BaseComponent[] customName) { -+ return new net.md_5.bungee.api.chat.hover.content.Entity( -+ entity.getType().getKey().toString(), -+ entity.getUniqueId().toString(), -+ new net.md_5.bungee.api.chat.TextComponent(customName)); -+ } -+ // Paper end - bungee hover events - } diff --git a/patches/server/0409-Cache-DataFixerUpper-Rewrite-Rules-on-demand.patch b/patches/server/0406-Cache-DataFixerUpper-Rewrite-Rules-on-demand.patch similarity index 100% rename from patches/server/0409-Cache-DataFixerUpper-Rewrite-Rules-on-demand.patch rename to patches/server/0406-Cache-DataFixerUpper-Rewrite-Rules-on-demand.patch diff --git a/patches/server/0406-Cache-block-data-strings.patch b/patches/server/0406-Cache-block-data-strings.patch deleted file mode 100644 index d9ac173303b3..000000000000 --- a/patches/server/0406-Cache-block-data-strings.patch +++ /dev/null @@ -1,70 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: miclebrick -Date: Thu, 6 Dec 2018 19:52:50 -0500 -Subject: [PATCH] Cache block data strings - - -diff --git a/src/main/java/net/minecraft/server/MinecraftServer.java b/src/main/java/net/minecraft/server/MinecraftServer.java -index dd5f74fe4c8ca81b1d1102805ea8acb9087ba91e..d2866b9f7e6c8f0ca30d451c93c56caefb2c1b5c 100644 ---- a/src/main/java/net/minecraft/server/MinecraftServer.java -+++ b/src/main/java/net/minecraft/server/MinecraftServer.java -@@ -2145,6 +2145,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop>, Enum[]> ENUM_VALUES = new HashMap<>(); -+ private static final Map>, Enum[]> ENUM_VALUES = new java.util.concurrent.ConcurrentHashMap<>(); // Paper - cache block data strings; make thread safe - - /** - * Convert an NMS Enum (usually a BlockStateEnum) to its appropriate Bukkit -@@ -539,7 +539,38 @@ public class CraftBlockData implements BlockData { - Preconditions.checkState(CraftBlockData.MAP.put(nms, bukkit) == null, "Duplicate mapping %s->%s", nms, bukkit); - } - -+ // Paper start - cache block data strings -+ private static Map stringDataCache = new java.util.concurrent.ConcurrentHashMap<>(); -+ -+ static { -+ // cache all of the default states at startup, will not cache ones with the custom states inside of the -+ // brackets in a different order, though -+ reloadCache(); -+ } -+ -+ public static void reloadCache() { -+ stringDataCache.clear(); -+ Block.BLOCK_STATE_REGISTRY.forEach(blockData -> stringDataCache.put(blockData.toString(), blockData.createCraftBlockData())); -+ } -+ // Paper end - cache block data strings -+ - public static CraftBlockData newData(BlockType blockType, String data) { -+ -+ // Paper start - cache block data strings -+ if (blockType != null) { -+ Block block = CraftBlockType.bukkitToMinecraftNew(blockType); -+ if (block != null) { -+ net.minecraft.resources.ResourceLocation key = BuiltInRegistries.BLOCK.getKey(block); -+ data = data == null ? key.toString() : key + data; -+ } -+ } -+ -+ CraftBlockData cached = stringDataCache.computeIfAbsent(data, s -> createNewData(null, s)); -+ return (CraftBlockData) cached.clone(); -+ } -+ -+ private static CraftBlockData createNewData(BlockType blockType, String data) { -+ // Paper end - cache block data strings - net.minecraft.world.level.block.state.BlockState blockData; - Block block = blockType == null ? null : ((CraftBlockType) blockType).getHandle(); - Map, Comparable> parsed = null; diff --git a/patches/server/0407-Extend-block-drop-capture-to-capture-all-items-added.patch b/patches/server/0407-Extend-block-drop-capture-to-capture-all-items-added.patch new file mode 100644 index 000000000000..304750f90e66 --- /dev/null +++ b/patches/server/0407-Extend-block-drop-capture-to-capture-all-items-added.patch @@ -0,0 +1,43 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Shane Freeder +Date: Thu, 17 Sep 2020 00:36:05 +0100 +Subject: [PATCH] Extend block drop capture to capture all items added to the + world + + +diff --git a/src/main/java/net/minecraft/server/level/ServerLevel.java b/src/main/java/net/minecraft/server/level/ServerLevel.java +index 17725a575a29aa8076582e1b8c644ffd928cfaae..c5029210d059a62577ede8b6e944e2f4dcbac420 100644 +--- a/src/main/java/net/minecraft/server/level/ServerLevel.java ++++ b/src/main/java/net/minecraft/server/level/ServerLevel.java +@@ -1193,6 +1193,12 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe + // WorldServer.LOGGER.warn("Tried to add entity {} but it was marked as removed already", EntityTypes.getKey(entity.getType())); // CraftBukkit + return false; + } else { ++ // Paper start - capture all item additions to the world ++ if (captureDrops != null && entity instanceof net.minecraft.world.entity.item.ItemEntity) { ++ captureDrops.add((net.minecraft.world.entity.item.ItemEntity) entity); ++ return true; ++ } ++ // Paper end - capture all item additions to the world + // SPIGOT-6415: Don't call spawn event when reason is null. For example when an entity teleports to a new world. + if (spawnReason != null && !CraftEventFactory.doEntityAddEventCalling(this, entity, spawnReason)) { + return false; +diff --git a/src/main/java/net/minecraft/server/level/ServerPlayerGameMode.java b/src/main/java/net/minecraft/server/level/ServerPlayerGameMode.java +index 86e4559da2344f228ef4d1c4ac3c115fa0266d23..546be40a8e4470fb5a6686072cdd342cdaa6fe15 100644 +--- a/src/main/java/net/minecraft/server/level/ServerPlayerGameMode.java ++++ b/src/main/java/net/minecraft/server/level/ServerPlayerGameMode.java +@@ -435,10 +435,12 @@ public class ServerPlayerGameMode { + // return true; // CraftBukkit + } + // CraftBukkit start ++ java.util.List itemsToDrop = this.level.captureDrops; // Paper - capture all item additions to the world ++ this.level.captureDrops = null; // Paper - capture all item additions to the world; Remove this earlier so that we can actually drop stuff + if (event.isDropItems()) { +- org.bukkit.craftbukkit.event.CraftEventFactory.handleBlockDropItemEvent(bblock, state, this.player, this.level.captureDrops); ++ org.bukkit.craftbukkit.event.CraftEventFactory.handleBlockDropItemEvent(bblock, state, this.player, itemsToDrop); // Paper - capture all item additions to the world + } +- this.level.captureDrops = null; ++ //this.level.captureDrops = null; // Paper - capture all item additions to the world; move up + + // Drop event experience + if (flag && event != null) { diff --git a/patches/server/0407-Fix-Entity-Teleportation-and-cancel-velocity-if-tele.patch b/patches/server/0407-Fix-Entity-Teleportation-and-cancel-velocity-if-tele.patch deleted file mode 100644 index a480b44af125..000000000000 --- a/patches/server/0407-Fix-Entity-Teleportation-and-cancel-velocity-if-tele.patch +++ /dev/null @@ -1,107 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Aikar -Date: Tue, 25 Aug 2020 20:45:36 -0400 -Subject: [PATCH] Fix Entity Teleportation and cancel velocity if teleported - -Uses correct setPositionRotation for Entity teleporting instead of setLocation -as this is how Vanilla teleports entities. - -Cancel any pending motion when teleported. - -diff --git a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java -index 90e5f263dcb8033dae3ca32cbf5dbd332a109282..82f49aad1d0ce2d62bf61aa634ebef3711d4d930 100644 ---- a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java -+++ b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java -@@ -679,7 +679,7 @@ public class ServerGamePacketListenerImpl extends ServerCommonPacketListenerImpl - return; - } - -- this.player.absMoveTo(this.awaitingPositionFromClient.x, this.awaitingPositionFromClient.y, this.awaitingPositionFromClient.z, this.player.getYRot(), this.player.getXRot()); -+ this.player.moveTo(this.awaitingPositionFromClient.x, this.awaitingPositionFromClient.y, this.awaitingPositionFromClient.z, this.player.getYRot(), this.player.getXRot()); // Paper - Fix Entity Teleportation and cancel velocity if teleported - this.lastGoodX = this.awaitingPositionFromClient.x; - this.lastGoodY = this.awaitingPositionFromClient.y; - this.lastGoodZ = this.awaitingPositionFromClient.z; -@@ -1594,7 +1594,7 @@ public class ServerGamePacketListenerImpl extends ServerCommonPacketListenerImpl - // CraftBukkit end - - this.awaitingTeleportTime = this.tickCount; -- this.player.absMoveTo(d0, d1, d2, f, f1); -+ this.player.moveTo(d0, d1, d2, f, f1); // Paper - Fix Entity Teleportation and cancel velocity if teleported - this.player.connection.send(new ClientboundPlayerPositionPacket(d0 - d3, d1 - d4, d2 - d5, f - f2, f1 - f3, set, this.awaitingTeleport)); - } - -diff --git a/src/main/java/net/minecraft/world/entity/Entity.java b/src/main/java/net/minecraft/world/entity/Entity.java -index 9d08d0610be1741b4cd18a1454e538f46928fd94..f1383906dbd16e088f57c9c77c051c8501b155cc 100644 ---- a/src/main/java/net/minecraft/world/entity/Entity.java -+++ b/src/main/java/net/minecraft/world/entity/Entity.java -@@ -171,6 +171,7 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess - - // CraftBukkit start - private static final int CURRENT_LEVEL = 2; -+ public boolean preserveMotion = true; // Paper - Fix Entity Teleportation and cancel velocity if teleported; keep initial motion on first setPositionRotation - static boolean isLevelAtLeast(CompoundTag tag, int level) { - return tag.contains("Bukkit.updateLevel") && tag.getInt("Bukkit.updateLevel") >= level; - } -@@ -1873,6 +1874,13 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess - } - - public void moveTo(double x, double y, double z, float yaw, float pitch) { -+ // Paper start - Fix Entity Teleportation and cancel velocity if teleported -+ if (!preserveMotion) { -+ this.deltaMovement = Vec3.ZERO; -+ } else { -+ this.preserveMotion = false; -+ } -+ // Paper end - Fix Entity Teleportation and cancel velocity if teleported - this.setPosRaw(x, y, z); - this.setYRot(yaw); - this.setXRot(pitch); -diff --git a/src/main/java/net/minecraft/world/entity/boss/enderdragon/phases/DragonStrafePlayerPhase.java b/src/main/java/net/minecraft/world/entity/boss/enderdragon/phases/DragonStrafePlayerPhase.java -index a28e6b6a50cfd9191732ad2e4aca5f639a1fae75..244e328c6f67cfa543ee283715bb3b89dbaa0f0c 100644 ---- a/src/main/java/net/minecraft/world/entity/boss/enderdragon/phases/DragonStrafePlayerPhase.java -+++ b/src/main/java/net/minecraft/world/entity/boss/enderdragon/phases/DragonStrafePlayerPhase.java -@@ -78,6 +78,7 @@ public class DragonStrafePlayerPhase extends AbstractDragonPhaseInstance { - } - - DragonFireball dragonFireball = new DragonFireball(this.dragon.level(), this.dragon, vec34.normalize()); -+ dragonFireball.preserveMotion = true; // Paper - Fix Entity Teleportation and cancel velocity if teleported - dragonFireball.moveTo(o, p, q, 0.0F, 0.0F); - if (new com.destroystokyo.paper.event.entity.EnderDragonShootFireballEvent((org.bukkit.entity.EnderDragon) dragon.getBukkitEntity(), (org.bukkit.entity.DragonFireball) dragonFireball.getBukkitEntity()).callEvent()) // Paper - EnderDragon Events - this.dragon.level().addFreshEntity(dragonFireball); -diff --git a/src/main/java/net/minecraft/world/level/BaseSpawner.java b/src/main/java/net/minecraft/world/level/BaseSpawner.java -index b90127f9f805fdb5bb43a4b8ad2b10499b0b6b78..8efc06d29c62fa2be8515ed3359d52a6d4b807d2 100644 ---- a/src/main/java/net/minecraft/world/level/BaseSpawner.java -+++ b/src/main/java/net/minecraft/world/level/BaseSpawner.java -@@ -164,6 +164,7 @@ public abstract class BaseSpawner { - return; - } - -+ entity.preserveMotion = true; // Paper - Fix Entity Teleportation and cancel velocity if teleported; preserve entity motion from tag - entity.moveTo(entity.getX(), entity.getY(), entity.getZ(), randomsource.nextFloat() * 360.0F, 0.0F); - if (entity instanceof Mob) { - Mob entityinsentient = (Mob) entity; -diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java -index 34321f095e12ea0cca34ff1ec00819c6350205a8..5f5788a502642463091fb76e98703aaec7a86836 100644 ---- a/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java -+++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java -@@ -240,7 +240,7 @@ public abstract class CraftEntity implements org.bukkit.entity.Entity { - } - - // entity.setLocation() throws no event, and so cannot be cancelled -- this.entity.absMoveTo(location.getX(), location.getY(), location.getZ(), location.getYaw(), location.getPitch()); -+ entity.moveTo(location.getX(), location.getY(), location.getZ(), location.getYaw(), location.getPitch()); // Paper - use proper moveTo, as per vanilla teleporting - // SPIGOT-619: Force sync head rotation also - this.entity.setYHeadRot(location.getYaw()); - -diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftLivingEntity.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftLivingEntity.java -index fad7d8130f6db70c7bfca9d02027d8a41f5309c1..8a8189e8f2f201880748eb79805bb0b33688e814 100644 ---- a/src/main/java/org/bukkit/craftbukkit/entity/CraftLivingEntity.java -+++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftLivingEntity.java -@@ -596,6 +596,7 @@ public class CraftLivingEntity extends CraftEntity implements LivingEntity { - } - - ((AbstractHurtingProjectile) launch).projectileSource = this; -+ launch.preserveMotion = true; // Paper - Fix Entity Teleportation and cancel velocity if teleported - launch.moveTo(location.getX(), location.getY(), location.getZ(), location.getYaw(), location.getPitch()); - } else if (LlamaSpit.class.isAssignableFrom(projectile)) { - Location location = this.getEyeLocation(); diff --git a/patches/server/0408-Add-additional-open-container-api-to-HumanEntity.patch b/patches/server/0408-Add-additional-open-container-api-to-HumanEntity.patch deleted file mode 100644 index 15f9a92e4435..000000000000 --- a/patches/server/0408-Add-additional-open-container-api-to-HumanEntity.patch +++ /dev/null @@ -1,83 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: JRoy -Date: Wed, 26 Aug 2020 02:12:31 -0400 -Subject: [PATCH] Add additional open container api to HumanEntity - -== AT == -public net/minecraft/world/level/block/state/BlockBehaviour getMenuProvider(Lnet/minecraft/world/level/block/state/BlockState;Lnet/minecraft/world/level/Level;Lnet/minecraft/core/BlockPos;)Lnet/minecraft/world/MenuProvider; - -diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftHumanEntity.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftHumanEntity.java -index c17dd4205983855615289cf0a5619056d237f325..6cda13df52ee4d56dd1d3c213307bfd38175584c 100644 ---- a/src/main/java/org/bukkit/craftbukkit/entity/CraftHumanEntity.java -+++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftHumanEntity.java -@@ -471,6 +471,70 @@ public class CraftHumanEntity extends CraftLivingEntity implements HumanEntity { - return this.getHandle().containerMenu.getBukkitView(); - } - -+ // Paper start - Add additional containers -+ @Override -+ public InventoryView openAnvil(Location location, boolean force) { -+ return this.openInventory(location, force, Material.ANVIL); -+ } -+ -+ @Override -+ public InventoryView openCartographyTable(Location location, boolean force) { -+ return this.openInventory(location, force, Material.CARTOGRAPHY_TABLE); -+ } -+ -+ @Override -+ public InventoryView openGrindstone(Location location, boolean force) { -+ return this.openInventory(location, force, Material.GRINDSTONE); -+ } -+ -+ @Override -+ public InventoryView openLoom(Location location, boolean force) { -+ return this.openInventory(location, force, Material.LOOM); -+ } -+ -+ @Override -+ public InventoryView openSmithingTable(Location location, boolean force) { -+ return this.openInventory(location, force, Material.SMITHING_TABLE); -+ } -+ -+ @Override -+ public InventoryView openStonecutter(Location location, boolean force) { -+ return this.openInventory(location, force, Material.STONECUTTER); -+ } -+ -+ private InventoryView openInventory(Location location, boolean force, Material material) { -+ org.spigotmc.AsyncCatcher.catchOp("open" + material); -+ if (location == null) { -+ location = this.getLocation(); -+ } -+ if (!force) { -+ Block block = location.getBlock(); -+ if (block.getType() != material) { -+ return null; -+ } -+ } -+ net.minecraft.world.level.block.Block block; -+ if (material == Material.ANVIL) { -+ block = Blocks.ANVIL; -+ } else if (material == Material.CARTOGRAPHY_TABLE) { -+ block = Blocks.CARTOGRAPHY_TABLE; -+ } else if (material == Material.GRINDSTONE) { -+ block = Blocks.GRINDSTONE; -+ } else if (material == Material.LOOM) { -+ block = Blocks.LOOM; -+ } else if (material == Material.SMITHING_TABLE) { -+ block = Blocks.SMITHING_TABLE; -+ } else if (material == Material.STONECUTTER) { -+ block = Blocks.STONECUTTER; -+ } else { -+ throw new IllegalArgumentException("Unsupported inventory type: " + material); -+ } -+ this.getHandle().openMenu(block.getMenuProvider(null, this.getHandle().level(), new BlockPos(location.getBlockX(), location.getBlockY(), location.getBlockZ()))); -+ this.getHandle().containerMenu.checkReachable = !force; -+ return this.getHandle().containerMenu.getBukkitView(); -+ } -+ // Paper end -+ - @Override - public void closeInventory() { - // Paper start - Inventory close reason diff --git a/patches/server/0408-Expose-the-Entity-Counter-to-allow-plugins-to-use-va.patch b/patches/server/0408-Expose-the-Entity-Counter-to-allow-plugins-to-use-va.patch new file mode 100644 index 000000000000..4a538824cb89 --- /dev/null +++ b/patches/server/0408-Expose-the-Entity-Counter-to-allow-plugins-to-use-va.patch @@ -0,0 +1,38 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: MeFisto94 +Date: Fri, 28 Aug 2020 01:41:26 +0200 +Subject: [PATCH] Expose the Entity Counter to allow plugins to use valid and + non-conflicting Entity Ids + + +diff --git a/src/main/java/net/minecraft/world/entity/Entity.java b/src/main/java/net/minecraft/world/entity/Entity.java +index 2c9e57436469f94beb45f656a1df71aba7b1e408..c3e28cc070993be5afe9323c2c2d54ffc81d82f3 100644 +--- a/src/main/java/net/minecraft/world/entity/Entity.java ++++ b/src/main/java/net/minecraft/world/entity/Entity.java +@@ -4704,4 +4704,10 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess + + void accept(Entity entity, double x, double y, double z); + } ++ ++ // Paper start - Expose entity id counter ++ public static int nextEntityId() { ++ return ENTITY_COUNTER.incrementAndGet(); ++ } ++ // Paper end - Expose entity id counter + } +diff --git a/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java b/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java +index 150ab63f231fc3c39661ab876a8c90d608ee8568..93505eba28d9fb20ce25866e932fc1cdcb006db8 100644 +--- a/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java ++++ b/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java +@@ -516,6 +516,11 @@ public final class CraftMagicNumbers implements UnsafeValues { + Preconditions.checkArgument(dataVersion <= getDataVersion(), "Newer version! Server downgrades are not supported!"); + return compound; + } ++ ++ @Override ++ public int nextEntityId() { ++ return net.minecraft.world.entity.Entity.nextEntityId(); ++ } + // Paper end + + /** diff --git a/patches/server/0412-Lazily-track-plugin-scoreboards-by-default.patch b/patches/server/0409-Lazily-track-plugin-scoreboards-by-default.patch similarity index 100% rename from patches/server/0412-Lazily-track-plugin-scoreboards-by-default.patch rename to patches/server/0409-Lazily-track-plugin-scoreboards-by-default.patch diff --git a/patches/server/0410-Entity-isTicking.patch b/patches/server/0410-Entity-isTicking.patch new file mode 100644 index 000000000000..ce1685263561 --- /dev/null +++ b/patches/server/0410-Entity-isTicking.patch @@ -0,0 +1,36 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: William Blake Galbreath +Date: Sat, 3 Oct 2020 21:39:16 -0500 +Subject: [PATCH] Entity#isTicking + + +diff --git a/src/main/java/net/minecraft/world/entity/Entity.java b/src/main/java/net/minecraft/world/entity/Entity.java +index c3e28cc070993be5afe9323c2c2d54ffc81d82f3..6cefe8e0de375d3b192cc8be2b553a2dcbe098f5 100644 +--- a/src/main/java/net/minecraft/world/entity/Entity.java ++++ b/src/main/java/net/minecraft/world/entity/Entity.java +@@ -4709,5 +4709,9 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess + public static int nextEntityId() { + return ENTITY_COUNTER.incrementAndGet(); + } ++ ++ public boolean isTicking() { ++ return ((net.minecraft.server.level.ServerChunkCache) level.getChunkSource()).isPositionTicking(this); ++ } + // Paper end - Expose entity id counter + } +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java +index abecb0235eee3e62a84f15f2ef100efcf3fcbf2a..925626b74005ec9c031c3773171f7684ecc9ccd3 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java +@@ -1063,4 +1063,11 @@ public abstract class CraftEntity implements org.bukkit.entity.Entity { + return getHandle().isInLava(); + } + // Paper end - entity liquid API ++ ++ // Paper start - isTicking API ++ @Override ++ public boolean isTicking() { ++ return getHandle().isTicking(); ++ } ++ // Paper end - isTicking API + } diff --git a/patches/server/0410-Extend-block-drop-capture-to-capture-all-items-added.patch b/patches/server/0410-Extend-block-drop-capture-to-capture-all-items-added.patch deleted file mode 100644 index 9cb478d12045..000000000000 --- a/patches/server/0410-Extend-block-drop-capture-to-capture-all-items-added.patch +++ /dev/null @@ -1,43 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Shane Freeder -Date: Thu, 17 Sep 2020 00:36:05 +0100 -Subject: [PATCH] Extend block drop capture to capture all items added to the - world - - -diff --git a/src/main/java/net/minecraft/server/level/ServerLevel.java b/src/main/java/net/minecraft/server/level/ServerLevel.java -index 9114ba1742a4fc8683848d431fa92046a85fe871..c60967b7833a23cff0305219b02b308d92448109 100644 ---- a/src/main/java/net/minecraft/server/level/ServerLevel.java -+++ b/src/main/java/net/minecraft/server/level/ServerLevel.java -@@ -1228,6 +1228,12 @@ public class ServerLevel extends Level implements WorldGenLevel { - // WorldServer.LOGGER.warn("Tried to add entity {} but it was marked as removed already", EntityTypes.getKey(entity.getType())); // CraftBukkit - return false; - } else { -+ // Paper start - capture all item additions to the world -+ if (captureDrops != null && entity instanceof net.minecraft.world.entity.item.ItemEntity) { -+ captureDrops.add((net.minecraft.world.entity.item.ItemEntity) entity); -+ return true; -+ } -+ // Paper end - capture all item additions to the world - // SPIGOT-6415: Don't call spawn event when reason is null. For example when an entity teleports to a new world. - if (spawnReason != null && !CraftEventFactory.doEntityAddEventCalling(this, entity, spawnReason)) { - return false; -diff --git a/src/main/java/net/minecraft/server/level/ServerPlayerGameMode.java b/src/main/java/net/minecraft/server/level/ServerPlayerGameMode.java -index d4bd44210d58b30696feeea48e1909472a546702..5de472df78940d1b8320f73d18b2edf3a796227e 100644 ---- a/src/main/java/net/minecraft/server/level/ServerPlayerGameMode.java -+++ b/src/main/java/net/minecraft/server/level/ServerPlayerGameMode.java -@@ -437,10 +437,12 @@ public class ServerPlayerGameMode { - // return true; // CraftBukkit - } - // CraftBukkit start -+ java.util.List itemsToDrop = this.level.captureDrops; // Paper - capture all item additions to the world -+ this.level.captureDrops = null; // Paper - capture all item additions to the world; Remove this earlier so that we can actually drop stuff - if (event.isDropItems()) { -- org.bukkit.craftbukkit.event.CraftEventFactory.handleBlockDropItemEvent(bblock, state, this.player, this.level.captureDrops); -+ org.bukkit.craftbukkit.event.CraftEventFactory.handleBlockDropItemEvent(bblock, state, this.player, itemsToDrop); // Paper - capture all item additions to the world - } -- this.level.captureDrops = null; -+ //this.level.captureDrops = null; // Paper - capture all item additions to the world; move up - - // Drop event experience - if (flag && event != null) { diff --git a/patches/server/0411-Expose-the-Entity-Counter-to-allow-plugins-to-use-va.patch b/patches/server/0411-Expose-the-Entity-Counter-to-allow-plugins-to-use-va.patch deleted file mode 100644 index 587ee1ed675d..000000000000 --- a/patches/server/0411-Expose-the-Entity-Counter-to-allow-plugins-to-use-va.patch +++ /dev/null @@ -1,38 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: MeFisto94 -Date: Fri, 28 Aug 2020 01:41:26 +0200 -Subject: [PATCH] Expose the Entity Counter to allow plugins to use valid and - non-conflicting Entity Ids - - -diff --git a/src/main/java/net/minecraft/world/entity/Entity.java b/src/main/java/net/minecraft/world/entity/Entity.java -index f1383906dbd16e088f57c9c77c051c8501b155cc..c01a9305eb1c3e2ee5effab1e11980c2540d3c2a 100644 ---- a/src/main/java/net/minecraft/world/entity/Entity.java -+++ b/src/main/java/net/minecraft/world/entity/Entity.java -@@ -4508,4 +4508,10 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess - - void accept(Entity entity, double x, double y, double z); - } -+ -+ // Paper start - Expose entity id counter -+ public static int nextEntityId() { -+ return ENTITY_COUNTER.incrementAndGet(); -+ } -+ // Paper end - Expose entity id counter - } -diff --git a/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java b/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java -index 8b11f5f8cec74c57d614d73233a449c97cd56d18..d7e8663e21ade1b53d4b936147f57b632f67a156 100644 ---- a/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java -+++ b/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java -@@ -511,6 +511,11 @@ public final class CraftMagicNumbers implements UnsafeValues { - Preconditions.checkArgument(dataVersion <= getDataVersion(), "Newer version! Server downgrades are not supported!"); - return compound; - } -+ -+ @Override -+ public int nextEntityId() { -+ return net.minecraft.world.entity.Entity.nextEntityId(); -+ } - // Paper end - - @Override diff --git a/patches/server/0411-Fix-deop-kicking-non-whitelisted-player-when-white-l.patch b/patches/server/0411-Fix-deop-kicking-non-whitelisted-player-when-white-l.patch new file mode 100644 index 000000000000..0dc0df3b453c --- /dev/null +++ b/patches/server/0411-Fix-deop-kicking-non-whitelisted-player-when-white-l.patch @@ -0,0 +1,27 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: William Blake Galbreath +Date: Sat, 3 Oct 2020 22:00:27 -0500 +Subject: [PATCH] Fix deop kicking non-whitelisted player when white list is + not enabled + + +diff --git a/src/main/java/net/minecraft/server/MinecraftServer.java b/src/main/java/net/minecraft/server/MinecraftServer.java +index b9acf51e30839447cb7fc48e202321ed4bb2b7ea..f6667d9f514a821fd539e193dde1f78e77ca0fbf 100644 +--- a/src/main/java/net/minecraft/server/MinecraftServer.java ++++ b/src/main/java/net/minecraft/server/MinecraftServer.java +@@ -2309,13 +2309,14 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop list = Lists.newArrayList(playerlist.getPlayers()); + Iterator iterator = list.iterator(); + + while (iterator.hasNext()) { + ServerPlayer entityplayer = (ServerPlayer) iterator.next(); + +- if (!whitelist.isWhiteListed(entityplayer.getGameProfile())) { ++ if (!whitelist.isWhiteListed(entityplayer.getGameProfile()) && !this.getPlayerList().isOp(entityplayer.getGameProfile())) { // Paper - Fix kicking ops when whitelist is reloaded (MC-171420) + entityplayer.connection.disconnect((Component) Component.translatable("multiplayer.disconnect.not_whitelisted")); + } + } diff --git a/patches/server/0415-Fix-Concurrency-issue-in-ShufflingList.patch b/patches/server/0412-Fix-Concurrency-issue-in-ShufflingList.patch similarity index 100% rename from patches/server/0415-Fix-Concurrency-issue-in-ShufflingList.patch rename to patches/server/0412-Fix-Concurrency-issue-in-ShufflingList.patch diff --git a/patches/server/0413-Entity-isTicking.patch b/patches/server/0413-Entity-isTicking.patch deleted file mode 100644 index 502eb209370d..000000000000 --- a/patches/server/0413-Entity-isTicking.patch +++ /dev/null @@ -1,36 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: William Blake Galbreath -Date: Sat, 3 Oct 2020 21:39:16 -0500 -Subject: [PATCH] Entity#isTicking - - -diff --git a/src/main/java/net/minecraft/world/entity/Entity.java b/src/main/java/net/minecraft/world/entity/Entity.java -index c01a9305eb1c3e2ee5effab1e11980c2540d3c2a..6ab665efcac4c2543bab9d95472026e0ec8580c5 100644 ---- a/src/main/java/net/minecraft/world/entity/Entity.java -+++ b/src/main/java/net/minecraft/world/entity/Entity.java -@@ -4513,5 +4513,9 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess - public static int nextEntityId() { - return ENTITY_COUNTER.incrementAndGet(); - } -+ -+ public boolean isTicking() { -+ return ((net.minecraft.server.level.ServerChunkCache) level.getChunkSource()).isPositionTicking(this); -+ } - // Paper end - Expose entity id counter - } -diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java -index 5f5788a502642463091fb76e98703aaec7a86836..98e8ad81b8c9c0636abe59f70ce891fe926a37fe 100644 ---- a/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java -+++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java -@@ -1062,4 +1062,11 @@ public abstract class CraftEntity implements org.bukkit.entity.Entity { - return getHandle().isInLava(); - } - // Paper end - entity liquid API -+ -+ // Paper start - isTicking API -+ @Override -+ public boolean isTicking() { -+ return getHandle().isTicking(); -+ } -+ // Paper end - isTicking API - } diff --git a/patches/server/0413-Reset-Ender-Crystals-on-Dragon-Spawn.patch b/patches/server/0413-Reset-Ender-Crystals-on-Dragon-Spawn.patch new file mode 100644 index 000000000000..0c53eb55ed22 --- /dev/null +++ b/patches/server/0413-Reset-Ender-Crystals-on-Dragon-Spawn.patch @@ -0,0 +1,24 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Aikar +Date: Wed, 1 Jun 2016 23:29:17 -0400 +Subject: [PATCH] Reset Ender Crystals on Dragon Spawn + +Crystals can end up in a bad state in certain conditions which causes +an exception on the expected number of crystals going negative. + +This ensures the crystals/pillars are in expected state when the dragon spawns. + +See #3522 + +diff --git a/src/main/java/net/minecraft/world/level/dimension/end/EndDragonFight.java b/src/main/java/net/minecraft/world/level/dimension/end/EndDragonFight.java +index a16229f5eaa9e8320751e4fd1faf016f796d3414..0f34a5ce0352f627099a719b78e530e5e572581a 100644 +--- a/src/main/java/net/minecraft/world/level/dimension/end/EndDragonFight.java ++++ b/src/main/java/net/minecraft/world/level/dimension/end/EndDragonFight.java +@@ -475,6 +475,7 @@ public class EndDragonFight { + entityenderdragon.moveTo((double) this.origin.getX(), (double) (128 + this.origin.getY()), (double) this.origin.getZ(), this.level.random.nextFloat() * 360.0F, 0.0F); + this.level.addFreshEntity(entityenderdragon); + this.dragonUUID = entityenderdragon.getUUID(); ++ this.resetSpikeCrystals(); // Paper - Reset ender crystals on dragon spawn + } + + return entityenderdragon; diff --git a/patches/server/0414-Fix-deop-kicking-non-whitelisted-player-when-white-l.patch b/patches/server/0414-Fix-deop-kicking-non-whitelisted-player-when-white-l.patch deleted file mode 100644 index 284340c436f7..000000000000 --- a/patches/server/0414-Fix-deop-kicking-non-whitelisted-player-when-white-l.patch +++ /dev/null @@ -1,27 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: William Blake Galbreath -Date: Sat, 3 Oct 2020 22:00:27 -0500 -Subject: [PATCH] Fix deop kicking non-whitelisted player when white list is - not enabled - - -diff --git a/src/main/java/net/minecraft/server/MinecraftServer.java b/src/main/java/net/minecraft/server/MinecraftServer.java -index d2866b9f7e6c8f0ca30d451c93c56caefb2c1b5c..05aa3323e9ae971fba5ab8c6c319c7805a3808aa 100644 ---- a/src/main/java/net/minecraft/server/MinecraftServer.java -+++ b/src/main/java/net/minecraft/server/MinecraftServer.java -@@ -2270,13 +2270,14 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop list = Lists.newArrayList(playerlist.getPlayers()); - Iterator iterator = list.iterator(); - - while (iterator.hasNext()) { - ServerPlayer entityplayer = (ServerPlayer) iterator.next(); - -- if (!whitelist.isWhiteListed(entityplayer.getGameProfile())) { -+ if (!whitelist.isWhiteListed(entityplayer.getGameProfile()) && !this.getPlayerList().isOp(entityplayer.getGameProfile())) { // Paper - Fix kicking ops when whitelist is reloaded (MC-171420) - entityplayer.connection.disconnect((Component) Component.translatable("multiplayer.disconnect.not_whitelisted")); - } - } diff --git a/patches/server/0414-Fix-for-large-move-vectors-crashing-server.patch b/patches/server/0414-Fix-for-large-move-vectors-crashing-server.patch new file mode 100644 index 000000000000..82dc6a2eef9d --- /dev/null +++ b/patches/server/0414-Fix-for-large-move-vectors-crashing-server.patch @@ -0,0 +1,86 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Spottedleaf +Date: Sun, 17 May 2020 23:47:33 -0700 +Subject: [PATCH] Fix for large move vectors crashing server + +Check movement distance also based on current position. + +diff --git a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java +index 1754e413dc1b7a18b3fc0b981eae11283b2106e5..fc06281b6d37d72a0a333343fc800bd388713a5f 100644 +--- a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java ++++ b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java +@@ -497,9 +497,9 @@ public class ServerGamePacketListenerImpl extends ServerCommonPacketListenerImpl + float prevYaw = this.player.getYRot(); + float prevPitch = this.player.getXRot(); + // CraftBukkit end +- double d0 = entity.getX(); +- double d1 = entity.getY(); +- double d2 = entity.getZ(); ++ double d0 = entity.getX();final double fromX = d0; // Paper - OBFHELPER ++ double d1 = entity.getY();final double fromY = d1; // Paper - OBFHELPER ++ double d2 = entity.getZ();final double fromZ = d2; // Paper - OBFHELPER + double d3 = ServerGamePacketListenerImpl.clampHorizontal(packet.getX()); final double toX = d3; // Paper - OBFHELPER + double d4 = ServerGamePacketListenerImpl.clampVertical(packet.getY()); final double toY = d4; // Paper - OBFHELPER + double d5 = ServerGamePacketListenerImpl.clampHorizontal(packet.getZ()); final double toZ = d5; // Paper - OBFHELPER +@@ -509,7 +509,16 @@ public class ServerGamePacketListenerImpl extends ServerCommonPacketListenerImpl + double d7 = d4 - this.vehicleFirstGoodY; + double d8 = d5 - this.vehicleFirstGoodZ; + double d9 = entity.getDeltaMovement().lengthSqr(); +- double d10 = d6 * d6 + d7 * d7 + d8 * d8; ++ // Paper start - fix large move vectors killing the server ++ double currDeltaX = toX - fromX; ++ double currDeltaY = toY - fromY; ++ double currDeltaZ = toZ - fromZ; ++ double d10 = Math.max(d6 * d6 + d7 * d7 + d8 * d8, (currDeltaX * currDeltaX + currDeltaY * currDeltaY + currDeltaZ * currDeltaZ) - 1); ++ double otherFieldX = d3 - this.vehicleLastGoodX; ++ double otherFieldY = d4 - this.vehicleLastGoodY - 1.0E-6D; ++ double otherFieldZ = d5 - this.vehicleLastGoodZ; ++ d10 = Math.max(d10, (otherFieldX * otherFieldX + otherFieldY * otherFieldY + otherFieldZ * otherFieldZ) - 1); ++ // Paper end - fix large move vectors killing the server + + // CraftBukkit start - handle custom speeds and skipped ticks + this.allowedPlayerTicks += (System.currentTimeMillis() / 50) - this.lastTick; +@@ -555,9 +564,9 @@ public class ServerGamePacketListenerImpl extends ServerCommonPacketListenerImpl + + boolean flag = worldserver.noCollision(entity, entity.getBoundingBox().deflate(0.0625D)); + +- d6 = d3 - this.vehicleLastGoodX; +- d7 = d4 - this.vehicleLastGoodY - 1.0E-6D; +- d8 = d5 - this.vehicleLastGoodZ; ++ d6 = d3 - this.vehicleLastGoodX; // Paper - diff on change, used for checking large move vectors above ++ d7 = d4 - this.vehicleLastGoodY - 1.0E-6D; // Paper - diff on change, used for checking large move vectors above ++ d8 = d5 - this.vehicleLastGoodZ; // Paper - diff on change, used for checking large move vectors above + boolean flag1 = entity.verticalCollisionBelow; + + if (entity instanceof LivingEntity) { +@@ -1264,7 +1273,16 @@ public class ServerGamePacketListenerImpl extends ServerCommonPacketListenerImpl + double d7 = d1 - this.firstGoodY; + double d8 = d2 - this.firstGoodZ; + double d9 = this.player.getDeltaMovement().lengthSqr(); +- double d10 = d6 * d6 + d7 * d7 + d8 * d8; ++ // Paper start - fix large move vectors killing the server ++ double currDeltaX = toX - prevX; ++ double currDeltaY = toY - prevY; ++ double currDeltaZ = toZ - prevZ; ++ double d10 = Math.max(d6 * d6 + d7 * d7 + d8 * d8, (currDeltaX * currDeltaX + currDeltaY * currDeltaY + currDeltaZ * currDeltaZ) - 1); ++ double otherFieldX = d0 - this.lastGoodX; ++ double otherFieldY = d1 - this.lastGoodY; ++ double otherFieldZ = d2 - this.lastGoodZ; ++ d10 = Math.max(d10, (otherFieldX * otherFieldX + otherFieldY * otherFieldY + otherFieldZ * otherFieldZ) - 1); ++ // Paper end - fix large move vectors killing the server + + if (this.player.isSleeping()) { + if (d10 > 1.0D) { +@@ -1320,9 +1338,9 @@ public class ServerGamePacketListenerImpl extends ServerCommonPacketListenerImpl + + AABB axisalignedbb = this.player.getBoundingBox(); + +- d6 = d0 - this.lastGoodX; +- d7 = d1 - this.lastGoodY; +- d8 = d2 - this.lastGoodZ; ++ d6 = d0 - this.lastGoodX; // Paper - diff on change, used for checking large move vectors above ++ d7 = d1 - this.lastGoodY; // Paper - diff on change, used for checking large move vectors above ++ d8 = d2 - this.lastGoodZ; // Paper - diff on change, used for checking large move vectors above + boolean flag1 = d7 > 0.0D; + + if (this.player.onGround() && !packet.isOnGround() && flag1) { diff --git a/patches/server/0415-Optimise-getType-calls.patch b/patches/server/0415-Optimise-getType-calls.patch new file mode 100644 index 000000000000..07a3b551c2d3 --- /dev/null +++ b/patches/server/0415-Optimise-getType-calls.patch @@ -0,0 +1,93 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Spottedleaf +Date: Wed, 3 Jun 2020 11:37:13 -0700 +Subject: [PATCH] Optimise getType calls + +Remove the map lookup for converting from Block->Bukkit Material + +diff --git a/src/main/java/net/minecraft/world/level/block/state/BlockState.java b/src/main/java/net/minecraft/world/level/block/state/BlockState.java +index 065d140ca4f987e14138a37f4c7d60879dd7b6e1..601135f3368272bf1ca3a43ec9c199e3ee838462 100644 +--- a/src/main/java/net/minecraft/world/level/block/state/BlockState.java ++++ b/src/main/java/net/minecraft/world/level/block/state/BlockState.java +@@ -10,6 +10,16 @@ import net.minecraft.world.level.block.state.properties.Property; + public class BlockState extends BlockBehaviour.BlockStateBase { + public static final Codec CODEC = codec(BuiltInRegistries.BLOCK.byNameCodec(), Block::defaultBlockState).stable(); + ++ // Paper start - optimise getType calls ++ org.bukkit.Material cachedMaterial; ++ ++ public final org.bukkit.Material getBukkitMaterial() { ++ if (this.cachedMaterial == null) { ++ this.cachedMaterial = org.bukkit.craftbukkit.block.CraftBlockType.minecraftToBukkit(this.getBlock()); ++ } ++ return this.cachedMaterial; ++ } ++ // Paper end - optimise getType calls + public BlockState(Block block, Reference2ObjectArrayMap, Comparable> propertyMap, MapCodec codec) { + super(block, propertyMap, codec); + } +diff --git a/src/main/java/org/bukkit/craftbukkit/CraftChunkSnapshot.java b/src/main/java/org/bukkit/craftbukkit/CraftChunkSnapshot.java +index a845ebd6262e05a1395c9a503480319e4b4a65ce..c88e4ba701b2a2325b76478b7f47278157afd2ef 100644 +--- a/src/main/java/org/bukkit/craftbukkit/CraftChunkSnapshot.java ++++ b/src/main/java/org/bukkit/craftbukkit/CraftChunkSnapshot.java +@@ -100,7 +100,7 @@ public class CraftChunkSnapshot implements ChunkSnapshot { + public Material getBlockType(int x, int y, int z) { + this.validateChunkCoordinates(x, y, z); + +- return CraftBlockType.minecraftToBukkit(this.blockids[this.getSectionIndex(y)].get(x, y & 0xF, z).getBlock()); ++ return this.blockids[this.getSectionIndex(y)].get(x, y & 0xF, z).getBukkitMaterial(); // Paper - optimise getType calls + } + + @Override +diff --git a/src/main/java/org/bukkit/craftbukkit/block/CraftBlock.java b/src/main/java/org/bukkit/craftbukkit/block/CraftBlock.java +index 3fa3de9a89550ec2fcb8ca663742826c0c3136b6..5f4dcf6d86db66186dc31075bdb739f5dbae6480 100644 +--- a/src/main/java/org/bukkit/craftbukkit/block/CraftBlock.java ++++ b/src/main/java/org/bukkit/craftbukkit/block/CraftBlock.java +@@ -220,7 +220,7 @@ public class CraftBlock implements Block { + + @Override + public Material getType() { +- return CraftBlockType.minecraftToBukkit(this.world.getBlockState(this.position).getBlock()); ++ return this.world.getBlockState(this.position).getBukkitMaterial(); // Paper - optimise getType calls + } + + @Override +diff --git a/src/main/java/org/bukkit/craftbukkit/block/CraftBlockState.java b/src/main/java/org/bukkit/craftbukkit/block/CraftBlockState.java +index fabdec2d66cc6d676ed58fa570e2c318ab0927e2..1002123cd0c6f57cecc4e80f5f21cc6ff5886d37 100644 +--- a/src/main/java/org/bukkit/craftbukkit/block/CraftBlockState.java ++++ b/src/main/java/org/bukkit/craftbukkit/block/CraftBlockState.java +@@ -175,7 +175,7 @@ public class CraftBlockState implements BlockState { + + @Override + public Material getType() { +- return CraftBlockType.minecraftToBukkit(this.data.getBlock()); ++ return this.data.getBukkitMaterial(); // Paper - optimise getType calls + } + + public void setFlag(int flag) { +diff --git a/src/main/java/org/bukkit/craftbukkit/block/data/CraftBlockData.java b/src/main/java/org/bukkit/craftbukkit/block/data/CraftBlockData.java +index f73858663162cb594db382d584b6500bb03e74b1..7f0d87bd43a54885fc521068116888083327f37e 100644 +--- a/src/main/java/org/bukkit/craftbukkit/block/data/CraftBlockData.java ++++ b/src/main/java/org/bukkit/craftbukkit/block/data/CraftBlockData.java +@@ -63,7 +63,7 @@ public class CraftBlockData implements BlockData { + + @Override + public Material getMaterial() { +- return CraftBlockType.minecraftToBukkit(this.state.getBlock()); ++ return this.state.getBukkitMaterial(); // Paper - optimise getType calls + } + + public net.minecraft.world.level.block.state.BlockState getState() { +diff --git a/src/main/java/org/bukkit/craftbukkit/generator/CraftChunkData.java b/src/main/java/org/bukkit/craftbukkit/generator/CraftChunkData.java +index c96aaa185d9d929cb19f427be82053f0cfa13bad..0fb580530d0b6d4d63ea4b85fec9240eb5c74df4 100644 +--- a/src/main/java/org/bukkit/craftbukkit/generator/CraftChunkData.java ++++ b/src/main/java/org/bukkit/craftbukkit/generator/CraftChunkData.java +@@ -96,7 +96,7 @@ public final class CraftChunkData implements ChunkGenerator.ChunkData { + + @Override + public Material getType(int x, int y, int z) { +- return CraftBlockType.minecraftToBukkit(this.getTypeId(x, y, z).getBlock()); ++ return this.getTypeId(x, y, z).getBukkitMaterial(); // Paper - optimise getType calls + } + + @Override diff --git a/patches/server/0416-Reset-Ender-Crystals-on-Dragon-Spawn.patch b/patches/server/0416-Reset-Ender-Crystals-on-Dragon-Spawn.patch deleted file mode 100644 index bc690f31a359..000000000000 --- a/patches/server/0416-Reset-Ender-Crystals-on-Dragon-Spawn.patch +++ /dev/null @@ -1,24 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Aikar -Date: Wed, 1 Jun 2016 23:29:17 -0400 -Subject: [PATCH] Reset Ender Crystals on Dragon Spawn - -Crystals can end up in a bad state in certain conditions which causes -an exception on the expected number of crystals going negative. - -This ensures the crystals/pillars are in expected state when the dragon spawns. - -See #3522 - -diff --git a/src/main/java/net/minecraft/world/level/dimension/end/EndDragonFight.java b/src/main/java/net/minecraft/world/level/dimension/end/EndDragonFight.java -index 93337fd026fefb76c8a288674fed05cb3c1eca38..a12299604ea88c268db5065191d641eb7d52c3e3 100644 ---- a/src/main/java/net/minecraft/world/level/dimension/end/EndDragonFight.java -+++ b/src/main/java/net/minecraft/world/level/dimension/end/EndDragonFight.java -@@ -474,6 +474,7 @@ public class EndDragonFight { - entityenderdragon.moveTo((double) this.origin.getX(), (double) (128 + this.origin.getY()), (double) this.origin.getZ(), this.level.random.nextFloat() * 360.0F, 0.0F); - this.level.addFreshEntity(entityenderdragon); - this.dragonUUID = entityenderdragon.getUUID(); -+ this.resetSpikeCrystals(); // Paper - Reset ender crystals on dragon spawn - } - - return entityenderdragon; diff --git a/patches/server/0416-Villager-resetOffers.patch b/patches/server/0416-Villager-resetOffers.patch new file mode 100644 index 000000000000..2b919a864638 --- /dev/null +++ b/patches/server/0416-Villager-resetOffers.patch @@ -0,0 +1,40 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: William Blake Galbreath +Date: Mon, 7 Oct 2019 00:15:37 -0500 +Subject: [PATCH] Villager#resetOffers + + +diff --git a/src/main/java/net/minecraft/world/entity/npc/AbstractVillager.java b/src/main/java/net/minecraft/world/entity/npc/AbstractVillager.java +index 3cdd48c35d50fa3107e0f2bf439e75b7f306b3d1..c8231e421e6c3f1cf8a5aaf8cc64741abe868f41 100644 +--- a/src/main/java/net/minecraft/world/entity/npc/AbstractVillager.java ++++ b/src/main/java/net/minecraft/world/entity/npc/AbstractVillager.java +@@ -114,6 +114,13 @@ public abstract class AbstractVillager extends AgeableMob implements InventoryCa + return this.tradingPlayer != null; + } + ++ // Paper start - Villager#resetOffers ++ public void resetOffers() { ++ this.offers = new MerchantOffers(); ++ this.updateTrades(); ++ } ++ // Paper end - Villager#resetOffers ++ + @Override + public MerchantOffers getOffers() { + if (this.level().isClientSide) { +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftAbstractVillager.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftAbstractVillager.java +index e5f733a765068b5640e811abf9fda945a9e91c7c..3199f04d00836a0a51547c679f3f3c80d00da502 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftAbstractVillager.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftAbstractVillager.java +@@ -34,4 +34,11 @@ public class CraftAbstractVillager extends CraftAgeable implements CraftMerchant + public Inventory getInventory() { + return new CraftInventory(this.getHandle().getInventory()); + } ++ ++ // Paper start - Villager#resetOffers ++ @Override ++ public void resetOffers() { ++ getHandle().resetOffers(); ++ } ++ // Paper end - Villager#resetOffers + } diff --git a/patches/server/0417-Fix-for-large-move-vectors-crashing-server.patch b/patches/server/0417-Fix-for-large-move-vectors-crashing-server.patch deleted file mode 100644 index 251671c2f14c..000000000000 --- a/patches/server/0417-Fix-for-large-move-vectors-crashing-server.patch +++ /dev/null @@ -1,86 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Spottedleaf -Date: Sun, 17 May 2020 23:47:33 -0700 -Subject: [PATCH] Fix for large move vectors crashing server - -Check movement distance also based on current position. - -diff --git a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java -index 82f49aad1d0ce2d62bf61aa634ebef3711d4d930..d33cf08f7c47111823abbce481505946db249577 100644 ---- a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java -+++ b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java -@@ -492,9 +492,9 @@ public class ServerGamePacketListenerImpl extends ServerCommonPacketListenerImpl - float prevYaw = this.player.getYRot(); - float prevPitch = this.player.getXRot(); - // CraftBukkit end -- double d0 = entity.getX(); -- double d1 = entity.getY(); -- double d2 = entity.getZ(); -+ double d0 = entity.getX();final double fromX = d0; // Paper - OBFHELPER -+ double d1 = entity.getY();final double fromY = d1; // Paper - OBFHELPER -+ double d2 = entity.getZ();final double fromZ = d2; // Paper - OBFHELPER - double d3 = ServerGamePacketListenerImpl.clampHorizontal(packet.getX()); final double toX = d3; // Paper - OBFHELPER - double d4 = ServerGamePacketListenerImpl.clampVertical(packet.getY()); final double toY = d4; // Paper - OBFHELPER - double d5 = ServerGamePacketListenerImpl.clampHorizontal(packet.getZ()); final double toZ = d5; // Paper - OBFHELPER -@@ -504,7 +504,16 @@ public class ServerGamePacketListenerImpl extends ServerCommonPacketListenerImpl - double d7 = d4 - this.vehicleFirstGoodY; - double d8 = d5 - this.vehicleFirstGoodZ; - double d9 = entity.getDeltaMovement().lengthSqr(); -- double d10 = d6 * d6 + d7 * d7 + d8 * d8; -+ // Paper start - fix large move vectors killing the server -+ double currDeltaX = toX - fromX; -+ double currDeltaY = toY - fromY; -+ double currDeltaZ = toZ - fromZ; -+ double d10 = Math.max(d6 * d6 + d7 * d7 + d8 * d8, (currDeltaX * currDeltaX + currDeltaY * currDeltaY + currDeltaZ * currDeltaZ) - 1); -+ double otherFieldX = d3 - this.vehicleLastGoodX; -+ double otherFieldY = d4 - this.vehicleLastGoodY - 1.0E-6D; -+ double otherFieldZ = d5 - this.vehicleLastGoodZ; -+ d10 = Math.max(d10, (otherFieldX * otherFieldX + otherFieldY * otherFieldY + otherFieldZ * otherFieldZ) - 1); -+ // Paper end - fix large move vectors killing the server - - // CraftBukkit start - handle custom speeds and skipped ticks - this.allowedPlayerTicks += (System.currentTimeMillis() / 50) - this.lastTick; -@@ -550,9 +559,9 @@ public class ServerGamePacketListenerImpl extends ServerCommonPacketListenerImpl - - boolean flag = worldserver.noCollision(entity, entity.getBoundingBox().deflate(0.0625D)); - -- d6 = d3 - this.vehicleLastGoodX; -- d7 = d4 - this.vehicleLastGoodY - 1.0E-6D; -- d8 = d5 - this.vehicleLastGoodZ; -+ d6 = d3 - this.vehicleLastGoodX; // Paper - diff on change, used for checking large move vectors above -+ d7 = d4 - this.vehicleLastGoodY - 1.0E-6D; // Paper - diff on change, used for checking large move vectors above -+ d8 = d5 - this.vehicleLastGoodZ; // Paper - diff on change, used for checking large move vectors above - boolean flag1 = entity.verticalCollisionBelow; - - if (entity instanceof LivingEntity) { -@@ -1253,7 +1262,16 @@ public class ServerGamePacketListenerImpl extends ServerCommonPacketListenerImpl - double d7 = d1 - this.firstGoodY; - double d8 = d2 - this.firstGoodZ; - double d9 = this.player.getDeltaMovement().lengthSqr(); -- double d10 = d6 * d6 + d7 * d7 + d8 * d8; -+ // Paper start - fix large move vectors killing the server -+ double currDeltaX = toX - prevX; -+ double currDeltaY = toY - prevY; -+ double currDeltaZ = toZ - prevZ; -+ double d10 = Math.max(d6 * d6 + d7 * d7 + d8 * d8, (currDeltaX * currDeltaX + currDeltaY * currDeltaY + currDeltaZ * currDeltaZ) - 1); -+ double otherFieldX = d0 - this.lastGoodX; -+ double otherFieldY = d1 - this.lastGoodY; -+ double otherFieldZ = d2 - this.lastGoodZ; -+ d10 = Math.max(d10, (otherFieldX * otherFieldX + otherFieldY * otherFieldY + otherFieldZ * otherFieldZ) - 1); -+ // Paper end - fix large move vectors killing the server - - if (this.player.isSleeping()) { - if (d10 > 1.0D) { -@@ -1309,9 +1327,9 @@ public class ServerGamePacketListenerImpl extends ServerCommonPacketListenerImpl - - AABB axisalignedbb = this.player.getBoundingBox(); - -- d6 = d0 - this.lastGoodX; -- d7 = d1 - this.lastGoodY; -- d8 = d2 - this.lastGoodZ; -+ d6 = d0 - this.lastGoodX; // Paper - diff on change, used for checking large move vectors above -+ d7 = d1 - this.lastGoodY; // Paper - diff on change, used for checking large move vectors above -+ d8 = d2 - this.lastGoodZ; // Paper - diff on change, used for checking large move vectors above - boolean flag1 = d7 > 0.0D; - - if (this.player.onGround() && !packet.isOnGround() && flag1) { diff --git a/patches/server/0417-Retain-block-place-order-when-capturing-blockstates.patch b/patches/server/0417-Retain-block-place-order-when-capturing-blockstates.patch new file mode 100644 index 000000000000..6da68ddaf25b --- /dev/null +++ b/patches/server/0417-Retain-block-place-order-when-capturing-blockstates.patch @@ -0,0 +1,24 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Spottedleaf +Date: Fri, 7 Aug 2020 04:27:56 -0700 +Subject: [PATCH] Retain block place order when capturing blockstates + +Fixes twisted vines not connecting properly when grown via +bonemeal by a player. + +In general, look at making this logic more robust (i.e properly handling +cases where a captured entry is overriden) - but for now this will do. + +diff --git a/src/main/java/net/minecraft/world/level/Level.java b/src/main/java/net/minecraft/world/level/Level.java +index 1e22ee380237a33c506316e3cfe3f6efb7f9ae4a..71e9c1504d4b85ffb695401974748d56fefb66e6 100644 +--- a/src/main/java/net/minecraft/world/level/Level.java ++++ b/src/main/java/net/minecraft/world/level/Level.java +@@ -153,7 +153,7 @@ public abstract class Level implements LevelAccessor, AutoCloseable { + public boolean captureBlockStates = false; + public boolean captureTreeGeneration = false; + public Map capturedBlockStates = new java.util.LinkedHashMap<>(); // Paper +- public Map capturedTileEntities = new HashMap<>(); ++ public Map capturedTileEntities = new java.util.LinkedHashMap<>(); // Paper - Retain block place order when capturing blockstates + public List captureDrops; + public final it.unimi.dsi.fastutil.objects.Object2LongOpenHashMap ticksPerSpawnCategory = new it.unimi.dsi.fastutil.objects.Object2LongOpenHashMap<>(); + public boolean populating; diff --git a/patches/server/0418-Fix-item-locations-dropped-from-campfires.patch b/patches/server/0418-Fix-item-locations-dropped-from-campfires.patch new file mode 100644 index 000000000000..df4949c1b82a --- /dev/null +++ b/patches/server/0418-Fix-item-locations-dropped-from-campfires.patch @@ -0,0 +1,28 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: William Blake Galbreath +Date: Sat, 3 Oct 2020 20:32:25 -0500 +Subject: [PATCH] Fix item locations dropped from campfires + +Fixes #4259 by not flooring the blockposition among other weirdness +Vanilla Issue: MC-267622 + +diff --git a/src/main/java/net/minecraft/world/level/block/entity/CampfireBlockEntity.java b/src/main/java/net/minecraft/world/level/block/entity/CampfireBlockEntity.java +index 0694b2a041c0012bbb7ab5c4c787c1e08db7757c..30035d534e144bf31f94073c57b0195be7e62772 100644 +--- a/src/main/java/net/minecraft/world/level/block/entity/CampfireBlockEntity.java ++++ b/src/main/java/net/minecraft/world/level/block/entity/CampfireBlockEntity.java +@@ -85,7 +85,14 @@ public class CampfireBlockEntity extends BlockEntity implements Clearable { + result = blockCookEvent.getResult(); + itemstack1 = CraftItemStack.asNMSCopy(result); + // CraftBukkit end +- Containers.dropItemStack(world, (double) pos.getX(), (double) pos.getY(), (double) pos.getZ(), itemstack1); ++ // Paper start - Fix item locations dropped from campfires ++ double deviation = 0.05F * RandomSource.GAUSSIAN_SPREAD_FACTOR; ++ while (!itemstack1.isEmpty()) { ++ net.minecraft.world.entity.item.ItemEntity droppedItem = new net.minecraft.world.entity.item.ItemEntity(world, pos.getX() + 0.5D, pos.getY() + 0.5D, pos.getZ() + 0.5D, itemstack1.split(world.random.nextInt(21) + 10)); ++ droppedItem.setDeltaMovement(world.random.triangle(0.0D, deviation), world.random.triangle(0.2D, deviation), world.random.triangle(0.0D, deviation)); ++ world.addFreshEntity(droppedItem); ++ } ++ // Paper end - Fix item locations dropped from campfires + blockEntity.items.set(i, ItemStack.EMPTY); + world.sendBlockUpdated(pos, state, state, 3); + world.gameEvent((Holder) GameEvent.BLOCK_CHANGE, pos, GameEvent.Context.of(state)); diff --git a/patches/server/0418-Optimise-getType-calls.patch b/patches/server/0418-Optimise-getType-calls.patch deleted file mode 100644 index 1b14b0202bb4..000000000000 --- a/patches/server/0418-Optimise-getType-calls.patch +++ /dev/null @@ -1,93 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Spottedleaf -Date: Wed, 3 Jun 2020 11:37:13 -0700 -Subject: [PATCH] Optimise getType calls - -Remove the map lookup for converting from Block->Bukkit Material - -diff --git a/src/main/java/net/minecraft/world/level/block/state/BlockState.java b/src/main/java/net/minecraft/world/level/block/state/BlockState.java -index 065d140ca4f987e14138a37f4c7d60879dd7b6e1..601135f3368272bf1ca3a43ec9c199e3ee838462 100644 ---- a/src/main/java/net/minecraft/world/level/block/state/BlockState.java -+++ b/src/main/java/net/minecraft/world/level/block/state/BlockState.java -@@ -10,6 +10,16 @@ import net.minecraft.world.level.block.state.properties.Property; - public class BlockState extends BlockBehaviour.BlockStateBase { - public static final Codec CODEC = codec(BuiltInRegistries.BLOCK.byNameCodec(), Block::defaultBlockState).stable(); - -+ // Paper start - optimise getType calls -+ org.bukkit.Material cachedMaterial; -+ -+ public final org.bukkit.Material getBukkitMaterial() { -+ if (this.cachedMaterial == null) { -+ this.cachedMaterial = org.bukkit.craftbukkit.block.CraftBlockType.minecraftToBukkit(this.getBlock()); -+ } -+ return this.cachedMaterial; -+ } -+ // Paper end - optimise getType calls - public BlockState(Block block, Reference2ObjectArrayMap, Comparable> propertyMap, MapCodec codec) { - super(block, propertyMap, codec); - } -diff --git a/src/main/java/org/bukkit/craftbukkit/CraftChunkSnapshot.java b/src/main/java/org/bukkit/craftbukkit/CraftChunkSnapshot.java -index f2ce97e46cdbda0f8960eed9b601c797d8eaef48..85029f1acfdbb411d9ebdf95838d6db3898f4e58 100644 ---- a/src/main/java/org/bukkit/craftbukkit/CraftChunkSnapshot.java -+++ b/src/main/java/org/bukkit/craftbukkit/CraftChunkSnapshot.java -@@ -99,7 +99,7 @@ public class CraftChunkSnapshot implements ChunkSnapshot { - public Material getBlockType(int x, int y, int z) { - this.validateChunkCoordinates(x, y, z); - -- return CraftBlockType.minecraftToBukkit(this.blockids[this.getSectionIndex(y)].get(x, y & 0xF, z).getBlock()); -+ return this.blockids[this.getSectionIndex(y)].get(x, y & 0xF, z).getBukkitMaterial(); // Paper - optimise getType calls - } - - @Override -diff --git a/src/main/java/org/bukkit/craftbukkit/block/CraftBlock.java b/src/main/java/org/bukkit/craftbukkit/block/CraftBlock.java -index a586442422a2b2c06b785af0d261d3e19eb1d59b..aa644231425b9622437538b5c092d4064a40cced 100644 ---- a/src/main/java/org/bukkit/craftbukkit/block/CraftBlock.java -+++ b/src/main/java/org/bukkit/craftbukkit/block/CraftBlock.java -@@ -220,7 +220,7 @@ public class CraftBlock implements Block { - - @Override - public Material getType() { -- return CraftBlockType.minecraftToBukkit(this.world.getBlockState(this.position).getBlock()); -+ return this.world.getBlockState(this.position).getBukkitMaterial(); // Paper - optimise getType calls - } - - @Override -diff --git a/src/main/java/org/bukkit/craftbukkit/block/CraftBlockState.java b/src/main/java/org/bukkit/craftbukkit/block/CraftBlockState.java -index fabdec2d66cc6d676ed58fa570e2c318ab0927e2..1002123cd0c6f57cecc4e80f5f21cc6ff5886d37 100644 ---- a/src/main/java/org/bukkit/craftbukkit/block/CraftBlockState.java -+++ b/src/main/java/org/bukkit/craftbukkit/block/CraftBlockState.java -@@ -175,7 +175,7 @@ public class CraftBlockState implements BlockState { - - @Override - public Material getType() { -- return CraftBlockType.minecraftToBukkit(this.data.getBlock()); -+ return this.data.getBukkitMaterial(); // Paper - optimise getType calls - } - - public void setFlag(int flag) { -diff --git a/src/main/java/org/bukkit/craftbukkit/block/data/CraftBlockData.java b/src/main/java/org/bukkit/craftbukkit/block/data/CraftBlockData.java -index 67ff2241aa8869b41abb0a93467b8694618264e2..9953b6b36cbcbfd1756bac478b568ca5700fc898 100644 ---- a/src/main/java/org/bukkit/craftbukkit/block/data/CraftBlockData.java -+++ b/src/main/java/org/bukkit/craftbukkit/block/data/CraftBlockData.java -@@ -61,7 +61,7 @@ public class CraftBlockData implements BlockData { - - @Override - public Material getMaterial() { -- return CraftBlockType.minecraftToBukkit(this.state.getBlock()); -+ return this.state.getBukkitMaterial(); // Paper - optimise getType calls - } - - public net.minecraft.world.level.block.state.BlockState getState() { -diff --git a/src/main/java/org/bukkit/craftbukkit/generator/CraftChunkData.java b/src/main/java/org/bukkit/craftbukkit/generator/CraftChunkData.java -index c96aaa185d9d929cb19f427be82053f0cfa13bad..0fb580530d0b6d4d63ea4b85fec9240eb5c74df4 100644 ---- a/src/main/java/org/bukkit/craftbukkit/generator/CraftChunkData.java -+++ b/src/main/java/org/bukkit/craftbukkit/generator/CraftChunkData.java -@@ -96,7 +96,7 @@ public final class CraftChunkData implements ChunkGenerator.ChunkData { - - @Override - public Material getType(int x, int y, int z) { -- return CraftBlockType.minecraftToBukkit(this.getTypeId(x, y, z).getBlock()); -+ return this.getTypeId(x, y, z).getBukkitMaterial(); // Paper - optimise getType calls - } - - @Override diff --git a/patches/server/0422-Fix-bell-block-entity-memory-leak.patch b/patches/server/0419-Fix-bell-block-entity-memory-leak.patch similarity index 100% rename from patches/server/0422-Fix-bell-block-entity-memory-leak.patch rename to patches/server/0419-Fix-bell-block-entity-memory-leak.patch diff --git a/patches/server/0419-Villager-resetOffers.patch b/patches/server/0419-Villager-resetOffers.patch deleted file mode 100644 index ce8f9949cd51..000000000000 --- a/patches/server/0419-Villager-resetOffers.patch +++ /dev/null @@ -1,40 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: William Blake Galbreath -Date: Mon, 7 Oct 2019 00:15:37 -0500 -Subject: [PATCH] Villager#resetOffers - - -diff --git a/src/main/java/net/minecraft/world/entity/npc/AbstractVillager.java b/src/main/java/net/minecraft/world/entity/npc/AbstractVillager.java -index 89e14bb2662fe03b4661aaa54fd65af41b1d438b..fcb3b66617150ad503bffe65de4900b1e3af8764 100644 ---- a/src/main/java/net/minecraft/world/entity/npc/AbstractVillager.java -+++ b/src/main/java/net/minecraft/world/entity/npc/AbstractVillager.java -@@ -114,6 +114,13 @@ public abstract class AbstractVillager extends AgeableMob implements InventoryCa - return this.tradingPlayer != null; - } - -+ // Paper start - Villager#resetOffers -+ public void resetOffers() { -+ this.offers = new MerchantOffers(); -+ this.updateTrades(); -+ } -+ // Paper end - Villager#resetOffers -+ - @Override - public MerchantOffers getOffers() { - if (this.level().isClientSide) { -diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftAbstractVillager.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftAbstractVillager.java -index e5f733a765068b5640e811abf9fda945a9e91c7c..3199f04d00836a0a51547c679f3f3c80d00da502 100644 ---- a/src/main/java/org/bukkit/craftbukkit/entity/CraftAbstractVillager.java -+++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftAbstractVillager.java -@@ -34,4 +34,11 @@ public class CraftAbstractVillager extends CraftAgeable implements CraftMerchant - public Inventory getInventory() { - return new CraftInventory(this.getHandle().getInventory()); - } -+ -+ // Paper start - Villager#resetOffers -+ @Override -+ public void resetOffers() { -+ getHandle().resetOffers(); -+ } -+ // Paper end - Villager#resetOffers - } diff --git a/patches/server/0420-Avoid-error-bubbling-up-when-item-stack-is-empty-in-.patch b/patches/server/0420-Avoid-error-bubbling-up-when-item-stack-is-empty-in-.patch new file mode 100644 index 000000000000..67b54defcacc --- /dev/null +++ b/patches/server/0420-Avoid-error-bubbling-up-when-item-stack-is-empty-in-.patch @@ -0,0 +1,46 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Toon Schoenmakers +Date: Fri, 23 Oct 2020 15:01:44 +0200 +Subject: [PATCH] Avoid error bubbling up when item stack is empty in fishing + loot + +This can realistically only happen if there's custom loot active on fishing +which can return 0 items. This would disconnect the player who's fishing. + +diff --git a/src/main/java/net/minecraft/world/entity/projectile/FishingHook.java b/src/main/java/net/minecraft/world/entity/projectile/FishingHook.java +index 9d24d4c3802c525546dae92530c9c5b3cf77924e..536196a740f607adda2a5ae7f644981ac26bef98 100644 +--- a/src/main/java/net/minecraft/world/entity/projectile/FishingHook.java ++++ b/src/main/java/net/minecraft/world/entity/projectile/FishingHook.java +@@ -511,9 +511,15 @@ public class FishingHook extends Projectile { + + while (iterator.hasNext()) { + ItemStack itemstack1 = (ItemStack) iterator.next(); +- ItemEntity entityitem = new ItemEntity(this.level(), this.getX(), this.getY(), this.getZ(), itemstack1); ++ // Paper start - new ItemEntity would throw if for whatever reason (mostly shitty datapacks) the itemstack1 turns out to be empty ++ // if the item stack is empty we instead just have our entityitem as null ++ ItemEntity entityitem = null; ++ if (!itemstack1.isEmpty()) { ++ entityitem = new ItemEntity(this.level(), this.getX(), this.getY(), this.getZ(), itemstack1); ++ } ++ // Paper end + // CraftBukkit start +- PlayerFishEvent playerFishEvent = new PlayerFishEvent((Player) entityhuman.getBukkitEntity(), entityitem.getBukkitEntity(), (FishHook) this.getBukkitEntity(), PlayerFishEvent.State.CAUGHT_FISH); ++ PlayerFishEvent playerFishEvent = new PlayerFishEvent((Player) entityhuman.getBukkitEntity(), entityitem != null ? entityitem.getBukkitEntity() : null, (FishHook) this.getBukkitEntity(), PlayerFishEvent.State.CAUGHT_FISH); // Paper - entityitem may be null + playerFishEvent.setExpToDrop(this.random.nextInt(6) + 1); + this.level().getCraftServer().getPluginManager().callEvent(playerFishEvent); + +@@ -526,8 +532,12 @@ public class FishingHook extends Projectile { + double d2 = entityhuman.getZ() - this.getZ(); + double d3 = 0.1D; + +- entityitem.setDeltaMovement(d0 * 0.1D, d1 * 0.1D + Math.sqrt(Math.sqrt(d0 * d0 + d1 * d1 + d2 * d2)) * 0.08D, d2 * 0.1D); +- this.level().addFreshEntity(entityitem); ++ // Paper start - entity item can be null, so we need to check against this ++ if (entityitem != null) { ++ entityitem.setDeltaMovement(d0 * 0.1D, d1 * 0.1D + Math.sqrt(Math.sqrt(d0 * d0 + d1 * d1 + d2 * d2)) * 0.08D, d2 * 0.1D); ++ this.level().addFreshEntity(entityitem); ++ } ++ // Paper end + // CraftBukkit start - this.random.nextInt(6) + 1 -> playerFishEvent.getExpToDrop() + if (playerFishEvent.getExpToDrop() > 0) { + entityhuman.level().addFreshEntity(new ExperienceOrb(entityhuman.level(), entityhuman.getX(), entityhuman.getY() + 0.5D, entityhuman.getZ() + 0.5D, playerFishEvent.getExpToDrop(), org.bukkit.entity.ExperienceOrb.SpawnReason.FISHING, this.getPlayerOwner(), this)); // Paper diff --git a/patches/server/0420-Retain-block-place-order-when-capturing-blockstates.patch b/patches/server/0420-Retain-block-place-order-when-capturing-blockstates.patch deleted file mode 100644 index bac1a1074bfb..000000000000 --- a/patches/server/0420-Retain-block-place-order-when-capturing-blockstates.patch +++ /dev/null @@ -1,24 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Spottedleaf -Date: Fri, 7 Aug 2020 04:27:56 -0700 -Subject: [PATCH] Retain block place order when capturing blockstates - -Fixes twisted vines not connecting properly when grown via -bonemeal by a player. - -In general, look at making this logic more robust (i.e properly handling -cases where a captured entry is overriden) - but for now this will do. - -diff --git a/src/main/java/net/minecraft/world/level/Level.java b/src/main/java/net/minecraft/world/level/Level.java -index 60b04a16c6cb0a7109bda5c16e23c1d56ab7afad..144d243e0d6ba3ae3f0b0bf457fa516e2b4f416f 100644 ---- a/src/main/java/net/minecraft/world/level/Level.java -+++ b/src/main/java/net/minecraft/world/level/Level.java -@@ -152,7 +152,7 @@ public abstract class Level implements LevelAccessor, AutoCloseable { - public boolean captureBlockStates = false; - public boolean captureTreeGeneration = false; - public Map capturedBlockStates = new java.util.LinkedHashMap<>(); // Paper -- public Map capturedTileEntities = new HashMap<>(); -+ public Map capturedTileEntities = new java.util.LinkedHashMap<>(); // Paper - Retain block place order when capturing blockstates - public List captureDrops; - public final it.unimi.dsi.fastutil.objects.Object2LongOpenHashMap ticksPerSpawnCategory = new it.unimi.dsi.fastutil.objects.Object2LongOpenHashMap<>(); - public boolean populating; diff --git a/patches/server/0421-Add-getOfflinePlayerIfCached-String.patch b/patches/server/0421-Add-getOfflinePlayerIfCached-String.patch new file mode 100644 index 000000000000..d20fbc6b6dae --- /dev/null +++ b/patches/server/0421-Add-getOfflinePlayerIfCached-String.patch @@ -0,0 +1,39 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: oxygencraft <21054297+oxygencraft@users.noreply.github.com> +Date: Sun, 25 Oct 2020 18:34:50 +1100 +Subject: [PATCH] Add getOfflinePlayerIfCached(String) + + +diff --git a/src/main/java/org/bukkit/craftbukkit/CraftServer.java b/src/main/java/org/bukkit/craftbukkit/CraftServer.java +index 134cd194962be898253e034b19524fad0d48ada5..30675a23a25dc065e09d97b9b08386c9f41989d8 100644 +--- a/src/main/java/org/bukkit/craftbukkit/CraftServer.java ++++ b/src/main/java/org/bukkit/craftbukkit/CraftServer.java +@@ -1968,6 +1968,28 @@ public final class CraftServer implements Server { + return result; + } + ++ // Paper start ++ @Override ++ @Nullable ++ public OfflinePlayer getOfflinePlayerIfCached(String name) { ++ Preconditions.checkArgument(name != null, "Name cannot be null"); ++ Preconditions.checkArgument(!name.isEmpty(), "Name cannot be empty"); ++ ++ OfflinePlayer result = getPlayerExact(name); ++ if (result == null) { ++ GameProfile profile = console.getProfileCache().getProfileIfCached(name); ++ ++ if (profile != null) { ++ result = getOfflinePlayer(profile); ++ } ++ } else { ++ offlinePlayers.remove(result.getUniqueId()); ++ } ++ ++ return result; ++ } ++ // Paper end ++ + @Override + public OfflinePlayer getOfflinePlayer(UUID id) { + Preconditions.checkArgument(id != null, "UUID id cannot be null"); diff --git a/patches/server/0421-Fix-item-locations-dropped-from-campfires.patch b/patches/server/0421-Fix-item-locations-dropped-from-campfires.patch deleted file mode 100644 index 26a43eb1862e..000000000000 --- a/patches/server/0421-Fix-item-locations-dropped-from-campfires.patch +++ /dev/null @@ -1,28 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: William Blake Galbreath -Date: Sat, 3 Oct 2020 20:32:25 -0500 -Subject: [PATCH] Fix item locations dropped from campfires - -Fixes #4259 by not flooring the blockposition among other weirdness -Vanilla Issue: MC-267622 - -diff --git a/src/main/java/net/minecraft/world/level/block/entity/CampfireBlockEntity.java b/src/main/java/net/minecraft/world/level/block/entity/CampfireBlockEntity.java -index 391acc9dadc653e9e1285a71b4f1e7c063e8ca49..80f911692c97585a696a19ebbe616d6aa312b2d9 100644 ---- a/src/main/java/net/minecraft/world/level/block/entity/CampfireBlockEntity.java -+++ b/src/main/java/net/minecraft/world/level/block/entity/CampfireBlockEntity.java -@@ -86,7 +86,14 @@ public class CampfireBlockEntity extends BlockEntity implements Clearable { - result = blockCookEvent.getResult(); - itemstack1 = CraftItemStack.asNMSCopy(result); - // CraftBukkit end -- Containers.dropItemStack(world, (double) pos.getX(), (double) pos.getY(), (double) pos.getZ(), itemstack1); -+ // Paper start - Fix item locations dropped from campfires -+ double deviation = 0.05F * RandomSource.GAUSSIAN_SPREAD_FACTOR; -+ while (!itemstack1.isEmpty()) { -+ net.minecraft.world.entity.item.ItemEntity droppedItem = new net.minecraft.world.entity.item.ItemEntity(world, pos.getX() + 0.5D, pos.getY() + 0.5D, pos.getZ() + 0.5D, itemstack1.split(world.random.nextInt(21) + 10)); -+ droppedItem.setDeltaMovement(world.random.triangle(0.0D, deviation), world.random.triangle(0.2D, deviation), world.random.triangle(0.0D, deviation)); -+ world.addFreshEntity(droppedItem); -+ } -+ // Paper end - Fix item locations dropped from campfires - campfire.items.set(i, ItemStack.EMPTY); - world.sendBlockUpdated(pos, state, state, 3); - world.gameEvent((Holder) GameEvent.BLOCK_CHANGE, pos, GameEvent.Context.of(state)); diff --git a/patches/server/0422-Add-ignore-discounts-API.patch b/patches/server/0422-Add-ignore-discounts-API.patch new file mode 100644 index 000000000000..84103c5a2e78 --- /dev/null +++ b/patches/server/0422-Add-ignore-discounts-API.patch @@ -0,0 +1,150 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Mariell Hoversholm +Date: Mon, 9 Nov 2020 20:44:51 +0100 +Subject: [PATCH] Add ignore discounts API + + +diff --git a/src/main/java/net/minecraft/world/entity/npc/Villager.java b/src/main/java/net/minecraft/world/entity/npc/Villager.java +index 82ed0ce824e84ea09ea963caa61fbb75f6ce6fe7..f51078e4b9e6267fa43795d009f5d149b86acb5a 100644 +--- a/src/main/java/net/minecraft/world/entity/npc/Villager.java ++++ b/src/main/java/net/minecraft/world/entity/npc/Villager.java +@@ -490,6 +490,7 @@ public class Villager extends AbstractVillager implements ReputationEventHandler + + while (iterator.hasNext()) { + MerchantOffer merchantrecipe = (MerchantOffer) iterator.next(); ++ if (merchantrecipe.ignoreDiscounts) continue; // Paper - Add ignore discounts API + + merchantrecipe.addToSpecialPriceDiff(-Mth.floor((float) i * merchantrecipe.getPriceMultiplier())); + } +@@ -502,6 +503,7 @@ public class Villager extends AbstractVillager implements ReputationEventHandler + + while (iterator1.hasNext()) { + MerchantOffer merchantrecipe1 = (MerchantOffer) iterator1.next(); ++ if (merchantrecipe1.ignoreDiscounts) continue; // Paper - Add ignore discounts API + double d0 = 0.3D + 0.0625D * (double) j; + int k = (int) Math.floor(d0 * (double) merchantrecipe1.getBaseCostA().getCount()); + +diff --git a/src/main/java/net/minecraft/world/item/trading/MerchantOffer.java b/src/main/java/net/minecraft/world/item/trading/MerchantOffer.java +index 89982d25f60c8b60ba91e559ef88278f338fe215..0efc8d997b34302c3e0a5d7ec73a11a940dbeefe 100644 +--- a/src/main/java/net/minecraft/world/item/trading/MerchantOffer.java ++++ b/src/main/java/net/minecraft/world/item/trading/MerchantOffer.java +@@ -33,6 +33,10 @@ public class MerchantOffer { + return merchantrecipe.priceMultiplier; + }), Codec.INT.lenientOptionalFieldOf("xp", 1).forGetter((merchantrecipe) -> { + return merchantrecipe.xp; ++ // Paper start ++ }), Codec.BOOL.lenientOptionalFieldOf("Paper.IgnoreDiscounts", false).forGetter((merchantrecipe) -> { ++ return merchantrecipe.ignoreDiscounts; ++ // Paper end + })).apply(instance, MerchantOffer::new); + }); + public static final StreamCodec STREAM_CODEC = StreamCodec.of(MerchantOffer::writeToStream, MerchantOffer::createFromStream); +@@ -46,6 +50,7 @@ public class MerchantOffer { + public int demand; + public float priceMultiplier; + public int xp; ++ public boolean ignoreDiscounts; // Paper - Add ignore discounts API + // CraftBukkit start + private CraftMerchantRecipe bukkitHandle; + +@@ -53,13 +58,14 @@ public class MerchantOffer { + return (this.bukkitHandle == null) ? this.bukkitHandle = new CraftMerchantRecipe(this) : this.bukkitHandle; + } + +- public MerchantOffer(ItemCost baseCostA, Optional costB, ItemStack result, int uses, int maxUses, int experience, float priceMultiplier, int demand, CraftMerchantRecipe bukkit) { ++ public MerchantOffer(ItemCost baseCostA, Optional costB, ItemStack result, int uses, int maxUses, int experience, float priceMultiplier, int demand, final boolean ignoreDiscounts, CraftMerchantRecipe bukkit) { // Paper + this(baseCostA, costB, result, uses, maxUses, experience, priceMultiplier, demand); ++ this.ignoreDiscounts = ignoreDiscounts; // Paper + this.bukkitHandle = bukkit; + } + // CraftBukkit end + +- private MerchantOffer(ItemCost firstBuyItem, Optional secondBuyItem, ItemStack sellItem, int uses, int maxUses, boolean rewardingPlayerExperience, int specialPrice, int demandBonus, float priceMultiplier, int merchantExperience) { ++ private MerchantOffer(ItemCost firstBuyItem, Optional secondBuyItem, ItemStack sellItem, int uses, int maxUses, boolean rewardingPlayerExperience, int specialPrice, int demandBonus, float priceMultiplier, int merchantExperience, final boolean ignoreDiscounts) { // Paper + this.baseCostA = firstBuyItem; + this.costB = secondBuyItem; + this.result = sellItem; +@@ -70,6 +76,7 @@ public class MerchantOffer { + this.demand = demandBonus; + this.priceMultiplier = priceMultiplier; + this.xp = merchantExperience; ++ this.ignoreDiscounts = ignoreDiscounts; // Paper + } + + public MerchantOffer(ItemCost buyItem, ItemStack sellItem, int maxUses, int merchantExperience, float priceMultiplier) { +@@ -85,11 +92,11 @@ public class MerchantOffer { + } + + public MerchantOffer(ItemCost firstBuyItem, Optional secondBuyItem, ItemStack sellItem, int uses, int maxUses, int merchantExperience, float priceMultiplier, int demandBonus) { +- this(firstBuyItem, secondBuyItem, sellItem, uses, maxUses, true, 0, demandBonus, priceMultiplier, merchantExperience); ++ this(firstBuyItem, secondBuyItem, sellItem, uses, maxUses, true, 0, demandBonus, priceMultiplier, merchantExperience, false); // Paper + } + + private MerchantOffer(MerchantOffer offer) { +- this(offer.baseCostA, offer.costB, offer.result.copy(), offer.uses, offer.maxUses, offer.rewardExp, offer.specialPriceDiff, offer.demand, offer.priceMultiplier, offer.xp); ++ this(offer.baseCostA, offer.costB, offer.result.copy(), offer.uses, offer.maxUses, offer.rewardExp, offer.specialPriceDiff, offer.demand, offer.priceMultiplier, offer.xp, offer.ignoreDiscounts); // Paper + } + + public ItemStack getBaseCostA() { +diff --git a/src/main/java/org/bukkit/craftbukkit/inventory/CraftMerchantRecipe.java b/src/main/java/org/bukkit/craftbukkit/inventory/CraftMerchantRecipe.java +index bc1a92707c65474c1464d6f7c3a3265df6195228..e86cee25703a3c02ef62e302816253c360d557f3 100644 +--- a/src/main/java/org/bukkit/craftbukkit/inventory/CraftMerchantRecipe.java ++++ b/src/main/java/org/bukkit/craftbukkit/inventory/CraftMerchantRecipe.java +@@ -24,11 +24,19 @@ public class CraftMerchantRecipe extends MerchantRecipe { + + @Deprecated + public CraftMerchantRecipe(ItemStack result, int uses, int maxUses, boolean experienceReward, int experience, float priceMultiplier) { +- this(result, uses, maxUses, experienceReward, experience, priceMultiplier, 0, 0); ++ // Paper start - add ignoreDiscounts param ++ this(result, uses, maxUses, experienceReward, experience, priceMultiplier, 0, 0, false); ++ } ++ public CraftMerchantRecipe(ItemStack result, int uses, int maxUses, boolean experienceReward, int experience, float priceMultiplier, boolean ignoreDiscounts) { ++ this(result, uses, maxUses, experienceReward, experience, priceMultiplier, 0, 0, ignoreDiscounts); + } + + public CraftMerchantRecipe(ItemStack result, int uses, int maxUses, boolean experienceReward, int experience, float priceMultiplier, int demand, int specialPrice) { +- super(result, uses, maxUses, experienceReward, experience, priceMultiplier, demand, specialPrice); ++ this(result, uses, maxUses, experienceReward, experience, priceMultiplier, demand, specialPrice, false); ++ } ++ public CraftMerchantRecipe(ItemStack result, int uses, int maxUses, boolean experienceReward, int experience, float priceMultiplier, int demand, int specialPrice, boolean ignoreDiscounts) { ++ super(result, uses, maxUses, experienceReward, experience, priceMultiplier, demand, specialPrice, ignoreDiscounts); ++ // Paper end + this.handle = new net.minecraft.world.item.trading.MerchantOffer( + new ItemCost(Items.AIR), + Optional.empty(), +@@ -38,6 +46,7 @@ public class CraftMerchantRecipe extends MerchantRecipe { + experience, + priceMultiplier, + demand, ++ ignoreDiscounts, // Paper - add ignoreDiscounts param + this + ); + this.setSpecialPrice(specialPrice); +@@ -114,6 +123,18 @@ public class CraftMerchantRecipe extends MerchantRecipe { + this.handle.priceMultiplier = priceMultiplier; + } + ++ // Paper start ++ @Override ++ public boolean shouldIgnoreDiscounts() { ++ return this.handle.ignoreDiscounts; ++ } ++ ++ @Override ++ public void setIgnoreDiscounts(boolean ignoreDiscounts) { ++ this.handle.ignoreDiscounts = ignoreDiscounts; ++ } ++ // Paper end ++ + public net.minecraft.world.item.trading.MerchantOffer toMinecraft() { + List ingredients = this.getIngredients(); + Preconditions.checkState(!ingredients.isEmpty(), "No offered ingredients"); +@@ -134,7 +155,7 @@ public class CraftMerchantRecipe extends MerchantRecipe { + if (recipe instanceof CraftMerchantRecipe) { + return (CraftMerchantRecipe) recipe; + } else { +- CraftMerchantRecipe craft = new CraftMerchantRecipe(recipe.getResult(), recipe.getUses(), recipe.getMaxUses(), recipe.hasExperienceReward(), recipe.getVillagerExperience(), recipe.getPriceMultiplier(), recipe.getDemand(), recipe.getSpecialPrice()); ++ CraftMerchantRecipe craft = new CraftMerchantRecipe(recipe.getResult(), recipe.getUses(), recipe.getMaxUses(), recipe.hasExperienceReward(), recipe.getVillagerExperience(), recipe.getPriceMultiplier(), recipe.getDemand(), recipe.getSpecialPrice(), recipe.shouldIgnoreDiscounts()); // Paper - shouldIgnoreDiscounts + craft.setIngredients(recipe.getIngredients()); + + return craft; diff --git a/patches/server/0423-Avoid-error-bubbling-up-when-item-stack-is-empty-in-.patch b/patches/server/0423-Avoid-error-bubbling-up-when-item-stack-is-empty-in-.patch deleted file mode 100644 index 6ab78855c47b..000000000000 --- a/patches/server/0423-Avoid-error-bubbling-up-when-item-stack-is-empty-in-.patch +++ /dev/null @@ -1,46 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Toon Schoenmakers -Date: Fri, 23 Oct 2020 15:01:44 +0200 -Subject: [PATCH] Avoid error bubbling up when item stack is empty in fishing - loot - -This can realistically only happen if there's custom loot active on fishing -which can return 0 items. This would disconnect the player who's fishing. - -diff --git a/src/main/java/net/minecraft/world/entity/projectile/FishingHook.java b/src/main/java/net/minecraft/world/entity/projectile/FishingHook.java -index 0b4c67b9de6893601f032a8fae103e8a98f2c767..5b7734020b496ade3740d92908ad2d399bfd55e6 100644 ---- a/src/main/java/net/minecraft/world/entity/projectile/FishingHook.java -+++ b/src/main/java/net/minecraft/world/entity/projectile/FishingHook.java -@@ -506,9 +506,15 @@ public class FishingHook extends Projectile { - - while (iterator.hasNext()) { - ItemStack itemstack1 = (ItemStack) iterator.next(); -- ItemEntity entityitem = new ItemEntity(this.level(), this.getX(), this.getY(), this.getZ(), itemstack1); -+ // Paper start - new ItemEntity would throw if for whatever reason (mostly shitty datapacks) the itemstack1 turns out to be empty -+ // if the item stack is empty we instead just have our entityitem as null -+ ItemEntity entityitem = null; -+ if (!itemstack1.isEmpty()) { -+ entityitem = new ItemEntity(this.level(), this.getX(), this.getY(), this.getZ(), itemstack1); -+ } -+ // Paper end - // CraftBukkit start -- PlayerFishEvent playerFishEvent = new PlayerFishEvent((Player) entityhuman.getBukkitEntity(), entityitem.getBukkitEntity(), (FishHook) this.getBukkitEntity(), PlayerFishEvent.State.CAUGHT_FISH); -+ PlayerFishEvent playerFishEvent = new PlayerFishEvent((Player) entityhuman.getBukkitEntity(), entityitem != null ? entityitem.getBukkitEntity() : null, (FishHook) this.getBukkitEntity(), PlayerFishEvent.State.CAUGHT_FISH); // Paper - entityitem may be null - playerFishEvent.setExpToDrop(this.random.nextInt(6) + 1); - this.level().getCraftServer().getPluginManager().callEvent(playerFishEvent); - -@@ -521,8 +527,12 @@ public class FishingHook extends Projectile { - double d2 = entityhuman.getZ() - this.getZ(); - double d3 = 0.1D; - -- entityitem.setDeltaMovement(d0 * 0.1D, d1 * 0.1D + Math.sqrt(Math.sqrt(d0 * d0 + d1 * d1 + d2 * d2)) * 0.08D, d2 * 0.1D); -- this.level().addFreshEntity(entityitem); -+ // Paper start - entity item can be null, so we need to check against this -+ if (entityitem != null) { -+ entityitem.setDeltaMovement(d0 * 0.1D, d1 * 0.1D + Math.sqrt(Math.sqrt(d0 * d0 + d1 * d1 + d2 * d2)) * 0.08D, d2 * 0.1D); -+ this.level().addFreshEntity(entityitem); -+ } -+ // Paper end - // CraftBukkit start - this.random.nextInt(6) + 1 -> playerFishEvent.getExpToDrop() - if (playerFishEvent.getExpToDrop() > 0) { - entityhuman.level().addFreshEntity(new ExperienceOrb(entityhuman.level(), entityhuman.getX(), entityhuman.getY() + 0.5D, entityhuman.getZ() + 0.5D, playerFishEvent.getExpToDrop(), org.bukkit.entity.ExperienceOrb.SpawnReason.FISHING, this.getPlayerOwner(), this)); // Paper diff --git a/patches/server/0423-Toggle-for-removing-existing-dragon.patch b/patches/server/0423-Toggle-for-removing-existing-dragon.patch new file mode 100644 index 000000000000..5efff5ff3f38 --- /dev/null +++ b/patches/server/0423-Toggle-for-removing-existing-dragon.patch @@ -0,0 +1,19 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Mariell Hoversholm +Date: Wed, 30 Sep 2020 22:49:14 +0200 +Subject: [PATCH] Toggle for removing existing dragon + + +diff --git a/src/main/java/net/minecraft/world/level/dimension/end/EndDragonFight.java b/src/main/java/net/minecraft/world/level/dimension/end/EndDragonFight.java +index 0f34a5ce0352f627099a719b78e530e5e572581a..323fbf684b7062c1b9084f1718538a3b3414d5bf 100644 +--- a/src/main/java/net/minecraft/world/level/dimension/end/EndDragonFight.java ++++ b/src/main/java/net/minecraft/world/level/dimension/end/EndDragonFight.java +@@ -212,7 +212,7 @@ public class EndDragonFight { + this.dragonUUID = entityenderdragon.getUUID(); + EndDragonFight.LOGGER.info("Found that there's a dragon still alive ({})", entityenderdragon); + this.dragonKilled = false; +- if (!flag) { ++ if (!flag && this.level.paperConfig().entities.behavior.shouldRemoveDragon) { // Paper - Toggle for removing existing dragon + EndDragonFight.LOGGER.info("But we didn't have a portal, let's remove it."); + entityenderdragon.discard(null); // CraftBukkit - add Bukkit remove cause + this.dragonUUID = null; diff --git a/patches/server/0424-Add-getOfflinePlayerIfCached-String.patch b/patches/server/0424-Add-getOfflinePlayerIfCached-String.patch deleted file mode 100644 index 5eeace36f69d..000000000000 --- a/patches/server/0424-Add-getOfflinePlayerIfCached-String.patch +++ /dev/null @@ -1,39 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: oxygencraft <21054297+oxygencraft@users.noreply.github.com> -Date: Sun, 25 Oct 2020 18:34:50 +1100 -Subject: [PATCH] Add getOfflinePlayerIfCached(String) - - -diff --git a/src/main/java/org/bukkit/craftbukkit/CraftServer.java b/src/main/java/org/bukkit/craftbukkit/CraftServer.java -index 42ec4aed187b41729a3c985ae440097db0388d3c..25bfb93568ea0a6c0b827c6d6a736f950981144e 100644 ---- a/src/main/java/org/bukkit/craftbukkit/CraftServer.java -+++ b/src/main/java/org/bukkit/craftbukkit/CraftServer.java -@@ -1965,6 +1965,28 @@ public final class CraftServer implements Server { - return result; - } - -+ // Paper start -+ @Override -+ @Nullable -+ public OfflinePlayer getOfflinePlayerIfCached(String name) { -+ Preconditions.checkArgument(name != null, "Name cannot be null"); -+ Preconditions.checkArgument(!name.isEmpty(), "Name cannot be empty"); -+ -+ OfflinePlayer result = getPlayerExact(name); -+ if (result == null) { -+ GameProfile profile = console.getProfileCache().getProfileIfCached(name); -+ -+ if (profile != null) { -+ result = getOfflinePlayer(profile); -+ } -+ } else { -+ offlinePlayers.remove(result.getUniqueId()); -+ } -+ -+ return result; -+ } -+ // Paper end -+ - @Override - public OfflinePlayer getOfflinePlayer(UUID id) { - Preconditions.checkArgument(id != null, "UUID id cannot be null"); diff --git a/patches/server/0424-Fix-client-lag-on-advancement-loading.patch b/patches/server/0424-Fix-client-lag-on-advancement-loading.patch new file mode 100644 index 000000000000..67c377a6f1a2 --- /dev/null +++ b/patches/server/0424-Fix-client-lag-on-advancement-loading.patch @@ -0,0 +1,35 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Jason Penilla <11360596+jpenilla@users.noreply.github.com> +Date: Sat, 31 Oct 2020 11:49:01 -0700 +Subject: [PATCH] Fix client lag on advancement loading + +When new advancements are added via the UnsafeValues#loadAdvancement +API, it triggers a full datapack reload when this is not necessary. The +advancement is already loaded directly into the advancement registry, +and the point of saving the advancement to the Bukkit datapack seems to +be for persistence. By removing the call to reload datapacks when an +advancement is loaded, the client no longer completely freezes up when +adding a new advancement. +To ensure the client still receives the updated advancement data, we +manually reload the advancement data for all players, which +normally takes place as a part of the datapack reloading. + +diff --git a/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java b/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java +index 93505eba28d9fb20ce25866e932fc1cdcb006db8..af8fde74b3162d2de740ecae1122b4f2115baeb6 100644 +--- a/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java ++++ b/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java +@@ -317,7 +317,13 @@ public final class CraftMagicNumbers implements UnsafeValues { + Bukkit.getLogger().log(Level.SEVERE, "Error saving advancement " + key, ex); + } + +- MinecraftServer.getServer().getPlayerList().reloadResources(); ++ // Paper start - Fix client lag on advancement loading ++ //MinecraftServer.getServer().getPlayerList().reload(); ++ MinecraftServer.getServer().getPlayerList().getPlayers().forEach(player -> { ++ player.getAdvancements().reload(MinecraftServer.getServer().getAdvancements()); ++ player.getAdvancements().flushDirty(player); ++ }); ++ // Paper end - Fix client lag on advancement loading + + return bukkit; + } diff --git a/patches/server/0425-Add-ignore-discounts-API.patch b/patches/server/0425-Add-ignore-discounts-API.patch deleted file mode 100644 index b2ed91d9bf4b..000000000000 --- a/patches/server/0425-Add-ignore-discounts-API.patch +++ /dev/null @@ -1,150 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Mariell Hoversholm -Date: Mon, 9 Nov 2020 20:44:51 +0100 -Subject: [PATCH] Add ignore discounts API - - -diff --git a/src/main/java/net/minecraft/world/entity/npc/Villager.java b/src/main/java/net/minecraft/world/entity/npc/Villager.java -index e23674dd5db3c429efd3b7c71fe36b420494c03a..9d5a5a7fff7f75871e167f83edb0e9d5348748d7 100644 ---- a/src/main/java/net/minecraft/world/entity/npc/Villager.java -+++ b/src/main/java/net/minecraft/world/entity/npc/Villager.java -@@ -493,6 +493,7 @@ public class Villager extends AbstractVillager implements ReputationEventHandler - - while (iterator.hasNext()) { - MerchantOffer merchantrecipe = (MerchantOffer) iterator.next(); -+ if (merchantrecipe.ignoreDiscounts) continue; // Paper - Add ignore discounts API - - merchantrecipe.addToSpecialPriceDiff(-Mth.floor((float) i * merchantrecipe.getPriceMultiplier())); - } -@@ -505,6 +506,7 @@ public class Villager extends AbstractVillager implements ReputationEventHandler - - while (iterator1.hasNext()) { - MerchantOffer merchantrecipe1 = (MerchantOffer) iterator1.next(); -+ if (merchantrecipe1.ignoreDiscounts) continue; // Paper - Add ignore discounts API - double d0 = 0.3D + 0.0625D * (double) j; - int k = (int) Math.floor(d0 * (double) merchantrecipe1.getBaseCostA().getCount()); - -diff --git a/src/main/java/net/minecraft/world/item/trading/MerchantOffer.java b/src/main/java/net/minecraft/world/item/trading/MerchantOffer.java -index 89982d25f60c8b60ba91e559ef88278f338fe215..0efc8d997b34302c3e0a5d7ec73a11a940dbeefe 100644 ---- a/src/main/java/net/minecraft/world/item/trading/MerchantOffer.java -+++ b/src/main/java/net/minecraft/world/item/trading/MerchantOffer.java -@@ -33,6 +33,10 @@ public class MerchantOffer { - return merchantrecipe.priceMultiplier; - }), Codec.INT.lenientOptionalFieldOf("xp", 1).forGetter((merchantrecipe) -> { - return merchantrecipe.xp; -+ // Paper start -+ }), Codec.BOOL.lenientOptionalFieldOf("Paper.IgnoreDiscounts", false).forGetter((merchantrecipe) -> { -+ return merchantrecipe.ignoreDiscounts; -+ // Paper end - })).apply(instance, MerchantOffer::new); - }); - public static final StreamCodec STREAM_CODEC = StreamCodec.of(MerchantOffer::writeToStream, MerchantOffer::createFromStream); -@@ -46,6 +50,7 @@ public class MerchantOffer { - public int demand; - public float priceMultiplier; - public int xp; -+ public boolean ignoreDiscounts; // Paper - Add ignore discounts API - // CraftBukkit start - private CraftMerchantRecipe bukkitHandle; - -@@ -53,13 +58,14 @@ public class MerchantOffer { - return (this.bukkitHandle == null) ? this.bukkitHandle = new CraftMerchantRecipe(this) : this.bukkitHandle; - } - -- public MerchantOffer(ItemCost baseCostA, Optional costB, ItemStack result, int uses, int maxUses, int experience, float priceMultiplier, int demand, CraftMerchantRecipe bukkit) { -+ public MerchantOffer(ItemCost baseCostA, Optional costB, ItemStack result, int uses, int maxUses, int experience, float priceMultiplier, int demand, final boolean ignoreDiscounts, CraftMerchantRecipe bukkit) { // Paper - this(baseCostA, costB, result, uses, maxUses, experience, priceMultiplier, demand); -+ this.ignoreDiscounts = ignoreDiscounts; // Paper - this.bukkitHandle = bukkit; - } - // CraftBukkit end - -- private MerchantOffer(ItemCost firstBuyItem, Optional secondBuyItem, ItemStack sellItem, int uses, int maxUses, boolean rewardingPlayerExperience, int specialPrice, int demandBonus, float priceMultiplier, int merchantExperience) { -+ private MerchantOffer(ItemCost firstBuyItem, Optional secondBuyItem, ItemStack sellItem, int uses, int maxUses, boolean rewardingPlayerExperience, int specialPrice, int demandBonus, float priceMultiplier, int merchantExperience, final boolean ignoreDiscounts) { // Paper - this.baseCostA = firstBuyItem; - this.costB = secondBuyItem; - this.result = sellItem; -@@ -70,6 +76,7 @@ public class MerchantOffer { - this.demand = demandBonus; - this.priceMultiplier = priceMultiplier; - this.xp = merchantExperience; -+ this.ignoreDiscounts = ignoreDiscounts; // Paper - } - - public MerchantOffer(ItemCost buyItem, ItemStack sellItem, int maxUses, int merchantExperience, float priceMultiplier) { -@@ -85,11 +92,11 @@ public class MerchantOffer { - } - - public MerchantOffer(ItemCost firstBuyItem, Optional secondBuyItem, ItemStack sellItem, int uses, int maxUses, int merchantExperience, float priceMultiplier, int demandBonus) { -- this(firstBuyItem, secondBuyItem, sellItem, uses, maxUses, true, 0, demandBonus, priceMultiplier, merchantExperience); -+ this(firstBuyItem, secondBuyItem, sellItem, uses, maxUses, true, 0, demandBonus, priceMultiplier, merchantExperience, false); // Paper - } - - private MerchantOffer(MerchantOffer offer) { -- this(offer.baseCostA, offer.costB, offer.result.copy(), offer.uses, offer.maxUses, offer.rewardExp, offer.specialPriceDiff, offer.demand, offer.priceMultiplier, offer.xp); -+ this(offer.baseCostA, offer.costB, offer.result.copy(), offer.uses, offer.maxUses, offer.rewardExp, offer.specialPriceDiff, offer.demand, offer.priceMultiplier, offer.xp, offer.ignoreDiscounts); // Paper - } - - public ItemStack getBaseCostA() { -diff --git a/src/main/java/org/bukkit/craftbukkit/inventory/CraftMerchantRecipe.java b/src/main/java/org/bukkit/craftbukkit/inventory/CraftMerchantRecipe.java -index bc1a92707c65474c1464d6f7c3a3265df6195228..e86cee25703a3c02ef62e302816253c360d557f3 100644 ---- a/src/main/java/org/bukkit/craftbukkit/inventory/CraftMerchantRecipe.java -+++ b/src/main/java/org/bukkit/craftbukkit/inventory/CraftMerchantRecipe.java -@@ -24,11 +24,19 @@ public class CraftMerchantRecipe extends MerchantRecipe { - - @Deprecated - public CraftMerchantRecipe(ItemStack result, int uses, int maxUses, boolean experienceReward, int experience, float priceMultiplier) { -- this(result, uses, maxUses, experienceReward, experience, priceMultiplier, 0, 0); -+ // Paper start - add ignoreDiscounts param -+ this(result, uses, maxUses, experienceReward, experience, priceMultiplier, 0, 0, false); -+ } -+ public CraftMerchantRecipe(ItemStack result, int uses, int maxUses, boolean experienceReward, int experience, float priceMultiplier, boolean ignoreDiscounts) { -+ this(result, uses, maxUses, experienceReward, experience, priceMultiplier, 0, 0, ignoreDiscounts); - } - - public CraftMerchantRecipe(ItemStack result, int uses, int maxUses, boolean experienceReward, int experience, float priceMultiplier, int demand, int specialPrice) { -- super(result, uses, maxUses, experienceReward, experience, priceMultiplier, demand, specialPrice); -+ this(result, uses, maxUses, experienceReward, experience, priceMultiplier, demand, specialPrice, false); -+ } -+ public CraftMerchantRecipe(ItemStack result, int uses, int maxUses, boolean experienceReward, int experience, float priceMultiplier, int demand, int specialPrice, boolean ignoreDiscounts) { -+ super(result, uses, maxUses, experienceReward, experience, priceMultiplier, demand, specialPrice, ignoreDiscounts); -+ // Paper end - this.handle = new net.minecraft.world.item.trading.MerchantOffer( - new ItemCost(Items.AIR), - Optional.empty(), -@@ -38,6 +46,7 @@ public class CraftMerchantRecipe extends MerchantRecipe { - experience, - priceMultiplier, - demand, -+ ignoreDiscounts, // Paper - add ignoreDiscounts param - this - ); - this.setSpecialPrice(specialPrice); -@@ -114,6 +123,18 @@ public class CraftMerchantRecipe extends MerchantRecipe { - this.handle.priceMultiplier = priceMultiplier; - } - -+ // Paper start -+ @Override -+ public boolean shouldIgnoreDiscounts() { -+ return this.handle.ignoreDiscounts; -+ } -+ -+ @Override -+ public void setIgnoreDiscounts(boolean ignoreDiscounts) { -+ this.handle.ignoreDiscounts = ignoreDiscounts; -+ } -+ // Paper end -+ - public net.minecraft.world.item.trading.MerchantOffer toMinecraft() { - List ingredients = this.getIngredients(); - Preconditions.checkState(!ingredients.isEmpty(), "No offered ingredients"); -@@ -134,7 +155,7 @@ public class CraftMerchantRecipe extends MerchantRecipe { - if (recipe instanceof CraftMerchantRecipe) { - return (CraftMerchantRecipe) recipe; - } else { -- CraftMerchantRecipe craft = new CraftMerchantRecipe(recipe.getResult(), recipe.getUses(), recipe.getMaxUses(), recipe.hasExperienceReward(), recipe.getVillagerExperience(), recipe.getPriceMultiplier(), recipe.getDemand(), recipe.getSpecialPrice()); -+ CraftMerchantRecipe craft = new CraftMerchantRecipe(recipe.getResult(), recipe.getUses(), recipe.getMaxUses(), recipe.hasExperienceReward(), recipe.getVillagerExperience(), recipe.getPriceMultiplier(), recipe.getDemand(), recipe.getSpecialPrice(), recipe.shouldIgnoreDiscounts()); // Paper - shouldIgnoreDiscounts - craft.setIngredients(recipe.getIngredients()); - - return craft; diff --git a/patches/server/0428-Item-no-age-no-player-pickup.patch b/patches/server/0425-Item-no-age-no-player-pickup.patch similarity index 100% rename from patches/server/0428-Item-no-age-no-player-pickup.patch rename to patches/server/0425-Item-no-age-no-player-pickup.patch diff --git a/patches/server/0426-Beacon-API-custom-effect-ranges.patch b/patches/server/0426-Beacon-API-custom-effect-ranges.patch new file mode 100644 index 000000000000..6afe0e8f7ce2 --- /dev/null +++ b/patches/server/0426-Beacon-API-custom-effect-ranges.patch @@ -0,0 +1,131 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Jake Potrebic +Date: Wed, 24 Jun 2020 12:39:08 -0600 +Subject: [PATCH] Beacon API - custom effect ranges + + +diff --git a/src/main/java/net/minecraft/world/level/block/entity/BeaconBlockEntity.java b/src/main/java/net/minecraft/world/level/block/entity/BeaconBlockEntity.java +index 8332296663b845df1d09d403b49a4769b2d54afc..dc3171b1493d7c4c8ddf1c79587c4e27bd819c17 100644 +--- a/src/main/java/net/minecraft/world/level/block/entity/BeaconBlockEntity.java ++++ b/src/main/java/net/minecraft/world/level/block/entity/BeaconBlockEntity.java +@@ -86,6 +86,26 @@ public class BeaconBlockEntity extends BlockEntity implements MenuProvider, Name + return (BeaconBlockEntity.hasSecondaryEffect(this.levels, this.primaryPower, this.secondaryPower)) ? CraftPotionUtil.toBukkit(new MobEffectInstance(this.secondaryPower, BeaconBlockEntity.getLevel(this.levels), BeaconBlockEntity.getAmplification(this.levels, this.primaryPower, this.secondaryPower), true, true)) : null; + } + // CraftBukkit end ++ // Paper start - Custom beacon ranges ++ private final String PAPER_RANGE_TAG = "Paper.Range"; ++ private double effectRange = -1; ++ ++ public double getEffectRange() { ++ if (this.effectRange < 0) { ++ return this.levels * 10 + 10; ++ } else { ++ return effectRange; ++ } ++ } ++ ++ public void setEffectRange(double range) { ++ this.effectRange = range; ++ } ++ ++ public void resetEffectRange() { ++ this.effectRange = -1; ++ } ++ // Paper end - Custom beacon ranges + + @Nullable + static Holder filterEffect(@Nullable Holder effect) { +@@ -201,7 +221,7 @@ public class BeaconBlockEntity extends BlockEntity implements MenuProvider, Name + } + + if (blockEntity.levels > 0 && !blockEntity.beamSections.isEmpty()) { +- BeaconBlockEntity.applyEffects(world, pos, blockEntity.levels, blockEntity.primaryPower, blockEntity.secondaryPower); ++ BeaconBlockEntity.applyEffects(world, pos, blockEntity.levels, blockEntity.primaryPower, blockEntity.secondaryPower, blockEntity); // Paper - Custom beacon ranges + BeaconBlockEntity.playSound(world, pos, SoundEvents.BEACON_AMBIENT); + } + } +@@ -287,8 +307,13 @@ public class BeaconBlockEntity extends BlockEntity implements MenuProvider, Name + } + + public static List getHumansInRange(Level world, BlockPos blockposition, int i) { ++ // Paper start - Custom beacon ranges ++ return BeaconBlockEntity.getHumansInRange(world, blockposition, i, null); ++ } ++ public static List getHumansInRange(Level world, BlockPos blockposition, int i, @Nullable BeaconBlockEntity blockEntity) { ++ // Paper end - Custom beacon ranges + { +- double d0 = (double) (i * 10 + 10); ++ double d0 = blockEntity != null ? blockEntity.getEffectRange() : (i * 10 + 10); // Paper - Custom beacon ranges + + AABB axisalignedbb = (new AABB(blockposition)).inflate(d0).expandTowards(0.0D, (double) world.getHeight(), 0.0D); + List list = world.getEntitiesOfClass(Player.class, axisalignedbb); +@@ -329,12 +354,17 @@ public class BeaconBlockEntity extends BlockEntity implements MenuProvider, Name + } + + private static void applyEffects(Level world, BlockPos pos, int beaconLevel, @Nullable Holder primaryEffect, @Nullable Holder secondaryEffect) { ++ // Paper start - Custom beacon ranges ++ BeaconBlockEntity.applyEffects(world, pos, beaconLevel, primaryEffect, secondaryEffect, null); ++ } ++ private static void applyEffects(Level world, BlockPos pos, int beaconLevel, @Nullable Holder primaryEffect, @Nullable Holder secondaryEffect, @Nullable BeaconBlockEntity blockEntity) { ++ // Paper end - Custom beacon ranges + if (!world.isClientSide && primaryEffect != null) { + double d0 = (double) (beaconLevel * 10 + 10); + byte b0 = BeaconBlockEntity.getAmplification(beaconLevel, primaryEffect, secondaryEffect); + + int j = BeaconBlockEntity.getLevel(beaconLevel); +- List list = BeaconBlockEntity.getHumansInRange(world, pos, beaconLevel); ++ List list = BeaconBlockEntity.getHumansInRange(world, pos, beaconLevel, blockEntity); // Paper - Custom beacon ranges + + BeaconBlockEntity.applyEffect(list, primaryEffect, j, b0, true, pos); // Paper - BeaconEffectEvent + +@@ -395,6 +425,7 @@ public class BeaconBlockEntity extends BlockEntity implements MenuProvider, Name + } + + this.lockKey = LockCode.fromTag(nbt, registries); ++ this.effectRange = nbt.contains(PAPER_RANGE_TAG, 6) ? nbt.getDouble(PAPER_RANGE_TAG) : -1; // Paper - Custom beacon ranges + } + + @Override +@@ -408,6 +439,7 @@ public class BeaconBlockEntity extends BlockEntity implements MenuProvider, Name + } + + this.lockKey.addToTag(nbt, registries); ++ nbt.putDouble(PAPER_RANGE_TAG, this.effectRange); // Paper - Custom beacon ranges + } + + public void setCustomName(@Nullable Component customName) { +diff --git a/src/main/java/org/bukkit/craftbukkit/block/CraftBeacon.java b/src/main/java/org/bukkit/craftbukkit/block/CraftBeacon.java +index 8021ac39cb9c1ff45123d51e6f13b840d1290bb2..275d4f9e07ff7383b18238071e067b7f44483ece 100644 +--- a/src/main/java/org/bukkit/craftbukkit/block/CraftBeacon.java ++++ b/src/main/java/org/bukkit/craftbukkit/block/CraftBeacon.java +@@ -42,7 +42,7 @@ public class CraftBeacon extends CraftBlockEntityState implem + if (tileEntity instanceof BeaconBlockEntity) { + BeaconBlockEntity beacon = (BeaconBlockEntity) tileEntity; + +- Collection nms = BeaconBlockEntity.getHumansInRange(beacon.getLevel(), beacon.getBlockPos(), beacon.levels); ++ Collection nms = BeaconBlockEntity.getHumansInRange(beacon.getLevel(), beacon.getBlockPos(), beacon.levels, beacon); // Paper - Custom beacon ranges + Collection bukkit = new ArrayList(nms.size()); + + for (Player human : nms) { +@@ -145,4 +145,21 @@ public class CraftBeacon extends CraftBlockEntityState implem + public CraftBeacon copy(Location location) { + return new CraftBeacon(this, location); + } ++ ++ // Paper start ++ @Override ++ public double getEffectRange() { ++ return this.getSnapshot().getEffectRange(); ++ } ++ ++ @Override ++ public void setEffectRange(double range) { ++ this.getSnapshot().setEffectRange(range); ++ } ++ ++ @Override ++ public void resetEffectRange() { ++ this.getSnapshot().resetEffectRange(); ++ } ++ // Paper end + } diff --git a/patches/server/0426-Toggle-for-removing-existing-dragon.patch b/patches/server/0426-Toggle-for-removing-existing-dragon.patch deleted file mode 100644 index 26ae1ded8a1f..000000000000 --- a/patches/server/0426-Toggle-for-removing-existing-dragon.patch +++ /dev/null @@ -1,19 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Mariell Hoversholm -Date: Wed, 30 Sep 2020 22:49:14 +0200 -Subject: [PATCH] Toggle for removing existing dragon - - -diff --git a/src/main/java/net/minecraft/world/level/dimension/end/EndDragonFight.java b/src/main/java/net/minecraft/world/level/dimension/end/EndDragonFight.java -index a12299604ea88c268db5065191d641eb7d52c3e3..d6deedb96583c19eeb27e671289c4edc8a9245b4 100644 ---- a/src/main/java/net/minecraft/world/level/dimension/end/EndDragonFight.java -+++ b/src/main/java/net/minecraft/world/level/dimension/end/EndDragonFight.java -@@ -211,7 +211,7 @@ public class EndDragonFight { - this.dragonUUID = entityenderdragon.getUUID(); - EndDragonFight.LOGGER.info("Found that there's a dragon still alive ({})", entityenderdragon); - this.dragonKilled = false; -- if (!flag) { -+ if (!flag && this.level.paperConfig().entities.behavior.shouldRemoveDragon) { // Paper - Toggle for removing existing dragon - EndDragonFight.LOGGER.info("But we didn't have a portal, let's remove it."); - entityenderdragon.discard(null); // CraftBukkit - add Bukkit remove cause - this.dragonUUID = null; diff --git a/patches/server/0427-Add-API-for-quit-reason.patch b/patches/server/0427-Add-API-for-quit-reason.patch new file mode 100644 index 000000000000..b6a32be1b4db --- /dev/null +++ b/patches/server/0427-Add-API-for-quit-reason.patch @@ -0,0 +1,66 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Mariell Hoversholm +Date: Sat, 14 Nov 2020 16:19:52 +0100 +Subject: [PATCH] Add API for quit reason + + +diff --git a/src/main/java/net/minecraft/network/Connection.java b/src/main/java/net/minecraft/network/Connection.java +index 134810ac91d828d67759cd1ed56f11b71e292917..ba41646a5edb57c4d9766df08bbc57016e2de189 100644 +--- a/src/main/java/net/minecraft/network/Connection.java ++++ b/src/main/java/net/minecraft/network/Connection.java +@@ -167,8 +167,10 @@ public class Connection extends SimpleChannelInboundHandler> { + + this.handlingFault = true; + if (this.channel.isOpen()) { ++ net.minecraft.server.level.ServerPlayer player = this.getPlayer(); // Paper - Add API for quit reason + if (throwable instanceof TimeoutException) { + Connection.LOGGER.debug("Timeout", throwable); ++ if (player != null) player.quitReason = org.bukkit.event.player.PlayerQuitEvent.QuitReason.TIMED_OUT; // Paper - Add API for quit reason + this.disconnect((Component) Component.translatable("disconnect.timeout")); + } else { + MutableComponent ichatmutablecomponent = Component.translatable("disconnect.genericReason", "Internal Exception: " + String.valueOf(throwable)); +@@ -181,6 +183,7 @@ public class Connection extends SimpleChannelInboundHandler> { + disconnectiondetails = new DisconnectionDetails(ichatmutablecomponent); + } + ++ if (player != null) player.quitReason = org.bukkit.event.player.PlayerQuitEvent.QuitReason.ERRONEOUS_STATE; // Paper - Add API for quit reason + if (flag) { + Connection.LOGGER.debug("Failed to sent packet", throwable); + if (this.getSending() == PacketFlow.CLIENTBOUND) { +diff --git a/src/main/java/net/minecraft/server/level/ServerPlayer.java b/src/main/java/net/minecraft/server/level/ServerPlayer.java +index c468947990cf05e554006e51d87b72fad7c28e55..0ae490bb9b4cd781876054d0824c5392060208eb 100644 +--- a/src/main/java/net/minecraft/server/level/ServerPlayer.java ++++ b/src/main/java/net/minecraft/server/level/ServerPlayer.java +@@ -323,6 +323,7 @@ public class ServerPlayer extends net.minecraft.world.entity.player.Player { + public boolean isRealPlayer; // Paper + public com.destroystokyo.paper.event.entity.PlayerNaturallySpawnCreaturesEvent playerNaturallySpawnedEvent; // Paper - PlayerNaturallySpawnCreaturesEvent + public @Nullable String clientBrandName = null; // Paper - Brand support ++ public org.bukkit.event.player.PlayerQuitEvent.QuitReason quitReason = null; // Paper - Add API for quit reason; there are a lot of changes to do if we change all methods leading to the event + + public ServerPlayer(MinecraftServer server, ServerLevel world, GameProfile profile, ClientInformation clientOptions) { + super(world, world.getSharedSpawnPos(), world.getSharedSpawnAngle(), profile); +diff --git a/src/main/java/net/minecraft/server/network/ServerCommonPacketListenerImpl.java b/src/main/java/net/minecraft/server/network/ServerCommonPacketListenerImpl.java +index 9a8b08d4b70b8890961e4af7ce6e870aa1c7c810..f8ae8c8eff73e4e87eb34d0f2635517f1688a6f1 100644 +--- a/src/main/java/net/minecraft/server/network/ServerCommonPacketListenerImpl.java ++++ b/src/main/java/net/minecraft/server/network/ServerCommonPacketListenerImpl.java +@@ -383,6 +383,7 @@ public abstract class ServerCommonPacketListenerImpl implements ServerCommonPack + + private void disconnect0(DisconnectionDetails disconnectiondetails) { + // CraftBukkit end ++ this.player.quitReason = org.bukkit.event.player.PlayerQuitEvent.QuitReason.KICKED; // Paper - Add API for quit reason + this.connection.send(new ClientboundDisconnectPacket(disconnectiondetails.reason()), PacketSendListener.thenRun(() -> { + this.connection.disconnect(disconnectiondetails); + })); +diff --git a/src/main/java/net/minecraft/server/players/PlayerList.java b/src/main/java/net/minecraft/server/players/PlayerList.java +index 530369764cad77466995f8f65070eec6a5de74f2..819fb4ef54dce33ec91604491132771f157c8d83 100644 +--- a/src/main/java/net/minecraft/server/players/PlayerList.java ++++ b/src/main/java/net/minecraft/server/players/PlayerList.java +@@ -519,7 +519,7 @@ public abstract class PlayerList { + entityplayer.closeContainer(org.bukkit.event.inventory.InventoryCloseEvent.Reason.DISCONNECT); // Paper - Inventory close reason + } + +- PlayerQuitEvent playerQuitEvent = new PlayerQuitEvent(entityplayer.getBukkitEntity(), net.kyori.adventure.text.Component.translatable("multiplayer.player.left", net.kyori.adventure.text.format.NamedTextColor.YELLOW, io.papermc.paper.configuration.GlobalConfiguration.get().messages.useDisplayNameInQuitMessage ? entityplayer.getBukkitEntity().displayName() : io.papermc.paper.adventure.PaperAdventure.asAdventure(entityplayer.getDisplayName()))); // Paper - Adventure ++ PlayerQuitEvent playerQuitEvent = new PlayerQuitEvent(entityplayer.getBukkitEntity(), net.kyori.adventure.text.Component.translatable("multiplayer.player.left", net.kyori.adventure.text.format.NamedTextColor.YELLOW, io.papermc.paper.configuration.GlobalConfiguration.get().messages.useDisplayNameInQuitMessage ? entityplayer.getBukkitEntity().displayName() : io.papermc.paper.adventure.PaperAdventure.asAdventure(entityplayer.getDisplayName())), entityplayer.quitReason); // Paper - Adventure & Add API for quit reason + this.cserver.getPluginManager().callEvent(playerQuitEvent); + entityplayer.getBukkitEntity().disconnect(playerQuitEvent.getQuitMessage()); + diff --git a/patches/server/0427-Fix-client-lag-on-advancement-loading.patch b/patches/server/0427-Fix-client-lag-on-advancement-loading.patch deleted file mode 100644 index 5cc82219f2c6..000000000000 --- a/patches/server/0427-Fix-client-lag-on-advancement-loading.patch +++ /dev/null @@ -1,35 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Jason Penilla <11360596+jpenilla@users.noreply.github.com> -Date: Sat, 31 Oct 2020 11:49:01 -0700 -Subject: [PATCH] Fix client lag on advancement loading - -When new advancements are added via the UnsafeValues#loadAdvancement -API, it triggers a full datapack reload when this is not necessary. The -advancement is already loaded directly into the advancement registry, -and the point of saving the advancement to the Bukkit datapack seems to -be for persistence. By removing the call to reload datapacks when an -advancement is loaded, the client no longer completely freezes up when -adding a new advancement. -To ensure the client still receives the updated advancement data, we -manually reload the advancement data for all players, which -normally takes place as a part of the datapack reloading. - -diff --git a/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java b/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java -index d7e8663e21ade1b53d4b936147f57b632f67a156..66249c5caefb0879e13c02d5553b09b4122418b9 100644 ---- a/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java -+++ b/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java -@@ -323,7 +323,13 @@ public final class CraftMagicNumbers implements UnsafeValues { - Bukkit.getLogger().log(Level.SEVERE, "Error saving advancement " + key, ex); - } - -- MinecraftServer.getServer().getPlayerList().reloadResources(); -+ // Paper start - Fix client lag on advancement loading -+ //MinecraftServer.getServer().getPlayerList().reload(); -+ MinecraftServer.getServer().getPlayerList().getPlayers().forEach(player -> { -+ player.getAdvancements().reload(MinecraftServer.getServer().getAdvancements()); -+ player.getAdvancements().flushDirty(player); -+ }); -+ // Paper end - Fix client lag on advancement loading - - return bukkit; - } diff --git a/patches/server/0428-Add-Wandering-Trader-spawn-rate-config-options.patch b/patches/server/0428-Add-Wandering-Trader-spawn-rate-config-options.patch new file mode 100644 index 000000000000..30e4e03f249d --- /dev/null +++ b/patches/server/0428-Add-Wandering-Trader-spawn-rate-config-options.patch @@ -0,0 +1,87 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Jason Penilla <11360596+jpenilla@users.noreply.github.com> +Date: Thu, 20 Aug 2020 11:20:12 -0700 +Subject: [PATCH] Add Wandering Trader spawn rate config options + +Adds config options for modifying the spawn rates of Wandering Traders. +These values are all easy to understand and configure after a quick read of this +page on the Minecraft wiki: https://minecraft.wiki/wiki/Wandering_Trader#Spawning +Usages of the vanilla WanderingTraderSpawnDelay and WanderingTraderSpawnChance values +in IWorldServerData are removed as they were only used in certain places, with hardcoded +values used in other places. + +diff --git a/src/main/java/net/minecraft/world/entity/npc/WanderingTraderSpawner.java b/src/main/java/net/minecraft/world/entity/npc/WanderingTraderSpawner.java +index cfa1787b955fd9b80bf9906b88d423cb59b12ff1..08a3c7140867f339dd99a95094ed0fd8ff344fca 100644 +--- a/src/main/java/net/minecraft/world/entity/npc/WanderingTraderSpawner.java ++++ b/src/main/java/net/minecraft/world/entity/npc/WanderingTraderSpawner.java +@@ -40,43 +40,53 @@ public class WanderingTraderSpawner implements CustomSpawner { + + public WanderingTraderSpawner(ServerLevelData properties) { + this.serverLevelData = properties; +- this.tickDelay = 1200; +- this.spawnDelay = properties.getWanderingTraderSpawnDelay(); +- this.spawnChance = properties.getWanderingTraderSpawnChance(); +- if (this.spawnDelay == 0 && this.spawnChance == 0) { +- this.spawnDelay = 24000; +- properties.setWanderingTraderSpawnDelay(this.spawnDelay); +- this.spawnChance = 25; +- properties.setWanderingTraderSpawnChance(this.spawnChance); +- } ++ // Paper start - Add Wandering Trader spawn rate config options ++ this.tickDelay = Integer.MIN_VALUE; ++ //this.spawnDelay = properties.getWanderingTraderSpawnDelay(); // Paper - This value is read from the world file only for the first spawn, after which vanilla uses a hardcoded value ++ //this.spawnChance = properties.getWanderingTraderSpawnChance(); // Paper - This value is read from the world file only for the first spawn, after which vanilla uses a hardcoded value ++ //if (this.spawnDelay == 0 && this.spawnChance == 0) { ++ // this.spawnDelay = 24000; ++ // properties.setWanderingTraderSpawnDelay(this.spawnDelay); ++ // this.spawnChance = 25; ++ // properties.setWanderingTraderSpawnChance(this.spawnChance); ++ //} ++ // Paper end - Add Wandering Trader spawn rate config options + + } + + @Override + public int tick(ServerLevel world, boolean spawnMonsters, boolean spawnAnimals) { ++ // Paper start - Add Wandering Trader spawn rate config options ++ if (this.tickDelay == Integer.MIN_VALUE) { ++ this.tickDelay = world.paperConfig().entities.spawning.wanderingTrader.spawnMinuteLength; ++ this.spawnDelay = world.paperConfig().entities.spawning.wanderingTrader.spawnDayLength; ++ this.spawnChance = world.paperConfig().entities.spawning.wanderingTrader.spawnChanceMin; ++ } + if (!world.getGameRules().getBoolean(GameRules.RULE_DO_TRADER_SPAWNING)) { + return 0; +- } else if (--this.tickDelay > 0) { ++ } else if (this.tickDelay - 1 > 0) { ++ this.tickDelay = this.tickDelay - 1; + return 0; + } else { +- this.tickDelay = 1200; +- this.spawnDelay -= 1200; +- this.serverLevelData.setWanderingTraderSpawnDelay(this.spawnDelay); ++ this.tickDelay = world.paperConfig().entities.spawning.wanderingTrader.spawnMinuteLength; ++ this.spawnDelay = this.spawnDelay - world.paperConfig().entities.spawning.wanderingTrader.spawnMinuteLength; ++ //this.serverLevelData.setWanderingTraderSpawnDelay(this.spawnDelay); // Paper - We don't need to save this value to disk if it gets set back to a hardcoded value anyways + if (this.spawnDelay > 0) { + return 0; + } else { +- this.spawnDelay = 24000; ++ this.spawnDelay = world.paperConfig().entities.spawning.wanderingTrader.spawnDayLength; + if (!world.getGameRules().getBoolean(GameRules.RULE_DOMOBSPAWNING)) { + return 0; + } else { + int i = this.spawnChance; + +- this.spawnChance = Mth.clamp(this.spawnChance + 25, 25, 75); +- this.serverLevelData.setWanderingTraderSpawnChance(this.spawnChance); ++ // this.serverLevelData.setWanderingTraderSpawnChance(this.spawnChance); // Paper - We don't need to save this value to disk if it gets set back to a hardcoded value anyways ++ this.spawnChance = Mth.clamp(i + world.paperConfig().entities.spawning.wanderingTrader.spawnChanceFailureIncrement, world.paperConfig().entities.spawning.wanderingTrader.spawnChanceMin, world.paperConfig().entities.spawning.wanderingTrader.spawnChanceMax); + if (this.random.nextInt(100) > i) { + return 0; + } else if (this.spawn(world)) { +- this.spawnChance = 25; ++ this.spawnChance = world.paperConfig().entities.spawning.wanderingTrader.spawnChanceMin; ++ // Paper end - Add Wandering Trader spawn rate config options + return 1; + } else { + return 0; diff --git a/patches/server/0429-Add-Destroy-Speed-API.patch b/patches/server/0429-Add-Destroy-Speed-API.patch new file mode 100644 index 000000000000..2274f8a5d44f --- /dev/null +++ b/patches/server/0429-Add-Destroy-Speed-API.patch @@ -0,0 +1,220 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Ineusia +Date: Mon, 26 Oct 2020 11:48:06 -0500 +Subject: [PATCH] Add Destroy Speed API + +Co-authored-by: Jake Potrebic + +diff --git a/src/main/java/net/minecraft/world/entity/ai/attributes/AttributeInstance.java b/src/main/java/net/minecraft/world/entity/ai/attributes/AttributeInstance.java +index a2fe7149a837040cf9f23eb426d398a5f90be394..27a7852a5d3f8c8960f098646ff5587c50556aa5 100644 +--- a/src/main/java/net/minecraft/world/entity/ai/attributes/AttributeInstance.java ++++ b/src/main/java/net/minecraft/world/entity/ai/attributes/AttributeInstance.java +@@ -153,20 +153,20 @@ public class AttributeInstance { + double d = this.getBaseValue(); + + for (AttributeModifier attributeModifier : this.getModifiersOrEmpty(AttributeModifier.Operation.ADD_VALUE)) { +- d += attributeModifier.amount(); ++ d += attributeModifier.amount(); // Paper - destroy speed API - diff on change + } + + double e = d; + + for (AttributeModifier attributeModifier2 : this.getModifiersOrEmpty(AttributeModifier.Operation.ADD_MULTIPLIED_BASE)) { +- e += d * attributeModifier2.amount(); ++ e += d * attributeModifier2.amount(); // Paper - destroy speed API - diff on change + } + + for (AttributeModifier attributeModifier3 : this.getModifiersOrEmpty(AttributeModifier.Operation.ADD_MULTIPLIED_TOTAL)) { +- e *= 1.0 + attributeModifier3.amount(); ++ e *= 1.0 + attributeModifier3.amount(); // Paper - destroy speed API - diff on change + } + +- return this.attribute.value().sanitizeValue(e); ++ return attribute.value().sanitizeValue(e); // Paper - destroy speed API - diff on change + } + + private Collection getModifiersOrEmpty(AttributeModifier.Operation operation) { +diff --git a/src/main/java/org/bukkit/craftbukkit/block/data/CraftBlockData.java b/src/main/java/org/bukkit/craftbukkit/block/data/CraftBlockData.java +index 7f0d87bd43a54885fc521068116888083327f37e..a17c1d1651d4d36c40ef97c1cf0b1e0d61f53418 100644 +--- a/src/main/java/org/bukkit/craftbukkit/block/data/CraftBlockData.java ++++ b/src/main/java/org/bukkit/craftbukkit/block/data/CraftBlockData.java +@@ -725,4 +725,35 @@ public class CraftBlockData implements BlockData { + public BlockState createBlockState() { + return CraftBlockStates.getBlockState(this.state, null); + } ++ ++ // Paper start - destroy speed API ++ @Override ++ public float getDestroySpeed(final ItemStack itemStack, final boolean considerEnchants) { ++ net.minecraft.world.item.ItemStack nmsItemStack = CraftItemStack.unwrap(itemStack); ++ float speed = nmsItemStack.getDestroySpeed(this.state); ++ if (speed > 1.0F && considerEnchants) { ++ final net.minecraft.core.Holder attribute = net.minecraft.world.entity.ai.attributes.Attributes.MINING_EFFICIENCY; ++ // Logic sourced from AttributeInstance#calculateValue ++ final double initialBaseValue = attribute.value().getDefaultValue(); ++ final org.apache.commons.lang3.mutable.MutableDouble modifiedBaseValue = new org.apache.commons.lang3.mutable.MutableDouble(initialBaseValue); ++ final org.apache.commons.lang3.mutable.MutableDouble baseValMul = new org.apache.commons.lang3.mutable.MutableDouble(1); ++ final org.apache.commons.lang3.mutable.MutableDouble totalValMul = new org.apache.commons.lang3.mutable.MutableDouble(1); ++ ++ net.minecraft.world.item.enchantment.EnchantmentHelper.forEachModifier( ++ nmsItemStack, net.minecraft.world.entity.EquipmentSlot.MAINHAND, (attributeHolder, attributeModifier) -> { ++ switch (attributeModifier.operation()) { ++ case ADD_VALUE -> modifiedBaseValue.add(attributeModifier.amount()); ++ case ADD_MULTIPLIED_BASE -> baseValMul.add(attributeModifier.amount()); ++ case ADD_MULTIPLIED_TOTAL -> totalValMul.setValue(totalValMul.doubleValue() * (1D + attributeModifier.amount())); ++ } ++ } ++ ); ++ ++ final double actualModifier = modifiedBaseValue.doubleValue() * baseValMul.doubleValue() * totalValMul.doubleValue(); ++ ++ speed += (float) attribute.value().sanitizeValue(actualModifier); ++ } ++ return speed; ++ } ++ // Paper end - destroy speed API + } +diff --git a/src/test/java/io/papermc/paper/block/CraftBlockDataDestroySpeedTest.java b/src/test/java/io/papermc/paper/block/CraftBlockDataDestroySpeedTest.java +new file mode 100644 +index 0000000000000000000000000000000000000000..32d38205a5a72c3c1838ed28cb83bcea5ad59b6b +--- /dev/null ++++ b/src/test/java/io/papermc/paper/block/CraftBlockDataDestroySpeedTest.java +@@ -0,0 +1,138 @@ ++package io.papermc.paper.block; ++ ++import java.util.List; ++import java.util.Optional; ++import net.minecraft.core.Holder; ++import net.minecraft.core.HolderSet; ++import net.minecraft.core.component.DataComponentMap; ++import net.minecraft.core.component.DataComponents; ++import net.minecraft.network.chat.Component; ++import net.minecraft.world.entity.EquipmentSlot; ++import net.minecraft.world.entity.EquipmentSlotGroup; ++import net.minecraft.world.entity.ai.attributes.AttributeInstance; ++import net.minecraft.world.entity.ai.attributes.AttributeModifier; ++import net.minecraft.world.entity.ai.attributes.Attributes; ++import net.minecraft.world.item.Items; ++import net.minecraft.world.item.enchantment.Enchantment; ++import net.minecraft.world.item.enchantment.EnchantmentEffectComponents; ++import net.minecraft.world.item.enchantment.EnchantmentHelper; ++import net.minecraft.world.item.enchantment.ItemEnchantments; ++import net.minecraft.world.item.enchantment.LevelBasedValue; ++import net.minecraft.world.item.enchantment.effects.EnchantmentAttributeEffect; ++import net.minecraft.world.level.block.Blocks; ++import net.minecraft.world.level.block.state.BlockState; ++import org.bukkit.craftbukkit.block.data.CraftBlockData; ++import org.bukkit.craftbukkit.inventory.CraftItemStack; ++import org.bukkit.inventory.ItemStack; ++import org.bukkit.support.environment.AllFeatures; ++import org.bukkit.util.Vector; ++import org.jetbrains.annotations.NotNull; ++import org.junit.jupiter.api.Assertions; ++import org.junit.jupiter.api.Test; ++ ++import static net.minecraft.resources.ResourceLocation.fromNamespaceAndPath; ++ ++/** ++ * CraftBlockData's {@link org.bukkit.craftbukkit.block.data.CraftBlockData#getDestroySpeed(ItemStack, boolean)} ++ * uses a reimplementation of AttributeValue without any map to avoid attribute instance allocation and mutation ++ * for 0 gain. ++ *

    ++ * This test is responsible for ensuring that said logic emits the expected destroy speed under heavy attribute ++ * modifier use. ++ */ ++@AllFeatures ++public class CraftBlockDataDestroySpeedTest { ++ ++ @Test ++ public void testCorrectEnchantmentDestroySpeedComputation() { ++ // Construct fake enchantment that has *all and multiple of* operations ++ final Enchantment speedEnchantment = speedEnchantment(); ++ final BlockState blockStateToMine = Blocks.STONE.defaultBlockState(); ++ ++ final ItemEnchantments.Mutable mutable = new ItemEnchantments.Mutable(ItemEnchantments.EMPTY); ++ mutable.set(Holder.direct(speedEnchantment), 1); ++ ++ final net.minecraft.world.item.ItemStack itemStack = new net.minecraft.world.item.ItemStack(Items.DIAMOND_PICKAXE); ++ itemStack.set(DataComponents.ENCHANTMENTS, mutable.toImmutable()); ++ ++ // Compute expected value by running the entire attribute instance chain ++ final AttributeInstance dummyInstance = new AttributeInstance(Attributes.MINING_EFFICIENCY, $ -> { ++ }); ++ EnchantmentHelper.forEachModifier(itemStack, EquipmentSlot.MAINHAND, (attributeHolder, attributeModifier) -> { ++ if (attributeHolder.is(Attributes.MINING_EFFICIENCY)) dummyInstance.addTransientModifier(attributeModifier); ++ }); ++ ++ final double toolSpeed = itemStack.getDestroySpeed(blockStateToMine); ++ final double expectedSpeed = toolSpeed <= 1.0F ? toolSpeed : toolSpeed + dummyInstance.getValue(); ++ ++ // API stack + computation ++ final CraftItemStack craftMirror = CraftItemStack.asCraftMirror(itemStack); ++ final CraftBlockData data = CraftBlockData.createData(blockStateToMine); ++ final float actualSpeed = data.getDestroySpeed(craftMirror, true); ++ ++ Assertions.assertEquals(expectedSpeed, actualSpeed, Vector.getEpsilon()); ++ } ++ ++ /** ++ * Complex enchantment that holds attribute modifiers for the mining efficiency. ++ * The enchantment holds 2 of each operation to also ensure that such behaviour works correctly. ++ * ++ * @return the enchantment. ++ */ ++ private static @NotNull Enchantment speedEnchantment() { ++ return new Enchantment( ++ Component.empty(), ++ new Enchantment.EnchantmentDefinition( ++ HolderSet.empty(), ++ Optional.empty(), ++ 0, 0, ++ Enchantment.constantCost(0), ++ Enchantment.constantCost(0), ++ 0, ++ List.of(EquipmentSlotGroup.ANY) ++ ), ++ HolderSet.empty(), ++ DataComponentMap.builder() ++ .set(EnchantmentEffectComponents.ATTRIBUTES, List.of( ++ new EnchantmentAttributeEffect( ++ fromNamespaceAndPath("paper", "base1"), ++ Attributes.MINING_EFFICIENCY, ++ LevelBasedValue.constant(1), ++ AttributeModifier.Operation.ADD_VALUE ++ ), ++ new EnchantmentAttributeEffect( ++ fromNamespaceAndPath("paper", "base2"), ++ Attributes.MINING_EFFICIENCY, ++ LevelBasedValue.perLevel(3), ++ AttributeModifier.Operation.ADD_VALUE ++ ), ++ new EnchantmentAttributeEffect( ++ fromNamespaceAndPath("paper", "base-mul1"), ++ Attributes.MINING_EFFICIENCY, ++ LevelBasedValue.perLevel(7), ++ AttributeModifier.Operation.ADD_MULTIPLIED_BASE ++ ), ++ new EnchantmentAttributeEffect( ++ fromNamespaceAndPath("paper", "base-mul2"), ++ Attributes.MINING_EFFICIENCY, ++ LevelBasedValue.constant(10), ++ AttributeModifier.Operation.ADD_MULTIPLIED_BASE ++ ), ++ new EnchantmentAttributeEffect( ++ fromNamespaceAndPath("paper", "total-mul1"), ++ Attributes.MINING_EFFICIENCY, ++ LevelBasedValue.constant(.2f), ++ AttributeModifier.Operation.ADD_MULTIPLIED_TOTAL ++ ), ++ new EnchantmentAttributeEffect( ++ fromNamespaceAndPath("paper", "total-mul2"), ++ Attributes.MINING_EFFICIENCY, ++ LevelBasedValue.constant(-.5F), ++ AttributeModifier.Operation.ADD_MULTIPLIED_TOTAL ++ ) ++ )) ++ .build() ++ ); ++ } ++ ++} diff --git a/patches/server/0429-Beacon-API-custom-effect-ranges.patch b/patches/server/0429-Beacon-API-custom-effect-ranges.patch deleted file mode 100644 index b80f1772cda1..000000000000 --- a/patches/server/0429-Beacon-API-custom-effect-ranges.patch +++ /dev/null @@ -1,131 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Jake Potrebic -Date: Wed, 24 Jun 2020 12:39:08 -0600 -Subject: [PATCH] Beacon API - custom effect ranges - - -diff --git a/src/main/java/net/minecraft/world/level/block/entity/BeaconBlockEntity.java b/src/main/java/net/minecraft/world/level/block/entity/BeaconBlockEntity.java -index fc915797d2a085447747d9ce23a5a354fb3eb6b6..52776ce9b7b4edd1eb474f14705d7fd83f5a66ca 100644 ---- a/src/main/java/net/minecraft/world/level/block/entity/BeaconBlockEntity.java -+++ b/src/main/java/net/minecraft/world/level/block/entity/BeaconBlockEntity.java -@@ -86,6 +86,26 @@ public class BeaconBlockEntity extends BlockEntity implements MenuProvider, Name - return (BeaconBlockEntity.hasSecondaryEffect(this.levels, this.primaryPower, this.secondaryPower)) ? CraftPotionUtil.toBukkit(new MobEffectInstance(this.secondaryPower, BeaconBlockEntity.getLevel(this.levels), BeaconBlockEntity.getAmplification(this.levels, this.primaryPower, this.secondaryPower), true, true)) : null; - } - // CraftBukkit end -+ // Paper start - Custom beacon ranges -+ private final String PAPER_RANGE_TAG = "Paper.Range"; -+ private double effectRange = -1; -+ -+ public double getEffectRange() { -+ if (this.effectRange < 0) { -+ return this.levels * 10 + 10; -+ } else { -+ return effectRange; -+ } -+ } -+ -+ public void setEffectRange(double range) { -+ this.effectRange = range; -+ } -+ -+ public void resetEffectRange() { -+ this.effectRange = -1; -+ } -+ // Paper end - Custom beacon ranges - - @Nullable - static Holder filterEffect(@Nullable Holder effect) { -@@ -201,7 +221,7 @@ public class BeaconBlockEntity extends BlockEntity implements MenuProvider, Name - } - - if (blockEntity.levels > 0 && !blockEntity.beamSections.isEmpty()) { -- BeaconBlockEntity.applyEffects(world, pos, blockEntity.levels, blockEntity.primaryPower, blockEntity.secondaryPower); -+ BeaconBlockEntity.applyEffects(world, pos, blockEntity.levels, blockEntity.primaryPower, blockEntity.secondaryPower, blockEntity); // Paper - Custom beacon ranges - BeaconBlockEntity.playSound(world, pos, SoundEvents.BEACON_AMBIENT); - } - } -@@ -287,8 +307,13 @@ public class BeaconBlockEntity extends BlockEntity implements MenuProvider, Name - } - - public static List getHumansInRange(Level world, BlockPos blockposition, int i) { -+ // Paper start - Custom beacon ranges -+ return BeaconBlockEntity.getHumansInRange(world, blockposition, i, null); -+ } -+ public static List getHumansInRange(Level world, BlockPos blockposition, int i, @Nullable BeaconBlockEntity blockEntity) { -+ // Paper end - Custom beacon ranges - { -- double d0 = (double) (i * 10 + 10); -+ double d0 = blockEntity != null ? blockEntity.getEffectRange() : (i * 10 + 10); // Paper - Custom beacon ranges - - AABB axisalignedbb = (new AABB(blockposition)).inflate(d0).expandTowards(0.0D, (double) world.getHeight(), 0.0D); - List list = world.getEntitiesOfClass(Player.class, axisalignedbb); -@@ -329,12 +354,17 @@ public class BeaconBlockEntity extends BlockEntity implements MenuProvider, Name - } - - private static void applyEffects(Level world, BlockPos pos, int beaconLevel, @Nullable Holder primaryEffect, @Nullable Holder secondaryEffect) { -+ // Paper start - Custom beacon ranges -+ BeaconBlockEntity.applyEffects(world, pos, beaconLevel, primaryEffect, secondaryEffect, null); -+ } -+ private static void applyEffects(Level world, BlockPos pos, int beaconLevel, @Nullable Holder primaryEffect, @Nullable Holder secondaryEffect, @Nullable BeaconBlockEntity blockEntity) { -+ // Paper end - Custom beacon ranges - if (!world.isClientSide && primaryEffect != null) { - double d0 = (double) (beaconLevel * 10 + 10); - byte b0 = BeaconBlockEntity.getAmplification(beaconLevel, primaryEffect, secondaryEffect); - - int j = BeaconBlockEntity.getLevel(beaconLevel); -- List list = BeaconBlockEntity.getHumansInRange(world, pos, beaconLevel); -+ List list = BeaconBlockEntity.getHumansInRange(world, pos, beaconLevel, blockEntity); // Paper - Custom beacon ranges - - BeaconBlockEntity.applyEffect(list, primaryEffect, j, b0, true, pos); // Paper - BeaconEffectEvent - -@@ -395,6 +425,7 @@ public class BeaconBlockEntity extends BlockEntity implements MenuProvider, Name - } - - this.lockKey = LockCode.fromTag(nbt); -+ this.effectRange = nbt.contains(PAPER_RANGE_TAG, 6) ? nbt.getDouble(PAPER_RANGE_TAG) : -1; // Paper - Custom beacon ranges - } - - @Override -@@ -408,6 +439,7 @@ public class BeaconBlockEntity extends BlockEntity implements MenuProvider, Name - } - - this.lockKey.addToTag(nbt); -+ nbt.putDouble(PAPER_RANGE_TAG, this.effectRange); // Paper - Custom beacon ranges - } - - public void setCustomName(@Nullable Component customName) { -diff --git a/src/main/java/org/bukkit/craftbukkit/block/CraftBeacon.java b/src/main/java/org/bukkit/craftbukkit/block/CraftBeacon.java -index 2dfbe061a064b0c79b96f644a1c3639bb900eca4..3f1bb225c4e6acdd9104e856d6a11d519119c9f4 100644 ---- a/src/main/java/org/bukkit/craftbukkit/block/CraftBeacon.java -+++ b/src/main/java/org/bukkit/craftbukkit/block/CraftBeacon.java -@@ -33,7 +33,7 @@ public class CraftBeacon extends CraftBlockEntityState implem - if (tileEntity instanceof BeaconBlockEntity) { - BeaconBlockEntity beacon = (BeaconBlockEntity) tileEntity; - -- Collection nms = BeaconBlockEntity.getHumansInRange(beacon.getLevel(), beacon.getBlockPos(), beacon.levels); -+ Collection nms = BeaconBlockEntity.getHumansInRange(beacon.getLevel(), beacon.getBlockPos(), beacon.levels, beacon); // Paper - Custom beacon ranges - Collection bukkit = new ArrayList(nms.size()); - - for (Player human : nms) { -@@ -120,4 +120,21 @@ public class CraftBeacon extends CraftBlockEntityState implem - public CraftBeacon copy(Location location) { - return new CraftBeacon(this, location); - } -+ -+ // Paper start -+ @Override -+ public double getEffectRange() { -+ return this.getSnapshot().getEffectRange(); -+ } -+ -+ @Override -+ public void setEffectRange(double range) { -+ this.getSnapshot().setEffectRange(range); -+ } -+ -+ @Override -+ public void resetEffectRange() { -+ this.getSnapshot().resetEffectRange(); -+ } -+ // Paper end - } diff --git a/patches/server/0430-Add-API-for-quit-reason.patch b/patches/server/0430-Add-API-for-quit-reason.patch deleted file mode 100644 index c4ed7bf11f36..000000000000 --- a/patches/server/0430-Add-API-for-quit-reason.patch +++ /dev/null @@ -1,66 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Mariell Hoversholm -Date: Sat, 14 Nov 2020 16:19:52 +0100 -Subject: [PATCH] Add API for quit reason - - -diff --git a/src/main/java/net/minecraft/network/Connection.java b/src/main/java/net/minecraft/network/Connection.java -index 134810ac91d828d67759cd1ed56f11b71e292917..ba41646a5edb57c4d9766df08bbc57016e2de189 100644 ---- a/src/main/java/net/minecraft/network/Connection.java -+++ b/src/main/java/net/minecraft/network/Connection.java -@@ -167,8 +167,10 @@ public class Connection extends SimpleChannelInboundHandler> { - - this.handlingFault = true; - if (this.channel.isOpen()) { -+ net.minecraft.server.level.ServerPlayer player = this.getPlayer(); // Paper - Add API for quit reason - if (throwable instanceof TimeoutException) { - Connection.LOGGER.debug("Timeout", throwable); -+ if (player != null) player.quitReason = org.bukkit.event.player.PlayerQuitEvent.QuitReason.TIMED_OUT; // Paper - Add API for quit reason - this.disconnect((Component) Component.translatable("disconnect.timeout")); - } else { - MutableComponent ichatmutablecomponent = Component.translatable("disconnect.genericReason", "Internal Exception: " + String.valueOf(throwable)); -@@ -181,6 +183,7 @@ public class Connection extends SimpleChannelInboundHandler> { - disconnectiondetails = new DisconnectionDetails(ichatmutablecomponent); - } - -+ if (player != null) player.quitReason = org.bukkit.event.player.PlayerQuitEvent.QuitReason.ERRONEOUS_STATE; // Paper - Add API for quit reason - if (flag) { - Connection.LOGGER.debug("Failed to sent packet", throwable); - if (this.getSending() == PacketFlow.CLIENTBOUND) { -diff --git a/src/main/java/net/minecraft/server/level/ServerPlayer.java b/src/main/java/net/minecraft/server/level/ServerPlayer.java -index 45181bc9c422507682d479e4d43177ecd3253971..2ea613f818403f8e8ece4b36891738139345cf89 100644 ---- a/src/main/java/net/minecraft/server/level/ServerPlayer.java -+++ b/src/main/java/net/minecraft/server/level/ServerPlayer.java -@@ -295,6 +295,7 @@ public class ServerPlayer extends net.minecraft.world.entity.player.Player { - public boolean isRealPlayer; // Paper - public com.destroystokyo.paper.event.entity.PlayerNaturallySpawnCreaturesEvent playerNaturallySpawnedEvent; // Paper - PlayerNaturallySpawnCreaturesEvent - public @Nullable String clientBrandName = null; // Paper - Brand support -+ public org.bukkit.event.player.PlayerQuitEvent.QuitReason quitReason = null; // Paper - Add API for quit reason; there are a lot of changes to do if we change all methods leading to the event - - public ServerPlayer(MinecraftServer server, ServerLevel world, GameProfile profile, ClientInformation clientOptions) { - super(world, world.getSharedSpawnPos(), world.getSharedSpawnAngle(), profile); -diff --git a/src/main/java/net/minecraft/server/network/ServerCommonPacketListenerImpl.java b/src/main/java/net/minecraft/server/network/ServerCommonPacketListenerImpl.java -index 48085b2e7197dc44e76b812bdd514af729e21e83..a1124405412cdac673f34a63988e7be957506dba 100644 ---- a/src/main/java/net/minecraft/server/network/ServerCommonPacketListenerImpl.java -+++ b/src/main/java/net/minecraft/server/network/ServerCommonPacketListenerImpl.java -@@ -376,6 +376,7 @@ public abstract class ServerCommonPacketListenerImpl implements ServerCommonPack - - private void disconnect0(DisconnectionDetails disconnectiondetails) { - // CraftBukkit end -+ this.player.quitReason = org.bukkit.event.player.PlayerQuitEvent.QuitReason.KICKED; // Paper - Add API for quit reason - this.connection.send(new ClientboundDisconnectPacket(disconnectiondetails.reason()), PacketSendListener.thenRun(() -> { - this.connection.disconnect(disconnectiondetails); - })); -diff --git a/src/main/java/net/minecraft/server/players/PlayerList.java b/src/main/java/net/minecraft/server/players/PlayerList.java -index 6f587c4bb4704d93552ba61335d87b1852fc169c..98eb00a8ee23543714d424d3ff5ca19887d6db19 100644 ---- a/src/main/java/net/minecraft/server/players/PlayerList.java -+++ b/src/main/java/net/minecraft/server/players/PlayerList.java -@@ -570,7 +570,7 @@ public abstract class PlayerList { - entityplayer.closeContainer(org.bukkit.event.inventory.InventoryCloseEvent.Reason.DISCONNECT); // Paper - Inventory close reason - } - -- PlayerQuitEvent playerQuitEvent = new PlayerQuitEvent(entityplayer.getBukkitEntity(), net.kyori.adventure.text.Component.translatable("multiplayer.player.left", net.kyori.adventure.text.format.NamedTextColor.YELLOW, io.papermc.paper.configuration.GlobalConfiguration.get().messages.useDisplayNameInQuitMessage ? entityplayer.getBukkitEntity().displayName() : io.papermc.paper.adventure.PaperAdventure.asAdventure(entityplayer.getDisplayName()))); // Paper - Adventure -+ PlayerQuitEvent playerQuitEvent = new PlayerQuitEvent(entityplayer.getBukkitEntity(), net.kyori.adventure.text.Component.translatable("multiplayer.player.left", net.kyori.adventure.text.format.NamedTextColor.YELLOW, io.papermc.paper.configuration.GlobalConfiguration.get().messages.useDisplayNameInQuitMessage ? entityplayer.getBukkitEntity().displayName() : io.papermc.paper.adventure.PaperAdventure.asAdventure(entityplayer.getDisplayName())), entityplayer.quitReason); // Paper - Adventure & Add API for quit reason - this.cserver.getPluginManager().callEvent(playerQuitEvent); - entityplayer.getBukkitEntity().disconnect(playerQuitEvent.getQuitMessage()); - diff --git a/patches/server/0430-Fix-Player-spawnParticle-x-y-z-precision-loss.patch b/patches/server/0430-Fix-Player-spawnParticle-x-y-z-precision-loss.patch new file mode 100644 index 000000000000..066a872e128d --- /dev/null +++ b/patches/server/0430-Fix-Player-spawnParticle-x-y-z-precision-loss.patch @@ -0,0 +1,19 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Esophose +Date: Sat, 3 Oct 2020 18:57:47 -0600 +Subject: [PATCH] Fix Player spawnParticle x/y/z precision loss + + +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java +index 3191c66c9e5b9c5fcfd07716733aaa51612d507f..e7f2d02de2ac58ffa484d8eca5f8b5688e7e02d8 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java +@@ -2734,7 +2734,7 @@ public class CraftPlayer extends CraftHumanEntity implements Player { + + @Override + public void spawnParticle(Particle particle, double x, double y, double z, int count, double offsetX, double offsetY, double offsetZ, double extra, T data, boolean force) { +- ClientboundLevelParticlesPacket packetplayoutworldparticles = new ClientboundLevelParticlesPacket(CraftParticle.createParticleParam(particle, data), force, (float) x, (float) y, (float) z, (float) offsetX, (float) offsetY, (float) offsetZ, (float) extra, count); ++ ClientboundLevelParticlesPacket packetplayoutworldparticles = new ClientboundLevelParticlesPacket(CraftParticle.createParticleParam(particle, data), force, x, y, z, (float) offsetX, (float) offsetY, (float) offsetZ, (float) extra, count); // Paper - fix x/y/z precision loss + this.getHandle().connection.send(packetplayoutworldparticles); + } + diff --git a/patches/server/0431-Add-LivingEntity-clearActiveItem.patch b/patches/server/0431-Add-LivingEntity-clearActiveItem.patch new file mode 100644 index 000000000000..3c6621ccfabf --- /dev/null +++ b/patches/server/0431-Add-LivingEntity-clearActiveItem.patch @@ -0,0 +1,24 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Anrza +Date: Wed, 15 Jul 2020 12:08:49 +0200 +Subject: [PATCH] Add LivingEntity#clearActiveItem + + +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftLivingEntity.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftLivingEntity.java +index db15382f35464de43a2fe0a41aca070a8c834777..de41ee2cc1be6ef412ec15c79a65cbca653b35d3 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftLivingEntity.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftLivingEntity.java +@@ -967,6 +967,13 @@ public class CraftLivingEntity extends CraftEntity implements LivingEntity { + return this.getHandle().getUseItem().asBukkitMirror(); + } + ++ // Paper start ++ @Override ++ public void clearActiveItem() { ++ getHandle().stopUsingItem(); ++ } ++ // Paper end ++ + @Override + public int getActiveItemRemainingTime() { + return this.getHandle().getUseItemRemainingTicks(); diff --git a/patches/server/0431-Add-Wandering-Trader-spawn-rate-config-options.patch b/patches/server/0431-Add-Wandering-Trader-spawn-rate-config-options.patch deleted file mode 100644 index 98525387d730..000000000000 --- a/patches/server/0431-Add-Wandering-Trader-spawn-rate-config-options.patch +++ /dev/null @@ -1,87 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Jason Penilla <11360596+jpenilla@users.noreply.github.com> -Date: Thu, 20 Aug 2020 11:20:12 -0700 -Subject: [PATCH] Add Wandering Trader spawn rate config options - -Adds config options for modifying the spawn rates of Wandering Traders. -These values are all easy to understand and configure after a quick read of this -page on the Minecraft wiki: https://minecraft.wiki/wiki/Wandering_Trader#Spawning -Usages of the vanilla WanderingTraderSpawnDelay and WanderingTraderSpawnChance values -in IWorldServerData are removed as they were only used in certain places, with hardcoded -values used in other places. - -diff --git a/src/main/java/net/minecraft/world/entity/npc/WanderingTraderSpawner.java b/src/main/java/net/minecraft/world/entity/npc/WanderingTraderSpawner.java -index 4b7e9bc8aa253d0eb12a2195416c618cccdc8690..d5d27b0d352ca3fd57a26605cb35c499e88b57fc 100644 ---- a/src/main/java/net/minecraft/world/entity/npc/WanderingTraderSpawner.java -+++ b/src/main/java/net/minecraft/world/entity/npc/WanderingTraderSpawner.java -@@ -40,43 +40,53 @@ public class WanderingTraderSpawner implements CustomSpawner { - - public WanderingTraderSpawner(ServerLevelData properties) { - this.serverLevelData = properties; -- this.tickDelay = 1200; -- this.spawnDelay = properties.getWanderingTraderSpawnDelay(); -- this.spawnChance = properties.getWanderingTraderSpawnChance(); -- if (this.spawnDelay == 0 && this.spawnChance == 0) { -- this.spawnDelay = 24000; -- properties.setWanderingTraderSpawnDelay(this.spawnDelay); -- this.spawnChance = 25; -- properties.setWanderingTraderSpawnChance(this.spawnChance); -- } -+ // Paper start - Add Wandering Trader spawn rate config options -+ this.tickDelay = Integer.MIN_VALUE; -+ //this.spawnDelay = properties.getWanderingTraderSpawnDelay(); // Paper - This value is read from the world file only for the first spawn, after which vanilla uses a hardcoded value -+ //this.spawnChance = properties.getWanderingTraderSpawnChance(); // Paper - This value is read from the world file only for the first spawn, after which vanilla uses a hardcoded value -+ //if (this.spawnDelay == 0 && this.spawnChance == 0) { -+ // this.spawnDelay = 24000; -+ // properties.setWanderingTraderSpawnDelay(this.spawnDelay); -+ // this.spawnChance = 25; -+ // properties.setWanderingTraderSpawnChance(this.spawnChance); -+ //} -+ // Paper end - Add Wandering Trader spawn rate config options - - } - - @Override - public int tick(ServerLevel world, boolean spawnMonsters, boolean spawnAnimals) { -+ // Paper start - Add Wandering Trader spawn rate config options -+ if (this.tickDelay == Integer.MIN_VALUE) { -+ this.tickDelay = world.paperConfig().entities.spawning.wanderingTrader.spawnMinuteLength; -+ this.spawnDelay = world.paperConfig().entities.spawning.wanderingTrader.spawnDayLength; -+ this.spawnChance = world.paperConfig().entities.spawning.wanderingTrader.spawnChanceMin; -+ } - if (!world.getGameRules().getBoolean(GameRules.RULE_DO_TRADER_SPAWNING)) { - return 0; -- } else if (--this.tickDelay > 0) { -+ } else if (this.tickDelay - 1 > 0) { -+ this.tickDelay = this.tickDelay - 1; - return 0; - } else { -- this.tickDelay = 1200; -- this.spawnDelay -= 1200; -- this.serverLevelData.setWanderingTraderSpawnDelay(this.spawnDelay); -+ this.tickDelay = world.paperConfig().entities.spawning.wanderingTrader.spawnMinuteLength; -+ this.spawnDelay = this.spawnDelay - world.paperConfig().entities.spawning.wanderingTrader.spawnMinuteLength; -+ //this.serverLevelData.setWanderingTraderSpawnDelay(this.spawnDelay); // Paper - We don't need to save this value to disk if it gets set back to a hardcoded value anyways - if (this.spawnDelay > 0) { - return 0; - } else { -- this.spawnDelay = 24000; -+ this.spawnDelay = world.paperConfig().entities.spawning.wanderingTrader.spawnDayLength; - if (!world.getGameRules().getBoolean(GameRules.RULE_DOMOBSPAWNING)) { - return 0; - } else { - int i = this.spawnChance; - -- this.spawnChance = Mth.clamp(this.spawnChance + 25, 25, 75); -- this.serverLevelData.setWanderingTraderSpawnChance(this.spawnChance); -+ // this.serverLevelData.setWanderingTraderSpawnChance(this.spawnChance); // Paper - We don't need to save this value to disk if it gets set back to a hardcoded value anyways -+ this.spawnChance = Mth.clamp(i + world.paperConfig().entities.spawning.wanderingTrader.spawnChanceFailureIncrement, world.paperConfig().entities.spawning.wanderingTrader.spawnChanceMin, world.paperConfig().entities.spawning.wanderingTrader.spawnChanceMax); - if (this.random.nextInt(100) > i) { - return 0; - } else if (this.spawn(world)) { -- this.spawnChance = 25; -+ this.spawnChance = world.paperConfig().entities.spawning.wanderingTrader.spawnChanceMin; -+ // Paper end - Add Wandering Trader spawn rate config options - return 1; - } else { - return 0; diff --git a/patches/server/0432-Add-Destroy-Speed-API.patch b/patches/server/0432-Add-Destroy-Speed-API.patch deleted file mode 100644 index 22e992d6a9fe..000000000000 --- a/patches/server/0432-Add-Destroy-Speed-API.patch +++ /dev/null @@ -1,220 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Ineusia -Date: Mon, 26 Oct 2020 11:48:06 -0500 -Subject: [PATCH] Add Destroy Speed API - -Co-authored-by: Jake Potrebic - -diff --git a/src/main/java/net/minecraft/world/entity/ai/attributes/AttributeInstance.java b/src/main/java/net/minecraft/world/entity/ai/attributes/AttributeInstance.java -index 36c943d709c1c0ae49ec0baf0ccf7b6cb9a2ecf3..d28f9e077a50122e86848cfa9db83f6b0e8eef6c 100644 ---- a/src/main/java/net/minecraft/world/entity/ai/attributes/AttributeInstance.java -+++ b/src/main/java/net/minecraft/world/entity/ai/attributes/AttributeInstance.java -@@ -143,20 +143,20 @@ public class AttributeInstance { - double d = this.getBaseValue(); - - for (AttributeModifier attributeModifier : this.getModifiersOrEmpty(AttributeModifier.Operation.ADD_VALUE)) { -- d += attributeModifier.amount(); -+ d += attributeModifier.amount(); // Paper - destroy speed API - diff on change - } - - double e = d; - - for (AttributeModifier attributeModifier2 : this.getModifiersOrEmpty(AttributeModifier.Operation.ADD_MULTIPLIED_BASE)) { -- e += d * attributeModifier2.amount(); -+ e += d * attributeModifier2.amount(); // Paper - destroy speed API - diff on change - } - - for (AttributeModifier attributeModifier3 : this.getModifiersOrEmpty(AttributeModifier.Operation.ADD_MULTIPLIED_TOTAL)) { -- e *= 1.0 + attributeModifier3.amount(); -+ e *= 1.0 + attributeModifier3.amount(); // Paper - destroy speed API - diff on change - } - -- return this.attribute.value().sanitizeValue(e); -+ return attribute.value().sanitizeValue(e); // Paper - destroy speed API - diff on change - } - - private Collection getModifiersOrEmpty(AttributeModifier.Operation operation) { -diff --git a/src/main/java/org/bukkit/craftbukkit/block/data/CraftBlockData.java b/src/main/java/org/bukkit/craftbukkit/block/data/CraftBlockData.java -index 9953b6b36cbcbfd1756bac478b568ca5700fc898..33869e725c3b3f2120fa36ca468019a78321e862 100644 ---- a/src/main/java/org/bukkit/craftbukkit/block/data/CraftBlockData.java -+++ b/src/main/java/org/bukkit/craftbukkit/block/data/CraftBlockData.java -@@ -721,4 +721,35 @@ public class CraftBlockData implements BlockData { - public BlockState createBlockState() { - return CraftBlockStates.getBlockState(this.state, null); - } -+ -+ // Paper start - destroy speed API -+ @Override -+ public float getDestroySpeed(final ItemStack itemStack, final boolean considerEnchants) { -+ net.minecraft.world.item.ItemStack nmsItemStack = CraftItemStack.unwrap(itemStack); -+ float speed = nmsItemStack.getDestroySpeed(this.state); -+ if (speed > 1.0F && considerEnchants) { -+ final net.minecraft.core.Holder attribute = net.minecraft.world.entity.ai.attributes.Attributes.MINING_EFFICIENCY; -+ // Logic sourced from AttributeInstance#calculateValue -+ final double initialBaseValue = attribute.value().getDefaultValue(); -+ final org.apache.commons.lang3.mutable.MutableDouble modifiedBaseValue = new org.apache.commons.lang3.mutable.MutableDouble(initialBaseValue); -+ final org.apache.commons.lang3.mutable.MutableDouble baseValMul = new org.apache.commons.lang3.mutable.MutableDouble(1); -+ final org.apache.commons.lang3.mutable.MutableDouble totalValMul = new org.apache.commons.lang3.mutable.MutableDouble(1); -+ -+ net.minecraft.world.item.enchantment.EnchantmentHelper.forEachModifier( -+ nmsItemStack, net.minecraft.world.entity.EquipmentSlot.MAINHAND, (attributeHolder, attributeModifier) -> { -+ switch (attributeModifier.operation()) { -+ case ADD_VALUE -> modifiedBaseValue.add(attributeModifier.amount()); -+ case ADD_MULTIPLIED_BASE -> baseValMul.add(attributeModifier.amount()); -+ case ADD_MULTIPLIED_TOTAL -> totalValMul.setValue(totalValMul.doubleValue() * (1D + attributeModifier.amount())); -+ } -+ } -+ ); -+ -+ final double actualModifier = modifiedBaseValue.doubleValue() * baseValMul.doubleValue() * totalValMul.doubleValue(); -+ -+ speed += (float) attribute.value().sanitizeValue(actualModifier); -+ } -+ return speed; -+ } -+ // Paper end - destroy speed API - } -diff --git a/src/test/java/io/papermc/paper/block/CraftBlockDataDestroySpeedTest.java b/src/test/java/io/papermc/paper/block/CraftBlockDataDestroySpeedTest.java -new file mode 100644 -index 0000000000000000000000000000000000000000..32d38205a5a72c3c1838ed28cb83bcea5ad59b6b ---- /dev/null -+++ b/src/test/java/io/papermc/paper/block/CraftBlockDataDestroySpeedTest.java -@@ -0,0 +1,138 @@ -+package io.papermc.paper.block; -+ -+import java.util.List; -+import java.util.Optional; -+import net.minecraft.core.Holder; -+import net.minecraft.core.HolderSet; -+import net.minecraft.core.component.DataComponentMap; -+import net.minecraft.core.component.DataComponents; -+import net.minecraft.network.chat.Component; -+import net.minecraft.world.entity.EquipmentSlot; -+import net.minecraft.world.entity.EquipmentSlotGroup; -+import net.minecraft.world.entity.ai.attributes.AttributeInstance; -+import net.minecraft.world.entity.ai.attributes.AttributeModifier; -+import net.minecraft.world.entity.ai.attributes.Attributes; -+import net.minecraft.world.item.Items; -+import net.minecraft.world.item.enchantment.Enchantment; -+import net.minecraft.world.item.enchantment.EnchantmentEffectComponents; -+import net.minecraft.world.item.enchantment.EnchantmentHelper; -+import net.minecraft.world.item.enchantment.ItemEnchantments; -+import net.minecraft.world.item.enchantment.LevelBasedValue; -+import net.minecraft.world.item.enchantment.effects.EnchantmentAttributeEffect; -+import net.minecraft.world.level.block.Blocks; -+import net.minecraft.world.level.block.state.BlockState; -+import org.bukkit.craftbukkit.block.data.CraftBlockData; -+import org.bukkit.craftbukkit.inventory.CraftItemStack; -+import org.bukkit.inventory.ItemStack; -+import org.bukkit.support.environment.AllFeatures; -+import org.bukkit.util.Vector; -+import org.jetbrains.annotations.NotNull; -+import org.junit.jupiter.api.Assertions; -+import org.junit.jupiter.api.Test; -+ -+import static net.minecraft.resources.ResourceLocation.fromNamespaceAndPath; -+ -+/** -+ * CraftBlockData's {@link org.bukkit.craftbukkit.block.data.CraftBlockData#getDestroySpeed(ItemStack, boolean)} -+ * uses a reimplementation of AttributeValue without any map to avoid attribute instance allocation and mutation -+ * for 0 gain. -+ *

    -+ * This test is responsible for ensuring that said logic emits the expected destroy speed under heavy attribute -+ * modifier use. -+ */ -+@AllFeatures -+public class CraftBlockDataDestroySpeedTest { -+ -+ @Test -+ public void testCorrectEnchantmentDestroySpeedComputation() { -+ // Construct fake enchantment that has *all and multiple of* operations -+ final Enchantment speedEnchantment = speedEnchantment(); -+ final BlockState blockStateToMine = Blocks.STONE.defaultBlockState(); -+ -+ final ItemEnchantments.Mutable mutable = new ItemEnchantments.Mutable(ItemEnchantments.EMPTY); -+ mutable.set(Holder.direct(speedEnchantment), 1); -+ -+ final net.minecraft.world.item.ItemStack itemStack = new net.minecraft.world.item.ItemStack(Items.DIAMOND_PICKAXE); -+ itemStack.set(DataComponents.ENCHANTMENTS, mutable.toImmutable()); -+ -+ // Compute expected value by running the entire attribute instance chain -+ final AttributeInstance dummyInstance = new AttributeInstance(Attributes.MINING_EFFICIENCY, $ -> { -+ }); -+ EnchantmentHelper.forEachModifier(itemStack, EquipmentSlot.MAINHAND, (attributeHolder, attributeModifier) -> { -+ if (attributeHolder.is(Attributes.MINING_EFFICIENCY)) dummyInstance.addTransientModifier(attributeModifier); -+ }); -+ -+ final double toolSpeed = itemStack.getDestroySpeed(blockStateToMine); -+ final double expectedSpeed = toolSpeed <= 1.0F ? toolSpeed : toolSpeed + dummyInstance.getValue(); -+ -+ // API stack + computation -+ final CraftItemStack craftMirror = CraftItemStack.asCraftMirror(itemStack); -+ final CraftBlockData data = CraftBlockData.createData(blockStateToMine); -+ final float actualSpeed = data.getDestroySpeed(craftMirror, true); -+ -+ Assertions.assertEquals(expectedSpeed, actualSpeed, Vector.getEpsilon()); -+ } -+ -+ /** -+ * Complex enchantment that holds attribute modifiers for the mining efficiency. -+ * The enchantment holds 2 of each operation to also ensure that such behaviour works correctly. -+ * -+ * @return the enchantment. -+ */ -+ private static @NotNull Enchantment speedEnchantment() { -+ return new Enchantment( -+ Component.empty(), -+ new Enchantment.EnchantmentDefinition( -+ HolderSet.empty(), -+ Optional.empty(), -+ 0, 0, -+ Enchantment.constantCost(0), -+ Enchantment.constantCost(0), -+ 0, -+ List.of(EquipmentSlotGroup.ANY) -+ ), -+ HolderSet.empty(), -+ DataComponentMap.builder() -+ .set(EnchantmentEffectComponents.ATTRIBUTES, List.of( -+ new EnchantmentAttributeEffect( -+ fromNamespaceAndPath("paper", "base1"), -+ Attributes.MINING_EFFICIENCY, -+ LevelBasedValue.constant(1), -+ AttributeModifier.Operation.ADD_VALUE -+ ), -+ new EnchantmentAttributeEffect( -+ fromNamespaceAndPath("paper", "base2"), -+ Attributes.MINING_EFFICIENCY, -+ LevelBasedValue.perLevel(3), -+ AttributeModifier.Operation.ADD_VALUE -+ ), -+ new EnchantmentAttributeEffect( -+ fromNamespaceAndPath("paper", "base-mul1"), -+ Attributes.MINING_EFFICIENCY, -+ LevelBasedValue.perLevel(7), -+ AttributeModifier.Operation.ADD_MULTIPLIED_BASE -+ ), -+ new EnchantmentAttributeEffect( -+ fromNamespaceAndPath("paper", "base-mul2"), -+ Attributes.MINING_EFFICIENCY, -+ LevelBasedValue.constant(10), -+ AttributeModifier.Operation.ADD_MULTIPLIED_BASE -+ ), -+ new EnchantmentAttributeEffect( -+ fromNamespaceAndPath("paper", "total-mul1"), -+ Attributes.MINING_EFFICIENCY, -+ LevelBasedValue.constant(.2f), -+ AttributeModifier.Operation.ADD_MULTIPLIED_TOTAL -+ ), -+ new EnchantmentAttributeEffect( -+ fromNamespaceAndPath("paper", "total-mul2"), -+ Attributes.MINING_EFFICIENCY, -+ LevelBasedValue.constant(-.5F), -+ AttributeModifier.Operation.ADD_MULTIPLIED_TOTAL -+ ) -+ )) -+ .build() -+ ); -+ } -+ -+} diff --git a/patches/server/0432-Add-PlayerItemCooldownEvent.patch b/patches/server/0432-Add-PlayerItemCooldownEvent.patch new file mode 100644 index 000000000000..954ff181f9fe --- /dev/null +++ b/patches/server/0432-Add-PlayerItemCooldownEvent.patch @@ -0,0 +1,27 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Nassim Jahnke +Date: Tue, 25 Aug 2020 13:48:33 +0200 +Subject: [PATCH] Add PlayerItemCooldownEvent + + +diff --git a/src/main/java/net/minecraft/world/item/ServerItemCooldowns.java b/src/main/java/net/minecraft/world/item/ServerItemCooldowns.java +index 3a45a149ec4a28f25ea9e45803ecbb7392b63f86..2a80b8e962bf9d01e720b5967f0feac7d5dcaa28 100644 +--- a/src/main/java/net/minecraft/world/item/ServerItemCooldowns.java ++++ b/src/main/java/net/minecraft/world/item/ServerItemCooldowns.java +@@ -11,6 +11,16 @@ public class ServerItemCooldowns extends ItemCooldowns { + this.player = player; + } + ++ // Paper start - Add PlayerItemCooldownEvent ++ @Override ++ public void addCooldown(ItemStack item, int duration) { ++ io.papermc.paper.event.player.PlayerItemCooldownEvent event = new io.papermc.paper.event.player.PlayerItemCooldownEvent(this.player.getBukkitEntity(), org.bukkit.craftbukkit.inventory.CraftItemType.minecraftToBukkit(item.getItem()), duration); ++ if (event.callEvent()) { ++ super.addCooldown(item, event.getCooldown()); ++ } ++ } ++ // Paper end - Add PlayerItemCooldownEvent ++ + @Override + protected void onCooldownStarted(ResourceLocation groupId, int duration) { + super.onCooldownStarted(groupId, duration); diff --git a/patches/server/0433-Fix-Player-spawnParticle-x-y-z-precision-loss.patch b/patches/server/0433-Fix-Player-spawnParticle-x-y-z-precision-loss.patch deleted file mode 100644 index e34ff10d4d2f..000000000000 --- a/patches/server/0433-Fix-Player-spawnParticle-x-y-z-precision-loss.patch +++ /dev/null @@ -1,19 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Esophose -Date: Sat, 3 Oct 2020 18:57:47 -0600 -Subject: [PATCH] Fix Player spawnParticle x/y/z precision loss - - -diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java -index 854533854dfba24b59a15265ac759331e3ddfc74..73fde8be0098238582fb3661ae67d4139be417dc 100644 ---- a/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java -+++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java -@@ -2707,7 +2707,7 @@ public class CraftPlayer extends CraftHumanEntity implements Player { - - @Override - public void spawnParticle(Particle particle, double x, double y, double z, int count, double offsetX, double offsetY, double offsetZ, double extra, T data, boolean force) { -- ClientboundLevelParticlesPacket packetplayoutworldparticles = new ClientboundLevelParticlesPacket(CraftParticle.createParticleParam(particle, data), force, (float) x, (float) y, (float) z, (float) offsetX, (float) offsetY, (float) offsetZ, (float) extra, count); -+ ClientboundLevelParticlesPacket packetplayoutworldparticles = new ClientboundLevelParticlesPacket(CraftParticle.createParticleParam(particle, data), force, x, y, z, (float) offsetX, (float) offsetY, (float) offsetZ, (float) extra, count); // Paper - fix x/y/z precision loss - this.getHandle().connection.send(packetplayoutworldparticles); - } - diff --git a/patches/server/0436-Significantly-improve-performance-of-the-end-generat.patch b/patches/server/0433-Significantly-improve-performance-of-the-end-generat.patch similarity index 100% rename from patches/server/0436-Significantly-improve-performance-of-the-end-generat.patch rename to patches/server/0433-Significantly-improve-performance-of-the-end-generat.patch diff --git a/patches/server/0434-Add-LivingEntity-clearActiveItem.patch b/patches/server/0434-Add-LivingEntity-clearActiveItem.patch deleted file mode 100644 index c22e85e85866..000000000000 --- a/patches/server/0434-Add-LivingEntity-clearActiveItem.patch +++ /dev/null @@ -1,24 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Anrza -Date: Wed, 15 Jul 2020 12:08:49 +0200 -Subject: [PATCH] Add LivingEntity#clearActiveItem - - -diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftLivingEntity.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftLivingEntity.java -index 8a8189e8f2f201880748eb79805bb0b33688e814..925c842fcf546ad270641b3be7e8a8c432571501 100644 ---- a/src/main/java/org/bukkit/craftbukkit/entity/CraftLivingEntity.java -+++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftLivingEntity.java -@@ -948,6 +948,13 @@ public class CraftLivingEntity extends CraftEntity implements LivingEntity { - return this.getHandle().getUseItem().asBukkitMirror(); - } - -+ // Paper start -+ @Override -+ public void clearActiveItem() { -+ getHandle().stopUsingItem(); -+ } -+ // Paper end -+ - @Override - public int getActiveItemRemainingTime() { - return this.getHandle().getUseItemRemainingTicks(); diff --git a/patches/server/0437-More-lightning-API.patch b/patches/server/0434-More-lightning-API.patch similarity index 100% rename from patches/server/0437-More-lightning-API.patch rename to patches/server/0434-More-lightning-API.patch diff --git a/patches/server/0435-Add-PlayerItemCooldownEvent.patch b/patches/server/0435-Add-PlayerItemCooldownEvent.patch deleted file mode 100644 index 4b90903dd251..000000000000 --- a/patches/server/0435-Add-PlayerItemCooldownEvent.patch +++ /dev/null @@ -1,27 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Nassim Jahnke -Date: Tue, 25 Aug 2020 13:48:33 +0200 -Subject: [PATCH] Add PlayerItemCooldownEvent - - -diff --git a/src/main/java/net/minecraft/world/item/ServerItemCooldowns.java b/src/main/java/net/minecraft/world/item/ServerItemCooldowns.java -index 47283d2a49209839002212e663a503a82ea86587..e0c4c0a9bab0bbc32358030a482aa04c2e1d3894 100644 ---- a/src/main/java/net/minecraft/world/item/ServerItemCooldowns.java -+++ b/src/main/java/net/minecraft/world/item/ServerItemCooldowns.java -@@ -10,6 +10,16 @@ public class ServerItemCooldowns extends ItemCooldowns { - this.player = player; - } - -+ // Paper start - Add PlayerItemCooldownEvent -+ @Override -+ public void addCooldown(Item item, int duration) { -+ io.papermc.paper.event.player.PlayerItemCooldownEvent event = new io.papermc.paper.event.player.PlayerItemCooldownEvent(this.player.getBukkitEntity(), org.bukkit.craftbukkit.inventory.CraftItemType.minecraftToBukkit(item), duration); -+ if (event.callEvent()) { -+ super.addCooldown(item, event.getCooldown()); -+ } -+ } -+ // Paper end - Add PlayerItemCooldownEvent -+ - @Override - protected void onCooldownStarted(Item item, int duration) { - super.onCooldownStarted(item, duration); diff --git a/patches/server/0435-Climbing-should-not-bypass-cramming-gamerule.patch b/patches/server/0435-Climbing-should-not-bypass-cramming-gamerule.patch new file mode 100644 index 000000000000..cb1a5d5b2ac3 --- /dev/null +++ b/patches/server/0435-Climbing-should-not-bypass-cramming-gamerule.patch @@ -0,0 +1,156 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: William Blake Galbreath +Date: Sun, 23 Aug 2020 20:59:00 +0200 +Subject: [PATCH] Climbing should not bypass cramming gamerule + + +diff --git a/src/main/java/net/minecraft/world/entity/Entity.java b/src/main/java/net/minecraft/world/entity/Entity.java +index 6cefe8e0de375d3b192cc8be2b553a2dcbe098f5..7feed6383fdc367796ccb943bd086814ec989e6d 100644 +--- a/src/main/java/net/minecraft/world/entity/Entity.java ++++ b/src/main/java/net/minecraft/world/entity/Entity.java +@@ -2195,6 +2195,12 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess + } + + public boolean isPushable() { ++ // Paper start - Climbing should not bypass cramming gamerule ++ return isCollidable(false); ++ } ++ ++ public boolean isCollidable(boolean ignoreClimbing) { ++ // Paper end - Climbing should not bypass cramming gamerule + return false; + } + +diff --git a/src/main/java/net/minecraft/world/entity/EntitySelector.java b/src/main/java/net/minecraft/world/entity/EntitySelector.java +index b8d57e25851dd7da905100dfd4022e4b99fd7f02..721321a19ce056f82de2bef44a8791dc9d5b3418 100644 +--- a/src/main/java/net/minecraft/world/entity/EntitySelector.java ++++ b/src/main/java/net/minecraft/world/entity/EntitySelector.java +@@ -45,11 +45,16 @@ public final class EntitySelector { + } + + public static Predicate pushableBy(Entity entity) { ++ // Paper start - Climbing should not bypass cramming gamerule ++ return pushable(entity, false); ++ } ++ public static Predicate pushable(Entity entity, boolean ignoreClimbing) { ++ // Paper end - Climbing should not bypass cramming gamerule + PlayerTeam scoreboardteam = entity.getTeam(); + Team.CollisionRule scoreboardteambase_enumteampush = scoreboardteam == null ? Team.CollisionRule.ALWAYS : scoreboardteam.getCollisionRule(); + + return (Predicate) (scoreboardteambase_enumteampush == Team.CollisionRule.NEVER ? Predicates.alwaysFalse() : EntitySelector.NO_SPECTATORS.and((entity1) -> { +- if (!entity1.canCollideWithBukkit(entity) || !entity.canCollideWithBukkit(entity1)) { // CraftBukkit - collidable API ++ if (!entity1.isCollidable(ignoreClimbing) || !entity1.canCollideWithBukkit(entity) || !entity.canCollideWithBukkit(entity1)) { // CraftBukkit - collidable API // Paper - Climbing should not bypass cramming gamerule + return false; + } else if (entity1 instanceof Player && entity instanceof Player && !io.papermc.paper.configuration.GlobalConfiguration.get().collisions.enablePlayerCollisions) { // Paper - Configurable player collision + return false; +diff --git a/src/main/java/net/minecraft/world/entity/LivingEntity.java b/src/main/java/net/minecraft/world/entity/LivingEntity.java +index 7e3670bee52407d768d1f7c0be66d7c0d0f72c46..d361e39ba50958e7bdaea0b95c37d13f48a89771 100644 +--- a/src/main/java/net/minecraft/world/entity/LivingEntity.java ++++ b/src/main/java/net/minecraft/world/entity/LivingEntity.java +@@ -3710,7 +3710,7 @@ public abstract class LivingEntity extends Entity implements Attackable { + return; + } + // Paper end - don't run getEntities if we're not going to use its result +- List list = this.level().getEntities((Entity) this, this.getBoundingBox(), EntitySelector.pushableBy(this)); ++ List list = this.level().getEntities((Entity) this, this.getBoundingBox(), EntitySelector.pushable(this, this.level().paperConfig().collisions.fixClimbingBypassingCrammingRule)); // Paper - Climbing should not bypass cramming gamerule + + if (!list.isEmpty()) { + // Paper - don't run getEntities if we're not going to use its result; moved up +@@ -3915,9 +3915,16 @@ public abstract class LivingEntity extends Entity implements Attackable { + return !this.isRemoved() && this.collides; // CraftBukkit + } + ++ // Paper start - Climbing should not bypass cramming gamerule + @Override + public boolean isPushable() { +- return this.isAlive() && !this.isSpectator() && !this.onClimbable() && this.collides; // CraftBukkit ++ return this.isCollidable(this.level().paperConfig().collisions.fixClimbingBypassingCrammingRule); ++ } ++ ++ @Override ++ public boolean isCollidable(boolean ignoreClimbing) { ++ return this.isAlive() && !this.isSpectator() && (ignoreClimbing || !this.onClimbable()) && this.collides; // CraftBukkit ++ // Paper end - Climbing should not bypass cramming gamerule + } + + // CraftBukkit start - collidable API +diff --git a/src/main/java/net/minecraft/world/entity/ambient/Bat.java b/src/main/java/net/minecraft/world/entity/ambient/Bat.java +index d8a2fbb33f4b77ffb1b3c928a369c2824ad815d7..60c2868f255d372226e0c1389caaa5477bbef41e 100644 +--- a/src/main/java/net/minecraft/world/entity/ambient/Bat.java ++++ b/src/main/java/net/minecraft/world/entity/ambient/Bat.java +@@ -91,7 +91,7 @@ public class Bat extends AmbientCreature { + } + + @Override +- public boolean isPushable() { ++ public boolean isCollidable(boolean ignoreClimbing) { // Paper - Climbing should not bypass cramming gamerule + return false; + } + +diff --git a/src/main/java/net/minecraft/world/entity/animal/Parrot.java b/src/main/java/net/minecraft/world/entity/animal/Parrot.java +index 9a187b9b20e4e5ee4cdb7df31629faee9d3ebb60..8883894da73c7d5975a8826d23ee7f542db98b0b 100644 +--- a/src/main/java/net/minecraft/world/entity/animal/Parrot.java ++++ b/src/main/java/net/minecraft/world/entity/animal/Parrot.java +@@ -364,8 +364,8 @@ public class Parrot extends ShoulderRidingEntity implements VariantHolder +Date: Thu, 27 Aug 2020 15:02:48 -0400 +Subject: [PATCH] Add PlayerShearBlockEvent + + +diff --git a/src/main/java/net/minecraft/world/level/block/BeehiveBlock.java b/src/main/java/net/minecraft/world/level/block/BeehiveBlock.java +index 7e2a39a1ee092b46ab46caec0214255d9c333536..20f705c90a0a96321cfe29c0cf564013dcccd18f 100644 +--- a/src/main/java/net/minecraft/world/level/block/BeehiveBlock.java ++++ b/src/main/java/net/minecraft/world/level/block/BeehiveBlock.java +@@ -141,7 +141,7 @@ public class BeehiveBlock extends BaseEntityBlock { + } + + public static void dropHoneycomb(Level world, BlockPos pos) { +- popResource(world, pos, new ItemStack(Items.HONEYCOMB, 3)); ++ popResource(world, pos, new ItemStack(Items.HONEYCOMB, 3)); // Paper - Add PlayerShearBlockEvent; conflict on change, item needs to be set below + } + + @Override +@@ -153,8 +153,19 @@ public class BeehiveBlock extends BaseEntityBlock { + Item item = stack.getItem(); + + if (stack.is(Items.SHEARS)) { ++ // Paper start - Add PlayerShearBlockEvent ++ io.papermc.paper.event.block.PlayerShearBlockEvent event = new io.papermc.paper.event.block.PlayerShearBlockEvent((org.bukkit.entity.Player) player.getBukkitEntity(), org.bukkit.craftbukkit.block.CraftBlock.at(world, pos), org.bukkit.craftbukkit.inventory.CraftItemStack.asCraftMirror(stack), org.bukkit.craftbukkit.CraftEquipmentSlot.getHand(hand), new java.util.ArrayList<>()); ++ event.getDrops().add(org.bukkit.craftbukkit.inventory.CraftItemStack.asCraftMirror(new ItemStack(Items.HONEYCOMB, 3))); ++ if (!event.callEvent()) { ++ return InteractionResult.PASS; ++ } ++ // Paper end + world.playSound(player, player.getX(), player.getY(), player.getZ(), SoundEvents.BEEHIVE_SHEAR, SoundSource.BLOCKS, 1.0F, 1.0F); +- BeehiveBlock.dropHoneycomb(world, pos); ++ // Paper start - Add PlayerShearBlockEvent ++ for (org.bukkit.inventory.ItemStack itemDrop : event.getDrops()) { ++ popResource(world, pos, org.bukkit.craftbukkit.inventory.CraftItemStack.asNMSCopy(itemDrop)); ++ } ++ // Paper end - Add PlayerShearBlockEvent + stack.hurtAndBreak(1, player, LivingEntity.getSlotForHand(hand)); + flag = true; + world.gameEvent((Entity) player, (Holder) GameEvent.SHEAR, pos); +diff --git a/src/main/java/net/minecraft/world/level/block/PumpkinBlock.java b/src/main/java/net/minecraft/world/level/block/PumpkinBlock.java +index dad7a610286b0ab6b916a6169fa8f6f641deb39a..2b43c77d5aa609d4716df827cefcf008dfd13a06 100644 +--- a/src/main/java/net/minecraft/world/level/block/PumpkinBlock.java ++++ b/src/main/java/net/minecraft/world/level/block/PumpkinBlock.java +@@ -38,16 +38,24 @@ public class PumpkinBlock extends Block { + } else if (world.isClientSide) { + return InteractionResult.SUCCESS; + } else { ++ // Paper start - Add PlayerShearBlockEvent ++ io.papermc.paper.event.block.PlayerShearBlockEvent event = new io.papermc.paper.event.block.PlayerShearBlockEvent((org.bukkit.entity.Player) player.getBukkitEntity(), org.bukkit.craftbukkit.block.CraftBlock.at(world, pos), org.bukkit.craftbukkit.inventory.CraftItemStack.asCraftMirror(stack), org.bukkit.craftbukkit.CraftEquipmentSlot.getHand(hand), new java.util.ArrayList<>()); ++ event.getDrops().add(org.bukkit.craftbukkit.inventory.CraftItemStack.asCraftMirror(new ItemStack(Items.PUMPKIN_SEEDS, 4))); ++ if (!event.callEvent()) { ++ return InteractionResult.PASS; ++ } ++ // Paper end - Add PlayerShearBlockEvent + Direction direction = hit.getDirection(); + Direction direction2 = direction.getAxis() == Direction.Axis.Y ? player.getDirection().getOpposite() : direction; + world.playSound(null, pos, SoundEvents.PUMPKIN_CARVE, SoundSource.BLOCKS, 1.0F, 1.0F); + world.setBlock(pos, Blocks.CARVED_PUMPKIN.defaultBlockState().setValue(CarvedPumpkinBlock.FACING, direction2), 11); ++ for (org.bukkit.inventory.ItemStack item : event.getDrops()) { // Paper - Add PlayerShearBlockEvent + ItemEntity itemEntity = new ItemEntity( + world, + (double)pos.getX() + 0.5 + (double)direction2.getStepX() * 0.65, + (double)pos.getY() + 0.1, + (double)pos.getZ() + 0.5 + (double)direction2.getStepZ() * 0.65, +- new ItemStack(Items.PUMPKIN_SEEDS, 4) ++ org.bukkit.craftbukkit.inventory.CraftItemStack.asNMSCopy(item) // Paper - Add PlayerShearBlockEvent + ); + itemEntity.setDeltaMovement( + 0.05 * (double)direction2.getStepX() + world.random.nextDouble() * 0.02, +@@ -55,6 +63,7 @@ public class PumpkinBlock extends Block { + 0.05 * (double)direction2.getStepZ() + world.random.nextDouble() * 0.02 + ); + world.addFreshEntity(itemEntity); ++ } // Paper - Add PlayerShearBlockEvent + stack.hurtAndBreak(1, player, LivingEntity.getSlotForHand(hand)); + world.gameEvent(player, GameEvent.SHEAR, pos); + player.awardStat(Stats.ITEM_USED.get(Items.SHEARS)); diff --git a/patches/server/0438-Climbing-should-not-bypass-cramming-gamerule.patch b/patches/server/0438-Climbing-should-not-bypass-cramming-gamerule.patch deleted file mode 100644 index 0f9101c994d3..000000000000 --- a/patches/server/0438-Climbing-should-not-bypass-cramming-gamerule.patch +++ /dev/null @@ -1,156 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: William Blake Galbreath -Date: Sun, 23 Aug 2020 20:59:00 +0200 -Subject: [PATCH] Climbing should not bypass cramming gamerule - - -diff --git a/src/main/java/net/minecraft/world/entity/Entity.java b/src/main/java/net/minecraft/world/entity/Entity.java -index 6ab665efcac4c2543bab9d95472026e0ec8580c5..dae7643897b47dba304cbea56112445df2736cff 100644 ---- a/src/main/java/net/minecraft/world/entity/Entity.java -+++ b/src/main/java/net/minecraft/world/entity/Entity.java -@@ -2079,6 +2079,12 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess - } - - public boolean isPushable() { -+ // Paper start - Climbing should not bypass cramming gamerule -+ return isCollidable(false); -+ } -+ -+ public boolean isCollidable(boolean ignoreClimbing) { -+ // Paper end - Climbing should not bypass cramming gamerule - return false; - } - -diff --git a/src/main/java/net/minecraft/world/entity/EntitySelector.java b/src/main/java/net/minecraft/world/entity/EntitySelector.java -index eb425fac573881f3aad09ae75a32184fb0e325a6..302decdccd37c5579473c8fc33adda3956be7603 100644 ---- a/src/main/java/net/minecraft/world/entity/EntitySelector.java -+++ b/src/main/java/net/minecraft/world/entity/EntitySelector.java -@@ -46,11 +46,16 @@ public final class EntitySelector { - } - - public static Predicate pushableBy(Entity entity) { -+ // Paper start - Climbing should not bypass cramming gamerule -+ return pushable(entity, false); -+ } -+ public static Predicate pushable(Entity entity, boolean ignoreClimbing) { -+ // Paper end - Climbing should not bypass cramming gamerule - PlayerTeam scoreboardteam = entity.getTeam(); - Team.CollisionRule scoreboardteambase_enumteampush = scoreboardteam == null ? Team.CollisionRule.ALWAYS : scoreboardteam.getCollisionRule(); - - return (Predicate) (scoreboardteambase_enumteampush == Team.CollisionRule.NEVER ? Predicates.alwaysFalse() : EntitySelector.NO_SPECTATORS.and((entity1) -> { -- if (!entity1.canCollideWithBukkit(entity) || !entity.canCollideWithBukkit(entity1)) { // CraftBukkit - collidable API -+ if (!entity1.isCollidable(ignoreClimbing) || !entity1.canCollideWithBukkit(entity) || !entity.canCollideWithBukkit(entity1)) { // CraftBukkit - collidable API // Paper - Climbing should not bypass cramming gamerule - return false; - } else if (entity1 instanceof Player && entity instanceof Player && !io.papermc.paper.configuration.GlobalConfiguration.get().collisions.enablePlayerCollisions) { // Paper - Configurable player collision - return false; -diff --git a/src/main/java/net/minecraft/world/entity/LivingEntity.java b/src/main/java/net/minecraft/world/entity/LivingEntity.java -index 906e8589483b93bcdb55677f7f942f6c7a89caf8..d94c3e86885c5a8ba636988d29f98b496e13b627 100644 ---- a/src/main/java/net/minecraft/world/entity/LivingEntity.java -+++ b/src/main/java/net/minecraft/world/entity/LivingEntity.java -@@ -3573,7 +3573,7 @@ public abstract class LivingEntity extends Entity implements Attackable { - return; - } - // Paper end - don't run getEntities if we're not going to use its result -- List list = this.level().getEntities((Entity) this, this.getBoundingBox(), EntitySelector.pushableBy(this)); -+ List list = this.level().getEntities((Entity) this, this.getBoundingBox(), EntitySelector.pushable(this, this.level().paperConfig().collisions.fixClimbingBypassingCrammingRule)); // Paper - Climbing should not bypass cramming gamerule - - if (!list.isEmpty()) { - // Paper - don't run getEntities if we're not going to use its result; moved up -@@ -3765,9 +3765,16 @@ public abstract class LivingEntity extends Entity implements Attackable { - return !this.isRemoved() && this.collides; // CraftBukkit - } - -+ // Paper start - Climbing should not bypass cramming gamerule - @Override - public boolean isPushable() { -- return this.isAlive() && !this.isSpectator() && !this.onClimbable() && this.collides; // CraftBukkit -+ return this.isCollidable(this.level().paperConfig().collisions.fixClimbingBypassingCrammingRule); -+ } -+ -+ @Override -+ public boolean isCollidable(boolean ignoreClimbing) { -+ return this.isAlive() && !this.isSpectator() && (ignoreClimbing || !this.onClimbable()) && this.collides; // CraftBukkit -+ // Paper end - Climbing should not bypass cramming gamerule - } - - // CraftBukkit start - collidable API -diff --git a/src/main/java/net/minecraft/world/entity/ambient/Bat.java b/src/main/java/net/minecraft/world/entity/ambient/Bat.java -index 934fa26b8f347b8b2d90f79370b62165c0cdc312..dc27ddf5131e7398a5390a5187261d4c7fb6ccaa 100644 ---- a/src/main/java/net/minecraft/world/entity/ambient/Bat.java -+++ b/src/main/java/net/minecraft/world/entity/ambient/Bat.java -@@ -88,7 +88,7 @@ public class Bat extends AmbientCreature { - } - - @Override -- public boolean isPushable() { -+ public boolean isCollidable(boolean ignoreClimbing) { // Paper - Climbing should not bypass cramming gamerule - return false; - } - -diff --git a/src/main/java/net/minecraft/world/entity/animal/Parrot.java b/src/main/java/net/minecraft/world/entity/animal/Parrot.java -index 12b2267cba476547d510d9161b25a28f4f5c798e..97931bfd360725945ab9606ff698b518ae101076 100644 ---- a/src/main/java/net/minecraft/world/entity/animal/Parrot.java -+++ b/src/main/java/net/minecraft/world/entity/animal/Parrot.java -@@ -362,8 +362,8 @@ public class Parrot extends ShoulderRidingEntity implements VariantHolder +Date: Sat, 12 Dec 2020 23:45:28 +0000 +Subject: [PATCH] Limit recipe packets + + +diff --git a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java +index fc06281b6d37d72a0a333343fc800bd388713a5f..f2c1893677004b1699beb16ebfcbe9816aad2a78 100644 +--- a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java ++++ b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java +@@ -277,6 +277,7 @@ public class ServerGamePacketListenerImpl extends ServerCommonPacketListenerImpl + private final TickThrottler chatSpamThrottler = new TickThrottler(20, 200); + private final TickThrottler tabSpamThrottler = new TickThrottler(io.papermc.paper.configuration.GlobalConfiguration.get().spamLimiter.tabSpamIncrement, io.papermc.paper.configuration.GlobalConfiguration.get().spamLimiter.tabSpamLimit); // Paper - configurable tab spam limits + private final TickThrottler dropSpamThrottler = new TickThrottler(20, 1480); ++ private final TickThrottler recipeSpamPackets = new TickThrottler(io.papermc.paper.configuration.GlobalConfiguration.get().spamLimiter.recipeSpamIncrement, io.papermc.paper.configuration.GlobalConfiguration.get().spamLimiter.recipeSpamLimit); + private double firstGoodX; + private double firstGoodY; + private double firstGoodZ; +@@ -393,6 +394,7 @@ public class ServerGamePacketListenerImpl extends ServerCommonPacketListenerImpl + this.keepConnectionAlive(); + this.chatSpamThrottler.tick(); + this.tabSpamThrottler.tick(); // Paper - configurable tab spam limits ++ this.recipeSpamPackets.tick(); // Paper - auto recipe limit + this.dropSpamThrottler.tick(); + if (this.player.getLastActionTime() > 0L && this.server.getPlayerIdleTimeout() > 0 && Util.getMillis() - this.player.getLastActionTime() > (long) this.server.getPlayerIdleTimeout() * 1000L * 60L) { + this.player.resetLastActionTime(); // CraftBukkit - SPIGOT-854 +@@ -3088,6 +3090,14 @@ public class ServerGamePacketListenerImpl extends ServerCommonPacketListenerImpl + + @Override + public void handlePlaceRecipe(ServerboundPlaceRecipePacket packet) { ++ // Paper start - auto recipe limit ++ if (!org.bukkit.Bukkit.isPrimaryThread()) { ++ if (!this.recipeSpamPackets.isIncrementAndUnderThreshold()) { ++ this.disconnect(net.minecraft.network.chat.Component.translatable("disconnect.spam")); ++ return; ++ } ++ } ++ // Paper end - auto recipe limit + PacketUtils.ensureRunningOnSameThread(packet, this, this.player.serverLevel()); + this.player.resetLastActionTime(); + if (!this.player.isSpectator() && this.player.containerMenu.containerId == packet.containerId()) { diff --git a/patches/server/0442-Fix-CraftSound-backwards-compatibility.patch b/patches/server/0439-Fix-CraftSound-backwards-compatibility.patch similarity index 100% rename from patches/server/0442-Fix-CraftSound-backwards-compatibility.patch rename to patches/server/0439-Fix-CraftSound-backwards-compatibility.patch diff --git a/patches/server/0440-Add-PlayerShearBlockEvent.patch b/patches/server/0440-Add-PlayerShearBlockEvent.patch deleted file mode 100644 index c659b655c0e0..000000000000 --- a/patches/server/0440-Add-PlayerShearBlockEvent.patch +++ /dev/null @@ -1,78 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: JRoy -Date: Thu, 27 Aug 2020 15:02:48 -0400 -Subject: [PATCH] Add PlayerShearBlockEvent - - -diff --git a/src/main/java/net/minecraft/world/level/block/BeehiveBlock.java b/src/main/java/net/minecraft/world/level/block/BeehiveBlock.java -index c4b9c574bfb034fc78a596367f0f41dbde5eb93d..8d6736003934c5958f600660bdee58b386c39da4 100644 ---- a/src/main/java/net/minecraft/world/level/block/BeehiveBlock.java -+++ b/src/main/java/net/minecraft/world/level/block/BeehiveBlock.java -@@ -127,7 +127,7 @@ public class BeehiveBlock extends BaseEntityBlock { - } - - public static void dropHoneycomb(Level world, BlockPos pos) { -- popResource(world, pos, new ItemStack(Items.HONEYCOMB, 3)); -+ popResource(world, pos, new ItemStack(Items.HONEYCOMB, 3)); // Paper - Add PlayerShearBlockEvent; conflict on change, item needs to be set below - } - - @Override -@@ -139,8 +139,19 @@ public class BeehiveBlock extends BaseEntityBlock { - Item item = stack.getItem(); - - if (stack.is(Items.SHEARS)) { -+ // Paper start - Add PlayerShearBlockEvent -+ io.papermc.paper.event.block.PlayerShearBlockEvent event = new io.papermc.paper.event.block.PlayerShearBlockEvent((org.bukkit.entity.Player) player.getBukkitEntity(), org.bukkit.craftbukkit.block.CraftBlock.at(world, pos), org.bukkit.craftbukkit.inventory.CraftItemStack.asCraftMirror(stack), org.bukkit.craftbukkit.CraftEquipmentSlot.getHand(hand), new java.util.ArrayList<>()); -+ event.getDrops().add(org.bukkit.craftbukkit.inventory.CraftItemStack.asCraftMirror(new ItemStack(Items.HONEYCOMB, 3))); -+ if (!event.callEvent()) { -+ return ItemInteractionResult.SKIP_DEFAULT_BLOCK_INTERACTION; -+ } -+ // Paper end - world.playSound(player, player.getX(), player.getY(), player.getZ(), SoundEvents.BEEHIVE_SHEAR, SoundSource.BLOCKS, 1.0F, 1.0F); -- BeehiveBlock.dropHoneycomb(world, pos); -+ // Paper start - Add PlayerShearBlockEvent -+ for (org.bukkit.inventory.ItemStack itemDrop : event.getDrops()) { -+ popResource(world, pos, org.bukkit.craftbukkit.inventory.CraftItemStack.asNMSCopy(itemDrop)); -+ } -+ // Paper end - Add PlayerShearBlockEvent - stack.hurtAndBreak(1, player, LivingEntity.getSlotForHand(hand)); - flag = true; - world.gameEvent((Entity) player, (Holder) GameEvent.SHEAR, pos); -diff --git a/src/main/java/net/minecraft/world/level/block/PumpkinBlock.java b/src/main/java/net/minecraft/world/level/block/PumpkinBlock.java -index aa8667f0b14dc8944dd3457b431162e59bf54ada..5f5b2dd2bb45a0d3ae7de063b3bc611d01af21c0 100644 ---- a/src/main/java/net/minecraft/world/level/block/PumpkinBlock.java -+++ b/src/main/java/net/minecraft/world/level/block/PumpkinBlock.java -@@ -40,16 +40,24 @@ public class PumpkinBlock extends Block { - } else if (world.isClientSide) { - return ItemInteractionResult.sidedSuccess(world.isClientSide); - } else { -+ // Paper start - Add PlayerShearBlockEvent -+ io.papermc.paper.event.block.PlayerShearBlockEvent event = new io.papermc.paper.event.block.PlayerShearBlockEvent((org.bukkit.entity.Player) player.getBukkitEntity(), org.bukkit.craftbukkit.block.CraftBlock.at(world, pos), org.bukkit.craftbukkit.inventory.CraftItemStack.asCraftMirror(stack), org.bukkit.craftbukkit.CraftEquipmentSlot.getHand(hand), new java.util.ArrayList<>()); -+ event.getDrops().add(org.bukkit.craftbukkit.inventory.CraftItemStack.asCraftMirror(new ItemStack(Items.PUMPKIN_SEEDS, 4))); -+ if (!event.callEvent()) { -+ return ItemInteractionResult.SKIP_DEFAULT_BLOCK_INTERACTION; -+ } -+ // Paper end - Add PlayerShearBlockEvent - Direction direction = hit.getDirection(); - Direction direction2 = direction.getAxis() == Direction.Axis.Y ? player.getDirection().getOpposite() : direction; - world.playSound(null, pos, SoundEvents.PUMPKIN_CARVE, SoundSource.BLOCKS, 1.0F, 1.0F); - world.setBlock(pos, Blocks.CARVED_PUMPKIN.defaultBlockState().setValue(CarvedPumpkinBlock.FACING, direction2), 11); -+ for (org.bukkit.inventory.ItemStack item : event.getDrops()) { // Paper - Add PlayerShearBlockEvent - ItemEntity itemEntity = new ItemEntity( - world, - (double)pos.getX() + 0.5 + (double)direction2.getStepX() * 0.65, - (double)pos.getY() + 0.1, - (double)pos.getZ() + 0.5 + (double)direction2.getStepZ() * 0.65, -- new ItemStack(Items.PUMPKIN_SEEDS, 4) -+ org.bukkit.craftbukkit.inventory.CraftItemStack.asNMSCopy(item) // Paper - Add PlayerShearBlockEvent - ); - itemEntity.setDeltaMovement( - 0.05 * (double)direction2.getStepX() + world.random.nextDouble() * 0.02, -@@ -57,6 +65,7 @@ public class PumpkinBlock extends Block { - 0.05 * (double)direction2.getStepZ() + world.random.nextDouble() * 0.02 - ); - world.addFreshEntity(itemEntity); -+ } // Paper - Add PlayerShearBlockEvent - stack.hurtAndBreak(1, player, LivingEntity.getSlotForHand(hand)); - world.gameEvent(player, GameEvent.SHEAR, pos); - player.awardStat(Stats.ITEM_USED.get(Items.SHEARS)); diff --git a/patches/server/0443-Player-Chunk-Load-Unload-Events.patch b/patches/server/0440-Player-Chunk-Load-Unload-Events.patch similarity index 100% rename from patches/server/0443-Player-Chunk-Load-Unload-Events.patch rename to patches/server/0440-Player-Chunk-Load-Unload-Events.patch diff --git a/patches/server/0441-Limit-recipe-packets.patch b/patches/server/0441-Limit-recipe-packets.patch deleted file mode 100644 index a18fe7565015..000000000000 --- a/patches/server/0441-Limit-recipe-packets.patch +++ /dev/null @@ -1,41 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Shane Freeder -Date: Sat, 12 Dec 2020 23:45:28 +0000 -Subject: [PATCH] Limit recipe packets - - -diff --git a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java -index d33cf08f7c47111823abbce481505946db249577..57063f01993445a9965976332306c7f2ab20ed1f 100644 ---- a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java -+++ b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java -@@ -266,6 +266,7 @@ public class ServerGamePacketListenerImpl extends ServerCommonPacketListenerImpl - // CraftBukkit start - multithreaded fields - private final AtomicInteger chatSpamTickCount = new AtomicInteger(); - private final java.util.concurrent.atomic.AtomicInteger tabSpamLimiter = new java.util.concurrent.atomic.AtomicInteger(); // Paper - configurable tab spam limits -+ private final java.util.concurrent.atomic.AtomicInteger recipeSpamPackets = new java.util.concurrent.atomic.AtomicInteger(); // Paper - auto recipe limit - // CraftBukkit end - private int dropSpamTickCount; - private double firstGoodX; -@@ -384,6 +385,7 @@ public class ServerGamePacketListenerImpl extends ServerCommonPacketListenerImpl - // CraftBukkit start - for (int spam; (spam = this.chatSpamTickCount.get()) > 0 && !this.chatSpamTickCount.compareAndSet(spam, spam - 1); ) ; - if (tabSpamLimiter.get() > 0) tabSpamLimiter.getAndDecrement(); // Paper - configurable tab spam limits -+ if (recipeSpamPackets.get() > 0) recipeSpamPackets.getAndDecrement(); // Paper - auto recipe limit - /* Use thread-safe field access instead - if (this.chatSpamTickCount > 0) { - --this.chatSpamTickCount; -@@ -3068,6 +3070,14 @@ public class ServerGamePacketListenerImpl extends ServerCommonPacketListenerImpl - - @Override - public void handlePlaceRecipe(ServerboundPlaceRecipePacket packet) { -+ // Paper start - auto recipe limit -+ if (!org.bukkit.Bukkit.isPrimaryThread()) { -+ if (this.recipeSpamPackets.addAndGet(io.papermc.paper.configuration.GlobalConfiguration.get().spamLimiter.recipeSpamIncrement) > io.papermc.paper.configuration.GlobalConfiguration.get().spamLimiter.recipeSpamLimit) { -+ this.disconnect(net.minecraft.network.chat.Component.translatable("disconnect.spam")); -+ return; -+ } -+ } -+ // Paper end - auto recipe limit - PacketUtils.ensureRunningOnSameThread(packet, this, this.player.serverLevel()); - this.player.resetLastActionTime(); - if (!this.player.isSpectator() && this.player.containerMenu.containerId == packet.getContainerId() && this.player.containerMenu instanceof RecipeBookMenu) { diff --git a/patches/server/0444-Optimize-Dynamic-get-Missing-Keys.patch b/patches/server/0441-Optimize-Dynamic-get-Missing-Keys.patch similarity index 100% rename from patches/server/0444-Optimize-Dynamic-get-Missing-Keys.patch rename to patches/server/0441-Optimize-Dynamic-get-Missing-Keys.patch diff --git a/patches/server/0442-Expose-LivingEntity-hurt-direction.patch b/patches/server/0442-Expose-LivingEntity-hurt-direction.patch new file mode 100644 index 000000000000..86eb7976a72b --- /dev/null +++ b/patches/server/0442-Expose-LivingEntity-hurt-direction.patch @@ -0,0 +1,58 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Mark Vainomaa +Date: Sun, 13 Dec 2020 05:32:05 +0200 +Subject: [PATCH] Expose LivingEntity hurt direction + + +diff --git a/src/main/java/net/minecraft/world/entity/player/Player.java b/src/main/java/net/minecraft/world/entity/player/Player.java +index 917ac21794f1aabc6e95ab2fff2ea7547b9778a8..78bb666dbc5ccd84820e1c7b382249510dd5795c 100644 +--- a/src/main/java/net/minecraft/world/entity/player/Player.java ++++ b/src/main/java/net/minecraft/world/entity/player/Player.java +@@ -188,7 +188,7 @@ public abstract class Player extends LivingEntity { + private Optional lastDeathLocation; + @Nullable + public FishingHook fishing; +- protected float hurtDir; ++ public float hurtDir; // Paper - protected -> public + @Nullable + public Vec3 currentImpulseImpactPos; + @Nullable +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftHumanEntity.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftHumanEntity.java +index 9022555db0df8c269fc039c895422cf36c08097e..cb56c75be83e839bafdae4356f85d33499d01d8a 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftHumanEntity.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftHumanEntity.java +@@ -125,6 +125,13 @@ public class CraftHumanEntity extends CraftLivingEntity implements HumanEntity { + } + } + ++ // Paper start ++ @Override ++ public void setHurtDirection(float hurtDirection) { ++ this.getHandle().hurtDir = hurtDirection; ++ } ++ // Paper end ++ + @Override + public int getSleepTicks() { + return this.getHandle().sleepCounter; +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftLivingEntity.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftLivingEntity.java +index de41ee2cc1be6ef412ec15c79a65cbca653b35d3..ff3b53eff8f5fc1e02e7b30d59ff27dfe8f5d431 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftLivingEntity.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftLivingEntity.java +@@ -1024,4 +1024,16 @@ public class CraftLivingEntity extends CraftEntity implements LivingEntity { + this.getHandle().take(((CraftItem) item).getHandle(), quantity); + } + // Paper end - pickup animation API ++ ++ // Paper start - hurt direction API ++ @Override ++ public float getHurtDirection() { ++ return this.getHandle().getHurtDir(); ++ } ++ ++ @Override ++ public void setHurtDirection(final float hurtDirection) { ++ throw new UnsupportedOperationException("Cannot set the hurt direction on a non player"); ++ } ++ // Paper end - hurt direction API + } diff --git a/patches/server/0443-Add-OBSTRUCTED-reason-to-BedEnterResult.patch b/patches/server/0443-Add-OBSTRUCTED-reason-to-BedEnterResult.patch new file mode 100644 index 000000000000..c118aa890ed1 --- /dev/null +++ b/patches/server/0443-Add-OBSTRUCTED-reason-to-BedEnterResult.patch @@ -0,0 +1,21 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Jake Potrebic +Date: Thu, 24 Dec 2020 12:43:39 -0800 +Subject: [PATCH] Add OBSTRUCTED reason to BedEnterResult + + +diff --git a/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java b/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java +index 629936b6266dadcac9d9a1fd584e7ba6b67a0002..9f97216a4be562acb69281f4a638dad139c6bc7d 100644 +--- a/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java ++++ b/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java +@@ -312,6 +312,10 @@ public class CraftEventFactory { + return BedEnterResult.TOO_FAR_AWAY; + case NOT_SAFE: + return BedEnterResult.NOT_SAFE; ++ // Paper start ++ case OBSTRUCTED: ++ return BedEnterResult.OBSTRUCTED; ++ // Paper end + default: + return BedEnterResult.OTHER_PROBLEM; + } diff --git a/patches/server/0444-Fix-crash-from-invalid-ingredient-lists-in-VillagerA.patch b/patches/server/0444-Fix-crash-from-invalid-ingredient-lists-in-VillagerA.patch new file mode 100644 index 000000000000..8c0b0c171bbd --- /dev/null +++ b/patches/server/0444-Fix-crash-from-invalid-ingredient-lists-in-VillagerA.patch @@ -0,0 +1,24 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Shane Freeder +Date: Sun, 27 Dec 2020 11:31:06 +0000 +Subject: [PATCH] Fix crash from invalid ingredient lists in + VillagerAcquireTradeEvent + + +diff --git a/src/main/java/net/minecraft/world/entity/npc/AbstractVillager.java b/src/main/java/net/minecraft/world/entity/npc/AbstractVillager.java +index c8231e421e6c3f1cf8a5aaf8cc64741abe868f41..39cb40b077e9c07471437d5bec16ba9a7e6bce52 100644 +--- a/src/main/java/net/minecraft/world/entity/npc/AbstractVillager.java ++++ b/src/main/java/net/minecraft/world/entity/npc/AbstractVillager.java +@@ -273,7 +273,11 @@ public abstract class AbstractVillager extends AgeableMob implements InventoryCa + Bukkit.getPluginManager().callEvent(event); + } + if (!event.isCancelled()) { +- recipeList.add(CraftMerchantRecipe.fromBukkit(event.getRecipe()).toMinecraft()); ++ // Paper start - Fix crash from invalid ingredient list ++ final CraftMerchantRecipe craftMerchantRecipe = CraftMerchantRecipe.fromBukkit(event.getRecipe()); ++ if (craftMerchantRecipe.getIngredients().isEmpty()) return; ++ recipeList.add(craftMerchantRecipe.toMinecraft()); ++ // Paper end - Fix crash from invalid ingredient list + } + // CraftBukkit end + ++j; diff --git a/patches/server/0448-Add-TargetHitEvent.patch b/patches/server/0445-Add-TargetHitEvent.patch similarity index 100% rename from patches/server/0448-Add-TargetHitEvent.patch rename to patches/server/0445-Add-TargetHitEvent.patch diff --git a/patches/server/0445-Expose-LivingEntity-hurt-direction.patch b/patches/server/0445-Expose-LivingEntity-hurt-direction.patch deleted file mode 100644 index 5df6e30894b5..000000000000 --- a/patches/server/0445-Expose-LivingEntity-hurt-direction.patch +++ /dev/null @@ -1,58 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Mark Vainomaa -Date: Sun, 13 Dec 2020 05:32:05 +0200 -Subject: [PATCH] Expose LivingEntity hurt direction - - -diff --git a/src/main/java/net/minecraft/world/entity/player/Player.java b/src/main/java/net/minecraft/world/entity/player/Player.java -index cb89b020d93ac838843ec2cbad562326a1e4257b..513e6505706e64f9410fa190014976dc469793af 100644 ---- a/src/main/java/net/minecraft/world/entity/player/Player.java -+++ b/src/main/java/net/minecraft/world/entity/player/Player.java -@@ -188,7 +188,7 @@ public abstract class Player extends LivingEntity { - private Optional lastDeathLocation; - @Nullable - public FishingHook fishing; -- protected float hurtDir; -+ public float hurtDir; // Paper - protected -> public - @Nullable - public Vec3 currentImpulseImpactPos; - @Nullable -diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftHumanEntity.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftHumanEntity.java -index 6cda13df52ee4d56dd1d3c213307bfd38175584c..24aa891ffa9115c05439b06aece85df7a382b7c4 100644 ---- a/src/main/java/org/bukkit/craftbukkit/entity/CraftHumanEntity.java -+++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftHumanEntity.java -@@ -125,6 +125,13 @@ public class CraftHumanEntity extends CraftLivingEntity implements HumanEntity { - } - } - -+ // Paper start -+ @Override -+ public void setHurtDirection(float hurtDirection) { -+ this.getHandle().hurtDir = hurtDirection; -+ } -+ // Paper end -+ - @Override - public int getSleepTicks() { - return this.getHandle().sleepCounter; -diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftLivingEntity.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftLivingEntity.java -index 925c842fcf546ad270641b3be7e8a8c432571501..10f3defa8f4b57fb45cf7de06d415b72102e47d5 100644 ---- a/src/main/java/org/bukkit/craftbukkit/entity/CraftLivingEntity.java -+++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftLivingEntity.java -@@ -1005,4 +1005,16 @@ public class CraftLivingEntity extends CraftEntity implements LivingEntity { - this.getHandle().take(((CraftItem) item).getHandle(), quantity); - } - // Paper end - pickup animation API -+ -+ // Paper start - hurt direction API -+ @Override -+ public float getHurtDirection() { -+ return this.getHandle().getHurtDir(); -+ } -+ -+ @Override -+ public void setHurtDirection(final float hurtDirection) { -+ throw new UnsupportedOperationException("Cannot set the hurt direction on a non player"); -+ } -+ // Paper end - hurt direction API - } diff --git a/patches/server/0446-Add-OBSTRUCTED-reason-to-BedEnterResult.patch b/patches/server/0446-Add-OBSTRUCTED-reason-to-BedEnterResult.patch deleted file mode 100644 index f1f4c227eea8..000000000000 --- a/patches/server/0446-Add-OBSTRUCTED-reason-to-BedEnterResult.patch +++ /dev/null @@ -1,21 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Jake Potrebic -Date: Thu, 24 Dec 2020 12:43:39 -0800 -Subject: [PATCH] Add OBSTRUCTED reason to BedEnterResult - - -diff --git a/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java b/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java -index dbe7931dd7eb43f9381463d3a57ba1eb53820985..24fcb0eb139174671ffb7a8e5222ec9833835f87 100644 ---- a/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java -+++ b/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java -@@ -313,6 +313,10 @@ public class CraftEventFactory { - return BedEnterResult.TOO_FAR_AWAY; - case NOT_SAFE: - return BedEnterResult.NOT_SAFE; -+ // Paper start -+ case OBSTRUCTED: -+ return BedEnterResult.OBSTRUCTED; -+ // Paper end - default: - return BedEnterResult.OTHER_PROBLEM; - } diff --git a/patches/server/0446-MC-4-Fix-item-position-desync.patch b/patches/server/0446-MC-4-Fix-item-position-desync.patch new file mode 100644 index 000000000000..f4858b59c3e8 --- /dev/null +++ b/patches/server/0446-MC-4-Fix-item-position-desync.patch @@ -0,0 +1,50 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: BillyGalbreath +Date: Tue, 8 Dec 2020 20:24:52 -0600 +Subject: [PATCH] MC-4: Fix item position desync + +This fixes item position desync (MC-4) by running the item coordinates +through the encode/decode methods of the packet that causes the precision +loss, which forces the server to lose the same precision as the client +keeping them in sync. + +diff --git a/src/main/java/net/minecraft/network/protocol/game/VecDeltaCodec.java b/src/main/java/net/minecraft/network/protocol/game/VecDeltaCodec.java +index 488ebd443903af812913437f1ade3002093f2470..a043ac10834562d357ef0b5aded2e916e2a0d056 100644 +--- a/src/main/java/net/minecraft/network/protocol/game/VecDeltaCodec.java ++++ b/src/main/java/net/minecraft/network/protocol/game/VecDeltaCodec.java +@@ -9,12 +9,12 @@ public class VecDeltaCodec { + + @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 (double)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/src/main/java/net/minecraft/world/entity/Entity.java b/src/main/java/net/minecraft/world/entity/Entity.java +index 7feed6383fdc367796ccb943bd086814ec989e6d..7c07ffaabce7a3964114b0859ad0028d52c662cc 100644 +--- a/src/main/java/net/minecraft/world/entity/Entity.java ++++ b/src/main/java/net/minecraft/world/entity/Entity.java +@@ -4449,6 +4449,16 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess + 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 ClientboundMoveEntityPacket ++ 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) { + this.position = new Vec3(x, y, z); + int i = Mth.floor(x); diff --git a/patches/server/0447-Additional-Block-Material-API.patch b/patches/server/0447-Additional-Block-Material-API.patch new file mode 100644 index 000000000000..19f5b4b7ef3f --- /dev/null +++ b/patches/server/0447-Additional-Block-Material-API.patch @@ -0,0 +1,40 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Aikar +Date: Wed, 30 Dec 2020 19:43:01 -0500 +Subject: [PATCH] Additional Block Material API + +Faster version for isSolid() that utilizes NMS's state for isSolid instead of the slower +process to do this in the Bukkit API + +Adds API for buildable, replaceable, burnable too. + +diff --git a/src/main/java/org/bukkit/craftbukkit/block/CraftBlock.java b/src/main/java/org/bukkit/craftbukkit/block/CraftBlock.java +index 5f4dcf6d86db66186dc31075bdb739f5dbae6480..1595e877b02b447b36f5c316ae70d4023b78a862 100644 +--- a/src/main/java/org/bukkit/craftbukkit/block/CraftBlock.java ++++ b/src/main/java/org/bukkit/craftbukkit/block/CraftBlock.java +@@ -440,6 +440,25 @@ public class CraftBlock implements Block { + return this.getNMS().liquid(); + } + ++ // Paper start ++ @Override ++ public boolean isBuildable() { ++ return this.getNMS().isSolid(); // This is in fact isSolid, despite the fact that isSolid below returns blocksMotion ++ } ++ @Override ++ public boolean isBurnable() { ++ return this.getNMS().ignitedByLava(); ++ } ++ @Override ++ public boolean isReplaceable() { ++ return this.getNMS().canBeReplaced(); ++ } ++ @Override ++ public boolean isSolid() { ++ return this.getNMS().blocksMotion(); ++ } ++ // Paper end ++ + @Override + public PistonMoveReaction getPistonMoveReaction() { + return PistonMoveReaction.getById(this.getNMS().getPistonPushReaction().ordinal()); diff --git a/patches/server/0447-Fix-crash-from-invalid-ingredient-lists-in-VillagerA.patch b/patches/server/0447-Fix-crash-from-invalid-ingredient-lists-in-VillagerA.patch deleted file mode 100644 index 7ac0fb0202e3..000000000000 --- a/patches/server/0447-Fix-crash-from-invalid-ingredient-lists-in-VillagerA.patch +++ /dev/null @@ -1,24 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Shane Freeder -Date: Sun, 27 Dec 2020 11:31:06 +0000 -Subject: [PATCH] Fix crash from invalid ingredient lists in - VillagerAcquireTradeEvent - - -diff --git a/src/main/java/net/minecraft/world/entity/npc/AbstractVillager.java b/src/main/java/net/minecraft/world/entity/npc/AbstractVillager.java -index fcb3b66617150ad503bffe65de4900b1e3af8764..2a155d3611ca2370830ca763d40074df6641958f 100644 ---- a/src/main/java/net/minecraft/world/entity/npc/AbstractVillager.java -+++ b/src/main/java/net/minecraft/world/entity/npc/AbstractVillager.java -@@ -273,7 +273,11 @@ public abstract class AbstractVillager extends AgeableMob implements InventoryCa - Bukkit.getPluginManager().callEvent(event); - } - if (!event.isCancelled()) { -- recipeList.add(CraftMerchantRecipe.fromBukkit(event.getRecipe()).toMinecraft()); -+ // Paper start - Fix crash from invalid ingredient list -+ final CraftMerchantRecipe craftMerchantRecipe = CraftMerchantRecipe.fromBukkit(event.getRecipe()); -+ if (craftMerchantRecipe.getIngredients().isEmpty()) return; -+ recipeList.add(craftMerchantRecipe.toMinecraft()); -+ // Paper end - Fix crash from invalid ingredient list - } - // CraftBukkit end - ++j; diff --git a/patches/server/0448-API-to-get-Material-from-Boats-and-Minecarts.patch b/patches/server/0448-API-to-get-Material-from-Boats-and-Minecarts.patch new file mode 100644 index 000000000000..4194a22c1987 --- /dev/null +++ b/patches/server/0448-API-to-get-Material-from-Boats-and-Minecarts.patch @@ -0,0 +1,63 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Madeline Miller +Date: Thu, 31 Dec 2020 12:48:19 +1000 +Subject: [PATCH] API to get Material from Boats and Minecarts + +== AT == +public net.minecraft.world.entity.vehicle.AbstractBoat getDropItem()Lnet/minecraft/world/item/Item; + +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftBoat.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftBoat.java +index c101d01b55472efc9fc2829b8c17db5377ed57ff..5d51a49228eaee94f91cd04843e27c7918ca8796 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftBoat.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftBoat.java +@@ -78,6 +78,13 @@ public abstract class CraftBoat extends CraftVehicle implements Boat { + this.getHandle().landBoats = workOnLand; + } + ++ // Paper start ++ @Override ++ public org.bukkit.Material getBoatMaterial() { ++ return org.bukkit.craftbukkit.util.CraftMagicNumbers.getMaterial(this.getHandle().getDropItem()); ++ } ++ // Paper end ++ + @Override + public Status getStatus() { + return CraftBoat.boatStatusFromNms(this.getHandle().status); +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftMinecart.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftMinecart.java +index ee010d53f8c671d17d68f3f43dca9978e23ac8ab..d35c1a10e58932b19c8053c5dacdc25fd7f22e8c 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftMinecart.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftMinecart.java +@@ -3,6 +3,7 @@ package org.bukkit.craftbukkit.entity; + import net.minecraft.world.entity.vehicle.AbstractMinecart; + import net.minecraft.world.level.block.Blocks; + import net.minecraft.world.level.block.state.BlockState; ++import org.bukkit.Material; // Paper + import org.bukkit.block.data.BlockData; + import org.bukkit.craftbukkit.CraftServer; + import org.bukkit.craftbukkit.block.data.CraftBlockData; +@@ -68,6 +69,24 @@ public abstract class CraftMinecart extends CraftVehicle implements Minecart { + this.getHandle().setDerailedVelocityMod(derailed); + } + ++ // Paper start ++ @Override ++ public Material getMinecartMaterial() { ++ return CraftMagicNumbers.getMaterial(minecartEntityTypeToMaterial(this.getHandle().getType())); ++ } ++ ++ static net.minecraft.world.item.Item minecartEntityTypeToMaterial(final net.minecraft.world.entity.EntityType type) { ++ if (type == net.minecraft.world.entity.EntityType.MINECART) return net.minecraft.world.item.Items.MINECART; ++ else if (type == net.minecraft.world.entity.EntityType.CHEST_MINECART) return net.minecraft.world.item.Items.CHEST_MINECART; ++ else if (type == net.minecraft.world.entity.EntityType.FURNACE_MINECART) return net.minecraft.world.item.Items.FURNACE_MINECART; ++ else if (type == net.minecraft.world.entity.EntityType.SPAWNER_MINECART) return net.minecraft.world.item.Items.MINECART; ++ else if (type == net.minecraft.world.entity.EntityType.COMMAND_BLOCK_MINECART) return net.minecraft.world.item.Items.COMMAND_BLOCK_MINECART; ++ else if (type == net.minecraft.world.entity.EntityType.HOPPER_MINECART) return net.minecraft.world.item.Items.HOPPER_MINECART; ++ else if (type == net.minecraft.world.entity.EntityType.TNT_MINECART) return net.minecraft.world.item.Items.TNT_MINECART; ++ else throw new UnsupportedOperationException("Server implementation is missing minecart material binding for entity type " + type.toShortString()); ++ } ++ // Paper end ++ + @Override + public AbstractMinecart getHandle() { + return (AbstractMinecart) this.entity; diff --git a/patches/server/0449-Allow-disabling-mob-spawner-spawn-egg-transformation.patch b/patches/server/0449-Allow-disabling-mob-spawner-spawn-egg-transformation.patch new file mode 100644 index 000000000000..893e6ffe6020 --- /dev/null +++ b/patches/server/0449-Allow-disabling-mob-spawner-spawn-egg-transformation.patch @@ -0,0 +1,19 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: BrodyBeckwith +Date: Fri, 9 Oct 2020 20:30:12 -0400 +Subject: [PATCH] Allow disabling mob spawner spawn egg transformation + + +diff --git a/src/main/java/net/minecraft/world/item/SpawnEggItem.java b/src/main/java/net/minecraft/world/item/SpawnEggItem.java +index ac35651e4f415243a0f84cca9a4f776b1f1623a5..9956ed42df55daa6d97fd6e3ab5368dad91cfaf0 100644 +--- a/src/main/java/net/minecraft/world/item/SpawnEggItem.java ++++ b/src/main/java/net/minecraft/world/item/SpawnEggItem.java +@@ -68,6 +68,8 @@ public class SpawnEggItem extends Item { + EntityType entitytypes; + + if (tileentity instanceof Spawner) { ++ if (world.paperConfig().entities.spawning.disableMobSpawnerSpawnEggTransformation) return InteractionResult.FAIL; // Paper - Allow disabling mob spawner spawn egg transformation ++ + Spawner spawner = (Spawner) tileentity; + + entitytypes = this.getType(itemstack); diff --git a/patches/server/0449-MC-4-Fix-item-position-desync.patch b/patches/server/0449-MC-4-Fix-item-position-desync.patch deleted file mode 100644 index 3f4003c4f5b2..000000000000 --- a/patches/server/0449-MC-4-Fix-item-position-desync.patch +++ /dev/null @@ -1,50 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: BillyGalbreath -Date: Tue, 8 Dec 2020 20:24:52 -0600 -Subject: [PATCH] MC-4: Fix item position desync - -This fixes item position desync (MC-4) by running the item coordinates -through the encode/decode methods of the packet that causes the precision -loss, which forces the server to lose the same precision as the client -keeping them in sync. - -diff --git a/src/main/java/net/minecraft/network/protocol/game/VecDeltaCodec.java b/src/main/java/net/minecraft/network/protocol/game/VecDeltaCodec.java -index 488ebd443903af812913437f1ade3002093f2470..a043ac10834562d357ef0b5aded2e916e2a0d056 100644 ---- a/src/main/java/net/minecraft/network/protocol/game/VecDeltaCodec.java -+++ b/src/main/java/net/minecraft/network/protocol/game/VecDeltaCodec.java -@@ -9,12 +9,12 @@ public class VecDeltaCodec { - - @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 (double)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/src/main/java/net/minecraft/world/entity/Entity.java b/src/main/java/net/minecraft/world/entity/Entity.java -index dae7643897b47dba304cbea56112445df2736cff..bed376337035545e7ec677f2f7fe3372a3c9ea25 100644 ---- a/src/main/java/net/minecraft/world/entity/Entity.java -+++ b/src/main/java/net/minecraft/world/entity/Entity.java -@@ -4260,6 +4260,16 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess - 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 ClientboundMoveEntityPacket -+ 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) { - this.position = new Vec3(x, y, z); - int i = Mth.floor(x); diff --git a/patches/server/0450-Additional-Block-Material-API.patch b/patches/server/0450-Additional-Block-Material-API.patch deleted file mode 100644 index 81728edf4850..000000000000 --- a/patches/server/0450-Additional-Block-Material-API.patch +++ /dev/null @@ -1,40 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Aikar -Date: Wed, 30 Dec 2020 19:43:01 -0500 -Subject: [PATCH] Additional Block Material API - -Faster version for isSolid() that utilizes NMS's state for isSolid instead of the slower -process to do this in the Bukkit API - -Adds API for buildable, replaceable, burnable too. - -diff --git a/src/main/java/org/bukkit/craftbukkit/block/CraftBlock.java b/src/main/java/org/bukkit/craftbukkit/block/CraftBlock.java -index aa644231425b9622437538b5c092d4064a40cced..98e87dc15e2ed23f6897ba6359846ff5bc32b655 100644 ---- a/src/main/java/org/bukkit/craftbukkit/block/CraftBlock.java -+++ b/src/main/java/org/bukkit/craftbukkit/block/CraftBlock.java -@@ -440,6 +440,25 @@ public class CraftBlock implements Block { - return this.getNMS().liquid(); - } - -+ // Paper start -+ @Override -+ public boolean isBuildable() { -+ return this.getNMS().isSolid(); // This is in fact isSolid, despite the fact that isSolid below returns blocksMotion -+ } -+ @Override -+ public boolean isBurnable() { -+ return this.getNMS().ignitedByLava(); -+ } -+ @Override -+ public boolean isReplaceable() { -+ return this.getNMS().canBeReplaced(); -+ } -+ @Override -+ public boolean isSolid() { -+ return this.getNMS().blocksMotion(); -+ } -+ // Paper end -+ - @Override - public PistonMoveReaction getPistonMoveReaction() { - return PistonMoveReaction.getById(this.getNMS().getPistonPushReaction().ordinal()); diff --git a/patches/server/0450-Fix-Not-a-string-Map-Conversion-spam.patch b/patches/server/0450-Fix-Not-a-string-Map-Conversion-spam.patch new file mode 100644 index 000000000000..8952de76333c --- /dev/null +++ b/patches/server/0450-Fix-Not-a-string-Map-Conversion-spam.patch @@ -0,0 +1,45 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Aikar +Date: Thu, 8 Oct 2020 00:00:25 -0400 +Subject: [PATCH] Fix "Not a string" Map Conversion spam + +The maps did convert successfully, but had noisy logs due to Spigot +implementing this logic incorrectly. + +This stops the spam by converting the old format to new before +requesting the world. + +Track spigot issue to see when fixed: https://hub.spigotmc.org/jira/browse/SPIGOT-6181 + +diff --git a/src/main/java/net/minecraft/world/level/saveddata/maps/MapItemSavedData.java b/src/main/java/net/minecraft/world/level/saveddata/maps/MapItemSavedData.java +index c21ae4975206398e7d20b37a749b830b9219c746..a89f0b652c515efbc24ffc9af47fa65082946e54 100644 +--- a/src/main/java/net/minecraft/world/level/saveddata/maps/MapItemSavedData.java ++++ b/src/main/java/net/minecraft/world/level/saveddata/maps/MapItemSavedData.java +@@ -123,7 +123,26 @@ public class MapItemSavedData extends SavedData { + } + + public static MapItemSavedData load(CompoundTag nbt, HolderLookup.Provider registries) { +- DataResult> dataresult = DimensionType.parseLegacy(new Dynamic(NbtOps.INSTANCE, nbt.get("dimension"))); // CraftBukkit - decompile error ++ // Paper start - fix "Not a string" spam ++ Tag dimension = nbt.get("dimension"); ++ if (dimension instanceof final net.minecraft.nbt.NumericTag numericTag && numericTag.getAsInt() >= CraftWorld.CUSTOM_DIMENSION_OFFSET) { ++ long least = nbt.getLong("UUIDLeast"); ++ long most = nbt.getLong("UUIDMost"); ++ ++ if (least != 0L && most != 0L) { ++ UUID uuid = new UUID(most, least); ++ CraftWorld world = (CraftWorld) Bukkit.getWorld(uuid); ++ if (world != null) { ++ dimension = net.minecraft.nbt.StringTag.valueOf("minecraft:" + world.getName().toLowerCase(java.util.Locale.ENGLISH)); ++ } else { ++ dimension = net.minecraft.nbt.StringTag.valueOf("bukkit:_invalidworld_"); ++ } ++ } else { ++ dimension = net.minecraft.nbt.StringTag.valueOf("bukkit:_invalidworld_"); ++ } ++ } ++ DataResult> dataresult = DimensionType.parseLegacy(new Dynamic(NbtOps.INSTANCE, dimension)); // CraftBukkit - decompile error ++ // Paper end - fix "Not a string" spam + Logger logger = MapItemSavedData.LOGGER; + + Objects.requireNonNull(logger); diff --git a/patches/server/0451-Add-PlayerFlowerPotManipulateEvent.patch b/patches/server/0451-Add-PlayerFlowerPotManipulateEvent.patch new file mode 100644 index 000000000000..5b13a05793ce --- /dev/null +++ b/patches/server/0451-Add-PlayerFlowerPotManipulateEvent.patch @@ -0,0 +1,48 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: MisterVector +Date: Tue, 13 Aug 2019 19:45:06 -0700 +Subject: [PATCH] Add PlayerFlowerPotManipulateEvent + + +diff --git a/src/main/java/net/minecraft/world/level/block/FlowerPotBlock.java b/src/main/java/net/minecraft/world/level/block/FlowerPotBlock.java +index c6de1b6587b300404c38c27f566c80de11bc0814..bee71ed48365694c488af88317a738fc6a4e5893 100644 +--- a/src/main/java/net/minecraft/world/level/block/FlowerPotBlock.java ++++ b/src/main/java/net/minecraft/world/level/block/FlowerPotBlock.java +@@ -61,6 +61,18 @@ public class FlowerPotBlock extends Block { + } else if (!this.isEmpty()) { + return InteractionResult.CONSUME; + } else { ++ // Paper start - Add PlayerFlowerPotManipulateEvent ++ org.bukkit.block.Block block = org.bukkit.craftbukkit.block.CraftBlock.at(world, pos); ++ org.bukkit.inventory.ItemStack placedStack = org.bukkit.craftbukkit.inventory.CraftItemStack.asBukkitCopy(stack); ++ ++ io.papermc.paper.event.player.PlayerFlowerPotManipulateEvent event = new io.papermc.paper.event.player.PlayerFlowerPotManipulateEvent((org.bukkit.entity.Player) player.getBukkitEntity(), block, placedStack, true); ++ if (!event.callEvent()) { ++ // Update client ++ player.containerMenu.sendAllDataToRemote(); ++ ++ return InteractionResult.CONSUME; ++ } ++ // Paper end - Add PlayerFlowerPotManipulateEvent + world.setBlock(pos, blockState, 3); + world.gameEvent(player, GameEvent.BLOCK_CHANGE, pos); + player.awardStat(Stats.POT_FLOWER); +@@ -75,6 +87,18 @@ public class FlowerPotBlock extends Block { + return InteractionResult.CONSUME; + } else { + ItemStack itemStack = new ItemStack(this.potted); ++ // Paper start - Add PlayerFlowerPotManipulateEvent ++ org.bukkit.block.Block block = org.bukkit.craftbukkit.block.CraftBlock.at(world, pos); ++ org.bukkit.inventory.ItemStack pottedStack = new org.bukkit.inventory.ItemStack(org.bukkit.craftbukkit.block.CraftBlockType.minecraftToBukkit(this.potted)); ++ ++ io.papermc.paper.event.player.PlayerFlowerPotManipulateEvent event = new io.papermc.paper.event.player.PlayerFlowerPotManipulateEvent((org.bukkit.entity.Player) player.getBukkitEntity(), block, pottedStack, false); ++ if (!event.callEvent()) { ++ // Update client ++ player.containerMenu.sendAllDataToRemote(); ++ ++ return InteractionResult.PASS; ++ } ++ // Paper end - Add PlayerFlowerPotManipulateEvent + if (!player.addItem(itemStack)) { + player.drop(itemStack, false); + } diff --git a/patches/server/0452-API-to-get-Material-from-Boats-and-Minecarts.patch b/patches/server/0452-API-to-get-Material-from-Boats-and-Minecarts.patch deleted file mode 100644 index 9bcb17ad1410..000000000000 --- a/patches/server/0452-API-to-get-Material-from-Boats-and-Minecarts.patch +++ /dev/null @@ -1,62 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Madeline Miller -Date: Thu, 31 Dec 2020 12:48:19 +1000 -Subject: [PATCH] API to get Material from Boats and Minecarts - - -diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftBoat.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftBoat.java -index f332bd4e6f663147c9ef6ce03d926feb74b55e93..d161cbf9c83cd78593864850b98f688da2c85aa5 100644 ---- a/src/main/java/org/bukkit/craftbukkit/entity/CraftBoat.java -+++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftBoat.java -@@ -79,6 +79,13 @@ public class CraftBoat extends CraftVehicle implements Boat { - this.getHandle().landBoats = workOnLand; - } - -+ // Paper start -+ @Override -+ public org.bukkit.Material getBoatMaterial() { -+ return org.bukkit.craftbukkit.util.CraftMagicNumbers.getMaterial(this.getHandle().getDropItem()); -+ } -+ // Paper end -+ - @Override - public Status getStatus() { - return CraftBoat.boatStatusFromNms(this.getHandle().status); -diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftMinecart.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftMinecart.java -index ee010d53f8c671d17d68f3f43dca9978e23ac8ab..8920af5a0dfe737c1f38d906b53e6a278456d2aa 100644 ---- a/src/main/java/org/bukkit/craftbukkit/entity/CraftMinecart.java -+++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftMinecart.java -@@ -1,8 +1,10 @@ - package org.bukkit.craftbukkit.entity; - - import net.minecraft.world.entity.vehicle.AbstractMinecart; -+import net.minecraft.world.item.Items; // Paper - import net.minecraft.world.level.block.Blocks; - import net.minecraft.world.level.block.state.BlockState; -+import org.bukkit.Material; // Paper - import org.bukkit.block.data.BlockData; - import org.bukkit.craftbukkit.CraftServer; - import org.bukkit.craftbukkit.block.data.CraftBlockData; -@@ -68,6 +70,22 @@ public abstract class CraftMinecart extends CraftVehicle implements Minecart { - this.getHandle().setDerailedVelocityMod(derailed); - } - -+ // Paper start -+ @Override -+ public Material getMinecartMaterial() { -+ net.minecraft.world.item.Item minecartItem = switch (getHandle().getMinecartType()) { -+ case CHEST -> Items.CHEST_MINECART; -+ case FURNACE -> Items.FURNACE_MINECART; -+ case TNT -> Items.TNT_MINECART; -+ case HOPPER -> Items.HOPPER_MINECART; -+ case COMMAND_BLOCK -> Items.COMMAND_BLOCK_MINECART; -+ case RIDEABLE, SPAWNER -> Items.MINECART; -+ }; -+ -+ return CraftMagicNumbers.getMaterial(minecartItem); -+ } -+ // Paper end -+ - @Override - public AbstractMinecart getHandle() { - return (AbstractMinecart) this.entity; diff --git a/patches/server/0452-Fix-interact-event-not-being-called-sometimes.patch b/patches/server/0452-Fix-interact-event-not-being-called-sometimes.patch new file mode 100644 index 000000000000..72c54e87a3f1 --- /dev/null +++ b/patches/server/0452-Fix-interact-event-not-being-called-sometimes.patch @@ -0,0 +1,48 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: TheMolkaPL +Date: Sun, 21 Jun 2020 17:21:46 +0200 +Subject: [PATCH] Fix interact event not being called sometimes + +* Call PlayerInteractEvent when left-clicking on a block in adventure + mode. +* Call PlayerInteractEvent when left-clicking an Entity that is out of + range in adventure/survival (entity reach is 3.0). + +Co-authored-by: Moulberry + +diff --git a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java +index f2c1893677004b1699beb16ebfcbe9816aad2a78..03e87e35f4478d948602569c319a7b5a0e938cd5 100644 +--- a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java ++++ b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java +@@ -1785,7 +1785,7 @@ public class ServerGamePacketListenerImpl extends ServerCommonPacketListenerImpl + } else if (enuminteractionresult instanceof InteractionResult.Success) { + InteractionResult.Success enuminteractionresult_d = (InteractionResult.Success) enuminteractionresult; + +- if (enuminteractionresult_d.swingSource() == InteractionResult.SwingSource.SERVER) { ++ if (enuminteractionresult_d.swingSource() == InteractionResult.SwingSource.SERVER && !this.player.gameMode.interactResult) { + this.player.swing(enumhand, true); + } + } +@@ -2406,13 +2406,20 @@ public class ServerGamePacketListenerImpl extends ServerCommonPacketListenerImpl + double d3 = Math.max(this.player.blockInteractionRange(), this.player.entityInteractionRange()); + // SPIGOT-5607: Only call interact event if no block or entity is being clicked. Use bukkit ray trace method, because it handles blocks and entities at the same time + // SPIGOT-7429: Make sure to call PlayerInteractEvent for spectators and non-pickable entities +- org.bukkit.util.RayTraceResult result = this.player.level().getWorld().rayTrace(origin, origin.getDirection(), d3, org.bukkit.FluidCollisionMode.NEVER, false, 0.1, entity -> { ++ org.bukkit.util.RayTraceResult result = this.player.level().getWorld().rayTrace(origin, origin.getDirection(), d3, org.bukkit.FluidCollisionMode.NEVER, false, 0.0, entity -> { // Paper - Call interact event; change raySize from 0.1 to 0.0 + Entity handle = ((CraftEntity) entity).getHandle(); + return entity != this.player.getBukkitEntity() && this.player.getBukkitEntity().canSee(entity) && !handle.isSpectator() && handle.isPickable() && !handle.isPassengerOfSameVehicle(this.player); + }); + if (result == null) { + CraftEventFactory.callPlayerInteractEvent(this.player, Action.LEFT_CLICK_AIR, this.player.getInventory().getSelected(), InteractionHand.MAIN_HAND); +- } ++ } else { // Paper start - Call interact event ++ GameType gameType = this.player.gameMode.getGameModeForPlayer(); ++ if (gameType == GameType.ADVENTURE && result.getHitBlock() != null) { ++ CraftEventFactory.callPlayerInteractEvent(this.player, Action.LEFT_CLICK_BLOCK, ((org.bukkit.craftbukkit.block.CraftBlock) result.getHitBlock()).getPosition(), org.bukkit.craftbukkit.block.CraftBlock.blockFaceToNotch(result.getHitBlockFace()), this.player.getInventory().getSelected(), InteractionHand.MAIN_HAND); ++ } else if (gameType != GameType.CREATIVE && result.getHitEntity() != null && origin.toVector().distanceSquared(result.getHitPosition()) > this.player.entityInteractionRange() * this.player.entityInteractionRange()) { ++ CraftEventFactory.callPlayerInteractEvent(this.player, Action.LEFT_CLICK_AIR, this.player.getInventory().getSelected(), InteractionHand.MAIN_HAND); ++ } ++ } // Paper end - Call interact event + + // Arm swing animation + PlayerAnimationEvent event = new PlayerAnimationEvent(this.getCraftPlayer(), (packet.getHand() == InteractionHand.MAIN_HAND) ? PlayerAnimationType.ARM_SWING : PlayerAnimationType.OFF_ARM_SWING); diff --git a/patches/server/0453-Allow-disabling-mob-spawner-spawn-egg-transformation.patch b/patches/server/0453-Allow-disabling-mob-spawner-spawn-egg-transformation.patch deleted file mode 100644 index da8441444c48..000000000000 --- a/patches/server/0453-Allow-disabling-mob-spawner-spawn-egg-transformation.patch +++ /dev/null @@ -1,19 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: BrodyBeckwith -Date: Fri, 9 Oct 2020 20:30:12 -0400 -Subject: [PATCH] Allow disabling mob spawner spawn egg transformation - - -diff --git a/src/main/java/net/minecraft/world/item/SpawnEggItem.java b/src/main/java/net/minecraft/world/item/SpawnEggItem.java -index 6e91fec416493b2fabe1e8a6214ab7673212e451..9cea8da84f39bb3f687139ef213ccea358724dee 100644 ---- a/src/main/java/net/minecraft/world/item/SpawnEggItem.java -+++ b/src/main/java/net/minecraft/world/item/SpawnEggItem.java -@@ -69,6 +69,8 @@ public class SpawnEggItem extends Item { - EntityType entitytypes; - - if (tileentity instanceof Spawner) { -+ if (world.paperConfig().entities.spawning.disableMobSpawnerSpawnEggTransformation) return InteractionResult.FAIL; // Paper - Allow disabling mob spawner spawn egg transformation -+ - Spawner spawner = (Spawner) tileentity; - - entitytypes = this.getType(itemstack); diff --git a/patches/server/0453-Zombie-API-breaking-doors.patch b/patches/server/0453-Zombie-API-breaking-doors.patch new file mode 100644 index 000000000000..b0741f110fdc --- /dev/null +++ b/patches/server/0453-Zombie-API-breaking-doors.patch @@ -0,0 +1,24 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Jake Potrebic +Date: Wed, 18 Nov 2020 11:32:46 -0800 +Subject: [PATCH] Zombie API - breaking doors + +== AT == +public net.minecraft.world.entity.monster.Zombie supportsBreakDoorGoal()Z + +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftZombie.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftZombie.java +index 4412c913123f7521f449c98b60378e8d3b1671ce..dfc2b40e20069705f92d86a6898e3e8348bf4dcd 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftZombie.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftZombie.java +@@ -122,6 +122,11 @@ public class CraftZombie extends CraftMonster implements Zombie { + public void setShouldBurnInDay(boolean shouldBurnInDay) { + getHandle().setShouldBurnInDay(shouldBurnInDay); + } ++ ++ @Override ++ public boolean supportsBreakingDoors() { ++ return true; // All zombies are now capable of breaking doors, see https://bugs.mojang.com/browse/MC-137053 ++ } + // Paper end + + @Override diff --git a/patches/server/0454-Fix-Not-a-string-Map-Conversion-spam.patch b/patches/server/0454-Fix-Not-a-string-Map-Conversion-spam.patch deleted file mode 100644 index 5b2328afe8a1..000000000000 --- a/patches/server/0454-Fix-Not-a-string-Map-Conversion-spam.patch +++ /dev/null @@ -1,54 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Aikar -Date: Thu, 8 Oct 2020 00:00:25 -0400 -Subject: [PATCH] Fix "Not a string" Map Conversion spam - -The maps did convert successfully, but had noisy logs due to Spigot -implementing this logic incorrectly. - -This stops the spam by converting the old format to new before -requesting the world. - -Track spigot issue to see when fixed: https://hub.spigotmc.org/jira/browse/SPIGOT-6181 - -diff --git a/src/main/java/net/minecraft/world/level/saveddata/maps/MapItemSavedData.java b/src/main/java/net/minecraft/world/level/saveddata/maps/MapItemSavedData.java -index 5e469bd4d9ca428abdd9d758993164635dc86f27..d6a0a882331226c3ae4ced09e449eb7931740f8f 100644 ---- a/src/main/java/net/minecraft/world/level/saveddata/maps/MapItemSavedData.java -+++ b/src/main/java/net/minecraft/world/level/saveddata/maps/MapItemSavedData.java -@@ -21,6 +21,8 @@ import net.minecraft.core.component.DataComponents; - import net.minecraft.nbt.CompoundTag; - import net.minecraft.nbt.ListTag; - import net.minecraft.nbt.NbtOps; -+import net.minecraft.nbt.NumericTag; -+import net.minecraft.nbt.StringTag; - import net.minecraft.nbt.Tag; - import net.minecraft.network.FriendlyByteBuf; - import net.minecraft.network.chat.Component; -@@ -121,7 +123,26 @@ public class MapItemSavedData extends SavedData { - } - - public static MapItemSavedData load(CompoundTag nbt, HolderLookup.Provider registryLookup) { -- DataResult> dataresult = DimensionType.parseLegacy(new Dynamic(NbtOps.INSTANCE, nbt.get("dimension"))); // CraftBukkit - decompile error -+ // Paper start - fix "Not a string" spam -+ Tag dimension = nbt.get("dimension"); -+ if (dimension instanceof NumericTag && ((NumericTag) dimension).getAsInt() >= CraftWorld.CUSTOM_DIMENSION_OFFSET) { -+ long least = nbt.getLong("UUIDLeast"); -+ long most = nbt.getLong("UUIDMost"); -+ -+ if (least != 0L && most != 0L) { -+ UUID uuid = new UUID(most, least); -+ CraftWorld world = (CraftWorld) Bukkit.getWorld(uuid); -+ if (world != null) { -+ dimension = StringTag.valueOf("minecraft:" + world.getName().toLowerCase(java.util.Locale.ENGLISH)); -+ } else { -+ dimension = StringTag.valueOf("bukkit:_invalidworld_"); -+ } -+ } else { -+ dimension = StringTag.valueOf("bukkit:_invalidworld_"); -+ } -+ } -+ DataResult> dataresult = DimensionType.parseLegacy(new Dynamic(NbtOps.INSTANCE, dimension)); // CraftBukkit - decompile error -+ // Paper end - fix "Not a string" spam - Logger logger = MapItemSavedData.LOGGER; - - Objects.requireNonNull(logger); diff --git a/patches/server/0454-Fix-nerfed-slime-when-splitting.patch b/patches/server/0454-Fix-nerfed-slime-when-splitting.patch new file mode 100644 index 000000000000..c35358b895cf --- /dev/null +++ b/patches/server/0454-Fix-nerfed-slime-when-splitting.patch @@ -0,0 +1,18 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Jake Potrebic +Date: Mon, 24 Aug 2020 08:39:06 -0700 +Subject: [PATCH] Fix nerfed slime when splitting + + +diff --git a/src/main/java/net/minecraft/world/entity/monster/Slime.java b/src/main/java/net/minecraft/world/entity/monster/Slime.java +index 26f4db572dc6c25a9815b8f352d8829e252fa1a2..129f0cbc0469cb2804db6088b53347d88d91f4eb 100644 +--- a/src/main/java/net/minecraft/world/entity/monster/Slime.java ++++ b/src/main/java/net/minecraft/world/entity/monster/Slime.java +@@ -250,6 +250,7 @@ public class Slime extends Mob implements Enemy { + float f3 = ((float) (l / 2) - 0.5F) * f1; + + Slime converted = this.convertTo(this.getType(), new ConversionParams(ConversionType.SPLIT_ON_DEATH, false, false, scoreboardteam), EntitySpawnReason.TRIGGERED, (entityslime) -> { // CraftBukkit ++ entityslime.aware = this.aware; // Paper - Fix nerfed slime when splitting + entityslime.setSize(j, true); + entityslime.moveTo(this.getX() + (double) f2, this.getY() + 0.5D, this.getZ() + (double) f3, this.random.nextFloat() * 360.0F, 0.0F); + // CraftBukkit start diff --git a/patches/server/0455-Add-EntityLoadCrossbowEvent.patch b/patches/server/0455-Add-EntityLoadCrossbowEvent.patch new file mode 100644 index 000000000000..72ec5141a02a --- /dev/null +++ b/patches/server/0455-Add-EntityLoadCrossbowEvent.patch @@ -0,0 +1,68 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: JRoy +Date: Wed, 7 Oct 2020 12:04:01 -0400 +Subject: [PATCH] Add EntityLoadCrossbowEvent + + +diff --git a/src/main/java/net/minecraft/world/item/CrossbowItem.java b/src/main/java/net/minecraft/world/item/CrossbowItem.java +index 710181cf04563f06690eee5b46a5a0d84844ac29..52c40eafc77e50a6fd21b9a7a250cea501f11690 100644 +--- a/src/main/java/net/minecraft/world/item/CrossbowItem.java ++++ b/src/main/java/net/minecraft/world/item/CrossbowItem.java +@@ -89,7 +89,14 @@ public class CrossbowItem extends ProjectileWeaponItem { + public boolean releaseUsing(ItemStack stack, Level world, LivingEntity user, int remainingUseTicks) { + int i = this.getUseDuration(stack, user) - remainingUseTicks; + float f = getPowerForTime(i, stack, user); +- if (f >= 1.0F && !isCharged(stack) && tryLoadProjectiles(user, stack)) { ++ // Paper start - Add EntityLoadCrossbowEvent ++ if (f >= 1.0F && !isCharged(stack)) { ++ final io.papermc.paper.event.entity.EntityLoadCrossbowEvent event = new io.papermc.paper.event.entity.EntityLoadCrossbowEvent(user.getBukkitLivingEntity(), stack.asBukkitMirror(), org.bukkit.craftbukkit.CraftEquipmentSlot.getHand(user.getUsedItemHand())); ++ if (!event.callEvent() || !tryLoadProjectiles(user, stack, event.shouldConsumeItem()) || !event.shouldConsumeItem()) { ++ if (user instanceof ServerPlayer player) player.containerMenu.sendAllDataToRemote(); ++ return false; ++ } ++ // Paper end - Add EntityLoadCrossbowEvent + CrossbowItem.ChargingSounds chargingSounds = this.getChargingSounds(stack); + chargingSounds.end() + .ifPresent( +@@ -110,8 +117,14 @@ public class CrossbowItem extends ProjectileWeaponItem { + } + } + +- private static boolean tryLoadProjectiles(LivingEntity shooter, ItemStack crossbow) { +- List list = draw(crossbow, shooter.getProjectile(crossbow), shooter); ++ @io.papermc.paper.annotation.DoNotUse // Paper - Add EntityLoadCrossbowEvent ++ private static boolean tryLoadProjectiles(LivingEntity shooter, ItemStack crossbow) { ++ // Paper start - Add EntityLoadCrossbowEvent ++ return CrossbowItem.tryLoadProjectiles(shooter, crossbow, true); ++ } ++ private static boolean tryLoadProjectiles(LivingEntity shooter, ItemStack crossbow, boolean consume) { ++ List list = draw(crossbow, shooter.getProjectile(crossbow), shooter, consume); ++ // Paper end - Add EntityLoadCrossbowEvent + if (!list.isEmpty()) { + crossbow.set(DataComponents.CHARGED_PROJECTILES, ChargedProjectiles.of(list)); + return true; +diff --git a/src/main/java/net/minecraft/world/item/ProjectileWeaponItem.java b/src/main/java/net/minecraft/world/item/ProjectileWeaponItem.java +index a7d0ac6513fd888e222b3128afc1a227ea91f1d2..78ba170a83f8c026bd110eae494c52577182ed61 100644 +--- a/src/main/java/net/minecraft/world/item/ProjectileWeaponItem.java ++++ b/src/main/java/net/minecraft/world/item/ProjectileWeaponItem.java +@@ -109,6 +109,11 @@ public abstract class ProjectileWeaponItem extends Item { + } + + protected static List draw(ItemStack stack, ItemStack projectileStack, LivingEntity shooter) { ++ // Paper start ++ return draw(stack, projectileStack, shooter, true); ++ } ++ protected static List draw(ItemStack stack, ItemStack projectileStack, LivingEntity shooter, boolean consume) { ++ // Paper end + if (projectileStack.isEmpty()) { + return List.of(); + } else { +@@ -128,7 +133,7 @@ public abstract class ProjectileWeaponItem extends Item { + ItemStack itemstack2 = projectileStack.copy(); + + for (int k = 0; k < j; ++k) { +- ItemStack itemstack3 = ProjectileWeaponItem.useAmmo(stack, k == 0 ? projectileStack : itemstack2, shooter, k > 0); ++ ItemStack itemstack3 = ProjectileWeaponItem.useAmmo(stack, k == 0 ? projectileStack : itemstack2, shooter, k > 0 || !consume); // Paper + + if (!itemstack3.isEmpty()) { + list.add(itemstack3); diff --git a/patches/server/0455-Add-PlayerFlowerPotManipulateEvent.patch b/patches/server/0455-Add-PlayerFlowerPotManipulateEvent.patch deleted file mode 100644 index 8b61833059ca..000000000000 --- a/patches/server/0455-Add-PlayerFlowerPotManipulateEvent.patch +++ /dev/null @@ -1,48 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: MisterVector -Date: Tue, 13 Aug 2019 19:45:06 -0700 -Subject: [PATCH] Add PlayerFlowerPotManipulateEvent - - -diff --git a/src/main/java/net/minecraft/world/level/block/FlowerPotBlock.java b/src/main/java/net/minecraft/world/level/block/FlowerPotBlock.java -index b0c950fa7426dc762b99080d98831b961fc19da2..7194565ad1a910fe300d8664a30b35c4eff18cb7 100644 ---- a/src/main/java/net/minecraft/world/level/block/FlowerPotBlock.java -+++ b/src/main/java/net/minecraft/world/level/block/FlowerPotBlock.java -@@ -63,6 +63,18 @@ public class FlowerPotBlock extends Block { - } else if (!this.isEmpty()) { - return ItemInteractionResult.CONSUME; - } else { -+ // Paper start - Add PlayerFlowerPotManipulateEvent -+ org.bukkit.block.Block block = org.bukkit.craftbukkit.block.CraftBlock.at(world, pos); -+ org.bukkit.inventory.ItemStack placedStack = org.bukkit.craftbukkit.inventory.CraftItemStack.asBukkitCopy(stack); -+ -+ io.papermc.paper.event.player.PlayerFlowerPotManipulateEvent event = new io.papermc.paper.event.player.PlayerFlowerPotManipulateEvent((org.bukkit.entity.Player) player.getBukkitEntity(), block, placedStack, true); -+ if (!event.callEvent()) { -+ // Update client -+ player.containerMenu.sendAllDataToRemote(); -+ -+ return ItemInteractionResult.CONSUME; -+ } -+ // Paper end - Add PlayerFlowerPotManipulateEvent - world.setBlock(pos, blockState, 3); - world.gameEvent(player, GameEvent.BLOCK_CHANGE, pos); - player.awardStat(Stats.POT_FLOWER); -@@ -77,6 +89,18 @@ public class FlowerPotBlock extends Block { - return InteractionResult.CONSUME; - } else { - ItemStack itemStack = new ItemStack(this.potted); -+ // Paper start - Add PlayerFlowerPotManipulateEvent -+ org.bukkit.block.Block block = org.bukkit.craftbukkit.block.CraftBlock.at(world, pos); -+ org.bukkit.inventory.ItemStack pottedStack = new org.bukkit.inventory.ItemStack(org.bukkit.craftbukkit.block.CraftBlockType.minecraftToBukkit(this.potted)); -+ -+ io.papermc.paper.event.player.PlayerFlowerPotManipulateEvent event = new io.papermc.paper.event.player.PlayerFlowerPotManipulateEvent((org.bukkit.entity.Player) player.getBukkitEntity(), block, pottedStack, false); -+ if (!event.callEvent()) { -+ // Update client -+ player.containerMenu.sendAllDataToRemote(); -+ -+ return InteractionResult.PASS; -+ } -+ // Paper end - Add PlayerFlowerPotManipulateEvent - if (!player.addItem(itemStack)) { - player.drop(itemStack, false); - } diff --git a/patches/server/0456-Add-WorldGameRuleChangeEvent.patch b/patches/server/0456-Add-WorldGameRuleChangeEvent.patch new file mode 100644 index 000000000000..b03d1f41d621 --- /dev/null +++ b/patches/server/0456-Add-WorldGameRuleChangeEvent.patch @@ -0,0 +1,100 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Jake Potrebic +Date: Sun, 20 Dec 2020 16:41:44 -0800 +Subject: [PATCH] Add WorldGameRuleChangeEvent + + +diff --git a/src/main/java/net/minecraft/server/commands/GameRuleCommand.java b/src/main/java/net/minecraft/server/commands/GameRuleCommand.java +index 6fc70f07f9ba964ff6f5176367dab788decfc917..da0600ab3b0a983ac36cb777d21160bdaced7c52 100644 +--- a/src/main/java/net/minecraft/server/commands/GameRuleCommand.java ++++ b/src/main/java/net/minecraft/server/commands/GameRuleCommand.java +@@ -36,7 +36,7 @@ public class GameRuleCommand { + CommandSourceStack commandlistenerwrapper = (CommandSourceStack) context.getSource(); + T t0 = commandlistenerwrapper.getLevel().getGameRules().getRule(key); // CraftBukkit + +- t0.setFromArgument(context, "value"); ++ t0.setFromArgument(context, "value", key); // Paper - Add WorldGameRuleChangeEvent + commandlistenerwrapper.sendSuccess(() -> { + return Component.translatable("commands.gamerule.set", key.getId(), t0.toString()); + }, true); +diff --git a/src/main/java/net/minecraft/world/level/GameRules.java b/src/main/java/net/minecraft/world/level/GameRules.java +index 7ea92a0b0f5d4eb6bd873e61c42bc0499d5d2028..09299e45552eb998fd02123c3921c0653f85083d 100644 +--- a/src/main/java/net/minecraft/world/level/GameRules.java ++++ b/src/main/java/net/minecraft/world/level/GameRules.java +@@ -322,10 +322,10 @@ public class GameRules { + this.type = type; + } + +- protected abstract void updateFromArgument(CommandContext context, String name); ++ protected abstract void updateFromArgument(CommandContext context, String name, GameRules.Key gameRuleKey); // Paper - Add WorldGameRuleChangeEvent + +- public void setFromArgument(CommandContext context, String name) { +- this.updateFromArgument(context, name); ++ public void setFromArgument(CommandContext context, String name, GameRules.Key gameRuleKey) { // Paper - Add WorldGameRuleChangeEvent ++ this.updateFromArgument(context, name, gameRuleKey); // Paper - Add WorldGameRuleChangeEvent + this.onChanged(((CommandSourceStack) context.getSource()).getLevel()); // CraftBukkit - per-world + } + +@@ -383,8 +383,11 @@ public class GameRules { + } + + @Override +- protected void updateFromArgument(CommandContext context, String name) { +- this.value = BoolArgumentType.getBool(context, name); ++ protected void updateFromArgument(CommandContext context, String name, GameRules.Key gameRuleKey) { // Paper start - Add WorldGameRuleChangeEvent ++ io.papermc.paper.event.world.WorldGameRuleChangeEvent event = new io.papermc.paper.event.world.WorldGameRuleChangeEvent(context.getSource().getBukkitWorld(), context.getSource().getBukkitSender(), (org.bukkit.GameRule) org.bukkit.GameRule.getByName(gameRuleKey.toString()), String.valueOf(BoolArgumentType.getBool(context, name))); ++ if (!event.callEvent()) return; ++ this.value = Boolean.parseBoolean(event.getValue()); ++ // Paper end - Add WorldGameRuleChangeEvent + } + + public boolean get() { +@@ -456,8 +459,11 @@ public class GameRules { + } + + @Override +- protected void updateFromArgument(CommandContext context, String name) { +- this.value = IntegerArgumentType.getInteger(context, name); ++ protected void updateFromArgument(CommandContext context, String name, GameRules.Key gameRuleKey) { // Paper start - Add WorldGameRuleChangeEvent ++ io.papermc.paper.event.world.WorldGameRuleChangeEvent event = new io.papermc.paper.event.world.WorldGameRuleChangeEvent(context.getSource().getBukkitWorld(), context.getSource().getBukkitSender(), (org.bukkit.GameRule) org.bukkit.GameRule.getByName(gameRuleKey.toString()), String.valueOf(IntegerArgumentType.getInteger(context, name))); ++ if (!event.callEvent()) return; ++ this.value = Integer.parseInt(event.getValue()); ++ // Paper end - Add WorldGameRuleChangeEvent + } + + public int get() { +diff --git a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java +index b0fbb828306194fd91f0e62244d5db6404b4f10a..e19e5f1223f8b4fae9292a936dd05b9170610134 100644 +--- a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java ++++ b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java +@@ -1899,9 +1899,14 @@ public class CraftWorld extends CraftRegionAccessor implements World { + if (rule == null || value == null) return false; + + if (!this.isGameRule(rule)) return false; ++ // Paper start - Add WorldGameRuleChangeEvent ++ GameRule gameRule = GameRule.getByName(rule); ++ io.papermc.paper.event.world.WorldGameRuleChangeEvent event = new io.papermc.paper.event.world.WorldGameRuleChangeEvent(this, null, gameRule, value); ++ if (!event.callEvent()) return false; ++ // Paper end - Add WorldGameRuleChangeEvent + + GameRules.Value handle = this.getHandle().getGameRules().getRule(this.getGameRulesNMS().get(rule)); +- handle.deserialize(value); ++ handle.deserialize(event.getValue()); // Paper - Add WorldGameRuleChangeEvent + handle.onChanged(this.getHandle()); + return true; + } +@@ -1936,9 +1941,13 @@ public class CraftWorld extends CraftRegionAccessor implements World { + Preconditions.checkArgument(newValue != null, "GameRule value cannot be null"); + + if (!this.isGameRule(rule.getName())) return false; ++ // Paper start - Add WorldGameRuleChangeEvent ++ io.papermc.paper.event.world.WorldGameRuleChangeEvent event = new io.papermc.paper.event.world.WorldGameRuleChangeEvent(this, null, rule, String.valueOf(newValue)); ++ if (!event.callEvent()) return false; ++ // Paper end - Add WorldGameRuleChangeEvent + + GameRules.Value handle = this.getHandle().getGameRules().getRule(this.getGameRulesNMS().get(rule.getName())); +- handle.deserialize(newValue.toString()); ++ handle.deserialize(event.getValue()); // Paper - Add WorldGameRuleChangeEvent + handle.onChanged(this.getHandle()); + return true; + } diff --git a/patches/server/0456-Fix-interact-event-not-being-called-sometimes.patch b/patches/server/0456-Fix-interact-event-not-being-called-sometimes.patch deleted file mode 100644 index fc4bcdc2d173..000000000000 --- a/patches/server/0456-Fix-interact-event-not-being-called-sometimes.patch +++ /dev/null @@ -1,48 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: TheMolkaPL -Date: Sun, 21 Jun 2020 17:21:46 +0200 -Subject: [PATCH] Fix interact event not being called sometimes - -* Call PlayerInteractEvent when left-clicking on a block in adventure - mode. -* Call PlayerInteractEvent when left-clicking an Entity that is out of - range in adventure/survival (entity reach is 3.0). - -Co-authored-by: Moulberry - -diff --git a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java -index 57063f01993445a9965976332306c7f2ab20ed1f..d7d36b5148f2e7280816cc70b300b9b10720f751 100644 ---- a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java -+++ b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java -@@ -1770,7 +1770,7 @@ public class ServerGamePacketListenerImpl extends ServerCommonPacketListenerImpl - MutableComponent ichatmutablecomponent = Component.translatable("build.tooHigh", i - 1).withStyle(ChatFormatting.RED); - - this.player.sendSystemMessage(ichatmutablecomponent, true); -- } else if (enuminteractionresult.shouldSwing()) { -+ } else if (enuminteractionresult.shouldSwing() && !this.player.gameMode.interactResult) { // Paper - Call interact event - this.player.swing(enumhand, true); - } - } -@@ -2391,13 +2391,20 @@ public class ServerGamePacketListenerImpl extends ServerCommonPacketListenerImpl - double d3 = Math.max(this.player.blockInteractionRange(), this.player.entityInteractionRange()); - // SPIGOT-5607: Only call interact event if no block or entity is being clicked. Use bukkit ray trace method, because it handles blocks and entities at the same time - // SPIGOT-7429: Make sure to call PlayerInteractEvent for spectators and non-pickable entities -- org.bukkit.util.RayTraceResult result = this.player.level().getWorld().rayTrace(origin, origin.getDirection(), d3, org.bukkit.FluidCollisionMode.NEVER, false, 0.1, entity -> { -+ org.bukkit.util.RayTraceResult result = this.player.level().getWorld().rayTrace(origin, origin.getDirection(), d3, org.bukkit.FluidCollisionMode.NEVER, false, 0.0, entity -> { // Paper - Call interact event; change raySize from 0.1 to 0.0 - Entity handle = ((CraftEntity) entity).getHandle(); - return entity != this.player.getBukkitEntity() && this.player.getBukkitEntity().canSee(entity) && !handle.isSpectator() && handle.isPickable() && !handle.isPassengerOfSameVehicle(this.player); - }); - if (result == null) { - CraftEventFactory.callPlayerInteractEvent(this.player, Action.LEFT_CLICK_AIR, this.player.getInventory().getSelected(), InteractionHand.MAIN_HAND); -- } -+ } else { // Paper start - Call interact event -+ GameType gameType = this.player.gameMode.getGameModeForPlayer(); -+ if (gameType == GameType.ADVENTURE && result.getHitBlock() != null) { -+ CraftEventFactory.callPlayerInteractEvent(this.player, Action.LEFT_CLICK_BLOCK, ((org.bukkit.craftbukkit.block.CraftBlock) result.getHitBlock()).getPosition(), org.bukkit.craftbukkit.block.CraftBlock.blockFaceToNotch(result.getHitBlockFace()), this.player.getInventory().getSelected(), InteractionHand.MAIN_HAND); -+ } else if (gameType != GameType.CREATIVE && result.getHitEntity() != null && origin.toVector().distanceSquared(result.getHitPosition()) > this.player.entityInteractionRange() * this.player.entityInteractionRange()) { -+ CraftEventFactory.callPlayerInteractEvent(this.player, Action.LEFT_CLICK_AIR, this.player.getInventory().getSelected(), InteractionHand.MAIN_HAND); -+ } -+ } // Paper end - Call interact event - - // Arm swing animation - PlayerAnimationEvent event = new PlayerAnimationEvent(this.getCraftPlayer(), (packet.getHand() == InteractionHand.MAIN_HAND) ? PlayerAnimationType.ARM_SWING : PlayerAnimationType.OFF_ARM_SWING); diff --git a/patches/server/0457-Add-ServerResourcesReloadedEvent.patch b/patches/server/0457-Add-ServerResourcesReloadedEvent.patch new file mode 100644 index 000000000000..71fc31fbfd1b --- /dev/null +++ b/patches/server/0457-Add-ServerResourcesReloadedEvent.patch @@ -0,0 +1,54 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Jake Potrebic +Date: Wed, 2 Dec 2020 20:04:01 -0800 +Subject: [PATCH] Add ServerResourcesReloadedEvent + + +diff --git a/src/main/java/net/minecraft/server/MinecraftServer.java b/src/main/java/net/minecraft/server/MinecraftServer.java +index f6667d9f514a821fd539e193dde1f78e77ca0fbf..264d0f57422b4fc03c5845d7456b22c9a48dd7ae 100644 +--- a/src/main/java/net/minecraft/server/MinecraftServer.java ++++ b/src/main/java/net/minecraft/server/MinecraftServer.java +@@ -2150,7 +2150,13 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop reloadResources(Collection dataPacks) { ++ return this.reloadResources(dataPacks, io.papermc.paper.event.server.ServerResourcesReloadedEvent.Cause.PLUGIN); ++ } ++ public CompletableFuture reloadResources(Collection dataPacks, io.papermc.paper.event.server.ServerResourcesReloadedEvent.Cause cause) { ++ // Paper end - Add ServerResourcesReloadedEvent + CompletableFuture completablefuture = CompletableFuture.supplyAsync(() -> { + Stream stream = dataPacks.stream(); // CraftBukkit - decompile error + PackRepository resourcepackrepository = this.packRepository; +@@ -2185,6 +2191,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop dataPacks, CommandSourceStack source) { +- source.getServer().reloadResources(dataPacks).exceptionally((throwable) -> { ++ source.getServer().reloadResources(dataPacks, io.papermc.paper.event.server.ServerResourcesReloadedEvent.Cause.COMMAND).exceptionally((throwable) -> { // Paper - Add ServerResourcesReloadedEvent + ReloadCommand.LOGGER.warn("Failed to execute reload", throwable); + source.sendFailure(Component.translatable("commands.reload.failure")); + return null; +@@ -50,7 +50,7 @@ public class ReloadCommand { + WorldData savedata = minecraftserver.getWorldData(); + Collection collection = resourcepackrepository.getSelectedIds(); + Collection collection1 = ReloadCommand.discoverNewPacks(resourcepackrepository, savedata, collection); +- minecraftserver.reloadResources(collection1); ++ minecraftserver.reloadResources(collection1, io.papermc.paper.event.server.ServerResourcesReloadedEvent.Cause.PLUGIN); // Paper - Add ServerResourcesReloadedEvent + } + // CraftBukkit end + diff --git a/patches/server/0457-Zombie-API-breaking-doors.patch b/patches/server/0457-Zombie-API-breaking-doors.patch deleted file mode 100644 index 50bb6965cf53..000000000000 --- a/patches/server/0457-Zombie-API-breaking-doors.patch +++ /dev/null @@ -1,24 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Jake Potrebic -Date: Wed, 18 Nov 2020 11:32:46 -0800 -Subject: [PATCH] Zombie API - breaking doors - -== AT == -public net.minecraft.world.entity.monster.Zombie supportsBreakDoorGoal()Z - -diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftZombie.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftZombie.java -index 4412c913123f7521f449c98b60378e8d3b1671ce..46336111dcf62a29390e724b1879c84c697076e9 100644 ---- a/src/main/java/org/bukkit/craftbukkit/entity/CraftZombie.java -+++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftZombie.java -@@ -122,6 +122,11 @@ public class CraftZombie extends CraftMonster implements Zombie { - public void setShouldBurnInDay(boolean shouldBurnInDay) { - getHandle().setShouldBurnInDay(shouldBurnInDay); - } -+ -+ @Override -+ public boolean supportsBreakingDoors() { -+ return getHandle().supportsBreakDoorGoal(); -+ } - // Paper end - - @Override diff --git a/patches/server/0458-Add-world-settings-for-mobs-picking-up-loot.patch b/patches/server/0458-Add-world-settings-for-mobs-picking-up-loot.patch new file mode 100644 index 000000000000..9e08c62541b3 --- /dev/null +++ b/patches/server/0458-Add-world-settings-for-mobs-picking-up-loot.patch @@ -0,0 +1,32 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Jake Potrebic +Date: Sat, 28 Nov 2020 18:43:52 -0800 +Subject: [PATCH] Add world settings for mobs picking up loot + + +diff --git a/src/main/java/net/minecraft/world/entity/monster/AbstractSkeleton.java b/src/main/java/net/minecraft/world/entity/monster/AbstractSkeleton.java +index 8f63e27d904abb33492daf627d48d33d1193deef..723a098eabcc632caeb096f39c90e4e0581edc35 100644 +--- a/src/main/java/net/minecraft/world/entity/monster/AbstractSkeleton.java ++++ b/src/main/java/net/minecraft/world/entity/monster/AbstractSkeleton.java +@@ -150,7 +150,7 @@ public abstract class AbstractSkeleton extends Monster implements RangedAttackMo + this.populateDefaultEquipmentSlots(randomsource, difficulty); + this.populateDefaultEquipmentEnchantments(world, randomsource, difficulty); + this.reassessWeaponGoal(); +- this.setCanPickUpLoot(randomsource.nextFloat() < 0.55F * difficulty.getSpecialMultiplier()); ++ this.setCanPickUpLoot(this.level().paperConfig().entities.behavior.mobsCanAlwaysPickUpLoot.skeletons || randomsource.nextFloat() < 0.55F * difficulty.getSpecialMultiplier()); // Paper - Add world settings for mobs picking up loot + if (this.getItemBySlot(EquipmentSlot.HEAD).isEmpty()) { + LocalDate localdate = LocalDate.now(); + int i = localdate.get(ChronoField.DAY_OF_MONTH); +diff --git a/src/main/java/net/minecraft/world/entity/monster/Zombie.java b/src/main/java/net/minecraft/world/entity/monster/Zombie.java +index c182bdcc5da5652f8b34b4cb8d28651cf79009fe..34e46a64b3638f749a571d080fd8e7ac1f57edba 100644 +--- a/src/main/java/net/minecraft/world/entity/monster/Zombie.java ++++ b/src/main/java/net/minecraft/world/entity/monster/Zombie.java +@@ -518,7 +518,7 @@ public class Zombie extends Monster { + float f = difficulty.getSpecialMultiplier(); + + if (spawnReason != EntitySpawnReason.CONVERSION) { +- this.setCanPickUpLoot(randomsource.nextFloat() < 0.55F * f); ++ this.setCanPickUpLoot(this.level().paperConfig().entities.behavior.mobsCanAlwaysPickUpLoot.zombies || randomsource.nextFloat() < 0.55F * f); // Paper - Add world settings for mobs picking up loot + } + + if (object == null) { diff --git a/patches/server/0458-Fix-nerfed-slime-when-splitting.patch b/patches/server/0458-Fix-nerfed-slime-when-splitting.patch deleted file mode 100644 index a32a88f5ad3e..000000000000 --- a/patches/server/0458-Fix-nerfed-slime-when-splitting.patch +++ /dev/null @@ -1,18 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Jake Potrebic -Date: Mon, 24 Aug 2020 08:39:06 -0700 -Subject: [PATCH] Fix nerfed slime when splitting - - -diff --git a/src/main/java/net/minecraft/world/entity/monster/Slime.java b/src/main/java/net/minecraft/world/entity/monster/Slime.java -index ef27b0af849f071f79271689783b7a557e6d660a..b54c30ba73b6ab069c0c7c1cd2b193090da79667 100644 ---- a/src/main/java/net/minecraft/world/entity/monster/Slime.java -+++ b/src/main/java/net/minecraft/world/entity/monster/Slime.java -@@ -254,6 +254,7 @@ public class Slime extends Mob implements Enemy { - entityslime.setPersistenceRequired(); - } - -+ entityslime.aware = this.aware; // Paper - Fix nerfed slime when splitting - entityslime.setCustomName(ichatbasecomponent); - entityslime.setNoAi(flag); - entityslime.setInvulnerable(this.isInvulnerable()); diff --git a/patches/server/0459-Add-BlockFailedDispenseEvent.patch b/patches/server/0459-Add-BlockFailedDispenseEvent.patch new file mode 100644 index 000000000000..ee65c26c996d --- /dev/null +++ b/patches/server/0459-Add-BlockFailedDispenseEvent.patch @@ -0,0 +1,50 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: TheViperShow <29604693+TheViperShow@users.noreply.github.com> +Date: Wed, 22 Apr 2020 09:40:38 +0200 +Subject: [PATCH] Add BlockFailedDispenseEvent + + +diff --git a/src/main/java/net/minecraft/world/level/block/DispenserBlock.java b/src/main/java/net/minecraft/world/level/block/DispenserBlock.java +index f4510b8e14d0b9997907ce1d1368ba3b3b5daf3e..f4853a5ff8a45efcda2d7781c1fa897c47d8ea46 100644 +--- a/src/main/java/net/minecraft/world/level/block/DispenserBlock.java ++++ b/src/main/java/net/minecraft/world/level/block/DispenserBlock.java +@@ -98,8 +98,10 @@ public class DispenserBlock extends BaseEntityBlock { + int i = tileentitydispenser.getRandomSlot(world.random); + + if (i < 0) { ++ if (org.bukkit.craftbukkit.event.CraftEventFactory.handleBlockFailedDispenseEvent(world, pos)) { // Paper - Add BlockFailedDispenseEvent + world.levelEvent(1001, pos, 0); + world.gameEvent((Holder) GameEvent.BLOCK_ACTIVATE, pos, GameEvent.Context.of(tileentitydispenser.getBlockState())); ++ } // Paper - Add BlockFailedDispenseEvent + } else { + ItemStack itemstack = tileentitydispenser.getItem(i); + DispenseItemBehavior idispensebehavior = this.getDispenseMethod(world, itemstack); +diff --git a/src/main/java/net/minecraft/world/level/block/DropperBlock.java b/src/main/java/net/minecraft/world/level/block/DropperBlock.java +index a08e8571f3a83afc80c2f1758a9029cd28ed6947..91b514967405115f22edf4255775361a672e5c2f 100644 +--- a/src/main/java/net/minecraft/world/level/block/DropperBlock.java ++++ b/src/main/java/net/minecraft/world/level/block/DropperBlock.java +@@ -60,6 +60,7 @@ public class DropperBlock extends DispenserBlock { + int i = tileentitydispenser.getRandomSlot(world.random); + + if (i < 0) { ++ if (org.bukkit.craftbukkit.event.CraftEventFactory.handleBlockFailedDispenseEvent(world, pos)) // Paper - Add BlockFailedDispenseEvent + world.levelEvent(1001, pos, 0); + } else { + ItemStack itemstack = tileentitydispenser.getItem(i); +diff --git a/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java b/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java +index 9f97216a4be562acb69281f4a638dad139c6bc7d..1e918ef9296e5127b78a7eb0460225448982772e 100644 +--- a/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java ++++ b/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java +@@ -2125,4 +2125,12 @@ public class CraftEventFactory { + return org.bukkit.craftbukkit.inventory.CraftItemStack.asNMSCopy(event.getPotion()); + } + // Paper end - WitchReadyPotionEvent ++ ++ // Paper start ++ public static boolean handleBlockFailedDispenseEvent(ServerLevel serverLevel, BlockPos pos) { ++ org.bukkit.block.Block block = CraftBlock.at(serverLevel, pos); ++ io.papermc.paper.event.block.BlockFailedDispenseEvent event = new io.papermc.paper.event.block.BlockFailedDispenseEvent(block); ++ return event.callEvent(); ++ } ++ // Paper end + } diff --git a/patches/server/0459-Add-EntityLoadCrossbowEvent.patch b/patches/server/0459-Add-EntityLoadCrossbowEvent.patch deleted file mode 100644 index a2f95139f888..000000000000 --- a/patches/server/0459-Add-EntityLoadCrossbowEvent.patch +++ /dev/null @@ -1,68 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: JRoy -Date: Wed, 7 Oct 2020 12:04:01 -0400 -Subject: [PATCH] Add EntityLoadCrossbowEvent - - -diff --git a/src/main/java/net/minecraft/world/item/CrossbowItem.java b/src/main/java/net/minecraft/world/item/CrossbowItem.java -index f64cdfac1fc1333845ea4ea5efb7922f0ae39619..c39fa953accd6cf35672f452052cca42fe6f29d0 100644 ---- a/src/main/java/net/minecraft/world/item/CrossbowItem.java -+++ b/src/main/java/net/minecraft/world/item/CrossbowItem.java -@@ -89,7 +89,14 @@ public class CrossbowItem extends ProjectileWeaponItem { - public void releaseUsing(ItemStack stack, Level world, LivingEntity user, int remainingUseTicks) { - int i = this.getUseDuration(stack, user) - remainingUseTicks; - float f = getPowerForTime(i, stack, user); -- if (f >= 1.0F && !isCharged(stack) && tryLoadProjectiles(user, stack)) { -+ // Paper start - Add EntityLoadCrossbowEvent -+ if (f >= 1.0F && !isCharged(stack)) { -+ final io.papermc.paper.event.entity.EntityLoadCrossbowEvent event = new io.papermc.paper.event.entity.EntityLoadCrossbowEvent(user.getBukkitLivingEntity(), stack.asBukkitMirror(), org.bukkit.craftbukkit.CraftEquipmentSlot.getHand(user.getUsedItemHand())); -+ if (!event.callEvent() || !tryLoadProjectiles(user, stack, event.shouldConsumeItem()) || !event.shouldConsumeItem()) { -+ if (user instanceof ServerPlayer player) player.containerMenu.sendAllDataToRemote(); -+ return; -+ } -+ // Paper end - Add EntityLoadCrossbowEvent - CrossbowItem.ChargingSounds chargingSounds = this.getChargingSounds(stack); - chargingSounds.end() - .ifPresent( -@@ -107,8 +114,14 @@ public class CrossbowItem extends ProjectileWeaponItem { - } - } - -- private static boolean tryLoadProjectiles(LivingEntity shooter, ItemStack crossbow) { -- List list = draw(crossbow, shooter.getProjectile(crossbow), shooter); -+ @io.papermc.paper.annotation.DoNotUse // Paper - Add EntityLoadCrossbowEvent -+ private static boolean tryLoadProjectiles(LivingEntity shooter, ItemStack crossbow) { -+ // Paper start - Add EntityLoadCrossbowEvent -+ return CrossbowItem.tryLoadProjectiles(shooter, crossbow, true); -+ } -+ private static boolean tryLoadProjectiles(LivingEntity shooter, ItemStack crossbow, boolean consume) { -+ List list = draw(crossbow, shooter.getProjectile(crossbow), shooter, consume); -+ // Paper end - Add EntityLoadCrossbowEvent - if (!list.isEmpty()) { - crossbow.set(DataComponents.CHARGED_PROJECTILES, ChargedProjectiles.of(list)); - return true; -diff --git a/src/main/java/net/minecraft/world/item/ProjectileWeaponItem.java b/src/main/java/net/minecraft/world/item/ProjectileWeaponItem.java -index 56595dd3a0b7df4b5f9819ade797212278c8fd40..32dd0b13a0819f597d8a93c6bc3a155781067544 100644 ---- a/src/main/java/net/minecraft/world/item/ProjectileWeaponItem.java -+++ b/src/main/java/net/minecraft/world/item/ProjectileWeaponItem.java -@@ -114,6 +114,11 @@ public abstract class ProjectileWeaponItem extends Item { - } - - protected static List draw(ItemStack stack, ItemStack projectileStack, LivingEntity shooter) { -+ // Paper start -+ return draw(stack, projectileStack, shooter, true); -+ } -+ protected static List draw(ItemStack stack, ItemStack projectileStack, LivingEntity shooter, boolean consume) { -+ // Paper end - if (projectileStack.isEmpty()) { - return List.of(); - } else { -@@ -133,7 +138,7 @@ public abstract class ProjectileWeaponItem extends Item { - ItemStack itemstack2 = projectileStack.copy(); - - for (int k = 0; k < j; ++k) { -- ItemStack itemstack3 = ProjectileWeaponItem.useAmmo(stack, k == 0 ? projectileStack : itemstack2, shooter, k > 0); -+ ItemStack itemstack3 = ProjectileWeaponItem.useAmmo(stack, k == 0 ? projectileStack : itemstack2, shooter, k > 0 || !consume); // Paper - - if (!itemstack3.isEmpty()) { - list.add(itemstack3); diff --git a/patches/server/0464-Add-PlayerLecternPageChangeEvent.patch b/patches/server/0460-Add-PlayerLecternPageChangeEvent.patch similarity index 100% rename from patches/server/0464-Add-PlayerLecternPageChangeEvent.patch rename to patches/server/0460-Add-PlayerLecternPageChangeEvent.patch diff --git a/patches/server/0460-Add-WorldGameRuleChangeEvent.patch b/patches/server/0460-Add-WorldGameRuleChangeEvent.patch deleted file mode 100644 index 25259dc06e37..000000000000 --- a/patches/server/0460-Add-WorldGameRuleChangeEvent.patch +++ /dev/null @@ -1,98 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Jake Potrebic -Date: Sun, 20 Dec 2020 16:41:44 -0800 -Subject: [PATCH] Add WorldGameRuleChangeEvent - - -diff --git a/src/main/java/net/minecraft/server/commands/GameRuleCommand.java b/src/main/java/net/minecraft/server/commands/GameRuleCommand.java -index c8c358531dbc167e249bac2af246c5e34fbdd4df..10c1790226e25da3b9b599c9a40de57d5727ddc4 100644 ---- a/src/main/java/net/minecraft/server/commands/GameRuleCommand.java -+++ b/src/main/java/net/minecraft/server/commands/GameRuleCommand.java -@@ -33,7 +33,7 @@ public class GameRuleCommand { - CommandSourceStack commandlistenerwrapper = (CommandSourceStack) context.getSource(); - T t0 = commandlistenerwrapper.getLevel().getGameRules().getRule(key); // CraftBukkit - -- t0.setFromArgument(context, "value"); -+ t0.setFromArgument(context, "value", key); // Paper - Add WorldGameRuleChangeEvent - commandlistenerwrapper.sendSuccess(() -> { - return Component.translatable("commands.gamerule.set", key.getId(), t0.toString()); - }, true); -diff --git a/src/main/java/net/minecraft/world/level/GameRules.java b/src/main/java/net/minecraft/world/level/GameRules.java -index 0b46ad360be919e4aeb0ffc0eebae9fe712fb861..51e560d7856f230c5aa2dc32706c3a4996720aa5 100644 ---- a/src/main/java/net/minecraft/world/level/GameRules.java -+++ b/src/main/java/net/minecraft/world/level/GameRules.java -@@ -293,10 +293,10 @@ public class GameRules { - this.type = type; - } - -- protected abstract void updateFromArgument(CommandContext context, String name); -+ protected abstract void updateFromArgument(CommandContext context, String name, GameRules.Key gameRuleKey); // Paper - Add WorldGameRuleChangeEvent - -- public void setFromArgument(CommandContext context, String name) { -- this.updateFromArgument(context, name); -+ public void setFromArgument(CommandContext context, String name, GameRules.Key gameRuleKey) { // Paper - Add WorldGameRuleChangeEvent -+ this.updateFromArgument(context, name, gameRuleKey); // Paper - Add WorldGameRuleChangeEvent - this.onChanged(((CommandSourceStack) context.getSource()).getLevel()); // CraftBukkit - per-world - } - -@@ -354,8 +354,11 @@ public class GameRules { - } - - @Override -- protected void updateFromArgument(CommandContext context, String name) { -- this.value = BoolArgumentType.getBool(context, name); -+ protected void updateFromArgument(CommandContext context, String name, GameRules.Key gameRuleKey) { // Paper start - Add WorldGameRuleChangeEvent -+ io.papermc.paper.event.world.WorldGameRuleChangeEvent event = new io.papermc.paper.event.world.WorldGameRuleChangeEvent(context.getSource().getBukkitWorld(), context.getSource().getBukkitSender(), (org.bukkit.GameRule) org.bukkit.GameRule.getByName(gameRuleKey.toString()), String.valueOf(BoolArgumentType.getBool(context, name))); -+ if (!event.callEvent()) return; -+ this.value = Boolean.parseBoolean(event.getValue()); -+ // Paper end - Add WorldGameRuleChangeEvent - } - - public boolean get() { -@@ -427,8 +430,11 @@ public class GameRules { - } - - @Override -- protected void updateFromArgument(CommandContext context, String name) { -- this.value = IntegerArgumentType.getInteger(context, name); -+ protected void updateFromArgument(CommandContext context, String name, GameRules.Key gameRuleKey) { // Paper start - Add WorldGameRuleChangeEvent -+ io.papermc.paper.event.world.WorldGameRuleChangeEvent event = new io.papermc.paper.event.world.WorldGameRuleChangeEvent(context.getSource().getBukkitWorld(), context.getSource().getBukkitSender(), (org.bukkit.GameRule) org.bukkit.GameRule.getByName(gameRuleKey.toString()), String.valueOf(IntegerArgumentType.getInteger(context, name))); -+ if (!event.callEvent()) return; -+ this.value = Integer.parseInt(event.getValue()); -+ // Paper end - Add WorldGameRuleChangeEvent - } - - public int get() { -diff --git a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java -index 99fccedaab2600881683140e10ee17377375b911..f698ac90bd3086519f49e92451228415efaf5530 100644 ---- a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java -+++ b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java -@@ -1887,8 +1887,13 @@ public class CraftWorld extends CraftRegionAccessor implements World { - - if (!this.isGameRule(rule)) return false; - -+ // Paper start - Add WorldGameRuleChangeEvent -+ GameRule gameRule = GameRule.getByName(rule); -+ io.papermc.paper.event.world.WorldGameRuleChangeEvent event = new io.papermc.paper.event.world.WorldGameRuleChangeEvent(this, null, gameRule, value); -+ if (!event.callEvent()) return false; -+ // Paper end - Add WorldGameRuleChangeEvent - GameRules.Value handle = this.getHandle().getGameRules().getRule(CraftWorld.getGameRulesNMS().get(rule)); -- handle.deserialize(value); -+ handle.deserialize(event.getValue()); // Paper - Add WorldGameRuleChangeEvent - handle.onChanged(this.getHandle()); - return true; - } -@@ -1924,8 +1929,12 @@ public class CraftWorld extends CraftRegionAccessor implements World { - - if (!this.isGameRule(rule.getName())) return false; - -+ // Paper start - Add WorldGameRuleChangeEvent -+ io.papermc.paper.event.world.WorldGameRuleChangeEvent event = new io.papermc.paper.event.world.WorldGameRuleChangeEvent(this, null, rule, String.valueOf(newValue)); -+ if (!event.callEvent()) return false; -+ // Paper end - Add WorldGameRuleChangeEvent - GameRules.Value handle = this.getHandle().getGameRules().getRule(CraftWorld.getGameRulesNMS().get(rule.getName())); -- handle.deserialize(newValue.toString()); -+ handle.deserialize(event.getValue()); // Paper - Add WorldGameRuleChangeEvent - handle.onChanged(this.getHandle()); - return true; - } diff --git a/patches/server/0461-Add-PlayerLoomPatternSelectEvent.patch b/patches/server/0461-Add-PlayerLoomPatternSelectEvent.patch new file mode 100644 index 000000000000..b523b29e0f15 --- /dev/null +++ b/patches/server/0461-Add-PlayerLoomPatternSelectEvent.patch @@ -0,0 +1,45 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Jake Potrebic +Date: Wed, 25 Nov 2020 16:33:27 -0800 +Subject: [PATCH] Add PlayerLoomPatternSelectEvent + + +diff --git a/src/main/java/net/minecraft/world/inventory/LoomMenu.java b/src/main/java/net/minecraft/world/inventory/LoomMenu.java +index 58470d8e9a4ceb1eca05b342481ed8260588e225..1b7cf165ab0818792870f43719a6324b282bb57a 100644 +--- a/src/main/java/net/minecraft/world/inventory/LoomMenu.java ++++ b/src/main/java/net/minecraft/world/inventory/LoomMenu.java +@@ -162,8 +162,32 @@ public class LoomMenu extends AbstractContainerMenu { + @Override + public boolean clickMenuButton(net.minecraft.world.entity.player.Player player, int id) { + if (id >= 0 && id < this.selectablePatterns.size()) { +- this.selectedBannerPatternIndex.set(id); +- this.setupResultSlot((Holder) this.selectablePatterns.get(id)); ++ // Paper start - Add PlayerLoomPatternSelectEvent ++ int selectablePatternIndex = id; ++ io.papermc.paper.event.player.PlayerLoomPatternSelectEvent event = new io.papermc.paper.event.player.PlayerLoomPatternSelectEvent((Player) player.getBukkitEntity(), ((CraftInventoryLoom) getBukkitView().getTopInventory()), org.bukkit.craftbukkit.block.banner.CraftPatternType.minecraftHolderToBukkit(this.selectablePatterns.get(selectablePatternIndex))); ++ if (!event.callEvent()) { ++ player.containerMenu.sendAllDataToRemote(); ++ return false; ++ } ++ final Holder eventPattern = org.bukkit.craftbukkit.block.banner.CraftPatternType.bukkitToMinecraftHolder(event.getPatternType()); ++ Holder selectedPattern = null; ++ for (int i = 0; i < this.selectablePatterns.size(); i++) { ++ final Holder holder = this.selectablePatterns.get(i); ++ if (eventPattern.equals(holder)) { ++ selectablePatternIndex = i; ++ selectedPattern = holder; ++ break; ++ } ++ } ++ if (selectedPattern == null) { ++ selectedPattern = eventPattern; ++ selectablePatternIndex = -1; ++ } ++ ++ player.containerMenu.sendAllDataToRemote(); ++ this.selectedBannerPatternIndex.set(selectablePatternIndex); ++ this.setupResultSlot(java.util.Objects.requireNonNull(selectedPattern, "selectedPattern was null, this is unexpected")); ++ // Paper end - Add PlayerLoomPatternSelectEvent + return true; + } else { + return false; diff --git a/patches/server/0461-Add-ServerResourcesReloadedEvent.patch b/patches/server/0461-Add-ServerResourcesReloadedEvent.patch deleted file mode 100644 index 830f76c6157d..000000000000 --- a/patches/server/0461-Add-ServerResourcesReloadedEvent.patch +++ /dev/null @@ -1,54 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Jake Potrebic -Date: Wed, 2 Dec 2020 20:04:01 -0800 -Subject: [PATCH] Add ServerResourcesReloadedEvent - - -diff --git a/src/main/java/net/minecraft/server/MinecraftServer.java b/src/main/java/net/minecraft/server/MinecraftServer.java -index 05aa3323e9ae971fba5ab8c6c319c7805a3808aa..39ce74bc3fbb282f56028273d53f980171bdb464 100644 ---- a/src/main/java/net/minecraft/server/MinecraftServer.java -+++ b/src/main/java/net/minecraft/server/MinecraftServer.java -@@ -2114,7 +2114,13 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop reloadResources(Collection dataPacks) { -+ return this.reloadResources(dataPacks, io.papermc.paper.event.server.ServerResourcesReloadedEvent.Cause.PLUGIN); -+ } -+ public CompletableFuture reloadResources(Collection dataPacks, io.papermc.paper.event.server.ServerResourcesReloadedEvent.Cause cause) { -+ // Paper end - Add ServerResourcesReloadedEvent - CompletableFuture completablefuture = CompletableFuture.supplyAsync(() -> { - Stream stream = dataPacks.stream(); // CraftBukkit - decompile error - PackRepository resourcepackrepository = this.packRepository; -@@ -2146,6 +2152,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop dataPacks, CommandSourceStack source) { -- source.getServer().reloadResources(dataPacks).exceptionally((throwable) -> { -+ source.getServer().reloadResources(dataPacks, io.papermc.paper.event.server.ServerResourcesReloadedEvent.Cause.COMMAND).exceptionally((throwable) -> { // Paper - Add ServerResourcesReloadedEvent - ReloadCommand.LOGGER.warn("Failed to execute reload", throwable); - source.sendFailure(Component.translatable("commands.reload.failure")); - return null; -@@ -50,7 +50,7 @@ public class ReloadCommand { - WorldData savedata = minecraftserver.getWorldData(); - Collection collection = resourcepackrepository.getSelectedIds(); - Collection collection1 = ReloadCommand.discoverNewPacks(resourcepackrepository, savedata, collection); -- minecraftserver.reloadResources(collection1); -+ minecraftserver.reloadResources(collection1, io.papermc.paper.event.server.ServerResourcesReloadedEvent.Cause.PLUGIN); // Paper - Add ServerResourcesReloadedEvent - } - // CraftBukkit end - diff --git a/patches/server/0462-Add-world-settings-for-mobs-picking-up-loot.patch b/patches/server/0462-Add-world-settings-for-mobs-picking-up-loot.patch deleted file mode 100644 index a6e23dc93147..000000000000 --- a/patches/server/0462-Add-world-settings-for-mobs-picking-up-loot.patch +++ /dev/null @@ -1,32 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Jake Potrebic -Date: Sat, 28 Nov 2020 18:43:52 -0800 -Subject: [PATCH] Add world settings for mobs picking up loot - - -diff --git a/src/main/java/net/minecraft/world/entity/monster/AbstractSkeleton.java b/src/main/java/net/minecraft/world/entity/monster/AbstractSkeleton.java -index aec440d32eb97fa8ce738b98dae1cdc346e8a59b..a5593ac33878efc970c1bdd6f636438d160e9d50 100644 ---- a/src/main/java/net/minecraft/world/entity/monster/AbstractSkeleton.java -+++ b/src/main/java/net/minecraft/world/entity/monster/AbstractSkeleton.java -@@ -148,7 +148,7 @@ public abstract class AbstractSkeleton extends Monster implements RangedAttackMo - this.populateDefaultEquipmentSlots(randomsource, difficulty); - this.populateDefaultEquipmentEnchantments(world, randomsource, difficulty); - this.reassessWeaponGoal(); -- this.setCanPickUpLoot(randomsource.nextFloat() < 0.55F * difficulty.getSpecialMultiplier()); -+ this.setCanPickUpLoot(this.level().paperConfig().entities.behavior.mobsCanAlwaysPickUpLoot.skeletons || randomsource.nextFloat() < 0.55F * difficulty.getSpecialMultiplier()); // Paper - Add world settings for mobs picking up loot - if (this.getItemBySlot(EquipmentSlot.HEAD).isEmpty()) { - LocalDate localdate = LocalDate.now(); - int i = localdate.get(ChronoField.DAY_OF_MONTH); -diff --git a/src/main/java/net/minecraft/world/entity/monster/Zombie.java b/src/main/java/net/minecraft/world/entity/monster/Zombie.java -index 78254df3e5fbcb0a90c2f9eb9c9343792238f685..5908f1646b542d61b5a2b23b284b1532e4b276c1 100644 ---- a/src/main/java/net/minecraft/world/entity/monster/Zombie.java -+++ b/src/main/java/net/minecraft/world/entity/monster/Zombie.java -@@ -509,7 +509,7 @@ public class Zombie extends Monster { - Object object = super.finalizeSpawn(world, difficulty, spawnReason, entityData); - float f = difficulty.getSpecialMultiplier(); - -- this.setCanPickUpLoot(randomsource.nextFloat() < 0.55F * f); -+ this.setCanPickUpLoot(this.level().paperConfig().entities.behavior.mobsCanAlwaysPickUpLoot.zombies || randomsource.nextFloat() < 0.55F * f); // Paper - Add world settings for mobs picking up loot - if (object == null) { - object = new Zombie.ZombieGroupData(Zombie.getSpawnAsBabyOdds(randomsource), true); - } diff --git a/patches/server/0462-Configurable-door-breaking-difficulty.patch b/patches/server/0462-Configurable-door-breaking-difficulty.patch new file mode 100644 index 000000000000..94b37c735f6f --- /dev/null +++ b/patches/server/0462-Configurable-door-breaking-difficulty.patch @@ -0,0 +1,37 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Jake Potrebic +Date: Sun, 3 Jan 2021 22:27:43 -0800 +Subject: [PATCH] Configurable door breaking difficulty + +== AT == +public net.minecraft.world.entity.monster.Vindicator DOOR_BREAKING_PREDICATE +public net.minecraft.world.entity.monster.Zombie DOOR_BREAKING_PREDICATE + +Co-authored-by: Doc + +diff --git a/src/main/java/net/minecraft/world/entity/monster/Vindicator.java b/src/main/java/net/minecraft/world/entity/monster/Vindicator.java +index b06eedb1cb13771bbc7d0b812a9df864d1f73142..96b105697c91314148fd1b783501389214b1a3f0 100644 +--- a/src/main/java/net/minecraft/world/entity/monster/Vindicator.java ++++ b/src/main/java/net/minecraft/world/entity/monster/Vindicator.java +@@ -184,7 +184,7 @@ public class Vindicator extends AbstractIllager { + + static class VindicatorBreakDoorGoal extends BreakDoorGoal { + public VindicatorBreakDoorGoal(Mob mob) { +- super(mob, 6, Vindicator.DOOR_BREAKING_PREDICATE); ++ super(mob, 6, com.google.common.base.Predicates.in(mob.level().paperConfig().entities.behavior.doorBreakingDifficulty.getOrDefault(mob.getType(), mob.level().paperConfig().entities.behavior.doorBreakingDifficulty.get(EntityType.VINDICATOR)))); // Paper - Configurable door breaking difficulty + this.setFlags(EnumSet.of(Goal.Flag.MOVE)); + } + +diff --git a/src/main/java/net/minecraft/world/entity/monster/Zombie.java b/src/main/java/net/minecraft/world/entity/monster/Zombie.java +index 34e46a64b3638f749a571d080fd8e7ac1f57edba..a835ec6e063dd247a008da84446f8647f38d89d4 100644 +--- a/src/main/java/net/minecraft/world/entity/monster/Zombie.java ++++ b/src/main/java/net/minecraft/world/entity/monster/Zombie.java +@@ -103,7 +103,7 @@ public class Zombie extends Monster { + + public Zombie(EntityType type, Level world) { + super(type, world); +- this.breakDoorGoal = new BreakDoorGoal(this, Zombie.DOOR_BREAKING_PREDICATE); ++ this.breakDoorGoal = new BreakDoorGoal(this, com.google.common.base.Predicates.in(world.paperConfig().entities.behavior.doorBreakingDifficulty.getOrDefault(type, world.paperConfig().entities.behavior.doorBreakingDifficulty.get(EntityType.ZOMBIE)))); // Paper - Configurable door breaking difficulty + } + + public Zombie(Level world) { diff --git a/patches/server/0463-Add-BlockFailedDispenseEvent.patch b/patches/server/0463-Add-BlockFailedDispenseEvent.patch deleted file mode 100644 index d293da6e03a7..000000000000 --- a/patches/server/0463-Add-BlockFailedDispenseEvent.patch +++ /dev/null @@ -1,50 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: TheViperShow <29604693+TheViperShow@users.noreply.github.com> -Date: Wed, 22 Apr 2020 09:40:38 +0200 -Subject: [PATCH] Add BlockFailedDispenseEvent - - -diff --git a/src/main/java/net/minecraft/world/level/block/DispenserBlock.java b/src/main/java/net/minecraft/world/level/block/DispenserBlock.java -index c7256ffbe57c594556ee85134c9ab166f8c0e0c7..94bcbaf7daf7dfe566f508d1170a433930d9d49a 100644 ---- a/src/main/java/net/minecraft/world/level/block/DispenserBlock.java -+++ b/src/main/java/net/minecraft/world/level/block/DispenserBlock.java -@@ -101,8 +101,10 @@ public class DispenserBlock extends BaseEntityBlock { - int i = tileentitydispenser.getRandomSlot(world.random); - - if (i < 0) { -+ if (org.bukkit.craftbukkit.event.CraftEventFactory.handleBlockFailedDispenseEvent(world, pos)) { // Paper - Add BlockFailedDispenseEvent - world.levelEvent(1001, pos, 0); - world.gameEvent((Holder) GameEvent.BLOCK_ACTIVATE, pos, GameEvent.Context.of(tileentitydispenser.getBlockState())); -+ } // Paper - Add BlockFailedDispenseEvent - } else { - ItemStack itemstack = tileentitydispenser.getItem(i); - DispenseItemBehavior idispensebehavior = this.getDispenseMethod(world, itemstack); -diff --git a/src/main/java/net/minecraft/world/level/block/DropperBlock.java b/src/main/java/net/minecraft/world/level/block/DropperBlock.java -index a08e8571f3a83afc80c2f1758a9029cd28ed6947..91b514967405115f22edf4255775361a672e5c2f 100644 ---- a/src/main/java/net/minecraft/world/level/block/DropperBlock.java -+++ b/src/main/java/net/minecraft/world/level/block/DropperBlock.java -@@ -60,6 +60,7 @@ public class DropperBlock extends DispenserBlock { - int i = tileentitydispenser.getRandomSlot(world.random); - - if (i < 0) { -+ if (org.bukkit.craftbukkit.event.CraftEventFactory.handleBlockFailedDispenseEvent(world, pos)) // Paper - Add BlockFailedDispenseEvent - world.levelEvent(1001, pos, 0); - } else { - ItemStack itemstack = tileentitydispenser.getItem(i); -diff --git a/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java b/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java -index 24fcb0eb139174671ffb7a8e5222ec9833835f87..9e67dc35ea12149265517b951a96828460b2ca67 100644 ---- a/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java -+++ b/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java -@@ -2127,4 +2127,12 @@ public class CraftEventFactory { - return org.bukkit.craftbukkit.inventory.CraftItemStack.asNMSCopy(event.getPotion()); - } - // Paper end - WitchReadyPotionEvent -+ -+ // Paper start -+ public static boolean handleBlockFailedDispenseEvent(ServerLevel serverLevel, BlockPos pos) { -+ org.bukkit.block.Block block = CraftBlock.at(serverLevel, pos); -+ io.papermc.paper.event.block.BlockFailedDispenseEvent event = new io.papermc.paper.event.block.BlockFailedDispenseEvent(block); -+ return event.callEvent(); -+ } -+ // Paper end - } diff --git a/patches/server/0463-Empty-commands-shall-not-be-dispatched.patch b/patches/server/0463-Empty-commands-shall-not-be-dispatched.patch new file mode 100644 index 000000000000..ca4dd56d56bd --- /dev/null +++ b/patches/server/0463-Empty-commands-shall-not-be-dispatched.patch @@ -0,0 +1,18 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Mariell Hoversholm +Date: Wed, 6 Jan 2021 23:38:43 +0100 +Subject: [PATCH] Empty commands shall not be dispatched + + +diff --git a/src/main/java/net/minecraft/commands/Commands.java b/src/main/java/net/minecraft/commands/Commands.java +index a7eb2a37a81a414dcb19319c075faefe0382aeba..e38f1d972d87f33a5f28459aa988e09f24614453 100644 +--- a/src/main/java/net/minecraft/commands/Commands.java ++++ b/src/main/java/net/minecraft/commands/Commands.java +@@ -291,6 +291,7 @@ public class Commands { + command = event.getCommand(); + + String[] args = command.split(" "); ++ if (args.length == 0) return; // Paper - empty commands shall not be dispatched + + String cmd = args[0]; + if (cmd.startsWith("minecraft:")) cmd = cmd.substring("minecraft:".length()); diff --git a/patches/server/0464-Remove-stale-POIs.patch b/patches/server/0464-Remove-stale-POIs.patch new file mode 100644 index 000000000000..8c0a881067ce --- /dev/null +++ b/patches/server/0464-Remove-stale-POIs.patch @@ -0,0 +1,22 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Shane Freeder +Date: Sat, 9 Jan 2021 14:17:07 +0100 +Subject: [PATCH] Remove stale POIs + + +diff --git a/src/main/java/net/minecraft/server/level/ServerLevel.java b/src/main/java/net/minecraft/server/level/ServerLevel.java +index c5029210d059a62577ede8b6e944e2f4dcbac420..0e984f3521b578779dd9d0142bce7db433b78f07 100644 +--- a/src/main/java/net/minecraft/server/level/ServerLevel.java ++++ b/src/main/java/net/minecraft/server/level/ServerLevel.java +@@ -1775,6 +1775,11 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe + }); + optional1.ifPresent((holder) -> { + this.getServer().execute(() -> { ++ // Paper start - Remove stale POIs ++ if (optional.isEmpty() && this.getPoiManager().exists(blockposition1, poiType -> true)) { ++ this.getPoiManager().remove(blockposition1); ++ } ++ // Paper end - Remove stale POIs + this.getPoiManager().add(blockposition1, holder); + DebugPackets.sendPoiAddedPacket(this, blockposition1); + }); diff --git a/patches/server/0465-Add-PlayerLoomPatternSelectEvent.patch b/patches/server/0465-Add-PlayerLoomPatternSelectEvent.patch deleted file mode 100644 index f7032ecdded5..000000000000 --- a/patches/server/0465-Add-PlayerLoomPatternSelectEvent.patch +++ /dev/null @@ -1,45 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Jake Potrebic -Date: Wed, 25 Nov 2020 16:33:27 -0800 -Subject: [PATCH] Add PlayerLoomPatternSelectEvent - - -diff --git a/src/main/java/net/minecraft/world/inventory/LoomMenu.java b/src/main/java/net/minecraft/world/inventory/LoomMenu.java -index 2de558dd205a1078fdcac1bce256d059b9bf5d5f..dc23b646e55bb66e0aa584d82b75b4b3d233276a 100644 ---- a/src/main/java/net/minecraft/world/inventory/LoomMenu.java -+++ b/src/main/java/net/minecraft/world/inventory/LoomMenu.java -@@ -174,8 +174,32 @@ public class LoomMenu extends AbstractContainerMenu { - @Override - public boolean clickMenuButton(net.minecraft.world.entity.player.Player player, int id) { - if (id >= 0 && id < this.selectablePatterns.size()) { -- this.selectedBannerPatternIndex.set(id); -- this.setupResultSlot((Holder) this.selectablePatterns.get(id)); -+ // Paper start - Add PlayerLoomPatternSelectEvent -+ int selectablePatternIndex = id; -+ io.papermc.paper.event.player.PlayerLoomPatternSelectEvent event = new io.papermc.paper.event.player.PlayerLoomPatternSelectEvent((Player) player.getBukkitEntity(), ((CraftInventoryLoom) getBukkitView().getTopInventory()), org.bukkit.craftbukkit.block.banner.CraftPatternType.minecraftHolderToBukkit(this.selectablePatterns.get(selectablePatternIndex))); -+ if (!event.callEvent()) { -+ player.containerMenu.sendAllDataToRemote(); -+ return false; -+ } -+ final Holder eventPattern = org.bukkit.craftbukkit.block.banner.CraftPatternType.bukkitToMinecraftHolder(event.getPatternType()); -+ Holder selectedPattern = null; -+ for (int i = 0; i < this.selectablePatterns.size(); i++) { -+ final Holder holder = this.selectablePatterns.get(i); -+ if (eventPattern.equals(holder)) { -+ selectablePatternIndex = i; -+ selectedPattern = holder; -+ break; -+ } -+ } -+ if (selectedPattern == null) { -+ selectedPattern = eventPattern; -+ selectablePatternIndex = -1; -+ } -+ -+ player.containerMenu.sendAllDataToRemote(); -+ this.selectedBannerPatternIndex.set(selectablePatternIndex); -+ this.setupResultSlot(java.util.Objects.requireNonNull(selectedPattern, "selectedPattern was null, this is unexpected")); -+ // Paper end - Add PlayerLoomPatternSelectEvent - return true; - } else { - return false; diff --git a/patches/server/0465-Fix-villager-boat-exploit.patch b/patches/server/0465-Fix-villager-boat-exploit.patch new file mode 100644 index 000000000000..adb461e1ed98 --- /dev/null +++ b/patches/server/0465-Fix-villager-boat-exploit.patch @@ -0,0 +1,25 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Jason Penilla <11360596+jpenilla@users.noreply.github.com> +Date: Mon, 11 Jan 2021 12:43:51 -0800 +Subject: [PATCH] Fix villager boat exploit + + +diff --git a/src/main/java/net/minecraft/server/players/PlayerList.java b/src/main/java/net/minecraft/server/players/PlayerList.java +index 819fb4ef54dce33ec91604491132771f157c8d83..1e5cc810748af34f591d305055fe00a688213617 100644 +--- a/src/main/java/net/minecraft/server/players/PlayerList.java ++++ b/src/main/java/net/minecraft/server/players/PlayerList.java +@@ -544,6 +544,14 @@ public abstract class PlayerList { + PlayerList.LOGGER.debug("Removing player mount"); + entityplayer.stopRiding(); + entity.getPassengersAndSelf().forEach((entity1) -> { ++ // Paper start - Fix villager boat exploit ++ if (entity1 instanceof net.minecraft.world.entity.npc.AbstractVillager villager) { ++ final net.minecraft.world.entity.player.Player human = villager.getTradingPlayer(); ++ if (human != null) { ++ villager.setTradingPlayer(null); ++ } ++ } ++ // Paper end - Fix villager boat exploit + entity1.setRemoved(Entity.RemovalReason.UNLOADED_WITH_PLAYER, EntityRemoveEvent.Cause.PLAYER_QUIT); // CraftBukkit - add Bukkit remove cause + }); + } diff --git a/patches/server/0466-Add-sendOpLevel-API.patch b/patches/server/0466-Add-sendOpLevel-API.patch new file mode 100644 index 000000000000..c938f70a51a8 --- /dev/null +++ b/patches/server/0466-Add-sendOpLevel-API.patch @@ -0,0 +1,53 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Mariell Hoversholm +Date: Tue, 29 Dec 2020 15:03:03 +0100 +Subject: [PATCH] Add sendOpLevel API + + +diff --git a/src/main/java/net/minecraft/server/players/PlayerList.java b/src/main/java/net/minecraft/server/players/PlayerList.java +index 1e5cc810748af34f591d305055fe00a688213617..240a0e487ede08d1dd5050812ca4f68db4cd2dd1 100644 +--- a/src/main/java/net/minecraft/server/players/PlayerList.java ++++ b/src/main/java/net/minecraft/server/players/PlayerList.java +@@ -1025,6 +1025,11 @@ public abstract class PlayerList { + } + + private void sendPlayerPermissionLevel(ServerPlayer player, int permissionLevel) { ++ // Paper start - Add sendOpLevel API ++ this.sendPlayerPermissionLevel(player, permissionLevel, true); ++ } ++ public void sendPlayerPermissionLevel(ServerPlayer player, int permissionLevel, boolean recalculatePermissions) { ++ // Paper end - Add sendOpLevel API + if (player.connection != null) { + byte b0; + +@@ -1039,8 +1044,10 @@ public abstract class PlayerList { + player.connection.send(new ClientboundEntityEventPacket(player, b0)); + } + ++ if (recalculatePermissions) { // Paper - Add sendOpLevel API + player.getBukkitEntity().recalculatePermissions(); // CraftBukkit + this.server.getCommands().sendCommands(player); ++ } // Paper - Add sendOpLevel API + } + + public boolean isWhiteListed(GameProfile profile) { +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java +index e7f2d02de2ac58ffa484d8eca5f8b5688e7e02d8..711697d039da58c1b93392f70a70e1ad827b1646 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java +@@ -694,6 +694,15 @@ public class CraftPlayer extends CraftHumanEntity implements Player { + } + // Paper end + ++ // Paper start - Add sendOpLevel API ++ @Override ++ public void sendOpLevel(byte level) { ++ Preconditions.checkArgument(level >= 0 && level <= 4, "Level must be within [0, 4]"); ++ ++ this.getHandle().getServer().getPlayerList().sendPlayerPermissionLevel(this.getHandle(), level, false); ++ } ++ // Paper end - Add sendOpLevel API ++ + @Override + public void setCompassTarget(Location loc) { + Preconditions.checkArgument(loc != null, "Location cannot be null"); diff --git a/patches/server/0466-Configurable-door-breaking-difficulty.patch b/patches/server/0466-Configurable-door-breaking-difficulty.patch deleted file mode 100644 index 679bb5f5e745..000000000000 --- a/patches/server/0466-Configurable-door-breaking-difficulty.patch +++ /dev/null @@ -1,37 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Jake Potrebic -Date: Sun, 3 Jan 2021 22:27:43 -0800 -Subject: [PATCH] Configurable door breaking difficulty - -== AT == -public net.minecraft.world.entity.monster.Vindicator DOOR_BREAKING_PREDICATE -public net.minecraft.world.entity.monster.Zombie DOOR_BREAKING_PREDICATE - -Co-authored-by: Doc - -diff --git a/src/main/java/net/minecraft/world/entity/monster/Vindicator.java b/src/main/java/net/minecraft/world/entity/monster/Vindicator.java -index 7c3aff97ba777af131d7efad0b08b844bf6e8946..0615bb305d70f660a6baa7f78078990d6db227d3 100644 ---- a/src/main/java/net/minecraft/world/entity/monster/Vindicator.java -+++ b/src/main/java/net/minecraft/world/entity/monster/Vindicator.java -@@ -179,7 +179,7 @@ public class Vindicator extends AbstractIllager { - - static class VindicatorBreakDoorGoal extends BreakDoorGoal { - public VindicatorBreakDoorGoal(Mob mob) { -- super(mob, 6, Vindicator.DOOR_BREAKING_PREDICATE); -+ super(mob, 6, com.google.common.base.Predicates.in(mob.level().paperConfig().entities.behavior.doorBreakingDifficulty.getOrDefault(mob.getType(), mob.level().paperConfig().entities.behavior.doorBreakingDifficulty.get(EntityType.VINDICATOR)))); // Paper - Configurable door breaking difficulty - this.setFlags(EnumSet.of(Goal.Flag.MOVE)); - } - -diff --git a/src/main/java/net/minecraft/world/entity/monster/Zombie.java b/src/main/java/net/minecraft/world/entity/monster/Zombie.java -index 5908f1646b542d61b5a2b23b284b1532e4b276c1..fd304e7d8305877d56de7a38b8664e5a70bf0c33 100644 ---- a/src/main/java/net/minecraft/world/entity/monster/Zombie.java -+++ b/src/main/java/net/minecraft/world/entity/monster/Zombie.java -@@ -101,7 +101,7 @@ public class Zombie extends Monster { - - public Zombie(EntityType type, Level world) { - super(type, world); -- this.breakDoorGoal = new BreakDoorGoal(this, Zombie.DOOR_BREAKING_PREDICATE); -+ this.breakDoorGoal = new BreakDoorGoal(this, com.google.common.base.Predicates.in(world.paperConfig().entities.behavior.doorBreakingDifficulty.getOrDefault(type, world.paperConfig().entities.behavior.doorBreakingDifficulty.get(EntityType.ZOMBIE)))); // Paper - Configurable door breaking difficulty - } - - public Zombie(Level world) { diff --git a/patches/server/0467-Add-RegistryAccess-for-managing-Registries.patch b/patches/server/0467-Add-RegistryAccess-for-managing-Registries.patch new file mode 100644 index 000000000000..ccdf448b22c2 --- /dev/null +++ b/patches/server/0467-Add-RegistryAccess-for-managing-Registries.patch @@ -0,0 +1,1338 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Jake Potrebic +Date: Mon, 27 Feb 2023 18:28:39 -0800 +Subject: [PATCH] Add RegistryAccess for managing Registries + +RegistryAccess is independant from CraftServer and +doesn't require one to be created allowing the +org.bukkit.Registry class to be loaded earlier. + +== AT == +public net.minecraft.server.RegistryLayer STATIC_ACCESS + +diff --git a/src/main/java/io/papermc/paper/registry/PaperRegistries.java b/src/main/java/io/papermc/paper/registry/PaperRegistries.java +new file mode 100644 +index 0000000000000000000000000000000000000000..633b01431750d4b40159a57bf25fb35c6670ff1b +--- /dev/null ++++ b/src/main/java/io/papermc/paper/registry/PaperRegistries.java +@@ -0,0 +1,146 @@ ++package io.papermc.paper.registry; ++ ++import io.papermc.paper.adventure.PaperAdventure; ++import io.papermc.paper.registry.entry.RegistryEntry; ++import java.util.Collections; ++import java.util.IdentityHashMap; ++import java.util.List; ++import java.util.Map; ++import java.util.Objects; ++import net.minecraft.core.Registry; ++import net.minecraft.core.registries.Registries; ++import net.minecraft.resources.ResourceKey; ++import org.bukkit.GameEvent; ++import org.bukkit.JukeboxSong; ++import org.bukkit.Keyed; ++import org.bukkit.MusicInstrument; ++import org.bukkit.block.BlockType; ++import org.bukkit.block.banner.PatternType; ++import org.bukkit.craftbukkit.CraftGameEvent; ++import org.bukkit.craftbukkit.CraftJukeboxSong; ++import org.bukkit.craftbukkit.CraftMusicInstrument; ++import org.bukkit.craftbukkit.block.CraftBlockType; ++import org.bukkit.craftbukkit.block.banner.CraftPatternType; ++import org.bukkit.craftbukkit.damage.CraftDamageType; ++import org.bukkit.craftbukkit.enchantments.CraftEnchantment; ++import org.bukkit.craftbukkit.entity.CraftCat; ++import org.bukkit.craftbukkit.entity.CraftFrog; ++import org.bukkit.craftbukkit.entity.CraftVillager; ++import org.bukkit.craftbukkit.entity.CraftWolf; ++import org.bukkit.craftbukkit.generator.structure.CraftStructure; ++import org.bukkit.craftbukkit.generator.structure.CraftStructureType; ++import org.bukkit.craftbukkit.inventory.CraftItemType; ++import org.bukkit.craftbukkit.inventory.CraftMenuType; ++import org.bukkit.craftbukkit.inventory.trim.CraftTrimMaterial; ++import org.bukkit.craftbukkit.inventory.trim.CraftTrimPattern; ++import org.bukkit.craftbukkit.legacy.FieldRename; ++import org.bukkit.craftbukkit.map.CraftMapCursor; ++import org.bukkit.craftbukkit.potion.CraftPotionEffectType; ++import org.bukkit.craftbukkit.util.CraftNamespacedKey; ++import org.bukkit.damage.DamageType; ++import org.bukkit.enchantments.Enchantment; ++import org.bukkit.entity.Cat; ++import org.bukkit.entity.Frog; ++import org.bukkit.entity.Villager; ++import org.bukkit.entity.Wolf; ++import org.bukkit.entity.memory.MemoryKey; ++import org.bukkit.generator.structure.Structure; ++import org.bukkit.generator.structure.StructureType; ++import org.bukkit.inventory.ItemType; ++import org.bukkit.inventory.MenuType; ++import org.bukkit.inventory.meta.trim.TrimMaterial; ++import org.bukkit.inventory.meta.trim.TrimPattern; ++import org.bukkit.map.MapCursor; ++import org.bukkit.potion.PotionEffectType; ++import org.checkerframework.checker.nullness.qual.NonNull; ++import org.checkerframework.checker.nullness.qual.Nullable; ++import org.checkerframework.framework.qual.DefaultQualifier; ++ ++import static io.papermc.paper.registry.entry.RegistryEntry.apiOnly; ++import static io.papermc.paper.registry.entry.RegistryEntry.entry; ++ ++@DefaultQualifier(NonNull.class) ++public final class PaperRegistries { ++ ++ static final List> REGISTRY_ENTRIES; ++ private static final Map, RegistryEntry> BY_REGISTRY_KEY; ++ private static final Map, RegistryEntry> BY_RESOURCE_KEY; ++ static { ++ REGISTRY_ENTRIES = List.of( ++ // built-ins ++ entry(Registries.GAME_EVENT, RegistryKey.GAME_EVENT, GameEvent.class, CraftGameEvent::new), ++ entry(Registries.STRUCTURE_TYPE, RegistryKey.STRUCTURE_TYPE, StructureType.class, CraftStructureType::new), ++ entry(Registries.INSTRUMENT, RegistryKey.INSTRUMENT, MusicInstrument.class, CraftMusicInstrument::new), ++ entry(Registries.MOB_EFFECT, RegistryKey.MOB_EFFECT, PotionEffectType.class, CraftPotionEffectType::new), ++ entry(Registries.BLOCK, RegistryKey.BLOCK, BlockType.class, CraftBlockType::new), ++ entry(Registries.ITEM, RegistryKey.ITEM, ItemType.class, CraftItemType::new), ++ entry(Registries.CAT_VARIANT, RegistryKey.CAT_VARIANT, Cat.Type.class, CraftCat.CraftType::new), ++ entry(Registries.FROG_VARIANT, RegistryKey.FROG_VARIANT, Frog.Variant.class, CraftFrog.CraftVariant::new), ++ entry(Registries.VILLAGER_PROFESSION, RegistryKey.VILLAGER_PROFESSION, Villager.Profession.class, CraftVillager.CraftProfession::new), ++ entry(Registries.VILLAGER_TYPE, RegistryKey.VILLAGER_TYPE, Villager.Type.class, CraftVillager.CraftType::new), ++ entry(Registries.MAP_DECORATION_TYPE, RegistryKey.MAP_DECORATION_TYPE, MapCursor.Type.class, CraftMapCursor.CraftType::new), ++ entry(Registries.MENU, RegistryKey.MENU, MenuType.class, CraftMenuType::new), ++ ++ // data-drivens ++ entry(Registries.STRUCTURE, RegistryKey.STRUCTURE, Structure.class, CraftStructure::new).delayed(), ++ entry(Registries.TRIM_MATERIAL, RegistryKey.TRIM_MATERIAL, TrimMaterial.class, CraftTrimMaterial::new).delayed(), ++ entry(Registries.TRIM_PATTERN, RegistryKey.TRIM_PATTERN, TrimPattern.class, CraftTrimPattern::new).delayed(), ++ entry(Registries.DAMAGE_TYPE, RegistryKey.DAMAGE_TYPE, DamageType.class, CraftDamageType::new).delayed(), ++ entry(Registries.WOLF_VARIANT, RegistryKey.WOLF_VARIANT, Wolf.Variant.class, CraftWolf.CraftVariant::new).delayed(), ++ entry(Registries.ENCHANTMENT, RegistryKey.ENCHANTMENT, Enchantment.class, CraftEnchantment::new).withSerializationUpdater(FieldRename.ENCHANTMENT_RENAME).delayed(), ++ entry(Registries.JUKEBOX_SONG, RegistryKey.JUKEBOX_SONG, JukeboxSong.class, CraftJukeboxSong::new).delayed(), ++ entry(Registries.BANNER_PATTERN, RegistryKey.BANNER_PATTERN, PatternType.class, CraftPatternType::new).delayed(), ++ ++ // api-only ++ apiOnly(Registries.BIOME, RegistryKey.BIOME, () -> org.bukkit.Registry.BIOME), ++ apiOnly(Registries.PAINTING_VARIANT, RegistryKey.PAINTING_VARIANT, () -> org.bukkit.Registry.ART), ++ apiOnly(Registries.ATTRIBUTE, RegistryKey.ATTRIBUTE, () -> org.bukkit.Registry.ATTRIBUTE), ++ apiOnly(Registries.ENTITY_TYPE, RegistryKey.ENTITY_TYPE, () -> org.bukkit.Registry.ENTITY_TYPE), ++ apiOnly(Registries.PARTICLE_TYPE, RegistryKey.PARTICLE_TYPE, () -> org.bukkit.Registry.PARTICLE_TYPE), ++ apiOnly(Registries.POTION, RegistryKey.POTION, () -> org.bukkit.Registry.POTION), ++ apiOnly(Registries.SOUND_EVENT, RegistryKey.SOUND_EVENT, () -> org.bukkit.Registry.SOUNDS), ++ apiOnly(Registries.MEMORY_MODULE_TYPE, RegistryKey.MEMORY_MODULE_TYPE, () -> (org.bukkit.Registry>) (org.bukkit.Registry) org.bukkit.Registry.MEMORY_MODULE_TYPE), ++ apiOnly(Registries.FLUID, RegistryKey.FLUID, () -> org.bukkit.Registry.FLUID) ++ ); ++ final Map, RegistryEntry> byRegistryKey = new IdentityHashMap<>(REGISTRY_ENTRIES.size()); ++ final Map, RegistryEntry> byResourceKey = new IdentityHashMap<>(REGISTRY_ENTRIES.size()); ++ for (final RegistryEntry entry : REGISTRY_ENTRIES) { ++ byRegistryKey.put(entry.apiKey(), entry); ++ byResourceKey.put(entry.mcKey(), entry); ++ } ++ BY_REGISTRY_KEY = Collections.unmodifiableMap(byRegistryKey); ++ BY_RESOURCE_KEY = Collections.unmodifiableMap(byResourceKey); ++ } ++ ++ @SuppressWarnings("unchecked") ++ public static @Nullable RegistryEntry getEntry(final ResourceKey> resourceKey) { ++ return (RegistryEntry) BY_RESOURCE_KEY.get(resourceKey); ++ } ++ ++ @SuppressWarnings("unchecked") ++ public static @Nullable RegistryEntry getEntry(final RegistryKey registryKey) { ++ return (RegistryEntry) BY_REGISTRY_KEY.get(registryKey); ++ } ++ ++ @SuppressWarnings("unchecked") ++ public static RegistryKey registryFromNms(final ResourceKey> registryResourceKey) { ++ return (RegistryKey) Objects.requireNonNull(BY_RESOURCE_KEY.get(registryResourceKey), registryResourceKey + " doesn't have an api RegistryKey").apiKey(); ++ } ++ ++ @SuppressWarnings("unchecked") ++ public static ResourceKey> registryToNms(final RegistryKey registryKey) { ++ return (ResourceKey>) Objects.requireNonNull(BY_REGISTRY_KEY.get(registryKey), registryKey + " doesn't have an mc registry ResourceKey").mcKey(); ++ } ++ ++ public static TypedKey fromNms(final ResourceKey resourceKey) { ++ return TypedKey.create(registryFromNms(resourceKey.registryKey()), CraftNamespacedKey.fromMinecraft(resourceKey.location())); ++ } ++ ++ @SuppressWarnings({"unchecked", "RedundantCast"}) ++ public static ResourceKey toNms(final TypedKey typedKey) { ++ return ResourceKey.create((ResourceKey>) PaperRegistries.registryToNms(typedKey.registryKey()), PaperAdventure.asVanilla(typedKey.key())); ++ } ++ ++ private PaperRegistries() { ++ } ++} +diff --git a/src/main/java/io/papermc/paper/registry/PaperRegistryAccess.java b/src/main/java/io/papermc/paper/registry/PaperRegistryAccess.java +new file mode 100644 +index 0000000000000000000000000000000000000000..35b6a7c5bac9640332a833bd3627f2bcb1bbf2f3 +--- /dev/null ++++ b/src/main/java/io/papermc/paper/registry/PaperRegistryAccess.java +@@ -0,0 +1,127 @@ ++package io.papermc.paper.registry; ++ ++import io.papermc.paper.registry.entry.ApiRegistryEntry; ++import io.papermc.paper.registry.entry.RegistryEntry; ++import io.papermc.paper.registry.legacy.DelayedRegistry; ++import io.papermc.paper.registry.legacy.DelayedRegistryEntry; ++import io.papermc.paper.registry.legacy.LegacyRegistryIdentifiers; ++import java.util.Map; ++import java.util.NoSuchElementException; ++import java.util.Set; ++import java.util.concurrent.ConcurrentHashMap; ++import java.util.stream.Collectors; ++import net.minecraft.resources.ResourceKey; ++import org.bukkit.Keyed; ++import org.bukkit.Registry; ++import org.checkerframework.checker.nullness.qual.NonNull; ++import org.checkerframework.checker.nullness.qual.Nullable; ++import org.checkerframework.framework.qual.DefaultQualifier; ++import org.jetbrains.annotations.VisibleForTesting; ++ ++import static java.util.Objects.requireNonNull; ++ ++@DefaultQualifier(NonNull.class) ++public class PaperRegistryAccess implements RegistryAccess { ++ ++ // We store the API registries in a memoized supplier, so they can be created on-demand. ++ // These suppliers are added to this map right after the instance of nms.Registry is created before it is loaded. ++ // We want to do registration there, so we have access to the nms.Registry instance in order to wrap it in a CraftRegistry instance. ++ // The memoized Supplier is needed because we *can't* instantiate any CraftRegistry class until **all** the BuiltInRegistries have been added ++ // to this map because that would class-load org.bukkit.Registry which would query this map. ++ private final Map, RegistryHolder> registries = new ConcurrentHashMap<>(); // is "identity" because RegistryKey overrides equals and hashCode ++ ++ public static PaperRegistryAccess instance() { ++ return (PaperRegistryAccess) RegistryAccessHolder.INSTANCE.orElseThrow(() -> new IllegalStateException("No RegistryAccess implementation found")); ++ } ++ ++ @VisibleForTesting ++ public Set> getLoadedServerBackedRegistries() { ++ return this.registries.keySet().stream().filter(registryHolder -> !(PaperRegistries.getEntry(registryHolder) instanceof ApiRegistryEntry)).collect(Collectors.toUnmodifiableSet()); ++ } ++ ++ @SuppressWarnings("unchecked") ++ @Deprecated(forRemoval = true) ++ @Override ++ public @Nullable Registry getRegistry(final Class type) { ++ final @Nullable RegistryKey registryKey = byType(type); ++ // If our mapping from Class -> RegistryKey did not contain the passed type it was either a completely invalid type or a registry ++ // that merely exists as a SimpleRegistry in the org.bukkit.Registry type. We cannot return a registry for these, return null ++ // as per method contract in Bukkit#getRegistry. ++ if (registryKey == null) return null; ++ ++ final @Nullable RegistryEntry entry = PaperRegistries.getEntry(registryKey); ++ final @Nullable RegistryHolder registry = (RegistryHolder) this.registries.get(registryKey); ++ if (registry != null) { ++ // if the registry exists, return right away. Since this is the "legacy" method, we return DelayedRegistry ++ // for the non-builtin Registry instances stored as fields in Registry. ++ return registry.get(); ++ } else if (entry instanceof DelayedRegistryEntry) { ++ // if the registry doesn't exist and the entry is marked as "delayed", we create a registry holder that is empty ++ // which will later be filled with the actual registry. This is so the fields on org.bukkit.Registry can be populated with ++ // registries that don't exist at the time org.bukkit.Registry is statically initialized. ++ final RegistryHolder delayedHolder = new RegistryHolder.Delayed<>(); ++ this.registries.put(registryKey, delayedHolder); ++ return delayedHolder.get(); ++ } else { ++ // if the registry doesn't exist yet or doesn't have a delayed entry, just return null ++ return null; ++ } ++ } ++ ++ @SuppressWarnings("unchecked") ++ @Override ++ public Registry getRegistry(final RegistryKey key) { ++ if (PaperRegistries.getEntry(key) == null) { ++ throw new NoSuchElementException(key + " is not a valid registry key"); ++ } ++ final @Nullable RegistryHolder registryHolder = (RegistryHolder) this.registries.get(key); ++ if (registryHolder == null) { ++ throw new IllegalArgumentException(key + " points to a registry that is not available yet"); ++ } ++ // since this is the getRegistry method that uses the modern RegistryKey, we unwrap any DelayedRegistry instances ++ // that might be returned here. I don't think reference equality is required when doing getRegistry(RegistryKey.WOLF_VARIANT) == Registry.WOLF_VARIANT ++ return possiblyUnwrap(registryHolder.get()); ++ } ++ ++ private static Registry possiblyUnwrap(final Registry registry) { ++ if (registry instanceof final DelayedRegistry delayedRegistry) { // if not coming from legacy, unwrap the delayed registry ++ return delayedRegistry.delegate(); ++ } ++ return registry; ++ } ++ ++ public void registerReloadableRegistry(final ResourceKey> resourceKey, final net.minecraft.core.Registry registry) { ++ this.registerRegistry(resourceKey, registry, true); ++ } ++ ++ public void registerRegistry(final ResourceKey> resourceKey, final net.minecraft.core.Registry registry) { ++ this.registerRegistry(resourceKey, registry, false); ++ } ++ ++ @SuppressWarnings("unchecked") // this method should be called right after any new MappedRegistry instances are created to later be used by the server. ++ private > void registerRegistry(final ResourceKey> resourceKey, final net.minecraft.core.Registry registry, final boolean replace) { ++ final @Nullable RegistryEntry entry = PaperRegistries.getEntry(resourceKey); ++ if (entry == null) { // skip registries that don't have API entries ++ return; ++ } ++ final @Nullable RegistryHolder registryHolder = (RegistryHolder) this.registries.get(entry.apiKey()); ++ if (registryHolder == null || replace) { ++ // if the holder doesn't exist yet, or is marked as "replaceable", put it in the map. ++ this.registries.put(entry.apiKey(), entry.createRegistryHolder(registry)); ++ } else { ++ if (registryHolder instanceof RegistryHolder.Delayed && entry instanceof final DelayedRegistryEntry delayedEntry) { ++ // if the registry holder is delayed, and the entry is marked as "delayed", then load the holder with the CraftRegistry instance that wraps the actual nms Registry. ++ ((RegistryHolder.Delayed) registryHolder).loadFrom(delayedEntry, registry); ++ } else { ++ throw new IllegalArgumentException(resourceKey + " has already been created"); ++ } ++ } ++ } ++ ++ @SuppressWarnings("unchecked") ++ @Deprecated ++ @VisibleForTesting ++ public static @Nullable RegistryKey byType(final Class type) { ++ return (RegistryKey) LegacyRegistryIdentifiers.CLASS_TO_KEY_MAP.get(type); ++ } ++} +diff --git a/src/main/java/io/papermc/paper/registry/RegistryHolder.java b/src/main/java/io/papermc/paper/registry/RegistryHolder.java +new file mode 100644 +index 0000000000000000000000000000000000000000..a31bdd9f02fe75a87fceb2ebe8c36b3232a561cc +--- /dev/null ++++ b/src/main/java/io/papermc/paper/registry/RegistryHolder.java +@@ -0,0 +1,47 @@ ++package io.papermc.paper.registry; ++ ++import com.google.common.base.Suppliers; ++import io.papermc.paper.registry.legacy.DelayedRegistry; ++import io.papermc.paper.registry.legacy.DelayedRegistryEntry; ++import java.util.function.Supplier; ++import org.bukkit.Keyed; ++import org.bukkit.Registry; ++import org.checkerframework.checker.nullness.qual.NonNull; ++import org.checkerframework.framework.qual.DefaultQualifier; ++ ++@DefaultQualifier(NonNull.class) ++public interface RegistryHolder { ++ ++ Registry get(); ++ ++ final class Memoized> implements RegistryHolder { ++ ++ private final Supplier memoizedSupplier; ++ ++ public Memoized(final Supplier supplier) { ++ this.memoizedSupplier = Suppliers.memoize(supplier::get); ++ } ++ ++ public Registry get() { ++ return this.memoizedSupplier.get(); ++ } ++ } ++ ++ final class Delayed> implements RegistryHolder { ++ ++ private final DelayedRegistry delayedRegistry = new DelayedRegistry<>(); ++ ++ @Override ++ public DelayedRegistry get() { ++ return this.delayedRegistry; ++ } ++ ++ void loadFrom(final DelayedRegistryEntry delayedEntry, final net.minecraft.core.Registry registry) { ++ final RegistryHolder delegateHolder = delayedEntry.delegate().createRegistryHolder(registry); ++ if (!(delegateHolder instanceof RegistryHolder.Memoized)) { ++ throw new IllegalArgumentException(delegateHolder + " must be a memoized holder"); ++ } ++ this.delayedRegistry.load(((Memoized) delegateHolder).memoizedSupplier); ++ } ++ } ++} +diff --git a/src/main/java/io/papermc/paper/registry/entry/ApiRegistryEntry.java b/src/main/java/io/papermc/paper/registry/entry/ApiRegistryEntry.java +new file mode 100644 +index 0000000000000000000000000000000000000000..2295b0d145cbaabef5d29482c817575dcbe2ba54 +--- /dev/null ++++ b/src/main/java/io/papermc/paper/registry/entry/ApiRegistryEntry.java +@@ -0,0 +1,27 @@ ++package io.papermc.paper.registry.entry; ++ ++import io.papermc.paper.registry.RegistryHolder; ++import io.papermc.paper.registry.RegistryKey; ++import java.util.function.Supplier; ++import net.minecraft.core.Registry; ++import net.minecraft.resources.ResourceKey; ++import org.bukkit.Keyed; ++ ++public class ApiRegistryEntry extends BaseRegistryEntry { ++ ++ private final Supplier> registrySupplier; ++ ++ protected ApiRegistryEntry( ++ final ResourceKey> mcKey, ++ final RegistryKey apiKey, ++ final Supplier> registrySupplier ++ ) { ++ super(mcKey, apiKey); ++ this.registrySupplier = registrySupplier; ++ } ++ ++ @Override ++ public RegistryHolder createRegistryHolder(final Registry nmsRegistry) { ++ return new RegistryHolder.Memoized<>(this.registrySupplier); ++ } ++} +diff --git a/src/main/java/io/papermc/paper/registry/entry/BaseRegistryEntry.java b/src/main/java/io/papermc/paper/registry/entry/BaseRegistryEntry.java +new file mode 100644 +index 0000000000000000000000000000000000000000..ceb217dbbb84e8bd51365dd47bf91971e364d298 +--- /dev/null ++++ b/src/main/java/io/papermc/paper/registry/entry/BaseRegistryEntry.java +@@ -0,0 +1,27 @@ ++package io.papermc.paper.registry.entry; ++ ++import io.papermc.paper.registry.RegistryKey; ++import net.minecraft.core.Registry; ++import net.minecraft.resources.ResourceKey; ++import org.bukkit.Keyed; ++ ++public abstract class BaseRegistryEntry implements RegistryEntry { // TODO remove Keyed ++ ++ private final ResourceKey> minecraftRegistryKey; ++ private final RegistryKey apiRegistryKey; ++ ++ protected BaseRegistryEntry(final ResourceKey> minecraftRegistryKey, final RegistryKey apiRegistryKey) { ++ this.minecraftRegistryKey = minecraftRegistryKey; ++ this.apiRegistryKey = apiRegistryKey; ++ } ++ ++ @Override ++ public final ResourceKey> mcKey() { ++ return this.minecraftRegistryKey; ++ } ++ ++ @Override ++ public final RegistryKey apiKey() { ++ return this.apiRegistryKey; ++ } ++} +diff --git a/src/main/java/io/papermc/paper/registry/entry/CraftRegistryEntry.java b/src/main/java/io/papermc/paper/registry/entry/CraftRegistryEntry.java +new file mode 100644 +index 0000000000000000000000000000000000000000..9bb4aa926978f117901c9f99c45a6862a1d5ce30 +--- /dev/null ++++ b/src/main/java/io/papermc/paper/registry/entry/CraftRegistryEntry.java +@@ -0,0 +1,51 @@ ++package io.papermc.paper.registry.entry; ++ ++import com.google.common.base.Preconditions; ++import io.papermc.paper.registry.RegistryHolder; ++import io.papermc.paper.registry.RegistryKey; ++import java.util.function.BiFunction; ++import net.minecraft.core.Registry; ++import net.minecraft.resources.ResourceKey; ++import org.bukkit.Keyed; ++import org.bukkit.NamespacedKey; ++import org.bukkit.craftbukkit.CraftRegistry; ++import org.bukkit.craftbukkit.util.ApiVersion; ++import org.checkerframework.checker.nullness.qual.NonNull; ++import org.checkerframework.framework.qual.DefaultQualifier; ++ ++@DefaultQualifier(NonNull.class) ++public class CraftRegistryEntry extends BaseRegistryEntry { // TODO remove Keyed ++ ++ private static final BiFunction EMPTY = (namespacedKey, apiVersion) -> namespacedKey; ++ ++ protected final Class classToPreload; ++ protected final BiFunction minecraftToBukkit; ++ protected BiFunction updater = EMPTY; ++ ++ protected CraftRegistryEntry( ++ final ResourceKey> mcKey, ++ final RegistryKey apiKey, ++ final Class classToPreload, ++ final BiFunction minecraftToBukkit ++ ) { ++ super(mcKey, apiKey); ++ Preconditions.checkArgument(!classToPreload.getPackageName().startsWith("net.minecraft"), classToPreload + " should not be in the net.minecraft package as the class-to-preload"); ++ this.classToPreload = classToPreload; ++ this.minecraftToBukkit = minecraftToBukkit; ++ } ++ ++ @Override ++ public RegistryEntry withSerializationUpdater(final BiFunction updater) { ++ this.updater = updater; ++ return this; ++ } ++ ++ @Override ++ public RegistryHolder createRegistryHolder(final Registry nmsRegistry) { ++ return new RegistryHolder.Memoized<>(() -> this.createApiRegistry(nmsRegistry)); ++ } ++ ++ private CraftRegistry createApiRegistry(final Registry nmsRegistry) { ++ return new CraftRegistry<>(this.classToPreload, nmsRegistry, this.minecraftToBukkit, this.updater); ++ } ++} +diff --git a/src/main/java/io/papermc/paper/registry/entry/RegistryEntry.java b/src/main/java/io/papermc/paper/registry/entry/RegistryEntry.java +new file mode 100644 +index 0000000000000000000000000000000000000000..15991bf13894d850f360a520d1815711d25973ec +--- /dev/null ++++ b/src/main/java/io/papermc/paper/registry/entry/RegistryEntry.java +@@ -0,0 +1,51 @@ ++package io.papermc.paper.registry.entry; ++ ++import io.papermc.paper.registry.RegistryHolder; ++import io.papermc.paper.registry.RegistryKey; ++import io.papermc.paper.registry.legacy.DelayedRegistryEntry; ++import java.util.function.BiFunction; ++import java.util.function.Supplier; ++import net.minecraft.core.Registry; ++import net.minecraft.resources.ResourceKey; ++import org.bukkit.Keyed; ++import org.bukkit.NamespacedKey; ++import org.bukkit.craftbukkit.util.ApiVersion; ++import org.checkerframework.checker.nullness.qual.NonNull; ++import org.checkerframework.framework.qual.DefaultQualifier; ++ ++@DefaultQualifier(NonNull.class) ++public interface RegistryEntry extends RegistryEntryInfo { // TODO remove Keyed ++ ++ RegistryHolder createRegistryHolder(Registry nmsRegistry); ++ ++ default RegistryEntry withSerializationUpdater(final BiFunction updater) { ++ return this; ++ } ++ ++ /** ++ * This should only be used if the registry instance needs to exist early due to the need ++ * to populate a field in {@link org.bukkit.Registry}. Data-driven registries shouldn't exist ++ * as fields, but instead be obtained via {@link io.papermc.paper.registry.RegistryAccess#getRegistry(RegistryKey)} ++ */ ++ @Deprecated ++ default RegistryEntry delayed() { ++ return new DelayedRegistryEntry<>(this); ++ } ++ ++ static RegistryEntry entry( ++ final ResourceKey> mcKey, ++ final RegistryKey apiKey, ++ final Class classToPreload, ++ final BiFunction minecraftToBukkit ++ ) { ++ return new CraftRegistryEntry<>(mcKey, apiKey, classToPreload, minecraftToBukkit); ++ } ++ ++ static RegistryEntry apiOnly( ++ final ResourceKey> mcKey, ++ final RegistryKey apiKey, ++ final Supplier> apiRegistrySupplier ++ ) { ++ return new ApiRegistryEntry<>(mcKey, apiKey, apiRegistrySupplier); ++ } ++} +diff --git a/src/main/java/io/papermc/paper/registry/entry/RegistryEntryInfo.java b/src/main/java/io/papermc/paper/registry/entry/RegistryEntryInfo.java +new file mode 100644 +index 0000000000000000000000000000000000000000..0ae855e80fc9fddfc1feb33c7a9748204828b7cc +--- /dev/null ++++ b/src/main/java/io/papermc/paper/registry/entry/RegistryEntryInfo.java +@@ -0,0 +1,12 @@ ++package io.papermc.paper.registry.entry; ++ ++import io.papermc.paper.registry.RegistryKey; ++import net.minecraft.core.Registry; ++import net.minecraft.resources.ResourceKey; ++ ++public interface RegistryEntryInfo { ++ ++ ResourceKey> mcKey(); ++ ++ RegistryKey apiKey(); ++} +diff --git a/src/main/java/io/papermc/paper/registry/entry/package-info.java b/src/main/java/io/papermc/paper/registry/entry/package-info.java +new file mode 100644 +index 0000000000000000000000000000000000000000..e4c94d6860e0f5b643cde1ded20b5503c02a4866 +--- /dev/null ++++ b/src/main/java/io/papermc/paper/registry/entry/package-info.java +@@ -0,0 +1,5 @@ ++@DefaultQualifier(NonNull.class) ++package io.papermc.paper.registry.entry; ++ ++import org.checkerframework.checker.nullness.qual.NonNull; ++import org.checkerframework.framework.qual.DefaultQualifier; +diff --git a/src/main/java/io/papermc/paper/registry/legacy/DelayedRegistry.java b/src/main/java/io/papermc/paper/registry/legacy/DelayedRegistry.java +new file mode 100644 +index 0000000000000000000000000000000000000000..9400fed345344a0a8e4fb301cca6a1867adf625b +--- /dev/null ++++ b/src/main/java/io/papermc/paper/registry/legacy/DelayedRegistry.java +@@ -0,0 +1,61 @@ ++package io.papermc.paper.registry.legacy; ++ ++import java.util.Iterator; ++import java.util.function.Supplier; ++import java.util.stream.Stream; ++import net.kyori.adventure.key.Key; ++import org.bukkit.Keyed; ++import org.bukkit.NamespacedKey; ++import org.bukkit.Registry; ++import org.bukkit.craftbukkit.CraftRegistry; ++import org.checkerframework.checker.nullness.qual.MonotonicNonNull; ++import org.checkerframework.checker.nullness.qual.Nullable; ++import org.jetbrains.annotations.NotNull; ++ ++/** ++ * This is to support the now-deprecated fields in {@link Registry} for ++ * data-driven registries. ++ */ ++public final class DelayedRegistry> implements Registry { ++ ++ private @MonotonicNonNull Supplier delegate; ++ ++ public void load(final Supplier registry) { ++ if (this.delegate != null) { ++ throw new IllegalStateException("Registry already loaded!"); ++ } ++ this.delegate = registry; ++ } ++ ++ public Registry delegate() { ++ if (this.delegate == null) { ++ throw new IllegalStateException("You are trying to access this registry too early!"); ++ } ++ return this.delegate.get(); ++ } ++ ++ @Override ++ public @Nullable T get(final NamespacedKey key) { ++ return this.delegate().get(key); ++ } ++ ++ @Override ++ public @NotNull T getOrThrow(@NotNull final NamespacedKey key) { ++ return this.delegate().getOrThrow(key); ++ } ++ ++ @Override ++ public Iterator iterator() { ++ return this.delegate().iterator(); ++ } ++ ++ @Override ++ public Stream stream() { ++ return this.delegate().stream(); ++ } ++ ++ @Override ++ public NamespacedKey getKey(final T value) { ++ return this.delegate().getKey(value); ++ } ++} +diff --git a/src/main/java/io/papermc/paper/registry/legacy/DelayedRegistryEntry.java b/src/main/java/io/papermc/paper/registry/legacy/DelayedRegistryEntry.java +new file mode 100644 +index 0000000000000000000000000000000000000000..110b8d559f49f9e4f181b47663962a139a273a72 +--- /dev/null ++++ b/src/main/java/io/papermc/paper/registry/legacy/DelayedRegistryEntry.java +@@ -0,0 +1,26 @@ ++package io.papermc.paper.registry.legacy; ++ ++import io.papermc.paper.registry.RegistryHolder; ++import io.papermc.paper.registry.RegistryKey; ++import io.papermc.paper.registry.entry.RegistryEntry; ++import net.minecraft.core.Registry; ++import net.minecraft.resources.ResourceKey; ++import org.bukkit.Keyed; ++ ++public record DelayedRegistryEntry(RegistryEntry delegate) implements RegistryEntry { ++ ++ @Override ++ public ResourceKey> mcKey() { ++ return this.delegate.mcKey(); ++ } ++ ++ @Override ++ public RegistryKey apiKey() { ++ return this.delegate.apiKey(); ++ } ++ ++ @Override ++ public RegistryHolder createRegistryHolder(final Registry nmsRegistry) { ++ return this.delegate.createRegistryHolder(nmsRegistry); ++ } ++} +diff --git a/src/main/java/io/papermc/paper/registry/legacy/LegacyRegistryIdentifiers.java b/src/main/java/io/papermc/paper/registry/legacy/LegacyRegistryIdentifiers.java +new file mode 100644 +index 0000000000000000000000000000000000000000..83870816cd4c54f94a3c603ffe41c11e457f3dec +--- /dev/null ++++ b/src/main/java/io/papermc/paper/registry/legacy/LegacyRegistryIdentifiers.java +@@ -0,0 +1,33 @@ ++package io.papermc.paper.registry.legacy; ++ ++import com.google.common.collect.ImmutableMap; ++import io.leangen.geantyref.GenericTypeReflector; ++import io.papermc.paper.registry.RegistryKey; ++import java.lang.reflect.Field; ++import java.lang.reflect.ParameterizedType; ++import java.util.Map; ++ ++@Deprecated ++public final class LegacyRegistryIdentifiers { ++ ++ public static final Map, RegistryKey> CLASS_TO_KEY_MAP; ++ ++ static { ++ final ImmutableMap.Builder, RegistryKey> builder = ImmutableMap.builder(); ++ try { ++ for (final Field field : RegistryKey.class.getFields()) { ++ if (field.getType() == RegistryKey.class) { ++ // get the legacy type from the RegistryKey generic parameter on the field ++ final Class legacyType = GenericTypeReflector.erase(((ParameterizedType) field.getGenericType()).getActualTypeArguments()[0]); ++ builder.put(legacyType, (RegistryKey) field.get(null)); ++ } ++ } ++ } catch (final ReflectiveOperationException ex) { ++ throw new RuntimeException(ex); ++ } ++ CLASS_TO_KEY_MAP = builder.build(); ++ } ++ ++ private LegacyRegistryIdentifiers() { ++ } ++} +diff --git a/src/main/java/io/papermc/paper/registry/legacy/package-info.java b/src/main/java/io/papermc/paper/registry/legacy/package-info.java +new file mode 100644 +index 0000000000000000000000000000000000000000..4396982af55872fafbfeaf8161ad6f392726c773 +--- /dev/null ++++ b/src/main/java/io/papermc/paper/registry/legacy/package-info.java +@@ -0,0 +1,5 @@ ++@DefaultQualifier(NonNull.class) ++package io.papermc.paper.registry.legacy; ++ ++import org.checkerframework.checker.nullness.qual.NonNull; ++import org.checkerframework.framework.qual.DefaultQualifier; +diff --git a/src/main/java/net/minecraft/core/registries/BuiltInRegistries.java b/src/main/java/net/minecraft/core/registries/BuiltInRegistries.java +index 3f72e30b57fb2a4231e22a2234729408c1240af4..4638ba98dbbdb0f880337347be85a6e0fbed2191 100644 +--- a/src/main/java/net/minecraft/core/registries/BuiltInRegistries.java ++++ b/src/main/java/net/minecraft/core/registries/BuiltInRegistries.java +@@ -323,6 +323,7 @@ public class BuiltInRegistries { + ResourceKey> key, R registry, BuiltInRegistries.RegistryBootstrap initializer + ) { + Bootstrap.checkBootstrapCalled(() -> "registry " + key.location()); ++ io.papermc.paper.registry.PaperRegistryAccess.instance().registerRegistry(registry.key(), registry); // Paper - initialize API registry + ResourceLocation resourceLocation = key.location(); + LOADERS.put(resourceLocation, () -> initializer.run(registry)); + WRITABLE_REGISTRY.register((ResourceKey)key, registry, RegistrationInfo.BUILT_IN); // Paper - decompile fix +diff --git a/src/main/java/net/minecraft/resources/RegistryDataLoader.java b/src/main/java/net/minecraft/resources/RegistryDataLoader.java +index a75f6fefdd72188fa8d16df2b5cbb34c4129f52d..61774165a25209ee6d26cf8d80149b220c3874e6 100644 +--- a/src/main/java/net/minecraft/resources/RegistryDataLoader.java ++++ b/src/main/java/net/minecraft/resources/RegistryDataLoader.java +@@ -351,6 +351,7 @@ public class RegistryDataLoader { + + RegistryDataLoader.Loader create(Lifecycle lifecycle, Map, Exception> errors) { + WritableRegistry writableRegistry = new MappedRegistry<>(this.key, lifecycle); ++ io.papermc.paper.registry.PaperRegistryAccess.instance().registerRegistry(this.key, writableRegistry); // Paper - initialize API registry + return new RegistryDataLoader.Loader<>(this, writableRegistry, errors); + } + +diff --git a/src/main/java/net/minecraft/server/ReloadableServerRegistries.java b/src/main/java/net/minecraft/server/ReloadableServerRegistries.java +index 8e8d6214adbd21a221147f0fc0d91cd9c06a080c..6fddef967b6314ca0158f5bd4b8898670ea5e9ec 100644 +--- a/src/main/java/net/minecraft/server/ReloadableServerRegistries.java ++++ b/src/main/java/net/minecraft/server/ReloadableServerRegistries.java +@@ -64,6 +64,7 @@ public class ReloadableServerRegistries { + ) { + return CompletableFuture.supplyAsync(() -> { + WritableRegistry writableRegistry = new MappedRegistry<>(type.registryKey(), Lifecycle.experimental()); ++ io.papermc.paper.registry.PaperRegistryAccess.instance().registerReloadableRegistry(type.registryKey(), writableRegistry); // Paper - register reloadable registry + Map map = new HashMap<>(); + String string = Registries.elementsDirPath(type.registryKey()); + SimpleJsonResourceReloadListener.scanDirectory(resourceManager, string, ops, type.codec(), map); +diff --git a/src/main/java/org/bukkit/craftbukkit/CraftRegistry.java b/src/main/java/org/bukkit/craftbukkit/CraftRegistry.java +index b4ed857f2437759b71b75d7ab36c986a2fd71dbc..09929f580164abcd1c04061d04c6aa992767e256 100644 +--- a/src/main/java/org/bukkit/craftbukkit/CraftRegistry.java ++++ b/src/main/java/org/bukkit/craftbukkit/CraftRegistry.java +@@ -122,81 +122,12 @@ public class CraftRegistry implements Registry { + + ", this can happen if a plugin creates its own registry entry with out properly registering it."); + } + +- /** +- * Note: Newly added registries should also be added to RegistriesArgumentProvider in the test package +- * +- * @param bukkitClass the bukkit class of the registry +- * @param registryHolder the minecraft registry holder +- * @return the bukkit registry of the provided class +- */ +- public static Registry createRegistry(Class bukkitClass, RegistryAccess registryHolder) { +- if (bukkitClass == Enchantment.class) { +- return new CraftRegistry<>(Enchantment.class, registryHolder.lookupOrThrow(Registries.ENCHANTMENT), CraftEnchantment::new, FieldRename.ENCHANTMENT_RENAME); +- } +- if (bukkitClass == GameEvent.class) { +- return new CraftRegistry<>(GameEvent.class, registryHolder.lookupOrThrow(Registries.GAME_EVENT), CraftGameEvent::new, FieldRename.NONE); +- } +- if (bukkitClass == MusicInstrument.class) { +- return new CraftRegistry<>(MusicInstrument.class, registryHolder.lookupOrThrow(Registries.INSTRUMENT), CraftMusicInstrument::new, FieldRename.NONE); +- } +- if (bukkitClass == MenuType.class) { +- return new CraftRegistry<>(MenuType.class, registryHolder.lookupOrThrow(Registries.MENU), CraftMenuType::new, FieldRename.NONE); +- } +- if (bukkitClass == PotionEffectType.class) { +- return new CraftRegistry<>(PotionEffectType.class, registryHolder.lookupOrThrow(Registries.MOB_EFFECT), CraftPotionEffectType::new, FieldRename.NONE); +- } +- if (bukkitClass == Structure.class) { +- return new CraftRegistry<>(Structure.class, registryHolder.lookupOrThrow(Registries.STRUCTURE), CraftStructure::new, FieldRename.NONE); +- } +- if (bukkitClass == StructureType.class) { +- return new CraftRegistry<>(StructureType.class, registryHolder.lookupOrThrow(Registries.STRUCTURE_TYPE), CraftStructureType::new, FieldRename.NONE); +- } +- if (bukkitClass == Villager.Type.class) { +- return new CraftRegistry<>(Villager.Type.class, registryHolder.lookupOrThrow(Registries.VILLAGER_TYPE), CraftVillager.CraftType::new, FieldRename.NONE); +- } +- if (bukkitClass == Villager.Profession.class) { +- return new CraftRegistry<>(Villager.Profession.class, registryHolder.lookupOrThrow(Registries.VILLAGER_PROFESSION), CraftVillager.CraftProfession::new, FieldRename.NONE); +- } +- if (bukkitClass == TrimMaterial.class) { +- return new CraftRegistry<>(TrimMaterial.class, registryHolder.lookupOrThrow(Registries.TRIM_MATERIAL), CraftTrimMaterial::new, FieldRename.NONE); +- } +- if (bukkitClass == TrimPattern.class) { +- return new CraftRegistry<>(TrimPattern.class, registryHolder.lookupOrThrow(Registries.TRIM_PATTERN), CraftTrimPattern::new, FieldRename.NONE); +- } +- if (bukkitClass == DamageType.class) { +- return new CraftRegistry<>(DamageType.class, registryHolder.lookupOrThrow(Registries.DAMAGE_TYPE), CraftDamageType::new, FieldRename.NONE); +- } +- if (bukkitClass == JukeboxSong.class) { +- return new CraftRegistry<>(JukeboxSong.class, registryHolder.lookupOrThrow(Registries.JUKEBOX_SONG), CraftJukeboxSong::new, FieldRename.NONE); +- } +- if (bukkitClass == Wolf.Variant.class) { +- return new CraftRegistry<>(Wolf.Variant.class, registryHolder.lookupOrThrow(Registries.WOLF_VARIANT), CraftWolf.CraftVariant::new, FieldRename.NONE); +- } +- if (bukkitClass == BlockType.class) { +- return new CraftRegistry<>(BlockType.class, registryHolder.lookupOrThrow(Registries.BLOCK), CraftBlockType::new, FieldRename.NONE); +- } +- if (bukkitClass == ItemType.class) { +- return new CraftRegistry<>(ItemType.class, registryHolder.lookupOrThrow(Registries.ITEM), CraftItemType::new, FieldRename.NONE); +- } +- if (bukkitClass == Frog.Variant.class) { +- return new CraftRegistry<>(Frog.Variant.class, registryHolder.lookupOrThrow(Registries.FROG_VARIANT), CraftFrog.CraftVariant::new, FieldRename.NONE); +- } +- if (bukkitClass == Cat.Type.class) { +- return new CraftRegistry<>(Cat.Type.class, registryHolder.lookupOrThrow(Registries.CAT_VARIANT), CraftCat.CraftType::new, FieldRename.NONE); +- } +- if (bukkitClass == MapCursor.Type.class) { +- return new CraftRegistry<>(MapCursor.Type.class, registryHolder.lookupOrThrow(Registries.MAP_DECORATION_TYPE), CraftMapCursor.CraftType::new, FieldRename.NONE); +- } +- if (bukkitClass == PatternType.class) { +- return new CraftRegistry<>(PatternType.class, registryHolder.lookupOrThrow(Registries.BANNER_PATTERN), CraftPatternType::new, FieldRename.NONE); +- } +- +- return null; +- } ++ // Paper - move to PaperRegistries + ++ // Paper - NOTE: As long as all uses of the method below relate to *serialization* via ConfigurationSerializable, it's fine + public static B get(Registry bukkit, NamespacedKey namespacedKey, ApiVersion apiVersion) { + if (bukkit instanceof CraftRegistry craft) { +- return craft.get(namespacedKey, apiVersion); ++ return craft.get(craft.serializationUpdater.apply(namespacedKey, apiVersion)); // Paper + } + + if (bukkit instanceof Registry.SimpleRegistry simple) { +@@ -222,23 +153,21 @@ public class CraftRegistry implements Registry { + return bukkit.get(namespacedKey); + } + +- private final Class bukkitClass; ++ private final Class bukkitClass; // Paper - relax preload class + private final Map cache = new HashMap<>(); + private final net.minecraft.core.Registry minecraftRegistry; + private final BiFunction minecraftToBukkit; +- private final BiFunction updater; ++ private final BiFunction serializationUpdater; // Paper - rename to make it *clear* what it is *only* for + private boolean init; + +- public CraftRegistry(Class bukkitClass, net.minecraft.core.Registry minecraftRegistry, BiFunction minecraftToBukkit, BiFunction updater) { ++ public CraftRegistry(Class bukkitClass, net.minecraft.core.Registry minecraftRegistry, BiFunction minecraftToBukkit, BiFunction serializationUpdater) { // Paper - relax preload class + this.bukkitClass = bukkitClass; + this.minecraftRegistry = minecraftRegistry; + this.minecraftToBukkit = minecraftToBukkit; +- this.updater = updater; ++ this.serializationUpdater = serializationUpdater; + } + +- public B get(NamespacedKey namespacedKey, ApiVersion apiVersion) { +- return this.get(this.updater.apply(namespacedKey, apiVersion)); +- } ++ // Paper - inline into CraftRegistry#get(Registry, NamespacedKey, ApiVersion) above + + @Override + public B get(NamespacedKey namespacedKey) { +diff --git a/src/main/java/org/bukkit/craftbukkit/CraftServer.java b/src/main/java/org/bukkit/craftbukkit/CraftServer.java +index 30675a23a25dc065e09d97b9b08386c9f41989d8..a12dc990a9094e964be2af26a5135e3b798f9666 100644 +--- a/src/main/java/org/bukkit/craftbukkit/CraftServer.java ++++ b/src/main/java/org/bukkit/craftbukkit/CraftServer.java +@@ -284,7 +284,7 @@ public final class CraftServer implements Server { + protected final DedicatedServer console; + protected final DedicatedPlayerList playerList; + private final Map worlds = new LinkedHashMap(); +- private final Map, Registry> registries = new HashMap<>(); ++ // private final Map, Registry> registries = new HashMap<>(); // Paper - replace with RegistryAccess + private YamlConfiguration configuration; + private YamlConfiguration commandsConfiguration; + private final Yaml yaml = new Yaml(new SafeConstructor(new LoaderOptions())); +@@ -431,6 +431,7 @@ public final class CraftServer implements Server { + } + + private void loadCompatibilities() { ++ if (true) return; // Paper - Big nope + ConfigurationSection compatibilities = this.configuration.getConfigurationSection("settings.compatibility"); + if (compatibilities == null) { + this.activeCompatibilities = Collections.emptySet(); +@@ -2744,7 +2745,7 @@ public final class CraftServer implements Server { + + @Override + public Registry getRegistry(Class aClass) { +- return (Registry) this.registries.computeIfAbsent(aClass, key -> CraftRegistry.createRegistry(aClass, this.console.registryAccess())); ++ return io.papermc.paper.registry.RegistryAccess.registryAccess().getRegistry(aClass); // Paper - replace with RegistryAccess + } + + @Deprecated +diff --git a/src/main/java/org/bukkit/craftbukkit/legacy/FieldRename.java b/src/main/java/org/bukkit/craftbukkit/legacy/FieldRename.java +index c1023eff9f391c07b57e57450b756fe16299f723..b5e0023e431f9fb43c93a3f977144b03545322bb 100644 +--- a/src/main/java/org/bukkit/craftbukkit/legacy/FieldRename.java ++++ b/src/main/java/org/bukkit/craftbukkit/legacy/FieldRename.java +@@ -51,11 +51,14 @@ public class FieldRename { + }; + } + +- @RequireCompatibility("allow-old-keys-in-registry") +- public static T get(Registry registry, NamespacedKey namespacedKey) { +- // We don't have version-specific changes, so just use current, and don't inject a version +- return CraftRegistry.get(registry, namespacedKey, ApiVersion.CURRENT); +- } ++ // Paper start - absolutely not, having this as an expectation for plugin developers opens a huge ++ // can of worms in the future, especially if mojang comes back and reuses some old key ++ //@RequireCompatibility("allow-old-keys-in-registry") ++ //public static T get(Registry registry, NamespacedKey namespacedKey) { ++ // // We don't have version-specific changes, so just use current, and don't inject a version ++ // return CraftRegistry.get(registry, namespacedKey, ApiVersion.CURRENT); ++ //} ++ // Paper end + + // PatternType + private static final FieldRenameData PATTERN_TYPE_DATA = FieldRenameData.Builder.newBuilder() +diff --git a/src/main/java/org/bukkit/craftbukkit/util/Commodore.java b/src/main/java/org/bukkit/craftbukkit/util/Commodore.java +index b85223ebff4dbb8aa74b501663afc87ef11e2a96..760f56d36f0e4a74b58628408a286a499d6664ec 100644 +--- a/src/main/java/org/bukkit/craftbukkit/util/Commodore.java ++++ b/src/main/java/org/bukkit/craftbukkit/util/Commodore.java +@@ -215,20 +215,10 @@ public class Commodore { + + public byte[] convert(byte[] b, final String pluginName, final ApiVersion pluginVersion, final Set activeCompatibilities) { + final boolean modern = pluginVersion.isNewerThanOrSameAs(ApiVersion.FLATTENING); +- final boolean enumCompatibility = pluginVersion.isOlderThanOrSameAs(ApiVersion.getOrCreateVersion("1.20.6")) && activeCompatibilities.contains("enum-compatibility-mode"); + ClassReader cr = new ClassReader(b); + ClassWriter cw = new ClassWriter(cr, 0); + +- List methodEnumSignatures = Commodore.getMethodSignatures(b); +- Multimap enumLessToEnum = HashMultimap.create(); +- for (String method : methodEnumSignatures) { +- enumLessToEnum.put(method.replace("Ljava/lang/Enum;", "Ljava/lang/Object;"), method); +- } +- + ClassVisitor visitor = cw; +- if (enumCompatibility) { +- visitor = new LimitedClassRemapper(cw, new SimpleRemapper(Commodore.ENUM_RENAMES)); +- } + + visitor = io.papermc.paper.pluginremap.reflect.ReflectionRemapper.visitor(visitor); // Paper + cr.accept(new ClassRemapper(new ClassVisitor(Opcodes.ASM9, visitor) { +@@ -295,15 +285,6 @@ public class Commodore { + + @Override + public MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) { +- if (enumCompatibility && (access & Opcodes.ACC_SYNTHETIC) != 0 && (access & Opcodes.ACC_BRIDGE) != 0 && desc.contains("Ljava/lang/Object;")) { +- // SPIGOT-7820: Do not use object method if enum method is present +- // The object method does only redirect to the enum method +- Collection result = enumLessToEnum.get(desc.replace("Ljava/lang/Enum;", "Ljava/lang/Object;") + " " + name); +- if (result.size() == 2) { +- name = name + "_BUKKIT_UNUSED"; +- } +- } +- + return new MethodVisitor(this.api, super.visitMethod(access, name, desc, signature, exceptions)) { + // Paper start - Plugin rewrites + @Override +diff --git a/src/main/resources/META-INF/services/io.papermc.paper.registry.RegistryAccess b/src/main/resources/META-INF/services/io.papermc.paper.registry.RegistryAccess +new file mode 100644 +index 0000000000000000000000000000000000000000..8a083d45004f82fc9c51c219fb20f34624adb501 +--- /dev/null ++++ b/src/main/resources/META-INF/services/io.papermc.paper.registry.RegistryAccess +@@ -0,0 +1 @@ ++io.papermc.paper.registry.PaperRegistryAccess +diff --git a/src/main/resources/configurations/bukkit.yml b/src/main/resources/configurations/bukkit.yml +index 543e37737bc6fdca23ed9ed0606805d345515a5a..eef7c125b2689f29cae5464659eacdf33f5695b2 100644 +--- a/src/main/resources/configurations/bukkit.yml ++++ b/src/main/resources/configurations/bukkit.yml +@@ -23,9 +23,6 @@ settings: + shutdown-message: Server closed + minimum-api: none + use-map-color-cache: true +- compatibility: +- allow-old-keys-in-registry: false +- enum-compatibility-mode: false + spawn-limits: + monsters: 70 + animals: 10 +diff --git a/src/test/java/io/papermc/paper/registry/LegacyRegistryIdentifierTest.java b/src/test/java/io/papermc/paper/registry/LegacyRegistryIdentifierTest.java +new file mode 100644 +index 0000000000000000000000000000000000000000..a80b0ded74c0be657e734de61cbf5e32e16c26a8 +--- /dev/null ++++ b/src/test/java/io/papermc/paper/registry/LegacyRegistryIdentifierTest.java +@@ -0,0 +1,21 @@ ++package io.papermc.paper.registry; ++ ++import org.bukkit.GameEvent; ++import org.bukkit.MusicInstrument; ++import org.bukkit.inventory.meta.trim.TrimPattern; ++import org.bukkit.support.environment.VanillaFeature; ++import org.junit.jupiter.api.Test; ++ ++import static org.junit.jupiter.api.Assertions.assertSame; ++ ++@Deprecated ++@VanillaFeature ++class LegacyRegistryIdentifierTest { ++ ++ @Test ++ void testSeveralConversions() { ++ assertSame(RegistryKey.GAME_EVENT, PaperRegistryAccess.byType(GameEvent.class)); ++ assertSame(RegistryKey.TRIM_PATTERN, PaperRegistryAccess.byType(TrimPattern.class)); ++ assertSame(RegistryKey.INSTRUMENT, PaperRegistryAccess.byType(MusicInstrument.class)); ++ } ++} +diff --git a/src/test/java/io/papermc/paper/registry/RegistryKeyTest.java b/src/test/java/io/papermc/paper/registry/RegistryKeyTest.java +index d8857a05858585113bc7efde3416748effb53d01..41c38b1b6d25c7a7ed08d70b19f5f4a70fc2df94 100644 +--- a/src/test/java/io/papermc/paper/registry/RegistryKeyTest.java ++++ b/src/test/java/io/papermc/paper/registry/RegistryKeyTest.java +@@ -1,15 +1,19 @@ + package io.papermc.paper.registry; + ++import io.papermc.paper.registry.entry.RegistryEntry; + import java.util.Optional; + import java.util.stream.Stream; + import net.minecraft.core.Registry; + import net.minecraft.resources.ResourceKey; + import net.minecraft.resources.ResourceLocation; +-import org.bukkit.support.AbstractTestingBase; ++import org.bukkit.support.RegistryHelper; ++import org.bukkit.support.environment.AllFeatures; ++import org.checkerframework.checker.nullness.qual.Nullable; + import org.junit.jupiter.api.BeforeAll; + import org.junit.jupiter.params.ParameterizedTest; + import org.junit.jupiter.params.provider.MethodSource; + ++import static org.junit.jupiter.api.Assertions.assertNotNull; + import static org.junit.jupiter.api.Assertions.assertTrue; + + @AllFeatures +@@ -29,6 +33,12 @@ class RegistryKeyTest { + void testApiRegistryKeysExist(final RegistryKey key) { + final Optional> registry = RegistryHelper.getRegistry().lookup(ResourceKey.createRegistryKey(ResourceLocation.parse(key.key().asString()))); + assertTrue(registry.isPresent(), "Missing vanilla registry for " + key.key().asString()); ++ } + ++ @ParameterizedTest ++ @MethodSource("data") ++ void testRegistryEntryExists(final RegistryKey key) { ++ final @Nullable RegistryEntry entry = PaperRegistries.getEntry(key); ++ assertNotNull(entry, "Missing PaperRegistries entry for " + key); + } + } +diff --git a/src/test/java/org/bukkit/registry/RegistryArgumentAddedTest.java b/src/test/java/org/bukkit/registry/RegistryArgumentAddedTest.java +index b65a3ee68c177da7ef5a57608187dc1672257c7f..c1016e0eb00e952551370c874e8d678fef8ba3dc 100644 +--- a/src/test/java/org/bukkit/registry/RegistryArgumentAddedTest.java ++++ b/src/test/java/org/bukkit/registry/RegistryArgumentAddedTest.java +@@ -22,14 +22,17 @@ public class RegistryArgumentAddedTest { + // Make sure every registry is created + Class.forName(Registry.class.getName()); + +- Set> loadedRegistries = new HashSet<>(AllFeaturesExtension.getRealRegistries().keySet()); +- Set> notFound = new HashSet<>(); ++ // Paper start ++ Set> loadedRegistries = java.util.Collections.newSetFromMap(new java.util.IdentityHashMap<>()); ++ loadedRegistries.addAll(io.papermc.paper.registry.PaperRegistryAccess.instance().getLoadedServerBackedRegistries()); ++ // Paper end ++ Set> notFound = new HashSet<>(); // Paper + + RegistriesArgumentProvider + .getData() + .map(Arguments::get) + .map(array -> array[0]) +- .map(clazz -> (Class) clazz) ++ .map(clazz -> (io.papermc.paper.registry.RegistryKey) clazz) // Paper + .forEach(clazz -> { + if (!loadedRegistries.remove(clazz)) { + notFound.add(clazz); +diff --git a/src/test/java/org/bukkit/registry/RegistryConversionTest.java b/src/test/java/org/bukkit/registry/RegistryConversionTest.java +index e97328b95973db52d44bc4d0aefd8eb69f2ebdea..01e351f4e292efe78fc1a1db0f31b2c0a313b101 100644 +--- a/src/test/java/org/bukkit/registry/RegistryConversionTest.java ++++ b/src/test/java/org/bukkit/registry/RegistryConversionTest.java +@@ -41,9 +41,9 @@ public class RegistryConversionTest { + + @Order(1) + @RegistriesTest +- public void testHandleableImplementation(Class clazz) { ++ public void testHandleableImplementation(io.papermc.paper.registry.RegistryKey type, Class clazz) { // Paper + Set> notImplemented = new HashSet<>(); +- Registry registry = Bukkit.getRegistry(clazz); ++ Registry registry = io.papermc.paper.registry.RegistryAccess.registryAccess().getRegistry(type); // Paper + + for (Keyed item : registry) { + if (!(item instanceof Handleable)) { +@@ -63,7 +63,7 @@ public class RegistryConversionTest { + + @Order(2) + @RegistriesTest +- public void testMinecraftToBukkitPresent(Class clazz, ResourceKey> registryKey, ++ public void testMinecraftToBukkitPresent(io.papermc.paper.registry.RegistryKey type, Class clazz, ResourceKey> registryKey, + Class craftClazz, Class minecraftClazz, boolean newMethod) { + String methodName = (newMethod) ? RegistryConversionTest.MINECRAFT_TO_BUKKIT_NEW : RegistryConversionTest.MINECRAFT_TO_BUKKIT; + Method method = null; +@@ -112,7 +112,7 @@ public class RegistryConversionTest { + + @Order(2) + @RegistriesTest +- public void testBukkitToMinecraftPresent(Class clazz, ResourceKey> registryKey, ++ public void testBukkitToMinecraftPresent(io.papermc.paper.registry.RegistryKey type, Class clazz, ResourceKey> registryKey, + Class craftClazz, Class minecraftClazz, boolean newMethod) { + String methodName = (newMethod) ? RegistryConversionTest.BUKKIT_TO_MINECRAFT_NEW : RegistryConversionTest.BUKKIT_TO_MINECRAFT; + Method method = null; +@@ -159,9 +159,9 @@ public class RegistryConversionTest { + """, minecraftClazz.getName(), methodName, clazz.getSimpleName()); + } + +- @Order(2) ++ @Order(3) + @RegistriesTest +- public void testMinecraftToBukkitNullValue(Class clazz) throws IllegalAccessException { ++ public void testMinecraftToBukkitNullValue(io.papermc.paper.registry.RegistryKey type, Class clazz) throws IllegalAccessException { // Paper + this.checkValidMinecraftToBukkit(clazz); + + try { +@@ -180,7 +180,7 @@ public class RegistryConversionTest { + + @Order(3) + @RegistriesTest +- public void testBukkitToMinecraftNullValue(Class clazz) throws IllegalAccessException { ++ public void testBukkitToMinecraftNullValue(io.papermc.paper.registry.RegistryKey type, Class clazz) throws IllegalAccessException { // Paper + this.checkValidBukkitToMinecraft(clazz); + + try { +@@ -199,14 +199,14 @@ public class RegistryConversionTest { + + @Order(3) + @RegistriesTest +- public void testMinecraftToBukkit(Class clazz) { ++ public void testMinecraftToBukkit(io.papermc.paper.registry.RegistryKey type, Class clazz) { // Paper + this.checkValidMinecraftToBukkit(clazz); + this.checkValidHandle(clazz); + + Map notMatching = new HashMap<>(); + Method method = RegistryConversionTest.MINECRAFT_TO_BUKKIT_METHODS.get(clazz); + +- RegistryArgumentProvider.getValues(clazz).map(Arguments::get).forEach(arguments -> { ++ RegistryArgumentProvider.getValues(type).map(Arguments::get).forEach(arguments -> { // Paper + Keyed bukkit = (Keyed) arguments[0]; + Object minecraft = arguments[1]; + +@@ -230,14 +230,14 @@ public class RegistryConversionTest { + + @Order(3) + @RegistriesTest +- public void testBukkitToMinecraft(Class clazz) { ++ public void testBukkitToMinecraft(io.papermc.paper.registry.RegistryKey type, Class clazz) { // Paper + this.checkValidBukkitToMinecraft(clazz); + this.checkValidHandle(clazz); + + Map notMatching = new HashMap<>(); + Method method = RegistryConversionTest.BUKKIT_TO_MINECRAFT_METHODS.get(clazz); + +- RegistryArgumentProvider.getValues(clazz).map(Arguments::get).forEach(arguments -> { ++ RegistryArgumentProvider.getValues(type).map(Arguments::get).forEach(arguments -> { // Paper + Keyed bukkit = (Keyed) arguments[0]; + Object minecraft = arguments[1]; + +@@ -265,7 +265,7 @@ public class RegistryConversionTest { + */ + @Order(3) + @RegistriesTest +- public void testMinecraftToBukkitNoValidMinecraft(Class clazz, ResourceKey> registryKey, ++ public void testMinecraftToBukkitNoValidMinecraft(io.papermc.paper.registry.RegistryKey type, Class clazz, ResourceKey> registryKey, // Paper + Class craftClazz, Class minecraftClazz) throws IllegalAccessException { + this.checkValidMinecraftToBukkit(clazz); + +diff --git a/src/test/java/org/bukkit/support/extension/AllFeaturesExtension.java b/src/test/java/org/bukkit/support/extension/AllFeaturesExtension.java +index e9eb521419bbacb03d7000ace355f2a9f5a6a9c5..8fef8421e3cf87913746a314a477634bd3e99300 100644 +--- a/src/test/java/org/bukkit/support/extension/AllFeaturesExtension.java ++++ b/src/test/java/org/bukkit/support/extension/AllFeaturesExtension.java +@@ -39,22 +39,7 @@ public class AllFeaturesExtension extends BaseExtension { + + Bukkit.setServer(server); + +- when(server.getRegistry(any())) +- .then(invocation -> { +- Class keyed = invocation.getArgument(0); +- if (spyRegistries.containsKey(keyed)) { +- return spyRegistries.get(keyed); +- } +- +- Registry registry = CraftRegistry.createRegistry(keyed, RegistryHelper.getRegistry()); +- realRegistries.put(keyed, registry); +- +- Registry spy = mock(registry.getClass(), withSettings().stubOnly().spiedInstance(registry).defaultAnswer(CALLS_REAL_METHODS)); +- +- spyRegistries.put(keyed, spy); +- +- return spy; +- }); ++ // Paper - Add RegistryAccess for managing registries - replaced with registry access + + CraftRegistry.setMinecraftRegistry(RegistryHelper.getRegistry()); + } +diff --git a/src/test/java/org/bukkit/support/extension/LegacyExtension.java b/src/test/java/org/bukkit/support/extension/LegacyExtension.java +index 94cf52cf7603e6814682c92b26fcf03a8b927838..c9c3227c3b7fa36ed80f2dc828885a0128e1e3d0 100644 +--- a/src/test/java/org/bukkit/support/extension/LegacyExtension.java ++++ b/src/test/java/org/bukkit/support/extension/LegacyExtension.java +@@ -30,11 +30,7 @@ public class LegacyExtension extends BaseExtension { + + Bukkit.setServer(server); + +- when(server.getRegistry(any())) +- .then(invocation -> { +- Class keyed = invocation.getArgument(0); +- return registries.computeIfAbsent(keyed, k -> CraftRegistry.createRegistry(keyed, RegistryHelper.getRegistry())); +- }); ++ // Paper - Add RegistryAccess for managing registries - replaced with registry access + + CraftRegistry.setMinecraftRegistry(RegistryHelper.getRegistry()); + } +diff --git a/src/test/java/org/bukkit/support/extension/SlowExtension.java b/src/test/java/org/bukkit/support/extension/SlowExtension.java +index e0ce6836d4365c36303f6c675a75ef6a9b047b92..87364f223fbd6185b041138550fcb6e3ed07d1ae 100644 +--- a/src/test/java/org/bukkit/support/extension/SlowExtension.java ++++ b/src/test/java/org/bukkit/support/extension/SlowExtension.java +@@ -30,11 +30,7 @@ public class SlowExtension extends BaseExtension { + + Bukkit.setServer(server); + +- when(server.getRegistry(any())) +- .then(invocation -> { +- Class keyed = invocation.getArgument(0); +- return registries.computeIfAbsent(keyed, k -> CraftRegistry.createRegistry(keyed, RegistryHelper.getRegistry())); +- }); ++ // Paper - Add RegistryAccess for managing registries - replaced with registry access + + CraftRegistry.setMinecraftRegistry(RegistryHelper.getRegistry()); + } +diff --git a/src/test/java/org/bukkit/support/extension/VanillaFeatureExtension.java b/src/test/java/org/bukkit/support/extension/VanillaFeatureExtension.java +index bbd5dd5b27937ddc3d8c57f2b604331495b0f311..626c3033e36897846fe84a77d05e2e91a15598e5 100644 +--- a/src/test/java/org/bukkit/support/extension/VanillaFeatureExtension.java ++++ b/src/test/java/org/bukkit/support/extension/VanillaFeatureExtension.java +@@ -30,11 +30,7 @@ public class VanillaFeatureExtension extends BaseExtension { + + Bukkit.setServer(server); + +- when(server.getRegistry(any())) +- .then(invocation -> { +- Class keyed = invocation.getArgument(0); +- return registries.computeIfAbsent(keyed, k -> CraftRegistry.createRegistry(keyed, RegistryHelper.getRegistry())); +- }); ++ // Paper - Add RegistryAccess for managing registries - replaced with registry access + + CraftRegistry.setMinecraftRegistry(RegistryHelper.getRegistry()); + } +diff --git a/src/test/java/org/bukkit/support/provider/RegistriesArgumentProvider.java b/src/test/java/org/bukkit/support/provider/RegistriesArgumentProvider.java +index 47f3b79d76399ff2185ea753260a702441ecadf5..eb3974690fb12ffe678522ed47e0f730712db016 100644 +--- a/src/test/java/org/bukkit/support/provider/RegistriesArgumentProvider.java ++++ b/src/test/java/org/bukkit/support/provider/RegistriesArgumentProvider.java +@@ -1,6 +1,7 @@ + package org.bukkit.support.provider; + + import com.google.common.collect.Lists; ++import io.papermc.paper.registry.RegistryKey; + import java.util.List; + import java.util.stream.Stream; + import net.minecraft.core.registries.Registries; +@@ -61,36 +62,35 @@ public class RegistriesArgumentProvider implements ArgumentsProvider { + private static final List DATA = Lists.newArrayList(); + + static { +- // Order: Bukkit class, Minecraft Registry key, CraftBukkit class, Minecraft class +- register(Enchantment.class, Registries.ENCHANTMENT, CraftEnchantment.class, net.minecraft.world.item.enchantment.Enchantment.class); +- register(GameEvent.class, Registries.GAME_EVENT, CraftGameEvent.class, net.minecraft.world.level.gameevent.GameEvent.class); +- register(MusicInstrument.class, Registries.INSTRUMENT, CraftMusicInstrument.class, Instrument.class); +- register(MenuType.class, Registries.MENU, CraftMenuType.class, net.minecraft.world.inventory.MenuType.class); +- register(PotionEffectType.class, Registries.MOB_EFFECT, CraftPotionEffectType.class, MobEffect.class); +- register(Structure.class, Registries.STRUCTURE, CraftStructure.class, net.minecraft.world.level.levelgen.structure.Structure.class); +- register(StructureType.class, Registries.STRUCTURE_TYPE, CraftStructureType.class, net.minecraft.world.level.levelgen.structure.StructureType.class); +- register(Villager.Type.class, Registries.VILLAGER_TYPE, CraftVillager.CraftType.class, VillagerType.class); +- register(Villager.Profession.class, Registries.VILLAGER_PROFESSION, CraftVillager.CraftProfession.class, VillagerProfession.class); +- register(TrimMaterial.class, Registries.TRIM_MATERIAL, CraftTrimMaterial.class, net.minecraft.world.item.equipment.trim.TrimMaterial.class); +- register(TrimPattern.class, Registries.TRIM_PATTERN, CraftTrimPattern.class, net.minecraft.world.item.equipment.trim.TrimPattern.class); +- register(DamageType.class, Registries.DAMAGE_TYPE, CraftDamageType.class, net.minecraft.world.damagesource.DamageType.class); +- register(JukeboxSong.class, Registries.JUKEBOX_SONG, CraftJukeboxSong.class, net.minecraft.world.item.JukeboxSong.class); +- register(Wolf.Variant.class, Registries.WOLF_VARIANT, CraftWolf.CraftVariant.class, WolfVariant.class); +- register(ItemType.class, Registries.ITEM, CraftItemType.class, net.minecraft.world.item.Item.class, true); +- register(BlockType.class, Registries.BLOCK, CraftBlockType.class, net.minecraft.world.level.block.Block.class, true); +- register(Frog.Variant.class, Registries.FROG_VARIANT, CraftFrog.CraftVariant.class, FrogVariant.class); +- register(Cat.Type.class, Registries.CAT_VARIANT, CraftCat.CraftType.class, CatVariant.class); +- register(MapCursor.Type.class, Registries.MAP_DECORATION_TYPE, CraftMapCursor.CraftType.class, MapDecorationType.class); +- register(PatternType.class, Registries.BANNER_PATTERN, CraftPatternType.class, BannerPattern.class); +- ++ // Order: RegistryKey, Bukkit class, Minecraft Registry key, CraftBukkit class, Minecraft class ++ register(RegistryKey.ENCHANTMENT, Enchantment.class, Registries.ENCHANTMENT, CraftEnchantment.class, net.minecraft.world.item.enchantment.Enchantment.class); ++ register(RegistryKey.GAME_EVENT, GameEvent.class, Registries.GAME_EVENT, CraftGameEvent.class, net.minecraft.world.level.gameevent.GameEvent.class); ++ register(RegistryKey.INSTRUMENT, MusicInstrument.class, Registries.INSTRUMENT, CraftMusicInstrument.class, Instrument.class); ++ register(RegistryKey.MOB_EFFECT, PotionEffectType.class, Registries.MOB_EFFECT, CraftPotionEffectType.class, MobEffect.class); ++ register(RegistryKey.STRUCTURE, Structure.class, Registries.STRUCTURE, CraftStructure.class, net.minecraft.world.level.levelgen.structure.Structure.class); ++ register(RegistryKey.STRUCTURE_TYPE, StructureType.class, Registries.STRUCTURE_TYPE, CraftStructureType.class, net.minecraft.world.level.levelgen.structure.StructureType.class); ++ register(RegistryKey.VILLAGER_TYPE, Villager.Type.class, Registries.VILLAGER_TYPE, CraftVillager.CraftType.class, VillagerType.class); ++ register(RegistryKey.VILLAGER_PROFESSION, Villager.Profession.class, Registries.VILLAGER_PROFESSION, CraftVillager.CraftProfession.class, VillagerProfession.class); ++ register(RegistryKey.TRIM_MATERIAL, TrimMaterial.class, Registries.TRIM_MATERIAL, CraftTrimMaterial.class, net.minecraft.world.item.equipment.trim.TrimMaterial.class); ++ register(RegistryKey.TRIM_PATTERN, TrimPattern.class, Registries.TRIM_PATTERN, CraftTrimPattern.class, net.minecraft.world.item.equipment.trim.TrimPattern.class); ++ register(RegistryKey.DAMAGE_TYPE, DamageType.class, Registries.DAMAGE_TYPE, CraftDamageType.class, net.minecraft.world.damagesource.DamageType.class); ++ register(RegistryKey.JUKEBOX_SONG, JukeboxSong.class, Registries.JUKEBOX_SONG, CraftJukeboxSong.class, net.minecraft.world.item.JukeboxSong.class); ++ register(RegistryKey.WOLF_VARIANT, Wolf.Variant.class, Registries.WOLF_VARIANT, CraftWolf.CraftVariant.class, WolfVariant.class); ++ register(RegistryKey.ITEM, ItemType.class, Registries.ITEM, CraftItemType.class, net.minecraft.world.item.Item.class, true); ++ register(RegistryKey.BLOCK, BlockType.class, Registries.BLOCK, CraftBlockType.class, net.minecraft.world.level.block.Block.class, true); ++ register(RegistryKey.FROG_VARIANT, Frog.Variant.class, Registries.FROG_VARIANT, CraftFrog.CraftVariant.class, FrogVariant.class); ++ register(RegistryKey.CAT_VARIANT, Cat.Type.class, Registries.CAT_VARIANT, CraftCat.CraftType.class, CatVariant.class); ++ register(RegistryKey.MAP_DECORATION_TYPE, MapCursor.Type.class, Registries.MAP_DECORATION_TYPE, CraftMapCursor.CraftType.class, MapDecorationType.class); ++ register(RegistryKey.BANNER_PATTERN, PatternType.class, Registries.BANNER_PATTERN, CraftPatternType.class, BannerPattern.class); ++ register(RegistryKey.MENU, MenuType.class, Registries.MENU, CraftMenuType.class, net.minecraft.world.inventory.MenuType.class); + } + +- private static void register(Class bukkit, ResourceKey registry, Class craft, Class minecraft) { +- RegistriesArgumentProvider.register(bukkit, registry, craft, minecraft, false); ++ private static void register(RegistryKey registryKey, Class bukkit, ResourceKey registry, Class craft, Class minecraft) { // Paper ++ RegistriesArgumentProvider.register(registryKey, bukkit, registry, craft, minecraft, false); + } + +- private static void register(Class bukkit, ResourceKey registry, Class craft, Class minecraft, boolean newClass) { +- RegistriesArgumentProvider.DATA.add(Arguments.of(bukkit, registry, craft, minecraft, newClass)); ++ private static void register(RegistryKey registryKey, Class bukkit, ResourceKey registry, Class craft, Class minecraft, boolean newClass) { // Paper ++ RegistriesArgumentProvider.DATA.add(Arguments.of(registryKey, bukkit, registry, craft, minecraft, newClass)); + } + + @Override +diff --git a/src/test/java/org/bukkit/support/provider/RegistryArgumentProvider.java b/src/test/java/org/bukkit/support/provider/RegistryArgumentProvider.java +index f2ceb5e67536dfa5de792dc64a7898fd2e8aa810..beb5fc9e721f5de54064c3d241df9ca9f4cd4f65 100644 +--- a/src/test/java/org/bukkit/support/provider/RegistryArgumentProvider.java ++++ b/src/test/java/org/bukkit/support/provider/RegistryArgumentProvider.java +@@ -22,11 +22,11 @@ public class RegistryArgumentProvider implements ArgumentsProvider, AnnotationCo + + @Override + public Stream provideArguments(ExtensionContext extensionContext) throws Exception { +- return RegistryArgumentProvider.getValues(this.registryType); ++ return RegistryArgumentProvider.getValues(io.papermc.paper.registry.PaperRegistryAccess.byType(this.registryType)); // Paper + } + +- public static Stream getValues(Class registryType) { +- Registry registry = Bukkit.getRegistry(registryType); ++ public static Stream getValues(io.papermc.paper.registry.RegistryKey registryType) { // Paper ++ Registry registry = io.papermc.paper.registry.RegistryAccess.registryAccess().getRegistry(registryType); // Paper + return registry.stream().map(keyed -> (Handleable) keyed) + .map(handleAble -> Arguments.of(handleAble, handleAble.getHandle())); + } diff --git a/patches/server/0467-Empty-commands-shall-not-be-dispatched.patch b/patches/server/0467-Empty-commands-shall-not-be-dispatched.patch deleted file mode 100644 index f58be08904c8..000000000000 --- a/patches/server/0467-Empty-commands-shall-not-be-dispatched.patch +++ /dev/null @@ -1,18 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Mariell Hoversholm -Date: Wed, 6 Jan 2021 23:38:43 +0100 -Subject: [PATCH] Empty commands shall not be dispatched - - -diff --git a/src/main/java/net/minecraft/commands/Commands.java b/src/main/java/net/minecraft/commands/Commands.java -index 8ca9ac8eff9d605baa878ca24e165ac5642bf160..f52e9732524b62b0fecdc48e099163f31fe367b4 100644 ---- a/src/main/java/net/minecraft/commands/Commands.java -+++ b/src/main/java/net/minecraft/commands/Commands.java -@@ -286,6 +286,7 @@ public class Commands { - command = event.getCommand(); - - String[] args = command.split(" "); -+ if (args.length == 0) return; // Paper - empty commands shall not be dispatched - - String cmd = args[0]; - if (cmd.startsWith("minecraft:")) cmd = cmd.substring("minecraft:".length()); diff --git a/patches/server/0468-Add-StructuresLocateEvent.patch b/patches/server/0468-Add-StructuresLocateEvent.patch new file mode 100644 index 000000000000..5556e96212ef --- /dev/null +++ b/patches/server/0468-Add-StructuresLocateEvent.patch @@ -0,0 +1,36 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: dfsek +Date: Wed, 16 Sep 2020 01:12:29 -0700 +Subject: [PATCH] Add StructuresLocateEvent + +Co-authored-by: Jake Potrebic + +diff --git a/src/main/java/net/minecraft/world/level/chunk/ChunkGenerator.java b/src/main/java/net/minecraft/world/level/chunk/ChunkGenerator.java +index 115deba41ec48143570489e8494785a3a48cd789..e3c5a49611d584fbd19a44da5aa78ff6d7c43881 100644 +--- a/src/main/java/net/minecraft/world/level/chunk/ChunkGenerator.java ++++ b/src/main/java/net/minecraft/world/level/chunk/ChunkGenerator.java +@@ -126,6 +126,24 @@ public abstract class ChunkGenerator { + + @Nullable + public Pair> findNearestMapStructure(ServerLevel world, HolderSet structures, BlockPos center, int radius, boolean skipReferencedStructures) { ++ // Paper start - StructuresLocateEvent ++ final org.bukkit.World bukkitWorld = world.getWorld(); ++ final org.bukkit.Location origin = io.papermc.paper.util.MCUtil.toLocation(world, center); ++ final List apiStructures = structures.stream().map(Holder::value).map(nms -> org.bukkit.craftbukkit.generator.structure.CraftStructure.minecraftToBukkit(nms)).toList(); ++ if (!apiStructures.isEmpty()) { ++ final io.papermc.paper.event.world.StructuresLocateEvent event = new io.papermc.paper.event.world.StructuresLocateEvent(bukkitWorld, origin, apiStructures, radius, skipReferencedStructures); ++ if (!event.callEvent()) { ++ return null; ++ } ++ if (event.getResult() != null) { ++ return Pair.of(io.papermc.paper.util.MCUtil.toBlockPos(event.getResult().pos()), world.registryAccess().lookupOrThrow(Registries.STRUCTURE).wrapAsHolder(org.bukkit.craftbukkit.generator.structure.CraftStructure.bukkitToMinecraft(event.getResult().structure()))); ++ } ++ center = io.papermc.paper.util.MCUtil.toBlockPosition(event.getOrigin()); ++ radius = event.getRadius(); ++ skipReferencedStructures = event.shouldFindUnexplored(); ++ structures = HolderSet.direct(api -> world.registryAccess().lookupOrThrow(Registries.STRUCTURE).wrapAsHolder(org.bukkit.craftbukkit.generator.structure.CraftStructure.bukkitToMinecraft(api)), event.getStructures()); ++ } ++ // Paper end + ChunkGeneratorStructureState chunkgeneratorstructurestate = world.getChunkSource().getGeneratorState(); + Map>> map = new Object2ObjectArrayMap(); + Iterator iterator = structures.iterator(); diff --git a/patches/server/0468-Remove-stale-POIs.patch b/patches/server/0468-Remove-stale-POIs.patch deleted file mode 100644 index 15a12eff2dd7..000000000000 --- a/patches/server/0468-Remove-stale-POIs.patch +++ /dev/null @@ -1,22 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Shane Freeder -Date: Sat, 9 Jan 2021 14:17:07 +0100 -Subject: [PATCH] Remove stale POIs - - -diff --git a/src/main/java/net/minecraft/server/level/ServerLevel.java b/src/main/java/net/minecraft/server/level/ServerLevel.java -index fdcc744cd210535067a69362411305e23f0e4273..85c83c900c33d139072d17e1850e95d86da324a7 100644 ---- a/src/main/java/net/minecraft/server/level/ServerLevel.java -+++ b/src/main/java/net/minecraft/server/level/ServerLevel.java -@@ -1743,6 +1743,11 @@ public class ServerLevel extends Level implements WorldGenLevel { - }); - optional1.ifPresent((holder) -> { - this.getServer().execute(() -> { -+ // Paper start - Remove stale POIs -+ if (optional.isEmpty() && this.getPoiManager().exists(blockposition1, poiType -> true)) { -+ this.getPoiManager().remove(blockposition1); -+ } -+ // Paper end - Remove stale POIs - this.getPoiManager().add(blockposition1, holder); - DebugPackets.sendPoiAddedPacket(this, blockposition1); - }); diff --git a/patches/server/0469-Collision-option-for-requiring-a-player-participant.patch b/patches/server/0469-Collision-option-for-requiring-a-player-participant.patch new file mode 100644 index 000000000000..f939c99a2459 --- /dev/null +++ b/patches/server/0469-Collision-option-for-requiring-a-player-participant.patch @@ -0,0 +1,42 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Mariell Hoversholm +Date: Sat, 14 Nov 2020 16:48:37 +0100 +Subject: [PATCH] Collision option for requiring a player participant + + +diff --git a/src/main/java/net/minecraft/world/entity/Entity.java b/src/main/java/net/minecraft/world/entity/Entity.java +index 7c07ffaabce7a3964114b0859ad0028d52c662cc..0d10abc918e31bf8a529706b3350c80dec11b313 100644 +--- a/src/main/java/net/minecraft/world/entity/Entity.java ++++ b/src/main/java/net/minecraft/world/entity/Entity.java +@@ -2024,6 +2024,7 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess + public void push(Entity entity) { + if (!this.isPassengerOfSameVehicle(entity)) { + if (!entity.noPhysics && !this.noPhysics) { ++ if (this.level.paperConfig().collisions.onlyPlayersCollide && !(entity instanceof ServerPlayer || this instanceof ServerPlayer)) return; // Paper - Collision option for requiring a player participant + double d0 = entity.getX() - this.getX(); + double d1 = entity.getZ() - this.getZ(); + double d2 = Mth.absMax(d0, d1); +diff --git a/src/main/java/net/minecraft/world/entity/vehicle/AbstractBoat.java b/src/main/java/net/minecraft/world/entity/vehicle/AbstractBoat.java +index a9661ab34bc98c19d525eb4b60b1f0d05d73241e..3590f4bc1af829cdb6e0cfdc8fa6857197b9219e 100644 +--- a/src/main/java/net/minecraft/world/entity/vehicle/AbstractBoat.java ++++ b/src/main/java/net/minecraft/world/entity/vehicle/AbstractBoat.java +@@ -196,6 +196,7 @@ public abstract class AbstractBoat extends VehicleEntity implements Leashable { + + @Override + public void push(Entity entity) { ++ if (!this.level().paperConfig().collisions.allowVehicleCollisions && this.level().paperConfig().collisions.onlyPlayersCollide && !(entity instanceof Player)) return; // Paper - Collision option for requiring a player participant + if (entity instanceof AbstractBoat) { + if (entity.getBoundingBox().minY < this.getBoundingBox().maxY) { + // CraftBukkit start +diff --git a/src/main/java/net/minecraft/world/entity/vehicle/AbstractMinecart.java b/src/main/java/net/minecraft/world/entity/vehicle/AbstractMinecart.java +index 50c0055d80735313c280821991bd2a76e427f082..ee7350e19a86ffa115e4bce6b186a2422951e89b 100644 +--- a/src/main/java/net/minecraft/world/entity/vehicle/AbstractMinecart.java ++++ b/src/main/java/net/minecraft/world/entity/vehicle/AbstractMinecart.java +@@ -562,6 +562,7 @@ public abstract class AbstractMinecart extends VehicleEntity { + public void push(Entity entity) { + if (!this.level().isClientSide) { + if (!entity.noPhysics && !this.noPhysics) { ++ if (!this.level().paperConfig().collisions.allowVehicleCollisions && this.level().paperConfig().collisions.onlyPlayersCollide && !(entity instanceof Player)) return; // Paper - Collision option for requiring a player participant + if (!this.hasPassenger(entity)) { + // CraftBukkit start + VehicleEntityCollisionEvent collisionEvent = new VehicleEntityCollisionEvent((Vehicle) this.getBukkitEntity(), entity.getBukkitEntity()); diff --git a/patches/server/0469-Fix-villager-boat-exploit.patch b/patches/server/0469-Fix-villager-boat-exploit.patch deleted file mode 100644 index 5856a8c32b7f..000000000000 --- a/patches/server/0469-Fix-villager-boat-exploit.patch +++ /dev/null @@ -1,25 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Jason Penilla <11360596+jpenilla@users.noreply.github.com> -Date: Mon, 11 Jan 2021 12:43:51 -0800 -Subject: [PATCH] Fix villager boat exploit - - -diff --git a/src/main/java/net/minecraft/server/players/PlayerList.java b/src/main/java/net/minecraft/server/players/PlayerList.java -index 98eb00a8ee23543714d424d3ff5ca19887d6db19..9436a9614dea81d14c4f77cc21ca91f85ca87152 100644 ---- a/src/main/java/net/minecraft/server/players/PlayerList.java -+++ b/src/main/java/net/minecraft/server/players/PlayerList.java -@@ -595,6 +595,14 @@ public abstract class PlayerList { - PlayerList.LOGGER.debug("Removing player mount"); - entityplayer.stopRiding(); - entity.getPassengersAndSelf().forEach((entity1) -> { -+ // Paper start - Fix villager boat exploit -+ if (entity1 instanceof net.minecraft.world.entity.npc.AbstractVillager villager) { -+ final net.minecraft.world.entity.player.Player human = villager.getTradingPlayer(); -+ if (human != null) { -+ villager.setTradingPlayer(null); -+ } -+ } -+ // Paper end - Fix villager boat exploit - entity1.setRemoved(Entity.RemovalReason.UNLOADED_WITH_PLAYER, EntityRemoveEvent.Cause.PLAYER_QUIT); // CraftBukkit - add Bukkit remove cause - }); - } diff --git a/patches/server/0470-Add-sendOpLevel-API.patch b/patches/server/0470-Add-sendOpLevel-API.patch deleted file mode 100644 index 77f057bbd2aa..000000000000 --- a/patches/server/0470-Add-sendOpLevel-API.patch +++ /dev/null @@ -1,53 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Mariell Hoversholm -Date: Tue, 29 Dec 2020 15:03:03 +0100 -Subject: [PATCH] Add sendOpLevel API - - -diff --git a/src/main/java/net/minecraft/server/players/PlayerList.java b/src/main/java/net/minecraft/server/players/PlayerList.java -index 9436a9614dea81d14c4f77cc21ca91f85ca87152..9b94ca912f99a3a465f30130b24347fc9aca72ff 100644 ---- a/src/main/java/net/minecraft/server/players/PlayerList.java -+++ b/src/main/java/net/minecraft/server/players/PlayerList.java -@@ -1066,6 +1066,11 @@ public abstract class PlayerList { - } - - private void sendPlayerPermissionLevel(ServerPlayer player, int permissionLevel) { -+ // Paper start - Add sendOpLevel API -+ this.sendPlayerPermissionLevel(player, permissionLevel, true); -+ } -+ public void sendPlayerPermissionLevel(ServerPlayer player, int permissionLevel, boolean recalculatePermissions) { -+ // Paper end - Add sendOpLevel API - if (player.connection != null) { - byte b0; - -@@ -1080,8 +1085,10 @@ public abstract class PlayerList { - player.connection.send(new ClientboundEntityEventPacket(player, b0)); - } - -+ if (recalculatePermissions) { // Paper - Add sendOpLevel API - player.getBukkitEntity().recalculatePermissions(); // CraftBukkit - this.server.getCommands().sendCommands(player); -+ } // Paper - Add sendOpLevel API - } - - public boolean isWhiteListed(GameProfile profile) { -diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java -index 73fde8be0098238582fb3661ae67d4139be417dc..7de53f6750d478a052bc8ade6edac056565fe068 100644 ---- a/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java -+++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java -@@ -677,6 +677,15 @@ public class CraftPlayer extends CraftHumanEntity implements Player { - } - // Paper end - -+ // Paper start - Add sendOpLevel API -+ @Override -+ public void sendOpLevel(byte level) { -+ Preconditions.checkArgument(level >= 0 && level <= 4, "Level must be within [0, 4]"); -+ -+ this.getHandle().getServer().getPlayerList().sendPlayerPermissionLevel(this.getHandle(), level, false); -+ } -+ // Paper end - Add sendOpLevel API -+ - @Override - public void setCompassTarget(Location loc) { - Preconditions.checkArgument(loc != null, "Location cannot be null"); diff --git a/patches/server/0470-Return-chat-component-with-empty-text-instead-of-thr.patch b/patches/server/0470-Return-chat-component-with-empty-text-instead-of-thr.patch new file mode 100644 index 000000000000..20c17b39c054 --- /dev/null +++ b/patches/server/0470-Return-chat-component-with-empty-text-instead-of-thr.patch @@ -0,0 +1,25 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: CDFN +Date: Tue, 7 Jul 2020 17:53:23 +0200 +Subject: [PATCH] Return chat component with empty text instead of throwing + exception + + +diff --git a/src/main/java/net/minecraft/world/inventory/AbstractContainerMenu.java b/src/main/java/net/minecraft/world/inventory/AbstractContainerMenu.java +index 8a03f31d07e467288dd518c99f3f29bc77eac058..3b92fb173f623a05ae99c86d98f2ecdf907f58c4 100644 +--- a/src/main/java/net/minecraft/world/inventory/AbstractContainerMenu.java ++++ b/src/main/java/net/minecraft/world/inventory/AbstractContainerMenu.java +@@ -93,7 +93,12 @@ public abstract class AbstractContainerMenu { + } + private Component title; + public final Component getTitle() { +- Preconditions.checkState(this.title != null, "Title not set"); ++ // Paper start - return chat component with empty text instead of throwing error ++ // Preconditions.checkState(this.title != null, "Title not set"); ++ if (this.title == null){ ++ return Component.literal(""); ++ } ++ // Paper end - return chat component with empty text instead of throwing error + return this.title; + } + public final void setTitle(Component title) { diff --git a/patches/server/0471-Add-RegistryAccess-for-managing-Registries.patch b/patches/server/0471-Add-RegistryAccess-for-managing-Registries.patch deleted file mode 100644 index 54c9cc8744a3..000000000000 --- a/patches/server/0471-Add-RegistryAccess-for-managing-Registries.patch +++ /dev/null @@ -1,1338 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Jake Potrebic -Date: Mon, 27 Feb 2023 18:28:39 -0800 -Subject: [PATCH] Add RegistryAccess for managing Registries - -RegistryAccess is independant from CraftServer and -doesn't require one to be created allowing the -org.bukkit.Registry class to be loaded earlier. - -== AT == -public net.minecraft.server.RegistryLayer STATIC_ACCESS - -diff --git a/src/main/java/io/papermc/paper/registry/PaperRegistries.java b/src/main/java/io/papermc/paper/registry/PaperRegistries.java -new file mode 100644 -index 0000000000000000000000000000000000000000..633b01431750d4b40159a57bf25fb35c6670ff1b ---- /dev/null -+++ b/src/main/java/io/papermc/paper/registry/PaperRegistries.java -@@ -0,0 +1,146 @@ -+package io.papermc.paper.registry; -+ -+import io.papermc.paper.adventure.PaperAdventure; -+import io.papermc.paper.registry.entry.RegistryEntry; -+import java.util.Collections; -+import java.util.IdentityHashMap; -+import java.util.List; -+import java.util.Map; -+import java.util.Objects; -+import net.minecraft.core.Registry; -+import net.minecraft.core.registries.Registries; -+import net.minecraft.resources.ResourceKey; -+import org.bukkit.GameEvent; -+import org.bukkit.JukeboxSong; -+import org.bukkit.Keyed; -+import org.bukkit.MusicInstrument; -+import org.bukkit.block.BlockType; -+import org.bukkit.block.banner.PatternType; -+import org.bukkit.craftbukkit.CraftGameEvent; -+import org.bukkit.craftbukkit.CraftJukeboxSong; -+import org.bukkit.craftbukkit.CraftMusicInstrument; -+import org.bukkit.craftbukkit.block.CraftBlockType; -+import org.bukkit.craftbukkit.block.banner.CraftPatternType; -+import org.bukkit.craftbukkit.damage.CraftDamageType; -+import org.bukkit.craftbukkit.enchantments.CraftEnchantment; -+import org.bukkit.craftbukkit.entity.CraftCat; -+import org.bukkit.craftbukkit.entity.CraftFrog; -+import org.bukkit.craftbukkit.entity.CraftVillager; -+import org.bukkit.craftbukkit.entity.CraftWolf; -+import org.bukkit.craftbukkit.generator.structure.CraftStructure; -+import org.bukkit.craftbukkit.generator.structure.CraftStructureType; -+import org.bukkit.craftbukkit.inventory.CraftItemType; -+import org.bukkit.craftbukkit.inventory.CraftMenuType; -+import org.bukkit.craftbukkit.inventory.trim.CraftTrimMaterial; -+import org.bukkit.craftbukkit.inventory.trim.CraftTrimPattern; -+import org.bukkit.craftbukkit.legacy.FieldRename; -+import org.bukkit.craftbukkit.map.CraftMapCursor; -+import org.bukkit.craftbukkit.potion.CraftPotionEffectType; -+import org.bukkit.craftbukkit.util.CraftNamespacedKey; -+import org.bukkit.damage.DamageType; -+import org.bukkit.enchantments.Enchantment; -+import org.bukkit.entity.Cat; -+import org.bukkit.entity.Frog; -+import org.bukkit.entity.Villager; -+import org.bukkit.entity.Wolf; -+import org.bukkit.entity.memory.MemoryKey; -+import org.bukkit.generator.structure.Structure; -+import org.bukkit.generator.structure.StructureType; -+import org.bukkit.inventory.ItemType; -+import org.bukkit.inventory.MenuType; -+import org.bukkit.inventory.meta.trim.TrimMaterial; -+import org.bukkit.inventory.meta.trim.TrimPattern; -+import org.bukkit.map.MapCursor; -+import org.bukkit.potion.PotionEffectType; -+import org.checkerframework.checker.nullness.qual.NonNull; -+import org.checkerframework.checker.nullness.qual.Nullable; -+import org.checkerframework.framework.qual.DefaultQualifier; -+ -+import static io.papermc.paper.registry.entry.RegistryEntry.apiOnly; -+import static io.papermc.paper.registry.entry.RegistryEntry.entry; -+ -+@DefaultQualifier(NonNull.class) -+public final class PaperRegistries { -+ -+ static final List> REGISTRY_ENTRIES; -+ private static final Map, RegistryEntry> BY_REGISTRY_KEY; -+ private static final Map, RegistryEntry> BY_RESOURCE_KEY; -+ static { -+ REGISTRY_ENTRIES = List.of( -+ // built-ins -+ entry(Registries.GAME_EVENT, RegistryKey.GAME_EVENT, GameEvent.class, CraftGameEvent::new), -+ entry(Registries.STRUCTURE_TYPE, RegistryKey.STRUCTURE_TYPE, StructureType.class, CraftStructureType::new), -+ entry(Registries.INSTRUMENT, RegistryKey.INSTRUMENT, MusicInstrument.class, CraftMusicInstrument::new), -+ entry(Registries.MOB_EFFECT, RegistryKey.MOB_EFFECT, PotionEffectType.class, CraftPotionEffectType::new), -+ entry(Registries.BLOCK, RegistryKey.BLOCK, BlockType.class, CraftBlockType::new), -+ entry(Registries.ITEM, RegistryKey.ITEM, ItemType.class, CraftItemType::new), -+ entry(Registries.CAT_VARIANT, RegistryKey.CAT_VARIANT, Cat.Type.class, CraftCat.CraftType::new), -+ entry(Registries.FROG_VARIANT, RegistryKey.FROG_VARIANT, Frog.Variant.class, CraftFrog.CraftVariant::new), -+ entry(Registries.VILLAGER_PROFESSION, RegistryKey.VILLAGER_PROFESSION, Villager.Profession.class, CraftVillager.CraftProfession::new), -+ entry(Registries.VILLAGER_TYPE, RegistryKey.VILLAGER_TYPE, Villager.Type.class, CraftVillager.CraftType::new), -+ entry(Registries.MAP_DECORATION_TYPE, RegistryKey.MAP_DECORATION_TYPE, MapCursor.Type.class, CraftMapCursor.CraftType::new), -+ entry(Registries.MENU, RegistryKey.MENU, MenuType.class, CraftMenuType::new), -+ -+ // data-drivens -+ entry(Registries.STRUCTURE, RegistryKey.STRUCTURE, Structure.class, CraftStructure::new).delayed(), -+ entry(Registries.TRIM_MATERIAL, RegistryKey.TRIM_MATERIAL, TrimMaterial.class, CraftTrimMaterial::new).delayed(), -+ entry(Registries.TRIM_PATTERN, RegistryKey.TRIM_PATTERN, TrimPattern.class, CraftTrimPattern::new).delayed(), -+ entry(Registries.DAMAGE_TYPE, RegistryKey.DAMAGE_TYPE, DamageType.class, CraftDamageType::new).delayed(), -+ entry(Registries.WOLF_VARIANT, RegistryKey.WOLF_VARIANT, Wolf.Variant.class, CraftWolf.CraftVariant::new).delayed(), -+ entry(Registries.ENCHANTMENT, RegistryKey.ENCHANTMENT, Enchantment.class, CraftEnchantment::new).withSerializationUpdater(FieldRename.ENCHANTMENT_RENAME).delayed(), -+ entry(Registries.JUKEBOX_SONG, RegistryKey.JUKEBOX_SONG, JukeboxSong.class, CraftJukeboxSong::new).delayed(), -+ entry(Registries.BANNER_PATTERN, RegistryKey.BANNER_PATTERN, PatternType.class, CraftPatternType::new).delayed(), -+ -+ // api-only -+ apiOnly(Registries.BIOME, RegistryKey.BIOME, () -> org.bukkit.Registry.BIOME), -+ apiOnly(Registries.PAINTING_VARIANT, RegistryKey.PAINTING_VARIANT, () -> org.bukkit.Registry.ART), -+ apiOnly(Registries.ATTRIBUTE, RegistryKey.ATTRIBUTE, () -> org.bukkit.Registry.ATTRIBUTE), -+ apiOnly(Registries.ENTITY_TYPE, RegistryKey.ENTITY_TYPE, () -> org.bukkit.Registry.ENTITY_TYPE), -+ apiOnly(Registries.PARTICLE_TYPE, RegistryKey.PARTICLE_TYPE, () -> org.bukkit.Registry.PARTICLE_TYPE), -+ apiOnly(Registries.POTION, RegistryKey.POTION, () -> org.bukkit.Registry.POTION), -+ apiOnly(Registries.SOUND_EVENT, RegistryKey.SOUND_EVENT, () -> org.bukkit.Registry.SOUNDS), -+ apiOnly(Registries.MEMORY_MODULE_TYPE, RegistryKey.MEMORY_MODULE_TYPE, () -> (org.bukkit.Registry>) (org.bukkit.Registry) org.bukkit.Registry.MEMORY_MODULE_TYPE), -+ apiOnly(Registries.FLUID, RegistryKey.FLUID, () -> org.bukkit.Registry.FLUID) -+ ); -+ final Map, RegistryEntry> byRegistryKey = new IdentityHashMap<>(REGISTRY_ENTRIES.size()); -+ final Map, RegistryEntry> byResourceKey = new IdentityHashMap<>(REGISTRY_ENTRIES.size()); -+ for (final RegistryEntry entry : REGISTRY_ENTRIES) { -+ byRegistryKey.put(entry.apiKey(), entry); -+ byResourceKey.put(entry.mcKey(), entry); -+ } -+ BY_REGISTRY_KEY = Collections.unmodifiableMap(byRegistryKey); -+ BY_RESOURCE_KEY = Collections.unmodifiableMap(byResourceKey); -+ } -+ -+ @SuppressWarnings("unchecked") -+ public static @Nullable RegistryEntry getEntry(final ResourceKey> resourceKey) { -+ return (RegistryEntry) BY_RESOURCE_KEY.get(resourceKey); -+ } -+ -+ @SuppressWarnings("unchecked") -+ public static @Nullable RegistryEntry getEntry(final RegistryKey registryKey) { -+ return (RegistryEntry) BY_REGISTRY_KEY.get(registryKey); -+ } -+ -+ @SuppressWarnings("unchecked") -+ public static RegistryKey registryFromNms(final ResourceKey> registryResourceKey) { -+ return (RegistryKey) Objects.requireNonNull(BY_RESOURCE_KEY.get(registryResourceKey), registryResourceKey + " doesn't have an api RegistryKey").apiKey(); -+ } -+ -+ @SuppressWarnings("unchecked") -+ public static ResourceKey> registryToNms(final RegistryKey registryKey) { -+ return (ResourceKey>) Objects.requireNonNull(BY_REGISTRY_KEY.get(registryKey), registryKey + " doesn't have an mc registry ResourceKey").mcKey(); -+ } -+ -+ public static TypedKey fromNms(final ResourceKey resourceKey) { -+ return TypedKey.create(registryFromNms(resourceKey.registryKey()), CraftNamespacedKey.fromMinecraft(resourceKey.location())); -+ } -+ -+ @SuppressWarnings({"unchecked", "RedundantCast"}) -+ public static ResourceKey toNms(final TypedKey typedKey) { -+ return ResourceKey.create((ResourceKey>) PaperRegistries.registryToNms(typedKey.registryKey()), PaperAdventure.asVanilla(typedKey.key())); -+ } -+ -+ private PaperRegistries() { -+ } -+} -diff --git a/src/main/java/io/papermc/paper/registry/PaperRegistryAccess.java b/src/main/java/io/papermc/paper/registry/PaperRegistryAccess.java -new file mode 100644 -index 0000000000000000000000000000000000000000..35b6a7c5bac9640332a833bd3627f2bcb1bbf2f3 ---- /dev/null -+++ b/src/main/java/io/papermc/paper/registry/PaperRegistryAccess.java -@@ -0,0 +1,127 @@ -+package io.papermc.paper.registry; -+ -+import io.papermc.paper.registry.entry.ApiRegistryEntry; -+import io.papermc.paper.registry.entry.RegistryEntry; -+import io.papermc.paper.registry.legacy.DelayedRegistry; -+import io.papermc.paper.registry.legacy.DelayedRegistryEntry; -+import io.papermc.paper.registry.legacy.LegacyRegistryIdentifiers; -+import java.util.Map; -+import java.util.NoSuchElementException; -+import java.util.Set; -+import java.util.concurrent.ConcurrentHashMap; -+import java.util.stream.Collectors; -+import net.minecraft.resources.ResourceKey; -+import org.bukkit.Keyed; -+import org.bukkit.Registry; -+import org.checkerframework.checker.nullness.qual.NonNull; -+import org.checkerframework.checker.nullness.qual.Nullable; -+import org.checkerframework.framework.qual.DefaultQualifier; -+import org.jetbrains.annotations.VisibleForTesting; -+ -+import static java.util.Objects.requireNonNull; -+ -+@DefaultQualifier(NonNull.class) -+public class PaperRegistryAccess implements RegistryAccess { -+ -+ // We store the API registries in a memoized supplier, so they can be created on-demand. -+ // These suppliers are added to this map right after the instance of nms.Registry is created before it is loaded. -+ // We want to do registration there, so we have access to the nms.Registry instance in order to wrap it in a CraftRegistry instance. -+ // The memoized Supplier is needed because we *can't* instantiate any CraftRegistry class until **all** the BuiltInRegistries have been added -+ // to this map because that would class-load org.bukkit.Registry which would query this map. -+ private final Map, RegistryHolder> registries = new ConcurrentHashMap<>(); // is "identity" because RegistryKey overrides equals and hashCode -+ -+ public static PaperRegistryAccess instance() { -+ return (PaperRegistryAccess) RegistryAccessHolder.INSTANCE.orElseThrow(() -> new IllegalStateException("No RegistryAccess implementation found")); -+ } -+ -+ @VisibleForTesting -+ public Set> getLoadedServerBackedRegistries() { -+ return this.registries.keySet().stream().filter(registryHolder -> !(PaperRegistries.getEntry(registryHolder) instanceof ApiRegistryEntry)).collect(Collectors.toUnmodifiableSet()); -+ } -+ -+ @SuppressWarnings("unchecked") -+ @Deprecated(forRemoval = true) -+ @Override -+ public @Nullable Registry getRegistry(final Class type) { -+ final @Nullable RegistryKey registryKey = byType(type); -+ // If our mapping from Class -> RegistryKey did not contain the passed type it was either a completely invalid type or a registry -+ // that merely exists as a SimpleRegistry in the org.bukkit.Registry type. We cannot return a registry for these, return null -+ // as per method contract in Bukkit#getRegistry. -+ if (registryKey == null) return null; -+ -+ final @Nullable RegistryEntry entry = PaperRegistries.getEntry(registryKey); -+ final @Nullable RegistryHolder registry = (RegistryHolder) this.registries.get(registryKey); -+ if (registry != null) { -+ // if the registry exists, return right away. Since this is the "legacy" method, we return DelayedRegistry -+ // for the non-builtin Registry instances stored as fields in Registry. -+ return registry.get(); -+ } else if (entry instanceof DelayedRegistryEntry) { -+ // if the registry doesn't exist and the entry is marked as "delayed", we create a registry holder that is empty -+ // which will later be filled with the actual registry. This is so the fields on org.bukkit.Registry can be populated with -+ // registries that don't exist at the time org.bukkit.Registry is statically initialized. -+ final RegistryHolder delayedHolder = new RegistryHolder.Delayed<>(); -+ this.registries.put(registryKey, delayedHolder); -+ return delayedHolder.get(); -+ } else { -+ // if the registry doesn't exist yet or doesn't have a delayed entry, just return null -+ return null; -+ } -+ } -+ -+ @SuppressWarnings("unchecked") -+ @Override -+ public Registry getRegistry(final RegistryKey key) { -+ if (PaperRegistries.getEntry(key) == null) { -+ throw new NoSuchElementException(key + " is not a valid registry key"); -+ } -+ final @Nullable RegistryHolder registryHolder = (RegistryHolder) this.registries.get(key); -+ if (registryHolder == null) { -+ throw new IllegalArgumentException(key + " points to a registry that is not available yet"); -+ } -+ // since this is the getRegistry method that uses the modern RegistryKey, we unwrap any DelayedRegistry instances -+ // that might be returned here. I don't think reference equality is required when doing getRegistry(RegistryKey.WOLF_VARIANT) == Registry.WOLF_VARIANT -+ return possiblyUnwrap(registryHolder.get()); -+ } -+ -+ private static Registry possiblyUnwrap(final Registry registry) { -+ if (registry instanceof final DelayedRegistry delayedRegistry) { // if not coming from legacy, unwrap the delayed registry -+ return delayedRegistry.delegate(); -+ } -+ return registry; -+ } -+ -+ public void registerReloadableRegistry(final ResourceKey> resourceKey, final net.minecraft.core.Registry registry) { -+ this.registerRegistry(resourceKey, registry, true); -+ } -+ -+ public void registerRegistry(final ResourceKey> resourceKey, final net.minecraft.core.Registry registry) { -+ this.registerRegistry(resourceKey, registry, false); -+ } -+ -+ @SuppressWarnings("unchecked") // this method should be called right after any new MappedRegistry instances are created to later be used by the server. -+ private > void registerRegistry(final ResourceKey> resourceKey, final net.minecraft.core.Registry registry, final boolean replace) { -+ final @Nullable RegistryEntry entry = PaperRegistries.getEntry(resourceKey); -+ if (entry == null) { // skip registries that don't have API entries -+ return; -+ } -+ final @Nullable RegistryHolder registryHolder = (RegistryHolder) this.registries.get(entry.apiKey()); -+ if (registryHolder == null || replace) { -+ // if the holder doesn't exist yet, or is marked as "replaceable", put it in the map. -+ this.registries.put(entry.apiKey(), entry.createRegistryHolder(registry)); -+ } else { -+ if (registryHolder instanceof RegistryHolder.Delayed && entry instanceof final DelayedRegistryEntry delayedEntry) { -+ // if the registry holder is delayed, and the entry is marked as "delayed", then load the holder with the CraftRegistry instance that wraps the actual nms Registry. -+ ((RegistryHolder.Delayed) registryHolder).loadFrom(delayedEntry, registry); -+ } else { -+ throw new IllegalArgumentException(resourceKey + " has already been created"); -+ } -+ } -+ } -+ -+ @SuppressWarnings("unchecked") -+ @Deprecated -+ @VisibleForTesting -+ public static @Nullable RegistryKey byType(final Class type) { -+ return (RegistryKey) LegacyRegistryIdentifiers.CLASS_TO_KEY_MAP.get(type); -+ } -+} -diff --git a/src/main/java/io/papermc/paper/registry/RegistryHolder.java b/src/main/java/io/papermc/paper/registry/RegistryHolder.java -new file mode 100644 -index 0000000000000000000000000000000000000000..a31bdd9f02fe75a87fceb2ebe8c36b3232a561cc ---- /dev/null -+++ b/src/main/java/io/papermc/paper/registry/RegistryHolder.java -@@ -0,0 +1,47 @@ -+package io.papermc.paper.registry; -+ -+import com.google.common.base.Suppliers; -+import io.papermc.paper.registry.legacy.DelayedRegistry; -+import io.papermc.paper.registry.legacy.DelayedRegistryEntry; -+import java.util.function.Supplier; -+import org.bukkit.Keyed; -+import org.bukkit.Registry; -+import org.checkerframework.checker.nullness.qual.NonNull; -+import org.checkerframework.framework.qual.DefaultQualifier; -+ -+@DefaultQualifier(NonNull.class) -+public interface RegistryHolder { -+ -+ Registry get(); -+ -+ final class Memoized> implements RegistryHolder { -+ -+ private final Supplier memoizedSupplier; -+ -+ public Memoized(final Supplier supplier) { -+ this.memoizedSupplier = Suppliers.memoize(supplier::get); -+ } -+ -+ public Registry get() { -+ return this.memoizedSupplier.get(); -+ } -+ } -+ -+ final class Delayed> implements RegistryHolder { -+ -+ private final DelayedRegistry delayedRegistry = new DelayedRegistry<>(); -+ -+ @Override -+ public DelayedRegistry get() { -+ return this.delayedRegistry; -+ } -+ -+ void loadFrom(final DelayedRegistryEntry delayedEntry, final net.minecraft.core.Registry registry) { -+ final RegistryHolder delegateHolder = delayedEntry.delegate().createRegistryHolder(registry); -+ if (!(delegateHolder instanceof RegistryHolder.Memoized)) { -+ throw new IllegalArgumentException(delegateHolder + " must be a memoized holder"); -+ } -+ this.delayedRegistry.load(((Memoized) delegateHolder).memoizedSupplier); -+ } -+ } -+} -diff --git a/src/main/java/io/papermc/paper/registry/entry/ApiRegistryEntry.java b/src/main/java/io/papermc/paper/registry/entry/ApiRegistryEntry.java -new file mode 100644 -index 0000000000000000000000000000000000000000..2295b0d145cbaabef5d29482c817575dcbe2ba54 ---- /dev/null -+++ b/src/main/java/io/papermc/paper/registry/entry/ApiRegistryEntry.java -@@ -0,0 +1,27 @@ -+package io.papermc.paper.registry.entry; -+ -+import io.papermc.paper.registry.RegistryHolder; -+import io.papermc.paper.registry.RegistryKey; -+import java.util.function.Supplier; -+import net.minecraft.core.Registry; -+import net.minecraft.resources.ResourceKey; -+import org.bukkit.Keyed; -+ -+public class ApiRegistryEntry extends BaseRegistryEntry { -+ -+ private final Supplier> registrySupplier; -+ -+ protected ApiRegistryEntry( -+ final ResourceKey> mcKey, -+ final RegistryKey apiKey, -+ final Supplier> registrySupplier -+ ) { -+ super(mcKey, apiKey); -+ this.registrySupplier = registrySupplier; -+ } -+ -+ @Override -+ public RegistryHolder createRegistryHolder(final Registry nmsRegistry) { -+ return new RegistryHolder.Memoized<>(this.registrySupplier); -+ } -+} -diff --git a/src/main/java/io/papermc/paper/registry/entry/BaseRegistryEntry.java b/src/main/java/io/papermc/paper/registry/entry/BaseRegistryEntry.java -new file mode 100644 -index 0000000000000000000000000000000000000000..ceb217dbbb84e8bd51365dd47bf91971e364d298 ---- /dev/null -+++ b/src/main/java/io/papermc/paper/registry/entry/BaseRegistryEntry.java -@@ -0,0 +1,27 @@ -+package io.papermc.paper.registry.entry; -+ -+import io.papermc.paper.registry.RegistryKey; -+import net.minecraft.core.Registry; -+import net.minecraft.resources.ResourceKey; -+import org.bukkit.Keyed; -+ -+public abstract class BaseRegistryEntry implements RegistryEntry { // TODO remove Keyed -+ -+ private final ResourceKey> minecraftRegistryKey; -+ private final RegistryKey apiRegistryKey; -+ -+ protected BaseRegistryEntry(final ResourceKey> minecraftRegistryKey, final RegistryKey apiRegistryKey) { -+ this.minecraftRegistryKey = minecraftRegistryKey; -+ this.apiRegistryKey = apiRegistryKey; -+ } -+ -+ @Override -+ public final ResourceKey> mcKey() { -+ return this.minecraftRegistryKey; -+ } -+ -+ @Override -+ public final RegistryKey apiKey() { -+ return this.apiRegistryKey; -+ } -+} -diff --git a/src/main/java/io/papermc/paper/registry/entry/CraftRegistryEntry.java b/src/main/java/io/papermc/paper/registry/entry/CraftRegistryEntry.java -new file mode 100644 -index 0000000000000000000000000000000000000000..9bb4aa926978f117901c9f99c45a6862a1d5ce30 ---- /dev/null -+++ b/src/main/java/io/papermc/paper/registry/entry/CraftRegistryEntry.java -@@ -0,0 +1,51 @@ -+package io.papermc.paper.registry.entry; -+ -+import com.google.common.base.Preconditions; -+import io.papermc.paper.registry.RegistryHolder; -+import io.papermc.paper.registry.RegistryKey; -+import java.util.function.BiFunction; -+import net.minecraft.core.Registry; -+import net.minecraft.resources.ResourceKey; -+import org.bukkit.Keyed; -+import org.bukkit.NamespacedKey; -+import org.bukkit.craftbukkit.CraftRegistry; -+import org.bukkit.craftbukkit.util.ApiVersion; -+import org.checkerframework.checker.nullness.qual.NonNull; -+import org.checkerframework.framework.qual.DefaultQualifier; -+ -+@DefaultQualifier(NonNull.class) -+public class CraftRegistryEntry extends BaseRegistryEntry { // TODO remove Keyed -+ -+ private static final BiFunction EMPTY = (namespacedKey, apiVersion) -> namespacedKey; -+ -+ protected final Class classToPreload; -+ protected final BiFunction minecraftToBukkit; -+ protected BiFunction updater = EMPTY; -+ -+ protected CraftRegistryEntry( -+ final ResourceKey> mcKey, -+ final RegistryKey apiKey, -+ final Class classToPreload, -+ final BiFunction minecraftToBukkit -+ ) { -+ super(mcKey, apiKey); -+ Preconditions.checkArgument(!classToPreload.getPackageName().startsWith("net.minecraft"), classToPreload + " should not be in the net.minecraft package as the class-to-preload"); -+ this.classToPreload = classToPreload; -+ this.minecraftToBukkit = minecraftToBukkit; -+ } -+ -+ @Override -+ public RegistryEntry withSerializationUpdater(final BiFunction updater) { -+ this.updater = updater; -+ return this; -+ } -+ -+ @Override -+ public RegistryHolder createRegistryHolder(final Registry nmsRegistry) { -+ return new RegistryHolder.Memoized<>(() -> this.createApiRegistry(nmsRegistry)); -+ } -+ -+ private CraftRegistry createApiRegistry(final Registry nmsRegistry) { -+ return new CraftRegistry<>(this.classToPreload, nmsRegistry, this.minecraftToBukkit, this.updater); -+ } -+} -diff --git a/src/main/java/io/papermc/paper/registry/entry/RegistryEntry.java b/src/main/java/io/papermc/paper/registry/entry/RegistryEntry.java -new file mode 100644 -index 0000000000000000000000000000000000000000..15991bf13894d850f360a520d1815711d25973ec ---- /dev/null -+++ b/src/main/java/io/papermc/paper/registry/entry/RegistryEntry.java -@@ -0,0 +1,51 @@ -+package io.papermc.paper.registry.entry; -+ -+import io.papermc.paper.registry.RegistryHolder; -+import io.papermc.paper.registry.RegistryKey; -+import io.papermc.paper.registry.legacy.DelayedRegistryEntry; -+import java.util.function.BiFunction; -+import java.util.function.Supplier; -+import net.minecraft.core.Registry; -+import net.minecraft.resources.ResourceKey; -+import org.bukkit.Keyed; -+import org.bukkit.NamespacedKey; -+import org.bukkit.craftbukkit.util.ApiVersion; -+import org.checkerframework.checker.nullness.qual.NonNull; -+import org.checkerframework.framework.qual.DefaultQualifier; -+ -+@DefaultQualifier(NonNull.class) -+public interface RegistryEntry extends RegistryEntryInfo { // TODO remove Keyed -+ -+ RegistryHolder createRegistryHolder(Registry nmsRegistry); -+ -+ default RegistryEntry withSerializationUpdater(final BiFunction updater) { -+ return this; -+ } -+ -+ /** -+ * This should only be used if the registry instance needs to exist early due to the need -+ * to populate a field in {@link org.bukkit.Registry}. Data-driven registries shouldn't exist -+ * as fields, but instead be obtained via {@link io.papermc.paper.registry.RegistryAccess#getRegistry(RegistryKey)} -+ */ -+ @Deprecated -+ default RegistryEntry delayed() { -+ return new DelayedRegistryEntry<>(this); -+ } -+ -+ static RegistryEntry entry( -+ final ResourceKey> mcKey, -+ final RegistryKey apiKey, -+ final Class classToPreload, -+ final BiFunction minecraftToBukkit -+ ) { -+ return new CraftRegistryEntry<>(mcKey, apiKey, classToPreload, minecraftToBukkit); -+ } -+ -+ static RegistryEntry apiOnly( -+ final ResourceKey> mcKey, -+ final RegistryKey apiKey, -+ final Supplier> apiRegistrySupplier -+ ) { -+ return new ApiRegistryEntry<>(mcKey, apiKey, apiRegistrySupplier); -+ } -+} -diff --git a/src/main/java/io/papermc/paper/registry/entry/RegistryEntryInfo.java b/src/main/java/io/papermc/paper/registry/entry/RegistryEntryInfo.java -new file mode 100644 -index 0000000000000000000000000000000000000000..0ae855e80fc9fddfc1feb33c7a9748204828b7cc ---- /dev/null -+++ b/src/main/java/io/papermc/paper/registry/entry/RegistryEntryInfo.java -@@ -0,0 +1,12 @@ -+package io.papermc.paper.registry.entry; -+ -+import io.papermc.paper.registry.RegistryKey; -+import net.minecraft.core.Registry; -+import net.minecraft.resources.ResourceKey; -+ -+public interface RegistryEntryInfo { -+ -+ ResourceKey> mcKey(); -+ -+ RegistryKey apiKey(); -+} -diff --git a/src/main/java/io/papermc/paper/registry/entry/package-info.java b/src/main/java/io/papermc/paper/registry/entry/package-info.java -new file mode 100644 -index 0000000000000000000000000000000000000000..e4c94d6860e0f5b643cde1ded20b5503c02a4866 ---- /dev/null -+++ b/src/main/java/io/papermc/paper/registry/entry/package-info.java -@@ -0,0 +1,5 @@ -+@DefaultQualifier(NonNull.class) -+package io.papermc.paper.registry.entry; -+ -+import org.checkerframework.checker.nullness.qual.NonNull; -+import org.checkerframework.framework.qual.DefaultQualifier; -diff --git a/src/main/java/io/papermc/paper/registry/legacy/DelayedRegistry.java b/src/main/java/io/papermc/paper/registry/legacy/DelayedRegistry.java -new file mode 100644 -index 0000000000000000000000000000000000000000..9400fed345344a0a8e4fb301cca6a1867adf625b ---- /dev/null -+++ b/src/main/java/io/papermc/paper/registry/legacy/DelayedRegistry.java -@@ -0,0 +1,61 @@ -+package io.papermc.paper.registry.legacy; -+ -+import java.util.Iterator; -+import java.util.function.Supplier; -+import java.util.stream.Stream; -+import net.kyori.adventure.key.Key; -+import org.bukkit.Keyed; -+import org.bukkit.NamespacedKey; -+import org.bukkit.Registry; -+import org.bukkit.craftbukkit.CraftRegistry; -+import org.checkerframework.checker.nullness.qual.MonotonicNonNull; -+import org.checkerframework.checker.nullness.qual.Nullable; -+import org.jetbrains.annotations.NotNull; -+ -+/** -+ * This is to support the now-deprecated fields in {@link Registry} for -+ * data-driven registries. -+ */ -+public final class DelayedRegistry> implements Registry { -+ -+ private @MonotonicNonNull Supplier delegate; -+ -+ public void load(final Supplier registry) { -+ if (this.delegate != null) { -+ throw new IllegalStateException("Registry already loaded!"); -+ } -+ this.delegate = registry; -+ } -+ -+ public Registry delegate() { -+ if (this.delegate == null) { -+ throw new IllegalStateException("You are trying to access this registry too early!"); -+ } -+ return this.delegate.get(); -+ } -+ -+ @Override -+ public @Nullable T get(final NamespacedKey key) { -+ return this.delegate().get(key); -+ } -+ -+ @Override -+ public @NotNull T getOrThrow(@NotNull final NamespacedKey key) { -+ return this.delegate().getOrThrow(key); -+ } -+ -+ @Override -+ public Iterator iterator() { -+ return this.delegate().iterator(); -+ } -+ -+ @Override -+ public Stream stream() { -+ return this.delegate().stream(); -+ } -+ -+ @Override -+ public NamespacedKey getKey(final T value) { -+ return this.delegate().getKey(value); -+ } -+} -diff --git a/src/main/java/io/papermc/paper/registry/legacy/DelayedRegistryEntry.java b/src/main/java/io/papermc/paper/registry/legacy/DelayedRegistryEntry.java -new file mode 100644 -index 0000000000000000000000000000000000000000..110b8d559f49f9e4f181b47663962a139a273a72 ---- /dev/null -+++ b/src/main/java/io/papermc/paper/registry/legacy/DelayedRegistryEntry.java -@@ -0,0 +1,26 @@ -+package io.papermc.paper.registry.legacy; -+ -+import io.papermc.paper.registry.RegistryHolder; -+import io.papermc.paper.registry.RegistryKey; -+import io.papermc.paper.registry.entry.RegistryEntry; -+import net.minecraft.core.Registry; -+import net.minecraft.resources.ResourceKey; -+import org.bukkit.Keyed; -+ -+public record DelayedRegistryEntry(RegistryEntry delegate) implements RegistryEntry { -+ -+ @Override -+ public ResourceKey> mcKey() { -+ return this.delegate.mcKey(); -+ } -+ -+ @Override -+ public RegistryKey apiKey() { -+ return this.delegate.apiKey(); -+ } -+ -+ @Override -+ public RegistryHolder createRegistryHolder(final Registry nmsRegistry) { -+ return this.delegate.createRegistryHolder(nmsRegistry); -+ } -+} -diff --git a/src/main/java/io/papermc/paper/registry/legacy/LegacyRegistryIdentifiers.java b/src/main/java/io/papermc/paper/registry/legacy/LegacyRegistryIdentifiers.java -new file mode 100644 -index 0000000000000000000000000000000000000000..83870816cd4c54f94a3c603ffe41c11e457f3dec ---- /dev/null -+++ b/src/main/java/io/papermc/paper/registry/legacy/LegacyRegistryIdentifiers.java -@@ -0,0 +1,33 @@ -+package io.papermc.paper.registry.legacy; -+ -+import com.google.common.collect.ImmutableMap; -+import io.leangen.geantyref.GenericTypeReflector; -+import io.papermc.paper.registry.RegistryKey; -+import java.lang.reflect.Field; -+import java.lang.reflect.ParameterizedType; -+import java.util.Map; -+ -+@Deprecated -+public final class LegacyRegistryIdentifiers { -+ -+ public static final Map, RegistryKey> CLASS_TO_KEY_MAP; -+ -+ static { -+ final ImmutableMap.Builder, RegistryKey> builder = ImmutableMap.builder(); -+ try { -+ for (final Field field : RegistryKey.class.getFields()) { -+ if (field.getType() == RegistryKey.class) { -+ // get the legacy type from the RegistryKey generic parameter on the field -+ final Class legacyType = GenericTypeReflector.erase(((ParameterizedType) field.getGenericType()).getActualTypeArguments()[0]); -+ builder.put(legacyType, (RegistryKey) field.get(null)); -+ } -+ } -+ } catch (final ReflectiveOperationException ex) { -+ throw new RuntimeException(ex); -+ } -+ CLASS_TO_KEY_MAP = builder.build(); -+ } -+ -+ private LegacyRegistryIdentifiers() { -+ } -+} -diff --git a/src/main/java/io/papermc/paper/registry/legacy/package-info.java b/src/main/java/io/papermc/paper/registry/legacy/package-info.java -new file mode 100644 -index 0000000000000000000000000000000000000000..4396982af55872fafbfeaf8161ad6f392726c773 ---- /dev/null -+++ b/src/main/java/io/papermc/paper/registry/legacy/package-info.java -@@ -0,0 +1,5 @@ -+@DefaultQualifier(NonNull.class) -+package io.papermc.paper.registry.legacy; -+ -+import org.checkerframework.checker.nullness.qual.NonNull; -+import org.checkerframework.framework.qual.DefaultQualifier; -diff --git a/src/main/java/net/minecraft/core/registries/BuiltInRegistries.java b/src/main/java/net/minecraft/core/registries/BuiltInRegistries.java -index cbc1658e0df4070605a6b2fbe99167b3bc001223..44b7927081b476813505cab6b3a2da2ec2942c54 100644 ---- a/src/main/java/net/minecraft/core/registries/BuiltInRegistries.java -+++ b/src/main/java/net/minecraft/core/registries/BuiltInRegistries.java -@@ -315,6 +315,7 @@ public class BuiltInRegistries { - ResourceKey> key, R registry, BuiltInRegistries.RegistryBootstrap initializer - ) { - Bootstrap.checkBootstrapCalled(() -> "registry " + key); -+ io.papermc.paper.registry.PaperRegistryAccess.instance().registerRegistry(registry.key(), registry); // Paper - initialize API registry - ResourceLocation resourceLocation = key.location(); - LOADERS.put(resourceLocation, () -> initializer.run(registry)); - WRITABLE_REGISTRY.register((ResourceKey)key, registry, RegistrationInfo.BUILT_IN); // Paper - decompile fix -diff --git a/src/main/java/net/minecraft/resources/RegistryDataLoader.java b/src/main/java/net/minecraft/resources/RegistryDataLoader.java -index deaf62913850a0e0fdffd3d52fd383bcdda979af..abadf4abe08dc3bb6612b42cbb3f7df3ffa28ce9 100644 ---- a/src/main/java/net/minecraft/resources/RegistryDataLoader.java -+++ b/src/main/java/net/minecraft/resources/RegistryDataLoader.java -@@ -299,6 +299,7 @@ public class RegistryDataLoader { - - RegistryDataLoader.Loader create(Lifecycle lifecycle, Map, Exception> errors) { - WritableRegistry writableRegistry = new MappedRegistry<>(this.key, lifecycle); -+ io.papermc.paper.registry.PaperRegistryAccess.instance().registerRegistry(this.key, writableRegistry); // Paper - initialize API registry - return new RegistryDataLoader.Loader<>(this, writableRegistry, errors); - } - -diff --git a/src/main/java/net/minecraft/server/ReloadableServerRegistries.java b/src/main/java/net/minecraft/server/ReloadableServerRegistries.java -index 1dd22f11b7e2983a3069dea94c0f02b43ff1f736..397bdacab9517354875ebc0bc68d35059b3c318b 100644 ---- a/src/main/java/net/minecraft/server/ReloadableServerRegistries.java -+++ b/src/main/java/net/minecraft/server/ReloadableServerRegistries.java -@@ -60,6 +60,7 @@ public class ReloadableServerRegistries { - return CompletableFuture.supplyAsync( - () -> { - WritableRegistry writableRegistry = new MappedRegistry<>(type.registryKey(), Lifecycle.experimental()); -+ io.papermc.paper.registry.PaperRegistryAccess.instance().registerReloadableRegistry(type.registryKey(), writableRegistry); // Paper - register reloadable registry - Map map = new HashMap<>(); - String string = Registries.elementsDirPath(type.registryKey()); - SimpleJsonResourceReloadListener.scanDirectory(resourceManager, string, GSON, map); -diff --git a/src/main/java/org/bukkit/craftbukkit/CraftRegistry.java b/src/main/java/org/bukkit/craftbukkit/CraftRegistry.java -index 6216db1811565e0d25b0d63f579f0a5ba69876a7..3dbdfc2fb973c3c9aecc6582451071e8a939f5f0 100644 ---- a/src/main/java/org/bukkit/craftbukkit/CraftRegistry.java -+++ b/src/main/java/org/bukkit/craftbukkit/CraftRegistry.java -@@ -122,81 +122,12 @@ public class CraftRegistry implements Registry { - + ", this can happen if a plugin creates its own registry entry with out properly registering it."); - } - -- /** -- * Note: Newly added registries should also be added to RegistriesArgumentProvider in the test package -- * -- * @param bukkitClass the bukkit class of the registry -- * @param registryHolder the minecraft registry holder -- * @return the bukkit registry of the provided class -- */ -- public static Registry createRegistry(Class bukkitClass, RegistryAccess registryHolder) { -- if (bukkitClass == Enchantment.class) { -- return new CraftRegistry<>(Enchantment.class, registryHolder.registryOrThrow(Registries.ENCHANTMENT), CraftEnchantment::new, FieldRename.ENCHANTMENT_RENAME); -- } -- if (bukkitClass == GameEvent.class) { -- return new CraftRegistry<>(GameEvent.class, registryHolder.registryOrThrow(Registries.GAME_EVENT), CraftGameEvent::new, FieldRename.NONE); -- } -- if (bukkitClass == MusicInstrument.class) { -- return new CraftRegistry<>(MusicInstrument.class, registryHolder.registryOrThrow(Registries.INSTRUMENT), CraftMusicInstrument::new, FieldRename.NONE); -- } -- if (bukkitClass == MenuType.class) { -- return new CraftRegistry<>(MenuType.class, registryHolder.registryOrThrow(Registries.MENU), CraftMenuType::new, FieldRename.NONE); -- } -- if (bukkitClass == PotionEffectType.class) { -- return new CraftRegistry<>(PotionEffectType.class, registryHolder.registryOrThrow(Registries.MOB_EFFECT), CraftPotionEffectType::new, FieldRename.NONE); -- } -- if (bukkitClass == Structure.class) { -- return new CraftRegistry<>(Structure.class, registryHolder.registryOrThrow(Registries.STRUCTURE), CraftStructure::new, FieldRename.NONE); -- } -- if (bukkitClass == StructureType.class) { -- return new CraftRegistry<>(StructureType.class, registryHolder.registryOrThrow(Registries.STRUCTURE_TYPE), CraftStructureType::new, FieldRename.NONE); -- } -- if (bukkitClass == Villager.Type.class) { -- return new CraftRegistry<>(Villager.Type.class, registryHolder.registryOrThrow(Registries.VILLAGER_TYPE), CraftVillager.CraftType::new, FieldRename.NONE); -- } -- if (bukkitClass == Villager.Profession.class) { -- return new CraftRegistry<>(Villager.Profession.class, registryHolder.registryOrThrow(Registries.VILLAGER_PROFESSION), CraftVillager.CraftProfession::new, FieldRename.NONE); -- } -- if (bukkitClass == TrimMaterial.class) { -- return new CraftRegistry<>(TrimMaterial.class, registryHolder.registryOrThrow(Registries.TRIM_MATERIAL), CraftTrimMaterial::new, FieldRename.NONE); -- } -- if (bukkitClass == TrimPattern.class) { -- return new CraftRegistry<>(TrimPattern.class, registryHolder.registryOrThrow(Registries.TRIM_PATTERN), CraftTrimPattern::new, FieldRename.NONE); -- } -- if (bukkitClass == DamageType.class) { -- return new CraftRegistry<>(DamageType.class, registryHolder.registryOrThrow(Registries.DAMAGE_TYPE), CraftDamageType::new, FieldRename.NONE); -- } -- if (bukkitClass == JukeboxSong.class) { -- return new CraftRegistry<>(JukeboxSong.class, registryHolder.registryOrThrow(Registries.JUKEBOX_SONG), CraftJukeboxSong::new, FieldRename.NONE); -- } -- if (bukkitClass == Wolf.Variant.class) { -- return new CraftRegistry<>(Wolf.Variant.class, registryHolder.registryOrThrow(Registries.WOLF_VARIANT), CraftWolf.CraftVariant::new, FieldRename.NONE); -- } -- if (bukkitClass == BlockType.class) { -- return new CraftRegistry<>(BlockType.class, registryHolder.registryOrThrow(Registries.BLOCK), CraftBlockType::new, FieldRename.NONE); -- } -- if (bukkitClass == ItemType.class) { -- return new CraftRegistry<>(ItemType.class, registryHolder.registryOrThrow(Registries.ITEM), CraftItemType::new, FieldRename.NONE); -- } -- if (bukkitClass == Frog.Variant.class) { -- return new CraftRegistry<>(Frog.Variant.class, registryHolder.registryOrThrow(Registries.FROG_VARIANT), CraftFrog.CraftVariant::new, FieldRename.NONE); -- } -- if (bukkitClass == Cat.Type.class) { -- return new CraftRegistry<>(Cat.Type.class, registryHolder.registryOrThrow(Registries.CAT_VARIANT), CraftCat.CraftType::new, FieldRename.NONE); -- } -- if (bukkitClass == MapCursor.Type.class) { -- return new CraftRegistry<>(MapCursor.Type.class, registryHolder.registryOrThrow(Registries.MAP_DECORATION_TYPE), CraftMapCursor.CraftType::new, FieldRename.NONE); -- } -- if (bukkitClass == PatternType.class) { -- return new CraftRegistry<>(PatternType.class, registryHolder.registryOrThrow(Registries.BANNER_PATTERN), CraftPatternType::new, FieldRename.NONE); -- } -- -- return null; -- } -+ // Paper - move to PaperRegistries - -+ // Paper - NOTE: As long as all uses of the method below relate to *serialization* via ConfigurationSerializable, it's fine - public static B get(Registry bukkit, NamespacedKey namespacedKey, ApiVersion apiVersion) { - if (bukkit instanceof CraftRegistry craft) { -- return craft.get(namespacedKey, apiVersion); -+ return craft.get(craft.serializationUpdater.apply(namespacedKey, apiVersion)); // Paper - } - - if (bukkit instanceof Registry.SimpleRegistry simple) { -@@ -222,23 +153,21 @@ public class CraftRegistry implements Registry { - return bukkit.get(namespacedKey); - } - -- private final Class bukkitClass; -+ private final Class bukkitClass; // Paper - relax preload class - private final Map cache = new HashMap<>(); - private final net.minecraft.core.Registry minecraftRegistry; - private final BiFunction minecraftToBukkit; -- private final BiFunction updater; -+ private final BiFunction serializationUpdater; // Paper - rename to make it *clear* what it is *only* for - private boolean init; - -- public CraftRegistry(Class bukkitClass, net.minecraft.core.Registry minecraftRegistry, BiFunction minecraftToBukkit, BiFunction updater) { -+ public CraftRegistry(Class bukkitClass, net.minecraft.core.Registry minecraftRegistry, BiFunction minecraftToBukkit, BiFunction serializationUpdater) { // Paper - relax preload class - this.bukkitClass = bukkitClass; - this.minecraftRegistry = minecraftRegistry; - this.minecraftToBukkit = minecraftToBukkit; -- this.updater = updater; -+ this.serializationUpdater = serializationUpdater; - } - -- public B get(NamespacedKey namespacedKey, ApiVersion apiVersion) { -- return this.get(this.updater.apply(namespacedKey, apiVersion)); -- } -+ // Paper - inline into CraftRegistry#get(Registry, NamespacedKey, ApiVersion) above - - @Override - public B get(NamespacedKey namespacedKey) { -diff --git a/src/main/java/org/bukkit/craftbukkit/CraftServer.java b/src/main/java/org/bukkit/craftbukkit/CraftServer.java -index 25bfb93568ea0a6c0b827c6d6a736f950981144e..f402ef662779e096ee354b9edd66cca785b85f33 100644 ---- a/src/main/java/org/bukkit/craftbukkit/CraftServer.java -+++ b/src/main/java/org/bukkit/craftbukkit/CraftServer.java -@@ -281,7 +281,7 @@ public final class CraftServer implements Server { - protected final DedicatedServer console; - protected final DedicatedPlayerList playerList; - private final Map worlds = new LinkedHashMap(); -- private final Map, Registry> registries = new HashMap<>(); -+ // private final Map, Registry> registries = new HashMap<>(); // Paper - replace with RegistryAccess - private YamlConfiguration configuration; - private YamlConfiguration commandsConfiguration; - private final Yaml yaml = new Yaml(new SafeConstructor(new LoaderOptions())); -@@ -429,6 +429,7 @@ public final class CraftServer implements Server { - } - - private void loadCompatibilities() { -+ if (true) return; // Paper - Big nope - ConfigurationSection compatibilities = this.configuration.getConfigurationSection("settings.compatibility"); - if (compatibilities == null) { - this.activeCompatibilities = Collections.emptySet(); -@@ -2728,7 +2729,7 @@ public final class CraftServer implements Server { - - @Override - public Registry getRegistry(Class aClass) { -- return (Registry) this.registries.computeIfAbsent(aClass, key -> CraftRegistry.createRegistry(aClass, this.console.registryAccess())); -+ return io.papermc.paper.registry.RegistryAccess.registryAccess().getRegistry(aClass); // Paper - replace with RegistryAccess - } - - @Deprecated -diff --git a/src/main/java/org/bukkit/craftbukkit/legacy/FieldRename.java b/src/main/java/org/bukkit/craftbukkit/legacy/FieldRename.java -index 1f58b92c17d28e14621e8dc28042a5368f1f4a1f..ef80e6b4dff557daaab1b9fde4d8d40171017e6c 100644 ---- a/src/main/java/org/bukkit/craftbukkit/legacy/FieldRename.java -+++ b/src/main/java/org/bukkit/craftbukkit/legacy/FieldRename.java -@@ -51,11 +51,14 @@ public class FieldRename { - }; - } - -- @RequireCompatibility("allow-old-keys-in-registry") -- public static T get(Registry registry, NamespacedKey namespacedKey) { -- // We don't have version-specific changes, so just use current, and don't inject a version -- return CraftRegistry.get(registry, namespacedKey, ApiVersion.CURRENT); -- } -+ // Paper start - absolutely not, having this as an expectation for plugin developers opens a huge -+ // can of worms in the future, especially if mojang comes back and reuses some old key -+ //@RequireCompatibility("allow-old-keys-in-registry") -+ //public static T get(Registry registry, NamespacedKey namespacedKey) { -+ // // We don't have version-specific changes, so just use current, and don't inject a version -+ // return CraftRegistry.get(registry, namespacedKey, ApiVersion.CURRENT); -+ //} -+ // Paper end - - // PatternType - private static final FieldRenameData PATTERN_TYPE_DATA = FieldRenameData.Builder.newBuilder() -diff --git a/src/main/java/org/bukkit/craftbukkit/util/Commodore.java b/src/main/java/org/bukkit/craftbukkit/util/Commodore.java -index b85223ebff4dbb8aa74b501663afc87ef11e2a96..760f56d36f0e4a74b58628408a286a499d6664ec 100644 ---- a/src/main/java/org/bukkit/craftbukkit/util/Commodore.java -+++ b/src/main/java/org/bukkit/craftbukkit/util/Commodore.java -@@ -215,20 +215,10 @@ public class Commodore { - - public byte[] convert(byte[] b, final String pluginName, final ApiVersion pluginVersion, final Set activeCompatibilities) { - final boolean modern = pluginVersion.isNewerThanOrSameAs(ApiVersion.FLATTENING); -- final boolean enumCompatibility = pluginVersion.isOlderThanOrSameAs(ApiVersion.getOrCreateVersion("1.20.6")) && activeCompatibilities.contains("enum-compatibility-mode"); - ClassReader cr = new ClassReader(b); - ClassWriter cw = new ClassWriter(cr, 0); - -- List methodEnumSignatures = Commodore.getMethodSignatures(b); -- Multimap enumLessToEnum = HashMultimap.create(); -- for (String method : methodEnumSignatures) { -- enumLessToEnum.put(method.replace("Ljava/lang/Enum;", "Ljava/lang/Object;"), method); -- } -- - ClassVisitor visitor = cw; -- if (enumCompatibility) { -- visitor = new LimitedClassRemapper(cw, new SimpleRemapper(Commodore.ENUM_RENAMES)); -- } - - visitor = io.papermc.paper.pluginremap.reflect.ReflectionRemapper.visitor(visitor); // Paper - cr.accept(new ClassRemapper(new ClassVisitor(Opcodes.ASM9, visitor) { -@@ -295,15 +285,6 @@ public class Commodore { - - @Override - public MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) { -- if (enumCompatibility && (access & Opcodes.ACC_SYNTHETIC) != 0 && (access & Opcodes.ACC_BRIDGE) != 0 && desc.contains("Ljava/lang/Object;")) { -- // SPIGOT-7820: Do not use object method if enum method is present -- // The object method does only redirect to the enum method -- Collection result = enumLessToEnum.get(desc.replace("Ljava/lang/Enum;", "Ljava/lang/Object;") + " " + name); -- if (result.size() == 2) { -- name = name + "_BUKKIT_UNUSED"; -- } -- } -- - return new MethodVisitor(this.api, super.visitMethod(access, name, desc, signature, exceptions)) { - // Paper start - Plugin rewrites - @Override -diff --git a/src/main/resources/META-INF/services/io.papermc.paper.registry.RegistryAccess b/src/main/resources/META-INF/services/io.papermc.paper.registry.RegistryAccess -new file mode 100644 -index 0000000000000000000000000000000000000000..8a083d45004f82fc9c51c219fb20f34624adb501 ---- /dev/null -+++ b/src/main/resources/META-INF/services/io.papermc.paper.registry.RegistryAccess -@@ -0,0 +1 @@ -+io.papermc.paper.registry.PaperRegistryAccess -diff --git a/src/main/resources/configurations/bukkit.yml b/src/main/resources/configurations/bukkit.yml -index 543e37737bc6fdca23ed9ed0606805d345515a5a..eef7c125b2689f29cae5464659eacdf33f5695b2 100644 ---- a/src/main/resources/configurations/bukkit.yml -+++ b/src/main/resources/configurations/bukkit.yml -@@ -23,9 +23,6 @@ settings: - shutdown-message: Server closed - minimum-api: none - use-map-color-cache: true -- compatibility: -- allow-old-keys-in-registry: false -- enum-compatibility-mode: false - spawn-limits: - monsters: 70 - animals: 10 -diff --git a/src/test/java/io/papermc/paper/registry/LegacyRegistryIdentifierTest.java b/src/test/java/io/papermc/paper/registry/LegacyRegistryIdentifierTest.java -new file mode 100644 -index 0000000000000000000000000000000000000000..a80b0ded74c0be657e734de61cbf5e32e16c26a8 ---- /dev/null -+++ b/src/test/java/io/papermc/paper/registry/LegacyRegistryIdentifierTest.java -@@ -0,0 +1,21 @@ -+package io.papermc.paper.registry; -+ -+import org.bukkit.GameEvent; -+import org.bukkit.MusicInstrument; -+import org.bukkit.inventory.meta.trim.TrimPattern; -+import org.bukkit.support.environment.VanillaFeature; -+import org.junit.jupiter.api.Test; -+ -+import static org.junit.jupiter.api.Assertions.assertSame; -+ -+@Deprecated -+@VanillaFeature -+class LegacyRegistryIdentifierTest { -+ -+ @Test -+ void testSeveralConversions() { -+ assertSame(RegistryKey.GAME_EVENT, PaperRegistryAccess.byType(GameEvent.class)); -+ assertSame(RegistryKey.TRIM_PATTERN, PaperRegistryAccess.byType(TrimPattern.class)); -+ assertSame(RegistryKey.INSTRUMENT, PaperRegistryAccess.byType(MusicInstrument.class)); -+ } -+} -diff --git a/src/test/java/io/papermc/paper/registry/RegistryKeyTest.java b/src/test/java/io/papermc/paper/registry/RegistryKeyTest.java -index fe52d229c31526cc32f6422328efe92edf75a7ff..67dadb1765a5ef9a391a459224e233f38201f5d5 100644 ---- a/src/test/java/io/papermc/paper/registry/RegistryKeyTest.java -+++ b/src/test/java/io/papermc/paper/registry/RegistryKeyTest.java -@@ -1,15 +1,19 @@ - package io.papermc.paper.registry; - -+import io.papermc.paper.registry.entry.RegistryEntry; - import java.util.Optional; - import java.util.stream.Stream; - import net.minecraft.core.Registry; - import net.minecraft.resources.ResourceKey; - import net.minecraft.resources.ResourceLocation; --import org.bukkit.support.AbstractTestingBase; -+import org.bukkit.support.RegistryHelper; -+import org.bukkit.support.environment.AllFeatures; -+import org.checkerframework.checker.nullness.qual.Nullable; - import org.junit.jupiter.api.BeforeAll; - import org.junit.jupiter.params.ParameterizedTest; - import org.junit.jupiter.params.provider.MethodSource; - -+import static org.junit.jupiter.api.Assertions.assertNotNull; - import static org.junit.jupiter.api.Assertions.assertTrue; - - @AllFeatures -@@ -29,6 +33,12 @@ class RegistryKeyTest { - void testApiRegistryKeysExist(final RegistryKey key) { - final Optional> registry = RegistryHelper.getRegistry().registry(ResourceKey.createRegistryKey(ResourceLocation.parse(key.key().asString()))); - assertTrue(registry.isPresent(), "Missing vanilla registry for " + key.key().asString()); -+ } - -+ @ParameterizedTest -+ @MethodSource("data") -+ void testRegistryEntryExists(final RegistryKey key) { -+ final @Nullable RegistryEntry entry = PaperRegistries.getEntry(key); -+ assertNotNull(entry, "Missing PaperRegistries entry for " + key); - } - } -diff --git a/src/test/java/org/bukkit/registry/RegistryArgumentAddedTest.java b/src/test/java/org/bukkit/registry/RegistryArgumentAddedTest.java -index b65a3ee68c177da7ef5a57608187dc1672257c7f..c1016e0eb00e952551370c874e8d678fef8ba3dc 100644 ---- a/src/test/java/org/bukkit/registry/RegistryArgumentAddedTest.java -+++ b/src/test/java/org/bukkit/registry/RegistryArgumentAddedTest.java -@@ -22,14 +22,17 @@ public class RegistryArgumentAddedTest { - // Make sure every registry is created - Class.forName(Registry.class.getName()); - -- Set> loadedRegistries = new HashSet<>(AllFeaturesExtension.getRealRegistries().keySet()); -- Set> notFound = new HashSet<>(); -+ // Paper start -+ Set> loadedRegistries = java.util.Collections.newSetFromMap(new java.util.IdentityHashMap<>()); -+ loadedRegistries.addAll(io.papermc.paper.registry.PaperRegistryAccess.instance().getLoadedServerBackedRegistries()); -+ // Paper end -+ Set> notFound = new HashSet<>(); // Paper - - RegistriesArgumentProvider - .getData() - .map(Arguments::get) - .map(array -> array[0]) -- .map(clazz -> (Class) clazz) -+ .map(clazz -> (io.papermc.paper.registry.RegistryKey) clazz) // Paper - .forEach(clazz -> { - if (!loadedRegistries.remove(clazz)) { - notFound.add(clazz); -diff --git a/src/test/java/org/bukkit/registry/RegistryConversionTest.java b/src/test/java/org/bukkit/registry/RegistryConversionTest.java -index e97328b95973db52d44bc4d0aefd8eb69f2ebdea..01e351f4e292efe78fc1a1db0f31b2c0a313b101 100644 ---- a/src/test/java/org/bukkit/registry/RegistryConversionTest.java -+++ b/src/test/java/org/bukkit/registry/RegistryConversionTest.java -@@ -41,9 +41,9 @@ public class RegistryConversionTest { - - @Order(1) - @RegistriesTest -- public void testHandleableImplementation(Class clazz) { -+ public void testHandleableImplementation(io.papermc.paper.registry.RegistryKey type, Class clazz) { // Paper - Set> notImplemented = new HashSet<>(); -- Registry registry = Bukkit.getRegistry(clazz); -+ Registry registry = io.papermc.paper.registry.RegistryAccess.registryAccess().getRegistry(type); // Paper - - for (Keyed item : registry) { - if (!(item instanceof Handleable)) { -@@ -63,7 +63,7 @@ public class RegistryConversionTest { - - @Order(2) - @RegistriesTest -- public void testMinecraftToBukkitPresent(Class clazz, ResourceKey> registryKey, -+ public void testMinecraftToBukkitPresent(io.papermc.paper.registry.RegistryKey type, Class clazz, ResourceKey> registryKey, - Class craftClazz, Class minecraftClazz, boolean newMethod) { - String methodName = (newMethod) ? RegistryConversionTest.MINECRAFT_TO_BUKKIT_NEW : RegistryConversionTest.MINECRAFT_TO_BUKKIT; - Method method = null; -@@ -112,7 +112,7 @@ public class RegistryConversionTest { - - @Order(2) - @RegistriesTest -- public void testBukkitToMinecraftPresent(Class clazz, ResourceKey> registryKey, -+ public void testBukkitToMinecraftPresent(io.papermc.paper.registry.RegistryKey type, Class clazz, ResourceKey> registryKey, - Class craftClazz, Class minecraftClazz, boolean newMethod) { - String methodName = (newMethod) ? RegistryConversionTest.BUKKIT_TO_MINECRAFT_NEW : RegistryConversionTest.BUKKIT_TO_MINECRAFT; - Method method = null; -@@ -159,9 +159,9 @@ public class RegistryConversionTest { - """, minecraftClazz.getName(), methodName, clazz.getSimpleName()); - } - -- @Order(2) -+ @Order(3) - @RegistriesTest -- public void testMinecraftToBukkitNullValue(Class clazz) throws IllegalAccessException { -+ public void testMinecraftToBukkitNullValue(io.papermc.paper.registry.RegistryKey type, Class clazz) throws IllegalAccessException { // Paper - this.checkValidMinecraftToBukkit(clazz); - - try { -@@ -180,7 +180,7 @@ public class RegistryConversionTest { - - @Order(3) - @RegistriesTest -- public void testBukkitToMinecraftNullValue(Class clazz) throws IllegalAccessException { -+ public void testBukkitToMinecraftNullValue(io.papermc.paper.registry.RegistryKey type, Class clazz) throws IllegalAccessException { // Paper - this.checkValidBukkitToMinecraft(clazz); - - try { -@@ -199,14 +199,14 @@ public class RegistryConversionTest { - - @Order(3) - @RegistriesTest -- public void testMinecraftToBukkit(Class clazz) { -+ public void testMinecraftToBukkit(io.papermc.paper.registry.RegistryKey type, Class clazz) { // Paper - this.checkValidMinecraftToBukkit(clazz); - this.checkValidHandle(clazz); - - Map notMatching = new HashMap<>(); - Method method = RegistryConversionTest.MINECRAFT_TO_BUKKIT_METHODS.get(clazz); - -- RegistryArgumentProvider.getValues(clazz).map(Arguments::get).forEach(arguments -> { -+ RegistryArgumentProvider.getValues(type).map(Arguments::get).forEach(arguments -> { // Paper - Keyed bukkit = (Keyed) arguments[0]; - Object minecraft = arguments[1]; - -@@ -230,14 +230,14 @@ public class RegistryConversionTest { - - @Order(3) - @RegistriesTest -- public void testBukkitToMinecraft(Class clazz) { -+ public void testBukkitToMinecraft(io.papermc.paper.registry.RegistryKey type, Class clazz) { // Paper - this.checkValidBukkitToMinecraft(clazz); - this.checkValidHandle(clazz); - - Map notMatching = new HashMap<>(); - Method method = RegistryConversionTest.BUKKIT_TO_MINECRAFT_METHODS.get(clazz); - -- RegistryArgumentProvider.getValues(clazz).map(Arguments::get).forEach(arguments -> { -+ RegistryArgumentProvider.getValues(type).map(Arguments::get).forEach(arguments -> { // Paper - Keyed bukkit = (Keyed) arguments[0]; - Object minecraft = arguments[1]; - -@@ -265,7 +265,7 @@ public class RegistryConversionTest { - */ - @Order(3) - @RegistriesTest -- public void testMinecraftToBukkitNoValidMinecraft(Class clazz, ResourceKey> registryKey, -+ public void testMinecraftToBukkitNoValidMinecraft(io.papermc.paper.registry.RegistryKey type, Class clazz, ResourceKey> registryKey, // Paper - Class craftClazz, Class minecraftClazz) throws IllegalAccessException { - this.checkValidMinecraftToBukkit(clazz); - -diff --git a/src/test/java/org/bukkit/support/extension/AllFeaturesExtension.java b/src/test/java/org/bukkit/support/extension/AllFeaturesExtension.java -index e9eb521419bbacb03d7000ace355f2a9f5a6a9c5..8fef8421e3cf87913746a314a477634bd3e99300 100644 ---- a/src/test/java/org/bukkit/support/extension/AllFeaturesExtension.java -+++ b/src/test/java/org/bukkit/support/extension/AllFeaturesExtension.java -@@ -39,22 +39,7 @@ public class AllFeaturesExtension extends BaseExtension { - - Bukkit.setServer(server); - -- when(server.getRegistry(any())) -- .then(invocation -> { -- Class keyed = invocation.getArgument(0); -- if (spyRegistries.containsKey(keyed)) { -- return spyRegistries.get(keyed); -- } -- -- Registry registry = CraftRegistry.createRegistry(keyed, RegistryHelper.getRegistry()); -- realRegistries.put(keyed, registry); -- -- Registry spy = mock(registry.getClass(), withSettings().stubOnly().spiedInstance(registry).defaultAnswer(CALLS_REAL_METHODS)); -- -- spyRegistries.put(keyed, spy); -- -- return spy; -- }); -+ // Paper - Add RegistryAccess for managing registries - replaced with registry access - - CraftRegistry.setMinecraftRegistry(RegistryHelper.getRegistry()); - } -diff --git a/src/test/java/org/bukkit/support/extension/LegacyExtension.java b/src/test/java/org/bukkit/support/extension/LegacyExtension.java -index 94cf52cf7603e6814682c92b26fcf03a8b927838..c9c3227c3b7fa36ed80f2dc828885a0128e1e3d0 100644 ---- a/src/test/java/org/bukkit/support/extension/LegacyExtension.java -+++ b/src/test/java/org/bukkit/support/extension/LegacyExtension.java -@@ -30,11 +30,7 @@ public class LegacyExtension extends BaseExtension { - - Bukkit.setServer(server); - -- when(server.getRegistry(any())) -- .then(invocation -> { -- Class keyed = invocation.getArgument(0); -- return registries.computeIfAbsent(keyed, k -> CraftRegistry.createRegistry(keyed, RegistryHelper.getRegistry())); -- }); -+ // Paper - Add RegistryAccess for managing registries - replaced with registry access - - CraftRegistry.setMinecraftRegistry(RegistryHelper.getRegistry()); - } -diff --git a/src/test/java/org/bukkit/support/extension/SlowExtension.java b/src/test/java/org/bukkit/support/extension/SlowExtension.java -index e0ce6836d4365c36303f6c675a75ef6a9b047b92..87364f223fbd6185b041138550fcb6e3ed07d1ae 100644 ---- a/src/test/java/org/bukkit/support/extension/SlowExtension.java -+++ b/src/test/java/org/bukkit/support/extension/SlowExtension.java -@@ -30,11 +30,7 @@ public class SlowExtension extends BaseExtension { - - Bukkit.setServer(server); - -- when(server.getRegistry(any())) -- .then(invocation -> { -- Class keyed = invocation.getArgument(0); -- return registries.computeIfAbsent(keyed, k -> CraftRegistry.createRegistry(keyed, RegistryHelper.getRegistry())); -- }); -+ // Paper - Add RegistryAccess for managing registries - replaced with registry access - - CraftRegistry.setMinecraftRegistry(RegistryHelper.getRegistry()); - } -diff --git a/src/test/java/org/bukkit/support/extension/VanillaFeatureExtension.java b/src/test/java/org/bukkit/support/extension/VanillaFeatureExtension.java -index bbd5dd5b27937ddc3d8c57f2b604331495b0f311..626c3033e36897846fe84a77d05e2e91a15598e5 100644 ---- a/src/test/java/org/bukkit/support/extension/VanillaFeatureExtension.java -+++ b/src/test/java/org/bukkit/support/extension/VanillaFeatureExtension.java -@@ -30,11 +30,7 @@ public class VanillaFeatureExtension extends BaseExtension { - - Bukkit.setServer(server); - -- when(server.getRegistry(any())) -- .then(invocation -> { -- Class keyed = invocation.getArgument(0); -- return registries.computeIfAbsent(keyed, k -> CraftRegistry.createRegistry(keyed, RegistryHelper.getRegistry())); -- }); -+ // Paper - Add RegistryAccess for managing registries - replaced with registry access - - CraftRegistry.setMinecraftRegistry(RegistryHelper.getRegistry()); - } -diff --git a/src/test/java/org/bukkit/support/provider/RegistriesArgumentProvider.java b/src/test/java/org/bukkit/support/provider/RegistriesArgumentProvider.java -index 185f219b23ac57e15f8d0167b0077b7103a2f3f9..f4ba15a1b4b43822bd81b513af56c6667237c327 100644 ---- a/src/test/java/org/bukkit/support/provider/RegistriesArgumentProvider.java -+++ b/src/test/java/org/bukkit/support/provider/RegistriesArgumentProvider.java -@@ -1,6 +1,7 @@ - package org.bukkit.support.provider; - - import com.google.common.collect.Lists; -+import io.papermc.paper.registry.RegistryKey; - import java.util.List; - import java.util.stream.Stream; - import net.minecraft.core.registries.Registries; -@@ -61,36 +62,35 @@ public class RegistriesArgumentProvider implements ArgumentsProvider { - private static final List DATA = Lists.newArrayList(); - - static { -- // Order: Bukkit class, Minecraft Registry key, CraftBukkit class, Minecraft class -- register(Enchantment.class, Registries.ENCHANTMENT, CraftEnchantment.class, net.minecraft.world.item.enchantment.Enchantment.class); -- register(GameEvent.class, Registries.GAME_EVENT, CraftGameEvent.class, net.minecraft.world.level.gameevent.GameEvent.class); -- register(MusicInstrument.class, Registries.INSTRUMENT, CraftMusicInstrument.class, Instrument.class); -- register(MenuType.class, Registries.MENU, CraftMenuType.class, net.minecraft.world.inventory.MenuType.class); -- register(PotionEffectType.class, Registries.MOB_EFFECT, CraftPotionEffectType.class, MobEffect.class); -- register(Structure.class, Registries.STRUCTURE, CraftStructure.class, net.minecraft.world.level.levelgen.structure.Structure.class); -- register(StructureType.class, Registries.STRUCTURE_TYPE, CraftStructureType.class, net.minecraft.world.level.levelgen.structure.StructureType.class); -- register(Villager.Type.class, Registries.VILLAGER_TYPE, CraftVillager.CraftType.class, VillagerType.class); -- register(Villager.Profession.class, Registries.VILLAGER_PROFESSION, CraftVillager.CraftProfession.class, VillagerProfession.class); -- register(TrimMaterial.class, Registries.TRIM_MATERIAL, CraftTrimMaterial.class, net.minecraft.world.item.armortrim.TrimMaterial.class); -- register(TrimPattern.class, Registries.TRIM_PATTERN, CraftTrimPattern.class, net.minecraft.world.item.armortrim.TrimPattern.class); -- register(DamageType.class, Registries.DAMAGE_TYPE, CraftDamageType.class, net.minecraft.world.damagesource.DamageType.class); -- register(JukeboxSong.class, Registries.JUKEBOX_SONG, CraftJukeboxSong.class, net.minecraft.world.item.JukeboxSong.class); -- register(Wolf.Variant.class, Registries.WOLF_VARIANT, CraftWolf.CraftVariant.class, WolfVariant.class); -- register(ItemType.class, Registries.ITEM, CraftItemType.class, net.minecraft.world.item.Item.class, true); -- register(BlockType.class, Registries.BLOCK, CraftBlockType.class, net.minecraft.world.level.block.Block.class, true); -- register(Frog.Variant.class, Registries.FROG_VARIANT, CraftFrog.CraftVariant.class, FrogVariant.class); -- register(Cat.Type.class, Registries.CAT_VARIANT, CraftCat.CraftType.class, CatVariant.class); -- register(MapCursor.Type.class, Registries.MAP_DECORATION_TYPE, CraftMapCursor.CraftType.class, MapDecorationType.class); -- register(PatternType.class, Registries.BANNER_PATTERN, CraftPatternType.class, BannerPattern.class); -- -+ // Order: RegistryKey, Bukkit class, Minecraft Registry key, CraftBukkit class, Minecraft class -+ register(RegistryKey.ENCHANTMENT, Enchantment.class, Registries.ENCHANTMENT, CraftEnchantment.class, net.minecraft.world.item.enchantment.Enchantment.class); -+ register(RegistryKey.GAME_EVENT, GameEvent.class, Registries.GAME_EVENT, CraftGameEvent.class, net.minecraft.world.level.gameevent.GameEvent.class); -+ register(RegistryKey.INSTRUMENT, MusicInstrument.class, Registries.INSTRUMENT, CraftMusicInstrument.class, Instrument.class); -+ register(RegistryKey.MOB_EFFECT, PotionEffectType.class, Registries.MOB_EFFECT, CraftPotionEffectType.class, MobEffect.class); -+ register(RegistryKey.STRUCTURE, Structure.class, Registries.STRUCTURE, CraftStructure.class, net.minecraft.world.level.levelgen.structure.Structure.class); -+ register(RegistryKey.STRUCTURE_TYPE, StructureType.class, Registries.STRUCTURE_TYPE, CraftStructureType.class, net.minecraft.world.level.levelgen.structure.StructureType.class); -+ register(RegistryKey.VILLAGER_TYPE, Villager.Type.class, Registries.VILLAGER_TYPE, CraftVillager.CraftType.class, VillagerType.class); -+ register(RegistryKey.VILLAGER_PROFESSION, Villager.Profession.class, Registries.VILLAGER_PROFESSION, CraftVillager.CraftProfession.class, VillagerProfession.class); -+ register(RegistryKey.TRIM_MATERIAL, TrimMaterial.class, Registries.TRIM_MATERIAL, CraftTrimMaterial.class, net.minecraft.world.item.armortrim.TrimMaterial.class); -+ register(RegistryKey.TRIM_PATTERN, TrimPattern.class, Registries.TRIM_PATTERN, CraftTrimPattern.class, net.minecraft.world.item.armortrim.TrimPattern.class); -+ register(RegistryKey.DAMAGE_TYPE, DamageType.class, Registries.DAMAGE_TYPE, CraftDamageType.class, net.minecraft.world.damagesource.DamageType.class); -+ register(RegistryKey.JUKEBOX_SONG, JukeboxSong.class, Registries.JUKEBOX_SONG, CraftJukeboxSong.class, net.minecraft.world.item.JukeboxSong.class); -+ register(RegistryKey.WOLF_VARIANT, Wolf.Variant.class, Registries.WOLF_VARIANT, CraftWolf.CraftVariant.class, WolfVariant.class); -+ register(RegistryKey.ITEM, ItemType.class, Registries.ITEM, CraftItemType.class, net.minecraft.world.item.Item.class, true); -+ register(RegistryKey.BLOCK, BlockType.class, Registries.BLOCK, CraftBlockType.class, net.minecraft.world.level.block.Block.class, true); -+ register(RegistryKey.FROG_VARIANT, Frog.Variant.class, Registries.FROG_VARIANT, CraftFrog.CraftVariant.class, FrogVariant.class); -+ register(RegistryKey.CAT_VARIANT, Cat.Type.class, Registries.CAT_VARIANT, CraftCat.CraftType.class, CatVariant.class); -+ register(RegistryKey.MAP_DECORATION_TYPE, MapCursor.Type.class, Registries.MAP_DECORATION_TYPE, CraftMapCursor.CraftType.class, MapDecorationType.class); -+ register(RegistryKey.BANNER_PATTERN, PatternType.class, Registries.BANNER_PATTERN, CraftPatternType.class, BannerPattern.class); -+ register(RegistryKey.MENU, MenuType.class, Registries.MENU, CraftMenuType.class, net.minecraft.world.inventory.MenuType.class); - } - -- private static void register(Class bukkit, ResourceKey registry, Class craft, Class minecraft) { -- RegistriesArgumentProvider.register(bukkit, registry, craft, minecraft, false); -+ private static void register(RegistryKey registryKey, Class bukkit, ResourceKey registry, Class craft, Class minecraft) { // Paper -+ RegistriesArgumentProvider.register(registryKey, bukkit, registry, craft, minecraft, false); - } - -- private static void register(Class bukkit, ResourceKey registry, Class craft, Class minecraft, boolean newClass) { -- RegistriesArgumentProvider.DATA.add(Arguments.of(bukkit, registry, craft, minecraft, newClass)); -+ private static void register(RegistryKey registryKey, Class bukkit, ResourceKey registry, Class craft, Class minecraft, boolean newClass) { // Paper -+ RegistriesArgumentProvider.DATA.add(Arguments.of(registryKey, bukkit, registry, craft, minecraft, newClass)); - } - - @Override -diff --git a/src/test/java/org/bukkit/support/provider/RegistryArgumentProvider.java b/src/test/java/org/bukkit/support/provider/RegistryArgumentProvider.java -index f2ceb5e67536dfa5de792dc64a7898fd2e8aa810..beb5fc9e721f5de54064c3d241df9ca9f4cd4f65 100644 ---- a/src/test/java/org/bukkit/support/provider/RegistryArgumentProvider.java -+++ b/src/test/java/org/bukkit/support/provider/RegistryArgumentProvider.java -@@ -22,11 +22,11 @@ public class RegistryArgumentProvider implements ArgumentsProvider, AnnotationCo - - @Override - public Stream provideArguments(ExtensionContext extensionContext) throws Exception { -- return RegistryArgumentProvider.getValues(this.registryType); -+ return RegistryArgumentProvider.getValues(io.papermc.paper.registry.PaperRegistryAccess.byType(this.registryType)); // Paper - } - -- public static Stream getValues(Class registryType) { -- Registry registry = Bukkit.getRegistry(registryType); -+ public static Stream getValues(io.papermc.paper.registry.RegistryKey registryType) { // Paper -+ Registry registry = io.papermc.paper.registry.RegistryAccess.registryAccess().getRegistry(registryType); // Paper - return registry.stream().map(keyed -> (Handleable) keyed) - .map(handleAble -> Arguments.of(handleAble, handleAble.getHandle())); - } diff --git a/patches/server/0471-Make-schedule-command-per-world.patch b/patches/server/0471-Make-schedule-command-per-world.patch new file mode 100644 index 000000000000..3ac2f8e3f4c6 --- /dev/null +++ b/patches/server/0471-Make-schedule-command-per-world.patch @@ -0,0 +1,28 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Jake Potrebic +Date: Mon, 4 Jan 2021 19:52:44 -0800 +Subject: [PATCH] Make schedule command per-world + + +diff --git a/src/main/java/net/minecraft/server/commands/ScheduleCommand.java b/src/main/java/net/minecraft/server/commands/ScheduleCommand.java +index 10b059a93762d9711fb4aedff572edfaebcb8264..1090759025969c04267de8b96ad045b1da0cb246 100644 +--- a/src/main/java/net/minecraft/server/commands/ScheduleCommand.java ++++ b/src/main/java/net/minecraft/server/commands/ScheduleCommand.java +@@ -33,7 +33,7 @@ public class ScheduleCommand { + }); + private static final SimpleCommandExceptionType ERROR_MACRO = new SimpleCommandExceptionType(Component.translatableEscape("commands.schedule.macro")); + private static final SuggestionProvider SUGGEST_SCHEDULE = (commandcontext, suggestionsbuilder) -> { +- return SharedSuggestionProvider.suggest((Iterable) ((CommandSourceStack) commandcontext.getSource()).getServer().getWorldData().overworldData().getScheduledEvents().getEventsIds(), suggestionsbuilder); ++ return SharedSuggestionProvider.suggest((Iterable) ((net.minecraft.commands.CommandSourceStack) commandcontext.getSource()).getLevel().serverLevelData.getScheduledEvents().getEventsIds(), suggestionsbuilder); // Paper - Make schedule command per-world + }; + + public ScheduleCommand() {} +@@ -93,7 +93,7 @@ public class ScheduleCommand { + } + + private static int remove(CommandSourceStack source, String eventName) throws CommandSyntaxException { +- int i = source.getServer().getWorldData().overworldData().getScheduledEvents().remove(eventName); ++ int i = source.getLevel().serverLevelData.getScheduledEvents().remove(eventName); // Paper - Make schedule command per-world + + if (i == 0) { + throw ScheduleCommand.ERROR_CANT_REMOVE.create(eventName); diff --git a/patches/server/0472-Add-StructuresLocateEvent.patch b/patches/server/0472-Add-StructuresLocateEvent.patch deleted file mode 100644 index d8c688e2ae97..000000000000 --- a/patches/server/0472-Add-StructuresLocateEvent.patch +++ /dev/null @@ -1,36 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: dfsek -Date: Wed, 16 Sep 2020 01:12:29 -0700 -Subject: [PATCH] Add StructuresLocateEvent - -Co-authored-by: Jake Potrebic - -diff --git a/src/main/java/net/minecraft/world/level/chunk/ChunkGenerator.java b/src/main/java/net/minecraft/world/level/chunk/ChunkGenerator.java -index 0a779632c9d11496fcfc147870fba2699d9cc274..5adc1952504b26772116b55a5144b7704136edfa 100644 ---- a/src/main/java/net/minecraft/world/level/chunk/ChunkGenerator.java -+++ b/src/main/java/net/minecraft/world/level/chunk/ChunkGenerator.java -@@ -126,6 +126,24 @@ public abstract class ChunkGenerator { - - @Nullable - public Pair> findNearestMapStructure(ServerLevel world, HolderSet structures, BlockPos center, int radius, boolean skipReferencedStructures) { -+ // Paper start - StructuresLocateEvent -+ final org.bukkit.World bukkitWorld = world.getWorld(); -+ final org.bukkit.Location origin = io.papermc.paper.util.MCUtil.toLocation(world, center); -+ final List apiStructures = structures.stream().map(Holder::value).map(nms -> org.bukkit.craftbukkit.generator.structure.CraftStructure.minecraftToBukkit(nms)).toList(); -+ if (!apiStructures.isEmpty()) { -+ final io.papermc.paper.event.world.StructuresLocateEvent event = new io.papermc.paper.event.world.StructuresLocateEvent(bukkitWorld, origin, apiStructures, radius, skipReferencedStructures); -+ if (!event.callEvent()) { -+ return null; -+ } -+ if (event.getResult() != null) { -+ return Pair.of(io.papermc.paper.util.MCUtil.toBlockPos(event.getResult().pos()), world.registryAccess().registryOrThrow(Registries.STRUCTURE).wrapAsHolder(org.bukkit.craftbukkit.generator.structure.CraftStructure.bukkitToMinecraft(event.getResult().structure()))); -+ } -+ center = io.papermc.paper.util.MCUtil.toBlockPosition(event.getOrigin()); -+ radius = event.getRadius(); -+ skipReferencedStructures = event.shouldFindUnexplored(); -+ structures = HolderSet.direct(api -> world.registryAccess().registryOrThrow(Registries.STRUCTURE).wrapAsHolder(org.bukkit.craftbukkit.generator.structure.CraftStructure.bukkitToMinecraft(api)), event.getStructures()); -+ } -+ // Paper end - ChunkGeneratorStructureState chunkgeneratorstructurestate = world.getChunkSource().getGeneratorState(); - Map>> map = new Object2ObjectArrayMap(); - Iterator iterator = structures.iterator(); diff --git a/patches/server/0472-Configurable-max-leash-distance.patch b/patches/server/0472-Configurable-max-leash-distance.patch new file mode 100644 index 000000000000..625e1c156b27 --- /dev/null +++ b/patches/server/0472-Configurable-max-leash-distance.patch @@ -0,0 +1,32 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Jake Potrebic +Date: Sun, 3 Jan 2021 21:04:03 -0800 +Subject: [PATCH] Configurable max leash distance + + +diff --git a/src/main/java/net/minecraft/world/entity/Leashable.java b/src/main/java/net/minecraft/world/entity/Leashable.java +index e53c3b53ea9c0932f0f049bf76f1f6de0432506a..dc39ecc3e1aada638337d31bfe68b400c6454af7 100644 +--- a/src/main/java/net/minecraft/world/entity/Leashable.java ++++ b/src/main/java/net/minecraft/world/entity/Leashable.java +@@ -180,7 +180,7 @@ public interface Leashable { + return; + } + +- if ((double) f > 10.0D) { ++ if ((double) f > entity.level().paperConfig().misc.maxLeashDistance.or(LEASH_TOO_FAR_DIST)) { // Paper - Configurable max leash distance + ((Leashable) entity).leashTooFarBehaviour(); + } else if ((double) f > 6.0D) { + ((Leashable) entity).elasticRangeLeashBehaviour(entity1, f); +diff --git a/src/main/java/net/minecraft/world/entity/TamableAnimal.java b/src/main/java/net/minecraft/world/entity/TamableAnimal.java +index f239ab65d914ee9ec819e112f0a0466a06a77929..d2785628368b65854b6e1f35005c478d490a2f8c 100644 +--- a/src/main/java/net/minecraft/world/entity/TamableAnimal.java ++++ b/src/main/java/net/minecraft/world/entity/TamableAnimal.java +@@ -98,7 +98,7 @@ public abstract class TamableAnimal extends Animal implements OwnableEntity { + @Override + public boolean handleLeashAtDistance(Entity leashHolder, float distance) { + if (this.isInSittingPose()) { +- if (distance > 10.0F) { ++ if (distance > (float) this.level().paperConfig().misc.maxLeashDistance.or(Leashable.LEASH_TOO_FAR_DIST)) { // Paper - Configurable max leash distance + this.dropLeash(true, true); + } + diff --git a/patches/server/0473-Add-BlockPreDispenseEvent.patch b/patches/server/0473-Add-BlockPreDispenseEvent.patch new file mode 100644 index 000000000000..d280bd07f124 --- /dev/null +++ b/patches/server/0473-Add-BlockPreDispenseEvent.patch @@ -0,0 +1,46 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Madeline Miller +Date: Sun, 17 Jan 2021 13:16:09 +1000 +Subject: [PATCH] Add BlockPreDispenseEvent + + +diff --git a/src/main/java/net/minecraft/world/level/block/DispenserBlock.java b/src/main/java/net/minecraft/world/level/block/DispenserBlock.java +index f4853a5ff8a45efcda2d7781c1fa897c47d8ea46..a02f24448b002824b068278fa427003008c0d0f1 100644 +--- a/src/main/java/net/minecraft/world/level/block/DispenserBlock.java ++++ b/src/main/java/net/minecraft/world/level/block/DispenserBlock.java +@@ -107,6 +107,7 @@ public class DispenserBlock extends BaseEntityBlock { + DispenseItemBehavior idispensebehavior = this.getDispenseMethod(world, itemstack); + + if (idispensebehavior != DispenseItemBehavior.NOOP) { ++ if (!org.bukkit.craftbukkit.event.CraftEventFactory.handleBlockPreDispenseEvent(world, pos, itemstack, i)) return; // Paper - Add BlockPreDispenseEvent + DispenserBlock.eventFired = false; // CraftBukkit - reset event status + tileentitydispenser.setItem(i, idispensebehavior.dispense(sourceblock, itemstack)); + } +diff --git a/src/main/java/net/minecraft/world/level/block/DropperBlock.java b/src/main/java/net/minecraft/world/level/block/DropperBlock.java +index 91b514967405115f22edf4255775361a672e5c2f..ddecf443df3679e3098eb54edd19585a0512e342 100644 +--- a/src/main/java/net/minecraft/world/level/block/DropperBlock.java ++++ b/src/main/java/net/minecraft/world/level/block/DropperBlock.java +@@ -71,6 +71,7 @@ public class DropperBlock extends DispenserBlock { + ItemStack itemstack1; + + if (iinventory == null) { ++ if (!org.bukkit.craftbukkit.event.CraftEventFactory.handleBlockPreDispenseEvent(world, pos, itemstack, i)) return; // Paper - Add BlockPreDispenseEvent + itemstack1 = DropperBlock.DISPENSE_BEHAVIOUR.dispense(sourceblock, itemstack); + } else { + // CraftBukkit start - Fire event when pushing items into other inventories +diff --git a/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java b/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java +index 1e918ef9296e5127b78a7eb0460225448982772e..a25e814978a2f19fa6fe74aaa5c529a524240d71 100644 +--- a/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java ++++ b/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java +@@ -2132,5 +2132,11 @@ public class CraftEventFactory { + io.papermc.paper.event.block.BlockFailedDispenseEvent event = new io.papermc.paper.event.block.BlockFailedDispenseEvent(block); + return event.callEvent(); + } ++ ++ public static boolean handleBlockPreDispenseEvent(ServerLevel serverLevel, BlockPos pos, ItemStack itemStack, int slot) { ++ org.bukkit.block.Block block = CraftBlock.at(serverLevel, pos); ++ io.papermc.paper.event.block.BlockPreDispenseEvent event = new io.papermc.paper.event.block.BlockPreDispenseEvent(block, org.bukkit.craftbukkit.inventory.CraftItemStack.asCraftMirror(itemStack), slot); ++ return event.callEvent(); ++ } + // Paper end + } diff --git a/patches/server/0473-Collision-option-for-requiring-a-player-participant.patch b/patches/server/0473-Collision-option-for-requiring-a-player-participant.patch deleted file mode 100644 index 28b12f77f8a2..000000000000 --- a/patches/server/0473-Collision-option-for-requiring-a-player-participant.patch +++ /dev/null @@ -1,42 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Mariell Hoversholm -Date: Sat, 14 Nov 2020 16:48:37 +0100 -Subject: [PATCH] Collision option for requiring a player participant - - -diff --git a/src/main/java/net/minecraft/world/entity/Entity.java b/src/main/java/net/minecraft/world/entity/Entity.java -index bed376337035545e7ec677f2f7fe3372a3c9ea25..7d131f3b3f5739468aa3115e97ed28b6bfeca33d 100644 ---- a/src/main/java/net/minecraft/world/entity/Entity.java -+++ b/src/main/java/net/minecraft/world/entity/Entity.java -@@ -1936,6 +1936,7 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess - public void push(Entity entity) { - if (!this.isPassengerOfSameVehicle(entity)) { - if (!entity.noPhysics && !this.noPhysics) { -+ if (this.level.paperConfig().collisions.onlyPlayersCollide && !(entity instanceof ServerPlayer || this instanceof ServerPlayer)) return; // Paper - Collision option for requiring a player participant - double d0 = entity.getX() - this.getX(); - double d1 = entity.getZ() - this.getZ(); - double d2 = Mth.absMax(d0, d1); -diff --git a/src/main/java/net/minecraft/world/entity/vehicle/AbstractMinecart.java b/src/main/java/net/minecraft/world/entity/vehicle/AbstractMinecart.java -index 4d8c14d3a42f3e4b963cf5f8fa764df79813912b..93634fb01962ca7a35026e3c365f2a7f6b258a39 100644 ---- a/src/main/java/net/minecraft/world/entity/vehicle/AbstractMinecart.java -+++ b/src/main/java/net/minecraft/world/entity/vehicle/AbstractMinecart.java -@@ -799,6 +799,7 @@ public abstract class AbstractMinecart extends VehicleEntity { - public void push(Entity entity) { - if (!this.level().isClientSide) { - if (!entity.noPhysics && !this.noPhysics) { -+ if (!this.level().paperConfig().collisions.allowVehicleCollisions && this.level().paperConfig().collisions.onlyPlayersCollide && !(entity instanceof Player)) return; // Paper - Collision option for requiring a player participant - if (!this.hasPassenger(entity)) { - // CraftBukkit start - VehicleEntityCollisionEvent collisionEvent = new VehicleEntityCollisionEvent((Vehicle) this.getBukkitEntity(), entity.getBukkitEntity()); -diff --git a/src/main/java/net/minecraft/world/entity/vehicle/Boat.java b/src/main/java/net/minecraft/world/entity/vehicle/Boat.java -index 2cb535639bff0e867c1b1e845fee6e34bb237044..907f751c859855484151fb5d607acee2f2a35076 100644 ---- a/src/main/java/net/minecraft/world/entity/vehicle/Boat.java -+++ b/src/main/java/net/minecraft/world/entity/vehicle/Boat.java -@@ -204,6 +204,7 @@ public class Boat extends VehicleEntity implements Leashable, VariantHolder +Date: Wed, 24 Jun 2020 15:14:51 -0600 +Subject: [PATCH] Add PlayerChangeBeaconEffectEvent + + +diff --git a/src/main/java/net/minecraft/world/inventory/BeaconMenu.java b/src/main/java/net/minecraft/world/inventory/BeaconMenu.java +index a191cef8037d94c7e93f79b86fc26ebefae95d8f..cad0b581c992edc5cd312a727695a443e26e96d8 100644 +--- a/src/main/java/net/minecraft/world/inventory/BeaconMenu.java ++++ b/src/main/java/net/minecraft/world/inventory/BeaconMenu.java +@@ -157,12 +157,25 @@ public class BeaconMenu extends AbstractContainerMenu { + return BeaconMenu.decodeEffect(this.beaconData.get(2)); + } + ++ // Paper start - Add PlayerChangeBeaconEffectEvent ++ private static @Nullable org.bukkit.potion.PotionEffectType convert(Optional> optionalEffect) { ++ return optionalEffect.map(org.bukkit.craftbukkit.potion.CraftPotionEffectType::minecraftHolderToBukkit).orElse(null); ++ } ++ // Paper end - Add PlayerChangeBeaconEffectEvent ++ + public void updateEffects(Optional> primary, Optional> secondary) { + if (this.paymentSlot.hasItem()) { +- this.beaconData.set(1, BeaconMenu.encodeEffect((Holder) primary.orElse(null)));// CraftBukkit - decompile error +- this.beaconData.set(2, BeaconMenu.encodeEffect((Holder) secondary.orElse(null)));// CraftBukkit - decompile error ++ // Paper start - Add PlayerChangeBeaconEffectEvent ++ io.papermc.paper.event.player.PlayerChangeBeaconEffectEvent event = new io.papermc.paper.event.player.PlayerChangeBeaconEffectEvent((org.bukkit.entity.Player) this.player.player.getBukkitEntity(), convert(primary), convert(secondary), this.access.getLocation().getBlock()); ++ if (event.callEvent()) { ++ // Paper end - Add PlayerChangeBeaconEffectEvent ++ this.beaconData.set(1, BeaconMenu.encodeEffect(event.getPrimary() == null ? null : org.bukkit.craftbukkit.potion.CraftPotionEffectType.bukkitToMinecraftHolder(event.getPrimary())));// CraftBukkit - decompile error ++ this.beaconData.set(2, BeaconMenu.encodeEffect(event.getSecondary() == null ? null : org.bukkit.craftbukkit.potion.CraftPotionEffectType.bukkitToMinecraftHolder(event.getSecondary())));// CraftBukkit - decompile error ++ if (event.willConsumeItem()) { // Paper + this.paymentSlot.remove(1); ++ } // Paper + this.access.execute(Level::blockEntityChanged); ++ } // Paper end - Add PlayerChangeBeaconEffectEvent + } + + } diff --git a/patches/server/0474-Return-chat-component-with-empty-text-instead-of-thr.patch b/patches/server/0474-Return-chat-component-with-empty-text-instead-of-thr.patch deleted file mode 100644 index af20b23c54ce..000000000000 --- a/patches/server/0474-Return-chat-component-with-empty-text-instead-of-thr.patch +++ /dev/null @@ -1,25 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: CDFN -Date: Tue, 7 Jul 2020 17:53:23 +0200 -Subject: [PATCH] Return chat component with empty text instead of throwing - exception - - -diff --git a/src/main/java/net/minecraft/world/inventory/AbstractContainerMenu.java b/src/main/java/net/minecraft/world/inventory/AbstractContainerMenu.java -index fe8d66893aa2c2ca6fc57ea96479ce4d646d8619..b8d8aad81f54f7f43c01da075e63ec9173750bec 100644 ---- a/src/main/java/net/minecraft/world/inventory/AbstractContainerMenu.java -+++ b/src/main/java/net/minecraft/world/inventory/AbstractContainerMenu.java -@@ -89,7 +89,12 @@ public abstract class AbstractContainerMenu { - } - private Component title; - public final Component getTitle() { -- Preconditions.checkState(this.title != null, "Title not set"); -+ // Paper start - return chat component with empty text instead of throwing error -+ // Preconditions.checkState(this.title != null, "Title not set"); -+ if (this.title == null){ -+ return Component.literal(""); -+ } -+ // Paper end - return chat component with empty text instead of throwing error - return this.title; - } - public final void setTitle(Component title) { diff --git a/patches/server/0475-Add-toggle-for-always-placing-the-dragon-egg.patch b/patches/server/0475-Add-toggle-for-always-placing-the-dragon-egg.patch new file mode 100644 index 000000000000..0b28271dd1b2 --- /dev/null +++ b/patches/server/0475-Add-toggle-for-always-placing-the-dragon-egg.patch @@ -0,0 +1,19 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: William Blake Galbreath +Date: Thu, 26 Nov 2020 11:47:24 +0000 +Subject: [PATCH] Add toggle for always placing the dragon egg + + +diff --git a/src/main/java/net/minecraft/world/level/dimension/end/EndDragonFight.java b/src/main/java/net/minecraft/world/level/dimension/end/EndDragonFight.java +index 323fbf684b7062c1b9084f1718538a3b3414d5bf..0e1eceb2b83aaafccbb5d58cf5098cfbc6f25a54 100644 +--- a/src/main/java/net/minecraft/world/level/dimension/end/EndDragonFight.java ++++ b/src/main/java/net/minecraft/world/level/dimension/end/EndDragonFight.java +@@ -410,7 +410,7 @@ public class EndDragonFight { + this.dragonEvent.setVisible(false); + this.spawnExitPortal(true); + this.spawnNewGateway(); +- if (!this.previouslyKilled) { ++ if (this.level.paperConfig().entities.behavior.enderDragonsDeathAlwaysPlacesDragonEgg || !this.previouslyKilled) { // Paper - Add toggle for always placing the dragon egg + this.level.setBlockAndUpdate(this.level.getHeightmapPos(Heightmap.Types.MOTION_BLOCKING, EndPodiumFeature.getLocation(this.origin)), Blocks.DRAGON_EGG.defaultBlockState()); + } + diff --git a/patches/server/0475-Make-schedule-command-per-world.patch b/patches/server/0475-Make-schedule-command-per-world.patch deleted file mode 100644 index 2d8f63ad1efa..000000000000 --- a/patches/server/0475-Make-schedule-command-per-world.patch +++ /dev/null @@ -1,28 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Jake Potrebic -Date: Mon, 4 Jan 2021 19:52:44 -0800 -Subject: [PATCH] Make schedule command per-world - - -diff --git a/src/main/java/net/minecraft/server/commands/ScheduleCommand.java b/src/main/java/net/minecraft/server/commands/ScheduleCommand.java -index f2d1430d714debc9e9b53eef0cbcda0d8cdcd459..d090f94a9a8a314df01e83a031dd18d5c6f1caf9 100644 ---- a/src/main/java/net/minecraft/server/commands/ScheduleCommand.java -+++ b/src/main/java/net/minecraft/server/commands/ScheduleCommand.java -@@ -30,7 +30,7 @@ public class ScheduleCommand { - return Component.translatableEscape("commands.schedule.cleared.failure", object); - }); - private static final SuggestionProvider SUGGEST_SCHEDULE = (commandcontext, suggestionsbuilder) -> { -- return SharedSuggestionProvider.suggest((Iterable) ((CommandSourceStack) commandcontext.getSource()).getServer().getWorldData().overworldData().getScheduledEvents().getEventsIds(), suggestionsbuilder); -+ return SharedSuggestionProvider.suggest((Iterable) ((net.minecraft.commands.CommandSourceStack) commandcontext.getSource()).getLevel().serverLevelData.getScheduledEvents().getEventsIds(), suggestionsbuilder); // Paper - Make schedule command per-world - }; - - public ScheduleCommand() {} -@@ -85,7 +85,7 @@ public class ScheduleCommand { - } - - private static int remove(CommandSourceStack source, String eventName) throws CommandSyntaxException { -- int i = source.getServer().getWorldData().overworldData().getScheduledEvents().remove(eventName); -+ int i = source.getLevel().serverLevelData.getScheduledEvents().remove(eventName); // Paper - Make schedule command per-world - - if (i == 0) { - throw ScheduleCommand.ERROR_CANT_REMOVE.create(eventName); diff --git a/patches/server/0476-Add-PlayerStonecutterRecipeSelectEvent.patch b/patches/server/0476-Add-PlayerStonecutterRecipeSelectEvent.patch new file mode 100644 index 000000000000..8a5e50313f5b --- /dev/null +++ b/patches/server/0476-Add-PlayerStonecutterRecipeSelectEvent.patch @@ -0,0 +1,57 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Jake Potrebic +Date: Fri, 27 Nov 2020 17:14:27 -0800 +Subject: [PATCH] Add PlayerStonecutterRecipeSelectEvent + +Co-Authored-By: MiniDigger + +diff --git a/src/main/java/net/minecraft/world/inventory/StonecutterMenu.java b/src/main/java/net/minecraft/world/inventory/StonecutterMenu.java +index 97837d1baf6b929a50e5562ef466050e70c2c8b1..a93870952e2ef674028b8a20aa52a685c743e7ea 100644 +--- a/src/main/java/net/minecraft/world/inventory/StonecutterMenu.java ++++ b/src/main/java/net/minecraft/world/inventory/StonecutterMenu.java +@@ -64,7 +64,7 @@ public class StonecutterMenu extends AbstractContainerMenu { + + public StonecutterMenu(int syncId, Inventory playerInventory, final ContainerLevelAccess context) { + super(MenuType.STONECUTTER, syncId); +- this.selectedRecipeIndex = DataSlot.standalone(); ++ this.selectedRecipeIndex = DataSlot.shared(new int[1], 0); // Paper - Add PlayerStonecutterRecipeSelectEvent + this.recipesForInput = SelectableRecipe.SingleInputSet.empty(); + this.input = ItemStack.EMPTY; + this.slotUpdateListener = () -> { +@@ -150,8 +150,34 @@ public class StonecutterMenu extends AbstractContainerMenu { + @Override + public boolean clickMenuButton(net.minecraft.world.entity.player.Player player, int id) { + if (this.isValidRecipeIndex(id)) { +- this.selectedRecipeIndex.set(id); +- this.setupResultSlot(id); ++ // Paper start - Add PlayerStonecutterRecipeSelectEvent ++ int recipeIndex = id; ++ this.selectedRecipeIndex.set(recipeIndex); ++ this.selectedRecipeIndex.checkAndClearUpdateFlag(); // mark as changed ++ paperEventBlock: if (this.isValidRecipeIndex(id)) { ++ final Optional> recipe = this.recipesForInput.entries().get(id).recipe().recipe(); ++ if (recipe.isEmpty()) break paperEventBlock; // The recipe selected does not have an actual server recipe (presumably its the empty one). Cannot call the event, just break. ++ ++ io.papermc.paper.event.player.PlayerStonecutterRecipeSelectEvent event = new io.papermc.paper.event.player.PlayerStonecutterRecipeSelectEvent((Player) player.getBukkitEntity(), getBukkitView().getTopInventory(), (org.bukkit.inventory.StonecuttingRecipe) recipe.get().toBukkitRecipe()); ++ if (!event.callEvent()) { ++ player.containerMenu.sendAllDataToRemote(); ++ return false; ++ } ++ ++ net.minecraft.resources.ResourceLocation key = org.bukkit.craftbukkit.util.CraftNamespacedKey.toMinecraft(event.getStonecuttingRecipe().getKey()); ++ if (!recipe.get().id().location().equals(key)) { // If the recipe did NOT stay the same ++ for (int newRecipeIndex = 0; newRecipeIndex < this.recipesForInput.entries().size(); newRecipeIndex++) { ++ if (this.recipesForInput.entries().get(newRecipeIndex).recipe().recipe().filter(r -> r.id().location().equals(key)).isPresent()) { ++ recipeIndex = newRecipeIndex; ++ break; ++ } ++ } ++ } ++ } ++ player.containerMenu.sendAllDataToRemote(); ++ this.selectedRecipeIndex.set(recipeIndex); // set new index, so that listeners can read it ++ this.setupResultSlot(recipeIndex); ++ // Paper end - Add PlayerStonecutterRecipeSelectEvent + } + + return true; diff --git a/patches/server/0476-Configurable-max-leash-distance.patch b/patches/server/0476-Configurable-max-leash-distance.patch deleted file mode 100644 index d2f544737b2f..000000000000 --- a/patches/server/0476-Configurable-max-leash-distance.patch +++ /dev/null @@ -1,32 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Jake Potrebic -Date: Sun, 3 Jan 2021 21:04:03 -0800 -Subject: [PATCH] Configurable max leash distance - - -diff --git a/src/main/java/net/minecraft/world/entity/Leashable.java b/src/main/java/net/minecraft/world/entity/Leashable.java -index 1f2d04a456c383815b0c5ef331471593556fd128..5c51dd5229689cba459655d488aee59bd159a414 100644 ---- a/src/main/java/net/minecraft/world/entity/Leashable.java -+++ b/src/main/java/net/minecraft/world/entity/Leashable.java -@@ -179,7 +179,7 @@ public interface Leashable { - return; - } - -- if ((double) f > 10.0D) { -+ if ((double) f > entity.level().paperConfig().misc.maxLeashDistance.or(LEASH_TOO_FAR_DIST)) { // Paper - Configurable max leash distance - ((Leashable) entity).leashTooFarBehaviour(); - } else if ((double) f > 6.0D) { - ((Leashable) entity).elasticRangeLeashBehaviour(entity1, f); -diff --git a/src/main/java/net/minecraft/world/entity/TamableAnimal.java b/src/main/java/net/minecraft/world/entity/TamableAnimal.java -index f2d311a5450eb684603580bbf7e9e7fc73fc2f5c..bf2c9134c7d9d5926add36b55e3cfea79e8c8243 100644 ---- a/src/main/java/net/minecraft/world/entity/TamableAnimal.java -+++ b/src/main/java/net/minecraft/world/entity/TamableAnimal.java -@@ -97,7 +97,7 @@ public abstract class TamableAnimal extends Animal implements OwnableEntity { - @Override - public boolean handleLeashAtDistance(Entity leashHolder, float distance) { - if (this.isInSittingPose()) { -- if (distance > 10.0F) { -+ if (distance > (float) this.level().paperConfig().misc.maxLeashDistance.or(Leashable.LEASH_TOO_FAR_DIST)) { // Paper - Configurable max leash distance - this.dropLeash(true, true); - } - diff --git a/patches/server/0477-Add-BlockPreDispenseEvent.patch b/patches/server/0477-Add-BlockPreDispenseEvent.patch deleted file mode 100644 index 6e9bb3b21683..000000000000 --- a/patches/server/0477-Add-BlockPreDispenseEvent.patch +++ /dev/null @@ -1,46 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Madeline Miller -Date: Sun, 17 Jan 2021 13:16:09 +1000 -Subject: [PATCH] Add BlockPreDispenseEvent - - -diff --git a/src/main/java/net/minecraft/world/level/block/DispenserBlock.java b/src/main/java/net/minecraft/world/level/block/DispenserBlock.java -index 94bcbaf7daf7dfe566f508d1170a433930d9d49a..f6edfea463b3725d3a79aca38825e86dbf82175c 100644 ---- a/src/main/java/net/minecraft/world/level/block/DispenserBlock.java -+++ b/src/main/java/net/minecraft/world/level/block/DispenserBlock.java -@@ -110,6 +110,7 @@ public class DispenserBlock extends BaseEntityBlock { - DispenseItemBehavior idispensebehavior = this.getDispenseMethod(world, itemstack); - - if (idispensebehavior != DispenseItemBehavior.NOOP) { -+ if (!org.bukkit.craftbukkit.event.CraftEventFactory.handleBlockPreDispenseEvent(world, pos, itemstack, i)) return; // Paper - Add BlockPreDispenseEvent - DispenserBlock.eventFired = false; // CraftBukkit - reset event status - tileentitydispenser.setItem(i, idispensebehavior.dispense(sourceblock, itemstack)); - } -diff --git a/src/main/java/net/minecraft/world/level/block/DropperBlock.java b/src/main/java/net/minecraft/world/level/block/DropperBlock.java -index 91b514967405115f22edf4255775361a672e5c2f..ddecf443df3679e3098eb54edd19585a0512e342 100644 ---- a/src/main/java/net/minecraft/world/level/block/DropperBlock.java -+++ b/src/main/java/net/minecraft/world/level/block/DropperBlock.java -@@ -71,6 +71,7 @@ public class DropperBlock extends DispenserBlock { - ItemStack itemstack1; - - if (iinventory == null) { -+ if (!org.bukkit.craftbukkit.event.CraftEventFactory.handleBlockPreDispenseEvent(world, pos, itemstack, i)) return; // Paper - Add BlockPreDispenseEvent - itemstack1 = DropperBlock.DISPENSE_BEHAVIOUR.dispense(sourceblock, itemstack); - } else { - // CraftBukkit start - Fire event when pushing items into other inventories -diff --git a/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java b/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java -index 9e67dc35ea12149265517b951a96828460b2ca67..251b605396b77b0c449972c1da75978f57587e1a 100644 ---- a/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java -+++ b/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java -@@ -2134,5 +2134,11 @@ public class CraftEventFactory { - io.papermc.paper.event.block.BlockFailedDispenseEvent event = new io.papermc.paper.event.block.BlockFailedDispenseEvent(block); - return event.callEvent(); - } -+ -+ public static boolean handleBlockPreDispenseEvent(ServerLevel serverLevel, BlockPos pos, ItemStack itemStack, int slot) { -+ org.bukkit.block.Block block = CraftBlock.at(serverLevel, pos); -+ io.papermc.paper.event.block.BlockPreDispenseEvent event = new io.papermc.paper.event.block.BlockPreDispenseEvent(block, org.bukkit.craftbukkit.inventory.CraftItemStack.asCraftMirror(itemStack), slot); -+ return event.callEvent(); -+ } - // Paper end - } diff --git a/patches/server/0477-Expand-EntityUnleashEvent.patch b/patches/server/0477-Expand-EntityUnleashEvent.patch new file mode 100644 index 000000000000..214eb8dd8ceb --- /dev/null +++ b/patches/server/0477-Expand-EntityUnleashEvent.patch @@ -0,0 +1,158 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Nassim Jahnke +Date: Fri, 29 Jan 2021 15:13:11 +0100 +Subject: [PATCH] Expand EntityUnleashEvent + + +diff --git a/src/main/java/net/minecraft/world/entity/Entity.java b/src/main/java/net/minecraft/world/entity/Entity.java +index 0d10abc918e31bf8a529706b3350c80dec11b313..3479b7c89741ba93e49e7e51fe4b45bb14fbc419 100644 +--- a/src/main/java/net/minecraft/world/entity/Entity.java ++++ b/src/main/java/net/minecraft/world/entity/Entity.java +@@ -2689,12 +2689,15 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess + if (leashable.getLeashHolder() == player) { + if (!this.level().isClientSide()) { + // CraftBukkit start - fire PlayerUnleashEntityEvent +- if (CraftEventFactory.callPlayerUnleashEntityEvent(this, player, hand).isCancelled()) { ++ // Paper start - Expand EntityUnleashEvent ++ org.bukkit.event.player.PlayerUnleashEntityEvent event = CraftEventFactory.callPlayerUnleashEntityEvent(this, player, hand, !player.hasInfiniteMaterials()); ++ if (event.isCancelled()) { ++ // Paper end - Expand EntityUnleashEvent + ((ServerPlayer) player).connection.send(new ClientboundSetEntityLinkPacket(this, leashable.getLeashHolder())); + return InteractionResult.PASS; + } + // CraftBukkit end +- leashable.dropLeash(true, !player.hasInfiniteMaterials()); ++ leashable.dropLeash(true, event.isDropLeash()); // Paper - Expand EntityUnleashEvent + this.gameEvent(GameEvent.ENTITY_INTERACT, player); + } + +@@ -3659,9 +3662,12 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess + + protected void removeAfterChangingDimensions() { + this.setRemoved(Entity.RemovalReason.CHANGED_DIMENSION, null); // CraftBukkit - add Bukkit remove cause +- if (this instanceof Leashable leashable) { +- this.level().getCraftServer().getPluginManager().callEvent(new EntityUnleashEvent(this.getBukkitEntity(), UnleashReason.UNKNOWN)); // CraftBukkit +- leashable.dropLeash(true, false); ++ if (this instanceof Leashable leashable && leashable.isLeashed()) { // Paper - only call if it is leashed ++ // Paper start - Expand EntityUnleashEvent ++ final EntityUnleashEvent event = new EntityUnleashEvent(this.getBukkitEntity(), UnleashReason.UNKNOWN, false); // CraftBukkit ++ event.callEvent(); ++ leashable.dropLeash(true, event.isDropLeash()); ++ // Paper end - Expand EntityUnleashEvent + } + + } +diff --git a/src/main/java/net/minecraft/world/entity/Leashable.java b/src/main/java/net/minecraft/world/entity/Leashable.java +index dc39ecc3e1aada638337d31bfe68b400c6454af7..b7721ed97305d1cd6725935f965c2effc1bef5a1 100644 +--- a/src/main/java/net/minecraft/world/entity/Leashable.java ++++ b/src/main/java/net/minecraft/world/entity/Leashable.java +@@ -167,8 +167,11 @@ public interface Leashable { + + if (leashable_a != null && leashable_a.leashHolder != null) { + if (!entity.isAlive() || !leashable_a.leashHolder.isAlive()) { +- world.getCraftServer().getPluginManager().callEvent(new EntityUnleashEvent(entity.getBukkitEntity(), (!entity.isAlive()) ? UnleashReason.PLAYER_UNLEASH : UnleashReason.HOLDER_GONE)); // CraftBukkit +- Leashable.dropLeash(entity, true, world.getGameRules().getBoolean(GameRules.RULE_DOENTITYDROPS) && !entity.pluginRemoved); // CraftBukkit - SPIGOT-7487: Don't drop leash, when the holder was removed by a plugin ++ // Paper start - Expand EntityUnleashEvent ++ final EntityUnleashEvent event = new EntityUnleashEvent(entity.getBukkitEntity(), (!entity.isAlive()) ? UnleashReason.PLAYER_UNLEASH : UnleashReason.HOLDER_GONE, world.getGameRules().getBoolean(GameRules.RULE_DOENTITYDROPS) && !entity.pluginRemoved); ++ event.callEvent(); ++ Leashable.dropLeash(entity, true, event.isDropLeash()); // CraftBukkit - SPIGOT-7487: Don't drop leash, when the holder was removed by a plugin ++ // Paper end - Expand EntityUnleashEvent + } + + Entity entity1 = ((Leashable) entity).getLeashHolder(); +@@ -199,11 +202,16 @@ public interface Leashable { + + default void leashTooFarBehaviour() { + // CraftBukkit start ++ boolean dropLeash = true; // Paper + if (this instanceof Entity entity) { +- entity.level().getCraftServer().getPluginManager().callEvent(new EntityUnleashEvent(entity.getBukkitEntity(), EntityUnleashEvent.UnleashReason.DISTANCE)); ++ // Paper start - Expand EntityUnleashEvent ++ final EntityUnleashEvent event = new EntityUnleashEvent(entity.getBukkitEntity(), EntityUnleashEvent.UnleashReason.DISTANCE, true); ++ if (!event.callEvent()) return; ++ dropLeash = event.isDropLeash(); ++ // Paper end - Expand EntityUnleashEvent + } + // CraftBukkit end +- this.dropLeash(true, true); ++ this.dropLeash(true, dropLeash); // Paper + } + + default void closeRangeLeashBehaviour(Entity entity) {} +diff --git a/src/main/java/net/minecraft/world/entity/Mob.java b/src/main/java/net/minecraft/world/entity/Mob.java +index a8ab3c03a6f96658ce2a3f5758225954a36de6a9..ac7a52e77fd2fcbe9f95709b95ba54f8c2a6514b 100644 +--- a/src/main/java/net/minecraft/world/entity/Mob.java ++++ b/src/main/java/net/minecraft/world/entity/Mob.java +@@ -1613,8 +1613,11 @@ public abstract class Mob extends LivingEntity implements EquipmentUser, Leashab + boolean flag1 = super.startRiding(entity, force); + + if (flag1 && this.isLeashed()) { +- this.level().getCraftServer().getPluginManager().callEvent(new EntityUnleashEvent(this.getBukkitEntity(), UnleashReason.UNKNOWN)); // CraftBukkit +- this.dropLeash(true, true); ++ // Paper start - Expand EntityUnleashEvent ++ EntityUnleashEvent event = new EntityUnleashEvent(this.getBukkitEntity(), EntityUnleashEvent.UnleashReason.UNKNOWN, true); ++ if (!event.callEvent()) { return flag1; } ++ this.dropLeash(true, event.isDropLeash()); ++ // Paper end - Expand EntityUnleashEvent + } + + return flag1; +diff --git a/src/main/java/net/minecraft/world/entity/TamableAnimal.java b/src/main/java/net/minecraft/world/entity/TamableAnimal.java +index d2785628368b65854b6e1f35005c478d490a2f8c..cd565d1a8dab8d45196e4d29cab3d93a3ca619eb 100644 +--- a/src/main/java/net/minecraft/world/entity/TamableAnimal.java ++++ b/src/main/java/net/minecraft/world/entity/TamableAnimal.java +@@ -99,7 +99,11 @@ public abstract class TamableAnimal extends Animal implements OwnableEntity { + public boolean handleLeashAtDistance(Entity leashHolder, float distance) { + if (this.isInSittingPose()) { + if (distance > (float) this.level().paperConfig().misc.maxLeashDistance.or(Leashable.LEASH_TOO_FAR_DIST)) { // Paper - Configurable max leash distance +- this.dropLeash(true, true); ++ // Paper start - Expand EntityUnleashEvent ++ org.bukkit.event.entity.EntityUnleashEvent event = new org.bukkit.event.entity.EntityUnleashEvent(this.getBukkitEntity(), org.bukkit.event.entity.EntityUnleashEvent.UnleashReason.DISTANCE, true); ++ if (!event.callEvent()) return false; ++ this.dropLeash(true, event.isDropLeash()); ++ // Paper end - Expand EntityUnleashEvent + } + + return false; +diff --git a/src/main/java/net/minecraft/world/entity/decoration/LeashFenceKnotEntity.java b/src/main/java/net/minecraft/world/entity/decoration/LeashFenceKnotEntity.java +index 4b44830ef5d08887274ebb39e2780606fe3a76b4..d3a7953a3f42a0020342845e9107c6991637b050 100644 +--- a/src/main/java/net/minecraft/world/entity/decoration/LeashFenceKnotEntity.java ++++ b/src/main/java/net/minecraft/world/entity/decoration/LeashFenceKnotEntity.java +@@ -119,13 +119,18 @@ public class LeashFenceKnotEntity extends BlockAttachedEntity { + + if (leashable1.isLeashed() && leashable1.getLeashHolder() == this) { + // CraftBukkit start ++ boolean dropLeash = !player.hasInfiniteMaterials(); + if (leashable1 instanceof Entity leashed) { +- if (CraftEventFactory.callPlayerUnleashEntityEvent(leashed, player, hand).isCancelled()) { ++ // Paper start - Expand EntityUnleashEvent ++ org.bukkit.event.player.PlayerUnleashEntityEvent event = CraftEventFactory.callPlayerUnleashEntityEvent(leashed, player, hand, dropLeash); ++ dropLeash = event.isDropLeash(); ++ if (event.isCancelled()) { ++ // Paper end - Expand EntityUnleashEvent + die = false; + continue; + } + } +- leashable1.dropLeash(true, !player.getAbilities().instabuild); // false -> survival mode boolean ++ leashable1.dropLeash(true, dropLeash); // false -> survival mode boolean // Paper - Expand EntityUnleashEvent + // CraftBukkit end + flag1 = true; + } +diff --git a/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java b/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java +index a25e814978a2f19fa6fe74aaa5c529a524240d71..5064dd8c97f45f0c4e2d1402500e7cf67a263dfa 100644 +--- a/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java ++++ b/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java +@@ -1596,8 +1596,10 @@ public class CraftEventFactory { + Bukkit.getPluginManager().callEvent(new PlayerRecipeBookSettingsChangeEvent(player.getBukkitEntity(), bukkitType, open, filter)); + } + +- public static PlayerUnleashEntityEvent callPlayerUnleashEntityEvent(Entity entity, net.minecraft.world.entity.player.Player player, InteractionHand enumhand) { +- PlayerUnleashEntityEvent event = new PlayerUnleashEntityEvent(entity.getBukkitEntity(), (Player) player.getBukkitEntity(), CraftEquipmentSlot.getHand(enumhand)); ++ // Paper start - Expand EntityUnleashEvent ++ public static PlayerUnleashEntityEvent callPlayerUnleashEntityEvent(Entity entity, net.minecraft.world.entity.player.Player player, InteractionHand enumhand, boolean dropLeash) { ++ PlayerUnleashEntityEvent event = new PlayerUnleashEntityEvent(entity.getBukkitEntity(), (Player) player.getBukkitEntity(), CraftEquipmentSlot.getHand(enumhand), dropLeash); ++ // Paper end - Expand EntityUnleashEvent + entity.level().getCraftServer().getPluginManager().callEvent(event); + return event; + } diff --git a/patches/server/0478-Add-PlayerChangeBeaconEffectEvent.patch b/patches/server/0478-Add-PlayerChangeBeaconEffectEvent.patch deleted file mode 100644 index 1903c21ba88d..000000000000 --- a/patches/server/0478-Add-PlayerChangeBeaconEffectEvent.patch +++ /dev/null @@ -1,38 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Jake Potrebic -Date: Wed, 24 Jun 2020 15:14:51 -0600 -Subject: [PATCH] Add PlayerChangeBeaconEffectEvent - - -diff --git a/src/main/java/net/minecraft/world/inventory/BeaconMenu.java b/src/main/java/net/minecraft/world/inventory/BeaconMenu.java -index 7c4d2c184d9a1e4a1856e6771d39db384381e300..396559c281eee9e8c677cb222721414e8d9e12a2 100644 ---- a/src/main/java/net/minecraft/world/inventory/BeaconMenu.java -+++ b/src/main/java/net/minecraft/world/inventory/BeaconMenu.java -@@ -171,12 +171,25 @@ public class BeaconMenu extends AbstractContainerMenu { - return BeaconMenu.decodeEffect(this.beaconData.get(2)); - } - -+ // Paper start - Add PlayerChangeBeaconEffectEvent -+ private static @Nullable org.bukkit.potion.PotionEffectType convert(Optional> optionalEffect) { -+ return optionalEffect.map(org.bukkit.craftbukkit.potion.CraftPotionEffectType::minecraftHolderToBukkit).orElse(null); -+ } -+ // Paper end - Add PlayerChangeBeaconEffectEvent -+ - public void updateEffects(Optional> primary, Optional> secondary) { - if (this.paymentSlot.hasItem()) { -- this.beaconData.set(1, BeaconMenu.encodeEffect((Holder) primary.orElse(null)));// CraftBukkit - decompile error -- this.beaconData.set(2, BeaconMenu.encodeEffect((Holder) secondary.orElse(null)));// CraftBukkit - decompile error -+ // Paper start - Add PlayerChangeBeaconEffectEvent -+ io.papermc.paper.event.player.PlayerChangeBeaconEffectEvent event = new io.papermc.paper.event.player.PlayerChangeBeaconEffectEvent((org.bukkit.entity.Player) this.player.player.getBukkitEntity(), convert(primary), convert(secondary), this.access.getLocation().getBlock()); -+ if (event.callEvent()) { -+ // Paper end - Add PlayerChangeBeaconEffectEvent -+ this.beaconData.set(1, BeaconMenu.encodeEffect(event.getPrimary() == null ? null : org.bukkit.craftbukkit.potion.CraftPotionEffectType.bukkitToMinecraftHolder(event.getPrimary())));// CraftBukkit - decompile error -+ this.beaconData.set(2, BeaconMenu.encodeEffect(event.getSecondary() == null ? null : org.bukkit.craftbukkit.potion.CraftPotionEffectType.bukkitToMinecraftHolder(event.getSecondary())));// CraftBukkit - decompile error -+ if (event.willConsumeItem()) { // Paper - this.paymentSlot.remove(1); -+ } // Paper - this.access.execute(Level::blockEntityChanged); -+ } // Paper end - Add PlayerChangeBeaconEffectEvent - } - - } diff --git a/patches/server/0478-Reset-shield-blocking-on-dimension-change.patch b/patches/server/0478-Reset-shield-blocking-on-dimension-change.patch new file mode 100644 index 000000000000..c1bc7de4e19d --- /dev/null +++ b/patches/server/0478-Reset-shield-blocking-on-dimension-change.patch @@ -0,0 +1,22 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Yive +Date: Sun, 24 Jan 2021 08:55:19 -0800 +Subject: [PATCH] Reset shield blocking on dimension change + + +diff --git a/src/main/java/net/minecraft/server/level/ServerPlayer.java b/src/main/java/net/minecraft/server/level/ServerPlayer.java +index 0ae490bb9b4cd781876054d0824c5392060208eb..3a2a4f6bc1de45c0dc2020357ee308064666f8d5 100644 +--- a/src/main/java/net/minecraft/server/level/ServerPlayer.java ++++ b/src/main/java/net/minecraft/server/level/ServerPlayer.java +@@ -1613,6 +1613,11 @@ public class ServerPlayer extends net.minecraft.world.entity.player.Player { + PlayerChangedWorldEvent changeEvent = new PlayerChangedWorldEvent(this.getBukkitEntity(), worldserver1.getWorld()); + this.level().getCraftServer().getPluginManager().callEvent(changeEvent); + // CraftBukkit end ++ // Paper start - Reset shield blocking on dimension change ++ if (this.isBlocking()) { ++ this.stopUsingItem(); ++ } ++ // Paper end - Reset shield blocking on dimension change + return this; + } + } diff --git a/patches/server/0479-Add-DragonEggFormEvent.patch b/patches/server/0479-Add-DragonEggFormEvent.patch new file mode 100644 index 000000000000..34506b3c79d0 --- /dev/null +++ b/patches/server/0479-Add-DragonEggFormEvent.patch @@ -0,0 +1,34 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Trigary +Date: Mon, 25 Jan 2021 14:53:57 +0100 +Subject: [PATCH] Add DragonEggFormEvent + + +diff --git a/src/main/java/net/minecraft/world/level/dimension/end/EndDragonFight.java b/src/main/java/net/minecraft/world/level/dimension/end/EndDragonFight.java +index 0e1eceb2b83aaafccbb5d58cf5098cfbc6f25a54..3d1a49d865e17a61ff74c6fe0efbd908447ee730 100644 +--- a/src/main/java/net/minecraft/world/level/dimension/end/EndDragonFight.java ++++ b/src/main/java/net/minecraft/world/level/dimension/end/EndDragonFight.java +@@ -410,8 +410,22 @@ public class EndDragonFight { + this.dragonEvent.setVisible(false); + this.spawnExitPortal(true); + this.spawnNewGateway(); ++ // Paper start - Add DragonEggFormEvent ++ BlockPos eggPosition = this.level.getHeightmapPos(Heightmap.Types.MOTION_BLOCKING, EndPodiumFeature.getLocation(this.origin)); ++ org.bukkit.craftbukkit.block.CraftBlockState eggState = org.bukkit.craftbukkit.block.CraftBlockStates.getBlockState(this.level, eggPosition); ++ eggState.setData(Blocks.DRAGON_EGG.defaultBlockState()); ++ io.papermc.paper.event.block.DragonEggFormEvent eggEvent = new io.papermc.paper.event.block.DragonEggFormEvent(org.bukkit.craftbukkit.block.CraftBlock.at(this.level, eggPosition), eggState, ++ new org.bukkit.craftbukkit.boss.CraftDragonBattle(this)); ++ // Paper end - Add DragonEggFormEvent + if (this.level.paperConfig().entities.behavior.enderDragonsDeathAlwaysPlacesDragonEgg || !this.previouslyKilled) { // Paper - Add toggle for always placing the dragon egg +- this.level.setBlockAndUpdate(this.level.getHeightmapPos(Heightmap.Types.MOTION_BLOCKING, EndPodiumFeature.getLocation(this.origin)), Blocks.DRAGON_EGG.defaultBlockState()); ++ // Paper start - Add DragonEggFormEvent ++ // this.level.setBlockAndUpdate(this.level.getHeightmapPos(Heightmap.Types.MOTION_BLOCKING, EndPodiumFeature.getLocation(this.origin)), Blocks.DRAGON_EGG.defaultBlockState()); ++ } else { ++ eggEvent.setCancelled(true); ++ } ++ if (eggEvent.callEvent()) { ++ eggEvent.getNewState().update(true); ++ // Paper end - Add DragonEggFormEvent + } + + this.previouslyKilled = true; diff --git a/patches/server/0479-Add-toggle-for-always-placing-the-dragon-egg.patch b/patches/server/0479-Add-toggle-for-always-placing-the-dragon-egg.patch deleted file mode 100644 index ccbab4766ec2..000000000000 --- a/patches/server/0479-Add-toggle-for-always-placing-the-dragon-egg.patch +++ /dev/null @@ -1,19 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: William Blake Galbreath -Date: Thu, 26 Nov 2020 11:47:24 +0000 -Subject: [PATCH] Add toggle for always placing the dragon egg - - -diff --git a/src/main/java/net/minecraft/world/level/dimension/end/EndDragonFight.java b/src/main/java/net/minecraft/world/level/dimension/end/EndDragonFight.java -index d6deedb96583c19eeb27e671289c4edc8a9245b4..4f3fdc6ea0cafd61f778edb6a967bd0eb00abdb9 100644 ---- a/src/main/java/net/minecraft/world/level/dimension/end/EndDragonFight.java -+++ b/src/main/java/net/minecraft/world/level/dimension/end/EndDragonFight.java -@@ -409,7 +409,7 @@ public class EndDragonFight { - this.dragonEvent.setVisible(false); - this.spawnExitPortal(true); - this.spawnNewGateway(); -- if (!this.previouslyKilled) { -+ if (this.level.paperConfig().entities.behavior.enderDragonsDeathAlwaysPlacesDragonEgg || !this.previouslyKilled) { // Paper - Add toggle for always placing the dragon egg - this.level.setBlockAndUpdate(this.level.getHeightmapPos(Heightmap.Types.MOTION_BLOCKING, EndPodiumFeature.getLocation(this.origin)), Blocks.DRAGON_EGG.defaultBlockState()); - } - diff --git a/patches/server/0480-Add-EntityMoveEvent.patch b/patches/server/0480-Add-EntityMoveEvent.patch new file mode 100644 index 000000000000..6dfa7990a673 --- /dev/null +++ b/patches/server/0480-Add-EntityMoveEvent.patch @@ -0,0 +1,55 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: William Blake Galbreath +Date: Tue, 11 Feb 2020 21:56:48 -0600 +Subject: [PATCH] Add EntityMoveEvent + + +diff --git a/src/main/java/net/minecraft/server/MinecraftServer.java b/src/main/java/net/minecraft/server/MinecraftServer.java +index 264d0f57422b4fc03c5845d7456b22c9a48dd7ae..a200aacb1eaa749baa36be8be63b3f1421913b60 100644 +--- a/src/main/java/net/minecraft/server/MinecraftServer.java ++++ b/src/main/java/net/minecraft/server/MinecraftServer.java +@@ -1648,6 +1648,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop 0; // Paper - BlockPhysicsEvent ++ worldserver.hasEntityMoveEvent = io.papermc.paper.event.entity.EntityMoveEvent.getHandlerList().getRegisteredListeners().length > 0; // Paper - Add EntityMoveEvent + + gameprofilerfiller.push(() -> { + String s = String.valueOf(worldserver); +diff --git a/src/main/java/net/minecraft/server/level/ServerLevel.java b/src/main/java/net/minecraft/server/level/ServerLevel.java +index 0e984f3521b578779dd9d0142bce7db433b78f07..1c3f41d7ed7320342fe68c5ab6eb57dbdfd4bf69 100644 +--- a/src/main/java/net/minecraft/server/level/ServerLevel.java ++++ b/src/main/java/net/minecraft/server/level/ServerLevel.java +@@ -229,6 +229,7 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe + public final LevelStorageSource.LevelStorageAccess convertable; + public final UUID uuid; + public boolean hasPhysicsEvent = true; // Paper - BlockPhysicsEvent ++ public boolean hasEntityMoveEvent; // Paper - Add EntityMoveEvent + + public LevelChunk getChunkIfLoaded(int x, int z) { + return this.chunkSource.getChunk(x, z, false); +diff --git a/src/main/java/net/minecraft/world/entity/LivingEntity.java b/src/main/java/net/minecraft/world/entity/LivingEntity.java +index d361e39ba50958e7bdaea0b95c37d13f48a89771..ce9369e730ba8862cd1e6e26f8825c35b16fcfb9 100644 +--- a/src/main/java/net/minecraft/world/entity/LivingEntity.java ++++ b/src/main/java/net/minecraft/world/entity/LivingEntity.java +@@ -3626,6 +3626,20 @@ public abstract class LivingEntity extends Entity implements Attackable { + + this.pushEntities(); + gameprofilerfiller.pop(); ++ // Paper start - Add EntityMoveEvent ++ if (((ServerLevel) this.level()).hasEntityMoveEvent && !(this instanceof net.minecraft.world.entity.player.Player)) { ++ if (this.xo != this.getX() || this.yo != this.getY() || this.zo != this.getZ() || this.yRotO != this.getYRot() || this.xRotO != this.getXRot()) { ++ Location from = new Location(this.level().getWorld(), this.xo, this.yo, this.zo, this.yRotO, this.xRotO); ++ Location to = new Location(this.level().getWorld(), this.getX(), this.getY(), this.getZ(), this.getYRot(), this.getXRot()); ++ io.papermc.paper.event.entity.EntityMoveEvent event = new io.papermc.paper.event.entity.EntityMoveEvent(this.getBukkitLivingEntity(), from, to.clone()); ++ if (!event.callEvent()) { ++ this.absMoveTo(from.getX(), from.getY(), from.getZ(), from.getYaw(), from.getPitch()); ++ } else if (!to.equals(event.getTo())) { ++ this.absMoveTo(event.getTo().getX(), event.getTo().getY(), event.getTo().getZ(), event.getTo().getYaw(), event.getTo().getPitch()); ++ } ++ } ++ } ++ // Paper end - Add EntityMoveEvent + world = this.level(); + if (world instanceof ServerLevel worldserver) { + if (this.isSensitiveToWater() && this.isInWaterRainOrBubble()) { diff --git a/patches/server/0480-Add-PlayerStonecutterRecipeSelectEvent.patch b/patches/server/0480-Add-PlayerStonecutterRecipeSelectEvent.patch deleted file mode 100644 index 0c96d8fa15f5..000000000000 --- a/patches/server/0480-Add-PlayerStonecutterRecipeSelectEvent.patch +++ /dev/null @@ -1,52 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Jake Potrebic -Date: Fri, 27 Nov 2020 17:14:27 -0800 -Subject: [PATCH] Add PlayerStonecutterRecipeSelectEvent - -Co-Authored-By: MiniDigger - -diff --git a/src/main/java/net/minecraft/world/inventory/StonecutterMenu.java b/src/main/java/net/minecraft/world/inventory/StonecutterMenu.java -index 5b4f03128499b0c1a4b8c5f5ccd17e4bdb391e81..37e75c02c374314372630f4bda0b92519809f2a4 100644 ---- a/src/main/java/net/minecraft/world/inventory/StonecutterMenu.java -+++ b/src/main/java/net/minecraft/world/inventory/StonecutterMenu.java -@@ -64,7 +64,7 @@ public class StonecutterMenu extends AbstractContainerMenu { - - public StonecutterMenu(int syncId, Inventory playerInventory, final ContainerLevelAccess context) { - super(MenuType.STONECUTTER, syncId); -- this.selectedRecipeIndex = DataSlot.standalone(); -+ this.selectedRecipeIndex = DataSlot.shared(new int[1], 0); // Paper - Add PlayerStonecutterRecipeSelectEvent - this.recipes = Lists.newArrayList(); - this.input = ItemStack.EMPTY; - this.slotUpdateListener = () -> { -@@ -162,7 +162,30 @@ public class StonecutterMenu extends AbstractContainerMenu { - @Override - public boolean clickMenuButton(net.minecraft.world.entity.player.Player player, int id) { - if (this.isValidRecipeIndex(id)) { -- this.selectedRecipeIndex.set(id); -+ // Paper start - Add PlayerStonecutterRecipeSelectEvent -+ int recipeIndex = id; -+ this.selectedRecipeIndex.set(recipeIndex); -+ this.selectedRecipeIndex.checkAndClearUpdateFlag(); // mark as changed -+ if (this.isValidRecipeIndex(id)) { -+ io.papermc.paper.event.player.PlayerStonecutterRecipeSelectEvent event = new io.papermc.paper.event.player.PlayerStonecutterRecipeSelectEvent((Player) player.getBukkitEntity(), (org.bukkit.inventory.StonecutterInventory) getBukkitView().getTopInventory(), (org.bukkit.inventory.StonecuttingRecipe) this.getRecipes().get(id).toBukkitRecipe()); -+ if (!event.callEvent()) { -+ player.containerMenu.sendAllDataToRemote(); -+ return false; -+ } -+ -+ net.minecraft.resources.ResourceLocation key = org.bukkit.craftbukkit.util.CraftNamespacedKey.toMinecraft(event.getStonecuttingRecipe().getKey()); -+ if (!this.getRecipes().get(recipeIndex).id().equals(key)) { // If the recipe did NOT stay the same -+ for (int newRecipeIndex = 0; newRecipeIndex < this.getRecipes().size(); newRecipeIndex++) { -+ if (this.getRecipes().get(newRecipeIndex).id().equals(key)) { -+ recipeIndex = newRecipeIndex; -+ break; -+ } -+ } -+ } -+ } -+ player.containerMenu.sendAllDataToRemote(); -+ this.selectedRecipeIndex.set(recipeIndex); // set new index, so that listeners can read it -+ // Paper end - Add PlayerStonecutterRecipeSelectEvent - this.setupResultSlot(); - } - diff --git a/patches/server/0481-Expand-EntityUnleashEvent.patch b/patches/server/0481-Expand-EntityUnleashEvent.patch deleted file mode 100644 index 39bfff38111e..000000000000 --- a/patches/server/0481-Expand-EntityUnleashEvent.patch +++ /dev/null @@ -1,158 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Nassim Jahnke -Date: Fri, 29 Jan 2021 15:13:11 +0100 -Subject: [PATCH] Expand EntityUnleashEvent - - -diff --git a/src/main/java/net/minecraft/world/entity/Entity.java b/src/main/java/net/minecraft/world/entity/Entity.java -index 7d131f3b3f5739468aa3115e97ed28b6bfeca33d..da184893d617311a43f9ce176a965f8417a2876d 100644 ---- a/src/main/java/net/minecraft/world/entity/Entity.java -+++ b/src/main/java/net/minecraft/world/entity/Entity.java -@@ -2574,12 +2574,15 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess - if (leashable.getLeashHolder() == player) { - if (!this.level().isClientSide()) { - // CraftBukkit start - fire PlayerUnleashEntityEvent -- if (CraftEventFactory.callPlayerUnleashEntityEvent(this, player, hand).isCancelled()) { -+ // Paper start - Expand EntityUnleashEvent -+ org.bukkit.event.player.PlayerUnleashEntityEvent event = CraftEventFactory.callPlayerUnleashEntityEvent(this, player, hand, !player.hasInfiniteMaterials()); -+ if (event.isCancelled()) { -+ // Paper end - Expand EntityUnleashEvent - ((ServerPlayer) player).connection.send(new ClientboundSetEntityLinkPacket(this, leashable.getLeashHolder())); - return InteractionResult.PASS; - } - // CraftBukkit end -- leashable.dropLeash(true, !player.hasInfiniteMaterials()); -+ leashable.dropLeash(true, event.isDropLeash()); // Paper - Expand EntityUnleashEvent - this.gameEvent(GameEvent.ENTITY_INTERACT, player); - } - -@@ -3449,9 +3452,12 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess - - protected void removeAfterChangingDimensions() { - this.setRemoved(Entity.RemovalReason.CHANGED_DIMENSION, null); // CraftBukkit - add Bukkit remove cause -- if (this instanceof Leashable leashable) { -- this.level().getCraftServer().getPluginManager().callEvent(new EntityUnleashEvent(this.getBukkitEntity(), UnleashReason.UNKNOWN)); // CraftBukkit -- leashable.dropLeash(true, false); -+ if (this instanceof Leashable leashable && leashable.isLeashed()) { // Paper - only call if it is leashed -+ // Paper start - Expand EntityUnleashEvent -+ final EntityUnleashEvent event = new EntityUnleashEvent(this.getBukkitEntity(), UnleashReason.UNKNOWN, false); // CraftBukkit -+ event.callEvent(); -+ leashable.dropLeash(true, event.isDropLeash()); -+ // Paper end - Expand EntityUnleashEvent - } - - } -diff --git a/src/main/java/net/minecraft/world/entity/Leashable.java b/src/main/java/net/minecraft/world/entity/Leashable.java -index 5c51dd5229689cba459655d488aee59bd159a414..e7535f15be3cc1537aafee53779ccfb4f21d1f38 100644 ---- a/src/main/java/net/minecraft/world/entity/Leashable.java -+++ b/src/main/java/net/minecraft/world/entity/Leashable.java -@@ -166,8 +166,11 @@ public interface Leashable { - - if (leashable_a != null && leashable_a.leashHolder != null) { - if (!entity.isAlive() || !leashable_a.leashHolder.isAlive()) { -- entity.level().getCraftServer().getPluginManager().callEvent(new EntityUnleashEvent(entity.getBukkitEntity(), (!entity.isAlive()) ? UnleashReason.PLAYER_UNLEASH : UnleashReason.HOLDER_GONE)); // CraftBukkit -- Leashable.dropLeash(entity, true, !entity.pluginRemoved); // CraftBukkit - SPIGOT-7487: Don't drop leash, when the holder was removed by a plugin -+ // Paper start - Expand EntityUnleashEvent -+ final EntityUnleashEvent event = new EntityUnleashEvent(entity.getBukkitEntity(), (!entity.isAlive()) ? UnleashReason.PLAYER_UNLEASH : UnleashReason.HOLDER_GONE, !entity.pluginRemoved); -+ event.callEvent(); -+ Leashable.dropLeash(entity, true, event.isDropLeash()); // CraftBukkit - SPIGOT-7487: Don't drop leash, when the holder was removed by a plugin -+ // Paper end - Expand EntityUnleashEvent - } - - Entity entity1 = ((Leashable) entity).getLeashHolder(); -@@ -198,11 +201,16 @@ public interface Leashable { - - default void leashTooFarBehaviour() { - // CraftBukkit start -+ boolean dropLeash = true; // Paper - if (this instanceof Entity entity) { -- entity.level().getCraftServer().getPluginManager().callEvent(new EntityUnleashEvent(entity.getBukkitEntity(), EntityUnleashEvent.UnleashReason.DISTANCE)); -+ // Paper start - Expand EntityUnleashEvent -+ final EntityUnleashEvent event = new EntityUnleashEvent(entity.getBukkitEntity(), EntityUnleashEvent.UnleashReason.DISTANCE, true); -+ if (!event.callEvent()) return; -+ dropLeash = event.isDropLeash(); -+ // Paper end - Expand EntityUnleashEvent - } - // CraftBukkit end -- this.dropLeash(true, true); -+ this.dropLeash(true, dropLeash); // Paper - } - - default void closeRangeLeashBehaviour(Entity entity) {} -diff --git a/src/main/java/net/minecraft/world/entity/Mob.java b/src/main/java/net/minecraft/world/entity/Mob.java -index b46572f6e3b52f498b395d3b8c5def2aa799ff03..e87360e21e6eb7b0161c34a3ac6cb83d18bcd1e8 100644 ---- a/src/main/java/net/minecraft/world/entity/Mob.java -+++ b/src/main/java/net/minecraft/world/entity/Mob.java -@@ -1621,8 +1621,11 @@ public abstract class Mob extends LivingEntity implements EquipmentUser, Leashab - boolean flag1 = super.startRiding(entity, force); - - if (flag1 && this.isLeashed()) { -- this.level().getCraftServer().getPluginManager().callEvent(new EntityUnleashEvent(this.getBukkitEntity(), UnleashReason.UNKNOWN)); // CraftBukkit -- this.dropLeash(true, true); -+ // Paper start - Expand EntityUnleashEvent -+ EntityUnleashEvent event = new EntityUnleashEvent(this.getBukkitEntity(), EntityUnleashEvent.UnleashReason.UNKNOWN, true); -+ if (!event.callEvent()) { return flag1; } -+ this.dropLeash(true, event.isDropLeash()); -+ // Paper end - Expand EntityUnleashEvent - } - - return flag1; -diff --git a/src/main/java/net/minecraft/world/entity/TamableAnimal.java b/src/main/java/net/minecraft/world/entity/TamableAnimal.java -index bf2c9134c7d9d5926add36b55e3cfea79e8c8243..7b7bc1a205dfacbe5709011b6b6799e75af9e4cc 100644 ---- a/src/main/java/net/minecraft/world/entity/TamableAnimal.java -+++ b/src/main/java/net/minecraft/world/entity/TamableAnimal.java -@@ -98,7 +98,11 @@ public abstract class TamableAnimal extends Animal implements OwnableEntity { - public boolean handleLeashAtDistance(Entity leashHolder, float distance) { - if (this.isInSittingPose()) { - if (distance > (float) this.level().paperConfig().misc.maxLeashDistance.or(Leashable.LEASH_TOO_FAR_DIST)) { // Paper - Configurable max leash distance -- this.dropLeash(true, true); -+ // Paper start - Expand EntityUnleashEvent -+ org.bukkit.event.entity.EntityUnleashEvent event = new org.bukkit.event.entity.EntityUnleashEvent(this.getBukkitEntity(), org.bukkit.event.entity.EntityUnleashEvent.UnleashReason.DISTANCE, true); -+ if (!event.callEvent()) return false; -+ this.dropLeash(true, event.isDropLeash()); -+ // Paper end - Expand EntityUnleashEvent - } - - return false; -diff --git a/src/main/java/net/minecraft/world/entity/decoration/LeashFenceKnotEntity.java b/src/main/java/net/minecraft/world/entity/decoration/LeashFenceKnotEntity.java -index 3c0af74ed65610b1d5e3b72fdcf28c5a3423f0da..01173fc7177d78588978e087e63efda0b0527c2f 100644 ---- a/src/main/java/net/minecraft/world/entity/decoration/LeashFenceKnotEntity.java -+++ b/src/main/java/net/minecraft/world/entity/decoration/LeashFenceKnotEntity.java -@@ -118,13 +118,18 @@ public class LeashFenceKnotEntity extends BlockAttachedEntity { - - if (leashable1.isLeashed() && leashable1.getLeashHolder() == this) { - // CraftBukkit start -+ boolean dropLeash = !player.hasInfiniteMaterials(); - if (leashable1 instanceof Entity leashed) { -- if (CraftEventFactory.callPlayerUnleashEntityEvent(leashed, player, hand).isCancelled()) { -+ // Paper start - Expand EntityUnleashEvent -+ org.bukkit.event.player.PlayerUnleashEntityEvent event = CraftEventFactory.callPlayerUnleashEntityEvent(leashed, player, hand, dropLeash); -+ dropLeash = event.isDropLeash(); -+ if (event.isCancelled()) { -+ // Paper end - Expand EntityUnleashEvent - die = false; - continue; - } - } -- leashable1.dropLeash(true, !player.getAbilities().instabuild); // false -> survival mode boolean -+ leashable1.dropLeash(true, dropLeash); // false -> survival mode boolean // Paper - Expand EntityUnleashEvent - // CraftBukkit end - flag1 = true; - } -diff --git a/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java b/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java -index 251b605396b77b0c449972c1da75978f57587e1a..c48a9026c4db2005b67c5f70e9e1fa95b7820bc0 100644 ---- a/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java -+++ b/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java -@@ -1598,8 +1598,10 @@ public class CraftEventFactory { - Bukkit.getPluginManager().callEvent(new PlayerRecipeBookSettingsChangeEvent(player.getBukkitEntity(), bukkitType, open, filter)); - } - -- public static PlayerUnleashEntityEvent callPlayerUnleashEntityEvent(Entity entity, net.minecraft.world.entity.player.Player player, InteractionHand enumhand) { -- PlayerUnleashEntityEvent event = new PlayerUnleashEntityEvent(entity.getBukkitEntity(), (Player) player.getBukkitEntity(), CraftEquipmentSlot.getHand(enumhand)); -+ // Paper start - Expand EntityUnleashEvent -+ public static PlayerUnleashEntityEvent callPlayerUnleashEntityEvent(Entity entity, net.minecraft.world.entity.player.Player player, InteractionHand enumhand, boolean dropLeash) { -+ PlayerUnleashEntityEvent event = new PlayerUnleashEntityEvent(entity.getBukkitEntity(), (Player) player.getBukkitEntity(), CraftEquipmentSlot.getHand(enumhand), dropLeash); -+ // Paper end - Expand EntityUnleashEvent - entity.level().getCraftServer().getPluginManager().callEvent(event); - return event; - } diff --git a/patches/server/0481-added-option-to-disable-pathfinding-updates-on-block.patch b/patches/server/0481-added-option-to-disable-pathfinding-updates-on-block.patch new file mode 100644 index 000000000000..d10ca06cedc3 --- /dev/null +++ b/patches/server/0481-added-option-to-disable-pathfinding-updates-on-block.patch @@ -0,0 +1,26 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: lukas81298 +Date: Mon, 25 Jan 2021 14:37:57 +0100 +Subject: [PATCH] added option to disable pathfinding updates on block changes + + +diff --git a/src/main/java/net/minecraft/server/level/ServerLevel.java b/src/main/java/net/minecraft/server/level/ServerLevel.java +index 1c3f41d7ed7320342fe68c5ab6eb57dbdfd4bf69..cefd68cd2f28e5c14dba99b31d9a88125f169337 100644 +--- a/src/main/java/net/minecraft/server/level/ServerLevel.java ++++ b/src/main/java/net/minecraft/server/level/ServerLevel.java +@@ -1357,6 +1357,7 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe + + this.getChunkSource().blockChanged(pos); + this.pathTypesByPosCache.invalidate(pos); ++ if (this.paperConfig().misc.updatePathfindingOnBlockUpdate) { // Paper - option to disable pathfinding updates + VoxelShape voxelshape = oldState.getCollisionShape(this, pos); + VoxelShape voxelshape1 = newState.getCollisionShape(this, pos); + +@@ -1398,6 +1399,7 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe + } + + } ++ } // Paper - option to disable pathfinding updates + } + + @Override diff --git a/patches/server/0482-Inline-shift-direction-fields.patch b/patches/server/0482-Inline-shift-direction-fields.patch new file mode 100644 index 000000000000..22234bc09a89 --- /dev/null +++ b/patches/server/0482-Inline-shift-direction-fields.patch @@ -0,0 +1,56 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Andrew Steinborn +Date: Mon, 18 Jan 2021 20:45:25 -0500 +Subject: [PATCH] Inline shift direction fields + +Removes a layer of indirection for EnumDirection.getAdjacent(X|Y|Z)(), which is in the +critical section for much of the server, including the lighting engine. + +diff --git a/src/main/java/net/minecraft/core/Direction.java b/src/main/java/net/minecraft/core/Direction.java +index 69ccada8ea78c05d3fb886222698c07dee421506..3fde5abde736b2c19d8819d9aec0397a0245ccd1 100644 +--- a/src/main/java/net/minecraft/core/Direction.java ++++ b/src/main/java/net/minecraft/core/Direction.java +@@ -57,6 +57,12 @@ public enum Direction implements StringRepresentable { + .sorted(Comparator.comparingInt(direction -> direction.data2d)) + .toArray(Direction[]::new); + ++ // Paper start - Perf: Inline shift direction fields ++ private final int adjX; ++ private final int adjY; ++ private final int adjZ; ++ // Paper end - Perf: Inline shift direction fields ++ + private Direction( + final int id, + final int idOpposite, +@@ -74,6 +80,11 @@ public enum Direction implements StringRepresentable { + this.axisDirection = direction; + this.normal = vector; + this.normalVec3 = Vec3.atLowerCornerOf(vector); ++ // Paper start - Perf: Inline shift direction fields ++ this.adjX = vector.getX(); ++ this.adjY = vector.getY(); ++ this.adjZ = vector.getZ(); ++ // Paper end - Perf: Inline shift direction fields + } + + public static Direction[] orderedByNearest(Entity entity) { +@@ -247,15 +258,15 @@ public enum Direction implements StringRepresentable { + } + + public int getStepX() { +- return this.normal.getX(); ++ return this.adjX; // Paper - Perf: Inline shift direction fields + } + + public int getStepY() { +- return this.normal.getY(); ++ return this.adjY; // Paper - Perf: Inline shift direction fields + } + + public int getStepZ() { +- return this.normal.getZ(); ++ return this.adjZ; // Paper - Perf: Inline shift direction fields + } + + public Vector3f step() { diff --git a/patches/server/0482-Reset-shield-blocking-on-dimension-change.patch b/patches/server/0482-Reset-shield-blocking-on-dimension-change.patch deleted file mode 100644 index f0f37986b6c8..000000000000 --- a/patches/server/0482-Reset-shield-blocking-on-dimension-change.patch +++ /dev/null @@ -1,22 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Yive -Date: Sun, 24 Jan 2021 08:55:19 -0800 -Subject: [PATCH] Reset shield blocking on dimension change - - -diff --git a/src/main/java/net/minecraft/server/level/ServerPlayer.java b/src/main/java/net/minecraft/server/level/ServerPlayer.java -index 2ea613f818403f8e8ece4b36891738139345cf89..fffd3ae6c4fc0b86057cd915bb4f0987f41a64d0 100644 ---- a/src/main/java/net/minecraft/server/level/ServerPlayer.java -+++ b/src/main/java/net/minecraft/server/level/ServerPlayer.java -@@ -1349,6 +1349,11 @@ public class ServerPlayer extends net.minecraft.world.entity.player.Player { - PlayerChangedWorldEvent changeEvent = new PlayerChangedWorldEvent(this.getBukkitEntity(), worldserver1.getWorld()); - this.level().getCraftServer().getPluginManager().callEvent(changeEvent); - // CraftBukkit end -+ // Paper start - Reset shield blocking on dimension change -+ if (this.isBlocking()) { -+ this.stopUsingItem(); -+ } -+ // Paper end - Reset shield blocking on dimension change - return this; - } - } diff --git a/patches/server/0483-Add-DragonEggFormEvent.patch b/patches/server/0483-Add-DragonEggFormEvent.patch deleted file mode 100644 index 8a1ea595343e..000000000000 --- a/patches/server/0483-Add-DragonEggFormEvent.patch +++ /dev/null @@ -1,34 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Trigary -Date: Mon, 25 Jan 2021 14:53:57 +0100 -Subject: [PATCH] Add DragonEggFormEvent - - -diff --git a/src/main/java/net/minecraft/world/level/dimension/end/EndDragonFight.java b/src/main/java/net/minecraft/world/level/dimension/end/EndDragonFight.java -index 4f3fdc6ea0cafd61f778edb6a967bd0eb00abdb9..460744ec3a1abe9a2d9d16c2ec521c52c7f8db95 100644 ---- a/src/main/java/net/minecraft/world/level/dimension/end/EndDragonFight.java -+++ b/src/main/java/net/minecraft/world/level/dimension/end/EndDragonFight.java -@@ -409,8 +409,22 @@ public class EndDragonFight { - this.dragonEvent.setVisible(false); - this.spawnExitPortal(true); - this.spawnNewGateway(); -+ // Paper start - Add DragonEggFormEvent -+ BlockPos eggPosition = this.level.getHeightmapPos(Heightmap.Types.MOTION_BLOCKING, EndPodiumFeature.getLocation(this.origin)); -+ org.bukkit.craftbukkit.block.CraftBlockState eggState = org.bukkit.craftbukkit.block.CraftBlockStates.getBlockState(this.level, eggPosition); -+ eggState.setData(Blocks.DRAGON_EGG.defaultBlockState()); -+ io.papermc.paper.event.block.DragonEggFormEvent eggEvent = new io.papermc.paper.event.block.DragonEggFormEvent(org.bukkit.craftbukkit.block.CraftBlock.at(this.level, eggPosition), eggState, -+ new org.bukkit.craftbukkit.boss.CraftDragonBattle(this)); -+ // Paper end - Add DragonEggFormEvent - if (this.level.paperConfig().entities.behavior.enderDragonsDeathAlwaysPlacesDragonEgg || !this.previouslyKilled) { // Paper - Add toggle for always placing the dragon egg -- this.level.setBlockAndUpdate(this.level.getHeightmapPos(Heightmap.Types.MOTION_BLOCKING, EndPodiumFeature.getLocation(this.origin)), Blocks.DRAGON_EGG.defaultBlockState()); -+ // Paper start - Add DragonEggFormEvent -+ // this.level.setBlockAndUpdate(this.level.getHeightmapPos(Heightmap.Types.MOTION_BLOCKING, EndPodiumFeature.getLocation(this.origin)), Blocks.DRAGON_EGG.defaultBlockState()); -+ } else { -+ eggEvent.setCancelled(true); -+ } -+ if (eggEvent.callEvent()) { -+ eggEvent.getNewState().update(true); -+ // Paper end - Add DragonEggFormEvent - } - - this.previouslyKilled = true; diff --git a/patches/server/0483-Allow-adding-items-to-BlockDropItemEvent.patch b/patches/server/0483-Allow-adding-items-to-BlockDropItemEvent.patch new file mode 100644 index 000000000000..5fe7914ae000 --- /dev/null +++ b/patches/server/0483-Allow-adding-items-to-BlockDropItemEvent.patch @@ -0,0 +1,44 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: BillyGalbreath +Date: Wed, 20 Jan 2021 14:23:37 -0600 +Subject: [PATCH] Allow adding items to BlockDropItemEvent + + +diff --git a/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java b/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java +index 5064dd8c97f45f0c4e2d1402500e7cf67a263dfa..a5c9ff3a2f06e7207ae4fcaf69be8bd0c874d4e2 100644 +--- a/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java ++++ b/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java +@@ -461,13 +461,30 @@ public class CraftEventFactory { + } + + public static void handleBlockDropItemEvent(Block block, BlockState state, ServerPlayer player, List items) { +- BlockDropItemEvent event = new BlockDropItemEvent(block, state, player.getBukkitEntity(), Lists.transform(items, (item) -> (org.bukkit.entity.Item) item.getBukkitEntity())); ++ // Paper start - Allow adding items to BlockDropItemEvent ++ List list = new ArrayList<>(); ++ for (ItemEntity item : items) { ++ list.add((Item) item.getBukkitEntity()); ++ } ++ BlockDropItemEvent event = new BlockDropItemEvent(block, state, player.getBukkitEntity(), list); ++ // Paper end - Allow adding items to BlockDropItemEvent + Bukkit.getPluginManager().callEvent(event); + + if (!event.isCancelled()) { +- for (ItemEntity item : items) { +- item.level().addFreshEntity(item); ++ // Paper start - Allow adding items to BlockDropItemEvent ++ for (Item bukkit : list) { ++ if (!bukkit.isValid()) { ++ Entity item = ((org.bukkit.craftbukkit.entity.CraftItem) bukkit).getHandle(); ++ item.level().addFreshEntity(item); ++ } ++ } ++ } else { ++ for (Item bukkit : list) { ++ if (bukkit.isValid()) { ++ bukkit.remove(); ++ } + } ++ // Paper end - Allow adding items to BlockDropItemEvent + } + } + diff --git a/patches/server/0484-Add-EntityMoveEvent.patch b/patches/server/0484-Add-EntityMoveEvent.patch deleted file mode 100644 index 9b7d66dfba07..000000000000 --- a/patches/server/0484-Add-EntityMoveEvent.patch +++ /dev/null @@ -1,55 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: William Blake Galbreath -Date: Tue, 11 Feb 2020 21:56:48 -0600 -Subject: [PATCH] Add EntityMoveEvent - - -diff --git a/src/main/java/net/minecraft/server/MinecraftServer.java b/src/main/java/net/minecraft/server/MinecraftServer.java -index 39ce74bc3fbb282f56028273d53f980171bdb464..43b28b56c7949f95af5b7e99b04b93ed711d8880 100644 ---- a/src/main/java/net/minecraft/server/MinecraftServer.java -+++ b/src/main/java/net/minecraft/server/MinecraftServer.java -@@ -1605,6 +1605,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop 0; // Paper - BlockPhysicsEvent -+ worldserver.hasEntityMoveEvent = io.papermc.paper.event.entity.EntityMoveEvent.getHandlerList().getRegisteredListeners().length > 0; // Paper - Add EntityMoveEvent - - this.profiler.push(() -> { - String s = String.valueOf(worldserver); -diff --git a/src/main/java/net/minecraft/server/level/ServerLevel.java b/src/main/java/net/minecraft/server/level/ServerLevel.java -index 373560fc82e491e6c8b755fecfe78d49a2fc3e2f..644341b8bc3079c6f092226acd11667f64bd55a1 100644 ---- a/src/main/java/net/minecraft/server/level/ServerLevel.java -+++ b/src/main/java/net/minecraft/server/level/ServerLevel.java -@@ -228,6 +228,7 @@ public class ServerLevel extends Level implements WorldGenLevel { - public final LevelStorageSource.LevelStorageAccess convertable; - public final UUID uuid; - public boolean hasPhysicsEvent = true; // Paper - BlockPhysicsEvent -+ public boolean hasEntityMoveEvent; // Paper - Add EntityMoveEvent - - public LevelChunk getChunkIfLoaded(int x, int z) { - return this.chunkSource.getChunk(x, z, false); -diff --git a/src/main/java/net/minecraft/world/entity/LivingEntity.java b/src/main/java/net/minecraft/world/entity/LivingEntity.java -index d94c3e86885c5a8ba636988d29f98b496e13b627..a89c6ba4140f15e7f68f0df5cf64a176d350123b 100644 ---- a/src/main/java/net/minecraft/world/entity/LivingEntity.java -+++ b/src/main/java/net/minecraft/world/entity/LivingEntity.java -@@ -3510,6 +3510,20 @@ public abstract class LivingEntity extends Entity implements Attackable { - - this.pushEntities(); - this.level().getProfiler().pop(); -+ // Paper start - Add EntityMoveEvent -+ if (((ServerLevel) this.level()).hasEntityMoveEvent && !(this instanceof net.minecraft.world.entity.player.Player)) { -+ if (this.xo != this.getX() || this.yo != this.getY() || this.zo != this.getZ() || this.yRotO != this.getYRot() || this.xRotO != this.getXRot()) { -+ Location from = new Location(this.level().getWorld(), this.xo, this.yo, this.zo, this.yRotO, this.xRotO); -+ Location to = new Location(this.level().getWorld(), this.getX(), this.getY(), this.getZ(), this.getYRot(), this.getXRot()); -+ io.papermc.paper.event.entity.EntityMoveEvent event = new io.papermc.paper.event.entity.EntityMoveEvent(this.getBukkitLivingEntity(), from, to.clone()); -+ if (!event.callEvent()) { -+ this.absMoveTo(from.getX(), from.getY(), from.getZ(), from.getYaw(), from.getPitch()); -+ } else if (!to.equals(event.getTo())) { -+ this.absMoveTo(event.getTo().getX(), event.getTo().getY(), event.getTo().getZ(), event.getTo().getYaw(), event.getTo().getPitch()); -+ } -+ } -+ } -+ // Paper end - Add EntityMoveEvent - if (!this.level().isClientSide && this.isSensitiveToWater() && this.isInWaterRainOrBubble()) { - this.hurt(this.damageSources().drown(), 1.0F); - } diff --git a/patches/server/0484-Add-getMainThreadExecutor-to-BukkitScheduler.patch b/patches/server/0484-Add-getMainThreadExecutor-to-BukkitScheduler.patch new file mode 100644 index 000000000000..ead28f59cbb5 --- /dev/null +++ b/patches/server/0484-Add-getMainThreadExecutor-to-BukkitScheduler.patch @@ -0,0 +1,26 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Aleksander Jagiello +Date: Sun, 24 Jan 2021 22:17:54 +0100 +Subject: [PATCH] Add getMainThreadExecutor to BukkitScheduler + + +diff --git a/src/main/java/org/bukkit/craftbukkit/scheduler/CraftScheduler.java b/src/main/java/org/bukkit/craftbukkit/scheduler/CraftScheduler.java +index a700dac93499650fdaa0af06ff77607ffa4dbbb2..6fef86e47e37eab6721cfd67d494afb25a2ded68 100644 +--- a/src/main/java/org/bukkit/craftbukkit/scheduler/CraftScheduler.java ++++ b/src/main/java/org/bukkit/craftbukkit/scheduler/CraftScheduler.java +@@ -637,4 +637,15 @@ public class CraftScheduler implements BukkitScheduler { + public BukkitTask runTaskTimerAsynchronously(Plugin plugin, BukkitRunnable task, long delay, long period) throws IllegalArgumentException { + throw new UnsupportedOperationException("Use BukkitRunnable#runTaskTimerAsynchronously(Plugin, long, long)"); + } ++ ++ // Paper start - add getMainThreadExecutor ++ @Override ++ public Executor getMainThreadExecutor(Plugin plugin) { ++ Preconditions.checkArgument(plugin != null, "Plugin cannot be null"); ++ return command -> { ++ Preconditions.checkArgument(command != null, "Command cannot be null"); ++ this.runTask(plugin, command); ++ }; ++ } ++ // Paper end + } diff --git a/patches/server/0485-added-option-to-disable-pathfinding-updates-on-block.patch b/patches/server/0485-added-option-to-disable-pathfinding-updates-on-block.patch deleted file mode 100644 index e40ef31e1624..000000000000 --- a/patches/server/0485-added-option-to-disable-pathfinding-updates-on-block.patch +++ /dev/null @@ -1,26 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: lukas81298 -Date: Mon, 25 Jan 2021 14:37:57 +0100 -Subject: [PATCH] added option to disable pathfinding updates on block changes - - -diff --git a/src/main/java/net/minecraft/server/level/ServerLevel.java b/src/main/java/net/minecraft/server/level/ServerLevel.java -index 644341b8bc3079c6f092226acd11667f64bd55a1..1fe013b94cf1b5332f1e4645dd35df01e11fe0d9 100644 ---- a/src/main/java/net/minecraft/server/level/ServerLevel.java -+++ b/src/main/java/net/minecraft/server/level/ServerLevel.java -@@ -1374,6 +1374,7 @@ public class ServerLevel extends Level implements WorldGenLevel { - - this.getChunkSource().blockChanged(pos); - this.pathTypesByPosCache.invalidate(pos); -+ if (this.paperConfig().misc.updatePathfindingOnBlockUpdate) { // Paper - option to disable pathfinding updates - VoxelShape voxelshape = oldState.getCollisionShape(this, pos); - VoxelShape voxelshape1 = newState.getCollisionShape(this, pos); - -@@ -1415,6 +1416,7 @@ public class ServerLevel extends Level implements WorldGenLevel { - } - - } -+ } // Paper - option to disable pathfinding updates - } - - @Override diff --git a/patches/server/0485-living-entity-allow-attribute-registration.patch b/patches/server/0485-living-entity-allow-attribute-registration.patch new file mode 100644 index 000000000000..e9e4ab2f4883 --- /dev/null +++ b/patches/server/0485-living-entity-allow-attribute-registration.patch @@ -0,0 +1,57 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: ysl3000 +Date: Sat, 24 Oct 2020 16:37:44 +0200 +Subject: [PATCH] living entity allow attribute registration + + +diff --git a/src/main/java/net/minecraft/world/entity/ai/attributes/AttributeMap.java b/src/main/java/net/minecraft/world/entity/ai/attributes/AttributeMap.java +index 3a6c55a7f07e8871a77c91679732dd63db604004..94d04a20f97405e02d7cccaabadc7a7e86e336f7 100644 +--- a/src/main/java/net/minecraft/world/entity/ai/attributes/AttributeMap.java ++++ b/src/main/java/net/minecraft/world/entity/ai/attributes/AttributeMap.java +@@ -149,4 +149,12 @@ public class AttributeMap { + } + } + } ++ ++ // Paper - start - living entity allow attribute registration ++ public void registerAttribute(Holder attributeBase) { ++ AttributeInstance attributeModifiable = new AttributeInstance(attributeBase, AttributeInstance::getAttribute); ++ attributes.put(attributeBase, attributeModifiable); ++ } ++ // Paper - end - living entity allow attribute registration ++ + } +diff --git a/src/main/java/org/bukkit/craftbukkit/attribute/CraftAttributeMap.java b/src/main/java/org/bukkit/craftbukkit/attribute/CraftAttributeMap.java +index 5678d2007d5adf45dec0638c5dd848b601801814..0a7ed5a4f1644a70d8f98ad7a6962b814ad6daf4 100644 +--- a/src/main/java/org/bukkit/craftbukkit/attribute/CraftAttributeMap.java ++++ b/src/main/java/org/bukkit/craftbukkit/attribute/CraftAttributeMap.java +@@ -35,4 +35,11 @@ public class CraftAttributeMap implements Attributable { + + return (nms == null) ? null : new CraftAttributeInstance(nms, attribute); + } ++ // Paper start - living entity allow attribute registration ++ @Override ++ public void registerAttribute(Attribute attribute) { ++ Preconditions.checkArgument(attribute != null, "attribute"); ++ handle.registerAttribute(CraftAttribute.bukkitToMinecraftHolder(attribute)); ++ } ++ // Paper end - living entity allow attribute registration + } +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftLivingEntity.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftLivingEntity.java +index ff3b53eff8f5fc1e02e7b30d59ff27dfe8f5d431..5749e2b5174be23633c8a811baec8c05da12e3e2 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftLivingEntity.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftLivingEntity.java +@@ -775,6 +775,13 @@ public class CraftLivingEntity extends CraftEntity implements LivingEntity { + return this.getHandle().craftAttributes.getAttribute(attribute); + } + ++ // Paper start - living entity allow attribute registration ++ @Override ++ public void registerAttribute(Attribute attribute) { ++ getHandle().craftAttributes.registerAttribute(attribute); ++ } ++ // Paper end - living entity allow attribute registration ++ + @Override + public void setAI(boolean ai) { + if (this.getHandle() instanceof Mob) { diff --git a/patches/server/0486-Inline-shift-direction-fields.patch b/patches/server/0486-Inline-shift-direction-fields.patch deleted file mode 100644 index 910b881e3a64..000000000000 --- a/patches/server/0486-Inline-shift-direction-fields.patch +++ /dev/null @@ -1,56 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Andrew Steinborn -Date: Mon, 18 Jan 2021 20:45:25 -0500 -Subject: [PATCH] Inline shift direction fields - -Removes a layer of indirection for EnumDirection.getAdjacent(X|Y|Z)(), which is in the -critical section for much of the server, including the lighting engine. - -diff --git a/src/main/java/net/minecraft/core/Direction.java b/src/main/java/net/minecraft/core/Direction.java -index c47e6acbd36737aa7af13b452f51b3f017d324f4..03c45ee77276462818a6f774b5945b25924aa3f0 100644 ---- a/src/main/java/net/minecraft/core/Direction.java -+++ b/src/main/java/net/minecraft/core/Direction.java -@@ -55,6 +55,12 @@ public enum Direction implements StringRepresentable { - .sorted(Comparator.comparingInt(direction -> direction.data2d)) - .toArray(Direction[]::new); - -+ // Paper start - Perf: Inline shift direction fields -+ private final int adjX; -+ private final int adjY; -+ private final int adjZ; -+ // Paper end - Perf: Inline shift direction fields -+ - private Direction( - final int id, - final int idOpposite, -@@ -71,6 +77,11 @@ public enum Direction implements StringRepresentable { - this.axis = axis; - this.axisDirection = direction; - this.normal = vector; -+ // Paper start - Perf: Inline shift direction fields -+ this.adjX = vector.getX(); -+ this.adjY = vector.getY(); -+ this.adjZ = vector.getZ(); -+ // Paper end - Perf: Inline shift direction fields - } - - public static Direction[] orderedByNearest(Entity entity) { -@@ -234,15 +245,15 @@ public enum Direction implements StringRepresentable { - } - - public int getStepX() { -- return this.normal.getX(); -+ return this.adjX; // Paper - Perf: Inline shift direction fields - } - - public int getStepY() { -- return this.normal.getY(); -+ return this.adjY; // Paper - Perf: Inline shift direction fields - } - - public int getStepZ() { -- return this.normal.getZ(); -+ return this.adjZ; // Paper - Perf: Inline shift direction fields - } - - public Vector3f step() { diff --git a/patches/server/0490-fix-dead-slime-setSize-invincibility.patch b/patches/server/0486-fix-dead-slime-setSize-invincibility.patch similarity index 100% rename from patches/server/0490-fix-dead-slime-setSize-invincibility.patch rename to patches/server/0486-fix-dead-slime-setSize-invincibility.patch diff --git a/patches/server/0487-Allow-adding-items-to-BlockDropItemEvent.patch b/patches/server/0487-Allow-adding-items-to-BlockDropItemEvent.patch deleted file mode 100644 index 4753f93e4114..000000000000 --- a/patches/server/0487-Allow-adding-items-to-BlockDropItemEvent.patch +++ /dev/null @@ -1,44 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: BillyGalbreath -Date: Wed, 20 Jan 2021 14:23:37 -0600 -Subject: [PATCH] Allow adding items to BlockDropItemEvent - - -diff --git a/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java b/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java -index c48a9026c4db2005b67c5f70e9e1fa95b7820bc0..36adcab02e97cae2d087bae74cc4ceaf3052a9f8 100644 ---- a/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java -+++ b/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java -@@ -462,13 +462,30 @@ public class CraftEventFactory { - } - - public static void handleBlockDropItemEvent(Block block, BlockState state, ServerPlayer player, List items) { -- BlockDropItemEvent event = new BlockDropItemEvent(block, state, player.getBukkitEntity(), Lists.transform(items, (item) -> (org.bukkit.entity.Item) item.getBukkitEntity())); -+ // Paper start - Allow adding items to BlockDropItemEvent -+ List list = new ArrayList<>(); -+ for (ItemEntity item : items) { -+ list.add((Item) item.getBukkitEntity()); -+ } -+ BlockDropItemEvent event = new BlockDropItemEvent(block, state, player.getBukkitEntity(), list); -+ // Paper end - Allow adding items to BlockDropItemEvent - Bukkit.getPluginManager().callEvent(event); - - if (!event.isCancelled()) { -- for (ItemEntity item : items) { -- item.level().addFreshEntity(item); -+ // Paper start - Allow adding items to BlockDropItemEvent -+ for (Item bukkit : list) { -+ if (!bukkit.isValid()) { -+ Entity item = ((org.bukkit.craftbukkit.entity.CraftItem) bukkit).getHandle(); -+ item.level().addFreshEntity(item); -+ } -+ } -+ } else { -+ for (Item bukkit : list) { -+ if (bukkit.isValid()) { -+ bukkit.remove(); -+ } - } -+ // Paper end - Allow adding items to BlockDropItemEvent - } - } - diff --git a/patches/server/0491-Merchant-getRecipes-should-return-an-immutable-list.patch b/patches/server/0487-Merchant-getRecipes-should-return-an-immutable-list.patch similarity index 100% rename from patches/server/0491-Merchant-getRecipes-should-return-an-immutable-list.patch rename to patches/server/0487-Merchant-getRecipes-should-return-an-immutable-list.patch diff --git a/patches/server/0488-Add-getMainThreadExecutor-to-BukkitScheduler.patch b/patches/server/0488-Add-getMainThreadExecutor-to-BukkitScheduler.patch deleted file mode 100644 index 6ae0c8d11ba0..000000000000 --- a/patches/server/0488-Add-getMainThreadExecutor-to-BukkitScheduler.patch +++ /dev/null @@ -1,26 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Aleksander Jagiello -Date: Sun, 24 Jan 2021 22:17:54 +0100 -Subject: [PATCH] Add getMainThreadExecutor to BukkitScheduler - - -diff --git a/src/main/java/org/bukkit/craftbukkit/scheduler/CraftScheduler.java b/src/main/java/org/bukkit/craftbukkit/scheduler/CraftScheduler.java -index d7c2d8993a172e343669228cf24a58d8992a1c10..2f4d6b56301195f8d39ed50dffe842464065bfe1 100644 ---- a/src/main/java/org/bukkit/craftbukkit/scheduler/CraftScheduler.java -+++ b/src/main/java/org/bukkit/craftbukkit/scheduler/CraftScheduler.java -@@ -642,4 +642,15 @@ public class CraftScheduler implements BukkitScheduler { - public BukkitTask runTaskTimerAsynchronously(Plugin plugin, BukkitRunnable task, long delay, long period) throws IllegalArgumentException { - throw new UnsupportedOperationException("Use BukkitRunnable#runTaskTimerAsynchronously(Plugin, long, long)"); - } -+ -+ // Paper start - add getMainThreadExecutor -+ @Override -+ public Executor getMainThreadExecutor(Plugin plugin) { -+ Preconditions.checkArgument(plugin != null, "Plugin cannot be null"); -+ return command -> { -+ Preconditions.checkArgument(command != null, "Command cannot be null"); -+ this.runTask(plugin, command); -+ }; -+ } -+ // Paper end - } diff --git a/patches/server/0488-Expose-Tracked-Players.patch b/patches/server/0488-Expose-Tracked-Players.patch new file mode 100644 index 000000000000..28357313028b --- /dev/null +++ b/patches/server/0488-Expose-Tracked-Players.patch @@ -0,0 +1,32 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Tom +Date: Fri, 26 Feb 2021 16:24:25 -0600 +Subject: [PATCH] Expose Tracked Players + + +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java +index 925626b74005ec9c031c3773171f7684ecc9ccd3..06fda053874e7ef0c8995b1a55e5f518b28eebc9 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java +@@ -1070,4 +1070,21 @@ public abstract class CraftEntity implements org.bukkit.entity.Entity { + return getHandle().isTicking(); + } + // Paper end - isTicking API ++ ++ // Paper start - tracked players API ++ @Override ++ public Set getTrackedPlayers() { ++ ServerLevel world = (net.minecraft.server.level.ServerLevel)this.entity.level(); ++ ChunkMap.TrackedEntity tracker = world == null ? null : world.getChunkSource().chunkMap.entityMap.get(this.entity.getId()); ++ if (tracker == null) { ++ return java.util.Collections.emptySet(); ++ } ++ ++ Set set = new java.util.HashSet<>(tracker.seenBy.size()); ++ for (net.minecraft.server.network.ServerPlayerConnection connection : tracker.seenBy) { ++ set.add(connection.getPlayer().getBukkitEntity().getPlayer()); ++ } ++ return set; ++ } ++ // Paper end - tracked players API + } diff --git a/patches/server/0493-Improve-ServerGUI.patch b/patches/server/0489-Improve-ServerGUI.patch similarity index 100% rename from patches/server/0493-Improve-ServerGUI.patch rename to patches/server/0489-Improve-ServerGUI.patch diff --git a/patches/server/0489-living-entity-allow-attribute-registration.patch b/patches/server/0489-living-entity-allow-attribute-registration.patch deleted file mode 100644 index 77576f8436eb..000000000000 --- a/patches/server/0489-living-entity-allow-attribute-registration.patch +++ /dev/null @@ -1,57 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: ysl3000 -Date: Sat, 24 Oct 2020 16:37:44 +0200 -Subject: [PATCH] living entity allow attribute registration - - -diff --git a/src/main/java/net/minecraft/world/entity/ai/attributes/AttributeMap.java b/src/main/java/net/minecraft/world/entity/ai/attributes/AttributeMap.java -index cd03d705337a0ea34c7c06a581294112433afb50..69992ebc999ea3ff9e47e4e049bcc514c01150ca 100644 ---- a/src/main/java/net/minecraft/world/entity/ai/attributes/AttributeMap.java -+++ b/src/main/java/net/minecraft/world/entity/ai/attributes/AttributeMap.java -@@ -140,4 +140,12 @@ public class AttributeMap { - } - } - } -+ -+ // Paper - start - living entity allow attribute registration -+ public void registerAttribute(Holder attributeBase) { -+ AttributeInstance attributeModifiable = new AttributeInstance(attributeBase, AttributeInstance::getAttribute); -+ attributes.put(attributeBase, attributeModifiable); -+ } -+ // Paper - end - living entity allow attribute registration -+ - } -diff --git a/src/main/java/org/bukkit/craftbukkit/attribute/CraftAttributeMap.java b/src/main/java/org/bukkit/craftbukkit/attribute/CraftAttributeMap.java -index 5678d2007d5adf45dec0638c5dd848b601801814..0a7ed5a4f1644a70d8f98ad7a6962b814ad6daf4 100644 ---- a/src/main/java/org/bukkit/craftbukkit/attribute/CraftAttributeMap.java -+++ b/src/main/java/org/bukkit/craftbukkit/attribute/CraftAttributeMap.java -@@ -35,4 +35,11 @@ public class CraftAttributeMap implements Attributable { - - return (nms == null) ? null : new CraftAttributeInstance(nms, attribute); - } -+ // Paper start - living entity allow attribute registration -+ @Override -+ public void registerAttribute(Attribute attribute) { -+ Preconditions.checkArgument(attribute != null, "attribute"); -+ handle.registerAttribute(CraftAttribute.bukkitToMinecraftHolder(attribute)); -+ } -+ // Paper end - living entity allow attribute registration - } -diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftLivingEntity.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftLivingEntity.java -index 10f3defa8f4b57fb45cf7de06d415b72102e47d5..e56a0d8928e3c0e27b1acd171162e4a53b70d925 100644 ---- a/src/main/java/org/bukkit/craftbukkit/entity/CraftLivingEntity.java -+++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftLivingEntity.java -@@ -768,6 +768,13 @@ public class CraftLivingEntity extends CraftEntity implements LivingEntity { - return this.getHandle().craftAttributes.getAttribute(attribute); - } - -+ // Paper start - living entity allow attribute registration -+ @Override -+ public void registerAttribute(Attribute attribute) { -+ getHandle().craftAttributes.registerAttribute(attribute); -+ } -+ // Paper end - living entity allow attribute registration -+ - @Override - public void setAI(boolean ai) { - if (this.getHandle() instanceof Mob) { diff --git a/patches/server/0490-fix-converting-txt-to-json-file.patch b/patches/server/0490-fix-converting-txt-to-json-file.patch new file mode 100644 index 000000000000..130584b001e8 --- /dev/null +++ b/patches/server/0490-fix-converting-txt-to-json-file.patch @@ -0,0 +1,61 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Jake Potrebic +Date: Mon, 4 Jan 2021 19:49:15 -0800 +Subject: [PATCH] fix converting txt to json file + + +diff --git a/src/main/java/net/minecraft/server/dedicated/DedicatedPlayerList.java b/src/main/java/net/minecraft/server/dedicated/DedicatedPlayerList.java +index 929f59bce01c8e6ed4b0b551744d42e131b8fc80..22c4f8dea99f92a1eb3da2baf0a15bf9d2ca0462 100644 +--- a/src/main/java/net/minecraft/server/dedicated/DedicatedPlayerList.java ++++ b/src/main/java/net/minecraft/server/dedicated/DedicatedPlayerList.java +@@ -18,6 +18,11 @@ public class DedicatedPlayerList extends PlayerList { + this.setViewDistance(dedicatedServerProperties.viewDistance); + this.setSimulationDistance(dedicatedServerProperties.simulationDistance); + super.setUsingWhiteList(dedicatedServerProperties.whiteList.get()); ++ // Paper start - fix converting txt to json file; moved from constructor ++ } ++ @Override ++ public void loadAndSaveFiles() { ++ // Paper end - fix converting txt to json file + this.loadUserBanList(); + this.saveUserBanList(); + this.loadIpBanList(); +diff --git a/src/main/java/net/minecraft/server/dedicated/DedicatedServer.java b/src/main/java/net/minecraft/server/dedicated/DedicatedServer.java +index 9a3e73a5c206b78dfcf6f41a47b614342e52acc8..9d05e998d6df1069c2de69478a1f9688ac435e67 100644 +--- a/src/main/java/net/minecraft/server/dedicated/DedicatedServer.java ++++ b/src/main/java/net/minecraft/server/dedicated/DedicatedServer.java +@@ -213,6 +213,12 @@ public class DedicatedServer extends MinecraftServer implements ServerInterface + this.paperConfigurations.initializeGlobalConfiguration(this.registryAccess()); + this.paperConfigurations.initializeWorldDefaultsConfiguration(this.registryAccess()); + // Paper end - initialize global and world-defaults configuration ++ // Paper start - fix converting txt to json file; convert old users earlier after PlayerList creation but before file load/save ++ if (this.convertOldUsers()) { ++ this.getProfileCache().save(false); // Paper ++ } ++ this.getPlayerList().loadAndSaveFiles(); // Must be after convertNames ++ // Paper end - fix converting txt to json file + org.spigotmc.WatchdogThread.doStart(org.spigotmc.SpigotConfig.timeoutTime, org.spigotmc.SpigotConfig.restartOnCrash); // Paper - start watchdog thread + io.papermc.paper.command.PaperCommands.registerCommands(this); // Paper - setup /paper command + com.destroystokyo.paper.Metrics.PaperMetrics.startMetrics(); // Paper - start metrics +@@ -267,9 +273,6 @@ public class DedicatedServer extends MinecraftServer implements ServerInterface + DedicatedServer.LOGGER.warn("To change this, set \"online-mode\" to \"true\" in the server.properties file."); + } + +- if (this.convertOldUsers()) { +- this.getProfileCache().save(false); // Paper - Perf: Async GameProfileCache saving +- } + + if (!OldUsersConverter.serverReadyAfterUserconversion(this)) { + return false; +diff --git a/src/main/java/net/minecraft/server/players/PlayerList.java b/src/main/java/net/minecraft/server/players/PlayerList.java +index 240a0e487ede08d1dd5050812ca4f68db4cd2dd1..82f5451add3652d9f1afba4f809f30ceaa1b2951 100644 +--- a/src/main/java/net/minecraft/server/players/PlayerList.java ++++ b/src/main/java/net/minecraft/server/players/PlayerList.java +@@ -179,6 +179,7 @@ public abstract class PlayerList { + this.maxPlayers = maxPlayers; + this.playerIo = saveHandler; + } ++ abstract public void loadAndSaveFiles(); // Paper - fix converting txt to json file; moved from DedicatedPlayerList constructor + + public void placeNewPlayer(Connection connection, ServerPlayer player, CommonListenerCookie clientData) { + player.isRealPlayer = true; // Paper diff --git a/patches/server/0491-Add-worldborder-events.patch b/patches/server/0491-Add-worldborder-events.patch new file mode 100644 index 000000000000..014dba4c88d2 --- /dev/null +++ b/patches/server/0491-Add-worldborder-events.patch @@ -0,0 +1,72 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Jake Potrebic +Date: Mon, 4 Jan 2021 22:40:34 -0800 +Subject: [PATCH] Add worldborder events + + +diff --git a/src/main/java/net/minecraft/world/level/border/WorldBorder.java b/src/main/java/net/minecraft/world/level/border/WorldBorder.java +index b50090df116697a12f5498d65dd2e5d6d5297fb5..807a097a7b6399f24ede741f94ce98eb67e55add 100644 +--- a/src/main/java/net/minecraft/world/level/border/WorldBorder.java ++++ b/src/main/java/net/minecraft/world/level/border/WorldBorder.java +@@ -148,6 +148,14 @@ public class WorldBorder { + } + + public void setCenter(double x, double z) { ++ // Paper start - Add worldborder events ++ if (this.world != null) { ++ io.papermc.paper.event.world.border.WorldBorderCenterChangeEvent event = new io.papermc.paper.event.world.border.WorldBorderCenterChangeEvent(world.getWorld(), world.getWorld().getWorldBorder(), new org.bukkit.Location(world.getWorld(), this.getCenterX(), 0, this.getCenterZ()), new org.bukkit.Location(world.getWorld(), x, 0, z)); ++ if (!event.callEvent()) return; ++ x = event.getNewCenter().getX(); ++ z = event.getNewCenter().getZ(); ++ } ++ // Paper end - Add worldborder events + this.centerX = x; + this.centerZ = z; + this.extent.onCenterChange(); +@@ -174,6 +182,17 @@ public class WorldBorder { + } + + public void setSize(double size) { ++ // Paper start - Add worldborder events ++ if (this.world != null) { ++ io.papermc.paper.event.world.border.WorldBorderBoundsChangeEvent event = new io.papermc.paper.event.world.border.WorldBorderBoundsChangeEvent(world.getWorld(), world.getWorld().getWorldBorder(), io.papermc.paper.event.world.border.WorldBorderBoundsChangeEvent.Type.INSTANT_MOVE, getSize(), size, 0); ++ if (!event.callEvent()) return; ++ if (event.getType() == io.papermc.paper.event.world.border.WorldBorderBoundsChangeEvent.Type.STARTED_MOVE && event.getDuration() > 0) { // If changed to a timed transition ++ lerpSizeBetween(event.getOldSize(), event.getNewSize(), event.getDuration()); ++ return; ++ } ++ size = event.getNewSize(); ++ } ++ // Paper end - Add worldborder events + this.extent = new WorldBorder.StaticBorderExtent(size); + Iterator iterator = this.getListeners().iterator(); + +@@ -186,6 +205,20 @@ public class WorldBorder { + } + + public void lerpSizeBetween(double fromSize, double toSize, long time) { ++ // Paper start - Add worldborder events ++ if (this.world != null) { ++ io.papermc.paper.event.world.border.WorldBorderBoundsChangeEvent.Type type; ++ if (fromSize == toSize) { // new size = old size ++ type = io.papermc.paper.event.world.border.WorldBorderBoundsChangeEvent.Type.INSTANT_MOVE; // Use INSTANT_MOVE because below it creates a Static border if they are equal. ++ } else { ++ type = io.papermc.paper.event.world.border.WorldBorderBoundsChangeEvent.Type.STARTED_MOVE; ++ } ++ io.papermc.paper.event.world.border.WorldBorderBoundsChangeEvent event = new io.papermc.paper.event.world.border.WorldBorderBoundsChangeEvent(world.getWorld(), world.getWorld().getWorldBorder(), type, fromSize, toSize, time); ++ if (!event.callEvent()) return; ++ toSize = event.getNewSize(); ++ time = event.getDuration(); ++ } ++ // Paper end - Add worldborder events + this.extent = (WorldBorder.BorderExtent) (fromSize == toSize ? new WorldBorder.StaticBorderExtent(toSize) : new WorldBorder.MovingBorderExtent(fromSize, toSize, time)); + Iterator iterator = this.getListeners().iterator(); + +@@ -497,6 +530,7 @@ public class WorldBorder { + + @Override + public WorldBorder.BorderExtent update() { ++ if (world != null && this.getLerpRemainingTime() <= 0L) new io.papermc.paper.event.world.border.WorldBorderBoundsChangeFinishEvent(world.getWorld(), world.getWorld().getWorldBorder(), this.from, this.to, this.lerpDuration).callEvent(); // Paper - Add worldborder events + return (WorldBorder.BorderExtent) (this.getLerpRemainingTime() <= 0L ? WorldBorder.this.new StaticBorderExtent(this.to) : this); + } + diff --git a/patches/server/0492-Add-PlayerNameEntityEvent.patch b/patches/server/0492-Add-PlayerNameEntityEvent.patch new file mode 100644 index 000000000000..4215e4291d03 --- /dev/null +++ b/patches/server/0492-Add-PlayerNameEntityEvent.patch @@ -0,0 +1,26 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Jake Potrebic +Date: Sun, 5 Jul 2020 00:33:54 -0700 +Subject: [PATCH] Add PlayerNameEntityEvent + + +diff --git a/src/main/java/net/minecraft/world/item/NameTagItem.java b/src/main/java/net/minecraft/world/item/NameTagItem.java +index dd5aa4bbf2e7bab3be1c3582b090fdf0fa3fb0df..000d1863bfba98b5132dfc6743362d687b2f54f3 100644 +--- a/src/main/java/net/minecraft/world/item/NameTagItem.java ++++ b/src/main/java/net/minecraft/world/item/NameTagItem.java +@@ -18,8 +18,13 @@ public class NameTagItem extends Item { + Component component = stack.get(DataComponents.CUSTOM_NAME); + if (component != null && entity.getType().canSerialize()) { + if (!user.level().isClientSide && entity.isAlive()) { +- entity.setCustomName(component); +- if (entity instanceof Mob mob) { ++ // Paper start - Add PlayerNameEntityEvent ++ io.papermc.paper.event.player.PlayerNameEntityEvent event = new io.papermc.paper.event.player.PlayerNameEntityEvent(((net.minecraft.server.level.ServerPlayer) user).getBukkitEntity(), entity.getBukkitLivingEntity(), io.papermc.paper.adventure.PaperAdventure.asAdventure(stack.getHoverName()), true); ++ if (!event.callEvent()) return InteractionResult.PASS; ++ LivingEntity newEntity = ((org.bukkit.craftbukkit.entity.CraftLivingEntity) event.getEntity()).getHandle(); ++ newEntity.setCustomName(event.getName() != null ? io.papermc.paper.adventure.PaperAdventure.asVanilla(event.getName()) : null); ++ if (event.isPersistent() && newEntity instanceof Mob mob) { ++ // Paper end - Add PlayerNameEntityEvent + mob.setPersistenceRequired(); + } + diff --git a/patches/server/0492-Expose-Tracked-Players.patch b/patches/server/0492-Expose-Tracked-Players.patch deleted file mode 100644 index d8ca4c111c4b..000000000000 --- a/patches/server/0492-Expose-Tracked-Players.patch +++ /dev/null @@ -1,32 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Tom -Date: Fri, 26 Feb 2021 16:24:25 -0600 -Subject: [PATCH] Expose Tracked Players - - -diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java -index 98e8ad81b8c9c0636abe59f70ce891fe926a37fe..96201ea45f8b53dcadb1a8732b1d49b1e8d1d7df 100644 ---- a/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java -+++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java -@@ -1069,4 +1069,21 @@ public abstract class CraftEntity implements org.bukkit.entity.Entity { - return getHandle().isTicking(); - } - // Paper end - isTicking API -+ -+ // Paper start - tracked players API -+ @Override -+ public Set getTrackedPlayers() { -+ ServerLevel world = (net.minecraft.server.level.ServerLevel)this.entity.level(); -+ ChunkMap.TrackedEntity tracker = world == null ? null : world.getChunkSource().chunkMap.entityMap.get(this.entity.getId()); -+ if (tracker == null) { -+ return java.util.Collections.emptySet(); -+ } -+ -+ Set set = new java.util.HashSet<>(tracker.seenBy.size()); -+ for (net.minecraft.server.network.ServerPlayerConnection connection : tracker.seenBy) { -+ set.add(connection.getPlayer().getBukkitEntity().getPlayer()); -+ } -+ return set; -+ } -+ // Paper end - tracked players API - } diff --git a/patches/server/0493-Add-recipe-to-cook-events.patch b/patches/server/0493-Add-recipe-to-cook-events.patch new file mode 100644 index 000000000000..db11d30abcbc --- /dev/null +++ b/patches/server/0493-Add-recipe-to-cook-events.patch @@ -0,0 +1,44 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Thonk <30448663+ExcessiveAmountsOfZombies@users.noreply.github.com> +Date: Wed, 6 Jan 2021 12:04:03 -0800 +Subject: [PATCH] Add recipe to cook events + + +diff --git a/src/main/java/net/minecraft/world/level/block/entity/AbstractFurnaceBlockEntity.java b/src/main/java/net/minecraft/world/level/block/entity/AbstractFurnaceBlockEntity.java +index 187f380eae3948eb5e37e8703db6ea785aaf833d..08b94e5e31ca835c1f9eaefbab07076c91deadeb 100644 +--- a/src/main/java/net/minecraft/world/level/block/entity/AbstractFurnaceBlockEntity.java ++++ b/src/main/java/net/minecraft/world/level/block/entity/AbstractFurnaceBlockEntity.java +@@ -332,7 +332,7 @@ public abstract class AbstractFurnaceBlockEntity extends BaseContainerBlockEntit + CraftItemStack source = CraftItemStack.asCraftMirror(itemstack); + org.bukkit.inventory.ItemStack result = CraftItemStack.asBukkitCopy(itemstack1); + +- FurnaceSmeltEvent furnaceSmeltEvent = new FurnaceSmeltEvent(CraftBlock.at(world, blockposition), source, result); ++ FurnaceSmeltEvent furnaceSmeltEvent = new FurnaceSmeltEvent(CraftBlock.at(world, blockposition), source, result, (org.bukkit.inventory.CookingRecipe) recipeholder.toBukkitRecipe()); // Paper - Add recipe to cook events + world.getCraftServer().getPluginManager().callEvent(furnaceSmeltEvent); + + if (furnaceSmeltEvent.isCancelled()) { +diff --git a/src/main/java/net/minecraft/world/level/block/entity/CampfireBlockEntity.java b/src/main/java/net/minecraft/world/level/block/entity/CampfireBlockEntity.java +index 30035d534e144bf31f94073c57b0195be7e62772..7fa1aea7942a1bc4d9779a9f8ab020ccd5566923 100644 +--- a/src/main/java/net/minecraft/world/level/block/entity/CampfireBlockEntity.java ++++ b/src/main/java/net/minecraft/world/level/block/entity/CampfireBlockEntity.java +@@ -66,7 +66,10 @@ public class CampfireBlockEntity extends BlockEntity implements Clearable { + + if (blockEntity.cookingProgress[i] >= blockEntity.cookingTime[i]) { + SingleRecipeInput singlerecipeinput = new SingleRecipeInput(itemstack); +- ItemStack itemstack1 = (ItemStack) recipeMatchGetter.getRecipeFor(singlerecipeinput, world).map((recipeholder) -> { ++ // Paper start - add recipe to cook events ++ final Optional> recipeHolderOptional = recipeMatchGetter.getRecipeFor(singlerecipeinput, world); ++ ItemStack itemstack1 = (ItemStack) recipeHolderOptional.map((recipeholder) -> { ++ // Paper end - add recipe to cook events + return ((CampfireCookingRecipe) recipeholder.value()).assemble(singlerecipeinput, world.registryAccess()); + }).orElse(itemstack); + +@@ -75,7 +78,7 @@ public class CampfireBlockEntity extends BlockEntity implements Clearable { + CraftItemStack source = CraftItemStack.asCraftMirror(itemstack); + org.bukkit.inventory.ItemStack result = CraftItemStack.asBukkitCopy(itemstack1); + +- BlockCookEvent blockCookEvent = new BlockCookEvent(CraftBlock.at(world, pos), source, result); ++ BlockCookEvent blockCookEvent = new BlockCookEvent(CraftBlock.at(world, pos), source, result, (org.bukkit.inventory.CookingRecipe) recipeHolderOptional.map(RecipeHolder::toBukkitRecipe).orElse(null)); // Paper - Add recipe to cook events + world.getCraftServer().getPluginManager().callEvent(blockCookEvent); + + if (blockCookEvent.isCancelled()) { diff --git a/patches/server/0494-Add-Block-isValidTool.patch b/patches/server/0494-Add-Block-isValidTool.patch new file mode 100644 index 000000000000..e0c42e8e3762 --- /dev/null +++ b/patches/server/0494-Add-Block-isValidTool.patch @@ -0,0 +1,20 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Jake Potrebic +Date: Mon, 6 Jul 2020 12:44:31 -0700 +Subject: [PATCH] Add Block#isValidTool + + +diff --git a/src/main/java/org/bukkit/craftbukkit/block/CraftBlock.java b/src/main/java/org/bukkit/craftbukkit/block/CraftBlock.java +index 1595e877b02b447b36f5c316ae70d4023b78a862..54fb380a6896731a18c0100722d12099e590cbc9 100644 +--- a/src/main/java/org/bukkit/craftbukkit/block/CraftBlock.java ++++ b/src/main/java/org/bukkit/craftbukkit/block/CraftBlock.java +@@ -693,5 +693,9 @@ public class CraftBlock implements Block { + public String translationKey() { + return this.getNMS().getBlock().getDescriptionId(); + } ++ ++ public boolean isValidTool(ItemStack itemStack) { ++ return getDrops(itemStack).size() != 0; ++ } + // Paper end + } diff --git a/patches/server/0494-fix-converting-txt-to-json-file.patch b/patches/server/0494-fix-converting-txt-to-json-file.patch deleted file mode 100644 index 2134c10628bd..000000000000 --- a/patches/server/0494-fix-converting-txt-to-json-file.patch +++ /dev/null @@ -1,61 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Jake Potrebic -Date: Mon, 4 Jan 2021 19:49:15 -0800 -Subject: [PATCH] fix converting txt to json file - - -diff --git a/src/main/java/net/minecraft/server/dedicated/DedicatedPlayerList.java b/src/main/java/net/minecraft/server/dedicated/DedicatedPlayerList.java -index 929f59bce01c8e6ed4b0b551744d42e131b8fc80..22c4f8dea99f92a1eb3da2baf0a15bf9d2ca0462 100644 ---- a/src/main/java/net/minecraft/server/dedicated/DedicatedPlayerList.java -+++ b/src/main/java/net/minecraft/server/dedicated/DedicatedPlayerList.java -@@ -18,6 +18,11 @@ public class DedicatedPlayerList extends PlayerList { - this.setViewDistance(dedicatedServerProperties.viewDistance); - this.setSimulationDistance(dedicatedServerProperties.simulationDistance); - super.setUsingWhiteList(dedicatedServerProperties.whiteList.get()); -+ // Paper start - fix converting txt to json file; moved from constructor -+ } -+ @Override -+ public void loadAndSaveFiles() { -+ // Paper end - fix converting txt to json file - this.loadUserBanList(); - this.saveUserBanList(); - this.loadIpBanList(); -diff --git a/src/main/java/net/minecraft/server/dedicated/DedicatedServer.java b/src/main/java/net/minecraft/server/dedicated/DedicatedServer.java -index 2b1d7a2360a9ee7bca9d93a2dc8c61d1648a8348..d5153f804cfcfd1a70c46975e3fb1e50c8a82999 100644 ---- a/src/main/java/net/minecraft/server/dedicated/DedicatedServer.java -+++ b/src/main/java/net/minecraft/server/dedicated/DedicatedServer.java -@@ -216,6 +216,12 @@ public class DedicatedServer extends MinecraftServer implements ServerInterface - this.paperConfigurations.initializeGlobalConfiguration(this.registryAccess()); - this.paperConfigurations.initializeWorldDefaultsConfiguration(this.registryAccess()); - // Paper end - initialize global and world-defaults configuration -+ // Paper start - fix converting txt to json file; convert old users earlier after PlayerList creation but before file load/save -+ if (this.convertOldUsers()) { -+ this.getProfileCache().save(false); // Paper -+ } -+ this.getPlayerList().loadAndSaveFiles(); // Must be after convertNames -+ // Paper end - fix converting txt to json file - org.spigotmc.WatchdogThread.doStart(org.spigotmc.SpigotConfig.timeoutTime, org.spigotmc.SpigotConfig.restartOnCrash); // Paper - start watchdog thread - io.papermc.paper.command.PaperCommands.registerCommands(this); // Paper - setup /paper command - com.destroystokyo.paper.Metrics.PaperMetrics.startMetrics(); // Paper - start metrics -@@ -270,9 +276,6 @@ public class DedicatedServer extends MinecraftServer implements ServerInterface - DedicatedServer.LOGGER.warn("To change this, set \"online-mode\" to \"true\" in the server.properties file."); - } - -- if (this.convertOldUsers()) { -- this.getProfileCache().save(false); // Paper - Perf: Async GameProfileCache saving -- } - - if (!OldUsersConverter.serverReadyAfterUserconversion(this)) { - return false; -diff --git a/src/main/java/net/minecraft/server/players/PlayerList.java b/src/main/java/net/minecraft/server/players/PlayerList.java -index 9b94ca912f99a3a465f30130b24347fc9aca72ff..123fe4f5b98a7483af86695ec82987abd45eafb2 100644 ---- a/src/main/java/net/minecraft/server/players/PlayerList.java -+++ b/src/main/java/net/minecraft/server/players/PlayerList.java -@@ -179,6 +179,7 @@ public abstract class PlayerList { - this.maxPlayers = maxPlayers; - this.playerIo = saveHandler; - } -+ abstract public void loadAndSaveFiles(); // Paper - fix converting txt to json file; moved from DedicatedPlayerList constructor - - public void placeNewPlayer(Connection connection, ServerPlayer player, CommonListenerCookie clientData) { - player.isRealPlayer = true; // Paper diff --git a/patches/server/0495-Add-worldborder-events.patch b/patches/server/0495-Add-worldborder-events.patch deleted file mode 100644 index 44a467547c80..000000000000 --- a/patches/server/0495-Add-worldborder-events.patch +++ /dev/null @@ -1,72 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Jake Potrebic -Date: Mon, 4 Jan 2021 22:40:34 -0800 -Subject: [PATCH] Add worldborder events - - -diff --git a/src/main/java/net/minecraft/world/level/border/WorldBorder.java b/src/main/java/net/minecraft/world/level/border/WorldBorder.java -index 3442e33a1146318228c4727a2a5afde685f69bf7..04e62c54f224f7949fde9ceded208e700db55aa1 100644 ---- a/src/main/java/net/minecraft/world/level/border/WorldBorder.java -+++ b/src/main/java/net/minecraft/world/level/border/WorldBorder.java -@@ -140,6 +140,14 @@ public class WorldBorder { - } - - public void setCenter(double x, double z) { -+ // Paper start - Add worldborder events -+ if (this.world != null) { -+ io.papermc.paper.event.world.border.WorldBorderCenterChangeEvent event = new io.papermc.paper.event.world.border.WorldBorderCenterChangeEvent(world.getWorld(), world.getWorld().getWorldBorder(), new org.bukkit.Location(world.getWorld(), this.getCenterX(), 0, this.getCenterZ()), new org.bukkit.Location(world.getWorld(), x, 0, z)); -+ if (!event.callEvent()) return; -+ x = event.getNewCenter().getX(); -+ z = event.getNewCenter().getZ(); -+ } -+ // Paper end - Add worldborder events - this.centerX = x; - this.centerZ = z; - this.extent.onCenterChange(); -@@ -166,6 +174,17 @@ public class WorldBorder { - } - - public void setSize(double size) { -+ // Paper start - Add worldborder events -+ if (this.world != null) { -+ io.papermc.paper.event.world.border.WorldBorderBoundsChangeEvent event = new io.papermc.paper.event.world.border.WorldBorderBoundsChangeEvent(world.getWorld(), world.getWorld().getWorldBorder(), io.papermc.paper.event.world.border.WorldBorderBoundsChangeEvent.Type.INSTANT_MOVE, getSize(), size, 0); -+ if (!event.callEvent()) return; -+ if (event.getType() == io.papermc.paper.event.world.border.WorldBorderBoundsChangeEvent.Type.STARTED_MOVE && event.getDuration() > 0) { // If changed to a timed transition -+ lerpSizeBetween(event.getOldSize(), event.getNewSize(), event.getDuration()); -+ return; -+ } -+ size = event.getNewSize(); -+ } -+ // Paper end - Add worldborder events - this.extent = new WorldBorder.StaticBorderExtent(size); - Iterator iterator = this.getListeners().iterator(); - -@@ -178,6 +197,20 @@ public class WorldBorder { - } - - public void lerpSizeBetween(double fromSize, double toSize, long time) { -+ // Paper start - Add worldborder events -+ if (this.world != null) { -+ io.papermc.paper.event.world.border.WorldBorderBoundsChangeEvent.Type type; -+ if (fromSize == toSize) { // new size = old size -+ type = io.papermc.paper.event.world.border.WorldBorderBoundsChangeEvent.Type.INSTANT_MOVE; // Use INSTANT_MOVE because below it creates a Static border if they are equal. -+ } else { -+ type = io.papermc.paper.event.world.border.WorldBorderBoundsChangeEvent.Type.STARTED_MOVE; -+ } -+ io.papermc.paper.event.world.border.WorldBorderBoundsChangeEvent event = new io.papermc.paper.event.world.border.WorldBorderBoundsChangeEvent(world.getWorld(), world.getWorld().getWorldBorder(), type, fromSize, toSize, time); -+ if (!event.callEvent()) return; -+ toSize = event.getNewSize(); -+ time = event.getDuration(); -+ } -+ // Paper end - Add worldborder events - this.extent = (WorldBorder.BorderExtent) (fromSize == toSize ? new WorldBorder.StaticBorderExtent(toSize) : new WorldBorder.MovingBorderExtent(fromSize, toSize, time)); - Iterator iterator = this.getListeners().iterator(); - -@@ -489,6 +522,7 @@ public class WorldBorder { - - @Override - public WorldBorder.BorderExtent update() { -+ if (world != null && this.getLerpRemainingTime() <= 0L) new io.papermc.paper.event.world.border.WorldBorderBoundsChangeFinishEvent(world.getWorld(), world.getWorld().getWorldBorder(), this.from, this.to, this.lerpDuration).callEvent(); // Paper - Add worldborder events - return (WorldBorder.BorderExtent) (this.getLerpRemainingTime() <= 0L ? WorldBorder.this.new StaticBorderExtent(this.to) : this); - } - diff --git a/patches/server/0495-Allow-using-signs-inside-spawn-protection.patch b/patches/server/0495-Allow-using-signs-inside-spawn-protection.patch new file mode 100644 index 000000000000..cbb5462bf66d --- /dev/null +++ b/patches/server/0495-Allow-using-signs-inside-spawn-protection.patch @@ -0,0 +1,19 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Anton Lindroth +Date: Wed, 15 Apr 2020 01:54:02 +0200 +Subject: [PATCH] Allow using signs inside spawn protection + + +diff --git a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java +index 03e87e35f4478d948602569c319a7b5a0e938cd5..03ab4e27e141716e1c9ae92d41f42cfb7a48db66 100644 +--- a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java ++++ b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java +@@ -1770,7 +1770,7 @@ public class ServerGamePacketListenerImpl extends ServerCommonPacketListenerImpl + int i = this.player.level().getMaxY(); + + if (blockposition.getY() <= i) { +- if (this.awaitingPositionFromClient == null && worldserver.mayInteract(this.player, blockposition)) { ++ if (this.awaitingPositionFromClient == null && (worldserver.mayInteract(this.player, blockposition) || (worldserver.paperConfig().spawn.allowUsingSignsInsideSpawnProtection && worldserver.getBlockState(blockposition).getBlock() instanceof net.minecraft.world.level.block.SignBlock))) { // Paper - Allow using signs inside spawn protection + this.player.stopUsingItem(); // CraftBukkit - SPIGOT-4706 + InteractionResult enuminteractionresult = this.player.gameMode.useItemOn(this.player, worldserver, itemstack, enumhand, movingobjectpositionblock); + diff --git a/patches/server/0496-Add-PlayerNameEntityEvent.patch b/patches/server/0496-Add-PlayerNameEntityEvent.patch deleted file mode 100644 index 9287edc1b759..000000000000 --- a/patches/server/0496-Add-PlayerNameEntityEvent.patch +++ /dev/null @@ -1,26 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Jake Potrebic -Date: Sun, 5 Jul 2020 00:33:54 -0700 -Subject: [PATCH] Add PlayerNameEntityEvent - - -diff --git a/src/main/java/net/minecraft/world/item/NameTagItem.java b/src/main/java/net/minecraft/world/item/NameTagItem.java -index 3ce8c725270510dbaaf2c1edc895d37ea9ca424c..774c982f28b4f127fc3f036c19dfb47fb9ae3264 100644 ---- a/src/main/java/net/minecraft/world/item/NameTagItem.java -+++ b/src/main/java/net/minecraft/world/item/NameTagItem.java -@@ -18,8 +18,13 @@ public class NameTagItem extends Item { - Component component = stack.get(DataComponents.CUSTOM_NAME); - if (component != null && !(entity instanceof Player)) { - if (!user.level().isClientSide && entity.isAlive()) { -- entity.setCustomName(component); -- if (entity instanceof Mob mob) { -+ // Paper start - Add PlayerNameEntityEvent -+ io.papermc.paper.event.player.PlayerNameEntityEvent event = new io.papermc.paper.event.player.PlayerNameEntityEvent(((net.minecraft.server.level.ServerPlayer) user).getBukkitEntity(), entity.getBukkitLivingEntity(), io.papermc.paper.adventure.PaperAdventure.asAdventure(stack.getHoverName()), true); -+ if (!event.callEvent()) return InteractionResult.PASS; -+ LivingEntity newEntity = ((org.bukkit.craftbukkit.entity.CraftLivingEntity) event.getEntity()).getHandle(); -+ newEntity.setCustomName(event.getName() != null ? io.papermc.paper.adventure.PaperAdventure.asVanilla(event.getName()) : null); -+ if (event.isPersistent() && newEntity instanceof Mob mob) { -+ // Paper end - Add PlayerNameEntityEvent - mob.setPersistenceRequired(); - } - diff --git a/patches/server/0496-Expand-world-key-API.patch b/patches/server/0496-Expand-world-key-API.patch new file mode 100644 index 000000000000..80f4011a34f9 --- /dev/null +++ b/patches/server/0496-Expand-world-key-API.patch @@ -0,0 +1,84 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Jake Potrebic +Date: Wed, 6 Jan 2021 00:34:04 -0800 +Subject: [PATCH] Expand world key API + + +diff --git a/src/main/java/org/bukkit/craftbukkit/CraftRegionAccessor.java b/src/main/java/org/bukkit/craftbukkit/CraftRegionAccessor.java +index 15da29058f80a2d7cf2be26c48421c1746815a10..a070b2a83edaa702b13bc6d3026914126c211576 100644 +--- a/src/main/java/org/bukkit/craftbukkit/CraftRegionAccessor.java ++++ b/src/main/java/org/bukkit/craftbukkit/CraftRegionAccessor.java +@@ -521,5 +521,10 @@ public abstract class CraftRegionAccessor implements RegionAccessor { + public io.papermc.paper.world.MoonPhase getMoonPhase() { + return io.papermc.paper.world.MoonPhase.getPhase(this.getHandle().dayTime() / 24000L); + } ++ ++ @Override ++ public org.bukkit.NamespacedKey getKey() { ++ return org.bukkit.craftbukkit.util.CraftNamespacedKey.fromMinecraft(this.getHandle().getLevel().dimension().location()); ++ } + // Paper end + } +diff --git a/src/main/java/org/bukkit/craftbukkit/CraftServer.java b/src/main/java/org/bukkit/craftbukkit/CraftServer.java +index a12dc990a9094e964be2af26a5135e3b798f9666..85b2298efface87ee97e0f996d939cc135ca981d 100644 +--- a/src/main/java/org/bukkit/craftbukkit/CraftServer.java ++++ b/src/main/java/org/bukkit/craftbukkit/CraftServer.java +@@ -1185,9 +1185,15 @@ public final class CraftServer implements Server { + File folder = new File(this.getWorldContainer(), name); + World world = this.getWorld(name); + +- if (world != null) { +- return world; ++ // Paper start ++ World worldByKey = this.getWorld(creator.key()); ++ if (world != null || worldByKey != null) { ++ if (world == worldByKey) { ++ return world; ++ } ++ throw new IllegalArgumentException("Cannot create a world with key " + creator.key() + " and name " + name + " one (or both) already match a world that exists"); + } ++ // Paper end + + if (folder.exists()) { + Preconditions.checkArgument(folder.isDirectory(), "File (%s) exists and isn't a folder", name); +@@ -1313,7 +1319,7 @@ public final class CraftServer implements Server { + } else if (name.equals(levelName + "_the_end")) { + worldKey = net.minecraft.world.level.Level.END; + } else { +- worldKey = ResourceKey.create(Registries.DIMENSION, ResourceLocation.withDefaultNamespace(name.toLowerCase(Locale.ROOT))); ++ worldKey = ResourceKey.create(Registries.DIMENSION, ResourceLocation.fromNamespaceAndPath(creator.key().namespace(), creator.key().value())); + } + + // If set to not keep spawn in memory (changed from default) then adjust rule accordingly +@@ -1409,6 +1415,15 @@ public final class CraftServer implements Server { + return null; + } + ++ // Paper start ++ @Override ++ public World getWorld(net.kyori.adventure.key.Key worldKey) { ++ ServerLevel worldServer = console.getLevel(ResourceKey.create(net.minecraft.core.registries.Registries.DIMENSION, io.papermc.paper.adventure.PaperAdventure.asVanilla(worldKey))); ++ if (worldServer == null) return null; ++ return worldServer.getWorld(); ++ } ++ // Paper end ++ + public void addWorld(World world) { + // Check if a World already exists with the UID. + if (this.getWorld(world.getUID()) != null) { +diff --git a/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java b/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java +index af8fde74b3162d2de740ecae1122b4f2115baeb6..77f0d6c44c3773968b1e858d02cfc4ffb25fe99b 100644 +--- a/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java ++++ b/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java +@@ -527,6 +527,11 @@ public final class CraftMagicNumbers implements UnsafeValues { + public int nextEntityId() { + return net.minecraft.world.entity.Entity.nextEntityId(); + } ++ ++ @Override ++ public String getMainLevelName() { ++ return ((net.minecraft.server.dedicated.DedicatedServer) net.minecraft.server.MinecraftServer.getServer()).getProperties().levelName; ++ } + // Paper end + + /** diff --git a/patches/server/0501-Add-fast-alternative-constructor-for-Rotations.patch b/patches/server/0497-Add-fast-alternative-constructor-for-Rotations.patch similarity index 100% rename from patches/server/0501-Add-fast-alternative-constructor-for-Rotations.patch rename to patches/server/0497-Add-fast-alternative-constructor-for-Rotations.patch diff --git a/patches/server/0497-Add-recipe-to-cook-events.patch b/patches/server/0497-Add-recipe-to-cook-events.patch deleted file mode 100644 index 6fe8720d2101..000000000000 --- a/patches/server/0497-Add-recipe-to-cook-events.patch +++ /dev/null @@ -1,44 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Thonk <30448663+ExcessiveAmountsOfZombies@users.noreply.github.com> -Date: Wed, 6 Jan 2021 12:04:03 -0800 -Subject: [PATCH] Add recipe to cook events - - -diff --git a/src/main/java/net/minecraft/world/level/block/entity/AbstractFurnaceBlockEntity.java b/src/main/java/net/minecraft/world/level/block/entity/AbstractFurnaceBlockEntity.java -index 5ea2b05961590732a43bb5a1abf00bf8a00c72c2..84a3130a31f7a0fd5a8ae1b293dd3f2ca07c86d3 100644 ---- a/src/main/java/net/minecraft/world/level/block/entity/AbstractFurnaceBlockEntity.java -+++ b/src/main/java/net/minecraft/world/level/block/entity/AbstractFurnaceBlockEntity.java -@@ -444,7 +444,7 @@ public abstract class AbstractFurnaceBlockEntity extends BaseContainerBlockEntit - CraftItemStack source = CraftItemStack.asCraftMirror(itemstack); - org.bukkit.inventory.ItemStack result = CraftItemStack.asBukkitCopy(itemstack1); - -- FurnaceSmeltEvent furnaceSmeltEvent = new FurnaceSmeltEvent(CraftBlock.at(world, blockposition), source, result); -+ FurnaceSmeltEvent furnaceSmeltEvent = new FurnaceSmeltEvent(CraftBlock.at(world, blockposition), source, result, (org.bukkit.inventory.CookingRecipe) recipeholder.toBukkitRecipe()); // Paper - Add recipe to cook events - world.getCraftServer().getPluginManager().callEvent(furnaceSmeltEvent); - - if (furnaceSmeltEvent.isCancelled()) { -diff --git a/src/main/java/net/minecraft/world/level/block/entity/CampfireBlockEntity.java b/src/main/java/net/minecraft/world/level/block/entity/CampfireBlockEntity.java -index 80f911692c97585a696a19ebbe616d6aa312b2d9..0c20a334be4b1c4cf7999826f8d9bff5e36bc2b8 100644 ---- a/src/main/java/net/minecraft/world/level/block/entity/CampfireBlockEntity.java -+++ b/src/main/java/net/minecraft/world/level/block/entity/CampfireBlockEntity.java -@@ -67,7 +67,10 @@ public class CampfireBlockEntity extends BlockEntity implements Clearable { - - if (campfire.cookingProgress[i] >= campfire.cookingTime[i]) { - SingleRecipeInput singlerecipeinput = new SingleRecipeInput(itemstack); -- ItemStack itemstack1 = (ItemStack) campfire.quickCheck.getRecipeFor(singlerecipeinput, world).map((recipeholder) -> { -+ // Paper start - add recipe to cook events -+ Optional> recipeHolderOptional = campfire.quickCheck.getRecipeFor(singlerecipeinput, world); -+ ItemStack itemstack1 = recipeHolderOptional.map((recipeholder) -> { -+ // Paper end - Add recipe to cook events - return ((CampfireCookingRecipe) recipeholder.value()).assemble(singlerecipeinput, world.registryAccess()); - }).orElse(itemstack); - -@@ -76,7 +79,7 @@ public class CampfireBlockEntity extends BlockEntity implements Clearable { - CraftItemStack source = CraftItemStack.asCraftMirror(itemstack); - org.bukkit.inventory.ItemStack result = CraftItemStack.asBukkitCopy(itemstack1); - -- BlockCookEvent blockCookEvent = new BlockCookEvent(CraftBlock.at(world, pos), source, result); -+ BlockCookEvent blockCookEvent = new BlockCookEvent(CraftBlock.at(world, pos), source, result, (org.bukkit.inventory.CookingRecipe) recipeHolderOptional.map(RecipeHolder::toBukkitRecipe).orElse(null)); // Paper - Add recipe to cook events - world.getCraftServer().getPluginManager().callEvent(blockCookEvent); - - if (blockCookEvent.isCancelled()) { diff --git a/patches/server/0498-Add-Block-isValidTool.patch b/patches/server/0498-Add-Block-isValidTool.patch deleted file mode 100644 index 5f36fb047c0c..000000000000 --- a/patches/server/0498-Add-Block-isValidTool.patch +++ /dev/null @@ -1,20 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Jake Potrebic -Date: Mon, 6 Jul 2020 12:44:31 -0700 -Subject: [PATCH] Add Block#isValidTool - - -diff --git a/src/main/java/org/bukkit/craftbukkit/block/CraftBlock.java b/src/main/java/org/bukkit/craftbukkit/block/CraftBlock.java -index 98e87dc15e2ed23f6897ba6359846ff5bc32b655..2034858a53c4c887da334cdc7713997daa01124f 100644 ---- a/src/main/java/org/bukkit/craftbukkit/block/CraftBlock.java -+++ b/src/main/java/org/bukkit/craftbukkit/block/CraftBlock.java -@@ -693,5 +693,9 @@ public class CraftBlock implements Block { - public String translationKey() { - return this.getNMS().getBlock().getDescriptionId(); - } -+ -+ public boolean isValidTool(ItemStack itemStack) { -+ return getDrops(itemStack).size() != 0; -+ } - // Paper end - } diff --git a/patches/server/0498-Drop-carried-item-when-player-has-disconnected.patch b/patches/server/0498-Drop-carried-item-when-player-has-disconnected.patch new file mode 100644 index 000000000000..bebcb8d12170 --- /dev/null +++ b/patches/server/0498-Drop-carried-item-when-player-has-disconnected.patch @@ -0,0 +1,27 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Dmitry Sidorov +Date: Thu, 4 Feb 2021 20:32:01 +0300 +Subject: [PATCH] Drop carried item when player has disconnected + +Fixes disappearance of held items, when a player gets disconnected and PlayerDropItemEvent is cancelled. +Closes #5036 + +diff --git a/src/main/java/net/minecraft/server/players/PlayerList.java b/src/main/java/net/minecraft/server/players/PlayerList.java +index 82f5451add3652d9f1afba4f809f30ceaa1b2951..7a18061834096a73b140bee37b55b3c1724b51ef 100644 +--- a/src/main/java/net/minecraft/server/players/PlayerList.java ++++ b/src/main/java/net/minecraft/server/players/PlayerList.java +@@ -537,6 +537,14 @@ public abstract class PlayerList { + } + // Paper end - Configurable player collision + ++ // Paper - Drop carried item when player has disconnected ++ if (!entityplayer.containerMenu.getCarried().isEmpty()) { ++ net.minecraft.world.item.ItemStack carried = entityplayer.containerMenu.getCarried(); ++ entityplayer.containerMenu.setCarried(net.minecraft.world.item.ItemStack.EMPTY); ++ entityplayer.drop(carried, false); ++ } ++ // Paper end - Drop carried item when player has disconnected ++ + this.save(entityplayer); + if (entityplayer.isPassenger()) { + Entity entity = entityplayer.getRootVehicle(); diff --git a/patches/server/0499-Allow-using-signs-inside-spawn-protection.patch b/patches/server/0499-Allow-using-signs-inside-spawn-protection.patch deleted file mode 100644 index 9f0399c60f80..000000000000 --- a/patches/server/0499-Allow-using-signs-inside-spawn-protection.patch +++ /dev/null @@ -1,20 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Anton Lindroth -Date: Wed, 15 Apr 2020 01:54:02 +0200 -Subject: [PATCH] Allow using signs inside spawn protection - - -diff --git a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java -index d7d36b5148f2e7280816cc70b300b9b10720f751..25d060e8d77b1e8ea2ac951d2b25d60a984398cd 100644 ---- a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java -+++ b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java -@@ -1758,8 +1758,7 @@ public class ServerGamePacketListenerImpl extends ServerCommonPacketListenerImpl - int i = this.player.level().getMaxBuildHeight(); - - if (blockposition.getY() < i) { -- if (this.awaitingPositionFromClient == null && worldserver.mayInteract(this.player, blockposition)) { -- this.player.stopUsingItem(); // CraftBukkit - SPIGOT-4706 -+ if (this.awaitingPositionFromClient == null && (worldserver.mayInteract(this.player, blockposition) || (worldserver.paperConfig().spawn.allowUsingSignsInsideSpawnProtection && worldserver.getBlockState(blockposition).getBlock() instanceof net.minecraft.world.level.block.SignBlock))) { // Paper - Allow using signs inside spawn protection - InteractionResult enuminteractionresult = this.player.gameMode.useItemOn(this.player, worldserver, itemstack, enumhand, movingobjectpositionblock); - - if (enuminteractionresult.consumesAction()) { diff --git a/patches/server/0499-forced-whitelist-use-configurable-kick-message.patch b/patches/server/0499-forced-whitelist-use-configurable-kick-message.patch new file mode 100644 index 000000000000..e3d3ef89a743 --- /dev/null +++ b/patches/server/0499-forced-whitelist-use-configurable-kick-message.patch @@ -0,0 +1,19 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Trigary +Date: Sat, 27 Mar 2021 09:24:23 +0100 +Subject: [PATCH] forced whitelist: use configurable kick message + + +diff --git a/src/main/java/net/minecraft/server/MinecraftServer.java b/src/main/java/net/minecraft/server/MinecraftServer.java +index a200aacb1eaa749baa36be8be63b3f1421913b60..67a9bc450f545dd7b05398968a278a14f1e518fe 100644 +--- a/src/main/java/net/minecraft/server/MinecraftServer.java ++++ b/src/main/java/net/minecraft/server/MinecraftServer.java +@@ -2325,7 +2325,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop +Date: Mon, 5 Apr 2021 18:35:15 -0700 +Subject: [PATCH] Don't ignore result of PlayerEditBookEvent + + +diff --git a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java +index 03ab4e27e141716e1c9ae92d41f42cfb7a48db66..1b4a19eb510c3efa2f2459554bed5e16bcb23430 100644 +--- a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java ++++ b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java +@@ -1165,7 +1165,7 @@ public class ServerGamePacketListenerImpl extends ServerCommonPacketListenerImpl + List> list1 = pages.stream().map(this::filterableFromOutgoing).toList(); + + itemstack.set(DataComponents.WRITABLE_BOOK_CONTENT, new WritableBookContent(list1)); +- CraftEventFactory.handleEditBookEvent(this.player, slotId, handItem, itemstack); // CraftBukkit ++ this.player.getInventory().setItem(slotId, CraftEventFactory.handleEditBookEvent(this.player, slotId, handItem, itemstack)); // CraftBukkit // Paper - Don't ignore result (see other callsite for handleEditBookEvent) + } + } + diff --git a/patches/server/0500-Expand-world-key-API.patch b/patches/server/0500-Expand-world-key-API.patch deleted file mode 100644 index d2ccfa3efb0c..000000000000 --- a/patches/server/0500-Expand-world-key-API.patch +++ /dev/null @@ -1,84 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Jake Potrebic -Date: Wed, 6 Jan 2021 00:34:04 -0800 -Subject: [PATCH] Expand world key API - - -diff --git a/src/main/java/org/bukkit/craftbukkit/CraftRegionAccessor.java b/src/main/java/org/bukkit/craftbukkit/CraftRegionAccessor.java -index 1963e826548c5a8859c50f57654784c3aef50e44..04a39cb6c13c26e2cb1d73a9da98df5d04df69bc 100644 ---- a/src/main/java/org/bukkit/craftbukkit/CraftRegionAccessor.java -+++ b/src/main/java/org/bukkit/craftbukkit/CraftRegionAccessor.java -@@ -515,5 +515,10 @@ public abstract class CraftRegionAccessor implements RegionAccessor { - public io.papermc.paper.world.MoonPhase getMoonPhase() { - return io.papermc.paper.world.MoonPhase.getPhase(this.getHandle().dayTime() / 24000L); - } -+ -+ @Override -+ public org.bukkit.NamespacedKey getKey() { -+ return org.bukkit.craftbukkit.util.CraftNamespacedKey.fromMinecraft(this.getHandle().getLevel().dimension().location()); -+ } - // Paper end - } -diff --git a/src/main/java/org/bukkit/craftbukkit/CraftServer.java b/src/main/java/org/bukkit/craftbukkit/CraftServer.java -index f402ef662779e096ee354b9edd66cca785b85f33..a9c6f53c7e828a4b7d0cefbaa98e50ff1db9354a 100644 ---- a/src/main/java/org/bukkit/craftbukkit/CraftServer.java -+++ b/src/main/java/org/bukkit/craftbukkit/CraftServer.java -@@ -1183,9 +1183,15 @@ public final class CraftServer implements Server { - File folder = new File(this.getWorldContainer(), name); - World world = this.getWorld(name); - -- if (world != null) { -- return world; -+ // Paper start -+ World worldByKey = this.getWorld(creator.key()); -+ if (world != null || worldByKey != null) { -+ if (world == worldByKey) { -+ return world; -+ } -+ throw new IllegalArgumentException("Cannot create a world with key " + creator.key() + " and name " + name + " one (or both) already match a world that exists"); - } -+ // Paper end - - if (folder.exists()) { - Preconditions.checkArgument(folder.isDirectory(), "File (%s) exists and isn't a folder", name); -@@ -1311,7 +1317,7 @@ public final class CraftServer implements Server { - } else if (name.equals(levelName + "_the_end")) { - worldKey = net.minecraft.world.level.Level.END; - } else { -- worldKey = ResourceKey.create(Registries.DIMENSION, ResourceLocation.withDefaultNamespace(name.toLowerCase(Locale.ROOT))); -+ worldKey = ResourceKey.create(Registries.DIMENSION, ResourceLocation.fromNamespaceAndPath(creator.key().namespace(), creator.key().value())); - } - - // If set to not keep spawn in memory (changed from default) then adjust rule accordingly -@@ -1407,6 +1413,15 @@ public final class CraftServer implements Server { - return null; - } - -+ // Paper start -+ @Override -+ public World getWorld(net.kyori.adventure.key.Key worldKey) { -+ ServerLevel worldServer = console.getLevel(ResourceKey.create(net.minecraft.core.registries.Registries.DIMENSION, io.papermc.paper.adventure.PaperAdventure.asVanilla(worldKey))); -+ if (worldServer == null) return null; -+ return worldServer.getWorld(); -+ } -+ // Paper end -+ - public void addWorld(World world) { - // Check if a World already exists with the UID. - if (this.getWorld(world.getUID()) != null) { -diff --git a/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java b/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java -index 66249c5caefb0879e13c02d5553b09b4122418b9..79915a93b36ddf2925444369a5df4e8f4fd5610e 100644 ---- a/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java -+++ b/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java -@@ -522,6 +522,11 @@ public final class CraftMagicNumbers implements UnsafeValues { - public int nextEntityId() { - return net.minecraft.world.entity.Entity.nextEntityId(); - } -+ -+ @Override -+ public String getMainLevelName() { -+ return ((net.minecraft.server.dedicated.DedicatedServer) net.minecraft.server.MinecraftServer.getServer()).getProperties().levelName; -+ } - // Paper end - - @Override diff --git a/patches/server/0501-Expose-protocol-version.patch b/patches/server/0501-Expose-protocol-version.patch new file mode 100644 index 000000000000..13133988e5a0 --- /dev/null +++ b/patches/server/0501-Expose-protocol-version.patch @@ -0,0 +1,22 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Nassim Jahnke +Date: Fri, 26 Mar 2021 11:23:17 +0100 +Subject: [PATCH] Expose protocol version + + +diff --git a/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java b/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java +index 77f0d6c44c3773968b1e858d02cfc4ffb25fe99b..ada83b5e843f761837a7510112162c6954ab4260 100644 +--- a/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java ++++ b/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java +@@ -532,6 +532,11 @@ public final class CraftMagicNumbers implements UnsafeValues { + public String getMainLevelName() { + return ((net.minecraft.server.dedicated.DedicatedServer) net.minecraft.server.MinecraftServer.getServer()).getProperties().levelName; + } ++ ++ @Override ++ public int getProtocolVersion() { ++ return net.minecraft.SharedConstants.getCurrentVersion().getProtocolVersion(); ++ } + // Paper end + + /** diff --git a/patches/server/0502-Drop-carried-item-when-player-has-disconnected.patch b/patches/server/0502-Drop-carried-item-when-player-has-disconnected.patch deleted file mode 100644 index ababb3984cb6..000000000000 --- a/patches/server/0502-Drop-carried-item-when-player-has-disconnected.patch +++ /dev/null @@ -1,27 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Dmitry Sidorov -Date: Thu, 4 Feb 2021 20:32:01 +0300 -Subject: [PATCH] Drop carried item when player has disconnected - -Fixes disappearance of held items, when a player gets disconnected and PlayerDropItemEvent is cancelled. -Closes #5036 - -diff --git a/src/main/java/net/minecraft/server/players/PlayerList.java b/src/main/java/net/minecraft/server/players/PlayerList.java -index 123fe4f5b98a7483af86695ec82987abd45eafb2..bbc6f64a3b2c0702a0a752acd48145cdcafd742e 100644 ---- a/src/main/java/net/minecraft/server/players/PlayerList.java -+++ b/src/main/java/net/minecraft/server/players/PlayerList.java -@@ -588,6 +588,14 @@ public abstract class PlayerList { - } - // Paper end - Configurable player collision - -+ // Paper - Drop carried item when player has disconnected -+ if (!entityplayer.containerMenu.getCarried().isEmpty()) { -+ net.minecraft.world.item.ItemStack carried = entityplayer.containerMenu.getCarried(); -+ entityplayer.containerMenu.setCarried(net.minecraft.world.item.ItemStack.EMPTY); -+ entityplayer.drop(carried, false); -+ } -+ // Paper end - Drop carried item when player has disconnected -+ - this.save(entityplayer); - if (entityplayer.isPassenger()) { - Entity entity = entityplayer.getRootVehicle(); diff --git a/patches/server/0502-Enhance-console-tab-completions-for-brigadier-comman.patch b/patches/server/0502-Enhance-console-tab-completions-for-brigadier-comman.patch new file mode 100644 index 000000000000..3d1a0fb945a4 --- /dev/null +++ b/patches/server/0502-Enhance-console-tab-completions-for-brigadier-comman.patch @@ -0,0 +1,445 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Jason Penilla <11360596+jpenilla@users.noreply.github.com> +Date: Tue, 30 Mar 2021 16:06:08 -0700 +Subject: [PATCH] Enhance console tab completions for brigadier commands + +Co-authored-by: Jake Potrebic + +diff --git a/src/main/java/com/destroystokyo/paper/console/PaperConsole.java b/src/main/java/com/destroystokyo/paper/console/PaperConsole.java +index a4070b59e261f0f1ac4beec47b11492f4724bf27..6ee39b534b8d992655bc0cef3c299d12cbae0034 100644 +--- a/src/main/java/com/destroystokyo/paper/console/PaperConsole.java ++++ b/src/main/java/com/destroystokyo/paper/console/PaperConsole.java +@@ -1,5 +1,8 @@ + package com.destroystokyo.paper.console; + ++import io.papermc.paper.configuration.GlobalConfiguration; ++import io.papermc.paper.console.BrigadierCompletionMatcher; ++import io.papermc.paper.console.BrigadierConsoleParser; + import net.minecraft.server.dedicated.DedicatedServer; + import net.minecrell.terminalconsole.SimpleTerminalConsole; + import org.bukkit.craftbukkit.command.ConsoleCommandCompleter; +@@ -16,11 +19,20 @@ public final class PaperConsole extends SimpleTerminalConsole { + + @Override + protected LineReader buildReader(LineReaderBuilder builder) { +- return super.buildReader(builder ++ builder + .appName("Paper") + .variable(LineReader.HISTORY_FILE, java.nio.file.Paths.get(".console_history")) + .completer(new ConsoleCommandCompleter(this.server)) +- ); ++ .option(LineReader.Option.COMPLETE_IN_WORD, true); ++ if (io.papermc.paper.configuration.GlobalConfiguration.get().console.enableBrigadierHighlighting) { ++ builder.highlighter(new io.papermc.paper.console.BrigadierCommandHighlighter(this.server)); ++ } ++ if (GlobalConfiguration.get().console.enableBrigadierCompletions) { ++ System.setProperty("org.jline.reader.support.parsedline", "true"); // to hide a warning message about the parser not supporting ++ builder.parser(new BrigadierConsoleParser(this.server)); ++ builder.completionMatcher(new BrigadierCompletionMatcher()); ++ } ++ return super.buildReader(builder); + } + + @Override +diff --git a/src/main/java/io/papermc/paper/console/BrigadierCommandCompleter.java b/src/main/java/io/papermc/paper/console/BrigadierCommandCompleter.java +new file mode 100644 +index 0000000000000000000000000000000000000000..bf7b9518c05ff8a6d4b7d7cd36187ca22257e3dc +--- /dev/null ++++ b/src/main/java/io/papermc/paper/console/BrigadierCommandCompleter.java +@@ -0,0 +1,119 @@ ++package io.papermc.paper.console; ++ ++import com.destroystokyo.paper.event.server.AsyncTabCompleteEvent; ++import com.destroystokyo.paper.event.server.AsyncTabCompleteEvent.Completion; ++import com.google.common.base.Suppliers; ++import com.mojang.brigadier.CommandDispatcher; ++import com.mojang.brigadier.ParseResults; ++import com.mojang.brigadier.StringReader; ++import com.mojang.brigadier.suggestion.Suggestion; ++import io.papermc.paper.adventure.PaperAdventure; ++import java.util.ArrayList; ++import java.util.Collections; ++import java.util.List; ++import java.util.function.Supplier; ++import net.kyori.adventure.text.Component; ++import net.minecraft.commands.CommandSourceStack; ++import net.minecraft.network.chat.ComponentUtils; ++import net.minecraft.server.dedicated.DedicatedServer; ++import org.checkerframework.checker.nullness.qual.NonNull; ++import org.checkerframework.checker.nullness.qual.Nullable; ++import org.jline.reader.Candidate; ++import org.jline.reader.LineReader; ++import org.jline.reader.ParsedLine; ++ ++import static com.destroystokyo.paper.event.server.AsyncTabCompleteEvent.Completion.completion; ++ ++public final class BrigadierCommandCompleter { ++ private final Supplier commandSourceStack; ++ private final DedicatedServer server; ++ ++ public BrigadierCommandCompleter(final @NonNull DedicatedServer server) { ++ this.server = server; ++ this.commandSourceStack = Suppliers.memoize(this.server::createCommandSourceStack); ++ } ++ ++ public void complete(final @NonNull LineReader reader, final @NonNull ParsedLine line, final @NonNull List candidates, final @NonNull List existing) { ++ //noinspection ConstantConditions ++ if (this.server.overworld() == null) { // check if overworld is null, as worlds haven't been loaded yet ++ return; ++ } else if (!io.papermc.paper.configuration.GlobalConfiguration.get().console.enableBrigadierCompletions) { ++ this.addCandidates(candidates, Collections.emptyList(), existing, new ParseContext(line.line(), 0)); ++ return; ++ } ++ final CommandDispatcher dispatcher = this.server.getCommands().getDispatcher(); ++ final ParseResults results = dispatcher.parse(new StringReader(line.line()), this.commandSourceStack.get()); ++ this.addCandidates( ++ candidates, ++ dispatcher.getCompletionSuggestions(results, line.cursor()).join().getList(), ++ existing, ++ new ParseContext(line.line(), results.getContext().findSuggestionContext(line.cursor()).startPos) ++ ); ++ } ++ ++ private void addCandidates( ++ final @NonNull List candidates, ++ final @NonNull List brigSuggestions, ++ final @NonNull List existing, ++ final @NonNull ParseContext context ++ ) { ++ brigSuggestions.forEach(it -> { ++ if (it.getText().isEmpty()) return; ++ candidates.add(toCandidate(it, context)); ++ }); ++ for (final AsyncTabCompleteEvent.Completion completion : existing) { ++ if (completion.suggestion().isEmpty() || brigSuggestions.stream().anyMatch(it -> it.getText().equals(completion.suggestion()))) { ++ continue; ++ } ++ candidates.add(toCandidate(completion)); ++ } ++ } ++ ++ private static Candidate toCandidate(final Suggestion suggestion, final @NonNull ParseContext context) { ++ Component tooltip = null; ++ if (suggestion.getTooltip() != null) { ++ tooltip = PaperAdventure.asAdventure(ComponentUtils.fromMessage(suggestion.getTooltip())); ++ } ++ return toCandidate(context.line.substring(context.suggestionStart, suggestion.getRange().getStart()) + suggestion.getText(), tooltip); ++ } ++ ++ private static @NonNull Candidate toCandidate(final @NonNull Completion completion) { ++ return toCandidate(completion.suggestion(), completion.tooltip()); ++ } ++ ++ private static @NonNull Candidate toCandidate(final @NonNull String suggestionText, final @Nullable Component tooltip) { ++ final String suggestionTooltip = PaperAdventure.ANSI_SERIALIZER.serializeOr(tooltip, null); ++ //noinspection SpellCheckingInspection ++ return new PaperCandidate( ++ suggestionText, ++ suggestionText, ++ null, ++ suggestionTooltip, ++ null, ++ null, ++ /* ++ in an ideal world, this would sometimes be true if the suggestion represented the final possible value for a word. ++ Like for `/execute alig`, pressing enter on align would add a trailing space if this value was true. But not all ++ suggestions should add spaces after, like `/execute as @`, accepting any suggestion here would be valid, but its also ++ valid to have a `[` following the selector ++ */ ++ false ++ ); ++ } ++ ++ private static @NonNull Completion toCompletion(final @NonNull Suggestion suggestion) { ++ if (suggestion.getTooltip() == null) { ++ return completion(suggestion.getText()); ++ } ++ return completion(suggestion.getText(), PaperAdventure.asAdventure(ComponentUtils.fromMessage(suggestion.getTooltip()))); ++ } ++ ++ private record ParseContext(String line, int suggestionStart) { ++ } ++ ++ public static final class PaperCandidate extends Candidate { ++ public PaperCandidate(final String value, final String display, final String group, final String descr, final String suffix, final String key, final boolean complete) { ++ super(value, display, group, descr, suffix, key, complete); ++ } ++ } ++} +diff --git a/src/main/java/io/papermc/paper/console/BrigadierCommandHighlighter.java b/src/main/java/io/papermc/paper/console/BrigadierCommandHighlighter.java +new file mode 100644 +index 0000000000000000000000000000000000000000..0b21dac4473e3ea8022ef5c17f5f7d4d49d3ac0a +--- /dev/null ++++ b/src/main/java/io/papermc/paper/console/BrigadierCommandHighlighter.java +@@ -0,0 +1,67 @@ ++package io.papermc.paper.console; ++ ++import com.google.common.base.Suppliers; ++import com.mojang.brigadier.ParseResults; ++import com.mojang.brigadier.StringReader; ++import com.mojang.brigadier.context.ParsedCommandNode; ++import com.mojang.brigadier.tree.LiteralCommandNode; ++import java.util.function.Supplier; ++import java.util.regex.Pattern; ++import net.minecraft.commands.CommandSourceStack; ++import net.minecraft.server.dedicated.DedicatedServer; ++import org.checkerframework.checker.nullness.qual.NonNull; ++import org.jline.reader.Highlighter; ++import org.jline.reader.LineReader; ++import org.jline.utils.AttributedString; ++import org.jline.utils.AttributedStringBuilder; ++import org.jline.utils.AttributedStyle; ++ ++public final class BrigadierCommandHighlighter implements Highlighter { ++ private static final int[] COLORS = {AttributedStyle.CYAN, AttributedStyle.YELLOW, AttributedStyle.GREEN, AttributedStyle.MAGENTA, /* Client uses GOLD here, not BLUE, however there is no GOLD AttributedStyle. */ AttributedStyle.BLUE}; ++ private final Supplier commandSourceStack; ++ private final DedicatedServer server; ++ ++ public BrigadierCommandHighlighter(final @NonNull DedicatedServer server) { ++ this.server = server; ++ this.commandSourceStack = Suppliers.memoize(this.server::createCommandSourceStack); ++ } ++ ++ @Override ++ public AttributedString highlight(final @NonNull LineReader reader, final @NonNull String buffer) { ++ //noinspection ConstantConditions ++ if (this.server.overworld() == null) { // check if overworld is null, as worlds haven't been loaded yet ++ return new AttributedString(buffer, AttributedStyle.DEFAULT.foreground(AttributedStyle.RED)); ++ } ++ final AttributedStringBuilder builder = new AttributedStringBuilder(); ++ final ParseResults results = this.server.getCommands().getDispatcher().parse(new StringReader(buffer), this.commandSourceStack.get()); ++ int pos = 0; ++ int component = -1; ++ for (final ParsedCommandNode node : results.getContext().getLastChild().getNodes()) { ++ if (node.getRange().getStart() >= buffer.length()) { ++ break; ++ } ++ final int start = node.getRange().getStart(); ++ final int end = Math.min(node.getRange().getEnd(), buffer.length()); ++ builder.append(buffer.substring(pos, start), AttributedStyle.DEFAULT); ++ if (node.getNode() instanceof LiteralCommandNode) { ++ builder.append(buffer.substring(start, end), AttributedStyle.DEFAULT); ++ } else { ++ if (++component >= COLORS.length) { ++ component = 0; ++ } ++ builder.append(buffer.substring(start, end), AttributedStyle.DEFAULT.foreground(COLORS[component])); ++ } ++ pos = end; ++ } ++ if (pos < buffer.length()) { ++ builder.append((buffer.substring(pos)), AttributedStyle.DEFAULT.foreground(AttributedStyle.RED)); ++ } ++ return builder.toAttributedString(); ++ } ++ ++ @Override ++ public void setErrorPattern(final Pattern errorPattern) {} ++ ++ @Override ++ public void setErrorIndex(final int errorIndex) {} ++} +diff --git a/src/main/java/io/papermc/paper/console/BrigadierCompletionMatcher.java b/src/main/java/io/papermc/paper/console/BrigadierCompletionMatcher.java +new file mode 100644 +index 0000000000000000000000000000000000000000..1e8028a43db0ff1d5b22d06ef12c1c32d992c09c +--- /dev/null ++++ b/src/main/java/io/papermc/paper/console/BrigadierCompletionMatcher.java +@@ -0,0 +1,27 @@ ++package io.papermc.paper.console; ++ ++import com.google.common.collect.Iterables; ++import java.util.HashMap; ++import java.util.List; ++import java.util.Map; ++import org.jline.reader.Candidate; ++import org.jline.reader.CompletingParsedLine; ++import org.jline.reader.LineReader; ++import org.jline.reader.impl.CompletionMatcherImpl; ++ ++public class BrigadierCompletionMatcher extends CompletionMatcherImpl { ++ ++ @Override ++ protected void defaultMatchers(final Map options, final boolean prefix, final CompletingParsedLine line, final boolean caseInsensitive, final int errors, final String originalGroupName) { ++ super.defaultMatchers(options, prefix, line, caseInsensitive, errors, originalGroupName); ++ this.matchers.addFirst(m -> { ++ final Map> candidates = new HashMap<>(); ++ for (final Map.Entry> entry : m.entrySet()) { ++ if (Iterables.all(entry.getValue(), BrigadierCommandCompleter.PaperCandidate.class::isInstance)) { ++ candidates.put(entry.getKey(), entry.getValue()); ++ } ++ } ++ return candidates; ++ }); ++ } ++} +diff --git a/src/main/java/io/papermc/paper/console/BrigadierConsoleParser.java b/src/main/java/io/papermc/paper/console/BrigadierConsoleParser.java +new file mode 100644 +index 0000000000000000000000000000000000000000..8239a8ba57f856cbbee237a601b3cabfce20ba26 +--- /dev/null ++++ b/src/main/java/io/papermc/paper/console/BrigadierConsoleParser.java +@@ -0,0 +1,79 @@ ++package io.papermc.paper.console; ++ ++import com.mojang.brigadier.ImmutableStringReader; ++import com.mojang.brigadier.ParseResults; ++import com.mojang.brigadier.StringReader; ++import com.mojang.brigadier.context.CommandContextBuilder; ++import com.mojang.brigadier.context.ParsedCommandNode; ++import com.mojang.brigadier.context.StringRange; ++import java.util.ArrayList; ++import java.util.List; ++import net.minecraft.commands.CommandSourceStack; ++import net.minecraft.server.dedicated.DedicatedServer; ++import org.jline.reader.ParsedLine; ++import org.jline.reader.Parser; ++import org.jline.reader.SyntaxError; ++ ++public class BrigadierConsoleParser implements Parser { ++ ++ private final DedicatedServer server; ++ ++ public BrigadierConsoleParser(DedicatedServer server) { ++ this.server = server; ++ } ++ ++ @Override ++ public ParsedLine parse(final String line, final int cursor, final ParseContext context) throws SyntaxError { ++ final ParseResults results = this.server.getCommands().getDispatcher().parse(new StringReader(line), this.server.createCommandSourceStack()); ++ final ImmutableStringReader reader = results.getReader(); ++ final List words = new ArrayList<>(); ++ CommandContextBuilder currentContext = results.getContext(); ++ int currentWordIdx = -1; ++ int wordIdx = -1; ++ int inWordCursor = -1; ++ if (currentContext.getRange().getLength() > 0) { ++ do { ++ for (final ParsedCommandNode node : currentContext.getNodes()) { ++ final StringRange nodeRange = node.getRange(); ++ String current = nodeRange.get(reader); ++ words.add(current); ++ currentWordIdx++; ++ if (wordIdx == -1 && nodeRange.getStart() <= cursor && nodeRange.getEnd() >= cursor) { ++ // if cursor is in the middle of a parsed word/node ++ wordIdx = currentWordIdx; ++ inWordCursor = cursor - nodeRange.getStart(); ++ } ++ } ++ currentContext = currentContext.getChild(); ++ } while (currentContext != null); ++ } ++ final String leftovers = reader.getRemaining(); ++ if (!leftovers.isEmpty() && leftovers.isBlank()) { ++ // if brig didn't consume the whole line, and everything else is blank, add a new empty word ++ currentWordIdx++; ++ words.add(""); ++ if (wordIdx == -1) { ++ wordIdx = currentWordIdx; ++ inWordCursor = 0; ++ } ++ } else if (!leftovers.isEmpty()) { ++ // if there are unparsed leftovers, add a new word with the remaining input ++ currentWordIdx++; ++ words.add(leftovers); ++ if (wordIdx == -1) { ++ wordIdx = currentWordIdx; ++ inWordCursor = cursor - reader.getCursor(); ++ } ++ } ++ if (wordIdx == -1) { ++ currentWordIdx++; ++ words.add(""); ++ wordIdx = currentWordIdx; ++ inWordCursor = 0; ++ } ++ return new BrigadierParsedLine(words.get(wordIdx), inWordCursor, wordIdx, words, line, cursor); ++ } ++ ++ record BrigadierParsedLine(String word, int wordCursor, int wordIndex, List words, String line, int cursor) implements ParsedLine { ++ } ++} +diff --git a/src/main/java/net/minecraft/server/dedicated/DedicatedServer.java b/src/main/java/net/minecraft/server/dedicated/DedicatedServer.java +index 9d05e998d6df1069c2de69478a1f9688ac435e67..7c92b2f0a59fe222ad13a998476e312bf571a1bf 100644 +--- a/src/main/java/net/minecraft/server/dedicated/DedicatedServer.java ++++ b/src/main/java/net/minecraft/server/dedicated/DedicatedServer.java +@@ -187,7 +187,7 @@ public class DedicatedServer extends MinecraftServer implements ServerInterface + + thread.setDaemon(true); + thread.setUncaughtExceptionHandler(new DefaultUncaughtExceptionHandler(DedicatedServer.LOGGER)); +- thread.start(); ++ // thread.start(); // Paper - Enhance console tab completions for brigadier commands; moved down + DedicatedServer.LOGGER.info("Starting minecraft server version {}", SharedConstants.getCurrentVersion().getName()); + if (Runtime.getRuntime().maxMemory() / 1024L / 1024L < 512L) { + DedicatedServer.LOGGER.warn("To start the server with more ram, launch it as \"java -Xmx1024M -Xms1024M -jar minecraft_server.jar\""); +@@ -220,6 +220,7 @@ public class DedicatedServer extends MinecraftServer implements ServerInterface + this.getPlayerList().loadAndSaveFiles(); // Must be after convertNames + // Paper end - fix converting txt to json file + org.spigotmc.WatchdogThread.doStart(org.spigotmc.SpigotConfig.timeoutTime, org.spigotmc.SpigotConfig.restartOnCrash); // Paper - start watchdog thread ++ thread.start(); // Paper - Enhance console tab completions for brigadier commands; start console thread after MinecraftServer.console & PaperConfig are initialized + io.papermc.paper.command.PaperCommands.registerCommands(this); // Paper - setup /paper command + com.destroystokyo.paper.Metrics.PaperMetrics.startMetrics(); // Paper - start metrics + com.destroystokyo.paper.VersionHistoryManager.INSTANCE.getClass(); // Paper - load version history now +diff --git a/src/main/java/org/bukkit/craftbukkit/command/ConsoleCommandCompleter.java b/src/main/java/org/bukkit/craftbukkit/command/ConsoleCommandCompleter.java +index 15bc85f4799a4b23edd2f1e93f1794de5ca3e8e3..a45e658996e483e9a21cfd8178153ddb7b87ae69 100644 +--- a/src/main/java/org/bukkit/craftbukkit/command/ConsoleCommandCompleter.java ++++ b/src/main/java/org/bukkit/craftbukkit/command/ConsoleCommandCompleter.java +@@ -18,9 +18,11 @@ import org.bukkit.event.server.TabCompleteEvent; + + public class ConsoleCommandCompleter implements Completer { + private final DedicatedServer server; // Paper - CraftServer -> DedicatedServer ++ private final io.papermc.paper.console.BrigadierCommandCompleter brigadierCompleter; // Paper - Enhance console tab completions for brigadier commands + + public ConsoleCommandCompleter(DedicatedServer server) { // Paper - CraftServer -> DedicatedServer + this.server = server; ++ this.brigadierCompleter = new io.papermc.paper.console.BrigadierCommandCompleter(this.server); // Paper - Enhance console tab completions for brigadier commands + } + + // Paper start - Change method signature for JLine update +@@ -64,7 +66,7 @@ public class ConsoleCommandCompleter implements Completer { + } + } + +- if (!completions.isEmpty()) { ++ if (false && !completions.isEmpty()) { + for (final com.destroystokyo.paper.event.server.AsyncTabCompleteEvent.Completion completion : completions) { + if (completion.suggestion().isEmpty()) { + continue; +@@ -80,6 +82,7 @@ public class ConsoleCommandCompleter implements Completer { + )); + } + } ++ this.addCompletions(reader, line, candidates, completions); + return; + } + +@@ -99,10 +102,12 @@ public class ConsoleCommandCompleter implements Completer { + try { + List offers = waitable.get(); + if (offers == null) { ++ this.addCompletions(reader, line, candidates, Collections.emptyList()); // Paper - Enhance console tab completions for brigadier commands + return; // Paper - Method returns void + } + + // Paper start - JLine update ++ /* + for (String completion : offers) { + if (completion.isEmpty()) { + continue; +@@ -110,6 +115,8 @@ public class ConsoleCommandCompleter implements Completer { + + candidates.add(new Candidate(completion)); + } ++ */ ++ this.addCompletions(reader, line, candidates, offers.stream().map(com.destroystokyo.paper.event.server.AsyncTabCompleteEvent.Completion::completion).collect(java.util.stream.Collectors.toList())); + // Paper end + + // Paper start - JLine handles cursor now +@@ -138,5 +145,9 @@ public class ConsoleCommandCompleter implements Completer { + } + return false; + } ++ ++ private void addCompletions(final LineReader reader, final ParsedLine line, final List candidates, final List existing) { ++ this.brigadierCompleter.complete(reader, line, candidates, existing); ++ } + // Paper end + } diff --git a/patches/server/0503-Fix-PlayerItemConsumeEvent-cancelling-properly.patch b/patches/server/0503-Fix-PlayerItemConsumeEvent-cancelling-properly.patch new file mode 100644 index 000000000000..85bf38a0c7eb --- /dev/null +++ b/patches/server/0503-Fix-PlayerItemConsumeEvent-cancelling-properly.patch @@ -0,0 +1,22 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: chickeneer +Date: Fri, 19 Mar 2021 00:33:15 -0500 +Subject: [PATCH] Fix PlayerItemConsumeEvent cancelling properly + +When the active item is not cleared, the item is still readied +for use and will repeatedly trigger the PlayerItemConsumeEvent +till their item is switched. +This patch clears the active item when the event is cancelled + +diff --git a/src/main/java/net/minecraft/world/entity/LivingEntity.java b/src/main/java/net/minecraft/world/entity/LivingEntity.java +index ce9369e730ba8862cd1e6e26f8825c35b16fcfb9..9d56e7d4185401767359907337d5891ff0058abb 100644 +--- a/src/main/java/net/minecraft/world/entity/LivingEntity.java ++++ b/src/main/java/net/minecraft/world/entity/LivingEntity.java +@@ -4134,6 +4134,7 @@ public abstract class LivingEntity extends Entity implements Attackable { + this.level().getCraftServer().getPluginManager().callEvent(event); + + if (event.isCancelled()) { ++ this.stopUsingItem(); // Paper - event is using an item, clear active item to reset its use + // Update client + Consumable consumable = this.useItem.get(DataComponents.CONSUMABLE); + if (consumable != null) { diff --git a/patches/server/0503-forced-whitelist-use-configurable-kick-message.patch b/patches/server/0503-forced-whitelist-use-configurable-kick-message.patch deleted file mode 100644 index 96926fac4a85..000000000000 --- a/patches/server/0503-forced-whitelist-use-configurable-kick-message.patch +++ /dev/null @@ -1,19 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Trigary -Date: Sat, 27 Mar 2021 09:24:23 +0100 -Subject: [PATCH] forced whitelist: use configurable kick message - - -diff --git a/src/main/java/net/minecraft/server/MinecraftServer.java b/src/main/java/net/minecraft/server/MinecraftServer.java -index 43b28b56c7949f95af5b7e99b04b93ed711d8880..6651afa50cfaf53959f89108904d4bf65a0fc495 100644 ---- a/src/main/java/net/minecraft/server/MinecraftServer.java -+++ b/src/main/java/net/minecraft/server/MinecraftServer.java -@@ -2286,7 +2286,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop -Date: Mon, 5 Apr 2021 18:35:15 -0700 -Subject: [PATCH] Don't ignore result of PlayerEditBookEvent - - -diff --git a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java -index 25d060e8d77b1e8ea2ac951d2b25d60a984398cd..403b2499374906daa0ca2bb774522f92e186e6d5 100644 ---- a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java -+++ b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java -@@ -1154,7 +1154,7 @@ public class ServerGamePacketListenerImpl extends ServerCommonPacketListenerImpl - List> list1 = pages.stream().map(this::filterableFromOutgoing).toList(); - - itemstack.set(DataComponents.WRITABLE_BOOK_CONTENT, new WritableBookContent(list1)); -- CraftEventFactory.handleEditBookEvent(this.player, slotId, handItem, itemstack); // CraftBukkit -+ this.player.getInventory().setItem(slotId, CraftEventFactory.handleEditBookEvent(this.player, slotId, handItem, itemstack)); // CraftBukkit // Paper - Don't ignore result (see other callsite for handleEditBookEvent) - } - } - diff --git a/patches/server/0505-Expose-protocol-version.patch b/patches/server/0505-Expose-protocol-version.patch deleted file mode 100644 index d47bd22bc7c5..000000000000 --- a/patches/server/0505-Expose-protocol-version.patch +++ /dev/null @@ -1,22 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Nassim Jahnke -Date: Fri, 26 Mar 2021 11:23:17 +0100 -Subject: [PATCH] Expose protocol version - - -diff --git a/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java b/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java -index 79915a93b36ddf2925444369a5df4e8f4fd5610e..09fa524fa1155d53d988c15c1af551f73c96ede5 100644 ---- a/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java -+++ b/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java -@@ -527,6 +527,11 @@ public final class CraftMagicNumbers implements UnsafeValues { - public String getMainLevelName() { - return ((net.minecraft.server.dedicated.DedicatedServer) net.minecraft.server.MinecraftServer.getServer()).getProperties().levelName; - } -+ -+ @Override -+ public int getProtocolVersion() { -+ return net.minecraft.SharedConstants.getCurrentVersion().getProtocolVersion(); -+ } - // Paper end - - @Override diff --git a/patches/server/0505-Set-area-affect-cloud-rotation.patch b/patches/server/0505-Set-area-affect-cloud-rotation.patch new file mode 100644 index 000000000000..2d17a3c0fc9a --- /dev/null +++ b/patches/server/0505-Set-area-affect-cloud-rotation.patch @@ -0,0 +1,19 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Owen1212055 <23108066+Owen1212055@users.noreply.github.com> +Date: Mon, 5 Apr 2021 16:58:20 -0400 +Subject: [PATCH] Set area affect cloud rotation + + +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftEntityTypes.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftEntityTypes.java +index 1c3aacda9242444c0c7bd92cb391e6fff963bb79..42ecef3dbb888ba716b6f63335efca6fb0f27457 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftEntityTypes.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftEntityTypes.java +@@ -428,7 +428,7 @@ public final class CraftEntityTypes { + register(new EntityTypeData<>(EntityType.EXPERIENCE_ORB, ExperienceOrb.class, CraftExperienceOrb::new, + spawnData -> new net.minecraft.world.entity.ExperienceOrb(spawnData.minecraftWorld(), spawnData.x(), spawnData.y(), spawnData.z(), 0, org.bukkit.entity.ExperienceOrb.SpawnReason.CUSTOM, null, null) // Paper + )); +- register(new EntityTypeData<>(EntityType.AREA_EFFECT_CLOUD, AreaEffectCloud.class, CraftAreaEffectCloud::new, spawnData -> new net.minecraft.world.entity.AreaEffectCloud(spawnData.minecraftWorld(), spawnData.x(), spawnData.y(), spawnData.z()))); ++ register(new EntityTypeData<>(EntityType.AREA_EFFECT_CLOUD, AreaEffectCloud.class, CraftAreaEffectCloud::new, createAndMove(net.minecraft.world.entity.EntityType.AREA_EFFECT_CLOUD))); // Paper - set area effect cloud rotation + register(new EntityTypeData<>(EntityType.EGG, Egg.class, CraftEgg::new, spawnData -> new ThrownEgg(spawnData.minecraftWorld(), spawnData.x(), spawnData.y(), spawnData.z(), new net.minecraft.world.item.ItemStack(Items.EGG)))); + register(new EntityTypeData<>(EntityType.LEASH_KNOT, LeashHitch.class, CraftLeash::new, spawnData -> new LeashFenceKnotEntity(spawnData.minecraftWorld(), BlockPos.containing(spawnData.x(), spawnData.y(), spawnData.z())))); // SPIGOT-5732: LeashHitch has no direction and is always centered at a block + register(new EntityTypeData<>(EntityType.SNOWBALL, Snowball.class, CraftSnowball::new, spawnData -> new net.minecraft.world.entity.projectile.Snowball(spawnData.minecraftWorld(), spawnData.x(), spawnData.y(), spawnData.z(), new net.minecraft.world.item.ItemStack(Items.SNOWBALL)))); diff --git a/patches/server/0506-Enhance-console-tab-completions-for-brigadier-comman.patch b/patches/server/0506-Enhance-console-tab-completions-for-brigadier-comman.patch deleted file mode 100644 index 596d2d9d6d15..000000000000 --- a/patches/server/0506-Enhance-console-tab-completions-for-brigadier-comman.patch +++ /dev/null @@ -1,445 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Jason Penilla <11360596+jpenilla@users.noreply.github.com> -Date: Tue, 30 Mar 2021 16:06:08 -0700 -Subject: [PATCH] Enhance console tab completions for brigadier commands - -Co-authored-by: Jake Potrebic - -diff --git a/src/main/java/com/destroystokyo/paper/console/PaperConsole.java b/src/main/java/com/destroystokyo/paper/console/PaperConsole.java -index a4070b59e261f0f1ac4beec47b11492f4724bf27..6ee39b534b8d992655bc0cef3c299d12cbae0034 100644 ---- a/src/main/java/com/destroystokyo/paper/console/PaperConsole.java -+++ b/src/main/java/com/destroystokyo/paper/console/PaperConsole.java -@@ -1,5 +1,8 @@ - package com.destroystokyo.paper.console; - -+import io.papermc.paper.configuration.GlobalConfiguration; -+import io.papermc.paper.console.BrigadierCompletionMatcher; -+import io.papermc.paper.console.BrigadierConsoleParser; - import net.minecraft.server.dedicated.DedicatedServer; - import net.minecrell.terminalconsole.SimpleTerminalConsole; - import org.bukkit.craftbukkit.command.ConsoleCommandCompleter; -@@ -16,11 +19,20 @@ public final class PaperConsole extends SimpleTerminalConsole { - - @Override - protected LineReader buildReader(LineReaderBuilder builder) { -- return super.buildReader(builder -+ builder - .appName("Paper") - .variable(LineReader.HISTORY_FILE, java.nio.file.Paths.get(".console_history")) - .completer(new ConsoleCommandCompleter(this.server)) -- ); -+ .option(LineReader.Option.COMPLETE_IN_WORD, true); -+ if (io.papermc.paper.configuration.GlobalConfiguration.get().console.enableBrigadierHighlighting) { -+ builder.highlighter(new io.papermc.paper.console.BrigadierCommandHighlighter(this.server)); -+ } -+ if (GlobalConfiguration.get().console.enableBrigadierCompletions) { -+ System.setProperty("org.jline.reader.support.parsedline", "true"); // to hide a warning message about the parser not supporting -+ builder.parser(new BrigadierConsoleParser(this.server)); -+ builder.completionMatcher(new BrigadierCompletionMatcher()); -+ } -+ return super.buildReader(builder); - } - - @Override -diff --git a/src/main/java/io/papermc/paper/console/BrigadierCommandCompleter.java b/src/main/java/io/papermc/paper/console/BrigadierCommandCompleter.java -new file mode 100644 -index 0000000000000000000000000000000000000000..bf7b9518c05ff8a6d4b7d7cd36187ca22257e3dc ---- /dev/null -+++ b/src/main/java/io/papermc/paper/console/BrigadierCommandCompleter.java -@@ -0,0 +1,119 @@ -+package io.papermc.paper.console; -+ -+import com.destroystokyo.paper.event.server.AsyncTabCompleteEvent; -+import com.destroystokyo.paper.event.server.AsyncTabCompleteEvent.Completion; -+import com.google.common.base.Suppliers; -+import com.mojang.brigadier.CommandDispatcher; -+import com.mojang.brigadier.ParseResults; -+import com.mojang.brigadier.StringReader; -+import com.mojang.brigadier.suggestion.Suggestion; -+import io.papermc.paper.adventure.PaperAdventure; -+import java.util.ArrayList; -+import java.util.Collections; -+import java.util.List; -+import java.util.function.Supplier; -+import net.kyori.adventure.text.Component; -+import net.minecraft.commands.CommandSourceStack; -+import net.minecraft.network.chat.ComponentUtils; -+import net.minecraft.server.dedicated.DedicatedServer; -+import org.checkerframework.checker.nullness.qual.NonNull; -+import org.checkerframework.checker.nullness.qual.Nullable; -+import org.jline.reader.Candidate; -+import org.jline.reader.LineReader; -+import org.jline.reader.ParsedLine; -+ -+import static com.destroystokyo.paper.event.server.AsyncTabCompleteEvent.Completion.completion; -+ -+public final class BrigadierCommandCompleter { -+ private final Supplier commandSourceStack; -+ private final DedicatedServer server; -+ -+ public BrigadierCommandCompleter(final @NonNull DedicatedServer server) { -+ this.server = server; -+ this.commandSourceStack = Suppliers.memoize(this.server::createCommandSourceStack); -+ } -+ -+ public void complete(final @NonNull LineReader reader, final @NonNull ParsedLine line, final @NonNull List candidates, final @NonNull List existing) { -+ //noinspection ConstantConditions -+ if (this.server.overworld() == null) { // check if overworld is null, as worlds haven't been loaded yet -+ return; -+ } else if (!io.papermc.paper.configuration.GlobalConfiguration.get().console.enableBrigadierCompletions) { -+ this.addCandidates(candidates, Collections.emptyList(), existing, new ParseContext(line.line(), 0)); -+ return; -+ } -+ final CommandDispatcher dispatcher = this.server.getCommands().getDispatcher(); -+ final ParseResults results = dispatcher.parse(new StringReader(line.line()), this.commandSourceStack.get()); -+ this.addCandidates( -+ candidates, -+ dispatcher.getCompletionSuggestions(results, line.cursor()).join().getList(), -+ existing, -+ new ParseContext(line.line(), results.getContext().findSuggestionContext(line.cursor()).startPos) -+ ); -+ } -+ -+ private void addCandidates( -+ final @NonNull List candidates, -+ final @NonNull List brigSuggestions, -+ final @NonNull List existing, -+ final @NonNull ParseContext context -+ ) { -+ brigSuggestions.forEach(it -> { -+ if (it.getText().isEmpty()) return; -+ candidates.add(toCandidate(it, context)); -+ }); -+ for (final AsyncTabCompleteEvent.Completion completion : existing) { -+ if (completion.suggestion().isEmpty() || brigSuggestions.stream().anyMatch(it -> it.getText().equals(completion.suggestion()))) { -+ continue; -+ } -+ candidates.add(toCandidate(completion)); -+ } -+ } -+ -+ private static Candidate toCandidate(final Suggestion suggestion, final @NonNull ParseContext context) { -+ Component tooltip = null; -+ if (suggestion.getTooltip() != null) { -+ tooltip = PaperAdventure.asAdventure(ComponentUtils.fromMessage(suggestion.getTooltip())); -+ } -+ return toCandidate(context.line.substring(context.suggestionStart, suggestion.getRange().getStart()) + suggestion.getText(), tooltip); -+ } -+ -+ private static @NonNull Candidate toCandidate(final @NonNull Completion completion) { -+ return toCandidate(completion.suggestion(), completion.tooltip()); -+ } -+ -+ private static @NonNull Candidate toCandidate(final @NonNull String suggestionText, final @Nullable Component tooltip) { -+ final String suggestionTooltip = PaperAdventure.ANSI_SERIALIZER.serializeOr(tooltip, null); -+ //noinspection SpellCheckingInspection -+ return new PaperCandidate( -+ suggestionText, -+ suggestionText, -+ null, -+ suggestionTooltip, -+ null, -+ null, -+ /* -+ in an ideal world, this would sometimes be true if the suggestion represented the final possible value for a word. -+ Like for `/execute alig`, pressing enter on align would add a trailing space if this value was true. But not all -+ suggestions should add spaces after, like `/execute as @`, accepting any suggestion here would be valid, but its also -+ valid to have a `[` following the selector -+ */ -+ false -+ ); -+ } -+ -+ private static @NonNull Completion toCompletion(final @NonNull Suggestion suggestion) { -+ if (suggestion.getTooltip() == null) { -+ return completion(suggestion.getText()); -+ } -+ return completion(suggestion.getText(), PaperAdventure.asAdventure(ComponentUtils.fromMessage(suggestion.getTooltip()))); -+ } -+ -+ private record ParseContext(String line, int suggestionStart) { -+ } -+ -+ public static final class PaperCandidate extends Candidate { -+ public PaperCandidate(final String value, final String display, final String group, final String descr, final String suffix, final String key, final boolean complete) { -+ super(value, display, group, descr, suffix, key, complete); -+ } -+ } -+} -diff --git a/src/main/java/io/papermc/paper/console/BrigadierCommandHighlighter.java b/src/main/java/io/papermc/paper/console/BrigadierCommandHighlighter.java -new file mode 100644 -index 0000000000000000000000000000000000000000..0b21dac4473e3ea8022ef5c17f5f7d4d49d3ac0a ---- /dev/null -+++ b/src/main/java/io/papermc/paper/console/BrigadierCommandHighlighter.java -@@ -0,0 +1,67 @@ -+package io.papermc.paper.console; -+ -+import com.google.common.base.Suppliers; -+import com.mojang.brigadier.ParseResults; -+import com.mojang.brigadier.StringReader; -+import com.mojang.brigadier.context.ParsedCommandNode; -+import com.mojang.brigadier.tree.LiteralCommandNode; -+import java.util.function.Supplier; -+import java.util.regex.Pattern; -+import net.minecraft.commands.CommandSourceStack; -+import net.minecraft.server.dedicated.DedicatedServer; -+import org.checkerframework.checker.nullness.qual.NonNull; -+import org.jline.reader.Highlighter; -+import org.jline.reader.LineReader; -+import org.jline.utils.AttributedString; -+import org.jline.utils.AttributedStringBuilder; -+import org.jline.utils.AttributedStyle; -+ -+public final class BrigadierCommandHighlighter implements Highlighter { -+ private static final int[] COLORS = {AttributedStyle.CYAN, AttributedStyle.YELLOW, AttributedStyle.GREEN, AttributedStyle.MAGENTA, /* Client uses GOLD here, not BLUE, however there is no GOLD AttributedStyle. */ AttributedStyle.BLUE}; -+ private final Supplier commandSourceStack; -+ private final DedicatedServer server; -+ -+ public BrigadierCommandHighlighter(final @NonNull DedicatedServer server) { -+ this.server = server; -+ this.commandSourceStack = Suppliers.memoize(this.server::createCommandSourceStack); -+ } -+ -+ @Override -+ public AttributedString highlight(final @NonNull LineReader reader, final @NonNull String buffer) { -+ //noinspection ConstantConditions -+ if (this.server.overworld() == null) { // check if overworld is null, as worlds haven't been loaded yet -+ return new AttributedString(buffer, AttributedStyle.DEFAULT.foreground(AttributedStyle.RED)); -+ } -+ final AttributedStringBuilder builder = new AttributedStringBuilder(); -+ final ParseResults results = this.server.getCommands().getDispatcher().parse(new StringReader(buffer), this.commandSourceStack.get()); -+ int pos = 0; -+ int component = -1; -+ for (final ParsedCommandNode node : results.getContext().getLastChild().getNodes()) { -+ if (node.getRange().getStart() >= buffer.length()) { -+ break; -+ } -+ final int start = node.getRange().getStart(); -+ final int end = Math.min(node.getRange().getEnd(), buffer.length()); -+ builder.append(buffer.substring(pos, start), AttributedStyle.DEFAULT); -+ if (node.getNode() instanceof LiteralCommandNode) { -+ builder.append(buffer.substring(start, end), AttributedStyle.DEFAULT); -+ } else { -+ if (++component >= COLORS.length) { -+ component = 0; -+ } -+ builder.append(buffer.substring(start, end), AttributedStyle.DEFAULT.foreground(COLORS[component])); -+ } -+ pos = end; -+ } -+ if (pos < buffer.length()) { -+ builder.append((buffer.substring(pos)), AttributedStyle.DEFAULT.foreground(AttributedStyle.RED)); -+ } -+ return builder.toAttributedString(); -+ } -+ -+ @Override -+ public void setErrorPattern(final Pattern errorPattern) {} -+ -+ @Override -+ public void setErrorIndex(final int errorIndex) {} -+} -diff --git a/src/main/java/io/papermc/paper/console/BrigadierCompletionMatcher.java b/src/main/java/io/papermc/paper/console/BrigadierCompletionMatcher.java -new file mode 100644 -index 0000000000000000000000000000000000000000..1e8028a43db0ff1d5b22d06ef12c1c32d992c09c ---- /dev/null -+++ b/src/main/java/io/papermc/paper/console/BrigadierCompletionMatcher.java -@@ -0,0 +1,27 @@ -+package io.papermc.paper.console; -+ -+import com.google.common.collect.Iterables; -+import java.util.HashMap; -+import java.util.List; -+import java.util.Map; -+import org.jline.reader.Candidate; -+import org.jline.reader.CompletingParsedLine; -+import org.jline.reader.LineReader; -+import org.jline.reader.impl.CompletionMatcherImpl; -+ -+public class BrigadierCompletionMatcher extends CompletionMatcherImpl { -+ -+ @Override -+ protected void defaultMatchers(final Map options, final boolean prefix, final CompletingParsedLine line, final boolean caseInsensitive, final int errors, final String originalGroupName) { -+ super.defaultMatchers(options, prefix, line, caseInsensitive, errors, originalGroupName); -+ this.matchers.addFirst(m -> { -+ final Map> candidates = new HashMap<>(); -+ for (final Map.Entry> entry : m.entrySet()) { -+ if (Iterables.all(entry.getValue(), BrigadierCommandCompleter.PaperCandidate.class::isInstance)) { -+ candidates.put(entry.getKey(), entry.getValue()); -+ } -+ } -+ return candidates; -+ }); -+ } -+} -diff --git a/src/main/java/io/papermc/paper/console/BrigadierConsoleParser.java b/src/main/java/io/papermc/paper/console/BrigadierConsoleParser.java -new file mode 100644 -index 0000000000000000000000000000000000000000..8239a8ba57f856cbbee237a601b3cabfce20ba26 ---- /dev/null -+++ b/src/main/java/io/papermc/paper/console/BrigadierConsoleParser.java -@@ -0,0 +1,79 @@ -+package io.papermc.paper.console; -+ -+import com.mojang.brigadier.ImmutableStringReader; -+import com.mojang.brigadier.ParseResults; -+import com.mojang.brigadier.StringReader; -+import com.mojang.brigadier.context.CommandContextBuilder; -+import com.mojang.brigadier.context.ParsedCommandNode; -+import com.mojang.brigadier.context.StringRange; -+import java.util.ArrayList; -+import java.util.List; -+import net.minecraft.commands.CommandSourceStack; -+import net.minecraft.server.dedicated.DedicatedServer; -+import org.jline.reader.ParsedLine; -+import org.jline.reader.Parser; -+import org.jline.reader.SyntaxError; -+ -+public class BrigadierConsoleParser implements Parser { -+ -+ private final DedicatedServer server; -+ -+ public BrigadierConsoleParser(DedicatedServer server) { -+ this.server = server; -+ } -+ -+ @Override -+ public ParsedLine parse(final String line, final int cursor, final ParseContext context) throws SyntaxError { -+ final ParseResults results = this.server.getCommands().getDispatcher().parse(new StringReader(line), this.server.createCommandSourceStack()); -+ final ImmutableStringReader reader = results.getReader(); -+ final List words = new ArrayList<>(); -+ CommandContextBuilder currentContext = results.getContext(); -+ int currentWordIdx = -1; -+ int wordIdx = -1; -+ int inWordCursor = -1; -+ if (currentContext.getRange().getLength() > 0) { -+ do { -+ for (final ParsedCommandNode node : currentContext.getNodes()) { -+ final StringRange nodeRange = node.getRange(); -+ String current = nodeRange.get(reader); -+ words.add(current); -+ currentWordIdx++; -+ if (wordIdx == -1 && nodeRange.getStart() <= cursor && nodeRange.getEnd() >= cursor) { -+ // if cursor is in the middle of a parsed word/node -+ wordIdx = currentWordIdx; -+ inWordCursor = cursor - nodeRange.getStart(); -+ } -+ } -+ currentContext = currentContext.getChild(); -+ } while (currentContext != null); -+ } -+ final String leftovers = reader.getRemaining(); -+ if (!leftovers.isEmpty() && leftovers.isBlank()) { -+ // if brig didn't consume the whole line, and everything else is blank, add a new empty word -+ currentWordIdx++; -+ words.add(""); -+ if (wordIdx == -1) { -+ wordIdx = currentWordIdx; -+ inWordCursor = 0; -+ } -+ } else if (!leftovers.isEmpty()) { -+ // if there are unparsed leftovers, add a new word with the remaining input -+ currentWordIdx++; -+ words.add(leftovers); -+ if (wordIdx == -1) { -+ wordIdx = currentWordIdx; -+ inWordCursor = cursor - reader.getCursor(); -+ } -+ } -+ if (wordIdx == -1) { -+ currentWordIdx++; -+ words.add(""); -+ wordIdx = currentWordIdx; -+ inWordCursor = 0; -+ } -+ return new BrigadierParsedLine(words.get(wordIdx), inWordCursor, wordIdx, words, line, cursor); -+ } -+ -+ record BrigadierParsedLine(String word, int wordCursor, int wordIndex, List words, String line, int cursor) implements ParsedLine { -+ } -+} -diff --git a/src/main/java/net/minecraft/server/dedicated/DedicatedServer.java b/src/main/java/net/minecraft/server/dedicated/DedicatedServer.java -index d5153f804cfcfd1a70c46975e3fb1e50c8a82999..764395fe8e49d811294ca82887fee91ca6cd01fc 100644 ---- a/src/main/java/net/minecraft/server/dedicated/DedicatedServer.java -+++ b/src/main/java/net/minecraft/server/dedicated/DedicatedServer.java -@@ -190,7 +190,7 @@ public class DedicatedServer extends MinecraftServer implements ServerInterface - - thread.setDaemon(true); - thread.setUncaughtExceptionHandler(new DefaultUncaughtExceptionHandler(DedicatedServer.LOGGER)); -- thread.start(); -+ // thread.start(); // Paper - Enhance console tab completions for brigadier commands; moved down - DedicatedServer.LOGGER.info("Starting minecraft server version {}", SharedConstants.getCurrentVersion().getName()); - if (Runtime.getRuntime().maxMemory() / 1024L / 1024L < 512L) { - DedicatedServer.LOGGER.warn("To start the server with more ram, launch it as \"java -Xmx1024M -Xms1024M -jar minecraft_server.jar\""); -@@ -223,6 +223,7 @@ public class DedicatedServer extends MinecraftServer implements ServerInterface - this.getPlayerList().loadAndSaveFiles(); // Must be after convertNames - // Paper end - fix converting txt to json file - org.spigotmc.WatchdogThread.doStart(org.spigotmc.SpigotConfig.timeoutTime, org.spigotmc.SpigotConfig.restartOnCrash); // Paper - start watchdog thread -+ thread.start(); // Paper - Enhance console tab completions for brigadier commands; start console thread after MinecraftServer.console & PaperConfig are initialized - io.papermc.paper.command.PaperCommands.registerCommands(this); // Paper - setup /paper command - com.destroystokyo.paper.Metrics.PaperMetrics.startMetrics(); // Paper - start metrics - com.destroystokyo.paper.VersionHistoryManager.INSTANCE.getClass(); // Paper - load version history now -diff --git a/src/main/java/org/bukkit/craftbukkit/command/ConsoleCommandCompleter.java b/src/main/java/org/bukkit/craftbukkit/command/ConsoleCommandCompleter.java -index 15bc85f4799a4b23edd2f1e93f1794de5ca3e8e3..a45e658996e483e9a21cfd8178153ddb7b87ae69 100644 ---- a/src/main/java/org/bukkit/craftbukkit/command/ConsoleCommandCompleter.java -+++ b/src/main/java/org/bukkit/craftbukkit/command/ConsoleCommandCompleter.java -@@ -18,9 +18,11 @@ import org.bukkit.event.server.TabCompleteEvent; - - public class ConsoleCommandCompleter implements Completer { - private final DedicatedServer server; // Paper - CraftServer -> DedicatedServer -+ private final io.papermc.paper.console.BrigadierCommandCompleter brigadierCompleter; // Paper - Enhance console tab completions for brigadier commands - - public ConsoleCommandCompleter(DedicatedServer server) { // Paper - CraftServer -> DedicatedServer - this.server = server; -+ this.brigadierCompleter = new io.papermc.paper.console.BrigadierCommandCompleter(this.server); // Paper - Enhance console tab completions for brigadier commands - } - - // Paper start - Change method signature for JLine update -@@ -64,7 +66,7 @@ public class ConsoleCommandCompleter implements Completer { - } - } - -- if (!completions.isEmpty()) { -+ if (false && !completions.isEmpty()) { - for (final com.destroystokyo.paper.event.server.AsyncTabCompleteEvent.Completion completion : completions) { - if (completion.suggestion().isEmpty()) { - continue; -@@ -80,6 +82,7 @@ public class ConsoleCommandCompleter implements Completer { - )); - } - } -+ this.addCompletions(reader, line, candidates, completions); - return; - } - -@@ -99,10 +102,12 @@ public class ConsoleCommandCompleter implements Completer { - try { - List offers = waitable.get(); - if (offers == null) { -+ this.addCompletions(reader, line, candidates, Collections.emptyList()); // Paper - Enhance console tab completions for brigadier commands - return; // Paper - Method returns void - } - - // Paper start - JLine update -+ /* - for (String completion : offers) { - if (completion.isEmpty()) { - continue; -@@ -110,6 +115,8 @@ public class ConsoleCommandCompleter implements Completer { - - candidates.add(new Candidate(completion)); - } -+ */ -+ this.addCompletions(reader, line, candidates, offers.stream().map(com.destroystokyo.paper.event.server.AsyncTabCompleteEvent.Completion::completion).collect(java.util.stream.Collectors.toList())); - // Paper end - - // Paper start - JLine handles cursor now -@@ -138,5 +145,9 @@ public class ConsoleCommandCompleter implements Completer { - } - return false; - } -+ -+ private void addCompletions(final LineReader reader, final ParsedLine line, final List candidates, final List existing) { -+ this.brigadierCompleter.complete(reader, line, candidates, existing); -+ } - // Paper end - } diff --git a/patches/server/0506-add-isDeeplySleeping-to-HumanEntity.patch b/patches/server/0506-add-isDeeplySleeping-to-HumanEntity.patch new file mode 100644 index 000000000000..0bb00244804b --- /dev/null +++ b/patches/server/0506-add-isDeeplySleeping-to-HumanEntity.patch @@ -0,0 +1,24 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Jake Potrebic +Date: Thu, 8 Apr 2021 17:36:10 -0700 +Subject: [PATCH] add isDeeplySleeping to HumanEntity + + +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftHumanEntity.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftHumanEntity.java +index cb56c75be83e839bafdae4356f85d33499d01d8a..1e49eae80730aa9d2e49cd92759d899deb49fb90 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftHumanEntity.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftHumanEntity.java +@@ -132,6 +132,13 @@ public class CraftHumanEntity extends CraftLivingEntity implements HumanEntity { + } + // Paper end + ++ // Paper start ++ @Override ++ public boolean isDeeplySleeping() { ++ return getHandle().isSleepingLongEnough(); ++ } ++ // Paper end ++ + @Override + public int getSleepTicks() { + return this.getHandle().sleepCounter; diff --git a/patches/server/0507-Fix-PlayerItemConsumeEvent-cancelling-properly.patch b/patches/server/0507-Fix-PlayerItemConsumeEvent-cancelling-properly.patch deleted file mode 100644 index a47593aa71ca..000000000000 --- a/patches/server/0507-Fix-PlayerItemConsumeEvent-cancelling-properly.patch +++ /dev/null @@ -1,22 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: chickeneer -Date: Fri, 19 Mar 2021 00:33:15 -0500 -Subject: [PATCH] Fix PlayerItemConsumeEvent cancelling properly - -When the active item is not cleared, the item is still readied -for use and will repeatedly trigger the PlayerItemConsumeEvent -till their item is switched. -This patch clears the active item when the event is cancelled - -diff --git a/src/main/java/net/minecraft/world/entity/LivingEntity.java b/src/main/java/net/minecraft/world/entity/LivingEntity.java -index a89c6ba4140f15e7f68f0df5cf64a176d350123b..afa2d3f5199fcac5c83639bb87e31425da4c5310 100644 ---- a/src/main/java/net/minecraft/world/entity/LivingEntity.java -+++ b/src/main/java/net/minecraft/world/entity/LivingEntity.java -@@ -4011,6 +4011,7 @@ public abstract class LivingEntity extends Entity implements Attackable { - this.level().getCraftServer().getPluginManager().callEvent(event); - - if (event.isCancelled()) { -+ this.stopUsingItem(); // Paper - event is using an item, clear active item to reset its use - // Update client - if (this.useItem.getItem() instanceof net.minecraft.world.item.SuspiciousStewItem itemSuspiciousStew) { - itemSuspiciousStew.cancelUsingItem(entityPlayer, this.useItem); diff --git a/patches/server/0507-add-consumeFuel-to-FurnaceBurnEvent.patch b/patches/server/0507-add-consumeFuel-to-FurnaceBurnEvent.patch new file mode 100644 index 000000000000..5b664856f408 --- /dev/null +++ b/patches/server/0507-add-consumeFuel-to-FurnaceBurnEvent.patch @@ -0,0 +1,19 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Jake Potrebic +Date: Thu, 22 Apr 2021 16:45:28 -0700 +Subject: [PATCH] add consumeFuel to FurnaceBurnEvent + + +diff --git a/src/main/java/net/minecraft/world/level/block/entity/AbstractFurnaceBlockEntity.java b/src/main/java/net/minecraft/world/level/block/entity/AbstractFurnaceBlockEntity.java +index 08b94e5e31ca835c1f9eaefbab07076c91deadeb..9c1267df7057caa3500c7a9e6c705ea58c2b5e11 100644 +--- a/src/main/java/net/minecraft/world/level/block/entity/AbstractFurnaceBlockEntity.java ++++ b/src/main/java/net/minecraft/world/level/block/entity/AbstractFurnaceBlockEntity.java +@@ -255,7 +255,7 @@ public abstract class AbstractFurnaceBlockEntity extends BaseContainerBlockEntit + if (blockEntity.isLit() && furnaceBurnEvent.isBurning()) { + // CraftBukkit end + flag1 = true; +- if (flag3) { ++ if (flag3 && furnaceBurnEvent.willConsumeFuel()) { // Paper - add consumeFuel to FurnaceBurnEvent + Item item = itemstack.getItem(); + + itemstack.shrink(1); diff --git a/patches/server/0508-add-get-set-drop-chance-to-EntityEquipment.patch b/patches/server/0508-add-get-set-drop-chance-to-EntityEquipment.patch new file mode 100644 index 000000000000..e7944704100c --- /dev/null +++ b/patches/server/0508-add-get-set-drop-chance-to-EntityEquipment.patch @@ -0,0 +1,72 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Jake Potrebic +Date: Thu, 22 Apr 2021 00:28:11 -0700 +Subject: [PATCH] add get-set drop chance to EntityEquipment + +== AT == +public net.minecraft.world.entity.Mob getEquipmentDropChance(Lnet/minecraft/world/entity/EquipmentSlot;)F + +diff --git a/src/main/java/org/bukkit/craftbukkit/inventory/CraftEntityEquipment.java b/src/main/java/org/bukkit/craftbukkit/inventory/CraftEntityEquipment.java +index cb704cef3845727c465fe3ea7210a11545da56c8..fdcc414f4fa246082ad0732133c870d915ae3084 100644 +--- a/src/main/java/org/bukkit/craftbukkit/inventory/CraftEntityEquipment.java ++++ b/src/main/java/org/bukkit/craftbukkit/inventory/CraftEntityEquipment.java +@@ -244,15 +244,22 @@ public class CraftEntityEquipment implements EntityEquipment { + public void setBootsDropChance(float chance) { + this.setDropChance(net.minecraft.world.entity.EquipmentSlot.FEET, chance); + } ++ // Paper start ++ @Override ++ public float getDropChance(EquipmentSlot slot) { ++ return getDropChance(CraftEquipmentSlot.getNMS(slot)); ++ } ++ ++ @Override ++ public void setDropChance(EquipmentSlot slot, float chance) { ++ setDropChance(CraftEquipmentSlot.getNMS(slot), chance); ++ } ++ // Paper end + + private void setDropChance(net.minecraft.world.entity.EquipmentSlot slot, float chance) { + Preconditions.checkArgument(this.entity.getHandle() instanceof Mob, "Cannot set drop chance for non-Mob entity"); + +- if (slot == net.minecraft.world.entity.EquipmentSlot.MAINHAND || slot == net.minecraft.world.entity.EquipmentSlot.OFFHAND) { +- ((Mob) this.entity.getHandle()).handDropChances[slot.getIndex()] = chance; +- } else { +- ((Mob) this.entity.getHandle()).armorDropChances[slot.getIndex()] = chance; +- } ++ ((Mob) this.entity.getHandle()).setDropChance(slot, chance); // Paper - use setter on Mob + } + + private float getDropChance(net.minecraft.world.entity.EquipmentSlot slot) { +@@ -260,10 +267,6 @@ public class CraftEntityEquipment implements EntityEquipment { + return 1; + } + +- if (slot == net.minecraft.world.entity.EquipmentSlot.MAINHAND || slot == net.minecraft.world.entity.EquipmentSlot.OFFHAND) { +- return ((Mob) this.entity.getHandle()).handDropChances[slot.getIndex()]; +- } else { +- return ((Mob) this.entity.getHandle()).armorDropChances[slot.getIndex()]; +- } ++ return ((Mob) this.entity.getHandle()).getEquipmentDropChance(slot); // Paper - use getter on Mob + } + } +diff --git a/src/main/java/org/bukkit/craftbukkit/inventory/CraftInventoryPlayer.java b/src/main/java/org/bukkit/craftbukkit/inventory/CraftInventoryPlayer.java +index f039c5041e783ef55e19194cb7db7610429b1d96..107fa1d4bd977d17dc062da280dda46eb3c5f81c 100644 +--- a/src/main/java/org/bukkit/craftbukkit/inventory/CraftInventoryPlayer.java ++++ b/src/main/java/org/bukkit/craftbukkit/inventory/CraftInventoryPlayer.java +@@ -353,4 +353,15 @@ public class CraftInventoryPlayer extends CraftInventory implements org.bukkit.i + public void setBootsDropChance(float chance) { + throw new UnsupportedOperationException("Cannot set drop chance for PlayerInventory"); + } ++ // Paper start ++ @Override ++ public float getDropChance(EquipmentSlot slot) { ++ return 1; ++ } ++ ++ @Override ++ public void setDropChance(EquipmentSlot slot, float chance) { ++ throw new UnsupportedOperationException("Cannot set drop chance for PlayerInventory"); ++ } ++ // Paper end + } diff --git a/patches/server/0509-Set-area-affect-cloud-rotation.patch b/patches/server/0509-Set-area-affect-cloud-rotation.patch deleted file mode 100644 index 18167918366f..000000000000 --- a/patches/server/0509-Set-area-affect-cloud-rotation.patch +++ /dev/null @@ -1,19 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Owen1212055 <23108066+Owen1212055@users.noreply.github.com> -Date: Mon, 5 Apr 2021 16:58:20 -0400 -Subject: [PATCH] Set area affect cloud rotation - - -diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftEntityTypes.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftEntityTypes.java -index 9b2b0bbb1755b6be1c23cf56e29a68b0002fd755..481f0c62fcd4435d655cfe2f94f46262b24a7144 100644 ---- a/src/main/java/org/bukkit/craftbukkit/entity/CraftEntityTypes.java -+++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftEntityTypes.java -@@ -374,7 +374,7 @@ public final class CraftEntityTypes { - register(new EntityTypeData<>(EntityType.EXPERIENCE_ORB, ExperienceOrb.class, CraftExperienceOrb::new, - spawnData -> new net.minecraft.world.entity.ExperienceOrb(spawnData.minecraftWorld(), spawnData.x(), spawnData.y(), spawnData.z(), 0, org.bukkit.entity.ExperienceOrb.SpawnReason.CUSTOM, null, null) // Paper - )); -- register(new EntityTypeData<>(EntityType.AREA_EFFECT_CLOUD, AreaEffectCloud.class, CraftAreaEffectCloud::new, spawnData -> new net.minecraft.world.entity.AreaEffectCloud(spawnData.minecraftWorld(), spawnData.x(), spawnData.y(), spawnData.z()))); -+ register(new EntityTypeData<>(EntityType.AREA_EFFECT_CLOUD, AreaEffectCloud.class, CraftAreaEffectCloud::new, createAndMove(net.minecraft.world.entity.EntityType.AREA_EFFECT_CLOUD))); // Paper - set area effect cloud rotation - register(new EntityTypeData<>(EntityType.EGG, Egg.class, CraftEgg::new, spawnData -> new ThrownEgg(spawnData.minecraftWorld(), spawnData.x(), spawnData.y(), spawnData.z()))); - register(new EntityTypeData<>(EntityType.LEASH_KNOT, LeashHitch.class, CraftLeash::new, spawnData -> new LeashFenceKnotEntity(spawnData.minecraftWorld(), BlockPos.containing(spawnData.x(), spawnData.y(), spawnData.z())))); // SPIGOT-5732: LeashHitch has no direction and is always centered at a block - register(new EntityTypeData<>(EntityType.SNOWBALL, Snowball.class, CraftSnowball::new, spawnData -> new net.minecraft.world.entity.projectile.Snowball(spawnData.minecraftWorld(), spawnData.x(), spawnData.y(), spawnData.z()))); diff --git a/patches/server/0509-fix-PigZombieAngerEvent-cancellation.patch b/patches/server/0509-fix-PigZombieAngerEvent-cancellation.patch new file mode 100644 index 000000000000..040d076f37cf --- /dev/null +++ b/patches/server/0509-fix-PigZombieAngerEvent-cancellation.patch @@ -0,0 +1,35 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Trigary +Date: Thu, 18 Mar 2021 21:38:01 +0100 +Subject: [PATCH] fix PigZombieAngerEvent cancellation + + +diff --git a/src/main/java/net/minecraft/world/entity/monster/ZombifiedPiglin.java b/src/main/java/net/minecraft/world/entity/monster/ZombifiedPiglin.java +index faaaee79e14adb62e810641ccc8f51428d39883d..03e3cbe73119ca76417d4dd192e1560bdfc373ec 100644 +--- a/src/main/java/net/minecraft/world/entity/monster/ZombifiedPiglin.java ++++ b/src/main/java/net/minecraft/world/entity/monster/ZombifiedPiglin.java +@@ -56,6 +56,7 @@ public class ZombifiedPiglin extends Zombie implements NeutralMob { + private static final int ALERT_RANGE_Y = 10; + private static final UniformInt ALERT_INTERVAL = TimeUtil.rangeOfSeconds(4, 6); + private int ticksUntilNextAlert; ++ private HurtByTargetGoal pathfinderGoalHurtByTarget; // Paper - fix PigZombieAngerEvent cancellation + + public ZombifiedPiglin(EntityType type, Level world) { + super(type, world); +@@ -71,7 +72,7 @@ public class ZombifiedPiglin extends Zombie implements NeutralMob { + protected void addBehaviourGoals() { + this.goalSelector.addGoal(2, new ZombieAttackGoal(this, 1.0D, false)); + this.goalSelector.addGoal(7, new WaterAvoidingRandomStrollGoal(this, 1.0D)); +- this.targetSelector.addGoal(1, (new HurtByTargetGoal(this, new Class[0])).setAlertOthers()); ++ this.targetSelector.addGoal(1, pathfinderGoalHurtByTarget = (new HurtByTargetGoal(this, new Class[0])).setAlertOthers()); // Paper - fix PigZombieAngerEvent cancellation + this.targetSelector.addGoal(2, new NearestAttackableTargetGoal<>(this, Player.class, 10, true, false, this::isAngryAt)); + this.targetSelector.addGoal(3, new ResetUniversalAngerTargetGoal<>(this, true)); + } +@@ -179,6 +180,7 @@ public class ZombifiedPiglin extends Zombie implements NeutralMob { + this.level().getCraftServer().getPluginManager().callEvent(event); + if (event.isCancelled()) { + this.setPersistentAngerTarget(null); ++ pathfinderGoalHurtByTarget.stop(); // Paper - fix PigZombieAngerEvent cancellation + return; + } + this.setRemainingPersistentAngerTime(event.getNewAnger()); diff --git a/patches/server/0510-add-isDeeplySleeping-to-HumanEntity.patch b/patches/server/0510-add-isDeeplySleeping-to-HumanEntity.patch deleted file mode 100644 index eaac6f65d83f..000000000000 --- a/patches/server/0510-add-isDeeplySleeping-to-HumanEntity.patch +++ /dev/null @@ -1,24 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Jake Potrebic -Date: Thu, 8 Apr 2021 17:36:10 -0700 -Subject: [PATCH] add isDeeplySleeping to HumanEntity - - -diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftHumanEntity.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftHumanEntity.java -index 24aa891ffa9115c05439b06aece85df7a382b7c4..0222b6c68112551336f17a722fc3399898cdc7bb 100644 ---- a/src/main/java/org/bukkit/craftbukkit/entity/CraftHumanEntity.java -+++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftHumanEntity.java -@@ -132,6 +132,13 @@ public class CraftHumanEntity extends CraftLivingEntity implements HumanEntity { - } - // Paper end - -+ // Paper start -+ @Override -+ public boolean isDeeplySleeping() { -+ return getHandle().isSleepingLongEnough(); -+ } -+ // Paper end -+ - @Override - public int getSleepTicks() { - return this.getHandle().sleepCounter; diff --git a/patches/server/0510-fix-PlayerItemHeldEvent-firing-twice.patch b/patches/server/0510-fix-PlayerItemHeldEvent-firing-twice.patch new file mode 100644 index 000000000000..5732fc5af670 --- /dev/null +++ b/patches/server/0510-fix-PlayerItemHeldEvent-firing-twice.patch @@ -0,0 +1,18 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: chickeneer +Date: Thu, 22 Apr 2021 19:02:07 -0700 +Subject: [PATCH] fix PlayerItemHeldEvent firing twice + + +diff --git a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java +index 1b4a19eb510c3efa2f2459554bed5e16bcb23430..2bd2f1cdf3467cacee55094d43bd3eccf61b9aa7 100644 +--- a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java ++++ b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java +@@ -1954,6 +1954,7 @@ public class ServerGamePacketListenerImpl extends ServerCommonPacketListenerImpl + PacketUtils.ensureRunningOnSameThread(packet, this, this.player.serverLevel()); + if (this.player.isImmobile()) return; // CraftBukkit + if (packet.getSlot() >= 0 && packet.getSlot() < Inventory.getSelectionSize()) { ++ if (packet.getSlot() == this.player.getInventory().selected) { return; } // Paper - don't fire itemheldevent when there wasn't a slot change + PlayerItemHeldEvent event = new PlayerItemHeldEvent(this.getCraftPlayer(), this.player.getInventory().selected, packet.getSlot()); + this.cserver.getPluginManager().callEvent(event); + if (event.isCancelled()) { diff --git a/patches/server/0511-Add-PlayerDeepSleepEvent.patch b/patches/server/0511-Add-PlayerDeepSleepEvent.patch new file mode 100644 index 000000000000..2f01c63a54ad --- /dev/null +++ b/patches/server/0511-Add-PlayerDeepSleepEvent.patch @@ -0,0 +1,24 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Jake Potrebic +Date: Wed, 21 Apr 2021 15:58:19 -0700 +Subject: [PATCH] Add PlayerDeepSleepEvent + + +diff --git a/src/main/java/net/minecraft/world/entity/player/Player.java b/src/main/java/net/minecraft/world/entity/player/Player.java +index 78bb666dbc5ccd84820e1c7b382249510dd5795c..57850f16a681af4fc302895c7608247675b44ab4 100644 +--- a/src/main/java/net/minecraft/world/entity/player/Player.java ++++ b/src/main/java/net/minecraft/world/entity/player/Player.java +@@ -262,6 +262,13 @@ public abstract class Player extends LivingEntity { + + if (this.isSleeping()) { + ++this.sleepCounter; ++ // Paper start - Add PlayerDeepSleepEvent ++ if (this.sleepCounter == SLEEP_DURATION) { ++ if (!new io.papermc.paper.event.player.PlayerDeepSleepEvent((org.bukkit.entity.Player) getBukkitEntity()).callEvent()) { ++ this.sleepCounter = Integer.MIN_VALUE; ++ } ++ } ++ // Paper end - Add PlayerDeepSleepEvent + if (this.sleepCounter > 100) { + this.sleepCounter = 100; + } diff --git a/patches/server/0511-add-consumeFuel-to-FurnaceBurnEvent.patch b/patches/server/0511-add-consumeFuel-to-FurnaceBurnEvent.patch deleted file mode 100644 index 1938dc07ed9f..000000000000 --- a/patches/server/0511-add-consumeFuel-to-FurnaceBurnEvent.patch +++ /dev/null @@ -1,19 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Jake Potrebic -Date: Thu, 22 Apr 2021 16:45:28 -0700 -Subject: [PATCH] add consumeFuel to FurnaceBurnEvent - - -diff --git a/src/main/java/net/minecraft/world/level/block/entity/AbstractFurnaceBlockEntity.java b/src/main/java/net/minecraft/world/level/block/entity/AbstractFurnaceBlockEntity.java -index 84a3130a31f7a0fd5a8ae1b293dd3f2ca07c86d3..aa59a45bcbe652996eddb80a3187618b7f6d1234 100644 ---- a/src/main/java/net/minecraft/world/level/block/entity/AbstractFurnaceBlockEntity.java -+++ b/src/main/java/net/minecraft/world/level/block/entity/AbstractFurnaceBlockEntity.java -@@ -365,7 +365,7 @@ public abstract class AbstractFurnaceBlockEntity extends BaseContainerBlockEntit - if (blockEntity.isLit() && furnaceBurnEvent.isBurning()) { - // CraftBukkit end - flag1 = true; -- if (flag3) { -+ if (flag3 && furnaceBurnEvent.willConsumeFuel()) { // Paper - add consumeFuel to FurnaceBurnEvent - Item item = itemstack.getItem(); - - itemstack.shrink(1); diff --git a/patches/server/0512-More-World-API.patch b/patches/server/0512-More-World-API.patch new file mode 100644 index 000000000000..3fdba120da2f --- /dev/null +++ b/patches/server/0512-More-World-API.patch @@ -0,0 +1,57 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Jake Potrebic +Date: Tue, 7 Jul 2020 10:52:34 -0700 +Subject: [PATCH] More World API + + +diff --git a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java +index e19e5f1223f8b4fae9292a936dd05b9170610134..e44a78a34aa572b10deb3f343c238cad1c521ab4 100644 +--- a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java ++++ b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java +@@ -2145,6 +2145,28 @@ public class CraftWorld extends CraftRegionAccessor implements World { + return new CraftStructureSearchResult(CraftStructure.minecraftToBukkit(found.getSecond().value()), CraftLocation.toBukkit(found.getFirst(), this)); + } + ++ // Paper start ++ @Override ++ public double getCoordinateScale() { ++ return getHandle().dimensionType().coordinateScale(); ++ } ++ ++ @Override ++ public boolean isFixedTime() { ++ return getHandle().dimensionType().hasFixedTime(); ++ } ++ ++ @Override ++ public Collection getInfiniburn() { ++ return com.google.common.collect.Sets.newHashSet(com.google.common.collect.Iterators.transform(net.minecraft.core.registries.BuiltInRegistries.BLOCK.getTagOrEmpty(this.getHandle().dimensionType().infiniburn()).iterator(), blockHolder -> CraftBlockType.minecraftToBukkit(blockHolder.value()))); ++ } ++ ++ @Override ++ public void sendGameEvent(Entity sourceEntity, org.bukkit.GameEvent gameEvent, Vector position) { ++ getHandle().gameEvent(sourceEntity != null ? ((CraftEntity) sourceEntity).getHandle(): null, net.minecraft.core.registries.BuiltInRegistries.GAME_EVENT.get(org.bukkit.craftbukkit.util.CraftNamespacedKey.toMinecraft(gameEvent.getKey())).orElseThrow(), org.bukkit.craftbukkit.util.CraftVector.toBlockPos(position)); ++ } ++ // Paper end ++ + @Override + public BiomeSearchResult locateNearestBiome(Location origin, int radius, Biome... biomes) { + return this.locateNearestBiome(origin, radius, 32, 64, biomes); +diff --git a/src/main/java/org/bukkit/craftbukkit/util/CraftVector.java b/src/main/java/org/bukkit/craftbukkit/util/CraftVector.java +index 3071ac1ac0e733d73dade49597a39f7d156bbc04..967445b2eb158454100a27369a1f463d69f54f27 100644 +--- a/src/main/java/org/bukkit/craftbukkit/util/CraftVector.java ++++ b/src/main/java/org/bukkit/craftbukkit/util/CraftVector.java +@@ -12,4 +12,13 @@ public final class CraftVector { + public static net.minecraft.world.phys.Vec3 toNMS(org.bukkit.util.Vector bukkit) { + return new net.minecraft.world.phys.Vec3(bukkit.getX(), bukkit.getY(), bukkit.getZ()); + } ++ // Paper start ++ public static org.bukkit.util.Vector toBukkit(net.minecraft.core.BlockPos blockPosition) { ++ return new org.bukkit.util.Vector(blockPosition.getX(), blockPosition.getY(), blockPosition.getZ()); ++ } ++ ++ public static net.minecraft.core.BlockPos toBlockPos(org.bukkit.util.Vector bukkit) { ++ return net.minecraft.core.BlockPos.containing(bukkit.getX(), bukkit.getY(), bukkit.getZ()); ++ } ++ // Paper end + } diff --git a/patches/server/0512-add-get-set-drop-chance-to-EntityEquipment.patch b/patches/server/0512-add-get-set-drop-chance-to-EntityEquipment.patch deleted file mode 100644 index 746b41078474..000000000000 --- a/patches/server/0512-add-get-set-drop-chance-to-EntityEquipment.patch +++ /dev/null @@ -1,72 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Jake Potrebic -Date: Thu, 22 Apr 2021 00:28:11 -0700 -Subject: [PATCH] add get-set drop chance to EntityEquipment - -== AT == -public net.minecraft.world.entity.Mob getEquipmentDropChance(Lnet/minecraft/world/entity/EquipmentSlot;)F - -diff --git a/src/main/java/org/bukkit/craftbukkit/inventory/CraftEntityEquipment.java b/src/main/java/org/bukkit/craftbukkit/inventory/CraftEntityEquipment.java -index cb704cef3845727c465fe3ea7210a11545da56c8..fdcc414f4fa246082ad0732133c870d915ae3084 100644 ---- a/src/main/java/org/bukkit/craftbukkit/inventory/CraftEntityEquipment.java -+++ b/src/main/java/org/bukkit/craftbukkit/inventory/CraftEntityEquipment.java -@@ -244,15 +244,22 @@ public class CraftEntityEquipment implements EntityEquipment { - public void setBootsDropChance(float chance) { - this.setDropChance(net.minecraft.world.entity.EquipmentSlot.FEET, chance); - } -+ // Paper start -+ @Override -+ public float getDropChance(EquipmentSlot slot) { -+ return getDropChance(CraftEquipmentSlot.getNMS(slot)); -+ } -+ -+ @Override -+ public void setDropChance(EquipmentSlot slot, float chance) { -+ setDropChance(CraftEquipmentSlot.getNMS(slot), chance); -+ } -+ // Paper end - - private void setDropChance(net.minecraft.world.entity.EquipmentSlot slot, float chance) { - Preconditions.checkArgument(this.entity.getHandle() instanceof Mob, "Cannot set drop chance for non-Mob entity"); - -- if (slot == net.minecraft.world.entity.EquipmentSlot.MAINHAND || slot == net.minecraft.world.entity.EquipmentSlot.OFFHAND) { -- ((Mob) this.entity.getHandle()).handDropChances[slot.getIndex()] = chance; -- } else { -- ((Mob) this.entity.getHandle()).armorDropChances[slot.getIndex()] = chance; -- } -+ ((Mob) this.entity.getHandle()).setDropChance(slot, chance); // Paper - use setter on Mob - } - - private float getDropChance(net.minecraft.world.entity.EquipmentSlot slot) { -@@ -260,10 +267,6 @@ public class CraftEntityEquipment implements EntityEquipment { - return 1; - } - -- if (slot == net.minecraft.world.entity.EquipmentSlot.MAINHAND || slot == net.minecraft.world.entity.EquipmentSlot.OFFHAND) { -- return ((Mob) this.entity.getHandle()).handDropChances[slot.getIndex()]; -- } else { -- return ((Mob) this.entity.getHandle()).armorDropChances[slot.getIndex()]; -- } -+ return ((Mob) this.entity.getHandle()).getEquipmentDropChance(slot); // Paper - use getter on Mob - } - } -diff --git a/src/main/java/org/bukkit/craftbukkit/inventory/CraftInventoryPlayer.java b/src/main/java/org/bukkit/craftbukkit/inventory/CraftInventoryPlayer.java -index 23abd543cd8e3cbb49e4927aef59ed95d3465360..972fe4237461f07f78b60845b2ebfefb06698ded 100644 ---- a/src/main/java/org/bukkit/craftbukkit/inventory/CraftInventoryPlayer.java -+++ b/src/main/java/org/bukkit/craftbukkit/inventory/CraftInventoryPlayer.java -@@ -353,4 +353,15 @@ public class CraftInventoryPlayer extends CraftInventory implements org.bukkit.i - public void setBootsDropChance(float chance) { - throw new UnsupportedOperationException("Cannot set drop chance for PlayerInventory"); - } -+ // Paper start -+ @Override -+ public float getDropChance(EquipmentSlot slot) { -+ return 1; -+ } -+ -+ @Override -+ public void setDropChance(EquipmentSlot slot, float chance) { -+ throw new UnsupportedOperationException("Cannot set drop chance for PlayerInventory"); -+ } -+ // Paper end - } diff --git a/patches/server/0513-Add-PlayerBedFailEnterEvent.patch b/patches/server/0513-Add-PlayerBedFailEnterEvent.patch new file mode 100644 index 000000000000..a592f428c225 --- /dev/null +++ b/patches/server/0513-Add-PlayerBedFailEnterEvent.patch @@ -0,0 +1,36 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Jake Potrebic +Date: Thu, 24 Dec 2020 12:27:41 -0800 +Subject: [PATCH] Add PlayerBedFailEnterEvent + + +diff --git a/src/main/java/net/minecraft/world/level/block/BedBlock.java b/src/main/java/net/minecraft/world/level/block/BedBlock.java +index bee869b62af5fc845425458b713d1efaf8b28b51..b09fa473426a6c7e006a071827e5a68560d6c685 100644 +--- a/src/main/java/net/minecraft/world/level/block/BedBlock.java ++++ b/src/main/java/net/minecraft/world/level/block/BedBlock.java +@@ -120,14 +120,23 @@ public class BedBlock extends HorizontalDirectionalBlock implements EntityBlock + BlockPos finalblockposition = pos; + // CraftBukkit end + player.startSleepInBed(pos).ifLeft((entityhuman_enumbedresult) -> { ++ // Paper start - PlayerBedFailEnterEvent ++ if (entityhuman_enumbedresult != null) { ++ io.papermc.paper.event.player.PlayerBedFailEnterEvent event = new io.papermc.paper.event.player.PlayerBedFailEnterEvent((org.bukkit.entity.Player) player.getBukkitEntity(), io.papermc.paper.event.player.PlayerBedFailEnterEvent.FailReason.values()[entityhuman_enumbedresult.ordinal()], org.bukkit.craftbukkit.block.CraftBlock.at(world, finalblockposition), !world.dimensionType().bedWorks(), io.papermc.paper.adventure.PaperAdventure.asAdventure(entityhuman_enumbedresult.getMessage())); ++ if (!event.callEvent()) { ++ return; ++ } ++ // Paper end - PlayerBedFailEnterEvent + // CraftBukkit start - handling bed explosion from below here +- if (!world.dimensionType().bedWorks()) { ++ if (event.getWillExplode()) { // Paper - PlayerBedFailEnterEvent + this.explodeBed(finaliblockdata, world, finalblockposition); + } else + // CraftBukkit end + if (entityhuman_enumbedresult.getMessage() != null) { +- player.displayClientMessage(entityhuman_enumbedresult.getMessage(), true); ++ final net.kyori.adventure.text.Component message = event.getMessage(); // Paper - PlayerBedFailEnterEvent ++ if (message != null) player.displayClientMessage(io.papermc.paper.adventure.PaperAdventure.asVanilla(message), true); // Paper - PlayerBedFailEnterEvent + } ++ } // Paper - PlayerBedFailEnterEvent + + }); + return InteractionResult.SUCCESS_SERVER; diff --git a/patches/server/0513-fix-PigZombieAngerEvent-cancellation.patch b/patches/server/0513-fix-PigZombieAngerEvent-cancellation.patch deleted file mode 100644 index 3f0d89aea9e3..000000000000 --- a/patches/server/0513-fix-PigZombieAngerEvent-cancellation.patch +++ /dev/null @@ -1,35 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Trigary -Date: Thu, 18 Mar 2021 21:38:01 +0100 -Subject: [PATCH] fix PigZombieAngerEvent cancellation - - -diff --git a/src/main/java/net/minecraft/world/entity/monster/ZombifiedPiglin.java b/src/main/java/net/minecraft/world/entity/monster/ZombifiedPiglin.java -index f4adf67392402faedd47c4a759fa2c13d85a1c26..10388cf33f6f33070aa84b3b2d7bd14fc50ceea8 100644 ---- a/src/main/java/net/minecraft/world/entity/monster/ZombifiedPiglin.java -+++ b/src/main/java/net/minecraft/world/entity/monster/ZombifiedPiglin.java -@@ -56,6 +56,7 @@ public class ZombifiedPiglin extends Zombie implements NeutralMob { - private static final int ALERT_RANGE_Y = 10; - private static final UniformInt ALERT_INTERVAL = TimeUtil.rangeOfSeconds(4, 6); - private int ticksUntilNextAlert; -+ private HurtByTargetGoal pathfinderGoalHurtByTarget; // Paper - fix PigZombieAngerEvent cancellation - - public ZombifiedPiglin(EntityType type, Level world) { - super(type, world); -@@ -71,7 +72,7 @@ public class ZombifiedPiglin extends Zombie implements NeutralMob { - protected void addBehaviourGoals() { - this.goalSelector.addGoal(2, new ZombieAttackGoal(this, 1.0D, false)); - this.goalSelector.addGoal(7, new WaterAvoidingRandomStrollGoal(this, 1.0D)); -- this.targetSelector.addGoal(1, (new HurtByTargetGoal(this, new Class[0])).setAlertOthers()); -+ this.targetSelector.addGoal(1, pathfinderGoalHurtByTarget = (new HurtByTargetGoal(this, new Class[0])).setAlertOthers()); // Paper - fix PigZombieAngerEvent cancellation - this.targetSelector.addGoal(2, new NearestAttackableTargetGoal<>(this, Player.class, 10, true, false, this::isAngryAt)); - this.targetSelector.addGoal(3, new ResetUniversalAngerTargetGoal<>(this, true)); - } -@@ -179,6 +180,7 @@ public class ZombifiedPiglin extends Zombie implements NeutralMob { - this.level().getCraftServer().getPluginManager().callEvent(event); - if (event.isCancelled()) { - this.setPersistentAngerTarget(null); -+ pathfinderGoalHurtByTarget.stop(); // Paper - fix PigZombieAngerEvent cancellation - return; - } - this.setRemainingPersistentAngerTime(event.getNewAnger()); diff --git a/patches/server/0514-Implement-methods-to-convert-between-Component-and-B.patch b/patches/server/0514-Implement-methods-to-convert-between-Component-and-B.patch new file mode 100644 index 000000000000..c5765e9de7e4 --- /dev/null +++ b/patches/server/0514-Implement-methods-to-convert-between-Component-and-B.patch @@ -0,0 +1,55 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Jason Penilla <11360596+jpenilla@users.noreply.github.com> +Date: Sat, 24 Apr 2021 02:09:32 -0700 +Subject: [PATCH] Implement methods to convert between Component and + Brigadier's Message + + +diff --git a/src/main/java/io/papermc/paper/brigadier/PaperBrigadierProviderImpl.java b/src/main/java/io/papermc/paper/brigadier/PaperBrigadierProviderImpl.java +new file mode 100644 +index 0000000000000000000000000000000000000000..dd6012b6a097575b2d1471be5069eccee4537c0a +--- /dev/null ++++ b/src/main/java/io/papermc/paper/brigadier/PaperBrigadierProviderImpl.java +@@ -0,0 +1,30 @@ ++package io.papermc.paper.brigadier; ++ ++import com.mojang.brigadier.Message; ++import io.papermc.paper.adventure.PaperAdventure; ++import net.kyori.adventure.text.Component; ++import net.kyori.adventure.text.ComponentLike; ++import net.minecraft.network.chat.ComponentUtils; ++import org.checkerframework.checker.nullness.qual.NonNull; ++ ++import static java.util.Objects.requireNonNull; ++ ++public enum PaperBrigadierProviderImpl implements PaperBrigadierProvider { ++ INSTANCE; ++ ++ PaperBrigadierProviderImpl() { ++ PaperBrigadierProvider.initialize(this); ++ } ++ ++ @Override ++ public @NonNull Message message(final @NonNull ComponentLike componentLike) { ++ requireNonNull(componentLike, "componentLike"); ++ return PaperAdventure.asVanilla(componentLike.asComponent()); ++ } ++ ++ @Override ++ public @NonNull Component componentFromMessage(final @NonNull Message message) { ++ requireNonNull(message, "message"); ++ return PaperAdventure.asAdventure(ComponentUtils.fromMessage(message)); ++ } ++} +diff --git a/src/main/java/net/minecraft/server/dedicated/DedicatedServer.java b/src/main/java/net/minecraft/server/dedicated/DedicatedServer.java +index 7c92b2f0a59fe222ad13a998476e312bf571a1bf..761663514b98a1c0a0a905150411aff450a0cc50 100644 +--- a/src/main/java/net/minecraft/server/dedicated/DedicatedServer.java ++++ b/src/main/java/net/minecraft/server/dedicated/DedicatedServer.java +@@ -224,6 +224,7 @@ public class DedicatedServer extends MinecraftServer implements ServerInterface + io.papermc.paper.command.PaperCommands.registerCommands(this); // Paper - setup /paper command + com.destroystokyo.paper.Metrics.PaperMetrics.startMetrics(); // Paper - start metrics + com.destroystokyo.paper.VersionHistoryManager.INSTANCE.getClass(); // Paper - load version history now ++ io.papermc.paper.brigadier.PaperBrigadierProviderImpl.INSTANCE.getClass(); // Paper - init PaperBrigadierProvider + + this.setPvpAllowed(dedicatedserverproperties.pvp); + this.setFlightAllowed(dedicatedserverproperties.allowFlight); diff --git a/patches/server/0514-fix-PlayerItemHeldEvent-firing-twice.patch b/patches/server/0514-fix-PlayerItemHeldEvent-firing-twice.patch deleted file mode 100644 index 9bb8e4f1e164..000000000000 --- a/patches/server/0514-fix-PlayerItemHeldEvent-firing-twice.patch +++ /dev/null @@ -1,18 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: chickeneer -Date: Thu, 22 Apr 2021 19:02:07 -0700 -Subject: [PATCH] fix PlayerItemHeldEvent firing twice - - -diff --git a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java -index 403b2499374906daa0ca2bb774522f92e186e6d5..276c0b017452a1d7b60a2a8e779f2f3fcf4960cc 100644 ---- a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java -+++ b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java -@@ -1933,6 +1933,7 @@ public class ServerGamePacketListenerImpl extends ServerCommonPacketListenerImpl - PacketUtils.ensureRunningOnSameThread(packet, this, this.player.serverLevel()); - if (this.player.isImmobile()) return; // CraftBukkit - if (packet.getSlot() >= 0 && packet.getSlot() < Inventory.getSelectionSize()) { -+ if (packet.getSlot() == this.player.getInventory().selected) { return; } // Paper - don't fire itemheldevent when there wasn't a slot change - PlayerItemHeldEvent event = new PlayerItemHeldEvent(this.getCraftPlayer(), this.player.getInventory().selected, packet.getSlot()); - this.cserver.getPluginManager().callEvent(event); - if (event.isCancelled()) { diff --git a/patches/server/0515-Add-PlayerDeepSleepEvent.patch b/patches/server/0515-Add-PlayerDeepSleepEvent.patch deleted file mode 100644 index c348ba15bc5b..000000000000 --- a/patches/server/0515-Add-PlayerDeepSleepEvent.patch +++ /dev/null @@ -1,24 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Jake Potrebic -Date: Wed, 21 Apr 2021 15:58:19 -0700 -Subject: [PATCH] Add PlayerDeepSleepEvent - - -diff --git a/src/main/java/net/minecraft/world/entity/player/Player.java b/src/main/java/net/minecraft/world/entity/player/Player.java -index 513e6505706e64f9410fa190014976dc469793af..b444b24a92bd2209ee4104ae82c7cfa9c876c910 100644 ---- a/src/main/java/net/minecraft/world/entity/player/Player.java -+++ b/src/main/java/net/minecraft/world/entity/player/Player.java -@@ -262,6 +262,13 @@ public abstract class Player extends LivingEntity { - - if (this.isSleeping()) { - ++this.sleepCounter; -+ // Paper start - Add PlayerDeepSleepEvent -+ if (this.sleepCounter == SLEEP_DURATION) { -+ if (!new io.papermc.paper.event.player.PlayerDeepSleepEvent((org.bukkit.entity.Player) getBukkitEntity()).callEvent()) { -+ this.sleepCounter = Integer.MIN_VALUE; -+ } -+ } -+ // Paper end - Add PlayerDeepSleepEvent - if (this.sleepCounter > 100) { - this.sleepCounter = 100; - } diff --git a/patches/server/0515-Expand-PlayerRespawnEvent-fix-passed-parameter-issue.patch b/patches/server/0515-Expand-PlayerRespawnEvent-fix-passed-parameter-issue.patch new file mode 100644 index 000000000000..8ba5f2f2a0f6 --- /dev/null +++ b/patches/server/0515-Expand-PlayerRespawnEvent-fix-passed-parameter-issue.patch @@ -0,0 +1,26 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: HexedHero <6012891+HexedHero@users.noreply.github.com> +Date: Fri, 23 Apr 2021 22:42:42 +0100 +Subject: [PATCH] Expand PlayerRespawnEvent, fix passed parameter issues + +Co-authored-by: Jake Potrebic + +diff --git a/src/main/java/net/minecraft/server/level/ServerPlayer.java b/src/main/java/net/minecraft/server/level/ServerPlayer.java +index 3a2a4f6bc1de45c0dc2020357ee308064666f8d5..262aa389b58708ed35ee88569be804c524f6e635 100644 +--- a/src/main/java/net/minecraft/server/level/ServerPlayer.java ++++ b/src/main/java/net/minecraft/server/level/ServerPlayer.java +@@ -1456,7 +1456,13 @@ public class ServerPlayer extends net.minecraft.world.entity.player.Player { + Player respawnPlayer = this.getBukkitEntity(); + Location location = CraftLocation.toBukkit(teleportTransition.position(), teleportTransition.newLevel().getWorld(), teleportTransition.yRot(), teleportTransition.xRot()); + +- PlayerRespawnEvent respawnEvent = new PlayerRespawnEvent(respawnPlayer, location, isBedSpawn, isAnchorSpawn, reason); ++ // Paper start - respawn flags ++ com.google.common.collect.ImmutableSet.Builder builder = com.google.common.collect.ImmutableSet.builder(); ++ if (reason == org.bukkit.event.player.PlayerRespawnEvent.RespawnReason.END_PORTAL) { ++ builder.add(org.bukkit.event.player.PlayerRespawnEvent.RespawnFlag.END_PORTAL); ++ } ++ PlayerRespawnEvent respawnEvent = new PlayerRespawnEvent(respawnPlayer, location, isBedSpawn, isAnchorSpawn, reason, builder); ++ // Paper end - respawn flags + this.level().getCraftServer().getPluginManager().callEvent(respawnEvent); + // Spigot Start + if (this.connection.isDisconnected()) { diff --git a/patches/server/0516-Introduce-beacon-activation-deactivation-events.patch b/patches/server/0516-Introduce-beacon-activation-deactivation-events.patch new file mode 100644 index 000000000000..336acbe811d8 --- /dev/null +++ b/patches/server/0516-Introduce-beacon-activation-deactivation-events.patch @@ -0,0 +1,37 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Spyridon Pagkalos +Date: Thu, 25 Mar 2021 20:28:04 +0200 +Subject: [PATCH] Introduce beacon activation/deactivation events + + +diff --git a/src/main/java/net/minecraft/world/level/block/entity/BeaconBlockEntity.java b/src/main/java/net/minecraft/world/level/block/entity/BeaconBlockEntity.java +index dc3171b1493d7c4c8ddf1c79587c4e27bd819c17..8aab6f68f576fb022eb59798585e264f5aafbc69 100644 +--- a/src/main/java/net/minecraft/world/level/block/entity/BeaconBlockEntity.java ++++ b/src/main/java/net/minecraft/world/level/block/entity/BeaconBlockEntity.java +@@ -225,6 +225,15 @@ public class BeaconBlockEntity extends BlockEntity implements MenuProvider, Name + BeaconBlockEntity.playSound(world, pos, SoundEvents.BEACON_AMBIENT); + } + } ++ // Paper start - beacon activation/deactivation events ++ if (i1 <= 0 && blockEntity.levels > 0) { ++ org.bukkit.block.Block block = org.bukkit.craftbukkit.block.CraftBlock.at(world, pos); ++ new io.papermc.paper.event.block.BeaconActivatedEvent(block).callEvent(); ++ } else if (i1 > 0 && blockEntity.levels <= 0) { ++ org.bukkit.block.Block block = org.bukkit.craftbukkit.block.CraftBlock.at(world, pos); ++ new io.papermc.paper.event.block.BeaconDeactivatedEvent(block).callEvent(); ++ } ++ // Paper end - beacon activation/deactivation events + + if (blockEntity.lastCheckY >= l) { + blockEntity.lastCheckY = world.getMinY() - 1; +@@ -282,6 +291,10 @@ public class BeaconBlockEntity extends BlockEntity implements MenuProvider, Name + + @Override + public void setRemoved() { ++ // Paper start - beacon activation/deactivation events ++ org.bukkit.block.Block block = org.bukkit.craftbukkit.block.CraftBlock.at(level, worldPosition); ++ new io.papermc.paper.event.block.BeaconDeactivatedEvent(block).callEvent(); ++ // Paper end - beacon activation/deactivation events + BeaconBlockEntity.playSound(this.level, this.worldPosition, SoundEvents.BEACON_DEACTIVATE); + super.setRemoved(); + } diff --git a/patches/server/0516-More-World-API.patch b/patches/server/0516-More-World-API.patch deleted file mode 100644 index fde155962872..000000000000 --- a/patches/server/0516-More-World-API.patch +++ /dev/null @@ -1,57 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Jake Potrebic -Date: Tue, 7 Jul 2020 10:52:34 -0700 -Subject: [PATCH] More World API - - -diff --git a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java -index f698ac90bd3086519f49e92451228415efaf5530..e882bc363f02b3acac489589e78b38468dfcaaa4 100644 ---- a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java -+++ b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java -@@ -2132,6 +2132,28 @@ public class CraftWorld extends CraftRegionAccessor implements World { - return new CraftStructureSearchResult(CraftStructure.minecraftToBukkit(found.getSecond().value()), CraftLocation.toBukkit(found.getFirst(), this)); - } - -+ // Paper start -+ @Override -+ public double getCoordinateScale() { -+ return getHandle().dimensionType().coordinateScale(); -+ } -+ -+ @Override -+ public boolean isFixedTime() { -+ return getHandle().dimensionType().hasFixedTime(); -+ } -+ -+ @Override -+ public Collection getInfiniburn() { -+ return com.google.common.collect.Sets.newHashSet(com.google.common.collect.Iterators.transform(net.minecraft.core.registries.BuiltInRegistries.BLOCK.getTagOrEmpty(this.getHandle().dimensionType().infiniburn()).iterator(), blockHolder -> CraftBlockType.minecraftToBukkit(blockHolder.value()))); -+ } -+ -+ @Override -+ public void sendGameEvent(Entity sourceEntity, org.bukkit.GameEvent gameEvent, Vector position) { -+ getHandle().gameEvent(sourceEntity != null ? ((CraftEntity) sourceEntity).getHandle(): null, net.minecraft.core.registries.BuiltInRegistries.GAME_EVENT.getHolder(org.bukkit.craftbukkit.util.CraftNamespacedKey.toMinecraft(gameEvent.getKey())).orElseThrow(), org.bukkit.craftbukkit.util.CraftVector.toBlockPos(position)); -+ } -+ // Paper end -+ - @Override - public BiomeSearchResult locateNearestBiome(Location origin, int radius, Biome... biomes) { - return this.locateNearestBiome(origin, radius, 32, 64, biomes); -diff --git a/src/main/java/org/bukkit/craftbukkit/util/CraftVector.java b/src/main/java/org/bukkit/craftbukkit/util/CraftVector.java -index 3071ac1ac0e733d73dade49597a39f7d156bbc04..967445b2eb158454100a27369a1f463d69f54f27 100644 ---- a/src/main/java/org/bukkit/craftbukkit/util/CraftVector.java -+++ b/src/main/java/org/bukkit/craftbukkit/util/CraftVector.java -@@ -12,4 +12,13 @@ public final class CraftVector { - public static net.minecraft.world.phys.Vec3 toNMS(org.bukkit.util.Vector bukkit) { - return new net.minecraft.world.phys.Vec3(bukkit.getX(), bukkit.getY(), bukkit.getZ()); - } -+ // Paper start -+ public static org.bukkit.util.Vector toBukkit(net.minecraft.core.BlockPos blockPosition) { -+ return new org.bukkit.util.Vector(blockPosition.getX(), blockPosition.getY(), blockPosition.getZ()); -+ } -+ -+ public static net.minecraft.core.BlockPos toBlockPos(org.bukkit.util.Vector bukkit) { -+ return net.minecraft.core.BlockPos.containing(bukkit.getX(), bukkit.getY(), bukkit.getZ()); -+ } -+ // Paper end - } diff --git a/patches/server/0521-Add-Channel-initialization-listeners.patch b/patches/server/0517-Add-Channel-initialization-listeners.patch similarity index 100% rename from patches/server/0521-Add-Channel-initialization-listeners.patch rename to patches/server/0517-Add-Channel-initialization-listeners.patch diff --git a/patches/server/0517-Add-PlayerBedFailEnterEvent.patch b/patches/server/0517-Add-PlayerBedFailEnterEvent.patch deleted file mode 100644 index feffef2aecf7..000000000000 --- a/patches/server/0517-Add-PlayerBedFailEnterEvent.patch +++ /dev/null @@ -1,36 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Jake Potrebic -Date: Thu, 24 Dec 2020 12:27:41 -0800 -Subject: [PATCH] Add PlayerBedFailEnterEvent - - -diff --git a/src/main/java/net/minecraft/world/level/block/BedBlock.java b/src/main/java/net/minecraft/world/level/block/BedBlock.java -index 4eb1a77e2a85a242dd5342a500763d87dbe7655e..18b9a62613c08eb5bf63ae26565b0e91e1f44d39 100644 ---- a/src/main/java/net/minecraft/world/level/block/BedBlock.java -+++ b/src/main/java/net/minecraft/world/level/block/BedBlock.java -@@ -118,14 +118,23 @@ public class BedBlock extends HorizontalDirectionalBlock implements EntityBlock - BlockPos finalblockposition = pos; - // CraftBukkit end - player.startSleepInBed(pos).ifLeft((entityhuman_enumbedresult) -> { -+ // Paper start - PlayerBedFailEnterEvent -+ if (entityhuman_enumbedresult != null) { -+ io.papermc.paper.event.player.PlayerBedFailEnterEvent event = new io.papermc.paper.event.player.PlayerBedFailEnterEvent((org.bukkit.entity.Player) player.getBukkitEntity(), io.papermc.paper.event.player.PlayerBedFailEnterEvent.FailReason.values()[entityhuman_enumbedresult.ordinal()], org.bukkit.craftbukkit.block.CraftBlock.at(world, finalblockposition), !world.dimensionType().bedWorks(), io.papermc.paper.adventure.PaperAdventure.asAdventure(entityhuman_enumbedresult.getMessage())); -+ if (!event.callEvent()) { -+ return; -+ } -+ // Paper end - PlayerBedFailEnterEvent - // CraftBukkit start - handling bed explosion from below here -- if (!world.dimensionType().bedWorks()) { -+ if (event.getWillExplode()) { // Paper - PlayerBedFailEnterEvent - this.explodeBed(finaliblockdata, world, finalblockposition); - } else - // CraftBukkit end - if (entityhuman_enumbedresult.getMessage() != null) { -- player.displayClientMessage(entityhuman_enumbedresult.getMessage(), true); -+ final net.kyori.adventure.text.Component message = event.getMessage(); // Paper - PlayerBedFailEnterEvent -+ if (message != null) player.displayClientMessage(io.papermc.paper.adventure.PaperAdventure.asVanilla(message), true); // Paper - PlayerBedFailEnterEvent - } -+ } // Paper - PlayerBedFailEnterEvent - - }); - return InteractionResult.SUCCESS; diff --git a/patches/server/0518-Implement-methods-to-convert-between-Component-and-B.patch b/patches/server/0518-Implement-methods-to-convert-between-Component-and-B.patch deleted file mode 100644 index 614386ecdece..000000000000 --- a/patches/server/0518-Implement-methods-to-convert-between-Component-and-B.patch +++ /dev/null @@ -1,55 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Jason Penilla <11360596+jpenilla@users.noreply.github.com> -Date: Sat, 24 Apr 2021 02:09:32 -0700 -Subject: [PATCH] Implement methods to convert between Component and - Brigadier's Message - - -diff --git a/src/main/java/io/papermc/paper/brigadier/PaperBrigadierProviderImpl.java b/src/main/java/io/papermc/paper/brigadier/PaperBrigadierProviderImpl.java -new file mode 100644 -index 0000000000000000000000000000000000000000..dd6012b6a097575b2d1471be5069eccee4537c0a ---- /dev/null -+++ b/src/main/java/io/papermc/paper/brigadier/PaperBrigadierProviderImpl.java -@@ -0,0 +1,30 @@ -+package io.papermc.paper.brigadier; -+ -+import com.mojang.brigadier.Message; -+import io.papermc.paper.adventure.PaperAdventure; -+import net.kyori.adventure.text.Component; -+import net.kyori.adventure.text.ComponentLike; -+import net.minecraft.network.chat.ComponentUtils; -+import org.checkerframework.checker.nullness.qual.NonNull; -+ -+import static java.util.Objects.requireNonNull; -+ -+public enum PaperBrigadierProviderImpl implements PaperBrigadierProvider { -+ INSTANCE; -+ -+ PaperBrigadierProviderImpl() { -+ PaperBrigadierProvider.initialize(this); -+ } -+ -+ @Override -+ public @NonNull Message message(final @NonNull ComponentLike componentLike) { -+ requireNonNull(componentLike, "componentLike"); -+ return PaperAdventure.asVanilla(componentLike.asComponent()); -+ } -+ -+ @Override -+ public @NonNull Component componentFromMessage(final @NonNull Message message) { -+ requireNonNull(message, "message"); -+ return PaperAdventure.asAdventure(ComponentUtils.fromMessage(message)); -+ } -+} -diff --git a/src/main/java/net/minecraft/server/dedicated/DedicatedServer.java b/src/main/java/net/minecraft/server/dedicated/DedicatedServer.java -index 764395fe8e49d811294ca82887fee91ca6cd01fc..4057ade698a227b4f6efd3aa30b16d78c777be83 100644 ---- a/src/main/java/net/minecraft/server/dedicated/DedicatedServer.java -+++ b/src/main/java/net/minecraft/server/dedicated/DedicatedServer.java -@@ -227,6 +227,7 @@ public class DedicatedServer extends MinecraftServer implements ServerInterface - io.papermc.paper.command.PaperCommands.registerCommands(this); // Paper - setup /paper command - com.destroystokyo.paper.Metrics.PaperMetrics.startMetrics(); // Paper - start metrics - com.destroystokyo.paper.VersionHistoryManager.INSTANCE.getClass(); // Paper - load version history now -+ io.papermc.paper.brigadier.PaperBrigadierProviderImpl.INSTANCE.getClass(); // Paper - init PaperBrigadierProvider - - this.setPvpAllowed(dedicatedserverproperties.pvp); - this.setFlightAllowed(dedicatedserverproperties.allowFlight); diff --git a/patches/server/0518-Send-empty-commands-if-tab-completion-is-disabled.patch b/patches/server/0518-Send-empty-commands-if-tab-completion-is-disabled.patch new file mode 100644 index 000000000000..58cea72f9bcf --- /dev/null +++ b/patches/server/0518-Send-empty-commands-if-tab-completion-is-disabled.patch @@ -0,0 +1,24 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Shane Freeder +Date: Mon, 26 Apr 2021 01:27:08 +0100 +Subject: [PATCH] Send empty commands if tab completion is disabled + + +diff --git a/src/main/java/net/minecraft/commands/Commands.java b/src/main/java/net/minecraft/commands/Commands.java +index e38f1d972d87f33a5f28459aa988e09f24614453..7acd7f60327106d55e8f48247650bc0064dd1b58 100644 +--- a/src/main/java/net/minecraft/commands/Commands.java ++++ b/src/main/java/net/minecraft/commands/Commands.java +@@ -453,7 +453,12 @@ public class Commands { + } + + public void sendCommands(ServerPlayer player) { +- if ( org.spigotmc.SpigotConfig.tabComplete < 0 ) return; // Spigot ++ // Paper start - Send empty commands if tab completion is disabled ++ if (org.spigotmc.SpigotConfig.tabComplete < 0) { ++ player.connection.send(new ClientboundCommandsPacket(new RootCommandNode<>())); ++ return; ++ } ++ // Paper end - Send empty commands if tab completion is disabled + // CraftBukkit start + // Register Vanilla commands into builtRoot as before + // Paper start - Perf: Async command map building diff --git a/patches/server/0519-Add-more-WanderingTrader-API.patch b/patches/server/0519-Add-more-WanderingTrader-API.patch new file mode 100644 index 000000000000..4221f2801547 --- /dev/null +++ b/patches/server/0519-Add-more-WanderingTrader-API.patch @@ -0,0 +1,65 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: HexedHero <6012891+HexedHero@users.noreply.github.com> +Date: Thu, 6 May 2021 14:56:43 +0100 +Subject: [PATCH] Add more WanderingTrader API + + +diff --git a/src/main/java/net/minecraft/world/entity/npc/WanderingTrader.java b/src/main/java/net/minecraft/world/entity/npc/WanderingTrader.java +index 8034588a9a87b907c35e28e220280d463f34554e..a65fba5621c067c453858efb7fee64cbee1e7916 100644 +--- a/src/main/java/net/minecraft/world/entity/npc/WanderingTrader.java ++++ b/src/main/java/net/minecraft/world/entity/npc/WanderingTrader.java +@@ -62,6 +62,10 @@ public class WanderingTrader extends net.minecraft.world.entity.npc.AbstractVill + @Nullable + private BlockPos wanderTarget; + private int despawnDelay; ++ // Paper start - Add more WanderingTrader API ++ public boolean canDrinkPotion = true; ++ public boolean canDrinkMilk = true; ++ // Paper end - Add more WanderingTrader API + + public WanderingTrader(EntityType type, Level world) { + super(type, world); +@@ -72,10 +76,10 @@ public class WanderingTrader extends net.minecraft.world.entity.npc.AbstractVill + protected void registerGoals() { + this.goalSelector.addGoal(0, new FloatGoal(this)); + this.goalSelector.addGoal(0, new UseItemGoal<>(this, PotionContents.createItemStack(Items.POTION, Potions.INVISIBILITY), SoundEvents.WANDERING_TRADER_DISAPPEARED, (entityvillagertrader) -> { +- return this.level().isNight() && !entityvillagertrader.isInvisible(); ++ return this.canDrinkPotion && this.level().isNight() && !entityvillagertrader.isInvisible(); // Paper - Add more WanderingTrader API + })); + this.goalSelector.addGoal(0, new UseItemGoal<>(this, new ItemStack(Items.MILK_BUCKET), SoundEvents.WANDERING_TRADER_REAPPEARED, (entityvillagertrader) -> { +- return this.level().isDay() && entityvillagertrader.isInvisible(); ++ return this.canDrinkMilk && this.level().isDay() && entityvillagertrader.isInvisible(); // Paper - Add more WanderingTrader API + })); + this.goalSelector.addGoal(1, new TradeWithPlayerGoal(this)); + this.goalSelector.addGoal(1, new AvoidEntityGoal<>(this, Zombie.class, 8.0F, 0.5D, 0.5D)); +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftWanderingTrader.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftWanderingTrader.java +index 08194a78c2170e971ee8ff440b276ed3590e8c4a..0e597394a3dd08f022614fc9777302fea581eb55 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftWanderingTrader.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftWanderingTrader.java +@@ -28,4 +28,26 @@ public class CraftWanderingTrader extends CraftAbstractVillager implements Wande + public void setDespawnDelay(int despawnDelay) { + this.getHandle().setDespawnDelay(despawnDelay); + } ++ ++ // Paper start - Add more WanderingTrader API ++ @Override ++ public void setCanDrinkPotion(boolean bool) { ++ getHandle().canDrinkPotion = bool; ++ } ++ ++ @Override ++ public boolean canDrinkPotion() { ++ return getHandle().canDrinkPotion; ++ } ++ ++ @Override ++ public void setCanDrinkMilk(boolean bool) { ++ getHandle().canDrinkMilk = bool; ++ } ++ ++ @Override ++ public boolean canDrinkMilk() { ++ return getHandle().canDrinkMilk; ++ } ++ // Paper end + } diff --git a/patches/server/0519-Expand-PlayerRespawnEvent-fix-passed-parameter-issue.patch b/patches/server/0519-Expand-PlayerRespawnEvent-fix-passed-parameter-issue.patch deleted file mode 100644 index 7d836d530743..000000000000 --- a/patches/server/0519-Expand-PlayerRespawnEvent-fix-passed-parameter-issue.patch +++ /dev/null @@ -1,26 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: HexedHero <6012891+HexedHero@users.noreply.github.com> -Date: Fri, 23 Apr 2021 22:42:42 +0100 -Subject: [PATCH] Expand PlayerRespawnEvent, fix passed parameter issues - -Co-authored-by: Jake Potrebic - -diff --git a/src/main/java/net/minecraft/server/level/ServerPlayer.java b/src/main/java/net/minecraft/server/level/ServerPlayer.java -index fffd3ae6c4fc0b86057cd915bb4f0987f41a64d0..52a5274fb811870481a48bf0505769d7ac5df31f 100644 ---- a/src/main/java/net/minecraft/server/level/ServerPlayer.java -+++ b/src/main/java/net/minecraft/server/level/ServerPlayer.java -@@ -1195,7 +1195,13 @@ public class ServerPlayer extends net.minecraft.world.entity.player.Player { - Player respawnPlayer = this.getBukkitEntity(); - Location location = CraftLocation.toBukkit(dimensionTransition.pos(), dimensionTransition.newLevel().getWorld(), dimensionTransition.yRot(), dimensionTransition.xRot()); - -- PlayerRespawnEvent respawnEvent = new PlayerRespawnEvent(respawnPlayer, location, isBedSpawn, isAnchorSpawn, reason); -+ // Paper start - respawn flags -+ com.google.common.collect.ImmutableSet.Builder builder = com.google.common.collect.ImmutableSet.builder(); -+ if (reason == org.bukkit.event.player.PlayerRespawnEvent.RespawnReason.END_PORTAL) { -+ builder.add(org.bukkit.event.player.PlayerRespawnEvent.RespawnFlag.END_PORTAL); -+ } -+ PlayerRespawnEvent respawnEvent = new PlayerRespawnEvent(respawnPlayer, location, isBedSpawn, isAnchorSpawn, reason, builder); -+ // Paper end - respawn flags - this.level().getCraftServer().getPluginManager().callEvent(respawnEvent); - // Spigot Start - if (this.connection.isDisconnected()) { diff --git a/patches/server/0520-Add-EntityBlockStorage-clearEntities.patch b/patches/server/0520-Add-EntityBlockStorage-clearEntities.patch new file mode 100644 index 000000000000..c37749bd5663 --- /dev/null +++ b/patches/server/0520-Add-EntityBlockStorage-clearEntities.patch @@ -0,0 +1,38 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Owen1212055 <23108066+Owen1212055@users.noreply.github.com> +Date: Mon, 5 Apr 2021 18:12:29 -0400 +Subject: [PATCH] Add EntityBlockStorage#clearEntities() + + +diff --git a/src/main/java/net/minecraft/world/level/block/entity/BeehiveBlockEntity.java b/src/main/java/net/minecraft/world/level/block/entity/BeehiveBlockEntity.java +index 0fab8826e14af9184f07bc1262555a71effcd84b..8224d5bddb7f7893dee09222a673af54791abcfa 100644 +--- a/src/main/java/net/minecraft/world/level/block/entity/BeehiveBlockEntity.java ++++ b/src/main/java/net/minecraft/world/level/block/entity/BeehiveBlockEntity.java +@@ -152,6 +152,11 @@ public class BeehiveBlockEntity extends BlockEntity { + return this.stored.size(); + } + ++ // Paper start - Add EntityBlockStorage clearEntities ++ public void clearBees() { ++ this.stored.clear(); ++ } ++ // Paper end - Add EntityBlockStorage clearEntities + public static int getHoneyLevel(BlockState state) { + return (Integer) state.getValue(BeehiveBlock.HONEY_LEVEL); + } +diff --git a/src/main/java/org/bukkit/craftbukkit/block/CraftBeehive.java b/src/main/java/org/bukkit/craftbukkit/block/CraftBeehive.java +index fbacbef0dd51037fa0af56fedeea50a5c7bed8c7..1a2a05160ba51d9c75f1ae6ae61d944d81428722 100644 +--- a/src/main/java/org/bukkit/craftbukkit/block/CraftBeehive.java ++++ b/src/main/java/org/bukkit/craftbukkit/block/CraftBeehive.java +@@ -95,4 +95,11 @@ public class CraftBeehive extends CraftBlockEntityState impl + public CraftBeehive copy(Location location) { + return new CraftBeehive(this, location); + } ++ ++ // Paper start - Add EntityBlockStorage clearEntities ++ @Override ++ public void clearEntities() { ++ getSnapshot().clearBees(); ++ } ++ // Paper end + } diff --git a/patches/server/0520-Introduce-beacon-activation-deactivation-events.patch b/patches/server/0520-Introduce-beacon-activation-deactivation-events.patch deleted file mode 100644 index 6d74a805c199..000000000000 --- a/patches/server/0520-Introduce-beacon-activation-deactivation-events.patch +++ /dev/null @@ -1,37 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Spyridon Pagkalos -Date: Thu, 25 Mar 2021 20:28:04 +0200 -Subject: [PATCH] Introduce beacon activation/deactivation events - - -diff --git a/src/main/java/net/minecraft/world/level/block/entity/BeaconBlockEntity.java b/src/main/java/net/minecraft/world/level/block/entity/BeaconBlockEntity.java -index 52776ce9b7b4edd1eb474f14705d7fd83f5a66ca..e124f040386e130aebd7135434c4f06d130d28f6 100644 ---- a/src/main/java/net/minecraft/world/level/block/entity/BeaconBlockEntity.java -+++ b/src/main/java/net/minecraft/world/level/block/entity/BeaconBlockEntity.java -@@ -225,6 +225,15 @@ public class BeaconBlockEntity extends BlockEntity implements MenuProvider, Name - BeaconBlockEntity.playSound(world, pos, SoundEvents.BEACON_AMBIENT); - } - } -+ // Paper start - beacon activation/deactivation events -+ if (i1 <= 0 && blockEntity.levels > 0) { -+ org.bukkit.block.Block block = org.bukkit.craftbukkit.block.CraftBlock.at(world, pos); -+ new io.papermc.paper.event.block.BeaconActivatedEvent(block).callEvent(); -+ } else if (i1 > 0 && blockEntity.levels <= 0) { -+ org.bukkit.block.Block block = org.bukkit.craftbukkit.block.CraftBlock.at(world, pos); -+ new io.papermc.paper.event.block.BeaconDeactivatedEvent(block).callEvent(); -+ } -+ // Paper end - beacon activation/deactivation events - - if (blockEntity.lastCheckY >= l) { - blockEntity.lastCheckY = world.getMinBuildHeight() - 1; -@@ -282,6 +291,10 @@ public class BeaconBlockEntity extends BlockEntity implements MenuProvider, Name - - @Override - public void setRemoved() { -+ // Paper start - beacon activation/deactivation events -+ org.bukkit.block.Block block = org.bukkit.craftbukkit.block.CraftBlock.at(level, worldPosition); -+ new io.papermc.paper.event.block.BeaconDeactivatedEvent(block).callEvent(); -+ // Paper end - beacon activation/deactivation events - BeaconBlockEntity.playSound(this.level, this.worldPosition, SoundEvents.BEACON_DEACTIVATE); - super.setRemoved(); - } diff --git a/patches/server/0521-Add-Adventure-message-to-PlayerAdvancementDoneEvent.patch b/patches/server/0521-Add-Adventure-message-to-PlayerAdvancementDoneEvent.patch new file mode 100644 index 000000000000..9df7276feb98 --- /dev/null +++ b/patches/server/0521-Add-Adventure-message-to-PlayerAdvancementDoneEvent.patch @@ -0,0 +1,35 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Alvinn8 <42838560+Alvinn8@users.noreply.github.com> +Date: Fri, 8 Jan 2021 20:31:13 +0100 +Subject: [PATCH] Add Adventure message to PlayerAdvancementDoneEvent + + +diff --git a/src/main/java/net/minecraft/server/PlayerAdvancements.java b/src/main/java/net/minecraft/server/PlayerAdvancements.java +index 0fe941b0802f2966ad55509baac124f56ecef999..1dcb8a287be7df2a59b5b4c1345be80637a7f679 100644 +--- a/src/main/java/net/minecraft/server/PlayerAdvancements.java ++++ b/src/main/java/net/minecraft/server/PlayerAdvancements.java +@@ -236,11 +236,21 @@ public class PlayerAdvancements { + this.progressChanged.add(advancement); + flag = true; + if (!flag1 && advancementprogress.isDone()) { +- this.player.level().getCraftServer().getPluginManager().callEvent(new org.bukkit.event.player.PlayerAdvancementDoneEvent(this.player.getBukkitEntity(), advancement.toBukkit())); // CraftBukkit ++ // Paper start - Add Adventure message to PlayerAdvancementDoneEvent ++ final net.kyori.adventure.text.Component message = advancement.value().display().flatMap(info -> { ++ return java.util.Optional.ofNullable( ++ info.shouldAnnounceChat() ? io.papermc.paper.adventure.PaperAdventure.asAdventure(info.getType().createAnnouncement(advancement, this.player)) : null ++ ); ++ }).orElse(null); ++ final org.bukkit.event.player.PlayerAdvancementDoneEvent event = new org.bukkit.event.player.PlayerAdvancementDoneEvent(this.player.getBukkitEntity(), advancement.toBukkit(), message); ++ this.player.level().getCraftServer().getPluginManager().callEvent(event); // CraftBukkit ++ // Paper end + advancement.value().rewards().grant(this.player); + advancement.value().display().ifPresent((advancementdisplay) -> { +- if (advancementdisplay.shouldAnnounceChat() && this.player.serverLevel().getGameRules().getBoolean(GameRules.RULE_ANNOUNCE_ADVANCEMENTS)) { +- this.playerList.broadcastSystemMessage(advancementdisplay.getType().createAnnouncement(advancement, this.player), false); ++ // Paper start - Add Adventure message to PlayerAdvancementDoneEvent ++ if (event.message() != null && this.player.serverLevel().getGameRules().getBoolean(GameRules.RULE_ANNOUNCE_ADVANCEMENTS)) { ++ this.playerList.broadcastSystemMessage(io.papermc.paper.adventure.PaperAdventure.asVanilla(event.message()), false); ++ // Paper end + } + + }); diff --git a/patches/server/0526-Add-HiddenPotionEffect-API.patch b/patches/server/0522-Add-HiddenPotionEffect-API.patch similarity index 100% rename from patches/server/0526-Add-HiddenPotionEffect-API.patch rename to patches/server/0522-Add-HiddenPotionEffect-API.patch diff --git a/patches/server/0522-Send-empty-commands-if-tab-completion-is-disabled.patch b/patches/server/0522-Send-empty-commands-if-tab-completion-is-disabled.patch deleted file mode 100644 index daa33fbefc81..000000000000 --- a/patches/server/0522-Send-empty-commands-if-tab-completion-is-disabled.patch +++ /dev/null @@ -1,24 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Shane Freeder -Date: Mon, 26 Apr 2021 01:27:08 +0100 -Subject: [PATCH] Send empty commands if tab completion is disabled - - -diff --git a/src/main/java/net/minecraft/commands/Commands.java b/src/main/java/net/minecraft/commands/Commands.java -index f52e9732524b62b0fecdc48e099163f31fe367b4..c5c9fb28fe858e2900e43f8aafccddf63f09676e 100644 ---- a/src/main/java/net/minecraft/commands/Commands.java -+++ b/src/main/java/net/minecraft/commands/Commands.java -@@ -448,7 +448,12 @@ public class Commands { - } - - public void sendCommands(ServerPlayer player) { -- if ( org.spigotmc.SpigotConfig.tabComplete < 0 ) return; // Spigot -+ // Paper start - Send empty commands if tab completion is disabled -+ if (org.spigotmc.SpigotConfig.tabComplete < 0) { -+ player.connection.send(new ClientboundCommandsPacket(new RootCommandNode<>())); -+ return; -+ } -+ // Paper end - Send empty commands if tab completion is disabled - // CraftBukkit start - // Register Vanilla commands into builtRoot as before - // Paper start - Perf: Async command map building diff --git a/patches/server/0523-Add-more-WanderingTrader-API.patch b/patches/server/0523-Add-more-WanderingTrader-API.patch deleted file mode 100644 index 0ce65786dc8d..000000000000 --- a/patches/server/0523-Add-more-WanderingTrader-API.patch +++ /dev/null @@ -1,65 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: HexedHero <6012891+HexedHero@users.noreply.github.com> -Date: Thu, 6 May 2021 14:56:43 +0100 -Subject: [PATCH] Add more WanderingTrader API - - -diff --git a/src/main/java/net/minecraft/world/entity/npc/WanderingTrader.java b/src/main/java/net/minecraft/world/entity/npc/WanderingTrader.java -index e51cb9c96e1bd13c00bf938436f4fc26d80055a1..856a93324f5ac411713851ccfb38dba52fb0af5e 100644 ---- a/src/main/java/net/minecraft/world/entity/npc/WanderingTrader.java -+++ b/src/main/java/net/minecraft/world/entity/npc/WanderingTrader.java -@@ -61,6 +61,10 @@ public class WanderingTrader extends net.minecraft.world.entity.npc.AbstractVill - @Nullable - private BlockPos wanderTarget; - private int despawnDelay; -+ // Paper start - Add more WanderingTrader API -+ public boolean canDrinkPotion = true; -+ public boolean canDrinkMilk = true; -+ // Paper end - Add more WanderingTrader API - - public WanderingTrader(EntityType type, Level world) { - super(type, world); -@@ -71,10 +75,10 @@ public class WanderingTrader extends net.minecraft.world.entity.npc.AbstractVill - protected void registerGoals() { - this.goalSelector.addGoal(0, new FloatGoal(this)); - this.goalSelector.addGoal(0, new UseItemGoal<>(this, PotionContents.createItemStack(Items.POTION, Potions.INVISIBILITY), SoundEvents.WANDERING_TRADER_DISAPPEARED, (entityvillagertrader) -> { -- return this.level().isNight() && !entityvillagertrader.isInvisible(); -+ return this.canDrinkPotion && this.level().isNight() && !entityvillagertrader.isInvisible(); // Paper - Add more WanderingTrader API - })); - this.goalSelector.addGoal(0, new UseItemGoal<>(this, new ItemStack(Items.MILK_BUCKET), SoundEvents.WANDERING_TRADER_REAPPEARED, (entityvillagertrader) -> { -- return this.level().isDay() && entityvillagertrader.isInvisible(); -+ return this.canDrinkMilk && this.level().isDay() && entityvillagertrader.isInvisible(); // Paper - Add more WanderingTrader API - })); - this.goalSelector.addGoal(1, new TradeWithPlayerGoal(this)); - this.goalSelector.addGoal(1, new AvoidEntityGoal<>(this, Zombie.class, 8.0F, 0.5D, 0.5D)); -diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftWanderingTrader.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftWanderingTrader.java -index 08194a78c2170e971ee8ff440b276ed3590e8c4a..0e597394a3dd08f022614fc9777302fea581eb55 100644 ---- a/src/main/java/org/bukkit/craftbukkit/entity/CraftWanderingTrader.java -+++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftWanderingTrader.java -@@ -28,4 +28,26 @@ public class CraftWanderingTrader extends CraftAbstractVillager implements Wande - public void setDespawnDelay(int despawnDelay) { - this.getHandle().setDespawnDelay(despawnDelay); - } -+ -+ // Paper start - Add more WanderingTrader API -+ @Override -+ public void setCanDrinkPotion(boolean bool) { -+ getHandle().canDrinkPotion = bool; -+ } -+ -+ @Override -+ public boolean canDrinkPotion() { -+ return getHandle().canDrinkPotion; -+ } -+ -+ @Override -+ public void setCanDrinkMilk(boolean bool) { -+ getHandle().canDrinkMilk = bool; -+ } -+ -+ @Override -+ public boolean canDrinkMilk() { -+ return getHandle().canDrinkMilk; -+ } -+ // Paper end - } diff --git a/patches/server/0527-Inventory-close.patch b/patches/server/0523-Inventory-close.patch similarity index 100% rename from patches/server/0527-Inventory-close.patch rename to patches/server/0523-Inventory-close.patch diff --git a/patches/server/0524-Add-EntityBlockStorage-clearEntities.patch b/patches/server/0524-Add-EntityBlockStorage-clearEntities.patch deleted file mode 100644 index b19812d9899f..000000000000 --- a/patches/server/0524-Add-EntityBlockStorage-clearEntities.patch +++ /dev/null @@ -1,38 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Owen1212055 <23108066+Owen1212055@users.noreply.github.com> -Date: Mon, 5 Apr 2021 18:12:29 -0400 -Subject: [PATCH] Add EntityBlockStorage#clearEntities() - - -diff --git a/src/main/java/net/minecraft/world/level/block/entity/BeehiveBlockEntity.java b/src/main/java/net/minecraft/world/level/block/entity/BeehiveBlockEntity.java -index b345403cdf5b2828f99708fef65136594a3331c3..ef8a0236ab4fb648c4bb2a8cfc90e3cefe8f9f1d 100644 ---- a/src/main/java/net/minecraft/world/level/block/entity/BeehiveBlockEntity.java -+++ b/src/main/java/net/minecraft/world/level/block/entity/BeehiveBlockEntity.java -@@ -151,6 +151,11 @@ public class BeehiveBlockEntity extends BlockEntity { - return this.stored.size(); - } - -+ // Paper start - Add EntityBlockStorage clearEntities -+ public void clearBees() { -+ this.stored.clear(); -+ } -+ // Paper end - Add EntityBlockStorage clearEntities - public static int getHoneyLevel(BlockState state) { - return (Integer) state.getValue(BeehiveBlock.HONEY_LEVEL); - } -diff --git a/src/main/java/org/bukkit/craftbukkit/block/CraftBeehive.java b/src/main/java/org/bukkit/craftbukkit/block/CraftBeehive.java -index fbacbef0dd51037fa0af56fedeea50a5c7bed8c7..1a2a05160ba51d9c75f1ae6ae61d944d81428722 100644 ---- a/src/main/java/org/bukkit/craftbukkit/block/CraftBeehive.java -+++ b/src/main/java/org/bukkit/craftbukkit/block/CraftBeehive.java -@@ -95,4 +95,11 @@ public class CraftBeehive extends CraftBlockEntityState impl - public CraftBeehive copy(Location location) { - return new CraftBeehive(this, location); - } -+ -+ // Paper start - Add EntityBlockStorage clearEntities -+ @Override -+ public void clearEntities() { -+ getSnapshot().clearBees(); -+ } -+ // Paper end - } diff --git a/patches/server/0524-Add-a-should-burn-in-sunlight-API-for-Phantoms-and-S.patch b/patches/server/0524-Add-a-should-burn-in-sunlight-API-for-Phantoms-and-S.patch new file mode 100644 index 000000000000..ca5b9a4b8ad0 --- /dev/null +++ b/patches/server/0524-Add-a-should-burn-in-sunlight-API-for-Phantoms-and-S.patch @@ -0,0 +1,130 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: MeFisto94 +Date: Tue, 11 May 2021 00:48:33 +0200 +Subject: [PATCH] Add a "should burn in sunlight" API for Phantoms and + Skeletons + + +diff --git a/src/main/java/net/minecraft/world/entity/monster/AbstractSkeleton.java b/src/main/java/net/minecraft/world/entity/monster/AbstractSkeleton.java +index 723a098eabcc632caeb096f39c90e4e0581edc35..0cb179bc28cc863b09a079b37b957744f26f3e1d 100644 +--- a/src/main/java/net/minecraft/world/entity/monster/AbstractSkeleton.java ++++ b/src/main/java/net/minecraft/world/entity/monster/AbstractSkeleton.java +@@ -95,9 +95,15 @@ public abstract class AbstractSkeleton extends Monster implements RangedAttackMo + + abstract SoundEvent getStepSound(); + ++ // Paper start - shouldBurnInDay API ++ private boolean shouldBurnInDay = true; ++ public boolean shouldBurnInDay() { return shouldBurnInDay; } ++ public void setShouldBurnInDay(boolean shouldBurnInDay) { this.shouldBurnInDay = shouldBurnInDay; } ++ // Paper end - shouldBurnInDay API ++ + @Override + public void aiStep() { +- boolean flag = this.isSunBurnTick(); ++ boolean flag = shouldBurnInDay && this.isSunBurnTick(); // Paper - shouldBurnInDay API + + if (flag) { + ItemStack itemstack = this.getItemBySlot(EquipmentSlot.HEAD); +@@ -236,7 +242,20 @@ public abstract class AbstractSkeleton extends Monster implements RangedAttackMo + public void readAdditionalSaveData(CompoundTag nbt) { + super.readAdditionalSaveData(nbt); + this.reassessWeaponGoal(); ++ // Paper start - shouldBurnInDay API ++ if (nbt.contains("Paper.ShouldBurnInDay")) { ++ this.shouldBurnInDay = nbt.getBoolean("Paper.ShouldBurnInDay"); ++ } ++ // Paper end - shouldBurnInDay API ++ } ++ ++ // Paper start - shouldBurnInDay API ++ @Override ++ public void addAdditionalSaveData(CompoundTag nbt) { ++ super.addAdditionalSaveData(nbt); ++ nbt.putBoolean("Paper.ShouldBurnInDay", this.shouldBurnInDay); + } ++ // Paper end - shouldBurnInDay API + + @Override + public void setItemSlot(EquipmentSlot slot, ItemStack stack) { +diff --git a/src/main/java/net/minecraft/world/entity/monster/Phantom.java b/src/main/java/net/minecraft/world/entity/monster/Phantom.java +index d0820b3a561db7e1e5667594b2b6ea13214dfa58..150fd890ac65097b5434fd88e8d2b24a89dca79a 100644 +--- a/src/main/java/net/minecraft/world/entity/monster/Phantom.java ++++ b/src/main/java/net/minecraft/world/entity/monster/Phantom.java +@@ -139,7 +139,7 @@ public class Phantom extends FlyingMob implements Enemy { + + @Override + public void aiStep() { +- if (this.isAlive() && this.isSunBurnTick()) { ++ if (this.isAlive() && this.shouldBurnInDay && this.isSunBurnTick()) { // Paper - shouldBurnInDay API + this.igniteForSeconds(8.0F); + } + +@@ -165,6 +165,9 @@ public class Phantom extends FlyingMob implements Enemy { + if (nbt.hasUUID("Paper.SpawningEntity")) { + this.spawningEntity = nbt.getUUID("Paper.SpawningEntity"); + } ++ if (nbt.contains("Paper.ShouldBurnInDay")) { ++ this.shouldBurnInDay = nbt.getBoolean("Paper.ShouldBurnInDay"); ++ } + // Paper end + } + +@@ -179,6 +182,7 @@ public class Phantom extends FlyingMob implements Enemy { + if (this.spawningEntity != null) { + nbt.putUUID("Paper.SpawningEntity", this.spawningEntity); + } ++ nbt.putBoolean("Paper.ShouldBurnInDay", shouldBurnInDay); + // Paper end + } + +@@ -238,6 +242,9 @@ public class Phantom extends FlyingMob implements Enemy { + return this.spawningEntity; + } + public void setSpawningEntity(java.util.UUID entity) { this.spawningEntity = entity; } ++ private boolean shouldBurnInDay = true; ++ public boolean shouldBurnInDay() { return shouldBurnInDay; } ++ public void setShouldBurnInDay(boolean shouldBurnInDay) { this.shouldBurnInDay = shouldBurnInDay; } + // Paper end + + private static enum AttackPhase { +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftAbstractSkeleton.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftAbstractSkeleton.java +index 5ff566186431440c25a26900aba14e4adb642031..5beaa2bb0d58fe477ce8d2de8b77600d3b416d8c 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftAbstractSkeleton.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftAbstractSkeleton.java +@@ -20,4 +20,15 @@ public abstract class CraftAbstractSkeleton extends CraftMonster implements Abst + return (net.minecraft.world.entity.monster.AbstractSkeleton) super.getHandle(); + } + // Paper end ++ // Paper start ++ @Override ++ public boolean shouldBurnInDay() { ++ return getHandle().shouldBurnInDay(); ++ } ++ ++ @Override ++ public void setShouldBurnInDay(boolean shouldBurnInDay) { ++ getHandle().setShouldBurnInDay(shouldBurnInDay); ++ } ++ // Paper end + } +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftPhantom.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftPhantom.java +index 305a635b049741ac5e2670060c6818cb2c07e5ab..9304e201db1ec96d0916aa8ea781f3e4bc7991e6 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftPhantom.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftPhantom.java +@@ -34,5 +34,15 @@ public class CraftPhantom extends CraftFlying implements Phantom, CraftEnemy { + public java.util.UUID getSpawningEntity() { + return getHandle().getSpawningEntity(); + } ++ ++ @Override ++ public boolean shouldBurnInDay() { ++ return getHandle().shouldBurnInDay(); ++ } ++ ++ @Override ++ public void setShouldBurnInDay(boolean shouldBurnInDay) { ++ getHandle().setShouldBurnInDay(shouldBurnInDay); ++ } + // Paper end + } diff --git a/patches/server/0525-Add-Adventure-message-to-PlayerAdvancementDoneEvent.patch b/patches/server/0525-Add-Adventure-message-to-PlayerAdvancementDoneEvent.patch deleted file mode 100644 index c0fd5f11dbf4..000000000000 --- a/patches/server/0525-Add-Adventure-message-to-PlayerAdvancementDoneEvent.patch +++ /dev/null @@ -1,35 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Alvinn8 <42838560+Alvinn8@users.noreply.github.com> -Date: Fri, 8 Jan 2021 20:31:13 +0100 -Subject: [PATCH] Add Adventure message to PlayerAdvancementDoneEvent - - -diff --git a/src/main/java/net/minecraft/server/PlayerAdvancements.java b/src/main/java/net/minecraft/server/PlayerAdvancements.java -index d85fb1e2ea0eaef81e9039b47d18f83507e05a59..9fabf9322acd663c4452b562494e74aa42eb19da 100644 ---- a/src/main/java/net/minecraft/server/PlayerAdvancements.java -+++ b/src/main/java/net/minecraft/server/PlayerAdvancements.java -@@ -236,11 +236,21 @@ public class PlayerAdvancements { - this.progressChanged.add(advancement); - flag = true; - if (!flag1 && advancementprogress.isDone()) { -- this.player.level().getCraftServer().getPluginManager().callEvent(new org.bukkit.event.player.PlayerAdvancementDoneEvent(this.player.getBukkitEntity(), advancement.toBukkit())); // CraftBukkit -+ // Paper start - Add Adventure message to PlayerAdvancementDoneEvent -+ final net.kyori.adventure.text.Component message = advancement.value().display().flatMap(info -> { -+ return java.util.Optional.ofNullable( -+ info.shouldAnnounceChat() ? io.papermc.paper.adventure.PaperAdventure.asAdventure(info.getType().createAnnouncement(advancement, this.player)) : null -+ ); -+ }).orElse(null); -+ final org.bukkit.event.player.PlayerAdvancementDoneEvent event = new org.bukkit.event.player.PlayerAdvancementDoneEvent(this.player.getBukkitEntity(), advancement.toBukkit(), message); -+ this.player.level().getCraftServer().getPluginManager().callEvent(event); // CraftBukkit -+ // Paper end - advancement.value().rewards().grant(this.player); - advancement.value().display().ifPresent((advancementdisplay) -> { -- if (advancementdisplay.shouldAnnounceChat() && this.player.level().getGameRules().getBoolean(GameRules.RULE_ANNOUNCE_ADVANCEMENTS)) { -- this.playerList.broadcastSystemMessage(advancementdisplay.getType().createAnnouncement(advancement, this.player), false); -+ // Paper start - Add Adventure message to PlayerAdvancementDoneEvent -+ if (event.message() != null && this.player.level().getGameRules().getBoolean(GameRules.RULE_ANNOUNCE_ADVANCEMENTS)) { -+ this.playerList.broadcastSystemMessage(io.papermc.paper.adventure.PaperAdventure.asVanilla(event.message()), false); -+ // Paper end - } - - }); diff --git a/patches/server/0525-Add-basic-Datapack-API.patch b/patches/server/0525-Add-basic-Datapack-API.patch new file mode 100644 index 000000000000..4c4f291ca5f7 --- /dev/null +++ b/patches/server/0525-Add-basic-Datapack-API.patch @@ -0,0 +1,209 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Connor Linfoot +Date: Sun, 16 May 2021 15:07:34 +0100 +Subject: [PATCH] Add basic Datapack API + +Co-authored-by: Jake Potrebic + +diff --git a/src/main/java/io/papermc/paper/datapack/PaperDatapack.java b/src/main/java/io/papermc/paper/datapack/PaperDatapack.java +new file mode 100644 +index 0000000000000000000000000000000000000000..8bd8263b51fb2bb364353565b1ba26b3b0d1d55e +--- /dev/null ++++ b/src/main/java/io/papermc/paper/datapack/PaperDatapack.java +@@ -0,0 +1,103 @@ ++package io.papermc.paper.datapack; ++ ++import io.papermc.paper.adventure.PaperAdventure; ++import io.papermc.paper.event.server.ServerResourcesReloadedEvent; ++import io.papermc.paper.world.flag.PaperFeatureFlagProviderImpl; ++import java.util.ArrayList; ++import java.util.List; ++import java.util.Map; ++import java.util.Set; ++import java.util.concurrent.ConcurrentHashMap; ++import net.kyori.adventure.text.Component; ++import net.minecraft.server.MinecraftServer; ++import net.minecraft.server.packs.repository.Pack; ++import net.minecraft.server.packs.repository.PackSource; ++import org.bukkit.FeatureFlag; ++import org.checkerframework.checker.nullness.qual.NonNull; ++import org.checkerframework.checker.nullness.qual.Nullable; ++import org.checkerframework.framework.qual.DefaultQualifier; ++ ++@DefaultQualifier(NonNull.class) ++public class PaperDatapack implements Datapack { ++ ++ private static final Map PACK_SOURCES = new ConcurrentHashMap<>(); ++ static { ++ PACK_SOURCES.put(PackSource.DEFAULT, DatapackSource.DEFAULT); ++ PACK_SOURCES.put(PackSource.BUILT_IN, DatapackSource.BUILT_IN); ++ PACK_SOURCES.put(PackSource.FEATURE, DatapackSource.FEATURE); ++ PACK_SOURCES.put(PackSource.WORLD, DatapackSource.WORLD); ++ PACK_SOURCES.put(PackSource.SERVER, DatapackSource.SERVER); ++ } ++ ++ private final Pack pack; ++ private final boolean enabled; ++ ++ PaperDatapack(final Pack pack, final boolean enabled) { ++ this.pack = pack; ++ this.enabled = enabled; ++ } ++ ++ @Override ++ public String getName() { ++ return this.pack.getId(); ++ } ++ ++ @Override ++ public Component getTitle() { ++ return PaperAdventure.asAdventure(this.pack.getTitle()); ++ } ++ ++ @Override ++ public Component getDescription() { ++ return PaperAdventure.asAdventure(this.pack.getDescription()); ++ } ++ ++ @Override ++ public boolean isRequired() { ++ return this.pack.isRequired(); ++ } ++ ++ @Override ++ public Compatibility getCompatibility() { ++ return Datapack.Compatibility.valueOf(this.pack.getCompatibility().name()); ++ } ++ ++ @Override ++ public Set getRequiredFeatures() { ++ return PaperFeatureFlagProviderImpl.fromNms(this.pack.getRequestedFeatures()); ++ } ++ ++ @Override ++ public boolean isEnabled() { ++ return this.enabled; ++ } ++ ++ @Override ++ public void setEnabled(final boolean enabled) { ++ final MinecraftServer server = MinecraftServer.getServer(); ++ final List enabledPacks = new ArrayList<>(server.getPackRepository().getSelectedPacks()); ++ final @Nullable Pack packToChange = server.getPackRepository().getPack(this.getName()); ++ if (packToChange == null) { ++ throw new IllegalStateException("Cannot toggle state of pack that doesn't exist: " + this.getName()); ++ } ++ if (enabled == enabledPacks.contains(packToChange)) { ++ return; ++ } ++ if (enabled) { ++ packToChange.getDefaultPosition().insert(enabledPacks, packToChange, Pack::selectionConfig, false); // modeled off the default /datapack enable logic ++ } else { ++ enabledPacks.remove(packToChange); ++ } ++ server.reloadResources(enabledPacks.stream().map(Pack::getId).toList(), ServerResourcesReloadedEvent.Cause.PLUGIN); ++ } ++ ++ @Override ++ public DatapackSource getSource() { ++ return PACK_SOURCES.computeIfAbsent(this.pack.location().source(), source -> new DatapackSourceImpl(source.toString())); ++ } ++ ++ @Override ++ public Component computeDisplayName() { ++ return PaperAdventure.asAdventure(this.pack.getChatLink(this.enabled)); ++ } ++} +diff --git a/src/main/java/io/papermc/paper/datapack/PaperDatapackManager.java b/src/main/java/io/papermc/paper/datapack/PaperDatapackManager.java +new file mode 100644 +index 0000000000000000000000000000000000000000..caa41c525d2b36b5a9f9942380f06c97d5781a93 +--- /dev/null ++++ b/src/main/java/io/papermc/paper/datapack/PaperDatapackManager.java +@@ -0,0 +1,55 @@ ++package io.papermc.paper.datapack; ++ ++import com.google.common.collect.Collections2; ++import java.util.Collection; ++import java.util.Collections; ++import java.util.function.Predicate; ++import net.minecraft.server.packs.repository.Pack; ++import net.minecraft.server.packs.repository.PackRepository; ++import org.checkerframework.checker.nullness.qual.NonNull; ++import org.checkerframework.checker.nullness.qual.Nullable; ++import org.checkerframework.framework.qual.DefaultQualifier; ++ ++@DefaultQualifier(NonNull.class) ++public class PaperDatapackManager implements DatapackManager { ++ ++ private final PackRepository repository; ++ ++ public PaperDatapackManager(final PackRepository repository) { ++ this.repository = repository; ++ } ++ ++ @Override ++ public void refreshPacks() { ++ this.repository.reload(); ++ } ++ ++ @Override ++ public @Nullable Datapack getPack(final @NonNull String name) { ++ final @Nullable Pack pack = this.repository.getPack(name); ++ if (pack == null) { ++ return null; ++ } ++ return new PaperDatapack(pack, this.repository.getSelectedPacks().contains(pack)); ++ } ++ ++ @Override ++ public Collection getPacks() { ++ final Collection enabledPacks = this.repository.getSelectedPacks(); ++ return this.transformPacks(this.repository.getAvailablePacks(), enabledPacks::contains); ++ } ++ ++ @Override ++ public Collection getEnabledPacks() { ++ return this.transformPacks(this.repository.getSelectedPacks(), pack -> true); ++ } ++ ++ private Collection transformPacks(final Collection packs, final Predicate enabled) { ++ return Collections.unmodifiableCollection( ++ Collections2.transform( ++ packs, ++ pack -> new PaperDatapack(pack, enabled.test(pack)) ++ ) ++ ); ++ } ++} +diff --git a/src/main/java/org/bukkit/craftbukkit/CraftServer.java b/src/main/java/org/bukkit/craftbukkit/CraftServer.java +index 85b2298efface87ee97e0f996d939cc135ca981d..a17b26dadbbbf6e6c84f80f6fe8293d87ca19d0a 100644 +--- a/src/main/java/org/bukkit/craftbukkit/CraftServer.java ++++ b/src/main/java/org/bukkit/craftbukkit/CraftServer.java +@@ -308,6 +308,7 @@ public final class CraftServer implements Server { + private final List playerView; + public int reloadCount; + public Set activeCompatibilities = Collections.emptySet(); ++ private final io.papermc.paper.datapack.PaperDatapackManager datapackManager; // Paper + public static Exception excessiveVelEx; // Paper - Velocity warnings + + static { +@@ -392,6 +393,7 @@ public final class CraftServer implements Server { + if (this.configuration.getBoolean("settings.use-map-color-cache")) { + MapPalette.setMapColorCache(new CraftMapColorCache(this.logger)); + } ++ datapackManager = new io.papermc.paper.datapack.PaperDatapackManager(console.getPackRepository()); // Paper + } + + public boolean getCommandBlockOverride(String command) { +@@ -3026,5 +3028,11 @@ public final class CraftServer implements Server { + public com.destroystokyo.paper.entity.ai.MobGoals getMobGoals() { + return mobGoals; + } ++ ++ @Override ++ public io.papermc.paper.datapack.PaperDatapackManager getDatapackManager() { ++ return datapackManager; ++ } ++ + // Paper end + } diff --git a/patches/server/0526-Add-environment-variable-to-disable-server-gui.patch b/patches/server/0526-Add-environment-variable-to-disable-server-gui.patch new file mode 100644 index 000000000000..5afe5fb870c6 --- /dev/null +++ b/patches/server/0526-Add-environment-variable-to-disable-server-gui.patch @@ -0,0 +1,18 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Riley Park +Date: Mon, 17 May 2021 00:34:55 -0700 +Subject: [PATCH] Add environment variable to disable server gui + + +diff --git a/src/main/java/net/minecraft/server/Main.java b/src/main/java/net/minecraft/server/Main.java +index 59d623dd45c395552ffd99e6fa744c0f71d94998..cf071610ed662c4a309cc26ee73a74fa490d846f 100644 +--- a/src/main/java/net/minecraft/server/Main.java ++++ b/src/main/java/net/minecraft/server/Main.java +@@ -318,6 +318,7 @@ public class Main { + */ + boolean flag2 = !optionset.has("nogui") && !optionset.nonOptionArguments().contains("nogui"); + ++ if(!Boolean.parseBoolean(System.getenv().getOrDefault("PAPER_DISABLE_SERVER_GUI", String.valueOf(false)))) // Paper - Add environment variable to disable server gui + if (flag2 && !GraphicsEnvironment.isHeadless()) { + dedicatedserver1.showGui(); + } diff --git a/patches/server/0527-Expand-PlayerGameModeChangeEvent.patch b/patches/server/0527-Expand-PlayerGameModeChangeEvent.patch new file mode 100644 index 000000000000..26fc7358be4c --- /dev/null +++ b/patches/server/0527-Expand-PlayerGameModeChangeEvent.patch @@ -0,0 +1,161 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Jake Potrebic +Date: Sat, 15 May 2021 10:04:43 -0700 +Subject: [PATCH] Expand PlayerGameModeChangeEvent + + +diff --git a/src/main/java/net/minecraft/server/commands/DefaultGameModeCommands.java b/src/main/java/net/minecraft/server/commands/DefaultGameModeCommands.java +index b95a979f8f58f43e0becedcd843ff8bb992eb455..a046a0b1519806ff3d987e6402f152b60a3a6f4c 100644 +--- a/src/main/java/net/minecraft/server/commands/DefaultGameModeCommands.java ++++ b/src/main/java/net/minecraft/server/commands/DefaultGameModeCommands.java +@@ -28,9 +28,13 @@ public class DefaultGameModeCommands { + GameType gameType = minecraftServer.getForcedGameType(); + if (gameType != null) { + for (ServerPlayer serverPlayer : minecraftServer.getPlayerList().getPlayers()) { +- if (serverPlayer.setGameMode(gameType)) { +- i++; ++ // Paper start - Expand PlayerGameModeChangeEvent ++ org.bukkit.event.player.PlayerGameModeChangeEvent event = serverPlayer.setGameMode(gameType, org.bukkit.event.player.PlayerGameModeChangeEvent.Cause.DEFAULT_GAMEMODE, net.kyori.adventure.text.Component.empty()); ++ if (event != null && event.isCancelled()) { ++ source.sendSuccess(() -> io.papermc.paper.adventure.PaperAdventure.asVanilla(event.cancelMessage()), false); + } ++ // Paper end - Expand PlayerGameModeChangeEvent ++ i++; + } + } + +diff --git a/src/main/java/net/minecraft/server/commands/GameModeCommand.java b/src/main/java/net/minecraft/server/commands/GameModeCommand.java +index 7f09119bc7d661e08a960dd2bd46006efe752d3e..d1da3600dc07107309b20ebe6e7c0c4da0e8de76 100644 +--- a/src/main/java/net/minecraft/server/commands/GameModeCommand.java ++++ b/src/main/java/net/minecraft/server/commands/GameModeCommand.java +@@ -60,9 +60,14 @@ public class GameModeCommand { + int i = 0; + + for (ServerPlayer serverPlayer : targets) { +- if (serverPlayer.setGameMode(gameMode)) { ++ // Paper start - Expand PlayerGameModeChangeEvent ++ org.bukkit.event.player.PlayerGameModeChangeEvent event = serverPlayer.setGameMode(gameMode, org.bukkit.event.player.PlayerGameModeChangeEvent.Cause.COMMAND, net.kyori.adventure.text.Component.empty()); ++ if (event != null && !event.isCancelled()) { + logGamemodeChange(context.getSource(), serverPlayer, gameMode); + i++; ++ } else if (event != null && event.cancelMessage() != null) { ++ context.getSource().sendSuccess(() -> io.papermc.paper.adventure.PaperAdventure.asVanilla(event.cancelMessage()), true); ++ // Paper end - Expand PlayerGameModeChangeEvent + } + } + +diff --git a/src/main/java/net/minecraft/server/level/ServerPlayer.java b/src/main/java/net/minecraft/server/level/ServerPlayer.java +index 262aa389b58708ed35ee88569be804c524f6e635..a022f0aee638bb12967b5fd20847fe20e6b286f3 100644 +--- a/src/main/java/net/minecraft/server/level/ServerPlayer.java ++++ b/src/main/java/net/minecraft/server/level/ServerPlayer.java +@@ -2335,10 +2335,18 @@ public class ServerPlayer extends net.minecraft.world.entity.player.Player { + } + + public boolean setGameMode(GameType gameMode) { ++ // Paper start - Expand PlayerGameModeChangeEvent ++ org.bukkit.event.player.PlayerGameModeChangeEvent event = this.setGameMode(gameMode, org.bukkit.event.player.PlayerGameModeChangeEvent.Cause.UNKNOWN, null); ++ return event == null ? false : event.isCancelled(); ++ } ++ @Nullable ++ public org.bukkit.event.player.PlayerGameModeChangeEvent setGameMode(GameType gameMode, org.bukkit.event.player.PlayerGameModeChangeEvent.Cause cause, @Nullable net.kyori.adventure.text.Component message) { + boolean flag = this.isSpectator(); + +- if (!this.gameMode.changeGameModeForPlayer(gameMode)) { +- return false; ++ org.bukkit.event.player.PlayerGameModeChangeEvent event = this.gameMode.changeGameModeForPlayer(gameMode, cause, message); ++ if (event == null || event.isCancelled()) { ++ return null; ++ // Paper end - Expand PlayerGameModeChangeEvent + } else { + this.connection.send(new ClientboundGameEventPacket(ClientboundGameEventPacket.CHANGE_GAME_MODE, (float) gameMode.getId())); + if (gameMode == GameType.SPECTATOR) { +@@ -2354,7 +2362,7 @@ public class ServerPlayer extends net.minecraft.world.entity.player.Player { + + this.onUpdateAbilities(); + this.updateEffectVisibility(); +- return true; ++ return event; // Paper - Expand PlayerGameModeChangeEvent + } + } + +@@ -2806,6 +2814,16 @@ public class ServerPlayer extends net.minecraft.world.entity.player.Player { + } + + public void loadGameTypes(@Nullable CompoundTag nbt) { ++ // Paper start - Expand PlayerGameModeChangeEvent ++ if (this.server.getForcedGameType() != null && this.server.getForcedGameType() != ServerPlayer.readPlayerMode(nbt, "playerGameType")) { ++ if (new org.bukkit.event.player.PlayerGameModeChangeEvent(this.getBukkitEntity(), org.bukkit.GameMode.getByValue(this.server.getDefaultGameType().getId()), org.bukkit.event.player.PlayerGameModeChangeEvent.Cause.DEFAULT_GAMEMODE, null).callEvent()) { ++ this.gameMode.setGameModeForPlayer(this.server.getForcedGameType(), GameType.DEFAULT_MODE); ++ } else { ++ this.gameMode.setGameModeForPlayer(ServerPlayer.readPlayerMode(nbt,"playerGameType"), ServerPlayer.readPlayerMode(nbt, "previousPlayerGameType")); ++ } ++ return; ++ } ++ // Paper end - Expand PlayerGameModeChangeEvent + this.gameMode.setGameModeForPlayer(this.calculateGameModeForNewPlayer(ServerPlayer.readPlayerMode(nbt, "playerGameType")), ServerPlayer.readPlayerMode(nbt, "previousPlayerGameType")); + } + +diff --git a/src/main/java/net/minecraft/server/level/ServerPlayerGameMode.java b/src/main/java/net/minecraft/server/level/ServerPlayerGameMode.java +index 546be40a8e4470fb5a6686072cdd342cdaa6fe15..e000a918230187f6841b03b7b0dd73687f3cc15e 100644 +--- a/src/main/java/net/minecraft/server/level/ServerPlayerGameMode.java ++++ b/src/main/java/net/minecraft/server/level/ServerPlayerGameMode.java +@@ -72,14 +72,21 @@ public class ServerPlayerGameMode { + } + + public boolean changeGameModeForPlayer(GameType gameMode) { ++ // Paper start - Expand PlayerGameModeChangeEvent ++ PlayerGameModeChangeEvent event = this.changeGameModeForPlayer(gameMode, org.bukkit.event.player.PlayerGameModeChangeEvent.Cause.UNKNOWN, null); ++ return event != null && event.isCancelled(); ++ } ++ @Nullable ++ public PlayerGameModeChangeEvent changeGameModeForPlayer(GameType gameMode, org.bukkit.event.player.PlayerGameModeChangeEvent.Cause cause, @Nullable net.kyori.adventure.text.Component cancelMessage) { ++ // Paper end - Expand PlayerGameModeChangeEvent + if (gameMode == this.gameModeForPlayer) { +- return false; ++ return null; // Paper - Expand PlayerGameModeChangeEvent + } else { + // CraftBukkit start +- PlayerGameModeChangeEvent event = new PlayerGameModeChangeEvent(this.player.getBukkitEntity(), GameMode.getByValue(gameMode.getId())); ++ PlayerGameModeChangeEvent event = new PlayerGameModeChangeEvent(this.player.getBukkitEntity(), GameMode.getByValue(gameMode.getId()), cause, cancelMessage); // Paper + this.level.getCraftServer().getPluginManager().callEvent(event); + if (event.isCancelled()) { +- return false; ++ return event; // Paper - Expand PlayerGameModeChangeEvent + } + // CraftBukkit end + this.setGameModeForPlayer(gameMode, this.previousGameModeForPlayer); +@@ -90,7 +97,7 @@ public class ServerPlayerGameMode { + this.player.resetCurrentImpulseContext(); + } + +- return true; ++ return event; // Paper - Expand PlayerGameModeChangeEvent + } + } + +diff --git a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java +index 2bd2f1cdf3467cacee55094d43bd3eccf61b9aa7..c3b4b800c5a34afa03ed7c31e14f26ca16109982 100644 +--- a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java ++++ b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java +@@ -2751,7 +2751,7 @@ public class ServerGamePacketListenerImpl extends ServerCommonPacketListenerImpl + this.player = this.server.getPlayerList().respawn(this.player, false, Entity.RemovalReason.KILLED, RespawnReason.DEATH); // CraftBukkit + this.resetPosition(); + if (this.server.isHardcore()) { +- this.player.setGameMode(GameType.SPECTATOR); ++ this.player.setGameMode(GameType.SPECTATOR, org.bukkit.event.player.PlayerGameModeChangeEvent.Cause.HARDCORE_DEATH, null); // Paper - Expand PlayerGameModeChangeEvent + ((GameRules.BooleanValue) this.player.serverLevel().getGameRules().getRule(GameRules.RULE_SPECTATORSGENERATECHUNKS)).set(false, this.player.serverLevel()); // CraftBukkit - per-world + } + } +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java +index 711697d039da58c1b93392f70a70e1ad827b1646..fc246d30808ad6242f4adfe201adc169a2ddeb90 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java +@@ -1681,7 +1681,7 @@ public class CraftPlayer extends CraftHumanEntity implements Player { + Preconditions.checkArgument(mode != null, "GameMode cannot be null"); + if (this.getHandle().connection == null) return; + +- this.getHandle().setGameMode(GameType.byId(mode.getValue())); ++ this.getHandle().setGameMode(GameType.byId(mode.getValue()), org.bukkit.event.player.PlayerGameModeChangeEvent.Cause.PLUGIN, null); // Paper - Expand PlayerGameModeChangeEvent + } + + @Override diff --git a/patches/server/0528-Add-a-should-burn-in-sunlight-API-for-Phantoms-and-S.patch b/patches/server/0528-Add-a-should-burn-in-sunlight-API-for-Phantoms-and-S.patch deleted file mode 100644 index d5f2f9db1f88..000000000000 --- a/patches/server/0528-Add-a-should-burn-in-sunlight-API-for-Phantoms-and-S.patch +++ /dev/null @@ -1,130 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: MeFisto94 -Date: Tue, 11 May 2021 00:48:33 +0200 -Subject: [PATCH] Add a "should burn in sunlight" API for Phantoms and - Skeletons - - -diff --git a/src/main/java/net/minecraft/world/entity/monster/AbstractSkeleton.java b/src/main/java/net/minecraft/world/entity/monster/AbstractSkeleton.java -index a5593ac33878efc970c1bdd6f636438d160e9d50..6627126ab02dbd5e9d1de6b186d75d850ef11280 100644 ---- a/src/main/java/net/minecraft/world/entity/monster/AbstractSkeleton.java -+++ b/src/main/java/net/minecraft/world/entity/monster/AbstractSkeleton.java -@@ -93,9 +93,15 @@ public abstract class AbstractSkeleton extends Monster implements RangedAttackMo - - abstract SoundEvent getStepSound(); - -+ // Paper start - shouldBurnInDay API -+ private boolean shouldBurnInDay = true; -+ public boolean shouldBurnInDay() { return shouldBurnInDay; } -+ public void setShouldBurnInDay(boolean shouldBurnInDay) { this.shouldBurnInDay = shouldBurnInDay; } -+ // Paper end - shouldBurnInDay API -+ - @Override - public void aiStep() { -- boolean flag = this.isSunBurnTick(); -+ boolean flag = shouldBurnInDay && this.isSunBurnTick(); // Paper - shouldBurnInDay API - - if (flag) { - ItemStack itemstack = this.getItemBySlot(EquipmentSlot.HEAD); -@@ -232,7 +238,20 @@ public abstract class AbstractSkeleton extends Monster implements RangedAttackMo - public void readAdditionalSaveData(CompoundTag nbt) { - super.readAdditionalSaveData(nbt); - this.reassessWeaponGoal(); -+ // Paper start - shouldBurnInDay API -+ if (nbt.contains("Paper.ShouldBurnInDay")) { -+ this.shouldBurnInDay = nbt.getBoolean("Paper.ShouldBurnInDay"); -+ } -+ // Paper end - shouldBurnInDay API -+ } -+ -+ // Paper start - shouldBurnInDay API -+ @Override -+ public void addAdditionalSaveData(CompoundTag nbt) { -+ super.addAdditionalSaveData(nbt); -+ nbt.putBoolean("Paper.ShouldBurnInDay", this.shouldBurnInDay); - } -+ // Paper end - shouldBurnInDay API - - @Override - public void setItemSlot(EquipmentSlot slot, ItemStack stack) { -diff --git a/src/main/java/net/minecraft/world/entity/monster/Phantom.java b/src/main/java/net/minecraft/world/entity/monster/Phantom.java -index 4b3bec32921feb1dcf71abf5e8d34fcbbc59baf5..c277dac448a64809e93dd7a447ee3dc2a86c860e 100644 ---- a/src/main/java/net/minecraft/world/entity/monster/Phantom.java -+++ b/src/main/java/net/minecraft/world/entity/monster/Phantom.java -@@ -138,7 +138,7 @@ public class Phantom extends FlyingMob implements Enemy { - - @Override - public void aiStep() { -- if (this.isAlive() && this.isSunBurnTick()) { -+ if (this.isAlive() && this.shouldBurnInDay && this.isSunBurnTick()) { // Paper - shouldBurnInDay API - this.igniteForSeconds(8.0F); - } - -@@ -169,6 +169,9 @@ public class Phantom extends FlyingMob implements Enemy { - if (nbt.hasUUID("Paper.SpawningEntity")) { - this.spawningEntity = nbt.getUUID("Paper.SpawningEntity"); - } -+ if (nbt.contains("Paper.ShouldBurnInDay")) { -+ this.shouldBurnInDay = nbt.getBoolean("Paper.ShouldBurnInDay"); -+ } - // Paper end - } - -@@ -183,6 +186,7 @@ public class Phantom extends FlyingMob implements Enemy { - if (this.spawningEntity != null) { - nbt.putUUID("Paper.SpawningEntity", this.spawningEntity); - } -+ nbt.putBoolean("Paper.ShouldBurnInDay", shouldBurnInDay); - // Paper end - } - -@@ -238,6 +242,9 @@ public class Phantom extends FlyingMob implements Enemy { - return this.spawningEntity; - } - public void setSpawningEntity(java.util.UUID entity) { this.spawningEntity = entity; } -+ private boolean shouldBurnInDay = true; -+ public boolean shouldBurnInDay() { return shouldBurnInDay; } -+ public void setShouldBurnInDay(boolean shouldBurnInDay) { this.shouldBurnInDay = shouldBurnInDay; } - // Paper end - - private static enum AttackPhase { -diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftAbstractSkeleton.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftAbstractSkeleton.java -index 5ff566186431440c25a26900aba14e4adb642031..5beaa2bb0d58fe477ce8d2de8b77600d3b416d8c 100644 ---- a/src/main/java/org/bukkit/craftbukkit/entity/CraftAbstractSkeleton.java -+++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftAbstractSkeleton.java -@@ -20,4 +20,15 @@ public abstract class CraftAbstractSkeleton extends CraftMonster implements Abst - return (net.minecraft.world.entity.monster.AbstractSkeleton) super.getHandle(); - } - // Paper end -+ // Paper start -+ @Override -+ public boolean shouldBurnInDay() { -+ return getHandle().shouldBurnInDay(); -+ } -+ -+ @Override -+ public void setShouldBurnInDay(boolean shouldBurnInDay) { -+ getHandle().setShouldBurnInDay(shouldBurnInDay); -+ } -+ // Paper end - } -diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftPhantom.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftPhantom.java -index 305a635b049741ac5e2670060c6818cb2c07e5ab..9304e201db1ec96d0916aa8ea781f3e4bc7991e6 100644 ---- a/src/main/java/org/bukkit/craftbukkit/entity/CraftPhantom.java -+++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftPhantom.java -@@ -34,5 +34,15 @@ public class CraftPhantom extends CraftFlying implements Phantom, CraftEnemy { - public java.util.UUID getSpawningEntity() { - return getHandle().getSpawningEntity(); - } -+ -+ @Override -+ public boolean shouldBurnInDay() { -+ return getHandle().shouldBurnInDay(); -+ } -+ -+ @Override -+ public void setShouldBurnInDay(boolean shouldBurnInDay) { -+ getHandle().setShouldBurnInDay(shouldBurnInDay); -+ } - // Paper end - } diff --git a/patches/server/0528-ItemStack-repair-check-API.patch b/patches/server/0528-ItemStack-repair-check-API.patch new file mode 100644 index 000000000000..b016fcc18dd2 --- /dev/null +++ b/patches/server/0528-ItemStack-repair-check-API.patch @@ -0,0 +1,72 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Jake Potrebic +Date: Sat, 15 May 2021 22:11:11 -0700 +Subject: [PATCH] ItemStack repair check API + + +diff --git a/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java b/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java +index ada83b5e843f761837a7510112162c6954ab4260..02306ed81a67faa94d98070d3e7c9044cb5d2825 100644 +--- a/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java ++++ b/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java +@@ -537,6 +537,14 @@ public final class CraftMagicNumbers implements UnsafeValues { + public int getProtocolVersion() { + return net.minecraft.SharedConstants.getCurrentVersion().getProtocolVersion(); + } ++ ++ @Override ++ public boolean isValidRepairItemStack(org.bukkit.inventory.ItemStack itemToBeRepaired, org.bukkit.inventory.ItemStack repairMaterial) { ++ if (!itemToBeRepaired.getType().isItem() || !repairMaterial.getType().isItem()) { ++ return false; ++ } ++ return CraftItemStack.unwrap(itemToBeRepaired).isValidRepairItem(CraftItemStack.unwrap(repairMaterial)); ++ } + // Paper end + + /** +diff --git a/src/test/java/io/papermc/paper/util/ItemStackRepairCheckTest.java b/src/test/java/io/papermc/paper/util/ItemStackRepairCheckTest.java +new file mode 100644 +index 0000000000000000000000000000000000000000..04e2568816f1fbe090b40e5a55d8d4effc045740 +--- /dev/null ++++ b/src/test/java/io/papermc/paper/util/ItemStackRepairCheckTest.java +@@ -0,0 +1,41 @@ ++package io.papermc.paper.util; ++ ++import org.bukkit.Material; ++import org.bukkit.inventory.ItemStack; ++import org.bukkit.support.environment.VanillaFeature; ++import org.junit.jupiter.api.Test; ++ ++import static org.junit.jupiter.api.Assertions.assertFalse; ++import static org.junit.jupiter.api.Assertions.assertTrue; ++ ++@VanillaFeature ++public class ItemStackRepairCheckTest { ++ ++ @Test ++ public void testIsRepariableBy() { ++ ItemStack diamondPick = new ItemStack(Material.DIAMOND_PICKAXE); ++ ++ assertTrue(diamondPick.isRepairableBy(new ItemStack(Material.DIAMOND)), "diamond pick isn't repairable by a diamond"); ++ } ++ ++ @Test ++ public void testCanRepair() { ++ ItemStack diamond = new ItemStack(Material.DIAMOND); ++ ++ assertTrue(diamond.canRepair(new ItemStack(Material.DIAMOND_AXE)), "diamond can't repair a diamond axe"); ++ } ++ ++ @Test ++ public void testIsNotRepairableBy() { ++ ItemStack notDiamondPick = new ItemStack(Material.ACACIA_SAPLING); ++ ++ assertFalse(notDiamondPick.isRepairableBy(new ItemStack(Material.DIAMOND)), "acacia sapling is repairable by a diamond"); ++ } ++ ++ @Test ++ public void testCanNotRepair() { ++ ItemStack diamond = new ItemStack(Material.DIAMOND); ++ ++ assertFalse(diamond.canRepair(new ItemStack(Material.OAK_BUTTON)), "diamond can repair oak button"); ++ } ++} diff --git a/patches/server/0529-Add-basic-Datapack-API.patch b/patches/server/0529-Add-basic-Datapack-API.patch deleted file mode 100644 index f34fd37d42ab..000000000000 --- a/patches/server/0529-Add-basic-Datapack-API.patch +++ /dev/null @@ -1,209 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Connor Linfoot -Date: Sun, 16 May 2021 15:07:34 +0100 -Subject: [PATCH] Add basic Datapack API - -Co-authored-by: Jake Potrebic - -diff --git a/src/main/java/io/papermc/paper/datapack/PaperDatapack.java b/src/main/java/io/papermc/paper/datapack/PaperDatapack.java -new file mode 100644 -index 0000000000000000000000000000000000000000..8bd8263b51fb2bb364353565b1ba26b3b0d1d55e ---- /dev/null -+++ b/src/main/java/io/papermc/paper/datapack/PaperDatapack.java -@@ -0,0 +1,103 @@ -+package io.papermc.paper.datapack; -+ -+import io.papermc.paper.adventure.PaperAdventure; -+import io.papermc.paper.event.server.ServerResourcesReloadedEvent; -+import io.papermc.paper.world.flag.PaperFeatureFlagProviderImpl; -+import java.util.ArrayList; -+import java.util.List; -+import java.util.Map; -+import java.util.Set; -+import java.util.concurrent.ConcurrentHashMap; -+import net.kyori.adventure.text.Component; -+import net.minecraft.server.MinecraftServer; -+import net.minecraft.server.packs.repository.Pack; -+import net.minecraft.server.packs.repository.PackSource; -+import org.bukkit.FeatureFlag; -+import org.checkerframework.checker.nullness.qual.NonNull; -+import org.checkerframework.checker.nullness.qual.Nullable; -+import org.checkerframework.framework.qual.DefaultQualifier; -+ -+@DefaultQualifier(NonNull.class) -+public class PaperDatapack implements Datapack { -+ -+ private static final Map PACK_SOURCES = new ConcurrentHashMap<>(); -+ static { -+ PACK_SOURCES.put(PackSource.DEFAULT, DatapackSource.DEFAULT); -+ PACK_SOURCES.put(PackSource.BUILT_IN, DatapackSource.BUILT_IN); -+ PACK_SOURCES.put(PackSource.FEATURE, DatapackSource.FEATURE); -+ PACK_SOURCES.put(PackSource.WORLD, DatapackSource.WORLD); -+ PACK_SOURCES.put(PackSource.SERVER, DatapackSource.SERVER); -+ } -+ -+ private final Pack pack; -+ private final boolean enabled; -+ -+ PaperDatapack(final Pack pack, final boolean enabled) { -+ this.pack = pack; -+ this.enabled = enabled; -+ } -+ -+ @Override -+ public String getName() { -+ return this.pack.getId(); -+ } -+ -+ @Override -+ public Component getTitle() { -+ return PaperAdventure.asAdventure(this.pack.getTitle()); -+ } -+ -+ @Override -+ public Component getDescription() { -+ return PaperAdventure.asAdventure(this.pack.getDescription()); -+ } -+ -+ @Override -+ public boolean isRequired() { -+ return this.pack.isRequired(); -+ } -+ -+ @Override -+ public Compatibility getCompatibility() { -+ return Datapack.Compatibility.valueOf(this.pack.getCompatibility().name()); -+ } -+ -+ @Override -+ public Set getRequiredFeatures() { -+ return PaperFeatureFlagProviderImpl.fromNms(this.pack.getRequestedFeatures()); -+ } -+ -+ @Override -+ public boolean isEnabled() { -+ return this.enabled; -+ } -+ -+ @Override -+ public void setEnabled(final boolean enabled) { -+ final MinecraftServer server = MinecraftServer.getServer(); -+ final List enabledPacks = new ArrayList<>(server.getPackRepository().getSelectedPacks()); -+ final @Nullable Pack packToChange = server.getPackRepository().getPack(this.getName()); -+ if (packToChange == null) { -+ throw new IllegalStateException("Cannot toggle state of pack that doesn't exist: " + this.getName()); -+ } -+ if (enabled == enabledPacks.contains(packToChange)) { -+ return; -+ } -+ if (enabled) { -+ packToChange.getDefaultPosition().insert(enabledPacks, packToChange, Pack::selectionConfig, false); // modeled off the default /datapack enable logic -+ } else { -+ enabledPacks.remove(packToChange); -+ } -+ server.reloadResources(enabledPacks.stream().map(Pack::getId).toList(), ServerResourcesReloadedEvent.Cause.PLUGIN); -+ } -+ -+ @Override -+ public DatapackSource getSource() { -+ return PACK_SOURCES.computeIfAbsent(this.pack.location().source(), source -> new DatapackSourceImpl(source.toString())); -+ } -+ -+ @Override -+ public Component computeDisplayName() { -+ return PaperAdventure.asAdventure(this.pack.getChatLink(this.enabled)); -+ } -+} -diff --git a/src/main/java/io/papermc/paper/datapack/PaperDatapackManager.java b/src/main/java/io/papermc/paper/datapack/PaperDatapackManager.java -new file mode 100644 -index 0000000000000000000000000000000000000000..caa41c525d2b36b5a9f9942380f06c97d5781a93 ---- /dev/null -+++ b/src/main/java/io/papermc/paper/datapack/PaperDatapackManager.java -@@ -0,0 +1,55 @@ -+package io.papermc.paper.datapack; -+ -+import com.google.common.collect.Collections2; -+import java.util.Collection; -+import java.util.Collections; -+import java.util.function.Predicate; -+import net.minecraft.server.packs.repository.Pack; -+import net.minecraft.server.packs.repository.PackRepository; -+import org.checkerframework.checker.nullness.qual.NonNull; -+import org.checkerframework.checker.nullness.qual.Nullable; -+import org.checkerframework.framework.qual.DefaultQualifier; -+ -+@DefaultQualifier(NonNull.class) -+public class PaperDatapackManager implements DatapackManager { -+ -+ private final PackRepository repository; -+ -+ public PaperDatapackManager(final PackRepository repository) { -+ this.repository = repository; -+ } -+ -+ @Override -+ public void refreshPacks() { -+ this.repository.reload(); -+ } -+ -+ @Override -+ public @Nullable Datapack getPack(final @NonNull String name) { -+ final @Nullable Pack pack = this.repository.getPack(name); -+ if (pack == null) { -+ return null; -+ } -+ return new PaperDatapack(pack, this.repository.getSelectedPacks().contains(pack)); -+ } -+ -+ @Override -+ public Collection getPacks() { -+ final Collection enabledPacks = this.repository.getSelectedPacks(); -+ return this.transformPacks(this.repository.getAvailablePacks(), enabledPacks::contains); -+ } -+ -+ @Override -+ public Collection getEnabledPacks() { -+ return this.transformPacks(this.repository.getSelectedPacks(), pack -> true); -+ } -+ -+ private Collection transformPacks(final Collection packs, final Predicate enabled) { -+ return Collections.unmodifiableCollection( -+ Collections2.transform( -+ packs, -+ pack -> new PaperDatapack(pack, enabled.test(pack)) -+ ) -+ ); -+ } -+} -diff --git a/src/main/java/org/bukkit/craftbukkit/CraftServer.java b/src/main/java/org/bukkit/craftbukkit/CraftServer.java -index a9c6f53c7e828a4b7d0cefbaa98e50ff1db9354a..a142c62a51ddf59e968c3ddb401dc137c4c07ef6 100644 ---- a/src/main/java/org/bukkit/craftbukkit/CraftServer.java -+++ b/src/main/java/org/bukkit/craftbukkit/CraftServer.java -@@ -305,6 +305,7 @@ public final class CraftServer implements Server { - private final List playerView; - public int reloadCount; - public Set activeCompatibilities = Collections.emptySet(); -+ private final io.papermc.paper.datapack.PaperDatapackManager datapackManager; // Paper - public static Exception excessiveVelEx; // Paper - Velocity warnings - - static { -@@ -390,6 +391,7 @@ public final class CraftServer implements Server { - if (this.configuration.getBoolean("settings.use-map-color-cache")) { - MapPalette.setMapColorCache(new CraftMapColorCache(this.logger)); - } -+ datapackManager = new io.papermc.paper.datapack.PaperDatapackManager(console.getPackRepository()); // Paper - } - - public boolean getCommandBlockOverride(String command) { -@@ -3008,5 +3010,11 @@ public final class CraftServer implements Server { - public com.destroystokyo.paper.entity.ai.MobGoals getMobGoals() { - return mobGoals; - } -+ -+ @Override -+ public io.papermc.paper.datapack.PaperDatapackManager getDatapackManager() { -+ return datapackManager; -+ } -+ - // Paper end - } diff --git a/patches/server/0533-More-Enchantment-API.patch b/patches/server/0529-More-Enchantment-API.patch similarity index 100% rename from patches/server/0533-More-Enchantment-API.patch rename to patches/server/0529-More-Enchantment-API.patch diff --git a/patches/server/0530-Add-environment-variable-to-disable-server-gui.patch b/patches/server/0530-Add-environment-variable-to-disable-server-gui.patch deleted file mode 100644 index e4de8ec4779d..000000000000 --- a/patches/server/0530-Add-environment-variable-to-disable-server-gui.patch +++ /dev/null @@ -1,18 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Riley Park -Date: Mon, 17 May 2021 00:34:55 -0700 -Subject: [PATCH] Add environment variable to disable server gui - - -diff --git a/src/main/java/net/minecraft/server/Main.java b/src/main/java/net/minecraft/server/Main.java -index ded1b7ab4ca1ae3a2d799fe31d05bd6a0c27dcb7..467b8ca8007c5d3a7b72b88e3a979bdf09f1a283 100644 ---- a/src/main/java/net/minecraft/server/Main.java -+++ b/src/main/java/net/minecraft/server/Main.java -@@ -318,6 +318,7 @@ public class Main { - */ - boolean flag2 = !optionset.has("nogui") && !optionset.nonOptionArguments().contains("nogui"); - -+ if(!Boolean.parseBoolean(System.getenv().getOrDefault("PAPER_DISABLE_SERVER_GUI", String.valueOf(false)))) // Paper - Add environment variable to disable server gui - if (flag2 && !GraphicsEnvironment.isHeadless()) { - dedicatedserver1.showGui(); - } diff --git a/patches/server/0530-Move-range-check-for-block-placing-up.patch b/patches/server/0530-Move-range-check-for-block-placing-up.patch new file mode 100644 index 000000000000..b93574bed658 --- /dev/null +++ b/patches/server/0530-Move-range-check-for-block-placing-up.patch @@ -0,0 +1,22 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Nassim Jahnke +Date: Wed, 8 Jun 2022 10:52:18 +0200 +Subject: [PATCH] Move range check for block placing up + + +diff --git a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java +index c3b4b800c5a34afa03ed7c31e14f26ca16109982..cad988c90749183e3af01b319206d49a8734ac15 100644 +--- a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java ++++ b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java +@@ -1757,6 +1757,11 @@ public class ServerGamePacketListenerImpl extends ServerCommonPacketListenerImpl + if (itemstack.isItemEnabled(worldserver.enabledFeatures())) { + BlockHitResult movingobjectpositionblock = packet.getHitResult(); + Vec3 vec3d = movingobjectpositionblock.getLocation(); ++ // Paper start - improve distance check ++ if (!Double.isFinite(vec3d.x) || !Double.isFinite(vec3d.y) || !Double.isFinite(vec3d.z)) { ++ return; ++ } ++ // Paper end - improve distance check + BlockPos blockposition = movingobjectpositionblock.getBlockPos(); + + if (this.player.canInteractWithBlock(blockposition, 1.0D)) { diff --git a/patches/server/0531-Add-Mob-lookAt-API.patch b/patches/server/0531-Add-Mob-lookAt-API.patch new file mode 100644 index 000000000000..8a8ba9fdcd60 --- /dev/null +++ b/patches/server/0531-Add-Mob-lookAt-API.patch @@ -0,0 +1,64 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: BillyGalbreath +Date: Fri, 14 May 2021 13:42:17 -0500 +Subject: [PATCH] Add Mob#lookAt API + + +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftMob.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftMob.java +index c67673772c876dab7c015dcdfb75b297d3c4fbad..bd739428a7e5e35ebcdb70cd187379b3d222339b 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftMob.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftMob.java +@@ -97,5 +97,53 @@ public abstract class CraftMob extends CraftLivingEntity implements Mob { + public boolean isInDaylight() { + return getHandle().isSunBurnTick(); + } ++ ++ @Override ++ public void lookAt(@org.jetbrains.annotations.NotNull org.bukkit.Location location) { ++ com.google.common.base.Preconditions.checkNotNull(location, "location cannot be null"); ++ com.google.common.base.Preconditions.checkArgument(location.getWorld().equals(getWorld()), "location in a different world"); ++ getHandle().getLookControl().setLookAt(location.getX(), location.getY(), location.getZ()); ++ } ++ ++ @Override ++ public void lookAt(@org.jetbrains.annotations.NotNull org.bukkit.Location location, float headRotationSpeed, float maxHeadPitch) { ++ com.google.common.base.Preconditions.checkNotNull(location, "location cannot be null"); ++ com.google.common.base.Preconditions.checkArgument(location.getWorld().equals(getWorld()), "location in a different world"); ++ getHandle().getLookControl().setLookAt(location.getX(), location.getY(), location.getZ(), headRotationSpeed, maxHeadPitch); ++ } ++ ++ @Override ++ public void lookAt(@org.jetbrains.annotations.NotNull org.bukkit.entity.Entity entity) { ++ com.google.common.base.Preconditions.checkNotNull(entity, "entity cannot be null"); ++ com.google.common.base.Preconditions.checkArgument(entity.getWorld().equals(getWorld()), "entity in a different world"); ++ getHandle().getLookControl().setLookAt(((CraftEntity) entity).getHandle()); ++ } ++ ++ @Override ++ public void lookAt(@org.jetbrains.annotations.NotNull org.bukkit.entity.Entity entity, float headRotationSpeed, float maxHeadPitch) { ++ com.google.common.base.Preconditions.checkNotNull(entity, "entity cannot be null"); ++ com.google.common.base.Preconditions.checkArgument(entity.getWorld().equals(getWorld()), "entity in a different world"); ++ getHandle().getLookControl().setLookAt(((CraftEntity) entity).getHandle(), headRotationSpeed, maxHeadPitch); ++ } ++ ++ @Override ++ public void lookAt(double x, double y, double z) { ++ getHandle().getLookControl().setLookAt(x, y, z); ++ } ++ ++ @Override ++ public void lookAt(double x, double y, double z, float headRotationSpeed, float maxHeadPitch) { ++ getHandle().getLookControl().setLookAt(x, y, z, headRotationSpeed, maxHeadPitch); ++ } ++ ++ @Override ++ public int getHeadRotationSpeed() { ++ return getHandle().getHeadRotSpeed(); ++ } ++ ++ @Override ++ public int getMaxHeadPitch() { ++ return getHandle().getMaxHeadXRot(); ++ } + // Paper end + } diff --git a/patches/server/0531-Expand-PlayerGameModeChangeEvent.patch b/patches/server/0531-Expand-PlayerGameModeChangeEvent.patch deleted file mode 100644 index 08394e448a18..000000000000 --- a/patches/server/0531-Expand-PlayerGameModeChangeEvent.patch +++ /dev/null @@ -1,161 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Jake Potrebic -Date: Sat, 15 May 2021 10:04:43 -0700 -Subject: [PATCH] Expand PlayerGameModeChangeEvent - - -diff --git a/src/main/java/net/minecraft/server/commands/DefaultGameModeCommands.java b/src/main/java/net/minecraft/server/commands/DefaultGameModeCommands.java -index b95a979f8f58f43e0becedcd843ff8bb992eb455..a046a0b1519806ff3d987e6402f152b60a3a6f4c 100644 ---- a/src/main/java/net/minecraft/server/commands/DefaultGameModeCommands.java -+++ b/src/main/java/net/minecraft/server/commands/DefaultGameModeCommands.java -@@ -28,9 +28,13 @@ public class DefaultGameModeCommands { - GameType gameType = minecraftServer.getForcedGameType(); - if (gameType != null) { - for (ServerPlayer serverPlayer : minecraftServer.getPlayerList().getPlayers()) { -- if (serverPlayer.setGameMode(gameType)) { -- i++; -+ // Paper start - Expand PlayerGameModeChangeEvent -+ org.bukkit.event.player.PlayerGameModeChangeEvent event = serverPlayer.setGameMode(gameType, org.bukkit.event.player.PlayerGameModeChangeEvent.Cause.DEFAULT_GAMEMODE, net.kyori.adventure.text.Component.empty()); -+ if (event != null && event.isCancelled()) { -+ source.sendSuccess(() -> io.papermc.paper.adventure.PaperAdventure.asVanilla(event.cancelMessage()), false); - } -+ // Paper end - Expand PlayerGameModeChangeEvent -+ i++; - } - } - -diff --git a/src/main/java/net/minecraft/server/commands/GameModeCommand.java b/src/main/java/net/minecraft/server/commands/GameModeCommand.java -index 7f09119bc7d661e08a960dd2bd46006efe752d3e..d1da3600dc07107309b20ebe6e7c0c4da0e8de76 100644 ---- a/src/main/java/net/minecraft/server/commands/GameModeCommand.java -+++ b/src/main/java/net/minecraft/server/commands/GameModeCommand.java -@@ -60,9 +60,14 @@ public class GameModeCommand { - int i = 0; - - for (ServerPlayer serverPlayer : targets) { -- if (serverPlayer.setGameMode(gameMode)) { -+ // Paper start - Expand PlayerGameModeChangeEvent -+ org.bukkit.event.player.PlayerGameModeChangeEvent event = serverPlayer.setGameMode(gameMode, org.bukkit.event.player.PlayerGameModeChangeEvent.Cause.COMMAND, net.kyori.adventure.text.Component.empty()); -+ if (event != null && !event.isCancelled()) { - logGamemodeChange(context.getSource(), serverPlayer, gameMode); - i++; -+ } else if (event != null && event.cancelMessage() != null) { -+ context.getSource().sendSuccess(() -> io.papermc.paper.adventure.PaperAdventure.asVanilla(event.cancelMessage()), true); -+ // Paper end - Expand PlayerGameModeChangeEvent - } - } - -diff --git a/src/main/java/net/minecraft/server/level/ServerPlayer.java b/src/main/java/net/minecraft/server/level/ServerPlayer.java -index 52a5274fb811870481a48bf0505769d7ac5df31f..42df8d45ceb963043cf6467b8fc47272ce0873da 100644 ---- a/src/main/java/net/minecraft/server/level/ServerPlayer.java -+++ b/src/main/java/net/minecraft/server/level/ServerPlayer.java -@@ -2084,10 +2084,18 @@ public class ServerPlayer extends net.minecraft.world.entity.player.Player { - } - - public boolean setGameMode(GameType gameMode) { -+ // Paper start - Expand PlayerGameModeChangeEvent -+ org.bukkit.event.player.PlayerGameModeChangeEvent event = this.setGameMode(gameMode, org.bukkit.event.player.PlayerGameModeChangeEvent.Cause.UNKNOWN, null); -+ return event == null ? false : event.isCancelled(); -+ } -+ @Nullable -+ public org.bukkit.event.player.PlayerGameModeChangeEvent setGameMode(GameType gameMode, org.bukkit.event.player.PlayerGameModeChangeEvent.Cause cause, @Nullable net.kyori.adventure.text.Component message) { - boolean flag = this.isSpectator(); - -- if (!this.gameMode.changeGameModeForPlayer(gameMode)) { -- return false; -+ org.bukkit.event.player.PlayerGameModeChangeEvent event = this.gameMode.changeGameModeForPlayer(gameMode, cause, message); -+ if (event == null || event.isCancelled()) { -+ return null; -+ // Paper end - Expand PlayerGameModeChangeEvent - } else { - this.connection.send(new ClientboundGameEventPacket(ClientboundGameEventPacket.CHANGE_GAME_MODE, (float) gameMode.getId())); - if (gameMode == GameType.SPECTATOR) { -@@ -2103,7 +2111,7 @@ public class ServerPlayer extends net.minecraft.world.entity.player.Player { - - this.onUpdateAbilities(); - this.updateEffectVisibility(); -- return true; -+ return event; // Paper - Expand PlayerGameModeChangeEvent - } - } - -@@ -2509,6 +2517,16 @@ public class ServerPlayer extends net.minecraft.world.entity.player.Player { - } - - public void loadGameTypes(@Nullable CompoundTag nbt) { -+ // Paper start - Expand PlayerGameModeChangeEvent -+ if (this.server.getForcedGameType() != null && this.server.getForcedGameType() != ServerPlayer.readPlayerMode(nbt, "playerGameType")) { -+ if (new org.bukkit.event.player.PlayerGameModeChangeEvent(this.getBukkitEntity(), org.bukkit.GameMode.getByValue(this.server.getDefaultGameType().getId()), org.bukkit.event.player.PlayerGameModeChangeEvent.Cause.DEFAULT_GAMEMODE, null).callEvent()) { -+ this.gameMode.setGameModeForPlayer(this.server.getForcedGameType(), GameType.DEFAULT_MODE); -+ } else { -+ this.gameMode.setGameModeForPlayer(ServerPlayer.readPlayerMode(nbt,"playerGameType"), ServerPlayer.readPlayerMode(nbt, "previousPlayerGameType")); -+ } -+ return; -+ } -+ // Paper end - Expand PlayerGameModeChangeEvent - this.gameMode.setGameModeForPlayer(this.calculateGameModeForNewPlayer(ServerPlayer.readPlayerMode(nbt, "playerGameType")), ServerPlayer.readPlayerMode(nbt, "previousPlayerGameType")); - } - -diff --git a/src/main/java/net/minecraft/server/level/ServerPlayerGameMode.java b/src/main/java/net/minecraft/server/level/ServerPlayerGameMode.java -index 5de472df78940d1b8320f73d18b2edf3a796227e..073cf184a0e7af41048ae67a9b17b4cdfcc43c35 100644 ---- a/src/main/java/net/minecraft/server/level/ServerPlayerGameMode.java -+++ b/src/main/java/net/minecraft/server/level/ServerPlayerGameMode.java -@@ -74,14 +74,21 @@ public class ServerPlayerGameMode { - } - - public boolean changeGameModeForPlayer(GameType gameMode) { -+ // Paper start - Expand PlayerGameModeChangeEvent -+ PlayerGameModeChangeEvent event = this.changeGameModeForPlayer(gameMode, org.bukkit.event.player.PlayerGameModeChangeEvent.Cause.UNKNOWN, null); -+ return event != null && event.isCancelled(); -+ } -+ @Nullable -+ public PlayerGameModeChangeEvent changeGameModeForPlayer(GameType gameMode, org.bukkit.event.player.PlayerGameModeChangeEvent.Cause cause, @Nullable net.kyori.adventure.text.Component cancelMessage) { -+ // Paper end - Expand PlayerGameModeChangeEvent - if (gameMode == this.gameModeForPlayer) { -- return false; -+ return null; // Paper - Expand PlayerGameModeChangeEvent - } else { - // CraftBukkit start -- PlayerGameModeChangeEvent event = new PlayerGameModeChangeEvent(this.player.getBukkitEntity(), GameMode.getByValue(gameMode.getId())); -+ PlayerGameModeChangeEvent event = new PlayerGameModeChangeEvent(this.player.getBukkitEntity(), GameMode.getByValue(gameMode.getId()), cause, cancelMessage); // Paper - this.level.getCraftServer().getPluginManager().callEvent(event); - if (event.isCancelled()) { -- return false; -+ return event; // Paper - Expand PlayerGameModeChangeEvent - } - // CraftBukkit end - this.setGameModeForPlayer(gameMode, this.previousGameModeForPlayer); -@@ -92,7 +99,7 @@ public class ServerPlayerGameMode { - this.player.resetCurrentImpulseContext(); - } - -- return true; -+ return event; // Paper - Expand PlayerGameModeChangeEvent - } - } - -diff --git a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java -index 276c0b017452a1d7b60a2a8e779f2f3fcf4960cc..9781047aa5e0a6e6bc9f9c43a1eb347b6ecdff66 100644 ---- a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java -+++ b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java -@@ -2730,7 +2730,7 @@ public class ServerGamePacketListenerImpl extends ServerCommonPacketListenerImpl - - this.player = this.server.getPlayerList().respawn(this.player, false, Entity.RemovalReason.KILLED, RespawnReason.DEATH); // CraftBukkit - if (this.server.isHardcore()) { -- this.player.setGameMode(GameType.SPECTATOR); -+ this.player.setGameMode(GameType.SPECTATOR, org.bukkit.event.player.PlayerGameModeChangeEvent.Cause.HARDCORE_DEATH, null); // Paper - Expand PlayerGameModeChangeEvent - ((GameRules.BooleanValue) this.player.level().getGameRules().getRule(GameRules.RULE_SPECTATORSGENERATECHUNKS)).set(false, this.player.serverLevel()); // CraftBukkit - per-world - } - } -diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java -index 7de53f6750d478a052bc8ade6edac056565fe068..a85983b947147a1557908846b8773aab29c17fae 100644 ---- a/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java -+++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java -@@ -1654,7 +1654,7 @@ public class CraftPlayer extends CraftHumanEntity implements Player { - Preconditions.checkArgument(mode != null, "GameMode cannot be null"); - if (this.getHandle().connection == null) return; - -- this.getHandle().setGameMode(GameType.byId(mode.getValue())); -+ this.getHandle().setGameMode(GameType.byId(mode.getValue()), org.bukkit.event.player.PlayerGameModeChangeEvent.Cause.PLUGIN, null); // Paper - Expand PlayerGameModeChangeEvent - } - - @Override diff --git a/patches/server/0532-Correctly-check-if-bucket-dispenses-will-succeed-for.patch b/patches/server/0532-Correctly-check-if-bucket-dispenses-will-succeed-for.patch new file mode 100644 index 000000000000..85ef521c40df --- /dev/null +++ b/patches/server/0532-Correctly-check-if-bucket-dispenses-will-succeed-for.patch @@ -0,0 +1,28 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Jake Potrebic +Date: Mon, 1 Jan 2024 12:57:19 -0800 +Subject: [PATCH] Correctly check if bucket dispenses will succeed for event + +Upstream incorrectly checks if the bucket place will succeed +in order to fire the BlockDispenseEvent. This patch corrects +that. + +diff --git a/src/main/java/net/minecraft/core/dispenser/DispenseItemBehavior.java b/src/main/java/net/minecraft/core/dispenser/DispenseItemBehavior.java +index c63a86af95849cacdfed3b061122b90651fd5bdf..5793569ae8a088f21b0d8d6771a5099b1e88be09 100644 +--- a/src/main/java/net/minecraft/core/dispenser/DispenseItemBehavior.java ++++ b/src/main/java/net/minecraft/core/dispenser/DispenseItemBehavior.java +@@ -330,7 +330,13 @@ public interface DispenseItemBehavior { + int y = blockposition.getY(); + int z = blockposition.getZ(); + BlockState iblockdata = worldserver.getBlockState(blockposition); +- if (iblockdata.isAir() || iblockdata.canBeReplaced() || (dispensiblecontaineritem instanceof BucketItem && iblockdata.getBlock() instanceof LiquidBlockContainer && ((LiquidBlockContainer) iblockdata.getBlock()).canPlaceLiquid((Player) null, worldserver, blockposition, iblockdata, ((BucketItem) dispensiblecontaineritem).content))) { ++ // Paper start - correctly check if the bucket place will succeed ++ /* Taken from SolidBucketItem#emptyContents */ ++ boolean willEmptyContentsSolidBucketItem = dispensiblecontaineritem instanceof net.minecraft.world.item.SolidBucketItem && worldserver.isInWorldBounds(blockposition) && iblockdata.isAir(); ++ /* Taken from BucketItem#emptyContents */ ++ boolean willEmptyBucketItem = dispensiblecontaineritem instanceof final BucketItem bucketItem && bucketItem.content instanceof net.minecraft.world.level.material.FlowingFluid && (iblockdata.isAir() || iblockdata.canBeReplaced(bucketItem.content) || (iblockdata.getBlock() instanceof LiquidBlockContainer liquidBlockContainer && liquidBlockContainer.canPlaceLiquid(null, worldserver, blockposition, iblockdata, bucketItem.content))); ++ if (willEmptyContentsSolidBucketItem || willEmptyBucketItem) { ++ // Paper end - correctly check if the bucket place will succeed + org.bukkit.block.Block block = CraftBlock.at(worldserver, pointer.pos()); + CraftItemStack craftItem = CraftItemStack.asCraftMirror(stack); + diff --git a/patches/server/0532-ItemStack-repair-check-API.patch b/patches/server/0532-ItemStack-repair-check-API.patch deleted file mode 100644 index 75f490441ebf..000000000000 --- a/patches/server/0532-ItemStack-repair-check-API.patch +++ /dev/null @@ -1,72 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Jake Potrebic -Date: Sat, 15 May 2021 22:11:11 -0700 -Subject: [PATCH] ItemStack repair check API - - -diff --git a/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java b/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java -index 09fa524fa1155d53d988c15c1af551f73c96ede5..78d1b33a554ac8ca0f76585c6b97e35c2d337293 100644 ---- a/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java -+++ b/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java -@@ -532,6 +532,14 @@ public final class CraftMagicNumbers implements UnsafeValues { - public int getProtocolVersion() { - return net.minecraft.SharedConstants.getCurrentVersion().getProtocolVersion(); - } -+ -+ @Override -+ public boolean isValidRepairItemStack(org.bukkit.inventory.ItemStack itemToBeRepaired, org.bukkit.inventory.ItemStack repairMaterial) { -+ if (!itemToBeRepaired.getType().isItem() || !repairMaterial.getType().isItem()) { -+ return false; -+ } -+ return CraftMagicNumbers.getItem(itemToBeRepaired.getType()).isValidRepairItem(CraftItemStack.asNMSCopy(itemToBeRepaired), CraftItemStack.asNMSCopy(repairMaterial)); -+ } - // Paper end - - @Override -diff --git a/src/test/java/io/papermc/paper/util/ItemStackRepairCheckTest.java b/src/test/java/io/papermc/paper/util/ItemStackRepairCheckTest.java -new file mode 100644 -index 0000000000000000000000000000000000000000..04e2568816f1fbe090b40e5a55d8d4effc045740 ---- /dev/null -+++ b/src/test/java/io/papermc/paper/util/ItemStackRepairCheckTest.java -@@ -0,0 +1,41 @@ -+package io.papermc.paper.util; -+ -+import org.bukkit.Material; -+import org.bukkit.inventory.ItemStack; -+import org.bukkit.support.environment.VanillaFeature; -+import org.junit.jupiter.api.Test; -+ -+import static org.junit.jupiter.api.Assertions.assertFalse; -+import static org.junit.jupiter.api.Assertions.assertTrue; -+ -+@VanillaFeature -+public class ItemStackRepairCheckTest { -+ -+ @Test -+ public void testIsRepariableBy() { -+ ItemStack diamondPick = new ItemStack(Material.DIAMOND_PICKAXE); -+ -+ assertTrue(diamondPick.isRepairableBy(new ItemStack(Material.DIAMOND)), "diamond pick isn't repairable by a diamond"); -+ } -+ -+ @Test -+ public void testCanRepair() { -+ ItemStack diamond = new ItemStack(Material.DIAMOND); -+ -+ assertTrue(diamond.canRepair(new ItemStack(Material.DIAMOND_AXE)), "diamond can't repair a diamond axe"); -+ } -+ -+ @Test -+ public void testIsNotRepairableBy() { -+ ItemStack notDiamondPick = new ItemStack(Material.ACACIA_SAPLING); -+ -+ assertFalse(notDiamondPick.isRepairableBy(new ItemStack(Material.DIAMOND)), "acacia sapling is repairable by a diamond"); -+ } -+ -+ @Test -+ public void testCanNotRepair() { -+ ItemStack diamond = new ItemStack(Material.DIAMOND); -+ -+ assertFalse(diamond.canRepair(new ItemStack(Material.OAK_BUTTON)), "diamond can repair oak button"); -+ } -+} diff --git a/patches/server/0533-Add-Unix-domain-socket-support.patch b/patches/server/0533-Add-Unix-domain-socket-support.patch new file mode 100644 index 000000000000..34dd3ee13487 --- /dev/null +++ b/patches/server/0533-Add-Unix-domain-socket-support.patch @@ -0,0 +1,137 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Andrew Steinborn +Date: Tue, 11 May 2021 17:39:22 -0400 +Subject: [PATCH] Add Unix domain socket support + + +diff --git a/src/main/java/net/minecraft/server/dedicated/DedicatedServer.java b/src/main/java/net/minecraft/server/dedicated/DedicatedServer.java +index 761663514b98a1c0a0a905150411aff450a0cc50..21d6f728d6ecd35a05933e9406a386c36135a456 100644 +--- a/src/main/java/net/minecraft/server/dedicated/DedicatedServer.java ++++ b/src/main/java/net/minecraft/server/dedicated/DedicatedServer.java +@@ -233,6 +233,20 @@ public class DedicatedServer extends MinecraftServer implements ServerInterface + this.setEnforceWhitelist(dedicatedserverproperties.enforceWhitelist); + // this.worldData.setGameType(dedicatedserverproperties.gamemode); // CraftBukkit - moved to world loading + DedicatedServer.LOGGER.info("Default game type: {}", dedicatedserverproperties.gamemode); ++ // Paper start - Unix domain socket support ++ java.net.SocketAddress bindAddress; ++ if (this.getLocalIp().startsWith("unix:")) { ++ if (!io.netty.channel.epoll.Epoll.isAvailable()) { ++ DedicatedServer.LOGGER.error("**** INVALID CONFIGURATION!"); ++ DedicatedServer.LOGGER.error("You are trying to use a Unix domain socket but you're not on a supported OS."); ++ return false; ++ } else if (!io.papermc.paper.configuration.GlobalConfiguration.get().proxies.velocity.enabled && !org.spigotmc.SpigotConfig.bungee) { ++ DedicatedServer.LOGGER.error("**** INVALID CONFIGURATION!"); ++ DedicatedServer.LOGGER.error("Unix domain sockets require IPs to be forwarded from a proxy."); ++ return false; ++ } ++ bindAddress = new io.netty.channel.unix.DomainSocketAddress(this.getLocalIp().substring("unix:".length())); ++ } else { + InetAddress inetaddress = null; + + if (!this.getLocalIp().isEmpty()) { +@@ -242,12 +256,15 @@ public class DedicatedServer extends MinecraftServer implements ServerInterface + if (this.getPort() < 0) { + this.setPort(dedicatedserverproperties.serverPort); + } ++ bindAddress = new java.net.InetSocketAddress(inetaddress, this.getPort()); ++ } ++ // Paper end - Unix domain socket support + + this.initializeKeyPair(); + DedicatedServer.LOGGER.info("Starting Minecraft server on {}:{}", this.getLocalIp().isEmpty() ? "*" : this.getLocalIp(), this.getPort()); + + try { +- this.getConnection().startTcpServerListener(inetaddress, this.getPort()); ++ this.getConnection().bind(bindAddress); // Paper - Unix domain socket support + } catch (IOException ioexception) { + DedicatedServer.LOGGER.warn("**** FAILED TO BIND TO PORT!"); + DedicatedServer.LOGGER.warn("The exception was: {}", ioexception.toString()); +diff --git a/src/main/java/net/minecraft/server/network/ServerConnectionListener.java b/src/main/java/net/minecraft/server/network/ServerConnectionListener.java +index d6d7f1c446ba5507f67038ff27775ba75156f4a7..c63c194c44646e6bc1a59426552787011fc2ced5 100644 +--- a/src/main/java/net/minecraft/server/network/ServerConnectionListener.java ++++ b/src/main/java/net/minecraft/server/network/ServerConnectionListener.java +@@ -76,7 +76,12 @@ public class ServerConnectionListener { + this.running = true; + } + ++ // Paper start - Unix domain socket support + public void startTcpServerListener(@Nullable InetAddress address, int port) throws IOException { ++ bind(new java.net.InetSocketAddress(address, port)); ++ } ++ public void bind(java.net.SocketAddress address) throws IOException { ++ // Paper end - Unix domain socket support + List list = this.channels; + + synchronized (this.channels) { +@@ -84,7 +89,13 @@ public class ServerConnectionListener { + EventLoopGroup eventloopgroup; + + if (Epoll.isAvailable() && this.server.isEpollEnabled()) { ++ // Paper start - Unix domain socket support ++ if (address instanceof io.netty.channel.unix.DomainSocketAddress) { ++ oclass = io.netty.channel.epoll.EpollServerDomainSocketChannel.class; ++ } else { + oclass = EpollServerSocketChannel.class; ++ } ++ // Paper end - Unix domain socket support + eventloopgroup = (EventLoopGroup) ServerConnectionListener.SERVER_EPOLL_EVENT_GROUP.get(); + ServerConnectionListener.LOGGER.info("Using epoll channel type"); + } else { +@@ -117,7 +128,7 @@ public class ServerConnectionListener { + ((Connection) object).setListenerForServerboundHandshake(new ServerHandshakePacketListenerImpl(ServerConnectionListener.this.server, (Connection) object)); + io.papermc.paper.network.ChannelInitializeListenerHolder.callListeners(channel); // Paper - Add Channel initialization listeners + } +- }).group(eventloopgroup).localAddress(address, port)).option(ChannelOption.AUTO_READ, false).bind().syncUninterruptibly()); // CraftBukkit ++ }).group(eventloopgroup).localAddress(address)).option(ChannelOption.AUTO_READ, false).bind().syncUninterruptibly()); // CraftBukkit // Paper - Unix domain socket support + } + } + +diff --git a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java +index cad988c90749183e3af01b319206d49a8734ac15..34258a58ea7b75363eff825e3db27832d3559557 100644 +--- a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java ++++ b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java +@@ -2566,6 +2566,11 @@ public class ServerGamePacketListenerImpl extends ServerCommonPacketListenerImpl + // Spigot Start + public SocketAddress getRawAddress() + { ++ // Paper start - Unix domain socket support; this can be nullable in the case of a Unix domain socket, so if it is, fake something ++ if (connection.channel.remoteAddress() == null) { ++ return new java.net.InetSocketAddress(java.net.InetAddress.getLoopbackAddress(), 0); ++ } ++ // Paper end - Unix domain socket support + return this.connection.channel.remoteAddress(); + } + // Spigot End +diff --git a/src/main/java/net/minecraft/server/network/ServerHandshakePacketListenerImpl.java b/src/main/java/net/minecraft/server/network/ServerHandshakePacketListenerImpl.java +index a5bbea6a073e00c10c3c5facd997eb8473fd9a5f..ddf42645402afefc0f5caebc684b191eef9d6ec2 100644 +--- a/src/main/java/net/minecraft/server/network/ServerHandshakePacketListenerImpl.java ++++ b/src/main/java/net/minecraft/server/network/ServerHandshakePacketListenerImpl.java +@@ -81,6 +81,7 @@ public class ServerHandshakePacketListenerImpl implements ServerHandshakePacketL + this.connection.setupOutboundProtocol(LoginProtocols.CLIENTBOUND); + // CraftBukkit start - Connection throttle + try { ++ if (!(this.connection.channel.localAddress() instanceof io.netty.channel.unix.DomainSocketAddress)) { // Paper - Unix domain socket support; the connection throttle is useless when you have a Unix domain socket + long currentTime = System.currentTimeMillis(); + long connectionThrottle = this.server.server.getConnectionThrottle(); + InetAddress address = ((java.net.InetSocketAddress) this.connection.getRemoteAddress()).getAddress(); +@@ -109,6 +110,7 @@ public class ServerHandshakePacketListenerImpl implements ServerHandshakePacketL + } + } + } ++ } // Paper - Unix domain socket support + } catch (Throwable t) { + org.apache.logging.log4j.LogManager.getLogger().debug("Failed to check connection throttle", t); + } +@@ -166,8 +168,11 @@ public class ServerHandshakePacketListenerImpl implements ServerHandshakePacketL + if (!handledByEvent && proxyLogicEnabled) { // Paper + // if (org.spigotmc.SpigotConfig.bungee) { // Paper - comment out, we check above! + if ( ( split.length == 3 || split.length == 4 ) && ( ServerHandshakePacketListenerImpl.BYPASS_HOSTCHECK || ServerHandshakePacketListenerImpl.HOST_PATTERN.matcher( split[1] ).matches() ) ) { // Paper - Add bypass host check ++ // Paper start - Unix domain socket support ++ java.net.SocketAddress socketAddress = this.connection.getRemoteAddress(); + this.connection.hostname = split[0]; +- this.connection.address = new java.net.InetSocketAddress(split[1], ((java.net.InetSocketAddress) this.connection.getRemoteAddress()).getPort()); ++ this.connection.address = new java.net.InetSocketAddress(split[1], socketAddress instanceof java.net.InetSocketAddress ? ((java.net.InetSocketAddress) socketAddress).getPort() : 0); ++ // Paper end - Unix domain socket support + this.connection.spoofedUUID = com.mojang.util.UndashedUuid.fromStringLenient( split[2] ); + } else + { diff --git a/patches/server/0534-Add-EntityInsideBlockEvent.patch b/patches/server/0534-Add-EntityInsideBlockEvent.patch new file mode 100644 index 000000000000..0009e3fbdfdc --- /dev/null +++ b/patches/server/0534-Add-EntityInsideBlockEvent.patch @@ -0,0 +1,294 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Jake Potrebic +Date: Sat, 8 May 2021 18:02:36 -0700 +Subject: [PATCH] Add EntityInsideBlockEvent + + +diff --git a/src/main/java/net/minecraft/world/level/block/BaseFireBlock.java b/src/main/java/net/minecraft/world/level/block/BaseFireBlock.java +index 7f0811bc22d78cdc0aca4c6869c90252d8a59eed..c8ca41cd81a72f9bff40f5c1b3bfc1189bf51f98 100644 +--- a/src/main/java/net/minecraft/world/level/block/BaseFireBlock.java ++++ b/src/main/java/net/minecraft/world/level/block/BaseFireBlock.java +@@ -128,6 +128,7 @@ public abstract class BaseFireBlock extends Block { + + @Override + protected void entityInside(BlockState state, Level world, BlockPos pos, Entity entity) { ++ if (!new io.papermc.paper.event.entity.EntityInsideBlockEvent(entity.getBukkitEntity(), org.bukkit.craftbukkit.block.CraftBlock.at(world, pos)).callEvent()) { return; } // Paper - Add EntityInsideBlockEvent + if (!entity.fireImmune()) { + if (entity.getRemainingFireTicks() < 0) { + entity.setRemainingFireTicks(entity.getRemainingFireTicks() + 1); +diff --git a/src/main/java/net/minecraft/world/level/block/BasePressurePlateBlock.java b/src/main/java/net/minecraft/world/level/block/BasePressurePlateBlock.java +index cd3d9e69653afba51a55a3dfc6764b712b8df859..9afa811579ac2e556b5c5c23b3b49587439dfadc 100644 +--- a/src/main/java/net/minecraft/world/level/block/BasePressurePlateBlock.java ++++ b/src/main/java/net/minecraft/world/level/block/BasePressurePlateBlock.java +@@ -77,6 +77,7 @@ public abstract class BasePressurePlateBlock extends Block { + + @Override + protected void entityInside(BlockState state, Level world, BlockPos pos, Entity entity) { ++ if (!new io.papermc.paper.event.entity.EntityInsideBlockEvent(entity.getBukkitEntity(), org.bukkit.craftbukkit.block.CraftBlock.at(world, pos)).callEvent()) { return; } // Paper - Add EntityInsideBlockEvent + if (!world.isClientSide) { + int i = this.getSignalForState(state); + +diff --git a/src/main/java/net/minecraft/world/level/block/BigDripleafBlock.java b/src/main/java/net/minecraft/world/level/block/BigDripleafBlock.java +index 87083f7de53e32713b54315803ccd414db2debc8..9e3f1441d62128535112621bf259c24f1a90595b 100644 +--- a/src/main/java/net/minecraft/world/level/block/BigDripleafBlock.java ++++ b/src/main/java/net/minecraft/world/level/block/BigDripleafBlock.java +@@ -180,6 +180,7 @@ public class BigDripleafBlock extends HorizontalDirectionalBlock implements Bone + + @Override + protected void entityInside(BlockState state, Level world, BlockPos pos, Entity entity) { ++ if (!new io.papermc.paper.event.entity.EntityInsideBlockEvent(entity.getBukkitEntity(), org.bukkit.craftbukkit.block.CraftBlock.at(world, pos)).callEvent()) { return; } // Paper - Add EntityInsideBlockEvent + if (!world.isClientSide) { + if (state.getValue(BigDripleafBlock.TILT) == Tilt.NONE && BigDripleafBlock.canEntityTilt(pos, entity) && !world.hasNeighborSignal(pos)) { + // CraftBukkit start - tilt dripleaf +diff --git a/src/main/java/net/minecraft/world/level/block/BubbleColumnBlock.java b/src/main/java/net/minecraft/world/level/block/BubbleColumnBlock.java +index c2d8caee4acb878aaa43c0cdc6f6a37555b69a12..385da0585f409ee453f10d45f5837cdc09adc21b 100644 +--- a/src/main/java/net/minecraft/world/level/block/BubbleColumnBlock.java ++++ b/src/main/java/net/minecraft/world/level/block/BubbleColumnBlock.java +@@ -48,6 +48,7 @@ public class BubbleColumnBlock extends Block implements BucketPickup { + + @Override + protected void entityInside(BlockState state, Level world, BlockPos pos, Entity entity) { ++ if (!new io.papermc.paper.event.entity.EntityInsideBlockEvent(entity.getBukkitEntity(), org.bukkit.craftbukkit.block.CraftBlock.at(world, pos)).callEvent()) { return; } // Paper - Add EntityInsideBlockEvent + BlockState blockState = world.getBlockState(pos.above()); + if (blockState.isAir()) { + entity.onAboveBubbleCol(state.getValue(DRAG_DOWN)); +diff --git a/src/main/java/net/minecraft/world/level/block/ButtonBlock.java b/src/main/java/net/minecraft/world/level/block/ButtonBlock.java +index e210c210b733fc968b9c3816bb1f9755d76660ef..061a8f8b58d9fa7959333e2f59d3b7ee03cbf92d 100644 +--- a/src/main/java/net/minecraft/world/level/block/ButtonBlock.java ++++ b/src/main/java/net/minecraft/world/level/block/ButtonBlock.java +@@ -208,6 +208,7 @@ public class ButtonBlock extends FaceAttachedHorizontalDirectionalBlock { + + @Override + protected void entityInside(BlockState state, Level world, BlockPos pos, Entity entity) { ++ if (!new io.papermc.paper.event.entity.EntityInsideBlockEvent(entity.getBukkitEntity(), org.bukkit.craftbukkit.block.CraftBlock.at(world, pos)).callEvent()) { return; } // Paper - Add EntityInsideBlockEvent + if (!world.isClientSide && this.type.canButtonBeActivatedByArrows() && !(Boolean) state.getValue(ButtonBlock.POWERED)) { + this.checkPressed(state, world, pos); + } +diff --git a/src/main/java/net/minecraft/world/level/block/CactusBlock.java b/src/main/java/net/minecraft/world/level/block/CactusBlock.java +index de1b64e0cbe7f2de63f04262428c9e6ec340916e..c045b1cccf0047dbef8c04d5a28d31d53389054f 100644 +--- a/src/main/java/net/minecraft/world/level/block/CactusBlock.java ++++ b/src/main/java/net/minecraft/world/level/block/CactusBlock.java +@@ -122,6 +122,7 @@ public class CactusBlock extends Block { + + @Override + protected void entityInside(BlockState state, Level world, BlockPos pos, Entity entity) { ++ if (!new io.papermc.paper.event.entity.EntityInsideBlockEvent(entity.getBukkitEntity(), org.bukkit.craftbukkit.block.CraftBlock.at(world, pos)).callEvent()) { return; } // Paper - Add EntityInsideBlockEvent + entity.hurt(world.damageSources().cactus().directBlock(world, pos), 1.0F); // CraftBukkit + } + +diff --git a/src/main/java/net/minecraft/world/level/block/CampfireBlock.java b/src/main/java/net/minecraft/world/level/block/CampfireBlock.java +index 1cc807a7a73ea35919259d7d8370fa3bcc44fb2f..18d4020017d76303d3179fad8974574777ea6305 100644 +--- a/src/main/java/net/minecraft/world/level/block/CampfireBlock.java ++++ b/src/main/java/net/minecraft/world/level/block/CampfireBlock.java +@@ -112,6 +112,7 @@ public class CampfireBlock extends BaseEntityBlock implements SimpleWaterloggedB + + @Override + protected void entityInside(BlockState state, Level world, BlockPos pos, Entity entity) { ++ if (!new io.papermc.paper.event.entity.EntityInsideBlockEvent(entity.getBukkitEntity(), org.bukkit.craftbukkit.block.CraftBlock.at(world, pos)).callEvent()) { return; } // Paper - Add EntityInsideBlockEvent + if ((Boolean) state.getValue(CampfireBlock.LIT) && entity instanceof LivingEntity) { + entity.hurt(world.damageSources().campfire().directBlock(world, pos), (float) this.fireDamage); // CraftBukkit + } +diff --git a/src/main/java/net/minecraft/world/level/block/CropBlock.java b/src/main/java/net/minecraft/world/level/block/CropBlock.java +index 93c9f093eef0568a3816412fa497c12487ff04e3..79ebc37a779bf6cba66a34a7604f819e95fd86a2 100644 +--- a/src/main/java/net/minecraft/world/level/block/CropBlock.java ++++ b/src/main/java/net/minecraft/world/level/block/CropBlock.java +@@ -174,6 +174,7 @@ public class CropBlock extends BushBlock implements BonemealableBlock { + + @Override + protected void entityInside(BlockState state, Level world, BlockPos pos, Entity entity) { ++ if (!new io.papermc.paper.event.entity.EntityInsideBlockEvent(entity.getBukkitEntity(), org.bukkit.craftbukkit.block.CraftBlock.at(world, pos)).callEvent()) { return; } // Paper - Add EntityInsideBlockEvent + if (world instanceof ServerLevel worldserver) { + if (entity instanceof Ravager && CraftEventFactory.callEntityChangeBlockEvent(entity, pos, Blocks.AIR.defaultBlockState(), !worldserver.getGameRules().getBoolean(GameRules.RULE_MOBGRIEFING))) { // CraftBukkit + worldserver.destroyBlock(pos, true, entity); +diff --git a/src/main/java/net/minecraft/world/level/block/DetectorRailBlock.java b/src/main/java/net/minecraft/world/level/block/DetectorRailBlock.java +index 1e2f56b5c40c3dc72bc38354160f8e7de1f4f5cf..fa1c4defd0d4e4cd888eb26eed131539d0ed573f 100644 +--- a/src/main/java/net/minecraft/world/level/block/DetectorRailBlock.java ++++ b/src/main/java/net/minecraft/world/level/block/DetectorRailBlock.java +@@ -52,6 +52,7 @@ public class DetectorRailBlock extends BaseRailBlock { + + @Override + protected void entityInside(BlockState state, Level world, BlockPos pos, Entity entity) { ++ if (!new io.papermc.paper.event.entity.EntityInsideBlockEvent(entity.getBukkitEntity(), org.bukkit.craftbukkit.block.CraftBlock.at(world, pos)).callEvent()) { return; } // Paper - Add EntityInsideBlockEvent + if (!world.isClientSide) { + if (!(Boolean) state.getValue(DetectorRailBlock.POWERED)) { + this.checkPressed(world, pos, state); +diff --git a/src/main/java/net/minecraft/world/level/block/EndGatewayBlock.java b/src/main/java/net/minecraft/world/level/block/EndGatewayBlock.java +index ae32d7819ee45d6d334be19c78f8150d104fb787..bb4800c60ac05f2db8821737b2b884ea99b64799 100644 +--- a/src/main/java/net/minecraft/world/level/block/EndGatewayBlock.java ++++ b/src/main/java/net/minecraft/world/level/block/EndGatewayBlock.java +@@ -92,6 +92,7 @@ public class EndGatewayBlock extends BaseEntityBlock implements Portal { + + @Override + protected void entityInside(BlockState state, Level world, BlockPos pos, Entity entity) { ++ if (!new io.papermc.paper.event.entity.EntityInsideBlockEvent(entity.getBukkitEntity(), org.bukkit.craftbukkit.block.CraftBlock.at(world, pos)).callEvent()) { return; } // Paper - Add EntityInsideBlockEvent + if (entity.canUsePortal(false)) { + BlockEntity tileentity = world.getBlockEntity(pos); + +diff --git a/src/main/java/net/minecraft/world/level/block/EndPortalBlock.java b/src/main/java/net/minecraft/world/level/block/EndPortalBlock.java +index 5744944455b08d45a7c0fe2289414b50b6c0d66a..6ed6c2123ed4c54f191ed8cf6da72109fb95eb69 100644 +--- a/src/main/java/net/minecraft/world/level/block/EndPortalBlock.java ++++ b/src/main/java/net/minecraft/world/level/block/EndPortalBlock.java +@@ -68,6 +68,7 @@ public class EndPortalBlock extends BaseEntityBlock implements Portal { + + @Override + protected void entityInside(BlockState state, Level world, BlockPos pos, Entity entity) { ++ if (!new io.papermc.paper.event.entity.EntityInsideBlockEvent(entity.getBukkitEntity(), org.bukkit.craftbukkit.block.CraftBlock.at(world, pos)).callEvent()) { return; } // Paper - Add EntityInsideBlockEvent + if (entity.canUsePortal(false)) { + // CraftBukkit start - Entity in portal + EntityPortalEnterEvent event = new EntityPortalEnterEvent(entity.getBukkitEntity(), new org.bukkit.Location(world.getWorld(), pos.getX(), pos.getY(), pos.getZ())); +diff --git a/src/main/java/net/minecraft/world/level/block/FrogspawnBlock.java b/src/main/java/net/minecraft/world/level/block/FrogspawnBlock.java +index aee71779f31def5f1ef7438cf06219d1de7092ec..34be6b349722240e99f91d28067578aa0b4bd1fe 100644 +--- a/src/main/java/net/minecraft/world/level/block/FrogspawnBlock.java ++++ b/src/main/java/net/minecraft/world/level/block/FrogspawnBlock.java +@@ -89,6 +89,7 @@ public class FrogspawnBlock extends Block { + + @Override + protected void entityInside(BlockState state, Level world, BlockPos pos, Entity entity) { ++ if (!new io.papermc.paper.event.entity.EntityInsideBlockEvent(entity.getBukkitEntity(), org.bukkit.craftbukkit.block.CraftBlock.at(world, pos)).callEvent()) { return; } // Paper - Add EntityInsideBlockEvent + if (entity.getType().equals(EntityType.FALLING_BLOCK)) { + this.destroyBlock(world, pos); + } +diff --git a/src/main/java/net/minecraft/world/level/block/HoneyBlock.java b/src/main/java/net/minecraft/world/level/block/HoneyBlock.java +index 3ddf43a8afe8f00ca910d7838356dfc6d007a4f9..5c360c6768582c1a35431739613e9b406875cc21 100644 +--- a/src/main/java/net/minecraft/world/level/block/HoneyBlock.java ++++ b/src/main/java/net/minecraft/world/level/block/HoneyBlock.java +@@ -60,6 +60,7 @@ public class HoneyBlock extends HalfTransparentBlock { + + @Override + protected void entityInside(BlockState state, Level world, BlockPos pos, Entity entity) { ++ if (!new io.papermc.paper.event.entity.EntityInsideBlockEvent(entity.getBukkitEntity(), org.bukkit.craftbukkit.block.CraftBlock.at(world, pos)).callEvent()) { return; } // Paper - Add EntityInsideBlockEvent + if (this.isSlidingDown(pos, entity)) { + this.maybeDoSlideAchievement(entity, pos); + this.doSlideMovement(entity); +diff --git a/src/main/java/net/minecraft/world/level/block/HopperBlock.java b/src/main/java/net/minecraft/world/level/block/HopperBlock.java +index 19ace6115a80f2696746bd98a5465259e4f1a633..b61324fe162f32817b87e4adb80df57b9433259f 100644 +--- a/src/main/java/net/minecraft/world/level/block/HopperBlock.java ++++ b/src/main/java/net/minecraft/world/level/block/HopperBlock.java +@@ -183,6 +183,7 @@ public class HopperBlock extends BaseEntityBlock { + + @Override + protected void entityInside(BlockState state, Level world, BlockPos pos, Entity entity) { ++ if (!new io.papermc.paper.event.entity.EntityInsideBlockEvent(entity.getBukkitEntity(), org.bukkit.craftbukkit.block.CraftBlock.at(world, pos)).callEvent()) { return; } // Paper - Add EntityInsideBlockEvent + BlockEntity blockEntity = world.getBlockEntity(pos); + if (blockEntity instanceof HopperBlockEntity) { + HopperBlockEntity.entityInside(world, pos, state, entity, (HopperBlockEntity)blockEntity); +diff --git a/src/main/java/net/minecraft/world/level/block/LavaCauldronBlock.java b/src/main/java/net/minecraft/world/level/block/LavaCauldronBlock.java +index 7bf2c33a194517d4e52511fe32a8434cbed0361f..d29a62775913922ffb8e3c58ae0db7e37f77226e 100644 +--- a/src/main/java/net/minecraft/world/level/block/LavaCauldronBlock.java ++++ b/src/main/java/net/minecraft/world/level/block/LavaCauldronBlock.java +@@ -32,6 +32,7 @@ public class LavaCauldronBlock extends AbstractCauldronBlock { + + @Override + protected void entityInside(BlockState state, Level world, BlockPos pos, Entity entity) { ++ if (!new io.papermc.paper.event.entity.EntityInsideBlockEvent(entity.getBukkitEntity(), org.bukkit.craftbukkit.block.CraftBlock.at(world, pos)).callEvent()) { return; } // Paper - Add EntityInsideBlockEvent + if (this.isEntityInsideContent(state, pos, entity)) { + entity.lavaHurt(); + } +diff --git a/src/main/java/net/minecraft/world/level/block/LayeredCauldronBlock.java b/src/main/java/net/minecraft/world/level/block/LayeredCauldronBlock.java +index c088d713e80f16ead333ea5283f7f95a2de08523..4ab73a083eba2ad3e12526af0a0dbcfba5cf6c14 100644 +--- a/src/main/java/net/minecraft/world/level/block/LayeredCauldronBlock.java ++++ b/src/main/java/net/minecraft/world/level/block/LayeredCauldronBlock.java +@@ -67,6 +67,7 @@ public class LayeredCauldronBlock extends AbstractCauldronBlock { + + @Override + protected void entityInside(BlockState state, Level world, BlockPos pos, Entity entity) { ++ if (!new io.papermc.paper.event.entity.EntityInsideBlockEvent(entity.getBukkitEntity(), org.bukkit.craftbukkit.block.CraftBlock.at(world, pos)).callEvent()) { return; } // Paper - Add EntityInsideBlockEvent + if (world instanceof ServerLevel worldserver) { + if (entity.isOnFire() && this.isEntityInsideContent(state, pos, entity)) { + // CraftBukkit start - moved down +diff --git a/src/main/java/net/minecraft/world/level/block/NetherPortalBlock.java b/src/main/java/net/minecraft/world/level/block/NetherPortalBlock.java +index dea13596eb8a3b7bc69c19545b2227b4c45ed241..c00507fa7af579263caed67dafc2ea9eba09512b 100644 +--- a/src/main/java/net/minecraft/world/level/block/NetherPortalBlock.java ++++ b/src/main/java/net/minecraft/world/level/block/NetherPortalBlock.java +@@ -115,6 +115,7 @@ public class NetherPortalBlock extends Block implements Portal { + + @Override + protected void entityInside(BlockState state, Level world, BlockPos pos, Entity entity) { ++ if (!new io.papermc.paper.event.entity.EntityInsideBlockEvent(entity.getBukkitEntity(), org.bukkit.craftbukkit.block.CraftBlock.at(world, pos)).callEvent()) { return; } // Paper - Add EntityInsideBlockEvent + if (entity.canUsePortal(false)) { + // CraftBukkit start - Entity in portal + EntityPortalEnterEvent event = new EntityPortalEnterEvent(entity.getBukkitEntity(), new org.bukkit.Location(world.getWorld(), pos.getX(), pos.getY(), pos.getZ())); +diff --git a/src/main/java/net/minecraft/world/level/block/PitcherCropBlock.java b/src/main/java/net/minecraft/world/level/block/PitcherCropBlock.java +index 3825b25c7417e4c2e5a25154879199b155a4921f..972d8833127090c01d620cab10b3eca3d3601710 100644 +--- a/src/main/java/net/minecraft/world/level/block/PitcherCropBlock.java ++++ b/src/main/java/net/minecraft/world/level/block/PitcherCropBlock.java +@@ -107,6 +107,7 @@ public class PitcherCropBlock extends DoublePlantBlock implements BonemealableBl + + @Override + public void entityInside(BlockState state, Level world, BlockPos pos, Entity entity) { ++ if (!new io.papermc.paper.event.entity.EntityInsideBlockEvent(entity.getBukkitEntity(), org.bukkit.craftbukkit.block.CraftBlock.at(world, pos)).callEvent()) { return; } // Paper - Add EntityInsideBlockEvent + if (world instanceof ServerLevel serverLevel && entity instanceof Ravager && serverLevel.getGameRules().getBoolean(GameRules.RULE_MOBGRIEFING)) { + serverLevel.destroyBlock(pos, true, entity); + } +diff --git a/src/main/java/net/minecraft/world/level/block/PowderSnowBlock.java b/src/main/java/net/minecraft/world/level/block/PowderSnowBlock.java +index aaf9350a096022c87ccb788d657c1ae6a7b53a47..53f1a7ed6b4bd6e2d8460531226aabf249994c02 100644 +--- a/src/main/java/net/minecraft/world/level/block/PowderSnowBlock.java ++++ b/src/main/java/net/minecraft/world/level/block/PowderSnowBlock.java +@@ -59,6 +59,7 @@ public class PowderSnowBlock extends Block implements BucketPickup { + + @Override + protected void entityInside(BlockState state, Level world, BlockPos pos, Entity entity) { ++ if (!new io.papermc.paper.event.entity.EntityInsideBlockEvent(entity.getBukkitEntity(), org.bukkit.craftbukkit.block.CraftBlock.at(world, pos)).callEvent()) { return; } // Paper - Add EntityInsideBlockEvent + if (!(entity instanceof LivingEntity) || entity.getInBlockState().is((Block) this)) { + entity.makeStuckInBlock(state, new Vec3(0.8999999761581421D, 1.5D, 0.8999999761581421D)); + if (world.isClientSide) { +diff --git a/src/main/java/net/minecraft/world/level/block/SweetBerryBushBlock.java b/src/main/java/net/minecraft/world/level/block/SweetBerryBushBlock.java +index 3cd04097fc698d29d395c0ee28aadaec7c87b5a2..d61173ae5818fe6b507823fd9ddc0584cd5809b9 100644 +--- a/src/main/java/net/minecraft/world/level/block/SweetBerryBushBlock.java ++++ b/src/main/java/net/minecraft/world/level/block/SweetBerryBushBlock.java +@@ -84,6 +84,7 @@ public class SweetBerryBushBlock extends BushBlock implements BonemealableBlock + + @Override + protected void entityInside(BlockState state, Level world, BlockPos pos, Entity entity) { ++ if (!new io.papermc.paper.event.entity.EntityInsideBlockEvent(entity.getBukkitEntity(), org.bukkit.craftbukkit.block.CraftBlock.at(world, pos)).callEvent()) { return; } // Paper - Add EntityInsideBlockEvent + if (entity instanceof LivingEntity && entity.getType() != EntityType.FOX && entity.getType() != EntityType.BEE) { + entity.makeStuckInBlock(state, new Vec3(0.800000011920929D, 0.75D, 0.800000011920929D)); + if (world instanceof ServerLevel) { +diff --git a/src/main/java/net/minecraft/world/level/block/TripWireBlock.java b/src/main/java/net/minecraft/world/level/block/TripWireBlock.java +index 93777f83cdd2de30ccf597fadd8418853954d1ce..f079e5a9aa098225acf09ed9b4aa7ddbc2381270 100644 +--- a/src/main/java/net/minecraft/world/level/block/TripWireBlock.java ++++ b/src/main/java/net/minecraft/world/level/block/TripWireBlock.java +@@ -141,6 +141,7 @@ public class TripWireBlock extends Block { + + @Override + protected void entityInside(BlockState state, Level world, BlockPos pos, Entity entity) { ++ if (!new io.papermc.paper.event.entity.EntityInsideBlockEvent(entity.getBukkitEntity(), org.bukkit.craftbukkit.block.CraftBlock.at(world, pos)).callEvent()) { return; } // Paper - Add EntityInsideBlockEvent + if (!world.isClientSide) { + if (!(Boolean) state.getValue(TripWireBlock.POWERED)) { + this.checkPressed(world, pos, List.of(entity)); +diff --git a/src/main/java/net/minecraft/world/level/block/WaterlilyBlock.java b/src/main/java/net/minecraft/world/level/block/WaterlilyBlock.java +index 34599b88011aff718f5a6373229489ea3d947ff7..674d710ff88db5eced9e017284d1b7ec7a4fe7cd 100644 +--- a/src/main/java/net/minecraft/world/level/block/WaterlilyBlock.java ++++ b/src/main/java/net/minecraft/world/level/block/WaterlilyBlock.java +@@ -33,6 +33,7 @@ public class WaterlilyBlock extends BushBlock { + + @Override + protected void entityInside(BlockState state, Level world, BlockPos pos, Entity entity) { ++ if (!new io.papermc.paper.event.entity.EntityInsideBlockEvent(entity.getBukkitEntity(), org.bukkit.craftbukkit.block.CraftBlock.at(world, pos)).callEvent()) { return; } // Paper - Add EntityInsideBlockEvent + super.entityInside(state, world, pos, entity); + if (world instanceof ServerLevel && entity instanceof AbstractBoat) { + // CraftBukkit start +diff --git a/src/main/java/net/minecraft/world/level/block/WebBlock.java b/src/main/java/net/minecraft/world/level/block/WebBlock.java +index 4b621793da3d6fbc44f90df863b099ba992930fb..fc209fab3ed1ccb35706a5529ec23ad8b902e491 100644 +--- a/src/main/java/net/minecraft/world/level/block/WebBlock.java ++++ b/src/main/java/net/minecraft/world/level/block/WebBlock.java +@@ -24,6 +24,7 @@ public class WebBlock extends Block { + + @Override + protected void entityInside(BlockState state, Level world, BlockPos pos, Entity entity) { ++ if (!new io.papermc.paper.event.entity.EntityInsideBlockEvent(entity.getBukkitEntity(), org.bukkit.craftbukkit.block.CraftBlock.at(world, pos)).callEvent()) { return; } // Paper - Add EntityInsideBlockEvent + Vec3 vec3 = new Vec3(0.25, 0.05F, 0.25); + if (entity instanceof LivingEntity livingEntity && livingEntity.hasEffect(MobEffects.WEAVING)) { + vec3 = new Vec3(0.5, 0.25, 0.5); +diff --git a/src/main/java/net/minecraft/world/level/block/WitherRoseBlock.java b/src/main/java/net/minecraft/world/level/block/WitherRoseBlock.java +index 2f67e26b4c030c1747346f6d02076bd21f11d32e..b481d67e3646242b8eb1b9890e09b56b9406fecb 100644 +--- a/src/main/java/net/minecraft/world/level/block/WitherRoseBlock.java ++++ b/src/main/java/net/minecraft/world/level/block/WitherRoseBlock.java +@@ -63,6 +63,7 @@ public class WitherRoseBlock extends FlowerBlock { + + @Override + protected void entityInside(BlockState state, Level world, BlockPos pos, Entity entity) { ++ if (!new io.papermc.paper.event.entity.EntityInsideBlockEvent(entity.getBukkitEntity(), org.bukkit.craftbukkit.block.CraftBlock.at(world, pos)).callEvent()) { return; } // Paper - Add EntityInsideBlockEvent + if (world instanceof ServerLevel worldserver) { + if (world.getDifficulty() != Difficulty.PEACEFUL && entity instanceof LivingEntity entityliving) { + if (!entityliving.isInvulnerableTo(worldserver, world.damageSources().wither())) { diff --git a/patches/server/0534-Move-range-check-for-block-placing-up.patch b/patches/server/0534-Move-range-check-for-block-placing-up.patch deleted file mode 100644 index 4269998b6e44..000000000000 --- a/patches/server/0534-Move-range-check-for-block-placing-up.patch +++ /dev/null @@ -1,22 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Nassim Jahnke -Date: Wed, 8 Jun 2022 10:52:18 +0200 -Subject: [PATCH] Move range check for block placing up - - -diff --git a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java -index 9781047aa5e0a6e6bc9f9c43a1eb347b6ecdff66..39e74591d3aaa7fecb0ed6f4213a5f4c60360b0e 100644 ---- a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java -+++ b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java -@@ -1745,6 +1745,11 @@ public class ServerGamePacketListenerImpl extends ServerCommonPacketListenerImpl - if (itemstack.isItemEnabled(worldserver.enabledFeatures())) { - BlockHitResult movingobjectpositionblock = packet.getHitResult(); - Vec3 vec3d = movingobjectpositionblock.getLocation(); -+ // Paper start - improve distance check -+ if (!Double.isFinite(vec3d.x) || !Double.isFinite(vec3d.y) || !Double.isFinite(vec3d.z)) { -+ return; -+ } -+ // Paper end - improve distance check - BlockPos blockposition = movingobjectpositionblock.getBlockPos(); - - if (this.player.canInteractWithBlock(blockposition, 1.0D)) { diff --git a/patches/server/0535-Add-Mob-lookAt-API.patch b/patches/server/0535-Add-Mob-lookAt-API.patch deleted file mode 100644 index 3139490a6152..000000000000 --- a/patches/server/0535-Add-Mob-lookAt-API.patch +++ /dev/null @@ -1,64 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: BillyGalbreath -Date: Fri, 14 May 2021 13:42:17 -0500 -Subject: [PATCH] Add Mob#lookAt API - - -diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftMob.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftMob.java -index 2a8596e4f9d7be966c18e867c2c7b5bfbea9742c..60d09655c5b8b9ff289291ee6badfb5aadf8533e 100644 ---- a/src/main/java/org/bukkit/craftbukkit/entity/CraftMob.java -+++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftMob.java -@@ -96,5 +96,53 @@ public abstract class CraftMob extends CraftLivingEntity implements Mob { - public boolean isInDaylight() { - return getHandle().isSunBurnTick(); - } -+ -+ @Override -+ public void lookAt(@org.jetbrains.annotations.NotNull org.bukkit.Location location) { -+ com.google.common.base.Preconditions.checkNotNull(location, "location cannot be null"); -+ com.google.common.base.Preconditions.checkArgument(location.getWorld().equals(getWorld()), "location in a different world"); -+ getHandle().getLookControl().setLookAt(location.getX(), location.getY(), location.getZ()); -+ } -+ -+ @Override -+ public void lookAt(@org.jetbrains.annotations.NotNull org.bukkit.Location location, float headRotationSpeed, float maxHeadPitch) { -+ com.google.common.base.Preconditions.checkNotNull(location, "location cannot be null"); -+ com.google.common.base.Preconditions.checkArgument(location.getWorld().equals(getWorld()), "location in a different world"); -+ getHandle().getLookControl().setLookAt(location.getX(), location.getY(), location.getZ(), headRotationSpeed, maxHeadPitch); -+ } -+ -+ @Override -+ public void lookAt(@org.jetbrains.annotations.NotNull org.bukkit.entity.Entity entity) { -+ com.google.common.base.Preconditions.checkNotNull(entity, "entity cannot be null"); -+ com.google.common.base.Preconditions.checkArgument(entity.getWorld().equals(getWorld()), "entity in a different world"); -+ getHandle().getLookControl().setLookAt(((CraftEntity) entity).getHandle()); -+ } -+ -+ @Override -+ public void lookAt(@org.jetbrains.annotations.NotNull org.bukkit.entity.Entity entity, float headRotationSpeed, float maxHeadPitch) { -+ com.google.common.base.Preconditions.checkNotNull(entity, "entity cannot be null"); -+ com.google.common.base.Preconditions.checkArgument(entity.getWorld().equals(getWorld()), "entity in a different world"); -+ getHandle().getLookControl().setLookAt(((CraftEntity) entity).getHandle(), headRotationSpeed, maxHeadPitch); -+ } -+ -+ @Override -+ public void lookAt(double x, double y, double z) { -+ getHandle().getLookControl().setLookAt(x, y, z); -+ } -+ -+ @Override -+ public void lookAt(double x, double y, double z, float headRotationSpeed, float maxHeadPitch) { -+ getHandle().getLookControl().setLookAt(x, y, z, headRotationSpeed, maxHeadPitch); -+ } -+ -+ @Override -+ public int getHeadRotationSpeed() { -+ return getHandle().getHeadRotSpeed(); -+ } -+ -+ @Override -+ public int getMaxHeadPitch() { -+ return getHandle().getMaxHeadXRot(); -+ } - // Paper end - } diff --git a/patches/server/0535-Improve-item-default-attribute-API.patch b/patches/server/0535-Improve-item-default-attribute-API.patch new file mode 100644 index 000000000000..6f2c104c786a --- /dev/null +++ b/patches/server/0535-Improve-item-default-attribute-API.patch @@ -0,0 +1,80 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Jake Potrebic +Date: Sat, 8 May 2021 15:01:54 -0700 +Subject: [PATCH] Improve item default attribute API + + +diff --git a/src/main/java/org/bukkit/craftbukkit/attribute/CraftAttributeInstance.java b/src/main/java/org/bukkit/craftbukkit/attribute/CraftAttributeInstance.java +index de0eba19c0c963adb4f17cea22333240021fd801..3b171a08bd0bedfe224905feb5838d2540199bce 100644 +--- a/src/main/java/org/bukkit/craftbukkit/attribute/CraftAttributeInstance.java ++++ b/src/main/java/org/bukkit/craftbukkit/attribute/CraftAttributeInstance.java +@@ -75,7 +75,7 @@ public class CraftAttributeInstance implements AttributeInstance { + return new AttributeModifier(CraftNamespacedKey.fromMinecraft(nms.id()), nms.amount(), AttributeModifier.Operation.values()[nms.operation().ordinal()], org.bukkit.inventory.EquipmentSlotGroup.ANY); + } + +- public static AttributeModifier convert(net.minecraft.world.entity.ai.attributes.AttributeModifier nms, EquipmentSlot slot) { +- return new AttributeModifier(CraftNamespacedKey.fromMinecraft(nms.id()), nms.amount(), AttributeModifier.Operation.values()[nms.operation().ordinal()], slot.getGroup()); ++ public static AttributeModifier convert(net.minecraft.world.entity.ai.attributes.AttributeModifier nms, net.minecraft.world.entity.EquipmentSlotGroup slot) { // Paper ++ return new AttributeModifier(CraftNamespacedKey.fromMinecraft(nms.id()), nms.amount(), AttributeModifier.Operation.values()[nms.operation().ordinal()], org.bukkit.craftbukkit.CraftEquipmentSlot.getSlot(slot)); // Paper + } + } +diff --git a/src/main/java/org/bukkit/craftbukkit/inventory/CraftItemType.java b/src/main/java/org/bukkit/craftbukkit/inventory/CraftItemType.java +index 68756419ac6ee292db9569eab380a5c14d748002..6d76cc1db3ac3f1ae74c13511937fb86082a0b3d 100644 +--- a/src/main/java/org/bukkit/craftbukkit/inventory/CraftItemType.java ++++ b/src/main/java/org/bukkit/craftbukkit/inventory/CraftItemType.java +@@ -197,16 +197,33 @@ public class CraftItemType implements ItemType.Typed, Han + // return CraftEquipmentSlot.getSlot(EntityInsentient.getEquipmentSlotForItem(CraftItemStack.asNMSCopy(ItemStack.of(this)))); + // } + ++ // Paper start - improve default attribute API ++ @Override ++ public @NotNull Multimap getDefaultAttributeModifiers() { ++ return this.getDefaultAttributeModifiers(sg -> true); ++ } ++ // Paper end - improve default attribute API ++ + @Override + public Multimap getDefaultAttributeModifiers(EquipmentSlot slot) { ++ // Paper start - improve/fix item default attribute API ++ final net.minecraft.world.entity.EquipmentSlot nmsSlot = CraftEquipmentSlot.getNMS(slot); ++ return this.getDefaultAttributeModifiers(sg -> sg.test(nmsSlot)); ++ } ++ ++ private Multimap getDefaultAttributeModifiers(final java.util.function.Predicate slotPredicate) { ++ // Paper end - improve/fix item default attribute API + ImmutableMultimap.Builder defaultAttributes = ImmutableMultimap.builder(); + + ItemAttributeModifiers nmsDefaultAttributes = this.item.components().getOrDefault(DataComponents.ATTRIBUTE_MODIFIERS, ItemAttributeModifiers.EMPTY); +- +- nmsDefaultAttributes.forEach(CraftEquipmentSlot.getNMS(slot), (key, value) -> { +- Attribute attribute = CraftAttribute.minecraftToBukkit(key.value()); +- defaultAttributes.put(attribute, CraftAttributeInstance.convert(value, slot)); +- }); ++ // Paper start - improve/fix item default attribute API ++ for (final net.minecraft.world.item.component.ItemAttributeModifiers.Entry entry : nmsDefaultAttributes.modifiers()) { ++ if (!slotPredicate.test(entry.slot())) continue; ++ final Attribute attribute = CraftAttribute.minecraftHolderToBukkit(entry.attribute()); ++ final AttributeModifier modifier = CraftAttributeInstance.convert(entry.modifier(), entry.slot()); ++ defaultAttributes.put(attribute, modifier); ++ } ++ // Paper end - improve/fix item default attribute API + + return defaultAttributes.build(); + } +diff --git a/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java b/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java +index 02306ed81a67faa94d98070d3e7c9044cb5d2825..ffc98d8ed238cc653a7a6518a46c4e45a1b3682c 100644 +--- a/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java ++++ b/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java +@@ -385,7 +385,11 @@ public final class CraftMagicNumbers implements UnsafeValues { + + @Override + public Multimap getDefaultAttributeModifiers(Material material, EquipmentSlot slot) { +- return material.getDefaultAttributeModifiers(slot); ++ // Paper start - delegate to method on ItemType ++ final org.bukkit.inventory.ItemType item = material.asItemType(); ++ Preconditions.checkArgument(item != null, material + " is not an item and does not have default attributes"); ++ return item.getDefaultAttributeModifiers(slot); ++ // Paper end - delegate to method on ItemType + } + + @Override diff --git a/patches/server/0536-Add-cause-to-Weather-ThunderChangeEvents.patch b/patches/server/0536-Add-cause-to-Weather-ThunderChangeEvents.patch new file mode 100644 index 000000000000..ef0b70b8d806 --- /dev/null +++ b/patches/server/0536-Add-cause-to-Weather-ThunderChangeEvents.patch @@ -0,0 +1,118 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Jake Potrebic +Date: Wed, 2 Dec 2020 18:23:26 -0800 +Subject: [PATCH] Add cause to Weather/ThunderChangeEvents + + +diff --git a/src/main/java/net/minecraft/server/level/ServerLevel.java b/src/main/java/net/minecraft/server/level/ServerLevel.java +index cefd68cd2f28e5c14dba99b31d9a88125f169337..f58069f0c9d836cb33f3ea09c562708951a91797 100644 +--- a/src/main/java/net/minecraft/server/level/ServerLevel.java ++++ b/src/main/java/net/minecraft/server/level/ServerLevel.java +@@ -429,8 +429,8 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe + this.serverLevelData.setClearWeatherTime(clearDuration); + this.serverLevelData.setRainTime(rainDuration); + this.serverLevelData.setThunderTime(rainDuration); +- this.serverLevelData.setRaining(raining); +- this.serverLevelData.setThundering(thundering); ++ this.serverLevelData.setRaining(raining, org.bukkit.event.weather.WeatherChangeEvent.Cause.COMMAND); // Paper - Add cause to Weather/ThunderChangeEvents ++ this.serverLevelData.setThundering(thundering, org.bukkit.event.weather.ThunderChangeEvent.Cause.COMMAND); // Paper - Add cause to Weather/ThunderChangeEvents + } + + @Override +@@ -846,8 +846,8 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe + this.serverLevelData.setThunderTime(j); + this.serverLevelData.setRainTime(k); + this.serverLevelData.setClearWeatherTime(i); +- this.serverLevelData.setThundering(flag1); +- this.serverLevelData.setRaining(flag2); ++ this.serverLevelData.setThundering(flag1, org.bukkit.event.weather.ThunderChangeEvent.Cause.NATURAL); // Paper - Add cause to Weather/ThunderChangeEvents ++ this.serverLevelData.setRaining(flag2, org.bukkit.event.weather.WeatherChangeEvent.Cause.NATURAL); // Paper - Add cause to Weather/ThunderChangeEvents + } + + this.oThunderLevel = this.thunderLevel; +@@ -914,14 +914,14 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe + @VisibleForTesting + public void resetWeatherCycle() { + // CraftBukkit start +- this.serverLevelData.setRaining(false); ++ this.serverLevelData.setRaining(false, org.bukkit.event.weather.WeatherChangeEvent.Cause.SLEEP); // Paper - Add cause to Weather/ThunderChangeEvents + // If we stop due to everyone sleeping we should reset the weather duration to some other random value. + // Not that everyone ever manages to get the whole server to sleep at the same time.... + if (!this.serverLevelData.isRaining()) { + this.serverLevelData.setRainTime(0); + } + // CraftBukkit end +- this.serverLevelData.setThundering(false); ++ this.serverLevelData.setThundering(false, org.bukkit.event.weather.ThunderChangeEvent.Cause.SLEEP); // Paper - Add cause to Weather/ThunderChangeEvents + // CraftBukkit start + // If we stop due to everyone sleeping we should reset the weather duration to some other random value. + // Not that everyone ever manages to get the whole server to sleep at the same time.... +diff --git a/src/main/java/net/minecraft/world/level/storage/PrimaryLevelData.java b/src/main/java/net/minecraft/world/level/storage/PrimaryLevelData.java +index 6a3959095e57f76b3a092b32d26ff91cf1c5e068..0fa16ff37f09ecfda104b751e48bf246820afc98 100644 +--- a/src/main/java/net/minecraft/world/level/storage/PrimaryLevelData.java ++++ b/src/main/java/net/minecraft/world/level/storage/PrimaryLevelData.java +@@ -337,6 +337,11 @@ public class PrimaryLevelData implements ServerLevelData, WorldData { + + @Override + public void setThundering(boolean thundering) { ++ // Paper start - Add cause to Weather/ThunderChangeEvents ++ this.setThundering(thundering, org.bukkit.event.weather.ThunderChangeEvent.Cause.UNKNOWN); ++ } ++ public void setThundering(boolean thundering, org.bukkit.event.weather.ThunderChangeEvent.Cause cause) { ++ // Paper end - Add cause to Weather/ThunderChangeEvents + // CraftBukkit start + if (this.thundering == thundering) { + return; +@@ -344,7 +349,7 @@ public class PrimaryLevelData implements ServerLevelData, WorldData { + + org.bukkit.World world = Bukkit.getWorld(this.getLevelName()); + if (world != null) { +- ThunderChangeEvent thunder = new ThunderChangeEvent(world, thundering); ++ ThunderChangeEvent thunder = new ThunderChangeEvent(world, thundering, cause); // Paper - Add cause to Weather/ThunderChangeEvents + Bukkit.getServer().getPluginManager().callEvent(thunder); + if (thunder.isCancelled()) { + return; +@@ -371,6 +376,12 @@ public class PrimaryLevelData implements ServerLevelData, WorldData { + + @Override + public void setRaining(boolean raining) { ++ // Paper start - Add cause to Weather/ThunderChangeEvents ++ this.setRaining(raining, org.bukkit.event.weather.WeatherChangeEvent.Cause.UNKNOWN); ++ } ++ ++ public void setRaining(boolean raining, org.bukkit.event.weather.WeatherChangeEvent.Cause cause) { ++ // Paper end - Add cause to Weather/ThunderChangeEvents + // CraftBukkit start + if (this.raining == raining) { + return; +@@ -378,7 +389,7 @@ public class PrimaryLevelData implements ServerLevelData, WorldData { + + org.bukkit.World world = Bukkit.getWorld(this.getLevelName()); + if (world != null) { +- WeatherChangeEvent weather = new WeatherChangeEvent(world, raining); ++ WeatherChangeEvent weather = new WeatherChangeEvent(world, raining, cause); // Paper - Add cause to Weather/ThunderChangeEvents + Bukkit.getServer().getPluginManager().callEvent(weather); + if (weather.isCancelled()) { + return; +diff --git a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java +index e44a78a34aa572b10deb3f343c238cad1c521ab4..07986523451a755515c912e5cab5172195b929de 100644 +--- a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java ++++ b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java +@@ -1220,7 +1220,7 @@ public class CraftWorld extends CraftRegionAccessor implements World { + + @Override + public void setStorm(boolean hasStorm) { +- this.world.levelData.setRaining(hasStorm); ++ this.world.serverLevelData.setRaining(hasStorm, org.bukkit.event.weather.WeatherChangeEvent.Cause.PLUGIN); // Paper - Add cause to Weather/ThunderChangeEvents + this.setWeatherDuration(0); // Reset weather duration (legacy behaviour) + this.setClearWeatherDuration(0); // Reset clear weather duration (reset "/weather clear" commands) + } +@@ -1242,7 +1242,7 @@ public class CraftWorld extends CraftRegionAccessor implements World { + + @Override + public void setThundering(boolean thundering) { +- this.world.serverLevelData.setThundering(thundering); ++ this.world.serverLevelData.setThundering(thundering, org.bukkit.event.weather.ThunderChangeEvent.Cause.PLUGIN); // Paper - Add cause to Weather/ThunderChangeEvents + this.setThunderDuration(0); // Reset weather duration (legacy behaviour) + this.setClearWeatherDuration(0); // Reset clear weather duration (reset "/weather clear" commands) + } diff --git a/patches/server/0536-Correctly-check-if-bucket-dispenses-will-succeed-for.patch b/patches/server/0536-Correctly-check-if-bucket-dispenses-will-succeed-for.patch deleted file mode 100644 index 5a8b0c1cb53e..000000000000 --- a/patches/server/0536-Correctly-check-if-bucket-dispenses-will-succeed-for.patch +++ /dev/null @@ -1,28 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Jake Potrebic -Date: Mon, 1 Jan 2024 12:57:19 -0800 -Subject: [PATCH] Correctly check if bucket dispenses will succeed for event - -Upstream incorrectly checks if the bucket place will succeed -in order to fire the BlockDispenseEvent. This patch corrects -that. - -diff --git a/src/main/java/net/minecraft/core/dispenser/DispenseItemBehavior.java b/src/main/java/net/minecraft/core/dispenser/DispenseItemBehavior.java -index 32f06b29930906cdb6d3ceefa609c1a45518f80f..fb80b00b34ae5a4b1491c618a7fe1bdbbde0de9b 100644 ---- a/src/main/java/net/minecraft/core/dispenser/DispenseItemBehavior.java -+++ b/src/main/java/net/minecraft/core/dispenser/DispenseItemBehavior.java -@@ -405,7 +405,13 @@ public interface DispenseItemBehavior { - int y = blockposition.getY(); - int z = blockposition.getZ(); - BlockState iblockdata = worldserver.getBlockState(blockposition); -- if (iblockdata.isAir() || iblockdata.canBeReplaced() || (dispensiblecontaineritem instanceof BucketItem && iblockdata.getBlock() instanceof LiquidBlockContainer && ((LiquidBlockContainer) iblockdata.getBlock()).canPlaceLiquid((Player) null, worldserver, blockposition, iblockdata, ((BucketItem) dispensiblecontaineritem).content))) { -+ // Paper start - correctly check if the bucket place will succeed -+ /* Taken from SolidBucketItem#emptyContents */ -+ boolean willEmptyContentsSolidBucketItem = dispensiblecontaineritem instanceof net.minecraft.world.item.SolidBucketItem && worldserver.isInWorldBounds(blockposition) && iblockdata.isAir(); -+ /* Taken from BucketItem#emptyContents */ -+ boolean willEmptyBucketItem = dispensiblecontaineritem instanceof final BucketItem bucketItem && bucketItem.content instanceof net.minecraft.world.level.material.FlowingFluid && (iblockdata.isAir() || iblockdata.canBeReplaced(bucketItem.content) || (iblockdata.getBlock() instanceof LiquidBlockContainer liquidBlockContainer && liquidBlockContainer.canPlaceLiquid(null, worldserver, blockposition, iblockdata, bucketItem.content))); -+ if (willEmptyContentsSolidBucketItem || willEmptyBucketItem) { -+ // Paper end - correctly check if the bucket place will succeed - org.bukkit.block.Block block = CraftBlock.at(worldserver, pointer.pos()); - CraftItemStack craftItem = CraftItemStack.asCraftMirror(stack); - diff --git a/patches/server/0537-Add-Unix-domain-socket-support.patch b/patches/server/0537-Add-Unix-domain-socket-support.patch deleted file mode 100644 index 7c44f07cea27..000000000000 --- a/patches/server/0537-Add-Unix-domain-socket-support.patch +++ /dev/null @@ -1,137 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Andrew Steinborn -Date: Tue, 11 May 2021 17:39:22 -0400 -Subject: [PATCH] Add Unix domain socket support - - -diff --git a/src/main/java/net/minecraft/server/dedicated/DedicatedServer.java b/src/main/java/net/minecraft/server/dedicated/DedicatedServer.java -index 4057ade698a227b4f6efd3aa30b16d78c777be83..adbd61c41cc30afa89c6ee3544c562b351304a01 100644 ---- a/src/main/java/net/minecraft/server/dedicated/DedicatedServer.java -+++ b/src/main/java/net/minecraft/server/dedicated/DedicatedServer.java -@@ -236,6 +236,20 @@ public class DedicatedServer extends MinecraftServer implements ServerInterface - this.setEnforceWhitelist(dedicatedserverproperties.enforceWhitelist); - // this.worldData.setGameType(dedicatedserverproperties.gamemode); // CraftBukkit - moved to world loading - DedicatedServer.LOGGER.info("Default game type: {}", dedicatedserverproperties.gamemode); -+ // Paper start - Unix domain socket support -+ java.net.SocketAddress bindAddress; -+ if (this.getLocalIp().startsWith("unix:")) { -+ if (!io.netty.channel.epoll.Epoll.isAvailable()) { -+ DedicatedServer.LOGGER.error("**** INVALID CONFIGURATION!"); -+ DedicatedServer.LOGGER.error("You are trying to use a Unix domain socket but you're not on a supported OS."); -+ return false; -+ } else if (!io.papermc.paper.configuration.GlobalConfiguration.get().proxies.velocity.enabled && !org.spigotmc.SpigotConfig.bungee) { -+ DedicatedServer.LOGGER.error("**** INVALID CONFIGURATION!"); -+ DedicatedServer.LOGGER.error("Unix domain sockets require IPs to be forwarded from a proxy."); -+ return false; -+ } -+ bindAddress = new io.netty.channel.unix.DomainSocketAddress(this.getLocalIp().substring("unix:".length())); -+ } else { - InetAddress inetaddress = null; - - if (!this.getLocalIp().isEmpty()) { -@@ -245,12 +259,15 @@ public class DedicatedServer extends MinecraftServer implements ServerInterface - if (this.getPort() < 0) { - this.setPort(dedicatedserverproperties.serverPort); - } -+ bindAddress = new java.net.InetSocketAddress(inetaddress, this.getPort()); -+ } -+ // Paper end - Unix domain socket support - - this.initializeKeyPair(); - DedicatedServer.LOGGER.info("Starting Minecraft server on {}:{}", this.getLocalIp().isEmpty() ? "*" : this.getLocalIp(), this.getPort()); - - try { -- this.getConnection().startTcpServerListener(inetaddress, this.getPort()); -+ this.getConnection().bind(bindAddress); // Paper - Unix domain socket support - } catch (IOException ioexception) { - DedicatedServer.LOGGER.warn("**** FAILED TO BIND TO PORT!"); - DedicatedServer.LOGGER.warn("The exception was: {}", ioexception.toString()); -diff --git a/src/main/java/net/minecraft/server/network/ServerConnectionListener.java b/src/main/java/net/minecraft/server/network/ServerConnectionListener.java -index d6d7f1c446ba5507f67038ff27775ba75156f4a7..c63c194c44646e6bc1a59426552787011fc2ced5 100644 ---- a/src/main/java/net/minecraft/server/network/ServerConnectionListener.java -+++ b/src/main/java/net/minecraft/server/network/ServerConnectionListener.java -@@ -76,7 +76,12 @@ public class ServerConnectionListener { - this.running = true; - } - -+ // Paper start - Unix domain socket support - public void startTcpServerListener(@Nullable InetAddress address, int port) throws IOException { -+ bind(new java.net.InetSocketAddress(address, port)); -+ } -+ public void bind(java.net.SocketAddress address) throws IOException { -+ // Paper end - Unix domain socket support - List list = this.channels; - - synchronized (this.channels) { -@@ -84,7 +89,13 @@ public class ServerConnectionListener { - EventLoopGroup eventloopgroup; - - if (Epoll.isAvailable() && this.server.isEpollEnabled()) { -+ // Paper start - Unix domain socket support -+ if (address instanceof io.netty.channel.unix.DomainSocketAddress) { -+ oclass = io.netty.channel.epoll.EpollServerDomainSocketChannel.class; -+ } else { - oclass = EpollServerSocketChannel.class; -+ } -+ // Paper end - Unix domain socket support - eventloopgroup = (EventLoopGroup) ServerConnectionListener.SERVER_EPOLL_EVENT_GROUP.get(); - ServerConnectionListener.LOGGER.info("Using epoll channel type"); - } else { -@@ -117,7 +128,7 @@ public class ServerConnectionListener { - ((Connection) object).setListenerForServerboundHandshake(new ServerHandshakePacketListenerImpl(ServerConnectionListener.this.server, (Connection) object)); - io.papermc.paper.network.ChannelInitializeListenerHolder.callListeners(channel); // Paper - Add Channel initialization listeners - } -- }).group(eventloopgroup).localAddress(address, port)).option(ChannelOption.AUTO_READ, false).bind().syncUninterruptibly()); // CraftBukkit -+ }).group(eventloopgroup).localAddress(address)).option(ChannelOption.AUTO_READ, false).bind().syncUninterruptibly()); // CraftBukkit // Paper - Unix domain socket support - } - } - -diff --git a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java -index 39e74591d3aaa7fecb0ed6f4213a5f4c60360b0e..568f5d7165521304c7a92f32984a1d605d545ad5 100644 ---- a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java -+++ b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java -@@ -2550,6 +2550,11 @@ public class ServerGamePacketListenerImpl extends ServerCommonPacketListenerImpl - // Spigot Start - public SocketAddress getRawAddress() - { -+ // Paper start - Unix domain socket support; this can be nullable in the case of a Unix domain socket, so if it is, fake something -+ if (connection.channel.remoteAddress() == null) { -+ return new java.net.InetSocketAddress(java.net.InetAddress.getLoopbackAddress(), 0); -+ } -+ // Paper end - Unix domain socket support - return this.connection.channel.remoteAddress(); - } - // Spigot End -diff --git a/src/main/java/net/minecraft/server/network/ServerHandshakePacketListenerImpl.java b/src/main/java/net/minecraft/server/network/ServerHandshakePacketListenerImpl.java -index a5bbea6a073e00c10c3c5facd997eb8473fd9a5f..ddf42645402afefc0f5caebc684b191eef9d6ec2 100644 ---- a/src/main/java/net/minecraft/server/network/ServerHandshakePacketListenerImpl.java -+++ b/src/main/java/net/minecraft/server/network/ServerHandshakePacketListenerImpl.java -@@ -81,6 +81,7 @@ public class ServerHandshakePacketListenerImpl implements ServerHandshakePacketL - this.connection.setupOutboundProtocol(LoginProtocols.CLIENTBOUND); - // CraftBukkit start - Connection throttle - try { -+ if (!(this.connection.channel.localAddress() instanceof io.netty.channel.unix.DomainSocketAddress)) { // Paper - Unix domain socket support; the connection throttle is useless when you have a Unix domain socket - long currentTime = System.currentTimeMillis(); - long connectionThrottle = this.server.server.getConnectionThrottle(); - InetAddress address = ((java.net.InetSocketAddress) this.connection.getRemoteAddress()).getAddress(); -@@ -109,6 +110,7 @@ public class ServerHandshakePacketListenerImpl implements ServerHandshakePacketL - } - } - } -+ } // Paper - Unix domain socket support - } catch (Throwable t) { - org.apache.logging.log4j.LogManager.getLogger().debug("Failed to check connection throttle", t); - } -@@ -166,8 +168,11 @@ public class ServerHandshakePacketListenerImpl implements ServerHandshakePacketL - if (!handledByEvent && proxyLogicEnabled) { // Paper - // if (org.spigotmc.SpigotConfig.bungee) { // Paper - comment out, we check above! - if ( ( split.length == 3 || split.length == 4 ) && ( ServerHandshakePacketListenerImpl.BYPASS_HOSTCHECK || ServerHandshakePacketListenerImpl.HOST_PATTERN.matcher( split[1] ).matches() ) ) { // Paper - Add bypass host check -+ // Paper start - Unix domain socket support -+ java.net.SocketAddress socketAddress = this.connection.getRemoteAddress(); - this.connection.hostname = split[0]; -- this.connection.address = new java.net.InetSocketAddress(split[1], ((java.net.InetSocketAddress) this.connection.getRemoteAddress()).getPort()); -+ this.connection.address = new java.net.InetSocketAddress(split[1], socketAddress instanceof java.net.InetSocketAddress ? ((java.net.InetSocketAddress) socketAddress).getPort() : 0); -+ // Paper end - Unix domain socket support - this.connection.spoofedUUID = com.mojang.util.UndashedUuid.fromStringLenient( split[2] ); - } else - { diff --git a/patches/server/0541-More-Lidded-Block-API.patch b/patches/server/0537-More-Lidded-Block-API.patch similarity index 100% rename from patches/server/0541-More-Lidded-Block-API.patch rename to patches/server/0537-More-Lidded-Block-API.patch diff --git a/patches/server/0538-Add-EntityInsideBlockEvent.patch b/patches/server/0538-Add-EntityInsideBlockEvent.patch deleted file mode 100644 index 0b2938737cb8..000000000000 --- a/patches/server/0538-Add-EntityInsideBlockEvent.patch +++ /dev/null @@ -1,294 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Jake Potrebic -Date: Sat, 8 May 2021 18:02:36 -0700 -Subject: [PATCH] Add EntityInsideBlockEvent - - -diff --git a/src/main/java/net/minecraft/world/level/block/BaseFireBlock.java b/src/main/java/net/minecraft/world/level/block/BaseFireBlock.java -index 779d188bac9744889c1f4f554b300311e0effa29..0c5409af685ef1f251db3d9f9e21295c82a1e02a 100644 ---- a/src/main/java/net/minecraft/world/level/block/BaseFireBlock.java -+++ b/src/main/java/net/minecraft/world/level/block/BaseFireBlock.java -@@ -125,6 +125,7 @@ public abstract class BaseFireBlock extends Block { - - @Override - protected void entityInside(BlockState state, Level world, BlockPos pos, Entity entity) { -+ if (!new io.papermc.paper.event.entity.EntityInsideBlockEvent(entity.getBukkitEntity(), org.bukkit.craftbukkit.block.CraftBlock.at(world, pos)).callEvent()) { return; } // Paper - Add EntityInsideBlockEvent - if (!entity.fireImmune()) { - entity.setRemainingFireTicks(entity.getRemainingFireTicks() + 1); - if (entity.getRemainingFireTicks() == 0) { -diff --git a/src/main/java/net/minecraft/world/level/block/BasePressurePlateBlock.java b/src/main/java/net/minecraft/world/level/block/BasePressurePlateBlock.java -index 2b6e126f57cf67cd719f67e925fee97b2485f73f..8b33e35c843e5c0b8988a2ef2a38a2673035292f 100644 ---- a/src/main/java/net/minecraft/world/level/block/BasePressurePlateBlock.java -+++ b/src/main/java/net/minecraft/world/level/block/BasePressurePlateBlock.java -@@ -77,6 +77,7 @@ public abstract class BasePressurePlateBlock extends Block { - - @Override - protected void entityInside(BlockState state, Level world, BlockPos pos, Entity entity) { -+ if (!new io.papermc.paper.event.entity.EntityInsideBlockEvent(entity.getBukkitEntity(), org.bukkit.craftbukkit.block.CraftBlock.at(world, pos)).callEvent()) { return; } // Paper - Add EntityInsideBlockEvent - if (!world.isClientSide) { - int i = this.getSignalForState(state); - -diff --git a/src/main/java/net/minecraft/world/level/block/BigDripleafBlock.java b/src/main/java/net/minecraft/world/level/block/BigDripleafBlock.java -index c1f020efdc5a1170fc43b2579531be8bdcacc83b..8240c32d676a88aa23dcd052ee0136767e54fb0d 100644 ---- a/src/main/java/net/minecraft/world/level/block/BigDripleafBlock.java -+++ b/src/main/java/net/minecraft/world/level/block/BigDripleafBlock.java -@@ -178,6 +178,7 @@ public class BigDripleafBlock extends HorizontalDirectionalBlock implements Bone - - @Override - protected void entityInside(BlockState state, Level world, BlockPos pos, Entity entity) { -+ if (!new io.papermc.paper.event.entity.EntityInsideBlockEvent(entity.getBukkitEntity(), org.bukkit.craftbukkit.block.CraftBlock.at(world, pos)).callEvent()) { return; } // Paper - Add EntityInsideBlockEvent - if (!world.isClientSide) { - if (state.getValue(BigDripleafBlock.TILT) == Tilt.NONE && BigDripleafBlock.canEntityTilt(pos, entity) && !world.hasNeighborSignal(pos)) { - // CraftBukkit start - tilt dripleaf -diff --git a/src/main/java/net/minecraft/world/level/block/BubbleColumnBlock.java b/src/main/java/net/minecraft/world/level/block/BubbleColumnBlock.java -index 7c70c98ee27a6a09e73942ff4dc0f88ceb77936f..4c1f20fafdbd86011959cc2d4983b6c2f8e87a5f 100644 ---- a/src/main/java/net/minecraft/world/level/block/BubbleColumnBlock.java -+++ b/src/main/java/net/minecraft/world/level/block/BubbleColumnBlock.java -@@ -47,6 +47,7 @@ public class BubbleColumnBlock extends Block implements BucketPickup { - - @Override - protected void entityInside(BlockState state, Level world, BlockPos pos, Entity entity) { -+ if (!new io.papermc.paper.event.entity.EntityInsideBlockEvent(entity.getBukkitEntity(), org.bukkit.craftbukkit.block.CraftBlock.at(world, pos)).callEvent()) { return; } // Paper - Add EntityInsideBlockEvent - BlockState blockState = world.getBlockState(pos.above()); - if (blockState.isAir()) { - entity.onAboveBubbleCol(state.getValue(DRAG_DOWN)); -diff --git a/src/main/java/net/minecraft/world/level/block/ButtonBlock.java b/src/main/java/net/minecraft/world/level/block/ButtonBlock.java -index 4a58bf1081e57bd34858481dee824e2a75120281..4c37d9ebd74b32b9ad6dc46d000afc7a7d1bb4a3 100644 ---- a/src/main/java/net/minecraft/world/level/block/ButtonBlock.java -+++ b/src/main/java/net/minecraft/world/level/block/ButtonBlock.java -@@ -206,6 +206,7 @@ public class ButtonBlock extends FaceAttachedHorizontalDirectionalBlock { - - @Override - protected void entityInside(BlockState state, Level world, BlockPos pos, Entity entity) { -+ if (!new io.papermc.paper.event.entity.EntityInsideBlockEvent(entity.getBukkitEntity(), org.bukkit.craftbukkit.block.CraftBlock.at(world, pos)).callEvent()) { return; } // Paper - Add EntityInsideBlockEvent - if (!world.isClientSide && this.type.canButtonBeActivatedByArrows() && !(Boolean) state.getValue(ButtonBlock.POWERED)) { - this.checkPressed(state, world, pos); - } -diff --git a/src/main/java/net/minecraft/world/level/block/CactusBlock.java b/src/main/java/net/minecraft/world/level/block/CactusBlock.java -index fd344c5cf0d6d523abe34d5e3f8d939106942cbb..ff4dda48116a2969704b355ff96407ba869b466e 100644 ---- a/src/main/java/net/minecraft/world/level/block/CactusBlock.java -+++ b/src/main/java/net/minecraft/world/level/block/CactusBlock.java -@@ -121,6 +121,7 @@ public class CactusBlock extends Block { - - @Override - protected void entityInside(BlockState state, Level world, BlockPos pos, Entity entity) { -+ if (!new io.papermc.paper.event.entity.EntityInsideBlockEvent(entity.getBukkitEntity(), org.bukkit.craftbukkit.block.CraftBlock.at(world, pos)).callEvent()) { return; } // Paper - Add EntityInsideBlockEvent - entity.hurt(world.damageSources().cactus().directBlock(world, pos), 1.0F); // CraftBukkit - } - -diff --git a/src/main/java/net/minecraft/world/level/block/CampfireBlock.java b/src/main/java/net/minecraft/world/level/block/CampfireBlock.java -index e81bf62cf9dbc27391deaad46d2098e81f746746..7f6058f4def83867971121751acd51c398583651 100644 ---- a/src/main/java/net/minecraft/world/level/block/CampfireBlock.java -+++ b/src/main/java/net/minecraft/world/level/block/CampfireBlock.java -@@ -104,6 +104,7 @@ public class CampfireBlock extends BaseEntityBlock implements SimpleWaterloggedB - - @Override - protected void entityInside(BlockState state, Level world, BlockPos pos, Entity entity) { -+ if (!new io.papermc.paper.event.entity.EntityInsideBlockEvent(entity.getBukkitEntity(), org.bukkit.craftbukkit.block.CraftBlock.at(world, pos)).callEvent()) { return; } // Paper - Add EntityInsideBlockEvent - if ((Boolean) state.getValue(CampfireBlock.LIT) && entity instanceof LivingEntity) { - entity.hurt(world.damageSources().campfire().directBlock(world, pos), (float) this.fireDamage); // CraftBukkit - } -diff --git a/src/main/java/net/minecraft/world/level/block/CropBlock.java b/src/main/java/net/minecraft/world/level/block/CropBlock.java -index fcaf6b4bb7371ce9f00a7d4306f7b2b6884b7c4c..73595922dcff8e7a8595fcf033ab238bc4096630 100644 ---- a/src/main/java/net/minecraft/world/level/block/CropBlock.java -+++ b/src/main/java/net/minecraft/world/level/block/CropBlock.java -@@ -174,6 +174,7 @@ public class CropBlock extends BushBlock implements BonemealableBlock { - - @Override - protected void entityInside(BlockState state, Level world, BlockPos pos, Entity entity) { -+ if (!new io.papermc.paper.event.entity.EntityInsideBlockEvent(entity.getBukkitEntity(), org.bukkit.craftbukkit.block.CraftBlock.at(world, pos)).callEvent()) { return; } // Paper - Add EntityInsideBlockEvent - if (entity instanceof Ravager && CraftEventFactory.callEntityChangeBlockEvent(entity, pos, Blocks.AIR.defaultBlockState(), !world.getGameRules().getBoolean(GameRules.RULE_MOBGRIEFING))) { // CraftBukkit - world.destroyBlock(pos, true, entity); - } -diff --git a/src/main/java/net/minecraft/world/level/block/DetectorRailBlock.java b/src/main/java/net/minecraft/world/level/block/DetectorRailBlock.java -index 55a97da8786ec0ae98abe56876c00f4678ba0007..9d69e439ff853465303c2abd896e6c5314752e1e 100644 ---- a/src/main/java/net/minecraft/world/level/block/DetectorRailBlock.java -+++ b/src/main/java/net/minecraft/world/level/block/DetectorRailBlock.java -@@ -51,6 +51,7 @@ public class DetectorRailBlock extends BaseRailBlock { - - @Override - protected void entityInside(BlockState state, Level world, BlockPos pos, Entity entity) { -+ if (!new io.papermc.paper.event.entity.EntityInsideBlockEvent(entity.getBukkitEntity(), org.bukkit.craftbukkit.block.CraftBlock.at(world, pos)).callEvent()) { return; } // Paper - Add EntityInsideBlockEvent - if (!world.isClientSide) { - if (!(Boolean) state.getValue(DetectorRailBlock.POWERED)) { - this.checkPressed(world, pos, state); -diff --git a/src/main/java/net/minecraft/world/level/block/EndGatewayBlock.java b/src/main/java/net/minecraft/world/level/block/EndGatewayBlock.java -index 2ad77d7666cc9a5101b3da58f07f497409676a26..11486419dd98a013c7387d3d73f322a95a18c574 100644 ---- a/src/main/java/net/minecraft/world/level/block/EndGatewayBlock.java -+++ b/src/main/java/net/minecraft/world/level/block/EndGatewayBlock.java -@@ -90,6 +90,7 @@ public class EndGatewayBlock extends BaseEntityBlock implements Portal { - - @Override - protected void entityInside(BlockState state, Level world, BlockPos pos, Entity entity) { -+ if (!new io.papermc.paper.event.entity.EntityInsideBlockEvent(entity.getBukkitEntity(), org.bukkit.craftbukkit.block.CraftBlock.at(world, pos)).callEvent()) { return; } // Paper - Add EntityInsideBlockEvent - if (entity.canUsePortal(false)) { - BlockEntity tileentity = world.getBlockEntity(pos); - -diff --git a/src/main/java/net/minecraft/world/level/block/EndPortalBlock.java b/src/main/java/net/minecraft/world/level/block/EndPortalBlock.java -index a9c7a74b38a57c118c1ad67a77ba6f2e5c05d91e..28fba1448309805fc3d687de6bc8454d2c85fcd3 100644 ---- a/src/main/java/net/minecraft/world/level/block/EndPortalBlock.java -+++ b/src/main/java/net/minecraft/world/level/block/EndPortalBlock.java -@@ -63,6 +63,7 @@ public class EndPortalBlock extends BaseEntityBlock implements Portal { - - @Override - protected void entityInside(BlockState state, Level world, BlockPos pos, Entity entity) { -+ if (!new io.papermc.paper.event.entity.EntityInsideBlockEvent(entity.getBukkitEntity(), org.bukkit.craftbukkit.block.CraftBlock.at(world, pos)).callEvent()) { return; } // Paper - Add EntityInsideBlockEvent - if (entity.canUsePortal(false) && Shapes.joinIsNotEmpty(Shapes.create(entity.getBoundingBox().move((double) (-pos.getX()), (double) (-pos.getY()), (double) (-pos.getZ()))), state.getShape(world, pos), BooleanOp.AND)) { - // CraftBukkit start - Entity in portal - EntityPortalEnterEvent event = new EntityPortalEnterEvent(entity.getBukkitEntity(), new org.bukkit.Location(world.getWorld(), pos.getX(), pos.getY(), pos.getZ())); -diff --git a/src/main/java/net/minecraft/world/level/block/FrogspawnBlock.java b/src/main/java/net/minecraft/world/level/block/FrogspawnBlock.java -index b968129b9a93fdf771caba5f768456070543ba6a..669234bca9fa50548447f77dc5f314df8d9dd7c9 100644 ---- a/src/main/java/net/minecraft/world/level/block/FrogspawnBlock.java -+++ b/src/main/java/net/minecraft/world/level/block/FrogspawnBlock.java -@@ -79,6 +79,7 @@ public class FrogspawnBlock extends Block { - - @Override - protected void entityInside(BlockState state, Level world, BlockPos pos, Entity entity) { -+ if (!new io.papermc.paper.event.entity.EntityInsideBlockEvent(entity.getBukkitEntity(), org.bukkit.craftbukkit.block.CraftBlock.at(world, pos)).callEvent()) { return; } // Paper - Add EntityInsideBlockEvent - if (entity.getType().equals(EntityType.FALLING_BLOCK)) { - this.destroyBlock(world, pos); - } -diff --git a/src/main/java/net/minecraft/world/level/block/HoneyBlock.java b/src/main/java/net/minecraft/world/level/block/HoneyBlock.java -index 1ed883c62be357d32365ef182e391e96172d2b56..c6f7815b5fad3aad4635208aa2e5c6739e13cb45 100644 ---- a/src/main/java/net/minecraft/world/level/block/HoneyBlock.java -+++ b/src/main/java/net/minecraft/world/level/block/HoneyBlock.java -@@ -60,6 +60,7 @@ public class HoneyBlock extends HalfTransparentBlock { - - @Override - protected void entityInside(BlockState state, Level world, BlockPos pos, Entity entity) { -+ if (!new io.papermc.paper.event.entity.EntityInsideBlockEvent(entity.getBukkitEntity(), org.bukkit.craftbukkit.block.CraftBlock.at(world, pos)).callEvent()) { return; } // Paper - Add EntityInsideBlockEvent - if (this.isSlidingDown(pos, entity)) { - this.maybeDoSlideAchievement(entity, pos); - this.doSlideMovement(entity); -diff --git a/src/main/java/net/minecraft/world/level/block/HopperBlock.java b/src/main/java/net/minecraft/world/level/block/HopperBlock.java -index 089ead2d55c4fbe361255391f553822715269c38..86e5617d445ce762aa374e236a0ccdfe5901fce5 100644 ---- a/src/main/java/net/minecraft/world/level/block/HopperBlock.java -+++ b/src/main/java/net/minecraft/world/level/block/HopperBlock.java -@@ -187,6 +187,7 @@ public class HopperBlock extends BaseEntityBlock { - - @Override - protected void entityInside(BlockState state, Level world, BlockPos pos, Entity entity) { -+ if (!new io.papermc.paper.event.entity.EntityInsideBlockEvent(entity.getBukkitEntity(), org.bukkit.craftbukkit.block.CraftBlock.at(world, pos)).callEvent()) { return; } // Paper - Add EntityInsideBlockEvent - BlockEntity blockEntity = world.getBlockEntity(pos); - if (blockEntity instanceof HopperBlockEntity) { - HopperBlockEntity.entityInside(world, pos, state, entity, (HopperBlockEntity)blockEntity); -diff --git a/src/main/java/net/minecraft/world/level/block/LavaCauldronBlock.java b/src/main/java/net/minecraft/world/level/block/LavaCauldronBlock.java -index 7bf2c33a194517d4e52511fe32a8434cbed0361f..d29a62775913922ffb8e3c58ae0db7e37f77226e 100644 ---- a/src/main/java/net/minecraft/world/level/block/LavaCauldronBlock.java -+++ b/src/main/java/net/minecraft/world/level/block/LavaCauldronBlock.java -@@ -32,6 +32,7 @@ public class LavaCauldronBlock extends AbstractCauldronBlock { - - @Override - protected void entityInside(BlockState state, Level world, BlockPos pos, Entity entity) { -+ if (!new io.papermc.paper.event.entity.EntityInsideBlockEvent(entity.getBukkitEntity(), org.bukkit.craftbukkit.block.CraftBlock.at(world, pos)).callEvent()) { return; } // Paper - Add EntityInsideBlockEvent - if (this.isEntityInsideContent(state, pos, entity)) { - entity.lavaHurt(); - } -diff --git a/src/main/java/net/minecraft/world/level/block/LayeredCauldronBlock.java b/src/main/java/net/minecraft/world/level/block/LayeredCauldronBlock.java -index 5f778ea22efa76ced1ba4455d50b94b3519113fc..7c67efa6e344870b764eb39d5508190349e2e911 100644 ---- a/src/main/java/net/minecraft/world/level/block/LayeredCauldronBlock.java -+++ b/src/main/java/net/minecraft/world/level/block/LayeredCauldronBlock.java -@@ -66,6 +66,7 @@ public class LayeredCauldronBlock extends AbstractCauldronBlock { - - @Override - protected void entityInside(BlockState state, Level world, BlockPos pos, Entity entity) { -+ if (!new io.papermc.paper.event.entity.EntityInsideBlockEvent(entity.getBukkitEntity(), org.bukkit.craftbukkit.block.CraftBlock.at(world, pos)).callEvent()) { return; } // Paper - Add EntityInsideBlockEvent - if (!world.isClientSide && entity.isOnFire() && this.isEntityInsideContent(state, pos, entity)) { - // CraftBukkit start - if (entity.mayInteract(world, pos)) { -diff --git a/src/main/java/net/minecraft/world/level/block/NetherPortalBlock.java b/src/main/java/net/minecraft/world/level/block/NetherPortalBlock.java -index a4e1c878dc7677e8ccc7c568ab455e85513d86cb..e2969b49494c55c3705312361ee8083b05ef569e 100644 ---- a/src/main/java/net/minecraft/world/level/block/NetherPortalBlock.java -+++ b/src/main/java/net/minecraft/world/level/block/NetherPortalBlock.java -@@ -107,6 +107,7 @@ public class NetherPortalBlock extends Block implements Portal { - - @Override - protected void entityInside(BlockState state, Level world, BlockPos pos, Entity entity) { -+ if (!new io.papermc.paper.event.entity.EntityInsideBlockEvent(entity.getBukkitEntity(), org.bukkit.craftbukkit.block.CraftBlock.at(world, pos)).callEvent()) { return; } // Paper - Add EntityInsideBlockEvent - if (entity.canUsePortal(false)) { - // CraftBukkit start - Entity in portal - EntityPortalEnterEvent event = new EntityPortalEnterEvent(entity.getBukkitEntity(), new org.bukkit.Location(world.getWorld(), pos.getX(), pos.getY(), pos.getZ())); -diff --git a/src/main/java/net/minecraft/world/level/block/PitcherCropBlock.java b/src/main/java/net/minecraft/world/level/block/PitcherCropBlock.java -index 15696602249465fa1d13eaec3b8b06574ec06f07..c9ed129db2cadd0a33d69993961f43088725c3cb 100644 ---- a/src/main/java/net/minecraft/world/level/block/PitcherCropBlock.java -+++ b/src/main/java/net/minecraft/world/level/block/PitcherCropBlock.java -@@ -98,6 +98,7 @@ public class PitcherCropBlock extends DoublePlantBlock implements BonemealableBl - - @Override - public void entityInside(BlockState state, Level world, BlockPos pos, Entity entity) { -+ if (!new io.papermc.paper.event.entity.EntityInsideBlockEvent(entity.getBukkitEntity(), org.bukkit.craftbukkit.block.CraftBlock.at(world, pos)).callEvent()) { return; } // Paper - Add EntityInsideBlockEvent - if (entity instanceof Ravager && world.getGameRules().getBoolean(GameRules.RULE_MOBGRIEFING)) { - world.destroyBlock(pos, true, entity); - } -diff --git a/src/main/java/net/minecraft/world/level/block/PowderSnowBlock.java b/src/main/java/net/minecraft/world/level/block/PowderSnowBlock.java -index 1f9cab69fb19c96fb01fff0d7a5ecfdfff46a5d4..a6e6545402904141ffc6218a0158b0e9c67217c8 100644 ---- a/src/main/java/net/minecraft/world/level/block/PowderSnowBlock.java -+++ b/src/main/java/net/minecraft/world/level/block/PowderSnowBlock.java -@@ -63,6 +63,7 @@ public class PowderSnowBlock extends Block implements BucketPickup { - - @Override - protected void entityInside(BlockState state, Level world, BlockPos pos, Entity entity) { -+ if (!new io.papermc.paper.event.entity.EntityInsideBlockEvent(entity.getBukkitEntity(), org.bukkit.craftbukkit.block.CraftBlock.at(world, pos)).callEvent()) { return; } // Paper - Add EntityInsideBlockEvent - if (!(entity instanceof LivingEntity) || entity.getInBlockState().is((Block) this)) { - entity.makeStuckInBlock(state, new Vec3(0.8999999761581421D, 1.5D, 0.8999999761581421D)); - if (world.isClientSide) { -diff --git a/src/main/java/net/minecraft/world/level/block/SweetBerryBushBlock.java b/src/main/java/net/minecraft/world/level/block/SweetBerryBushBlock.java -index 49fce6000ffafc6f53767c971db951b20261b91d..6008c634c408c4eed563815da4d57b2eef69835c 100644 ---- a/src/main/java/net/minecraft/world/level/block/SweetBerryBushBlock.java -+++ b/src/main/java/net/minecraft/world/level/block/SweetBerryBushBlock.java -@@ -85,6 +85,7 @@ public class SweetBerryBushBlock extends BushBlock implements BonemealableBlock - - @Override - protected void entityInside(BlockState state, Level world, BlockPos pos, Entity entity) { -+ if (!new io.papermc.paper.event.entity.EntityInsideBlockEvent(entity.getBukkitEntity(), org.bukkit.craftbukkit.block.CraftBlock.at(world, pos)).callEvent()) { return; } // Paper - Add EntityInsideBlockEvent - if (entity instanceof LivingEntity && entity.getType() != EntityType.FOX && entity.getType() != EntityType.BEE) { - entity.makeStuckInBlock(state, new Vec3(0.800000011920929D, 0.75D, 0.800000011920929D)); - if (!world.isClientSide && (Integer) state.getValue(SweetBerryBushBlock.AGE) > 0 && (entity.xOld != entity.getX() || entity.zOld != entity.getZ())) { -diff --git a/src/main/java/net/minecraft/world/level/block/TripWireBlock.java b/src/main/java/net/minecraft/world/level/block/TripWireBlock.java -index 65b30286020b65e23d05307aade344cde827fcf2..e032d8907045c653c3dd449f65e93e40fd0bb6be 100644 ---- a/src/main/java/net/minecraft/world/level/block/TripWireBlock.java -+++ b/src/main/java/net/minecraft/world/level/block/TripWireBlock.java -@@ -135,6 +135,7 @@ public class TripWireBlock extends Block { - - @Override - protected void entityInside(BlockState state, Level world, BlockPos pos, Entity entity) { -+ if (!new io.papermc.paper.event.entity.EntityInsideBlockEvent(entity.getBukkitEntity(), org.bukkit.craftbukkit.block.CraftBlock.at(world, pos)).callEvent()) { return; } // Paper - Add EntityInsideBlockEvent - if (!world.isClientSide) { - if (!(Boolean) state.getValue(TripWireBlock.POWERED)) { - this.checkPressed(world, pos); -diff --git a/src/main/java/net/minecraft/world/level/block/WaterlilyBlock.java b/src/main/java/net/minecraft/world/level/block/WaterlilyBlock.java -index 7f5e092e400c3a8422b677ca246031a945a0d359..edc20745649b0837f1371c8d29e71fc0c8e5528f 100644 ---- a/src/main/java/net/minecraft/world/level/block/WaterlilyBlock.java -+++ b/src/main/java/net/minecraft/world/level/block/WaterlilyBlock.java -@@ -34,6 +34,7 @@ public class WaterlilyBlock extends BushBlock { - @Override - protected void entityInside(BlockState state, Level world, BlockPos pos, Entity entity) { - super.entityInside(state, world, pos, entity); -+ if (!new io.papermc.paper.event.entity.EntityInsideBlockEvent(entity.getBukkitEntity(), org.bukkit.craftbukkit.block.CraftBlock.at(world, pos)).callEvent()) { return; } // Paper - Add EntityInsideBlockEvent - if (world instanceof ServerLevel && entity instanceof Boat) { - // CraftBukkit start - if (!CraftEventFactory.callEntityChangeBlockEvent(entity, pos, Blocks.AIR.defaultBlockState())) { -diff --git a/src/main/java/net/minecraft/world/level/block/WebBlock.java b/src/main/java/net/minecraft/world/level/block/WebBlock.java -index 4b621793da3d6fbc44f90df863b099ba992930fb..fc209fab3ed1ccb35706a5529ec23ad8b902e491 100644 ---- a/src/main/java/net/minecraft/world/level/block/WebBlock.java -+++ b/src/main/java/net/minecraft/world/level/block/WebBlock.java -@@ -24,6 +24,7 @@ public class WebBlock extends Block { - - @Override - protected void entityInside(BlockState state, Level world, BlockPos pos, Entity entity) { -+ if (!new io.papermc.paper.event.entity.EntityInsideBlockEvent(entity.getBukkitEntity(), org.bukkit.craftbukkit.block.CraftBlock.at(world, pos)).callEvent()) { return; } // Paper - Add EntityInsideBlockEvent - Vec3 vec3 = new Vec3(0.25, 0.05F, 0.25); - if (entity instanceof LivingEntity livingEntity && livingEntity.hasEffect(MobEffects.WEAVING)) { - vec3 = new Vec3(0.5, 0.25, 0.5); -diff --git a/src/main/java/net/minecraft/world/level/block/WitherRoseBlock.java b/src/main/java/net/minecraft/world/level/block/WitherRoseBlock.java -index e861cf48aae27a8299437d76f6a84336cdcaddb7..3445916c2915b42967eb396b50b62d1912e3a49f 100644 ---- a/src/main/java/net/minecraft/world/level/block/WitherRoseBlock.java -+++ b/src/main/java/net/minecraft/world/level/block/WitherRoseBlock.java -@@ -62,6 +62,7 @@ public class WitherRoseBlock extends FlowerBlock { - - @Override - protected void entityInside(BlockState state, Level world, BlockPos pos, Entity entity) { -+ if (!new io.papermc.paper.event.entity.EntityInsideBlockEvent(entity.getBukkitEntity(), org.bukkit.craftbukkit.block.CraftBlock.at(world, pos)).callEvent()) { return; } // Paper - Add EntityInsideBlockEvent - if (!world.isClientSide && world.getDifficulty() != Difficulty.PEACEFUL) { - if (entity instanceof LivingEntity) { - LivingEntity entityliving = (LivingEntity) entity; diff --git a/patches/server/0538-Limit-item-frame-cursors-on-maps.patch b/patches/server/0538-Limit-item-frame-cursors-on-maps.patch new file mode 100644 index 000000000000..a55a4490c0e5 --- /dev/null +++ b/patches/server/0538-Limit-item-frame-cursors-on-maps.patch @@ -0,0 +1,30 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Yive +Date: Wed, 26 May 2021 15:09:33 -0700 +Subject: [PATCH] Limit item frame cursors on maps + + +diff --git a/src/main/java/net/minecraft/world/level/saveddata/maps/MapItemSavedData.java b/src/main/java/net/minecraft/world/level/saveddata/maps/MapItemSavedData.java +index a89f0b652c515efbc24ffc9af47fa65082946e54..2d5e7380e8a14cbc01ba48cd05deccc0c7f53430 100644 +--- a/src/main/java/net/minecraft/world/level/saveddata/maps/MapItemSavedData.java ++++ b/src/main/java/net/minecraft/world/level/saveddata/maps/MapItemSavedData.java +@@ -326,8 +326,10 @@ public class MapItemSavedData extends SavedData { + + MapFrame worldmapframe1 = new MapFrame(blockposition, entityitemframe.getDirection().get2DDataValue() * 90, entityitemframe.getId()); + ++ if (this.decorations.size() < player.level().paperConfig().maps.itemFrameCursorLimit) { // Paper - Limit item frame cursors on maps + this.addDecoration(MapDecorationTypes.FRAME, player.level(), MapItemSavedData.getFrameKey(entityitemframe.getId()), (double) blockposition.getX(), (double) blockposition.getZ(), (double) (entityitemframe.getDirection().get2DDataValue() * 90), (Component) null); + this.frameMarkers.put(worldmapframe1.getId(), worldmapframe1); ++ } // Paper - Limit item frame cursors on maps + } + + MapDecorations mapdecorations = (MapDecorations) stack.getOrDefault(DataComponents.MAP_DECORATIONS, MapDecorations.EMPTY); +@@ -520,7 +522,7 @@ public class MapItemSavedData extends SavedData { + return true; + } + +- if (!this.isTrackedCountOverLimit(256)) { ++ if (!this.isTrackedCountOverLimit(((Level) world).paperConfig().maps.itemFrameCursorLimit)) { // Paper - Limit item frame cursors on maps + this.bannerMarkers.put(mapiconbanner.getId(), mapiconbanner); + this.addDecoration(mapiconbanner.getDecoration(), world, mapiconbanner.getId(), d0, d1, 180.0D, (Component) mapiconbanner.name().orElse(null)); // CraftBukkit - decompile error + return true; diff --git a/patches/server/0539-Add-PlayerKickEvent-causes.patch b/patches/server/0539-Add-PlayerKickEvent-causes.patch new file mode 100644 index 000000000000..7c4ea36fb36b --- /dev/null +++ b/patches/server/0539-Add-PlayerKickEvent-causes.patch @@ -0,0 +1,557 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Jake Potrebic +Date: Sat, 15 May 2021 20:30:45 -0700 +Subject: [PATCH] Add PlayerKickEvent causes + + +diff --git a/src/main/java/net/minecraft/network/chat/SignedMessageChain.java b/src/main/java/net/minecraft/network/chat/SignedMessageChain.java +index dbcf183483766f39334d7f7e8336033906625f3f..300929a406905f5ff1ede664d5b99fb0938d4d2e 100644 +--- a/src/main/java/net/minecraft/network/chat/SignedMessageChain.java ++++ b/src/main/java/net/minecraft/network/chat/SignedMessageChain.java +@@ -40,14 +40,14 @@ public class SignedMessageChain { + if (signature == null) { + throw new SignedMessageChain.DecodeException(SignedMessageChain.DecodeException.MISSING_PROFILE_KEY); + } else if (playerPublicKey.data().hasExpired()) { +- throw new SignedMessageChain.DecodeException(SignedMessageChain.DecodeException.EXPIRED_PROFILE_KEY); ++ throw new SignedMessageChain.DecodeException(SignedMessageChain.DecodeException.EXPIRED_PROFILE_KEY, org.bukkit.event.player.PlayerKickEvent.Cause.EXPIRED_PROFILE_PUBLIC_KEY); // Paper - kick event causes + } else { + SignedMessageLink signedMessageLink = SignedMessageChain.this.nextLink; + if (signedMessageLink == null) { + throw new SignedMessageChain.DecodeException(SignedMessageChain.DecodeException.CHAIN_BROKEN); + } else if (body.timeStamp().isBefore(SignedMessageChain.this.lastTimeStamp)) { + this.setChainBroken(); +- throw new SignedMessageChain.DecodeException(SignedMessageChain.DecodeException.OUT_OF_ORDER_CHAT); ++ throw new SignedMessageChain.DecodeException(SignedMessageChain.DecodeException.OUT_OF_ORDER_CHAT, org.bukkit.event.player.PlayerKickEvent.Cause.OUT_OF_ORDER_CHAT); // Paper - kick event causes + } else { + SignedMessageChain.this.lastTimeStamp = body.timeStamp(); + PlayerChatMessage playerChatMessage = new PlayerChatMessage(signedMessageLink, signature, body, null, FilterMask.PASS_THROUGH); +@@ -80,8 +80,15 @@ public class SignedMessageChain { + static final Component INVALID_SIGNATURE = Component.translatable("chat.disabled.invalid_signature"); + static final Component OUT_OF_ORDER_CHAT = Component.translatable("chat.disabled.out_of_order_chat"); + +- public DecodeException(Component message) { ++ // Paper start ++ public final org.bukkit.event.player.PlayerKickEvent.Cause kickCause; ++ public DecodeException(Component message, org.bukkit.event.player.PlayerKickEvent.Cause event) { + super(message); ++ this.kickCause = event; ++ } ++ // Paper end ++ public DecodeException(Component message) { ++ this(message, org.bukkit.event.player.PlayerKickEvent.Cause.UNKNOWN); // Paper + } + } + +diff --git a/src/main/java/net/minecraft/server/MinecraftServer.java b/src/main/java/net/minecraft/server/MinecraftServer.java +index 67a9bc450f545dd7b05398968a278a14f1e518fe..7ab9a9c04f94a68dccb5ec9bfc8171bfc02927a2 100644 +--- a/src/main/java/net/minecraft/server/MinecraftServer.java ++++ b/src/main/java/net/minecraft/server/MinecraftServer.java +@@ -2325,7 +2325,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop Component.translatable("commands.kick.success", serverPlayer.getDisplayName(), reason), true); + i++; + } +diff --git a/src/main/java/net/minecraft/server/network/ServerCommonPacketListenerImpl.java b/src/main/java/net/minecraft/server/network/ServerCommonPacketListenerImpl.java +index f8ae8c8eff73e4e87eb34d0f2635517f1688a6f1..59d20fd62e850a38380d877cef95ed69cb46ecbd 100644 +--- a/src/main/java/net/minecraft/server/network/ServerCommonPacketListenerImpl.java ++++ b/src/main/java/net/minecraft/server/network/ServerCommonPacketListenerImpl.java +@@ -63,8 +63,8 @@ public abstract class ServerCommonPacketListenerImpl implements ServerCommonPack + } + + @Override +- public void kickPlayer(Component reason) { +- this.disconnect(reason); ++ public void kickPlayer(Component reason, org.bukkit.event.player.PlayerKickEvent.Cause cause) { // Paper - kick event causes ++ this.disconnect(reason, cause); // Paper - kick event causes + } + // CraftBukkit end + private static final Logger LOGGER = LogUtils.getLogger(); +@@ -140,7 +140,7 @@ public abstract class ServerCommonPacketListenerImpl implements ServerCommonPack + } else if (!this.isSingleplayerOwner()) { + // Paper start - This needs to be handled on the main thread for plugins + server.submit(() -> { +- this.disconnect(ServerCommonPacketListenerImpl.TIMEOUT_DISCONNECTION_MESSAGE); ++ this.disconnect(ServerCommonPacketListenerImpl.TIMEOUT_DISCONNECTION_MESSAGE, PlayerKickEvent.Cause.TIMEOUT); // Paper - kick event cause + }); + // Paper end - This needs to be handled on the main thread for plugins + } +@@ -176,7 +176,7 @@ public abstract class ServerCommonPacketListenerImpl implements ServerCommonPack + } + } catch (Exception ex) { + ServerGamePacketListenerImpl.LOGGER.error("Couldn\'t register custom payload", ex); +- this.disconnect(Component.literal("Invalid payload REGISTER!")); ++ this.disconnect(Component.literal("Invalid payload REGISTER!"), PlayerKickEvent.Cause.INVALID_PAYLOAD); // Paper - kick event cause + } + } else if (identifier.equals(ServerCommonPacketListenerImpl.CUSTOM_UNREGISTER)) { + try { +@@ -186,7 +186,7 @@ public abstract class ServerCommonPacketListenerImpl implements ServerCommonPack + } + } catch (Exception ex) { + ServerGamePacketListenerImpl.LOGGER.error("Couldn\'t unregister custom payload", ex); +- this.disconnect(Component.literal("Invalid payload UNREGISTER!")); ++ this.disconnect(Component.literal("Invalid payload UNREGISTER!"), PlayerKickEvent.Cause.INVALID_PAYLOAD); // Paper - kick event cause + } + } else { + try { +@@ -204,7 +204,7 @@ public abstract class ServerCommonPacketListenerImpl implements ServerCommonPack + this.cserver.getMessenger().dispatchIncomingMessage(this.player.getBukkitEntity(), identifier.toString(), data); + } catch (Exception ex) { + ServerGamePacketListenerImpl.LOGGER.error("Couldn\'t dispatch custom payload", ex); +- this.disconnect(Component.literal("Invalid custom payload!")); ++ this.disconnect(Component.literal("Invalid custom payload!"), PlayerKickEvent.Cause.INVALID_PAYLOAD); // Paper - kick event cause + } + } + +@@ -220,7 +220,7 @@ public abstract class ServerCommonPacketListenerImpl implements ServerCommonPack + PacketUtils.ensureRunningOnSameThread(packet, this, (BlockableEventLoop) this.server); + if (packet.action() == ServerboundResourcePackPacket.Action.DECLINED && this.server.isResourcePackRequired()) { + ServerCommonPacketListenerImpl.LOGGER.info("Disconnecting {} due to resource pack {} rejection", this.playerProfile().getName(), packet.id()); +- this.disconnect((Component) Component.translatable("multiplayer.requiredTexturePrompt.disconnect")); ++ this.disconnect((Component) Component.translatable("multiplayer.requiredTexturePrompt.disconnect"), PlayerKickEvent.Cause.RESOURCE_PACK_REJECTION); // Paper - kick event cause + } + // Paper start - adventure pack callbacks + // call the callbacks before the previously-existing event so the event has final say +@@ -250,7 +250,7 @@ public abstract class ServerCommonPacketListenerImpl implements ServerCommonPack + return; + } + // CraftBukkit end +- this.disconnect(ServerCommonPacketListenerImpl.DISCONNECT_UNEXPECTED_QUERY); ++ this.disconnect(ServerCommonPacketListenerImpl.DISCONNECT_UNEXPECTED_QUERY, PlayerKickEvent.Cause.INVALID_COOKIE); // Paper - kick event cause + } + + protected void keepConnectionAlive() { +@@ -262,7 +262,7 @@ public abstract class ServerCommonPacketListenerImpl implements ServerCommonPack + + if (!this.isSingleplayerOwner() && elapsedTime >= 15000L) { // Paper - use vanilla's 15000L between keep alive packets + if (this.keepAlivePending && !this.processedDisconnect && elapsedTime >= KEEPALIVE_LIMIT) { // Paper - check keepalive limit, don't fire if already disconnected +- this.disconnect(ServerCommonPacketListenerImpl.TIMEOUT_DISCONNECTION_MESSAGE); ++ this.disconnect(ServerCommonPacketListenerImpl.TIMEOUT_DISCONNECTION_MESSAGE, PlayerKickEvent.Cause.TIMEOUT); // Paper - kick event cause + } else if (this.checkIfClosed(currentTime)) { // Paper + this.keepAlivePending = true; + this.keepAliveTime = currentTime; +@@ -278,7 +278,7 @@ public abstract class ServerCommonPacketListenerImpl implements ServerCommonPack + private boolean checkIfClosed(long time) { + if (this.closed) { + if (time - this.closedListenerTime >= 15000L) { +- this.disconnect(ServerCommonPacketListenerImpl.TIMEOUT_DISCONNECTION_MESSAGE); ++ this.disconnect(ServerCommonPacketListenerImpl.TIMEOUT_DISCONNECTION_MESSAGE, PlayerKickEvent.Cause.TIMEOUT); // Paper - kick event cause + } + + return false; +@@ -330,15 +330,25 @@ public abstract class ServerCommonPacketListenerImpl implements ServerCommonPack + + // Paper start - adventure + public void disconnect(final net.kyori.adventure.text.Component reason) { +- this.disconnect(io.papermc.paper.adventure.PaperAdventure.asVanilla(reason)); ++ this.disconnect(reason, PlayerKickEvent.Cause.UNKNOWN); ++ } ++ public void disconnect(final net.kyori.adventure.text.Component reason, PlayerKickEvent.Cause cause) { ++ this.disconnect(io.papermc.paper.adventure.PaperAdventure.asVanilla(reason), cause); ++ // Paper end - kick event causes + } + // Paper end - adventure + ++ @Deprecated @io.papermc.paper.annotation.DoNotUse // Paper - kick event causes + public void disconnect(Component reason) { +- this.disconnect(new DisconnectionDetails(reason)); ++ // Paper start - kick event causes ++ this.disconnect(reason, PlayerKickEvent.Cause.UNKNOWN); ++ } ++ public void disconnect(final Component reason, PlayerKickEvent.Cause cause) { ++ this.disconnect(new DisconnectionDetails(reason), cause); ++ // Paper end - kick event causes + } + +- public void disconnect(DisconnectionDetails disconnectionInfo) { ++ public void disconnect(DisconnectionDetails disconnectionInfo, PlayerKickEvent.Cause cause) { // Paper - kick event cause + // CraftBukkit start - fire PlayerKickEvent + if (this.processedDisconnect) { + return; +@@ -347,7 +357,7 @@ public abstract class ServerCommonPacketListenerImpl implements ServerCommonPack + Waitable waitable = new Waitable() { + @Override + protected Object evaluate() { +- ServerCommonPacketListenerImpl.this.disconnect(disconnectionInfo); ++ ServerCommonPacketListenerImpl.this.disconnect(disconnectionInfo, cause); // Paper - kick event causes + return null; + } + }; +@@ -366,7 +376,7 @@ public abstract class ServerCommonPacketListenerImpl implements ServerCommonPack + + net.kyori.adventure.text.Component leaveMessage = net.kyori.adventure.text.Component.translatable("multiplayer.player.left", net.kyori.adventure.text.format.NamedTextColor.YELLOW, io.papermc.paper.configuration.GlobalConfiguration.get().messages.useDisplayNameInQuitMessage ? this.player.getBukkitEntity().displayName() : net.kyori.adventure.text.Component.text(this.player.getScoreboardName())); // Paper - Adventure + +- PlayerKickEvent event = new PlayerKickEvent(this.player.getBukkitEntity(), io.papermc.paper.adventure.PaperAdventure.asAdventure(disconnectionInfo.reason()), leaveMessage); // Paper - adventure ++ PlayerKickEvent event = new PlayerKickEvent(this.player.getBukkitEntity(), io.papermc.paper.adventure.PaperAdventure.asAdventure(disconnectionInfo.reason()), leaveMessage, cause); // Paper - adventure & kick event causes + + if (this.cserver.getServer().isRunning()) { + this.cserver.getPluginManager().callEvent(event); +diff --git a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java +index 34258a58ea7b75363eff825e3db27832d3559557..36127f7c9ae50a628e88e7b456889a8f259b06da 100644 +--- a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java ++++ b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java +@@ -359,7 +359,7 @@ public class ServerGamePacketListenerImpl extends ServerCommonPacketListenerImpl + if (this.clientIsFloating && !this.player.isSleeping() && !this.player.isPassenger() && !this.player.isDeadOrDying()) { + if (++this.aboveGroundTickCount > this.getMaximumFlyingTicks(this.player)) { + ServerGamePacketListenerImpl.LOGGER.warn("{} was kicked for floating too long!", this.player.getName().getString()); +- this.disconnect(io.papermc.paper.configuration.GlobalConfiguration.get().messages.kick.flyingPlayer); // Paper - use configurable kick message ++ this.disconnect(io.papermc.paper.configuration.GlobalConfiguration.get().messages.kick.flyingPlayer, org.bukkit.event.player.PlayerKickEvent.Cause.FLYING_PLAYER); // Paper - use configurable kick message & kick event cause + return; + } + } else { +@@ -378,7 +378,7 @@ public class ServerGamePacketListenerImpl extends ServerCommonPacketListenerImpl + if (this.clientVehicleIsFloating && this.lastVehicle.getControllingPassenger() == this.player) { + if (++this.aboveGroundVehicleTickCount > this.getMaximumFlyingTicks(this.lastVehicle)) { + ServerGamePacketListenerImpl.LOGGER.warn("{} was kicked for floating a vehicle too long!", this.player.getName().getString()); +- this.disconnect(io.papermc.paper.configuration.GlobalConfiguration.get().messages.kick.flyingVehicle); // Paper - use configurable kick message ++ this.disconnect(io.papermc.paper.configuration.GlobalConfiguration.get().messages.kick.flyingVehicle, org.bukkit.event.player.PlayerKickEvent.Cause.FLYING_VEHICLE); // Paper - use configurable kick message & kick event cause + return; + } + } else { +@@ -398,7 +398,7 @@ public class ServerGamePacketListenerImpl extends ServerCommonPacketListenerImpl + this.dropSpamThrottler.tick(); + if (this.player.getLastActionTime() > 0L && this.server.getPlayerIdleTimeout() > 0 && Util.getMillis() - this.player.getLastActionTime() > (long) this.server.getPlayerIdleTimeout() * 1000L * 60L) { + this.player.resetLastActionTime(); // CraftBukkit - SPIGOT-854 +- this.disconnect((Component) Component.translatable("multiplayer.disconnect.idling")); ++ this.disconnect((Component) Component.translatable("multiplayer.disconnect.idling"), org.bukkit.event.player.PlayerKickEvent.Cause.IDLING); // Paper - kick event cause + } + + } +@@ -486,7 +486,7 @@ public class ServerGamePacketListenerImpl extends ServerCommonPacketListenerImpl + public void handleMoveVehicle(ServerboundMoveVehiclePacket packet) { + PacketUtils.ensureRunningOnSameThread(packet, this, this.player.serverLevel()); + if (ServerGamePacketListenerImpl.containsInvalidValues(packet.getX(), packet.getY(), packet.getZ(), packet.getYRot(), packet.getXRot())) { +- this.disconnect((Component) Component.translatable("multiplayer.disconnect.invalid_vehicle_movement")); ++ this.disconnect((Component) Component.translatable("multiplayer.disconnect.invalid_vehicle_movement"), org.bukkit.event.player.PlayerKickEvent.Cause.INVALID_VEHICLE_MOVEMENT); // Paper - kick event cause + } else if (!this.updateAwaitingTeleport()) { + Entity entity = this.player.getRootVehicle(); + +@@ -692,7 +692,7 @@ public class ServerGamePacketListenerImpl extends ServerCommonPacketListenerImpl + PacketUtils.ensureRunningOnSameThread(packet, this, this.player.serverLevel()); + if (packet.getId() == this.awaitingTeleport) { + if (this.awaitingPositionFromClient == null) { +- this.disconnect((Component) Component.translatable("multiplayer.disconnect.invalid_player_movement")); ++ this.disconnect((Component) Component.translatable("multiplayer.disconnect.invalid_player_movement"), org.bukkit.event.player.PlayerKickEvent.Cause.INVALID_PLAYER_MOVEMENT); // Paper - kick event cause + return; + } + +@@ -756,7 +756,7 @@ public class ServerGamePacketListenerImpl extends ServerCommonPacketListenerImpl + // PacketUtils.ensureRunningOnSameThread(packet, this, this.player.serverLevel()); // Paper - AsyncTabCompleteEvent; run this async + // CraftBukkit start + if (!this.tabSpamThrottler.isIncrementAndUnderThreshold() && !this.server.getPlayerList().isOp(this.player.getGameProfile()) && !this.server.isSingleplayerOwner(this.player.getGameProfile())) { // Paper - configurable tab spam limits +- this.disconnect(Component.translatable("disconnect.spam")); ++ this.disconnect(Component.translatable("disconnect.spam"), org.bukkit.event.player.PlayerKickEvent.Cause.SPAM); // Paper - Kick event cause + return; + } + // CraftBukkit end +@@ -921,7 +921,7 @@ public class ServerGamePacketListenerImpl extends ServerCommonPacketListenerImpl + // Paper start - validate pick item position + if (!(packet.getSlot() >= 0 && packet.getSlot() < this.player.getInventory().items.size())) { + ServerGamePacketListenerImpl.LOGGER.warn("{} tried to set an invalid carried item", this.player.getName().getString()); +- this.disconnect(Component.literal("Invalid hotbar selection (Hacking?)")); ++ this.disconnect(Component.literal("Invalid hotbar selection (Hacking?)"), org.bukkit.event.player.PlayerKickEvent.Cause.ILLEGAL_ACTION); // Paper - kick event cause + return; + } + this.player.getInventory().pickSlot(packet.getSlot()); // Paper - Diff above if changed +@@ -1124,14 +1124,14 @@ public class ServerGamePacketListenerImpl extends ServerCommonPacketListenerImpl + + if (byteTotal > byteAllowed) { + ServerGamePacketListenerImpl.LOGGER.warn("{} tried to send a book too large. Book size: {} - Allowed: {} - Pages: {}", this.player.getScoreboardName(), byteTotal, byteAllowed, pageList.size()); +- this.disconnect(Component.literal("Book too large!")); ++ this.disconnect(Component.literal("Book too large!"), org.bukkit.event.player.PlayerKickEvent.Cause.ILLEGAL_ACTION); // Paper - kick event cause + return; + } + } + // Paper end - Book size limits + // CraftBukkit start + if (this.lastBookTick + 20 > MinecraftServer.currentTick) { +- this.disconnect(Component.literal("Book edited too quickly!")); ++ this.disconnect(Component.literal("Book edited too quickly!"), org.bukkit.event.player.PlayerKickEvent.Cause.ILLEGAL_ACTION); // Paper - kick event cause + return; + } + this.lastBookTick = MinecraftServer.currentTick; +@@ -1240,7 +1240,7 @@ public class ServerGamePacketListenerImpl extends ServerCommonPacketListenerImpl + public void handleMovePlayer(ServerboundMovePlayerPacket packet) { + PacketUtils.ensureRunningOnSameThread(packet, this, this.player.serverLevel()); + if (ServerGamePacketListenerImpl.containsInvalidValues(packet.getX(0.0D), packet.getY(0.0D), packet.getZ(0.0D), packet.getYRot(0.0F), packet.getXRot(0.0F))) { +- this.disconnect((Component) Component.translatable("multiplayer.disconnect.invalid_player_movement")); ++ this.disconnect((Component) Component.translatable("multiplayer.disconnect.invalid_player_movement"), org.bukkit.event.player.PlayerKickEvent.Cause.INVALID_PLAYER_MOVEMENT); // Paper - kick event cause + } else { + ServerLevel worldserver = this.player.serverLevel(); + +@@ -1679,7 +1679,7 @@ public class ServerGamePacketListenerImpl extends ServerCommonPacketListenerImpl + this.dropCount++; + if (this.dropCount >= 20) { + ServerGamePacketListenerImpl.LOGGER.warn(this.player.getScoreboardName() + " dropped their items too quickly!"); +- this.disconnect(Component.literal("You dropped your items too quickly (Hacking?)")); ++ this.disconnect(Component.literal("You dropped your items too quickly (Hacking?)"), org.bukkit.event.player.PlayerKickEvent.Cause.ILLEGAL_ACTION); // Paper - kick event cause + return; + } + } +@@ -1976,7 +1976,7 @@ public class ServerGamePacketListenerImpl extends ServerCommonPacketListenerImpl + this.player.resetLastActionTime(); + } else { + ServerGamePacketListenerImpl.LOGGER.warn("{} tried to set an invalid carried item", this.player.getName().getString()); +- this.disconnect(Component.literal("Invalid hotbar selection (Hacking?)")); // CraftBukkit ++ this.disconnect(Component.literal("Invalid hotbar selection (Hacking?)"), org.bukkit.event.player.PlayerKickEvent.Cause.ILLEGAL_ACTION); // CraftBukkit // Paper - kick event cause + } + } + +@@ -2174,7 +2174,7 @@ public class ServerGamePacketListenerImpl extends ServerCommonPacketListenerImpl + + private void tryHandleChat(String s, Runnable runnable, boolean sync) { // CraftBukkit + if (ServerGamePacketListenerImpl.isChatMessageIllegal(s)) { +- this.disconnect((Component) Component.translatable("multiplayer.disconnect.illegal_characters")); ++ this.disconnect((Component) Component.translatable("multiplayer.disconnect.illegal_characters"), org.bukkit.event.player.PlayerKickEvent.Cause.ILLEGAL_CHARACTERS); // Paper + } else if (this.player.isRemoved() || this.player.getChatVisibility() == ChatVisiblity.HIDDEN) { // CraftBukkit - dead men tell no tales + this.send(new ClientboundSystemChatPacket(Component.translatable("chat.disabled.options").withStyle(ChatFormatting.RED), false)); + } else { +@@ -2197,7 +2197,7 @@ public class ServerGamePacketListenerImpl extends ServerCommonPacketListenerImpl + + if (optional.isEmpty()) { + ServerGamePacketListenerImpl.LOGGER.warn("Failed to validate message acknowledgements from {}", this.player.getName().getString()); +- this.disconnect(ServerGamePacketListenerImpl.CHAT_VALIDATION_FAILED); ++ this.disconnect(ServerGamePacketListenerImpl.CHAT_VALIDATION_FAILED, org.bukkit.event.player.PlayerKickEvent.Cause.CHAT_VALIDATION_FAILED); // Paper - kick event causes + } + + return optional; +@@ -2378,7 +2378,7 @@ public class ServerGamePacketListenerImpl extends ServerCommonPacketListenerImpl + // this.chatSpamThrottler.increment(); + if (!this.chatSpamThrottler.isIncrementAndUnderThreshold() && !this.server.getPlayerList().isOp(this.player.getGameProfile()) && !this.server.isSingleplayerOwner(this.player.getGameProfile())) { + // CraftBukkit end +- this.disconnect((Component) Component.translatable("disconnect.spam")); ++ this.disconnect((Component) Component.translatable("disconnect.spam"), org.bukkit.event.player.PlayerKickEvent.Cause.SPAM); // Paper - kick event cause + } + + } +@@ -2390,7 +2390,7 @@ public class ServerGamePacketListenerImpl extends ServerCommonPacketListenerImpl + synchronized (this.lastSeenMessages) { + if (!this.lastSeenMessages.applyOffset(packet.offset())) { + ServerGamePacketListenerImpl.LOGGER.warn("Failed to validate message acknowledgements from {}", this.player.getName().getString()); +- this.disconnect(ServerGamePacketListenerImpl.CHAT_VALIDATION_FAILED); ++ this.disconnect(ServerGamePacketListenerImpl.CHAT_VALIDATION_FAILED, org.bukkit.event.player.PlayerKickEvent.Cause.CHAT_VALIDATION_FAILED); // Paper - kick event causes + } + + } +@@ -2538,7 +2538,7 @@ public class ServerGamePacketListenerImpl extends ServerCommonPacketListenerImpl + } + + if (i > 4096) { +- this.disconnect((Component) Component.translatable("multiplayer.disconnect.too_many_pending_chats")); ++ this.disconnect((Component) Component.translatable("multiplayer.disconnect.too_many_pending_chats"), org.bukkit.event.player.PlayerKickEvent.Cause.TOO_MANY_PENDING_CHATS); // Paper - kick event cause + } + + } +@@ -2596,7 +2596,7 @@ public class ServerGamePacketListenerImpl extends ServerCommonPacketListenerImpl + // Spigot Start + if ( entity == this.player && !this.player.isSpectator() ) + { +- this.disconnect( Component.literal( "Cannot interact with self!" ) ); ++ this.disconnect( Component.literal( "Cannot interact with self!" ), org.bukkit.event.player.PlayerKickEvent.Cause.SELF_INTERACTION ); // Paper - kick event cause + return; + } + // Spigot End +@@ -2712,7 +2712,7 @@ public class ServerGamePacketListenerImpl extends ServerCommonPacketListenerImpl + } + } + +- ServerGamePacketListenerImpl.this.disconnect((Component) Component.translatable("multiplayer.disconnect.invalid_entity_attacked")); ++ ServerGamePacketListenerImpl.this.disconnect((Component) Component.translatable("multiplayer.disconnect.invalid_entity_attacked"), org.bukkit.event.player.PlayerKickEvent.Cause.INVALID_ENTITY_ATTACKED); // Paper - add cause + ServerGamePacketListenerImpl.LOGGER.warn("Player {} tried to attack an invalid entity", ServerGamePacketListenerImpl.this.player.getName().getString()); + } + }); +@@ -3111,7 +3111,7 @@ public class ServerGamePacketListenerImpl extends ServerCommonPacketListenerImpl + // Paper start - auto recipe limit + if (!org.bukkit.Bukkit.isPrimaryThread()) { + if (!this.recipeSpamPackets.isIncrementAndUnderThreshold()) { +- this.disconnect(net.minecraft.network.chat.Component.translatable("disconnect.spam")); ++ this.disconnect(net.minecraft.network.chat.Component.translatable("disconnect.spam"), org.bukkit.event.player.PlayerKickEvent.Cause.SPAM); // Paper - kick event cause + return; + } + } +@@ -3382,7 +3382,7 @@ public class ServerGamePacketListenerImpl extends ServerCommonPacketListenerImpl + + if (!Objects.equals(profilepublickey_a, profilepublickey_a1)) { + if (profilepublickey_a != null && profilepublickey_a1.expiresAt().isBefore(profilepublickey_a.expiresAt())) { +- this.disconnect(ProfilePublicKey.EXPIRED_PROFILE_PUBLIC_KEY); ++ this.disconnect(ProfilePublicKey.EXPIRED_PROFILE_PUBLIC_KEY, org.bukkit.event.player.PlayerKickEvent.Cause.EXPIRED_PROFILE_PUBLIC_KEY); // Paper - kick event causes + } else { + try { + SignatureValidator signaturevalidator = this.server.getProfileKeySignatureValidator(); +@@ -3395,7 +3395,7 @@ public class ServerGamePacketListenerImpl extends ServerCommonPacketListenerImpl + this.resetPlayerChatState(remotechatsession_a.validate(this.player.getGameProfile(), signaturevalidator)); + } catch (ProfilePublicKey.ValidationException profilepublickey_b) { + ServerGamePacketListenerImpl.LOGGER.error("Failed to validate profile key: {}", profilepublickey_b.getMessage()); +- this.disconnect(profilepublickey_b.getComponent()); ++ this.disconnect(profilepublickey_b.getComponent(), profilepublickey_b.kickCause); // Paper - kick event causes + } + + } +diff --git a/src/main/java/net/minecraft/server/network/ServerLoginPacketListenerImpl.java b/src/main/java/net/minecraft/server/network/ServerLoginPacketListenerImpl.java +index 9d5723cdfdbf6257a71e57842aea9ba317fc049a..1e4b288f20153ce0c91fabf164c5c8320c90ba7d 100644 +--- a/src/main/java/net/minecraft/server/network/ServerLoginPacketListenerImpl.java ++++ b/src/main/java/net/minecraft/server/network/ServerLoginPacketListenerImpl.java +@@ -70,7 +70,7 @@ public class ServerLoginPacketListenerImpl implements ServerLoginPacketListener, + } + + @Override +- public void kickPlayer(Component reason) { ++ public void kickPlayer(Component reason, org.bukkit.event.player.PlayerKickEvent.Cause cause) { // Paper - kick event causes - during login, no event can be called. + this.disconnect(reason); + } + // CraftBukkit end +diff --git a/src/main/java/net/minecraft/server/players/PlayerList.java b/src/main/java/net/minecraft/server/players/PlayerList.java +index 7a18061834096a73b140bee37b55b3c1724b51ef..e6b92f085291aaf4fa78d96f8379aeef2200592b 100644 +--- a/src/main/java/net/minecraft/server/players/PlayerList.java ++++ b/src/main/java/net/minecraft/server/players/PlayerList.java +@@ -632,7 +632,7 @@ public abstract class PlayerList { + while (iterator.hasNext()) { + entityplayer = (ServerPlayer) iterator.next(); + this.save(entityplayer); // CraftBukkit - Force the player's inventory to be saved +- entityplayer.connection.disconnect(Component.translatable("multiplayer.disconnect.duplicate_login")); ++ entityplayer.connection.disconnect(Component.translatable("multiplayer.disconnect.duplicate_login"), org.bukkit.event.player.PlayerKickEvent.Cause.DUPLICATE_LOGIN); // Paper - kick event cause + } + + // Instead of kicking then returning, we need to store the kick reason +@@ -1236,7 +1236,7 @@ public abstract class PlayerList { + // Paper end + // CraftBukkit start - disconnect safely + for (ServerPlayer player : this.players) { +- if (isRestarting) player.connection.disconnect(net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer.legacySection().deserialize(org.spigotmc.SpigotConfig.restartMessage)); else // Paper ++ if (isRestarting) player.connection.disconnect(net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer.legacySection().deserialize(org.spigotmc.SpigotConfig.restartMessage), org.bukkit.event.player.PlayerKickEvent.Cause.UNKNOWN); else // Paper - kick event cause (cause is never used here) + player.connection.disconnect(java.util.Objects.requireNonNullElseGet(this.server.server.shutdownMessage(), net.kyori.adventure.text.Component::empty)); // CraftBukkit - add custom shutdown message // Paper - Adventure + } + // CraftBukkit end +diff --git a/src/main/java/net/minecraft/world/entity/player/ProfilePublicKey.java b/src/main/java/net/minecraft/world/entity/player/ProfilePublicKey.java +index 9e2ad78b12cadbf0e2bda1e12fe844120529c347..6a7d7fad990fc44fdda6849d43dad141e61f7f37 100644 +--- a/src/main/java/net/minecraft/world/entity/player/ProfilePublicKey.java ++++ b/src/main/java/net/minecraft/world/entity/player/ProfilePublicKey.java +@@ -24,7 +24,7 @@ public record ProfilePublicKey(ProfilePublicKey.Data data) { + + public static ProfilePublicKey createValidated(SignatureValidator servicesSignatureVerifier, UUID playerUuid, ProfilePublicKey.Data publicKeyData) throws ProfilePublicKey.ValidationException { + if (!publicKeyData.validateSignature(servicesSignatureVerifier, playerUuid)) { +- throw new ProfilePublicKey.ValidationException(INVALID_SIGNATURE); ++ throw new ProfilePublicKey.ValidationException(INVALID_SIGNATURE, org.bukkit.event.player.PlayerKickEvent.Cause.INVALID_PUBLIC_KEY_SIGNATURE); // Paper - kick event causes + } else { + return new ProfilePublicKey(publicKeyData); + } +@@ -88,8 +88,16 @@ public record ProfilePublicKey(ProfilePublicKey.Data data) { + } + + public static class ValidationException extends ThrowingComponent { ++ public final org.bukkit.event.player.PlayerKickEvent.Cause kickCause; // Paper ++ @io.papermc.paper.annotation.DoNotUse @Deprecated // Paper + public ValidationException(Component messageText) { ++ // Paper start ++ this(messageText, org.bukkit.event.player.PlayerKickEvent.Cause.UNKNOWN); ++ } ++ public ValidationException(Component messageText, org.bukkit.event.player.PlayerKickEvent.Cause kickCause) { ++ // Paper end + super(messageText); ++ this.kickCause = kickCause; // Paper + } + } + } +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java +index fc246d30808ad6242f4adfe201adc169a2ddeb90..e24d5992cfb89ccc4e727c91918ab1f94131987e 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java +@@ -280,7 +280,7 @@ public class CraftPlayer extends CraftHumanEntity implements Player { + + void sendPacket(Packet packet); + +- void kickPlayer(Component reason); ++ void kickPlayer(Component reason, org.bukkit.event.player.PlayerKickEvent.Cause cause); // Paper - kick event causes + } + + public record CookieFuture(ResourceLocation key, CompletableFuture future) { +@@ -652,7 +652,7 @@ public class CraftPlayer extends CraftHumanEntity implements Player { + @Override + public void kickPlayer(String message) { + org.spigotmc.AsyncCatcher.catchOp("player kick"); // Spigot +- this.getHandle().transferCookieConnection.kickPlayer(CraftChatMessage.fromStringOrEmpty(message, true)); ++ this.getHandle().transferCookieConnection.kickPlayer(CraftChatMessage.fromStringOrEmpty(message, true), org.bukkit.event.player.PlayerKickEvent.Cause.PLUGIN); // Paper - kick event cause + } + + // Paper start +@@ -664,10 +664,15 @@ public class CraftPlayer extends CraftHumanEntity implements Player { + + @Override + public void kick(final net.kyori.adventure.text.Component message) { ++ kick(message, org.bukkit.event.player.PlayerKickEvent.Cause.PLUGIN); ++ } ++ ++ @Override ++ public void kick(net.kyori.adventure.text.Component message, org.bukkit.event.player.PlayerKickEvent.Cause cause) { + org.spigotmc.AsyncCatcher.catchOp("player kick"); + final ServerGamePacketListenerImpl connection = this.getHandle().connection; + if (connection != null) { +- connection.disconnect(message == null ? net.kyori.adventure.text.Component.empty() : message); ++ connection.disconnect(message == null ? net.kyori.adventure.text.Component.empty() : message, cause); + } + } + +@@ -726,7 +731,7 @@ public class CraftPlayer extends CraftHumanEntity implements Player { + + // Paper start - Improve chat handling + if (ServerGamePacketListenerImpl.isChatMessageIllegal(msg)) { +- this.getHandle().connection.disconnect(Component.translatable("multiplayer.disconnect.illegal_characters")); ++ this.getHandle().connection.disconnect(Component.translatable("multiplayer.disconnect.illegal_characters"), org.bukkit.event.player.PlayerKickEvent.Cause.ILLEGAL_CHARACTERS); // Paper - kick event causes + } else { + if (msg.startsWith("/")) { + this.getHandle().connection.handleCommand(msg); +diff --git a/src/main/java/org/spigotmc/RestartCommand.java b/src/main/java/org/spigotmc/RestartCommand.java +index 824c4ad135ea5177f416687c7042639ed126b70b..39e56b95aaafbcd8ebe68fdefaace83702e9510d 100644 +--- a/src/main/java/org/spigotmc/RestartCommand.java ++++ b/src/main/java/org/spigotmc/RestartCommand.java +@@ -74,7 +74,7 @@ public class RestartCommand extends Command + // Kick all players + for ( ServerPlayer p : com.google.common.collect.ImmutableList.copyOf( MinecraftServer.getServer().getPlayerList().players ) ) + { +- p.connection.disconnect( CraftChatMessage.fromStringOrEmpty( SpigotConfig.restartMessage, true ) ); ++ p.connection.disconnect( CraftChatMessage.fromStringOrEmpty( SpigotConfig.restartMessage, true ), org.bukkit.event.player.PlayerKickEvent.Cause.RESTART_COMMAND); // Paper - kick event reason (cause is never used)) + } + // Give the socket a chance to send the packets + try diff --git a/patches/server/0539-Improve-item-default-attribute-API.patch b/patches/server/0539-Improve-item-default-attribute-API.patch deleted file mode 100644 index 9cb4f56ce338..000000000000 --- a/patches/server/0539-Improve-item-default-attribute-API.patch +++ /dev/null @@ -1,83 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Jake Potrebic -Date: Sat, 8 May 2021 15:01:54 -0700 -Subject: [PATCH] Improve item default attribute API - - -diff --git a/src/main/java/org/bukkit/craftbukkit/attribute/CraftAttributeInstance.java b/src/main/java/org/bukkit/craftbukkit/attribute/CraftAttributeInstance.java -index de0eba19c0c963adb4f17cea22333240021fd801..3b171a08bd0bedfe224905feb5838d2540199bce 100644 ---- a/src/main/java/org/bukkit/craftbukkit/attribute/CraftAttributeInstance.java -+++ b/src/main/java/org/bukkit/craftbukkit/attribute/CraftAttributeInstance.java -@@ -75,7 +75,7 @@ public class CraftAttributeInstance implements AttributeInstance { - return new AttributeModifier(CraftNamespacedKey.fromMinecraft(nms.id()), nms.amount(), AttributeModifier.Operation.values()[nms.operation().ordinal()], org.bukkit.inventory.EquipmentSlotGroup.ANY); - } - -- public static AttributeModifier convert(net.minecraft.world.entity.ai.attributes.AttributeModifier nms, EquipmentSlot slot) { -- return new AttributeModifier(CraftNamespacedKey.fromMinecraft(nms.id()), nms.amount(), AttributeModifier.Operation.values()[nms.operation().ordinal()], slot.getGroup()); -+ public static AttributeModifier convert(net.minecraft.world.entity.ai.attributes.AttributeModifier nms, net.minecraft.world.entity.EquipmentSlotGroup slot) { // Paper -+ return new AttributeModifier(CraftNamespacedKey.fromMinecraft(nms.id()), nms.amount(), AttributeModifier.Operation.values()[nms.operation().ordinal()], org.bukkit.craftbukkit.CraftEquipmentSlot.getSlot(slot)); // Paper - } - } -diff --git a/src/main/java/org/bukkit/craftbukkit/inventory/CraftItemType.java b/src/main/java/org/bukkit/craftbukkit/inventory/CraftItemType.java -index 66d773cadb74f9176e6cf68a565568034f52ec63..a1f2b9d40d374e8cdbaf916b25fa74b6c0970f81 100644 ---- a/src/main/java/org/bukkit/craftbukkit/inventory/CraftItemType.java -+++ b/src/main/java/org/bukkit/craftbukkit/inventory/CraftItemType.java -@@ -197,19 +197,36 @@ public class CraftItemType implements ItemType.Typed, Han - // return CraftEquipmentSlot.getSlot(EntityInsentient.getEquipmentSlotForItem(CraftItemStack.asNMSCopy(ItemStack.of(this)))); - // } - -+ // Paper start - improve default attribute API -+ @Override -+ public @NotNull Multimap getDefaultAttributeModifiers() { -+ return this.getDefaultAttributeModifiers(sg -> true); -+ } -+ // Paper end - improve default attribute API -+ - @Override - public Multimap getDefaultAttributeModifiers(EquipmentSlot slot) { -+ // Paper start - improve/fix item default attribute API -+ final net.minecraft.world.entity.EquipmentSlot nmsSlot = CraftEquipmentSlot.getNMS(slot); -+ return this.getDefaultAttributeModifiers(sg -> sg.test(nmsSlot)); -+ } -+ -+ private Multimap getDefaultAttributeModifiers(final java.util.function.Predicate slotPredicate) { -+ // Paper end - improve/fix item default attribute API - ImmutableMultimap.Builder defaultAttributes = ImmutableMultimap.builder(); - - ItemAttributeModifiers nmsDefaultAttributes = this.item.components().getOrDefault(DataComponents.ATTRIBUTE_MODIFIERS, ItemAttributeModifiers.EMPTY); - if (nmsDefaultAttributes.modifiers().isEmpty()) { - nmsDefaultAttributes = this.item.getDefaultAttributeModifiers(); - } -- -- nmsDefaultAttributes.forEach(CraftEquipmentSlot.getNMS(slot), (key, value) -> { -- Attribute attribute = CraftAttribute.minecraftToBukkit(key.value()); -- defaultAttributes.put(attribute, CraftAttributeInstance.convert(value, slot)); -- }); -+ // Paper start - improve/fix item default attribute API -+ for (final net.minecraft.world.item.component.ItemAttributeModifiers.Entry entry : nmsDefaultAttributes.modifiers()) { -+ if (!slotPredicate.test(entry.slot())) continue; -+ final Attribute attribute = CraftAttribute.minecraftHolderToBukkit(entry.attribute()); -+ final AttributeModifier modifier = CraftAttributeInstance.convert(entry.modifier(), entry.slot()); -+ defaultAttributes.put(attribute, modifier); -+ } -+ // Paper end - improve/fix item default attribute API - - return defaultAttributes.build(); - } -diff --git a/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java b/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java -index 78d1b33a554ac8ca0f76585c6b97e35c2d337293..5e8081350b2ec375373d8197bd1f3196652ec9d9 100644 ---- a/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java -+++ b/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java -@@ -391,7 +391,11 @@ public final class CraftMagicNumbers implements UnsafeValues { - - @Override - public Multimap getDefaultAttributeModifiers(Material material, EquipmentSlot slot) { -- return material.getDefaultAttributeModifiers(slot); -+ // Paper start - delegate to method on ItemType -+ final org.bukkit.inventory.ItemType item = material.asItemType(); -+ Preconditions.checkArgument(item != null, material + " is not an item and does not have default attributes"); -+ return item.getDefaultAttributeModifiers(slot); -+ // Paper end - delegate to method on ItemType - } - - @Override diff --git a/patches/server/0540-Add-PufferFishStateChangeEvent.patch b/patches/server/0540-Add-PufferFishStateChangeEvent.patch new file mode 100644 index 000000000000..0574c7ed8c10 --- /dev/null +++ b/patches/server/0540-Add-PufferFishStateChangeEvent.patch @@ -0,0 +1,50 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: HexedHero <6012891+HexedHero@users.noreply.github.com> +Date: Mon, 10 May 2021 16:59:05 +0100 +Subject: [PATCH] Add PufferFishStateChangeEvent + + +diff --git a/src/main/java/net/minecraft/world/entity/animal/Pufferfish.java b/src/main/java/net/minecraft/world/entity/animal/Pufferfish.java +index 6c5fa151867eb2b15b9aaf94eb4f5309c415a92b..cdb74f86ee92ee143af29962a85d45ca585cee44 100644 +--- a/src/main/java/net/minecraft/world/entity/animal/Pufferfish.java ++++ b/src/main/java/net/minecraft/world/entity/animal/Pufferfish.java +@@ -102,25 +102,39 @@ public class Pufferfish extends AbstractFish { + public void tick() { + if (!this.level().isClientSide && this.isAlive() && this.isEffectiveAi()) { + if (this.inflateCounter > 0) { ++ boolean increase = true; // Paper - Add PufferFishStateChangeEvent + if (this.getPuffState() == 0) { ++ if (new io.papermc.paper.event.entity.PufferFishStateChangeEvent((org.bukkit.entity.PufferFish) getBukkitEntity(), 1).callEvent()) { // Paper - Add PufferFishStateChangeEvent + this.makeSound(SoundEvents.PUFFER_FISH_BLOW_UP); + this.setPuffState(1); ++ } else { increase = false; } // Paper - Add PufferFishStateChangeEvent + } else if (this.inflateCounter > 40 && this.getPuffState() == 1) { ++ if (new io.papermc.paper.event.entity.PufferFishStateChangeEvent((org.bukkit.entity.PufferFish) getBukkitEntity(), 2).callEvent()) { // Paper - Add PufferFishStateChangeEvent + this.makeSound(SoundEvents.PUFFER_FISH_BLOW_UP); + this.setPuffState(2); ++ } else { increase = false; } // Paper - Add PufferFishStateChangeEvent + } + ++ if (increase) { // Paper - Add PufferFishStateChangeEvent + ++this.inflateCounter; ++ } // Paper - Add PufferFishStateChangeEvent + } else if (this.getPuffState() != 0) { ++ boolean increase = true; // Paper - Add PufferFishStateChangeEvent + if (this.deflateTimer > 60 && this.getPuffState() == 2) { ++ if (new io.papermc.paper.event.entity.PufferFishStateChangeEvent((org.bukkit.entity.PufferFish) getBukkitEntity(), 1).callEvent()) { // Paper - Add PufferFishStateChangeEvent + this.makeSound(SoundEvents.PUFFER_FISH_BLOW_OUT); + this.setPuffState(1); ++ } else { increase = false; } // Paper - Add PufferFishStateChangeEvent + } else if (this.deflateTimer > 100 && this.getPuffState() == 1) { ++ if (new io.papermc.paper.event.entity.PufferFishStateChangeEvent((org.bukkit.entity.PufferFish) getBukkitEntity(), 0).callEvent()) { // Paper - Add PufferFishStateChangeEvent + this.makeSound(SoundEvents.PUFFER_FISH_BLOW_OUT); + this.setPuffState(0); ++ } else { increase = false; } // Paper - Add PufferFishStateChangeEvent + } + ++ if (increase) { // Paper - Add PufferFishStateChangeEvent + ++this.deflateTimer; ++ } // Paper - Add PufferFishStateChangeEvent + } + } + diff --git a/patches/server/0540-Add-cause-to-Weather-ThunderChangeEvents.patch b/patches/server/0540-Add-cause-to-Weather-ThunderChangeEvents.patch deleted file mode 100644 index f1396c14e285..000000000000 --- a/patches/server/0540-Add-cause-to-Weather-ThunderChangeEvents.patch +++ /dev/null @@ -1,118 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Jake Potrebic -Date: Wed, 2 Dec 2020 18:23:26 -0800 -Subject: [PATCH] Add cause to Weather/ThunderChangeEvents - - -diff --git a/src/main/java/net/minecraft/server/level/ServerLevel.java b/src/main/java/net/minecraft/server/level/ServerLevel.java -index b877884ef4adf82d5ca9c56277ef27a79773b711..08d25d41641cd031d2d84843268b13e650d8fb00 100644 ---- a/src/main/java/net/minecraft/server/level/ServerLevel.java -+++ b/src/main/java/net/minecraft/server/level/ServerLevel.java -@@ -441,8 +441,8 @@ public class ServerLevel extends Level implements WorldGenLevel { - this.serverLevelData.setClearWeatherTime(clearDuration); - this.serverLevelData.setRainTime(rainDuration); - this.serverLevelData.setThunderTime(rainDuration); -- this.serverLevelData.setRaining(raining); -- this.serverLevelData.setThundering(thundering); -+ this.serverLevelData.setRaining(raining, org.bukkit.event.weather.WeatherChangeEvent.Cause.COMMAND); // Paper - Add cause to Weather/ThunderChangeEvents -+ this.serverLevelData.setThundering(thundering, org.bukkit.event.weather.ThunderChangeEvent.Cause.COMMAND); // Paper - Add cause to Weather/ThunderChangeEvents - } - - @Override -@@ -875,8 +875,8 @@ public class ServerLevel extends Level implements WorldGenLevel { - this.serverLevelData.setThunderTime(j); - this.serverLevelData.setRainTime(k); - this.serverLevelData.setClearWeatherTime(i); -- this.serverLevelData.setThundering(flag1); -- this.serverLevelData.setRaining(flag2); -+ this.serverLevelData.setThundering(flag1, org.bukkit.event.weather.ThunderChangeEvent.Cause.NATURAL); // Paper - Add cause to Weather/ThunderChangeEvents -+ this.serverLevelData.setRaining(flag2, org.bukkit.event.weather.WeatherChangeEvent.Cause.NATURAL); // Paper - Add cause to Weather/ThunderChangeEvents - } - - this.oThunderLevel = this.thunderLevel; -@@ -943,14 +943,14 @@ public class ServerLevel extends Level implements WorldGenLevel { - @VisibleForTesting - public void resetWeatherCycle() { - // CraftBukkit start -- this.serverLevelData.setRaining(false); -+ this.serverLevelData.setRaining(false, org.bukkit.event.weather.WeatherChangeEvent.Cause.SLEEP); // Paper - Add cause to Weather/ThunderChangeEvents - // If we stop due to everyone sleeping we should reset the weather duration to some other random value. - // Not that everyone ever manages to get the whole server to sleep at the same time.... - if (!this.serverLevelData.isRaining()) { - this.serverLevelData.setRainTime(0); - } - // CraftBukkit end -- this.serverLevelData.setThundering(false); -+ this.serverLevelData.setThundering(false, org.bukkit.event.weather.ThunderChangeEvent.Cause.SLEEP); // Paper - Add cause to Weather/ThunderChangeEvents - // CraftBukkit start - // If we stop due to everyone sleeping we should reset the weather duration to some other random value. - // Not that everyone ever manages to get the whole server to sleep at the same time.... -diff --git a/src/main/java/net/minecraft/world/level/storage/PrimaryLevelData.java b/src/main/java/net/minecraft/world/level/storage/PrimaryLevelData.java -index e50ad48658193f889d65d37c57b1e30ce46758b7..efd0bcfebb3b4f63018d4e20a6a89f79192898d1 100644 ---- a/src/main/java/net/minecraft/world/level/storage/PrimaryLevelData.java -+++ b/src/main/java/net/minecraft/world/level/storage/PrimaryLevelData.java -@@ -337,6 +337,11 @@ public class PrimaryLevelData implements ServerLevelData, WorldData { - - @Override - public void setThundering(boolean thundering) { -+ // Paper start - Add cause to Weather/ThunderChangeEvents -+ this.setThundering(thundering, org.bukkit.event.weather.ThunderChangeEvent.Cause.UNKNOWN); -+ } -+ public void setThundering(boolean thundering, org.bukkit.event.weather.ThunderChangeEvent.Cause cause) { -+ // Paper end - Add cause to Weather/ThunderChangeEvents - // CraftBukkit start - if (this.thundering == thundering) { - return; -@@ -344,7 +349,7 @@ public class PrimaryLevelData implements ServerLevelData, WorldData { - - org.bukkit.World world = Bukkit.getWorld(this.getLevelName()); - if (world != null) { -- ThunderChangeEvent thunder = new ThunderChangeEvent(world, thundering); -+ ThunderChangeEvent thunder = new ThunderChangeEvent(world, thundering, cause); // Paper - Add cause to Weather/ThunderChangeEvents - Bukkit.getServer().getPluginManager().callEvent(thunder); - if (thunder.isCancelled()) { - return; -@@ -371,6 +376,12 @@ public class PrimaryLevelData implements ServerLevelData, WorldData { - - @Override - public void setRaining(boolean raining) { -+ // Paper start - Add cause to Weather/ThunderChangeEvents -+ this.setRaining(raining, org.bukkit.event.weather.WeatherChangeEvent.Cause.UNKNOWN); -+ } -+ -+ public void setRaining(boolean raining, org.bukkit.event.weather.WeatherChangeEvent.Cause cause) { -+ // Paper end - Add cause to Weather/ThunderChangeEvents - // CraftBukkit start - if (this.raining == raining) { - return; -@@ -378,7 +389,7 @@ public class PrimaryLevelData implements ServerLevelData, WorldData { - - org.bukkit.World world = Bukkit.getWorld(this.getLevelName()); - if (world != null) { -- WeatherChangeEvent weather = new WeatherChangeEvent(world, raining); -+ WeatherChangeEvent weather = new WeatherChangeEvent(world, raining, cause); // Paper - Add cause to Weather/ThunderChangeEvents - Bukkit.getServer().getPluginManager().callEvent(weather); - if (weather.isCancelled()) { - return; -diff --git a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java -index e882bc363f02b3acac489589e78b38468dfcaaa4..1cafba435f53762cd5790998b0d96e1e743612b5 100644 ---- a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java -+++ b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java -@@ -1210,7 +1210,7 @@ public class CraftWorld extends CraftRegionAccessor implements World { - - @Override - public void setStorm(boolean hasStorm) { -- this.world.levelData.setRaining(hasStorm); -+ this.world.serverLevelData.setRaining(hasStorm, org.bukkit.event.weather.WeatherChangeEvent.Cause.PLUGIN); // Paper - Add cause to Weather/ThunderChangeEvents - this.setWeatherDuration(0); // Reset weather duration (legacy behaviour) - this.setClearWeatherDuration(0); // Reset clear weather duration (reset "/weather clear" commands) - } -@@ -1232,7 +1232,7 @@ public class CraftWorld extends CraftRegionAccessor implements World { - - @Override - public void setThundering(boolean thundering) { -- this.world.serverLevelData.setThundering(thundering); -+ this.world.serverLevelData.setThundering(thundering, org.bukkit.event.weather.ThunderChangeEvent.Cause.PLUGIN); // Paper - Add cause to Weather/ThunderChangeEvents - this.setThunderDuration(0); // Reset weather duration (legacy behaviour) - this.setClearWeatherDuration(0); // Reset clear weather duration (reset "/weather clear" commands) - } diff --git a/patches/server/0541-Fix-PlayerBucketEmptyEvent-result-itemstack.patch b/patches/server/0541-Fix-PlayerBucketEmptyEvent-result-itemstack.patch new file mode 100644 index 000000000000..646e52a326b5 --- /dev/null +++ b/patches/server/0541-Fix-PlayerBucketEmptyEvent-result-itemstack.patch @@ -0,0 +1,42 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Jake Potrebic +Date: Thu, 20 May 2021 22:16:37 -0700 +Subject: [PATCH] Fix PlayerBucketEmptyEvent result itemstack + +Fixes SPIGOT-2560: https://hub.spigotmc.org/jira/projects/SPIGOT/issues/SPIGOT-2560 + +diff --git a/src/main/java/net/minecraft/world/item/BucketItem.java b/src/main/java/net/minecraft/world/item/BucketItem.java +index a6aaef9de23bf8084ab13c8f704e9f59de3acdcf..002e2f8e956b2631529e2189be225385dfb501df 100644 +--- a/src/main/java/net/minecraft/world/item/BucketItem.java ++++ b/src/main/java/net/minecraft/world/item/BucketItem.java +@@ -40,6 +40,8 @@ import org.bukkit.event.player.PlayerBucketFillEvent; + + public class BucketItem extends Item implements DispensibleContainerItem { + ++ private static @Nullable ItemStack itemLeftInHandAfterPlayerBucketEmptyEvent = null; // Paper - Fix PlayerBucketEmptyEvent result itemstack ++ + public final Fluid content; + + public BucketItem(Fluid fluid, Item.Properties settings) { +@@ -125,6 +127,13 @@ public class BucketItem extends Item implements DispensibleContainerItem { + } + + public static ItemStack getEmptySuccessItem(ItemStack stack, Player player) { ++ // Paper start - Fix PlayerBucketEmptyEvent result itemstack ++ if (itemLeftInHandAfterPlayerBucketEmptyEvent != null) { ++ ItemStack itemInHand = itemLeftInHandAfterPlayerBucketEmptyEvent; ++ itemLeftInHandAfterPlayerBucketEmptyEvent = null; ++ return itemInHand; ++ } ++ // Paper end - Fix PlayerBucketEmptyEvent result itemstack + return !player.hasInfiniteMaterials() ? new ItemStack(Items.BUCKET) : stack; + } + +@@ -182,6 +191,7 @@ public class BucketItem extends Item implements DispensibleContainerItem { + ((ServerPlayer) entityhuman).getBukkitEntity().updateInventory(); // SPIGOT-4541 + return false; + } ++ itemLeftInHandAfterPlayerBucketEmptyEvent = event.getItemStack() != null ? event.getItemStack().equals(CraftItemStack.asNewCraftStack(net.minecraft.world.item.Items.BUCKET)) ? null : CraftItemStack.asNMSCopy(event.getItemStack()) : ItemStack.EMPTY; // Paper - Fix PlayerBucketEmptyEvent result itemstack + } + // CraftBukkit end + if (!flag2) { diff --git a/patches/server/0542-Limit-item-frame-cursors-on-maps.patch b/patches/server/0542-Limit-item-frame-cursors-on-maps.patch deleted file mode 100644 index d710b596857f..000000000000 --- a/patches/server/0542-Limit-item-frame-cursors-on-maps.patch +++ /dev/null @@ -1,30 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Yive -Date: Wed, 26 May 2021 15:09:33 -0700 -Subject: [PATCH] Limit item frame cursors on maps - - -diff --git a/src/main/java/net/minecraft/world/level/saveddata/maps/MapItemSavedData.java b/src/main/java/net/minecraft/world/level/saveddata/maps/MapItemSavedData.java -index d6a0a882331226c3ae4ced09e449eb7931740f8f..a43544704109f21bab230dd9bf0401e28f878582 100644 ---- a/src/main/java/net/minecraft/world/level/saveddata/maps/MapItemSavedData.java -+++ b/src/main/java/net/minecraft/world/level/saveddata/maps/MapItemSavedData.java -@@ -322,8 +322,10 @@ public class MapItemSavedData extends SavedData { - - MapFrame worldmapframe1 = new MapFrame(blockposition, entityitemframe.getDirection().get2DDataValue() * 90, entityitemframe.getId()); - -+ if (this.decorations.size() < player.level().paperConfig().maps.itemFrameCursorLimit) { // Paper - Limit item frame cursors on maps - this.addDecoration(MapDecorationTypes.FRAME, player.level(), MapItemSavedData.getFrameKey(entityitemframe.getId()), (double) blockposition.getX(), (double) blockposition.getZ(), (double) (entityitemframe.getDirection().get2DDataValue() * 90), (Component) null); - this.frameMarkers.put(worldmapframe1.getId(), worldmapframe1); -+ } // Paper - Limit item frame cursors on maps - } - - MapDecorations mapdecorations = (MapDecorations) stack.getOrDefault(DataComponents.MAP_DECORATIONS, MapDecorations.EMPTY); -@@ -488,7 +490,7 @@ public class MapItemSavedData extends SavedData { - return true; - } - -- if (!this.isTrackedCountOverLimit(256)) { -+ if (!this.isTrackedCountOverLimit(((Level) world).paperConfig().maps.itemFrameCursorLimit)) { // Paper - Limit item frame cursors on maps - this.bannerMarkers.put(mapiconbanner.getId(), mapiconbanner); - this.addDecoration(mapiconbanner.getDecoration(), world, mapiconbanner.getId(), d0, d1, 180.0D, (Component) mapiconbanner.name().orElse(null)); // CraftBukkit - decompile error - return true; diff --git a/patches/server/0542-Synchronize-PalettedContainer-instead-of-ThreadingDe.patch b/patches/server/0542-Synchronize-PalettedContainer-instead-of-ThreadingDe.patch new file mode 100644 index 000000000000..ee4d412a1c21 --- /dev/null +++ b/patches/server/0542-Synchronize-PalettedContainer-instead-of-ThreadingDe.patch @@ -0,0 +1,91 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Aikar +Date: Fri, 29 May 2020 20:29:02 -0400 +Subject: [PATCH] Synchronize PalettedContainer instead of + ThreadingDetector/Semaphore + +Mojang has flaws in their logic about chunks being concurrently +wrote to. So we constantly see crashes around multiple threads writing. + +Additionally, java has optimized synchronization so well that its +in many times faster than trying to manage read write locks for low +contention situations. + +And this is extremely a low contention situation. + +diff --git a/src/main/java/net/minecraft/world/level/chunk/PalettedContainer.java b/src/main/java/net/minecraft/world/level/chunk/PalettedContainer.java +index 8c318c5f9eaaae1e961ff24247283c232fa84c20..112d1259dd37743076ff6c67ffd711d084ba8698 100644 +--- a/src/main/java/net/minecraft/world/level/chunk/PalettedContainer.java ++++ b/src/main/java/net/minecraft/world/level/chunk/PalettedContainer.java +@@ -30,14 +30,14 @@ public class PalettedContainer implements PaletteResize, PalettedContainer + public final IdMap registry; + private volatile PalettedContainer.Data data; + private final PalettedContainer.Strategy strategy; +- private final ThreadingDetector threadingDetector = new ThreadingDetector("PalettedContainer"); ++ // private final ThreadingDetector threadingDetector = new ThreadingDetector("PalettedContainer"); // Paper - unused + + public void acquire() { +- this.threadingDetector.checkAndLock(); ++ // this.threadingDetector.checkAndLock(); // Paper - disable this - use proper synchronization + } + + public void release() { +- this.threadingDetector.checkAndUnlock(); ++ // this.threadingDetector.checkAndUnlock(); // Paper - disable this + } + + public static Codec> codecRW(IdMap idList, Codec entryCodec, PalettedContainer.Strategy paletteProvider, T defaultValue) { +@@ -110,7 +110,7 @@ public class PalettedContainer implements PaletteResize, PalettedContainer + } + + @Override +- public int onResize(int newBits, T object) { ++ public synchronized int onResize(int newBits, T object) { // Paper - synchronize + PalettedContainer.Data data = this.data; + PalettedContainer.Data data2 = this.createOrReuseData(data, newBits); + data2.copyFrom(data.palette, data.storage); +@@ -135,7 +135,7 @@ public class PalettedContainer implements PaletteResize, PalettedContainer + return this.getAndSet(this.strategy.getIndex(x, y, z), value); + } + +- private T getAndSet(int index, T value) { ++ private synchronized T getAndSet(int index, T value) { // Paper - synchronize + int i = this.data.palette.idFor(value); + int j = this.data.storage.getAndSet(index, i); + return this.data.palette.valueFor(j); +@@ -151,7 +151,7 @@ public class PalettedContainer implements PaletteResize, PalettedContainer + } + } + +- private void set(int index, T value) { ++ private synchronized void set(int index, T value) { // Paper - synchronize + int i = this.data.palette.idFor(value); + this.data.storage.set(index, i); + } +@@ -174,7 +174,7 @@ public class PalettedContainer implements PaletteResize, PalettedContainer + intSet.forEach(id -> action.accept(palette.valueFor(id))); + } + +- public void read(FriendlyByteBuf buf) { ++ public synchronized void read(FriendlyByteBuf buf) { // Paper - synchronize + this.acquire(); + + try { +@@ -189,7 +189,7 @@ public class PalettedContainer implements PaletteResize, PalettedContainer + } + + @Override +- public void write(FriendlyByteBuf buf) { ++ public synchronized void write(FriendlyByteBuf buf) { // Paper - synchronize + this.acquire(); + + try { +@@ -237,7 +237,7 @@ public class PalettedContainer implements PaletteResize, PalettedContainer + } + + @Override +- public PalettedContainerRO.PackedData pack(IdMap idList, PalettedContainer.Strategy paletteProvider) { ++ public synchronized PalettedContainerRO.PackedData pack(IdMap idList, PalettedContainer.Strategy paletteProvider) { // Paper - synchronize + this.acquire(); + + PalettedContainerRO.PackedData var12; diff --git a/patches/server/0543-Add-PlayerKickEvent-causes.patch b/patches/server/0543-Add-PlayerKickEvent-causes.patch deleted file mode 100644 index 62954e6153eb..000000000000 --- a/patches/server/0543-Add-PlayerKickEvent-causes.patch +++ /dev/null @@ -1,557 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Jake Potrebic -Date: Sat, 15 May 2021 20:30:45 -0700 -Subject: [PATCH] Add PlayerKickEvent causes - - -diff --git a/src/main/java/net/minecraft/network/chat/SignedMessageChain.java b/src/main/java/net/minecraft/network/chat/SignedMessageChain.java -index dbcf183483766f39334d7f7e8336033906625f3f..300929a406905f5ff1ede664d5b99fb0938d4d2e 100644 ---- a/src/main/java/net/minecraft/network/chat/SignedMessageChain.java -+++ b/src/main/java/net/minecraft/network/chat/SignedMessageChain.java -@@ -40,14 +40,14 @@ public class SignedMessageChain { - if (signature == null) { - throw new SignedMessageChain.DecodeException(SignedMessageChain.DecodeException.MISSING_PROFILE_KEY); - } else if (playerPublicKey.data().hasExpired()) { -- throw new SignedMessageChain.DecodeException(SignedMessageChain.DecodeException.EXPIRED_PROFILE_KEY); -+ throw new SignedMessageChain.DecodeException(SignedMessageChain.DecodeException.EXPIRED_PROFILE_KEY, org.bukkit.event.player.PlayerKickEvent.Cause.EXPIRED_PROFILE_PUBLIC_KEY); // Paper - kick event causes - } else { - SignedMessageLink signedMessageLink = SignedMessageChain.this.nextLink; - if (signedMessageLink == null) { - throw new SignedMessageChain.DecodeException(SignedMessageChain.DecodeException.CHAIN_BROKEN); - } else if (body.timeStamp().isBefore(SignedMessageChain.this.lastTimeStamp)) { - this.setChainBroken(); -- throw new SignedMessageChain.DecodeException(SignedMessageChain.DecodeException.OUT_OF_ORDER_CHAT); -+ throw new SignedMessageChain.DecodeException(SignedMessageChain.DecodeException.OUT_OF_ORDER_CHAT, org.bukkit.event.player.PlayerKickEvent.Cause.OUT_OF_ORDER_CHAT); // Paper - kick event causes - } else { - SignedMessageChain.this.lastTimeStamp = body.timeStamp(); - PlayerChatMessage playerChatMessage = new PlayerChatMessage(signedMessageLink, signature, body, null, FilterMask.PASS_THROUGH); -@@ -80,8 +80,15 @@ public class SignedMessageChain { - static final Component INVALID_SIGNATURE = Component.translatable("chat.disabled.invalid_signature"); - static final Component OUT_OF_ORDER_CHAT = Component.translatable("chat.disabled.out_of_order_chat"); - -- public DecodeException(Component message) { -+ // Paper start -+ public final org.bukkit.event.player.PlayerKickEvent.Cause kickCause; -+ public DecodeException(Component message, org.bukkit.event.player.PlayerKickEvent.Cause event) { - super(message); -+ this.kickCause = event; -+ } -+ // Paper end -+ public DecodeException(Component message) { -+ this(message, org.bukkit.event.player.PlayerKickEvent.Cause.UNKNOWN); // Paper - } - } - -diff --git a/src/main/java/net/minecraft/server/MinecraftServer.java b/src/main/java/net/minecraft/server/MinecraftServer.java -index 6651afa50cfaf53959f89108904d4bf65a0fc495..56589fee8dd09783e01f2ae290ffacb5dff3f05f 100644 ---- a/src/main/java/net/minecraft/server/MinecraftServer.java -+++ b/src/main/java/net/minecraft/server/MinecraftServer.java -@@ -2286,7 +2286,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop Component.translatable("commands.kick.success", serverPlayer.getDisplayName(), reason), true); - i++; - } -diff --git a/src/main/java/net/minecraft/server/network/ServerCommonPacketListenerImpl.java b/src/main/java/net/minecraft/server/network/ServerCommonPacketListenerImpl.java -index a1124405412cdac673f34a63988e7be957506dba..64450024ce8094874875321537ddab71ab5206fa 100644 ---- a/src/main/java/net/minecraft/server/network/ServerCommonPacketListenerImpl.java -+++ b/src/main/java/net/minecraft/server/network/ServerCommonPacketListenerImpl.java -@@ -62,8 +62,8 @@ public abstract class ServerCommonPacketListenerImpl implements ServerCommonPack - } - - @Override -- public void kickPlayer(Component reason) { -- this.disconnect(reason); -+ public void kickPlayer(Component reason, org.bukkit.event.player.PlayerKickEvent.Cause cause) { // Paper - kick event causes -+ this.disconnect(reason, cause); // Paper - kick event causes - } - // CraftBukkit end - private static final Logger LOGGER = LogUtils.getLogger(); -@@ -133,7 +133,7 @@ public abstract class ServerCommonPacketListenerImpl implements ServerCommonPack - } else if (!this.isSingleplayerOwner()) { - // Paper start - This needs to be handled on the main thread for plugins - server.submit(() -> { -- this.disconnect(ServerCommonPacketListenerImpl.TIMEOUT_DISCONNECTION_MESSAGE); -+ this.disconnect(ServerCommonPacketListenerImpl.TIMEOUT_DISCONNECTION_MESSAGE, PlayerKickEvent.Cause.TIMEOUT); // Paper - kick event cause - }); - // Paper end - This needs to be handled on the main thread for plugins - } -@@ -169,7 +169,7 @@ public abstract class ServerCommonPacketListenerImpl implements ServerCommonPack - } - } catch (Exception ex) { - ServerGamePacketListenerImpl.LOGGER.error("Couldn\'t register custom payload", ex); -- this.disconnect(Component.literal("Invalid payload REGISTER!")); -+ this.disconnect(Component.literal("Invalid payload REGISTER!"), PlayerKickEvent.Cause.INVALID_PAYLOAD); // Paper - kick event cause - } - } else if (identifier.equals(ServerCommonPacketListenerImpl.CUSTOM_UNREGISTER)) { - try { -@@ -179,7 +179,7 @@ public abstract class ServerCommonPacketListenerImpl implements ServerCommonPack - } - } catch (Exception ex) { - ServerGamePacketListenerImpl.LOGGER.error("Couldn\'t unregister custom payload", ex); -- this.disconnect(Component.literal("Invalid payload UNREGISTER!")); -+ this.disconnect(Component.literal("Invalid payload UNREGISTER!"), PlayerKickEvent.Cause.INVALID_PAYLOAD); // Paper - kick event cause - } - } else { - try { -@@ -197,7 +197,7 @@ public abstract class ServerCommonPacketListenerImpl implements ServerCommonPack - this.cserver.getMessenger().dispatchIncomingMessage(this.player.getBukkitEntity(), identifier.toString(), data); - } catch (Exception ex) { - ServerGamePacketListenerImpl.LOGGER.error("Couldn\'t dispatch custom payload", ex); -- this.disconnect(Component.literal("Invalid custom payload!")); -+ this.disconnect(Component.literal("Invalid custom payload!"), PlayerKickEvent.Cause.INVALID_PAYLOAD); // Paper - kick event cause - } - } - -@@ -213,7 +213,7 @@ public abstract class ServerCommonPacketListenerImpl implements ServerCommonPack - PacketUtils.ensureRunningOnSameThread(packet, this, (BlockableEventLoop) this.server); - if (packet.action() == ServerboundResourcePackPacket.Action.DECLINED && this.server.isResourcePackRequired()) { - ServerCommonPacketListenerImpl.LOGGER.info("Disconnecting {} due to resource pack {} rejection", this.playerProfile().getName(), packet.id()); -- this.disconnect((Component) Component.translatable("multiplayer.requiredTexturePrompt.disconnect")); -+ this.disconnect((Component) Component.translatable("multiplayer.requiredTexturePrompt.disconnect"), PlayerKickEvent.Cause.RESOURCE_PACK_REJECTION); // Paper - kick event cause - } - // Paper start - adventure pack callbacks - // call the callbacks before the previously-existing event so the event has final say -@@ -243,7 +243,7 @@ public abstract class ServerCommonPacketListenerImpl implements ServerCommonPack - return; - } - // CraftBukkit end -- this.disconnect(ServerCommonPacketListenerImpl.DISCONNECT_UNEXPECTED_QUERY); -+ this.disconnect(ServerCommonPacketListenerImpl.DISCONNECT_UNEXPECTED_QUERY, PlayerKickEvent.Cause.INVALID_COOKIE); // Paper - kick event cause - } - - protected void keepConnectionAlive() { -@@ -255,7 +255,7 @@ public abstract class ServerCommonPacketListenerImpl implements ServerCommonPack - - if (!this.isSingleplayerOwner() && elapsedTime >= 15000L) { // Paper - use vanilla's 15000L between keep alive packets - if (this.keepAlivePending && !this.processedDisconnect && elapsedTime >= KEEPALIVE_LIMIT) { // Paper - check keepalive limit, don't fire if already disconnected -- this.disconnect(ServerCommonPacketListenerImpl.TIMEOUT_DISCONNECTION_MESSAGE); -+ this.disconnect(ServerCommonPacketListenerImpl.TIMEOUT_DISCONNECTION_MESSAGE, PlayerKickEvent.Cause.TIMEOUT); // Paper - kick event cause - } else if (this.checkIfClosed(currentTime)) { // Paper - this.keepAlivePending = true; - this.keepAliveTime = currentTime; -@@ -271,7 +271,7 @@ public abstract class ServerCommonPacketListenerImpl implements ServerCommonPack - private boolean checkIfClosed(long time) { - if (this.closed) { - if (time - this.closedListenerTime >= 15000L) { -- this.disconnect(ServerCommonPacketListenerImpl.TIMEOUT_DISCONNECTION_MESSAGE); -+ this.disconnect(ServerCommonPacketListenerImpl.TIMEOUT_DISCONNECTION_MESSAGE, PlayerKickEvent.Cause.TIMEOUT); // Paper - kick event cause - } - - return false; -@@ -323,15 +323,25 @@ public abstract class ServerCommonPacketListenerImpl implements ServerCommonPack - - // Paper start - adventure - public void disconnect(final net.kyori.adventure.text.Component reason) { -- this.disconnect(io.papermc.paper.adventure.PaperAdventure.asVanilla(reason)); -+ this.disconnect(reason, PlayerKickEvent.Cause.UNKNOWN); -+ } -+ public void disconnect(final net.kyori.adventure.text.Component reason, PlayerKickEvent.Cause cause) { -+ this.disconnect(io.papermc.paper.adventure.PaperAdventure.asVanilla(reason), cause); -+ // Paper end - kick event causes - } - // Paper end - adventure - -+ @Deprecated @io.papermc.paper.annotation.DoNotUse // Paper - kick event causes - public void disconnect(Component reason) { -- this.disconnect(new DisconnectionDetails(reason)); -+ // Paper start - kick event causes -+ this.disconnect(reason, PlayerKickEvent.Cause.UNKNOWN); -+ } -+ public void disconnect(final Component reason, PlayerKickEvent.Cause cause) { -+ this.disconnect(new DisconnectionDetails(reason), cause); -+ // Paper end - kick event causes - } - -- public void disconnect(DisconnectionDetails disconnectionInfo) { -+ public void disconnect(DisconnectionDetails disconnectionInfo, PlayerKickEvent.Cause cause) { // Paper - kick event cause - // CraftBukkit start - fire PlayerKickEvent - if (this.processedDisconnect) { - return; -@@ -340,7 +350,7 @@ public abstract class ServerCommonPacketListenerImpl implements ServerCommonPack - Waitable waitable = new Waitable() { - @Override - protected Object evaluate() { -- ServerCommonPacketListenerImpl.this.disconnect(disconnectionInfo); -+ ServerCommonPacketListenerImpl.this.disconnect(disconnectionInfo, cause); // Paper - kick event causes - return null; - } - }; -@@ -359,7 +369,7 @@ public abstract class ServerCommonPacketListenerImpl implements ServerCommonPack - - net.kyori.adventure.text.Component leaveMessage = net.kyori.adventure.text.Component.translatable("multiplayer.player.left", net.kyori.adventure.text.format.NamedTextColor.YELLOW, io.papermc.paper.configuration.GlobalConfiguration.get().messages.useDisplayNameInQuitMessage ? this.player.getBukkitEntity().displayName() : net.kyori.adventure.text.Component.text(this.player.getScoreboardName())); // Paper - Adventure - -- PlayerKickEvent event = new PlayerKickEvent(this.player.getBukkitEntity(), io.papermc.paper.adventure.PaperAdventure.asAdventure(disconnectionInfo.reason()), leaveMessage); // Paper - adventure -+ PlayerKickEvent event = new PlayerKickEvent(this.player.getBukkitEntity(), io.papermc.paper.adventure.PaperAdventure.asAdventure(disconnectionInfo.reason()), leaveMessage, cause); // Paper - adventure & kick event causes - - if (this.cserver.getServer().isRunning()) { - this.cserver.getPluginManager().callEvent(event); -diff --git a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java -index 568f5d7165521304c7a92f32984a1d605d545ad5..b30c71ad0cc2602d2c026433a94c9ca45977855e 100644 ---- a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java -+++ b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java -@@ -349,7 +349,7 @@ public class ServerGamePacketListenerImpl extends ServerCommonPacketListenerImpl - if (this.clientIsFloating && !this.player.isSleeping() && !this.player.isPassenger() && !this.player.isDeadOrDying()) { - if (++this.aboveGroundTickCount > this.getMaximumFlyingTicks(this.player)) { - ServerGamePacketListenerImpl.LOGGER.warn("{} was kicked for floating too long!", this.player.getName().getString()); -- this.disconnect(io.papermc.paper.configuration.GlobalConfiguration.get().messages.kick.flyingPlayer); // Paper - use configurable kick message -+ this.disconnect(io.papermc.paper.configuration.GlobalConfiguration.get().messages.kick.flyingPlayer, org.bukkit.event.player.PlayerKickEvent.Cause.FLYING_PLAYER); // Paper - use configurable kick message & kick event cause - return; - } - } else { -@@ -368,7 +368,7 @@ public class ServerGamePacketListenerImpl extends ServerCommonPacketListenerImpl - if (this.clientVehicleIsFloating && this.lastVehicle.getControllingPassenger() == this.player) { - if (++this.aboveGroundVehicleTickCount > this.getMaximumFlyingTicks(this.lastVehicle)) { - ServerGamePacketListenerImpl.LOGGER.warn("{} was kicked for floating a vehicle too long!", this.player.getName().getString()); -- this.disconnect(io.papermc.paper.configuration.GlobalConfiguration.get().messages.kick.flyingVehicle); // Paper - use configurable kick message -+ this.disconnect(io.papermc.paper.configuration.GlobalConfiguration.get().messages.kick.flyingVehicle, org.bukkit.event.player.PlayerKickEvent.Cause.FLYING_VEHICLE); // Paper - use configurable kick message & kick event cause - return; - } - } else { -@@ -399,7 +399,7 @@ public class ServerGamePacketListenerImpl extends ServerCommonPacketListenerImpl - - if (this.player.getLastActionTime() > 0L && this.server.getPlayerIdleTimeout() > 0 && Util.getMillis() - this.player.getLastActionTime() > (long) this.server.getPlayerIdleTimeout() * 1000L * 60L) { - this.player.resetLastActionTime(); // CraftBukkit - SPIGOT-854 -- this.disconnect((Component) Component.translatable("multiplayer.disconnect.idling")); -+ this.disconnect((Component) Component.translatable("multiplayer.disconnect.idling"), org.bukkit.event.player.PlayerKickEvent.Cause.IDLING); // Paper - kick event cause - } - - } -@@ -481,7 +481,7 @@ public class ServerGamePacketListenerImpl extends ServerCommonPacketListenerImpl - public void handleMoveVehicle(ServerboundMoveVehiclePacket packet) { - PacketUtils.ensureRunningOnSameThread(packet, this, this.player.serverLevel()); - if (ServerGamePacketListenerImpl.containsInvalidValues(packet.getX(), packet.getY(), packet.getZ(), packet.getYRot(), packet.getXRot())) { -- this.disconnect((Component) Component.translatable("multiplayer.disconnect.invalid_vehicle_movement")); -+ this.disconnect((Component) Component.translatable("multiplayer.disconnect.invalid_vehicle_movement"), org.bukkit.event.player.PlayerKickEvent.Cause.INVALID_VEHICLE_MOVEMENT); // Paper - kick event cause - } else if (!this.updateAwaitingTeleport()) { - Entity entity = this.player.getRootVehicle(); - -@@ -686,7 +686,7 @@ public class ServerGamePacketListenerImpl extends ServerCommonPacketListenerImpl - PacketUtils.ensureRunningOnSameThread(packet, this, this.player.serverLevel()); - if (packet.getId() == this.awaitingTeleport) { - if (this.awaitingPositionFromClient == null) { -- this.disconnect((Component) Component.translatable("multiplayer.disconnect.invalid_player_movement")); -+ this.disconnect((Component) Component.translatable("multiplayer.disconnect.invalid_player_movement"), org.bukkit.event.player.PlayerKickEvent.Cause.INVALID_PLAYER_MOVEMENT); // Paper - kick event cause - return; - } - -@@ -744,7 +744,7 @@ public class ServerGamePacketListenerImpl extends ServerCommonPacketListenerImpl - // PacketUtils.ensureRunningOnSameThread(packet, this, this.player.serverLevel()); // Paper - AsyncTabCompleteEvent; run this async - // CraftBukkit start - if (this.chatSpamTickCount.addAndGet(io.papermc.paper.configuration.GlobalConfiguration.get().spamLimiter.tabSpamIncrement) > io.papermc.paper.configuration.GlobalConfiguration.get().spamLimiter.tabSpamLimit && !this.server.getPlayerList().isOp(this.player.getGameProfile())) { // Paper - configurable tab spam limits -- this.disconnect(Component.translatable("disconnect.spam")); -+ this.disconnect(Component.translatable("disconnect.spam"), org.bukkit.event.player.PlayerKickEvent.Cause.SPAM); // Paper - Kick event cause - return; - } - // CraftBukkit end -@@ -909,7 +909,7 @@ public class ServerGamePacketListenerImpl extends ServerCommonPacketListenerImpl - // Paper start - validate pick item position - if (!(packet.getSlot() >= 0 && packet.getSlot() < this.player.getInventory().items.size())) { - ServerGamePacketListenerImpl.LOGGER.warn("{} tried to set an invalid carried item", this.player.getName().getString()); -- this.disconnect(Component.literal("Invalid hotbar selection (Hacking?)")); -+ this.disconnect(Component.literal("Invalid hotbar selection (Hacking?)"), org.bukkit.event.player.PlayerKickEvent.Cause.ILLEGAL_ACTION); // Paper - kick event cause - return; - } - this.player.getInventory().pickSlot(packet.getSlot()); // Paper - Diff above if changed -@@ -1110,14 +1110,14 @@ public class ServerGamePacketListenerImpl extends ServerCommonPacketListenerImpl - - if (byteTotal > byteAllowed) { - ServerGamePacketListenerImpl.LOGGER.warn("{} tried to send a book too large. Book size: {} - Allowed: {} - Pages: {}", this.player.getScoreboardName(), byteTotal, byteAllowed, pageList.size()); -- this.disconnect(Component.literal("Book too large!")); -+ this.disconnect(Component.literal("Book too large!"), org.bukkit.event.player.PlayerKickEvent.Cause.ILLEGAL_ACTION); // Paper - kick event cause - return; - } - } - // Paper end - Book size limits - // CraftBukkit start - if (this.lastBookTick + 20 > MinecraftServer.currentTick) { -- this.disconnect(Component.literal("Book edited too quickly!")); -+ this.disconnect(Component.literal("Book edited too quickly!"), org.bukkit.event.player.PlayerKickEvent.Cause.ILLEGAL_ACTION); // Paper - kick event cause - return; - } - this.lastBookTick = MinecraftServer.currentTick; -@@ -1229,7 +1229,7 @@ public class ServerGamePacketListenerImpl extends ServerCommonPacketListenerImpl - public void handleMovePlayer(ServerboundMovePlayerPacket packet) { - PacketUtils.ensureRunningOnSameThread(packet, this, this.player.serverLevel()); - if (ServerGamePacketListenerImpl.containsInvalidValues(packet.getX(0.0D), packet.getY(0.0D), packet.getZ(0.0D), packet.getYRot(0.0F), packet.getXRot(0.0F))) { -- this.disconnect((Component) Component.translatable("multiplayer.disconnect.invalid_player_movement")); -+ this.disconnect((Component) Component.translatable("multiplayer.disconnect.invalid_player_movement"), org.bukkit.event.player.PlayerKickEvent.Cause.INVALID_PLAYER_MOVEMENT); // Paper - kick event cause - } else { - ServerLevel worldserver = this.player.serverLevel(); - -@@ -1667,7 +1667,7 @@ public class ServerGamePacketListenerImpl extends ServerCommonPacketListenerImpl - this.dropCount++; - if (this.dropCount >= 20) { - ServerGamePacketListenerImpl.LOGGER.warn(this.player.getScoreboardName() + " dropped their items too quickly!"); -- this.disconnect(Component.literal("You dropped your items too quickly (Hacking?)")); -+ this.disconnect(Component.literal("You dropped your items too quickly (Hacking?)"), org.bukkit.event.player.PlayerKickEvent.Cause.ILLEGAL_ACTION); // Paper - kick event cause - return; - } - } -@@ -1955,7 +1955,7 @@ public class ServerGamePacketListenerImpl extends ServerCommonPacketListenerImpl - this.player.resetLastActionTime(); - } else { - ServerGamePacketListenerImpl.LOGGER.warn("{} tried to set an invalid carried item", this.player.getName().getString()); -- this.disconnect(Component.literal("Invalid hotbar selection (Hacking?)")); // CraftBukkit -+ this.disconnect(Component.literal("Invalid hotbar selection (Hacking?)"), org.bukkit.event.player.PlayerKickEvent.Cause.ILLEGAL_ACTION); // CraftBukkit // Paper - kick event cause - } - } - -@@ -2153,7 +2153,7 @@ public class ServerGamePacketListenerImpl extends ServerCommonPacketListenerImpl - - private void tryHandleChat(String s, Runnable runnable, boolean sync) { // CraftBukkit - if (ServerGamePacketListenerImpl.isChatMessageIllegal(s)) { -- this.disconnect((Component) Component.translatable("multiplayer.disconnect.illegal_characters")); -+ this.disconnect((Component) Component.translatable("multiplayer.disconnect.illegal_characters"), org.bukkit.event.player.PlayerKickEvent.Cause.ILLEGAL_CHARACTERS); // Paper - } else if (this.player.isRemoved() || this.player.getChatVisibility() == ChatVisiblity.HIDDEN) { // CraftBukkit - dead men tell no tales - this.send(new ClientboundSystemChatPacket(Component.translatable("chat.disabled.options").withStyle(ChatFormatting.RED), false)); - } else { -@@ -2176,7 +2176,7 @@ public class ServerGamePacketListenerImpl extends ServerCommonPacketListenerImpl - - if (optional.isEmpty()) { - ServerGamePacketListenerImpl.LOGGER.warn("Failed to validate message acknowledgements from {}", this.player.getName().getString()); -- this.disconnect(ServerGamePacketListenerImpl.CHAT_VALIDATION_FAILED); -+ this.disconnect(ServerGamePacketListenerImpl.CHAT_VALIDATION_FAILED, org.bukkit.event.player.PlayerKickEvent.Cause.CHAT_VALIDATION_FAILED); // Paper - kick event causes - } - - return optional; -@@ -2362,7 +2362,7 @@ public class ServerGamePacketListenerImpl extends ServerCommonPacketListenerImpl - // this.chatSpamTickCount += 20; - if (this.chatSpamTickCount.addAndGet(20) > 200 && !this.server.getPlayerList().isOp(this.player.getGameProfile()) && !this.server.isSingleplayerOwner(this.player.getGameProfile())) { - // CraftBukkit end -- this.disconnect((Component) Component.translatable("disconnect.spam")); -+ this.disconnect((Component) Component.translatable("disconnect.spam"), org.bukkit.event.player.PlayerKickEvent.Cause.SPAM); // Paper - kick event cause - } - - } -@@ -2374,7 +2374,7 @@ public class ServerGamePacketListenerImpl extends ServerCommonPacketListenerImpl - synchronized (this.lastSeenMessages) { - if (!this.lastSeenMessages.applyOffset(packet.offset())) { - ServerGamePacketListenerImpl.LOGGER.warn("Failed to validate message acknowledgements from {}", this.player.getName().getString()); -- this.disconnect(ServerGamePacketListenerImpl.CHAT_VALIDATION_FAILED); -+ this.disconnect(ServerGamePacketListenerImpl.CHAT_VALIDATION_FAILED, org.bukkit.event.player.PlayerKickEvent.Cause.CHAT_VALIDATION_FAILED); // Paper - kick event causes - } - - } -@@ -2522,7 +2522,7 @@ public class ServerGamePacketListenerImpl extends ServerCommonPacketListenerImpl - } - - if (i > 4096) { -- this.disconnect((Component) Component.translatable("multiplayer.disconnect.too_many_pending_chats")); -+ this.disconnect((Component) Component.translatable("multiplayer.disconnect.too_many_pending_chats"), org.bukkit.event.player.PlayerKickEvent.Cause.TOO_MANY_PENDING_CHATS); // Paper - kick event cause - } - - } -@@ -2580,7 +2580,7 @@ public class ServerGamePacketListenerImpl extends ServerCommonPacketListenerImpl - // Spigot Start - if ( entity == this.player && !this.player.isSpectator() ) - { -- this.disconnect( Component.literal( "Cannot interact with self!" ) ); -+ this.disconnect( Component.literal( "Cannot interact with self!" ), org.bukkit.event.player.PlayerKickEvent.Cause.SELF_INTERACTION ); // Paper - kick event cause - return; - } - // Spigot End -@@ -2693,7 +2693,7 @@ public class ServerGamePacketListenerImpl extends ServerCommonPacketListenerImpl - } - } - -- ServerGamePacketListenerImpl.this.disconnect((Component) Component.translatable("multiplayer.disconnect.invalid_entity_attacked")); -+ ServerGamePacketListenerImpl.this.disconnect((Component) Component.translatable("multiplayer.disconnect.invalid_entity_attacked"), org.bukkit.event.player.PlayerKickEvent.Cause.INVALID_ENTITY_ATTACKED); // Paper - add cause - ServerGamePacketListenerImpl.LOGGER.warn("Player {} tried to attack an invalid entity", ServerGamePacketListenerImpl.this.player.getName().getString()); - } - }); -@@ -3090,7 +3090,7 @@ public class ServerGamePacketListenerImpl extends ServerCommonPacketListenerImpl - // Paper start - auto recipe limit - if (!org.bukkit.Bukkit.isPrimaryThread()) { - if (this.recipeSpamPackets.addAndGet(io.papermc.paper.configuration.GlobalConfiguration.get().spamLimiter.recipeSpamIncrement) > io.papermc.paper.configuration.GlobalConfiguration.get().spamLimiter.recipeSpamLimit) { -- this.disconnect(net.minecraft.network.chat.Component.translatable("disconnect.spam")); -+ this.disconnect(net.minecraft.network.chat.Component.translatable("disconnect.spam"), org.bukkit.event.player.PlayerKickEvent.Cause.SPAM); // Paper - kick event cause - return; - } - } -@@ -3332,7 +3332,7 @@ public class ServerGamePacketListenerImpl extends ServerCommonPacketListenerImpl - - if (!Objects.equals(profilepublickey_a, profilepublickey_a1)) { - if (profilepublickey_a != null && profilepublickey_a1.expiresAt().isBefore(profilepublickey_a.expiresAt())) { -- this.disconnect(ProfilePublicKey.EXPIRED_PROFILE_PUBLIC_KEY); -+ this.disconnect(ProfilePublicKey.EXPIRED_PROFILE_PUBLIC_KEY, org.bukkit.event.player.PlayerKickEvent.Cause.EXPIRED_PROFILE_PUBLIC_KEY); // Paper - kick event causes - } else { - try { - SignatureValidator signaturevalidator = this.server.getProfileKeySignatureValidator(); -@@ -3345,7 +3345,7 @@ public class ServerGamePacketListenerImpl extends ServerCommonPacketListenerImpl - this.resetPlayerChatState(remotechatsession_a.validate(this.player.getGameProfile(), signaturevalidator)); - } catch (ProfilePublicKey.ValidationException profilepublickey_b) { - ServerGamePacketListenerImpl.LOGGER.error("Failed to validate profile key: {}", profilepublickey_b.getMessage()); -- this.disconnect(profilepublickey_b.getComponent()); -+ this.disconnect(profilepublickey_b.getComponent(), profilepublickey_b.kickCause); // Paper - kick event causes - } - - } -diff --git a/src/main/java/net/minecraft/server/network/ServerLoginPacketListenerImpl.java b/src/main/java/net/minecraft/server/network/ServerLoginPacketListenerImpl.java -index d2d153e587e624025ef01fbe3dcfa4bf06f1a06b..e0a10f1d8bf2c0df66e62bdf2a174ce6a66bbd6d 100644 ---- a/src/main/java/net/minecraft/server/network/ServerLoginPacketListenerImpl.java -+++ b/src/main/java/net/minecraft/server/network/ServerLoginPacketListenerImpl.java -@@ -70,7 +70,7 @@ public class ServerLoginPacketListenerImpl implements ServerLoginPacketListener, - } - - @Override -- public void kickPlayer(Component reason) { -+ public void kickPlayer(Component reason, org.bukkit.event.player.PlayerKickEvent.Cause cause) { // Paper - kick event causes - during login, no event can be called. - this.disconnect(reason); - } - // CraftBukkit end -diff --git a/src/main/java/net/minecraft/server/players/PlayerList.java b/src/main/java/net/minecraft/server/players/PlayerList.java -index f7e442cd5eff3d7c6932d16c93a7a097f11251f9..393a93198b3a1d95a6cc5eb1d19e392f9ab7e2b9 100644 ---- a/src/main/java/net/minecraft/server/players/PlayerList.java -+++ b/src/main/java/net/minecraft/server/players/PlayerList.java -@@ -675,7 +675,7 @@ public abstract class PlayerList { - while (iterator.hasNext()) { - entityplayer = (ServerPlayer) iterator.next(); - this.save(entityplayer); // CraftBukkit - Force the player's inventory to be saved -- entityplayer.connection.disconnect(Component.translatable("multiplayer.disconnect.duplicate_login")); -+ entityplayer.connection.disconnect(Component.translatable("multiplayer.disconnect.duplicate_login"), org.bukkit.event.player.PlayerKickEvent.Cause.DUPLICATE_LOGIN); // Paper - kick event cause - } - - // Instead of kicking then returning, we need to store the kick reason -@@ -1278,7 +1278,7 @@ public abstract class PlayerList { - // Paper end - // CraftBukkit start - disconnect safely - for (ServerPlayer player : this.players) { -- if (isRestarting) player.connection.disconnect(net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer.legacySection().deserialize(org.spigotmc.SpigotConfig.restartMessage)); else // Paper -+ if (isRestarting) player.connection.disconnect(net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer.legacySection().deserialize(org.spigotmc.SpigotConfig.restartMessage), org.bukkit.event.player.PlayerKickEvent.Cause.UNKNOWN); else // Paper - kick event cause (cause is never used here) - player.connection.disconnect(java.util.Objects.requireNonNullElseGet(this.server.server.shutdownMessage(), net.kyori.adventure.text.Component::empty)); // CraftBukkit - add custom shutdown message // Paper - Adventure - } - // CraftBukkit end -diff --git a/src/main/java/net/minecraft/world/entity/player/ProfilePublicKey.java b/src/main/java/net/minecraft/world/entity/player/ProfilePublicKey.java -index f472dea0bd4f834c0c8f0aa59ae7cdae082b14af..2fa51c3a70f43cd23b8f494fc643d66cecfda7d2 100644 ---- a/src/main/java/net/minecraft/world/entity/player/ProfilePublicKey.java -+++ b/src/main/java/net/minecraft/world/entity/player/ProfilePublicKey.java -@@ -24,7 +24,7 @@ public record ProfilePublicKey(ProfilePublicKey.Data data) { - - public static ProfilePublicKey createValidated(SignatureValidator servicesSignatureVerifier, UUID playerUuid, ProfilePublicKey.Data publicKeyData) throws ProfilePublicKey.ValidationException { - if (!publicKeyData.validateSignature(servicesSignatureVerifier, playerUuid)) { -- throw new ProfilePublicKey.ValidationException(INVALID_SIGNATURE); -+ throw new ProfilePublicKey.ValidationException(INVALID_SIGNATURE, org.bukkit.event.player.PlayerKickEvent.Cause.INVALID_PUBLIC_KEY_SIGNATURE); // Paper - kick event causes - } else { - return new ProfilePublicKey(publicKeyData); - } -@@ -88,8 +88,16 @@ public record ProfilePublicKey(ProfilePublicKey.Data data) { - } - - public static class ValidationException extends ThrowingComponent { -+ public final org.bukkit.event.player.PlayerKickEvent.Cause kickCause; // Paper -+ @io.papermc.paper.annotation.DoNotUse @Deprecated // Paper - public ValidationException(Component messageText) { -+ // Paper start -+ this(messageText, org.bukkit.event.player.PlayerKickEvent.Cause.UNKNOWN); -+ } -+ public ValidationException(Component messageText, org.bukkit.event.player.PlayerKickEvent.Cause kickCause) { -+ // Paper end - super(messageText); -+ this.kickCause = kickCause; // Paper - } - } - } -diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java -index a85983b947147a1557908846b8773aab29c17fae..b747df28b862990d5db08329149272c67eb17d94 100644 ---- a/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java -+++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java -@@ -275,7 +275,7 @@ public class CraftPlayer extends CraftHumanEntity implements Player { - - void sendPacket(Packet packet); - -- void kickPlayer(Component reason); -+ void kickPlayer(Component reason, org.bukkit.event.player.PlayerKickEvent.Cause cause); // Paper - kick event causes - } - - public record CookieFuture(ResourceLocation key, CompletableFuture future) { -@@ -635,7 +635,7 @@ public class CraftPlayer extends CraftHumanEntity implements Player { - @Override - public void kickPlayer(String message) { - org.spigotmc.AsyncCatcher.catchOp("player kick"); // Spigot -- this.getHandle().transferCookieConnection.kickPlayer(CraftChatMessage.fromStringOrEmpty(message, true)); -+ this.getHandle().transferCookieConnection.kickPlayer(CraftChatMessage.fromStringOrEmpty(message, true), org.bukkit.event.player.PlayerKickEvent.Cause.PLUGIN); // Paper - kick event cause - } - - // Paper start -@@ -647,10 +647,15 @@ public class CraftPlayer extends CraftHumanEntity implements Player { - - @Override - public void kick(final net.kyori.adventure.text.Component message) { -+ kick(message, org.bukkit.event.player.PlayerKickEvent.Cause.PLUGIN); -+ } -+ -+ @Override -+ public void kick(net.kyori.adventure.text.Component message, org.bukkit.event.player.PlayerKickEvent.Cause cause) { - org.spigotmc.AsyncCatcher.catchOp("player kick"); - final ServerGamePacketListenerImpl connection = this.getHandle().connection; - if (connection != null) { -- connection.disconnect(message == null ? net.kyori.adventure.text.Component.empty() : message); -+ connection.disconnect(message == null ? net.kyori.adventure.text.Component.empty() : message, cause); - } - } - -@@ -709,7 +714,7 @@ public class CraftPlayer extends CraftHumanEntity implements Player { - - // Paper start - Improve chat handling - if (ServerGamePacketListenerImpl.isChatMessageIllegal(msg)) { -- this.getHandle().connection.disconnect(Component.translatable("multiplayer.disconnect.illegal_characters")); -+ this.getHandle().connection.disconnect(Component.translatable("multiplayer.disconnect.illegal_characters"), org.bukkit.event.player.PlayerKickEvent.Cause.ILLEGAL_CHARACTERS); // Paper - kick event causes - } else { - if (msg.startsWith("/")) { - this.getHandle().connection.handleCommand(msg); -diff --git a/src/main/java/org/spigotmc/RestartCommand.java b/src/main/java/org/spigotmc/RestartCommand.java -index 824c4ad135ea5177f416687c7042639ed126b70b..39e56b95aaafbcd8ebe68fdefaace83702e9510d 100644 ---- a/src/main/java/org/spigotmc/RestartCommand.java -+++ b/src/main/java/org/spigotmc/RestartCommand.java -@@ -74,7 +74,7 @@ public class RestartCommand extends Command - // Kick all players - for ( ServerPlayer p : com.google.common.collect.ImmutableList.copyOf( MinecraftServer.getServer().getPlayerList().players ) ) - { -- p.connection.disconnect( CraftChatMessage.fromStringOrEmpty( SpigotConfig.restartMessage, true ) ); -+ p.connection.disconnect( CraftChatMessage.fromStringOrEmpty( SpigotConfig.restartMessage, true ), org.bukkit.event.player.PlayerKickEvent.Cause.RESTART_COMMAND); // Paper - kick event reason (cause is never used)) - } - // Give the socket a chance to send the packets - try diff --git a/patches/server/0543-Add-option-to-fix-items-merging-through-walls.patch b/patches/server/0543-Add-option-to-fix-items-merging-through-walls.patch new file mode 100644 index 000000000000..b4e4e39ae1d7 --- /dev/null +++ b/patches/server/0543-Add-option-to-fix-items-merging-through-walls.patch @@ -0,0 +1,25 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: GioSDA +Date: Wed, 10 Mar 2021 10:06:45 -0800 +Subject: [PATCH] Add option to fix items merging through walls + + +diff --git a/src/main/java/net/minecraft/world/entity/item/ItemEntity.java b/src/main/java/net/minecraft/world/entity/item/ItemEntity.java +index 9974aec00935a1c3068eceee6d7042f14f15ac56..586257fe5c9f5cddd0ed164254f46777c6e71d66 100644 +--- a/src/main/java/net/minecraft/world/entity/item/ItemEntity.java ++++ b/src/main/java/net/minecraft/world/entity/item/ItemEntity.java +@@ -285,6 +285,14 @@ public class ItemEntity extends Entity implements TraceableEntity { + ItemEntity entityitem = (ItemEntity) iterator.next(); + + if (entityitem.isMergable()) { ++ // Paper start - Fix items merging through walls ++ if (this.level().paperConfig().fixes.fixItemsMergingThroughWalls) { ++ if (this.level().clipDirect(this.position(), entityitem.position(), ++ net.minecraft.world.phys.shapes.CollisionContext.of(this)) == net.minecraft.world.phys.HitResult.Type.BLOCK) { ++ continue; ++ } ++ } ++ // Paper end - Fix items merging through walls + this.tryToMerge(entityitem); + if (this.isRemoved()) { + break; diff --git a/patches/server/0548-Add-BellRevealRaiderEvent.patch b/patches/server/0544-Add-BellRevealRaiderEvent.patch similarity index 100% rename from patches/server/0548-Add-BellRevealRaiderEvent.patch rename to patches/server/0544-Add-BellRevealRaiderEvent.patch diff --git a/patches/server/0544-Add-PufferFishStateChangeEvent.patch b/patches/server/0544-Add-PufferFishStateChangeEvent.patch deleted file mode 100644 index 87e61bb2a359..000000000000 --- a/patches/server/0544-Add-PufferFishStateChangeEvent.patch +++ /dev/null @@ -1,50 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: HexedHero <6012891+HexedHero@users.noreply.github.com> -Date: Mon, 10 May 2021 16:59:05 +0100 -Subject: [PATCH] Add PufferFishStateChangeEvent - - -diff --git a/src/main/java/net/minecraft/world/entity/animal/Pufferfish.java b/src/main/java/net/minecraft/world/entity/animal/Pufferfish.java -index 9498bac12196637c187961424ef23ecb77eddff8..3f0fad476fe573c3ba946a9436d1b3f7c5260ee2 100644 ---- a/src/main/java/net/minecraft/world/entity/animal/Pufferfish.java -+++ b/src/main/java/net/minecraft/world/entity/animal/Pufferfish.java -@@ -101,25 +101,39 @@ public class Pufferfish extends AbstractFish { - public void tick() { - if (!this.level().isClientSide && this.isAlive() && this.isEffectiveAi()) { - if (this.inflateCounter > 0) { -+ boolean increase = true; // Paper - Add PufferFishStateChangeEvent - if (this.getPuffState() == 0) { -+ if (new io.papermc.paper.event.entity.PufferFishStateChangeEvent((org.bukkit.entity.PufferFish) getBukkitEntity(), 1).callEvent()) { // Paper - Add PufferFishStateChangeEvent - this.makeSound(SoundEvents.PUFFER_FISH_BLOW_UP); - this.setPuffState(1); -+ } else { increase = false; } // Paper - Add PufferFishStateChangeEvent - } else if (this.inflateCounter > 40 && this.getPuffState() == 1) { -+ if (new io.papermc.paper.event.entity.PufferFishStateChangeEvent((org.bukkit.entity.PufferFish) getBukkitEntity(), 2).callEvent()) { // Paper - Add PufferFishStateChangeEvent - this.makeSound(SoundEvents.PUFFER_FISH_BLOW_UP); - this.setPuffState(2); -+ } else { increase = false; } // Paper - Add PufferFishStateChangeEvent - } - -+ if (increase) { // Paper - Add PufferFishStateChangeEvent - ++this.inflateCounter; -+ } // Paper - Add PufferFishStateChangeEvent - } else if (this.getPuffState() != 0) { -+ boolean increase = true; // Paper - Add PufferFishStateChangeEvent - if (this.deflateTimer > 60 && this.getPuffState() == 2) { -+ if (new io.papermc.paper.event.entity.PufferFishStateChangeEvent((org.bukkit.entity.PufferFish) getBukkitEntity(), 1).callEvent()) { // Paper - Add PufferFishStateChangeEvent - this.makeSound(SoundEvents.PUFFER_FISH_BLOW_OUT); - this.setPuffState(1); -+ } else { increase = false; } // Paper - Add PufferFishStateChangeEvent - } else if (this.deflateTimer > 100 && this.getPuffState() == 1) { -+ if (new io.papermc.paper.event.entity.PufferFishStateChangeEvent((org.bukkit.entity.PufferFish) getBukkitEntity(), 0).callEvent()) { // Paper - Add PufferFishStateChangeEvent - this.makeSound(SoundEvents.PUFFER_FISH_BLOW_OUT); - this.setPuffState(0); -+ } else { increase = false; } // Paper - Add PufferFishStateChangeEvent - } - -+ if (increase) { // Paper - Add PufferFishStateChangeEvent - ++this.deflateTimer; -+ } // Paper - Add PufferFishStateChangeEvent - } - } - diff --git a/patches/server/0545-Fix-PlayerBucketEmptyEvent-result-itemstack.patch b/patches/server/0545-Fix-PlayerBucketEmptyEvent-result-itemstack.patch deleted file mode 100644 index 12c519eefe5b..000000000000 --- a/patches/server/0545-Fix-PlayerBucketEmptyEvent-result-itemstack.patch +++ /dev/null @@ -1,42 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Jake Potrebic -Date: Thu, 20 May 2021 22:16:37 -0700 -Subject: [PATCH] Fix PlayerBucketEmptyEvent result itemstack - -Fixes SPIGOT-2560: https://hub.spigotmc.org/jira/projects/SPIGOT/issues/SPIGOT-2560 - -diff --git a/src/main/java/net/minecraft/world/item/BucketItem.java b/src/main/java/net/minecraft/world/item/BucketItem.java -index 7617b6a0ad44e8b135d071836dc30df5ad062c42..6caed156ed0cfe0017d578f58cb963ee68272d78 100644 ---- a/src/main/java/net/minecraft/world/item/BucketItem.java -+++ b/src/main/java/net/minecraft/world/item/BucketItem.java -@@ -40,6 +40,8 @@ import org.bukkit.event.player.PlayerBucketFillEvent; - - public class BucketItem extends Item implements DispensibleContainerItem { - -+ private static @Nullable ItemStack itemLeftInHandAfterPlayerBucketEmptyEvent = null; // Paper - Fix PlayerBucketEmptyEvent result itemstack -+ - public final Fluid content; - - public BucketItem(Fluid fluid, Item.Properties settings) { -@@ -125,6 +127,13 @@ public class BucketItem extends Item implements DispensibleContainerItem { - } - - public static ItemStack getEmptySuccessItem(ItemStack stack, Player player) { -+ // Paper start - Fix PlayerBucketEmptyEvent result itemstack -+ if (itemLeftInHandAfterPlayerBucketEmptyEvent != null) { -+ ItemStack itemInHand = itemLeftInHandAfterPlayerBucketEmptyEvent; -+ itemLeftInHandAfterPlayerBucketEmptyEvent = null; -+ return itemInHand; -+ } -+ // Paper end - Fix PlayerBucketEmptyEvent result itemstack - return !player.hasInfiniteMaterials() ? new ItemStack(Items.BUCKET) : stack; - } - -@@ -182,6 +191,7 @@ public class BucketItem extends Item implements DispensibleContainerItem { - ((ServerPlayer) entityhuman).getBukkitEntity().updateInventory(); // SPIGOT-4541 - return false; - } -+ itemLeftInHandAfterPlayerBucketEmptyEvent = event.getItemStack() != null ? event.getItemStack().equals(CraftItemStack.asNewCraftStack(net.minecraft.world.item.Items.BUCKET)) ? null : CraftItemStack.asNMSCopy(event.getItemStack()) : ItemStack.EMPTY; // Paper - Fix PlayerBucketEmptyEvent result itemstack - } - // CraftBukkit end - if (!flag2) { diff --git a/patches/server/0545-Fix-invulnerable-end-crystals.patch b/patches/server/0545-Fix-invulnerable-end-crystals.patch new file mode 100644 index 000000000000..4349e5a8ad5b --- /dev/null +++ b/patches/server/0545-Fix-invulnerable-end-crystals.patch @@ -0,0 +1,65 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Max Lee +Date: Thu, 27 May 2021 14:52:30 -0700 +Subject: [PATCH] Fix invulnerable end crystals + +MC-108513 + +diff --git a/src/main/java/net/minecraft/world/entity/boss/enderdragon/EndCrystal.java b/src/main/java/net/minecraft/world/entity/boss/enderdragon/EndCrystal.java +index 671e8aa7ecc2b3fcc98af62356ff690a2604bcb0..7cb3d69a69e0e3ef4b7f9f9c8b1eb67edb5d116d 100644 +--- a/src/main/java/net/minecraft/world/entity/boss/enderdragon/EndCrystal.java ++++ b/src/main/java/net/minecraft/world/entity/boss/enderdragon/EndCrystal.java +@@ -30,6 +30,7 @@ public class EndCrystal extends Entity { + private static final EntityDataAccessor> DATA_BEAM_TARGET = SynchedEntityData.defineId(EndCrystal.class, EntityDataSerializers.OPTIONAL_BLOCK_POS); + private static final EntityDataAccessor DATA_SHOW_BOTTOM = SynchedEntityData.defineId(EndCrystal.class, EntityDataSerializers.BOOLEAN); + public int time; ++ public boolean generatedByDragonFight = false; // Paper - Fix invulnerable end crystals + + public EndCrystal(EntityType type, Level world) { + super(type, world); +@@ -68,6 +69,17 @@ public class EndCrystal extends Entity { + } + // CraftBukkit end + } ++ // Paper start - Fix invulnerable end crystals ++ if (this.level().paperConfig().unsupportedSettings.fixInvulnerableEndCrystalExploit && this.generatedByDragonFight && this.isInvulnerable()) { ++ if (!java.util.Objects.equals(((ServerLevel) this.level()).uuid, this.getOriginWorld()) ++ || ((ServerLevel) this.level()).getDragonFight() == null ++ || ((ServerLevel) this.level()).getDragonFight().respawnStage == null ++ || ((ServerLevel) this.level()).getDragonFight().respawnStage.ordinal() > net.minecraft.world.level.dimension.end.DragonRespawnAnimation.SUMMONING_DRAGON.ordinal()) { ++ this.setInvulnerable(false); ++ this.setBeamTarget(null); ++ } ++ } ++ // Paper end - Fix invulnerable end crystals + } + + } +@@ -79,6 +91,7 @@ public class EndCrystal extends Entity { + } + + nbt.putBoolean("ShowBottom", this.showsBottom()); ++ if (this.generatedByDragonFight) nbt.putBoolean("Paper.GeneratedByDragonFight", this.generatedByDragonFight); // Paper - Fix invulnerable end crystals + } + + @Override +@@ -87,6 +100,7 @@ public class EndCrystal extends Entity { + if (nbt.contains("ShowBottom", 1)) { + this.setShowBottom(nbt.getBoolean("ShowBottom")); + } ++ if (nbt.contains("Paper.GeneratedByDragonFight", 1)) this.generatedByDragonFight = nbt.getBoolean("Paper.GeneratedByDragonFight"); // Paper - Fix invulnerable end crystals + + } + +diff --git a/src/main/java/net/minecraft/world/level/levelgen/feature/SpikeFeature.java b/src/main/java/net/minecraft/world/level/levelgen/feature/SpikeFeature.java +index 6f0cd7121bf191d8fd01baf4c9b87df7f4f44564..fe5b2bcfaa243c3089f3df83ec1ae0948a63d1eb 100644 +--- a/src/main/java/net/minecraft/world/level/levelgen/feature/SpikeFeature.java ++++ b/src/main/java/net/minecraft/world/level/levelgen/feature/SpikeFeature.java +@@ -115,6 +115,7 @@ public class SpikeFeature extends Feature { + endCrystal.moveTo( + (double)spike.getCenterX() + 0.5, (double)(spike.getHeight() + 1), (double)spike.getCenterZ() + 0.5, random.nextFloat() * 360.0F, 0.0F + ); ++ endCrystal.generatedByDragonFight = true; // Paper - Fix invulnerable end crystals + world.addFreshEntity(endCrystal); + BlockPos blockPos2 = endCrystal.blockPosition(); + this.setBlock(world, blockPos2.below(), Blocks.BEDROCK.defaultBlockState()); diff --git a/patches/server/0546-Add-ElderGuardianAppearanceEvent.patch b/patches/server/0546-Add-ElderGuardianAppearanceEvent.patch new file mode 100644 index 000000000000..e28248ff5de3 --- /dev/null +++ b/patches/server/0546-Add-ElderGuardianAppearanceEvent.patch @@ -0,0 +1,48 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Owen1212055 <23108066+Owen1212055@users.noreply.github.com> +Date: Fri, 19 Mar 2021 23:39:09 -0400 +Subject: [PATCH] Add ElderGuardianAppearanceEvent + + +diff --git a/src/main/java/net/minecraft/world/effect/MobEffectUtil.java b/src/main/java/net/minecraft/world/effect/MobEffectUtil.java +index f8026eb1d9b10e468d28ee2e1296653e873c87db..23977c2074b920b646a639bef79c0f625b284bea 100644 +--- a/src/main/java/net/minecraft/world/effect/MobEffectUtil.java ++++ b/src/main/java/net/minecraft/world/effect/MobEffectUtil.java +@@ -55,10 +55,23 @@ public final class MobEffectUtil { + } + + public static List addEffectToPlayersAround(ServerLevel worldserver, @Nullable Entity entity, Vec3 vec3d, double d0, MobEffectInstance mobeffect, int i, org.bukkit.event.entity.EntityPotionEffectEvent.Cause cause) { ++ // Paper start - Add ElderGuardianAppearanceEvent ++ return addEffectToPlayersAround(worldserver, entity, vec3d, d0, mobeffect, i, cause, null); ++ } ++ ++ public static List addEffectToPlayersAround(ServerLevel worldserver, @Nullable Entity entity, Vec3 vec3d, double d0, MobEffectInstance mobeffect, int i, org.bukkit.event.entity.EntityPotionEffectEvent.Cause cause, @Nullable java.util.function.Predicate playerPredicate) { ++ // Paper end - Add ElderGuardianAppearanceEvent + // CraftBukkit end + Holder holder = mobeffect.getEffect(); + List list = worldserver.getPlayers((entityplayer) -> { +- return entityplayer.gameMode.isSurvival() && (entity == null || !entity.isAlliedTo((Entity) entityplayer)) && vec3d.closerThan(entityplayer.position(), d0) && (!entityplayer.hasEffect(holder) || entityplayer.getEffect(holder).getAmplifier() < mobeffect.getAmplifier() || entityplayer.getEffect(holder).endsWithin(i - 1)); ++ // Paper start - Add ElderGuardianAppearanceEvent ++ boolean condition = entityplayer.gameMode.isSurvival() && (entity == null || !entity.isAlliedTo((Entity) entityplayer)) && vec3d.closerThan(entityplayer.position(), d0) && (!entityplayer.hasEffect(holder) || entityplayer.getEffect(holder).getAmplifier() < mobeffect.getAmplifier() || entityplayer.getEffect(holder).endsWithin(i - 1)); ++ if (condition) { ++ return playerPredicate == null || playerPredicate.test(entityplayer); // Only test the player AFTER it is true ++ } else { ++ return false; ++ } ++ // Paper ned - Add ElderGuardianAppearanceEvent + }); + + list.forEach((entityplayer) -> { +diff --git a/src/main/java/net/minecraft/world/entity/monster/ElderGuardian.java b/src/main/java/net/minecraft/world/entity/monster/ElderGuardian.java +index 9290f43b4b37a7fa2afae81f8351ea76b7ee7de0..378694a38115c012978e1fea59d049d1ebd04110 100644 +--- a/src/main/java/net/minecraft/world/entity/monster/ElderGuardian.java ++++ b/src/main/java/net/minecraft/world/entity/monster/ElderGuardian.java +@@ -67,7 +67,7 @@ public class ElderGuardian extends Guardian { + super.customServerAiStep(world); + if ((this.tickCount + this.getId()) % 1200 == 0) { + MobEffectInstance mobeffect = new MobEffectInstance(MobEffects.DIG_SLOWDOWN, 6000, 2); +- List list = MobEffectUtil.addEffectToPlayersAround(world, this, this.position(), 50.0D, mobeffect, 1200, org.bukkit.event.entity.EntityPotionEffectEvent.Cause.ATTACK); // CraftBukkit ++ List list = MobEffectUtil.addEffectToPlayersAround(world, this, this.position(), 50.0D, mobeffect, 1200, org.bukkit.event.entity.EntityPotionEffectEvent.Cause.ATTACK, (player) -> new io.papermc.paper.event.entity.ElderGuardianAppearanceEvent((org.bukkit.entity.ElderGuardian) this.getBukkitEntity(), player.getBukkitEntity()).callEvent()); // CraftBukkit // Paper - Add ElderGuardianAppearanceEvent + + list.forEach((entityplayer) -> { + entityplayer.connection.send(new ClientboundGameEventPacket(ClientboundGameEventPacket.GUARDIAN_ELDER_EFFECT, this.isSilent() ? 0.0F : 1.0F)); diff --git a/patches/server/0546-Synchronize-PalettedContainer-instead-of-ThreadingDe.patch b/patches/server/0546-Synchronize-PalettedContainer-instead-of-ThreadingDe.patch deleted file mode 100644 index 04a6d787b62e..000000000000 --- a/patches/server/0546-Synchronize-PalettedContainer-instead-of-ThreadingDe.patch +++ /dev/null @@ -1,91 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Aikar -Date: Fri, 29 May 2020 20:29:02 -0400 -Subject: [PATCH] Synchronize PalettedContainer instead of - ThreadingDetector/Semaphore - -Mojang has flaws in their logic about chunks being concurrently -wrote to. So we constantly see crashes around multiple threads writing. - -Additionally, java has optimized synchronization so well that its -in many times faster than trying to manage read write locks for low -contention situations. - -And this is extremely a low contention situation. - -diff --git a/src/main/java/net/minecraft/world/level/chunk/PalettedContainer.java b/src/main/java/net/minecraft/world/level/chunk/PalettedContainer.java -index 2e5afbd2a69c4eeabd9a48bff6a37a7004565716..2fa0097a9374a89177e4f1068d1bfed30b8ff122 100644 ---- a/src/main/java/net/minecraft/world/level/chunk/PalettedContainer.java -+++ b/src/main/java/net/minecraft/world/level/chunk/PalettedContainer.java -@@ -30,14 +30,14 @@ public class PalettedContainer implements PaletteResize, PalettedContainer - public final IdMap registry; - private volatile PalettedContainer.Data data; - private final PalettedContainer.Strategy strategy; -- private final ThreadingDetector threadingDetector = new ThreadingDetector("PalettedContainer"); -+ // private final ThreadingDetector threadingDetector = new ThreadingDetector("PalettedContainer"); // Paper - unused - - public void acquire() { -- this.threadingDetector.checkAndLock(); -+ // this.threadingDetector.checkAndLock(); // Paper - disable this - use proper synchronization - } - - public void release() { -- this.threadingDetector.checkAndUnlock(); -+ // this.threadingDetector.checkAndUnlock(); // Paper - disable this - } - - public static Codec> codecRW(IdMap idList, Codec entryCodec, PalettedContainer.Strategy paletteProvider, T defaultValue) { -@@ -104,7 +104,7 @@ public class PalettedContainer implements PaletteResize, PalettedContainer - } - - @Override -- public int onResize(int newBits, T object) { -+ public synchronized int onResize(int newBits, T object) { // Paper - synchronize - PalettedContainer.Data data = this.data; - PalettedContainer.Data data2 = this.createOrReuseData(data, newBits); - data2.copyFrom(data.palette, data.storage); -@@ -129,7 +129,7 @@ public class PalettedContainer implements PaletteResize, PalettedContainer - return this.getAndSet(this.strategy.getIndex(x, y, z), value); - } - -- private T getAndSet(int index, T value) { -+ private synchronized T getAndSet(int index, T value) { // Paper - synchronize - int i = this.data.palette.idFor(value); - int j = this.data.storage.getAndSet(index, i); - return this.data.palette.valueFor(j); -@@ -145,7 +145,7 @@ public class PalettedContainer implements PaletteResize, PalettedContainer - } - } - -- private void set(int index, T value) { -+ private synchronized void set(int index, T value) { // Paper - synchronize - int i = this.data.palette.idFor(value); - this.data.storage.set(index, i); - } -@@ -168,7 +168,7 @@ public class PalettedContainer implements PaletteResize, PalettedContainer - intSet.forEach(id -> action.accept(palette.valueFor(id))); - } - -- public void read(FriendlyByteBuf buf) { -+ public synchronized void read(FriendlyByteBuf buf) { // Paper - synchronize - this.acquire(); - - try { -@@ -183,7 +183,7 @@ public class PalettedContainer implements PaletteResize, PalettedContainer - } - - @Override -- public void write(FriendlyByteBuf buf) { -+ public synchronized void write(FriendlyByteBuf buf) { // Paper - synchronize - this.acquire(); - - try { -@@ -231,7 +231,7 @@ public class PalettedContainer implements PaletteResize, PalettedContainer - } - - @Override -- public PalettedContainerRO.PackedData pack(IdMap idList, PalettedContainer.Strategy paletteProvider) { -+ public synchronized PalettedContainerRO.PackedData pack(IdMap idList, PalettedContainer.Strategy paletteProvider) { // Paper - synchronize - this.acquire(); - - PalettedContainerRO.PackedData var12; diff --git a/patches/server/0547-Add-option-to-fix-items-merging-through-walls.patch b/patches/server/0547-Add-option-to-fix-items-merging-through-walls.patch deleted file mode 100644 index ba1a219b4969..000000000000 --- a/patches/server/0547-Add-option-to-fix-items-merging-through-walls.patch +++ /dev/null @@ -1,25 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: GioSDA -Date: Wed, 10 Mar 2021 10:06:45 -0800 -Subject: [PATCH] Add option to fix items merging through walls - - -diff --git a/src/main/java/net/minecraft/world/entity/item/ItemEntity.java b/src/main/java/net/minecraft/world/entity/item/ItemEntity.java -index c724359995d65c88e7f365eea55f3e4382a46ddd..df90d5b934f41f5d8c232e93830d6690b6ccf401 100644 ---- a/src/main/java/net/minecraft/world/entity/item/ItemEntity.java -+++ b/src/main/java/net/minecraft/world/entity/item/ItemEntity.java -@@ -282,6 +282,14 @@ public class ItemEntity extends Entity implements TraceableEntity { - ItemEntity entityitem = (ItemEntity) iterator.next(); - - if (entityitem.isMergable()) { -+ // Paper start - Fix items merging through walls -+ if (this.level().paperConfig().fixes.fixItemsMergingThroughWalls) { -+ if (this.level().clipDirect(this.position(), entityitem.position(), -+ net.minecraft.world.phys.shapes.CollisionContext.of(this)) == net.minecraft.world.phys.HitResult.Type.BLOCK) { -+ continue; -+ } -+ } -+ // Paper end - Fix items merging through walls - this.tryToMerge(entityitem); - if (this.isRemoved()) { - break; diff --git a/patches/server/0551-Optimize-Biome-Mob-Lookups-for-Mob-Spawning.patch b/patches/server/0547-Optimize-Biome-Mob-Lookups-for-Mob-Spawning.patch similarity index 100% rename from patches/server/0551-Optimize-Biome-Mob-Lookups-for-Mob-Spawning.patch rename to patches/server/0547-Optimize-Biome-Mob-Lookups-for-Mob-Spawning.patch diff --git a/patches/server/0548-Line-Of-Sight-Changes.patch b/patches/server/0548-Line-Of-Sight-Changes.patch new file mode 100644 index 000000000000..0b72d78709ee --- /dev/null +++ b/patches/server/0548-Line-Of-Sight-Changes.patch @@ -0,0 +1,74 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: TwoLeggedCat <80929284+TwoLeggedCat@users.noreply.github.com> +Date: Sat, 29 May 2021 14:33:25 -0500 +Subject: [PATCH] Line Of Sight Changes + + +diff --git a/src/main/java/net/minecraft/world/entity/LivingEntity.java b/src/main/java/net/minecraft/world/entity/LivingEntity.java +index 9d56e7d4185401767359907337d5891ff0058abb..8939ce9fed5d935cec31c9a27833d1cec4de7b01 100644 +--- a/src/main/java/net/minecraft/world/entity/LivingEntity.java ++++ b/src/main/java/net/minecraft/world/entity/LivingEntity.java +@@ -3905,7 +3905,8 @@ public abstract class LivingEntity extends Entity implements Attackable { + Vec3 vec3d = new Vec3(this.getX(), this.getEyeY(), this.getZ()); + Vec3 vec3d1 = new Vec3(entity.getX(), entityY.getAsDouble(), entity.getZ()); + +- return vec3d1.distanceTo(vec3d) > 128.0D ? false : this.level().clip(new ClipContext(vec3d, vec3d1, shapeType, fluidHandling, this)).getType() == HitResult.Type.MISS; ++ // Paper - diff on change - used in CraftLivingEntity#hasLineOfSight(Location) and CraftWorld#lineOfSightExists ++ return vec3d1.distanceToSqr(vec3d) > 128.0D * 128.0D ? false : this.level().clip(new ClipContext(vec3d, vec3d1, shapeType, fluidHandling, this)).getType() == HitResult.Type.MISS; // Paper - Perf: Use distance squared + } + } + +diff --git a/src/main/java/org/bukkit/craftbukkit/CraftRegionAccessor.java b/src/main/java/org/bukkit/craftbukkit/CraftRegionAccessor.java +index a070b2a83edaa702b13bc6d3026914126c211576..ca35e93239eea09b3d0dc6ef18f58743e633996b 100644 +--- a/src/main/java/org/bukkit/craftbukkit/CraftRegionAccessor.java ++++ b/src/main/java/org/bukkit/craftbukkit/CraftRegionAccessor.java +@@ -526,5 +526,21 @@ public abstract class CraftRegionAccessor implements RegionAccessor { + public org.bukkit.NamespacedKey getKey() { + return org.bukkit.craftbukkit.util.CraftNamespacedKey.fromMinecraft(this.getHandle().getLevel().dimension().location()); + } ++ ++ public boolean lineOfSightExists(Location from, Location to) { ++ Preconditions.checkArgument(from != null, "from parameter in lineOfSightExists cannot be null"); ++ Preconditions.checkArgument(to != null, "to parameter in lineOfSightExists cannot be null"); ++ if (from.getWorld() != to.getWorld()) { ++ return false; ++ } ++ ++ net.minecraft.world.phys.Vec3 start = new net.minecraft.world.phys.Vec3(from.getX(), from.getY(), from.getZ()); ++ net.minecraft.world.phys.Vec3 end = new net.minecraft.world.phys.Vec3(to.getX(), to.getY(), to.getZ()); ++ if (end.distanceToSqr(start) > 128D * 128D) { ++ return false; // Return early if the distance is greater than 128 blocks ++ } ++ ++ return this.getHandle().clip(new net.minecraft.world.level.ClipContext(start, end, net.minecraft.world.level.ClipContext.Block.COLLIDER, net.minecraft.world.level.ClipContext.Fluid.NONE, net.minecraft.world.phys.shapes.CollisionContext.empty())).getType() == net.minecraft.world.phys.HitResult.Type.MISS; ++ } + // Paper end + } +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftLivingEntity.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftLivingEntity.java +index 5749e2b5174be23633c8a811baec8c05da12e3e2..b5830f31c3444fcc6fea83a3b71a05bf07ce379d 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftLivingEntity.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftLivingEntity.java +@@ -643,6 +643,23 @@ public class CraftLivingEntity extends CraftEntity implements LivingEntity { + return this.getHandle().hasLineOfSight(((CraftEntity) other).getHandle()); + } + ++ // Paper start ++ @Override ++ public boolean hasLineOfSight(Location loc) { ++ if (this.getHandle().level() != ((CraftWorld) loc.getWorld()).getHandle()) { ++ return false; ++ } ++ ++ net.minecraft.world.phys.Vec3 start = new net.minecraft.world.phys.Vec3(this.getHandle().getX(), this.getHandle().getEyeY(), this.getHandle().getZ()); ++ net.minecraft.world.phys.Vec3 end = new net.minecraft.world.phys.Vec3(loc.getX(), loc.getY(), loc.getZ()); ++ if (end.distanceToSqr(start) > 128D * 128D) { ++ return false; // Return early if the distance is greater than 128 blocks ++ } ++ ++ return this.getHandle().level().clipDirect(start, end, net.minecraft.world.phys.shapes.CollisionContext.of(this.getHandle())) == net.minecraft.world.phys.HitResult.Type.MISS; ++ } ++ // Paper end ++ + @Override + public boolean getRemoveWhenFarAway() { + return this.getHandle() instanceof Mob && !((Mob) this.getHandle()).isPersistenceRequired(); diff --git a/patches/server/0549-Fix-invulnerable-end-crystals.patch b/patches/server/0549-Fix-invulnerable-end-crystals.patch deleted file mode 100644 index 57cba1965662..000000000000 --- a/patches/server/0549-Fix-invulnerable-end-crystals.patch +++ /dev/null @@ -1,65 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Max Lee -Date: Thu, 27 May 2021 14:52:30 -0700 -Subject: [PATCH] Fix invulnerable end crystals - -MC-108513 - -diff --git a/src/main/java/net/minecraft/world/entity/boss/enderdragon/EndCrystal.java b/src/main/java/net/minecraft/world/entity/boss/enderdragon/EndCrystal.java -index 6f8d4584aae56fc7fbcbc38b1291ea415208fd5e..a33d89fe9ca9e343edab8bb1cc88c54130ddb4a7 100644 ---- a/src/main/java/net/minecraft/world/entity/boss/enderdragon/EndCrystal.java -+++ b/src/main/java/net/minecraft/world/entity/boss/enderdragon/EndCrystal.java -@@ -30,6 +30,7 @@ public class EndCrystal extends Entity { - private static final EntityDataAccessor> DATA_BEAM_TARGET = SynchedEntityData.defineId(EndCrystal.class, EntityDataSerializers.OPTIONAL_BLOCK_POS); - private static final EntityDataAccessor DATA_SHOW_BOTTOM = SynchedEntityData.defineId(EndCrystal.class, EntityDataSerializers.BOOLEAN); - public int time; -+ public boolean generatedByDragonFight = false; // Paper - Fix invulnerable end crystals - - public EndCrystal(EntityType type, Level world) { - super(type, world); -@@ -68,6 +69,17 @@ public class EndCrystal extends Entity { - } - // CraftBukkit end - } -+ // Paper start - Fix invulnerable end crystals -+ if (this.level().paperConfig().unsupportedSettings.fixInvulnerableEndCrystalExploit && this.generatedByDragonFight && this.isInvulnerable()) { -+ if (!java.util.Objects.equals(((ServerLevel) this.level()).uuid, this.getOriginWorld()) -+ || ((ServerLevel) this.level()).getDragonFight() == null -+ || ((ServerLevel) this.level()).getDragonFight().respawnStage == null -+ || ((ServerLevel) this.level()).getDragonFight().respawnStage.ordinal() > net.minecraft.world.level.dimension.end.DragonRespawnAnimation.SUMMONING_DRAGON.ordinal()) { -+ this.setInvulnerable(false); -+ this.setBeamTarget(null); -+ } -+ } -+ // Paper end - Fix invulnerable end crystals - } - - } -@@ -79,6 +91,7 @@ public class EndCrystal extends Entity { - } - - nbt.putBoolean("ShowBottom", this.showsBottom()); -+ if (this.generatedByDragonFight) nbt.putBoolean("Paper.GeneratedByDragonFight", this.generatedByDragonFight); // Paper - Fix invulnerable end crystals - } - - @Override -@@ -87,6 +100,7 @@ public class EndCrystal extends Entity { - if (nbt.contains("ShowBottom", 1)) { - this.setShowBottom(nbt.getBoolean("ShowBottom")); - } -+ if (nbt.contains("Paper.GeneratedByDragonFight", 1)) this.generatedByDragonFight = nbt.getBoolean("Paper.GeneratedByDragonFight"); // Paper - Fix invulnerable end crystals - - } - -diff --git a/src/main/java/net/minecraft/world/level/levelgen/feature/SpikeFeature.java b/src/main/java/net/minecraft/world/level/levelgen/feature/SpikeFeature.java -index c3bf90178abe89ccc987718237d472ed10c70d69..260c3a7dc592fba220ad4a7febb43ee2c9279115 100644 ---- a/src/main/java/net/minecraft/world/level/levelgen/feature/SpikeFeature.java -+++ b/src/main/java/net/minecraft/world/level/levelgen/feature/SpikeFeature.java -@@ -114,6 +114,7 @@ public class SpikeFeature extends Feature { - endCrystal.moveTo( - (double)spike.getCenterX() + 0.5, (double)(spike.getHeight() + 1), (double)spike.getCenterZ() + 0.5, random.nextFloat() * 360.0F, 0.0F - ); -+ endCrystal.generatedByDragonFight = true; // Paper - Fix invulnerable end crystals - world.addFreshEntity(endCrystal); - BlockPos blockPos2 = endCrystal.blockPosition(); - this.setBlock(world, blockPos2.below(), Blocks.BEDROCK.defaultBlockState()); diff --git a/patches/server/0549-add-per-world-spawn-limits.patch b/patches/server/0549-add-per-world-spawn-limits.patch new file mode 100644 index 000000000000..ef9111f7fc4e --- /dev/null +++ b/patches/server/0549-add-per-world-spawn-limits.patch @@ -0,0 +1,24 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: chase +Date: Wed, 2 Dec 2020 22:43:39 -0800 +Subject: [PATCH] add per world spawn limits + + +diff --git a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java +index 07986523451a755515c912e5cab5172195b929de..9e0f2900dea70c471ea73b190b8aa2d9abd37ee6 100644 +--- a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java ++++ b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java +@@ -226,6 +226,13 @@ public class CraftWorld extends CraftRegionAccessor implements World { + this.biomeProvider = biomeProvider; + + this.environment = env; ++ // Paper start - per world spawn limits ++ for (SpawnCategory spawnCategory : SpawnCategory.values()) { ++ if (CraftSpawnCategory.isValidForLimits(spawnCategory)) { ++ setSpawnLimit(spawnCategory, this.world.paperConfig().entities.spawning.spawnLimits.getInt(CraftSpawnCategory.toNMS(spawnCategory))); ++ } ++ } ++ // Paper end - per world spawn limits + } + + @Override diff --git a/patches/server/0550-Add-ElderGuardianAppearanceEvent.patch b/patches/server/0550-Add-ElderGuardianAppearanceEvent.patch deleted file mode 100644 index d9bc14356d26..000000000000 --- a/patches/server/0550-Add-ElderGuardianAppearanceEvent.patch +++ /dev/null @@ -1,48 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Owen1212055 <23108066+Owen1212055@users.noreply.github.com> -Date: Fri, 19 Mar 2021 23:39:09 -0400 -Subject: [PATCH] Add ElderGuardianAppearanceEvent - - -diff --git a/src/main/java/net/minecraft/world/effect/MobEffectUtil.java b/src/main/java/net/minecraft/world/effect/MobEffectUtil.java -index f8026eb1d9b10e468d28ee2e1296653e873c87db..23977c2074b920b646a639bef79c0f625b284bea 100644 ---- a/src/main/java/net/minecraft/world/effect/MobEffectUtil.java -+++ b/src/main/java/net/minecraft/world/effect/MobEffectUtil.java -@@ -55,10 +55,23 @@ public final class MobEffectUtil { - } - - public static List addEffectToPlayersAround(ServerLevel worldserver, @Nullable Entity entity, Vec3 vec3d, double d0, MobEffectInstance mobeffect, int i, org.bukkit.event.entity.EntityPotionEffectEvent.Cause cause) { -+ // Paper start - Add ElderGuardianAppearanceEvent -+ return addEffectToPlayersAround(worldserver, entity, vec3d, d0, mobeffect, i, cause, null); -+ } -+ -+ public static List addEffectToPlayersAround(ServerLevel worldserver, @Nullable Entity entity, Vec3 vec3d, double d0, MobEffectInstance mobeffect, int i, org.bukkit.event.entity.EntityPotionEffectEvent.Cause cause, @Nullable java.util.function.Predicate playerPredicate) { -+ // Paper end - Add ElderGuardianAppearanceEvent - // CraftBukkit end - Holder holder = mobeffect.getEffect(); - List list = worldserver.getPlayers((entityplayer) -> { -- return entityplayer.gameMode.isSurvival() && (entity == null || !entity.isAlliedTo((Entity) entityplayer)) && vec3d.closerThan(entityplayer.position(), d0) && (!entityplayer.hasEffect(holder) || entityplayer.getEffect(holder).getAmplifier() < mobeffect.getAmplifier() || entityplayer.getEffect(holder).endsWithin(i - 1)); -+ // Paper start - Add ElderGuardianAppearanceEvent -+ boolean condition = entityplayer.gameMode.isSurvival() && (entity == null || !entity.isAlliedTo((Entity) entityplayer)) && vec3d.closerThan(entityplayer.position(), d0) && (!entityplayer.hasEffect(holder) || entityplayer.getEffect(holder).getAmplifier() < mobeffect.getAmplifier() || entityplayer.getEffect(holder).endsWithin(i - 1)); -+ if (condition) { -+ return playerPredicate == null || playerPredicate.test(entityplayer); // Only test the player AFTER it is true -+ } else { -+ return false; -+ } -+ // Paper ned - Add ElderGuardianAppearanceEvent - }); - - list.forEach((entityplayer) -> { -diff --git a/src/main/java/net/minecraft/world/entity/monster/ElderGuardian.java b/src/main/java/net/minecraft/world/entity/monster/ElderGuardian.java -index 4e4b68904151d0d851b13f14f89c1c305e95fd5a..fd995b1f29c47884e9db2cb92f1dd615d62ae032 100644 ---- a/src/main/java/net/minecraft/world/entity/monster/ElderGuardian.java -+++ b/src/main/java/net/minecraft/world/entity/monster/ElderGuardian.java -@@ -67,7 +67,7 @@ public class ElderGuardian extends Guardian { - super.customServerAiStep(); - if ((this.tickCount + this.getId()) % 1200 == 0) { - MobEffectInstance mobeffect = new MobEffectInstance(MobEffects.DIG_SLOWDOWN, 6000, 2); -- List list = MobEffectUtil.addEffectToPlayersAround((ServerLevel) this.level(), this, this.position(), 50.0D, mobeffect, 1200, org.bukkit.event.entity.EntityPotionEffectEvent.Cause.ATTACK); // CraftBukkit -+ List list = MobEffectUtil.addEffectToPlayersAround((ServerLevel) this.level(), this, this.position(), 50.0D, mobeffect, 1200, org.bukkit.event.entity.EntityPotionEffectEvent.Cause.ATTACK, (player) -> new io.papermc.paper.event.entity.ElderGuardianAppearanceEvent((org.bukkit.entity.ElderGuardian) this.getBukkitEntity(), player.getBukkitEntity()).callEvent()); // CraftBukkit // Paper - Add ElderGuardianAppearanceEvent - - list.forEach((entityplayer) -> { - entityplayer.connection.send(new ClientboundGameEventPacket(ClientboundGameEventPacket.GUARDIAN_ELDER_EFFECT, this.isSilent() ? 0.0F : 1.0F)); diff --git a/patches/server/0550-Fix-potions-splash-events.patch b/patches/server/0550-Fix-potions-splash-events.patch new file mode 100644 index 000000000000..7d09edfbeba4 --- /dev/null +++ b/patches/server/0550-Fix-potions-splash-events.patch @@ -0,0 +1,181 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Jake Potrebic +Date: Thu, 20 May 2021 20:40:53 -0700 +Subject: [PATCH] Fix potions splash events + +Fix PotionSplashEvent for water splash potions +Fixes SPIGOT-6221: https://hub.spigotmc.org/jira/projects/SPIGOT/issues/SPIGOT-6221 +Fix splash events cancellation that still show particles/sound + +diff --git a/src/main/java/net/minecraft/world/entity/projectile/ThrownPotion.java b/src/main/java/net/minecraft/world/entity/projectile/ThrownPotion.java +index 224e768963d6969f25608065d37ad72d82acda68..d6ac07d9d5ee0430a1d91b7084b378aac1d047e5 100644 +--- a/src/main/java/net/minecraft/world/entity/projectile/ThrownPotion.java ++++ b/src/main/java/net/minecraft/world/entity/projectile/ThrownPotion.java +@@ -109,55 +109,76 @@ public class ThrownPotion extends ThrowableItemProjectile { + ItemStack itemstack = this.getItem(); + PotionContents potioncontents = (PotionContents) itemstack.getOrDefault(DataComponents.POTION_CONTENTS, PotionContents.EMPTY); + ++ boolean showParticles = true; // Paper - Fix potions splash events + if (potioncontents.is(Potions.WATER)) { +- this.applyWater(worldserver); ++ showParticles = this.applyWater(worldserver, hitResult); // Paper - Fix potions splash events + } else if (true || potioncontents.hasEffects()) { // CraftBukkit - Call event even if no effects to apply + if (this.isLingering()) { +- this.makeAreaOfEffectCloud(potioncontents, hitResult); // CraftBukkit - Pass MovingObjectPosition ++ showParticles = this.makeAreaOfEffectCloud(potioncontents, hitResult); // CraftBukkit - Pass MovingObjectPosition // Paper + } else { +- this.applySplash(worldserver, potioncontents.getAllEffects(), hitResult.getType() == HitResult.Type.ENTITY ? ((EntityHitResult) hitResult).getEntity() : null, hitResult); // CraftBukkit - Pass MovingObjectPosition ++ showParticles = this.applySplash(worldserver, potioncontents.getAllEffects(), hitResult.getType() == HitResult.Type.ENTITY ? ((EntityHitResult) hitResult).getEntity() : null, hitResult); // CraftBukkit - Pass MovingObjectPosition // Paper + } + } + ++ if (showParticles) { // Paper - Fix potions splash events + int i = potioncontents.potion().isPresent() && ((Potion) ((Holder) potioncontents.potion().get()).value()).hasInstantEffects() ? 2007 : 2002; + + worldserver.levelEvent(i, this.blockPosition(), potioncontents.getColor()); ++ } // Paper - Fix potions splash events + this.discard(EntityRemoveEvent.Cause.HIT); // CraftBukkit - add Bukkit remove cause + } + } + +- private void applyWater(ServerLevel world) { ++ private static final Predicate APPLY_WATER_GET_ENTITIES_PREDICATE = ThrownPotion.WATER_SENSITIVE_OR_ON_FIRE.or(Axolotl.class::isInstance); // Paper - Fix potions splash events ++ private boolean applyWater(ServerLevel world, @Nullable HitResult hitResult) { // Paper - Fix potions splash events + AABB axisalignedbb = this.getBoundingBox().inflate(4.0D, 2.0D, 4.0D); +- List list = this.level().getEntitiesOfClass(net.minecraft.world.entity.LivingEntity.class, axisalignedbb, ThrownPotion.WATER_SENSITIVE_OR_ON_FIRE); ++ // Paper start - Fix potions splash events ++ List list = this.level().getEntitiesOfClass(net.minecraft.world.entity.LivingEntity.class, axisalignedbb, ThrownPotion.APPLY_WATER_GET_ENTITIES_PREDICATE); ++ Map affected = new HashMap<>(); ++ java.util.Set rehydrate = new java.util.HashSet<>(); ++ java.util.Set extinguish = new java.util.HashSet<>(); + Iterator iterator = list.iterator(); + + while (iterator.hasNext()) { + net.minecraft.world.entity.LivingEntity entityliving = (net.minecraft.world.entity.LivingEntity) iterator.next(); ++ if (entityliving instanceof Axolotl axolotl) { ++ rehydrate.add(((org.bukkit.entity.Axolotl) axolotl.getBukkitEntity())); ++ } + double d0 = this.distanceToSqr((Entity) entityliving); + + if (d0 < 16.0D) { + if (entityliving.isSensitiveToWater()) { +- entityliving.hurtServer(world, this.damageSources().indirectMagic(this, this.getOwner()), 1.0F); ++ affected.put(entityliving.getBukkitLivingEntity(), 1.0); + } + + if (entityliving.isOnFire() && entityliving.isAlive()) { +- entityliving.extinguishFire(); ++ extinguish.add(entityliving.getBukkitLivingEntity()); + } + } + } + +- List list1 = this.level().getEntitiesOfClass(Axolotl.class, axisalignedbb); +- Iterator iterator1 = list1.iterator(); +- +- while (iterator1.hasNext()) { +- Axolotl axolotl = (Axolotl) iterator1.next(); +- +- axolotl.rehydrate(); ++ io.papermc.paper.event.entity.WaterBottleSplashEvent event = org.bukkit.craftbukkit.event.CraftEventFactory.callWaterBottleSplashEvent( ++ this, hitResult, affected, rehydrate, extinguish ++ ); ++ if (!event.isCancelled()) { ++ for (LivingEntity affectedEntity : event.getToDamage()) { ++ ((CraftLivingEntity) affectedEntity).getHandle().hurtServer(world, this.damageSources().indirectMagic(this, this.getOwner()), 1.0F); ++ } ++ for (LivingEntity toExtinguish : event.getToExtinguish()) { ++ ((CraftLivingEntity) toExtinguish).getHandle().extinguishFire(); ++ } ++ for (LivingEntity toRehydrate : event.getToRehydrate()) { ++ if (((CraftLivingEntity) toRehydrate).getHandle() instanceof Axolotl axolotl) { ++ axolotl.rehydrate(); ++ } ++ } ++ // Paper end - Fix potions splash events + } ++ return !event.isCancelled(); // Paper - Fix potions splash events + + } + +- private void applySplash(ServerLevel worldserver, Iterable iterable, @Nullable Entity entity, HitResult position) { // CraftBukkit - Pass MovingObjectPosition ++ private boolean applySplash(ServerLevel worldserver, Iterable iterable, @Nullable Entity entity, HitResult position) { // CraftBukkit - Pass MovingObjectPosition // Paper - Fix potions splash events + AABB axisalignedbb = this.getBoundingBox().inflate(4.0D, 2.0D, 4.0D); + List list = worldserver.getEntitiesOfClass(net.minecraft.world.entity.LivingEntity.class, axisalignedbb); + Map affected = new HashMap(); // CraftBukkit +@@ -175,6 +196,7 @@ public class ThrownPotion extends ThrowableItemProjectile { + if (d0 < 16.0D) { + double d1; + ++ // Paper - diff on change, used when calling the splash event for water splash potions + if (entityliving == entity) { + d1 = 1.0D; + } else { +@@ -230,10 +252,11 @@ public class ThrownPotion extends ThrowableItemProjectile { + } + } + } ++ return !event.isCancelled(); // Paper - Fix potions splash events + + } + +- private void makeAreaOfEffectCloud(PotionContents potioncontents, HitResult position) { // CraftBukkit - Pass MovingObjectPosition ++ private boolean makeAreaOfEffectCloud(PotionContents potioncontents, HitResult position) { // CraftBukkit - Pass MovingObjectPosition + AreaEffectCloud entityareaeffectcloud = new AreaEffectCloud(this.level(), this.getX(), this.getY(), this.getZ()); + Entity entity = this.getOwner(); + +@@ -246,14 +269,16 @@ public class ThrownPotion extends ThrowableItemProjectile { + entityareaeffectcloud.setWaitTime(10); + entityareaeffectcloud.setRadiusPerTick(-entityareaeffectcloud.getRadius() / (float) entityareaeffectcloud.getDuration()); + entityareaeffectcloud.setPotionContents(potioncontents); ++ boolean noEffects = potioncontents.hasEffects(); // Paper - Fix potions splash events + // CraftBukkit start + org.bukkit.event.entity.LingeringPotionSplashEvent event = org.bukkit.craftbukkit.event.CraftEventFactory.callLingeringPotionSplashEvent(this, position, entityareaeffectcloud); +- if (!(event.isCancelled() || entityareaeffectcloud.isRemoved())) { ++ if (!(event.isCancelled() || entityareaeffectcloud.isRemoved() || (noEffects && !entityareaeffectcloud.potionContents.hasEffects()))) { // Paper - don't spawn area effect cloud if the effects were empty and not changed during the event handling + this.level().addFreshEntity(entityareaeffectcloud); + } else { + entityareaeffectcloud.discard(null); // CraftBukkit - add Bukkit remove cause + } + // CraftBukkit end ++ return !event.isCancelled(); // Paper - Fix potions splash events + } + + public boolean isLingering() { +diff --git a/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java b/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java +index a5c9ff3a2f06e7207ae4fcaf69be8bd0c874d4e2..e6b22f24eba945863bb625b40319e6874ff25534 100644 +--- a/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java ++++ b/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java +@@ -884,6 +884,32 @@ public class CraftEventFactory { + return event; + } + ++ // Paper start - Fix potions splash events ++ public static io.papermc.paper.event.entity.WaterBottleSplashEvent callWaterBottleSplashEvent(net.minecraft.world.entity.projectile.ThrownPotion potion, @Nullable HitResult hitResult, Map affectedEntities, java.util.Set rehydrate, java.util.Set extinguish) { ++ ThrownPotion thrownPotion = (ThrownPotion) potion.getBukkitEntity(); ++ ++ Block hitBlock = null; ++ BlockFace hitFace = null; ++ org.bukkit.entity.Entity hitEntity = null; ++ ++ if (hitResult != null) { ++ if (hitResult.getType() == HitResult.Type.BLOCK) { ++ BlockHitResult blockHitResult = (BlockHitResult) hitResult; ++ hitBlock = CraftBlock.at(potion.level(), blockHitResult.getBlockPos()); ++ hitFace = CraftBlock.notchToBlockFace(blockHitResult.getDirection()); ++ } else if (hitResult.getType() == HitResult.Type.ENTITY) { ++ hitEntity = ((EntityHitResult) hitResult).getEntity().getBukkitEntity(); ++ } ++ } ++ ++ io.papermc.paper.event.entity.WaterBottleSplashEvent event = new io.papermc.paper.event.entity.WaterBottleSplashEvent( ++ thrownPotion, hitEntity, hitBlock, hitFace, affectedEntities, rehydrate, extinguish ++ ); ++ event.callEvent(); ++ return event; ++ } ++ // Paper end - Fix potions splash events ++ + /** + * BlockFadeEvent + */ diff --git a/patches/server/0551-Add-more-LimitedRegion-API.patch b/patches/server/0551-Add-more-LimitedRegion-API.patch new file mode 100644 index 000000000000..f46a2383c8db --- /dev/null +++ b/patches/server/0551-Add-more-LimitedRegion-API.patch @@ -0,0 +1,56 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: dfsek +Date: Sat, 19 Jun 2021 20:15:59 -0700 +Subject: [PATCH] Add more LimitedRegion API + + +diff --git a/src/main/java/org/bukkit/craftbukkit/generator/CraftLimitedRegion.java b/src/main/java/org/bukkit/craftbukkit/generator/CraftLimitedRegion.java +index ca250dba422f8092b99f72b322147061a7541d14..2c7b64bd7071cb803a042152d497982d753e0b5d 100644 +--- a/src/main/java/org/bukkit/craftbukkit/generator/CraftLimitedRegion.java ++++ b/src/main/java/org/bukkit/craftbukkit/generator/CraftLimitedRegion.java +@@ -255,4 +255,45 @@ public class CraftLimitedRegion extends CraftRegionAccessor implements LimitedRe + public void addEntityWithPassengers(net.minecraft.world.entity.Entity entity, CreatureSpawnEvent.SpawnReason reason) { + this.entities.add(entity); + } ++ ++ // Paper start - Add more LimitedRegion API ++ @Override ++ public void setBlockState(int x, int y, int z, BlockState state) { ++ BlockPos pos = new BlockPos(x, y, z); ++ if (!state.getBlockData().matches(getHandle().getBlockState(pos).createCraftBlockData())) { ++ throw new IllegalArgumentException("BlockData does not match! Expected " + state.getBlockData().getAsString(false) + ", got " + getHandle().getBlockState(pos).createCraftBlockData().getAsString(false)); ++ } ++ getHandle().getBlockEntity(pos).loadWithComponents(((org.bukkit.craftbukkit.block.CraftBlockEntityState) state).getSnapshotNBT(), this.getHandle().registryAccess()); ++ } ++ ++ @Override ++ public void scheduleBlockUpdate(int x, int y, int z) { ++ BlockPos position = new BlockPos(x, y, z); ++ getHandle().scheduleTick(position, getHandle().getBlockState(position).getBlock(), 0); ++ } ++ ++ @Override ++ public void scheduleFluidUpdate(int x, int y, int z) { ++ BlockPos position = new BlockPos(x, y, z); ++ getHandle().scheduleTick(position, getHandle().getFluidState(position).getType(), 0); ++ } ++ ++ @Override ++ public World getWorld() { ++ // reading/writing the returned Minecraft world causes a deadlock. ++ // By implementing this, and covering it in warnings, we're assuming people won't be stupid, and ++ // if they are stupid, they'll figure it out pretty fast. ++ return getHandle().getMinecraftWorld().getWorld(); ++ } ++ ++ @Override ++ public int getCenterChunkX() { ++ return centerChunkX; ++ } ++ ++ @Override ++ public int getCenterChunkZ() { ++ return centerChunkZ; ++ } ++ // Paper end - Add more LimitedRegion API + } diff --git a/patches/server/0552-Fix-PlayerDropItemEvent-using-wrong-item.patch b/patches/server/0552-Fix-PlayerDropItemEvent-using-wrong-item.patch new file mode 100644 index 000000000000..c6491e489853 --- /dev/null +++ b/patches/server/0552-Fix-PlayerDropItemEvent-using-wrong-item.patch @@ -0,0 +1,57 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Spottedleaf +Date: Sun, 20 Jun 2021 21:55:59 -0700 +Subject: [PATCH] Fix PlayerDropItemEvent using wrong item + + +diff --git a/src/main/java/net/minecraft/server/commands/GiveCommand.java b/src/main/java/net/minecraft/server/commands/GiveCommand.java +index c81fd3e1108fb0a02f9240263404af2b968c8494..0d9de4c61c7b26a6ff37c12fde629161fd0c3d5a 100644 +--- a/src/main/java/net/minecraft/server/commands/GiveCommand.java ++++ b/src/main/java/net/minecraft/server/commands/GiveCommand.java +@@ -38,6 +38,7 @@ public class GiveCommand { + + private static int giveItem(CommandSourceStack source, ItemInput item, Collection targets, int count) throws CommandSyntaxException { + ItemStack itemstack = item.createItemStack(1, false); ++ final Component displayName = itemstack.getDisplayName(); // Paper - get display name early + int j = itemstack.getMaxStackSize(); + int k = j * 100; + +@@ -79,11 +80,11 @@ public class GiveCommand { + + if (targets.size() == 1) { + source.sendSuccess(() -> { +- return Component.translatable("commands.give.success.single", count, itemstack.getDisplayName(), ((ServerPlayer) targets.iterator().next()).getDisplayName()); ++ return Component.translatable("commands.give.success.single", count, displayName, ((ServerPlayer) targets.iterator().next()).getDisplayName()); // Paper - use cached display name + }, true); + } else { + source.sendSuccess(() -> { +- return Component.translatable("commands.give.success.single", count, itemstack.getDisplayName(), targets.size()); ++ return Component.translatable("commands.give.success.single", count, displayName, targets.size()); // Paper - use cached display name + }, true); + } + +diff --git a/src/main/java/net/minecraft/server/level/ServerPlayer.java b/src/main/java/net/minecraft/server/level/ServerPlayer.java +index a022f0aee638bb12967b5fd20847fe20e6b286f3..7bcb3ef3cb498114428782848e0370ac2497ccdc 100644 +--- a/src/main/java/net/minecraft/server/level/ServerPlayer.java ++++ b/src/main/java/net/minecraft/server/level/ServerPlayer.java +@@ -2747,7 +2747,7 @@ public class ServerPlayer extends net.minecraft.world.entity.player.Player { + + if (flag1) { + if (!itemstack1.isEmpty()) { +- this.awardStat(Stats.ITEM_DROPPED.get(itemstack1.getItem()), itemstack.getCount()); ++ this.awardStat(Stats.ITEM_DROPPED.get(itemstack1.getItem()), itemstack1.getCount()); // Paper - Fix PlayerDropItemEvent using wrong item + } + + this.awardStat(Stats.DROP); +@@ -2763,6 +2763,11 @@ public class ServerPlayer extends net.minecraft.world.entity.player.Player { + return null; + } else { + double d0 = this.getEyeY() - 0.30000001192092896D; ++ // Paper start ++ ItemStack tmp = stack.copy(); ++ stack.setCount(0); ++ stack = tmp; ++ // Paper end + ItemEntity entityitem = new ItemEntity(this.level(), this.getX(), d0, this.getZ(), stack); + + entityitem.setPickUpDelay(40); diff --git a/patches/server/0552-Line-Of-Sight-Changes.patch b/patches/server/0552-Line-Of-Sight-Changes.patch deleted file mode 100644 index 1cf3200c8195..000000000000 --- a/patches/server/0552-Line-Of-Sight-Changes.patch +++ /dev/null @@ -1,74 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: TwoLeggedCat <80929284+TwoLeggedCat@users.noreply.github.com> -Date: Sat, 29 May 2021 14:33:25 -0500 -Subject: [PATCH] Line Of Sight Changes - - -diff --git a/src/main/java/net/minecraft/world/entity/LivingEntity.java b/src/main/java/net/minecraft/world/entity/LivingEntity.java -index afa2d3f5199fcac5c83639bb87e31425da4c5310..e56ae5170526fdc9d4e6f8b94a1e6ebc77cae582 100644 ---- a/src/main/java/net/minecraft/world/entity/LivingEntity.java -+++ b/src/main/java/net/minecraft/world/entity/LivingEntity.java -@@ -3755,7 +3755,8 @@ public abstract class LivingEntity extends Entity implements Attackable { - Vec3 vec3d = new Vec3(this.getX(), this.getEyeY(), this.getZ()); - Vec3 vec3d1 = new Vec3(entity.getX(), entity.getEyeY(), entity.getZ()); - -- return vec3d1.distanceTo(vec3d) > 128.0D ? false : this.level().clip(new ClipContext(vec3d, vec3d1, ClipContext.Block.COLLIDER, ClipContext.Fluid.NONE, this)).getType() == HitResult.Type.MISS; -+ // Paper - diff on change - used in CraftLivingEntity#hasLineOfSight(Location) and CraftWorld#lineOfSightExists -+ return vec3d1.distanceToSqr(vec3d) > 128.0D * 128.0D ? false : this.level().clip(new ClipContext(vec3d, vec3d1, ClipContext.Block.COLLIDER, ClipContext.Fluid.NONE, this)).getType() == HitResult.Type.MISS; // Paper - Perf: Use distance squared - } - } - -diff --git a/src/main/java/org/bukkit/craftbukkit/CraftRegionAccessor.java b/src/main/java/org/bukkit/craftbukkit/CraftRegionAccessor.java -index 04a39cb6c13c26e2cb1d73a9da98df5d04df69bc..5d137f8c42356359701e1bea7525f82c018b502c 100644 ---- a/src/main/java/org/bukkit/craftbukkit/CraftRegionAccessor.java -+++ b/src/main/java/org/bukkit/craftbukkit/CraftRegionAccessor.java -@@ -520,5 +520,21 @@ public abstract class CraftRegionAccessor implements RegionAccessor { - public org.bukkit.NamespacedKey getKey() { - return org.bukkit.craftbukkit.util.CraftNamespacedKey.fromMinecraft(this.getHandle().getLevel().dimension().location()); - } -+ -+ public boolean lineOfSightExists(Location from, Location to) { -+ Preconditions.checkArgument(from != null, "from parameter in lineOfSightExists cannot be null"); -+ Preconditions.checkArgument(to != null, "to parameter in lineOfSightExists cannot be null"); -+ if (from.getWorld() != to.getWorld()) { -+ return false; -+ } -+ -+ net.minecraft.world.phys.Vec3 start = new net.minecraft.world.phys.Vec3(from.getX(), from.getY(), from.getZ()); -+ net.minecraft.world.phys.Vec3 end = new net.minecraft.world.phys.Vec3(to.getX(), to.getY(), to.getZ()); -+ if (end.distanceToSqr(start) > 128D * 128D) { -+ return false; // Return early if the distance is greater than 128 blocks -+ } -+ -+ return this.getHandle().clip(new net.minecraft.world.level.ClipContext(start, end, net.minecraft.world.level.ClipContext.Block.COLLIDER, net.minecraft.world.level.ClipContext.Fluid.NONE, net.minecraft.world.phys.shapes.CollisionContext.empty())).getType() == net.minecraft.world.phys.HitResult.Type.MISS; -+ } - // Paper end - } -diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftLivingEntity.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftLivingEntity.java -index e56a0d8928e3c0e27b1acd171162e4a53b70d925..637bac756a8f41ed4abd8e3828886c561513e384 100644 ---- a/src/main/java/org/bukkit/craftbukkit/entity/CraftLivingEntity.java -+++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftLivingEntity.java -@@ -636,6 +636,23 @@ public class CraftLivingEntity extends CraftEntity implements LivingEntity { - return this.getHandle().hasLineOfSight(((CraftEntity) other).getHandle()); - } - -+ // Paper start -+ @Override -+ public boolean hasLineOfSight(Location loc) { -+ if (this.getHandle().level() != ((CraftWorld) loc.getWorld()).getHandle()) { -+ return false; -+ } -+ -+ net.minecraft.world.phys.Vec3 start = new net.minecraft.world.phys.Vec3(this.getHandle().getX(), this.getHandle().getEyeY(), this.getHandle().getZ()); -+ net.minecraft.world.phys.Vec3 end = new net.minecraft.world.phys.Vec3(loc.getX(), loc.getY(), loc.getZ()); -+ if (end.distanceToSqr(start) > 128D * 128D) { -+ return false; // Return early if the distance is greater than 128 blocks -+ } -+ -+ return this.getHandle().level().clipDirect(start, end, net.minecraft.world.phys.shapes.CollisionContext.of(this.getHandle())) == net.minecraft.world.phys.HitResult.Type.MISS; -+ } -+ // Paper end -+ - @Override - public boolean getRemoveWhenFarAway() { - return this.getHandle() instanceof Mob && !((Mob) this.getHandle()).isPersistenceRequired(); diff --git a/patches/server/0553-Missing-Entity-API.patch b/patches/server/0553-Missing-Entity-API.patch new file mode 100644 index 000000000000..64f88445fb30 --- /dev/null +++ b/patches/server/0553-Missing-Entity-API.patch @@ -0,0 +1,1402 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Owen1212055 <23108066+Owen1212055@users.noreply.github.com> +Date: Mon, 21 Jun 2021 23:56:07 -0400 +Subject: [PATCH] Missing Entity API + +== AT == +public net.minecraft.world.entity.animal.Fox isDefending()Z +public net.minecraft.world.entity.animal.Fox setDefending(Z)V +public net.minecraft.world.entity.animal.Fox setFaceplanted(Z)V +public net.minecraft.world.entity.animal.Panda getEatCounter()I +public net.minecraft.world.entity.animal.Panda setEatCounter(I)V +public net.minecraft.world.entity.animal.Bee isRolling()Z +public net.minecraft.world.entity.animal.Bee setRolling(Z)V +public net.minecraft.world.entity.animal.Bee numCropsGrownSincePollination +public net.minecraft.world.entity.animal.Bee ticksWithoutNectarSinceExitingHive +public net.minecraft.world.entity.monster.piglin.Piglin isChargingCrossbow()Z +public net.minecraft.world.entity.ambient.Bat targetPosition +public net.minecraft.world.entity.monster.Ravager attackTick +public net.minecraft.world.entity.monster.Ravager stunnedTick +public net.minecraft.world.entity.monster.Ravager roarTick +public net.minecraft.world.entity.vehicle.MinecartTNT explode(D)V +public net.minecraft.world.entity.vehicle.MinecartTNT fuse +public net.minecraft.world.entity.monster.Endermite life +public net.minecraft.world.entity.projectile.AbstractArrow soundEvent +public net.minecraft.world.entity.monster.Phantom anchorPoint +public net.minecraft.world.entity.npc.WanderingTrader getWanderTarget()Lnet/minecraft/core/BlockPos; +public net.minecraft.world.entity.animal.AbstractSchoolingFish leader +public net.minecraft.world.entity.animal.AbstractSchoolingFish schoolSize +public net.minecraft.world.entity.animal.Rabbit moreCarrotTicks +public net.minecraft.world.entity.AreaEffectCloud ownerUUID +public net.minecraft.world.entity.animal.MushroomCow stewEffects +public net.minecraft.world.entity.Entity FLAG_INVISIBLE +public net.minecraft.world.entity.animal.Cat setRelaxStateOne(Z)V +public net.minecraft.world.entity.animal.Cat isRelaxStateOne()Z + +Co-authored-by: Nassim Jahnke +Co-authored-by: Jake Potrebic +Co-authored-by: William Blake Galbreath +Co-authored-by: SoSeDiK +Co-authored-by: booky10 +Co-authored-by: Amin +Co-authored-by: TrollyLoki +Co-authored-by: FireInstall +Co-authored-by: maxcom1 <46265094+maxcom1@users.noreply.github.com> +Co-authored-by: TotalledZebra + +diff --git a/src/main/java/com/destroystokyo/paper/entity/ai/MobGoalHelper.java b/src/main/java/com/destroystokyo/paper/entity/ai/MobGoalHelper.java +index 05e793e722bbb367ca64cd7f26156fa3dabb8474..3470720466fc81f977c18e3a97bb918926025a22 100644 +--- a/src/main/java/com/destroystokyo/paper/entity/ai/MobGoalHelper.java ++++ b/src/main/java/com/destroystokyo/paper/entity/ai/MobGoalHelper.java +@@ -164,7 +164,7 @@ public class MobGoalHelper { + bukkitMap.put(net.minecraft.world.entity.monster.Endermite.class, Endermite.class); + bukkitMap.put(net.minecraft.world.entity.monster.Evoker.class, Evoker.class); + bukkitMap.put(AbstractFish.class, Fish.class); +- bukkitMap.put(AbstractSchoolingFish.class, Fish.class); // close enough ++ bukkitMap.put(AbstractSchoolingFish.class, io.papermc.paper.entity.SchoolableFish.class); + bukkitMap.put(FlyingMob.class, Flying.class); + bukkitMap.put(net.minecraft.world.entity.animal.Fox.class, Fox.class); + bukkitMap.put(net.minecraft.world.entity.monster.Ghast.class, Ghast.class); +diff --git a/src/main/java/io/papermc/paper/entity/PaperSchoolableFish.java b/src/main/java/io/papermc/paper/entity/PaperSchoolableFish.java +new file mode 100644 +index 0000000000000000000000000000000000000000..41bf71d116ffc5431586ce54abba7f8def6c1dcf +--- /dev/null ++++ b/src/main/java/io/papermc/paper/entity/PaperSchoolableFish.java +@@ -0,0 +1,52 @@ ++package io.papermc.paper.entity; ++ ++import net.minecraft.world.entity.animal.AbstractSchoolingFish; ++import org.bukkit.craftbukkit.CraftServer; ++import org.bukkit.craftbukkit.entity.CraftFish; ++import org.jetbrains.annotations.NotNull; ++ ++public class PaperSchoolableFish extends CraftFish implements SchoolableFish { ++ ++ public PaperSchoolableFish(CraftServer server, AbstractSchoolingFish entity) { ++ super(server, entity); ++ } ++ ++ @Override ++ public AbstractSchoolingFish getHandle() { ++ return (AbstractSchoolingFish) super.getHandle(); ++ } ++ ++ @Override ++ public void startFollowing(@NotNull SchoolableFish fish) { ++ if (this.getHandle().isFollower()) { // If following a fish already, properly remove the old one ++ this.stopFollowing(); ++ } ++ ++ this.getHandle().startFollowing(((PaperSchoolableFish) fish).getHandle()); ++ } ++ ++ @Override ++ public void stopFollowing() { ++ this.getHandle().stopFollowing(); ++ } ++ ++ @Override ++ public int getSchoolSize() { ++ return this.getHandle().schoolSize; ++ } ++ ++ @Override ++ public int getMaxSchoolSize() { ++ return this.getHandle().getMaxSchoolSize(); ++ } ++ ++ @Override ++ public SchoolableFish getSchoolLeader() { ++ AbstractSchoolingFish leader = this.getHandle().leader; ++ if (leader == null) { ++ return null; ++ } ++ ++ return (SchoolableFish) leader.getBukkitEntity(); ++ } ++} +diff --git a/src/main/java/net/minecraft/world/entity/animal/AbstractSchoolingFish.java b/src/main/java/net/minecraft/world/entity/animal/AbstractSchoolingFish.java +index 30095df7b64cfda4931dbfa22549ff5abefd53e0..c8ae49f58c254119c0e64a4e1501ebc5a70f9a46 100644 +--- a/src/main/java/net/minecraft/world/entity/animal/AbstractSchoolingFish.java ++++ b/src/main/java/net/minecraft/world/entity/animal/AbstractSchoolingFish.java +@@ -51,6 +51,7 @@ public abstract class AbstractSchoolingFish extends AbstractFish { + } + + public void stopFollowing() { ++ if (this.leader == null) return; // Avoid NPE, plugins can now set the leader and certain fish goals might cause this method to be called + this.leader.removeFollower(); + this.leader = null; + } +diff --git a/src/main/java/net/minecraft/world/entity/animal/Bee.java b/src/main/java/net/minecraft/world/entity/animal/Bee.java +index adff3bec90786b87323653cf4f94a38c7b9ef7ff..048a357546c8f5ad5dbb86e2e1ada2730a52d061 100644 +--- a/src/main/java/net/minecraft/world/entity/animal/Bee.java ++++ b/src/main/java/net/minecraft/world/entity/animal/Bee.java +@@ -561,11 +561,13 @@ public class Bee extends Animal implements NeutralMob, FlyingAnimal { + this.setFlag(4, hasStung); + } + ++ public net.kyori.adventure.util.TriState rollingOverride = net.kyori.adventure.util.TriState.NOT_SET; // Paper - Rolling override + public boolean isRolling() { + return this.getFlag(2); + } + + public void setRolling(boolean nearTarget) { ++ nearTarget = rollingOverride.toBooleanOrElse(nearTarget); // Paper - Rolling override + this.setFlag(2, nearTarget); + } + +diff --git a/src/main/java/net/minecraft/world/entity/animal/frog/Tadpole.java b/src/main/java/net/minecraft/world/entity/animal/frog/Tadpole.java +index b9d7aa4aa0fc3472a2a0700e526778daf62bdc6f..48ac8c3f6e00c3c2dc67b6c994be7c0ac6dfcf81 100644 +--- a/src/main/java/net/minecraft/world/entity/animal/frog/Tadpole.java ++++ b/src/main/java/net/minecraft/world/entity/animal/frog/Tadpole.java +@@ -50,6 +50,7 @@ public class Tadpole extends AbstractFish { + public int age; + protected static final ImmutableList>> SENSOR_TYPES = ImmutableList.of(SensorType.NEAREST_LIVING_ENTITIES, SensorType.NEAREST_PLAYERS, SensorType.HURT_BY, SensorType.FROG_TEMPTATIONS); + protected static final ImmutableList> MEMORY_TYPES = ImmutableList.of(MemoryModuleType.LOOK_TARGET, MemoryModuleType.NEAREST_VISIBLE_LIVING_ENTITIES, MemoryModuleType.WALK_TARGET, MemoryModuleType.CANT_REACH_WALK_TARGET_SINCE, MemoryModuleType.PATH, MemoryModuleType.NEAREST_VISIBLE_ADULT, MemoryModuleType.TEMPTATION_COOLDOWN_TICKS, MemoryModuleType.IS_TEMPTED, MemoryModuleType.TEMPTING_PLAYER, MemoryModuleType.BREED_TARGET, MemoryModuleType.IS_PANICKING); ++ public boolean ageLocked; // Paper + + public Tadpole(EntityType type, Level world) { + super(type, world); +@@ -102,7 +103,7 @@ public class Tadpole extends AbstractFish { + @Override + public void aiStep() { + super.aiStep(); +- if (!this.level().isClientSide) { ++ if (!this.level().isClientSide && !this.ageLocked) { // Paper + this.setAge(this.age + 1); + } + +@@ -112,12 +113,14 @@ public class Tadpole extends AbstractFish { + public void addAdditionalSaveData(CompoundTag nbt) { + super.addAdditionalSaveData(nbt); + nbt.putInt("Age", this.age); ++ nbt.putBoolean("AgeLocked", this.ageLocked); // Paper + } + + @Override + public void readAdditionalSaveData(CompoundTag nbt) { + super.readAdditionalSaveData(nbt); + this.setAge(nbt.getInt("Age")); ++ this.ageLocked = nbt.getBoolean("AgeLocked"); // Paper + } + + @Nullable +@@ -169,6 +172,7 @@ public class Tadpole extends AbstractFish { + Bucketable.saveDefaultDataToBucketTag(this, stack); + CustomData.update(DataComponents.BUCKET_ENTITY_DATA, stack, (nbttagcompound) -> { + nbttagcompound.putInt("Age", this.getAge()); ++ nbttagcompound.putBoolean("AgeLocked", this.ageLocked); // Paper + }); + } + +@@ -179,6 +183,7 @@ public class Tadpole extends AbstractFish { + this.setAge(nbt.getInt("Age")); + } + ++ this.ageLocked = nbt.getBoolean("AgeLocked"); // Paper + } + + @Override +@@ -210,6 +215,7 @@ public class Tadpole extends AbstractFish { + } + + private void ageUp(int seconds) { ++ if (this.ageLocked) return; // Paper + this.setAge(this.age + seconds * 20); + } + +diff --git a/src/main/java/net/minecraft/world/entity/animal/horse/AbstractHorse.java b/src/main/java/net/minecraft/world/entity/animal/horse/AbstractHorse.java +index 709237639ef0ec1cb623f270302a27b0072e8685..74151d69380e4adede40c7d7fc20834553706730 100644 +--- a/src/main/java/net/minecraft/world/entity/animal/horse/AbstractHorse.java ++++ b/src/main/java/net/minecraft/world/entity/animal/horse/AbstractHorse.java +@@ -779,6 +779,15 @@ public abstract class AbstractHorse extends Animal implements ContainerListener, + + } + ++ // Paper start - Horse API ++ public void setMouthOpen(boolean open) { ++ this.setFlag(FLAG_OPEN_MOUTH, open); ++ } ++ public boolean isMouthOpen() { ++ return this.getFlag(FLAG_OPEN_MOUTH); ++ } ++ // Paper end - Horse API ++ + @Override + public InteractionResult mobInteract(Player player, InteractionHand hand) { + if (!this.isVehicle() && !this.isBaby()) { +@@ -821,6 +830,11 @@ public abstract class AbstractHorse extends Animal implements ContainerListener, + this.setFlag(16, eatingGrass); + } + ++ // Paper start - Horse API ++ public void setForceStanding(boolean standing) { ++ this.setFlag(FLAG_STANDING, standing); ++ } ++ // Paper end - Horse API + public void setStanding(boolean angry) { + if (angry) { + this.setEating(false); +diff --git a/src/main/java/net/minecraft/world/entity/animal/horse/Llama.java b/src/main/java/net/minecraft/world/entity/animal/horse/Llama.java +index 04842dd7b9beabcecbd492d0b98faaebeea1a5d9..d5808d0c190877554a4a8191f68e8b4a36f2ff46 100644 +--- a/src/main/java/net/minecraft/world/entity/animal/horse/Llama.java ++++ b/src/main/java/net/minecraft/world/entity/animal/horse/Llama.java +@@ -71,11 +71,12 @@ public class Llama extends AbstractChestedHorse implements VariantHolder type, Level world) { + super(type, world); + this.getNavigation().setRequiredPathLength(40.0F); ++ this.maxDomestication = 30; // Paper - Missing entity API; configure max temper instead of a hardcoded value + } + + public boolean isTraderLlama() { +@@ -294,7 +295,7 @@ public class Llama extends AbstractChestedHorse implements VariantHolder type, Level world) { + super(type, world); +@@ -592,7 +597,7 @@ public class WitherBoss extends Monster implements RangedAttackMob { + + @Override + public boolean canUsePortal(boolean allowVehicles) { +- return false; ++ return this.canPortal; // Paper + } + + @Override +diff --git a/src/main/java/net/minecraft/world/entity/monster/EnderMan.java b/src/main/java/net/minecraft/world/entity/monster/EnderMan.java +index 512de8e79a842d4389e8528983b94af4843ffd11..0e8349aa6cb23860a4dd884e730ac8f22d422205 100644 +--- a/src/main/java/net/minecraft/world/entity/monster/EnderMan.java ++++ b/src/main/java/net/minecraft/world/entity/monster/EnderMan.java +@@ -429,6 +429,16 @@ public class EnderMan extends Monster implements NeutralMob { + this.entityData.set(EnderMan.DATA_STARED_AT, true); + } + ++ // Paper start ++ public void setCreepy(boolean creepy) { ++ this.entityData.set(EnderMan.DATA_CREEPY, creepy); ++ } ++ ++ public void setHasBeenStaredAt(boolean hasBeenStaredAt) { ++ this.entityData.set(EnderMan.DATA_STARED_AT, hasBeenStaredAt); ++ } ++ // Paper end ++ + @Override + public boolean requiresCustomPersistence() { + return super.requiresCustomPersistence() || this.getCarriedBlock() != null; +diff --git a/src/main/java/net/minecraft/world/entity/monster/Ghast.java b/src/main/java/net/minecraft/world/entity/monster/Ghast.java +index 71259b92a01a4feca270a250b1964f25f6da2d33..a8c8c03e972aa6352843cf4c3e4aebfb8f493125 100644 +--- a/src/main/java/net/minecraft/world/entity/monster/Ghast.java ++++ b/src/main/java/net/minecraft/world/entity/monster/Ghast.java +@@ -66,6 +66,12 @@ public class Ghast extends FlyingMob implements Enemy { + return this.explosionPower; + } + ++ // Paper start ++ public void setExplosionPower(int explosionPower) { ++ this.explosionPower = explosionPower; ++ } ++ // Paper end ++ + @Override + protected boolean shouldDespawnInPeaceful() { + return true; +diff --git a/src/main/java/net/minecraft/world/entity/monster/ZombieVillager.java b/src/main/java/net/minecraft/world/entity/monster/ZombieVillager.java +index 69af895863a2d0fa25a128518bda99d1754f9ac0..533cb2eff3d56e7e8a70aba5e1047250e192bf2c 100644 +--- a/src/main/java/net/minecraft/world/entity/monster/ZombieVillager.java ++++ b/src/main/java/net/minecraft/world/entity/monster/ZombieVillager.java +@@ -207,6 +207,12 @@ public class ZombieVillager extends Zombie implements VillagerDataHolder { + } + + public void startConverting(@Nullable UUID uuid, int delay) { ++ // Paper start - missing entity behaviour api - converting without entity event ++ this.startConverting(uuid, delay, true); ++ } ++ ++ public void startConverting(@Nullable UUID uuid, int delay, boolean broadcastEntityEvent) { ++ // Paper end - missing entity behaviour api - converting without entity event + this.conversionStarter = uuid; + this.villagerConversionTime = delay; + this.getEntityData().set(ZombieVillager.DATA_CONVERTING_ID, true); +@@ -214,7 +220,7 @@ public class ZombieVillager extends Zombie implements VillagerDataHolder { + this.removeEffect(MobEffects.WEAKNESS, org.bukkit.event.entity.EntityPotionEffectEvent.Cause.CONVERSION); + this.addEffect(new MobEffectInstance(MobEffects.DAMAGE_BOOST, delay, Math.min(this.level().getDifficulty().getId() - 1, 0)), org.bukkit.event.entity.EntityPotionEffectEvent.Cause.CONVERSION); + // CraftBukkit end +- this.level().broadcastEntityEvent(this, (byte) 16); ++ if (broadcastEntityEvent) this.level().broadcastEntityEvent(this, (byte) 16); // Paper - missing entity behaviour api - converting without entity event + } + + @Override +diff --git a/src/main/java/net/minecraft/world/entity/projectile/ThrownTrident.java b/src/main/java/net/minecraft/world/entity/projectile/ThrownTrident.java +index c26ef5e3ed4bb0e0729585167d696226f312c214..248410547de380e3195bbdc8b7b39cff908a0c32 100644 +--- a/src/main/java/net/minecraft/world/entity/projectile/ThrownTrident.java ++++ b/src/main/java/net/minecraft/world/entity/projectile/ThrownTrident.java +@@ -113,6 +113,20 @@ public class ThrownTrident extends AbstractArrow { + return (Boolean) this.entityData.get(ThrownTrident.ID_FOIL); + } + ++ // Paper start ++ public void setFoil(boolean foil) { ++ this.entityData.set(ThrownTrident.ID_FOIL, foil); ++ } ++ ++ public int getLoyalty() { ++ return this.entityData.get(ThrownTrident.ID_LOYALTY); ++ } ++ ++ public void setLoyalty(byte loyalty) { ++ this.entityData.set(ThrownTrident.ID_LOYALTY, loyalty); ++ } ++ // Paper end ++ + @Nullable + @Override + protected EntityHitResult findHitEntity(Vec3 currentPosition, Vec3 nextPosition) { +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftAbstractHorse.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftAbstractHorse.java +index 3952e52b94c1cc97e1d2d3885f59d7690efb74ad..9bcc0931510607b8fbd01233e2b3c346369b214d 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftAbstractHorse.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftAbstractHorse.java +@@ -114,4 +114,36 @@ public abstract class CraftAbstractHorse extends CraftAnimals implements Abstrac + public AbstractHorseInventory getInventory() { + return new CraftSaddledInventory(getHandle().inventory); + } ++ ++ // Paper start - Horse API ++ @Override ++ public boolean isEatingGrass() { ++ return this.getHandle().isEating(); ++ } ++ ++ @Override ++ public void setEatingGrass(boolean eating) { ++ this.getHandle().setEating(eating); ++ } ++ ++ @Override ++ public boolean isRearing() { ++ return this.getHandle().isStanding(); ++ } ++ ++ @Override ++ public void setRearing(boolean rearing) { ++ this.getHandle().setForceStanding(rearing); ++ } ++ ++ @Override ++ public boolean isEating() { ++ return this.getHandle().isMouthOpen(); ++ } ++ ++ @Override ++ public void setEating(boolean eating) { ++ this.getHandle().setMouthOpen(eating); ++ } ++ // Paper end - Horse API + } +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftAreaEffectCloud.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftAreaEffectCloud.java +index 17bffb45453f15328ca91794e26f6be1defef700..6591513bb62226b6f85fd2ef9f6ebe376f0f7362 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftAreaEffectCloud.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftAreaEffectCloud.java +@@ -229,4 +229,17 @@ public class CraftAreaEffectCloud extends CraftEntity implements AreaEffectCloud + this.getHandle().setOwner(null); + } + } ++ ++ // Paper start - owner API ++ @Override ++ public java.util.UUID getOwnerUniqueId() { ++ return this.getHandle().ownerUUID; ++ } ++ ++ @Override ++ public void setOwnerUniqueId(final java.util.UUID ownerUuid) { ++ this.getHandle().setOwner(null); ++ this.getHandle().ownerUUID = ownerUuid; ++ } ++ // Paper end + } +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftBat.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftBat.java +index b0a3531476f5a05ae846b68d825eddc35ebddea9..1bb72f28085f3885bec068b586ec222111044884 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftBat.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftBat.java +@@ -27,4 +27,25 @@ public class CraftBat extends CraftAmbient implements Bat { + public void setAwake(boolean state) { + this.getHandle().setResting(!state); + } ++ // Paper start ++ @Override ++ public org.bukkit.Location getTargetLocation() { ++ net.minecraft.core.BlockPos pos = this.getHandle().targetPosition; ++ if (pos == null) { ++ return null; ++ } ++ ++ return io.papermc.paper.util.MCUtil.toLocation(this.getHandle().level(), pos); ++ } ++ ++ @Override ++ public void setTargetLocation(org.bukkit.Location location) { ++ net.minecraft.core.BlockPos pos = null; ++ if (location != null) { ++ pos = io.papermc.paper.util.MCUtil.toBlockPosition(location); ++ } ++ ++ this.getHandle().targetPosition = pos; ++ } ++ // Paper end + } +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftBee.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftBee.java +index cfff1be6a4a4936a2dadb2590abc3d33c123d048..3dac93b0ab5d5acf5b33dc4b0efed60319eb657b 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftBee.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftBee.java +@@ -86,4 +86,42 @@ public class CraftBee extends CraftAnimals implements Bee { + public void setCannotEnterHiveTicks(int ticks) { + this.getHandle().setStayOutOfHiveCountdown(ticks); + } ++ // Paper start ++ @Override ++ public void setRollingOverride(net.kyori.adventure.util.TriState rolling) { ++ this.getHandle().rollingOverride = rolling; ++ ++ this.getHandle().setRolling(this.getHandle().isRolling()); // Refresh rolling state ++ } ++ ++ @Override ++ public boolean isRolling() { ++ return this.getRollingOverride().toBooleanOrElse(this.getHandle().isRolling()); ++ } ++ ++ @Override ++ public net.kyori.adventure.util.TriState getRollingOverride() { ++ return this.getHandle().rollingOverride; ++ } ++ ++ @Override ++ public void setCropsGrownSincePollination(int crops) { ++ this.getHandle().numCropsGrownSincePollination = crops; ++ } ++ ++ @Override ++ public int getCropsGrownSincePollination() { ++ return this.getHandle().numCropsGrownSincePollination; ++ } ++ ++ @Override ++ public void setTicksSincePollination(int ticks) { ++ this.getHandle().ticksWithoutNectarSinceExitingHive = ticks; ++ } ++ ++ @Override ++ public int getTicksSincePollination() { ++ return this.getHandle().ticksWithoutNectarSinceExitingHive; ++ } ++ // Paper end + } +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftCat.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftCat.java +index 57664124968e6268ad6699c6bd932981cc2fe6ba..88e876da7df64b68a5b71fd1deab75b59c5a64e3 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftCat.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftCat.java +@@ -139,4 +139,26 @@ public class CraftCat extends CraftTameableAnimal implements Cat { + return this.getKey().hashCode(); + } + } ++ ++ // Paper start - More cat api ++ @Override ++ public void setLyingDown(boolean lyingDown) { ++ this.getHandle().setLying(lyingDown); ++ } ++ ++ @Override ++ public boolean isLyingDown() { ++ return this.getHandle().isLying(); ++ } ++ ++ @Override ++ public void setHeadUp(boolean headUp) { ++ this.getHandle().setRelaxStateOne(headUp); ++ } ++ ++ @Override ++ public boolean isHeadUp() { ++ return this.getHandle().isRelaxStateOne(); ++ } ++ // Paper end - More cat api + } +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftChicken.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftChicken.java +index 64b75682a936e071353707f7615d6ff512fd617d..96f6e2fd9c6b20d34122abfe5c7fba732502d5a0 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftChicken.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftChicken.java +@@ -18,4 +18,26 @@ public class CraftChicken extends CraftAnimals implements Chicken { + public String toString() { + return "CraftChicken"; + } ++ ++ // Paper start ++ @Override ++ public boolean isChickenJockey() { ++ return this.getHandle().isChickenJockey(); ++ } ++ ++ @Override ++ public void setIsChickenJockey(boolean isChickenJockey) { ++ this.getHandle().setChickenJockey(isChickenJockey); ++ } ++ ++ @Override ++ public int getEggLayTime() { ++ return this.getHandle().eggTime; ++ } ++ ++ @Override ++ public void setEggLayTime(int eggLayTime) { ++ this.getHandle().eggTime = eggLayTime; ++ } ++ // Paper end + } +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftCod.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftCod.java +index fa0bf7db880063427ba12df1df1c72240fff93e9..63e6b07e3b159c74d9ef17be20b5ab43d07f0f5f 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftCod.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftCod.java +@@ -3,7 +3,7 @@ package org.bukkit.craftbukkit.entity; + import org.bukkit.craftbukkit.CraftServer; + import org.bukkit.entity.Cod; + +-public class CraftCod extends CraftFish implements Cod { ++public class CraftCod extends io.papermc.paper.entity.PaperSchoolableFish implements Cod { // Paper - School Fish API + + public CraftCod(CraftServer server, net.minecraft.world.entity.animal.Cod entity) { + super(server, entity); +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftDolphin.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftDolphin.java +index 5bae70ad160d0d0912aa9ef054c5515812957289..83867b9c5497e6e793b21c482646cc419587e182 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftDolphin.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftDolphin.java +@@ -18,4 +18,36 @@ public class CraftDolphin extends CraftAgeable implements Dolphin { + public String toString() { + return "CraftDolphin"; + } ++ ++ // Paper start - Missing Dolphin API ++ @Override ++ public int getMoistness() { ++ return this.getHandle().getMoistnessLevel(); ++ } ++ ++ @Override ++ public void setMoistness(int moistness) { ++ this.getHandle().setMoisntessLevel(moistness); ++ } ++ ++ @Override ++ public void setHasFish(boolean hasFish) { ++ this.getHandle().setGotFish(hasFish); ++ } ++ ++ @Override ++ public boolean hasFish() { ++ return this.getHandle().gotFish(); ++ } ++ ++ @Override ++ public org.bukkit.Location getTreasureLocation() { ++ return io.papermc.paper.util.MCUtil.toLocation(this.getHandle().level(), this.getHandle().getTreasurePos()); ++ } ++ ++ @Override ++ public void setTreasureLocation(org.bukkit.Location location) { ++ this.getHandle().setTreasurePos(io.papermc.paper.util.MCUtil.toBlockPosition(location)); ++ } ++ // Paper end - Missing Dolphin API + } +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftEnderDragonPart.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftEnderDragonPart.java +index cc4194ac9d7501b5d15655674dade14d59cb6733..33ae03b78b01c005a291a343b42507fb539e81a6 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftEnderDragonPart.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftEnderDragonPart.java +@@ -51,6 +51,13 @@ public class CraftEnderDragonPart extends CraftComplexPart implements EnderDrago + this.getParent().setHealth(health); + } + ++ // Paper start - entity heal API ++ @Override ++ public void heal(final double amount, final org.bukkit.event.entity.EntityRegainHealthEvent.RegainReason reason) { ++ this.getParent().heal(amount, reason); ++ } ++ // Paper end - entity heal API ++ + @Override + public double getAbsorptionAmount() { + return this.getParent().getAbsorptionAmount(); +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftEnderman.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftEnderman.java +index 21dc209e6f98b6306833b41e2763e746047d5a94..983b9d6ddb58eff297e96e5c8b28ec427efa267d 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftEnderman.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftEnderman.java +@@ -40,6 +40,28 @@ public class CraftEnderman extends CraftMonster implements Enderman { + this.getHandle().setCarriedBlock(blockData == null ? null : ((CraftBlockData) blockData).getState()); + } + ++ // Paper start ++ @Override ++ public boolean isScreaming() { ++ return this.getHandle().isCreepy(); ++ } ++ ++ @Override ++ public void setScreaming(boolean screaming) { ++ this.getHandle().setCreepy(screaming); ++ } ++ ++ @Override ++ public boolean hasBeenStaredAt() { ++ return this.getHandle().hasBeenStaredAt(); ++ } ++ ++ @Override ++ public void setHasBeenStaredAt(boolean hasBeenStaredAt) { ++ this.getHandle().setHasBeenStaredAt(hasBeenStaredAt); ++ } ++ // Paper end ++ + @Override + public EnderMan getHandle() { + return (EnderMan) this.entity; +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftEndermite.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftEndermite.java +index fc0f0e841dc974d080e1abb9bbafb5165801131f..d657fd2c507a5b215aeab0a5f3e9c2ee892a27c8 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftEndermite.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftEndermite.java +@@ -28,4 +28,15 @@ public class CraftEndermite extends CraftMonster implements Endermite { + public void setPlayerSpawned(boolean playerSpawned) { + // Nop + } ++ // Paper start ++ @Override ++ public void setLifetimeTicks(int ticks) { ++ this.getHandle().life = ticks; ++ } ++ ++ @Override ++ public int getLifetimeTicks() { ++ return this.getHandle().life; ++ } ++ // Paper end + } +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java +index 06fda053874e7ef0c8995b1a55e5f518b28eebc9..0c1c9033646dedcf1d11dee74d6965683adadf0a 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java +@@ -1087,4 +1087,27 @@ public abstract class CraftEntity implements org.bukkit.entity.Entity { + return set; + } + // Paper end - tracked players API ++ ++ // Paper start - missing entity api ++ @Override ++ public boolean isInvisible() { // Paper - moved up from LivingEntity ++ return this.getHandle().isInvisible(); ++ } ++ ++ @Override ++ public void setInvisible(boolean invisible) { // Paper - moved up from LivingEntity ++ this.getHandle().persistentInvisibility = invisible; ++ this.getHandle().setSharedFlag(Entity.FLAG_INVISIBLE, invisible); ++ } ++ ++ @Override ++ public void setNoPhysics(boolean noPhysics) { ++ this.getHandle().noPhysics = noPhysics; ++ } ++ ++ @Override ++ public boolean hasNoPhysics() { ++ return this.getHandle().noPhysics; ++ } ++ // Paper end - missing entity api + } +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftFireball.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftFireball.java +index 142f3e3257afebb2e831fd0970678123d99a1717..1b084d63bdbb24dad45d28eed1693eb6e26e24dc 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftFireball.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftFireball.java +@@ -84,6 +84,18 @@ public class CraftFireball extends AbstractProjectile implements Fireball { + return new Vector(delta.x, delta.y, delta.z); + } + ++ // Paper start - Expose power on fireball projectiles ++ @Override ++ public void setPower(final Vector power) { ++ this.setAcceleration(power); ++ } ++ ++ @Override ++ public Vector getPower() { ++ return this.getAcceleration(); ++ } ++ // Paper end - Expose power on fireball projectiles ++ + @Override + public AbstractHurtingProjectile getHandle() { + return (AbstractHurtingProjectile) this.entity; +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftFox.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftFox.java +index dd912be2933864236cd4fb35631d505972082d77..bb2b59ce9775a0d1dd9828885e57c14cf40d9f04 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftFox.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftFox.java +@@ -113,4 +113,41 @@ public class CraftFox extends CraftAnimals implements Fox { + public boolean isFaceplanted() { + return this.getHandle().isFaceplanted(); + } ++ ++ // Paper start - Add more fox behavior API ++ @Override ++ public void setInterested(boolean interested) { ++ this.getHandle().setIsInterested(interested); ++ } ++ ++ @Override ++ public boolean isInterested() { ++ return this.getHandle().isInterested(); ++ } ++ ++ @Override ++ public void setLeaping(boolean leaping) { ++ this.getHandle().setIsPouncing(leaping); ++ } ++ ++ @Override ++ public boolean isLeaping() { ++ return this.getHandle().isPouncing(); ++ } ++ ++ @Override ++ public void setDefending(boolean defending) { ++ this.getHandle().setDefending(defending); ++ } ++ ++ @Override ++ public boolean isDefending() { ++ return this.getHandle().isDefending(); ++ } ++ ++ @Override ++ public void setFaceplanted(boolean faceplanted) { ++ this.getHandle().setFaceplanted(faceplanted); ++ } ++ // Paper end - Add more fox behavior API + } +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftGhast.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftGhast.java +index 2cec61a1bb050c1ef81c5fc3d0afafe9ff29d459..97fa4e1e70203194bd939618b2fad92665af6d59 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftGhast.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftGhast.java +@@ -28,4 +28,17 @@ public class CraftGhast extends CraftFlying implements Ghast, CraftEnemy { + public void setCharging(boolean flag) { + this.getHandle().setCharging(flag); + } ++ ++ // Paper start ++ @Override ++ public int getExplosionPower() { ++ return this.getHandle().getExplosionPower(); ++ } ++ ++ @Override ++ public void setExplosionPower(int explosionPower) { ++ com.google.common.base.Preconditions.checkArgument(explosionPower >= 0 && explosionPower <= 127, "The explosion power has to be between 0 and 127"); ++ this.getHandle().setExplosionPower(explosionPower); ++ } ++ // Paper end + } +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftLivingEntity.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftLivingEntity.java +index b5830f31c3444fcc6fea83a3b71a05bf07ce379d..00db6ba96bda7ceaae8bc69a6b3a42e7a3929485 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftLivingEntity.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftLivingEntity.java +@@ -128,6 +128,13 @@ public class CraftLivingEntity extends CraftEntity implements LivingEntity { + } + } + ++ // Paper start - entity heal API ++ @Override ++ public void heal(final double amount, final org.bukkit.event.entity.EntityRegainHealthEvent.RegainReason reason) { ++ this.getHandle().heal((float) amount, reason); ++ } ++ // Paper end - entity heal API ++ + @Override + public double getAbsorptionAmount() { + return this.getHandle().getAbsorptionAmount(); +@@ -939,14 +946,29 @@ public class CraftLivingEntity extends CraftEntity implements LivingEntity { + + @Override + public boolean isInvisible() { +- return this.getHandle().isInvisible(); ++ return super.isInvisible(); // Paper - move invisibility up to Entity - diff on change + } + + @Override + public void setInvisible(boolean invisible) { +- this.getHandle().persistentInvisibility = invisible; +- this.getHandle().setSharedFlag(5, invisible); ++ super.setInvisible(invisible); // Paper - move invisibility up to Entity + } ++ // Paper start ++ @Override ++ public float getSidewaysMovement() { ++ return this.getHandle().xxa; ++ } ++ ++ @Override ++ public float getForwardsMovement() { ++ return this.getHandle().zza; ++ } ++ ++ @Override ++ public float getUpwardsMovement() { ++ return this.getHandle().yya; ++ } ++ // Paper end + + // Paper start + @Override +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftLlama.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftLlama.java +index bf297388c75521266c93580a9caafe6bad70ab45..351f42842b780d053cd2e5bad9ae299449141b10 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftLlama.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftLlama.java +@@ -58,4 +58,36 @@ public class CraftLlama extends CraftChestedHorse implements Llama, com.destroys + public String toString() { + return "CraftLlama"; + } ++ ++ // Paper start ++ @Override ++ public boolean inCaravan() { ++ return this.getHandle().inCaravan(); ++ } ++ ++ @Override ++ public void joinCaravan(@org.jetbrains.annotations.NotNull Llama llama) { ++ this.getHandle().joinCaravan(((CraftLlama) llama).getHandle()); ++ } ++ ++ @Override ++ public void leaveCaravan() { ++ this.getHandle().leaveCaravan(); ++ } ++ ++ @Override ++ public boolean hasCaravanTail() { ++ return this.getHandle().hasCaravanTail(); ++ } ++ ++ @Override ++ public Llama getCaravanHead() { ++ return this.getHandle().getCaravanHead() == null ? null : (Llama) this.getHandle().getCaravanHead().getBukkitEntity(); ++ } ++ ++ @Override ++ public Llama getCaravanTail() { ++ return this.getHandle().caravanTail == null ? null : (Llama) this.getHandle().caravanTail.getBukkitEntity(); ++ } ++ // Paper end + } +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftMinecartHopper.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftMinecartHopper.java +index 17f5684cba9d3ed22d9925d1951520cc4751dfe2..3a3563a1bdbc0d84d973b3a04b50b78b4bc3d379 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftMinecartHopper.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftMinecartHopper.java +@@ -33,4 +33,20 @@ public final class CraftMinecartHopper extends CraftMinecartContainer implements + public void setEnabled(boolean enabled) { + ((MinecartHopper) this.getHandle()).setEnabled(enabled); + } ++ // Paper start ++ @Override ++ public net.minecraft.world.entity.vehicle.MinecartHopper getHandle() { ++ return (net.minecraft.world.entity.vehicle.MinecartHopper) super.getHandle(); ++ } ++ ++ @Override ++ public int getPickupCooldown() { ++ throw new UnsupportedOperationException("Hopper minecarts don't have cooldowns"); ++ } ++ ++ @Override ++ public void setPickupCooldown(int cooldown) { ++ throw new UnsupportedOperationException("Hopper minecarts don't have cooldowns"); ++ } ++ // Paper end + } +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftMob.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftMob.java +index bd739428a7e5e35ebcdb70cd187379b3d222339b..7cf42f62d91c131b1cab576979f85c58c3cecb3b 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftMob.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftMob.java +@@ -146,4 +146,16 @@ public abstract class CraftMob extends CraftLivingEntity implements Mob { + return getHandle().getMaxHeadXRot(); + } + // Paper end ++ ++ // Paper start ++ @Override ++ public boolean isAggressive() { ++ return this.getHandle().isAggressive(); ++ } ++ ++ @Override ++ public void setAggressive(boolean aggressive) { ++ this.getHandle().setAggressive(aggressive); ++ } ++ // Paper end + } +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftPanda.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftPanda.java +index 5467e4a74b70ff57b49d9e6bc686c493178f8511..01d104d91de9e1319d27e39d3f474318c7809486 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftPanda.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftPanda.java +@@ -41,6 +41,38 @@ public class CraftPanda extends CraftAnimals implements Panda { + this.getHandle().setHiddenGene(CraftPanda.toNms(gene)); + } + ++ // Paper start - Panda API ++ @Override ++ public void setSneezeTicks(int ticks) { ++ this.getHandle().setSneezeCounter(ticks); ++ } ++ ++ @Override ++ public int getSneezeTicks() { ++ return this.getHandle().getSneezeCounter(); ++ } ++ ++ @Override ++ public void setEatingTicks(int ticks) { ++ this.getHandle().setEatCounter(ticks); ++ } ++ ++ @Override ++ public int getEatingTicks() { ++ return this.getHandle().getEatCounter(); ++ } ++ ++ @Override ++ public void setUnhappyTicks(int ticks) { ++ this.getHandle().setUnhappyCounter(ticks); ++ } ++ ++ @Override ++ public Gene getCombinedGene() { ++ return CraftPanda.fromNms(this.getHandle().getVariant()); ++ } ++ // Paper end - Panda API ++ + @Override + public boolean isRolling() { + return this.getHandle().isRolling(); +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftPhantom.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftPhantom.java +index 9304e201db1ec96d0916aa8ea781f3e4bc7991e6..83e77c6d287d8e239d2f55f3e9f19ef74946be7c 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftPhantom.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftPhantom.java +@@ -44,5 +44,17 @@ public class CraftPhantom extends CraftFlying implements Phantom, CraftEnemy { + public void setShouldBurnInDay(boolean shouldBurnInDay) { + getHandle().setShouldBurnInDay(shouldBurnInDay); + } ++ ++ @Override ++ public org.bukkit.Location getAnchorLocation() { ++ net.minecraft.core.BlockPos pos = this.getHandle().anchorPoint; ++ return io.papermc.paper.util.MCUtil.toLocation(this.getHandle().level(), pos); ++ } ++ ++ @Override ++ public void setAnchorLocation(org.bukkit.Location location) { ++ com.google.common.base.Preconditions.checkArgument(location != null, "location cannot be null"); ++ this.getHandle().anchorPoint = io.papermc.paper.util.MCUtil.toBlockPosition(location); ++ } + // Paper end + } +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftPiglin.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftPiglin.java +index f5ecb8c1dc92e5a4b123effd2859123b17a586d3..5124a383b60b2c8de89fa992547d0c61db760c21 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftPiglin.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftPiglin.java +@@ -84,4 +84,37 @@ public class CraftPiglin extends CraftPiglinAbstract implements Piglin, com.dest + public String toString() { + return "CraftPiglin"; + } ++ // Paper start ++ @Override ++ public void setChargingCrossbow(boolean chargingCrossbow) { ++ this.getHandle().setChargingCrossbow(chargingCrossbow); ++ } ++ ++ @Override ++ public boolean isChargingCrossbow() { ++ return this.getHandle().isChargingCrossbow(); ++ } ++ ++ @Override ++ public void setDancing(boolean dancing) { ++ if (dancing) { ++ this.getHandle().getBrain().setMemory(net.minecraft.world.entity.ai.memory.MemoryModuleType.DANCING, true); ++ this.getHandle().getBrain().setMemory(net.minecraft.world.entity.ai.memory.MemoryModuleType.CELEBRATE_LOCATION, this.getHandle().getOnPos()); ++ } else { ++ this.getHandle().getBrain().eraseMemory(net.minecraft.world.entity.ai.memory.MemoryModuleType.DANCING); ++ this.getHandle().getBrain().eraseMemory(net.minecraft.world.entity.ai.memory.MemoryModuleType.CELEBRATE_LOCATION); ++ } ++ } ++ ++ @Override ++ public void setDancing(long duration) { ++ this.getHandle().getBrain().setMemoryWithExpiry(net.minecraft.world.entity.ai.memory.MemoryModuleType.DANCING, true, duration); ++ this.getHandle().getBrain().setMemoryWithExpiry(net.minecraft.world.entity.ai.memory.MemoryModuleType.CELEBRATE_LOCATION, this.getHandle().getOnPos(), duration); ++ } ++ ++ @Override ++ public boolean isDancing() { ++ return this.getHandle().isDancing(); ++ } ++ // Paper end + } +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftPolarBear.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftPolarBear.java +index c7aec6f28e5d3546235b30f6b1112440a76163c5..fe075cfdf3097d6cb768e71b8cc360abb8eaf367 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftPolarBear.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftPolarBear.java +@@ -17,4 +17,16 @@ public class CraftPolarBear extends CraftAnimals implements PolarBear { + public String toString() { + return "CraftPolarBear"; + } ++ ++ // Paper start ++ @Override ++ public boolean isStanding() { ++ return this.getHandle().isStanding(); ++ } ++ ++ @Override ++ public void setStanding(boolean standing) { ++ this.getHandle().setStanding(standing); ++ } ++ // Paper end + } +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftRabbit.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftRabbit.java +index 6b48b117a9cba12aae055c0ea981dfb5bc03a86e..519ef701a7d6534f7cb516f6296b95ee521f661d 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftRabbit.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftRabbit.java +@@ -29,4 +29,15 @@ public class CraftRabbit extends CraftAnimals implements Rabbit { + public void setRabbitType(Type type) { + this.getHandle().setVariant(net.minecraft.world.entity.animal.Rabbit.Variant.values()[type.ordinal()]); + } ++ // Paper start ++ @Override ++ public void setMoreCarrotTicks(int ticks) { ++ this.getHandle().moreCarrotTicks = ticks; ++ } ++ ++ @Override ++ public int getMoreCarrotTicks() { ++ return this.getHandle().moreCarrotTicks; ++ } ++ // Paper end + } +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftRavager.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftRavager.java +index cae59f77c704a5b9515dc4917ed5fdc89631ecfb..09796ce15658e3f7c223a265a547a51ee729ed40 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftRavager.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftRavager.java +@@ -18,4 +18,35 @@ public class CraftRavager extends CraftRaider implements Ravager { + public String toString() { + return "CraftRavager"; + } ++ // Paper start - Missing Entity Behavior ++ @Override ++ public int getAttackTicks() { ++ return this.getHandle().getAttackTick(); ++ } ++ ++ @Override ++ public void setAttackTicks(int ticks) { ++ this.getHandle().attackTick = ticks; ++ } ++ ++ @Override ++ public int getStunnedTicks() { ++ return this.getHandle().getStunnedTick(); ++ } ++ ++ @Override ++ public void setStunnedTicks(int ticks) { ++ this.getHandle().stunnedTick = ticks; ++ } ++ ++ @Override ++ public int getRoarTicks() { ++ return this.getHandle().getRoarTick(); ++ } ++ ++ @Override ++ public void setRoarTicks(int ticks) { ++ this.getHandle().roarTick = ticks; ++ } ++ // Paper end + } +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftSalmon.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftSalmon.java +index 551c30cb0fb41dfe4a03663d34ecf9764566c215..7660cc21e936002ebb23510f0ec2b58d71e5157d 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftSalmon.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftSalmon.java +@@ -4,7 +4,7 @@ import com.google.common.base.Preconditions; + import org.bukkit.craftbukkit.CraftServer; + import org.bukkit.entity.Salmon; + +-public class CraftSalmon extends CraftFish implements Salmon { ++public class CraftSalmon extends io.papermc.paper.entity.PaperSchoolableFish implements Salmon { // Paper - Schooling Fish API + + public CraftSalmon(CraftServer server, net.minecraft.world.entity.animal.Salmon entity) { + super(server, entity); +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftTNTPrimed.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftTNTPrimed.java +index 36ab282e2c4060bdea4e57f3ab9dfef9f6cd622c..a61aec087fa7cec27a803668bdc1b9e6eb336755 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftTNTPrimed.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftTNTPrimed.java +@@ -67,4 +67,17 @@ public class CraftTNTPrimed extends CraftEntity implements TNTPrimed { + this.getHandle().owner = null; + } + } ++ ++ // Paper start ++ @Override ++ public void setBlockData(org.bukkit.block.data.BlockData data) { ++ com.google.common.base.Preconditions.checkArgument(data != null, "The visual block data of this tnt cannot be null. To reset it just set to the TNT default block data"); ++ this.getHandle().setBlockState(((org.bukkit.craftbukkit.block.data.CraftBlockData) data).getState()); ++ } ++ ++ @Override ++ public org.bukkit.block.data.BlockData getBlockData() { ++ return org.bukkit.craftbukkit.block.data.CraftBlockData.fromData(this.getHandle().getBlockState()); ++ } ++ // Paper end + } +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftTadpole.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftTadpole.java +index 451a9bfd9b9b6945e224f1bb05c7951ed934b4e3..d7c6a0bbc5671ea8f2488230c94df5146a1e98b9 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftTadpole.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftTadpole.java +@@ -28,4 +28,15 @@ public class CraftTadpole extends CraftFish implements org.bukkit.entity.Tadpole + public void setAge(int age) { + this.getHandle().age = age; + } ++ // Paper start ++ @Override ++ public void setAgeLock(boolean lock) { ++ this.getHandle().ageLocked = lock; ++ } ++ ++ @Override ++ public boolean getAgeLock() { ++ return this.getHandle().ageLocked; ++ } ++ // Paper end + } +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftTrident.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftTrident.java +index 33d6e8121755ad6cddacb4fc69e795f9831c27bd..e374b9f40eddca13b30855d25a2030f8df98138f 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftTrident.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftTrident.java +@@ -31,4 +31,27 @@ public class CraftTrident extends CraftAbstractArrow implements Trident { + public String toString() { + return "CraftTrident"; + } ++ ++ // Paper start ++ @Override ++ public boolean hasGlint() { ++ return this.getHandle().isFoil(); ++ } ++ ++ @Override ++ public void setGlint(boolean glint) { ++ this.getHandle().setFoil(glint); ++ } ++ ++ @Override ++ public int getLoyaltyLevel() { ++ return this.getHandle().getLoyalty(); ++ } ++ ++ @Override ++ public void setLoyaltyLevel(int loyaltyLevel) { ++ com.google.common.base.Preconditions.checkArgument(loyaltyLevel >= 0 && loyaltyLevel <= 127, "The loyalty level has to be between 0 and 127"); ++ this.getHandle().setLoyalty((byte) loyaltyLevel); ++ } ++ // Paper end + } +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftTropicalFish.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftTropicalFish.java +index e3bde6d1c0e03407af1382a61748470063bb2e18..9e53c30801c700719c78c0fd521fd615c94e02c8 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftTropicalFish.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftTropicalFish.java +@@ -7,7 +7,7 @@ import org.bukkit.craftbukkit.CraftServer; + import org.bukkit.entity.TropicalFish; + import org.bukkit.entity.TropicalFish.Pattern; + +-public class CraftTropicalFish extends CraftFish implements TropicalFish { ++public class CraftTropicalFish extends io.papermc.paper.entity.PaperSchoolableFish implements TropicalFish { // Paper - Schooling Fish API + + public CraftTropicalFish(CraftServer server, net.minecraft.world.entity.animal.TropicalFish entity) { + super(server, entity); +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftVex.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftVex.java +index 1cfbe9c476f4a254edf3edf4b70696bbaba78558..e9ec3455eabc473e104b5342a615a38c1ac25a4f 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftVex.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftVex.java +@@ -29,6 +29,26 @@ public class CraftVex extends CraftMonster implements Vex { + public void setSummoner(org.bukkit.entity.Mob summoner) { + getHandle().setOwner(summoner == null ? null : ((CraftMob) summoner).getHandle()); + } ++ ++ @Override ++ public boolean hasLimitedLifetime() { ++ return this.getHandle().hasLimitedLife; ++ } ++ ++ @Override ++ public void setLimitedLifetime(boolean hasLimitedLifetime) { ++ this.getHandle().hasLimitedLife = hasLimitedLifetime; ++ } ++ ++ @Override ++ public int getLimitedLifetimeTicks() { ++ return this.getHandle().limitedLifeTicks; ++ } ++ ++ @Override ++ public void setLimitedLifetimeTicks(int ticks) { ++ this.getHandle().limitedLifeTicks = ticks; ++ } + // Paper end + + @Override +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftVillagerZombie.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftVillagerZombie.java +index e2a0c11867abee6add8775259c54f2052de7b1ad..3aa23d9f22d5cd22231293fd7d1ca4cb79eb7cb3 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftVillagerZombie.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftVillagerZombie.java +@@ -60,13 +60,20 @@ public class CraftVillagerZombie extends CraftZombie implements ZombieVillager { + + @Override + public void setConversionTime(int time) { ++ // Paper start - missing entity behaviour api - converting without entity event ++ this.setConversionTime(time, true); ++ } ++ ++ @Override ++ public void setConversionTime(int time, boolean broadcastEntityEvent) { ++ // Paper end - missing entity behaviour api - converting without entity event + if (time < 0) { + this.getHandle().villagerConversionTime = -1; + this.getHandle().getEntityData().set(net.minecraft.world.entity.monster.ZombieVillager.DATA_CONVERTING_ID, false); + this.getHandle().conversionStarter = null; + this.getHandle().removeEffect(MobEffects.DAMAGE_BOOST, org.bukkit.event.entity.EntityPotionEffectEvent.Cause.CONVERSION); + } else { +- this.getHandle().startConverting(null, time); ++ this.getHandle().startConverting(null, time, broadcastEntityEvent); // Paper - missing entity behaviour api - converting without entity event + } + } + +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftWanderingTrader.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftWanderingTrader.java +index 0e597394a3dd08f022614fc9777302fea581eb55..3cceefa0d6278924a19641a49bdf16bcdacb2233 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftWanderingTrader.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftWanderingTrader.java +@@ -49,5 +49,25 @@ public class CraftWanderingTrader extends CraftAbstractVillager implements Wande + public boolean canDrinkMilk() { + return getHandle().canDrinkMilk; + } ++ ++ @Override ++ public org.bukkit.Location getWanderingTowards() { ++ net.minecraft.core.BlockPos pos = this.getHandle().getWanderTarget(); ++ if (pos == null) { ++ return null; ++ } ++ ++ return io.papermc.paper.util.MCUtil.toLocation(this.getHandle().level(), pos); ++ } ++ ++ @Override ++ public void setWanderingTowards(org.bukkit.Location location) { ++ net.minecraft.core.BlockPos pos = null; ++ if (location != null) { ++ pos = io.papermc.paper.util.MCUtil.toBlockPosition(location); ++ } ++ ++ this.getHandle().setWanderTarget(pos); ++ } + // Paper end + } +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftWarden.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftWarden.java +index 794e4fe0a3fbd967f665b2707865c15491370c76..c284eb96a1e330078076cbe61f0f6e2ff4ed89bd 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftWarden.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftWarden.java +@@ -37,6 +37,13 @@ public class CraftWarden extends CraftMonster implements org.bukkit.entity.Warde + return this.getHandle().getAngerManagement().getActiveAnger(((CraftEntity) entity).getHandle()); + } + ++ // Paper start ++ @Override ++ public int getHighestAnger() { ++ return this.getHandle().getAngerManagement().getActiveAnger(null); ++ } ++ // Paper end ++ + @Override + public void increaseAnger(Entity entity, int increase) { + Preconditions.checkArgument(entity != null, "Entity cannot be null"); +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftWither.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftWither.java +index 1113533d281ed159bb735040fb1f913482debf3a..7881c6253c1d652c0c0d54a9a8accdf0a1ff0f3e 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftWither.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftWither.java +@@ -67,4 +67,36 @@ public class CraftWither extends CraftMonster implements Wither, com.destroystok + + this.getHandle().setInvulnerableTicks(ticks); + } ++ ++ // Paper start ++ @Override ++ public boolean isCharged() { ++ return getHandle().isPowered(); ++ } ++ ++ @Override ++ public int getInvulnerableTicks() { ++ return getHandle().getInvulnerableTicks(); ++ } ++ ++ @Override ++ public void setInvulnerableTicks(int ticks) { ++ getHandle().setInvulnerableTicks(ticks); ++ } ++ ++ @Override ++ public boolean canTravelThroughPortals() { ++ return getHandle().canUsePortal(false); ++ } ++ ++ @Override ++ public void setCanTravelThroughPortals(boolean value) { ++ getHandle().setCanTravelThroughPortals(value); ++ } ++ ++ @Override ++ public void enterInvulnerabilityPhase() { ++ this.getHandle().makeInvulnerable(); ++ } ++ // Paper end + } diff --git a/patches/server/0553-add-per-world-spawn-limits.patch b/patches/server/0553-add-per-world-spawn-limits.patch deleted file mode 100644 index 2af34cd4398f..000000000000 --- a/patches/server/0553-add-per-world-spawn-limits.patch +++ /dev/null @@ -1,24 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: chase -Date: Wed, 2 Dec 2020 22:43:39 -0800 -Subject: [PATCH] add per world spawn limits - - -diff --git a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java -index dc16ed952719a5203b1b9a61e0ad2bd39021e8f1..a41c6705aa7e04ad32395f89b95ca76617c9416d 100644 ---- a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java -+++ b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java -@@ -222,6 +222,13 @@ public class CraftWorld extends CraftRegionAccessor implements World { - this.biomeProvider = biomeProvider; - - this.environment = env; -+ // Paper start - per world spawn limits -+ for (SpawnCategory spawnCategory : SpawnCategory.values()) { -+ if (CraftSpawnCategory.isValidForLimits(spawnCategory)) { -+ setSpawnLimit(spawnCategory, this.world.paperConfig().entities.spawning.spawnLimits.getInt(CraftSpawnCategory.toNMS(spawnCategory))); -+ } -+ } -+ // Paper end - per world spawn limits - } - - @Override diff --git a/patches/server/0554-Fix-potions-splash-events.patch b/patches/server/0554-Fix-potions-splash-events.patch deleted file mode 100644 index 38cfaf97698b..000000000000 --- a/patches/server/0554-Fix-potions-splash-events.patch +++ /dev/null @@ -1,181 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Jake Potrebic -Date: Thu, 20 May 2021 20:40:53 -0700 -Subject: [PATCH] Fix potions splash events - -Fix PotionSplashEvent for water splash potions -Fixes SPIGOT-6221: https://hub.spigotmc.org/jira/projects/SPIGOT/issues/SPIGOT-6221 -Fix splash events cancellation that still show particles/sound - -diff --git a/src/main/java/net/minecraft/world/entity/projectile/ThrownPotion.java b/src/main/java/net/minecraft/world/entity/projectile/ThrownPotion.java -index be787a5b52e90796d4f06e17e564f4324807c3e6..cb34cc9443da56c0497c7a0192c8b8363c3426fe 100644 ---- a/src/main/java/net/minecraft/world/entity/projectile/ThrownPotion.java -+++ b/src/main/java/net/minecraft/world/entity/projectile/ThrownPotion.java -@@ -106,55 +106,76 @@ public class ThrownPotion extends ThrowableItemProjectile implements ItemSupplie - ItemStack itemstack = this.getItem(); - PotionContents potioncontents = (PotionContents) itemstack.getOrDefault(DataComponents.POTION_CONTENTS, PotionContents.EMPTY); - -+ boolean showParticles = true; // Paper - Fix potions splash events - if (potioncontents.is(Potions.WATER)) { -- this.applyWater(); -+ showParticles = this.applyWater(hitResult); // Paper - Fix potions splash events - } else if (true || potioncontents.hasEffects()) { // CraftBukkit - Call event even if no effects to apply - if (this.isLingering()) { -- this.makeAreaOfEffectCloud(potioncontents, hitResult); // CraftBukkit - Pass MovingObjectPosition -+ showParticles = this.makeAreaOfEffectCloud(potioncontents, hitResult); // CraftBukkit - Pass MovingObjectPosition // Paper - } else { -- this.applySplash(potioncontents.getAllEffects(), hitResult.getType() == HitResult.Type.ENTITY ? ((EntityHitResult) hitResult).getEntity() : null, hitResult); // CraftBukkit - Pass MovingObjectPosition -+ showParticles = this.applySplash(potioncontents.getAllEffects(), hitResult.getType() == HitResult.Type.ENTITY ? ((EntityHitResult) hitResult).getEntity() : null, hitResult); // CraftBukkit - Pass MovingObjectPosition // Paper - } - } - -+ if (showParticles) { // Paper - Fix potions splash events - int i = potioncontents.potion().isPresent() && ((Potion) ((Holder) potioncontents.potion().get()).value()).hasInstantEffects() ? 2007 : 2002; - - this.level().levelEvent(i, this.blockPosition(), potioncontents.getColor()); -+ } // Paper - Fix potions splash events - this.discard(EntityRemoveEvent.Cause.HIT); // CraftBukkit - add Bukkit remove cause - } - } - -- private void applyWater() { -+ private static final Predicate APPLY_WATER_GET_ENTITIES_PREDICATE = ThrownPotion.WATER_SENSITIVE_OR_ON_FIRE.or(Axolotl.class::isInstance); // Paper - Fix potions splash events -+ private boolean applyWater(@Nullable HitResult hitResult) { // Paper - Fix potions splash events - AABB axisalignedbb = this.getBoundingBox().inflate(4.0D, 2.0D, 4.0D); -- List list = this.level().getEntitiesOfClass(net.minecraft.world.entity.LivingEntity.class, axisalignedbb, ThrownPotion.WATER_SENSITIVE_OR_ON_FIRE); -+ // Paper start - Fix potions splash events -+ List list = this.level().getEntitiesOfClass(net.minecraft.world.entity.LivingEntity.class, axisalignedbb, ThrownPotion.APPLY_WATER_GET_ENTITIES_PREDICATE); -+ Map affected = new HashMap<>(); -+ java.util.Set rehydrate = new java.util.HashSet<>(); -+ java.util.Set extinguish = new java.util.HashSet<>(); - Iterator iterator = list.iterator(); - - while (iterator.hasNext()) { - net.minecraft.world.entity.LivingEntity entityliving = (net.minecraft.world.entity.LivingEntity) iterator.next(); -+ if (entityliving instanceof Axolotl axolotl) { -+ rehydrate.add(((org.bukkit.entity.Axolotl) axolotl.getBukkitEntity())); -+ } - double d0 = this.distanceToSqr((Entity) entityliving); - - if (d0 < 16.0D) { - if (entityliving.isSensitiveToWater()) { -- entityliving.hurt(this.damageSources().indirectMagic(this, this.getOwner()), 1.0F); -+ affected.put(entityliving.getBukkitLivingEntity(), 1.0); - } - - if (entityliving.isOnFire() && entityliving.isAlive()) { -- entityliving.extinguishFire(); -+ extinguish.add(entityliving.getBukkitLivingEntity()); - } - } - } - -- List list1 = this.level().getEntitiesOfClass(Axolotl.class, axisalignedbb); -- Iterator iterator1 = list1.iterator(); -- -- while (iterator1.hasNext()) { -- Axolotl axolotl = (Axolotl) iterator1.next(); -- -- axolotl.rehydrate(); -+ io.papermc.paper.event.entity.WaterBottleSplashEvent event = org.bukkit.craftbukkit.event.CraftEventFactory.callWaterBottleSplashEvent( -+ this, hitResult, affected, rehydrate, extinguish -+ ); -+ if (!event.isCancelled()) { -+ for (LivingEntity affectedEntity : event.getToDamage()) { -+ ((CraftLivingEntity) affectedEntity).getHandle().hurt(this.damageSources().indirectMagic(this, this.getOwner()), 1.0F); -+ } -+ for (LivingEntity toExtinguish : event.getToExtinguish()) { -+ ((CraftLivingEntity) toExtinguish).getHandle().extinguishFire(); -+ } -+ for (LivingEntity toRehydrate : event.getToRehydrate()) { -+ if (((CraftLivingEntity) toRehydrate).getHandle() instanceof Axolotl axolotl) { -+ axolotl.rehydrate(); -+ } -+ } -+ // Paper end - Fix potions splash events - } -+ return !event.isCancelled(); // Paper - Fix potions splash events - - } - -- private void applySplash(Iterable iterable, @Nullable Entity entity, HitResult position) { // CraftBukkit - Pass MovingObjectPosition -+ private boolean applySplash(Iterable iterable, @Nullable Entity entity, HitResult position) { // CraftBukkit - Pass MovingObjectPosition // Paper - Fix potions splash events - AABB axisalignedbb = this.getBoundingBox().inflate(4.0D, 2.0D, 4.0D); - List list = this.level().getEntitiesOfClass(net.minecraft.world.entity.LivingEntity.class, axisalignedbb); - Map affected = new HashMap(); // CraftBukkit -@@ -172,6 +193,7 @@ public class ThrownPotion extends ThrowableItemProjectile implements ItemSupplie - if (d0 < 16.0D) { - double d1; - -+ // Paper - diff on change, used when calling the splash event for water splash potions - if (entityliving == entity) { - d1 = 1.0D; - } else { -@@ -227,10 +249,11 @@ public class ThrownPotion extends ThrowableItemProjectile implements ItemSupplie - } - } - } -+ return !event.isCancelled(); // Paper - Fix potions splash events - - } - -- private void makeAreaOfEffectCloud(PotionContents potioncontents, HitResult position) { // CraftBukkit - Pass MovingObjectPosition -+ private boolean makeAreaOfEffectCloud(PotionContents potioncontents, HitResult position) { // CraftBukkit - Pass MovingObjectPosition - AreaEffectCloud entityareaeffectcloud = new AreaEffectCloud(this.level(), this.getX(), this.getY(), this.getZ()); - Entity entity = this.getOwner(); - -@@ -243,14 +266,16 @@ public class ThrownPotion extends ThrowableItemProjectile implements ItemSupplie - entityareaeffectcloud.setWaitTime(10); - entityareaeffectcloud.setRadiusPerTick(-entityareaeffectcloud.getRadius() / (float) entityareaeffectcloud.getDuration()); - entityareaeffectcloud.setPotionContents(potioncontents); -+ boolean noEffects = potioncontents.hasEffects(); // Paper - Fix potions splash events - // CraftBukkit start - org.bukkit.event.entity.LingeringPotionSplashEvent event = org.bukkit.craftbukkit.event.CraftEventFactory.callLingeringPotionSplashEvent(this, position, entityareaeffectcloud); -- if (!(event.isCancelled() || entityareaeffectcloud.isRemoved())) { -+ if (!(event.isCancelled() || entityareaeffectcloud.isRemoved() || (noEffects && !entityareaeffectcloud.potionContents.hasEffects()))) { // Paper - don't spawn area effect cloud if the effects were empty and not changed during the event handling - this.level().addFreshEntity(entityareaeffectcloud); - } else { - entityareaeffectcloud.discard(null); // CraftBukkit - add Bukkit remove cause - } - // CraftBukkit end -+ return !event.isCancelled(); // Paper - Fix potions splash events - } - - public boolean isLingering() { -diff --git a/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java b/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java -index 36adcab02e97cae2d087bae74cc4ceaf3052a9f8..b93fa1ea73f0b218e6c6bed8bd36694e26544ab0 100644 ---- a/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java -+++ b/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java -@@ -886,6 +886,32 @@ public class CraftEventFactory { - return event; - } - -+ // Paper start - Fix potions splash events -+ public static io.papermc.paper.event.entity.WaterBottleSplashEvent callWaterBottleSplashEvent(net.minecraft.world.entity.projectile.ThrownPotion potion, @Nullable HitResult hitResult, Map affectedEntities, java.util.Set rehydrate, java.util.Set extinguish) { -+ ThrownPotion thrownPotion = (ThrownPotion) potion.getBukkitEntity(); -+ -+ Block hitBlock = null; -+ BlockFace hitFace = null; -+ org.bukkit.entity.Entity hitEntity = null; -+ -+ if (hitResult != null) { -+ if (hitResult.getType() == HitResult.Type.BLOCK) { -+ BlockHitResult blockHitResult = (BlockHitResult) hitResult; -+ hitBlock = CraftBlock.at(potion.level(), blockHitResult.getBlockPos()); -+ hitFace = CraftBlock.notchToBlockFace(blockHitResult.getDirection()); -+ } else if (hitResult.getType() == HitResult.Type.ENTITY) { -+ hitEntity = ((EntityHitResult) hitResult).getEntity().getBukkitEntity(); -+ } -+ } -+ -+ io.papermc.paper.event.entity.WaterBottleSplashEvent event = new io.papermc.paper.event.entity.WaterBottleSplashEvent( -+ thrownPotion, hitEntity, hitBlock, hitFace, affectedEntities, rehydrate, extinguish -+ ); -+ event.callEvent(); -+ return event; -+ } -+ // Paper end - Fix potions splash events -+ - /** - * BlockFadeEvent - */ diff --git a/patches/server/0554-Fix-return-value-of-Block-applyBoneMeal-always-being.patch b/patches/server/0554-Fix-return-value-of-Block-applyBoneMeal-always-being.patch new file mode 100644 index 000000000000..40cc161932c9 --- /dev/null +++ b/patches/server/0554-Fix-return-value-of-Block-applyBoneMeal-always-being.patch @@ -0,0 +1,19 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Jason Penilla <11360596+jpenilla@users.noreply.github.com> +Date: Mon, 28 Jun 2021 18:16:52 -0700 +Subject: [PATCH] Fix return value of Block#applyBoneMeal always being false + + +diff --git a/src/main/java/org/bukkit/craftbukkit/block/CraftBlock.java b/src/main/java/org/bukkit/craftbukkit/block/CraftBlock.java +index 54fb380a6896731a18c0100722d12099e590cbc9..9c8aac69f01db647e20d49d272ccc107a7edceaf 100644 +--- a/src/main/java/org/bukkit/craftbukkit/block/CraftBlock.java ++++ b/src/main/java/org/bukkit/craftbukkit/block/CraftBlock.java +@@ -558,7 +558,7 @@ public class CraftBlock implements Block { + } + } + +- return result == InteractionResult.SUCCESS && (event == null || !event.isCancelled()); ++ return result == InteractionResult.CONSUME && (event == null || !event.isCancelled()); // Paper - CONSUME is returned on success server-side (see BoneMealItem.applyBoneMeal and InteractionResult.sidedSuccess(boolean)) + } + + @Override diff --git a/patches/server/0555-Add-more-LimitedRegion-API.patch b/patches/server/0555-Add-more-LimitedRegion-API.patch deleted file mode 100644 index 6e183e954e83..000000000000 --- a/patches/server/0555-Add-more-LimitedRegion-API.patch +++ /dev/null @@ -1,56 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: dfsek -Date: Sat, 19 Jun 2021 20:15:59 -0700 -Subject: [PATCH] Add more LimitedRegion API - - -diff --git a/src/main/java/org/bukkit/craftbukkit/generator/CraftLimitedRegion.java b/src/main/java/org/bukkit/craftbukkit/generator/CraftLimitedRegion.java -index e7266835946c3bf49dbd83fe677481e8eddd16e5..7d6ee60b1d3e023e1eabc59eb591c3ae3d8ac043 100644 ---- a/src/main/java/org/bukkit/craftbukkit/generator/CraftLimitedRegion.java -+++ b/src/main/java/org/bukkit/craftbukkit/generator/CraftLimitedRegion.java -@@ -254,4 +254,45 @@ public class CraftLimitedRegion extends CraftRegionAccessor implements LimitedRe - public void addEntityWithPassengers(net.minecraft.world.entity.Entity entity, CreatureSpawnEvent.SpawnReason reason) { - this.entities.add(entity); - } -+ -+ // Paper start - Add more LimitedRegion API -+ @Override -+ public void setBlockState(int x, int y, int z, BlockState state) { -+ BlockPos pos = new BlockPos(x, y, z); -+ if (!state.getBlockData().matches(getHandle().getBlockState(pos).createCraftBlockData())) { -+ throw new IllegalArgumentException("BlockData does not match! Expected " + state.getBlockData().getAsString(false) + ", got " + getHandle().getBlockState(pos).createCraftBlockData().getAsString(false)); -+ } -+ getHandle().getBlockEntity(pos).loadWithComponents(((org.bukkit.craftbukkit.block.CraftBlockEntityState) state).getSnapshotNBT(), this.getHandle().registryAccess()); -+ } -+ -+ @Override -+ public void scheduleBlockUpdate(int x, int y, int z) { -+ BlockPos position = new BlockPos(x, y, z); -+ getHandle().scheduleTick(position, getHandle().getBlockState(position).getBlock(), 0); -+ } -+ -+ @Override -+ public void scheduleFluidUpdate(int x, int y, int z) { -+ BlockPos position = new BlockPos(x, y, z); -+ getHandle().scheduleTick(position, getHandle().getFluidState(position).getType(), 0); -+ } -+ -+ @Override -+ public World getWorld() { -+ // reading/writing the returned Minecraft world causes a deadlock. -+ // By implementing this, and covering it in warnings, we're assuming people won't be stupid, and -+ // if they are stupid, they'll figure it out pretty fast. -+ return getHandle().getMinecraftWorld().getWorld(); -+ } -+ -+ @Override -+ public int getCenterChunkX() { -+ return centerChunkX; -+ } -+ -+ @Override -+ public int getCenterChunkZ() { -+ return centerChunkZ; -+ } -+ // Paper end - Add more LimitedRegion API - } diff --git a/patches/server/0555-Use-getChunkIfLoadedImmediately-in-places.patch b/patches/server/0555-Use-getChunkIfLoadedImmediately-in-places.patch new file mode 100644 index 000000000000..99a97d953d9b --- /dev/null +++ b/patches/server/0555-Use-getChunkIfLoadedImmediately-in-places.patch @@ -0,0 +1,53 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Spottedleaf +Date: Mon, 8 Jul 2019 00:13:36 -0700 +Subject: [PATCH] Use getChunkIfLoadedImmediately in places + +This prevents us from hitting chunk loads for chunks at or less-than +ticket level 33 (yes getChunkIfLoaded will actually perform a chunk +load in that case). + +diff --git a/src/main/java/net/minecraft/server/level/ServerLevel.java b/src/main/java/net/minecraft/server/level/ServerLevel.java +index f58069f0c9d836cb33f3ea09c562708951a91797..91fb83761885752743adb53cc9ed30ddc879263d 100644 +--- a/src/main/java/net/minecraft/server/level/ServerLevel.java ++++ b/src/main/java/net/minecraft/server/level/ServerLevel.java +@@ -232,7 +232,7 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe + public boolean hasEntityMoveEvent; // Paper - Add EntityMoveEvent + + public LevelChunk getChunkIfLoaded(int x, int z) { +- return this.chunkSource.getChunk(x, z, false); ++ return this.chunkSource.getChunkAtIfLoadedImmediately(x, z); // Paper - Use getChunkIfLoadedImmediately + } + + @Override +diff --git a/src/main/java/net/minecraft/world/level/Level.java b/src/main/java/net/minecraft/world/level/Level.java +index 71e9c1504d4b85ffb695401974748d56fefb66e6..9536e127ff4d45ca59b74fe0f3dbde9a18c04f42 100644 +--- a/src/main/java/net/minecraft/world/level/Level.java ++++ b/src/main/java/net/minecraft/world/level/Level.java +@@ -179,6 +179,13 @@ public abstract class Level implements LevelAccessor, AutoCloseable { + public CraftServer getCraftServer() { + return (CraftServer) Bukkit.getServer(); + } ++ // Paper start - Use getChunkIfLoadedImmediately ++ @Override ++ public boolean hasChunk(int chunkX, int chunkZ) { ++ return this.getChunkIfLoaded(chunkX, chunkZ) != null; ++ } ++ // Paper end - Use getChunkIfLoadedImmediately ++ + + public abstract ResourceKey getTypeKey(); + +diff --git a/src/main/java/net/minecraft/world/level/gameevent/GameEventDispatcher.java b/src/main/java/net/minecraft/world/level/gameevent/GameEventDispatcher.java +index 13b34e89bd3e55df1bb1d4d0cf013bafae43f502..df6c97be1b278c97a20390be5d3e60f429383702 100644 +--- a/src/main/java/net/minecraft/world/level/gameevent/GameEventDispatcher.java ++++ b/src/main/java/net/minecraft/world/level/gameevent/GameEventDispatcher.java +@@ -56,7 +56,7 @@ public class GameEventDispatcher { + + for (int l1 = j; l1 <= i1; ++l1) { + for (int i2 = l; i2 <= k1; ++i2) { +- LevelChunk chunk = this.level.getChunkSource().getChunkNow(l1, i2); ++ LevelChunk chunk = (LevelChunk) this.level.getChunkIfLoadedImmediately(l1, i2); // Paper - Use getChunkIfLoadedImmediately + + if (chunk != null) { + for (int j2 = k; j2 <= j1; ++j2) { diff --git a/patches/server/0556-Fix-PlayerDropItemEvent-using-wrong-item.patch b/patches/server/0556-Fix-PlayerDropItemEvent-using-wrong-item.patch deleted file mode 100644 index 8f8228a1d434..000000000000 --- a/patches/server/0556-Fix-PlayerDropItemEvent-using-wrong-item.patch +++ /dev/null @@ -1,61 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Spottedleaf -Date: Sun, 20 Jun 2021 21:55:59 -0700 -Subject: [PATCH] Fix PlayerDropItemEvent using wrong item - - -diff --git a/src/main/java/net/minecraft/server/commands/GiveCommand.java b/src/main/java/net/minecraft/server/commands/GiveCommand.java -index c81fd3e1108fb0a02f9240263404af2b968c8494..0d9de4c61c7b26a6ff37c12fde629161fd0c3d5a 100644 ---- a/src/main/java/net/minecraft/server/commands/GiveCommand.java -+++ b/src/main/java/net/minecraft/server/commands/GiveCommand.java -@@ -38,6 +38,7 @@ public class GiveCommand { - - private static int giveItem(CommandSourceStack source, ItemInput item, Collection targets, int count) throws CommandSyntaxException { - ItemStack itemstack = item.createItemStack(1, false); -+ final Component displayName = itemstack.getDisplayName(); // Paper - get display name early - int j = itemstack.getMaxStackSize(); - int k = j * 100; - -@@ -79,11 +80,11 @@ public class GiveCommand { - - if (targets.size() == 1) { - source.sendSuccess(() -> { -- return Component.translatable("commands.give.success.single", count, itemstack.getDisplayName(), ((ServerPlayer) targets.iterator().next()).getDisplayName()); -+ return Component.translatable("commands.give.success.single", count, displayName, ((ServerPlayer) targets.iterator().next()).getDisplayName()); // Paper - use cached display name - }, true); - } else { - source.sendSuccess(() -> { -- return Component.translatable("commands.give.success.single", count, itemstack.getDisplayName(), targets.size()); -+ return Component.translatable("commands.give.success.single", count, displayName, targets.size()); // Paper - use cached display name - }, true); - } - -diff --git a/src/main/java/net/minecraft/server/level/ServerPlayer.java b/src/main/java/net/minecraft/server/level/ServerPlayer.java -index 42df8d45ceb963043cf6467b8fc47272ce0873da..f20019261a09f425137731f7a4b92e889b617334 100644 ---- a/src/main/java/net/minecraft/server/level/ServerPlayer.java -+++ b/src/main/java/net/minecraft/server/level/ServerPlayer.java -@@ -2486,7 +2486,7 @@ public class ServerPlayer extends net.minecraft.world.entity.player.Player { - - if (flag1) { - if (!itemstack1.isEmpty()) { -- this.awardStat(Stats.ITEM_DROPPED.get(itemstack1.getItem()), itemstack.getCount()); -+ this.awardStat(Stats.ITEM_DROPPED.get(itemstack1.getItem()), itemstack1.getCount()); // Paper - Fix PlayerDropItemEvent using wrong item - } - - this.awardStat(Stats.DROP); -diff --git a/src/main/java/net/minecraft/world/entity/player/Player.java b/src/main/java/net/minecraft/world/entity/player/Player.java -index b444b24a92bd2209ee4104ae82c7cfa9c876c910..7fee6ffeb8ccde965fcc1454eb0d8c6b3637da41 100644 ---- a/src/main/java/net/minecraft/world/entity/player/Player.java -+++ b/src/main/java/net/minecraft/world/entity/player/Player.java -@@ -745,6 +745,11 @@ public abstract class Player extends LivingEntity { - } - - double d0 = this.getEyeY() - 0.30000001192092896D; -+ // Paper start -+ ItemStack tmp = itemstack.copy(); -+ itemstack.setCount(0); -+ itemstack = tmp; -+ // Paper end - ItemEntity entityitem = new ItemEntity(this.level(), this.getX(), d0, this.getZ(), itemstack); - - entityitem.setPickUpDelay(40); diff --git a/patches/server/0556-Fix-commands-from-signs-not-firing-command-events.patch b/patches/server/0556-Fix-commands-from-signs-not-firing-command-events.patch new file mode 100644 index 000000000000..aa87a425fd3a --- /dev/null +++ b/patches/server/0556-Fix-commands-from-signs-not-firing-command-events.patch @@ -0,0 +1,121 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Jake Potrebic +Date: Fri, 9 Jul 2021 13:50:48 -0700 +Subject: [PATCH] Fix commands from signs not firing command events + +This patch changes sign command logic so that `run_command` click events: + - are logged to the console + - fire PlayerCommandPreprocessEvent + - work with double-slash commands like `//wand` + - sends failure messages to the player who clicked the sign + +diff --git a/src/main/java/io/papermc/paper/commands/DelegatingCommandSource.java b/src/main/java/io/papermc/paper/commands/DelegatingCommandSource.java +new file mode 100644 +index 0000000000000000000000000000000000000000..01a2bc1feec808790bb93618ce46adb9bea5a9c8 +--- /dev/null ++++ b/src/main/java/io/papermc/paper/commands/DelegatingCommandSource.java +@@ -0,0 +1,42 @@ ++package io.papermc.paper.commands; ++ ++import net.minecraft.commands.CommandSource; ++import net.minecraft.commands.CommandSourceStack; ++import net.minecraft.network.chat.Component; ++import org.bukkit.command.CommandSender; ++ ++import java.util.UUID; ++ ++public class DelegatingCommandSource implements CommandSource { ++ ++ private final CommandSource delegate; ++ ++ public DelegatingCommandSource(CommandSource delegate) { ++ this.delegate = delegate; ++ } ++ ++ @Override ++ public void sendSystemMessage(Component message) { ++ delegate.sendSystemMessage(message); ++ } ++ ++ @Override ++ public boolean acceptsSuccess() { ++ return delegate.acceptsSuccess(); ++ } ++ ++ @Override ++ public boolean acceptsFailure() { ++ return delegate.acceptsFailure(); ++ } ++ ++ @Override ++ public boolean shouldInformAdmins() { ++ return delegate.shouldInformAdmins(); ++ } ++ ++ @Override ++ public CommandSender getBukkitSender(CommandSourceStack wrapper) { ++ return delegate.getBukkitSender(wrapper); ++ } ++} +diff --git a/src/main/java/net/minecraft/world/level/block/entity/SignBlockEntity.java b/src/main/java/net/minecraft/world/level/block/entity/SignBlockEntity.java +index 3339130f3a02e6e395bcd8e3047b35f6f4eca9a9..a34c2fc6ac1418a89d789b8945ca3dad57f64151 100644 +--- a/src/main/java/net/minecraft/world/level/block/entity/SignBlockEntity.java ++++ b/src/main/java/net/minecraft/world/level/block/entity/SignBlockEntity.java +@@ -274,7 +274,17 @@ public class SignBlockEntity extends BlockEntity { + ClickEvent chatclickable = chatmodifier.getClickEvent(); + + if (chatclickable != null && chatclickable.getAction() == ClickEvent.Action.RUN_COMMAND) { +- player.getServer().getCommands().performPrefixedCommand(this.createCommandSourceStack(player, world, pos), chatclickable.getValue()); ++ // Paper start - Fix commands from signs not firing command events ++ String command = chatclickable.getValue().startsWith("/") ? chatclickable.getValue() : "/" + chatclickable.getValue(); ++ if (org.spigotmc.SpigotConfig.logCommands) { ++ LOGGER.info("{} issued server command: {}", player.getScoreboardName(), command); ++ } ++ io.papermc.paper.event.player.PlayerSignCommandPreprocessEvent event = new io.papermc.paper.event.player.PlayerSignCommandPreprocessEvent((org.bukkit.entity.Player) player.getBukkitEntity(), command, new org.bukkit.craftbukkit.util.LazyPlayerSet(player.getServer()), (org.bukkit.block.Sign) CraftBlock.at(this.level, this.worldPosition).getState(), front ? Side.FRONT : Side.BACK); ++ if (!event.callEvent()) { ++ return false; ++ } ++ player.getServer().getCommands().performPrefixedCommand(this.createCommandSourceStack(((org.bukkit.craftbukkit.entity.CraftPlayer) event.getPlayer()).getHandle(), world, pos), event.getMessage()); ++ // Paper end - Fix commands from signs not firing command events + flag1 = true; + } + } +@@ -314,8 +324,23 @@ public class SignBlockEntity extends BlockEntity { + String s = player == null ? "Sign" : player.getName().getString(); + Object object = player == null ? Component.literal("Sign") : player.getDisplayName(); + +- // CraftBukkit - commandSource +- return new CommandSourceStack(this.commandSource, Vec3.atCenterOf(pos), Vec2.ZERO, (ServerLevel) world, 2, s, (Component) object, world.getServer(), player); ++ // Paper start - Fix commands from signs not firing command events ++ CommandSource commandSource = this.level.paperConfig().misc.showSignClickCommandFailureMsgsToPlayer ? new io.papermc.paper.commands.DelegatingCommandSource(this.commandSource) { ++ @Override ++ public void sendSystemMessage(Component message) { ++ if (player instanceof final ServerPlayer serverPlayer) { ++ serverPlayer.sendSystemMessage(message); ++ } ++ } ++ ++ @Override ++ public boolean acceptsFailure() { ++ return true; ++ } ++ } : this.commandSource; ++ // Paper end - Fix commands from signs not firing command events ++ // CraftBukkit - this ++ return new CommandSourceStack(commandSource, Vec3.atCenterOf(pos), Vec2.ZERO, (ServerLevel) world, 2, s, (Component) object, world.getServer(), player); // Paper - Fix commands from signs not firing command events + } + + @Override +diff --git a/src/main/java/org/bukkit/craftbukkit/command/BukkitCommandWrapper.java b/src/main/java/org/bukkit/craftbukkit/command/BukkitCommandWrapper.java +index d113e54a30db16e2ad955170df6030d15de530d6..21b6f90cf5bd7087d1a0f512289d971f2c3e1afa 100644 +--- a/src/main/java/org/bukkit/craftbukkit/command/BukkitCommandWrapper.java ++++ b/src/main/java/org/bukkit/craftbukkit/command/BukkitCommandWrapper.java +@@ -61,7 +61,7 @@ public class BukkitCommandWrapper implements com.mojang.brigadier.Command +Date: Fri, 12 Mar 2021 19:22:21 -0800 +Subject: [PATCH] Add PlayerArmSwingEvent + + +diff --git a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java +index 36127f7c9ae50a628e88e7b456889a8f259b06da..e48c6f37ba0ebe698e28042e9331ab2ec0c39e7c 100644 +--- a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java ++++ b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java +@@ -2428,7 +2428,7 @@ public class ServerGamePacketListenerImpl extends ServerCommonPacketListenerImpl + } // Paper end - Call interact event + + // Arm swing animation +- PlayerAnimationEvent event = new PlayerAnimationEvent(this.getCraftPlayer(), (packet.getHand() == InteractionHand.MAIN_HAND) ? PlayerAnimationType.ARM_SWING : PlayerAnimationType.OFF_ARM_SWING); ++ io.papermc.paper.event.player.PlayerArmSwingEvent event = new io.papermc.paper.event.player.PlayerArmSwingEvent(this.getCraftPlayer(), org.bukkit.craftbukkit.CraftEquipmentSlot.getHand(packet.getHand())); // Paper - Add PlayerArmSwingEvent + this.cserver.getPluginManager().callEvent(event); + + if (event.isCancelled()) return; diff --git a/patches/server/0557-Missing-Entity-API.patch b/patches/server/0557-Missing-Entity-API.patch deleted file mode 100644 index 971be97b6363..000000000000 --- a/patches/server/0557-Missing-Entity-API.patch +++ /dev/null @@ -1,1401 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Owen1212055 <23108066+Owen1212055@users.noreply.github.com> -Date: Mon, 21 Jun 2021 23:56:07 -0400 -Subject: [PATCH] Missing Entity API - -== AT == -public net.minecraft.world.entity.animal.Fox isDefending()Z -public net.minecraft.world.entity.animal.Fox setDefending(Z)V -public net.minecraft.world.entity.animal.Fox setFaceplanted(Z)V -public net.minecraft.world.entity.animal.Panda getEatCounter()I -public net.minecraft.world.entity.animal.Panda setEatCounter(I)V -public net.minecraft.world.entity.animal.Bee isRolling()Z -public net.minecraft.world.entity.animal.Bee setRolling(Z)V -public net.minecraft.world.entity.animal.Bee numCropsGrownSincePollination -public net.minecraft.world.entity.animal.Bee ticksWithoutNectarSinceExitingHive -public net.minecraft.world.entity.monster.piglin.Piglin isChargingCrossbow()Z -public net.minecraft.world.entity.ambient.Bat targetPosition -public net.minecraft.world.entity.monster.Ravager attackTick -public net.minecraft.world.entity.monster.Ravager stunnedTick -public net.minecraft.world.entity.monster.Ravager roarTick -public net.minecraft.world.entity.vehicle.MinecartTNT explode(D)V -public net.minecraft.world.entity.vehicle.MinecartTNT fuse -public net.minecraft.world.entity.monster.Endermite life -public net.minecraft.world.entity.projectile.AbstractArrow soundEvent -public net.minecraft.world.entity.monster.Phantom anchorPoint -public net.minecraft.world.entity.npc.WanderingTrader getWanderTarget()Lnet/minecraft/core/BlockPos; -public net.minecraft.world.entity.animal.AbstractSchoolingFish leader -public net.minecraft.world.entity.animal.AbstractSchoolingFish schoolSize -public net.minecraft.world.entity.animal.Rabbit moreCarrotTicks -public net.minecraft.world.entity.AreaEffectCloud ownerUUID -public net.minecraft.world.entity.animal.MushroomCow stewEffects -public net.minecraft.world.entity.Entity FLAG_INVISIBLE -public net.minecraft.world.entity.animal.Cat setRelaxStateOne(Z)V -public net.minecraft.world.entity.animal.Cat isRelaxStateOne()Z - -Co-authored-by: Nassim Jahnke -Co-authored-by: Jake Potrebic -Co-authored-by: William Blake Galbreath -Co-authored-by: SoSeDiK -Co-authored-by: booky10 -Co-authored-by: Amin -Co-authored-by: TrollyLoki -Co-authored-by: FireInstall -Co-authored-by: maxcom1 <46265094+maxcom1@users.noreply.github.com> -Co-authored-by: TotalledZebra - -diff --git a/src/main/java/com/destroystokyo/paper/entity/ai/MobGoalHelper.java b/src/main/java/com/destroystokyo/paper/entity/ai/MobGoalHelper.java -index c99eafab2103c7f5bca7ffba68a10bd853df055f..f7241c5292f1c012404eea11256813fbc2c2df1a 100644 ---- a/src/main/java/com/destroystokyo/paper/entity/ai/MobGoalHelper.java -+++ b/src/main/java/com/destroystokyo/paper/entity/ai/MobGoalHelper.java -@@ -164,7 +164,7 @@ public class MobGoalHelper { - bukkitMap.put(net.minecraft.world.entity.monster.Endermite.class, Endermite.class); - bukkitMap.put(net.minecraft.world.entity.monster.Evoker.class, Evoker.class); - bukkitMap.put(AbstractFish.class, Fish.class); -- bukkitMap.put(AbstractSchoolingFish.class, Fish.class); // close enough -+ bukkitMap.put(AbstractSchoolingFish.class, io.papermc.paper.entity.SchoolableFish.class); - bukkitMap.put(FlyingMob.class, Flying.class); - bukkitMap.put(net.minecraft.world.entity.animal.Fox.class, Fox.class); - bukkitMap.put(net.minecraft.world.entity.monster.Ghast.class, Ghast.class); -diff --git a/src/main/java/io/papermc/paper/entity/PaperSchoolableFish.java b/src/main/java/io/papermc/paper/entity/PaperSchoolableFish.java -new file mode 100644 -index 0000000000000000000000000000000000000000..41bf71d116ffc5431586ce54abba7f8def6c1dcf ---- /dev/null -+++ b/src/main/java/io/papermc/paper/entity/PaperSchoolableFish.java -@@ -0,0 +1,52 @@ -+package io.papermc.paper.entity; -+ -+import net.minecraft.world.entity.animal.AbstractSchoolingFish; -+import org.bukkit.craftbukkit.CraftServer; -+import org.bukkit.craftbukkit.entity.CraftFish; -+import org.jetbrains.annotations.NotNull; -+ -+public class PaperSchoolableFish extends CraftFish implements SchoolableFish { -+ -+ public PaperSchoolableFish(CraftServer server, AbstractSchoolingFish entity) { -+ super(server, entity); -+ } -+ -+ @Override -+ public AbstractSchoolingFish getHandle() { -+ return (AbstractSchoolingFish) super.getHandle(); -+ } -+ -+ @Override -+ public void startFollowing(@NotNull SchoolableFish fish) { -+ if (this.getHandle().isFollower()) { // If following a fish already, properly remove the old one -+ this.stopFollowing(); -+ } -+ -+ this.getHandle().startFollowing(((PaperSchoolableFish) fish).getHandle()); -+ } -+ -+ @Override -+ public void stopFollowing() { -+ this.getHandle().stopFollowing(); -+ } -+ -+ @Override -+ public int getSchoolSize() { -+ return this.getHandle().schoolSize; -+ } -+ -+ @Override -+ public int getMaxSchoolSize() { -+ return this.getHandle().getMaxSchoolSize(); -+ } -+ -+ @Override -+ public SchoolableFish getSchoolLeader() { -+ AbstractSchoolingFish leader = this.getHandle().leader; -+ if (leader == null) { -+ return null; -+ } -+ -+ return (SchoolableFish) leader.getBukkitEntity(); -+ } -+} -diff --git a/src/main/java/net/minecraft/world/entity/animal/AbstractSchoolingFish.java b/src/main/java/net/minecraft/world/entity/animal/AbstractSchoolingFish.java -index 2cd61138dfaa82fa698ef8d32d690f51f621ee3b..957eb2ba3f647f70522243fedf36b921e58142bd 100644 ---- a/src/main/java/net/minecraft/world/entity/animal/AbstractSchoolingFish.java -+++ b/src/main/java/net/minecraft/world/entity/animal/AbstractSchoolingFish.java -@@ -51,6 +51,7 @@ public abstract class AbstractSchoolingFish extends AbstractFish { - } - - public void stopFollowing() { -+ if (this.leader == null) return; // Avoid NPE, plugins can now set the leader and certain fish goals might cause this method to be called - this.leader.removeFollower(); - this.leader = null; - } -diff --git a/src/main/java/net/minecraft/world/entity/animal/Bee.java b/src/main/java/net/minecraft/world/entity/animal/Bee.java -index 615b57fac9def18d9dcaefcfe397c74c11cac627..f933654b66f7474dc071da5f10cf1684fdac367a 100644 ---- a/src/main/java/net/minecraft/world/entity/animal/Bee.java -+++ b/src/main/java/net/minecraft/world/entity/animal/Bee.java -@@ -554,11 +554,13 @@ public class Bee extends Animal implements NeutralMob, FlyingAnimal { - this.setFlag(4, hasStung); - } - -+ public net.kyori.adventure.util.TriState rollingOverride = net.kyori.adventure.util.TriState.NOT_SET; // Paper - Rolling override - public boolean isRolling() { - return this.getFlag(2); - } - - public void setRolling(boolean nearTarget) { -+ nearTarget = rollingOverride.toBooleanOrElse(nearTarget); // Paper - Rolling override - this.setFlag(2, nearTarget); - } - -diff --git a/src/main/java/net/minecraft/world/entity/animal/frog/Tadpole.java b/src/main/java/net/minecraft/world/entity/animal/frog/Tadpole.java -index b06d39d3bd39a4dc4f273a359a89592d3b8cf184..43046f4a0cff620834ac4647efdcde227185b2ff 100644 ---- a/src/main/java/net/minecraft/world/entity/animal/frog/Tadpole.java -+++ b/src/main/java/net/minecraft/world/entity/animal/frog/Tadpole.java -@@ -50,6 +50,7 @@ public class Tadpole extends AbstractFish { - public int age; - protected static final ImmutableList>> SENSOR_TYPES = ImmutableList.of(SensorType.NEAREST_LIVING_ENTITIES, SensorType.NEAREST_PLAYERS, SensorType.HURT_BY, SensorType.FROG_TEMPTATIONS); - protected static final ImmutableList> MEMORY_TYPES = ImmutableList.of(MemoryModuleType.LOOK_TARGET, MemoryModuleType.NEAREST_VISIBLE_LIVING_ENTITIES, MemoryModuleType.WALK_TARGET, MemoryModuleType.CANT_REACH_WALK_TARGET_SINCE, MemoryModuleType.PATH, MemoryModuleType.NEAREST_VISIBLE_ADULT, MemoryModuleType.TEMPTATION_COOLDOWN_TICKS, MemoryModuleType.IS_TEMPTED, MemoryModuleType.TEMPTING_PLAYER, MemoryModuleType.BREED_TARGET, MemoryModuleType.IS_PANICKING); -+ public boolean ageLocked; // Paper - - public Tadpole(EntityType type, Level world) { - super(type, world); -@@ -100,7 +101,7 @@ public class Tadpole extends AbstractFish { - @Override - public void aiStep() { - super.aiStep(); -- if (!this.level().isClientSide) { -+ if (!this.level().isClientSide && !this.ageLocked) { // Paper - this.setAge(this.age + 1); - } - -@@ -110,12 +111,14 @@ public class Tadpole extends AbstractFish { - public void addAdditionalSaveData(CompoundTag nbt) { - super.addAdditionalSaveData(nbt); - nbt.putInt("Age", this.age); -+ nbt.putBoolean("AgeLocked", this.ageLocked); // Paper - } - - @Override - public void readAdditionalSaveData(CompoundTag nbt) { - super.readAdditionalSaveData(nbt); - this.setAge(nbt.getInt("Age")); -+ this.ageLocked = nbt.getBoolean("AgeLocked"); // Paper - } - - @Nullable -@@ -167,6 +170,7 @@ public class Tadpole extends AbstractFish { - Bucketable.saveDefaultDataToBucketTag(this, stack); - CustomData.update(DataComponents.BUCKET_ENTITY_DATA, stack, (nbttagcompound) -> { - nbttagcompound.putInt("Age", this.getAge()); -+ nbttagcompound.putBoolean("AgeLocked", this.ageLocked); // Paper - }); - } - -@@ -177,6 +181,7 @@ public class Tadpole extends AbstractFish { - this.setAge(nbt.getInt("Age")); - } - -+ this.ageLocked = nbt.getBoolean("AgeLocked"); // Paper - } - - @Override -@@ -208,6 +213,7 @@ public class Tadpole extends AbstractFish { - } - - private void ageUp(int seconds) { -+ if (this.ageLocked) return; // Paper - this.setAge(this.age + seconds * 20); - } - -diff --git a/src/main/java/net/minecraft/world/entity/animal/horse/AbstractHorse.java b/src/main/java/net/minecraft/world/entity/animal/horse/AbstractHorse.java -index affa2e133611b7a045a99bac801398263d114ba7..f1e43254936feedfe0ffbf77071505f3a65e5053 100644 ---- a/src/main/java/net/minecraft/world/entity/animal/horse/AbstractHorse.java -+++ b/src/main/java/net/minecraft/world/entity/animal/horse/AbstractHorse.java -@@ -760,6 +760,15 @@ public abstract class AbstractHorse extends Animal implements ContainerListener, - - } - -+ // Paper start - Horse API -+ public void setMouthOpen(boolean open) { -+ this.setFlag(FLAG_OPEN_MOUTH, open); -+ } -+ public boolean isMouthOpen() { -+ return this.getFlag(FLAG_OPEN_MOUTH); -+ } -+ // Paper end - Horse API -+ - @Override - public InteractionResult mobInteract(Player player, InteractionHand hand) { - if (!this.isVehicle() && !this.isBaby()) { -@@ -802,6 +811,11 @@ public abstract class AbstractHorse extends Animal implements ContainerListener, - this.setFlag(16, eatingGrass); - } - -+ // Paper start - Horse API -+ public void setForceStanding(boolean standing) { -+ this.setFlag(FLAG_STANDING, standing); -+ } -+ // Paper end - Horse API - public void setStanding(boolean angry) { - if (angry) { - this.setEating(false); -diff --git a/src/main/java/net/minecraft/world/entity/animal/horse/Llama.java b/src/main/java/net/minecraft/world/entity/animal/horse/Llama.java -index 8afd453deda455bd486c9a4a69790151f5012f62..33b7e578f39608d522a9c270cac69be44a34456b 100644 ---- a/src/main/java/net/minecraft/world/entity/animal/horse/Llama.java -+++ b/src/main/java/net/minecraft/world/entity/animal/horse/Llama.java -@@ -74,10 +74,11 @@ public class Llama extends AbstractChestedHorse implements VariantHolder type, Level world) { - super(type, world); -+ this.maxDomestication = 30; // Paper - Missing entity API; configure max temper instead of a hardcoded value - } - - public boolean isTraderLlama() { -@@ -313,7 +314,7 @@ public class Llama extends AbstractChestedHorse implements VariantHolder type, Level world) { - super(type, world); -@@ -595,7 +600,7 @@ public class WitherBoss extends Monster implements PowerableMob, RangedAttackMob - - @Override - public boolean canUsePortal(boolean allowVehicles) { -- return false; -+ return this.canPortal; // Paper - } - - @Override -diff --git a/src/main/java/net/minecraft/world/entity/monster/EnderMan.java b/src/main/java/net/minecraft/world/entity/monster/EnderMan.java -index 0214e8bbcaefdd92ee3719d9a570f9d256ee29ba..7caa5469a4daa5d0c569f446ff2d597a9f10e8ac 100644 ---- a/src/main/java/net/minecraft/world/entity/monster/EnderMan.java -+++ b/src/main/java/net/minecraft/world/entity/monster/EnderMan.java -@@ -442,6 +442,16 @@ public class EnderMan extends Monster implements NeutralMob { - this.entityData.set(EnderMan.DATA_STARED_AT, true); - } - -+ // Paper start -+ public void setCreepy(boolean creepy) { -+ this.entityData.set(EnderMan.DATA_CREEPY, creepy); -+ } -+ -+ public void setHasBeenStaredAt(boolean hasBeenStaredAt) { -+ this.entityData.set(EnderMan.DATA_STARED_AT, hasBeenStaredAt); -+ } -+ // Paper end -+ - @Override - public boolean requiresCustomPersistence() { - return super.requiresCustomPersistence() || this.getCarriedBlock() != null; -diff --git a/src/main/java/net/minecraft/world/entity/monster/Ghast.java b/src/main/java/net/minecraft/world/entity/monster/Ghast.java -index 603e948583ce0a99fc0061a85f495e8262659035..a836a902bebf318ceabaed4e98fab1141b46a28b 100644 ---- a/src/main/java/net/minecraft/world/entity/monster/Ghast.java -+++ b/src/main/java/net/minecraft/world/entity/monster/Ghast.java -@@ -65,6 +65,12 @@ public class Ghast extends FlyingMob implements Enemy { - return this.explosionPower; - } - -+ // Paper start -+ public void setExplosionPower(int explosionPower) { -+ this.explosionPower = explosionPower; -+ } -+ // Paper end -+ - @Override - protected boolean shouldDespawnInPeaceful() { - return true; -diff --git a/src/main/java/net/minecraft/world/entity/monster/ZombieVillager.java b/src/main/java/net/minecraft/world/entity/monster/ZombieVillager.java -index 9cf453248b6ee9e1af9f5945b1e515a9ad7ff236..f8c733961015ace508bfe14fd61d5188ca9d551b 100644 ---- a/src/main/java/net/minecraft/world/entity/monster/ZombieVillager.java -+++ b/src/main/java/net/minecraft/world/entity/monster/ZombieVillager.java -@@ -205,6 +205,12 @@ public class ZombieVillager extends Zombie implements VillagerDataHolder { - } - - public void startConverting(@Nullable UUID uuid, int delay) { -+ // Paper start - missing entity behaviour api - converting without entity event -+ this.startConverting(uuid, delay, true); -+ } -+ -+ public void startConverting(@Nullable UUID uuid, int delay, boolean broadcastEntityEvent) { -+ // Paper end - missing entity behaviour api - converting without entity event - this.conversionStarter = uuid; - this.villagerConversionTime = delay; - this.getEntityData().set(ZombieVillager.DATA_CONVERTING_ID, true); -@@ -212,7 +218,7 @@ public class ZombieVillager extends Zombie implements VillagerDataHolder { - this.removeEffect(MobEffects.WEAKNESS, org.bukkit.event.entity.EntityPotionEffectEvent.Cause.CONVERSION); - this.addEffect(new MobEffectInstance(MobEffects.DAMAGE_BOOST, delay, Math.min(this.level().getDifficulty().getId() - 1, 0)), org.bukkit.event.entity.EntityPotionEffectEvent.Cause.CONVERSION); - // CraftBukkit end -- this.level().broadcastEntityEvent(this, (byte) 16); -+ if (broadcastEntityEvent) this.level().broadcastEntityEvent(this, (byte) 16); // Paper - missing entity behaviour api - converting without entity event - } - - @Override -diff --git a/src/main/java/net/minecraft/world/entity/projectile/ThrownTrident.java b/src/main/java/net/minecraft/world/entity/projectile/ThrownTrident.java -index 46b67c38dccf911973e6a7643f06972019073eb2..e45c3a9805d9fac1fabe6d891c817743acd9969e 100644 ---- a/src/main/java/net/minecraft/world/entity/projectile/ThrownTrident.java -+++ b/src/main/java/net/minecraft/world/entity/projectile/ThrownTrident.java -@@ -106,6 +106,20 @@ public class ThrownTrident extends AbstractArrow { - return (Boolean) this.entityData.get(ThrownTrident.ID_FOIL); - } - -+ // Paper start -+ public void setFoil(boolean foil) { -+ this.entityData.set(ThrownTrident.ID_FOIL, foil); -+ } -+ -+ public int getLoyalty() { -+ return this.entityData.get(ThrownTrident.ID_LOYALTY); -+ } -+ -+ public void setLoyalty(byte loyalty) { -+ this.entityData.set(ThrownTrident.ID_LOYALTY, loyalty); -+ } -+ // Paper end -+ - @Nullable - @Override - protected EntityHitResult findHitEntity(Vec3 currentPosition, Vec3 nextPosition) { -diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftAbstractHorse.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftAbstractHorse.java -index 3952e52b94c1cc97e1d2d3885f59d7690efb74ad..9bcc0931510607b8fbd01233e2b3c346369b214d 100644 ---- a/src/main/java/org/bukkit/craftbukkit/entity/CraftAbstractHorse.java -+++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftAbstractHorse.java -@@ -114,4 +114,36 @@ public abstract class CraftAbstractHorse extends CraftAnimals implements Abstrac - public AbstractHorseInventory getInventory() { - return new CraftSaddledInventory(getHandle().inventory); - } -+ -+ // Paper start - Horse API -+ @Override -+ public boolean isEatingGrass() { -+ return this.getHandle().isEating(); -+ } -+ -+ @Override -+ public void setEatingGrass(boolean eating) { -+ this.getHandle().setEating(eating); -+ } -+ -+ @Override -+ public boolean isRearing() { -+ return this.getHandle().isStanding(); -+ } -+ -+ @Override -+ public void setRearing(boolean rearing) { -+ this.getHandle().setForceStanding(rearing); -+ } -+ -+ @Override -+ public boolean isEating() { -+ return this.getHandle().isMouthOpen(); -+ } -+ -+ @Override -+ public void setEating(boolean eating) { -+ this.getHandle().setMouthOpen(eating); -+ } -+ // Paper end - Horse API - } -diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftAreaEffectCloud.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftAreaEffectCloud.java -index 2f99fc44de05bfbb5c9a8c859312cb7d32310d62..81f5e1d866128af8fb2acc13aca715580fdf9886 100644 ---- a/src/main/java/org/bukkit/craftbukkit/entity/CraftAreaEffectCloud.java -+++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftAreaEffectCloud.java -@@ -229,4 +229,17 @@ public class CraftAreaEffectCloud extends CraftEntity implements AreaEffectCloud - this.getHandle().setOwner(null); - } - } -+ -+ // Paper start - owner API -+ @Override -+ public java.util.UUID getOwnerUniqueId() { -+ return this.getHandle().ownerUUID; -+ } -+ -+ @Override -+ public void setOwnerUniqueId(final java.util.UUID ownerUuid) { -+ this.getHandle().setOwner(null); -+ this.getHandle().ownerUUID = ownerUuid; -+ } -+ // Paper end - } -diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftBat.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftBat.java -index b0a3531476f5a05ae846b68d825eddc35ebddea9..1bb72f28085f3885bec068b586ec222111044884 100644 ---- a/src/main/java/org/bukkit/craftbukkit/entity/CraftBat.java -+++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftBat.java -@@ -27,4 +27,25 @@ public class CraftBat extends CraftAmbient implements Bat { - public void setAwake(boolean state) { - this.getHandle().setResting(!state); - } -+ // Paper start -+ @Override -+ public org.bukkit.Location getTargetLocation() { -+ net.minecraft.core.BlockPos pos = this.getHandle().targetPosition; -+ if (pos == null) { -+ return null; -+ } -+ -+ return io.papermc.paper.util.MCUtil.toLocation(this.getHandle().level(), pos); -+ } -+ -+ @Override -+ public void setTargetLocation(org.bukkit.Location location) { -+ net.minecraft.core.BlockPos pos = null; -+ if (location != null) { -+ pos = io.papermc.paper.util.MCUtil.toBlockPosition(location); -+ } -+ -+ this.getHandle().targetPosition = pos; -+ } -+ // Paper end - } -diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftBee.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftBee.java -index cfff1be6a4a4936a2dadb2590abc3d33c123d048..3dac93b0ab5d5acf5b33dc4b0efed60319eb657b 100644 ---- a/src/main/java/org/bukkit/craftbukkit/entity/CraftBee.java -+++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftBee.java -@@ -86,4 +86,42 @@ public class CraftBee extends CraftAnimals implements Bee { - public void setCannotEnterHiveTicks(int ticks) { - this.getHandle().setStayOutOfHiveCountdown(ticks); - } -+ // Paper start -+ @Override -+ public void setRollingOverride(net.kyori.adventure.util.TriState rolling) { -+ this.getHandle().rollingOverride = rolling; -+ -+ this.getHandle().setRolling(this.getHandle().isRolling()); // Refresh rolling state -+ } -+ -+ @Override -+ public boolean isRolling() { -+ return this.getRollingOverride().toBooleanOrElse(this.getHandle().isRolling()); -+ } -+ -+ @Override -+ public net.kyori.adventure.util.TriState getRollingOverride() { -+ return this.getHandle().rollingOverride; -+ } -+ -+ @Override -+ public void setCropsGrownSincePollination(int crops) { -+ this.getHandle().numCropsGrownSincePollination = crops; -+ } -+ -+ @Override -+ public int getCropsGrownSincePollination() { -+ return this.getHandle().numCropsGrownSincePollination; -+ } -+ -+ @Override -+ public void setTicksSincePollination(int ticks) { -+ this.getHandle().ticksWithoutNectarSinceExitingHive = ticks; -+ } -+ -+ @Override -+ public int getTicksSincePollination() { -+ return this.getHandle().ticksWithoutNectarSinceExitingHive; -+ } -+ // Paper end - } -diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftCat.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftCat.java -index 57664124968e6268ad6699c6bd932981cc2fe6ba..88e876da7df64b68a5b71fd1deab75b59c5a64e3 100644 ---- a/src/main/java/org/bukkit/craftbukkit/entity/CraftCat.java -+++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftCat.java -@@ -139,4 +139,26 @@ public class CraftCat extends CraftTameableAnimal implements Cat { - return this.getKey().hashCode(); - } - } -+ -+ // Paper start - More cat api -+ @Override -+ public void setLyingDown(boolean lyingDown) { -+ this.getHandle().setLying(lyingDown); -+ } -+ -+ @Override -+ public boolean isLyingDown() { -+ return this.getHandle().isLying(); -+ } -+ -+ @Override -+ public void setHeadUp(boolean headUp) { -+ this.getHandle().setRelaxStateOne(headUp); -+ } -+ -+ @Override -+ public boolean isHeadUp() { -+ return this.getHandle().isRelaxStateOne(); -+ } -+ // Paper end - More cat api - } -diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftChicken.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftChicken.java -index 64b75682a936e071353707f7615d6ff512fd617d..96f6e2fd9c6b20d34122abfe5c7fba732502d5a0 100644 ---- a/src/main/java/org/bukkit/craftbukkit/entity/CraftChicken.java -+++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftChicken.java -@@ -18,4 +18,26 @@ public class CraftChicken extends CraftAnimals implements Chicken { - public String toString() { - return "CraftChicken"; - } -+ -+ // Paper start -+ @Override -+ public boolean isChickenJockey() { -+ return this.getHandle().isChickenJockey(); -+ } -+ -+ @Override -+ public void setIsChickenJockey(boolean isChickenJockey) { -+ this.getHandle().setChickenJockey(isChickenJockey); -+ } -+ -+ @Override -+ public int getEggLayTime() { -+ return this.getHandle().eggTime; -+ } -+ -+ @Override -+ public void setEggLayTime(int eggLayTime) { -+ this.getHandle().eggTime = eggLayTime; -+ } -+ // Paper end - } -diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftCod.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftCod.java -index fa0bf7db880063427ba12df1df1c72240fff93e9..63e6b07e3b159c74d9ef17be20b5ab43d07f0f5f 100644 ---- a/src/main/java/org/bukkit/craftbukkit/entity/CraftCod.java -+++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftCod.java -@@ -3,7 +3,7 @@ package org.bukkit.craftbukkit.entity; - import org.bukkit.craftbukkit.CraftServer; - import org.bukkit.entity.Cod; - --public class CraftCod extends CraftFish implements Cod { -+public class CraftCod extends io.papermc.paper.entity.PaperSchoolableFish implements Cod { // Paper - School Fish API - - public CraftCod(CraftServer server, net.minecraft.world.entity.animal.Cod entity) { - super(server, entity); -diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftDolphin.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftDolphin.java -index af432f9a1d255a56c31c3b97aeb4457d17f37e3e..f93f8f6509b12eb9b1e07c829278bb0822dd7988 100644 ---- a/src/main/java/org/bukkit/craftbukkit/entity/CraftDolphin.java -+++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftDolphin.java -@@ -18,4 +18,36 @@ public class CraftDolphin extends CraftWaterMob implements Dolphin { - public String toString() { - return "CraftDolphin"; - } -+ -+ // Paper start - Missing Dolphin API -+ @Override -+ public int getMoistness() { -+ return this.getHandle().getMoistnessLevel(); -+ } -+ -+ @Override -+ public void setMoistness(int moistness) { -+ this.getHandle().setMoisntessLevel(moistness); -+ } -+ -+ @Override -+ public void setHasFish(boolean hasFish) { -+ this.getHandle().setGotFish(hasFish); -+ } -+ -+ @Override -+ public boolean hasFish() { -+ return this.getHandle().gotFish(); -+ } -+ -+ @Override -+ public org.bukkit.Location getTreasureLocation() { -+ return io.papermc.paper.util.MCUtil.toLocation(this.getHandle().level(), this.getHandle().getTreasurePos()); -+ } -+ -+ @Override -+ public void setTreasureLocation(org.bukkit.Location location) { -+ this.getHandle().setTreasurePos(io.papermc.paper.util.MCUtil.toBlockPosition(location)); -+ } -+ // Paper end - Missing Dolphin API - } -diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftEnderDragonPart.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftEnderDragonPart.java -index cc4194ac9d7501b5d15655674dade14d59cb6733..33ae03b78b01c005a291a343b42507fb539e81a6 100644 ---- a/src/main/java/org/bukkit/craftbukkit/entity/CraftEnderDragonPart.java -+++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftEnderDragonPart.java -@@ -51,6 +51,13 @@ public class CraftEnderDragonPart extends CraftComplexPart implements EnderDrago - this.getParent().setHealth(health); - } - -+ // Paper start - entity heal API -+ @Override -+ public void heal(final double amount, final org.bukkit.event.entity.EntityRegainHealthEvent.RegainReason reason) { -+ this.getParent().heal(amount, reason); -+ } -+ // Paper end - entity heal API -+ - @Override - public double getAbsorptionAmount() { - return this.getParent().getAbsorptionAmount(); -diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftEnderman.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftEnderman.java -index 21dc209e6f98b6306833b41e2763e746047d5a94..983b9d6ddb58eff297e96e5c8b28ec427efa267d 100644 ---- a/src/main/java/org/bukkit/craftbukkit/entity/CraftEnderman.java -+++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftEnderman.java -@@ -40,6 +40,28 @@ public class CraftEnderman extends CraftMonster implements Enderman { - this.getHandle().setCarriedBlock(blockData == null ? null : ((CraftBlockData) blockData).getState()); - } - -+ // Paper start -+ @Override -+ public boolean isScreaming() { -+ return this.getHandle().isCreepy(); -+ } -+ -+ @Override -+ public void setScreaming(boolean screaming) { -+ this.getHandle().setCreepy(screaming); -+ } -+ -+ @Override -+ public boolean hasBeenStaredAt() { -+ return this.getHandle().hasBeenStaredAt(); -+ } -+ -+ @Override -+ public void setHasBeenStaredAt(boolean hasBeenStaredAt) { -+ this.getHandle().setHasBeenStaredAt(hasBeenStaredAt); -+ } -+ // Paper end -+ - @Override - public EnderMan getHandle() { - return (EnderMan) this.entity; -diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftEndermite.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftEndermite.java -index fc0f0e841dc974d080e1abb9bbafb5165801131f..d657fd2c507a5b215aeab0a5f3e9c2ee892a27c8 100644 ---- a/src/main/java/org/bukkit/craftbukkit/entity/CraftEndermite.java -+++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftEndermite.java -@@ -28,4 +28,15 @@ public class CraftEndermite extends CraftMonster implements Endermite { - public void setPlayerSpawned(boolean playerSpawned) { - // Nop - } -+ // Paper start -+ @Override -+ public void setLifetimeTicks(int ticks) { -+ this.getHandle().life = ticks; -+ } -+ -+ @Override -+ public int getLifetimeTicks() { -+ return this.getHandle().life; -+ } -+ // Paper end - } -diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java -index 96201ea45f8b53dcadb1a8732b1d49b1e8d1d7df..7c04eb9e7eb5ff728465b46e3739eb2598ef1204 100644 ---- a/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java -+++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java -@@ -1086,4 +1086,27 @@ public abstract class CraftEntity implements org.bukkit.entity.Entity { - return set; - } - // Paper end - tracked players API -+ -+ // Paper start - missing entity api -+ @Override -+ public boolean isInvisible() { // Paper - moved up from LivingEntity -+ return this.getHandle().isInvisible(); -+ } -+ -+ @Override -+ public void setInvisible(boolean invisible) { // Paper - moved up from LivingEntity -+ this.getHandle().persistentInvisibility = invisible; -+ this.getHandle().setSharedFlag(Entity.FLAG_INVISIBLE, invisible); -+ } -+ -+ @Override -+ public void setNoPhysics(boolean noPhysics) { -+ this.getHandle().noPhysics = noPhysics; -+ } -+ -+ @Override -+ public boolean hasNoPhysics() { -+ return this.getHandle().noPhysics; -+ } -+ // Paper end - missing entity api - } -diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftFireball.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftFireball.java -index 142f3e3257afebb2e831fd0970678123d99a1717..1b084d63bdbb24dad45d28eed1693eb6e26e24dc 100644 ---- a/src/main/java/org/bukkit/craftbukkit/entity/CraftFireball.java -+++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftFireball.java -@@ -84,6 +84,18 @@ public class CraftFireball extends AbstractProjectile implements Fireball { - return new Vector(delta.x, delta.y, delta.z); - } - -+ // Paper start - Expose power on fireball projectiles -+ @Override -+ public void setPower(final Vector power) { -+ this.setAcceleration(power); -+ } -+ -+ @Override -+ public Vector getPower() { -+ return this.getAcceleration(); -+ } -+ // Paper end - Expose power on fireball projectiles -+ - @Override - public AbstractHurtingProjectile getHandle() { - return (AbstractHurtingProjectile) this.entity; -diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftFox.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftFox.java -index 17164811bbcf983bef62c47bc99330074762267b..c455deb4fd2a7684bcc01a8212c362a2375c190b 100644 ---- a/src/main/java/org/bukkit/craftbukkit/entity/CraftFox.java -+++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftFox.java -@@ -113,4 +113,41 @@ public class CraftFox extends CraftAnimals implements Fox { - public boolean isFaceplanted() { - return this.getHandle().isFaceplanted(); - } -+ -+ // Paper start - Add more fox behavior API -+ @Override -+ public void setInterested(boolean interested) { -+ this.getHandle().setIsInterested(interested); -+ } -+ -+ @Override -+ public boolean isInterested() { -+ return this.getHandle().isInterested(); -+ } -+ -+ @Override -+ public void setLeaping(boolean leaping) { -+ this.getHandle().setIsPouncing(leaping); -+ } -+ -+ @Override -+ public boolean isLeaping() { -+ return this.getHandle().isPouncing(); -+ } -+ -+ @Override -+ public void setDefending(boolean defending) { -+ this.getHandle().setDefending(defending); -+ } -+ -+ @Override -+ public boolean isDefending() { -+ return this.getHandle().isDefending(); -+ } -+ -+ @Override -+ public void setFaceplanted(boolean faceplanted) { -+ this.getHandle().setFaceplanted(faceplanted); -+ } -+ // Paper end - Add more fox behavior API - } -diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftGhast.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftGhast.java -index 2cec61a1bb050c1ef81c5fc3d0afafe9ff29d459..97fa4e1e70203194bd939618b2fad92665af6d59 100644 ---- a/src/main/java/org/bukkit/craftbukkit/entity/CraftGhast.java -+++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftGhast.java -@@ -28,4 +28,17 @@ public class CraftGhast extends CraftFlying implements Ghast, CraftEnemy { - public void setCharging(boolean flag) { - this.getHandle().setCharging(flag); - } -+ -+ // Paper start -+ @Override -+ public int getExplosionPower() { -+ return this.getHandle().getExplosionPower(); -+ } -+ -+ @Override -+ public void setExplosionPower(int explosionPower) { -+ com.google.common.base.Preconditions.checkArgument(explosionPower >= 0 && explosionPower <= 127, "The explosion power has to be between 0 and 127"); -+ this.getHandle().setExplosionPower(explosionPower); -+ } -+ // Paper end - } -diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftLivingEntity.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftLivingEntity.java -index 637bac756a8f41ed4abd8e3828886c561513e384..19fb8acf614da707f49d922e520e4be93237b2cc 100644 ---- a/src/main/java/org/bukkit/craftbukkit/entity/CraftLivingEntity.java -+++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftLivingEntity.java -@@ -124,6 +124,13 @@ public class CraftLivingEntity extends CraftEntity implements LivingEntity { - } - } - -+ // Paper start - entity heal API -+ @Override -+ public void heal(final double amount, final org.bukkit.event.entity.EntityRegainHealthEvent.RegainReason reason) { -+ this.getHandle().heal((float) amount, reason); -+ } -+ // Paper end - entity heal API -+ - @Override - public double getAbsorptionAmount() { - return this.getHandle().getAbsorptionAmount(); -@@ -920,14 +927,29 @@ public class CraftLivingEntity extends CraftEntity implements LivingEntity { - - @Override - public boolean isInvisible() { -- return this.getHandle().isInvisible(); -+ return super.isInvisible(); // Paper - move invisibility up to Entity - diff on change - } - - @Override - public void setInvisible(boolean invisible) { -- this.getHandle().persistentInvisibility = invisible; -- this.getHandle().setSharedFlag(5, invisible); -+ super.setInvisible(invisible); // Paper - move invisibility up to Entity - } -+ // Paper start -+ @Override -+ public float getSidewaysMovement() { -+ return this.getHandle().xxa; -+ } -+ -+ @Override -+ public float getForwardsMovement() { -+ return this.getHandle().zza; -+ } -+ -+ @Override -+ public float getUpwardsMovement() { -+ return this.getHandle().yya; -+ } -+ // Paper end - - // Paper start - @Override -diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftLlama.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftLlama.java -index bf297388c75521266c93580a9caafe6bad70ab45..351f42842b780d053cd2e5bad9ae299449141b10 100644 ---- a/src/main/java/org/bukkit/craftbukkit/entity/CraftLlama.java -+++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftLlama.java -@@ -58,4 +58,36 @@ public class CraftLlama extends CraftChestedHorse implements Llama, com.destroys - public String toString() { - return "CraftLlama"; - } -+ -+ // Paper start -+ @Override -+ public boolean inCaravan() { -+ return this.getHandle().inCaravan(); -+ } -+ -+ @Override -+ public void joinCaravan(@org.jetbrains.annotations.NotNull Llama llama) { -+ this.getHandle().joinCaravan(((CraftLlama) llama).getHandle()); -+ } -+ -+ @Override -+ public void leaveCaravan() { -+ this.getHandle().leaveCaravan(); -+ } -+ -+ @Override -+ public boolean hasCaravanTail() { -+ return this.getHandle().hasCaravanTail(); -+ } -+ -+ @Override -+ public Llama getCaravanHead() { -+ return this.getHandle().getCaravanHead() == null ? null : (Llama) this.getHandle().getCaravanHead().getBukkitEntity(); -+ } -+ -+ @Override -+ public Llama getCaravanTail() { -+ return this.getHandle().caravanTail == null ? null : (Llama) this.getHandle().caravanTail.getBukkitEntity(); -+ } -+ // Paper end - } -diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftMinecartHopper.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftMinecartHopper.java -index 17f5684cba9d3ed22d9925d1951520cc4751dfe2..3a3563a1bdbc0d84d973b3a04b50b78b4bc3d379 100644 ---- a/src/main/java/org/bukkit/craftbukkit/entity/CraftMinecartHopper.java -+++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftMinecartHopper.java -@@ -33,4 +33,20 @@ public final class CraftMinecartHopper extends CraftMinecartContainer implements - public void setEnabled(boolean enabled) { - ((MinecartHopper) this.getHandle()).setEnabled(enabled); - } -+ // Paper start -+ @Override -+ public net.minecraft.world.entity.vehicle.MinecartHopper getHandle() { -+ return (net.minecraft.world.entity.vehicle.MinecartHopper) super.getHandle(); -+ } -+ -+ @Override -+ public int getPickupCooldown() { -+ throw new UnsupportedOperationException("Hopper minecarts don't have cooldowns"); -+ } -+ -+ @Override -+ public void setPickupCooldown(int cooldown) { -+ throw new UnsupportedOperationException("Hopper minecarts don't have cooldowns"); -+ } -+ // Paper end - } -diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftMob.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftMob.java -index 60d09655c5b8b9ff289291ee6badfb5aadf8533e..fb29afb6517b009b81285adc9e6dca2eb7f74aee 100644 ---- a/src/main/java/org/bukkit/craftbukkit/entity/CraftMob.java -+++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftMob.java -@@ -145,4 +145,16 @@ public abstract class CraftMob extends CraftLivingEntity implements Mob { - return getHandle().getMaxHeadXRot(); - } - // Paper end -+ -+ // Paper start -+ @Override -+ public boolean isAggressive() { -+ return this.getHandle().isAggressive(); -+ } -+ -+ @Override -+ public void setAggressive(boolean aggressive) { -+ this.getHandle().setAggressive(aggressive); -+ } -+ // Paper end - } -diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftPanda.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftPanda.java -index 5467e4a74b70ff57b49d9e6bc686c493178f8511..01d104d91de9e1319d27e39d3f474318c7809486 100644 ---- a/src/main/java/org/bukkit/craftbukkit/entity/CraftPanda.java -+++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftPanda.java -@@ -41,6 +41,38 @@ public class CraftPanda extends CraftAnimals implements Panda { - this.getHandle().setHiddenGene(CraftPanda.toNms(gene)); - } - -+ // Paper start - Panda API -+ @Override -+ public void setSneezeTicks(int ticks) { -+ this.getHandle().setSneezeCounter(ticks); -+ } -+ -+ @Override -+ public int getSneezeTicks() { -+ return this.getHandle().getSneezeCounter(); -+ } -+ -+ @Override -+ public void setEatingTicks(int ticks) { -+ this.getHandle().setEatCounter(ticks); -+ } -+ -+ @Override -+ public int getEatingTicks() { -+ return this.getHandle().getEatCounter(); -+ } -+ -+ @Override -+ public void setUnhappyTicks(int ticks) { -+ this.getHandle().setUnhappyCounter(ticks); -+ } -+ -+ @Override -+ public Gene getCombinedGene() { -+ return CraftPanda.fromNms(this.getHandle().getVariant()); -+ } -+ // Paper end - Panda API -+ - @Override - public boolean isRolling() { - return this.getHandle().isRolling(); -diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftPhantom.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftPhantom.java -index 9304e201db1ec96d0916aa8ea781f3e4bc7991e6..83e77c6d287d8e239d2f55f3e9f19ef74946be7c 100644 ---- a/src/main/java/org/bukkit/craftbukkit/entity/CraftPhantom.java -+++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftPhantom.java -@@ -44,5 +44,17 @@ public class CraftPhantom extends CraftFlying implements Phantom, CraftEnemy { - public void setShouldBurnInDay(boolean shouldBurnInDay) { - getHandle().setShouldBurnInDay(shouldBurnInDay); - } -+ -+ @Override -+ public org.bukkit.Location getAnchorLocation() { -+ net.minecraft.core.BlockPos pos = this.getHandle().anchorPoint; -+ return io.papermc.paper.util.MCUtil.toLocation(this.getHandle().level(), pos); -+ } -+ -+ @Override -+ public void setAnchorLocation(org.bukkit.Location location) { -+ com.google.common.base.Preconditions.checkArgument(location != null, "location cannot be null"); -+ this.getHandle().anchorPoint = io.papermc.paper.util.MCUtil.toBlockPosition(location); -+ } - // Paper end - } -diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftPiglin.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftPiglin.java -index f5ecb8c1dc92e5a4b123effd2859123b17a586d3..5124a383b60b2c8de89fa992547d0c61db760c21 100644 ---- a/src/main/java/org/bukkit/craftbukkit/entity/CraftPiglin.java -+++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftPiglin.java -@@ -84,4 +84,37 @@ public class CraftPiglin extends CraftPiglinAbstract implements Piglin, com.dest - public String toString() { - return "CraftPiglin"; - } -+ // Paper start -+ @Override -+ public void setChargingCrossbow(boolean chargingCrossbow) { -+ this.getHandle().setChargingCrossbow(chargingCrossbow); -+ } -+ -+ @Override -+ public boolean isChargingCrossbow() { -+ return this.getHandle().isChargingCrossbow(); -+ } -+ -+ @Override -+ public void setDancing(boolean dancing) { -+ if (dancing) { -+ this.getHandle().getBrain().setMemory(net.minecraft.world.entity.ai.memory.MemoryModuleType.DANCING, true); -+ this.getHandle().getBrain().setMemory(net.minecraft.world.entity.ai.memory.MemoryModuleType.CELEBRATE_LOCATION, this.getHandle().getOnPos()); -+ } else { -+ this.getHandle().getBrain().eraseMemory(net.minecraft.world.entity.ai.memory.MemoryModuleType.DANCING); -+ this.getHandle().getBrain().eraseMemory(net.minecraft.world.entity.ai.memory.MemoryModuleType.CELEBRATE_LOCATION); -+ } -+ } -+ -+ @Override -+ public void setDancing(long duration) { -+ this.getHandle().getBrain().setMemoryWithExpiry(net.minecraft.world.entity.ai.memory.MemoryModuleType.DANCING, true, duration); -+ this.getHandle().getBrain().setMemoryWithExpiry(net.minecraft.world.entity.ai.memory.MemoryModuleType.CELEBRATE_LOCATION, this.getHandle().getOnPos(), duration); -+ } -+ -+ @Override -+ public boolean isDancing() { -+ return this.getHandle().isDancing(); -+ } -+ // Paper end - } -diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftPolarBear.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftPolarBear.java -index c7aec6f28e5d3546235b30f6b1112440a76163c5..fe075cfdf3097d6cb768e71b8cc360abb8eaf367 100644 ---- a/src/main/java/org/bukkit/craftbukkit/entity/CraftPolarBear.java -+++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftPolarBear.java -@@ -17,4 +17,16 @@ public class CraftPolarBear extends CraftAnimals implements PolarBear { - public String toString() { - return "CraftPolarBear"; - } -+ -+ // Paper start -+ @Override -+ public boolean isStanding() { -+ return this.getHandle().isStanding(); -+ } -+ -+ @Override -+ public void setStanding(boolean standing) { -+ this.getHandle().setStanding(standing); -+ } -+ // Paper end - } -diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftRabbit.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftRabbit.java -index 6b48b117a9cba12aae055c0ea981dfb5bc03a86e..519ef701a7d6534f7cb516f6296b95ee521f661d 100644 ---- a/src/main/java/org/bukkit/craftbukkit/entity/CraftRabbit.java -+++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftRabbit.java -@@ -29,4 +29,15 @@ public class CraftRabbit extends CraftAnimals implements Rabbit { - public void setRabbitType(Type type) { - this.getHandle().setVariant(net.minecraft.world.entity.animal.Rabbit.Variant.values()[type.ordinal()]); - } -+ // Paper start -+ @Override -+ public void setMoreCarrotTicks(int ticks) { -+ this.getHandle().moreCarrotTicks = ticks; -+ } -+ -+ @Override -+ public int getMoreCarrotTicks() { -+ return this.getHandle().moreCarrotTicks; -+ } -+ // Paper end - } -diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftRavager.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftRavager.java -index cae59f77c704a5b9515dc4917ed5fdc89631ecfb..09796ce15658e3f7c223a265a547a51ee729ed40 100644 ---- a/src/main/java/org/bukkit/craftbukkit/entity/CraftRavager.java -+++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftRavager.java -@@ -18,4 +18,35 @@ public class CraftRavager extends CraftRaider implements Ravager { - public String toString() { - return "CraftRavager"; - } -+ // Paper start - Missing Entity Behavior -+ @Override -+ public int getAttackTicks() { -+ return this.getHandle().getAttackTick(); -+ } -+ -+ @Override -+ public void setAttackTicks(int ticks) { -+ this.getHandle().attackTick = ticks; -+ } -+ -+ @Override -+ public int getStunnedTicks() { -+ return this.getHandle().getStunnedTick(); -+ } -+ -+ @Override -+ public void setStunnedTicks(int ticks) { -+ this.getHandle().stunnedTick = ticks; -+ } -+ -+ @Override -+ public int getRoarTicks() { -+ return this.getHandle().getRoarTick(); -+ } -+ -+ @Override -+ public void setRoarTicks(int ticks) { -+ this.getHandle().roarTick = ticks; -+ } -+ // Paper end - } -diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftSalmon.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftSalmon.java -index b8140aa25a25870259b5644091c6643da1e14b54..d4d8ce60098c74508e2de9541bf6534988779764 100644 ---- a/src/main/java/org/bukkit/craftbukkit/entity/CraftSalmon.java -+++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftSalmon.java -@@ -3,7 +3,7 @@ package org.bukkit.craftbukkit.entity; - import org.bukkit.craftbukkit.CraftServer; - import org.bukkit.entity.Salmon; - --public class CraftSalmon extends CraftFish implements Salmon { -+public class CraftSalmon extends io.papermc.paper.entity.PaperSchoolableFish implements Salmon { // Paper - Schooling Fish API - - public CraftSalmon(CraftServer server, net.minecraft.world.entity.animal.Salmon entity) { - super(server, entity); -diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftTNTPrimed.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftTNTPrimed.java -index 3f32c683ddc6999b89f2e4051eb6ae784b296b8f..dac3d34677688ac560bc1be2087a08479ef71b87 100644 ---- a/src/main/java/org/bukkit/craftbukkit/entity/CraftTNTPrimed.java -+++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftTNTPrimed.java -@@ -67,4 +67,17 @@ public class CraftTNTPrimed extends CraftEntity implements TNTPrimed { - this.getHandle().owner = null; - } - } -+ -+ // Paper start -+ @Override -+ public void setBlockData(org.bukkit.block.data.BlockData data) { -+ com.google.common.base.Preconditions.checkArgument(data != null, "The visual block data of this tnt cannot be null. To reset it just set to the TNT default block data"); -+ this.getHandle().setBlockState(((org.bukkit.craftbukkit.block.data.CraftBlockData) data).getState()); -+ } -+ -+ @Override -+ public org.bukkit.block.data.BlockData getBlockData() { -+ return org.bukkit.craftbukkit.block.data.CraftBlockData.fromData(this.getHandle().getBlockState()); -+ } -+ // Paper end - } -diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftTadpole.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftTadpole.java -index 451a9bfd9b9b6945e224f1bb05c7951ed934b4e3..d7c6a0bbc5671ea8f2488230c94df5146a1e98b9 100644 ---- a/src/main/java/org/bukkit/craftbukkit/entity/CraftTadpole.java -+++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftTadpole.java -@@ -28,4 +28,15 @@ public class CraftTadpole extends CraftFish implements org.bukkit.entity.Tadpole - public void setAge(int age) { - this.getHandle().age = age; - } -+ // Paper start -+ @Override -+ public void setAgeLock(boolean lock) { -+ this.getHandle().ageLocked = lock; -+ } -+ -+ @Override -+ public boolean getAgeLock() { -+ return this.getHandle().ageLocked; -+ } -+ // Paper end - } -diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftTrident.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftTrident.java -index 33d6e8121755ad6cddacb4fc69e795f9831c27bd..e374b9f40eddca13b30855d25a2030f8df98138f 100644 ---- a/src/main/java/org/bukkit/craftbukkit/entity/CraftTrident.java -+++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftTrident.java -@@ -31,4 +31,27 @@ public class CraftTrident extends CraftAbstractArrow implements Trident { - public String toString() { - return "CraftTrident"; - } -+ -+ // Paper start -+ @Override -+ public boolean hasGlint() { -+ return this.getHandle().isFoil(); -+ } -+ -+ @Override -+ public void setGlint(boolean glint) { -+ this.getHandle().setFoil(glint); -+ } -+ -+ @Override -+ public int getLoyaltyLevel() { -+ return this.getHandle().getLoyalty(); -+ } -+ -+ @Override -+ public void setLoyaltyLevel(int loyaltyLevel) { -+ com.google.common.base.Preconditions.checkArgument(loyaltyLevel >= 0 && loyaltyLevel <= 127, "The loyalty level has to be between 0 and 127"); -+ this.getHandle().setLoyalty((byte) loyaltyLevel); -+ } -+ // Paper end - } -diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftTropicalFish.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftTropicalFish.java -index e3bde6d1c0e03407af1382a61748470063bb2e18..9e53c30801c700719c78c0fd521fd615c94e02c8 100644 ---- a/src/main/java/org/bukkit/craftbukkit/entity/CraftTropicalFish.java -+++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftTropicalFish.java -@@ -7,7 +7,7 @@ import org.bukkit.craftbukkit.CraftServer; - import org.bukkit.entity.TropicalFish; - import org.bukkit.entity.TropicalFish.Pattern; - --public class CraftTropicalFish extends CraftFish implements TropicalFish { -+public class CraftTropicalFish extends io.papermc.paper.entity.PaperSchoolableFish implements TropicalFish { // Paper - Schooling Fish API - - public CraftTropicalFish(CraftServer server, net.minecraft.world.entity.animal.TropicalFish entity) { - super(server, entity); -diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftVex.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftVex.java -index 1cfbe9c476f4a254edf3edf4b70696bbaba78558..e9ec3455eabc473e104b5342a615a38c1ac25a4f 100644 ---- a/src/main/java/org/bukkit/craftbukkit/entity/CraftVex.java -+++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftVex.java -@@ -29,6 +29,26 @@ public class CraftVex extends CraftMonster implements Vex { - public void setSummoner(org.bukkit.entity.Mob summoner) { - getHandle().setOwner(summoner == null ? null : ((CraftMob) summoner).getHandle()); - } -+ -+ @Override -+ public boolean hasLimitedLifetime() { -+ return this.getHandle().hasLimitedLife; -+ } -+ -+ @Override -+ public void setLimitedLifetime(boolean hasLimitedLifetime) { -+ this.getHandle().hasLimitedLife = hasLimitedLifetime; -+ } -+ -+ @Override -+ public int getLimitedLifetimeTicks() { -+ return this.getHandle().limitedLifeTicks; -+ } -+ -+ @Override -+ public void setLimitedLifetimeTicks(int ticks) { -+ this.getHandle().limitedLifeTicks = ticks; -+ } - // Paper end - - @Override -diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftVillagerZombie.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftVillagerZombie.java -index e2a0c11867abee6add8775259c54f2052de7b1ad..3aa23d9f22d5cd22231293fd7d1ca4cb79eb7cb3 100644 ---- a/src/main/java/org/bukkit/craftbukkit/entity/CraftVillagerZombie.java -+++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftVillagerZombie.java -@@ -60,13 +60,20 @@ public class CraftVillagerZombie extends CraftZombie implements ZombieVillager { - - @Override - public void setConversionTime(int time) { -+ // Paper start - missing entity behaviour api - converting without entity event -+ this.setConversionTime(time, true); -+ } -+ -+ @Override -+ public void setConversionTime(int time, boolean broadcastEntityEvent) { -+ // Paper end - missing entity behaviour api - converting without entity event - if (time < 0) { - this.getHandle().villagerConversionTime = -1; - this.getHandle().getEntityData().set(net.minecraft.world.entity.monster.ZombieVillager.DATA_CONVERTING_ID, false); - this.getHandle().conversionStarter = null; - this.getHandle().removeEffect(MobEffects.DAMAGE_BOOST, org.bukkit.event.entity.EntityPotionEffectEvent.Cause.CONVERSION); - } else { -- this.getHandle().startConverting(null, time); -+ this.getHandle().startConverting(null, time, broadcastEntityEvent); // Paper - missing entity behaviour api - converting without entity event - } - } - -diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftWanderingTrader.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftWanderingTrader.java -index 0e597394a3dd08f022614fc9777302fea581eb55..3cceefa0d6278924a19641a49bdf16bcdacb2233 100644 ---- a/src/main/java/org/bukkit/craftbukkit/entity/CraftWanderingTrader.java -+++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftWanderingTrader.java -@@ -49,5 +49,25 @@ public class CraftWanderingTrader extends CraftAbstractVillager implements Wande - public boolean canDrinkMilk() { - return getHandle().canDrinkMilk; - } -+ -+ @Override -+ public org.bukkit.Location getWanderingTowards() { -+ net.minecraft.core.BlockPos pos = this.getHandle().getWanderTarget(); -+ if (pos == null) { -+ return null; -+ } -+ -+ return io.papermc.paper.util.MCUtil.toLocation(this.getHandle().level(), pos); -+ } -+ -+ @Override -+ public void setWanderingTowards(org.bukkit.Location location) { -+ net.minecraft.core.BlockPos pos = null; -+ if (location != null) { -+ pos = io.papermc.paper.util.MCUtil.toBlockPosition(location); -+ } -+ -+ this.getHandle().setWanderTarget(pos); -+ } - // Paper end - } -diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftWarden.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftWarden.java -index 794e4fe0a3fbd967f665b2707865c15491370c76..c284eb96a1e330078076cbe61f0f6e2ff4ed89bd 100644 ---- a/src/main/java/org/bukkit/craftbukkit/entity/CraftWarden.java -+++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftWarden.java -@@ -37,6 +37,13 @@ public class CraftWarden extends CraftMonster implements org.bukkit.entity.Warde - return this.getHandle().getAngerManagement().getActiveAnger(((CraftEntity) entity).getHandle()); - } - -+ // Paper start -+ @Override -+ public int getHighestAnger() { -+ return this.getHandle().getAngerManagement().getActiveAnger(null); -+ } -+ // Paper end -+ - @Override - public void increaseAnger(Entity entity, int increase) { - Preconditions.checkArgument(entity != null, "Entity cannot be null"); -diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftWither.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftWither.java -index 1113533d281ed159bb735040fb1f913482debf3a..7881c6253c1d652c0c0d54a9a8accdf0a1ff0f3e 100644 ---- a/src/main/java/org/bukkit/craftbukkit/entity/CraftWither.java -+++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftWither.java -@@ -67,4 +67,36 @@ public class CraftWither extends CraftMonster implements Wither, com.destroystok - - this.getHandle().setInvulnerableTicks(ticks); - } -+ -+ // Paper start -+ @Override -+ public boolean isCharged() { -+ return getHandle().isPowered(); -+ } -+ -+ @Override -+ public int getInvulnerableTicks() { -+ return getHandle().getInvulnerableTicks(); -+ } -+ -+ @Override -+ public void setInvulnerableTicks(int ticks) { -+ getHandle().setInvulnerableTicks(ticks); -+ } -+ -+ @Override -+ public boolean canTravelThroughPortals() { -+ return getHandle().canUsePortal(false); -+ } -+ -+ @Override -+ public void setCanTravelThroughPortals(boolean value) { -+ getHandle().setCanTravelThroughPortals(value); -+ } -+ -+ @Override -+ public void enterInvulnerabilityPhase() { -+ this.getHandle().makeInvulnerable(); -+ } -+ // Paper end - } diff --git a/patches/server/0558-Fix-kick-event-leave-message-not-being-sent.patch b/patches/server/0558-Fix-kick-event-leave-message-not-being-sent.patch new file mode 100644 index 000000000000..8334a5dd25a5 --- /dev/null +++ b/patches/server/0558-Fix-kick-event-leave-message-not-being-sent.patch @@ -0,0 +1,127 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Jake Potrebic +Date: Wed, 7 Jul 2021 16:19:41 -0700 +Subject: [PATCH] Fix kick event leave message not being sent + + +diff --git a/src/main/java/net/minecraft/server/level/ServerPlayer.java b/src/main/java/net/minecraft/server/level/ServerPlayer.java +index 7bcb3ef3cb498114428782848e0370ac2497ccdc..99a634760cd04d5e3182254b1d0b817baf1cf87a 100644 +--- a/src/main/java/net/minecraft/server/level/ServerPlayer.java ++++ b/src/main/java/net/minecraft/server/level/ServerPlayer.java +@@ -318,7 +318,6 @@ public class ServerPlayer extends net.minecraft.world.entity.player.Player { + public boolean joining = true; + public boolean sentListPacket = false; + public boolean supressTrackerForLogin = false; // Paper - Fire PlayerJoinEvent when Player is actually ready +- public String kickLeaveMessage = null; // SPIGOT-3034: Forward leave message to PlayerQuitEvent + // CraftBukkit end + public boolean isRealPlayer; // Paper + public com.destroystokyo.paper.event.entity.PlayerNaturallySpawnCreaturesEvent playerNaturallySpawnedEvent; // Paper - PlayerNaturallySpawnCreaturesEvent +diff --git a/src/main/java/net/minecraft/server/network/ServerCommonPacketListenerImpl.java b/src/main/java/net/minecraft/server/network/ServerCommonPacketListenerImpl.java +index 59d20fd62e850a38380d877cef95ed69cb46ecbd..fc242acade3ff06c9213428cde103cf078216382 100644 +--- a/src/main/java/net/minecraft/server/network/ServerCommonPacketListenerImpl.java ++++ b/src/main/java/net/minecraft/server/network/ServerCommonPacketListenerImpl.java +@@ -116,6 +116,11 @@ public abstract class ServerCommonPacketListenerImpl implements ServerCommonPack + + @Override + public void onDisconnect(DisconnectionDetails info) { ++ // Paper start - Fix kick event leave message not being sent ++ this.onDisconnect(info, null); ++ } ++ public void onDisconnect(DisconnectionDetails info, @Nullable net.kyori.adventure.text.Component quitMessage) { ++ // Paper end - Fix kick event leave message not being sent + if (this.isSingleplayerOwner()) { + ServerCommonPacketListenerImpl.LOGGER.info("Stopping singleplayer server as player logged out"); + this.server.halt(false); +@@ -386,18 +391,17 @@ public abstract class ServerCommonPacketListenerImpl implements ServerCommonPack + // Do not kick the player + return; + } +- this.player.kickLeaveMessage = event.getLeaveMessage(); // CraftBukkit - SPIGOT-3034: Forward leave message to PlayerQuitEvent + // Send the possibly modified leave message +- this.disconnect0(new DisconnectionDetails(io.papermc.paper.adventure.PaperAdventure.asVanilla(event.reason()), disconnectionInfo.report(), disconnectionInfo.bugReportLink())); // Paper - Adventure ++ this.disconnect0(new DisconnectionDetails(io.papermc.paper.adventure.PaperAdventure.asVanilla(event.reason()), disconnectionInfo.report(), disconnectionInfo.bugReportLink()), event.leaveMessage()); // Paper - Adventure & use kick event leave message + } + +- private void disconnect0(DisconnectionDetails disconnectiondetails) { ++ private void disconnect0(DisconnectionDetails disconnectiondetails, @Nullable net.kyori.adventure.text.Component leaveMessage) { // Paper - use kick event leave message + // CraftBukkit end + this.player.quitReason = org.bukkit.event.player.PlayerQuitEvent.QuitReason.KICKED; // Paper - Add API for quit reason + this.connection.send(new ClientboundDisconnectPacket(disconnectiondetails.reason()), PacketSendListener.thenRun(() -> { + this.connection.disconnect(disconnectiondetails); + })); +- this.onDisconnect(disconnectiondetails); // CraftBukkit - fire quit instantly ++ this.onDisconnect(disconnectiondetails, leaveMessage); // CraftBukkit - fire quit instantly // Paper - use kick event leave message + this.connection.setReadOnly(); + MinecraftServer minecraftserver = this.server; + Connection networkmanager = this.connection; +diff --git a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java +index e48c6f37ba0ebe698e28042e9331ab2ec0c39e7c..0229b3e6c27b142ff726de8e2e15104a6acbc1f0 100644 +--- a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java ++++ b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java +@@ -1915,6 +1915,12 @@ public class ServerGamePacketListenerImpl extends ServerCommonPacketListenerImpl + + @Override + public void onDisconnect(DisconnectionDetails info) { ++ // Paper start - Fix kick event leave message not being sent ++ this.onDisconnect(info, null); ++ } ++ @Override ++ public void onDisconnect(DisconnectionDetails info, @Nullable net.kyori.adventure.text.Component quitMessage) { ++ // Paper end - Fix kick event leave message not being sent + // CraftBukkit start - Rarely it would send a disconnect line twice + if (this.processedDisconnect) { + return; +@@ -1923,11 +1929,17 @@ public class ServerGamePacketListenerImpl extends ServerCommonPacketListenerImpl + } + // CraftBukkit end + ServerGamePacketListenerImpl.LOGGER.info("{} lost connection: {}", this.player.getName().getString(), info.reason().getString()); +- this.removePlayerFromWorld(); +- super.onDisconnect(info); ++ this.removePlayerFromWorld(quitMessage); // Paper - Fix kick event leave message not being sent ++ super.onDisconnect(info, quitMessage); // Paper - Fix kick event leave message not being sent + } + ++ // Paper start - Fix kick event leave message not being sent + private void removePlayerFromWorld() { ++ this.removePlayerFromWorld(null); ++ } ++ ++ private void removePlayerFromWorld(@Nullable net.kyori.adventure.text.Component quitMessage) { ++ // Paper end - Fix kick event leave message not being sent + this.chatMessageChain.close(); + // CraftBukkit start - Replace vanilla quit message handling with our own. + /* +@@ -1937,7 +1949,7 @@ public class ServerGamePacketListenerImpl extends ServerCommonPacketListenerImpl + + this.player.disconnect(); + // Paper start - Adventure +- net.kyori.adventure.text.Component quitMessage = this.server.getPlayerList().remove(this.player); ++ quitMessage = quitMessage == null ? this.server.getPlayerList().remove(this.player) : this.server.getPlayerList().remove(this.player, quitMessage); // Paper - pass in quitMessage to fix kick message not being used + if ((quitMessage != null) && !quitMessage.equals(net.kyori.adventure.text.Component.empty())) { + this.server.getPlayerList().broadcastSystemMessage(PaperAdventure.asVanilla(quitMessage), false); + // Paper end +diff --git a/src/main/java/net/minecraft/server/players/PlayerList.java b/src/main/java/net/minecraft/server/players/PlayerList.java +index e6b92f085291aaf4fa78d96f8379aeef2200592b..2c810cb5d2fcf1a88423cfe4e063dd810ecaaeb0 100644 +--- a/src/main/java/net/minecraft/server/players/PlayerList.java ++++ b/src/main/java/net/minecraft/server/players/PlayerList.java +@@ -510,6 +510,11 @@ public abstract class PlayerList { + } + + public net.kyori.adventure.text.Component remove(ServerPlayer entityplayer) { // CraftBukkit - return string // Paper - return Component ++ // Paper start - Fix kick event leave message not being sent ++ return this.remove(entityplayer, net.kyori.adventure.text.Component.translatable("multiplayer.player.left", net.kyori.adventure.text.format.NamedTextColor.YELLOW, io.papermc.paper.configuration.GlobalConfiguration.get().messages.useDisplayNameInQuitMessage ? entityplayer.getBukkitEntity().displayName() : io.papermc.paper.adventure.PaperAdventure.asAdventure(entityplayer.getDisplayName()))); ++ } ++ public net.kyori.adventure.text.Component remove(ServerPlayer entityplayer, net.kyori.adventure.text.Component leaveMessage) { ++ // Paper end - Fix kick event leave message not being sent + ServerLevel worldserver = entityplayer.serverLevel(); + + entityplayer.awardStat(Stats.LEAVE_GAME); +@@ -520,7 +525,7 @@ public abstract class PlayerList { + entityplayer.closeContainer(org.bukkit.event.inventory.InventoryCloseEvent.Reason.DISCONNECT); // Paper - Inventory close reason + } + +- PlayerQuitEvent playerQuitEvent = new PlayerQuitEvent(entityplayer.getBukkitEntity(), net.kyori.adventure.text.Component.translatable("multiplayer.player.left", net.kyori.adventure.text.format.NamedTextColor.YELLOW, io.papermc.paper.configuration.GlobalConfiguration.get().messages.useDisplayNameInQuitMessage ? entityplayer.getBukkitEntity().displayName() : io.papermc.paper.adventure.PaperAdventure.asAdventure(entityplayer.getDisplayName())), entityplayer.quitReason); // Paper - Adventure & Add API for quit reason ++ PlayerQuitEvent playerQuitEvent = new PlayerQuitEvent(entityplayer.getBukkitEntity(), leaveMessage, entityplayer.quitReason); // Paper - Adventure & Add API for quit reason + this.cserver.getPluginManager().callEvent(playerQuitEvent); + entityplayer.getBukkitEntity().disconnect(playerQuitEvent.getQuitMessage()); + diff --git a/patches/server/0558-Fix-return-value-of-Block-applyBoneMeal-always-being.patch b/patches/server/0558-Fix-return-value-of-Block-applyBoneMeal-always-being.patch deleted file mode 100644 index a5c4eefb9aa2..000000000000 --- a/patches/server/0558-Fix-return-value-of-Block-applyBoneMeal-always-being.patch +++ /dev/null @@ -1,19 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Jason Penilla <11360596+jpenilla@users.noreply.github.com> -Date: Mon, 28 Jun 2021 18:16:52 -0700 -Subject: [PATCH] Fix return value of Block#applyBoneMeal always being false - - -diff --git a/src/main/java/org/bukkit/craftbukkit/block/CraftBlock.java b/src/main/java/org/bukkit/craftbukkit/block/CraftBlock.java -index 2034858a53c4c887da334cdc7713997daa01124f..ce297420f695404356655b1df2847a32fb98ec59 100644 ---- a/src/main/java/org/bukkit/craftbukkit/block/CraftBlock.java -+++ b/src/main/java/org/bukkit/craftbukkit/block/CraftBlock.java -@@ -558,7 +558,7 @@ public class CraftBlock implements Block { - } - } - -- return result == InteractionResult.SUCCESS && (event == null || !event.isCancelled()); -+ return result == InteractionResult.CONSUME && (event == null || !event.isCancelled()); // Paper - CONSUME is returned on success server-side (see BoneMealItem.applyBoneMeal and InteractionResult.sidedSuccess(boolean)) - } - - @Override diff --git a/patches/server/0559-Don-t-apply-cramming-damage-to-players.patch b/patches/server/0559-Don-t-apply-cramming-damage-to-players.patch new file mode 100644 index 000000000000..a1e4db537b25 --- /dev/null +++ b/patches/server/0559-Don-t-apply-cramming-damage-to-players.patch @@ -0,0 +1,25 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Phoenix616 +Date: Sun, 20 Jun 2021 16:35:42 +0100 +Subject: [PATCH] Don't apply cramming damage to players + +It does not make a lot of sense to damage players if they get crammed, + especially as the usecase of teleporting lots of players to the same + location isn't too uncommon and killing all those players isn't + really what one would expect to happen. + +For those who really want it a config option is provided. + +diff --git a/src/main/java/net/minecraft/server/level/ServerPlayer.java b/src/main/java/net/minecraft/server/level/ServerPlayer.java +index 99a634760cd04d5e3182254b1d0b817baf1cf87a..94cc69ed1ccbcfcc8f431762fef641c313b2f634 100644 +--- a/src/main/java/net/minecraft/server/level/ServerPlayer.java ++++ b/src/main/java/net/minecraft/server/level/ServerPlayer.java +@@ -1814,7 +1814,7 @@ public class ServerPlayer extends net.minecraft.world.entity.player.Player { + + @Override + public boolean isInvulnerableTo(ServerLevel world, DamageSource source) { +- return super.isInvulnerableTo(world, source) || this.isChangingDimension() && !source.is(DamageTypes.ENDER_PEARL); ++ return super.isInvulnerableTo(world, source) || (this.isChangingDimension() && !source.is(DamageTypes.ENDER_PEARL)) || (!this.level().paperConfig().collisions.allowPlayerCrammingDamage && source.is(DamageTypes.CRAMMING)); // Paper - disable player cramming; + } + + @Override diff --git a/patches/server/0559-Use-getChunkIfLoadedImmediately-in-places.patch b/patches/server/0559-Use-getChunkIfLoadedImmediately-in-places.patch deleted file mode 100644 index b2446b7c2b8b..000000000000 --- a/patches/server/0559-Use-getChunkIfLoadedImmediately-in-places.patch +++ /dev/null @@ -1,53 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Spottedleaf -Date: Mon, 8 Jul 2019 00:13:36 -0700 -Subject: [PATCH] Use getChunkIfLoadedImmediately in places - -This prevents us from hitting chunk loads for chunks at or less-than -ticket level 33 (yes getChunkIfLoaded will actually perform a chunk -load in that case). - -diff --git a/src/main/java/net/minecraft/server/level/ServerLevel.java b/src/main/java/net/minecraft/server/level/ServerLevel.java -index 055650b315d53b56798ded7af2054c3e8e3ee319..c72687fb23e8d01639cce7d79e3f97805d51e01f 100644 ---- a/src/main/java/net/minecraft/server/level/ServerLevel.java -+++ b/src/main/java/net/minecraft/server/level/ServerLevel.java -@@ -231,7 +231,7 @@ public class ServerLevel extends Level implements WorldGenLevel { - public boolean hasEntityMoveEvent; // Paper - Add EntityMoveEvent - - public LevelChunk getChunkIfLoaded(int x, int z) { -- return this.chunkSource.getChunk(x, z, false); -+ return this.chunkSource.getChunkAtIfLoadedImmediately(x, z); // Paper - Use getChunkIfLoadedImmediately - } - - @Override -diff --git a/src/main/java/net/minecraft/world/level/Level.java b/src/main/java/net/minecraft/world/level/Level.java -index 144d243e0d6ba3ae3f0b0bf457fa516e2b4f416f..20a14b4163807b806bf2ce5a88d3c35098bed929 100644 ---- a/src/main/java/net/minecraft/world/level/Level.java -+++ b/src/main/java/net/minecraft/world/level/Level.java -@@ -180,6 +180,13 @@ public abstract class Level implements LevelAccessor, AutoCloseable { - return (CraftServer) Bukkit.getServer(); - } - -+ // Paper start - Use getChunkIfLoadedImmediately -+ @Override -+ public boolean hasChunk(int chunkX, int chunkZ) { -+ return this.getChunkIfLoaded(chunkX, chunkZ) != null; -+ } -+ // Paper end - Use getChunkIfLoadedImmediately -+ - public abstract ResourceKey getTypeKey(); - - protected Level(WritableLevelData worlddatamutable, ResourceKey resourcekey, RegistryAccess iregistrycustom, Holder holder, Supplier supplier, boolean flag, boolean flag1, long i, int j, org.bukkit.generator.ChunkGenerator gen, org.bukkit.generator.BiomeProvider biomeProvider, org.bukkit.World.Environment env, java.util.function.Function paperWorldConfigCreator) { // Paper - create paper world config -diff --git a/src/main/java/net/minecraft/world/level/gameevent/GameEventDispatcher.java b/src/main/java/net/minecraft/world/level/gameevent/GameEventDispatcher.java -index 13b34e89bd3e55df1bb1d4d0cf013bafae43f502..df6c97be1b278c97a20390be5d3e60f429383702 100644 ---- a/src/main/java/net/minecraft/world/level/gameevent/GameEventDispatcher.java -+++ b/src/main/java/net/minecraft/world/level/gameevent/GameEventDispatcher.java -@@ -56,7 +56,7 @@ public class GameEventDispatcher { - - for (int l1 = j; l1 <= i1; ++l1) { - for (int i2 = l; i2 <= k1; ++i2) { -- LevelChunk chunk = this.level.getChunkSource().getChunkNow(l1, i2); -+ LevelChunk chunk = (LevelChunk) this.level.getChunkIfLoadedImmediately(l1, i2); // Paper - Use getChunkIfLoadedImmediately - - if (chunk != null) { - for (int j2 = k; j2 <= j1; ++j2) { diff --git a/patches/server/0560-Fix-commands-from-signs-not-firing-command-events.patch b/patches/server/0560-Fix-commands-from-signs-not-firing-command-events.patch deleted file mode 100644 index b86e34135933..000000000000 --- a/patches/server/0560-Fix-commands-from-signs-not-firing-command-events.patch +++ /dev/null @@ -1,120 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Jake Potrebic -Date: Fri, 9 Jul 2021 13:50:48 -0700 -Subject: [PATCH] Fix commands from signs not firing command events - -This patch changes sign command logic so that `run_command` click events: - - are logged to the console - - fire PlayerCommandPreprocessEvent - - work with double-slash commands like `//wand` - - sends failure messages to the player who clicked the sign - -diff --git a/src/main/java/io/papermc/paper/commands/DelegatingCommandSource.java b/src/main/java/io/papermc/paper/commands/DelegatingCommandSource.java -new file mode 100644 -index 0000000000000000000000000000000000000000..01a2bc1feec808790bb93618ce46adb9bea5a9c8 ---- /dev/null -+++ b/src/main/java/io/papermc/paper/commands/DelegatingCommandSource.java -@@ -0,0 +1,42 @@ -+package io.papermc.paper.commands; -+ -+import net.minecraft.commands.CommandSource; -+import net.minecraft.commands.CommandSourceStack; -+import net.minecraft.network.chat.Component; -+import org.bukkit.command.CommandSender; -+ -+import java.util.UUID; -+ -+public class DelegatingCommandSource implements CommandSource { -+ -+ private final CommandSource delegate; -+ -+ public DelegatingCommandSource(CommandSource delegate) { -+ this.delegate = delegate; -+ } -+ -+ @Override -+ public void sendSystemMessage(Component message) { -+ delegate.sendSystemMessage(message); -+ } -+ -+ @Override -+ public boolean acceptsSuccess() { -+ return delegate.acceptsSuccess(); -+ } -+ -+ @Override -+ public boolean acceptsFailure() { -+ return delegate.acceptsFailure(); -+ } -+ -+ @Override -+ public boolean shouldInformAdmins() { -+ return delegate.shouldInformAdmins(); -+ } -+ -+ @Override -+ public CommandSender getBukkitSender(CommandSourceStack wrapper) { -+ return delegate.getBukkitSender(wrapper); -+ } -+} -diff --git a/src/main/java/net/minecraft/world/level/block/entity/SignBlockEntity.java b/src/main/java/net/minecraft/world/level/block/entity/SignBlockEntity.java -index 87e272cfb145c37d26b0bf56f97ec784a9bfd98e..bfe8029852385875af4ebe73c63e688f61042021 100644 ---- a/src/main/java/net/minecraft/world/level/block/entity/SignBlockEntity.java -+++ b/src/main/java/net/minecraft/world/level/block/entity/SignBlockEntity.java -@@ -274,7 +274,17 @@ public class SignBlockEntity extends BlockEntity implements CommandSource { // C - ClickEvent chatclickable = chatmodifier.getClickEvent(); - - if (chatclickable != null && chatclickable.getAction() == ClickEvent.Action.RUN_COMMAND) { -- player.getServer().getCommands().performPrefixedCommand(this.createCommandSourceStack(player, world, pos), chatclickable.getValue()); -+ // Paper start - Fix commands from signs not firing command events -+ String command = chatclickable.getValue().startsWith("/") ? chatclickable.getValue() : "/" + chatclickable.getValue(); -+ if (org.spigotmc.SpigotConfig.logCommands) { -+ LOGGER.info("{} issued server command: {}", player.getScoreboardName(), command); -+ } -+ io.papermc.paper.event.player.PlayerSignCommandPreprocessEvent event = new io.papermc.paper.event.player.PlayerSignCommandPreprocessEvent((org.bukkit.entity.Player) player.getBukkitEntity(), command, new org.bukkit.craftbukkit.util.LazyPlayerSet(player.getServer()), (org.bukkit.block.Sign) CraftBlock.at(this.level, this.worldPosition).getState(), front ? Side.FRONT : Side.BACK); -+ if (!event.callEvent()) { -+ return false; -+ } -+ player.getServer().getCommands().performPrefixedCommand(this.createCommandSourceStack(((org.bukkit.craftbukkit.entity.CraftPlayer) event.getPlayer()).getHandle(), world, pos), event.getMessage()); -+ // Paper end - Fix commands from signs not firing command events - flag1 = true; - } - } -@@ -311,8 +321,23 @@ public class SignBlockEntity extends BlockEntity implements CommandSource { // C - String s = player == null ? "Sign" : player.getName().getString(); - Object object = player == null ? Component.literal("Sign") : player.getDisplayName(); - -+ // Paper start - Fix commands from signs not firing command events -+ CommandSource commandSource = this.level.paperConfig().misc.showSignClickCommandFailureMsgsToPlayer ? new io.papermc.paper.commands.DelegatingCommandSource(this) { -+ @Override -+ public void sendSystemMessage(Component message) { -+ if (player != null) { -+ player.sendSystemMessage(message); -+ } -+ } -+ -+ @Override -+ public boolean acceptsFailure() { -+ return true; -+ } -+ } : this; -+ // Paper end - Fix commands from signs not firing command events - // CraftBukkit - this -- return new CommandSourceStack(this, Vec3.atCenterOf(pos), Vec2.ZERO, (ServerLevel) world, 2, s, (Component) object, world.getServer(), player); -+ return new CommandSourceStack(commandSource, Vec3.atCenterOf(pos), Vec2.ZERO, (ServerLevel) world, 2, s, (Component) object, world.getServer(), player); // Paper - Fix commands from signs not firing command events - } - - @Override -diff --git a/src/main/java/org/bukkit/craftbukkit/command/BukkitCommandWrapper.java b/src/main/java/org/bukkit/craftbukkit/command/BukkitCommandWrapper.java -index d113e54a30db16e2ad955170df6030d15de530d6..21b6f90cf5bd7087d1a0f512289d971f2c3e1afa 100644 ---- a/src/main/java/org/bukkit/craftbukkit/command/BukkitCommandWrapper.java -+++ b/src/main/java/org/bukkit/craftbukkit/command/BukkitCommandWrapper.java -@@ -61,7 +61,7 @@ public class BukkitCommandWrapper implements com.mojang.brigadier.Command +Date: Mon, 28 Jun 2021 22:38:29 +0100 +Subject: [PATCH] Rate options and timings for sensors and behaviors + +This adds config options to specify the tick rate for sensors + and behaviors of different entity types as well as timings + for those in order to be able to have some metrics as to which + ones might need tweaking. + +diff --git a/src/main/java/net/minecraft/world/entity/ai/behavior/Behavior.java b/src/main/java/net/minecraft/world/entity/ai/behavior/Behavior.java +index f639cafa64d98a001e622882c647701547f5c3ac..ba951cc1aaa94b58ee7985f197d41cc8be747fc8 100644 +--- a/src/main/java/net/minecraft/world/entity/ai/behavior/Behavior.java ++++ b/src/main/java/net/minecraft/world/entity/ai/behavior/Behavior.java +@@ -14,6 +14,9 @@ public abstract class Behavior implements BehaviorContro + private long endTimestamp; + private final int minDuration; + private final int maxDuration; ++ // Paper start - configurable behavior tick rate and timings ++ private final String configKey; ++ // Paper end - configurable behavior tick rate and timings + + public Behavior(Map, MemoryStatus> requiredMemoryState) { + this(requiredMemoryState, 60); +@@ -27,6 +30,14 @@ public abstract class Behavior implements BehaviorContro + this.minDuration = minRunTime; + this.maxDuration = maxRunTime; + this.entryCondition = requiredMemoryState; ++ // Paper start - configurable behavior tick rate and timings ++ String key = io.papermc.paper.util.MappingEnvironment.reobf() ? io.papermc.paper.util.ObfHelper.INSTANCE.deobfClassName(this.getClass().getName()) : this.getClass().getName(); ++ int lastSeparator = key.lastIndexOf('.'); ++ if (lastSeparator != -1) { ++ key = key.substring(lastSeparator + 1); ++ } ++ this.configKey = key.toLowerCase(java.util.Locale.ROOT); ++ // Paper end - configurable behavior tick rate and timings + } + + @Override +@@ -36,6 +47,12 @@ public abstract class Behavior implements BehaviorContro + + @Override + public final boolean tryStart(ServerLevel world, E entity, long time) { ++ // Paper start - configurable behavior tick rate and timings ++ int tickRate = java.util.Objects.requireNonNullElse(world.paperConfig().tickRates.behavior.get(entity.getType(), this.configKey), -1); ++ if (tickRate > -1 && time < this.endTimestamp + tickRate) { ++ return false; ++ } ++ // Paper end - configurable behavior tick rate and timings + if (this.hasRequiredMemories(entity) && this.checkExtraStartConditions(world, entity)) { + this.status = Behavior.Status.RUNNING; + int i = this.minDuration + world.getRandom().nextInt(this.maxDuration + 1 - this.minDuration); +diff --git a/src/main/java/net/minecraft/world/entity/ai/sensing/Sensor.java b/src/main/java/net/minecraft/world/entity/ai/sensing/Sensor.java +index 4d451f6cb5862411848bb9b6b5692ab512dcaa25..fb1f5375eafb030ae08c735a80e9c8149726cda4 100644 +--- a/src/main/java/net/minecraft/world/entity/ai/sensing/Sensor.java ++++ b/src/main/java/net/minecraft/world/entity/ai/sensing/Sensor.java +@@ -29,8 +29,19 @@ public abstract class Sensor { + .ignoreInvisibilityTesting(); + private final int scanRate; + private long timeToTick; ++ // Paper start - configurable sensor tick rate and timings ++ private final String configKey; ++ // Paper end + + public Sensor(int senseInterval) { ++ // Paper start - configurable sensor tick rate and timings ++ String key = io.papermc.paper.util.MappingEnvironment.reobf() ? io.papermc.paper.util.ObfHelper.INSTANCE.deobfClassName(this.getClass().getName()) : this.getClass().getName(); ++ int lastSeparator = key.lastIndexOf('.'); ++ if (lastSeparator != -1) { ++ key = key.substring(lastSeparator + 1); ++ } ++ this.configKey = key.toLowerCase(java.util.Locale.ROOT); ++ // Paper end + this.scanRate = senseInterval; + this.timeToTick = (long)RANDOM.nextInt(senseInterval); + } +@@ -41,8 +52,10 @@ public abstract class Sensor { + + public final void tick(ServerLevel world, E entity) { + if (--this.timeToTick <= 0L) { +- this.timeToTick = (long)this.scanRate; ++ // Paper start - configurable sensor tick rate and timings ++ this.timeToTick = java.util.Objects.requireNonNullElse(world.paperConfig().tickRates.sensor.get(entity.getType(), this.configKey), this.scanRate); + this.updateTargetingConditionRanges(entity); ++ // Paper end + this.doTick(world, entity); + } + } diff --git a/patches/server/0561-Add-PlayerArmSwingEvent.patch b/patches/server/0561-Add-PlayerArmSwingEvent.patch deleted file mode 100644 index 8e77eadc2373..000000000000 --- a/patches/server/0561-Add-PlayerArmSwingEvent.patch +++ /dev/null @@ -1,19 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Jake Potrebic -Date: Fri, 12 Mar 2021 19:22:21 -0800 -Subject: [PATCH] Add PlayerArmSwingEvent - - -diff --git a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java -index b30c71ad0cc2602d2c026433a94c9ca45977855e..3b20dce53403e241261f270f0a9e32f12b9e368a 100644 ---- a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java -+++ b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java -@@ -2412,7 +2412,7 @@ public class ServerGamePacketListenerImpl extends ServerCommonPacketListenerImpl - } // Paper end - Call interact event - - // Arm swing animation -- PlayerAnimationEvent event = new PlayerAnimationEvent(this.getCraftPlayer(), (packet.getHand() == InteractionHand.MAIN_HAND) ? PlayerAnimationType.ARM_SWING : PlayerAnimationType.OFF_ARM_SWING); -+ io.papermc.paper.event.player.PlayerArmSwingEvent event = new io.papermc.paper.event.player.PlayerArmSwingEvent(this.getCraftPlayer(), org.bukkit.craftbukkit.CraftEquipmentSlot.getHand(packet.getHand())); // Paper - Add PlayerArmSwingEvent - this.cserver.getPluginManager().callEvent(event); - - if (event.isCancelled()) return; diff --git a/patches/server/0561-Add-missing-forceDrop-toggles.patch b/patches/server/0561-Add-missing-forceDrop-toggles.patch new file mode 100644 index 000000000000..acc8574a8135 --- /dev/null +++ b/patches/server/0561-Add-missing-forceDrop-toggles.patch @@ -0,0 +1,108 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Jake Potrebic +Date: Tue, 20 Jul 2021 21:25:35 -0700 +Subject: [PATCH] Add missing forceDrop toggles + + +diff --git a/src/main/java/net/minecraft/world/entity/ai/behavior/WorkAtComposter.java b/src/main/java/net/minecraft/world/entity/ai/behavior/WorkAtComposter.java +index 4ec1f881c05d96d72814ac3dffd3b4bef40c1bce..c34cb8c918e400636856317cc58356d2677e1d52 100644 +--- a/src/main/java/net/minecraft/world/entity/ai/behavior/WorkAtComposter.java ++++ b/src/main/java/net/minecraft/world/entity/ai/behavior/WorkAtComposter.java +@@ -86,7 +86,9 @@ public class WorkAtComposter extends WorkAtPoi { + simpleContainer.removeItemType(Items.WHEAT, m); + ItemStack itemStack = simpleContainer.addItem(new ItemStack(Items.BREAD, l)); + if (!itemStack.isEmpty()) { ++ villager.forceDrops = true; // Paper - Add missing forceDrop toggles + villager.spawnAtLocation(world, itemStack, 0.5F); ++ villager.forceDrops = false; // Paper - Add missing forceDrop toggles + } + } + } +diff --git a/src/main/java/net/minecraft/world/entity/animal/Panda.java b/src/main/java/net/minecraft/world/entity/animal/Panda.java +index 705c26ceff9371b09311bd7fa796c0efde7ebfee..4f04170b3ec4ff59358e10ccfd0799af3ab590c3 100644 +--- a/src/main/java/net/minecraft/world/entity/animal/Panda.java ++++ b/src/main/java/net/minecraft/world/entity/animal/Panda.java +@@ -540,7 +540,9 @@ public class Panda extends Animal { + + if (world1 instanceof ServerLevel worldserver) { + if (worldserver.getGameRules().getBoolean(GameRules.RULE_DOMOBLOOT)) { ++ this.forceDrops = true; // Paper - Add missing forceDrop toggles + this.dropFromGiftLootTable(worldserver, BuiltInLootTables.PANDA_SNEEZE, this::spawnAtLocation); ++ this.forceDrops = false; // Paper - Add missing forceDrop toggles + } + } + +@@ -664,7 +666,9 @@ public class Panda extends Animal { + ItemStack itemstack1 = this.getItemBySlot(EquipmentSlot.MAINHAND); + + if (!itemstack1.isEmpty() && !player.hasInfiniteMaterials()) { ++ this.forceDrops = true; // Paper - Add missing forceDrop toggles + this.spawnAtLocation(worldserver, itemstack1); ++ this.forceDrops = false; // Paper - Add missing forceDrop toggles + } + + this.setItemSlot(EquipmentSlot.MAINHAND, new ItemStack(itemstack.getItem(), 1)); +@@ -942,7 +946,9 @@ public class Panda extends Animal { + ItemStack itemstack = Panda.this.getItemBySlot(EquipmentSlot.MAINHAND); + + if (!itemstack.isEmpty()) { ++ Panda.this.forceDrops = true; // Paper - Add missing forceDrop toggles + Panda.this.spawnAtLocation(getServerLevel(Panda.this.level()), itemstack); ++ Panda.this.forceDrops = false; // Paper - Add missing forceDrop toggles + Panda.this.setItemSlot(EquipmentSlot.MAINHAND, ItemStack.EMPTY); + int i = Panda.this.isLazy() ? Panda.this.random.nextInt(50) + 10 : Panda.this.random.nextInt(150) + 10; + +diff --git a/src/main/java/net/minecraft/world/entity/monster/piglin/Piglin.java b/src/main/java/net/minecraft/world/entity/monster/piglin/Piglin.java +index ec733e71e41a4c89ed9f35ad1d9d4fa912160d27..15a49e3541c8b45db5e472a64fa0cb94c5a72f67 100644 +--- a/src/main/java/net/minecraft/world/entity/monster/piglin/Piglin.java ++++ b/src/main/java/net/minecraft/world/entity/monster/piglin/Piglin.java +@@ -323,9 +323,11 @@ public class Piglin extends AbstractPiglin implements CrossbowAttackMob, Invento + @Override + protected void finishConversion(ServerLevel world) { + PiglinAi.cancelAdmiring(world, this); ++ this.forceDrops = true; // Paper - Add missing forceDrop toggles + this.inventory.removeAllItems().forEach((itemstack) -> { + this.spawnAtLocation(world, itemstack); + }); ++ this.forceDrops = false; // Paper - Add missing forceDrop toggles + super.finishConversion(world); + } + +diff --git a/src/main/java/net/minecraft/world/entity/monster/piglin/PiglinAi.java b/src/main/java/net/minecraft/world/entity/monster/piglin/PiglinAi.java +index 42b1bd58c6e2c3bd1170171eabfefe315202f340..55868c82bf8bd61ce3494aa9f363c20c88ec6aa6 100644 +--- a/src/main/java/net/minecraft/world/entity/monster/piglin/PiglinAi.java ++++ b/src/main/java/net/minecraft/world/entity/monster/piglin/PiglinAi.java +@@ -273,7 +273,9 @@ public class PiglinAi { + + private static void holdInOffhand(ServerLevel world, Piglin piglin, ItemStack stack) { + if (PiglinAi.isHoldingItemInOffHand(piglin)) { ++ piglin.forceDrops = true; // Paper - Add missing forceDrop toggles + piglin.spawnAtLocation(world, piglin.getItemInHand(InteractionHand.OFF_HAND)); ++ piglin.forceDrops = false; // Paper - Add missing forceDrop toggles + } + + piglin.holdInOffHand(stack); +@@ -333,7 +335,9 @@ public class PiglinAi { + + protected static void cancelAdmiring(ServerLevel world, Piglin piglin) { + if (PiglinAi.isAdmiringItem(piglin) && !piglin.getOffhandItem().isEmpty()) { ++ piglin.forceDrops = true; // Paper - Add missing forceDrop toggles + piglin.spawnAtLocation(world, piglin.getOffhandItem()); ++ piglin.forceDrops = false; // Paper - Add missing forceDrop toggles + piglin.setItemInHand(InteractionHand.OFF_HAND, ItemStack.EMPTY); + } + +diff --git a/src/main/java/net/minecraft/world/entity/raid/Raider.java b/src/main/java/net/minecraft/world/entity/raid/Raider.java +index 6ac2351b3476aa04872196836ce00c622adab315..45375ccdcf730732dd915304dea2f523807eedd6 100644 +--- a/src/main/java/net/minecraft/world/entity/raid/Raider.java ++++ b/src/main/java/net/minecraft/world/entity/raid/Raider.java +@@ -233,7 +233,9 @@ public abstract class Raider extends PatrollingMonster { + double d0 = (double) this.getEquipmentDropChance(enumitemslot); + + if (!itemstack1.isEmpty() && (double) Math.max(this.random.nextFloat() - 0.1F, 0.0F) < d0) { ++ this.forceDrops = true; // Paper - Add missing forceDrop toggles + this.spawnAtLocation(world, itemstack1); ++ this.forceDrops = false; // Paper - Add missing forceDrop toggles + } + + this.onItemPickup(itemEntity); diff --git a/patches/server/0562-Fix-kick-event-leave-message-not-being-sent.patch b/patches/server/0562-Fix-kick-event-leave-message-not-being-sent.patch deleted file mode 100644 index 461885e49a28..000000000000 --- a/patches/server/0562-Fix-kick-event-leave-message-not-being-sent.patch +++ /dev/null @@ -1,118 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Jake Potrebic -Date: Wed, 7 Jul 2021 16:19:41 -0700 -Subject: [PATCH] Fix kick event leave message not being sent - - -diff --git a/src/main/java/net/minecraft/server/level/ServerPlayer.java b/src/main/java/net/minecraft/server/level/ServerPlayer.java -index f20019261a09f425137731f7a4b92e889b617334..9982940af7d10ca7799e2c21ac994ea3afa0b805 100644 ---- a/src/main/java/net/minecraft/server/level/ServerPlayer.java -+++ b/src/main/java/net/minecraft/server/level/ServerPlayer.java -@@ -290,7 +290,6 @@ public class ServerPlayer extends net.minecraft.world.entity.player.Player { - public boolean joining = true; - public boolean sentListPacket = false; - public boolean supressTrackerForLogin = false; // Paper - Fire PlayerJoinEvent when Player is actually ready -- public String kickLeaveMessage = null; // SPIGOT-3034: Forward leave message to PlayerQuitEvent - // CraftBukkit end - public boolean isRealPlayer; // Paper - public com.destroystokyo.paper.event.entity.PlayerNaturallySpawnCreaturesEvent playerNaturallySpawnedEvent; // Paper - PlayerNaturallySpawnCreaturesEvent -diff --git a/src/main/java/net/minecraft/server/network/ServerCommonPacketListenerImpl.java b/src/main/java/net/minecraft/server/network/ServerCommonPacketListenerImpl.java -index 64450024ce8094874875321537ddab71ab5206fa..6998f32f8d79dbdb6b31ffaa126602fc4a428616 100644 ---- a/src/main/java/net/minecraft/server/network/ServerCommonPacketListenerImpl.java -+++ b/src/main/java/net/minecraft/server/network/ServerCommonPacketListenerImpl.java -@@ -115,6 +115,11 @@ public abstract class ServerCommonPacketListenerImpl implements ServerCommonPack - - @Override - public void onDisconnect(DisconnectionDetails info) { -+ // Paper start - Fix kick event leave message not being sent -+ this.onDisconnect(info, null); -+ } -+ public void onDisconnect(DisconnectionDetails info, @Nullable net.kyori.adventure.text.Component quitMessage) { -+ // Paper end - Fix kick event leave message not being sent - if (this.isSingleplayerOwner()) { - ServerCommonPacketListenerImpl.LOGGER.info("Stopping singleplayer server as player logged out"); - this.server.halt(false); -@@ -379,18 +384,17 @@ public abstract class ServerCommonPacketListenerImpl implements ServerCommonPack - // Do not kick the player - return; - } -- this.player.kickLeaveMessage = event.getLeaveMessage(); // CraftBukkit - SPIGOT-3034: Forward leave message to PlayerQuitEvent - // Send the possibly modified leave message -- this.disconnect0(new DisconnectionDetails(io.papermc.paper.adventure.PaperAdventure.asVanilla(event.reason()), disconnectionInfo.report(), disconnectionInfo.bugReportLink())); // Paper - Adventure -+ this.disconnect0(new DisconnectionDetails(io.papermc.paper.adventure.PaperAdventure.asVanilla(event.reason()), disconnectionInfo.report(), disconnectionInfo.bugReportLink()), event.leaveMessage()); // Paper - Adventure & use kick event leave message - } - -- private void disconnect0(DisconnectionDetails disconnectiondetails) { -+ private void disconnect0(DisconnectionDetails disconnectiondetails, @Nullable net.kyori.adventure.text.Component leaveMessage) { // Paper - use kick event leave message - // CraftBukkit end - this.player.quitReason = org.bukkit.event.player.PlayerQuitEvent.QuitReason.KICKED; // Paper - Add API for quit reason - this.connection.send(new ClientboundDisconnectPacket(disconnectiondetails.reason()), PacketSendListener.thenRun(() -> { - this.connection.disconnect(disconnectiondetails); - })); -- this.onDisconnect(disconnectiondetails); // CraftBukkit - fire quit instantly -+ this.onDisconnect(disconnectiondetails, leaveMessage); // CraftBukkit - fire quit instantly // Paper - use kick event leave message - this.connection.setReadOnly(); - MinecraftServer minecraftserver = this.server; - Connection networkmanager = this.connection; -diff --git a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java -index 3b20dce53403e241261f270f0a9e32f12b9e368a..08c4d0b79dc1c6e51105487b96e9333a5dd904d7 100644 ---- a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java -+++ b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java -@@ -1894,6 +1894,12 @@ public class ServerGamePacketListenerImpl extends ServerCommonPacketListenerImpl - - @Override - public void onDisconnect(DisconnectionDetails info) { -+ // Paper start - Fix kick event leave message not being sent -+ this.onDisconnect(info, null); -+ } -+ @Override -+ public void onDisconnect(DisconnectionDetails info, @Nullable net.kyori.adventure.text.Component quitMessage) { -+ // Paper end - Fix kick event leave message not being sent - // CraftBukkit start - Rarely it would send a disconnect line twice - if (this.processedDisconnect) { - return; -@@ -1902,11 +1908,17 @@ public class ServerGamePacketListenerImpl extends ServerCommonPacketListenerImpl - } - // CraftBukkit end - ServerGamePacketListenerImpl.LOGGER.info("{} lost connection: {}", this.player.getName().getString(), info.reason().getString()); -- this.removePlayerFromWorld(); -- super.onDisconnect(info); -+ this.removePlayerFromWorld(quitMessage); // Paper - Fix kick event leave message not being sent -+ super.onDisconnect(info, quitMessage); // Paper - Fix kick event leave message not being sent - } - -+ // Paper start - Fix kick event leave message not being sent - private void removePlayerFromWorld() { -+ this.removePlayerFromWorld(null); -+ } -+ -+ private void removePlayerFromWorld(@Nullable net.kyori.adventure.text.Component quitMessage) { -+ // Paper end - Fix kick event leave message not being sent - this.chatMessageChain.close(); - // CraftBukkit start - Replace vanilla quit message handling with our own. - /* -@@ -1916,7 +1928,7 @@ public class ServerGamePacketListenerImpl extends ServerCommonPacketListenerImpl - - this.player.disconnect(); - // Paper start - Adventure -- net.kyori.adventure.text.Component quitMessage = this.server.getPlayerList().remove(this.player); -+ quitMessage = quitMessage == null ? this.server.getPlayerList().remove(this.player) : this.server.getPlayerList().remove(this.player, quitMessage); // Paper - pass in quitMessage to fix kick message not being used - if ((quitMessage != null) && !quitMessage.equals(net.kyori.adventure.text.Component.empty())) { - this.server.getPlayerList().broadcastSystemMessage(PaperAdventure.asVanilla(quitMessage), false); - // Paper end -diff --git a/src/main/java/net/minecraft/server/players/PlayerList.java b/src/main/java/net/minecraft/server/players/PlayerList.java -index 55a2f234436808258ef59f889a9b253fe79b82e8..2b0eed116327e74ff66aa065e42899e74b90abf1 100644 ---- a/src/main/java/net/minecraft/server/players/PlayerList.java -+++ b/src/main/java/net/minecraft/server/players/PlayerList.java -@@ -561,6 +561,11 @@ public abstract class PlayerList { - } - - public net.kyori.adventure.text.Component remove(ServerPlayer entityplayer) { // CraftBukkit - return string // Paper - return Component -+ // Paper start - Fix kick event leave message not being sent -+ return this.remove(entityplayer, net.kyori.adventure.text.Component.translatable("multiplayer.player.left", net.kyori.adventure.text.format.NamedTextColor.YELLOW, io.papermc.paper.configuration.GlobalConfiguration.get().messages.useDisplayNameInQuitMessage ? entityplayer.getBukkitEntity().displayName() : io.papermc.paper.adventure.PaperAdventure.asAdventure(entityplayer.getDisplayName()))); -+ } -+ public net.kyori.adventure.text.Component remove(ServerPlayer entityplayer, net.kyori.adventure.text.Component leaveMessage) { -+ // Paper end - Fix kick event leave message not being sent - ServerLevel worldserver = entityplayer.serverLevel(); - - entityplayer.awardStat(Stats.LEAVE_GAME); diff --git a/patches/server/0562-Stinger-API.patch b/patches/server/0562-Stinger-API.patch new file mode 100644 index 000000000000..6b8d804d91f8 --- /dev/null +++ b/patches/server/0562-Stinger-API.patch @@ -0,0 +1,50 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Owen1212055 <23108066+Owen1212055@users.noreply.github.com> +Date: Tue, 22 Jun 2021 23:15:44 -0400 +Subject: [PATCH] Stinger API + + +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftLivingEntity.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftLivingEntity.java +index 00db6ba96bda7ceaae8bc69a6b3a42e7a3929485..9870222cc1c46bcc37f9d3d44881606f2b9d038e 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftLivingEntity.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftLivingEntity.java +@@ -384,6 +384,39 @@ public class CraftLivingEntity extends CraftEntity implements LivingEntity { + public boolean isInvulnerable() { + return this.getHandle().isInvulnerableTo((ServerLevel) this.getHandle().level(), this.getHandle().damageSources().generic()); + } ++ // Paper start - Bee Stinger API ++ @Override ++ public int getBeeStingerCooldown() { ++ return getHandle().removeStingerTime; ++ } ++ ++ @Override ++ public void setBeeStingerCooldown(int ticks) { ++ getHandle().removeStingerTime = ticks; ++ } ++ ++ @Override ++ public int getBeeStingersInBody() { ++ return getHandle().getStingerCount(); ++ } ++ ++ @Override ++ public void setBeeStingersInBody(int count) { ++ Preconditions.checkArgument(count >= 0, "New bee stinger amount must be >= 0"); ++ getHandle().setStingerCount(count); ++ } ++ ++ @Override ++ public void setNextBeeStingerRemoval(final int ticks) { ++ Preconditions.checkArgument(ticks >= 0, "New amount of ticks before next bee stinger removal must be >= 0"); ++ this.getHandle().removeStingerTime = ticks; ++ } ++ ++ @Override ++ public int getNextBeeStingerRemoval() { ++ return this.getHandle().removeStingerTime; ++ } ++ // Paper end - Bee Stinger API + + @Override + public void damage(double amount) { diff --git a/patches/server/0563-Add-System.out-err-catcher.patch b/patches/server/0563-Add-System.out-err-catcher.patch new file mode 100644 index 000000000000..1904f817de28 --- /dev/null +++ b/patches/server/0563-Add-System.out-err-catcher.patch @@ -0,0 +1,118 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: underscore11code +Date: Fri, 23 Jul 2021 23:01:42 -0700 +Subject: [PATCH] Add System.out/err catcher + + +diff --git a/src/main/java/io/papermc/paper/logging/SysoutCatcher.java b/src/main/java/io/papermc/paper/logging/SysoutCatcher.java +new file mode 100644 +index 0000000000000000000000000000000000000000..a8e813ca89b033f061e695288b3383bdcf128531 +--- /dev/null ++++ b/src/main/java/io/papermc/paper/logging/SysoutCatcher.java +@@ -0,0 +1,94 @@ ++package io.papermc.paper.logging; ++ ++import org.bukkit.Bukkit; ++import org.bukkit.plugin.java.JavaPlugin; ++import org.jetbrains.annotations.NotNull; ++import org.jetbrains.annotations.Nullable; ++ ++import java.io.OutputStream; ++import java.io.PrintStream; ++import java.util.Objects; ++import java.util.concurrent.ConcurrentHashMap; ++import java.util.concurrent.ConcurrentMap; ++import java.util.concurrent.TimeUnit; ++import java.util.logging.Level; ++ ++public final class SysoutCatcher { ++ private static final boolean SUPPRESS_NAGS = Boolean.getBoolean("io.papermc.paper.suppress.sout.nags"); ++ // Nanoseconds between nag at most; if interval is caught first, this is reset. ++ // <= 0 for disabling. ++ private static final long NAG_TIMEOUT = TimeUnit.MILLISECONDS.toNanos( ++ Long.getLong("io.papermc.paper.sout.nags.timeout", TimeUnit.MINUTES.toMillis(5L))); ++ // Count since last nag; if timeout is first, this is reset. ++ // <= 0 for disabling. ++ private static final long NAG_INTERVAL = Long.getLong("io.papermc.paper.sout.nags.interval", 200L); ++ ++ // We don't particularly care about how correct this is at any given moment; let's do it on a best attempt basis. ++ // The records are also pretty small, so let's just go for a size of 64 to start... ++ // ++ // Content: Plugin name => nag object ++ // Why plugin name?: This doesn't store a reference to the plugin; keeps the reload ability. ++ // Why not clean on reload?: Effort. ++ private final ConcurrentMap nagRecords = new ConcurrentHashMap<>(64); ++ ++ public SysoutCatcher() { ++ System.setOut(new WrappedOutStream(System.out, Level.INFO, "[STDOUT] ")); ++ System.setErr(new WrappedOutStream(System.err, Level.SEVERE, "[STDERR] ")); ++ } ++ ++ private final class WrappedOutStream extends PrintStream { ++ private static final StackWalker STACK_WALKER = StackWalker.getInstance(StackWalker.Option.RETAIN_CLASS_REFERENCE); ++ private final Level level; ++ private final String prefix; ++ ++ public WrappedOutStream(@NotNull final OutputStream out, final Level level, final String prefix) { ++ super(out); ++ this.level = level; ++ this.prefix = prefix; ++ } ++ ++ @Override ++ public void println(@Nullable final String line) { ++ final Class clazz = STACK_WALKER.getCallerClass(); ++ try { ++ final JavaPlugin plugin = JavaPlugin.getProvidingPlugin(clazz); ++ ++ // Instead of just printing the message, send it to the plugin's logger ++ plugin.getLogger().log(this.level, this.prefix + line); ++ ++ if (SysoutCatcher.SUPPRESS_NAGS) { ++ return; ++ } ++ if (SysoutCatcher.NAG_INTERVAL > 0 || SysoutCatcher.NAG_TIMEOUT > 0) { ++ final PluginNag nagRecord = SysoutCatcher.this.nagRecords.computeIfAbsent(plugin.getName(), k -> new PluginNag()); ++ final boolean hasTimePassed = SysoutCatcher.NAG_TIMEOUT > 0 ++ && (nagRecord.lastNagTimestamp == Long.MIN_VALUE ++ || nagRecord.lastNagTimestamp + SysoutCatcher.NAG_TIMEOUT <= System.nanoTime()); ++ final boolean hasMessagesPassed = SysoutCatcher.NAG_INTERVAL > 0 ++ && (nagRecord.messagesSinceNag == Long.MIN_VALUE ++ || ++nagRecord.messagesSinceNag >= SysoutCatcher.NAG_INTERVAL); ++ if (!hasMessagesPassed && !hasTimePassed) { ++ return; ++ } ++ nagRecord.lastNagTimestamp = System.nanoTime(); ++ nagRecord.messagesSinceNag = 0; ++ } ++ Bukkit.getLogger().warning( ++ String.format("Nag author(s): '%s' of '%s' about their usage of System.out/err.print. " ++ + "Please use your plugin's logger instead (JavaPlugin#getLogger).", ++ plugin.getPluginMeta().getAuthors(), ++ plugin.getPluginMeta().getDisplayName()) ++ ); ++ } catch (final IllegalArgumentException | IllegalStateException e) { ++ // If anything happens, the calling class doesn't exist, there is no JavaPlugin that "owns" the calling class, etc ++ // Just print out normally, with some added information ++ Bukkit.getLogger().log(this.level, String.format("%s[%s] %s", this.prefix, clazz.getName(), line)); ++ } ++ } ++ } ++ ++ private static class PluginNag { ++ private long lastNagTimestamp = Long.MIN_VALUE; ++ private long messagesSinceNag = Long.MIN_VALUE; ++ } ++} +diff --git a/src/main/java/org/bukkit/craftbukkit/CraftServer.java b/src/main/java/org/bukkit/craftbukkit/CraftServer.java +index a17b26dadbbbf6e6c84f80f6fe8293d87ca19d0a..ecf7ee1ca39d58f1780580bd24366fc8037df34a 100644 +--- a/src/main/java/org/bukkit/craftbukkit/CraftServer.java ++++ b/src/main/java/org/bukkit/craftbukkit/CraftServer.java +@@ -310,6 +310,7 @@ public final class CraftServer implements Server { + public Set activeCompatibilities = Collections.emptySet(); + private final io.papermc.paper.datapack.PaperDatapackManager datapackManager; // Paper + public static Exception excessiveVelEx; // Paper - Velocity warnings ++ private final io.papermc.paper.logging.SysoutCatcher sysoutCatcher = new io.papermc.paper.logging.SysoutCatcher(); // Paper + + static { + ConfigurationSerialization.registerClass(CraftOfflinePlayer.class); diff --git a/patches/server/0563-Don-t-apply-cramming-damage-to-players.patch b/patches/server/0563-Don-t-apply-cramming-damage-to-players.patch deleted file mode 100644 index f23c0c584fa6..000000000000 --- a/patches/server/0563-Don-t-apply-cramming-damage-to-players.patch +++ /dev/null @@ -1,33 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Phoenix616 -Date: Sun, 20 Jun 2021 16:35:42 +0100 -Subject: [PATCH] Don't apply cramming damage to players - -It does not make a lot of sense to damage players if they get crammed, - especially as the usecase of teleporting lots of players to the same - location isn't too uncommon and killing all those players isn't - really what one would expect to happen. - -For those who really want it a config option is provided. - -diff --git a/src/main/java/net/minecraft/server/level/ServerPlayer.java b/src/main/java/net/minecraft/server/level/ServerPlayer.java -index 9982940af7d10ca7799e2c21ac994ea3afa0b805..d1b21afe48dbe1e53d4a046434336be580497165 100644 ---- a/src/main/java/net/minecraft/server/level/ServerPlayer.java -+++ b/src/main/java/net/minecraft/server/level/ServerPlayer.java -@@ -95,6 +95,7 @@ import net.minecraft.util.Mth; - import net.minecraft.util.RandomSource; - import net.minecraft.util.Unit; - import net.minecraft.world.damagesource.DamageSource; -+import net.minecraft.world.damagesource.DamageSources; - import net.minecraft.world.effect.MobEffectInstance; - import net.minecraft.world.effect.MobEffects; - import net.minecraft.world.entity.Entity; -@@ -1545,7 +1546,7 @@ public class ServerPlayer extends net.minecraft.world.entity.player.Player { - - @Override - public boolean isInvulnerableTo(DamageSource damageSource) { -- return super.isInvulnerableTo(damageSource) || this.isChangingDimension(); -+ return super.isInvulnerableTo(damageSource) || this.isChangingDimension() || !this.level().paperConfig().collisions.allowPlayerCrammingDamage && damageSource == damageSources().cramming(); // Paper - disable player cramming - } - - @Override diff --git a/patches/server/0564-Prevent-AFK-kick-while-watching-end-credits.patch b/patches/server/0564-Prevent-AFK-kick-while-watching-end-credits.patch new file mode 100644 index 000000000000..dc3eed68eb14 --- /dev/null +++ b/patches/server/0564-Prevent-AFK-kick-while-watching-end-credits.patch @@ -0,0 +1,19 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Noah van der Aa +Date: Sat, 24 Jul 2021 16:54:11 +0200 +Subject: [PATCH] Prevent AFK kick while watching end credits + + +diff --git a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java +index 0229b3e6c27b142ff726de8e2e15104a6acbc1f0..70b891bd018029eda8cda4fb9f919e77524dbc5e 100644 +--- a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java ++++ b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java +@@ -396,7 +396,7 @@ public class ServerGamePacketListenerImpl extends ServerCommonPacketListenerImpl + this.tabSpamThrottler.tick(); // Paper - configurable tab spam limits + this.recipeSpamPackets.tick(); // Paper - auto recipe limit + this.dropSpamThrottler.tick(); +- if (this.player.getLastActionTime() > 0L && this.server.getPlayerIdleTimeout() > 0 && Util.getMillis() - this.player.getLastActionTime() > (long) this.server.getPlayerIdleTimeout() * 1000L * 60L) { ++ if (this.player.getLastActionTime() > 0L && this.server.getPlayerIdleTimeout() > 0 && Util.getMillis() - this.player.getLastActionTime() > (long) this.server.getPlayerIdleTimeout() * 1000L * 60L && !this.player.wonGame) { // Paper - Prevent AFK kick while watching end credits + this.player.resetLastActionTime(); // CraftBukkit - SPIGOT-854 + this.disconnect((Component) Component.translatable("multiplayer.disconnect.idling"), org.bukkit.event.player.PlayerKickEvent.Cause.IDLING); // Paper - kick event cause + } diff --git a/patches/server/0564-Rate-options-and-timings-for-sensors-and-behaviors.patch b/patches/server/0564-Rate-options-and-timings-for-sensors-and-behaviors.patch deleted file mode 100644 index d74af14524dc..000000000000 --- a/patches/server/0564-Rate-options-and-timings-for-sensors-and-behaviors.patch +++ /dev/null @@ -1,134 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Phoenix616 -Date: Mon, 28 Jun 2021 22:38:29 +0100 -Subject: [PATCH] Rate options and timings for sensors and behaviors - -This adds config options to specify the tick rate for sensors - and behaviors of different entity types as well as timings - for those in order to be able to have some metrics as to which - ones might need tweaking. - -diff --git a/src/main/java/co/aikar/timings/MinecraftTimings.java b/src/main/java/co/aikar/timings/MinecraftTimings.java -index 4bd813161a5d76a83cdbd0a9209b9ea9e60ffe1b..e2764186bd6b838ed5cd86c15597a08d079ef984 100644 ---- a/src/main/java/co/aikar/timings/MinecraftTimings.java -+++ b/src/main/java/co/aikar/timings/MinecraftTimings.java -@@ -115,6 +115,14 @@ public final class MinecraftTimings { - return Timings.ofSafe("Minecraft", "## tickEntity - " + entityType + " - " + type, tickEntityTimer); - } - -+ public static Timing getBehaviorTimings(String type) { -+ return Timings.ofSafe("## Behavior - " + type); -+ } -+ -+ public static Timing getSensorTimings(String type, int rate) { -+ return Timings.ofSafe("## Sensor - " + type + " (Default rate: " + rate + ")"); -+ } -+ - /** - * Get a named timer for the specified tile entity type to track type specific timings. - * @param entity -diff --git a/src/main/java/net/minecraft/world/entity/ai/behavior/Behavior.java b/src/main/java/net/minecraft/world/entity/ai/behavior/Behavior.java -index f639cafa64d98a001e622882c647701547f5c3ac..9379dd4056018b52c93ed4888dcdc94579bd9691 100644 ---- a/src/main/java/net/minecraft/world/entity/ai/behavior/Behavior.java -+++ b/src/main/java/net/minecraft/world/entity/ai/behavior/Behavior.java -@@ -14,6 +14,10 @@ public abstract class Behavior implements BehaviorContro - private long endTimestamp; - private final int minDuration; - private final int maxDuration; -+ // Paper start - configurable behavior tick rate and timings -+ private final String configKey; -+ private final co.aikar.timings.Timing timing; -+ // Paper end - configurable behavior tick rate and timings - - public Behavior(Map, MemoryStatus> requiredMemoryState) { - this(requiredMemoryState, 60); -@@ -27,6 +31,15 @@ public abstract class Behavior implements BehaviorContro - this.minDuration = minRunTime; - this.maxDuration = maxRunTime; - this.entryCondition = requiredMemoryState; -+ // Paper start - configurable behavior tick rate and timings -+ String key = io.papermc.paper.util.MappingEnvironment.reobf() ? io.papermc.paper.util.ObfHelper.INSTANCE.deobfClassName(this.getClass().getName()) : this.getClass().getName(); -+ int lastSeparator = key.lastIndexOf('.'); -+ if (lastSeparator != -1) { -+ key = key.substring(lastSeparator + 1); -+ } -+ this.configKey = key.toLowerCase(java.util.Locale.ROOT); -+ this.timing = co.aikar.timings.MinecraftTimings.getBehaviorTimings(configKey); -+ // Paper end - configurable behavior tick rate and timings - } - - @Override -@@ -36,11 +49,19 @@ public abstract class Behavior implements BehaviorContro - - @Override - public final boolean tryStart(ServerLevel world, E entity, long time) { -+ // Paper start - configurable behavior tick rate and timings -+ int tickRate = java.util.Objects.requireNonNullElse(world.paperConfig().tickRates.behavior.get(entity.getType(), this.configKey), -1); -+ if (tickRate > -1 && time < this.endTimestamp + tickRate) { -+ return false; -+ } -+ // Paper end - configurable behavior tick rate and timings - if (this.hasRequiredMemories(entity) && this.checkExtraStartConditions(world, entity)) { - this.status = Behavior.Status.RUNNING; - int i = this.minDuration + world.getRandom().nextInt(this.maxDuration + 1 - this.minDuration); - this.endTimestamp = time + (long)i; -+ this.timing.startTiming(); // Paper - behavior timings - this.start(world, entity, time); -+ this.timing.stopTiming(); // Paper - behavior timings - return true; - } else { - return false; -@@ -52,11 +73,13 @@ public abstract class Behavior implements BehaviorContro - - @Override - public final void tickOrStop(ServerLevel world, E entity, long time) { -+ this.timing.startTiming(); // Paper - behavior timings - if (!this.timedOut(time) && this.canStillUse(world, entity, time)) { - this.tick(world, entity, time); - } else { - this.doStop(world, entity, time); - } -+ this.timing.stopTiming(); // Paper - behavior timings - } - - protected void tick(ServerLevel world, E entity, long time) { -diff --git a/src/main/java/net/minecraft/world/entity/ai/sensing/Sensor.java b/src/main/java/net/minecraft/world/entity/ai/sensing/Sensor.java -index 671fc7725d7c801a2ba009da5bd1bc1a9530f187..85b4b24361e785acf75571ff98f924c00ae80748 100644 ---- a/src/main/java/net/minecraft/world/entity/ai/sensing/Sensor.java -+++ b/src/main/java/net/minecraft/world/entity/ai/sensing/Sensor.java -@@ -26,8 +26,21 @@ public abstract class Sensor { - .ignoreInvisibilityTesting(); - private final int scanRate; - private long timeToTick; -+ // Paper start - configurable sensor tick rate and timings -+ private final String configKey; -+ private final co.aikar.timings.Timing timing; -+ // Paper end - - public Sensor(int senseInterval) { -+ // Paper start - configurable sensor tick rate and timings -+ String key = io.papermc.paper.util.MappingEnvironment.reobf() ? io.papermc.paper.util.ObfHelper.INSTANCE.deobfClassName(this.getClass().getName()) : this.getClass().getName(); -+ int lastSeparator = key.lastIndexOf('.'); -+ if (lastSeparator != -1) { -+ key = key.substring(lastSeparator + 1); -+ } -+ this.configKey = key.toLowerCase(java.util.Locale.ROOT); -+ this.timing = co.aikar.timings.MinecraftTimings.getSensorTimings(configKey, senseInterval); -+ // Paper end - this.scanRate = senseInterval; - this.timeToTick = (long)RANDOM.nextInt(senseInterval); - } -@@ -38,8 +51,12 @@ public abstract class Sensor { - - public final void tick(ServerLevel world, E entity) { - if (--this.timeToTick <= 0L) { -- this.timeToTick = (long)this.scanRate; -+ // Paper start - configurable sensor tick rate and timings -+ this.timeToTick = java.util.Objects.requireNonNullElse(world.paperConfig().tickRates.sensor.get(entity.getType(), this.configKey), this.scanRate); -+ this.timing.startTiming(); -+ // Paper end - this.doTick(world, entity); -+ this.timing.stopTiming(); // Paper - sensor timings - } - } - diff --git a/patches/server/0565-Add-missing-forceDrop-toggles.patch b/patches/server/0565-Add-missing-forceDrop-toggles.patch deleted file mode 100644 index e11380293a31..000000000000 --- a/patches/server/0565-Add-missing-forceDrop-toggles.patch +++ /dev/null @@ -1,110 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Jake Potrebic -Date: Tue, 20 Jul 2021 21:25:35 -0700 -Subject: [PATCH] Add missing forceDrop toggles - - -diff --git a/src/main/java/net/minecraft/world/entity/ai/behavior/WorkAtComposter.java b/src/main/java/net/minecraft/world/entity/ai/behavior/WorkAtComposter.java -index b9c2b41d9c46c871bab44cfb1d454f4141f1627b..d975b349aa81327c6b6c23e83e9552159217f11e 100644 ---- a/src/main/java/net/minecraft/world/entity/ai/behavior/WorkAtComposter.java -+++ b/src/main/java/net/minecraft/world/entity/ai/behavior/WorkAtComposter.java -@@ -86,7 +86,9 @@ public class WorkAtComposter extends WorkAtPoi { - simpleContainer.removeItemType(Items.WHEAT, m); - ItemStack itemStack = simpleContainer.addItem(new ItemStack(Items.BREAD, l)); - if (!itemStack.isEmpty()) { -+ entity.forceDrops = true; // Paper - Add missing forceDrop toggles - entity.spawnAtLocation(itemStack, 0.5F); -+ entity.forceDrops = false; // Paper - Add missing forceDrop toggles - } - } - } -diff --git a/src/main/java/net/minecraft/world/entity/animal/Panda.java b/src/main/java/net/minecraft/world/entity/animal/Panda.java -index 8df42121aa22ec9f95a1b8627b64b0ff71e36314..7b3d5322611990406028e59b1409907291e27b21 100644 ---- a/src/main/java/net/minecraft/world/entity/animal/Panda.java -+++ b/src/main/java/net/minecraft/world/entity/animal/Panda.java -@@ -554,11 +554,13 @@ public class Panda extends Animal { - List list1 = loottable.getRandomItems(lootparams); - Iterator iterator1 = list1.iterator(); - -+ this.forceDrops = true; // Paper - Add missing forceDrop toggles - while (iterator1.hasNext()) { - ItemStack itemstack = (ItemStack) iterator1.next(); - - this.spawnAtLocation(itemstack); - } -+ this.forceDrops = false; // Paper - Add missing forceDrop toggles - } - - } -@@ -682,7 +684,9 @@ public class Panda extends Animal { - ItemStack itemstack1 = this.getItemBySlot(EquipmentSlot.MAINHAND); - - if (!itemstack1.isEmpty() && !player.hasInfiniteMaterials()) { -+ this.forceDrops = true; // Paper - Add missing forceDrop toggles - this.spawnAtLocation(itemstack1); -+ this.forceDrops = false; // Paper - Add missing forceDrop toggles - } - - this.setItemSlot(EquipmentSlot.MAINHAND, new ItemStack(itemstack.getItem(), 1)); -@@ -959,7 +963,9 @@ public class Panda extends Animal { - ItemStack itemstack = Panda.this.getItemBySlot(EquipmentSlot.MAINHAND); - - if (!itemstack.isEmpty()) { -+ Panda.this.forceDrops = true; // Paper - Add missing forceDrop toggles - Panda.this.spawnAtLocation(itemstack); -+ Panda.this.forceDrops = false; // Paper - Add missing forceDrop toggles - Panda.this.setItemSlot(EquipmentSlot.MAINHAND, ItemStack.EMPTY); - int i = Panda.this.isLazy() ? Panda.this.random.nextInt(50) + 10 : Panda.this.random.nextInt(150) + 10; - -diff --git a/src/main/java/net/minecraft/world/entity/monster/piglin/Piglin.java b/src/main/java/net/minecraft/world/entity/monster/piglin/Piglin.java -index 691d23bcd3e34a89e14c2e124595e076325dedbc..d2dfa49e124460f4762b950f9ded106d2ec15dc2 100644 ---- a/src/main/java/net/minecraft/world/entity/monster/piglin/Piglin.java -+++ b/src/main/java/net/minecraft/world/entity/monster/piglin/Piglin.java -@@ -310,7 +310,9 @@ public class Piglin extends AbstractPiglin implements CrossbowAttackMob, Invento - @Override - protected void finishConversion(ServerLevel world) { - PiglinAi.cancelAdmiring(this); -+ this.forceDrops = true; // Paper - Add missing forceDrop toggles - this.inventory.removeAllItems().forEach(this::spawnAtLocation); -+ this.forceDrops = false; // Paper - Add missing forceDrop toggles - super.finishConversion(world); - } - -diff --git a/src/main/java/net/minecraft/world/entity/monster/piglin/PiglinAi.java b/src/main/java/net/minecraft/world/entity/monster/piglin/PiglinAi.java -index 545e20e558d3bb934ec4bf32847c9fd83edfd85e..3ca643747535bf7b71e5877ca47f730a2aca4ba5 100644 ---- a/src/main/java/net/minecraft/world/entity/monster/piglin/PiglinAi.java -+++ b/src/main/java/net/minecraft/world/entity/monster/piglin/PiglinAi.java -@@ -271,7 +271,9 @@ public class PiglinAi { - - private static void holdInOffhand(Piglin piglin, ItemStack stack) { - if (PiglinAi.isHoldingItemInOffHand(piglin)) { -+ piglin.forceDrops = true; // Paper - Add missing forceDrop toggles - piglin.spawnAtLocation(piglin.getItemInHand(InteractionHand.OFF_HAND)); -+ piglin.forceDrops = false; // Paper - Add missing forceDrop toggles - } - - piglin.holdInOffHand(stack); -@@ -331,7 +333,9 @@ public class PiglinAi { - - protected static void cancelAdmiring(Piglin piglin) { - if (PiglinAi.isAdmiringItem(piglin) && !piglin.getOffhandItem().isEmpty()) { -+ piglin.forceDrops = true; // Paper - Add missing forceDrop toggles - piglin.spawnAtLocation(piglin.getOffhandItem()); -+ piglin.forceDrops = false; // Paper - Add missing forceDrop toggles - piglin.setItemInHand(InteractionHand.OFF_HAND, ItemStack.EMPTY); - } - -diff --git a/src/main/java/net/minecraft/world/entity/raid/Raider.java b/src/main/java/net/minecraft/world/entity/raid/Raider.java -index f9bd6f5e54bdb4e2fe4cc73e54961721f440ef07..174d246b0a4d0fc9d769aad08da627ca8487bdf2 100644 ---- a/src/main/java/net/minecraft/world/entity/raid/Raider.java -+++ b/src/main/java/net/minecraft/world/entity/raid/Raider.java -@@ -230,7 +230,9 @@ public abstract class Raider extends PatrollingMonster { - double d0 = (double) this.getEquipmentDropChance(enumitemslot); - - if (!itemstack1.isEmpty() && (double) Math.max(this.random.nextFloat() - 0.1F, 0.0F) < d0) { -+ this.forceDrops = true; // Paper - Add missing forceDrop toggles - this.spawnAtLocation(itemstack1); -+ this.forceDrops = false; // Paper - Add missing forceDrop toggles - } - - this.onItemPickup(item); diff --git a/patches/server/0569-Allow-skipping-writing-of-comments-to-server.propert.patch b/patches/server/0565-Allow-skipping-writing-of-comments-to-server.propert.patch similarity index 100% rename from patches/server/0569-Allow-skipping-writing-of-comments-to-server.propert.patch rename to patches/server/0565-Allow-skipping-writing-of-comments-to-server.propert.patch diff --git a/patches/server/0566-Add-PlayerSetSpawnEvent.patch b/patches/server/0566-Add-PlayerSetSpawnEvent.patch new file mode 100644 index 000000000000..e6b0fc435a7f --- /dev/null +++ b/patches/server/0566-Add-PlayerSetSpawnEvent.patch @@ -0,0 +1,204 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Jake Potrebic +Date: Wed, 19 May 2021 18:59:10 -0700 +Subject: [PATCH] Add PlayerSetSpawnEvent + + +diff --git a/src/main/java/net/minecraft/server/commands/SetSpawnCommand.java b/src/main/java/net/minecraft/server/commands/SetSpawnCommand.java +index a2d0699e8427b2262a2396495111125eccafbb66..15db9368227dbc29d07d74e85bd126b345b526b6 100644 +--- a/src/main/java/net/minecraft/server/commands/SetSpawnCommand.java ++++ b/src/main/java/net/minecraft/server/commands/SetSpawnCommand.java +@@ -38,24 +38,34 @@ public class SetSpawnCommand { + ResourceKey resourcekey = source.getLevel().dimension(); + Iterator iterator = targets.iterator(); + ++ final Collection actualTargets = new java.util.ArrayList<>(); // Paper - Add PlayerSetSpawnEvent + while (iterator.hasNext()) { + ServerPlayer entityplayer = (ServerPlayer) iterator.next(); + +- entityplayer.setRespawnPosition(resourcekey, pos, angle, true, false, org.bukkit.event.player.PlayerSpawnChangeEvent.Cause.COMMAND); // CraftBukkit ++ // Paper start - Add PlayerSetSpawnEvent ++ if (entityplayer.setRespawnPosition(resourcekey, pos, angle, true, false, com.destroystokyo.paper.event.player.PlayerSetSpawnEvent.Cause.COMMAND)) { ++ actualTargets.add(entityplayer); ++ } ++ // Paper end - Add PlayerSetSpawnEvent + } ++ // Paper start - Add PlayerSetSpawnEvent ++ if (actualTargets.isEmpty()) { ++ return 0; ++ } ++ // Paper end - Add PlayerSetSpawnEvent + + String s = resourcekey.location().toString(); + +- if (targets.size() == 1) { ++ if (actualTargets.size() == 1) { // Paper - Add PlayerSetSpawnEvent + source.sendSuccess(() -> { +- return Component.translatable("commands.spawnpoint.success.single", pos.getX(), pos.getY(), pos.getZ(), angle, s, ((ServerPlayer) targets.iterator().next()).getDisplayName()); ++ return Component.translatable("commands.spawnpoint.success.single", pos.getX(), pos.getY(), pos.getZ(), angle, s, ((ServerPlayer) actualTargets.iterator().next()).getDisplayName()); // Paper - Add PlayerSetSpawnEvent + }, true); + } else { + source.sendSuccess(() -> { +- return Component.translatable("commands.spawnpoint.success.multiple", pos.getX(), pos.getY(), pos.getZ(), angle, s, targets.size()); ++ return Component.translatable("commands.spawnpoint.success.multiple", pos.getX(), pos.getY(), pos.getZ(), angle, s, actualTargets.size()); // Paper - Add PlayerSetSpawnEvent + }, true); + } + +- return targets.size(); ++ return actualTargets.size(); // Paper - Add PlayerSetSpawnEvent + } + } +diff --git a/src/main/java/net/minecraft/server/level/ServerPlayer.java b/src/main/java/net/minecraft/server/level/ServerPlayer.java +index 94cc69ed1ccbcfcc8f431762fef641c313b2f634..c680b311760601bb539d685bceddba6712d141d4 100644 +--- a/src/main/java/net/minecraft/server/level/ServerPlayer.java ++++ b/src/main/java/net/minecraft/server/level/ServerPlayer.java +@@ -1690,7 +1690,7 @@ public class ServerPlayer extends net.minecraft.world.entity.player.Player { + } else if (this.bedBlocked(blockposition, enumdirection)) { + return Either.left(net.minecraft.world.entity.player.Player.BedSleepingProblem.OBSTRUCTED); + } else { +- this.setRespawnPosition(this.level().dimension(), blockposition, this.getYRot(), false, true, PlayerSpawnChangeEvent.Cause.BED); // CraftBukkit ++ this.setRespawnPosition(this.level().dimension(), blockposition, this.getYRot(), false, true, com.destroystokyo.paper.event.player.PlayerSetSpawnEvent.Cause.BED); // Paper - Add PlayerSetSpawnEvent + if (this.level().isDay()) { + return Either.left(net.minecraft.world.entity.player.Player.BedSleepingProblem.NOT_POSSIBLE_NOW); + } else { +@@ -2640,44 +2640,50 @@ public class ServerPlayer extends net.minecraft.world.entity.player.Player { + this.setRespawnPosition(player.getRespawnDimension(), player.getRespawnPosition(), player.getRespawnAngle(), player.isRespawnForced(), false); + } + ++ @Deprecated // Paper - Add PlayerSetSpawnEvent + public void setRespawnPosition(ResourceKey dimension, @Nullable BlockPos pos, float angle, boolean forced, boolean sendMessage) { +- // CraftBukkit start +- this.setRespawnPosition(dimension, pos, angle, forced, sendMessage, PlayerSpawnChangeEvent.Cause.UNKNOWN); +- } +- +- public void setRespawnPosition(ResourceKey resourcekey, @Nullable BlockPos blockposition, float f, boolean flag, boolean flag1, PlayerSpawnChangeEvent.Cause cause) { +- ServerLevel newWorld = this.server.getLevel(resourcekey); +- Location newSpawn = (blockposition != null) ? CraftLocation.toBukkit(blockposition, newWorld.getWorld(), f, 0) : null; +- +- PlayerSpawnChangeEvent event = new PlayerSpawnChangeEvent(this.getBukkitEntity(), newSpawn, flag, cause); +- Bukkit.getServer().getPluginManager().callEvent(event); +- if (event.isCancelled()) { +- return; +- } +- newSpawn = event.getNewSpawn(); +- flag = event.isForced(); +- +- if (newSpawn != null) { +- resourcekey = ((CraftWorld) newSpawn.getWorld()).getHandle().dimension(); +- blockposition = BlockPos.containing(newSpawn.getX(), newSpawn.getY(), newSpawn.getZ()); +- f = newSpawn.getYaw(); +- } else { +- resourcekey = Level.OVERWORLD; +- blockposition = null; +- f = 0.0F; ++ // Paper start - Add PlayerSetSpawnEvent ++ this.setRespawnPosition(dimension, pos, angle, forced, sendMessage, com.destroystokyo.paper.event.player.PlayerSetSpawnEvent.Cause.UNKNOWN); ++ } ++ @Deprecated ++ public boolean setRespawnPosition(ResourceKey dimension, @Nullable BlockPos pos, float angle, boolean forced, boolean sendMessage, PlayerSpawnChangeEvent.Cause cause) { ++ return this.setRespawnPosition(dimension, pos, angle, forced, sendMessage, cause == PlayerSpawnChangeEvent.Cause.RESET ? ++ com.destroystokyo.paper.event.player.PlayerSetSpawnEvent.Cause.PLAYER_RESPAWN : com.destroystokyo.paper.event.player.PlayerSetSpawnEvent.Cause.valueOf(cause.name())); ++ } ++ public boolean setRespawnPosition(ResourceKey dimension, @Nullable BlockPos pos, float angle, boolean forced, boolean sendMessage, com.destroystokyo.paper.event.player.PlayerSetSpawnEvent.Cause cause) { ++ Location spawnLoc = null; ++ boolean willNotify = false; ++ if (pos != null) { ++ boolean flag2 = pos.equals(this.respawnPosition) && dimension.equals(this.respawnDimension); ++ spawnLoc = io.papermc.paper.util.MCUtil.toLocation(this.getServer().getLevel(dimension), pos); ++ spawnLoc.setYaw(angle); ++ willNotify = sendMessage && !flag2; ++ } ++ ++ PlayerSpawnChangeEvent dumbEvent = new PlayerSpawnChangeEvent(this.getBukkitEntity(), spawnLoc, forced, ++ cause == com.destroystokyo.paper.event.player.PlayerSetSpawnEvent.Cause.PLAYER_RESPAWN ? PlayerSpawnChangeEvent.Cause.RESET : PlayerSpawnChangeEvent.Cause.valueOf(cause.name())); ++ dumbEvent.callEvent(); ++ ++ com.destroystokyo.paper.event.player.PlayerSetSpawnEvent event = new com.destroystokyo.paper.event.player.PlayerSetSpawnEvent(this.getBukkitEntity(), cause, dumbEvent.getNewSpawn(), dumbEvent.isForced(), willNotify, willNotify ? net.kyori.adventure.text.Component.translatable("block.minecraft.set_spawn") : null); ++ event.setCancelled(dumbEvent.isCancelled()); ++ if (!event.callEvent()) { ++ return false; + } +- // CraftBukkit end +- if (blockposition != null) { +- boolean flag2 = blockposition.equals(this.respawnPosition) && resourcekey.equals(this.respawnDimension); ++ if (event.getLocation() != null) { ++ dimension = event.getLocation().getWorld() != null ? ((CraftWorld) event.getLocation().getWorld()).getHandle().dimension() : dimension; ++ pos = io.papermc.paper.util.MCUtil.toBlockPosition(event.getLocation()); ++ angle = event.getLocation().getYaw(); ++ forced = event.isForced(); ++ // Paper end - Add PlayerSetSpawnEvent + +- if (flag1 && !flag2) { +- this.sendSystemMessage(Component.translatable("block.minecraft.set_spawn")); ++ if (event.willNotifyPlayer() && event.getNotification() != null) { // Paper - Add PlayerSetSpawnEvent ++ this.sendSystemMessage(PaperAdventure.asVanilla(event.getNotification())); // Paper - Add PlayerSetSpawnEvent + } + +- this.respawnPosition = blockposition; +- this.respawnDimension = resourcekey; +- this.respawnAngle = f; +- this.respawnForced = flag; ++ this.respawnPosition = pos; ++ this.respawnDimension = dimension; ++ this.respawnAngle = angle; ++ this.respawnForced = forced; + } else { + this.respawnPosition = null; + this.respawnDimension = Level.OVERWORLD; +@@ -2685,6 +2691,7 @@ public class ServerPlayer extends net.minecraft.world.entity.player.Player { + this.respawnForced = false; + } + ++ return true; // Paper - Add PlayerSetSpawnEvent + } + + public SectionPos getLastSectionPos() { +diff --git a/src/main/java/net/minecraft/server/players/PlayerList.java b/src/main/java/net/minecraft/server/players/PlayerList.java +index 2c810cb5d2fcf1a88423cfe4e063dd810ecaaeb0..a62858d3cd83358f2356c15fc102b99c41969a74 100644 +--- a/src/main/java/net/minecraft/server/players/PlayerList.java ++++ b/src/main/java/net/minecraft/server/players/PlayerList.java +@@ -802,7 +802,7 @@ public abstract class PlayerList { + // CraftBukkit end + if (teleporttransition.missingRespawnBlock()) { + entityplayer1.connection.send(new ClientboundGameEventPacket(ClientboundGameEventPacket.NO_RESPAWN_BLOCK_AVAILABLE, 0.0F)); +- entityplayer1.setRespawnPosition(null, null, 0f, false, false, PlayerSpawnChangeEvent.Cause.RESET); // CraftBukkit - SPIGOT-5988: Clear respawn location when obstructed ++ entityplayer1.setRespawnPosition(null, null, 0f, false, false, com.destroystokyo.paper.event.player.PlayerSetSpawnEvent.Cause.PLAYER_RESPAWN); // CraftBukkit - SPIGOT-5988: Clear respawn location when obstructed // Paper - PlayerSetSpawnEvent + } + + int i = flag ? 1 : 0; +diff --git a/src/main/java/net/minecraft/world/level/block/RespawnAnchorBlock.java b/src/main/java/net/minecraft/world/level/block/RespawnAnchorBlock.java +index db26b5a0464bd6087eeacaf6dd61eba37365df92..9117c035d5a6ff114b028fad3380ceb1fc2b9691 100644 +--- a/src/main/java/net/minecraft/world/level/block/RespawnAnchorBlock.java ++++ b/src/main/java/net/minecraft/world/level/block/RespawnAnchorBlock.java +@@ -88,9 +88,14 @@ public class RespawnAnchorBlock extends Block { + ServerPlayer entityplayer = (ServerPlayer) player; + + if (entityplayer.getRespawnDimension() != world.dimension() || !pos.equals(entityplayer.getRespawnPosition())) { +- entityplayer.setRespawnPosition(world.dimension(), pos, 0.0F, false, true, org.bukkit.event.player.PlayerSpawnChangeEvent.Cause.RESPAWN_ANCHOR); // CraftBukkit ++ if (entityplayer.setRespawnPosition(world.dimension(), pos, 0.0F, false, true, com.destroystokyo.paper.event.player.PlayerSetSpawnEvent.Cause.RESPAWN_ANCHOR)) { // Paper - Add PlayerSetSpawnEvent + world.playSound((Player) null, (double) pos.getX() + 0.5D, (double) pos.getY() + 0.5D, (double) pos.getZ() + 0.5D, SoundEvents.RESPAWN_ANCHOR_SET_SPAWN, SoundSource.BLOCKS, 1.0F, 1.0F); + return InteractionResult.SUCCESS_SERVER; ++ // Paper start - Add PlayerSetSpawnEvent ++ } else { ++ return InteractionResult.FAIL; ++ } ++ // Paper end - Add PlayerSetSpawnEvent + } + } + +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java +index e24d5992cfb89ccc4e727c91918ab1f94131987e..ca13d317a5e1c7b7ccda3fdec63e2146562649f6 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java +@@ -1430,9 +1430,9 @@ public class CraftPlayer extends CraftHumanEntity implements Player { + @Override + public void setRespawnLocation(Location location, boolean override) { + if (location == null) { +- this.getHandle().setRespawnPosition(null, null, 0.0F, override, false, PlayerSpawnChangeEvent.Cause.PLUGIN); ++ this.getHandle().setRespawnPosition(null, null, 0.0F, override, false, com.destroystokyo.paper.event.player.PlayerSetSpawnEvent.Cause.PLUGIN); // Paper - Add PlayerSetSpawnEvent + } else { +- this.getHandle().setRespawnPosition(((CraftWorld) location.getWorld()).getHandle().dimension(), CraftLocation.toBlockPosition(location), location.getYaw(), override, false, PlayerSpawnChangeEvent.Cause.PLUGIN); ++ this.getHandle().setRespawnPosition(((CraftWorld) location.getWorld()).getHandle().dimension(), CraftLocation.toBlockPosition(location), location.getYaw(), override, false, com.destroystokyo.paper.event.player.PlayerSetSpawnEvent.Cause.PLUGIN); // Paper - Add PlayerSetSpawnEvent + } + } + diff --git a/patches/server/0566-Stinger-API.patch b/patches/server/0566-Stinger-API.patch deleted file mode 100644 index d19c93fe47f8..000000000000 --- a/patches/server/0566-Stinger-API.patch +++ /dev/null @@ -1,57 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Owen1212055 <23108066+Owen1212055@users.noreply.github.com> -Date: Tue, 22 Jun 2021 23:15:44 -0400 -Subject: [PATCH] Stinger API - - -diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftLivingEntity.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftLivingEntity.java -index 19fb8acf614da707f49d922e520e4be93237b2cc..efac0d3ed78c621a52f905b5d7f267b4fb180e65 100644 ---- a/src/main/java/org/bukkit/craftbukkit/entity/CraftLivingEntity.java -+++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftLivingEntity.java -@@ -362,6 +362,11 @@ public class CraftLivingEntity extends CraftEntity implements LivingEntity { - } - // Paper end - } -+ // Paper start - Bee Stinger API -+ @Override -+ public int getBeeStingerCooldown() { -+ return getHandle().removeStingerTime; -+ } - - // Paper start - Add methods for working with arrows stuck in living entities - @Override -@@ -376,6 +381,34 @@ public class CraftLivingEntity extends CraftEntity implements LivingEntity { - } - // Paper end - Add methods for working with arrows stuck in living entities - -+ @Override -+ public void setBeeStingerCooldown(int ticks) { -+ getHandle().removeStingerTime = ticks; -+ } -+ -+ @Override -+ public int getBeeStingersInBody() { -+ return getHandle().getStingerCount(); -+ } -+ -+ @Override -+ public void setBeeStingersInBody(int count) { -+ Preconditions.checkArgument(count >= 0, "New bee stinger amount must be >= 0"); -+ getHandle().setStingerCount(count); -+ } -+ -+ @Override -+ public void setNextBeeStingerRemoval(final int ticks) { -+ Preconditions.checkArgument(ticks >= 0, "New amount of ticks before next bee stinger removal must be >= 0"); -+ this.getHandle().removeStingerTime = ticks; -+ } -+ -+ @Override -+ public int getNextBeeStingerRemoval() { -+ return this.getHandle().removeStingerTime; -+ } -+ // Paper end - Bee Stinger API -+ - @Override - public void damage(double amount) { - this.damage(amount, this.getHandle().damageSources().generic()); diff --git a/patches/server/0567-Add-System.out-err-catcher.patch b/patches/server/0567-Add-System.out-err-catcher.patch deleted file mode 100644 index 139b410ef1f8..000000000000 --- a/patches/server/0567-Add-System.out-err-catcher.patch +++ /dev/null @@ -1,118 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: underscore11code -Date: Fri, 23 Jul 2021 23:01:42 -0700 -Subject: [PATCH] Add System.out/err catcher - - -diff --git a/src/main/java/io/papermc/paper/logging/SysoutCatcher.java b/src/main/java/io/papermc/paper/logging/SysoutCatcher.java -new file mode 100644 -index 0000000000000000000000000000000000000000..a8e813ca89b033f061e695288b3383bdcf128531 ---- /dev/null -+++ b/src/main/java/io/papermc/paper/logging/SysoutCatcher.java -@@ -0,0 +1,94 @@ -+package io.papermc.paper.logging; -+ -+import org.bukkit.Bukkit; -+import org.bukkit.plugin.java.JavaPlugin; -+import org.jetbrains.annotations.NotNull; -+import org.jetbrains.annotations.Nullable; -+ -+import java.io.OutputStream; -+import java.io.PrintStream; -+import java.util.Objects; -+import java.util.concurrent.ConcurrentHashMap; -+import java.util.concurrent.ConcurrentMap; -+import java.util.concurrent.TimeUnit; -+import java.util.logging.Level; -+ -+public final class SysoutCatcher { -+ private static final boolean SUPPRESS_NAGS = Boolean.getBoolean("io.papermc.paper.suppress.sout.nags"); -+ // Nanoseconds between nag at most; if interval is caught first, this is reset. -+ // <= 0 for disabling. -+ private static final long NAG_TIMEOUT = TimeUnit.MILLISECONDS.toNanos( -+ Long.getLong("io.papermc.paper.sout.nags.timeout", TimeUnit.MINUTES.toMillis(5L))); -+ // Count since last nag; if timeout is first, this is reset. -+ // <= 0 for disabling. -+ private static final long NAG_INTERVAL = Long.getLong("io.papermc.paper.sout.nags.interval", 200L); -+ -+ // We don't particularly care about how correct this is at any given moment; let's do it on a best attempt basis. -+ // The records are also pretty small, so let's just go for a size of 64 to start... -+ // -+ // Content: Plugin name => nag object -+ // Why plugin name?: This doesn't store a reference to the plugin; keeps the reload ability. -+ // Why not clean on reload?: Effort. -+ private final ConcurrentMap nagRecords = new ConcurrentHashMap<>(64); -+ -+ public SysoutCatcher() { -+ System.setOut(new WrappedOutStream(System.out, Level.INFO, "[STDOUT] ")); -+ System.setErr(new WrappedOutStream(System.err, Level.SEVERE, "[STDERR] ")); -+ } -+ -+ private final class WrappedOutStream extends PrintStream { -+ private static final StackWalker STACK_WALKER = StackWalker.getInstance(StackWalker.Option.RETAIN_CLASS_REFERENCE); -+ private final Level level; -+ private final String prefix; -+ -+ public WrappedOutStream(@NotNull final OutputStream out, final Level level, final String prefix) { -+ super(out); -+ this.level = level; -+ this.prefix = prefix; -+ } -+ -+ @Override -+ public void println(@Nullable final String line) { -+ final Class clazz = STACK_WALKER.getCallerClass(); -+ try { -+ final JavaPlugin plugin = JavaPlugin.getProvidingPlugin(clazz); -+ -+ // Instead of just printing the message, send it to the plugin's logger -+ plugin.getLogger().log(this.level, this.prefix + line); -+ -+ if (SysoutCatcher.SUPPRESS_NAGS) { -+ return; -+ } -+ if (SysoutCatcher.NAG_INTERVAL > 0 || SysoutCatcher.NAG_TIMEOUT > 0) { -+ final PluginNag nagRecord = SysoutCatcher.this.nagRecords.computeIfAbsent(plugin.getName(), k -> new PluginNag()); -+ final boolean hasTimePassed = SysoutCatcher.NAG_TIMEOUT > 0 -+ && (nagRecord.lastNagTimestamp == Long.MIN_VALUE -+ || nagRecord.lastNagTimestamp + SysoutCatcher.NAG_TIMEOUT <= System.nanoTime()); -+ final boolean hasMessagesPassed = SysoutCatcher.NAG_INTERVAL > 0 -+ && (nagRecord.messagesSinceNag == Long.MIN_VALUE -+ || ++nagRecord.messagesSinceNag >= SysoutCatcher.NAG_INTERVAL); -+ if (!hasMessagesPassed && !hasTimePassed) { -+ return; -+ } -+ nagRecord.lastNagTimestamp = System.nanoTime(); -+ nagRecord.messagesSinceNag = 0; -+ } -+ Bukkit.getLogger().warning( -+ String.format("Nag author(s): '%s' of '%s' about their usage of System.out/err.print. " -+ + "Please use your plugin's logger instead (JavaPlugin#getLogger).", -+ plugin.getPluginMeta().getAuthors(), -+ plugin.getPluginMeta().getDisplayName()) -+ ); -+ } catch (final IllegalArgumentException | IllegalStateException e) { -+ // If anything happens, the calling class doesn't exist, there is no JavaPlugin that "owns" the calling class, etc -+ // Just print out normally, with some added information -+ Bukkit.getLogger().log(this.level, String.format("%s[%s] %s", this.prefix, clazz.getName(), line)); -+ } -+ } -+ } -+ -+ private static class PluginNag { -+ private long lastNagTimestamp = Long.MIN_VALUE; -+ private long messagesSinceNag = Long.MIN_VALUE; -+ } -+} -diff --git a/src/main/java/org/bukkit/craftbukkit/CraftServer.java b/src/main/java/org/bukkit/craftbukkit/CraftServer.java -index 1b320ab17379da9b82320e63babb0f395d4b85a8..e09b0a624a80216db5b6f7882c3765f2eb967b06 100644 ---- a/src/main/java/org/bukkit/craftbukkit/CraftServer.java -+++ b/src/main/java/org/bukkit/craftbukkit/CraftServer.java -@@ -307,6 +307,7 @@ public final class CraftServer implements Server { - public Set activeCompatibilities = Collections.emptySet(); - private final io.papermc.paper.datapack.PaperDatapackManager datapackManager; // Paper - public static Exception excessiveVelEx; // Paper - Velocity warnings -+ private final io.papermc.paper.logging.SysoutCatcher sysoutCatcher = new io.papermc.paper.logging.SysoutCatcher(); // Paper - - static { - ConfigurationSerialization.registerClass(CraftOfflinePlayer.class); diff --git a/patches/server/0567-Make-hoppers-respect-inventory-max-stack-size.patch b/patches/server/0567-Make-hoppers-respect-inventory-max-stack-size.patch new file mode 100644 index 000000000000..fc01c5cad413 --- /dev/null +++ b/patches/server/0567-Make-hoppers-respect-inventory-max-stack-size.patch @@ -0,0 +1,30 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Jake Potrebic +Date: Wed, 7 Jul 2021 16:30:17 -0700 +Subject: [PATCH] Make hoppers respect inventory max stack size + + +diff --git a/src/main/java/net/minecraft/world/level/block/entity/HopperBlockEntity.java b/src/main/java/net/minecraft/world/level/block/entity/HopperBlockEntity.java +index 8913f434967457a16dd708252834ba001ada1a03..b35b44f0776aeb95ef0eda666ba0b652c5e90274 100644 +--- a/src/main/java/net/minecraft/world/level/block/entity/HopperBlockEntity.java ++++ b/src/main/java/net/minecraft/world/level/block/entity/HopperBlockEntity.java +@@ -495,15 +495,17 @@ public class HopperBlockEntity extends RandomizableContainerBlockEntity implemen + + if (itemstack1.isEmpty()) { + // Spigot start - SPIGOT-6693, InventorySubcontainer#setItem ++ ItemStack leftover = ItemStack.EMPTY; // Paper - Make hoppers respect inventory max stack size + if (!stack.isEmpty() && stack.getCount() > to.getMaxStackSize()) { ++ leftover = stack; // Paper - Make hoppers respect inventory max stack size + stack = stack.split(to.getMaxStackSize()); + } + // Spigot end + to.setItem(slot, stack); +- stack = ItemStack.EMPTY; ++ stack = leftover; // Paper - Make hoppers respect inventory max stack size + flag = true; + } else if (HopperBlockEntity.canMergeItems(itemstack1, stack)) { +- int j = stack.getMaxStackSize() - itemstack1.getCount(); ++ int j = Math.min(stack.getMaxStackSize(), to.getMaxStackSize()) - itemstack1.getCount(); // Paper - Make hoppers respect inventory max stack size + int k = Math.min(stack.getCount(), j); + + stack.shrink(k); diff --git a/patches/server/0568-Optimize-entity-tracker-passenger-checks.patch b/patches/server/0568-Optimize-entity-tracker-passenger-checks.patch new file mode 100644 index 000000000000..0b928beb6190 --- /dev/null +++ b/patches/server/0568-Optimize-entity-tracker-passenger-checks.patch @@ -0,0 +1,19 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Andrew Steinborn +Date: Sun, 8 Aug 2021 00:52:54 -0400 +Subject: [PATCH] Optimize entity tracker passenger checks + + +diff --git a/src/main/java/net/minecraft/server/level/ServerEntity.java b/src/main/java/net/minecraft/server/level/ServerEntity.java +index 52fcf6df392aaa795335ee9bb7ae406009e2fda1..cfb58901476b1f9bbffd205e82c92d39d587afab 100644 +--- a/src/main/java/net/minecraft/server/level/ServerEntity.java ++++ b/src/main/java/net/minecraft/server/level/ServerEntity.java +@@ -75,7 +75,7 @@ public class ServerEntity { + private Vec3 lastSentMovement; + private int tickCount; + private int teleportDelay; +- private List lastPassengers = Collections.emptyList(); ++ private List lastPassengers = com.google.common.collect.ImmutableList.of(); // Paper - optimize passenger checks + private boolean wasRiding; + private boolean wasOnGround; + @Nullable diff --git a/patches/server/0568-Prevent-AFK-kick-while-watching-end-credits.patch b/patches/server/0568-Prevent-AFK-kick-while-watching-end-credits.patch deleted file mode 100644 index 42e8c37d20ec..000000000000 --- a/patches/server/0568-Prevent-AFK-kick-while-watching-end-credits.patch +++ /dev/null @@ -1,19 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Noah van der Aa -Date: Sat, 24 Jul 2021 16:54:11 +0200 -Subject: [PATCH] Prevent AFK kick while watching end credits - - -diff --git a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java -index 08c4d0b79dc1c6e51105487b96e9333a5dd904d7..44d1cd3e6047590a989a2b85c759be4c64125a47 100644 ---- a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java -+++ b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java -@@ -397,7 +397,7 @@ public class ServerGamePacketListenerImpl extends ServerCommonPacketListenerImpl - --this.dropSpamTickCount; - } - -- if (this.player.getLastActionTime() > 0L && this.server.getPlayerIdleTimeout() > 0 && Util.getMillis() - this.player.getLastActionTime() > (long) this.server.getPlayerIdleTimeout() * 1000L * 60L) { -+ if (this.player.getLastActionTime() > 0L && this.server.getPlayerIdleTimeout() > 0 && Util.getMillis() - this.player.getLastActionTime() > (long) this.server.getPlayerIdleTimeout() * 1000L * 60L && !this.player.wonGame) { // Paper - Prevent AFK kick while watching end credits - this.player.resetLastActionTime(); // CraftBukkit - SPIGOT-854 - this.disconnect((Component) Component.translatable("multiplayer.disconnect.idling"), org.bukkit.event.player.PlayerKickEvent.Cause.IDLING); // Paper - kick event cause - } diff --git a/patches/server/0569-Config-option-for-Piglins-guarding-chests.patch b/patches/server/0569-Config-option-for-Piglins-guarding-chests.patch new file mode 100644 index 000000000000..32c61a7ec024 --- /dev/null +++ b/patches/server/0569-Config-option-for-Piglins-guarding-chests.patch @@ -0,0 +1,18 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Jason Penilla <11360596+jpenilla@users.noreply.github.com> +Date: Wed, 2 Dec 2020 03:07:58 -0800 +Subject: [PATCH] Config option for Piglins guarding chests + + +diff --git a/src/main/java/net/minecraft/world/entity/monster/piglin/PiglinAi.java b/src/main/java/net/minecraft/world/entity/monster/piglin/PiglinAi.java +index 55868c82bf8bd61ce3494aa9f363c20c88ec6aa6..4f3048615a34fc2c067e09aec76af94cde6a74e0 100644 +--- a/src/main/java/net/minecraft/world/entity/monster/piglin/PiglinAi.java ++++ b/src/main/java/net/minecraft/world/entity/monster/piglin/PiglinAi.java +@@ -478,6 +478,7 @@ public class PiglinAi { + } + + public static void angerNearbyPiglins(ServerLevel world, Player player, boolean blockOpen) { ++ if (!player.level().paperConfig().entities.behavior.piglinsGuardChests) return; // Paper - Config option for Piglins guarding chests + List list = player.level().getEntitiesOfClass(Piglin.class, player.getBoundingBox().inflate(16.0D)); + + list.stream().filter(PiglinAi::isIdle).filter((entitypiglin) -> { diff --git a/patches/server/0570-Add-EntityDamageItemEvent.patch b/patches/server/0570-Add-EntityDamageItemEvent.patch new file mode 100644 index 000000000000..e6822b715e6b --- /dev/null +++ b/patches/server/0570-Add-EntityDamageItemEvent.patch @@ -0,0 +1,94 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Jake Potrebic +Date: Tue, 22 Dec 2020 13:52:48 -0800 +Subject: [PATCH] Add EntityDamageItemEvent + + +diff --git a/src/main/java/net/minecraft/world/item/ItemStack.java b/src/main/java/net/minecraft/world/item/ItemStack.java +index f00b756fe5dad616323e3b11e35e27353f347042..8c9ae9ac38def29ae4cd8944395e566e434d46d0 100644 +--- a/src/main/java/net/minecraft/world/item/ItemStack.java ++++ b/src/main/java/net/minecraft/world/item/ItemStack.java +@@ -692,11 +692,11 @@ public final class ItemStack implements DataComponentHolder { + return this.isDamageableItem() && this.getDamageValue() >= this.getMaxDamage() - 1; + } + +- public void hurtAndBreak(int amount, ServerLevel world, @Nullable ServerPlayer player, Consumer breakCallback) { ++ public void hurtAndBreak(int amount, ServerLevel world, @Nullable LivingEntity player, Consumer breakCallback) { // Paper - Add EntityDamageItemEvent + int j = this.processDurabilityChange(amount, world, player); + // CraftBukkit start +- if (player != null) { +- PlayerItemDamageEvent event = new PlayerItemDamageEvent(player.getBukkitEntity(), CraftItemStack.asCraftMirror(this), j); ++ if (player instanceof final ServerPlayer serverPlayer) { // Paper - Add EntityDamageItemEvent ++ PlayerItemDamageEvent event = new PlayerItemDamageEvent(serverPlayer.getBukkitEntity(), CraftItemStack.asCraftMirror(this), j); // Paper - Add EntityDamageItemEvent + event.getPlayer().getServer().getPluginManager().callEvent(event); + + if (j != event.getDamage() || event.isCancelled()) { +@@ -707,6 +707,14 @@ public final class ItemStack implements DataComponentHolder { + } + + j = event.getDamage(); ++ // Paper start - Add EntityDamageItemEvent ++ } else if (player != null) { ++ io.papermc.paper.event.entity.EntityDamageItemEvent event = new io.papermc.paper.event.entity.EntityDamageItemEvent(player.getBukkitLivingEntity(), CraftItemStack.asCraftMirror(this), amount); ++ if (!event.callEvent()) { ++ return; ++ } ++ j = event.getDamage(); ++ // Paper end - Add EntityDamageItemEvent + } + // CraftBukkit end + +@@ -716,21 +724,21 @@ public final class ItemStack implements DataComponentHolder { + + } + +- private int processDurabilityChange(int baseDamage, ServerLevel world, @Nullable ServerPlayer player) { +- return !this.isDamageableItem() ? 0 : (player != null && player.hasInfiniteMaterials() ? 0 : (baseDamage > 0 ? EnchantmentHelper.processDurabilityChange(world, this, baseDamage) : baseDamage)); ++ private int processDurabilityChange(int baseDamage, ServerLevel world, @Nullable LivingEntity player) { // Paper - Add EntityDamageItemEvent ++ return !this.isDamageableItem() ? 0 : (player instanceof ServerPlayer && player.hasInfiniteMaterials() ? 0 : (baseDamage > 0 ? EnchantmentHelper.processDurabilityChange(world, this, baseDamage) : baseDamage)); // Paper - Add EntityDamageItemEvent + } + +- private void applyDamage(int damage, @Nullable ServerPlayer player, Consumer breakCallback) { +- if (player != null) { +- CriteriaTriggers.ITEM_DURABILITY_CHANGED.trigger(player, this, damage); ++ private void applyDamage(int damage, @Nullable LivingEntity player, Consumer breakCallback) { // Paper - Add EntityDamageItemEvent ++ if (player instanceof final ServerPlayer serverPlayer) { // Paper - Add EntityDamageItemEvent ++ CriteriaTriggers.ITEM_DURABILITY_CHANGED.trigger(serverPlayer, this, damage); // Paper - Add EntityDamageItemEvent + } + + this.setDamageValue(damage); + if (this.isBroken()) { + Item item = this.getItem(); + // CraftBukkit start - Check for item breaking +- if (this.count == 1 && player != null) { +- org.bukkit.craftbukkit.event.CraftEventFactory.callPlayerItemBreakEvent(player, this); ++ if (this.count == 1 && player instanceof final ServerPlayer serverPlayer) { // Paper - Add EntityDamageItemEvent ++ org.bukkit.craftbukkit.event.CraftEventFactory.callPlayerItemBreakEvent(serverPlayer, this); // Paper - Add EntityDamageItemEvent + } + // CraftBukkit end + +@@ -768,7 +776,7 @@ public final class ItemStack implements DataComponentHolder { + entityplayer = null; + } + +- this.hurtAndBreak(amount, worldserver, entityplayer, (item) -> { ++ this.hurtAndBreak(amount, worldserver, entity, (item) -> { // Paper - Add EntityDamageItemEvent + entity.onEquippedItemBroken(item, slot); + }); + } +diff --git a/src/main/java/net/minecraft/world/item/enchantment/effects/ChangeItemDamage.java b/src/main/java/net/minecraft/world/item/enchantment/effects/ChangeItemDamage.java +index 0019c8548aa4901b248ced32cc1475ad14b725bf..7e21c8b10debd70c35a138c14b9b177ef6fff37a 100644 +--- a/src/main/java/net/minecraft/world/item/enchantment/effects/ChangeItemDamage.java ++++ b/src/main/java/net/minecraft/world/item/enchantment/effects/ChangeItemDamage.java +@@ -21,9 +21,9 @@ public record ChangeItemDamage(LevelBasedValue amount) implements EnchantmentEnt + public void apply(ServerLevel world, int level, EnchantedItemInUse context, Entity user, Vec3 pos) { + ItemStack itemStack = context.itemStack(); + if (itemStack.has(DataComponents.MAX_DAMAGE) && itemStack.has(DataComponents.DAMAGE)) { +- ServerPlayer serverPlayer2 = context.owner() instanceof ServerPlayer serverPlayer ? serverPlayer : null; ++ // ServerPlayer serverPlayer2 = context.owner() instanceof ServerPlayer serverPlayer ? serverPlayer : null; // Paper - EntityDamageItemEvent - always pass in entity + int i = (int)this.amount.calculate(level); +- itemStack.hurtAndBreak(i, world, serverPlayer2, context.onBreak()); ++ itemStack.hurtAndBreak(i, world, context.owner(), context.onBreak()); // Paper - EntityDamageItemEvent - always pass in entity + } + } + diff --git a/patches/server/0570-Add-PlayerSetSpawnEvent.patch b/patches/server/0570-Add-PlayerSetSpawnEvent.patch deleted file mode 100644 index 28e265e4064c..000000000000 --- a/patches/server/0570-Add-PlayerSetSpawnEvent.patch +++ /dev/null @@ -1,204 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Jake Potrebic -Date: Wed, 19 May 2021 18:59:10 -0700 -Subject: [PATCH] Add PlayerSetSpawnEvent - - -diff --git a/src/main/java/net/minecraft/server/commands/SetSpawnCommand.java b/src/main/java/net/minecraft/server/commands/SetSpawnCommand.java -index a2d0699e8427b2262a2396495111125eccafbb66..15db9368227dbc29d07d74e85bd126b345b526b6 100644 ---- a/src/main/java/net/minecraft/server/commands/SetSpawnCommand.java -+++ b/src/main/java/net/minecraft/server/commands/SetSpawnCommand.java -@@ -38,24 +38,34 @@ public class SetSpawnCommand { - ResourceKey resourcekey = source.getLevel().dimension(); - Iterator iterator = targets.iterator(); - -+ final Collection actualTargets = new java.util.ArrayList<>(); // Paper - Add PlayerSetSpawnEvent - while (iterator.hasNext()) { - ServerPlayer entityplayer = (ServerPlayer) iterator.next(); - -- entityplayer.setRespawnPosition(resourcekey, pos, angle, true, false, org.bukkit.event.player.PlayerSpawnChangeEvent.Cause.COMMAND); // CraftBukkit -+ // Paper start - Add PlayerSetSpawnEvent -+ if (entityplayer.setRespawnPosition(resourcekey, pos, angle, true, false, com.destroystokyo.paper.event.player.PlayerSetSpawnEvent.Cause.COMMAND)) { -+ actualTargets.add(entityplayer); -+ } -+ // Paper end - Add PlayerSetSpawnEvent - } -+ // Paper start - Add PlayerSetSpawnEvent -+ if (actualTargets.isEmpty()) { -+ return 0; -+ } -+ // Paper end - Add PlayerSetSpawnEvent - - String s = resourcekey.location().toString(); - -- if (targets.size() == 1) { -+ if (actualTargets.size() == 1) { // Paper - Add PlayerSetSpawnEvent - source.sendSuccess(() -> { -- return Component.translatable("commands.spawnpoint.success.single", pos.getX(), pos.getY(), pos.getZ(), angle, s, ((ServerPlayer) targets.iterator().next()).getDisplayName()); -+ return Component.translatable("commands.spawnpoint.success.single", pos.getX(), pos.getY(), pos.getZ(), angle, s, ((ServerPlayer) actualTargets.iterator().next()).getDisplayName()); // Paper - Add PlayerSetSpawnEvent - }, true); - } else { - source.sendSuccess(() -> { -- return Component.translatable("commands.spawnpoint.success.multiple", pos.getX(), pos.getY(), pos.getZ(), angle, s, targets.size()); -+ return Component.translatable("commands.spawnpoint.success.multiple", pos.getX(), pos.getY(), pos.getZ(), angle, s, actualTargets.size()); // Paper - Add PlayerSetSpawnEvent - }, true); - } - -- return targets.size(); -+ return actualTargets.size(); // Paper - Add PlayerSetSpawnEvent - } - } -diff --git a/src/main/java/net/minecraft/server/level/ServerPlayer.java b/src/main/java/net/minecraft/server/level/ServerPlayer.java -index d1b21afe48dbe1e53d4a046434336be580497165..2dd10cada8d36ed5565481f3f5a5fba168ba9b26 100644 ---- a/src/main/java/net/minecraft/server/level/ServerPlayer.java -+++ b/src/main/java/net/minecraft/server/level/ServerPlayer.java -@@ -1422,7 +1422,7 @@ public class ServerPlayer extends net.minecraft.world.entity.player.Player { - } else if (this.bedBlocked(blockposition, enumdirection)) { - return Either.left(net.minecraft.world.entity.player.Player.BedSleepingProblem.OBSTRUCTED); - } else { -- this.setRespawnPosition(this.level().dimension(), blockposition, this.getYRot(), false, true, PlayerSpawnChangeEvent.Cause.BED); // CraftBukkit -+ this.setRespawnPosition(this.level().dimension(), blockposition, this.getYRot(), false, true, com.destroystokyo.paper.event.player.PlayerSetSpawnEvent.Cause.BED); // Paper - Add PlayerSetSpawnEvent - if (this.level().isDay()) { - return Either.left(net.minecraft.world.entity.player.Player.BedSleepingProblem.NOT_POSSIBLE_NOW); - } else { -@@ -2399,44 +2399,50 @@ public class ServerPlayer extends net.minecraft.world.entity.player.Player { - this.setRespawnPosition(player.getRespawnDimension(), player.getRespawnPosition(), player.getRespawnAngle(), player.isRespawnForced(), false); - } - -+ @Deprecated // Paper - Add PlayerSetSpawnEvent - public void setRespawnPosition(ResourceKey dimension, @Nullable BlockPos pos, float angle, boolean forced, boolean sendMessage) { -- // CraftBukkit start -- this.setRespawnPosition(dimension, pos, angle, forced, sendMessage, PlayerSpawnChangeEvent.Cause.UNKNOWN); -- } -- -- public void setRespawnPosition(ResourceKey resourcekey, @Nullable BlockPos blockposition, float f, boolean flag, boolean flag1, PlayerSpawnChangeEvent.Cause cause) { -- ServerLevel newWorld = this.server.getLevel(resourcekey); -- Location newSpawn = (blockposition != null) ? CraftLocation.toBukkit(blockposition, newWorld.getWorld(), f, 0) : null; -- -- PlayerSpawnChangeEvent event = new PlayerSpawnChangeEvent(this.getBukkitEntity(), newSpawn, flag, cause); -- Bukkit.getServer().getPluginManager().callEvent(event); -- if (event.isCancelled()) { -- return; -- } -- newSpawn = event.getNewSpawn(); -- flag = event.isForced(); -- -- if (newSpawn != null) { -- resourcekey = ((CraftWorld) newSpawn.getWorld()).getHandle().dimension(); -- blockposition = BlockPos.containing(newSpawn.getX(), newSpawn.getY(), newSpawn.getZ()); -- f = newSpawn.getYaw(); -- } else { -- resourcekey = Level.OVERWORLD; -- blockposition = null; -- f = 0.0F; -+ // Paper start - Add PlayerSetSpawnEvent -+ this.setRespawnPosition(dimension, pos, angle, forced, sendMessage, com.destroystokyo.paper.event.player.PlayerSetSpawnEvent.Cause.UNKNOWN); -+ } -+ @Deprecated -+ public boolean setRespawnPosition(ResourceKey dimension, @Nullable BlockPos pos, float angle, boolean forced, boolean sendMessage, PlayerSpawnChangeEvent.Cause cause) { -+ return this.setRespawnPosition(dimension, pos, angle, forced, sendMessage, cause == PlayerSpawnChangeEvent.Cause.RESET ? -+ com.destroystokyo.paper.event.player.PlayerSetSpawnEvent.Cause.PLAYER_RESPAWN : com.destroystokyo.paper.event.player.PlayerSetSpawnEvent.Cause.valueOf(cause.name())); -+ } -+ public boolean setRespawnPosition(ResourceKey dimension, @Nullable BlockPos pos, float angle, boolean forced, boolean sendMessage, com.destroystokyo.paper.event.player.PlayerSetSpawnEvent.Cause cause) { -+ Location spawnLoc = null; -+ boolean willNotify = false; -+ if (pos != null) { -+ boolean flag2 = pos.equals(this.respawnPosition) && dimension.equals(this.respawnDimension); -+ spawnLoc = io.papermc.paper.util.MCUtil.toLocation(this.getServer().getLevel(dimension), pos); -+ spawnLoc.setYaw(angle); -+ willNotify = sendMessage && !flag2; -+ } -+ -+ PlayerSpawnChangeEvent dumbEvent = new PlayerSpawnChangeEvent(this.getBukkitEntity(), spawnLoc, forced, -+ cause == com.destroystokyo.paper.event.player.PlayerSetSpawnEvent.Cause.PLAYER_RESPAWN ? PlayerSpawnChangeEvent.Cause.RESET : PlayerSpawnChangeEvent.Cause.valueOf(cause.name())); -+ dumbEvent.callEvent(); -+ -+ com.destroystokyo.paper.event.player.PlayerSetSpawnEvent event = new com.destroystokyo.paper.event.player.PlayerSetSpawnEvent(this.getBukkitEntity(), cause, dumbEvent.getNewSpawn(), dumbEvent.isForced(), willNotify, willNotify ? net.kyori.adventure.text.Component.translatable("block.minecraft.set_spawn") : null); -+ event.setCancelled(dumbEvent.isCancelled()); -+ if (!event.callEvent()) { -+ return false; - } -- // CraftBukkit end -- if (blockposition != null) { -- boolean flag2 = blockposition.equals(this.respawnPosition) && resourcekey.equals(this.respawnDimension); -+ if (event.getLocation() != null) { -+ dimension = event.getLocation().getWorld() != null ? ((CraftWorld) event.getLocation().getWorld()).getHandle().dimension() : dimension; -+ pos = io.papermc.paper.util.MCUtil.toBlockPosition(event.getLocation()); -+ angle = event.getLocation().getYaw(); -+ forced = event.isForced(); -+ // Paper end - Add PlayerSetSpawnEvent - -- if (flag1 && !flag2) { -- this.sendSystemMessage(Component.translatable("block.minecraft.set_spawn")); -+ if (event.willNotifyPlayer() && event.getNotification() != null) { // Paper - Add PlayerSetSpawnEvent -+ this.sendSystemMessage(PaperAdventure.asVanilla(event.getNotification())); // Paper - Add PlayerSetSpawnEvent - } - -- this.respawnPosition = blockposition; -- this.respawnDimension = resourcekey; -- this.respawnAngle = f; -- this.respawnForced = flag; -+ this.respawnPosition = pos; -+ this.respawnDimension = dimension; -+ this.respawnAngle = angle; -+ this.respawnForced = forced; - } else { - this.respawnPosition = null; - this.respawnDimension = Level.OVERWORLD; -@@ -2444,6 +2450,7 @@ public class ServerPlayer extends net.minecraft.world.entity.player.Player { - this.respawnForced = false; - } - -+ return true; // Paper - Add PlayerSetSpawnEvent - } - - public SectionPos getLastSectionPos() { -diff --git a/src/main/java/net/minecraft/server/players/PlayerList.java b/src/main/java/net/minecraft/server/players/PlayerList.java -index 2b0eed116327e74ff66aa065e42899e74b90abf1..c7dc335d6da208c224d94256eab11b2c3c63745f 100644 ---- a/src/main/java/net/minecraft/server/players/PlayerList.java -+++ b/src/main/java/net/minecraft/server/players/PlayerList.java -@@ -845,7 +845,7 @@ public abstract class PlayerList { - // CraftBukkit end - if (dimensiontransition.missingRespawnBlock()) { - entityplayer1.connection.send(new ClientboundGameEventPacket(ClientboundGameEventPacket.NO_RESPAWN_BLOCK_AVAILABLE, 0.0F)); -- entityplayer1.setRespawnPosition(null, null, 0f, false, false, PlayerSpawnChangeEvent.Cause.RESET); // CraftBukkit - SPIGOT-5988: Clear respawn location when obstructed -+ entityplayer1.setRespawnPosition(null, null, 0f, false, false, com.destroystokyo.paper.event.player.PlayerSetSpawnEvent.Cause.PLAYER_RESPAWN); // CraftBukkit - SPIGOT-5988: Clear respawn location when obstructed // Paper - PlayerSetSpawnEvent - } - - int i = flag ? 1 : 0; -diff --git a/src/main/java/net/minecraft/world/level/block/RespawnAnchorBlock.java b/src/main/java/net/minecraft/world/level/block/RespawnAnchorBlock.java -index ba22ad1e4253477572d10d71db6db0ebc14d6755..94d067e9eeee73183de25165d8c97043fe256103 100644 ---- a/src/main/java/net/minecraft/world/level/block/RespawnAnchorBlock.java -+++ b/src/main/java/net/minecraft/world/level/block/RespawnAnchorBlock.java -@@ -89,9 +89,14 @@ public class RespawnAnchorBlock extends Block { - ServerPlayer entityplayer = (ServerPlayer) player; - - if (entityplayer.getRespawnDimension() != world.dimension() || !pos.equals(entityplayer.getRespawnPosition())) { -- entityplayer.setRespawnPosition(world.dimension(), pos, 0.0F, false, true, org.bukkit.event.player.PlayerSpawnChangeEvent.Cause.RESPAWN_ANCHOR); // CraftBukkit -+ if (entityplayer.setRespawnPosition(world.dimension(), pos, 0.0F, false, true, com.destroystokyo.paper.event.player.PlayerSetSpawnEvent.Cause.RESPAWN_ANCHOR)) { // Paper - Add PlayerSetSpawnEvent - world.playSound((Player) null, (double) pos.getX() + 0.5D, (double) pos.getY() + 0.5D, (double) pos.getZ() + 0.5D, SoundEvents.RESPAWN_ANCHOR_SET_SPAWN, SoundSource.BLOCKS, 1.0F, 1.0F); - return InteractionResult.SUCCESS; -+ // Paper start - Add PlayerSetSpawnEvent -+ } else { -+ return InteractionResult.FAIL; -+ } -+ // Paper end - Add PlayerSetSpawnEvent - } - } - -diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java -index b747df28b862990d5db08329149272c67eb17d94..bc0d63f90e352ad4158d1dcc0f21a34954279095 100644 ---- a/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java -+++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java -@@ -1413,9 +1413,9 @@ public class CraftPlayer extends CraftHumanEntity implements Player { - @Override - public void setRespawnLocation(Location location, boolean override) { - if (location == null) { -- this.getHandle().setRespawnPosition(null, null, 0.0F, override, false, PlayerSpawnChangeEvent.Cause.PLUGIN); -+ this.getHandle().setRespawnPosition(null, null, 0.0F, override, false, com.destroystokyo.paper.event.player.PlayerSetSpawnEvent.Cause.PLUGIN); // Paper - Add PlayerSetSpawnEvent - } else { -- this.getHandle().setRespawnPosition(((CraftWorld) location.getWorld()).getHandle().dimension(), CraftLocation.toBlockPosition(location), location.getYaw(), override, false, PlayerSpawnChangeEvent.Cause.PLUGIN); -+ this.getHandle().setRespawnPosition(((CraftWorld) location.getWorld()).getHandle().dimension(), CraftLocation.toBlockPosition(location), location.getYaw(), override, false, com.destroystokyo.paper.event.player.PlayerSetSpawnEvent.Cause.PLUGIN); // Paper - Add PlayerSetSpawnEvent - } - } - diff --git a/patches/server/0571-Make-hoppers-respect-inventory-max-stack-size.patch b/patches/server/0571-Make-hoppers-respect-inventory-max-stack-size.patch deleted file mode 100644 index c5b4e6692664..000000000000 --- a/patches/server/0571-Make-hoppers-respect-inventory-max-stack-size.patch +++ /dev/null @@ -1,30 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Jake Potrebic -Date: Wed, 7 Jul 2021 16:30:17 -0700 -Subject: [PATCH] Make hoppers respect inventory max stack size - - -diff --git a/src/main/java/net/minecraft/world/level/block/entity/HopperBlockEntity.java b/src/main/java/net/minecraft/world/level/block/entity/HopperBlockEntity.java -index 1c8a08e317591413426285874de74f4de54efa07..542a5501ac94f57810d34e0f769a9a7855604f91 100644 ---- a/src/main/java/net/minecraft/world/level/block/entity/HopperBlockEntity.java -+++ b/src/main/java/net/minecraft/world/level/block/entity/HopperBlockEntity.java -@@ -495,15 +495,17 @@ public class HopperBlockEntity extends RandomizableContainerBlockEntity implemen - - if (itemstack1.isEmpty()) { - // Spigot start - SPIGOT-6693, InventorySubcontainer#setItem -+ ItemStack leftover = ItemStack.EMPTY; // Paper - Make hoppers respect inventory max stack size - if (!stack.isEmpty() && stack.getCount() > to.getMaxStackSize()) { -+ leftover = stack; // Paper - Make hoppers respect inventory max stack size - stack = stack.split(to.getMaxStackSize()); - } - // Spigot end - to.setItem(slot, stack); -- stack = ItemStack.EMPTY; -+ stack = leftover; // Paper - Make hoppers respect inventory max stack size - flag = true; - } else if (HopperBlockEntity.canMergeItems(itemstack1, stack)) { -- int j = stack.getMaxStackSize() - itemstack1.getCount(); -+ int j = Math.min(stack.getMaxStackSize(), to.getMaxStackSize()) - itemstack1.getCount(); // Paper - Make hoppers respect inventory max stack size - int k = Math.min(stack.getCount(), j); - - stack.shrink(k); diff --git a/patches/server/0571-Optimize-indirect-passenger-iteration.patch b/patches/server/0571-Optimize-indirect-passenger-iteration.patch new file mode 100644 index 000000000000..dcb94ae6027f --- /dev/null +++ b/patches/server/0571-Optimize-indirect-passenger-iteration.patch @@ -0,0 +1,53 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Andrew Steinborn +Date: Mon, 9 Aug 2021 00:38:37 -0400 +Subject: [PATCH] Optimize indirect passenger iteration + + +diff --git a/src/main/java/net/minecraft/world/entity/Entity.java b/src/main/java/net/minecraft/world/entity/Entity.java +index 3479b7c89741ba93e49e7e51fe4b45bb14fbc419..fe9cdd104d6203233a90068b55e0876be4964afe 100644 +--- a/src/main/java/net/minecraft/world/entity/Entity.java ++++ b/src/main/java/net/minecraft/world/entity/Entity.java +@@ -4087,20 +4087,34 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess + } + + private Stream getIndirectPassengersStream() { ++ if (this.passengers.isEmpty()) { return Stream.of(); } // Paper - Optimize indirect passenger iteration + return this.passengers.stream().flatMap(Entity::getSelfAndPassengers); + } + + @Override + public Stream getSelfAndPassengers() { ++ if (this.passengers.isEmpty()) { return Stream.of(this); } // Paper - Optimize indirect passenger iteration + return Stream.concat(Stream.of(this), this.getIndirectPassengersStream()); + } + + @Override + public Stream getPassengersAndSelf() { ++ if (this.passengers.isEmpty()) { return Stream.of(this); } // Paper - Optimize indirect passenger iteration + return Stream.concat(this.passengers.stream().flatMap(Entity::getPassengersAndSelf), Stream.of(this)); + } + + public Iterable getIndirectPassengers() { ++ // Paper start - Optimize indirect passenger iteration ++ if (this.passengers.isEmpty()) { return ImmutableList.of(); } ++ ImmutableList.Builder indirectPassengers = ImmutableList.builder(); ++ for (Entity passenger : this.passengers) { ++ indirectPassengers.add(passenger); ++ indirectPassengers.addAll(passenger.getIndirectPassengers()); ++ } ++ return indirectPassengers.build(); ++ } ++ private Iterable getIndirectPassengers_old() { ++ // Paper end - Optimize indirect passenger iteration + return () -> { + return this.getIndirectPassengersStream().iterator(); + }; +@@ -4113,6 +4127,7 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess + } + + public boolean hasExactlyOnePlayerPassenger() { ++ if (this.passengers.isEmpty()) { return false; } // Paper - Optimize indirect passenger iteration + return this.countPlayerPassengers() == 1; + } + diff --git a/patches/server/0572-Configurable-item-frame-map-cursor-update-interval.patch b/patches/server/0572-Configurable-item-frame-map-cursor-update-interval.patch new file mode 100644 index 000000000000..98f717ebc173 --- /dev/null +++ b/patches/server/0572-Configurable-item-frame-map-cursor-update-interval.patch @@ -0,0 +1,19 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Warrior <50800980+Warriorrrr@users.noreply.github.com> +Date: Fri, 13 Aug 2021 01:14:38 +0200 +Subject: [PATCH] Configurable item frame map cursor update interval + + +diff --git a/src/main/java/net/minecraft/server/level/ServerEntity.java b/src/main/java/net/minecraft/server/level/ServerEntity.java +index cfb58901476b1f9bbffd205e82c92d39d587afab..bc0f1aa61e68d2a8638d89c10bc5c71922d057f9 100644 +--- a/src/main/java/net/minecraft/server/level/ServerEntity.java ++++ b/src/main/java/net/minecraft/server/level/ServerEntity.java +@@ -120,7 +120,7 @@ public class ServerEntity { + if (true || this.tickCount % 10 == 0) { // CraftBukkit - Moved below, should always enter this block + ItemStack itemstack = entityitemframe.getItem(); + +- if (this.tickCount % 10 == 0 && itemstack.getItem() instanceof MapItem) { // CraftBukkit - Moved this.tickCounter % 10 logic here so item frames do not enter the other blocks ++ if (this.level.paperConfig().maps.itemFrameCursorUpdateInterval > 0 && this.tickCount % this.level.paperConfig().maps.itemFrameCursorUpdateInterval == 0 && itemstack.getItem() instanceof MapItem) { // CraftBukkit - Moved this.tickCounter % 10 logic here so item frames do not enter the other blocks // Paper - Make item frame map cursor update interval configurable + MapId mapid = (MapId) itemstack.get(DataComponents.MAP_ID); + MapItemSavedData worldmap = MapItem.getSavedData(mapid, this.level); + diff --git a/patches/server/0572-Optimize-entity-tracker-passenger-checks.patch b/patches/server/0572-Optimize-entity-tracker-passenger-checks.patch deleted file mode 100644 index 0dd7d25ba340..000000000000 --- a/patches/server/0572-Optimize-entity-tracker-passenger-checks.patch +++ /dev/null @@ -1,19 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Andrew Steinborn -Date: Sun, 8 Aug 2021 00:52:54 -0400 -Subject: [PATCH] Optimize entity tracker passenger checks - - -diff --git a/src/main/java/net/minecraft/server/level/ServerEntity.java b/src/main/java/net/minecraft/server/level/ServerEntity.java -index 9f04dc7817909d81f387b03bfc57d2d9a1d63478..7e8a43ea1b32f444fb66e270a5f6b48bf7bcd2a0 100644 ---- a/src/main/java/net/minecraft/server/level/ServerEntity.java -+++ b/src/main/java/net/minecraft/server/level/ServerEntity.java -@@ -71,7 +71,7 @@ public class ServerEntity { - private Vec3 lastSentMovement; - private int tickCount; - private int teleportDelay; -- private List lastPassengers = Collections.emptyList(); -+ private List lastPassengers = com.google.common.collect.ImmutableList.of(); // Paper - optimize passenger checks - private boolean wasRiding; - private boolean wasOnGround; - @Nullable diff --git a/patches/server/0573-Change-EnderEye-target-without-changing-other-things.patch b/patches/server/0573-Change-EnderEye-target-without-changing-other-things.patch new file mode 100644 index 000000000000..5612422ea7d8 --- /dev/null +++ b/patches/server/0573-Change-EnderEye-target-without-changing-other-things.patch @@ -0,0 +1,54 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Jake Potrebic +Date: Sat, 21 Aug 2021 12:13:53 -0700 +Subject: [PATCH] Change EnderEye target without changing other things + + +diff --git a/src/main/java/net/minecraft/world/entity/projectile/EyeOfEnder.java b/src/main/java/net/minecraft/world/entity/projectile/EyeOfEnder.java +index 573b96c9e0c89860c3da031c5aa239f6a7ad0c6e..fd1f5de7dc151dfd187d23e022b2c5435ed8accc 100644 +--- a/src/main/java/net/minecraft/world/entity/projectile/EyeOfEnder.java ++++ b/src/main/java/net/minecraft/world/entity/projectile/EyeOfEnder.java +@@ -76,6 +76,11 @@ public class EyeOfEnder extends Entity implements ItemSupplier { + } + + public void signalTo(BlockPos pos) { ++ // Paper start - Change EnderEye target without changing other things ++ this.signalTo(pos, true); ++ } ++ public void signalTo(BlockPos pos, boolean update) { ++ // Paper end - Change EnderEye target without changing other things + double d0 = (double) pos.getX(); + int i = pos.getY(); + double d1 = (double) pos.getZ(); +@@ -93,8 +98,10 @@ public class EyeOfEnder extends Entity implements ItemSupplier { + this.tz = d1; + } + ++ if (update) { // Paper - Change EnderEye target without changing other things + this.life = 0; + this.surviveAfterDeath = this.random.nextInt(5) > 0; ++ } // Paper - Change EnderEye target without changing other things + } + + @Override +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftEnderSignal.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftEnderSignal.java +index d4dfc7a0266086b9bf2c3966c6e149453d0583ba..27f56fa4b7ef92a9a4dfa6b782350424b88210f2 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftEnderSignal.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftEnderSignal.java +@@ -32,8 +32,15 @@ public class CraftEnderSignal extends CraftEntity implements EnderSignal { + + @Override + public void setTargetLocation(Location location) { ++ // Paper start - Change EnderEye target without changing other things ++ this.setTargetLocation(location, true); ++ } ++ ++ @Override ++ public void setTargetLocation(Location location, boolean update) { ++ // Paper end - Change EnderEye target without changing other things + Preconditions.checkArgument(this.getWorld().equals(location.getWorld()), "Cannot target EnderSignal across worlds"); +- this.getHandle().signalTo(CraftLocation.toBlockPosition(location)); ++ this.getHandle().signalTo(CraftLocation.toBlockPosition(location), update); // Paper - Change EnderEye target without changing other things + } + + @Override diff --git a/patches/server/0573-Config-option-for-Piglins-guarding-chests.patch b/patches/server/0573-Config-option-for-Piglins-guarding-chests.patch deleted file mode 100644 index 8e2094a53deb..000000000000 --- a/patches/server/0573-Config-option-for-Piglins-guarding-chests.patch +++ /dev/null @@ -1,18 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Jason Penilla <11360596+jpenilla@users.noreply.github.com> -Date: Wed, 2 Dec 2020 03:07:58 -0800 -Subject: [PATCH] Config option for Piglins guarding chests - - -diff --git a/src/main/java/net/minecraft/world/entity/monster/piglin/PiglinAi.java b/src/main/java/net/minecraft/world/entity/monster/piglin/PiglinAi.java -index 3ca643747535bf7b71e5877ca47f730a2aca4ba5..d601bff8e8f62af78791ad357b51b92faf04e55f 100644 ---- a/src/main/java/net/minecraft/world/entity/monster/piglin/PiglinAi.java -+++ b/src/main/java/net/minecraft/world/entity/monster/piglin/PiglinAi.java -@@ -476,6 +476,7 @@ public class PiglinAi { - } - - public static void angerNearbyPiglins(Player player, boolean blockOpen) { -+ if (!player.level().paperConfig().entities.behavior.piglinsGuardChests) return; // Paper - Config option for Piglins guarding chests - List list = player.level().getEntitiesOfClass(Piglin.class, player.getBoundingBox().inflate(16.0D)); - - list.stream().filter(PiglinAi::isIdle).filter((entitypiglin) -> { diff --git a/patches/server/0574-Add-BlockBreakBlockEvent.patch b/patches/server/0574-Add-BlockBreakBlockEvent.patch new file mode 100644 index 000000000000..fbd3627b5cc5 --- /dev/null +++ b/patches/server/0574-Add-BlockBreakBlockEvent.patch @@ -0,0 +1,87 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Jake Potrebic +Date: Sun, 3 Jan 2021 17:58:11 -0800 +Subject: [PATCH] Add BlockBreakBlockEvent + + +diff --git a/src/main/java/net/minecraft/world/level/block/Block.java b/src/main/java/net/minecraft/world/level/block/Block.java +index f1711f774f844024ca7b678385daaace6cda9f46..9d8c4ecd89b05a0e5d4ebb5e686eba5d899765f2 100644 +--- a/src/main/java/net/minecraft/world/level/block/Block.java ++++ b/src/main/java/net/minecraft/world/level/block/Block.java +@@ -295,6 +295,24 @@ public class Block extends BlockBehaviour implements ItemLike { + + } + ++ // Paper start - Add BlockBreakBlockEvent ++ public static boolean dropResources(BlockState state, LevelAccessor levelAccessor, BlockPos pos, @Nullable BlockEntity blockEntity, BlockPos source) { ++ if (levelAccessor instanceof ServerLevel serverLevel) { ++ List items = new java.util.ArrayList<>(); ++ for (ItemStack drop : Block.getDrops(state, serverLevel, pos, blockEntity)) { ++ items.add(org.bukkit.craftbukkit.inventory.CraftItemStack.asBukkitCopy(drop)); ++ } ++ io.papermc.paper.event.block.BlockBreakBlockEvent event = new io.papermc.paper.event.block.BlockBreakBlockEvent(org.bukkit.craftbukkit.block.CraftBlock.at(levelAccessor, pos), org.bukkit.craftbukkit.block.CraftBlock.at(levelAccessor, source), items); ++ event.callEvent(); ++ for (org.bukkit.inventory.ItemStack drop : event.getDrops()) { ++ popResource(serverLevel, pos, org.bukkit.craftbukkit.inventory.CraftItemStack.asNMSCopy(drop)); ++ } ++ state.spawnAfterBreak(serverLevel, pos, ItemStack.EMPTY, true); ++ } ++ return true; ++ } ++ // Paper end - Add BlockBreakBlockEvent ++ + public static void dropResources(BlockState state, Level world, BlockPos pos, @Nullable BlockEntity blockEntity, @Nullable Entity entity, ItemStack tool) { + if (world instanceof ServerLevel) { + Block.getDrops(state, (ServerLevel) world, pos, blockEntity, entity, tool).forEach((itemstack1) -> { +diff --git a/src/main/java/net/minecraft/world/level/block/piston/PistonBaseBlock.java b/src/main/java/net/minecraft/world/level/block/piston/PistonBaseBlock.java +index 0c6b517196d48ba4384eac240b7e580adfdbc4d4..4973d75b26880e39d42b5ef533896f43a1f07cba 100644 +--- a/src/main/java/net/minecraft/world/level/block/piston/PistonBaseBlock.java ++++ b/src/main/java/net/minecraft/world/level/block/piston/PistonBaseBlock.java +@@ -406,7 +406,7 @@ public class PistonBaseBlock extends DirectionalBlock { + iblockdata1 = world.getBlockState(blockposition3); + BlockEntity tileentity = iblockdata1.hasBlockEntity() ? world.getBlockEntity(blockposition3) : null; + +- dropResources(iblockdata1, world, blockposition3, tileentity); ++ dropResources(iblockdata1, world, blockposition3, tileentity, pos); // Paper - Add BlockBreakBlockEvent + world.setBlock(blockposition3, Blocks.AIR.defaultBlockState(), 18); + world.gameEvent((Holder) GameEvent.BLOCK_DESTROY, blockposition3, GameEvent.Context.of(iblockdata1)); + if (!iblockdata1.is(BlockTags.FIRE)) { +diff --git a/src/main/java/net/minecraft/world/level/material/FlowingFluid.java b/src/main/java/net/minecraft/world/level/material/FlowingFluid.java +index 83dc8bcd9e2b8ecbd32225e4e10aec392ef28325..261e5994d13f8bc30490b86691c80c0a21e7640a 100644 +--- a/src/main/java/net/minecraft/world/level/material/FlowingFluid.java ++++ b/src/main/java/net/minecraft/world/level/material/FlowingFluid.java +@@ -316,7 +316,7 @@ public abstract class FlowingFluid extends Fluid { + ifluidcontainer.placeLiquid(world, pos, state, fluidState); + } else { + if (!state.isAir()) { +- this.beforeDestroyingBlock(world, pos, state); ++ this.beforeDestroyingBlock(world, pos, state, pos.relative(direction.getOpposite())); // Paper - Add BlockBreakBlockEvent + } + + world.setBlock(pos, fluidState.createLegacyBlock(), 3); +@@ -324,6 +324,7 @@ public abstract class FlowingFluid extends Fluid { + + } + ++ protected void beforeDestroyingBlock(LevelAccessor world, BlockPos pos, BlockState state, BlockPos source) { beforeDestroyingBlock(world, pos, state); } // Paper - Add BlockBreakBlockEvent + protected abstract void beforeDestroyingBlock(LevelAccessor world, BlockPos pos, BlockState state); + + protected int getSlopeDistance(LevelReader world, BlockPos pos, int i, Direction direction, BlockState state, FlowingFluid.SpreadContext spreadCache) { +diff --git a/src/main/java/net/minecraft/world/level/material/WaterFluid.java b/src/main/java/net/minecraft/world/level/material/WaterFluid.java +index 421f5d9a57d87a87a801213d562ad5fe244e7b65..0a7c05c08bf8c6c331b91e399dc4103a91dc20fe 100644 +--- a/src/main/java/net/minecraft/world/level/material/WaterFluid.java ++++ b/src/main/java/net/minecraft/world/level/material/WaterFluid.java +@@ -81,6 +81,13 @@ public abstract class WaterFluid extends FlowingFluid { + return world.getGameRules().getBoolean(GameRules.RULE_WATER_SOURCE_CONVERSION); + } + ++ // Paper start - Add BlockBreakBlockEvent ++ @Override ++ protected void beforeDestroyingBlock(LevelAccessor world, BlockPos pos, BlockState state, BlockPos source) { ++ BlockEntity tileentity = state.hasBlockEntity() ? world.getBlockEntity(pos) : null; ++ Block.dropResources(state, world, pos, tileentity, source); ++ } ++ // Paper end - Add BlockBreakBlockEvent + @Override + protected void beforeDestroyingBlock(LevelAccessor world, BlockPos pos, BlockState state) { + BlockEntity blockEntity = state.hasBlockEntity() ? world.getBlockEntity(pos) : null; diff --git a/patches/server/0574-Add-EntityDamageItemEvent.patch b/patches/server/0574-Add-EntityDamageItemEvent.patch deleted file mode 100644 index fc8649b86d92..000000000000 --- a/patches/server/0574-Add-EntityDamageItemEvent.patch +++ /dev/null @@ -1,89 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Jake Potrebic -Date: Tue, 22 Dec 2020 13:52:48 -0800 -Subject: [PATCH] Add EntityDamageItemEvent - - -diff --git a/src/main/java/net/minecraft/world/item/ItemStack.java b/src/main/java/net/minecraft/world/item/ItemStack.java -index d1eac70fbfe2d863d3a342ed0e83223c65c36c03..a98d76c90cd855e723e7a8d810eee88a882d8b5c 100644 ---- a/src/main/java/net/minecraft/world/item/ItemStack.java -+++ b/src/main/java/net/minecraft/world/item/ItemStack.java -@@ -646,14 +646,14 @@ public final class ItemStack implements DataComponentHolder { - return (Integer) this.getOrDefault(DataComponents.MAX_DAMAGE, 0); - } - -- public void hurtAndBreak(int amount, ServerLevel world, @Nullable ServerPlayer player, Consumer breakCallback) { -+ public void hurtAndBreak(int amount, ServerLevel world, @Nullable LivingEntity player, Consumer breakCallback) { // Paper - Add EntityDamageItemEvent - if (this.isDamageableItem()) { - if (player == null || !player.hasInfiniteMaterials()) { - if (amount > 0) { - amount = EnchantmentHelper.processDurabilityChange(world, this, amount); - // CraftBukkit start -- if (player != null) { -- PlayerItemDamageEvent event = new PlayerItemDamageEvent(player.getBukkitEntity(), CraftItemStack.asCraftMirror(this), amount); -+ if (player instanceof ServerPlayer serverPlayer) { // Paper - Add EntityDamageItemEvent -+ PlayerItemDamageEvent event = new PlayerItemDamageEvent(serverPlayer.getBukkitEntity(), CraftItemStack.asCraftMirror(this), amount); // Paper - Add EntityDamageItemEvent - event.getPlayer().getServer().getPluginManager().callEvent(event); - - if (amount != event.getDamage() || event.isCancelled()) { -@@ -664,6 +664,14 @@ public final class ItemStack implements DataComponentHolder { - } - - amount = event.getDamage(); -+ // Paper start - Add EntityDamageItemEvent -+ } else if (player != null) { -+ io.papermc.paper.event.entity.EntityDamageItemEvent event = new io.papermc.paper.event.entity.EntityDamageItemEvent(player.getBukkitLivingEntity(), CraftItemStack.asCraftMirror(this), amount); -+ if (!event.callEvent()) { -+ return; -+ } -+ amount = event.getDamage(); -+ // Paper end - Add EntityDamageItemEvent - } - // CraftBukkit end - if (amount <= 0) { -@@ -671,8 +679,8 @@ public final class ItemStack implements DataComponentHolder { - } - } - -- if (player != null && amount != 0) { -- CriteriaTriggers.ITEM_DURABILITY_CHANGED.trigger(player, this, this.getDamageValue() + amount); -+ if (player instanceof ServerPlayer serverPlayer && amount != 0) { // Paper - Add EntityDamageItemEvent -+ CriteriaTriggers.ITEM_DURABILITY_CHANGED.trigger(serverPlayer, this, this.getDamageValue() + amount); // Paper - Add EntityDamageItemEvent - } - - int j = this.getDamageValue() + amount; -@@ -681,8 +689,8 @@ public final class ItemStack implements DataComponentHolder { - if (j >= this.getMaxDamage()) { - Item item = this.getItem(); - // CraftBukkit start - Check for item breaking -- if (this.count == 1 && player != null) { -- org.bukkit.craftbukkit.event.CraftEventFactory.callPlayerItemBreakEvent(player, this); -+ if (this.count == 1 && player != null && player instanceof final ServerPlayer serverPlayer) { // Paper - Add EntityDamageItemEvent -+ org.bukkit.craftbukkit.event.CraftEventFactory.callPlayerItemBreakEvent(serverPlayer, this); // Paper - Add EntityDamageItemEvent - } - // CraftBukkit end - -@@ -706,7 +714,7 @@ public final class ItemStack implements DataComponentHolder { - entityplayer = null; - } - -- this.hurtAndBreak(amount, worldserver, entityplayer, (item) -> { -+ this.hurtAndBreak(amount, worldserver, entity, (item) -> { // Paper - Add EntityDamageItemEvent - entity.onEquippedItemBroken(item, slot); - }); - } -diff --git a/src/main/java/net/minecraft/world/item/enchantment/effects/DamageItem.java b/src/main/java/net/minecraft/world/item/enchantment/effects/DamageItem.java -index 70796eef426eece0bc93a173f54e90645377b502..82ac989ca836e3beef7c3773db0183a7e51780e0 100644 ---- a/src/main/java/net/minecraft/world/item/enchantment/effects/DamageItem.java -+++ b/src/main/java/net/minecraft/world/item/enchantment/effects/DamageItem.java -@@ -16,8 +16,8 @@ public record DamageItem(LevelBasedValue amount) implements EnchantmentEntityEff - - @Override - public void apply(ServerLevel world, int level, EnchantedItemInUse context, Entity user, Vec3 pos) { -- ServerPlayer serverPlayer2 = context.owner() instanceof ServerPlayer serverPlayer ? serverPlayer : null; -- context.itemStack().hurtAndBreak((int)this.amount.calculate(level), world, serverPlayer2, context.onBreak()); -+ // ServerPlayer serverPlayer2 = context.owner() instanceof ServerPlayer serverPlayer ? serverPlayer : null; // Paper - always pass in entity -+ context.itemStack().hurtAndBreak((int)this.amount.calculate(level), world, context.owner(), context.onBreak()); // Paper - always pass in entity - } - - @Override diff --git a/patches/server/0575-Optimize-indirect-passenger-iteration.patch b/patches/server/0575-Optimize-indirect-passenger-iteration.patch deleted file mode 100644 index dc1758edec89..000000000000 --- a/patches/server/0575-Optimize-indirect-passenger-iteration.patch +++ /dev/null @@ -1,53 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Andrew Steinborn -Date: Mon, 9 Aug 2021 00:38:37 -0400 -Subject: [PATCH] Optimize indirect passenger iteration - - -diff --git a/src/main/java/net/minecraft/world/entity/Entity.java b/src/main/java/net/minecraft/world/entity/Entity.java -index da184893d617311a43f9ce176a965f8417a2876d..9844550e4ed6c150250b165acc26d52ec9401184 100644 ---- a/src/main/java/net/minecraft/world/entity/Entity.java -+++ b/src/main/java/net/minecraft/world/entity/Entity.java -@@ -3889,20 +3889,34 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess - } - - private Stream getIndirectPassengersStream() { -+ if (this.passengers.isEmpty()) { return Stream.of(); } // Paper - Optimize indirect passenger iteration - return this.passengers.stream().flatMap(Entity::getSelfAndPassengers); - } - - @Override - public Stream getSelfAndPassengers() { -+ if (this.passengers.isEmpty()) { return Stream.of(this); } // Paper - Optimize indirect passenger iteration - return Stream.concat(Stream.of(this), this.getIndirectPassengersStream()); - } - - @Override - public Stream getPassengersAndSelf() { -+ if (this.passengers.isEmpty()) { return Stream.of(this); } // Paper - Optimize indirect passenger iteration - return Stream.concat(this.passengers.stream().flatMap(Entity::getPassengersAndSelf), Stream.of(this)); - } - - public Iterable getIndirectPassengers() { -+ // Paper start - Optimize indirect passenger iteration -+ if (this.passengers.isEmpty()) { return ImmutableList.of(); } -+ ImmutableList.Builder indirectPassengers = ImmutableList.builder(); -+ for (Entity passenger : this.passengers) { -+ indirectPassengers.add(passenger); -+ indirectPassengers.addAll(passenger.getIndirectPassengers()); -+ } -+ return indirectPassengers.build(); -+ } -+ private Iterable getIndirectPassengers_old() { -+ // Paper end - Optimize indirect passenger iteration - return () -> { - return this.getIndirectPassengersStream().iterator(); - }; -@@ -3915,6 +3929,7 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess - } - - public boolean hasExactlyOnePlayerPassenger() { -+ if (this.passengers.isEmpty()) { return false; } // Paper - Optimize indirect passenger iteration - return this.countPlayerPassengers() == 1; - } - diff --git a/patches/server/0575-Option-to-prevent-data-components-copy-in-smithing-r.patch b/patches/server/0575-Option-to-prevent-data-components-copy-in-smithing-r.patch new file mode 100644 index 000000000000..1c9a4f5da547 --- /dev/null +++ b/patches/server/0575-Option-to-prevent-data-components-copy-in-smithing-r.patch @@ -0,0 +1,157 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Jake Potrebic +Date: Sun, 26 Sep 2021 12:57:28 -0700 +Subject: [PATCH] Option to prevent data components copy in smithing recipes + + +diff --git a/src/main/java/net/minecraft/world/item/crafting/SmithingTransformRecipe.java b/src/main/java/net/minecraft/world/item/crafting/SmithingTransformRecipe.java +index fa003ce16020eaab554bfd833ace779c8cefc617..017748b26590364d846a5cddaa3490b2293ad276 100644 +--- a/src/main/java/net/minecraft/world/item/crafting/SmithingTransformRecipe.java ++++ b/src/main/java/net/minecraft/world/item/crafting/SmithingTransformRecipe.java +@@ -30,8 +30,15 @@ public class SmithingTransformRecipe implements SmithingRecipe { + final ItemStack result; + @Nullable + private PlacementInfo placementInfo; ++ final boolean copyDataComponents; // Paper - Option to prevent data components copy + + public SmithingTransformRecipe(Optional template, Optional base, Optional addition, ItemStack result) { ++ // Paper start - Option to prevent data components copy ++ this(template, base, addition, result, true); ++ } ++ public SmithingTransformRecipe(Optional template, Optional base, Optional addition, ItemStack result, boolean copyDataComponents) { ++ this.copyDataComponents = copyDataComponents; ++ // Paper end - Option to prevent data components copy + this.template = template; + this.base = base; + this.addition = addition; +@@ -41,7 +48,9 @@ public class SmithingTransformRecipe implements SmithingRecipe { + public ItemStack assemble(SmithingRecipeInput input, HolderLookup.Provider registries) { + ItemStack itemstack = input.base().transmuteCopy(this.result.getItem(), this.result.getCount()); + ++ if (this.copyDataComponents) { // Paper - Option to prevent data components copy + itemstack.applyComponents(this.result.getComponentsPatch()); ++ } // Paper - Option to prevent data components copy + return itemstack; + } + +@@ -84,7 +93,7 @@ public class SmithingTransformRecipe implements SmithingRecipe { + public Recipe toBukkitRecipe(NamespacedKey id) { + CraftItemStack result = CraftItemStack.asCraftMirror(this.result); + +- CraftSmithingTransformRecipe recipe = new CraftSmithingTransformRecipe(id, result, CraftRecipe.toBukkit(this.template), CraftRecipe.toBukkit(this.base), CraftRecipe.toBukkit(this.addition)); ++ CraftSmithingTransformRecipe recipe = new CraftSmithingTransformRecipe(id, result, CraftRecipe.toBukkit(this.template), CraftRecipe.toBukkit(this.base), CraftRecipe.toBukkit(this.addition), this.copyDataComponents); // Paper - Option to prevent data components copy + + return recipe; + } +diff --git a/src/main/java/net/minecraft/world/item/crafting/SmithingTrimRecipe.java b/src/main/java/net/minecraft/world/item/crafting/SmithingTrimRecipe.java +index a5adce474b2e78233c39cbed367e3d14515923b1..7209170454f10225d7d4a4a107e6717fc18a02ad 100644 +--- a/src/main/java/net/minecraft/world/item/crafting/SmithingTrimRecipe.java ++++ b/src/main/java/net/minecraft/world/item/crafting/SmithingTrimRecipe.java +@@ -35,18 +35,28 @@ public class SmithingTrimRecipe implements SmithingRecipe { + final Optional addition; + @Nullable + private PlacementInfo placementInfo; ++ final boolean copyDataComponents; // Paper - Option to prevent data components copy + + public SmithingTrimRecipe(Optional template, Optional base, Optional addition) { ++ // Paper start - Option to prevent data components copy ++ this(template, base, addition, true); ++ } ++ public SmithingTrimRecipe(Optional template, Optional base, Optional addition, boolean copyDataComponents) { ++ this.copyDataComponents = copyDataComponents; ++ // Paper end - Option to prevent data components copy + this.template = template; + this.base = base; + this.addition = addition; + } + + public ItemStack assemble(SmithingRecipeInput input, HolderLookup.Provider registries) { +- return SmithingTrimRecipe.applyTrim(registries, input.base(), input.addition(), input.template()); ++ return SmithingTrimRecipe.applyTrim(registries, input.base(), input.addition(), input.template(), this.copyDataComponents); + } + + public static ItemStack applyTrim(HolderLookup.Provider registries, ItemStack base, ItemStack addition, ItemStack template) { ++ return applyTrim(registries, base, addition, template, true); ++ } ++ public static ItemStack applyTrim(HolderLookup.Provider registries, ItemStack base, ItemStack addition, ItemStack template, boolean copyDataComponents) { + Optional> optional = TrimMaterials.getFromIngredient(registries, addition); + Optional> optional1 = TrimPatterns.getFromTemplate(registries, template); + +@@ -56,7 +66,7 @@ public class SmithingTrimRecipe implements SmithingRecipe { + if (armortrim != null && armortrim.hasPatternAndMaterial((Holder) optional1.get(), (Holder) optional.get())) { + return ItemStack.EMPTY; + } else { +- ItemStack itemstack3 = base.copyWithCount(1); ++ ItemStack itemstack3 = copyDataComponents ? base.copyWithCount(1) : new ItemStack(base.getItem(), 1); // Paper - Option to prevent data components copy + + itemstack3.set(DataComponents.TRIM, new ArmorTrim((Holder) optional.get(), (Holder) optional1.get())); + return itemstack3; +@@ -107,7 +117,7 @@ public class SmithingTrimRecipe implements SmithingRecipe { + // CraftBukkit start + @Override + public Recipe toBukkitRecipe(NamespacedKey id) { +- return new CraftSmithingTrimRecipe(id, CraftRecipe.toBukkit(this.template), CraftRecipe.toBukkit(this.base), CraftRecipe.toBukkit(this.addition)); ++ return new CraftSmithingTrimRecipe(id, CraftRecipe.toBukkit(this.template), CraftRecipe.toBukkit(this.base), CraftRecipe.toBukkit(this.addition), this.copyDataComponents); // Paper - Option to prevent data components copy + } + // CraftBukkit end + +diff --git a/src/main/java/org/bukkit/craftbukkit/inventory/CraftSmithingTransformRecipe.java b/src/main/java/org/bukkit/craftbukkit/inventory/CraftSmithingTransformRecipe.java +index 0dc2be8f502a50f13d8fe860c209ebfa43a931ea..af6c1ccdf2b91b1284daee5552eb44cc9a34cd5f 100644 +--- a/src/main/java/org/bukkit/craftbukkit/inventory/CraftSmithingTransformRecipe.java ++++ b/src/main/java/org/bukkit/craftbukkit/inventory/CraftSmithingTransformRecipe.java +@@ -11,12 +11,17 @@ public class CraftSmithingTransformRecipe extends SmithingTransformRecipe implem + public CraftSmithingTransformRecipe(NamespacedKey key, ItemStack result, RecipeChoice template, RecipeChoice base, RecipeChoice addition) { + super(key, result, template, base, addition); + } ++ // Paper start - Option to prevent data components copy ++ public CraftSmithingTransformRecipe(NamespacedKey key, ItemStack result, RecipeChoice template, RecipeChoice base, RecipeChoice addition, boolean copyDataComponents) { ++ super(key, result, template, base, addition, copyDataComponents); ++ } ++ // Paper end - Option to prevent data components copy + + public static CraftSmithingTransformRecipe fromBukkitRecipe(SmithingTransformRecipe recipe) { + if (recipe instanceof CraftSmithingTransformRecipe) { + return (CraftSmithingTransformRecipe) recipe; + } +- CraftSmithingTransformRecipe ret = new CraftSmithingTransformRecipe(recipe.getKey(), recipe.getResult(), recipe.getTemplate(), recipe.getBase(), recipe.getAddition()); ++ CraftSmithingTransformRecipe ret = new CraftSmithingTransformRecipe(recipe.getKey(), recipe.getResult(), recipe.getTemplate(), recipe.getBase(), recipe.getAddition(), recipe.willCopyDataComponents()); // Paper - Option to prevent data components copy + return ret; + } + +@@ -24,6 +29,6 @@ public class CraftSmithingTransformRecipe extends SmithingTransformRecipe implem + public void addToCraftingManager() { + ItemStack result = this.getResult(); + +- MinecraftServer.getServer().getRecipeManager().addRecipe(new RecipeHolder<>(CraftRecipe.toMinecraft(this.getKey()), new net.minecraft.world.item.crafting.SmithingTransformRecipe(this.toNMSOptional(this.getTemplate(), false), this.toNMSOptional(this.getBase(), false), this.toNMSOptional(this.getAddition(), false), CraftItemStack.asNMSCopy(result)))); ++ MinecraftServer.getServer().getRecipeManager().addRecipe(new RecipeHolder<>(CraftRecipe.toMinecraft(this.getKey()), new net.minecraft.world.item.crafting.SmithingTransformRecipe(this.toNMSOptional(this.getTemplate(), false), this.toNMSOptional(this.getBase(), false), this.toNMSOptional(this.getAddition(), false), CraftItemStack.asNMSCopy(result), this.willCopyDataComponents()))); // Paper - Option to prevent data components copy + } + } +diff --git a/src/main/java/org/bukkit/craftbukkit/inventory/CraftSmithingTrimRecipe.java b/src/main/java/org/bukkit/craftbukkit/inventory/CraftSmithingTrimRecipe.java +index 202963e2f53b5e7d6fd43c58b27ad49ce009cc3c..fb710aa6dc416a3423345ad5b6e9494507eb0cb4 100644 +--- a/src/main/java/org/bukkit/craftbukkit/inventory/CraftSmithingTrimRecipe.java ++++ b/src/main/java/org/bukkit/craftbukkit/inventory/CraftSmithingTrimRecipe.java +@@ -11,17 +11,22 @@ public class CraftSmithingTrimRecipe extends SmithingTrimRecipe implements Craft + public CraftSmithingTrimRecipe(NamespacedKey key, RecipeChoice template, RecipeChoice base, RecipeChoice addition) { + super(key, template, base, addition); + } ++ // Paper start - Option to prevent data components copy ++ public CraftSmithingTrimRecipe(NamespacedKey key, RecipeChoice template, RecipeChoice base, RecipeChoice addition, boolean copyDataComponents) { ++ super(key, template, base, addition, copyDataComponents); ++ } ++ // Paper end - Option to prevent data components copy + + public static CraftSmithingTrimRecipe fromBukkitRecipe(SmithingTrimRecipe recipe) { + if (recipe instanceof CraftSmithingTrimRecipe) { + return (CraftSmithingTrimRecipe) recipe; + } +- CraftSmithingTrimRecipe ret = new CraftSmithingTrimRecipe(recipe.getKey(), recipe.getTemplate(), recipe.getBase(), recipe.getAddition()); ++ CraftSmithingTrimRecipe ret = new CraftSmithingTrimRecipe(recipe.getKey(), recipe.getTemplate(), recipe.getBase(), recipe.getAddition(), recipe.willCopyDataComponents()); // Paper - Option to prevent data components copy + return ret; + } + + @Override + public void addToCraftingManager() { +- MinecraftServer.getServer().getRecipeManager().addRecipe(new RecipeHolder<>(CraftRecipe.toMinecraft(this.getKey()), new net.minecraft.world.item.crafting.SmithingTrimRecipe(this.toNMSOptional(this.getTemplate(), false), this.toNMSOptional(this.getBase(), false), this.toNMSOptional(this.getAddition(), false)))); ++ MinecraftServer.getServer().getRecipeManager().addRecipe(new RecipeHolder<>(CraftRecipe.toMinecraft(this.getKey()), new net.minecraft.world.item.crafting.SmithingTrimRecipe(this.toNMSOptional(this.getTemplate(), false), this.toNMSOptional(this.getBase(), false), this.toNMSOptional(this.getAddition(), false), this.willCopyDataComponents()))); // Paper - Option to prevent data components copy + } + } diff --git a/patches/server/0576-Configurable-item-frame-map-cursor-update-interval.patch b/patches/server/0576-Configurable-item-frame-map-cursor-update-interval.patch deleted file mode 100644 index a6389cdcbb0d..000000000000 --- a/patches/server/0576-Configurable-item-frame-map-cursor-update-interval.patch +++ /dev/null @@ -1,19 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Warrior <50800980+Warriorrrr@users.noreply.github.com> -Date: Fri, 13 Aug 2021 01:14:38 +0200 -Subject: [PATCH] Configurable item frame map cursor update interval - - -diff --git a/src/main/java/net/minecraft/server/level/ServerEntity.java b/src/main/java/net/minecraft/server/level/ServerEntity.java -index 7e8a43ea1b32f444fb66e270a5f6b48bf7bcd2a0..f3f93710846ce0f6d53845e0b49331646a4e8332 100644 ---- a/src/main/java/net/minecraft/server/level/ServerEntity.java -+++ b/src/main/java/net/minecraft/server/level/ServerEntity.java -@@ -116,7 +116,7 @@ public class ServerEntity { - if (true || this.tickCount % 10 == 0) { // CraftBukkit - Moved below, should always enter this block - ItemStack itemstack = entityitemframe.getItem(); - -- if (this.tickCount % 10 == 0 && itemstack.getItem() instanceof MapItem) { // CraftBukkit - Moved this.tickCounter % 10 logic here so item frames do not enter the other blocks -+ if (this.level.paperConfig().maps.itemFrameCursorUpdateInterval > 0 && this.tickCount % this.level.paperConfig().maps.itemFrameCursorUpdateInterval == 0 && itemstack.getItem() instanceof MapItem) { // CraftBukkit - Moved this.tickCounter % 10 logic here so item frames do not enter the other blocks // Paper - Make item frame map cursor update interval configurable - MapId mapid = (MapId) itemstack.get(DataComponents.MAP_ID); - MapItemSavedData worldmap = MapItem.getSavedData(mapid, this.level); - diff --git a/patches/server/0580-More-CommandBlock-API.patch b/patches/server/0576-More-CommandBlock-API.patch similarity index 100% rename from patches/server/0580-More-CommandBlock-API.patch rename to patches/server/0576-More-CommandBlock-API.patch diff --git a/patches/server/0577-Add-missing-team-sidebar-display-slots.patch b/patches/server/0577-Add-missing-team-sidebar-display-slots.patch new file mode 100644 index 000000000000..1306837f9f5f --- /dev/null +++ b/patches/server/0577-Add-missing-team-sidebar-display-slots.patch @@ -0,0 +1,114 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Jake Potrebic +Date: Fri, 1 Oct 2021 08:04:39 -0700 +Subject: [PATCH] Add missing team sidebar display slots + +== AT == +public org.bukkit.craftbukkit.scoreboard.CraftScoreboardTranslations +public org.bukkit.craftbukkit.scoreboard.CraftScoreboardTranslations toBukkitSlot(Lnet/minecraft/world/scores/DisplaySlot;)Lorg/bukkit/scoreboard/DisplaySlot; +public org.bukkit.craftbukkit.scoreboard.CraftScoreboardTranslations fromBukkitSlot(Lorg/bukkit/scoreboard/DisplaySlot;)Lnet/minecraft/world/scores/DisplaySlot; + +diff --git a/src/main/java/org/bukkit/craftbukkit/legacy/FieldRename.java b/src/main/java/org/bukkit/craftbukkit/legacy/FieldRename.java +index b5e0023e431f9fb43c93a3f977144b03545322bb..3ecb1dd1a835efd7026af45562cd3394417130aa 100644 +--- a/src/main/java/org/bukkit/craftbukkit/legacy/FieldRename.java ++++ b/src/main/java/org/bukkit/craftbukkit/legacy/FieldRename.java +@@ -35,6 +35,7 @@ public class FieldRename { + } + + return switch (owner) { ++ case "org/bukkit/scoreboard/DisplaySlot" -> FieldRename.convertDisplaySlot(from); // Paper - DisplaySlot + case "org/bukkit/block/banner/PatternType" -> FieldRename.convertPatternTypeName(apiVersion, from); + case "org/bukkit/enchantments/Enchantment" -> FieldRename.convertEnchantmentName(apiVersion, from); + case "org/bukkit/block/Biome" -> FieldRename.convertBiomeName(apiVersion, from); +@@ -60,6 +61,16 @@ public class FieldRename { + //} + // Paper end + ++ // Paper start - DisplaySlot ++ @DoNotReroute ++ public static String convertDisplaySlot(final String from) { ++ if (from.startsWith("SIDEBAR_") && !from.startsWith("SIDEBAR_TEAM_")) { ++ return from.replace("SIDEBAR_", "SIDEBAR_TEAM_"); ++ } ++ return from; ++ } ++ // Paper end - DisplaySlot ++ + // PatternType + private static final FieldRenameData PATTERN_TYPE_DATA = FieldRenameData.Builder.newBuilder() + .forVersionsBefore(ApiVersion.FIELD_NAME_PARITY) +diff --git a/src/main/java/org/bukkit/craftbukkit/scoreboard/CraftScoreboardTranslations.java b/src/main/java/org/bukkit/craftbukkit/scoreboard/CraftScoreboardTranslations.java +index 73c5ffff70605b32188a9bb5fb6c0ee04cb66efe..711d227f5ee6d63356a94a0567968da48e9f284c 100644 +--- a/src/main/java/org/bukkit/craftbukkit/scoreboard/CraftScoreboardTranslations.java ++++ b/src/main/java/org/bukkit/craftbukkit/scoreboard/CraftScoreboardTranslations.java +@@ -7,35 +7,22 @@ import org.bukkit.scoreboard.RenderType; + + public final class CraftScoreboardTranslations { + static final int MAX_DISPLAY_SLOT = 19; ++ @Deprecated // Paper + static final ImmutableBiMap SLOTS = ImmutableBiMap.builder() + .put(DisplaySlot.BELOW_NAME, "below_name") + .put(DisplaySlot.PLAYER_LIST, "list") + .put(DisplaySlot.SIDEBAR, "sidebar") +- .put(DisplaySlot.SIDEBAR_BLACK, "sidebar.team.black") +- .put(DisplaySlot.SIDEBAR_DARK_BLUE, "sidebar.team.dark_blue") +- .put(DisplaySlot.SIDEBAR_DARK_GREEN, "sidebar.team.dark_green") +- .put(DisplaySlot.SIDEBAR_DARK_AQUA, "sidebar.team.dark_aqua") +- .put(DisplaySlot.SIDEBAR_DARK_RED, "sidebar.team.dark_red") +- .put(DisplaySlot.SIDEBAR_DARK_PURPLE, "sidebar.team.dark_purple") +- .put(DisplaySlot.SIDEBAR_GOLD, "sidebar.team.gold") +- .put(DisplaySlot.SIDEBAR_GRAY, "sidebar.team.gray") +- .put(DisplaySlot.SIDEBAR_DARK_GRAY, "sidebar.team.dark_gray") +- .put(DisplaySlot.SIDEBAR_BLUE, "sidebar.team.blue") +- .put(DisplaySlot.SIDEBAR_GREEN, "sidebar.team.green") +- .put(DisplaySlot.SIDEBAR_AQUA, "sidebar.team.aqua") +- .put(DisplaySlot.SIDEBAR_RED, "sidebar.team.red") +- .put(DisplaySlot.SIDEBAR_LIGHT_PURPLE, "sidebar.team.light_purple") +- .put(DisplaySlot.SIDEBAR_YELLOW, "sidebar.team.yellow") +- .put(DisplaySlot.SIDEBAR_WHITE, "sidebar.team.white") + .buildOrThrow(); + + private CraftScoreboardTranslations() {} + + public static DisplaySlot toBukkitSlot(net.minecraft.world.scores.DisplaySlot minecraft) { ++ if (true) return DisplaySlot.NAMES.value(minecraft.getSerializedName()); // Paper + return CraftScoreboardTranslations.SLOTS.inverse().get(minecraft.getSerializedName()); + } + + public static net.minecraft.world.scores.DisplaySlot fromBukkitSlot(DisplaySlot slot) { ++ if (true) return net.minecraft.world.scores.DisplaySlot.CODEC.byName(slot.getId()); // Paper + return net.minecraft.world.scores.DisplaySlot.CODEC.byName(CraftScoreboardTranslations.SLOTS.get(slot)); + } + +diff --git a/src/test/java/io/papermc/paper/scoreboard/DisplaySlotTest.java b/src/test/java/io/papermc/paper/scoreboard/DisplaySlotTest.java +new file mode 100644 +index 0000000000000000000000000000000000000000..345c96bfb6c559b41c2b6682067198a74d35b440 +--- /dev/null ++++ b/src/test/java/io/papermc/paper/scoreboard/DisplaySlotTest.java +@@ -0,0 +1,26 @@ ++package io.papermc.paper.scoreboard; ++ ++import org.bukkit.craftbukkit.scoreboard.CraftScoreboardTranslations; ++import org.bukkit.scoreboard.DisplaySlot; ++import org.bukkit.support.environment.Normal; ++import org.junit.jupiter.api.Test; ++ ++import static org.junit.jupiter.api.Assertions.assertNotNull; ++ ++@Normal ++public class DisplaySlotTest { ++ ++ @Test ++ public void testBukkitToMinecraftDisplaySlots() { ++ for (DisplaySlot bukkitSlot : DisplaySlot.values()) { ++ assertNotNull(CraftScoreboardTranslations.fromBukkitSlot(bukkitSlot)); ++ } ++ } ++ ++ @Test ++ public void testMinecraftToBukkitDisplaySlots() { ++ for (net.minecraft.world.scores.DisplaySlot nmsSlot : net.minecraft.world.scores.DisplaySlot.values()) { ++ assertNotNull(CraftScoreboardTranslations.toBukkitSlot(nmsSlot)); ++ } ++ } ++} diff --git a/patches/server/0577-Change-EnderEye-target-without-changing-other-things.patch b/patches/server/0577-Change-EnderEye-target-without-changing-other-things.patch deleted file mode 100644 index 53d06a36ec9d..000000000000 --- a/patches/server/0577-Change-EnderEye-target-without-changing-other-things.patch +++ /dev/null @@ -1,54 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Jake Potrebic -Date: Sat, 21 Aug 2021 12:13:53 -0700 -Subject: [PATCH] Change EnderEye target without changing other things - - -diff --git a/src/main/java/net/minecraft/world/entity/projectile/EyeOfEnder.java b/src/main/java/net/minecraft/world/entity/projectile/EyeOfEnder.java -index 8b0ccd1aa0f2f1d2326c6071f16e641afc101751..fca3786d0a3f99a3e61e7a4b2251361276eff9d7 100644 ---- a/src/main/java/net/minecraft/world/entity/projectile/EyeOfEnder.java -+++ b/src/main/java/net/minecraft/world/entity/projectile/EyeOfEnder.java -@@ -69,6 +69,11 @@ public class EyeOfEnder extends Entity implements ItemSupplier { - } - - public void signalTo(BlockPos pos) { -+ // Paper start - Change EnderEye target without changing other things -+ this.signalTo(pos, true); -+ } -+ public void signalTo(BlockPos pos, boolean update) { -+ // Paper end - Change EnderEye target without changing other things - double d0 = (double) pos.getX(); - int i = pos.getY(); - double d1 = (double) pos.getZ(); -@@ -86,8 +91,10 @@ public class EyeOfEnder extends Entity implements ItemSupplier { - this.tz = d1; - } - -+ if (update) { // Paper - Change EnderEye target without changing other things - this.life = 0; - this.surviveAfterDeath = this.random.nextInt(5) > 0; -+ } // Paper - Change EnderEye target without changing other things - } - - @Override -diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftEnderSignal.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftEnderSignal.java -index d4dfc7a0266086b9bf2c3966c6e149453d0583ba..27f56fa4b7ef92a9a4dfa6b782350424b88210f2 100644 ---- a/src/main/java/org/bukkit/craftbukkit/entity/CraftEnderSignal.java -+++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftEnderSignal.java -@@ -32,8 +32,15 @@ public class CraftEnderSignal extends CraftEntity implements EnderSignal { - - @Override - public void setTargetLocation(Location location) { -+ // Paper start - Change EnderEye target without changing other things -+ this.setTargetLocation(location, true); -+ } -+ -+ @Override -+ public void setTargetLocation(Location location, boolean update) { -+ // Paper end - Change EnderEye target without changing other things - Preconditions.checkArgument(this.getWorld().equals(location.getWorld()), "Cannot target EnderSignal across worlds"); -- this.getHandle().signalTo(CraftLocation.toBlockPosition(location)); -+ this.getHandle().signalTo(CraftLocation.toBlockPosition(location), update); // Paper - Change EnderEye target without changing other things - } - - @Override diff --git a/patches/server/0578-Add-BlockBreakBlockEvent.patch b/patches/server/0578-Add-BlockBreakBlockEvent.patch deleted file mode 100644 index 4bc0c5116fcb..000000000000 --- a/patches/server/0578-Add-BlockBreakBlockEvent.patch +++ /dev/null @@ -1,87 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Jake Potrebic -Date: Sun, 3 Jan 2021 17:58:11 -0800 -Subject: [PATCH] Add BlockBreakBlockEvent - - -diff --git a/src/main/java/net/minecraft/world/level/block/Block.java b/src/main/java/net/minecraft/world/level/block/Block.java -index 6d0a90e9c637edff5c5ce1355a3b45f0fb7f4154..232e6216dc36aa698047fc0badf78c347414b3a5 100644 ---- a/src/main/java/net/minecraft/world/level/block/Block.java -+++ b/src/main/java/net/minecraft/world/level/block/Block.java -@@ -305,6 +305,24 @@ public class Block extends BlockBehaviour implements ItemLike { - - } - -+ // Paper start - Add BlockBreakBlockEvent -+ public static boolean dropResources(BlockState state, LevelAccessor levelAccessor, BlockPos pos, @Nullable BlockEntity blockEntity, BlockPos source) { -+ if (levelAccessor instanceof ServerLevel serverLevel) { -+ List items = new java.util.ArrayList<>(); -+ for (ItemStack drop : Block.getDrops(state, serverLevel, pos, blockEntity)) { -+ items.add(org.bukkit.craftbukkit.inventory.CraftItemStack.asBukkitCopy(drop)); -+ } -+ io.papermc.paper.event.block.BlockBreakBlockEvent event = new io.papermc.paper.event.block.BlockBreakBlockEvent(org.bukkit.craftbukkit.block.CraftBlock.at(levelAccessor, pos), org.bukkit.craftbukkit.block.CraftBlock.at(levelAccessor, source), items); -+ event.callEvent(); -+ for (org.bukkit.inventory.ItemStack drop : event.getDrops()) { -+ popResource(serverLevel, pos, org.bukkit.craftbukkit.inventory.CraftItemStack.asNMSCopy(drop)); -+ } -+ state.spawnAfterBreak(serverLevel, pos, ItemStack.EMPTY, true); -+ } -+ return true; -+ } -+ // Paper end - Add BlockBreakBlockEvent -+ - public static void dropResources(BlockState state, Level world, BlockPos pos, @Nullable BlockEntity blockEntity, @Nullable Entity entity, ItemStack tool) { - if (world instanceof ServerLevel) { - Block.getDrops(state, (ServerLevel) world, pos, blockEntity, entity, tool).forEach((itemstack1) -> { -diff --git a/src/main/java/net/minecraft/world/level/block/piston/PistonBaseBlock.java b/src/main/java/net/minecraft/world/level/block/piston/PistonBaseBlock.java -index 4aa34b7df734bb755906b228e0df9eb629569ea0..2f2c9fb65d4cc8bd40303216e03c5c1956305ff4 100644 ---- a/src/main/java/net/minecraft/world/level/block/piston/PistonBaseBlock.java -+++ b/src/main/java/net/minecraft/world/level/block/piston/PistonBaseBlock.java -@@ -403,7 +403,7 @@ public class PistonBaseBlock extends DirectionalBlock { - iblockdata1 = world.getBlockState(blockposition3); - BlockEntity tileentity = iblockdata1.hasBlockEntity() ? world.getBlockEntity(blockposition3) : null; - -- dropResources(iblockdata1, world, blockposition3, tileentity); -+ dropResources(iblockdata1, world, blockposition3, tileentity, pos); // Paper - Add BlockBreakBlockEvent - world.setBlock(blockposition3, Blocks.AIR.defaultBlockState(), 18); - world.gameEvent((Holder) GameEvent.BLOCK_DESTROY, blockposition3, GameEvent.Context.of(iblockdata1)); - if (!iblockdata1.is(BlockTags.FIRE)) { -diff --git a/src/main/java/net/minecraft/world/level/material/FlowingFluid.java b/src/main/java/net/minecraft/world/level/material/FlowingFluid.java -index 3a2ae2bca410708736da64560e74b8010444f2dc..1c0712295695727ee9c4d430d4157b8e17cbd71f 100644 ---- a/src/main/java/net/minecraft/world/level/material/FlowingFluid.java -+++ b/src/main/java/net/minecraft/world/level/material/FlowingFluid.java -@@ -295,7 +295,7 @@ public abstract class FlowingFluid extends Fluid { - ((LiquidBlockContainer) state.getBlock()).placeLiquid(world, pos, state, fluidState); - } else { - if (!state.isAir()) { -- this.beforeDestroyingBlock(world, pos, state); -+ this.beforeDestroyingBlock(world, pos, state, pos.relative(direction.getOpposite())); // Paper - Add BlockBreakBlockEvent - } - - world.setBlock(pos, fluidState.createLegacyBlock(), 3); -@@ -303,6 +303,7 @@ public abstract class FlowingFluid extends Fluid { - - } - -+ protected void beforeDestroyingBlock(LevelAccessor world, BlockPos pos, BlockState state, BlockPos source) { beforeDestroyingBlock(world, pos, state); } // Paper - Add BlockBreakBlockEvent - protected abstract void beforeDestroyingBlock(LevelAccessor world, BlockPos pos, BlockState state); - - private static short getCacheKey(BlockPos from, BlockPos to) { -diff --git a/src/main/java/net/minecraft/world/level/material/WaterFluid.java b/src/main/java/net/minecraft/world/level/material/WaterFluid.java -index 7f40e8196cb966424ae63043d1f54e661dbce715..21b4afd053e01073eb514264d4960f0f3b1ee3d8 100644 ---- a/src/main/java/net/minecraft/world/level/material/WaterFluid.java -+++ b/src/main/java/net/minecraft/world/level/material/WaterFluid.java -@@ -80,6 +80,13 @@ public abstract class WaterFluid extends FlowingFluid { - return world.getGameRules().getBoolean(GameRules.RULE_WATER_SOURCE_CONVERSION); - } - -+ // Paper start - Add BlockBreakBlockEvent -+ @Override -+ protected void beforeDestroyingBlock(LevelAccessor world, BlockPos pos, BlockState state, BlockPos source) { -+ BlockEntity tileentity = state.hasBlockEntity() ? world.getBlockEntity(pos) : null; -+ Block.dropResources(state, world, pos, tileentity, source); -+ } -+ // Paper end - Add BlockBreakBlockEvent - @Override - protected void beforeDestroyingBlock(LevelAccessor world, BlockPos pos, BlockState state) { - BlockEntity blockEntity = state.hasBlockEntity() ? world.getBlockEntity(pos) : null; diff --git a/patches/server/0578-Add-back-EntityPortalExitEvent.patch b/patches/server/0578-Add-back-EntityPortalExitEvent.patch new file mode 100644 index 000000000000..5e3f9b5ffdcf --- /dev/null +++ b/patches/server/0578-Add-back-EntityPortalExitEvent.patch @@ -0,0 +1,48 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Jake Potrebic +Date: Sun, 16 May 2021 09:39:46 -0700 +Subject: [PATCH] Add back EntityPortalExitEvent + + +diff --git a/src/main/java/net/minecraft/world/entity/Entity.java b/src/main/java/net/minecraft/world/entity/Entity.java +index fe9cdd104d6203233a90068b55e0876be4964afe..88d3b1997bba632c5746d295bb39095f5d328369 100644 +--- a/src/main/java/net/minecraft/world/entity/Entity.java ++++ b/src/main/java/net/minecraft/world/entity/Entity.java +@@ -3491,6 +3491,7 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess + if (!this.isRemoved()) { + // CraftBukkit start + PositionMoveRotation absolutePosition = PositionMoveRotation.calculateAbsolute(PositionMoveRotation.of(this), PositionMoveRotation.of(teleportTarget), teleportTarget.relatives()); ++ Vec3 velocity = absolutePosition.deltaMovement(); // Paper + Location to = CraftLocation.toBukkit(absolutePosition.position(), teleportTarget.newLevel().getWorld(), absolutePosition.yRot(), absolutePosition.xRot()); + // Paper start - gateway-specific teleport event + final EntityTeleportEvent teleEvent; +@@ -3507,7 +3508,29 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess + if (!to.equals(teleEvent.getTo())) { + to = teleEvent.getTo(); + teleportTarget = new TeleportTransition(((CraftWorld) to.getWorld()).getHandle(), CraftLocation.toVec3D(to), Vec3.ZERO, to.getYaw(), to.getPitch(), teleportTarget.missingRespawnBlock(), teleportTarget.asPassenger(), Set.of(), teleportTarget.postTeleportTransition(), teleportTarget.cause()); ++ // Paper start - Call EntityPortalExitEvent ++ velocity = Vec3.ZERO; + } ++ if (this.portalProcess != null) { // if in a portal ++ CraftEntity bukkitEntity = this.getBukkitEntity(); ++ org.bukkit.event.entity.EntityPortalExitEvent event = new org.bukkit.event.entity.EntityPortalExitEvent( ++ bukkitEntity, ++ bukkitEntity.getLocation(), to.clone(), ++ bukkitEntity.getVelocity(), org.bukkit.craftbukkit.util.CraftVector.toBukkit(velocity) ++ ); ++ event.callEvent(); ++ ++ // Only change the target if actually needed, since we reset relative flags ++ if (!event.isCancelled() && event.getTo() != null && (!event.getTo().equals(event.getFrom()) || !event.getAfter().equals(event.getBefore()))) { ++ to = event.getTo().clone(); ++ velocity = org.bukkit.craftbukkit.util.CraftVector.toNMS(event.getAfter()); ++ teleportTarget = new TeleportTransition(((CraftWorld) to.getWorld()).getHandle(), CraftLocation.toVec3D(to), velocity, to.getYaw(), to.getPitch(), teleportTarget.missingRespawnBlock(), teleportTarget.asPassenger(), Set.of(), teleportTarget.postTeleportTransition(), teleportTarget.cause()); ++ } ++ } ++ if (this.isRemoved()) { ++ return null; ++ } ++ // Paper end - Call EntityPortalExitEvent + // CraftBukkit end + ServerLevel worldserver1 = teleportTarget.newLevel(); + boolean flag = worldserver1.dimension() != worldserver.dimension(); diff --git a/patches/server/0579-Add-methods-to-find-targets-for-lightning-strikes.patch b/patches/server/0579-Add-methods-to-find-targets-for-lightning-strikes.patch new file mode 100644 index 000000000000..0a2ba7ccde68 --- /dev/null +++ b/patches/server/0579-Add-methods-to-find-targets-for-lightning-strikes.patch @@ -0,0 +1,60 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Jakub Zacek +Date: Mon, 4 Oct 2021 10:16:44 +0200 +Subject: [PATCH] Add methods to find targets for lightning strikes + +== AT == +public net.minecraft.server.level.ServerLevel findLightningRod(Lnet/minecraft/core/BlockPos;)Ljava/util/Optional; + +diff --git a/src/main/java/net/minecraft/server/level/ServerLevel.java b/src/main/java/net/minecraft/server/level/ServerLevel.java +index 91fb83761885752743adb53cc9ed30ddc879263d..3c281cdd24acbc9484c968c07f737d50be2deced 100644 +--- a/src/main/java/net/minecraft/server/level/ServerLevel.java ++++ b/src/main/java/net/minecraft/server/level/ServerLevel.java +@@ -735,6 +735,11 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe + } + + protected BlockPos findLightningTargetAround(BlockPos pos) { ++ // Paper start - Add methods to find targets for lightning strikes ++ return this.findLightningTargetAround(pos, false); ++ } ++ public BlockPos findLightningTargetAround(BlockPos pos, boolean returnNullWhenNoTarget) { ++ // Paper end - Add methods to find targets for lightning strikes + BlockPos blockposition1 = this.getHeightmapPos(Heightmap.Types.MOTION_BLOCKING, pos); + Optional optional = this.findLightningRod(blockposition1); + +@@ -749,6 +754,7 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe + if (!list.isEmpty()) { + return ((LivingEntity) list.get(this.random.nextInt(list.size()))).blockPosition(); + } else { ++ if (returnNullWhenNoTarget) return null; // Paper - Add methods to find targets for lightning strikes + if (blockposition1.getY() == this.getMinY() - 1) { + blockposition1 = blockposition1.above(2); + } +diff --git a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java +index 9e0f2900dea70c471ea73b190b8aa2d9abd37ee6..06ccf20081ecd2b56f9e5aeef988804fef10df73 100644 +--- a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java ++++ b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java +@@ -695,6 +695,23 @@ public class CraftWorld extends CraftRegionAccessor implements World { + return (LightningStrike) lightning.getBukkitEntity(); + } + ++ // Paper start - Add methods to find targets for lightning strikes ++ @Override ++ public Location findLightningRod(Location location) { ++ return this.world.findLightningRod(io.papermc.paper.util.MCUtil.toBlockPosition(location)) ++ .map(blockPos -> io.papermc.paper.util.MCUtil.toLocation(this.world, blockPos) ++ // get the actual rod pos ++ .subtract(0, 1, 0)) ++ .orElse(null); ++ } ++ ++ @Override ++ public Location findLightningTarget(Location location) { ++ final BlockPos pos = this.world.findLightningTargetAround(io.papermc.paper.util.MCUtil.toBlockPosition(location), true); ++ return pos == null ? null : io.papermc.paper.util.MCUtil.toLocation(this.world, pos); ++ } ++ // Paper end - Add methods to find targets for lightning strikes ++ + @Override + public boolean generateTree(Location loc, TreeType type) { + return this.generateTree(loc, CraftWorld.rand, type); diff --git a/patches/server/0579-Option-to-prevent-data-components-copy-in-smithing-r.patch b/patches/server/0579-Option-to-prevent-data-components-copy-in-smithing-r.patch deleted file mode 100644 index b7f49fa1dfd5..000000000000 --- a/patches/server/0579-Option-to-prevent-data-components-copy-in-smithing-r.patch +++ /dev/null @@ -1,143 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Jake Potrebic -Date: Sun, 26 Sep 2021 12:57:28 -0700 -Subject: [PATCH] Option to prevent data components copy in smithing recipes - - -diff --git a/src/main/java/net/minecraft/world/item/crafting/SmithingTransformRecipe.java b/src/main/java/net/minecraft/world/item/crafting/SmithingTransformRecipe.java -index 83b77e170f2945e9b40f302c4cf65efb1628c84a..d64a1c1e146d5d9aa940a37dbee16889c9bab783 100644 ---- a/src/main/java/net/minecraft/world/item/crafting/SmithingTransformRecipe.java -+++ b/src/main/java/net/minecraft/world/item/crafting/SmithingTransformRecipe.java -@@ -22,8 +22,15 @@ public class SmithingTransformRecipe implements SmithingRecipe { - final Ingredient base; - final Ingredient addition; - final ItemStack result; -+ final boolean copyDataComponents; // Paper - Option to prevent data components copy - - public SmithingTransformRecipe(Ingredient template, Ingredient base, Ingredient addition, ItemStack result) { -+ // Paper start - Option to prevent data components copy -+ this(template, base, addition, result, true); -+ } -+ public SmithingTransformRecipe(Ingredient template, Ingredient base, Ingredient addition, ItemStack result, boolean copyDataComponents) { -+ this.copyDataComponents = copyDataComponents; -+ // Paper end - Option to prevent data components copy - this.template = template; - this.base = base; - this.addition = addition; -@@ -37,7 +44,9 @@ public class SmithingTransformRecipe implements SmithingRecipe { - public ItemStack assemble(SmithingRecipeInput input, HolderLookup.Provider lookup) { - ItemStack itemstack = input.base().transmuteCopy(this.result.getItem(), this.result.getCount()); - -+ if (this.copyDataComponents) { // Paper - Option to prevent data components copy - itemstack.applyComponents(this.result.getComponentsPatch()); -+ } // Paper - Option to prevent data components copy - return itemstack; - } - -@@ -76,7 +85,7 @@ public class SmithingTransformRecipe implements SmithingRecipe { - public Recipe toBukkitRecipe(NamespacedKey id) { - CraftItemStack result = CraftItemStack.asCraftMirror(this.result); - -- CraftSmithingTransformRecipe recipe = new CraftSmithingTransformRecipe(id, result, CraftRecipe.toBukkit(this.template), CraftRecipe.toBukkit(this.base), CraftRecipe.toBukkit(this.addition)); -+ CraftSmithingTransformRecipe recipe = new CraftSmithingTransformRecipe(id, result, CraftRecipe.toBukkit(this.template), CraftRecipe.toBukkit(this.base), CraftRecipe.toBukkit(this.addition), this.copyDataComponents); // Paper - Option to prevent data components copy - - return recipe; - } -diff --git a/src/main/java/net/minecraft/world/item/crafting/SmithingTrimRecipe.java b/src/main/java/net/minecraft/world/item/crafting/SmithingTrimRecipe.java -index 4ea43872f9da72ed959dd0709f959402d01d5fe0..f6f10da21a752e927409ea16076701c4ec403a0e 100644 ---- a/src/main/java/net/minecraft/world/item/crafting/SmithingTrimRecipe.java -+++ b/src/main/java/net/minecraft/world/item/crafting/SmithingTrimRecipe.java -@@ -30,8 +30,15 @@ public class SmithingTrimRecipe implements SmithingRecipe { - final Ingredient template; - final Ingredient base; - final Ingredient addition; -+ final boolean copyDataComponents; // Paper - Option to prevent data components copy - - public SmithingTrimRecipe(Ingredient template, Ingredient base, Ingredient addition) { -+ // Paper start - Option to prevent data components copy -+ this(template, base, addition, true); -+ } -+ public SmithingTrimRecipe(Ingredient template, Ingredient base, Ingredient addition, boolean copyDataComponents) { -+ this.copyDataComponents = copyDataComponents; -+ // Paper end - Option to prevent data components copy - this.template = template; - this.base = base; - this.addition = addition; -@@ -55,7 +62,7 @@ public class SmithingTrimRecipe implements SmithingRecipe { - return ItemStack.EMPTY; - } - -- ItemStack itemstack1 = itemstack.copyWithCount(1); -+ ItemStack itemstack1 = this.copyDataComponents ? itemstack.copyWithCount(1) : new ItemStack(itemstack.getItem(), 1); // Paper - Option to prevent data components copy - - itemstack1.set(DataComponents.TRIM, new ArmorTrim((Holder) optional.get(), (Holder) optional1.get())); - return itemstack1; -@@ -106,7 +113,7 @@ public class SmithingTrimRecipe implements SmithingRecipe { - // CraftBukkit start - @Override - public Recipe toBukkitRecipe(NamespacedKey id) { -- return new CraftSmithingTrimRecipe(id, CraftRecipe.toBukkit(this.template), CraftRecipe.toBukkit(this.base), CraftRecipe.toBukkit(this.addition)); -+ return new CraftSmithingTrimRecipe(id, CraftRecipe.toBukkit(this.template), CraftRecipe.toBukkit(this.base), CraftRecipe.toBukkit(this.addition), this.copyDataComponents); // Paper - Option to prevent data components copy - } - // CraftBukkit end - -diff --git a/src/main/java/org/bukkit/craftbukkit/inventory/CraftSmithingTransformRecipe.java b/src/main/java/org/bukkit/craftbukkit/inventory/CraftSmithingTransformRecipe.java -index 09f86aec08ec958b8e3015020e9ae213db27d85c..37b39a2c696a59b0f52324cc222b83c0c9f341e6 100644 ---- a/src/main/java/org/bukkit/craftbukkit/inventory/CraftSmithingTransformRecipe.java -+++ b/src/main/java/org/bukkit/craftbukkit/inventory/CraftSmithingTransformRecipe.java -@@ -12,12 +12,17 @@ public class CraftSmithingTransformRecipe extends SmithingTransformRecipe implem - public CraftSmithingTransformRecipe(NamespacedKey key, ItemStack result, RecipeChoice template, RecipeChoice base, RecipeChoice addition) { - super(key, result, template, base, addition); - } -+ // Paper start - Option to prevent data components copy -+ public CraftSmithingTransformRecipe(NamespacedKey key, ItemStack result, RecipeChoice template, RecipeChoice base, RecipeChoice addition, boolean copyDataComponents) { -+ super(key, result, template, base, addition, copyDataComponents); -+ } -+ // Paper end - Option to prevent data components copy - - public static CraftSmithingTransformRecipe fromBukkitRecipe(SmithingTransformRecipe recipe) { - if (recipe instanceof CraftSmithingTransformRecipe) { - return (CraftSmithingTransformRecipe) recipe; - } -- CraftSmithingTransformRecipe ret = new CraftSmithingTransformRecipe(recipe.getKey(), recipe.getResult(), recipe.getTemplate(), recipe.getBase(), recipe.getAddition()); -+ CraftSmithingTransformRecipe ret = new CraftSmithingTransformRecipe(recipe.getKey(), recipe.getResult(), recipe.getTemplate(), recipe.getBase(), recipe.getAddition(), recipe.willCopyDataComponents()); // Paper - Option to prevent data components copy - return ret; - } - -@@ -25,6 +30,6 @@ public class CraftSmithingTransformRecipe extends SmithingTransformRecipe implem - public void addToCraftingManager() { - ItemStack result = this.getResult(); - -- MinecraftServer.getServer().getRecipeManager().addRecipe(new RecipeHolder<>(CraftNamespacedKey.toMinecraft(this.getKey()), new net.minecraft.world.item.crafting.SmithingTransformRecipe(this.toNMS(this.getTemplate(), false), this.toNMS(this.getBase(), false), this.toNMS(this.getAddition(), false), CraftItemStack.asNMSCopy(result)))); -+ MinecraftServer.getServer().getRecipeManager().addRecipe(new RecipeHolder<>(CraftNamespacedKey.toMinecraft(this.getKey()), new net.minecraft.world.item.crafting.SmithingTransformRecipe(this.toNMS(this.getTemplate(), false), this.toNMS(this.getBase(), false), this.toNMS(this.getAddition(), false), CraftItemStack.asNMSCopy(result), this.willCopyDataComponents()))); // Paper - Option to prevent data components copy - } - } -diff --git a/src/main/java/org/bukkit/craftbukkit/inventory/CraftSmithingTrimRecipe.java b/src/main/java/org/bukkit/craftbukkit/inventory/CraftSmithingTrimRecipe.java -index 8245b728d341a64f25357edbd2c3c447b6c0a0cf..389fa313f811279091cace76faaabf8bdb0fc94c 100644 ---- a/src/main/java/org/bukkit/craftbukkit/inventory/CraftSmithingTrimRecipe.java -+++ b/src/main/java/org/bukkit/craftbukkit/inventory/CraftSmithingTrimRecipe.java -@@ -12,17 +12,22 @@ public class CraftSmithingTrimRecipe extends SmithingTrimRecipe implements Craft - public CraftSmithingTrimRecipe(NamespacedKey key, RecipeChoice template, RecipeChoice base, RecipeChoice addition) { - super(key, template, base, addition); - } -+ // Paper start - Option to prevent data components copy -+ public CraftSmithingTrimRecipe(NamespacedKey key, RecipeChoice template, RecipeChoice base, RecipeChoice addition, boolean copyDataComponents) { -+ super(key, template, base, addition, copyDataComponents); -+ } -+ // Paper end - Option to prevent data components copy - - public static CraftSmithingTrimRecipe fromBukkitRecipe(SmithingTrimRecipe recipe) { - if (recipe instanceof CraftSmithingTrimRecipe) { - return (CraftSmithingTrimRecipe) recipe; - } -- CraftSmithingTrimRecipe ret = new CraftSmithingTrimRecipe(recipe.getKey(), recipe.getTemplate(), recipe.getBase(), recipe.getAddition()); -+ CraftSmithingTrimRecipe ret = new CraftSmithingTrimRecipe(recipe.getKey(), recipe.getTemplate(), recipe.getBase(), recipe.getAddition(), recipe.willCopyDataComponents()); // Paper - Option to prevent data components copy - return ret; - } - - @Override - public void addToCraftingManager() { -- MinecraftServer.getServer().getRecipeManager().addRecipe(new RecipeHolder<>(CraftNamespacedKey.toMinecraft(this.getKey()), new net.minecraft.world.item.crafting.SmithingTrimRecipe(this.toNMS(this.getTemplate(), false), this.toNMS(this.getBase(), false), this.toNMS(this.getAddition(), false)))); -+ MinecraftServer.getServer().getRecipeManager().addRecipe(new RecipeHolder<>(CraftNamespacedKey.toMinecraft(this.getKey()), new net.minecraft.world.item.crafting.SmithingTrimRecipe(this.toNMS(this.getTemplate(), false), this.toNMS(this.getBase(), false), this.toNMS(this.getAddition(), false), this.willCopyDataComponents()))); // Paper - Option to prevent data components copy - } - } diff --git a/patches/server/0580-Get-entity-default-attributes.patch b/patches/server/0580-Get-entity-default-attributes.patch new file mode 100644 index 000000000000..6d512ca6376f --- /dev/null +++ b/patches/server/0580-Get-entity-default-attributes.patch @@ -0,0 +1,151 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Jake Potrebic +Date: Fri, 20 Aug 2021 13:03:21 -0700 +Subject: [PATCH] Get entity default attributes + +== AT == +public net.minecraft.world.entity.ai.attributes.AttributeSupplier getAttributeInstance(Lnet/minecraft/core/Holder;)Lnet/minecraft/world/entity/ai/attributes/AttributeInstance; + +diff --git a/src/main/java/io/papermc/paper/attribute/UnmodifiableAttributeInstance.java b/src/main/java/io/papermc/paper/attribute/UnmodifiableAttributeInstance.java +new file mode 100644 +index 0000000000000000000000000000000000000000..12135ffeacd648f6bc4d7d327059ea1a7e8c79c4 +--- /dev/null ++++ b/src/main/java/io/papermc/paper/attribute/UnmodifiableAttributeInstance.java +@@ -0,0 +1,30 @@ ++package io.papermc.paper.attribute; ++ ++import net.minecraft.world.entity.ai.attributes.AttributeInstance; ++import org.bukkit.attribute.Attribute; ++import org.bukkit.attribute.AttributeModifier; ++import org.bukkit.craftbukkit.attribute.CraftAttributeInstance; ++ ++import java.util.Collection; ++ ++public class UnmodifiableAttributeInstance extends CraftAttributeInstance { ++ ++ public UnmodifiableAttributeInstance(AttributeInstance handle, Attribute attribute) { ++ super(handle, attribute); ++ } ++ ++ @Override ++ public void setBaseValue(double d) { ++ throw new UnsupportedOperationException("Cannot modify default attributes"); ++ } ++ ++ @Override ++ public void addModifier(AttributeModifier modifier) { ++ throw new UnsupportedOperationException("Cannot modify default attributes"); ++ } ++ ++ @Override ++ public void removeModifier(AttributeModifier modifier) { ++ throw new UnsupportedOperationException("Cannot modify default attributes"); ++ } ++} +diff --git a/src/main/java/io/papermc/paper/attribute/UnmodifiableAttributeMap.java b/src/main/java/io/papermc/paper/attribute/UnmodifiableAttributeMap.java +new file mode 100644 +index 0000000000000000000000000000000000000000..ec9ebd2d539333293c51b7edfa18f18b066d7e43 +--- /dev/null ++++ b/src/main/java/io/papermc/paper/attribute/UnmodifiableAttributeMap.java +@@ -0,0 +1,32 @@ ++package io.papermc.paper.attribute; ++ ++import net.minecraft.world.entity.ai.attributes.AttributeSupplier; ++import org.bukkit.attribute.Attributable; ++import org.bukkit.attribute.Attribute; ++import org.bukkit.attribute.AttributeInstance; ++import org.bukkit.craftbukkit.attribute.CraftAttribute; ++import org.jetbrains.annotations.NotNull; ++import org.jetbrains.annotations.Nullable; ++ ++public class UnmodifiableAttributeMap implements Attributable { ++ ++ private final AttributeSupplier handle; ++ ++ public UnmodifiableAttributeMap(@NotNull AttributeSupplier handle) { ++ this.handle = handle; ++ } ++ ++ @Override ++ public @Nullable AttributeInstance getAttribute(@NotNull Attribute attribute) { ++ net.minecraft.core.Holder nmsAttribute = CraftAttribute.bukkitToMinecraftHolder(attribute); ++ if (!this.handle.hasAttribute(nmsAttribute)) { ++ return null; ++ } ++ return new UnmodifiableAttributeInstance(this.handle.getAttributeInstance(nmsAttribute), attribute); ++ } ++ ++ @Override ++ public void registerAttribute(@NotNull Attribute attribute) { ++ throw new UnsupportedOperationException("Cannot register new attributes here"); ++ } ++} +diff --git a/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java b/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java +index ffc98d8ed238cc653a7a6518a46c4e45a1b3682c..31b625779dfe27602ac198259258e64195c1796d 100644 +--- a/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java ++++ b/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java +@@ -549,6 +549,18 @@ public final class CraftMagicNumbers implements UnsafeValues { + } + return CraftItemStack.unwrap(itemToBeRepaired).isValidRepairItem(CraftItemStack.unwrap(repairMaterial)); + } ++ ++ @Override ++ public boolean hasDefaultEntityAttributes(NamespacedKey bukkitEntityKey) { ++ return net.minecraft.world.entity.ai.attributes.DefaultAttributes.hasSupplier(net.minecraft.core.registries.BuiltInRegistries.ENTITY_TYPE.getValue(CraftNamespacedKey.toMinecraft(bukkitEntityKey))); ++ } ++ ++ @Override ++ public org.bukkit.attribute.Attributable getDefaultEntityAttributes(NamespacedKey bukkitEntityKey) { ++ Preconditions.checkArgument(hasDefaultEntityAttributes(bukkitEntityKey), bukkitEntityKey + " doesn't have default attributes"); ++ var supplier = net.minecraft.world.entity.ai.attributes.DefaultAttributes.getSupplier((net.minecraft.world.entity.EntityType) net.minecraft.core.registries.BuiltInRegistries.ENTITY_TYPE.getValue(CraftNamespacedKey.toMinecraft(bukkitEntityKey))); ++ return new io.papermc.paper.attribute.UnmodifiableAttributeMap(supplier); ++ } + // Paper end + + /** +diff --git a/src/test/java/io/papermc/paper/attribute/EntityTypeAttributesTest.java b/src/test/java/io/papermc/paper/attribute/EntityTypeAttributesTest.java +new file mode 100644 +index 0000000000000000000000000000000000000000..f512d416df883036965ff6c802fd242a4c9c8079 +--- /dev/null ++++ b/src/test/java/io/papermc/paper/attribute/EntityTypeAttributesTest.java +@@ -0,0 +1,40 @@ ++package io.papermc.paper.attribute; ++ ++import org.bukkit.attribute.Attributable; ++import org.bukkit.attribute.Attribute; ++import org.bukkit.attribute.AttributeInstance; ++import org.bukkit.attribute.AttributeModifier; ++import org.bukkit.entity.EntityType; ++import org.bukkit.support.environment.AllFeatures; ++import org.junit.jupiter.api.Test; ++ ++import static org.junit.jupiter.api.Assertions.assertFalse; ++import static org.junit.jupiter.api.Assertions.assertNotNull; ++import static org.junit.jupiter.api.Assertions.assertThrows; ++import static org.junit.jupiter.api.Assertions.assertTrue; ++ ++@AllFeatures ++public class EntityTypeAttributesTest { ++ ++ @Test ++ public void testIllegalEntity() { ++ assertFalse(EntityType.EGG.hasDefaultAttributes()); ++ assertThrows(IllegalArgumentException.class, EntityType.EGG::getDefaultAttributes); ++ } ++ ++ @Test ++ public void testLegalEntity() { ++ assertTrue(EntityType.ZOMBIE.hasDefaultAttributes()); ++ EntityType.ZOMBIE.getDefaultAttributes(); ++ } ++ ++ @Test ++ public void testUnmodifiabilityOfAttributable() { ++ Attributable attributable = EntityType.ZOMBIE.getDefaultAttributes(); ++ assertThrows(UnsupportedOperationException.class, () -> attributable.registerAttribute(Attribute.GENERIC_ATTACK_DAMAGE)); ++ AttributeInstance instance = attributable.getAttribute(Attribute.GENERIC_FOLLOW_RANGE); ++ assertNotNull(instance); ++ assertThrows(UnsupportedOperationException.class, () -> instance.addModifier(new AttributeModifier("test", 3, AttributeModifier.Operation.ADD_NUMBER))); ++ assertThrows(UnsupportedOperationException.class, () -> instance.setBaseValue(3.2)); ++ } ++} diff --git a/patches/server/0581-Add-missing-team-sidebar-display-slots.patch b/patches/server/0581-Add-missing-team-sidebar-display-slots.patch deleted file mode 100644 index b248d7c5226e..000000000000 --- a/patches/server/0581-Add-missing-team-sidebar-display-slots.patch +++ /dev/null @@ -1,114 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Jake Potrebic -Date: Fri, 1 Oct 2021 08:04:39 -0700 -Subject: [PATCH] Add missing team sidebar display slots - -== AT == -public org.bukkit.craftbukkit.scoreboard.CraftScoreboardTranslations -public org.bukkit.craftbukkit.scoreboard.CraftScoreboardTranslations toBukkitSlot(Lnet/minecraft/world/scores/DisplaySlot;)Lorg/bukkit/scoreboard/DisplaySlot; -public org.bukkit.craftbukkit.scoreboard.CraftScoreboardTranslations fromBukkitSlot(Lorg/bukkit/scoreboard/DisplaySlot;)Lnet/minecraft/world/scores/DisplaySlot; - -diff --git a/src/main/java/org/bukkit/craftbukkit/legacy/FieldRename.java b/src/main/java/org/bukkit/craftbukkit/legacy/FieldRename.java -index ef80e6b4dff557daaab1b9fde4d8d40171017e6c..271aad69af4db015970aad842a7bb34dcb6bfd0e 100644 ---- a/src/main/java/org/bukkit/craftbukkit/legacy/FieldRename.java -+++ b/src/main/java/org/bukkit/craftbukkit/legacy/FieldRename.java -@@ -35,6 +35,7 @@ public class FieldRename { - } - - return switch (owner) { -+ case "org/bukkit/scoreboard/DisplaySlot" -> FieldRename.convertDisplaySlot(from); // Paper - DisplaySlot - case "org/bukkit/block/banner/PatternType" -> FieldRename.convertPatternTypeName(apiVersion, from); - case "org/bukkit/enchantments/Enchantment" -> FieldRename.convertEnchantmentName(apiVersion, from); - case "org/bukkit/block/Biome" -> FieldRename.convertBiomeName(apiVersion, from); -@@ -60,6 +61,16 @@ public class FieldRename { - //} - // Paper end - -+ // Paper start - DisplaySlot -+ @DoNotReroute -+ public static String convertDisplaySlot(final String from) { -+ if (from.startsWith("SIDEBAR_") && !from.startsWith("SIDEBAR_TEAM_")) { -+ return from.replace("SIDEBAR_", "SIDEBAR_TEAM_"); -+ } -+ return from; -+ } -+ // Paper end - DisplaySlot -+ - // PatternType - private static final FieldRenameData PATTERN_TYPE_DATA = FieldRenameData.Builder.newBuilder() - .forVersionsBefore(ApiVersion.FIELD_NAME_PARITY) -diff --git a/src/main/java/org/bukkit/craftbukkit/scoreboard/CraftScoreboardTranslations.java b/src/main/java/org/bukkit/craftbukkit/scoreboard/CraftScoreboardTranslations.java -index 73c5ffff70605b32188a9bb5fb6c0ee04cb66efe..711d227f5ee6d63356a94a0567968da48e9f284c 100644 ---- a/src/main/java/org/bukkit/craftbukkit/scoreboard/CraftScoreboardTranslations.java -+++ b/src/main/java/org/bukkit/craftbukkit/scoreboard/CraftScoreboardTranslations.java -@@ -7,35 +7,22 @@ import org.bukkit.scoreboard.RenderType; - - public final class CraftScoreboardTranslations { - static final int MAX_DISPLAY_SLOT = 19; -+ @Deprecated // Paper - static final ImmutableBiMap SLOTS = ImmutableBiMap.builder() - .put(DisplaySlot.BELOW_NAME, "below_name") - .put(DisplaySlot.PLAYER_LIST, "list") - .put(DisplaySlot.SIDEBAR, "sidebar") -- .put(DisplaySlot.SIDEBAR_BLACK, "sidebar.team.black") -- .put(DisplaySlot.SIDEBAR_DARK_BLUE, "sidebar.team.dark_blue") -- .put(DisplaySlot.SIDEBAR_DARK_GREEN, "sidebar.team.dark_green") -- .put(DisplaySlot.SIDEBAR_DARK_AQUA, "sidebar.team.dark_aqua") -- .put(DisplaySlot.SIDEBAR_DARK_RED, "sidebar.team.dark_red") -- .put(DisplaySlot.SIDEBAR_DARK_PURPLE, "sidebar.team.dark_purple") -- .put(DisplaySlot.SIDEBAR_GOLD, "sidebar.team.gold") -- .put(DisplaySlot.SIDEBAR_GRAY, "sidebar.team.gray") -- .put(DisplaySlot.SIDEBAR_DARK_GRAY, "sidebar.team.dark_gray") -- .put(DisplaySlot.SIDEBAR_BLUE, "sidebar.team.blue") -- .put(DisplaySlot.SIDEBAR_GREEN, "sidebar.team.green") -- .put(DisplaySlot.SIDEBAR_AQUA, "sidebar.team.aqua") -- .put(DisplaySlot.SIDEBAR_RED, "sidebar.team.red") -- .put(DisplaySlot.SIDEBAR_LIGHT_PURPLE, "sidebar.team.light_purple") -- .put(DisplaySlot.SIDEBAR_YELLOW, "sidebar.team.yellow") -- .put(DisplaySlot.SIDEBAR_WHITE, "sidebar.team.white") - .buildOrThrow(); - - private CraftScoreboardTranslations() {} - - public static DisplaySlot toBukkitSlot(net.minecraft.world.scores.DisplaySlot minecraft) { -+ if (true) return DisplaySlot.NAMES.value(minecraft.getSerializedName()); // Paper - return CraftScoreboardTranslations.SLOTS.inverse().get(minecraft.getSerializedName()); - } - - public static net.minecraft.world.scores.DisplaySlot fromBukkitSlot(DisplaySlot slot) { -+ if (true) return net.minecraft.world.scores.DisplaySlot.CODEC.byName(slot.getId()); // Paper - return net.minecraft.world.scores.DisplaySlot.CODEC.byName(CraftScoreboardTranslations.SLOTS.get(slot)); - } - -diff --git a/src/test/java/io/papermc/paper/scoreboard/DisplaySlotTest.java b/src/test/java/io/papermc/paper/scoreboard/DisplaySlotTest.java -new file mode 100644 -index 0000000000000000000000000000000000000000..345c96bfb6c559b41c2b6682067198a74d35b440 ---- /dev/null -+++ b/src/test/java/io/papermc/paper/scoreboard/DisplaySlotTest.java -@@ -0,0 +1,26 @@ -+package io.papermc.paper.scoreboard; -+ -+import org.bukkit.craftbukkit.scoreboard.CraftScoreboardTranslations; -+import org.bukkit.scoreboard.DisplaySlot; -+import org.bukkit.support.environment.Normal; -+import org.junit.jupiter.api.Test; -+ -+import static org.junit.jupiter.api.Assertions.assertNotNull; -+ -+@Normal -+public class DisplaySlotTest { -+ -+ @Test -+ public void testBukkitToMinecraftDisplaySlots() { -+ for (DisplaySlot bukkitSlot : DisplaySlot.values()) { -+ assertNotNull(CraftScoreboardTranslations.fromBukkitSlot(bukkitSlot)); -+ } -+ } -+ -+ @Test -+ public void testMinecraftToBukkitDisplaySlots() { -+ for (net.minecraft.world.scores.DisplaySlot nmsSlot : net.minecraft.world.scores.DisplaySlot.values()) { -+ assertNotNull(CraftScoreboardTranslations.toBukkitSlot(nmsSlot)); -+ } -+ } -+} diff --git a/patches/server/0581-Left-handed-API.patch b/patches/server/0581-Left-handed-API.patch new file mode 100644 index 000000000000..50bb0d4ebea4 --- /dev/null +++ b/patches/server/0581-Left-handed-API.patch @@ -0,0 +1,27 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: William Blake Galbreath +Date: Thu, 14 Oct 2021 12:36:58 -0500 +Subject: [PATCH] Left handed API + + +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftMob.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftMob.java +index 7cf42f62d91c131b1cab576979f85c58c3cecb3b..e226a99d00c990a4ca4f21b93fcae7a556e01dbb 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftMob.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftMob.java +@@ -145,6 +145,16 @@ public abstract class CraftMob extends CraftLivingEntity implements Mob { + public int getMaxHeadPitch() { + return getHandle().getMaxHeadXRot(); + } ++ ++ @Override ++ public boolean isLeftHanded() { ++ return getHandle().isLeftHanded(); ++ } ++ ++ @Override ++ public void setLeftHanded(boolean leftHanded) { ++ getHandle().setLeftHanded(leftHanded); ++ } + // Paper end + + // Paper start diff --git a/patches/server/0582-Add-back-EntityPortalExitEvent.patch b/patches/server/0582-Add-back-EntityPortalExitEvent.patch deleted file mode 100644 index b39d47b31a90..000000000000 --- a/patches/server/0582-Add-back-EntityPortalExitEvent.patch +++ /dev/null @@ -1,48 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Jake Potrebic -Date: Sun, 16 May 2021 09:39:46 -0700 -Subject: [PATCH] Add back EntityPortalExitEvent - - -diff --git a/src/main/java/net/minecraft/world/entity/Entity.java b/src/main/java/net/minecraft/world/entity/Entity.java -index 9844550e4ed6c150250b165acc26d52ec9401184..65b1833125ce4b1190bf276a69ad4c0c88875d58 100644 ---- a/src/main/java/net/minecraft/world/entity/Entity.java -+++ b/src/main/java/net/minecraft/world/entity/Entity.java -@@ -3363,7 +3363,7 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess - if (world instanceof ServerLevel worldserver) { - if (!this.isRemoved()) { - // CraftBukkit start -- Location to = new Location(teleportTarget.newLevel().getWorld(), teleportTarget.pos().x, teleportTarget.pos().y, teleportTarget.pos().z, teleportTarget.yRot(), teleportTarget.xRot()); -+ Location to = new Location(teleportTarget.newLevel().getWorld(), teleportTarget.pos().x, teleportTarget.pos().y, teleportTarget.pos().z, teleportTarget.yRot(), this.getXRot()); // Paper - use getXRot (doesn't respect DimensionTransition pitch) - // Paper start - gateway-specific teleport event - final EntityTeleportEvent teleEvent; - if (this.portalProcess != null && this.portalProcess.isSamePortal(((net.minecraft.world.level.block.EndGatewayBlock) net.minecraft.world.level.block.Blocks.END_GATEWAY)) && this.level.getBlockEntity(this.portalProcess.getEntryPosition()) instanceof net.minecraft.world.level.block.entity.TheEndGatewayBlockEntity theEndGatewayBlockEntity) { -@@ -3377,7 +3377,27 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess - return null; - } - to = teleEvent.getTo(); -- teleportTarget = new DimensionTransition(((CraftWorld) to.getWorld()).getHandle(), CraftLocation.toVec3D(to), teleportTarget.speed(), to.getYaw(), to.getPitch(), teleportTarget.missingRespawnBlock(), teleportTarget.postDimensionTransition(), teleportTarget.cause()); -+ // Paper start - Call EntityPortalExitEvent -+ if (this.portalProcess != null) { // if in a portal -+ CraftEntity bukkitEntity = this.getBukkitEntity(); -+ Vec3 velocity = teleportTarget.speed(); -+ org.bukkit.event.entity.EntityPortalExitEvent event = new org.bukkit.event.entity.EntityPortalExitEvent( -+ bukkitEntity, -+ bukkitEntity.getLocation(), to.clone(), -+ bukkitEntity.getVelocity(), org.bukkit.craftbukkit.util.CraftVector.toBukkit(velocity) -+ ); -+ event.callEvent(); -+ -+ if (!event.isCancelled() && event.getTo() != null) { -+ to = event.getTo().clone(); -+ velocity = org.bukkit.craftbukkit.util.CraftVector.toNMS(event.getAfter()); -+ } -+ teleportTarget = new DimensionTransition(((CraftWorld) to.getWorld()).getHandle(), CraftLocation.toVec3D(to), velocity, to.getYaw(), to.getPitch(), teleportTarget.missingRespawnBlock(), teleportTarget.postDimensionTransition(), teleportTarget.cause()); -+ } -+ if (this.isRemoved()) { -+ return null; -+ } -+ // Paper end - Call EntityPortalExitEvent - // CraftBukkit end - ServerLevel worldserver1 = teleportTarget.newLevel(); - List list = this.getPassengers(); diff --git a/patches/server/0586-Add-more-advancement-API.patch b/patches/server/0582-Add-more-advancement-API.patch similarity index 100% rename from patches/server/0586-Add-more-advancement-API.patch rename to patches/server/0582-Add-more-advancement-API.patch diff --git a/patches/server/0583-Add-ItemFactory-getSpawnEgg-API.patch b/patches/server/0583-Add-ItemFactory-getSpawnEgg-API.patch new file mode 100644 index 000000000000..715b0b753d24 --- /dev/null +++ b/patches/server/0583-Add-ItemFactory-getSpawnEgg-API.patch @@ -0,0 +1,58 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: William Blake Galbreath +Date: Thu, 14 Oct 2021 12:09:39 -0500 +Subject: [PATCH] Add ItemFactory#getSpawnEgg API + + +diff --git a/src/main/java/org/bukkit/craftbukkit/inventory/CraftItemFactory.java b/src/main/java/org/bukkit/craftbukkit/inventory/CraftItemFactory.java +index cab7a3d21699605cb7fc480830d7529f70e69e88..ad86ee4372e55c82968fd4fc6a65debab0092028 100644 +--- a/src/main/java/org/bukkit/craftbukkit/inventory/CraftItemFactory.java ++++ b/src/main/java/org/bukkit/craftbukkit/inventory/CraftItemFactory.java +@@ -9,6 +9,7 @@ import net.minecraft.core.HolderSet; + import net.minecraft.core.RegistryAccess; + import net.minecraft.core.component.DataComponentPatch; + import net.minecraft.core.registries.Registries; ++import net.minecraft.resources.ResourceLocation; + import net.minecraft.server.MinecraftServer; + import net.minecraft.tags.EnchantmentTags; + import net.minecraft.util.RandomSource; +@@ -289,4 +290,19 @@ public final class CraftItemFactory implements ItemFactory { + new net.md_5.bungee.api.chat.TextComponent(customName)); + } + // Paper end - bungee hover events ++ ++ // Paper start - old getSpawnEgg API ++ // @Override // used to override, upstream added conflicting method, is called via Commodore now ++ @Deprecated ++ public ItemStack getSpawnEgg0(org.bukkit.entity.EntityType type) { ++ if (type == null) { ++ return null; ++ } ++ String typeId = type.getKey().toString(); ++ net.minecraft.resources.ResourceLocation typeKey = ResourceLocation.parse(typeId); ++ net.minecraft.world.entity.EntityType nmsType = net.minecraft.core.registries.BuiltInRegistries.ENTITY_TYPE.getValue(typeKey); ++ net.minecraft.world.item.SpawnEggItem eggItem = net.minecraft.world.item.SpawnEggItem.byId(nmsType); ++ return eggItem == null ? null : new net.minecraft.world.item.ItemStack(eggItem).asBukkitMirror(); ++ } ++ // Paper end - old getSpawnEgg API + } +diff --git a/src/main/java/org/bukkit/craftbukkit/util/Commodore.java b/src/main/java/org/bukkit/craftbukkit/util/Commodore.java +index 34e41ea24e1673109f14153a1a0c8e794fed20bb..63d4ffe93a445abf3c766d4f1f8fbf4a412a2a03 100644 +--- a/src/main/java/org/bukkit/craftbukkit/util/Commodore.java ++++ b/src/main/java/org/bukkit/craftbukkit/util/Commodore.java +@@ -460,6 +460,15 @@ public class Commodore { + } + // Paper end + ++ // Paper start - ItemFactory#getSpawnEgg (paper had original method that returned ItemStack, upstream added identical but returned Material) ++ if (owner.equals("org/bukkit/inventory/ItemFactory") && name.equals("getSpawnEgg") && desc.equals("(Lorg/bukkit/entity/EntityType;)Lorg/bukkit/inventory/ItemStack;")) { ++ super.visitInsn(Opcodes.SWAP); // has 1 param, this moves the owner instance to the top for the checkcast ++ super.visitTypeInsn(Opcodes.CHECKCAST, runtimeCbPkgPrefix() + "inventory/CraftItemFactory"); ++ super.visitInsn(Opcodes.SWAP); // moves param back to the the top of stack ++ super.visitMethodInsn(Opcodes.INVOKEVIRTUAL, runtimeCbPkgPrefix() + "inventory/CraftItemFactory", "getSpawnEgg0", desc, false); ++ return; ++ } ++ // Paper end - ItemFactory#getSpawnEgg + if (modern) { + if (owner.equals("org/bukkit/Material") || (instantiatedMethodType != null && instantiatedMethodType.getDescriptor().startsWith("(Lorg/bukkit/Material;)"))) { + switch (name) { diff --git a/patches/server/0583-Add-methods-to-find-targets-for-lightning-strikes.patch b/patches/server/0583-Add-methods-to-find-targets-for-lightning-strikes.patch deleted file mode 100644 index ca096b9dd97c..000000000000 --- a/patches/server/0583-Add-methods-to-find-targets-for-lightning-strikes.patch +++ /dev/null @@ -1,60 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Jakub Zacek -Date: Mon, 4 Oct 2021 10:16:44 +0200 -Subject: [PATCH] Add methods to find targets for lightning strikes - -== AT == -public net.minecraft.server.level.ServerLevel findLightningRod(Lnet/minecraft/core/BlockPos;)Ljava/util/Optional; - -diff --git a/src/main/java/net/minecraft/server/level/ServerLevel.java b/src/main/java/net/minecraft/server/level/ServerLevel.java -index c72687fb23e8d01639cce7d79e3f97805d51e01f..778d3f3ea2247be5bd6edd382b872f6de5bc359c 100644 ---- a/src/main/java/net/minecraft/server/level/ServerLevel.java -+++ b/src/main/java/net/minecraft/server/level/ServerLevel.java -@@ -764,6 +764,11 @@ public class ServerLevel extends Level implements WorldGenLevel { - } - - protected BlockPos findLightningTargetAround(BlockPos pos) { -+ // Paper start - Add methods to find targets for lightning strikes -+ return this.findLightningTargetAround(pos, false); -+ } -+ public BlockPos findLightningTargetAround(BlockPos pos, boolean returnNullWhenNoTarget) { -+ // Paper end - Add methods to find targets for lightning strikes - BlockPos blockposition1 = this.getHeightmapPos(Heightmap.Types.MOTION_BLOCKING, pos); - Optional optional = this.findLightningRod(blockposition1); - -@@ -778,6 +783,7 @@ public class ServerLevel extends Level implements WorldGenLevel { - if (!list.isEmpty()) { - return ((LivingEntity) list.get(this.random.nextInt(list.size()))).blockPosition(); - } else { -+ if (returnNullWhenNoTarget) return null; // Paper - Add methods to find targets for lightning strikes - if (blockposition1.getY() == this.getMinBuildHeight() - 1) { - blockposition1 = blockposition1.above(2); - } -diff --git a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java -index a41c6705aa7e04ad32395f89b95ca76617c9416d..390cacc7916d1322a7e1e8bff004d415e9fa5622 100644 ---- a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java -+++ b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java -@@ -689,6 +689,23 @@ public class CraftWorld extends CraftRegionAccessor implements World { - return (LightningStrike) lightning.getBukkitEntity(); - } - -+ // Paper start - Add methods to find targets for lightning strikes -+ @Override -+ public Location findLightningRod(Location location) { -+ return this.world.findLightningRod(io.papermc.paper.util.MCUtil.toBlockPosition(location)) -+ .map(blockPos -> io.papermc.paper.util.MCUtil.toLocation(this.world, blockPos) -+ // get the actual rod pos -+ .subtract(0, 1, 0)) -+ .orElse(null); -+ } -+ -+ @Override -+ public Location findLightningTarget(Location location) { -+ final BlockPos pos = this.world.findLightningTargetAround(io.papermc.paper.util.MCUtil.toBlockPosition(location), true); -+ return pos == null ? null : io.papermc.paper.util.MCUtil.toLocation(this.world, pos); -+ } -+ // Paper end - Add methods to find targets for lightning strikes -+ - @Override - public boolean generateTree(Location loc, TreeType type) { - return this.generateTree(loc, CraftWorld.rand, type); diff --git a/patches/server/0584-Add-critical-damage-API.patch b/patches/server/0584-Add-critical-damage-API.patch new file mode 100644 index 000000000000..3ce4bba7c0f8 --- /dev/null +++ b/patches/server/0584-Add-critical-damage-API.patch @@ -0,0 +1,101 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: dodison +Date: Mon, 26 Jul 2021 17:32:36 +0200 +Subject: [PATCH] Add critical damage API + + +diff --git a/src/main/java/net/minecraft/world/damagesource/DamageSource.java b/src/main/java/net/minecraft/world/damagesource/DamageSource.java +index c1d121d83591ca1b5bf9d9406c9622b4f24eafef..aee26dd78953ff43306aaa64161f5b9edcdd4b83 100644 +--- a/src/main/java/net/minecraft/world/damagesource/DamageSource.java ++++ b/src/main/java/net/minecraft/world/damagesource/DamageSource.java +@@ -276,4 +276,18 @@ public class DamageSource { + public Holder typeHolder() { + return this.type; + } ++ ++ // Paper start - add critical damage API ++ private boolean critical; ++ public boolean isCritical() { ++ return this.critical; ++ } ++ public DamageSource critical() { ++ return this.critical(true); ++ } ++ public DamageSource critical(boolean critical) { ++ this.critical = critical; ++ return this; ++ } ++ // Paper end - add critical damage API + } +diff --git a/src/main/java/net/minecraft/world/entity/player/Player.java b/src/main/java/net/minecraft/world/entity/player/Player.java +index 57850f16a681af4fc302895c7608247675b44ab4..68a6b1508ce4544fe8b18746d440944d41a2fe5b 100644 +--- a/src/main/java/net/minecraft/world/entity/player/Player.java ++++ b/src/main/java/net/minecraft/world/entity/player/Player.java +@@ -1258,6 +1258,7 @@ public abstract class Player extends LivingEntity { + + flag2 = flag2 && !this.level().paperConfig().entities.behavior.disablePlayerCrits; // Paper - Toggleable player crits + if (flag2) { ++ damagesource = damagesource.critical(true); // Paper start - critical damage API + f *= 1.5F; + } + +@@ -1317,7 +1318,7 @@ public abstract class Player extends LivingEntity { + float f7 = this.getEnchantedDamage(entityliving2, f6, damagesource) * f2; + + // CraftBukkit start - Only apply knockback if the damage hits +- if (!entityliving2.hurtServer((ServerLevel) this.level(), this.damageSources().playerAttack(this).sweep(), f7)) { ++ if (!entityliving2.hurtServer((ServerLevel) this.level(), this.damageSources().playerAttack(this).sweep().critical(flag2), f7)) { // Paper - add critical damage API + continue; + } + // CraftBukkit end +diff --git a/src/main/java/net/minecraft/world/entity/projectile/AbstractArrow.java b/src/main/java/net/minecraft/world/entity/projectile/AbstractArrow.java +index bc167c21f82ad09952f6cdbf1016523062890f8b..44bcb1117cfa4d66c500011489ae193a0d1e7d78 100644 +--- a/src/main/java/net/minecraft/world/entity/projectile/AbstractArrow.java ++++ b/src/main/java/net/minecraft/world/entity/projectile/AbstractArrow.java +@@ -450,6 +450,7 @@ public abstract class AbstractArrow extends Projectile { + entityliving.setLastHurtMob(entity); + } + ++ if (this.isCritArrow()) damagesource = damagesource.critical(); // Paper - add critical damage API + boolean flag = entity.getType() == EntityType.ENDERMAN; + int k = entity.getRemainingFireTicks(); + +diff --git a/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java b/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java +index e6b22f24eba945863bb625b40319e6874ff25534..a9ba62a9d605234d993b6e5330889795099af391 100644 +--- a/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java ++++ b/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java +@@ -1078,7 +1078,7 @@ public class CraftEventFactory { + return CraftEventFactory.callEntityDamageEvent(source.getDirectBlock(), source.getDirectBlockState(), entity, DamageCause.BLOCK_EXPLOSION, bukkitDamageSource, modifiers, modifierFunctions, cancelled); + } + DamageCause damageCause = (damager.getBukkitEntity() instanceof org.bukkit.entity.TNTPrimed) ? DamageCause.BLOCK_EXPLOSION : DamageCause.ENTITY_EXPLOSION; +- return CraftEventFactory.callEntityDamageEvent(damager, entity, damageCause, bukkitDamageSource, modifiers, modifierFunctions, cancelled); ++ return CraftEventFactory.callEntityDamageEvent(damager, entity, damageCause, bukkitDamageSource, modifiers, modifierFunctions, cancelled, source.isCritical()); // Paper - add critical damage API + } else if (damager != null || source.getDirectEntity() != null) { + DamageCause cause = (source.isSweep()) ? DamageCause.ENTITY_SWEEP_ATTACK : DamageCause.ENTITY_ATTACK; + +@@ -1104,7 +1104,7 @@ public class CraftEventFactory { + cause = DamageCause.MAGIC; + } + +- return CraftEventFactory.callEntityDamageEvent(damager, entity, cause, bukkitDamageSource, modifiers, modifierFunctions, cancelled); ++ return CraftEventFactory.callEntityDamageEvent(damager, entity, cause, bukkitDamageSource, modifiers, modifierFunctions, cancelled, source.isCritical()); // Paper - add critical damage API + } else if (source.is(DamageTypes.FELL_OUT_OF_WORLD)) { + return CraftEventFactory.callEntityDamageEvent(source.getDirectBlock(), source.getDirectBlockState(), entity, DamageCause.VOID, bukkitDamageSource, modifiers, modifierFunctions, cancelled); + } else if (source.is(DamageTypes.LAVA)) { +@@ -1164,13 +1164,13 @@ public class CraftEventFactory { + cause = DamageCause.CUSTOM; + } + +- return CraftEventFactory.callEntityDamageEvent((Entity) null, entity, cause, bukkitDamageSource, modifiers, modifierFunctions, cancelled); ++ return CraftEventFactory.callEntityDamageEvent((Entity) null, entity, cause, bukkitDamageSource, modifiers, modifierFunctions, cancelled, source.isCritical()); // Paper - add critical damage API + } + +- private static EntityDamageEvent callEntityDamageEvent(Entity damager, Entity damagee, DamageCause cause, org.bukkit.damage.DamageSource bukkitDamageSource, Map modifiers, Map> modifierFunctions, boolean cancelled) { ++ private static EntityDamageEvent callEntityDamageEvent(Entity damager, Entity damagee, DamageCause cause, org.bukkit.damage.DamageSource bukkitDamageSource, Map modifiers, Map> modifierFunctions, boolean cancelled, boolean critical) { // Paper - add critical damage API + EntityDamageEvent event; + if (damager != null) { +- event = new EntityDamageByEntityEvent(damager.getBukkitEntity(), damagee.getBukkitEntity(), cause, bukkitDamageSource, modifiers, modifierFunctions); ++ event = new EntityDamageByEntityEvent(damager.getBukkitEntity(), damagee.getBukkitEntity(), cause, bukkitDamageSource, modifiers, modifierFunctions, critical); + } else { + event = new EntityDamageEvent(damagee.getBukkitEntity(), cause, bukkitDamageSource, modifiers, modifierFunctions); + } diff --git a/patches/server/0584-Get-entity-default-attributes.patch b/patches/server/0584-Get-entity-default-attributes.patch deleted file mode 100644 index fb93fa58062e..000000000000 --- a/patches/server/0584-Get-entity-default-attributes.patch +++ /dev/null @@ -1,151 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Jake Potrebic -Date: Fri, 20 Aug 2021 13:03:21 -0700 -Subject: [PATCH] Get entity default attributes - -== AT == -public net.minecraft.world.entity.ai.attributes.AttributeSupplier getAttributeInstance(Lnet/minecraft/core/Holder;)Lnet/minecraft/world/entity/ai/attributes/AttributeInstance; - -diff --git a/src/main/java/io/papermc/paper/attribute/UnmodifiableAttributeInstance.java b/src/main/java/io/papermc/paper/attribute/UnmodifiableAttributeInstance.java -new file mode 100644 -index 0000000000000000000000000000000000000000..12135ffeacd648f6bc4d7d327059ea1a7e8c79c4 ---- /dev/null -+++ b/src/main/java/io/papermc/paper/attribute/UnmodifiableAttributeInstance.java -@@ -0,0 +1,30 @@ -+package io.papermc.paper.attribute; -+ -+import net.minecraft.world.entity.ai.attributes.AttributeInstance; -+import org.bukkit.attribute.Attribute; -+import org.bukkit.attribute.AttributeModifier; -+import org.bukkit.craftbukkit.attribute.CraftAttributeInstance; -+ -+import java.util.Collection; -+ -+public class UnmodifiableAttributeInstance extends CraftAttributeInstance { -+ -+ public UnmodifiableAttributeInstance(AttributeInstance handle, Attribute attribute) { -+ super(handle, attribute); -+ } -+ -+ @Override -+ public void setBaseValue(double d) { -+ throw new UnsupportedOperationException("Cannot modify default attributes"); -+ } -+ -+ @Override -+ public void addModifier(AttributeModifier modifier) { -+ throw new UnsupportedOperationException("Cannot modify default attributes"); -+ } -+ -+ @Override -+ public void removeModifier(AttributeModifier modifier) { -+ throw new UnsupportedOperationException("Cannot modify default attributes"); -+ } -+} -diff --git a/src/main/java/io/papermc/paper/attribute/UnmodifiableAttributeMap.java b/src/main/java/io/papermc/paper/attribute/UnmodifiableAttributeMap.java -new file mode 100644 -index 0000000000000000000000000000000000000000..ec9ebd2d539333293c51b7edfa18f18b066d7e43 ---- /dev/null -+++ b/src/main/java/io/papermc/paper/attribute/UnmodifiableAttributeMap.java -@@ -0,0 +1,32 @@ -+package io.papermc.paper.attribute; -+ -+import net.minecraft.world.entity.ai.attributes.AttributeSupplier; -+import org.bukkit.attribute.Attributable; -+import org.bukkit.attribute.Attribute; -+import org.bukkit.attribute.AttributeInstance; -+import org.bukkit.craftbukkit.attribute.CraftAttribute; -+import org.jetbrains.annotations.NotNull; -+import org.jetbrains.annotations.Nullable; -+ -+public class UnmodifiableAttributeMap implements Attributable { -+ -+ private final AttributeSupplier handle; -+ -+ public UnmodifiableAttributeMap(@NotNull AttributeSupplier handle) { -+ this.handle = handle; -+ } -+ -+ @Override -+ public @Nullable AttributeInstance getAttribute(@NotNull Attribute attribute) { -+ net.minecraft.core.Holder nmsAttribute = CraftAttribute.bukkitToMinecraftHolder(attribute); -+ if (!this.handle.hasAttribute(nmsAttribute)) { -+ return null; -+ } -+ return new UnmodifiableAttributeInstance(this.handle.getAttributeInstance(nmsAttribute), attribute); -+ } -+ -+ @Override -+ public void registerAttribute(@NotNull Attribute attribute) { -+ throw new UnsupportedOperationException("Cannot register new attributes here"); -+ } -+} -diff --git a/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java b/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java -index 5e8081350b2ec375373d8197bd1f3196652ec9d9..70c06eb6e743232d0e8243b12a927084c6c9414b 100644 ---- a/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java -+++ b/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java -@@ -544,6 +544,18 @@ public final class CraftMagicNumbers implements UnsafeValues { - } - return CraftMagicNumbers.getItem(itemToBeRepaired.getType()).isValidRepairItem(CraftItemStack.asNMSCopy(itemToBeRepaired), CraftItemStack.asNMSCopy(repairMaterial)); - } -+ -+ @Override -+ public boolean hasDefaultEntityAttributes(NamespacedKey bukkitEntityKey) { -+ return net.minecraft.world.entity.ai.attributes.DefaultAttributes.hasSupplier(net.minecraft.core.registries.BuiltInRegistries.ENTITY_TYPE.get(CraftNamespacedKey.toMinecraft(bukkitEntityKey))); -+ } -+ -+ @Override -+ public org.bukkit.attribute.Attributable getDefaultEntityAttributes(NamespacedKey bukkitEntityKey) { -+ Preconditions.checkArgument(hasDefaultEntityAttributes(bukkitEntityKey), bukkitEntityKey + " doesn't have default attributes"); -+ var supplier = net.minecraft.world.entity.ai.attributes.DefaultAttributes.getSupplier((net.minecraft.world.entity.EntityType) net.minecraft.core.registries.BuiltInRegistries.ENTITY_TYPE.get(CraftNamespacedKey.toMinecraft(bukkitEntityKey))); -+ return new io.papermc.paper.attribute.UnmodifiableAttributeMap(supplier); -+ } - // Paper end - - @Override -diff --git a/src/test/java/io/papermc/paper/attribute/EntityTypeAttributesTest.java b/src/test/java/io/papermc/paper/attribute/EntityTypeAttributesTest.java -new file mode 100644 -index 0000000000000000000000000000000000000000..f512d416df883036965ff6c802fd242a4c9c8079 ---- /dev/null -+++ b/src/test/java/io/papermc/paper/attribute/EntityTypeAttributesTest.java -@@ -0,0 +1,40 @@ -+package io.papermc.paper.attribute; -+ -+import org.bukkit.attribute.Attributable; -+import org.bukkit.attribute.Attribute; -+import org.bukkit.attribute.AttributeInstance; -+import org.bukkit.attribute.AttributeModifier; -+import org.bukkit.entity.EntityType; -+import org.bukkit.support.environment.AllFeatures; -+import org.junit.jupiter.api.Test; -+ -+import static org.junit.jupiter.api.Assertions.assertFalse; -+import static org.junit.jupiter.api.Assertions.assertNotNull; -+import static org.junit.jupiter.api.Assertions.assertThrows; -+import static org.junit.jupiter.api.Assertions.assertTrue; -+ -+@AllFeatures -+public class EntityTypeAttributesTest { -+ -+ @Test -+ public void testIllegalEntity() { -+ assertFalse(EntityType.EGG.hasDefaultAttributes()); -+ assertThrows(IllegalArgumentException.class, EntityType.EGG::getDefaultAttributes); -+ } -+ -+ @Test -+ public void testLegalEntity() { -+ assertTrue(EntityType.ZOMBIE.hasDefaultAttributes()); -+ EntityType.ZOMBIE.getDefaultAttributes(); -+ } -+ -+ @Test -+ public void testUnmodifiabilityOfAttributable() { -+ Attributable attributable = EntityType.ZOMBIE.getDefaultAttributes(); -+ assertThrows(UnsupportedOperationException.class, () -> attributable.registerAttribute(Attribute.GENERIC_ATTACK_DAMAGE)); -+ AttributeInstance instance = attributable.getAttribute(Attribute.GENERIC_FOLLOW_RANGE); -+ assertNotNull(instance); -+ assertThrows(UnsupportedOperationException.class, () -> instance.addModifier(new AttributeModifier("test", 3, AttributeModifier.Operation.ADD_NUMBER))); -+ assertThrows(UnsupportedOperationException.class, () -> instance.setBaseValue(3.2)); -+ } -+} diff --git a/patches/server/0585-Fix-issues-with-mob-conversion.patch b/patches/server/0585-Fix-issues-with-mob-conversion.patch new file mode 100644 index 000000000000..281a6e42c558 --- /dev/null +++ b/patches/server/0585-Fix-issues-with-mob-conversion.patch @@ -0,0 +1,74 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Jake Potrebic +Date: Sun, 24 Oct 2021 20:29:45 -0700 +Subject: [PATCH] Fix issues with mob conversion + + +diff --git a/src/main/java/net/minecraft/world/entity/monster/Skeleton.java b/src/main/java/net/minecraft/world/entity/monster/Skeleton.java +index 6f6454bcec7e0d1cefbf818fc2fc6eb90adeec83..3972e2ed0554e2550519e994888e068df0a151e5 100644 +--- a/src/main/java/net/minecraft/world/entity/monster/Skeleton.java ++++ b/src/main/java/net/minecraft/world/entity/monster/Skeleton.java +@@ -94,12 +94,19 @@ public class Skeleton extends AbstractSkeleton { + } + + protected void doFreezeConversion() { +- this.convertTo(EntityType.STRAY, ConversionParams.single(this, true, true), (entityskeletonstray) -> { ++ final Stray stray = this.convertTo(EntityType.STRAY, ConversionParams.single(this, true, true), (entityskeletonstray) -> { // Paper - Fix issues with mob conversion; reset conversion time to prevent event spam + if (!this.isSilent()) { + this.level().levelEvent((Player) null, 1048, this.blockPosition(), 0); + } + +- }, org.bukkit.event.entity.EntityTransformEvent.TransformReason.FROZEN, org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason.FROZEN); // CraftBukkit - add spawn and transform reasons ++ }, org.bukkit.event.entity.EntityTransformEvent.TransformReason.FROZEN, org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason.FROZEN);// CraftBukkit - add spawn and transform reasons ++ ++ // Paper start - Fix issues with mob conversion; reset conversion time to prevent event spam ++ if (stray == null) { ++ this.conversionTime = 300; ++ } ++ // Paper end - Fix issues with mob conversion ++ + } + + @Override +diff --git a/src/main/java/net/minecraft/world/entity/monster/hoglin/Hoglin.java b/src/main/java/net/minecraft/world/entity/monster/hoglin/Hoglin.java +index d51acb788803048c0eacd762a2fd311115ac0970..6ea90e54759dbeab025e0a1896ee834ea9986427 100644 +--- a/src/main/java/net/minecraft/world/entity/monster/hoglin/Hoglin.java ++++ b/src/main/java/net/minecraft/world/entity/monster/hoglin/Hoglin.java +@@ -260,9 +260,15 @@ public class Hoglin extends Animal implements Enemy, HoglinBase { + } + + private void finishConversion() { +- this.convertTo( ++ net.minecraft.world.entity.Entity converted = this.convertTo( // Paper - Fix issues with mob conversion; reset to prevent event spam + EntityType.ZOGLIN, ConversionParams.single(this, true, false), zoglin -> zoglin.addEffect(new MobEffectInstance(MobEffects.CONFUSION, 200, 0)) + ); ++ ++ // Paper start - Fix issues with mob conversion; reset to prevent event spam ++ if (converted == null) { ++ this.timeInOverworld = 0; ++ } ++ // Paper end - Fix issues with mob conversion + } + + @Override +diff --git a/src/main/java/net/minecraft/world/entity/monster/piglin/AbstractPiglin.java b/src/main/java/net/minecraft/world/entity/monster/piglin/AbstractPiglin.java +index e2075fb22596b6dc4dbbb9c20c91c63f0ddb7ba7..e3a8aa0ae0d1fa6f2b697e20464224f8b8c6b8ea 100644 +--- a/src/main/java/net/minecraft/world/entity/monster/piglin/AbstractPiglin.java ++++ b/src/main/java/net/minecraft/world/entity/monster/piglin/AbstractPiglin.java +@@ -100,9 +100,15 @@ public abstract class AbstractPiglin extends Monster { + } + + protected void finishConversion(ServerLevel world) { +- this.convertTo(EntityType.ZOMBIFIED_PIGLIN, ConversionParams.single(this, true, true), (entitypigzombie) -> { ++ net.minecraft.world.entity.Entity converted = this.convertTo(EntityType.ZOMBIFIED_PIGLIN, ConversionParams.single(this, true, true), (entitypigzombie) -> { // Paper - Fix issues with mob conversion; reset to prevent event spam + entitypigzombie.addEffect(new MobEffectInstance(MobEffects.CONFUSION, 200, 0)); + }, org.bukkit.event.entity.EntityTransformEvent.TransformReason.PIGLIN_ZOMBIFIED, org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason.PIGLIN_ZOMBIFIED); // CraftBukkit - add spawn and transform reasons ++ ++ // Paper start - Fix issues with mob conversion; reset to prevent event spam ++ if (converted == null) { ++ this.timeInOverworld = 0; ++ } ++ // Paper end - Fix issues with mob conversion + } + + public boolean isAdult() { diff --git a/patches/server/0585-Left-handed-API.patch b/patches/server/0585-Left-handed-API.patch deleted file mode 100644 index 643df451a4a6..000000000000 --- a/patches/server/0585-Left-handed-API.patch +++ /dev/null @@ -1,27 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: William Blake Galbreath -Date: Thu, 14 Oct 2021 12:36:58 -0500 -Subject: [PATCH] Left handed API - - -diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftMob.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftMob.java -index fb29afb6517b009b81285adc9e6dca2eb7f74aee..921594a78ea511337434b29b5bc1a037eb30992c 100644 ---- a/src/main/java/org/bukkit/craftbukkit/entity/CraftMob.java -+++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftMob.java -@@ -144,6 +144,16 @@ public abstract class CraftMob extends CraftLivingEntity implements Mob { - public int getMaxHeadPitch() { - return getHandle().getMaxHeadXRot(); - } -+ -+ @Override -+ public boolean isLeftHanded() { -+ return getHandle().isLeftHanded(); -+ } -+ -+ @Override -+ public void setLeftHanded(boolean leftHanded) { -+ getHandle().setLeftHanded(leftHanded); -+ } - // Paper end - - // Paper start diff --git a/patches/server/0586-Add-hasCollision-methods-to-various-places.patch b/patches/server/0586-Add-hasCollision-methods-to-various-places.patch new file mode 100644 index 000000000000..421b0ba92e96 --- /dev/null +++ b/patches/server/0586-Add-hasCollision-methods-to-various-places.patch @@ -0,0 +1,56 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Jake Potrebic +Date: Thu, 4 Nov 2021 11:50:40 -0700 +Subject: [PATCH] Add hasCollision methods to various places + +== AT == +public net.minecraft.world.level.block.state.BlockBehaviour hasCollision + +diff --git a/src/main/java/org/bukkit/craftbukkit/block/CraftBlock.java b/src/main/java/org/bukkit/craftbukkit/block/CraftBlock.java +index 9c8aac69f01db647e20d49d272ccc107a7edceaf..d5b495b5a3ca7f4411d1a700f7149042a16509f1 100644 +--- a/src/main/java/org/bukkit/craftbukkit/block/CraftBlock.java ++++ b/src/main/java/org/bukkit/craftbukkit/block/CraftBlock.java +@@ -457,6 +457,11 @@ public class CraftBlock implements Block { + public boolean isSolid() { + return this.getNMS().blocksMotion(); + } ++ ++ @Override ++ public boolean isCollidable() { ++ return getNMS().getBlock().hasCollision; ++ } + // Paper end + + @Override +diff --git a/src/main/java/org/bukkit/craftbukkit/block/CraftBlockState.java b/src/main/java/org/bukkit/craftbukkit/block/CraftBlockState.java +index 1002123cd0c6f57cecc4e80f5f21cc6ff5886d37..e96023b71845526383288917e8d7c5759a4c0e9b 100644 +--- a/src/main/java/org/bukkit/craftbukkit/block/CraftBlockState.java ++++ b/src/main/java/org/bukkit/craftbukkit/block/CraftBlockState.java +@@ -341,4 +341,11 @@ public class CraftBlockState implements BlockState { + public BlockState copy(Location location) { + return new CraftBlockState(this, location); + } ++ ++ // Paper start ++ @Override ++ public boolean isCollidable() { ++ return this.data.getBlock().hasCollision; ++ } ++ // Paper end + } +diff --git a/src/main/java/org/bukkit/craftbukkit/block/CraftBlockType.java b/src/main/java/org/bukkit/craftbukkit/block/CraftBlockType.java +index 2d8a509446c0ed0d7358f10f67ef29c4df683696..785d3fe4929e008d4150f3ecab258fd5b6d7cdaf 100644 +--- a/src/main/java/org/bukkit/craftbukkit/block/CraftBlockType.java ++++ b/src/main/java/org/bukkit/craftbukkit/block/CraftBlockType.java +@@ -241,4 +241,11 @@ public class CraftBlockType implements BlockType.Typed, + return this.block.getDescriptionId(); + } + // Paper end - add Translatable ++ ++ // Paper start - hasCollision API ++ @Override ++ public boolean hasCollision() { ++ return this.block.hasCollision; ++ } ++ // Paper end - hasCollision API + } diff --git a/patches/server/0587-Add-ItemFactory-getSpawnEgg-API.patch b/patches/server/0587-Add-ItemFactory-getSpawnEgg-API.patch deleted file mode 100644 index 04c5e3b925ec..000000000000 --- a/patches/server/0587-Add-ItemFactory-getSpawnEgg-API.patch +++ /dev/null @@ -1,58 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: William Blake Galbreath -Date: Thu, 14 Oct 2021 12:09:39 -0500 -Subject: [PATCH] Add ItemFactory#getSpawnEgg API - - -diff --git a/src/main/java/org/bukkit/craftbukkit/inventory/CraftItemFactory.java b/src/main/java/org/bukkit/craftbukkit/inventory/CraftItemFactory.java -index eabb8b42b890224dd19b879ff276e9908674310d..803a19063c03627dbea79cb1c395ae35aaef2834 100644 ---- a/src/main/java/org/bukkit/craftbukkit/inventory/CraftItemFactory.java -+++ b/src/main/java/org/bukkit/craftbukkit/inventory/CraftItemFactory.java -@@ -9,6 +9,7 @@ import net.minecraft.core.HolderSet; - import net.minecraft.core.RegistryAccess; - import net.minecraft.core.component.DataComponentPatch; - import net.minecraft.core.registries.Registries; -+import net.minecraft.resources.ResourceLocation; - import net.minecraft.server.MinecraftServer; - import net.minecraft.tags.EnchantmentTags; - import net.minecraft.util.RandomSource; -@@ -286,4 +287,19 @@ public final class CraftItemFactory implements ItemFactory { - new net.md_5.bungee.api.chat.TextComponent(customName)); - } - // Paper end - bungee hover events -+ -+ // Paper start - old getSpawnEgg API -+ // @Override // used to override, upstream added conflicting method, is called via Commodore now -+ @Deprecated -+ public ItemStack getSpawnEgg0(org.bukkit.entity.EntityType type) { -+ if (type == null) { -+ return null; -+ } -+ String typeId = type.getKey().toString(); -+ net.minecraft.resources.ResourceLocation typeKey = ResourceLocation.parse(typeId); -+ net.minecraft.world.entity.EntityType nmsType = net.minecraft.core.registries.BuiltInRegistries.ENTITY_TYPE.get(typeKey); -+ net.minecraft.world.item.SpawnEggItem eggItem = net.minecraft.world.item.SpawnEggItem.byId(nmsType); -+ return eggItem == null ? null : new net.minecraft.world.item.ItemStack(eggItem).asBukkitMirror(); -+ } -+ // Paper end - old getSpawnEgg API - } -diff --git a/src/main/java/org/bukkit/craftbukkit/util/Commodore.java b/src/main/java/org/bukkit/craftbukkit/util/Commodore.java -index 34e41ea24e1673109f14153a1a0c8e794fed20bb..63d4ffe93a445abf3c766d4f1f8fbf4a412a2a03 100644 ---- a/src/main/java/org/bukkit/craftbukkit/util/Commodore.java -+++ b/src/main/java/org/bukkit/craftbukkit/util/Commodore.java -@@ -460,6 +460,15 @@ public class Commodore { - } - // Paper end - -+ // Paper start - ItemFactory#getSpawnEgg (paper had original method that returned ItemStack, upstream added identical but returned Material) -+ if (owner.equals("org/bukkit/inventory/ItemFactory") && name.equals("getSpawnEgg") && desc.equals("(Lorg/bukkit/entity/EntityType;)Lorg/bukkit/inventory/ItemStack;")) { -+ super.visitInsn(Opcodes.SWAP); // has 1 param, this moves the owner instance to the top for the checkcast -+ super.visitTypeInsn(Opcodes.CHECKCAST, runtimeCbPkgPrefix() + "inventory/CraftItemFactory"); -+ super.visitInsn(Opcodes.SWAP); // moves param back to the the top of stack -+ super.visitMethodInsn(Opcodes.INVOKEVIRTUAL, runtimeCbPkgPrefix() + "inventory/CraftItemFactory", "getSpawnEgg0", desc, false); -+ return; -+ } -+ // Paper end - ItemFactory#getSpawnEgg - if (modern) { - if (owner.equals("org/bukkit/Material") || (instantiatedMethodType != null && instantiatedMethodType.getDescriptor().startsWith("(Lorg/bukkit/Material;)"))) { - switch (name) { diff --git a/patches/server/0587-Goat-ram-API.patch b/patches/server/0587-Goat-ram-API.patch new file mode 100644 index 000000000000..21d6196f4c5e --- /dev/null +++ b/patches/server/0587-Goat-ram-API.patch @@ -0,0 +1,42 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Seggan +Date: Thu, 5 Aug 2021 13:10:27 -0400 +Subject: [PATCH] Goat ram API + + +diff --git a/src/main/java/net/minecraft/world/entity/animal/goat/Goat.java b/src/main/java/net/minecraft/world/entity/animal/goat/Goat.java +index cccf0084d273eaded91abe249d39a843f11d351b..14e02f9b0169db8388c515a68315ad5cc3f13d22 100644 +--- a/src/main/java/net/minecraft/world/entity/animal/goat/Goat.java ++++ b/src/main/java/net/minecraft/world/entity/animal/goat/Goat.java +@@ -395,4 +395,15 @@ public class Goat extends Animal { + public static boolean checkGoatSpawnRules(EntityType entityType, LevelAccessor world, EntitySpawnReason spawnReason, BlockPos pos, RandomSource random) { + return world.getBlockState(pos.below()).is(BlockTags.GOATS_SPAWNABLE_ON) && isBrightEnoughToSpawn(world, pos); + } ++ ++ // Paper start - Goat ram API ++ public void ram(net.minecraft.world.entity.LivingEntity entity) { ++ Brain brain = this.getBrain(); ++ brain.setMemory(MemoryModuleType.RAM_TARGET, entity.position()); ++ brain.eraseMemory(MemoryModuleType.RAM_COOLDOWN_TICKS); ++ brain.eraseMemory(MemoryModuleType.BREED_TARGET); ++ brain.eraseMemory(MemoryModuleType.TEMPTING_PLAYER); ++ brain.setActiveActivityIfPossible(net.minecraft.world.entity.schedule.Activity.RAM); ++ } ++ // Paper end - Goat ram API + } +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftGoat.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftGoat.java +index 65fcb36e849da6949c123a6f3672b485036f368e..2c21de478bff9cdf13ba46cd041831d54c11e924 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftGoat.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftGoat.java +@@ -48,4 +48,11 @@ public class CraftGoat extends CraftAnimals implements Goat { + public void setScreaming(boolean screaming) { + this.getHandle().setScreamingGoat(screaming); + } ++ ++ // Paper start - Goat ram API ++ @Override ++ public void ram(@org.jetbrains.annotations.NotNull org.bukkit.entity.LivingEntity entity) { ++ this.getHandle().ram(((CraftLivingEntity) entity).getHandle()); ++ } ++ // Paper end + } diff --git a/patches/server/0592-Add-API-for-resetting-a-single-score.patch b/patches/server/0588-Add-API-for-resetting-a-single-score.patch similarity index 100% rename from patches/server/0592-Add-API-for-resetting-a-single-score.patch rename to patches/server/0588-Add-API-for-resetting-a-single-score.patch diff --git a/patches/server/0588-Add-critical-damage-API.patch b/patches/server/0588-Add-critical-damage-API.patch deleted file mode 100644 index 554a8471d917..000000000000 --- a/patches/server/0588-Add-critical-damage-API.patch +++ /dev/null @@ -1,101 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: dodison -Date: Mon, 26 Jul 2021 17:32:36 +0200 -Subject: [PATCH] Add critical damage API - - -diff --git a/src/main/java/net/minecraft/world/damagesource/DamageSource.java b/src/main/java/net/minecraft/world/damagesource/DamageSource.java -index c1d121d83591ca1b5bf9d9406c9622b4f24eafef..aee26dd78953ff43306aaa64161f5b9edcdd4b83 100644 ---- a/src/main/java/net/minecraft/world/damagesource/DamageSource.java -+++ b/src/main/java/net/minecraft/world/damagesource/DamageSource.java -@@ -276,4 +276,18 @@ public class DamageSource { - public Holder typeHolder() { - return this.type; - } -+ -+ // Paper start - add critical damage API -+ private boolean critical; -+ public boolean isCritical() { -+ return this.critical; -+ } -+ public DamageSource critical() { -+ return this.critical(true); -+ } -+ public DamageSource critical(boolean critical) { -+ this.critical = critical; -+ return this; -+ } -+ // Paper end - add critical damage API - } -diff --git a/src/main/java/net/minecraft/world/entity/player/Player.java b/src/main/java/net/minecraft/world/entity/player/Player.java -index 7fee6ffeb8ccde965fcc1454eb0d8c6b3637da41..e772b6a501b225f13399365ad743cabe5f6f792e 100644 ---- a/src/main/java/net/minecraft/world/entity/player/Player.java -+++ b/src/main/java/net/minecraft/world/entity/player/Player.java -@@ -1293,6 +1293,7 @@ public abstract class Player extends LivingEntity { - - flag2 = flag2 && !this.level().paperConfig().entities.behavior.disablePlayerCrits; // Paper - Toggleable player crits - if (flag2) { -+ damagesource = damagesource.critical(true); // Paper start - critical damage API - f *= 1.5F; - } - -@@ -1352,7 +1353,7 @@ public abstract class Player extends LivingEntity { - float f7 = this.getEnchantedDamage(entityliving2, f6, damagesource) * f2; - - // CraftBukkit start - Only apply knockback if the damage hits -- if (!entityliving2.hurt(this.damageSources().playerAttack(this).sweep(), f7)) { -+ if (!entityliving2.hurt(this.damageSources().playerAttack(this).sweep().critical(flag2), f7)) { // Paper - add critical damage API - continue; - } - // CraftBukkit end -diff --git a/src/main/java/net/minecraft/world/entity/projectile/AbstractArrow.java b/src/main/java/net/minecraft/world/entity/projectile/AbstractArrow.java -index 746bb8a36bd6c6ef953289576af499caad588d79..57ebb96707748e90810dc07471f9769f1317df9d 100644 ---- a/src/main/java/net/minecraft/world/entity/projectile/AbstractArrow.java -+++ b/src/main/java/net/minecraft/world/entity/projectile/AbstractArrow.java -@@ -422,6 +422,7 @@ public abstract class AbstractArrow extends Projectile { - entityliving.setLastHurtMob(entity); - } - -+ if (this.isCritArrow()) damagesource = damagesource.critical(); // Paper - add critical damage API - boolean flag = entity.getType() == EntityType.ENDERMAN; - int k = entity.getRemainingFireTicks(); - -diff --git a/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java b/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java -index b93fa1ea73f0b218e6c6bed8bd36694e26544ab0..5fc6ef13cdc9df11b0fd2b0baf3cec862ccb5e37 100644 ---- a/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java -+++ b/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java -@@ -1080,7 +1080,7 @@ public class CraftEventFactory { - return CraftEventFactory.callEntityDamageEvent(source.getDirectBlock(), source.getDirectBlockState(), entity, DamageCause.BLOCK_EXPLOSION, bukkitDamageSource, modifiers, modifierFunctions, cancelled); - } - DamageCause damageCause = (damager.getBukkitEntity() instanceof org.bukkit.entity.TNTPrimed) ? DamageCause.BLOCK_EXPLOSION : DamageCause.ENTITY_EXPLOSION; -- return CraftEventFactory.callEntityDamageEvent(damager, entity, damageCause, bukkitDamageSource, modifiers, modifierFunctions, cancelled); -+ return CraftEventFactory.callEntityDamageEvent(damager, entity, damageCause, bukkitDamageSource, modifiers, modifierFunctions, cancelled, source.isCritical()); // Paper - add critical damage API - } else if (damager != null || source.getDirectEntity() != null) { - DamageCause cause = (source.isSweep()) ? DamageCause.ENTITY_SWEEP_ATTACK : DamageCause.ENTITY_ATTACK; - -@@ -1106,7 +1106,7 @@ public class CraftEventFactory { - cause = DamageCause.MAGIC; - } - -- return CraftEventFactory.callEntityDamageEvent(damager, entity, cause, bukkitDamageSource, modifiers, modifierFunctions, cancelled); -+ return CraftEventFactory.callEntityDamageEvent(damager, entity, cause, bukkitDamageSource, modifiers, modifierFunctions, cancelled, source.isCritical()); // Paper - add critical damage API - } else if (source.is(DamageTypes.FELL_OUT_OF_WORLD)) { - return CraftEventFactory.callEntityDamageEvent(source.getDirectBlock(), source.getDirectBlockState(), entity, DamageCause.VOID, bukkitDamageSource, modifiers, modifierFunctions, cancelled); - } else if (source.is(DamageTypes.LAVA)) { -@@ -1166,13 +1166,13 @@ public class CraftEventFactory { - cause = DamageCause.CUSTOM; - } - -- return CraftEventFactory.callEntityDamageEvent((Entity) null, entity, cause, bukkitDamageSource, modifiers, modifierFunctions, cancelled); -+ return CraftEventFactory.callEntityDamageEvent((Entity) null, entity, cause, bukkitDamageSource, modifiers, modifierFunctions, cancelled, source.isCritical()); // Paper - add critical damage API - } - -- private static EntityDamageEvent callEntityDamageEvent(Entity damager, Entity damagee, DamageCause cause, org.bukkit.damage.DamageSource bukkitDamageSource, Map modifiers, Map> modifierFunctions, boolean cancelled) { -+ private static EntityDamageEvent callEntityDamageEvent(Entity damager, Entity damagee, DamageCause cause, org.bukkit.damage.DamageSource bukkitDamageSource, Map modifiers, Map> modifierFunctions, boolean cancelled, boolean critical) { // Paper - add critical damage API - EntityDamageEvent event; - if (damager != null) { -- event = new EntityDamageByEntityEvent(damager.getBukkitEntity(), damagee.getBukkitEntity(), cause, bukkitDamageSource, modifiers, modifierFunctions); -+ event = new EntityDamageByEntityEvent(damager.getBukkitEntity(), damagee.getBukkitEntity(), cause, bukkitDamageSource, modifiers, modifierFunctions, critical); - } else { - event = new EntityDamageEvent(damagee.getBukkitEntity(), cause, bukkitDamageSource, modifiers, modifierFunctions); - } diff --git a/patches/server/0589-Add-Raw-Byte-Entity-Serialization.patch b/patches/server/0589-Add-Raw-Byte-Entity-Serialization.patch new file mode 100644 index 000000000000..660fb63c77aa --- /dev/null +++ b/patches/server/0589-Add-Raw-Byte-Entity-Serialization.patch @@ -0,0 +1,90 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Mariell Hoversholm +Date: Sun, 24 Oct 2021 16:20:31 -0400 +Subject: [PATCH] Add Raw Byte Entity Serialization + +== AT == +public net.minecraft.world.entity.Entity setLevel(Lnet/minecraft/world/level/Level;)V + +diff --git a/src/main/java/net/minecraft/world/entity/Entity.java b/src/main/java/net/minecraft/world/entity/Entity.java +index 88d3b1997bba632c5746d295bb39095f5d328369..900765b0129e8bf485aca93af6f523c9948e288b 100644 +--- a/src/main/java/net/minecraft/world/entity/Entity.java ++++ b/src/main/java/net/minecraft/world/entity/Entity.java +@@ -2260,6 +2260,15 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess + } + } + ++ // Paper start - Entity serialization api ++ public boolean serializeEntity(CompoundTag compound) { ++ List pass = new java.util.ArrayList<>(this.getPassengers()); ++ this.passengers = ImmutableList.of(); ++ boolean result = save(compound); ++ this.passengers = ImmutableList.copyOf(pass); ++ return result; ++ } ++ // Paper end - Entity serialization api + public boolean save(CompoundTag nbt) { + return this.isPassenger() ? false : this.saveAsPassenger(nbt); + } +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java +index 0c1c9033646dedcf1d11dee74d6965683adadf0a..1ed01978611cddb2558e441863dadc468bc07c3d 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java +@@ -1088,6 +1088,18 @@ public abstract class CraftEntity implements org.bukkit.entity.Entity { + } + // Paper end - tracked players API + ++ // Paper start - raw entity serialization API ++ @Override ++ public boolean spawnAt(Location location, org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason reason) { ++ Preconditions.checkNotNull(location, "location cannot be null"); ++ Preconditions.checkNotNull(reason, "reason cannot be null"); ++ this.entity.setLevel(((CraftWorld) location.getWorld()).getHandle()); ++ this.entity.setPos(location.getX(), location.getY(), location.getZ()); ++ this.entity.setRot(location.getYaw(), location.getPitch()); ++ return !this.entity.valid && this.entity.level().addFreshEntity(this.entity, reason); ++ } ++ // Paper end - raw entity serialization API ++ + // Paper start - missing entity api + @Override + public boolean isInvisible() { // Paper - moved up from LivingEntity +diff --git a/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java b/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java +index 31b625779dfe27602ac198259258e64195c1796d..1ab160c3d042be43df3bd19d095534b91c4c2f71 100644 +--- a/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java ++++ b/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java +@@ -499,7 +499,33 @@ public final class CraftMagicNumbers implements UnsafeValues { + return CraftItemStack.asCraftMirror(net.minecraft.world.item.ItemStack.parse(MinecraftServer.getServer().registryAccess(), compound).orElseThrow()); + } + +- private byte[] serializeNbtToBytes(CompoundTag compound) { ++ @Override ++ public byte[] serializeEntity(org.bukkit.entity.Entity entity) { ++ Preconditions.checkNotNull(entity, "null cannot be serialized"); ++ Preconditions.checkArgument(entity instanceof org.bukkit.craftbukkit.entity.CraftEntity, "only CraftEntities can be serialized"); ++ ++ net.minecraft.nbt.CompoundTag compound = new net.minecraft.nbt.CompoundTag(); ++ ((org.bukkit.craftbukkit.entity.CraftEntity) entity).getHandle().serializeEntity(compound); ++ return serializeNbtToBytes(compound); ++ } ++ ++ @Override ++ public org.bukkit.entity.Entity deserializeEntity(byte[] data, org.bukkit.World world, boolean preserveUUID) { ++ Preconditions.checkNotNull(data, "null cannot be deserialized"); ++ Preconditions.checkArgument(data.length > 0, "cannot deserialize nothing"); ++ ++ net.minecraft.nbt.CompoundTag compound = deserializeNbtFromBytes(data); ++ int dataVersion = compound.getInt("DataVersion"); ++ compound = (net.minecraft.nbt.CompoundTag) MinecraftServer.getServer().fixerUpper.update(References.ENTITY, new Dynamic<>(NbtOps.INSTANCE, compound), dataVersion, this.getDataVersion()).getValue(); ++ if (!preserveUUID) { ++ // Generate a new UUID so we don't have to worry about deserializing the same entity twice ++ compound.remove("UUID"); ++ } ++ return net.minecraft.world.entity.EntityType.create(compound, ((org.bukkit.craftbukkit.CraftWorld) world).getHandle(), net.minecraft.world.entity.EntitySpawnReason.LOAD) ++ .orElseThrow(() -> new IllegalArgumentException("An ID was not found for the data. Did you downgrade?")).getBukkitEntity(); ++ } ++ ++ private byte[] serializeNbtToBytes(net.minecraft.nbt.CompoundTag compound) { + compound.putInt("DataVersion", getDataVersion()); + java.io.ByteArrayOutputStream outputStream = new java.io.ByteArrayOutputStream(); + try { diff --git a/patches/server/0589-Fix-issues-with-mob-conversion.patch b/patches/server/0589-Fix-issues-with-mob-conversion.patch deleted file mode 100644 index 1b4597fe009c..000000000000 --- a/patches/server/0589-Fix-issues-with-mob-conversion.patch +++ /dev/null @@ -1,59 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Jake Potrebic -Date: Sun, 24 Oct 2021 20:29:45 -0700 -Subject: [PATCH] Fix issues with mob conversion - - -diff --git a/src/main/java/net/minecraft/world/entity/monster/Skeleton.java b/src/main/java/net/minecraft/world/entity/monster/Skeleton.java -index 44313973318cc330bb0288ec5b857c61d4c8f9be..cee42ae2b75c29c89e7fc5b1c77d3b45ce40e9ba 100644 ---- a/src/main/java/net/minecraft/world/entity/monster/Skeleton.java -+++ b/src/main/java/net/minecraft/world/entity/monster/Skeleton.java -@@ -91,10 +91,15 @@ public class Skeleton extends AbstractSkeleton { - } - - protected void doFreezeConversion() { -- this.convertTo(EntityType.STRAY, true, org.bukkit.event.entity.EntityTransformEvent.TransformReason.FROZEN, org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason.FROZEN); // CraftBukkit - add spawn and transform reasons -+ Stray stray = this.convertTo(EntityType.STRAY, true, org.bukkit.event.entity.EntityTransformEvent.TransformReason.FROZEN, org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason.FROZEN); // CraftBukkit - add spawn and transform reasons // Paper - Fix issues with mob conversion - if (!this.isSilent()) { - this.level().levelEvent((Player) null, 1048, this.blockPosition(), 0); - } -+ // Paper start - Fix issues with mob conversion; reset conversion time to prevent event spam -+ if (stray == null) { -+ this.conversionTime = 300; -+ } -+ // Paper end - Fix issues with mob conversion - - } - -diff --git a/src/main/java/net/minecraft/world/entity/monster/hoglin/Hoglin.java b/src/main/java/net/minecraft/world/entity/monster/hoglin/Hoglin.java -index d35214c485dfc3abdb3f2d6683c9293b3b5f035d..d5e0c493f4c348724958193795ceb987765a465f 100644 ---- a/src/main/java/net/minecraft/world/entity/monster/hoglin/Hoglin.java -+++ b/src/main/java/net/minecraft/world/entity/monster/hoglin/Hoglin.java -@@ -258,6 +258,11 @@ public class Hoglin extends Animal implements Enemy, HoglinBase { - if (zoglin != null) { - zoglin.addEffect(new MobEffectInstance(MobEffects.CONFUSION, 200, 0)); - } -+ // Paper start - Fix issues with mob conversion; reset to prevent event spam -+ else { -+ this.timeInOverworld = 0; -+ } -+ // Paper end - Fix issues with mob conversion - } - - @Override -diff --git a/src/main/java/net/minecraft/world/entity/monster/piglin/AbstractPiglin.java b/src/main/java/net/minecraft/world/entity/monster/piglin/AbstractPiglin.java -index 4e4cfbcaa5c236969da288b9d6f9cd7773bf4687..dedbf88e03d5840fa9f5c5198033379701a9a29e 100644 ---- a/src/main/java/net/minecraft/world/entity/monster/piglin/AbstractPiglin.java -+++ b/src/main/java/net/minecraft/world/entity/monster/piglin/AbstractPiglin.java -@@ -99,6 +99,11 @@ public abstract class AbstractPiglin extends Monster { - if (entitypigzombie != null) { - entitypigzombie.addEffect(new MobEffectInstance(MobEffects.CONFUSION, 200, 0)); - } -+ // Paper start - Fix issues with mob conversion; reset to prevent event spam -+ else { -+ this.timeInOverworld = 0; -+ } -+ // Paper end - Fix issues with mob conversion - - } - diff --git a/patches/server/0590-Add-hasCollision-methods-to-various-places.patch b/patches/server/0590-Add-hasCollision-methods-to-various-places.patch deleted file mode 100644 index 1acc4729a74f..000000000000 --- a/patches/server/0590-Add-hasCollision-methods-to-various-places.patch +++ /dev/null @@ -1,56 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Jake Potrebic -Date: Thu, 4 Nov 2021 11:50:40 -0700 -Subject: [PATCH] Add hasCollision methods to various places - -== AT == -public net.minecraft.world.level.block.state.BlockBehaviour hasCollision - -diff --git a/src/main/java/org/bukkit/craftbukkit/block/CraftBlock.java b/src/main/java/org/bukkit/craftbukkit/block/CraftBlock.java -index ce297420f695404356655b1df2847a32fb98ec59..068b3735b6c50a7a2053c7dc39856f728fb7218a 100644 ---- a/src/main/java/org/bukkit/craftbukkit/block/CraftBlock.java -+++ b/src/main/java/org/bukkit/craftbukkit/block/CraftBlock.java -@@ -457,6 +457,11 @@ public class CraftBlock implements Block { - public boolean isSolid() { - return this.getNMS().blocksMotion(); - } -+ -+ @Override -+ public boolean isCollidable() { -+ return getNMS().getBlock().hasCollision; -+ } - // Paper end - - @Override -diff --git a/src/main/java/org/bukkit/craftbukkit/block/CraftBlockState.java b/src/main/java/org/bukkit/craftbukkit/block/CraftBlockState.java -index 1002123cd0c6f57cecc4e80f5f21cc6ff5886d37..e96023b71845526383288917e8d7c5759a4c0e9b 100644 ---- a/src/main/java/org/bukkit/craftbukkit/block/CraftBlockState.java -+++ b/src/main/java/org/bukkit/craftbukkit/block/CraftBlockState.java -@@ -341,4 +341,11 @@ public class CraftBlockState implements BlockState { - public BlockState copy(Location location) { - return new CraftBlockState(this, location); - } -+ -+ // Paper start -+ @Override -+ public boolean isCollidable() { -+ return this.data.getBlock().hasCollision; -+ } -+ // Paper end - } -diff --git a/src/main/java/org/bukkit/craftbukkit/block/CraftBlockType.java b/src/main/java/org/bukkit/craftbukkit/block/CraftBlockType.java -index 2d8a509446c0ed0d7358f10f67ef29c4df683696..785d3fe4929e008d4150f3ecab258fd5b6d7cdaf 100644 ---- a/src/main/java/org/bukkit/craftbukkit/block/CraftBlockType.java -+++ b/src/main/java/org/bukkit/craftbukkit/block/CraftBlockType.java -@@ -241,4 +241,11 @@ public class CraftBlockType implements BlockType.Typed, - return this.block.getDescriptionId(); - } - // Paper end - add Translatable -+ -+ // Paper start - hasCollision API -+ @Override -+ public boolean hasCollision() { -+ return this.block.hasCollision; -+ } -+ // Paper end - hasCollision API - } diff --git a/patches/server/0590-Vanilla-command-permission-fixes.patch b/patches/server/0590-Vanilla-command-permission-fixes.patch new file mode 100644 index 000000000000..4c4787c7fc42 --- /dev/null +++ b/patches/server/0590-Vanilla-command-permission-fixes.patch @@ -0,0 +1,79 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Jason Penilla <11360596+jpenilla@users.noreply.github.com> +Date: Wed, 25 Aug 2021 13:19:53 -0700 +Subject: [PATCH] Vanilla command permission fixes + +Fixes permission checks for vanilla commands which don't have a +requirement, as well as for namespaced vanilla commands. + +== AT == +public-f com.mojang.brigadier.tree.CommandNode requirement + +diff --git a/src/main/java/com/mojang/brigadier/builder/ArgumentBuilder.java b/src/main/java/com/mojang/brigadier/builder/ArgumentBuilder.java +index 899008b2980d13f1be6280cd8cb959c53a29bebf..d5f7da3502575f6847f3c22ab0e942848a7c6031 100644 +--- a/src/main/java/com/mojang/brigadier/builder/ArgumentBuilder.java ++++ b/src/main/java/com/mojang/brigadier/builder/ArgumentBuilder.java +@@ -14,9 +14,17 @@ import java.util.Collections; + import java.util.function.Predicate; + + public abstract class ArgumentBuilder> { ++ // Paper start - Vanilla command permission fixes ++ private static final Predicate DEFAULT_REQUIREMENT = s -> true; ++ ++ @SuppressWarnings("unchecked") ++ public static Predicate defaultRequirement() { ++ return (Predicate) DEFAULT_REQUIREMENT; ++ } ++ // Paper end - Vanilla command permission fixes + private final RootCommandNode arguments = new RootCommandNode<>(); + private Command command; +- private Predicate requirement = s -> true; ++ private Predicate requirement = defaultRequirement(); // Paper - Vanilla command permission fixes + private CommandNode target; + private RedirectModifier modifier = null; + private boolean forks; +diff --git a/src/main/java/net/minecraft/commands/Commands.java b/src/main/java/net/minecraft/commands/Commands.java +index 7acd7f60327106d55e8f48247650bc0064dd1b58..bee79fab7f8195e14f6bd22d9cd59bfc704bf903 100644 +--- a/src/main/java/net/minecraft/commands/Commands.java ++++ b/src/main/java/net/minecraft/commands/Commands.java +@@ -261,6 +261,13 @@ public class Commands { + PublishCommand.register(this.dispatcher); + } + ++ // Paper start - Vanilla command permission fixes ++ for (final CommandNode node : this.dispatcher.getRoot().getChildren()) { ++ if (node.getRequirement() == com.mojang.brigadier.builder.ArgumentBuilder.defaultRequirement()) { ++ node.requirement = stack -> stack.source == CommandSource.NULL || stack.getBukkitSender().hasPermission(org.bukkit.craftbukkit.command.VanillaCommandWrapper.getPermission(node)); ++ } ++ } ++ // Paper end - Vanilla command permission fixes + // CraftBukkit start + } + +diff --git a/src/main/java/org/bukkit/craftbukkit/command/VanillaCommandWrapper.java b/src/main/java/org/bukkit/craftbukkit/command/VanillaCommandWrapper.java +index 87c93ee9bbbfff785b7b6a1f0c4b932e36362943..4e81c26fdbd089961b2577168c716bf29d504d40 100644 +--- a/src/main/java/org/bukkit/craftbukkit/command/VanillaCommandWrapper.java ++++ b/src/main/java/org/bukkit/craftbukkit/command/VanillaCommandWrapper.java +@@ -86,7 +86,21 @@ public final class VanillaCommandWrapper extends BukkitCommand { + } + + public static String getPermission(CommandNode vanillaCommand) { +- return "minecraft.command." + ((vanillaCommand.getRedirect() == null) ? vanillaCommand.getName() : vanillaCommand.getRedirect().getName()); ++ // Paper start - Vanilla command permission fixes ++ while (vanillaCommand.getRedirect() != null) { ++ vanillaCommand = vanillaCommand.getRedirect(); ++ } ++ final String commandName = vanillaCommand.getName(); ++ return "minecraft.command." + stripDefaultNamespace(commandName); ++ } ++ ++ private static String stripDefaultNamespace(final String maybeNamespaced) { ++ final String prefix = "minecraft:"; ++ if (maybeNamespaced.startsWith(prefix)) { ++ return maybeNamespaced.substring(prefix.length()); ++ } ++ return maybeNamespaced; ++ // Paper end - Vanilla command permission fixes + } + + private String toDispatcher(String[] args, String name) { diff --git a/patches/server/0591-Do-not-run-close-logic-for-inventories-on-chunk-unlo.patch b/patches/server/0591-Do-not-run-close-logic-for-inventories-on-chunk-unlo.patch new file mode 100644 index 000000000000..27c3f61502fd --- /dev/null +++ b/patches/server/0591-Do-not-run-close-logic-for-inventories-on-chunk-unlo.patch @@ -0,0 +1,68 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Spottedleaf +Date: Thu, 11 Mar 2021 03:03:32 -0800 +Subject: [PATCH] Do not run close logic for inventories on chunk unload + +Still call the event and change the active container though. We +want to avoid close logic because it's possible to load the +chunk through it. This should also be OK from a leak prevention/ +state desync POV because the TE is getting unloaded anyways. + +diff --git a/src/main/java/net/minecraft/server/level/ServerLevel.java b/src/main/java/net/minecraft/server/level/ServerLevel.java +index 3c281cdd24acbc9484c968c07f737d50be2deced..ecb4dc0642685d67621c82bb24fb0e939c805ce4 100644 +--- a/src/main/java/net/minecraft/server/level/ServerLevel.java ++++ b/src/main/java/net/minecraft/server/level/ServerLevel.java +@@ -1239,9 +1239,13 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe + // Spigot Start + for (net.minecraft.world.level.block.entity.BlockEntity tileentity : chunk.getBlockEntities().values()) { + if (tileentity instanceof net.minecraft.world.Container) { ++ // Paper start - this area looks like it can load chunks, change the behavior ++ // chests for example can apply physics to the world ++ // so instead we just change the active container and call the event + for (org.bukkit.entity.HumanEntity h : Lists.newArrayList(((net.minecraft.world.Container) tileentity).getViewers())) { +- h.closeInventory(org.bukkit.event.inventory.InventoryCloseEvent.Reason.UNLOADED); // Paper - Inventory close reason ++ ((org.bukkit.craftbukkit.entity.CraftHumanEntity) h).getHandle().closeUnloadedInventory(org.bukkit.event.inventory.InventoryCloseEvent.Reason.UNLOADED); // Paper - Inventory close reason + } ++ // Paper end - this area looks like it can load chunks, change the behavior + } + } + // Spigot End +diff --git a/src/main/java/net/minecraft/server/level/ServerPlayer.java b/src/main/java/net/minecraft/server/level/ServerPlayer.java +index c680b311760601bb539d685bceddba6712d141d4..d2fbbdbb451d6c54d847b4ba125397ad41c4f7b4 100644 +--- a/src/main/java/net/minecraft/server/level/ServerPlayer.java ++++ b/src/main/java/net/minecraft/server/level/ServerPlayer.java +@@ -1983,6 +1983,18 @@ public class ServerPlayer extends net.minecraft.world.entity.player.Player { + this.connection.send(new ClientboundContainerClosePacket(this.containerMenu.containerId)); + this.doCloseContainer(); + } ++ // Paper start - special close for unloaded inventory ++ @Override ++ public void closeUnloadedInventory(org.bukkit.event.inventory.InventoryCloseEvent.Reason reason) { ++ // copied from above ++ CraftEventFactory.handleInventoryCloseEvent(this, reason); // CraftBukkit ++ // Paper end ++ // copied from below ++ this.connection.send(new ClientboundContainerClosePacket(this.containerMenu.containerId)); ++ this.containerMenu = this.inventoryMenu; ++ // do not run close logic ++ } ++ // Paper end - special close for unloaded inventory + + @Override + public void doCloseContainer() { +diff --git a/src/main/java/net/minecraft/world/entity/player/Player.java b/src/main/java/net/minecraft/world/entity/player/Player.java +index 68a6b1508ce4544fe8b18746d440944d41a2fe5b..b8edbd23d547d7189ec64c5d3a8cd1d51859ce23 100644 +--- a/src/main/java/net/minecraft/world/entity/player/Player.java ++++ b/src/main/java/net/minecraft/world/entity/player/Player.java +@@ -544,6 +544,11 @@ public abstract class Player extends LivingEntity { + this.containerMenu = this.inventoryMenu; + } + // Paper end - Inventory close reason ++ // Paper start - special close for unloaded inventory ++ public void closeUnloadedInventory(org.bukkit.event.inventory.InventoryCloseEvent.Reason reason) { ++ this.containerMenu = this.inventoryMenu; ++ } ++ // Paper end - special close for unloaded inventory + + public void closeContainer() { + this.containerMenu = this.inventoryMenu; diff --git a/patches/server/0591-Goat-ram-API.patch b/patches/server/0591-Goat-ram-API.patch deleted file mode 100644 index 2f5ccdfed4f5..000000000000 --- a/patches/server/0591-Goat-ram-API.patch +++ /dev/null @@ -1,42 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Seggan -Date: Thu, 5 Aug 2021 13:10:27 -0400 -Subject: [PATCH] Goat ram API - - -diff --git a/src/main/java/net/minecraft/world/entity/animal/goat/Goat.java b/src/main/java/net/minecraft/world/entity/animal/goat/Goat.java -index fa380dcfa16f5d872c9d29d6fab9f9cf095f3791..3b2cf9ca8447321d64ffdb4fdb9569d736d63dbb 100644 ---- a/src/main/java/net/minecraft/world/entity/animal/goat/Goat.java -+++ b/src/main/java/net/minecraft/world/entity/animal/goat/Goat.java -@@ -391,4 +391,15 @@ public class Goat extends Animal { - public static boolean checkGoatSpawnRules(EntityType entityType, LevelAccessor world, MobSpawnType spawnReason, BlockPos pos, RandomSource random) { - return world.getBlockState(pos.below()).is(BlockTags.GOATS_SPAWNABLE_ON) && isBrightEnoughToSpawn(world, pos); - } -+ -+ // Paper start - Goat ram API -+ public void ram(net.minecraft.world.entity.LivingEntity entity) { -+ Brain brain = this.getBrain(); -+ brain.setMemory(MemoryModuleType.RAM_TARGET, entity.position()); -+ brain.eraseMemory(MemoryModuleType.RAM_COOLDOWN_TICKS); -+ brain.eraseMemory(MemoryModuleType.BREED_TARGET); -+ brain.eraseMemory(MemoryModuleType.TEMPTING_PLAYER); -+ brain.setActiveActivityIfPossible(net.minecraft.world.entity.schedule.Activity.RAM); -+ } -+ // Paper end - Goat ram API - } -diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftGoat.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftGoat.java -index 65fcb36e849da6949c123a6f3672b485036f368e..2c21de478bff9cdf13ba46cd041831d54c11e924 100644 ---- a/src/main/java/org/bukkit/craftbukkit/entity/CraftGoat.java -+++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftGoat.java -@@ -48,4 +48,11 @@ public class CraftGoat extends CraftAnimals implements Goat { - public void setScreaming(boolean screaming) { - this.getHandle().setScreamingGoat(screaming); - } -+ -+ // Paper start - Goat ram API -+ @Override -+ public void ram(@org.jetbrains.annotations.NotNull org.bukkit.entity.LivingEntity entity) { -+ this.getHandle().ram(((CraftLivingEntity) entity).getHandle()); -+ } -+ // Paper end - } diff --git a/patches/server/0596-Fix-GameProfileCache-concurrency.patch b/patches/server/0592-Fix-GameProfileCache-concurrency.patch similarity index 100% rename from patches/server/0596-Fix-GameProfileCache-concurrency.patch rename to patches/server/0592-Fix-GameProfileCache-concurrency.patch diff --git a/patches/server/0593-Add-Raw-Byte-Entity-Serialization.patch b/patches/server/0593-Add-Raw-Byte-Entity-Serialization.patch deleted file mode 100644 index 0613f5b2e244..000000000000 --- a/patches/server/0593-Add-Raw-Byte-Entity-Serialization.patch +++ /dev/null @@ -1,90 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Mariell Hoversholm -Date: Sun, 24 Oct 2021 16:20:31 -0400 -Subject: [PATCH] Add Raw Byte Entity Serialization - -== AT == -public net.minecraft.world.entity.Entity setLevel(Lnet/minecraft/world/level/Level;)V - -diff --git a/src/main/java/net/minecraft/world/entity/Entity.java b/src/main/java/net/minecraft/world/entity/Entity.java -index 65b1833125ce4b1190bf276a69ad4c0c88875d58..4c36bb6f61a75f20df911cd0a8a6ddc84800edd0 100644 ---- a/src/main/java/net/minecraft/world/entity/Entity.java -+++ b/src/main/java/net/minecraft/world/entity/Entity.java -@@ -2144,6 +2144,15 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess - } - } - -+ // Paper start - Entity serialization api -+ public boolean serializeEntity(CompoundTag compound) { -+ List pass = new java.util.ArrayList<>(this.getPassengers()); -+ this.passengers = ImmutableList.of(); -+ boolean result = save(compound); -+ this.passengers = ImmutableList.copyOf(pass); -+ return result; -+ } -+ // Paper end - Entity serialization api - public boolean save(CompoundTag nbt) { - return this.isPassenger() ? false : this.saveAsPassenger(nbt); - } -diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java -index 7c04eb9e7eb5ff728465b46e3739eb2598ef1204..6fab713531665298d3b03e7960a17ecb1471a6d7 100644 ---- a/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java -+++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java -@@ -1087,6 +1087,18 @@ public abstract class CraftEntity implements org.bukkit.entity.Entity { - } - // Paper end - tracked players API - -+ // Paper start - raw entity serialization API -+ @Override -+ public boolean spawnAt(Location location, org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason reason) { -+ Preconditions.checkNotNull(location, "location cannot be null"); -+ Preconditions.checkNotNull(reason, "reason cannot be null"); -+ this.entity.setLevel(((CraftWorld) location.getWorld()).getHandle()); -+ this.entity.setPos(location.getX(), location.getY(), location.getZ()); -+ this.entity.setRot(location.getYaw(), location.getPitch()); -+ return !this.entity.valid && this.entity.level().addFreshEntity(this.entity, reason); -+ } -+ // Paper end - raw entity serialization API -+ - // Paper start - missing entity api - @Override - public boolean isInvisible() { // Paper - moved up from LivingEntity -diff --git a/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java b/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java -index 70c06eb6e743232d0e8243b12a927084c6c9414b..c10273445c4b5ef089f86fc08a944da69d708244 100644 ---- a/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java -+++ b/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java -@@ -494,7 +494,33 @@ public final class CraftMagicNumbers implements UnsafeValues { - return CraftItemStack.asCraftMirror(net.minecraft.world.item.ItemStack.parse(MinecraftServer.getServer().registryAccess(), compound).orElseThrow()); - } - -- private byte[] serializeNbtToBytes(CompoundTag compound) { -+ @Override -+ public byte[] serializeEntity(org.bukkit.entity.Entity entity) { -+ Preconditions.checkNotNull(entity, "null cannot be serialized"); -+ Preconditions.checkArgument(entity instanceof org.bukkit.craftbukkit.entity.CraftEntity, "only CraftEntities can be serialized"); -+ -+ net.minecraft.nbt.CompoundTag compound = new net.minecraft.nbt.CompoundTag(); -+ ((org.bukkit.craftbukkit.entity.CraftEntity) entity).getHandle().serializeEntity(compound); -+ return serializeNbtToBytes(compound); -+ } -+ -+ @Override -+ public org.bukkit.entity.Entity deserializeEntity(byte[] data, org.bukkit.World world, boolean preserveUUID) { -+ Preconditions.checkNotNull(data, "null cannot be deserialized"); -+ Preconditions.checkArgument(data.length > 0, "cannot deserialize nothing"); -+ -+ net.minecraft.nbt.CompoundTag compound = deserializeNbtFromBytes(data); -+ int dataVersion = compound.getInt("DataVersion"); -+ compound = (net.minecraft.nbt.CompoundTag) MinecraftServer.getServer().fixerUpper.update(References.ENTITY, new Dynamic<>(NbtOps.INSTANCE, compound), dataVersion, this.getDataVersion()).getValue(); -+ if (!preserveUUID) { -+ // Generate a new UUID so we don't have to worry about deserializing the same entity twice -+ compound.remove("UUID"); -+ } -+ return net.minecraft.world.entity.EntityType.create(compound, ((org.bukkit.craftbukkit.CraftWorld) world).getHandle()) -+ .orElseThrow(() -> new IllegalArgumentException("An ID was not found for the data. Did you downgrade?")).getBukkitEntity(); -+ } -+ -+ private byte[] serializeNbtToBytes(net.minecraft.nbt.CompoundTag compound) { - compound.putInt("DataVersion", getDataVersion()); - java.io.ByteArrayOutputStream outputStream = new java.io.ByteArrayOutputStream(); - try { diff --git a/patches/server/0593-Improve-and-expand-AsyncCatcher.patch b/patches/server/0593-Improve-and-expand-AsyncCatcher.patch new file mode 100644 index 000000000000..0e27a17c00e7 --- /dev/null +++ b/patches/server/0593-Improve-and-expand-AsyncCatcher.patch @@ -0,0 +1,227 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Spottedleaf +Date: Wed, 25 Aug 2021 20:17:12 -0700 +Subject: [PATCH] Improve and expand AsyncCatcher + +Log when the async catcher is tripped + The chunk system can swallow the exception given it's all + built with completablefuture, so ensure it is at least printed. + +Add/move several async catchers + +Async catch modifications to critical entity state + These used to be here from Spigot, but were dropped with 1.17. + Now in 1.17, this state is _even more_ critical than it was before, + so these must exist to catch stupid plugins. + +Co-authored-by: Jake Potrebic + +diff --git a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java +index 70b891bd018029eda8cda4fb9f919e77524dbc5e..a4abcbc69ccd023a936d02d359ba4c08198ed31e 100644 +--- a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java ++++ b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java +@@ -1597,6 +1597,7 @@ public class ServerGamePacketListenerImpl extends ServerCommonPacketListenerImpl + } + + public void internalTeleport(PositionMoveRotation positionmoverotation, Set set) { ++ org.spigotmc.AsyncCatcher.catchOp("teleport"); // Paper + // Paper start - Prevent teleporting dead entities + if (player.isRemoved()) { + LOGGER.info("Attempt to teleport removed player {} restricted", player.getScoreboardName()); +diff --git a/src/main/java/net/minecraft/world/entity/LivingEntity.java b/src/main/java/net/minecraft/world/entity/LivingEntity.java +index 8939ce9fed5d935cec31c9a27833d1cec4de7b01..564024738cc346abc024967c2d55f2553af3e660 100644 +--- a/src/main/java/net/minecraft/world/entity/LivingEntity.java ++++ b/src/main/java/net/minecraft/world/entity/LivingEntity.java +@@ -1143,7 +1143,7 @@ public abstract class LivingEntity extends Entity implements Attackable { + } + + public boolean addEffect(MobEffectInstance mobeffect, @Nullable Entity entity, EntityPotionEffectEvent.Cause cause) { +- org.spigotmc.AsyncCatcher.catchOp("effect add"); // Spigot ++ // org.spigotmc.AsyncCatcher.catchOp("effect add"); // Spigot // Paper - move to API + if (this.isTickingEffects) { + this.effectsToProcess.add(new ProcessableEffect(mobeffect, cause)); + return true; +diff --git a/src/main/java/net/minecraft/world/level/entity/PersistentEntitySectionManager.java b/src/main/java/net/minecraft/world/level/entity/PersistentEntitySectionManager.java +index 4eb0b0969325f39a7ae65492cccd482515a50142..5aa74c00a61282830d82359eae2b114e2a48b6d9 100644 +--- a/src/main/java/net/minecraft/world/level/entity/PersistentEntitySectionManager.java ++++ b/src/main/java/net/minecraft/world/level/entity/PersistentEntitySectionManager.java +@@ -78,6 +78,7 @@ public class PersistentEntitySectionManager implements A + } + + private boolean addEntityUuid(T entity) { ++ org.spigotmc.AsyncCatcher.catchOp("Entity add by UUID"); // Paper + if (!this.knownUuids.add(entity.getUUID())) { + PersistentEntitySectionManager.LOGGER.warn("UUID of added entity already exists: {}", entity); + return false; +@@ -91,6 +92,7 @@ public class PersistentEntitySectionManager implements A + } + + private boolean addEntity(T entity, boolean existing) { ++ org.spigotmc.AsyncCatcher.catchOp("Entity add"); // Paper + // Paper start - chunk system hooks + // I don't want to know why this is a generic type. + Entity entityCasted = (Entity)entity; +@@ -144,19 +146,23 @@ public class PersistentEntitySectionManager implements A + } + + void startTicking(T entity) { ++ org.spigotmc.AsyncCatcher.catchOp("Entity start ticking"); // Paper + this.callbacks.onTickingStart(entity); + } + + void stopTicking(T entity) { ++ org.spigotmc.AsyncCatcher.catchOp("Entity stop ticking"); // Paper + this.callbacks.onTickingEnd(entity); + } + + void startTracking(T entity) { ++ org.spigotmc.AsyncCatcher.catchOp("Entity start tracking"); // Paper + this.visibleEntityStorage.add(entity); + this.callbacks.onTrackingStart(entity); + } + + void stopTracking(T entity) { ++ org.spigotmc.AsyncCatcher.catchOp("Entity stop tracking"); // Paper + this.callbacks.onTrackingEnd(entity); + this.visibleEntityStorage.remove(entity); + } +@@ -168,6 +174,7 @@ public class PersistentEntitySectionManager implements A + } + + public void updateChunkStatus(ChunkPos chunkPos, Visibility trackingStatus) { ++ org.spigotmc.AsyncCatcher.catchOp("Update chunk status"); // Paper + long i = chunkPos.toLong(); + + if (trackingStatus == Visibility.HIDDEN) { +@@ -212,6 +219,7 @@ public class PersistentEntitySectionManager implements A + } + + public void ensureChunkQueuedForLoad(long chunkPos) { ++ org.spigotmc.AsyncCatcher.catchOp("Entity chunk save"); // Paper + PersistentEntitySectionManager.ChunkLoadStatus persistententitysectionmanager_b = (PersistentEntitySectionManager.ChunkLoadStatus) this.chunkLoadStatuses.get(chunkPos); + + if (persistententitysectionmanager_b == PersistentEntitySectionManager.ChunkLoadStatus.FRESH) { +@@ -256,6 +264,7 @@ public class PersistentEntitySectionManager implements A + } + + private void requestChunkLoad(long chunkPos) { ++ org.spigotmc.AsyncCatcher.catchOp("Entity chunk load request"); // Paper + this.chunkLoadStatuses.put(chunkPos, PersistentEntitySectionManager.ChunkLoadStatus.PENDING); + ChunkPos chunkcoordintpair = new ChunkPos(chunkPos); + CompletableFuture completablefuture = this.permanentStorage.loadEntities(chunkcoordintpair); +@@ -269,6 +278,7 @@ public class PersistentEntitySectionManager implements A + } + + private boolean processChunkUnload(long chunkPos) { ++ org.spigotmc.AsyncCatcher.catchOp("Entity chunk unload process"); // Paper + boolean flag = this.storeChunkSections(chunkPos, (entityaccess) -> { + entityaccess.getPassengersAndSelf().forEach(this::unloadEntity); + }, true); // CraftBukkit - add boolean for event call +@@ -293,6 +303,7 @@ public class PersistentEntitySectionManager implements A + } + + private void processPendingLoads() { ++ org.spigotmc.AsyncCatcher.catchOp("Entity chunk process pending loads"); // Paper + ChunkEntities chunkentities; // CraftBukkit - decompile error + + while ((chunkentities = (ChunkEntities) this.loadingInbox.poll()) != null) { +@@ -309,6 +320,7 @@ public class PersistentEntitySectionManager implements A + } + + public void tick() { ++ org.spigotmc.AsyncCatcher.catchOp("Entity manager tick"); // Paper + this.processPendingLoads(); + this.processUnloads(); + } +@@ -329,6 +341,7 @@ public class PersistentEntitySectionManager implements A + } + + public void autoSave() { ++ org.spigotmc.AsyncCatcher.catchOp("Entity manager autosave"); // Paper + this.getAllChunksToSave().forEach((java.util.function.LongConsumer) (i) -> { // CraftBukkit - decompile error + boolean flag = this.chunkVisibility.get(i) == Visibility.HIDDEN; + +@@ -343,6 +356,7 @@ public class PersistentEntitySectionManager implements A + } + + public void saveAll() { ++ org.spigotmc.AsyncCatcher.catchOp("Entity manager save"); // Paper + LongSet longset = this.getAllChunksToSave(); + + while (!longset.isEmpty()) { +@@ -450,6 +464,7 @@ public class PersistentEntitySectionManager implements A + long i = SectionPos.asLong(blockposition); + + if (i != this.currentSectionKey) { ++ org.spigotmc.AsyncCatcher.catchOp("Entity move"); // Paper + Visibility visibility = this.currentSection.getStatus(); + + if (!this.currentSection.remove(this.entity)) { +@@ -504,6 +519,7 @@ public class PersistentEntitySectionManager implements A + + @Override + public void onRemove(Entity.RemovalReason reason) { ++ org.spigotmc.AsyncCatcher.catchOp("Entity remove"); // Paper + if (!this.currentSection.remove(this.entity)) { + PersistentEntitySectionManager.LOGGER.warn("Entity {} wasn't found in section {} (destroying due to {})", new Object[]{this.entity, SectionPos.of(this.currentSectionKey), reason}); + } +diff --git a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java +index 06ccf20081ecd2b56f9e5aeef988804fef10df73..f10b9898f180b231ce793cc03488df5a359b0ac0 100644 +--- a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java ++++ b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java +@@ -1770,6 +1770,7 @@ public class CraftWorld extends CraftRegionAccessor implements World { + + @Override + public void playSound(Location loc, Sound sound, org.bukkit.SoundCategory category, float volume, float pitch, long seed) { ++ org.spigotmc.AsyncCatcher.catchOp("play sound"); // Paper + if (loc == null || sound == null || category == null) return; + + double x = loc.getX(); +@@ -1781,6 +1782,7 @@ public class CraftWorld extends CraftRegionAccessor implements World { + + @Override + public void playSound(Location loc, String sound, org.bukkit.SoundCategory category, float volume, float pitch, long seed) { ++ org.spigotmc.AsyncCatcher.catchOp("play sound"); // Paper + if (loc == null || sound == null || category == null) return; + + double x = loc.getX(); +@@ -1813,6 +1815,7 @@ public class CraftWorld extends CraftRegionAccessor implements World { + + @Override + public void playSound(Entity entity, Sound sound, org.bukkit.SoundCategory category, float volume, float pitch, long seed) { ++ org.spigotmc.AsyncCatcher.catchOp("play sound"); // Paper + if (!(entity instanceof CraftEntity craftEntity) || entity.getWorld() != this || sound == null || category == null) return; + + ClientboundSoundEntityPacket packet = new ClientboundSoundEntityPacket(CraftSound.bukkitToMinecraftHolder(sound), net.minecraft.sounds.SoundSource.valueOf(category.name()), craftEntity.getHandle(), volume, pitch, seed); +@@ -1833,6 +1836,7 @@ public class CraftWorld extends CraftRegionAccessor implements World { + + @Override + public void playSound(Entity entity, String sound, org.bukkit.SoundCategory category, float volume, float pitch, long seed) { ++ org.spigotmc.AsyncCatcher.catchOp("play sound"); // Paper + if (!(entity instanceof CraftEntity craftEntity) || entity.getWorld() != this || sound == null || category == null) return; + + ClientboundSoundEntityPacket packet = new ClientboundSoundEntityPacket(Holder.direct(SoundEvent.createVariableRangeEvent(ResourceLocation.parse(sound))), net.minecraft.sounds.SoundSource.valueOf(category.name()), craftEntity.getHandle(), volume, pitch, seed); +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftLivingEntity.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftLivingEntity.java +index 9870222cc1c46bcc37f9d3d44881606f2b9d038e..e148239d4930e5cbb000beed4de386f992f28d88 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftLivingEntity.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftLivingEntity.java +@@ -534,6 +534,7 @@ public class CraftLivingEntity extends CraftEntity implements LivingEntity { + + @Override + public boolean addPotionEffect(PotionEffect effect, boolean force) { ++ org.spigotmc.AsyncCatcher.catchOp("effect add"); // Paper + this.getHandle().addEffect(org.bukkit.craftbukkit.potion.CraftPotionUtil.fromBukkit(effect), EntityPotionEffectEvent.Cause.PLUGIN); // Paper - Don't ignore icon + return true; + } +diff --git a/src/main/java/org/spigotmc/AsyncCatcher.java b/src/main/java/org/spigotmc/AsyncCatcher.java +index bbf0d9d9c44fe8d7add2f978994ec129420814c7..ef2598760458833021ef1bee92137f42c9fe591f 100644 +--- a/src/main/java/org/spigotmc/AsyncCatcher.java ++++ b/src/main/java/org/spigotmc/AsyncCatcher.java +@@ -11,6 +11,7 @@ public class AsyncCatcher + { + if ( AsyncCatcher.enabled && Thread.currentThread() != MinecraftServer.getServer().serverThread ) + { ++ MinecraftServer.LOGGER.error("Thread " + Thread.currentThread().getName() + " failed main thread check: " + reason, new Throwable()); // Paper + throw new IllegalStateException( "Asynchronous " + reason + "!" ); + } + } diff --git a/patches/server/0594-Add-paper-mobcaps-and-paper-playermobcaps.patch b/patches/server/0594-Add-paper-mobcaps-and-paper-playermobcaps.patch new file mode 100644 index 000000000000..2eb0c233895c --- /dev/null +++ b/patches/server/0594-Add-paper-mobcaps-and-paper-playermobcaps.patch @@ -0,0 +1,343 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Jason Penilla <11360596+jpenilla@users.noreply.github.com> +Date: Mon, 16 Aug 2021 01:31:54 -0500 +Subject: [PATCH] Add '/paper mobcaps' and '/paper playermobcaps' + +Add commands to get the mobcaps for a world, as well as the mobcaps for +each player when per-player mob spawning is enabled. + +Also has a hover text on each mob category listing what entity types are +in said category + +diff --git a/src/main/java/io/papermc/paper/command/PaperCommand.java b/src/main/java/io/papermc/paper/command/PaperCommand.java +index cdad0fd5257ae842f83b9c1c98b4565b468d4f54..fd4f37711989431f997d77fb0917f8a9232ce53f 100644 +--- a/src/main/java/io/papermc/paper/command/PaperCommand.java ++++ b/src/main/java/io/papermc/paper/command/PaperCommand.java +@@ -40,6 +40,7 @@ public final class PaperCommand extends Command { + commands.put(Set.of("dumpplugins"), new DumpPluginsCommand()); + commands.put(Set.of("syncloadinfo"), new SyncLoadInfoCommand()); + commands.put(Set.of("dumpitem"), new DumpItemCommand()); ++ commands.put(Set.of("mobcaps", "playermobcaps"), new MobcapsCommand()); + + return commands.entrySet().stream() + .flatMap(entry -> entry.getKey().stream().map(s -> Map.entry(s, entry.getValue()))) +diff --git a/src/main/java/io/papermc/paper/command/subcommands/MobcapsCommand.java b/src/main/java/io/papermc/paper/command/subcommands/MobcapsCommand.java +new file mode 100644 +index 0000000000000000000000000000000000000000..d3b39d88a72ca25057fd8574d32f28db0d420818 +--- /dev/null ++++ b/src/main/java/io/papermc/paper/command/subcommands/MobcapsCommand.java +@@ -0,0 +1,229 @@ ++package io.papermc.paper.command.subcommands; ++ ++import com.google.common.collect.ImmutableMap; ++import io.papermc.paper.command.CommandUtil; ++import io.papermc.paper.command.PaperSubcommand; ++import java.util.ArrayList; ++import java.util.Collections; ++import java.util.List; ++import java.util.Map; ++import java.util.function.ToIntFunction; ++import net.kyori.adventure.text.Component; ++import net.kyori.adventure.text.ComponentLike; ++import net.kyori.adventure.text.JoinConfiguration; ++import net.kyori.adventure.text.TextComponent; ++import net.kyori.adventure.text.format.NamedTextColor; ++import net.kyori.adventure.text.format.TextColor; ++import net.minecraft.core.registries.BuiltInRegistries; ++import net.minecraft.server.level.ServerLevel; ++import net.minecraft.server.level.ServerPlayer; ++import net.minecraft.world.entity.MobCategory; ++import net.minecraft.world.level.NaturalSpawner; ++import org.bukkit.Bukkit; ++import org.bukkit.World; ++import org.bukkit.command.CommandSender; ++import org.bukkit.craftbukkit.CraftWorld; ++import org.bukkit.craftbukkit.entity.CraftPlayer; ++import org.bukkit.entity.Player; ++import org.checkerframework.checker.nullness.qual.NonNull; ++import org.checkerframework.checker.nullness.qual.Nullable; ++import org.checkerframework.framework.qual.DefaultQualifier; ++ ++@DefaultQualifier(NonNull.class) ++public final class MobcapsCommand implements PaperSubcommand { ++ static final Map MOB_CATEGORY_COLORS = ImmutableMap.builder() ++ .put(MobCategory.MONSTER, NamedTextColor.RED) ++ .put(MobCategory.CREATURE, NamedTextColor.GREEN) ++ .put(MobCategory.AMBIENT, NamedTextColor.GRAY) ++ .put(MobCategory.AXOLOTLS, TextColor.color(0x7324FF)) ++ .put(MobCategory.UNDERGROUND_WATER_CREATURE, TextColor.color(0x3541E6)) ++ .put(MobCategory.WATER_CREATURE, TextColor.color(0x006EFF)) ++ .put(MobCategory.WATER_AMBIENT, TextColor.color(0x00B3FF)) ++ .put(MobCategory.MISC, TextColor.color(0x636363)) ++ .build(); ++ ++ @Override ++ public boolean execute(final CommandSender sender, final String subCommand, final String[] args) { ++ switch (subCommand) { ++ case "mobcaps" -> this.printMobcaps(sender, args); ++ case "playermobcaps" -> this.printPlayerMobcaps(sender, args); ++ } ++ return true; ++ } ++ ++ @Override ++ public List tabComplete(final CommandSender sender, final String subCommand, final String[] args) { ++ return switch (subCommand) { ++ case "mobcaps" -> CommandUtil.getListMatchingLast(sender, args, this.suggestMobcaps(args)); ++ case "playermobcaps" -> CommandUtil.getListMatchingLast(sender, args, this.suggestPlayerMobcaps(sender, args)); ++ default -> throw new IllegalArgumentException(); ++ }; ++ } ++ ++ private List suggestMobcaps(final String[] args) { ++ if (args.length == 1) { ++ final List worlds = new ArrayList<>(Bukkit.getWorlds().stream().map(World::getName).toList()); ++ worlds.add("*"); ++ return worlds; ++ } ++ ++ return Collections.emptyList(); ++ } ++ ++ private List suggestPlayerMobcaps(final CommandSender sender, final String[] args) { ++ if (args.length == 1) { ++ final List list = new ArrayList<>(); ++ for (final Player player : Bukkit.getOnlinePlayers()) { ++ if (!(sender instanceof Player senderPlayer) || senderPlayer.canSee(player)) { ++ list.add(player.getName()); ++ } ++ } ++ return list; ++ } ++ ++ return Collections.emptyList(); ++ } ++ ++ private void printMobcaps(final CommandSender sender, final String[] args) { ++ final List worlds; ++ if (args.length == 0) { ++ if (sender instanceof Player player) { ++ worlds = List.of(player.getWorld()); ++ } else { ++ sender.sendMessage(Component.text("Must specify a world! ex: '/paper mobcaps world'", NamedTextColor.RED)); ++ return; ++ } ++ } else if (args.length == 1) { ++ final String input = args[0]; ++ if (input.equals("*")) { ++ worlds = Bukkit.getWorlds(); ++ } else { ++ final @Nullable World world = Bukkit.getWorld(input); ++ if (world == null) { ++ sender.sendMessage(Component.text("'" + input + "' is not a valid world!", NamedTextColor.RED)); ++ return; ++ } else { ++ worlds = List.of(world); ++ } ++ } ++ } else { ++ sender.sendMessage(Component.text("Too many arguments!", NamedTextColor.RED)); ++ return; ++ } ++ ++ for (final World world : worlds) { ++ final ServerLevel level = ((CraftWorld) world).getHandle(); ++ final NaturalSpawner.@Nullable SpawnState state = level.getChunkSource().getLastSpawnState(); ++ ++ final int chunks; ++ if (state == null) { ++ chunks = 0; ++ } else { ++ chunks = state.getSpawnableChunkCount(); ++ } ++ sender.sendMessage(Component.join(JoinConfiguration.noSeparators(), ++ Component.text("Mobcaps for world: "), ++ Component.text(world.getName(), NamedTextColor.AQUA), ++ Component.text(" (" + chunks + " spawnable chunks)") ++ )); ++ ++ sender.sendMessage(createMobcapsComponent( ++ category -> { ++ if (state == null) { ++ return 0; ++ } else { ++ return state.getMobCategoryCounts().getOrDefault(category, 0); ++ } ++ }, ++ category -> NaturalSpawner.globalLimitForCategory(level, category, chunks) ++ )); ++ } ++ } ++ ++ private void printPlayerMobcaps(final CommandSender sender, final String[] args) { ++ final @Nullable Player player; ++ if (args.length == 0) { ++ if (sender instanceof Player pl) { ++ player = pl; ++ } else { ++ sender.sendMessage(Component.text("Must specify a player! ex: '/paper playermobcount playerName'", NamedTextColor.RED)); ++ return; ++ } ++ } else if (args.length == 1) { ++ final String input = args[0]; ++ player = Bukkit.getPlayerExact(input); ++ if (player == null) { ++ sender.sendMessage(Component.text("Could not find player named '" + input + "'", NamedTextColor.RED)); ++ return; ++ } ++ } else { ++ sender.sendMessage(Component.text("Too many arguments!", NamedTextColor.RED)); ++ return; ++ } ++ ++ final ServerPlayer serverPlayer = ((CraftPlayer) player).getHandle(); ++ final ServerLevel level = serverPlayer.serverLevel(); ++ ++ if (!level.paperConfig().entities.spawning.perPlayerMobSpawns) { ++ sender.sendMessage(Component.text("Use '/paper mobcaps' for worlds where per-player mob spawning is disabled.", NamedTextColor.RED)); ++ return; ++ } ++ ++ sender.sendMessage(Component.join(JoinConfiguration.noSeparators(), Component.text("Mobcaps for player: "), Component.text(player.getName(), NamedTextColor.GREEN))); ++ sender.sendMessage(createMobcapsComponent( ++ category -> level.chunkSource.chunkMap.getMobCountNear(serverPlayer, category), ++ category -> level.getWorld().getSpawnLimitUnsafe(org.bukkit.craftbukkit.util.CraftSpawnCategory.toBukkit(category)) ++ )); ++ } ++ ++ private static Component createMobcapsComponent(final ToIntFunction countGetter, final ToIntFunction limitGetter) { ++ return MOB_CATEGORY_COLORS.entrySet().stream() ++ .map(entry -> { ++ final MobCategory category = entry.getKey(); ++ final TextColor color = entry.getValue(); ++ ++ final Component categoryHover = Component.join(JoinConfiguration.noSeparators(), ++ Component.text("Entity types in category ", TextColor.color(0xE0E0E0)), ++ Component.text(category.getName(), color), ++ Component.text(':', NamedTextColor.GRAY), ++ Component.newline(), ++ Component.newline(), ++ BuiltInRegistries.ENTITY_TYPE.entrySet().stream() ++ .filter(it -> it.getValue().getCategory() == category) ++ .map(it -> Component.translatable(it.getValue().getDescriptionId())) ++ .collect(Component.toComponent(Component.text(", ", NamedTextColor.GRAY))) ++ ); ++ ++ final Component categoryComponent = Component.text() ++ .content(" " + category.getName()) ++ .color(color) ++ .hoverEvent(categoryHover) ++ .build(); ++ ++ final TextComponent.Builder builder = Component.text() ++ .append( ++ categoryComponent, ++ Component.text(": ", NamedTextColor.GRAY) ++ ); ++ final int limit = limitGetter.applyAsInt(category); ++ if (limit != -1) { ++ builder.append( ++ Component.text(countGetter.applyAsInt(category)), ++ Component.text("/", NamedTextColor.GRAY), ++ Component.text(limit) ++ ); ++ } else { ++ builder.append(Component.text() ++ .append( ++ Component.text('n'), ++ Component.text("/", NamedTextColor.GRAY), ++ Component.text('a') ++ ) ++ .hoverEvent(Component.text("This category does not naturally spawn."))); ++ } ++ return builder; ++ }) ++ .map(ComponentLike::asComponent) ++ .collect(Component.toComponent(Component.newline())); ++ } ++} +diff --git a/src/main/java/net/minecraft/world/level/NaturalSpawner.java b/src/main/java/net/minecraft/world/level/NaturalSpawner.java +index 606a60fe273974b71ed2bd40be819d848627e777..bf943feca387b77a3154773a59da7190d38d8621 100644 +--- a/src/main/java/net/minecraft/world/level/NaturalSpawner.java ++++ b/src/main/java/net/minecraft/world/level/NaturalSpawner.java +@@ -172,6 +172,16 @@ public final class NaturalSpawner { + gameprofilerfiller.pop(); + } + ++ // Paper start - Add mobcaps commands ++ public static int globalLimitForCategory(final ServerLevel level, final MobCategory category, final int spawnableChunkCount) { ++ final int categoryLimit = level.getWorld().getSpawnLimitUnsafe(CraftSpawnCategory.toBukkit(category)); ++ if (categoryLimit < 1) { ++ return categoryLimit; ++ } ++ return categoryLimit * spawnableChunkCount / NaturalSpawner.MAGIC_NUMBER; ++ } ++ // Paper end - Add mobcaps commands ++ + public static void spawnCategoryForChunk(MobCategory group, ServerLevel world, LevelChunk chunk, NaturalSpawner.SpawnPredicate checker, NaturalSpawner.AfterSpawnCallback runner) { + BlockPos blockposition = NaturalSpawner.getRandomPosWithin(world, chunk); + +diff --git a/src/main/java/org/bukkit/craftbukkit/CraftServer.java b/src/main/java/org/bukkit/craftbukkit/CraftServer.java +index ecf7ee1ca39d58f1780580bd24366fc8037df34a..1c72862b3167acc05f06b44cd9a0929ad8d2b9c8 100644 +--- a/src/main/java/org/bukkit/craftbukkit/CraftServer.java ++++ b/src/main/java/org/bukkit/craftbukkit/CraftServer.java +@@ -2334,6 +2334,11 @@ public final class CraftServer implements Server { + + @Override + public int getSpawnLimit(SpawnCategory spawnCategory) { ++ // Paper start - Add mobcaps commands ++ return this.getSpawnLimitUnsafe(spawnCategory); ++ } ++ public int getSpawnLimitUnsafe(final SpawnCategory spawnCategory) { ++ // Paper end - Add mobcaps commands + return this.spawnCategoryLimit.getOrDefault(spawnCategory, -1); + } + +diff --git a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java +index f10b9898f180b231ce793cc03488df5a359b0ac0..5050c9a9d5369a829f5b47a56b7621de05c9a6de 100644 +--- a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java ++++ b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java +@@ -1728,9 +1728,14 @@ public class CraftWorld extends CraftRegionAccessor implements World { + Preconditions.checkArgument(spawnCategory != null, "SpawnCategory cannot be null"); + Preconditions.checkArgument(CraftSpawnCategory.isValidForLimits(spawnCategory), "SpawnCategory.%s are not supported", spawnCategory); + ++ // Paper start - Add mobcaps commands ++ return this.getSpawnLimitUnsafe(spawnCategory); ++ } ++ public final int getSpawnLimitUnsafe(final SpawnCategory spawnCategory) { + int limit = this.spawnCategoryLimit.getOrDefault(spawnCategory, -1); + if (limit < 0) { +- limit = this.server.getSpawnLimit(spawnCategory); ++ limit = this.server.getSpawnLimitUnsafe(spawnCategory); ++ // Paper end - Add mobcaps commands + } + return limit; + } +diff --git a/src/test/java/io/papermc/paper/command/subcommands/MobcapsCommandTest.java b/src/test/java/io/papermc/paper/command/subcommands/MobcapsCommandTest.java +new file mode 100644 +index 0000000000000000000000000000000000000000..6fdc77caa74845786c78a6ba087062b4d698cb82 +--- /dev/null ++++ b/src/test/java/io/papermc/paper/command/subcommands/MobcapsCommandTest.java +@@ -0,0 +1,22 @@ ++package io.papermc.paper.command.subcommands; ++ ++import java.util.HashSet; ++import java.util.Set; ++import net.minecraft.world.entity.MobCategory; ++import org.bukkit.support.environment.Normal; ++import org.junit.jupiter.api.Assertions; ++import org.junit.jupiter.api.Test; ++ ++@Normal ++public class MobcapsCommandTest { ++ @Test ++ public void testMobCategoryColors() { ++ final Set missing = new HashSet<>(); ++ for (final MobCategory value : MobCategory.values()) { ++ if (!MobcapsCommand.MOB_CATEGORY_COLORS.containsKey(value)) { ++ missing.add(value.getName()); ++ } ++ } ++ Assertions.assertTrue(missing.isEmpty(), "MobcapsCommand.MOB_CATEGORY_COLORS map missing TextColors for [" + String.join(", ", missing + "]")); ++ } ++} diff --git a/patches/server/0594-Vanilla-command-permission-fixes.patch b/patches/server/0594-Vanilla-command-permission-fixes.patch deleted file mode 100644 index a9f23f8a53a6..000000000000 --- a/patches/server/0594-Vanilla-command-permission-fixes.patch +++ /dev/null @@ -1,79 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Jason Penilla <11360596+jpenilla@users.noreply.github.com> -Date: Wed, 25 Aug 2021 13:19:53 -0700 -Subject: [PATCH] Vanilla command permission fixes - -Fixes permission checks for vanilla commands which don't have a -requirement, as well as for namespaced vanilla commands. - -== AT == -public-f com.mojang.brigadier.tree.CommandNode requirement - -diff --git a/src/main/java/com/mojang/brigadier/builder/ArgumentBuilder.java b/src/main/java/com/mojang/brigadier/builder/ArgumentBuilder.java -index 899008b2980d13f1be6280cd8cb959c53a29bebf..d5f7da3502575f6847f3c22ab0e942848a7c6031 100644 ---- a/src/main/java/com/mojang/brigadier/builder/ArgumentBuilder.java -+++ b/src/main/java/com/mojang/brigadier/builder/ArgumentBuilder.java -@@ -14,9 +14,17 @@ import java.util.Collections; - import java.util.function.Predicate; - - public abstract class ArgumentBuilder> { -+ // Paper start - Vanilla command permission fixes -+ private static final Predicate DEFAULT_REQUIREMENT = s -> true; -+ -+ @SuppressWarnings("unchecked") -+ public static Predicate defaultRequirement() { -+ return (Predicate) DEFAULT_REQUIREMENT; -+ } -+ // Paper end - Vanilla command permission fixes - private final RootCommandNode arguments = new RootCommandNode<>(); - private Command command; -- private Predicate requirement = s -> true; -+ private Predicate requirement = defaultRequirement(); // Paper - Vanilla command permission fixes - private CommandNode target; - private RedirectModifier modifier = null; - private boolean forks; -diff --git a/src/main/java/net/minecraft/commands/Commands.java b/src/main/java/net/minecraft/commands/Commands.java -index c5c9fb28fe858e2900e43f8aafccddf63f09676e..64656e69dbeb6a1cf399ca143a2d7e0a1ee85957 100644 ---- a/src/main/java/net/minecraft/commands/Commands.java -+++ b/src/main/java/net/minecraft/commands/Commands.java -@@ -256,6 +256,13 @@ public class Commands { - PublishCommand.register(this.dispatcher); - } - -+ // Paper start - Vanilla command permission fixes -+ for (final CommandNode node : this.dispatcher.getRoot().getChildren()) { -+ if (node.getRequirement() == com.mojang.brigadier.builder.ArgumentBuilder.defaultRequirement()) { -+ node.requirement = stack -> stack.source == CommandSource.NULL || stack.getBukkitSender().hasPermission(org.bukkit.craftbukkit.command.VanillaCommandWrapper.getPermission(node)); -+ } -+ } -+ // Paper end - Vanilla command permission fixes - // CraftBukkit start - } - -diff --git a/src/main/java/org/bukkit/craftbukkit/command/VanillaCommandWrapper.java b/src/main/java/org/bukkit/craftbukkit/command/VanillaCommandWrapper.java -index 5e6645e16b185aaa6f719055ddbf670b8741fead..98d314cd293d462ef109e952f3239e08e14dda59 100644 ---- a/src/main/java/org/bukkit/craftbukkit/command/VanillaCommandWrapper.java -+++ b/src/main/java/org/bukkit/craftbukkit/command/VanillaCommandWrapper.java -@@ -86,7 +86,21 @@ public final class VanillaCommandWrapper extends BukkitCommand { - } - - public static String getPermission(CommandNode vanillaCommand) { -- return "minecraft.command." + ((vanillaCommand.getRedirect() == null) ? vanillaCommand.getName() : vanillaCommand.getRedirect().getName()); -+ // Paper start - Vanilla command permission fixes -+ while (vanillaCommand.getRedirect() != null) { -+ vanillaCommand = vanillaCommand.getRedirect(); -+ } -+ final String commandName = vanillaCommand.getName(); -+ return "minecraft.command." + stripDefaultNamespace(commandName); -+ } -+ -+ private static String stripDefaultNamespace(final String maybeNamespaced) { -+ final String prefix = "minecraft:"; -+ if (maybeNamespaced.startsWith(prefix)) { -+ return maybeNamespaced.substring(prefix.length()); -+ } -+ return maybeNamespaced; -+ // Paper end - Vanilla command permission fixes - } - - private String toDispatcher(String[] args, String name) { diff --git a/patches/server/0595-Do-not-run-close-logic-for-inventories-on-chunk-unlo.patch b/patches/server/0595-Do-not-run-close-logic-for-inventories-on-chunk-unlo.patch deleted file mode 100644 index 22f86aa476d1..000000000000 --- a/patches/server/0595-Do-not-run-close-logic-for-inventories-on-chunk-unlo.patch +++ /dev/null @@ -1,68 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Spottedleaf -Date: Thu, 11 Mar 2021 03:03:32 -0800 -Subject: [PATCH] Do not run close logic for inventories on chunk unload - -Still call the event and change the active container though. We -want to avoid close logic because it's possible to load the -chunk through it. This should also be OK from a leak prevention/ -state desync POV because the TE is getting unloaded anyways. - -diff --git a/src/main/java/net/minecraft/server/level/ServerLevel.java b/src/main/java/net/minecraft/server/level/ServerLevel.java -index 778d3f3ea2247be5bd6edd382b872f6de5bc359c..de154106419d57a6b6c410fedc29cec1dbe532de 100644 ---- a/src/main/java/net/minecraft/server/level/ServerLevel.java -+++ b/src/main/java/net/minecraft/server/level/ServerLevel.java -@@ -1274,9 +1274,13 @@ public class ServerLevel extends Level implements WorldGenLevel { - // Spigot Start - for (net.minecraft.world.level.block.entity.BlockEntity tileentity : chunk.getBlockEntities().values()) { - if (tileentity instanceof net.minecraft.world.Container) { -+ // Paper start - this area looks like it can load chunks, change the behavior -+ // chests for example can apply physics to the world -+ // so instead we just change the active container and call the event - for (org.bukkit.entity.HumanEntity h : Lists.newArrayList(((net.minecraft.world.Container) tileentity).getViewers())) { -- h.closeInventory(org.bukkit.event.inventory.InventoryCloseEvent.Reason.UNLOADED); // Paper - Inventory close reason -+ ((org.bukkit.craftbukkit.entity.CraftHumanEntity) h).getHandle().closeUnloadedInventory(org.bukkit.event.inventory.InventoryCloseEvent.Reason.UNLOADED); // Paper - Inventory close reason - } -+ // Paper end - this area looks like it can load chunks, change the behavior - } - } - // Spigot End -diff --git a/src/main/java/net/minecraft/server/level/ServerPlayer.java b/src/main/java/net/minecraft/server/level/ServerPlayer.java -index 2dd10cada8d36ed5565481f3f5a5fba168ba9b26..6648af4d8d795637fae444dc85803b399592fd7d 100644 ---- a/src/main/java/net/minecraft/server/level/ServerPlayer.java -+++ b/src/main/java/net/minecraft/server/level/ServerPlayer.java -@@ -1715,6 +1715,18 @@ public class ServerPlayer extends net.minecraft.world.entity.player.Player { - this.connection.send(new ClientboundContainerClosePacket(this.containerMenu.containerId)); - this.doCloseContainer(); - } -+ // Paper start - special close for unloaded inventory -+ @Override -+ public void closeUnloadedInventory(org.bukkit.event.inventory.InventoryCloseEvent.Reason reason) { -+ // copied from above -+ CraftEventFactory.handleInventoryCloseEvent(this, reason); // CraftBukkit -+ // Paper end -+ // copied from below -+ this.connection.send(new ClientboundContainerClosePacket(this.containerMenu.containerId)); -+ this.containerMenu = this.inventoryMenu; -+ // do not run close logic -+ } -+ // Paper end - special close for unloaded inventory - - @Override - public void doCloseContainer() { -diff --git a/src/main/java/net/minecraft/world/entity/player/Player.java b/src/main/java/net/minecraft/world/entity/player/Player.java -index e772b6a501b225f13399365ad743cabe5f6f792e..fab56040ecf496e74f583ec5d6c6c9861cfa4e13 100644 ---- a/src/main/java/net/minecraft/world/entity/player/Player.java -+++ b/src/main/java/net/minecraft/world/entity/player/Player.java -@@ -510,6 +510,11 @@ public abstract class Player extends LivingEntity { - this.containerMenu = this.inventoryMenu; - } - // Paper end - Inventory close reason -+ // Paper start - special close for unloaded inventory -+ public void closeUnloadedInventory(org.bukkit.event.inventory.InventoryCloseEvent.Reason reason) { -+ this.containerMenu = this.inventoryMenu; -+ } -+ // Paper end - special close for unloaded inventory - - public void closeContainer() { - this.containerMenu = this.inventoryMenu; diff --git a/patches/server/0599-Sanitize-ResourceLocation-error-logging.patch b/patches/server/0595-Sanitize-ResourceLocation-error-logging.patch similarity index 100% rename from patches/server/0599-Sanitize-ResourceLocation-error-logging.patch rename to patches/server/0595-Sanitize-ResourceLocation-error-logging.patch diff --git a/patches/server/0596-Manually-inline-methods-in-BlockPosition.patch b/patches/server/0596-Manually-inline-methods-in-BlockPosition.patch new file mode 100644 index 000000000000..89987ab3d210 --- /dev/null +++ b/patches/server/0596-Manually-inline-methods-in-BlockPosition.patch @@ -0,0 +1,63 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Spottedleaf +Date: Mon, 6 Jul 2020 22:48:48 -0700 +Subject: [PATCH] Manually inline methods in BlockPosition + + +diff --git a/src/main/java/net/minecraft/core/BlockPos.java b/src/main/java/net/minecraft/core/BlockPos.java +index 8e00fd1a1fb0d73e800395f4b9fffdbdb6c6b5cb..a1d54d978d34d75475f92dfb806113586e7e449c 100644 +--- a/src/main/java/net/minecraft/core/BlockPos.java ++++ b/src/main/java/net/minecraft/core/BlockPos.java +@@ -576,9 +576,9 @@ public class BlockPos extends Vec3i { + } + + public BlockPos.MutableBlockPos set(int x, int y, int z) { +- this.setX(x); +- this.setY(y); +- this.setZ(z); ++ this.x = x; // Paper - Perf: Manually inline methods in BlockPosition ++ this.y = y; // Paper - Perf: Manually inline methods in BlockPosition ++ this.z = z; // Paper - Perf: Manually inline methods in BlockPosition + return this; + } + +@@ -643,19 +643,19 @@ public class BlockPos extends Vec3i { + + @Override + public BlockPos.MutableBlockPos setX(int i) { +- super.setX(i); ++ this.x = i; // Paper - Perf: Manually inline methods in BlockPosition + return this; + } + + @Override + public BlockPos.MutableBlockPos setY(int i) { +- super.setY(i); ++ this.y = i; // Paper - Perf: Manually inline methods in BlockPosition + return this; + } + + @Override + public BlockPos.MutableBlockPos setZ(int i) { +- super.setZ(i); ++ this.z = i; // Paper - Perf: Manually inline methods in BlockPosition + return this; + } + +diff --git a/src/main/java/net/minecraft/core/Vec3i.java b/src/main/java/net/minecraft/core/Vec3i.java +index 7d5f99cac756c54e5922bf85d5d359edcc21f1e8..2f2bcc1b9b32e58bf70ae6c171177ceb333ed6cd 100644 +--- a/src/main/java/net/minecraft/core/Vec3i.java ++++ b/src/main/java/net/minecraft/core/Vec3i.java +@@ -16,9 +16,9 @@ public class Vec3i implements Comparable { + vec -> IntStream.of(vec.getX(), vec.getY(), vec.getZ()) + ); + public static final Vec3i ZERO = new Vec3i(0, 0, 0); +- private int x; +- private int y; +- private int z; ++ protected int x; // Paper - Perf: Manually inline methods in BlockPosition; protected ++ protected int y; // Paper - Perf: Manually inline methods in BlockPosition; protected ++ protected int z; // Paper - Perf: Manually inline methods in BlockPosition; protected + + public static Codec offsetCodec(int maxAbsValue) { + return CODEC.validate( diff --git a/patches/server/0597-Improve-and-expand-AsyncCatcher.patch b/patches/server/0597-Improve-and-expand-AsyncCatcher.patch deleted file mode 100644 index c36c1b44ca33..000000000000 --- a/patches/server/0597-Improve-and-expand-AsyncCatcher.patch +++ /dev/null @@ -1,227 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Spottedleaf -Date: Wed, 25 Aug 2021 20:17:12 -0700 -Subject: [PATCH] Improve and expand AsyncCatcher - -Log when the async catcher is tripped - The chunk system can swallow the exception given it's all - built with completablefuture, so ensure it is at least printed. - -Add/move several async catchers - -Async catch modifications to critical entity state - These used to be here from Spigot, but were dropped with 1.17. - Now in 1.17, this state is _even more_ critical than it was before, - so these must exist to catch stupid plugins. - -Co-authored-by: Jake Potrebic - -diff --git a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java -index 44d1cd3e6047590a989a2b85c759be4c64125a47..926c0a5f4eaaa8fb650905216d2303fb476d3fa5 100644 ---- a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java -+++ b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java -@@ -1577,6 +1577,7 @@ public class ServerGamePacketListenerImpl extends ServerCommonPacketListenerImpl - } - - public void internalTeleport(double d0, double d1, double d2, float f, float f1, Set set) { // Paper -+ org.spigotmc.AsyncCatcher.catchOp("teleport"); // Paper - // Paper start - Prevent teleporting dead entities - if (player.isRemoved()) { - LOGGER.info("Attempt to teleport removed player {} restricted", player.getScoreboardName()); -diff --git a/src/main/java/net/minecraft/world/entity/LivingEntity.java b/src/main/java/net/minecraft/world/entity/LivingEntity.java -index e56ae5170526fdc9d4e6f8b94a1e6ebc77cae582..4132b5dfbc6a7693f0bb923a8b14ded571560030 100644 ---- a/src/main/java/net/minecraft/world/entity/LivingEntity.java -+++ b/src/main/java/net/minecraft/world/entity/LivingEntity.java -@@ -1122,7 +1122,7 @@ public abstract class LivingEntity extends Entity implements Attackable { - } - - public boolean addEffect(MobEffectInstance mobeffect, @Nullable Entity entity, EntityPotionEffectEvent.Cause cause) { -- org.spigotmc.AsyncCatcher.catchOp("effect add"); // Spigot -+ // org.spigotmc.AsyncCatcher.catchOp("effect add"); // Spigot // Paper - move to API - if (this.isTickingEffects) { - this.effectsToProcess.add(new ProcessableEffect(mobeffect, cause)); - return true; -diff --git a/src/main/java/net/minecraft/world/level/entity/PersistentEntitySectionManager.java b/src/main/java/net/minecraft/world/level/entity/PersistentEntitySectionManager.java -index 1cfc906317f07a44f06a4adf021c44e34a2f1d07..6baa313b8201ed23193d7885c85606b0899ade3c 100644 ---- a/src/main/java/net/minecraft/world/level/entity/PersistentEntitySectionManager.java -+++ b/src/main/java/net/minecraft/world/level/entity/PersistentEntitySectionManager.java -@@ -78,6 +78,7 @@ public class PersistentEntitySectionManager implements A - } - - private boolean addEntityUuid(T entity) { -+ org.spigotmc.AsyncCatcher.catchOp("Entity add by UUID"); // Paper - if (!this.knownUuids.add(entity.getUUID())) { - PersistentEntitySectionManager.LOGGER.warn("UUID of added entity already exists: {}", entity); - return false; -@@ -91,6 +92,7 @@ public class PersistentEntitySectionManager implements A - } - - private boolean addEntity(T entity, boolean existing) { -+ org.spigotmc.AsyncCatcher.catchOp("Entity add"); // Paper - // Paper start - chunk system hooks - if (existing) { - // I don't want to know why this is a generic type. -@@ -146,19 +148,23 @@ public class PersistentEntitySectionManager implements A - } - - void startTicking(T entity) { -+ org.spigotmc.AsyncCatcher.catchOp("Entity start ticking"); // Paper - this.callbacks.onTickingStart(entity); - } - - void stopTicking(T entity) { -+ org.spigotmc.AsyncCatcher.catchOp("Entity stop ticking"); // Paper - this.callbacks.onTickingEnd(entity); - } - - void startTracking(T entity) { -+ org.spigotmc.AsyncCatcher.catchOp("Entity start tracking"); // Paper - this.visibleEntityStorage.add(entity); - this.callbacks.onTrackingStart(entity); - } - - void stopTracking(T entity) { -+ org.spigotmc.AsyncCatcher.catchOp("Entity stop tracking"); // Paper - this.callbacks.onTrackingEnd(entity); - this.visibleEntityStorage.remove(entity); - } -@@ -170,6 +176,7 @@ public class PersistentEntitySectionManager implements A - } - - public void updateChunkStatus(ChunkPos chunkPos, Visibility trackingStatus) { -+ org.spigotmc.AsyncCatcher.catchOp("Update chunk status"); // Paper - long i = chunkPos.toLong(); - - if (trackingStatus == Visibility.HIDDEN) { -@@ -214,6 +221,7 @@ public class PersistentEntitySectionManager implements A - } - - public void ensureChunkQueuedForLoad(long chunkPos) { -+ org.spigotmc.AsyncCatcher.catchOp("Entity chunk save"); // Paper - PersistentEntitySectionManager.ChunkLoadStatus persistententitysectionmanager_b = (PersistentEntitySectionManager.ChunkLoadStatus) this.chunkLoadStatuses.get(chunkPos); - - if (persistententitysectionmanager_b == PersistentEntitySectionManager.ChunkLoadStatus.FRESH) { -@@ -258,6 +266,7 @@ public class PersistentEntitySectionManager implements A - } - - private void requestChunkLoad(long chunkPos) { -+ org.spigotmc.AsyncCatcher.catchOp("Entity chunk load request"); // Paper - this.chunkLoadStatuses.put(chunkPos, PersistentEntitySectionManager.ChunkLoadStatus.PENDING); - ChunkPos chunkcoordintpair = new ChunkPos(chunkPos); - CompletableFuture completablefuture = this.permanentStorage.loadEntities(chunkcoordintpair); -@@ -271,6 +280,7 @@ public class PersistentEntitySectionManager implements A - } - - private boolean processChunkUnload(long chunkPos) { -+ org.spigotmc.AsyncCatcher.catchOp("Entity chunk unload process"); // Paper - boolean flag = this.storeChunkSections(chunkPos, (entityaccess) -> { - entityaccess.getPassengersAndSelf().forEach(this::unloadEntity); - }, true); // CraftBukkit - add boolean for event call -@@ -295,6 +305,7 @@ public class PersistentEntitySectionManager implements A - } - - private void processPendingLoads() { -+ org.spigotmc.AsyncCatcher.catchOp("Entity chunk process pending loads"); // Paper - ChunkEntities chunkentities; // CraftBukkit - decompile error - - while ((chunkentities = (ChunkEntities) this.loadingInbox.poll()) != null) { -@@ -311,6 +322,7 @@ public class PersistentEntitySectionManager implements A - } - - public void tick() { -+ org.spigotmc.AsyncCatcher.catchOp("Entity manager tick"); // Paper - this.processPendingLoads(); - this.processUnloads(); - } -@@ -331,6 +343,7 @@ public class PersistentEntitySectionManager implements A - } - - public void autoSave() { -+ org.spigotmc.AsyncCatcher.catchOp("Entity manager autosave"); // Paper - this.getAllChunksToSave().forEach((java.util.function.LongConsumer) (i) -> { // CraftBukkit - decompile error - boolean flag = this.chunkVisibility.get(i) == Visibility.HIDDEN; - -@@ -345,6 +358,7 @@ public class PersistentEntitySectionManager implements A - } - - public void saveAll() { -+ org.spigotmc.AsyncCatcher.catchOp("Entity manager save"); // Paper - LongSet longset = this.getAllChunksToSave(); - - while (!longset.isEmpty()) { -@@ -452,6 +466,7 @@ public class PersistentEntitySectionManager implements A - long i = SectionPos.asLong(blockposition); - - if (i != this.currentSectionKey) { -+ org.spigotmc.AsyncCatcher.catchOp("Entity move"); // Paper - Visibility visibility = this.currentSection.getStatus(); - - if (!this.currentSection.remove(this.entity)) { -@@ -506,6 +521,7 @@ public class PersistentEntitySectionManager implements A - - @Override - public void onRemove(Entity.RemovalReason reason) { -+ org.spigotmc.AsyncCatcher.catchOp("Entity remove"); // Paper - if (!this.currentSection.remove(this.entity)) { - PersistentEntitySectionManager.LOGGER.warn("Entity {} wasn't found in section {} (destroying due to {})", new Object[]{this.entity, SectionPos.of(this.currentSectionKey), reason}); - } -diff --git a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java -index 16e715c48acc882a9b355d885e181f1dd916fa76..bf4be21f24af1e569267be6413dbee533c153fc5 100644 ---- a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java -+++ b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java -@@ -1760,6 +1760,7 @@ public class CraftWorld extends CraftRegionAccessor implements World { - - @Override - public void playSound(Location loc, Sound sound, org.bukkit.SoundCategory category, float volume, float pitch, long seed) { -+ org.spigotmc.AsyncCatcher.catchOp("play sound"); // Paper - if (loc == null || sound == null || category == null) return; - - double x = loc.getX(); -@@ -1771,6 +1772,7 @@ public class CraftWorld extends CraftRegionAccessor implements World { - - @Override - public void playSound(Location loc, String sound, org.bukkit.SoundCategory category, float volume, float pitch, long seed) { -+ org.spigotmc.AsyncCatcher.catchOp("play sound"); // Paper - if (loc == null || sound == null || category == null) return; - - double x = loc.getX(); -@@ -1803,6 +1805,7 @@ public class CraftWorld extends CraftRegionAccessor implements World { - - @Override - public void playSound(Entity entity, Sound sound, org.bukkit.SoundCategory category, float volume, float pitch, long seed) { -+ org.spigotmc.AsyncCatcher.catchOp("play sound"); // Paper - if (!(entity instanceof CraftEntity craftEntity) || entity.getWorld() != this || sound == null || category == null) return; - - ClientboundSoundEntityPacket packet = new ClientboundSoundEntityPacket(CraftSound.bukkitToMinecraftHolder(sound), net.minecraft.sounds.SoundSource.valueOf(category.name()), craftEntity.getHandle(), volume, pitch, seed); -@@ -1814,6 +1817,7 @@ public class CraftWorld extends CraftRegionAccessor implements World { - - @Override - public void playSound(Entity entity, String sound, org.bukkit.SoundCategory category, float volume, float pitch, long seed) { -+ org.spigotmc.AsyncCatcher.catchOp("play sound"); // Paper - if (!(entity instanceof CraftEntity craftEntity) || entity.getWorld() != this || sound == null || category == null) return; - - ClientboundSoundEntityPacket packet = new ClientboundSoundEntityPacket(Holder.direct(SoundEvent.createVariableRangeEvent(ResourceLocation.parse(sound))), net.minecraft.sounds.SoundSource.valueOf(category.name()), craftEntity.getHandle(), volume, pitch, seed); -diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftLivingEntity.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftLivingEntity.java -index efac0d3ed78c621a52f905b5d7f267b4fb180e65..0ed2910e64b6efdb4180c5bc23a146aced87c3d9 100644 ---- a/src/main/java/org/bukkit/craftbukkit/entity/CraftLivingEntity.java -+++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftLivingEntity.java -@@ -525,6 +525,7 @@ public class CraftLivingEntity extends CraftEntity implements LivingEntity { - - @Override - public boolean addPotionEffect(PotionEffect effect, boolean force) { -+ org.spigotmc.AsyncCatcher.catchOp("effect add"); // Paper - this.getHandle().addEffect(org.bukkit.craftbukkit.potion.CraftPotionUtil.fromBukkit(effect), EntityPotionEffectEvent.Cause.PLUGIN); // Paper - Don't ignore icon - return true; - } -diff --git a/src/main/java/org/spigotmc/AsyncCatcher.java b/src/main/java/org/spigotmc/AsyncCatcher.java -index bbf0d9d9c44fe8d7add2f978994ec129420814c7..ef2598760458833021ef1bee92137f42c9fe591f 100644 ---- a/src/main/java/org/spigotmc/AsyncCatcher.java -+++ b/src/main/java/org/spigotmc/AsyncCatcher.java -@@ -11,6 +11,7 @@ public class AsyncCatcher - { - if ( AsyncCatcher.enabled && Thread.currentThread() != MinecraftServer.getServer().serverThread ) - { -+ MinecraftServer.LOGGER.error("Thread " + Thread.currentThread().getName() + " failed main thread check: " + reason, new Throwable()); // Paper - throw new IllegalStateException( "Asynchronous " + reason + "!" ); - } - } diff --git a/patches/server/0601-Name-craft-scheduler-threads-according-to-the-plugin.patch b/patches/server/0597-Name-craft-scheduler-threads-according-to-the-plugin.patch similarity index 100% rename from patches/server/0601-Name-craft-scheduler-threads-according-to-the-plugin.patch rename to patches/server/0597-Name-craft-scheduler-threads-according-to-the-plugin.patch diff --git a/patches/server/0598-Add-paper-mobcaps-and-paper-playermobcaps.patch b/patches/server/0598-Add-paper-mobcaps-and-paper-playermobcaps.patch deleted file mode 100644 index d2bd8ad4d665..000000000000 --- a/patches/server/0598-Add-paper-mobcaps-and-paper-playermobcaps.patch +++ /dev/null @@ -1,343 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Jason Penilla <11360596+jpenilla@users.noreply.github.com> -Date: Mon, 16 Aug 2021 01:31:54 -0500 -Subject: [PATCH] Add '/paper mobcaps' and '/paper playermobcaps' - -Add commands to get the mobcaps for a world, as well as the mobcaps for -each player when per-player mob spawning is enabled. - -Also has a hover text on each mob category listing what entity types are -in said category - -diff --git a/src/main/java/io/papermc/paper/command/PaperCommand.java b/src/main/java/io/papermc/paper/command/PaperCommand.java -index cdad0fd5257ae842f83b9c1c98b4565b468d4f54..fd4f37711989431f997d77fb0917f8a9232ce53f 100644 ---- a/src/main/java/io/papermc/paper/command/PaperCommand.java -+++ b/src/main/java/io/papermc/paper/command/PaperCommand.java -@@ -40,6 +40,7 @@ public final class PaperCommand extends Command { - commands.put(Set.of("dumpplugins"), new DumpPluginsCommand()); - commands.put(Set.of("syncloadinfo"), new SyncLoadInfoCommand()); - commands.put(Set.of("dumpitem"), new DumpItemCommand()); -+ commands.put(Set.of("mobcaps", "playermobcaps"), new MobcapsCommand()); - - return commands.entrySet().stream() - .flatMap(entry -> entry.getKey().stream().map(s -> Map.entry(s, entry.getValue()))) -diff --git a/src/main/java/io/papermc/paper/command/subcommands/MobcapsCommand.java b/src/main/java/io/papermc/paper/command/subcommands/MobcapsCommand.java -new file mode 100644 -index 0000000000000000000000000000000000000000..d3b39d88a72ca25057fd8574d32f28db0d420818 ---- /dev/null -+++ b/src/main/java/io/papermc/paper/command/subcommands/MobcapsCommand.java -@@ -0,0 +1,229 @@ -+package io.papermc.paper.command.subcommands; -+ -+import com.google.common.collect.ImmutableMap; -+import io.papermc.paper.command.CommandUtil; -+import io.papermc.paper.command.PaperSubcommand; -+import java.util.ArrayList; -+import java.util.Collections; -+import java.util.List; -+import java.util.Map; -+import java.util.function.ToIntFunction; -+import net.kyori.adventure.text.Component; -+import net.kyori.adventure.text.ComponentLike; -+import net.kyori.adventure.text.JoinConfiguration; -+import net.kyori.adventure.text.TextComponent; -+import net.kyori.adventure.text.format.NamedTextColor; -+import net.kyori.adventure.text.format.TextColor; -+import net.minecraft.core.registries.BuiltInRegistries; -+import net.minecraft.server.level.ServerLevel; -+import net.minecraft.server.level.ServerPlayer; -+import net.minecraft.world.entity.MobCategory; -+import net.minecraft.world.level.NaturalSpawner; -+import org.bukkit.Bukkit; -+import org.bukkit.World; -+import org.bukkit.command.CommandSender; -+import org.bukkit.craftbukkit.CraftWorld; -+import org.bukkit.craftbukkit.entity.CraftPlayer; -+import org.bukkit.entity.Player; -+import org.checkerframework.checker.nullness.qual.NonNull; -+import org.checkerframework.checker.nullness.qual.Nullable; -+import org.checkerframework.framework.qual.DefaultQualifier; -+ -+@DefaultQualifier(NonNull.class) -+public final class MobcapsCommand implements PaperSubcommand { -+ static final Map MOB_CATEGORY_COLORS = ImmutableMap.builder() -+ .put(MobCategory.MONSTER, NamedTextColor.RED) -+ .put(MobCategory.CREATURE, NamedTextColor.GREEN) -+ .put(MobCategory.AMBIENT, NamedTextColor.GRAY) -+ .put(MobCategory.AXOLOTLS, TextColor.color(0x7324FF)) -+ .put(MobCategory.UNDERGROUND_WATER_CREATURE, TextColor.color(0x3541E6)) -+ .put(MobCategory.WATER_CREATURE, TextColor.color(0x006EFF)) -+ .put(MobCategory.WATER_AMBIENT, TextColor.color(0x00B3FF)) -+ .put(MobCategory.MISC, TextColor.color(0x636363)) -+ .build(); -+ -+ @Override -+ public boolean execute(final CommandSender sender, final String subCommand, final String[] args) { -+ switch (subCommand) { -+ case "mobcaps" -> this.printMobcaps(sender, args); -+ case "playermobcaps" -> this.printPlayerMobcaps(sender, args); -+ } -+ return true; -+ } -+ -+ @Override -+ public List tabComplete(final CommandSender sender, final String subCommand, final String[] args) { -+ return switch (subCommand) { -+ case "mobcaps" -> CommandUtil.getListMatchingLast(sender, args, this.suggestMobcaps(args)); -+ case "playermobcaps" -> CommandUtil.getListMatchingLast(sender, args, this.suggestPlayerMobcaps(sender, args)); -+ default -> throw new IllegalArgumentException(); -+ }; -+ } -+ -+ private List suggestMobcaps(final String[] args) { -+ if (args.length == 1) { -+ final List worlds = new ArrayList<>(Bukkit.getWorlds().stream().map(World::getName).toList()); -+ worlds.add("*"); -+ return worlds; -+ } -+ -+ return Collections.emptyList(); -+ } -+ -+ private List suggestPlayerMobcaps(final CommandSender sender, final String[] args) { -+ if (args.length == 1) { -+ final List list = new ArrayList<>(); -+ for (final Player player : Bukkit.getOnlinePlayers()) { -+ if (!(sender instanceof Player senderPlayer) || senderPlayer.canSee(player)) { -+ list.add(player.getName()); -+ } -+ } -+ return list; -+ } -+ -+ return Collections.emptyList(); -+ } -+ -+ private void printMobcaps(final CommandSender sender, final String[] args) { -+ final List worlds; -+ if (args.length == 0) { -+ if (sender instanceof Player player) { -+ worlds = List.of(player.getWorld()); -+ } else { -+ sender.sendMessage(Component.text("Must specify a world! ex: '/paper mobcaps world'", NamedTextColor.RED)); -+ return; -+ } -+ } else if (args.length == 1) { -+ final String input = args[0]; -+ if (input.equals("*")) { -+ worlds = Bukkit.getWorlds(); -+ } else { -+ final @Nullable World world = Bukkit.getWorld(input); -+ if (world == null) { -+ sender.sendMessage(Component.text("'" + input + "' is not a valid world!", NamedTextColor.RED)); -+ return; -+ } else { -+ worlds = List.of(world); -+ } -+ } -+ } else { -+ sender.sendMessage(Component.text("Too many arguments!", NamedTextColor.RED)); -+ return; -+ } -+ -+ for (final World world : worlds) { -+ final ServerLevel level = ((CraftWorld) world).getHandle(); -+ final NaturalSpawner.@Nullable SpawnState state = level.getChunkSource().getLastSpawnState(); -+ -+ final int chunks; -+ if (state == null) { -+ chunks = 0; -+ } else { -+ chunks = state.getSpawnableChunkCount(); -+ } -+ sender.sendMessage(Component.join(JoinConfiguration.noSeparators(), -+ Component.text("Mobcaps for world: "), -+ Component.text(world.getName(), NamedTextColor.AQUA), -+ Component.text(" (" + chunks + " spawnable chunks)") -+ )); -+ -+ sender.sendMessage(createMobcapsComponent( -+ category -> { -+ if (state == null) { -+ return 0; -+ } else { -+ return state.getMobCategoryCounts().getOrDefault(category, 0); -+ } -+ }, -+ category -> NaturalSpawner.globalLimitForCategory(level, category, chunks) -+ )); -+ } -+ } -+ -+ private void printPlayerMobcaps(final CommandSender sender, final String[] args) { -+ final @Nullable Player player; -+ if (args.length == 0) { -+ if (sender instanceof Player pl) { -+ player = pl; -+ } else { -+ sender.sendMessage(Component.text("Must specify a player! ex: '/paper playermobcount playerName'", NamedTextColor.RED)); -+ return; -+ } -+ } else if (args.length == 1) { -+ final String input = args[0]; -+ player = Bukkit.getPlayerExact(input); -+ if (player == null) { -+ sender.sendMessage(Component.text("Could not find player named '" + input + "'", NamedTextColor.RED)); -+ return; -+ } -+ } else { -+ sender.sendMessage(Component.text("Too many arguments!", NamedTextColor.RED)); -+ return; -+ } -+ -+ final ServerPlayer serverPlayer = ((CraftPlayer) player).getHandle(); -+ final ServerLevel level = serverPlayer.serverLevel(); -+ -+ if (!level.paperConfig().entities.spawning.perPlayerMobSpawns) { -+ sender.sendMessage(Component.text("Use '/paper mobcaps' for worlds where per-player mob spawning is disabled.", NamedTextColor.RED)); -+ return; -+ } -+ -+ sender.sendMessage(Component.join(JoinConfiguration.noSeparators(), Component.text("Mobcaps for player: "), Component.text(player.getName(), NamedTextColor.GREEN))); -+ sender.sendMessage(createMobcapsComponent( -+ category -> level.chunkSource.chunkMap.getMobCountNear(serverPlayer, category), -+ category -> level.getWorld().getSpawnLimitUnsafe(org.bukkit.craftbukkit.util.CraftSpawnCategory.toBukkit(category)) -+ )); -+ } -+ -+ private static Component createMobcapsComponent(final ToIntFunction countGetter, final ToIntFunction limitGetter) { -+ return MOB_CATEGORY_COLORS.entrySet().stream() -+ .map(entry -> { -+ final MobCategory category = entry.getKey(); -+ final TextColor color = entry.getValue(); -+ -+ final Component categoryHover = Component.join(JoinConfiguration.noSeparators(), -+ Component.text("Entity types in category ", TextColor.color(0xE0E0E0)), -+ Component.text(category.getName(), color), -+ Component.text(':', NamedTextColor.GRAY), -+ Component.newline(), -+ Component.newline(), -+ BuiltInRegistries.ENTITY_TYPE.entrySet().stream() -+ .filter(it -> it.getValue().getCategory() == category) -+ .map(it -> Component.translatable(it.getValue().getDescriptionId())) -+ .collect(Component.toComponent(Component.text(", ", NamedTextColor.GRAY))) -+ ); -+ -+ final Component categoryComponent = Component.text() -+ .content(" " + category.getName()) -+ .color(color) -+ .hoverEvent(categoryHover) -+ .build(); -+ -+ final TextComponent.Builder builder = Component.text() -+ .append( -+ categoryComponent, -+ Component.text(": ", NamedTextColor.GRAY) -+ ); -+ final int limit = limitGetter.applyAsInt(category); -+ if (limit != -1) { -+ builder.append( -+ Component.text(countGetter.applyAsInt(category)), -+ Component.text("/", NamedTextColor.GRAY), -+ Component.text(limit) -+ ); -+ } else { -+ builder.append(Component.text() -+ .append( -+ Component.text('n'), -+ Component.text("/", NamedTextColor.GRAY), -+ Component.text('a') -+ ) -+ .hoverEvent(Component.text("This category does not naturally spawn."))); -+ } -+ return builder; -+ }) -+ .map(ComponentLike::asComponent) -+ .collect(Component.toComponent(Component.newline())); -+ } -+} -diff --git a/src/main/java/net/minecraft/world/level/NaturalSpawner.java b/src/main/java/net/minecraft/world/level/NaturalSpawner.java -index 58ea6a1f95a09c22125a8262b1b221004ebce0e4..ea6533c1ac218aa075da3401807a06fcb7892321 100644 ---- a/src/main/java/net/minecraft/world/level/NaturalSpawner.java -+++ b/src/main/java/net/minecraft/world/level/NaturalSpawner.java -@@ -152,6 +152,16 @@ public final class NaturalSpawner { - world.getProfiler().pop(); - } - -+ // Paper start - Add mobcaps commands -+ public static int globalLimitForCategory(final ServerLevel level, final MobCategory category, final int spawnableChunkCount) { -+ final int categoryLimit = level.getWorld().getSpawnLimitUnsafe(CraftSpawnCategory.toBukkit(category)); -+ if (categoryLimit < 1) { -+ return categoryLimit; -+ } -+ return categoryLimit * spawnableChunkCount / NaturalSpawner.MAGIC_NUMBER; -+ } -+ // Paper end - Add mobcaps commands -+ - public static void spawnCategoryForChunk(MobCategory group, ServerLevel world, LevelChunk chunk, NaturalSpawner.SpawnPredicate checker, NaturalSpawner.AfterSpawnCallback runner) { - BlockPos blockposition = NaturalSpawner.getRandomPosWithin(world, chunk); - -diff --git a/src/main/java/org/bukkit/craftbukkit/CraftServer.java b/src/main/java/org/bukkit/craftbukkit/CraftServer.java -index 36a1b2704debdb8a2f247180d263a1a95bcba24b..b638ccf0e46eeb375a59a42d6f29edd3f084fa17 100644 ---- a/src/main/java/org/bukkit/craftbukkit/CraftServer.java -+++ b/src/main/java/org/bukkit/craftbukkit/CraftServer.java -@@ -2331,6 +2331,11 @@ public final class CraftServer implements Server { - - @Override - public int getSpawnLimit(SpawnCategory spawnCategory) { -+ // Paper start - Add mobcaps commands -+ return this.getSpawnLimitUnsafe(spawnCategory); -+ } -+ public int getSpawnLimitUnsafe(final SpawnCategory spawnCategory) { -+ // Paper end - Add mobcaps commands - return this.spawnCategoryLimit.getOrDefault(spawnCategory, -1); - } - -diff --git a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java -index bf4be21f24af1e569267be6413dbee533c153fc5..838ffddba99964748dfe95d68ca93b578bc3292b 100644 ---- a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java -+++ b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java -@@ -1718,9 +1718,14 @@ public class CraftWorld extends CraftRegionAccessor implements World { - Preconditions.checkArgument(spawnCategory != null, "SpawnCategory cannot be null"); - Preconditions.checkArgument(CraftSpawnCategory.isValidForLimits(spawnCategory), "SpawnCategory.%s are not supported", spawnCategory); - -+ // Paper start - Add mobcaps commands -+ return this.getSpawnLimitUnsafe(spawnCategory); -+ } -+ public final int getSpawnLimitUnsafe(final SpawnCategory spawnCategory) { - int limit = this.spawnCategoryLimit.getOrDefault(spawnCategory, -1); - if (limit < 0) { -- limit = this.server.getSpawnLimit(spawnCategory); -+ limit = this.server.getSpawnLimitUnsafe(spawnCategory); -+ // Paper end - Add mobcaps commands - } - return limit; - } -diff --git a/src/test/java/io/papermc/paper/command/subcommands/MobcapsCommandTest.java b/src/test/java/io/papermc/paper/command/subcommands/MobcapsCommandTest.java -new file mode 100644 -index 0000000000000000000000000000000000000000..6fdc77caa74845786c78a6ba087062b4d698cb82 ---- /dev/null -+++ b/src/test/java/io/papermc/paper/command/subcommands/MobcapsCommandTest.java -@@ -0,0 +1,22 @@ -+package io.papermc.paper.command.subcommands; -+ -+import java.util.HashSet; -+import java.util.Set; -+import net.minecraft.world.entity.MobCategory; -+import org.bukkit.support.environment.Normal; -+import org.junit.jupiter.api.Assertions; -+import org.junit.jupiter.api.Test; -+ -+@Normal -+public class MobcapsCommandTest { -+ @Test -+ public void testMobCategoryColors() { -+ final Set missing = new HashSet<>(); -+ for (final MobCategory value : MobCategory.values()) { -+ if (!MobcapsCommand.MOB_CATEGORY_COLORS.containsKey(value)) { -+ missing.add(value.getName()); -+ } -+ } -+ Assertions.assertTrue(missing.isEmpty(), "MobcapsCommand.MOB_CATEGORY_COLORS map missing TextColors for [" + String.join(", ", missing + "]")); -+ } -+} diff --git a/patches/server/0598-Make-sure-inlined-getChunkAt-has-inlined-logic-for-l.patch b/patches/server/0598-Make-sure-inlined-getChunkAt-has-inlined-logic-for-l.patch new file mode 100644 index 000000000000..9efbcafadb65 --- /dev/null +++ b/patches/server/0598-Make-sure-inlined-getChunkAt-has-inlined-logic-for-l.patch @@ -0,0 +1,31 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Spottedleaf +Date: Sun, 20 Sep 2020 16:10:49 -0700 +Subject: [PATCH] Make sure inlined getChunkAt has inlined logic for loaded + chunks + +Tux did some profiling some time ago and showed that the +previous getChunkAt method which had inlined logic for loaded +chunks did get inlined, but the standard CPS.getChunkAt +method was not inlined. + +diff --git a/src/main/java/net/minecraft/world/level/Level.java b/src/main/java/net/minecraft/world/level/Level.java +index 9536e127ff4d45ca59b74fe0f3dbde9a18c04f42..9afc0eaaca5ab7b6445d90ce53e31a6ae76f8848 100644 +--- a/src/main/java/net/minecraft/world/level/Level.java ++++ b/src/main/java/net/minecraft/world/level/Level.java +@@ -350,7 +350,14 @@ public abstract class Level implements LevelAccessor, AutoCloseable { + + @Override + public final LevelChunk getChunk(int chunkX, int chunkZ) { // Paper - final to help inline +- return (LevelChunk) this.getChunk(chunkX, chunkZ, ChunkStatus.FULL, true); // Paper - avoid a method jump ++ // Paper start - Perf: make sure loaded chunks get the inlined variant of this function ++ net.minecraft.server.level.ServerChunkCache cps = ((ServerLevel)this).getChunkSource(); ++ LevelChunk ifLoaded = cps.getChunkAtIfLoadedImmediately(chunkX, chunkZ); ++ if (ifLoaded != null) { ++ return ifLoaded; ++ } ++ return (LevelChunk) cps.getChunk(chunkX, chunkZ, ChunkStatus.FULL, true); // Paper - avoid a method jump ++ // Paper end - Perf: make sure loaded chunks get the inlined variant of this function + } + + // Paper start - if loaded diff --git a/patches/server/0599-Don-t-read-neighbour-chunk-data-off-disk-when-conver.patch b/patches/server/0599-Don-t-read-neighbour-chunk-data-off-disk-when-conver.patch new file mode 100644 index 000000000000..eff1d0a95f92 --- /dev/null +++ b/patches/server/0599-Don-t-read-neighbour-chunk-data-off-disk-when-conver.patch @@ -0,0 +1,21 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Spottedleaf +Date: Sun, 11 Apr 2021 02:58:48 -0700 +Subject: [PATCH] Don't read neighbour chunk data off disk when converting + chunks + +Lighting is purged on update anyways, so let's not add more +into the conversion process + +diff --git a/src/main/java/net/minecraft/world/level/chunk/storage/ChunkStorage.java b/src/main/java/net/minecraft/world/level/chunk/storage/ChunkStorage.java +index 909acbf5b8c6edcb4529647c11464d911d6f8c15..7d5e2e6e96ea9017334dddade54a9dcb37518642 100644 +--- a/src/main/java/net/minecraft/world/level/chunk/storage/ChunkStorage.java ++++ b/src/main/java/net/minecraft/world/level/chunk/storage/ChunkStorage.java +@@ -47,6 +47,7 @@ public class ChunkStorage implements AutoCloseable { + + // CraftBukkit start + private boolean check(ServerChunkCache cps, int x, int z) { ++ if (true) return true; // Paper - Perf: this isn't even needed anymore, light is purged updating to 1.14+, why are we holding up the conversion process reading chunk data off disk - return true, we need to set light populated to true so the converter recognizes the chunk as being "full" + ChunkPos pos = new ChunkPos(x, z); + if (cps != null) { + com.google.common.base.Preconditions.checkState(org.bukkit.Bukkit.isPrimaryThread(), "primary thread"); diff --git a/patches/server/0600-Don-t-lookup-fluid-state-when-raytracing-skip-air-bl.patch b/patches/server/0600-Don-t-lookup-fluid-state-when-raytracing-skip-air-bl.patch new file mode 100644 index 000000000000..e01f02928a72 --- /dev/null +++ b/patches/server/0600-Don-t-lookup-fluid-state-when-raytracing-skip-air-bl.patch @@ -0,0 +1,26 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Spottedleaf +Date: Fri, 28 Aug 2020 12:33:47 -0700 +Subject: [PATCH] Don't lookup fluid state when raytracing, skip air blocks + +Just use the iblockdata already retrieved, removes a getType call. + +Also save approx. 5% for the raytrace call, as most (expensive) +raytracing tends to go through air and returning early is an +easy win. The remaining problems with this function +are mostly with the block getting itself. + +diff --git a/src/main/java/net/minecraft/world/level/BlockGetter.java b/src/main/java/net/minecraft/world/level/BlockGetter.java +index f39ccc0d2a4eea4e1e0b15608780c7c4a749e672..7e1a332168357b9af14dbe3299549c2c93903fa6 100644 +--- a/src/main/java/net/minecraft/world/level/BlockGetter.java ++++ b/src/main/java/net/minecraft/world/level/BlockGetter.java +@@ -80,7 +80,8 @@ public interface BlockGetter extends LevelHeightAccessor { + return BlockHitResult.miss(raytrace1.getTo(), Direction.getApproximateNearest(vec3d.x, vec3d.y, vec3d.z), BlockPos.containing(raytrace1.getTo())); + } + // Paper end - Prevent raytrace from loading chunks +- FluidState fluid = this.getFluidState(blockposition); ++ if (iblockdata.isAir()) return null; // Paper - Perf: optimise air cases ++ FluidState fluid = iblockdata.getFluidState(); // Paper - Perf: don't need to go to world state again + Vec3 vec3d = raytrace1.getFrom(); + Vec3 vec3d1 = raytrace1.getTo(); + VoxelShape voxelshape = raytrace1.getBlockShape(iblockdata, this, blockposition); diff --git a/patches/server/0600-Manually-inline-methods-in-BlockPosition.patch b/patches/server/0600-Manually-inline-methods-in-BlockPosition.patch deleted file mode 100644 index 77b9a47ac100..000000000000 --- a/patches/server/0600-Manually-inline-methods-in-BlockPosition.patch +++ /dev/null @@ -1,63 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Spottedleaf -Date: Mon, 6 Jul 2020 22:48:48 -0700 -Subject: [PATCH] Manually inline methods in BlockPosition - - -diff --git a/src/main/java/net/minecraft/core/BlockPos.java b/src/main/java/net/minecraft/core/BlockPos.java -index 73d7b5148e3a92c085b08303589827a6f0ae8d07..12ff8886bb53ca15db745989c25b9bd2f45335e4 100644 ---- a/src/main/java/net/minecraft/core/BlockPos.java -+++ b/src/main/java/net/minecraft/core/BlockPos.java -@@ -570,9 +570,9 @@ public class BlockPos extends Vec3i { - } - - public BlockPos.MutableBlockPos set(int x, int y, int z) { -- this.setX(x); -- this.setY(y); -- this.setZ(z); -+ this.x = x; // Paper - Perf: Manually inline methods in BlockPosition -+ this.y = y; // Paper - Perf: Manually inline methods in BlockPosition -+ this.z = z; // Paper - Perf: Manually inline methods in BlockPosition - return this; - } - -@@ -637,19 +637,19 @@ public class BlockPos extends Vec3i { - - @Override - public BlockPos.MutableBlockPos setX(int i) { -- super.setX(i); -+ this.x = i; // Paper - Perf: Manually inline methods in BlockPosition - return this; - } - - @Override - public BlockPos.MutableBlockPos setY(int i) { -- super.setY(i); -+ this.y = i; // Paper - Perf: Manually inline methods in BlockPosition - return this; - } - - @Override - public BlockPos.MutableBlockPos setZ(int i) { -- super.setZ(i); -+ this.z = i; // Paper - Perf: Manually inline methods in BlockPosition - return this; - } - -diff --git a/src/main/java/net/minecraft/core/Vec3i.java b/src/main/java/net/minecraft/core/Vec3i.java -index df4c9b275752ad97a4efe9380ae0d511ee760695..02367ef1371dde94ff6c4cd40bd32e800d6ccaaf 100644 ---- a/src/main/java/net/minecraft/core/Vec3i.java -+++ b/src/main/java/net/minecraft/core/Vec3i.java -@@ -16,9 +16,9 @@ public class Vec3i implements Comparable { - vec -> IntStream.of(vec.getX(), vec.getY(), vec.getZ()) - ); - public static final Vec3i ZERO = new Vec3i(0, 0, 0); -- private int x; -- private int y; -- private int z; -+ protected int x; // Paper - Perf: Manually inline methods in BlockPosition; protected -+ protected int y; // Paper - Perf: Manually inline methods in BlockPosition; protected -+ protected int z; // Paper - Perf: Manually inline methods in BlockPosition; protected - - public static Codec offsetCodec(int maxAbsValue) { - return CODEC.validate( diff --git a/patches/server/0601-Oprimise-map-impl-for-tracked-players.patch b/patches/server/0601-Oprimise-map-impl-for-tracked-players.patch new file mode 100644 index 000000000000..286a4f8c945d --- /dev/null +++ b/patches/server/0601-Oprimise-map-impl-for-tracked-players.patch @@ -0,0 +1,21 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Spottedleaf +Date: Fri, 19 Feb 2021 22:51:52 -0800 +Subject: [PATCH] Oprimise map impl for tracked players + +Reference2BooleanOpenHashMap is going to have +better lookups than HashMap. + +diff --git a/src/main/java/net/minecraft/server/level/ChunkMap.java b/src/main/java/net/minecraft/server/level/ChunkMap.java +index 0f8fc275af95750871aa6917aa12053f75c112f7..51a6735b35e73175680e61c2d67d4adbedf305c9 100644 +--- a/src/main/java/net/minecraft/server/level/ChunkMap.java ++++ b/src/main/java/net/minecraft/server/level/ChunkMap.java +@@ -1514,7 +1514,7 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider + final Entity entity; + private final int range; + SectionPos lastSectionPos; +- public final Set seenBy = Sets.newIdentityHashSet(); ++ public final Set seenBy = new it.unimi.dsi.fastutil.objects.ReferenceOpenHashSet<>(); // Paper - Perf: optimise map impl + + public TrackedEntity(final Entity entity, final int i, final int j, final boolean flag) { + this.serverEntity = new ServerEntity(ChunkMap.this.level, entity, j, flag, this::broadcast, this.seenBy); // CraftBukkit diff --git a/patches/server/0607-Add-missing-InventoryType.patch b/patches/server/0602-Add-missing-InventoryType.patch similarity index 100% rename from patches/server/0607-Add-missing-InventoryType.patch rename to patches/server/0602-Add-missing-InventoryType.patch diff --git a/patches/server/0602-Make-sure-inlined-getChunkAt-has-inlined-logic-for-l.patch b/patches/server/0602-Make-sure-inlined-getChunkAt-has-inlined-logic-for-l.patch deleted file mode 100644 index 23f6d7b2458a..000000000000 --- a/patches/server/0602-Make-sure-inlined-getChunkAt-has-inlined-logic-for-l.patch +++ /dev/null @@ -1,31 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Spottedleaf -Date: Sun, 20 Sep 2020 16:10:49 -0700 -Subject: [PATCH] Make sure inlined getChunkAt has inlined logic for loaded - chunks - -Tux did some profiling some time ago and showed that the -previous getChunkAt method which had inlined logic for loaded -chunks did get inlined, but the standard CPS.getChunkAt -method was not inlined. - -diff --git a/src/main/java/net/minecraft/world/level/Level.java b/src/main/java/net/minecraft/world/level/Level.java -index 20a14b4163807b806bf2ce5a88d3c35098bed929..b4111bcc6a676dc42b233761aa667708669c2ab8 100644 ---- a/src/main/java/net/minecraft/world/level/Level.java -+++ b/src/main/java/net/minecraft/world/level/Level.java -@@ -352,7 +352,14 @@ public abstract class Level implements LevelAccessor, AutoCloseable { - - @Override - public final LevelChunk getChunk(int chunkX, int chunkZ) { // Paper - final to help inline -- return (LevelChunk) this.getChunk(chunkX, chunkZ, ChunkStatus.FULL, true); // Paper - avoid a method jump -+ // Paper start - Perf: make sure loaded chunks get the inlined variant of this function -+ net.minecraft.server.level.ServerChunkCache cps = ((ServerLevel)this).getChunkSource(); -+ LevelChunk ifLoaded = cps.getChunkAtIfLoadedImmediately(chunkX, chunkZ); -+ if (ifLoaded != null) { -+ return ifLoaded; -+ } -+ return (LevelChunk) cps.getChunk(chunkX, chunkZ, ChunkStatus.FULL, true); // Paper - avoid a method jump -+ // Paper end - Perf: make sure loaded chunks get the inlined variant of this function - } - - // Paper start - if loaded diff --git a/patches/server/0603-Don-t-read-neighbour-chunk-data-off-disk-when-conver.patch b/patches/server/0603-Don-t-read-neighbour-chunk-data-off-disk-when-conver.patch deleted file mode 100644 index 48449be461ae..000000000000 --- a/patches/server/0603-Don-t-read-neighbour-chunk-data-off-disk-when-conver.patch +++ /dev/null @@ -1,21 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Spottedleaf -Date: Sun, 11 Apr 2021 02:58:48 -0700 -Subject: [PATCH] Don't read neighbour chunk data off disk when converting - chunks - -Lighting is purged on update anyways, so let's not add more -into the conversion process - -diff --git a/src/main/java/net/minecraft/world/level/chunk/storage/ChunkStorage.java b/src/main/java/net/minecraft/world/level/chunk/storage/ChunkStorage.java -index 9aa9ab894080a5819fc45698771afd034906d36a..f0f5e9bb5ac65250f0a151f9f90b58468335a8c2 100644 ---- a/src/main/java/net/minecraft/world/level/chunk/storage/ChunkStorage.java -+++ b/src/main/java/net/minecraft/world/level/chunk/storage/ChunkStorage.java -@@ -47,6 +47,7 @@ public class ChunkStorage implements AutoCloseable { - - // CraftBukkit start - private boolean check(ServerChunkCache cps, int x, int z) { -+ if (true) return true; // Paper - Perf: this isn't even needed anymore, light is purged updating to 1.14+, why are we holding up the conversion process reading chunk data off disk - return true, we need to set light populated to true so the converter recognizes the chunk as being "full" - ChunkPos pos = new ChunkPos(x, z); - if (cps != null) { - com.google.common.base.Preconditions.checkState(org.bukkit.Bukkit.isPrimaryThread(), "primary thread"); diff --git a/patches/server/0603-Optimise-BlockSoil-nearby-water-lookup.patch b/patches/server/0603-Optimise-BlockSoil-nearby-water-lookup.patch new file mode 100644 index 000000000000..4a5d6c5b9dca --- /dev/null +++ b/patches/server/0603-Optimise-BlockSoil-nearby-water-lookup.patch @@ -0,0 +1,52 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Spottedleaf +Date: Thu, 10 Jun 2021 14:36:00 -0700 +Subject: [PATCH] Optimise BlockSoil nearby water lookup + +Apparently the abstract block iteration was taking about +75% of the method call. + +diff --git a/src/main/java/net/minecraft/world/level/block/FarmBlock.java b/src/main/java/net/minecraft/world/level/block/FarmBlock.java +index a87f8345aa5520a867a8dd769b43526b20b8c16a..c3dba0c2c94f3804338f86621dc42405e380a6b3 100644 +--- a/src/main/java/net/minecraft/world/level/block/FarmBlock.java ++++ b/src/main/java/net/minecraft/world/level/block/FarmBlock.java +@@ -154,19 +154,28 @@ public class FarmBlock extends Block { + } + + private static boolean isNearWater(LevelReader world, BlockPos pos) { +- Iterator iterator = BlockPos.betweenClosed(pos.offset(-4, 0, -4), pos.offset(4, 1, 4)).iterator(); +- +- BlockPos blockposition1; +- +- do { +- if (!iterator.hasNext()) { +- return false; ++ // Paper start - Perf: remove abstract block iteration ++ int xOff = pos.getX(); ++ int yOff = pos.getY(); ++ int zOff = pos.getZ(); ++ ++ for (int dz = -4; dz <= 4; ++dz) { ++ int z = dz + zOff; ++ for (int dx = -4; dx <= 4; ++dx) { ++ int x = xOff + dx; ++ for (int dy = 0; dy <= 1; ++dy) { ++ int y = dy + yOff; ++ net.minecraft.world.level.chunk.LevelChunk chunk = (net.minecraft.world.level.chunk.LevelChunk)world.getChunk(x >> 4, z >> 4); ++ net.minecraft.world.level.material.FluidState fluid = chunk.getBlockStateFinal(x, y, z).getFluidState(); ++ if (fluid.is(FluidTags.WATER)) { ++ return true; ++ } ++ } + } ++ } + +- blockposition1 = (BlockPos) iterator.next(); +- } while (!world.getFluidState(blockposition1).is(FluidTags.WATER)); +- +- return true; ++ return false; ++ // Paper end - Perf: remove abstract block iteration + } + + @Override diff --git a/patches/server/0604-Don-t-lookup-fluid-state-when-raytracing-skip-air-bl.patch b/patches/server/0604-Don-t-lookup-fluid-state-when-raytracing-skip-air-bl.patch deleted file mode 100644 index 0ab3e48f486d..000000000000 --- a/patches/server/0604-Don-t-lookup-fluid-state-when-raytracing-skip-air-bl.patch +++ /dev/null @@ -1,26 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Spottedleaf -Date: Fri, 28 Aug 2020 12:33:47 -0700 -Subject: [PATCH] Don't lookup fluid state when raytracing, skip air blocks - -Just use the iblockdata already retrieved, removes a getType call. - -Also save approx. 5% for the raytrace call, as most (expensive) -raytracing tends to go through air and returning early is an -easy win. The remaining problems with this function -are mostly with the block getting itself. - -diff --git a/src/main/java/net/minecraft/world/level/BlockGetter.java b/src/main/java/net/minecraft/world/level/BlockGetter.java -index c3760e0c8ac0b3ea200f4e1c237e250137a78caf..c978f3b2d42f512e982f289e76c2422e41b7eec6 100644 ---- a/src/main/java/net/minecraft/world/level/BlockGetter.java -+++ b/src/main/java/net/minecraft/world/level/BlockGetter.java -@@ -79,7 +79,8 @@ public interface BlockGetter extends LevelHeightAccessor { - return BlockHitResult.miss(raytrace1.getTo(), Direction.getNearest(vec3d.x, vec3d.y, vec3d.z), BlockPos.containing(raytrace1.getTo())); - } - // Paper end - Prevent raytrace from loading chunks -- FluidState fluid = this.getFluidState(blockposition); -+ if (iblockdata.isAir()) return null; // Paper - Perf: optimise air cases -+ FluidState fluid = iblockdata.getFluidState(); // Paper - Perf: don't need to go to world state again - Vec3 vec3d = raytrace1.getFrom(); - Vec3 vec3d1 = raytrace1.getTo(); - VoxelShape voxelshape = raytrace1.getBlockShape(iblockdata, this, blockposition); diff --git a/patches/server/0604-Fix-merchant-inventory-not-closing-on-entity-removal.patch b/patches/server/0604-Fix-merchant-inventory-not-closing-on-entity-removal.patch new file mode 100644 index 000000000000..87e006c31f6e --- /dev/null +++ b/patches/server/0604-Fix-merchant-inventory-not-closing-on-entity-removal.patch @@ -0,0 +1,22 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Jake Potrebic +Date: Thu, 2 Sep 2021 00:24:06 -0700 +Subject: [PATCH] Fix merchant inventory not closing on entity removal + + +diff --git a/src/main/java/net/minecraft/server/level/ServerLevel.java b/src/main/java/net/minecraft/server/level/ServerLevel.java +index ecb4dc0642685d67621c82bb24fb0e939c805ce4..3b3024fcf39266cc6ae61fb77dbffb391dc96c92 100644 +--- a/src/main/java/net/minecraft/server/level/ServerLevel.java ++++ b/src/main/java/net/minecraft/server/level/ServerLevel.java +@@ -2298,6 +2298,11 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe + // Spigot end + // Spigot Start + if (entity.getBukkitEntity() instanceof org.bukkit.inventory.InventoryHolder && (!(entity instanceof ServerPlayer) || entity.getRemovalReason() != Entity.RemovalReason.KILLED)) { // SPIGOT-6876: closeInventory clears death message ++ // Paper start - Fix merchant inventory not closing on entity removal ++ if (entity.getBukkitEntity() instanceof org.bukkit.inventory.Merchant merchant && merchant.getTrader() != null) { ++ merchant.getTrader().closeInventory(org.bukkit.event.inventory.InventoryCloseEvent.Reason.UNLOADED); ++ } ++ // Paper end - Fix merchant inventory not closing on entity removal + for (org.bukkit.entity.HumanEntity h : Lists.newArrayList(((org.bukkit.inventory.InventoryHolder) entity.getBukkitEntity()).getInventory().getViewers())) { + h.closeInventory(org.bukkit.event.inventory.InventoryCloseEvent.Reason.UNLOADED); // Paper - Inventory close reason + } diff --git a/patches/server/0610-Check-requirement-before-suggesting-root-nodes.patch b/patches/server/0605-Check-requirement-before-suggesting-root-nodes.patch similarity index 100% rename from patches/server/0610-Check-requirement-before-suggesting-root-nodes.patch rename to patches/server/0605-Check-requirement-before-suggesting-root-nodes.patch diff --git a/patches/server/0605-Time-scoreboard-search.patch b/patches/server/0605-Time-scoreboard-search.patch deleted file mode 100644 index 8d1508475872..000000000000 --- a/patches/server/0605-Time-scoreboard-search.patch +++ /dev/null @@ -1,43 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Spottedleaf -Date: Tue, 21 Apr 2020 01:53:22 -0700 -Subject: [PATCH] Time scoreboard search - -Plugins leaking scoreboards will make this very expensive, -let server owners debug it easily - -diff --git a/src/main/java/co/aikar/timings/MinecraftTimings.java b/src/main/java/co/aikar/timings/MinecraftTimings.java -index e2764186bd6b838ed5cd86c15597a08d079ef984..6b3cde6d4d1e63bec01f502f2027ee9fddac08aa 100644 ---- a/src/main/java/co/aikar/timings/MinecraftTimings.java -+++ b/src/main/java/co/aikar/timings/MinecraftTimings.java -@@ -46,6 +46,7 @@ public final class MinecraftTimings { - - public static final Timing antiXrayUpdateTimer = Timings.ofSafe("anti-xray - update"); - public static final Timing antiXrayObfuscateTimer = Timings.ofSafe("anti-xray - obfuscate"); -+ public static final Timing scoreboardScoreSearch = Timings.ofSafe("Scoreboard score search"); // Paper - add timings for scoreboard search - - private static final Map, String> taskNameCache = new MapMaker().weakKeys().makeMap(); - -diff --git a/src/main/java/org/bukkit/craftbukkit/scoreboard/CraftScoreboardManager.java b/src/main/java/org/bukkit/craftbukkit/scoreboard/CraftScoreboardManager.java -index c7ca6210d6ae37fe95068c9baa5fb654f95307e0..cad42a0f3c016bf65181e50d139ae4e2fb9158a5 100644 ---- a/src/main/java/org/bukkit/craftbukkit/scoreboard/CraftScoreboardManager.java -+++ b/src/main/java/org/bukkit/craftbukkit/scoreboard/CraftScoreboardManager.java -@@ -113,9 +113,18 @@ public final class CraftScoreboardManager implements ScoreboardManager { - - // CraftBukkit method - public void forAllObjectives(ObjectiveCriteria criteria, ScoreHolder holder, Consumer consumer) { -+ // Paper start - add timings for scoreboard search -+ // plugins leaking scoreboards will make this very expensive, let server owners debug it easily -+ co.aikar.timings.MinecraftTimings.scoreboardScoreSearch.startTimingIfSync(); -+ try { -+ // Paper end - add timings for scoreboard search - for (CraftScoreboard scoreboard : this.scoreboards) { - Scoreboard board = scoreboard.board; - board.forAllObjectives(criteria, holder, (score) -> consumer.accept(score)); - } -+ } finally { // Paper start - add timings for scoreboard search -+ co.aikar.timings.MinecraftTimings.scoreboardScoreSearch.stopTimingIfSync(); -+ } -+ // Paper end - add timings for scoreboard search - } - } diff --git a/patches/server/0606-Don-t-respond-to-ServerboundCommandSuggestionPacket-.patch b/patches/server/0606-Don-t-respond-to-ServerboundCommandSuggestionPacket-.patch new file mode 100644 index 000000000000..db870d424045 --- /dev/null +++ b/patches/server/0606-Don-t-respond-to-ServerboundCommandSuggestionPacket-.patch @@ -0,0 +1,23 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: stonar96 +Date: Sun, 12 Sep 2021 00:14:21 +0200 +Subject: [PATCH] Don't respond to ServerboundCommandSuggestionPacket when + tab-complete is disabled + + +diff --git a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java +index a4abcbc69ccd023a936d02d359ba4c08198ed31e..7f1e0c6801a1d8b0857fba9826fc56e30bd41497 100644 +--- a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java ++++ b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java +@@ -760,6 +760,11 @@ public class ServerGamePacketListenerImpl extends ServerCommonPacketListenerImpl + return; + } + // CraftBukkit end ++ // Paper start - Don't suggest if tab-complete is disabled ++ if (org.spigotmc.SpigotConfig.tabComplete < 0) { ++ return; ++ } ++ // Paper end - Don't suggest if tab-complete is disabled + // Paper start - AsyncTabCompleteEvent + TAB_COMPLETE_EXECUTOR.execute(() -> this.handleCustomCommandSuggestions0(packet)); + } diff --git a/patches/server/0606-Oprimise-map-impl-for-tracked-players.patch b/patches/server/0606-Oprimise-map-impl-for-tracked-players.patch deleted file mode 100644 index dcfacfefe7fe..000000000000 --- a/patches/server/0606-Oprimise-map-impl-for-tracked-players.patch +++ /dev/null @@ -1,21 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Spottedleaf -Date: Fri, 19 Feb 2021 22:51:52 -0800 -Subject: [PATCH] Oprimise map impl for tracked players - -Reference2BooleanOpenHashMap is going to have -better lookups than HashMap. - -diff --git a/src/main/java/net/minecraft/server/level/ChunkMap.java b/src/main/java/net/minecraft/server/level/ChunkMap.java -index d91279f3bd009e1542e73354aadd6a16c80965e2..071e9ef3680c5dc492c6142ccd05f6788ebc3035 100644 ---- a/src/main/java/net/minecraft/server/level/ChunkMap.java -+++ b/src/main/java/net/minecraft/server/level/ChunkMap.java -@@ -1461,7 +1461,7 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider - final Entity entity; - private final int range; - SectionPos lastSectionPos; -- public final Set seenBy = Sets.newIdentityHashSet(); -+ public final Set seenBy = new it.unimi.dsi.fastutil.objects.ReferenceOpenHashSet<>(); // Paper - Perf: optimise map impl - - public TrackedEntity(final Entity entity, final int i, final int j, final boolean flag) { - this.serverEntity = new ServerEntity(ChunkMap.this.level, entity, j, flag, this::broadcast, this.seenBy); // CraftBukkit diff --git a/patches/server/0612-Add-packet-limiter-config.patch b/patches/server/0607-Add-packet-limiter-config.patch similarity index 100% rename from patches/server/0612-Add-packet-limiter-config.patch rename to patches/server/0607-Add-packet-limiter-config.patch diff --git a/patches/server/0613-Fix-setPatternColor-on-tropical-fish-bucket-meta.patch b/patches/server/0608-Fix-setPatternColor-on-tropical-fish-bucket-meta.patch similarity index 100% rename from patches/server/0613-Fix-setPatternColor-on-tropical-fish-bucket-meta.patch rename to patches/server/0608-Fix-setPatternColor-on-tropical-fish-bucket-meta.patch diff --git a/patches/server/0608-Optimise-BlockSoil-nearby-water-lookup.patch b/patches/server/0608-Optimise-BlockSoil-nearby-water-lookup.patch deleted file mode 100644 index 81b199a2436b..000000000000 --- a/patches/server/0608-Optimise-BlockSoil-nearby-water-lookup.patch +++ /dev/null @@ -1,52 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Spottedleaf -Date: Thu, 10 Jun 2021 14:36:00 -0700 -Subject: [PATCH] Optimise BlockSoil nearby water lookup - -Apparently the abstract block iteration was taking about -75% of the method call. - -diff --git a/src/main/java/net/minecraft/world/level/block/FarmBlock.java b/src/main/java/net/minecraft/world/level/block/FarmBlock.java -index 529f9f57249bd1ffa2698da76ffa9d4a284088db..d59e33e7326489c6d55d316d0130f22235f4c63c 100644 ---- a/src/main/java/net/minecraft/world/level/block/FarmBlock.java -+++ b/src/main/java/net/minecraft/world/level/block/FarmBlock.java -@@ -152,19 +152,28 @@ public class FarmBlock extends Block { - } - - private static boolean isNearWater(LevelReader world, BlockPos pos) { -- Iterator iterator = BlockPos.betweenClosed(pos.offset(-4, 0, -4), pos.offset(4, 1, 4)).iterator(); -- -- BlockPos blockposition1; -- -- do { -- if (!iterator.hasNext()) { -- return false; -+ // Paper start - Perf: remove abstract block iteration -+ int xOff = pos.getX(); -+ int yOff = pos.getY(); -+ int zOff = pos.getZ(); -+ -+ for (int dz = -4; dz <= 4; ++dz) { -+ int z = dz + zOff; -+ for (int dx = -4; dx <= 4; ++dx) { -+ int x = xOff + dx; -+ for (int dy = 0; dy <= 1; ++dy) { -+ int y = dy + yOff; -+ net.minecraft.world.level.chunk.LevelChunk chunk = (net.minecraft.world.level.chunk.LevelChunk)world.getChunk(x >> 4, z >> 4); -+ net.minecraft.world.level.material.FluidState fluid = chunk.getBlockStateFinal(x, y, z).getFluidState(); -+ if (fluid.is(FluidTags.WATER)) { -+ return true; -+ } -+ } - } -+ } - -- blockposition1 = (BlockPos) iterator.next(); -- } while (!world.getFluidState(blockposition1).is(FluidTags.WATER)); -- -- return true; -+ return false; -+ // Paper end - Perf: remove abstract block iteration - } - - @Override diff --git a/patches/server/0609-Ensure-valid-vehicle-status.patch b/patches/server/0609-Ensure-valid-vehicle-status.patch new file mode 100644 index 000000000000..c4359b205488 --- /dev/null +++ b/patches/server/0609-Ensure-valid-vehicle-status.patch @@ -0,0 +1,19 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Nassim Jahnke +Date: Tue, 28 Sep 2021 09:47:47 +0200 +Subject: [PATCH] Ensure valid vehicle status + + +diff --git a/src/main/java/net/minecraft/server/level/ServerPlayer.java b/src/main/java/net/minecraft/server/level/ServerPlayer.java +index d2fbbdbb451d6c54d847b4ba125397ad41c4f7b4..f72ab5e4e743cb0758ebca28e81f97c143c91b42 100644 +--- a/src/main/java/net/minecraft/server/level/ServerPlayer.java ++++ b/src/main/java/net/minecraft/server/level/ServerPlayer.java +@@ -698,7 +698,7 @@ public class ServerPlayer extends net.minecraft.world.entity.player.Player { + } + } + +- if (persistVehicle && entity1 != null && entity != this && entity.hasExactlyOnePlayerPassenger()) { ++ if (persistVehicle && entity1 != null && entity != this && entity.hasExactlyOnePlayerPassenger() && !entity.isRemoved()) { // Paper - Ensure valid vehicle status + // CraftBukkit end + CompoundTag nbttagcompound1 = new CompoundTag(); + CompoundTag nbttagcompound2 = new CompoundTag(); diff --git a/patches/server/0609-Fix-merchant-inventory-not-closing-on-entity-removal.patch b/patches/server/0609-Fix-merchant-inventory-not-closing-on-entity-removal.patch deleted file mode 100644 index 7a31528cdd3f..000000000000 --- a/patches/server/0609-Fix-merchant-inventory-not-closing-on-entity-removal.patch +++ /dev/null @@ -1,22 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Jake Potrebic -Date: Thu, 2 Sep 2021 00:24:06 -0700 -Subject: [PATCH] Fix merchant inventory not closing on entity removal - - -diff --git a/src/main/java/net/minecraft/server/level/ServerLevel.java b/src/main/java/net/minecraft/server/level/ServerLevel.java -index dba04a8de93b9afe8b51e473de9e1d485c93987e..a473993ea48afd05a884e3ffcbdd15abe74580d2 100644 ---- a/src/main/java/net/minecraft/server/level/ServerLevel.java -+++ b/src/main/java/net/minecraft/server/level/ServerLevel.java -@@ -2247,6 +2247,11 @@ public class ServerLevel extends Level implements WorldGenLevel { - // Spigot end - // Spigot Start - if (entity.getBukkitEntity() instanceof org.bukkit.inventory.InventoryHolder && (!(entity instanceof ServerPlayer) || entity.getRemovalReason() != Entity.RemovalReason.KILLED)) { // SPIGOT-6876: closeInventory clears death message -+ // Paper start - Fix merchant inventory not closing on entity removal -+ if (entity.getBukkitEntity() instanceof org.bukkit.inventory.Merchant merchant && merchant.getTrader() != null) { -+ merchant.getTrader().closeInventory(org.bukkit.event.inventory.InventoryCloseEvent.Reason.UNLOADED); -+ } -+ // Paper end - Fix merchant inventory not closing on entity removal - for (org.bukkit.entity.HumanEntity h : Lists.newArrayList(((org.bukkit.inventory.InventoryHolder) entity.getBukkitEntity()).getInventory().getViewers())) { - h.closeInventory(org.bukkit.event.inventory.InventoryCloseEvent.Reason.UNLOADED); // Paper - Inventory close reason - } diff --git a/patches/server/0610-Prevent-softlocked-end-exit-portal-generation.patch b/patches/server/0610-Prevent-softlocked-end-exit-portal-generation.patch new file mode 100644 index 000000000000..4c54f27f94c0 --- /dev/null +++ b/patches/server/0610-Prevent-softlocked-end-exit-portal-generation.patch @@ -0,0 +1,22 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Noah van der Aa +Date: Mon, 30 Aug 2021 15:22:18 +0200 +Subject: [PATCH] Prevent softlocked end exit portal generation + + +diff --git a/src/main/java/net/minecraft/world/level/dimension/end/EndDragonFight.java b/src/main/java/net/minecraft/world/level/dimension/end/EndDragonFight.java +index 3d1a49d865e17a61ff74c6fe0efbd908447ee730..bc6426c04ac1fd19949d587d2b7061895db0893b 100644 +--- a/src/main/java/net/minecraft/world/level/dimension/end/EndDragonFight.java ++++ b/src/main/java/net/minecraft/world/level/dimension/end/EndDragonFight.java +@@ -469,6 +469,11 @@ public class EndDragonFight { + } + } + ++ // Paper start - Prevent "softlocked" exit portal generation ++ if (this.portalLocation.getY() <= this.level.getMinY()) { ++ this.portalLocation = this.portalLocation.atY(this.level.getMinY() + 1); ++ } ++ // Paper end - Prevent "softlocked" exit portal generation + if (worldgenendtrophy.place(FeatureConfiguration.NONE, this.level, this.level.getChunkSource().getGenerator(), RandomSource.create(), this.portalLocation)) { + int i = Mth.positiveCeilDiv(4, 16); + diff --git a/patches/server/0611-Don-t-respond-to-ServerboundCommandSuggestionPacket-.patch b/patches/server/0611-Don-t-respond-to-ServerboundCommandSuggestionPacket-.patch deleted file mode 100644 index 39f096a88d74..000000000000 --- a/patches/server/0611-Don-t-respond-to-ServerboundCommandSuggestionPacket-.patch +++ /dev/null @@ -1,23 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: stonar96 -Date: Sun, 12 Sep 2021 00:14:21 +0200 -Subject: [PATCH] Don't respond to ServerboundCommandSuggestionPacket when - tab-complete is disabled - - -diff --git a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java -index 926c0a5f4eaaa8fb650905216d2303fb476d3fa5..1e806fd16ca5f90bdb4f71dea008d7936eb82562 100644 ---- a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java -+++ b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java -@@ -748,6 +748,11 @@ public class ServerGamePacketListenerImpl extends ServerCommonPacketListenerImpl - return; - } - // CraftBukkit end -+ // Paper start - Don't suggest if tab-complete is disabled -+ if (org.spigotmc.SpigotConfig.tabComplete < 0) { -+ return; -+ } -+ // Paper end - Don't suggest if tab-complete is disabled - // Paper start - AsyncTabCompleteEvent - TAB_COMPLETE_EXECUTOR.execute(() -> this.handleCustomCommandSuggestions0(packet)); - } diff --git a/patches/server/0616-Fix-CocaoDecorator-causing-a-crash-when-trying-to-ge.patch b/patches/server/0611-Fix-CocaoDecorator-causing-a-crash-when-trying-to-ge.patch similarity index 100% rename from patches/server/0616-Fix-CocaoDecorator-causing-a-crash-when-trying-to-ge.patch rename to patches/server/0611-Fix-CocaoDecorator-causing-a-crash-when-trying-to-ge.patch diff --git a/patches/server/0617-Don-t-log-debug-logging-being-disabled.patch b/patches/server/0612-Don-t-log-debug-logging-being-disabled.patch similarity index 100% rename from patches/server/0617-Don-t-log-debug-logging-being-disabled.patch rename to patches/server/0612-Don-t-log-debug-logging-being-disabled.patch diff --git a/patches/server/0618-fix-various-menus-with-empty-level-accesses.patch b/patches/server/0613-fix-various-menus-with-empty-level-accesses.patch similarity index 100% rename from patches/server/0618-fix-various-menus-with-empty-level-accesses.patch rename to patches/server/0613-fix-various-menus-with-empty-level-accesses.patch diff --git a/patches/server/0614-Ensure-valid-vehicle-status.patch b/patches/server/0614-Ensure-valid-vehicle-status.patch deleted file mode 100644 index f8ce44340df6..000000000000 --- a/patches/server/0614-Ensure-valid-vehicle-status.patch +++ /dev/null @@ -1,19 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Nassim Jahnke -Date: Tue, 28 Sep 2021 09:47:47 +0200 -Subject: [PATCH] Ensure valid vehicle status - - -diff --git a/src/main/java/net/minecraft/server/level/ServerPlayer.java b/src/main/java/net/minecraft/server/level/ServerPlayer.java -index 6648af4d8d795637fae444dc85803b399592fd7d..25ca370c0b19eca529fc2c980c50e899013f0897 100644 ---- a/src/main/java/net/minecraft/server/level/ServerPlayer.java -+++ b/src/main/java/net/minecraft/server/level/ServerPlayer.java -@@ -574,7 +574,7 @@ public class ServerPlayer extends net.minecraft.world.entity.player.Player { - } - } - -- if (persistVehicle && entity1 != null && entity != this && entity.hasExactlyOnePlayerPassenger()) { -+ if (persistVehicle && entity1 != null && entity != this && entity.hasExactlyOnePlayerPassenger() && !entity.isRemoved()) { // Paper - Ensure valid vehicle status - // CraftBukkit end - CompoundTag nbttagcompound2 = new CompoundTag(); - CompoundTag nbttagcompound3 = new CompoundTag(); diff --git a/patches/server/0614-Preserve-overstacked-loot.patch b/patches/server/0614-Preserve-overstacked-loot.patch new file mode 100644 index 000000000000..db50d2fd8a3e --- /dev/null +++ b/patches/server/0614-Preserve-overstacked-loot.patch @@ -0,0 +1,27 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: lexikiq +Date: Mon, 21 Jun 2021 23:21:58 -0400 +Subject: [PATCH] Preserve overstacked loot + +Preserves overstacked items in loot tables, such as shulker box drops, to prevent the items +from being deleted (as they'd overflow past the bounds of the container)-- or worse, causing +chunk bans via the large amount of NBT created by unstacking the items. + +Fixes GH-5140 and GH-4748. + +diff --git a/src/main/java/net/minecraft/world/level/storage/loot/LootTable.java b/src/main/java/net/minecraft/world/level/storage/loot/LootTable.java +index 4d4d413b8527e1a109276928611b8c857ad6f6aa..c2bded5094097f5615a2ddb0718942486ede93b5 100644 +--- a/src/main/java/net/minecraft/world/level/storage/loot/LootTable.java ++++ b/src/main/java/net/minecraft/world/level/storage/loot/LootTable.java +@@ -72,9 +72,10 @@ public class LootTable { + } + + public static Consumer createStackSplitter(ServerLevel world, Consumer consumer) { ++ boolean skipSplitter = world != null && !world.paperConfig().fixes.splitOverstackedLoot; // Paper - preserve overstacked items + return (itemstack) -> { + if (itemstack.isItemEnabled(world.enabledFeatures())) { +- if (itemstack.getCount() < itemstack.getMaxStackSize()) { ++ if (skipSplitter || itemstack.getCount() < itemstack.getMaxStackSize()) { // Paper - preserve overstacked items + consumer.accept(itemstack); + } else { + int i = itemstack.getCount(); diff --git a/patches/server/0615-Prevent-softlocked-end-exit-portal-generation.patch b/patches/server/0615-Prevent-softlocked-end-exit-portal-generation.patch deleted file mode 100644 index 9e8d9491cb87..000000000000 --- a/patches/server/0615-Prevent-softlocked-end-exit-portal-generation.patch +++ /dev/null @@ -1,22 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Noah van der Aa -Date: Mon, 30 Aug 2021 15:22:18 +0200 -Subject: [PATCH] Prevent softlocked end exit portal generation - - -diff --git a/src/main/java/net/minecraft/world/level/dimension/end/EndDragonFight.java b/src/main/java/net/minecraft/world/level/dimension/end/EndDragonFight.java -index 460744ec3a1abe9a2d9d16c2ec521c52c7f8db95..98d1c097fdbbc8080624f365634e0812a5eea5ac 100644 ---- a/src/main/java/net/minecraft/world/level/dimension/end/EndDragonFight.java -+++ b/src/main/java/net/minecraft/world/level/dimension/end/EndDragonFight.java -@@ -468,6 +468,11 @@ public class EndDragonFight { - } - } - -+ // Paper start - Prevent "softlocked" exit portal generation -+ if (this.portalLocation.getY() <= this.level.getMinBuildHeight()) { -+ this.portalLocation = this.portalLocation.atY(this.level.getMinBuildHeight() + 1); -+ } -+ // Paper end - Prevent "softlocked" exit portal generation - if (worldgenendtrophy.place(FeatureConfiguration.NONE, this.level, this.level.getChunkSource().getGenerator(), RandomSource.create(), this.portalLocation)) { - int i = Mth.positiveCeilDiv(4, 16); - diff --git a/patches/server/0615-Update-head-rotation-in-missing-places.patch b/patches/server/0615-Update-head-rotation-in-missing-places.patch new file mode 100644 index 000000000000..51b3e36d06e0 --- /dev/null +++ b/patches/server/0615-Update-head-rotation-in-missing-places.patch @@ -0,0 +1,29 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Owen1212055 <23108066+Owen1212055@users.noreply.github.com> +Date: Mon, 21 Jun 2021 21:55:23 -0400 +Subject: [PATCH] Update head rotation in missing places + +In certain areas the player's head rotation could be desynced when teleported/moved. +This is because bukkit uses a separate head rotation field for yaw. +This issue only applies to players. + +diff --git a/src/main/java/net/minecraft/world/entity/Entity.java b/src/main/java/net/minecraft/world/entity/Entity.java +index 900765b0129e8bf485aca93af6f523c9948e288b..3903bbc53d541e66d9362eea27029ed320a74772 100644 +--- a/src/main/java/net/minecraft/world/entity/Entity.java ++++ b/src/main/java/net/minecraft/world/entity/Entity.java +@@ -1914,6 +1914,7 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess + this.setXRot(Mth.clamp(pitch, -90.0F, 90.0F) % 360.0F); + this.yRotO = this.getYRot(); + this.xRotO = this.getXRot(); ++ this.setYHeadRot(yaw); // Paper - Update head rotation + } + + public void absMoveTo(double x, double y, double z) { +@@ -1956,6 +1957,7 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess + this.setXRot(pitch); + this.setOldPosAndRot(); + this.reapplyPosition(); ++ this.setYHeadRot(yaw); // Paper - Update head rotation + } + + public final void setOldPosAndRot() { diff --git a/patches/server/0616-prevent-unintended-light-block-manipulation.patch b/patches/server/0616-prevent-unintended-light-block-manipulation.patch new file mode 100644 index 000000000000..b8ca20506417 --- /dev/null +++ b/patches/server/0616-prevent-unintended-light-block-manipulation.patch @@ -0,0 +1,25 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Jake Potrebic +Date: Mon, 13 Sep 2021 18:55:45 -0700 +Subject: [PATCH] prevent unintended light block manipulation + + +diff --git a/src/main/java/net/minecraft/world/level/block/LightBlock.java b/src/main/java/net/minecraft/world/level/block/LightBlock.java +index 6c3ca57a29d3c5ad1add1cf2f707b930dfc422ea..fec6bf38f080039436ba80d5528857ba4787bf4e 100644 +--- a/src/main/java/net/minecraft/world/level/block/LightBlock.java ++++ b/src/main/java/net/minecraft/world/level/block/LightBlock.java +@@ -50,6 +50,14 @@ public class LightBlock extends Block implements SimpleWaterloggedBlock { + builder.add(LEVEL, WATERLOGGED); + } + ++ // Paper start - prevent unintended light block manipulation ++ @Override ++ protected InteractionResult useItemOn(ItemStack stack, BlockState state, Level world, BlockPos pos, Player player, net.minecraft.world.InteractionHand hand, BlockHitResult hit) { ++ if (player.getItemInHand(hand).getItem() != Items.LIGHT || (world instanceof final net.minecraft.server.level.ServerLevel serverLevel && !player.mayInteract(serverLevel, pos)) || !player.mayUseItemAt(pos, hit.getDirection(), player.getItemInHand(hand))) { return net.minecraft.world.InteractionResult.PASS; } // Paper - Prevent unintended light block manipulation ++ return super.useItemOn(stack, state, world, pos, player, hand, hit); ++ } ++ // Paper end - prevent unintended light block manipulation ++ + @Override + protected InteractionResult useWithoutItem(BlockState state, Level world, BlockPos pos, Player player, BlockHitResult hit) { + if (!world.isClientSide && player.canUseGameMasterBlocks()) { diff --git a/patches/server/0622-Fix-CraftCriteria-defaults-map.patch b/patches/server/0617-Fix-CraftCriteria-defaults-map.patch similarity index 100% rename from patches/server/0622-Fix-CraftCriteria-defaults-map.patch rename to patches/server/0617-Fix-CraftCriteria-defaults-map.patch diff --git a/patches/server/0618-Fix-upstreams-block-state-factories.patch b/patches/server/0618-Fix-upstreams-block-state-factories.patch new file mode 100644 index 000000000000..93376fc4b13f --- /dev/null +++ b/patches/server/0618-Fix-upstreams-block-state-factories.patch @@ -0,0 +1,491 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Jake Potrebic +Date: Wed, 6 Oct 2021 20:50:48 -0700 +Subject: [PATCH] Fix upstreams block state factories + +Sometimes, blocks are changed and then logic is called before the associated +tile entity is removed. When this happens, the factories were relying on the +block at the position, not the tile entity. This change prioritizes using the +tile entity type to determine the block state factory and falls back on +the material type of the block at that location. + +== AT == +public net.minecraft.world.level.block.entity.BlockEntityType validBlocks + +diff --git a/src/main/java/net/minecraft/world/level/block/entity/BlockEntity.java b/src/main/java/net/minecraft/world/level/block/entity/BlockEntity.java +index 50413d317ce0282752c57535637f87d529f4c09f..09b85c4caa4ebaae1e8c2910b090c40a039a1be7 100644 +--- a/src/main/java/net/minecraft/world/level/block/entity/BlockEntity.java ++++ b/src/main/java/net/minecraft/world/level/block/entity/BlockEntity.java +@@ -383,7 +383,7 @@ public abstract class BlockEntity { + // Paper end + if (this.level == null) return null; + org.bukkit.block.Block block = this.level.getWorld().getBlockAt(this.worldPosition.getX(), this.worldPosition.getY(), this.worldPosition.getZ()); +- if (block.getType() == org.bukkit.Material.AIR) return null; ++ // if (block.getType() == org.bukkit.Material.AIR) return null; // Paper - actually get the tile entity if it still exists + org.bukkit.block.BlockState state = block.getState(useSnapshot); // Paper + if (state instanceof InventoryHolder) return (InventoryHolder) state; + return null; +diff --git a/src/main/java/org/bukkit/craftbukkit/block/CraftBlockEntityState.java b/src/main/java/org/bukkit/craftbukkit/block/CraftBlockEntityState.java +index 80bd6e8a6dadb74356a9fa9aa394efbd31c49c37..fe7e3e0628783d8d1be9635b689da8a9cb46c5d7 100644 +--- a/src/main/java/org/bukkit/craftbukkit/block/CraftBlockEntityState.java ++++ b/src/main/java/org/bukkit/craftbukkit/block/CraftBlockEntityState.java +@@ -20,7 +20,7 @@ import org.bukkit.persistence.PersistentDataContainer; + import org.jetbrains.annotations.NotNull; + import org.jetbrains.annotations.Nullable; + +-public class CraftBlockEntityState extends CraftBlockState implements TileState { ++public abstract class CraftBlockEntityState extends CraftBlockState implements TileState { // Paper - revert upstream's revert of the block state changes + + private final T tileEntity; + private final T snapshot; +@@ -196,14 +196,10 @@ public class CraftBlockEntityState extends CraftBlockStat + } + + @Override +- public CraftBlockEntityState copy() { +- return new CraftBlockEntityState<>(this, null); +- } ++ public abstract CraftBlockEntityState copy(); // Paper - make abstract + + @Override +- public CraftBlockEntityState copy(Location location) { +- return new CraftBlockEntityState<>(this, location); +- } ++ public abstract CraftBlockEntityState copy(Location location); // Paper - make abstract + + // Paper start + @Override +diff --git a/src/main/java/org/bukkit/craftbukkit/block/CraftBlockStates.java b/src/main/java/org/bukkit/craftbukkit/block/CraftBlockStates.java +index 1a8dcde39a252a45046866349b848d79e1b13260..56453454cbd4b9e9270fc833f8ab38d5fa7a3763 100644 +--- a/src/main/java/org/bukkit/craftbukkit/block/CraftBlockStates.java ++++ b/src/main/java/org/bukkit/craftbukkit/block/CraftBlockStates.java +@@ -22,6 +22,7 @@ import net.minecraft.world.level.block.entity.BeehiveBlockEntity; + import net.minecraft.world.level.block.entity.BellBlockEntity; + import net.minecraft.world.level.block.entity.BlastFurnaceBlockEntity; + import net.minecraft.world.level.block.entity.BlockEntity; ++import net.minecraft.world.level.block.entity.BlockEntityType; // Paper + import net.minecraft.world.level.block.entity.BrewingStandBlockEntity; + import net.minecraft.world.level.block.entity.BrushableBlockEntity; + import net.minecraft.world.level.block.entity.CalibratedSculkSensorBlockEntity; +@@ -88,9 +89,9 @@ public final class CraftBlockStates { + private static class BlockEntityStateFactory> extends BlockStateFactory { + + private final BiFunction blockStateConstructor; +- private final BiFunction tileEntityConstructor; ++ private final BlockEntityType tileEntityConstructor; // Paper + +- protected BlockEntityStateFactory(Class blockStateType, BiFunction blockStateConstructor, BiFunction tileEntityConstructor) { ++ protected BlockEntityStateFactory(Class blockStateType, BiFunction blockStateConstructor, BlockEntityType tileEntityConstructor) { // Paper + super(blockStateType); + this.blockStateConstructor = blockStateConstructor; + this.tileEntityConstructor = tileEntityConstructor; +@@ -107,7 +108,7 @@ public final class CraftBlockStates { + } + + private T createTileEntity(BlockPos blockPosition, net.minecraft.world.level.block.state.BlockState blockData) { +- return this.tileEntityConstructor.apply(blockPosition, blockData); ++ return this.tileEntityConstructor.create(blockPosition, blockData); // Paper + } + + private B createBlockState(World world, T tileEntity) { +@@ -119,233 +120,66 @@ public final class CraftBlockStates { + private static final BlockStateFactory DEFAULT_FACTORY = new BlockStateFactory(CraftBlockState.class) { + @Override + public CraftBlockState createBlockState(World world, BlockPos blockPosition, net.minecraft.world.level.block.state.BlockState blockData, BlockEntity tileEntity) { +- // SPIGOT-6754, SPIGOT-6817: Restore previous behaviour for tile entities with removed blocks (loot generation post-destroy) +- if (tileEntity != null) { +- // block with unhandled TileEntity: +- return new CraftBlockEntityState<>(world, tileEntity); +- } ++ // Paper - revert upstream's revert of the block state changes. Block entities that have already had the block type set to AIR are still valid, upstream decided to ignore them + Preconditions.checkState(tileEntity == null, "Unexpected BlockState for %s", CraftBlockType.minecraftToBukkit(blockData.getBlock())); + return new CraftBlockState(world, blockPosition, blockData); + } + }; ++ // Paper start ++ private static final Map, BlockStateFactory> FACTORIES_BY_BLOCK_ENTITY_TYPE = new HashMap<>(); ++ private static void register(BlockEntityType type, BlockStateFactory factory) { ++ FACTORIES_BY_BLOCK_ENTITY_TYPE.put(type, factory); ++ } ++ // Paper end + + static { +- register( +- Arrays.asList( +- Material.ACACIA_SIGN, +- Material.ACACIA_WALL_SIGN, +- Material.BAMBOO_SIGN, +- Material.BAMBOO_WALL_SIGN, +- Material.BIRCH_SIGN, +- Material.BIRCH_WALL_SIGN, +- Material.CHERRY_SIGN, +- Material.CHERRY_WALL_SIGN, +- Material.CRIMSON_SIGN, +- Material.CRIMSON_WALL_SIGN, +- Material.DARK_OAK_SIGN, +- Material.DARK_OAK_WALL_SIGN, +- Material.JUNGLE_SIGN, +- Material.JUNGLE_WALL_SIGN, +- Material.MANGROVE_SIGN, +- Material.MANGROVE_WALL_SIGN, +- Material.OAK_SIGN, +- Material.OAK_WALL_SIGN, +- Material.PALE_OAK_SIGN, +- Material.PALE_OAK_WALL_SIGN, +- Material.SPRUCE_SIGN, +- Material.SPRUCE_WALL_SIGN, +- Material.WARPED_SIGN, +- Material.WARPED_WALL_SIGN +- ), CraftSign.class, CraftSign::new, SignBlockEntity::new +- ); +- +- register( +- Arrays.asList( +- Material.ACACIA_HANGING_SIGN, +- Material.ACACIA_WALL_HANGING_SIGN, +- Material.BAMBOO_HANGING_SIGN, +- Material.BAMBOO_WALL_HANGING_SIGN, +- Material.BIRCH_HANGING_SIGN, +- Material.BIRCH_WALL_HANGING_SIGN, +- Material.CHERRY_HANGING_SIGN, +- Material.CHERRY_WALL_HANGING_SIGN, +- Material.CRIMSON_HANGING_SIGN, +- Material.CRIMSON_WALL_HANGING_SIGN, +- Material.DARK_OAK_HANGING_SIGN, +- Material.DARK_OAK_WALL_HANGING_SIGN, +- Material.JUNGLE_HANGING_SIGN, +- Material.JUNGLE_WALL_HANGING_SIGN, +- Material.MANGROVE_HANGING_SIGN, +- Material.MANGROVE_WALL_HANGING_SIGN, +- Material.OAK_HANGING_SIGN, +- Material.OAK_WALL_HANGING_SIGN, +- Material.PALE_OAK_HANGING_SIGN, +- Material.PALE_OAK_WALL_HANGING_SIGN, +- Material.SPRUCE_HANGING_SIGN, +- Material.SPRUCE_WALL_HANGING_SIGN, +- Material.WARPED_HANGING_SIGN, +- Material.WARPED_WALL_HANGING_SIGN +- ), CraftHangingSign.class, CraftHangingSign::new, HangingSignBlockEntity::new +- ); +- +- register( +- Arrays.asList( +- Material.CREEPER_HEAD, +- Material.CREEPER_WALL_HEAD, +- Material.DRAGON_HEAD, +- Material.DRAGON_WALL_HEAD, +- Material.PIGLIN_HEAD, +- Material.PIGLIN_WALL_HEAD, +- Material.PLAYER_HEAD, +- Material.PLAYER_WALL_HEAD, +- Material.SKELETON_SKULL, +- Material.SKELETON_WALL_SKULL, +- Material.WITHER_SKELETON_SKULL, +- Material.WITHER_SKELETON_WALL_SKULL, +- Material.ZOMBIE_HEAD, +- Material.ZOMBIE_WALL_HEAD +- ), CraftSkull.class, CraftSkull::new, SkullBlockEntity::new +- ); +- +- register( +- Arrays.asList( +- Material.COMMAND_BLOCK, +- Material.REPEATING_COMMAND_BLOCK, +- Material.CHAIN_COMMAND_BLOCK +- ), CraftCommandBlock.class, CraftCommandBlock::new, CommandBlockEntity::new +- ); +- +- register( +- Arrays.asList( +- Material.BLACK_BANNER, +- Material.BLACK_WALL_BANNER, +- Material.BLUE_BANNER, +- Material.BLUE_WALL_BANNER, +- Material.BROWN_BANNER, +- Material.BROWN_WALL_BANNER, +- Material.CYAN_BANNER, +- Material.CYAN_WALL_BANNER, +- Material.GRAY_BANNER, +- Material.GRAY_WALL_BANNER, +- Material.GREEN_BANNER, +- Material.GREEN_WALL_BANNER, +- Material.LIGHT_BLUE_BANNER, +- Material.LIGHT_BLUE_WALL_BANNER, +- Material.LIGHT_GRAY_BANNER, +- Material.LIGHT_GRAY_WALL_BANNER, +- Material.LIME_BANNER, +- Material.LIME_WALL_BANNER, +- Material.MAGENTA_BANNER, +- Material.MAGENTA_WALL_BANNER, +- Material.ORANGE_BANNER, +- Material.ORANGE_WALL_BANNER, +- Material.PINK_BANNER, +- Material.PINK_WALL_BANNER, +- Material.PURPLE_BANNER, +- Material.PURPLE_WALL_BANNER, +- Material.RED_BANNER, +- Material.RED_WALL_BANNER, +- Material.WHITE_BANNER, +- Material.WHITE_WALL_BANNER, +- Material.YELLOW_BANNER, +- Material.YELLOW_WALL_BANNER +- ), CraftBanner.class, CraftBanner::new, BannerBlockEntity::new +- ); +- +- register( +- Arrays.asList( +- Material.SHULKER_BOX, +- Material.WHITE_SHULKER_BOX, +- Material.ORANGE_SHULKER_BOX, +- Material.MAGENTA_SHULKER_BOX, +- Material.LIGHT_BLUE_SHULKER_BOX, +- Material.YELLOW_SHULKER_BOX, +- Material.LIME_SHULKER_BOX, +- Material.PINK_SHULKER_BOX, +- Material.GRAY_SHULKER_BOX, +- Material.LIGHT_GRAY_SHULKER_BOX, +- Material.CYAN_SHULKER_BOX, +- Material.PURPLE_SHULKER_BOX, +- Material.BLUE_SHULKER_BOX, +- Material.BROWN_SHULKER_BOX, +- Material.GREEN_SHULKER_BOX, +- Material.RED_SHULKER_BOX, +- Material.BLACK_SHULKER_BOX +- ), CraftShulkerBox.class, CraftShulkerBox::new, ShulkerBoxBlockEntity::new +- ); +- +- register( +- Arrays.asList( +- Material.BLACK_BED, +- Material.BLUE_BED, +- Material.BROWN_BED, +- Material.CYAN_BED, +- Material.GRAY_BED, +- Material.GREEN_BED, +- Material.LIGHT_BLUE_BED, +- Material.LIGHT_GRAY_BED, +- Material.LIME_BED, +- Material.MAGENTA_BED, +- Material.ORANGE_BED, +- Material.PINK_BED, +- Material.PURPLE_BED, +- Material.RED_BED, +- Material.WHITE_BED, +- Material.YELLOW_BED +- ), CraftBed.class, CraftBed::new, BedBlockEntity::new +- ); +- +- register( +- Arrays.asList( +- Material.BEEHIVE, +- Material.BEE_NEST +- ), CraftBeehive.class, CraftBeehive::new, BeehiveBlockEntity::new +- ); +- +- register( +- Arrays.asList( +- Material.CAMPFIRE, +- Material.SOUL_CAMPFIRE +- ), CraftCampfire.class, CraftCampfire::new, CampfireBlockEntity::new +- ); +- +- register(Material.BARREL, CraftBarrel.class, CraftBarrel::new, BarrelBlockEntity::new); +- register(Material.BEACON, CraftBeacon.class, CraftBeacon::new, BeaconBlockEntity::new); +- register(Material.BELL, CraftBell.class, CraftBell::new, BellBlockEntity::new); +- register(Material.BLAST_FURNACE, CraftBlastFurnace.class, CraftBlastFurnace::new, BlastFurnaceBlockEntity::new); +- register(Material.BREWING_STAND, CraftBrewingStand.class, CraftBrewingStand::new, BrewingStandBlockEntity::new); +- register(Material.CHEST, CraftChest.class, CraftChest::new, ChestBlockEntity::new); +- register(Material.CHISELED_BOOKSHELF, CraftChiseledBookshelf.class, CraftChiseledBookshelf::new, ChiseledBookShelfBlockEntity::new); +- register(Material.COMPARATOR, CraftComparator.class, CraftComparator::new, ComparatorBlockEntity::new); +- register(Material.CONDUIT, CraftConduit.class, CraftConduit::new, ConduitBlockEntity::new); +- register(Material.CREAKING_HEART, CraftCreakingHeart.class, CraftCreakingHeart::new, CreakingHeartBlockEntity::new); +- register(Material.DAYLIGHT_DETECTOR, CraftDaylightDetector.class, CraftDaylightDetector::new, DaylightDetectorBlockEntity::new); +- register(Material.DECORATED_POT, CraftDecoratedPot.class, CraftDecoratedPot::new, DecoratedPotBlockEntity::new); +- register(Material.DISPENSER, CraftDispenser.class, CraftDispenser::new, DispenserBlockEntity::new); +- register(Material.DROPPER, CraftDropper.class, CraftDropper::new, DropperBlockEntity::new); +- register(Material.ENCHANTING_TABLE, CraftEnchantingTable.class, CraftEnchantingTable::new, EnchantingTableBlockEntity::new); +- register(Material.ENDER_CHEST, CraftEnderChest.class, CraftEnderChest::new, EnderChestBlockEntity::new); +- register(Material.END_GATEWAY, CraftEndGateway.class, CraftEndGateway::new, TheEndGatewayBlockEntity::new); +- register(Material.END_PORTAL, CraftEndPortal.class, CraftEndPortal::new, TheEndPortalBlockEntity::new); +- register(Material.FURNACE, CraftFurnaceFurnace.class, CraftFurnaceFurnace::new, FurnaceBlockEntity::new); +- register(Material.HOPPER, CraftHopper.class, CraftHopper::new, HopperBlockEntity::new); +- register(Material.JIGSAW, CraftJigsaw.class, CraftJigsaw::new, JigsawBlockEntity::new); +- register(Material.JUKEBOX, CraftJukebox.class, CraftJukebox::new, JukeboxBlockEntity::new); +- register(Material.LECTERN, CraftLectern.class, CraftLectern::new, LecternBlockEntity::new); +- register(Material.MOVING_PISTON, CraftMovingPiston.class, CraftMovingPiston::new, PistonMovingBlockEntity::new); +- register(Material.SCULK_CATALYST, CraftSculkCatalyst.class, CraftSculkCatalyst::new, SculkCatalystBlockEntity::new); +- register(Material.CALIBRATED_SCULK_SENSOR, CraftCalibratedSculkSensor.class, CraftCalibratedSculkSensor::new, CalibratedSculkSensorBlockEntity::new); +- register(Material.SCULK_SENSOR, CraftSculkSensor.class, CraftSculkSensor::new, SculkSensorBlockEntity::new); +- register(Material.SCULK_SHRIEKER, CraftSculkShrieker.class, CraftSculkShrieker::new, SculkShriekerBlockEntity::new); +- register(Material.SMOKER, CraftSmoker.class, CraftSmoker::new, SmokerBlockEntity::new); +- register(Material.SPAWNER, CraftCreatureSpawner.class, CraftCreatureSpawner::new, SpawnerBlockEntity::new); +- register(Material.STRUCTURE_BLOCK, CraftStructureBlock.class, CraftStructureBlock::new, StructureBlockEntity::new); +- register(Material.SUSPICIOUS_SAND, CraftSuspiciousSand.class, CraftSuspiciousSand::new, BrushableBlockEntity::new); +- register(Material.SUSPICIOUS_GRAVEL, CraftBrushableBlock.class, CraftBrushableBlock::new, BrushableBlockEntity::new); +- register(Material.TRAPPED_CHEST, CraftChest.class, CraftChest::new, TrappedChestBlockEntity::new); +- register(Material.CRAFTER, CraftCrafter.class, CraftCrafter::new, CrafterBlockEntity::new); +- register(Material.TRIAL_SPAWNER, CraftTrialSpawner.class, CraftTrialSpawner::new, TrialSpawnerBlockEntity::new); +- register(Material.VAULT, CraftVault.class, CraftVault::new, VaultBlockEntity::new); ++ // Paper start - simplify ++ register(BlockEntityType.SIGN, CraftSign.class, CraftSign::new); ++ register(BlockEntityType.HANGING_SIGN, CraftHangingSign.class, CraftHangingSign::new); ++ register(BlockEntityType.SKULL, CraftSkull.class, CraftSkull::new); ++ register(BlockEntityType.COMMAND_BLOCK, CraftCommandBlock.class, CraftCommandBlock::new); ++ register(BlockEntityType.BANNER, CraftBanner.class, CraftBanner::new); ++ register(BlockEntityType.SHULKER_BOX, CraftShulkerBox.class, CraftShulkerBox::new); ++ register(BlockEntityType.BED, CraftBed.class, CraftBed::new); ++ register(BlockEntityType.BEEHIVE, CraftBeehive.class, CraftBeehive::new); ++ register(BlockEntityType.CAMPFIRE, CraftCampfire.class, CraftCampfire::new); ++ register(BlockEntityType.BARREL, CraftBarrel.class, CraftBarrel::new); ++ register(BlockEntityType.BEACON, CraftBeacon.class, CraftBeacon::new); ++ register(BlockEntityType.BELL, CraftBell.class, CraftBell::new); ++ register(BlockEntityType.BLAST_FURNACE, CraftBlastFurnace.class, CraftBlastFurnace::new); ++ register(BlockEntityType.BREWING_STAND, CraftBrewingStand.class, CraftBrewingStand::new); ++ register(BlockEntityType.CHEST, CraftChest.class, CraftChest::new); ++ register(BlockEntityType.CHISELED_BOOKSHELF, CraftChiseledBookshelf.class, CraftChiseledBookshelf::new); ++ register(BlockEntityType.COMPARATOR, CraftComparator.class, CraftComparator::new); ++ register(BlockEntityType.CONDUIT, CraftConduit.class, CraftConduit::new); ++ register(BlockEntityType.CREAKING_HEART, CraftCreakingHeart.class, CraftCreakingHeart::new); ++ register(BlockEntityType.DAYLIGHT_DETECTOR, CraftDaylightDetector.class, CraftDaylightDetector::new); ++ register(BlockEntityType.DECORATED_POT, CraftDecoratedPot.class, CraftDecoratedPot::new); ++ register(BlockEntityType.DISPENSER, CraftDispenser.class, CraftDispenser::new); ++ register(BlockEntityType.DROPPER, CraftDropper.class, CraftDropper::new); ++ register(BlockEntityType.ENCHANTING_TABLE, CraftEnchantingTable.class, CraftEnchantingTable::new); ++ register(BlockEntityType.ENDER_CHEST, CraftEnderChest.class, CraftEnderChest::new); ++ register(BlockEntityType.END_GATEWAY, CraftEndGateway.class, CraftEndGateway::new); ++ register(BlockEntityType.END_PORTAL, CraftEndPortal.class, CraftEndPortal::new); ++ register(BlockEntityType.FURNACE, CraftFurnaceFurnace.class, CraftFurnaceFurnace::new); ++ register(BlockEntityType.HOPPER, CraftHopper.class, CraftHopper::new); ++ register(BlockEntityType.JIGSAW, CraftJigsaw.class, CraftJigsaw::new); ++ register(BlockEntityType.JUKEBOX, CraftJukebox.class, CraftJukebox::new); ++ register(BlockEntityType.LECTERN, CraftLectern.class, CraftLectern::new); ++ register(BlockEntityType.PISTON, CraftMovingPiston.class, CraftMovingPiston::new); ++ register(BlockEntityType.SCULK_CATALYST, CraftSculkCatalyst.class, CraftSculkCatalyst::new); ++ register(BlockEntityType.SCULK_SENSOR, CraftSculkSensor.class, CraftSculkSensor::new); ++ register(BlockEntityType.SCULK_SHRIEKER, CraftSculkShrieker.class, CraftSculkShrieker::new); ++ register(BlockEntityType.CALIBRATED_SCULK_SENSOR, CraftCalibratedSculkSensor.class, CraftCalibratedSculkSensor::new); ++ register(BlockEntityType.SMOKER, CraftSmoker.class, CraftSmoker::new); ++ register(BlockEntityType.MOB_SPAWNER, CraftCreatureSpawner.class, CraftCreatureSpawner::new); ++ register(BlockEntityType.STRUCTURE_BLOCK, CraftStructureBlock.class, CraftStructureBlock::new); ++ register(BlockEntityType.BRUSHABLE_BLOCK, CraftBrushableBlock.class, CraftBrushableBlock::new); // note: spigot still uses CraftSuspiciousSand impl for that block type ++ register(BlockEntityType.TRAPPED_CHEST, CraftChest.class, CraftChest::new); ++ register(BlockEntityType.CRAFTER, CraftCrafter.class, CraftCrafter::new); ++ register(BlockEntityType.TRIAL_SPAWNER, CraftTrialSpawner.class, CraftTrialSpawner::new); ++ register(BlockEntityType.VAULT, CraftVault.class, CraftVault::new); ++ // Paper end + } + + private static void register(Material blockType, BlockStateFactory factory) { +@@ -353,30 +187,33 @@ public final class CraftBlockStates { + } + + private static > void register( +- Material blockType, ++ net.minecraft.world.level.block.entity.BlockEntityType blockEntityType, // Paper + Class blockStateType, +- BiFunction blockStateConstructor, +- BiFunction tileEntityConstructor ++ BiFunction blockStateConstructor // Paper + ) { +- CraftBlockStates.register(Collections.singletonList(blockType), blockStateType, blockStateConstructor, tileEntityConstructor); +- } +- +- private static > void register( +- List blockTypes, +- Class blockStateType, +- BiFunction blockStateConstructor, +- BiFunction tileEntityConstructor +- ) { +- BlockStateFactory factory = new BlockEntityStateFactory<>(blockStateType, blockStateConstructor, tileEntityConstructor); +- for (Material blockType : blockTypes) { +- CraftBlockStates.register(blockType, factory); ++ // Paper start ++ BlockStateFactory factory = new BlockEntityStateFactory<>(blockStateType, blockStateConstructor, blockEntityType); // Paper ++ for (net.minecraft.world.level.block.Block block : blockEntityType.validBlocks) { ++ CraftBlockStates.register(CraftBlockType.minecraftToBukkit(block), factory); + } ++ CraftBlockStates.register(blockEntityType, factory); ++ // Paper end + } + + private static BlockStateFactory getFactory(Material material) { + return CraftBlockStates.FACTORIES.getOrDefault(material, CraftBlockStates.DEFAULT_FACTORY); + } + ++ // Paper start ++ private static BlockStateFactory getFactory(Material material, BlockEntityType type) { ++ if (type != null) { ++ return CraftBlockStates.FACTORIES_BY_BLOCK_ENTITY_TYPE.getOrDefault(type, getFactory(material)); ++ } else { ++ return getFactory(material); ++ } ++ } ++ // Paper end ++ + public static Class getBlockStateType(Material material) { + Preconditions.checkNotNull(material, "material is null"); + return CraftBlockStates.getFactory(material).blockStateType; +@@ -392,6 +229,13 @@ public final class CraftBlockStates { + return null; + } + ++ // Paper start ++ public static Class getBlockStateType(BlockEntityType blockEntityType) { ++ Preconditions.checkNotNull(blockEntityType, "blockEntityType is null"); ++ return CraftBlockStates.getFactory(null, blockEntityType).blockStateType; ++ } ++ // Paper end ++ + public static BlockState getBlockState(Block block) { + // Paper start + return CraftBlockStates.getBlockState(block, true); +@@ -459,7 +303,7 @@ public final class CraftBlockStates { + if (world != null && tileEntity == null && CraftBlockStates.isTileEntityOptional(material)) { + factory = CraftBlockStates.DEFAULT_FACTORY; + } else { +- factory = CraftBlockStates.getFactory(material); ++ factory = CraftBlockStates.getFactory(material, tileEntity != null ? tileEntity.getType() : null); // Paper + } + return factory.createBlockState(world, blockPosition, blockData, tileEntity); + } +@@ -478,6 +322,14 @@ public final class CraftBlockStates { + return new CraftBlockState(CraftBlock.at(world, pos), flag); + } + ++ // Paper start ++ @Nullable ++ public static BlockEntityType getBlockEntityType(final Material material) { ++ final BlockStateFactory factory = org.bukkit.craftbukkit.block.CraftBlockStates.FACTORIES.get(material); ++ return factory instanceof final BlockEntityStateFactory blockEntityStateFactory ? blockEntityStateFactory.tileEntityConstructor : null; ++ } ++ // Paper end ++ + private CraftBlockStates() { + } + } +diff --git a/src/test/java/org/bukkit/craftbukkit/block/BlockStateTest.java b/src/test/java/org/bukkit/craftbukkit/block/BlockStateTest.java +index c032daa6957df2ad8b621379e415ad925f5cc162..a9810c88e05ebc1af60c051faa45e50ee183924f 100644 +--- a/src/test/java/org/bukkit/craftbukkit/block/BlockStateTest.java ++++ b/src/test/java/org/bukkit/craftbukkit/block/BlockStateTest.java +@@ -7,6 +7,7 @@ import net.minecraft.core.registries.BuiltInRegistries; + import net.minecraft.world.level.block.Block; + import net.minecraft.world.level.block.EntityBlock; + import net.minecraft.world.level.block.entity.BlockEntity; ++import net.minecraft.world.level.block.entity.BlockEntityType; + import org.bukkit.Material; + import org.bukkit.support.environment.AllFeatures; + import org.junit.jupiter.api.Test; +@@ -42,4 +43,13 @@ public class BlockStateTest { + } + } + } ++ ++ // Paper start ++ @Test ++ public void testBlockEntityTypes() { ++ for (var blockEntityType : BuiltInRegistries.BLOCK_ENTITY_TYPE) { ++ org.junit.jupiter.api.Assertions.assertNotNull(CraftBlockStates.getBlockStateType(blockEntityType)); ++ } ++ } ++ // Paper end + } diff --git a/patches/server/0619-Configurable-feature-seeds.patch b/patches/server/0619-Configurable-feature-seeds.patch new file mode 100644 index 000000000000..b374cbddbfec --- /dev/null +++ b/patches/server/0619-Configurable-feature-seeds.patch @@ -0,0 +1,27 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Nassim Jahnke +Date: Tue, 31 Aug 2021 17:05:27 +0200 +Subject: [PATCH] Configurable feature seeds + +Co-authored-by: Thonk <30448663+ExcessiveAmountsOfZombies@users.noreply.github.com> + +diff --git a/src/main/java/net/minecraft/world/level/chunk/ChunkGenerator.java b/src/main/java/net/minecraft/world/level/chunk/ChunkGenerator.java +index e3c5a49611d584fbd19a44da5aa78ff6d7c43881..fc8e3edd9734fa7b69f0fc6b4eefd8a704e451cf 100644 +--- a/src/main/java/net/minecraft/world/level/chunk/ChunkGenerator.java ++++ b/src/main/java/net/minecraft/world/level/chunk/ChunkGenerator.java +@@ -436,7 +436,14 @@ public abstract class ChunkGenerator { + return (String) optional.orElseGet(placedfeature::toString); + }; + +- seededrandom.setFeatureSeed(i, l1, l); ++ // Paper start - Configurable feature seeds; change populationSeed used in random ++ long featurePopulationSeed = i; ++ final long configFeatureSeed = generatoraccessseed.getMinecraftWorld().paperConfig().featureSeeds.features.getLong(placedfeature.feature()); ++ if (configFeatureSeed != -1) { ++ featurePopulationSeed = seededrandom.setDecorationSeed(configFeatureSeed, blockposition.getX(), blockposition.getZ()); // See seededrandom.setDecorationSeed from above ++ } ++ seededrandom.setFeatureSeed(featurePopulationSeed, l1, l); ++ // Paper end - Configurable feature seeds + + try { + generatoraccessseed.setCurrentlyGenerating(supplier1); diff --git a/patches/server/0619-Preserve-overstacked-loot.patch b/patches/server/0619-Preserve-overstacked-loot.patch deleted file mode 100644 index bd657ec90674..000000000000 --- a/patches/server/0619-Preserve-overstacked-loot.patch +++ /dev/null @@ -1,27 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: lexikiq -Date: Mon, 21 Jun 2021 23:21:58 -0400 -Subject: [PATCH] Preserve overstacked loot - -Preserves overstacked items in loot tables, such as shulker box drops, to prevent the items -from being deleted (as they'd overflow past the bounds of the container)-- or worse, causing -chunk bans via the large amount of NBT created by unstacking the items. - -Fixes GH-5140 and GH-4748. - -diff --git a/src/main/java/net/minecraft/world/level/storage/loot/LootTable.java b/src/main/java/net/minecraft/world/level/storage/loot/LootTable.java -index b231f90317fe7df9133674b12d47873520b481cb..edaf7f1692ae059581f3abc24bb228874e6d114b 100644 ---- a/src/main/java/net/minecraft/world/level/storage/loot/LootTable.java -+++ b/src/main/java/net/minecraft/world/level/storage/loot/LootTable.java -@@ -72,9 +72,10 @@ public class LootTable { - } - - public static Consumer createStackSplitter(ServerLevel world, Consumer consumer) { -+ boolean skipSplitter = world != null && !world.paperConfig().fixes.splitOverstackedLoot; // Paper - preserve overstacked items - return (itemstack) -> { - if (itemstack.isItemEnabled(world.enabledFeatures())) { -- if (itemstack.getCount() < itemstack.getMaxStackSize()) { -+ if (skipSplitter || itemstack.getCount() < itemstack.getMaxStackSize()) { // Paper - preserve overstacked items - consumer.accept(itemstack); - } else { - int i = itemstack.getCount(); diff --git a/patches/server/0620-Add-root-admin-user-detection.patch b/patches/server/0620-Add-root-admin-user-detection.patch new file mode 100644 index 000000000000..df739cc33c9d --- /dev/null +++ b/patches/server/0620-Add-root-admin-user-detection.patch @@ -0,0 +1,62 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: egg82 +Date: Sat, 11 Sep 2021 22:55:14 +0200 +Subject: [PATCH] Add root/admin user detection + +This patch detects whether or not the server is currently executing as a privileged user and spits out a warning. +The warning serves as a sort-of PSA for newer server admins who don't understand the risks of running as root. +We've seen plenty of bad/malicious plugins hit markets, and there's been a few close-calls with exploits in the past. +Hopefully this helps mitigate some potential damage to servers, even if it is just a warning. + +Co-authored-by: Noah van der Aa + +diff --git a/src/main/java/io/papermc/paper/util/ServerEnvironment.java b/src/main/java/io/papermc/paper/util/ServerEnvironment.java +new file mode 100644 +index 0000000000000000000000000000000000000000..68098dfe716e93aafcca4d8d5b5a81d8648b3654 +--- /dev/null ++++ b/src/main/java/io/papermc/paper/util/ServerEnvironment.java +@@ -0,0 +1,23 @@ ++package io.papermc.paper.util; ++ ++import com.sun.security.auth.module.NTSystem; ++import com.sun.security.auth.module.UnixSystem; ++import java.util.Set; ++import org.apache.commons.lang.SystemUtils; ++ ++public class ServerEnvironment { ++ private static final boolean RUNNING_AS_ROOT_OR_ADMIN; ++ private static final String WINDOWS_HIGH_INTEGRITY_LEVEL = "S-1-16-12288"; ++ ++ static { ++ if (SystemUtils.IS_OS_WINDOWS) { ++ RUNNING_AS_ROOT_OR_ADMIN = Set.of(new NTSystem().getGroupIDs()).contains(WINDOWS_HIGH_INTEGRITY_LEVEL); ++ } else { ++ RUNNING_AS_ROOT_OR_ADMIN = new UnixSystem().getUid() == 0; ++ } ++ } ++ ++ public static boolean userIsRootOrAdmin() { ++ return RUNNING_AS_ROOT_OR_ADMIN; ++ } ++} +diff --git a/src/main/java/net/minecraft/server/dedicated/DedicatedServer.java b/src/main/java/net/minecraft/server/dedicated/DedicatedServer.java +index 21d6f728d6ecd35a05933e9406a386c36135a456..ac7fc2497b860a143ef08498f5f477a373a29be7 100644 +--- a/src/main/java/net/minecraft/server/dedicated/DedicatedServer.java ++++ b/src/main/java/net/minecraft/server/dedicated/DedicatedServer.java +@@ -193,6 +193,16 @@ public class DedicatedServer extends MinecraftServer implements ServerInterface + DedicatedServer.LOGGER.warn("To start the server with more ram, launch it as \"java -Xmx1024M -Xms1024M -jar minecraft_server.jar\""); + } + ++ // Paper start - detect running as root ++ if (io.papermc.paper.util.ServerEnvironment.userIsRootOrAdmin()) { ++ DedicatedServer.LOGGER.warn("****************************"); ++ DedicatedServer.LOGGER.warn("YOU ARE RUNNING THIS SERVER AS AN ADMINISTRATIVE OR ROOT USER. THIS IS NOT ADVISED."); ++ DedicatedServer.LOGGER.warn("YOU ARE OPENING YOURSELF UP TO POTENTIAL RISKS WHEN DOING THIS."); ++ DedicatedServer.LOGGER.warn("FOR MORE INFORMATION, SEE https://madelinemiller.dev/blog/root-minecraft-server/"); ++ DedicatedServer.LOGGER.warn("****************************"); ++ } ++ // Paper end - detect running as root ++ + DedicatedServer.LOGGER.info("Loading properties"); + DedicatedServerProperties dedicatedserverproperties = this.settings.getProperties(); + diff --git a/patches/server/0620-Update-head-rotation-in-missing-places.patch b/patches/server/0620-Update-head-rotation-in-missing-places.patch deleted file mode 100644 index a1c7566dc673..000000000000 --- a/patches/server/0620-Update-head-rotation-in-missing-places.patch +++ /dev/null @@ -1,29 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Owen1212055 <23108066+Owen1212055@users.noreply.github.com> -Date: Mon, 21 Jun 2021 21:55:23 -0400 -Subject: [PATCH] Update head rotation in missing places - -In certain areas the player's head rotation could be desynced when teleported/moved. -This is because bukkit uses a separate head rotation field for yaw. -This issue only applies to players. - -diff --git a/src/main/java/net/minecraft/world/entity/Entity.java b/src/main/java/net/minecraft/world/entity/Entity.java -index f21bf6c8bbf30c59f1588c2105dbd7f80c07a0f1..e34260c80d19869c9bec5ba67f7ce7db9d13a1f1 100644 ---- a/src/main/java/net/minecraft/world/entity/Entity.java -+++ b/src/main/java/net/minecraft/world/entity/Entity.java -@@ -1844,6 +1844,7 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess - this.setXRot(Mth.clamp(pitch, -90.0F, 90.0F) % 360.0F); - this.yRotO = this.getYRot(); - this.xRotO = this.getXRot(); -+ this.setYHeadRot(yaw); // Paper - Update head rotation - } - - public void absMoveTo(double x, double y, double z) { -@@ -1886,6 +1887,7 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess - this.setXRot(pitch); - this.setOldPosAndRot(); - this.reapplyPosition(); -+ this.setYHeadRot(yaw); // Paper - Update head rotation - } - - public final void setOldPosAndRot() { diff --git a/patches/server/0621-don-t-attempt-to-teleport-dead-entities.patch b/patches/server/0621-don-t-attempt-to-teleport-dead-entities.patch new file mode 100644 index 000000000000..92fa7ea8540a --- /dev/null +++ b/patches/server/0621-don-t-attempt-to-teleport-dead-entities.patch @@ -0,0 +1,19 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: sulu5890 +Date: Sun, 24 Oct 2021 22:48:14 -0500 +Subject: [PATCH] don't attempt to teleport dead entities + + +diff --git a/src/main/java/net/minecraft/world/entity/Entity.java b/src/main/java/net/minecraft/world/entity/Entity.java +index 3903bbc53d541e66d9362eea27029ed320a74772..d6bbefe3d3b21c6fc9be3cfe3ba304226bab6883 100644 +--- a/src/main/java/net/minecraft/world/entity/Entity.java ++++ b/src/main/java/net/minecraft/world/entity/Entity.java +@@ -713,7 +713,7 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess + // CraftBukkit start + public void postTick() { + // No clean way to break out of ticking once the entity has been copied to a new world, so instead we move the portalling later in the tick cycle +- if (!(this instanceof ServerPlayer)) { ++ if (!(this instanceof ServerPlayer) && this.isAlive()) { // Paper - don't attempt to teleport dead entities + this.handlePortal(); + } + } diff --git a/patches/server/0621-prevent-unintended-light-block-manipulation.patch b/patches/server/0621-prevent-unintended-light-block-manipulation.patch deleted file mode 100644 index bff44792dce8..000000000000 --- a/patches/server/0621-prevent-unintended-light-block-manipulation.patch +++ /dev/null @@ -1,25 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Jake Potrebic -Date: Mon, 13 Sep 2021 18:55:45 -0700 -Subject: [PATCH] prevent unintended light block manipulation - - -diff --git a/src/main/java/net/minecraft/world/level/block/LightBlock.java b/src/main/java/net/minecraft/world/level/block/LightBlock.java -index 01722473a4861533dba0ab6edf3982c0278c41e1..2b3c395529a15c9f07a4c0cff7f82199298bcb6d 100644 ---- a/src/main/java/net/minecraft/world/level/block/LightBlock.java -+++ b/src/main/java/net/minecraft/world/level/block/LightBlock.java -@@ -49,6 +49,14 @@ public class LightBlock extends Block implements SimpleWaterloggedBlock { - builder.add(LEVEL, WATERLOGGED); - } - -+ // Paper start - prevent unintended light block manipulation -+ @Override -+ protected net.minecraft.world.ItemInteractionResult useItemOn(final ItemStack stack, final BlockState state, final Level world, final BlockPos pos, final Player player, final net.minecraft.world.InteractionHand hand, final BlockHitResult hit) { -+ if (player.getItemInHand(hand).getItem() != Items.LIGHT || !player.mayInteract(world, pos) || !player.mayUseItemAt(pos, hit.getDirection(), player.getItemInHand(hand))) { return net.minecraft.world.ItemInteractionResult.SKIP_DEFAULT_BLOCK_INTERACTION; } // Paper - Prevent unintended light block manipulation -+ return super.useItemOn(stack, state, world, pos, player, hand, hit); -+ } -+ // Paper end - prevent unintended light block manipulation -+ - @Override - protected InteractionResult useWithoutItem(BlockState state, Level world, BlockPos pos, Player player, BlockHitResult hit) { - if (!world.isClientSide && player.canUseGameMasterBlocks()) { diff --git a/patches/server/0622-Prevent-excessive-velocity-through-repeated-crits.patch b/patches/server/0622-Prevent-excessive-velocity-through-repeated-crits.patch new file mode 100644 index 000000000000..d7f8266ad656 --- /dev/null +++ b/patches/server/0622-Prevent-excessive-velocity-through-repeated-crits.patch @@ -0,0 +1,41 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Nassim Jahnke +Date: Thu, 25 Nov 2021 10:25:09 +0100 +Subject: [PATCH] Prevent excessive velocity through repeated crits + + +diff --git a/src/main/java/net/minecraft/world/entity/LivingEntity.java b/src/main/java/net/minecraft/world/entity/LivingEntity.java +index 564024738cc346abc024967c2d55f2553af3e660..8e0805bf6d330717f7555fbdb28d416295f8495a 100644 +--- a/src/main/java/net/minecraft/world/entity/LivingEntity.java ++++ b/src/main/java/net/minecraft/world/entity/LivingEntity.java +@@ -2863,17 +2863,29 @@ public abstract class LivingEntity extends Entity implements Attackable { + return this.hasEffect(MobEffects.JUMP) ? 0.1F * ((float) this.getEffect(MobEffects.JUMP).getAmplifier() + 1.0F) : 0.0F; + } + ++ protected long lastJumpTime = 0L; // Paper - Prevent excessive velocity through repeated crits + @VisibleForTesting + public void jumpFromGround() { + float f = this.getJumpPower(); + + if (f > 1.0E-5F) { + Vec3 vec3d = this.getDeltaMovement(); ++ // Paper start - Prevent excessive velocity through repeated crits ++ long time = System.nanoTime(); ++ boolean canCrit = true; ++ if (this instanceof net.minecraft.world.entity.player.Player) { ++ canCrit = false; ++ if (time - this.lastJumpTime > (long)(0.250e9)) { ++ this.lastJumpTime = time; ++ canCrit = true; ++ } ++ } ++ // Paper end - Prevent excessive velocity through repeated crits + + this.setDeltaMovement(vec3d.x, Math.max((double) f, vec3d.y), vec3d.z); + if (this.isSprinting()) { + float f1 = this.getYRot() * 0.017453292F; +- ++ if (canCrit) // Paper - Prevent excessive velocity through repeated crits + this.addDeltaMovement(new Vec3((double) (-Mth.sin(f1)) * 0.2D, 0.0D, (double) Mth.cos(f1) * 0.2D)); + } + diff --git a/patches/server/0623-Fix-upstreams-block-state-factories.patch b/patches/server/0623-Fix-upstreams-block-state-factories.patch deleted file mode 100644 index 0607b0976836..000000000000 --- a/patches/server/0623-Fix-upstreams-block-state-factories.patch +++ /dev/null @@ -1,485 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Jake Potrebic -Date: Wed, 6 Oct 2021 20:50:48 -0700 -Subject: [PATCH] Fix upstreams block state factories - -Sometimes, blocks are changed and then logic is called before the associated -tile entity is removed. When this happens, the factories were relying on the -block at the position, not the tile entity. This change prioritizes using the -tile entity type to determine the block state factory and falls back on -the material type of the block at that location. - -== AT == -public net.minecraft.world.level.block.entity.BlockEntityType validBlocks - -diff --git a/src/main/java/net/minecraft/world/level/block/entity/BlockEntity.java b/src/main/java/net/minecraft/world/level/block/entity/BlockEntity.java -index 48bee70ba4188a4a55beb6584224b0f23784dd88..cbb777f499a4e8a447153c04d09c0c71321c663c 100644 ---- a/src/main/java/net/minecraft/world/level/block/entity/BlockEntity.java -+++ b/src/main/java/net/minecraft/world/level/block/entity/BlockEntity.java -@@ -387,7 +387,7 @@ public abstract class BlockEntity { - // Paper end - if (this.level == null) return null; - org.bukkit.block.Block block = this.level.getWorld().getBlockAt(this.worldPosition.getX(), this.worldPosition.getY(), this.worldPosition.getZ()); -- if (block.getType() == org.bukkit.Material.AIR) return null; -+ // if (block.getType() == org.bukkit.Material.AIR) return null; // Paper - actually get the tile entity if it still exists - org.bukkit.block.BlockState state = block.getState(useSnapshot); // Paper - if (state instanceof InventoryHolder) return (InventoryHolder) state; - return null; -diff --git a/src/main/java/org/bukkit/craftbukkit/block/CraftBlockEntityState.java b/src/main/java/org/bukkit/craftbukkit/block/CraftBlockEntityState.java -index 80bd6e8a6dadb74356a9fa9aa394efbd31c49c37..fe7e3e0628783d8d1be9635b689da8a9cb46c5d7 100644 ---- a/src/main/java/org/bukkit/craftbukkit/block/CraftBlockEntityState.java -+++ b/src/main/java/org/bukkit/craftbukkit/block/CraftBlockEntityState.java -@@ -20,7 +20,7 @@ import org.bukkit.persistence.PersistentDataContainer; - import org.jetbrains.annotations.NotNull; - import org.jetbrains.annotations.Nullable; - --public class CraftBlockEntityState extends CraftBlockState implements TileState { -+public abstract class CraftBlockEntityState extends CraftBlockState implements TileState { // Paper - revert upstream's revert of the block state changes - - private final T tileEntity; - private final T snapshot; -@@ -196,14 +196,10 @@ public class CraftBlockEntityState extends CraftBlockStat - } - - @Override -- public CraftBlockEntityState copy() { -- return new CraftBlockEntityState<>(this, null); -- } -+ public abstract CraftBlockEntityState copy(); // Paper - make abstract - - @Override -- public CraftBlockEntityState copy(Location location) { -- return new CraftBlockEntityState<>(this, location); -- } -+ public abstract CraftBlockEntityState copy(Location location); // Paper - make abstract - - // Paper start - @Override -diff --git a/src/main/java/org/bukkit/craftbukkit/block/CraftBlockStates.java b/src/main/java/org/bukkit/craftbukkit/block/CraftBlockStates.java -index 83ac5fc6cbbd249b5865ab203b150f53f01c9f05..b7ff7af2513204b151340538d50a65c850bdb75f 100644 ---- a/src/main/java/org/bukkit/craftbukkit/block/CraftBlockStates.java -+++ b/src/main/java/org/bukkit/craftbukkit/block/CraftBlockStates.java -@@ -22,6 +22,7 @@ import net.minecraft.world.level.block.entity.BeehiveBlockEntity; - import net.minecraft.world.level.block.entity.BellBlockEntity; - import net.minecraft.world.level.block.entity.BlastFurnaceBlockEntity; - import net.minecraft.world.level.block.entity.BlockEntity; -+import net.minecraft.world.level.block.entity.BlockEntityType; // Paper - import net.minecraft.world.level.block.entity.BrewingStandBlockEntity; - import net.minecraft.world.level.block.entity.BrushableBlockEntity; - import net.minecraft.world.level.block.entity.CalibratedSculkSensorBlockEntity; -@@ -87,9 +88,9 @@ public final class CraftBlockStates { - private static class BlockEntityStateFactory> extends BlockStateFactory { - - private final BiFunction blockStateConstructor; -- private final BiFunction tileEntityConstructor; -+ private final BlockEntityType tileEntityConstructor; // Paper - -- protected BlockEntityStateFactory(Class blockStateType, BiFunction blockStateConstructor, BiFunction tileEntityConstructor) { -+ protected BlockEntityStateFactory(Class blockStateType, BiFunction blockStateConstructor, BlockEntityType tileEntityConstructor) { // Paper - super(blockStateType); - this.blockStateConstructor = blockStateConstructor; - this.tileEntityConstructor = tileEntityConstructor; -@@ -106,7 +107,7 @@ public final class CraftBlockStates { - } - - private T createTileEntity(BlockPos blockPosition, net.minecraft.world.level.block.state.BlockState blockData) { -- return this.tileEntityConstructor.apply(blockPosition, blockData); -+ return this.tileEntityConstructor.create(blockPosition, blockData); // Paper - } - - private B createBlockState(World world, T tileEntity) { -@@ -118,228 +119,65 @@ public final class CraftBlockStates { - private static final BlockStateFactory DEFAULT_FACTORY = new BlockStateFactory(CraftBlockState.class) { - @Override - public CraftBlockState createBlockState(World world, BlockPos blockPosition, net.minecraft.world.level.block.state.BlockState blockData, BlockEntity tileEntity) { -- // SPIGOT-6754, SPIGOT-6817: Restore previous behaviour for tile entities with removed blocks (loot generation post-destroy) -- if (tileEntity != null) { -- // block with unhandled TileEntity: -- return new CraftBlockEntityState<>(world, tileEntity); -- } -+ // Paper - revert upstream's revert of the block state changes. Block entities that have already had the block type set to AIR are still valid, upstream decided to ignore them - Preconditions.checkState(tileEntity == null, "Unexpected BlockState for %s", CraftBlockType.minecraftToBukkit(blockData.getBlock())); - return new CraftBlockState(world, blockPosition, blockData); - } - }; -+ // Paper start -+ private static final Map, BlockStateFactory> FACTORIES_BY_BLOCK_ENTITY_TYPE = new HashMap<>(); -+ private static void register(BlockEntityType type, BlockStateFactory factory) { -+ FACTORIES_BY_BLOCK_ENTITY_TYPE.put(type, factory); -+ } -+ // Paper end - - static { -- register( -- Arrays.asList( -- Material.ACACIA_SIGN, -- Material.ACACIA_WALL_SIGN, -- Material.BAMBOO_SIGN, -- Material.BAMBOO_WALL_SIGN, -- Material.BIRCH_SIGN, -- Material.BIRCH_WALL_SIGN, -- Material.CHERRY_SIGN, -- Material.CHERRY_WALL_SIGN, -- Material.CRIMSON_SIGN, -- Material.CRIMSON_WALL_SIGN, -- Material.DARK_OAK_SIGN, -- Material.DARK_OAK_WALL_SIGN, -- Material.JUNGLE_SIGN, -- Material.JUNGLE_WALL_SIGN, -- Material.MANGROVE_SIGN, -- Material.MANGROVE_WALL_SIGN, -- Material.OAK_SIGN, -- Material.OAK_WALL_SIGN, -- Material.SPRUCE_SIGN, -- Material.SPRUCE_WALL_SIGN, -- Material.WARPED_SIGN, -- Material.WARPED_WALL_SIGN -- ), CraftSign.class, CraftSign::new, SignBlockEntity::new -- ); -- -- register( -- Arrays.asList( -- Material.ACACIA_HANGING_SIGN, -- Material.ACACIA_WALL_HANGING_SIGN, -- Material.BAMBOO_HANGING_SIGN, -- Material.BAMBOO_WALL_HANGING_SIGN, -- Material.BIRCH_HANGING_SIGN, -- Material.BIRCH_WALL_HANGING_SIGN, -- Material.CHERRY_HANGING_SIGN, -- Material.CHERRY_WALL_HANGING_SIGN, -- Material.CRIMSON_HANGING_SIGN, -- Material.CRIMSON_WALL_HANGING_SIGN, -- Material.DARK_OAK_HANGING_SIGN, -- Material.DARK_OAK_WALL_HANGING_SIGN, -- Material.JUNGLE_HANGING_SIGN, -- Material.JUNGLE_WALL_HANGING_SIGN, -- Material.MANGROVE_HANGING_SIGN, -- Material.MANGROVE_WALL_HANGING_SIGN, -- Material.OAK_HANGING_SIGN, -- Material.OAK_WALL_HANGING_SIGN, -- Material.SPRUCE_HANGING_SIGN, -- Material.SPRUCE_WALL_HANGING_SIGN, -- Material.WARPED_HANGING_SIGN, -- Material.WARPED_WALL_HANGING_SIGN -- ), CraftHangingSign.class, CraftHangingSign::new, HangingSignBlockEntity::new -- ); -- -- register( -- Arrays.asList( -- Material.CREEPER_HEAD, -- Material.CREEPER_WALL_HEAD, -- Material.DRAGON_HEAD, -- Material.DRAGON_WALL_HEAD, -- Material.PIGLIN_HEAD, -- Material.PIGLIN_WALL_HEAD, -- Material.PLAYER_HEAD, -- Material.PLAYER_WALL_HEAD, -- Material.SKELETON_SKULL, -- Material.SKELETON_WALL_SKULL, -- Material.WITHER_SKELETON_SKULL, -- Material.WITHER_SKELETON_WALL_SKULL, -- Material.ZOMBIE_HEAD, -- Material.ZOMBIE_WALL_HEAD -- ), CraftSkull.class, CraftSkull::new, SkullBlockEntity::new -- ); -- -- register( -- Arrays.asList( -- Material.COMMAND_BLOCK, -- Material.REPEATING_COMMAND_BLOCK, -- Material.CHAIN_COMMAND_BLOCK -- ), CraftCommandBlock.class, CraftCommandBlock::new, CommandBlockEntity::new -- ); -- -- register( -- Arrays.asList( -- Material.BLACK_BANNER, -- Material.BLACK_WALL_BANNER, -- Material.BLUE_BANNER, -- Material.BLUE_WALL_BANNER, -- Material.BROWN_BANNER, -- Material.BROWN_WALL_BANNER, -- Material.CYAN_BANNER, -- Material.CYAN_WALL_BANNER, -- Material.GRAY_BANNER, -- Material.GRAY_WALL_BANNER, -- Material.GREEN_BANNER, -- Material.GREEN_WALL_BANNER, -- Material.LIGHT_BLUE_BANNER, -- Material.LIGHT_BLUE_WALL_BANNER, -- Material.LIGHT_GRAY_BANNER, -- Material.LIGHT_GRAY_WALL_BANNER, -- Material.LIME_BANNER, -- Material.LIME_WALL_BANNER, -- Material.MAGENTA_BANNER, -- Material.MAGENTA_WALL_BANNER, -- Material.ORANGE_BANNER, -- Material.ORANGE_WALL_BANNER, -- Material.PINK_BANNER, -- Material.PINK_WALL_BANNER, -- Material.PURPLE_BANNER, -- Material.PURPLE_WALL_BANNER, -- Material.RED_BANNER, -- Material.RED_WALL_BANNER, -- Material.WHITE_BANNER, -- Material.WHITE_WALL_BANNER, -- Material.YELLOW_BANNER, -- Material.YELLOW_WALL_BANNER -- ), CraftBanner.class, CraftBanner::new, BannerBlockEntity::new -- ); -- -- register( -- Arrays.asList( -- Material.SHULKER_BOX, -- Material.WHITE_SHULKER_BOX, -- Material.ORANGE_SHULKER_BOX, -- Material.MAGENTA_SHULKER_BOX, -- Material.LIGHT_BLUE_SHULKER_BOX, -- Material.YELLOW_SHULKER_BOX, -- Material.LIME_SHULKER_BOX, -- Material.PINK_SHULKER_BOX, -- Material.GRAY_SHULKER_BOX, -- Material.LIGHT_GRAY_SHULKER_BOX, -- Material.CYAN_SHULKER_BOX, -- Material.PURPLE_SHULKER_BOX, -- Material.BLUE_SHULKER_BOX, -- Material.BROWN_SHULKER_BOX, -- Material.GREEN_SHULKER_BOX, -- Material.RED_SHULKER_BOX, -- Material.BLACK_SHULKER_BOX -- ), CraftShulkerBox.class, CraftShulkerBox::new, ShulkerBoxBlockEntity::new -- ); -- -- register( -- Arrays.asList( -- Material.BLACK_BED, -- Material.BLUE_BED, -- Material.BROWN_BED, -- Material.CYAN_BED, -- Material.GRAY_BED, -- Material.GREEN_BED, -- Material.LIGHT_BLUE_BED, -- Material.LIGHT_GRAY_BED, -- Material.LIME_BED, -- Material.MAGENTA_BED, -- Material.ORANGE_BED, -- Material.PINK_BED, -- Material.PURPLE_BED, -- Material.RED_BED, -- Material.WHITE_BED, -- Material.YELLOW_BED -- ), CraftBed.class, CraftBed::new, BedBlockEntity::new -- ); -- -- register( -- Arrays.asList( -- Material.BEEHIVE, -- Material.BEE_NEST -- ), CraftBeehive.class, CraftBeehive::new, BeehiveBlockEntity::new -- ); -- -- register( -- Arrays.asList( -- Material.CAMPFIRE, -- Material.SOUL_CAMPFIRE -- ), CraftCampfire.class, CraftCampfire::new, CampfireBlockEntity::new -- ); -- -- register(Material.BARREL, CraftBarrel.class, CraftBarrel::new, BarrelBlockEntity::new); -- register(Material.BEACON, CraftBeacon.class, CraftBeacon::new, BeaconBlockEntity::new); -- register(Material.BELL, CraftBell.class, CraftBell::new, BellBlockEntity::new); -- register(Material.BLAST_FURNACE, CraftBlastFurnace.class, CraftBlastFurnace::new, BlastFurnaceBlockEntity::new); -- register(Material.BREWING_STAND, CraftBrewingStand.class, CraftBrewingStand::new, BrewingStandBlockEntity::new); -- register(Material.CHEST, CraftChest.class, CraftChest::new, ChestBlockEntity::new); -- register(Material.CHISELED_BOOKSHELF, CraftChiseledBookshelf.class, CraftChiseledBookshelf::new, ChiseledBookShelfBlockEntity::new); -- register(Material.COMPARATOR, CraftComparator.class, CraftComparator::new, ComparatorBlockEntity::new); -- register(Material.CONDUIT, CraftConduit.class, CraftConduit::new, ConduitBlockEntity::new); -- register(Material.DAYLIGHT_DETECTOR, CraftDaylightDetector.class, CraftDaylightDetector::new, DaylightDetectorBlockEntity::new); -- register(Material.DECORATED_POT, CraftDecoratedPot.class, CraftDecoratedPot::new, DecoratedPotBlockEntity::new); -- register(Material.DISPENSER, CraftDispenser.class, CraftDispenser::new, DispenserBlockEntity::new); -- register(Material.DROPPER, CraftDropper.class, CraftDropper::new, DropperBlockEntity::new); -- register(Material.ENCHANTING_TABLE, CraftEnchantingTable.class, CraftEnchantingTable::new, EnchantingTableBlockEntity::new); -- register(Material.ENDER_CHEST, CraftEnderChest.class, CraftEnderChest::new, EnderChestBlockEntity::new); -- register(Material.END_GATEWAY, CraftEndGateway.class, CraftEndGateway::new, TheEndGatewayBlockEntity::new); -- register(Material.END_PORTAL, CraftEndPortal.class, CraftEndPortal::new, TheEndPortalBlockEntity::new); -- register(Material.FURNACE, CraftFurnaceFurnace.class, CraftFurnaceFurnace::new, FurnaceBlockEntity::new); -- register(Material.HOPPER, CraftHopper.class, CraftHopper::new, HopperBlockEntity::new); -- register(Material.JIGSAW, CraftJigsaw.class, CraftJigsaw::new, JigsawBlockEntity::new); -- register(Material.JUKEBOX, CraftJukebox.class, CraftJukebox::new, JukeboxBlockEntity::new); -- register(Material.LECTERN, CraftLectern.class, CraftLectern::new, LecternBlockEntity::new); -- register(Material.MOVING_PISTON, CraftMovingPiston.class, CraftMovingPiston::new, PistonMovingBlockEntity::new); -- register(Material.SCULK_CATALYST, CraftSculkCatalyst.class, CraftSculkCatalyst::new, SculkCatalystBlockEntity::new); -- register(Material.CALIBRATED_SCULK_SENSOR, CraftCalibratedSculkSensor.class, CraftCalibratedSculkSensor::new, CalibratedSculkSensorBlockEntity::new); -- register(Material.SCULK_SENSOR, CraftSculkSensor.class, CraftSculkSensor::new, SculkSensorBlockEntity::new); -- register(Material.SCULK_SHRIEKER, CraftSculkShrieker.class, CraftSculkShrieker::new, SculkShriekerBlockEntity::new); -- register(Material.SMOKER, CraftSmoker.class, CraftSmoker::new, SmokerBlockEntity::new); -- register(Material.SPAWNER, CraftCreatureSpawner.class, CraftCreatureSpawner::new, SpawnerBlockEntity::new); -- register(Material.STRUCTURE_BLOCK, CraftStructureBlock.class, CraftStructureBlock::new, StructureBlockEntity::new); -- register(Material.SUSPICIOUS_SAND, CraftSuspiciousSand.class, CraftSuspiciousSand::new, BrushableBlockEntity::new); -- register(Material.SUSPICIOUS_GRAVEL, CraftBrushableBlock.class, CraftBrushableBlock::new, BrushableBlockEntity::new); -- register(Material.TRAPPED_CHEST, CraftChest.class, CraftChest::new, TrappedChestBlockEntity::new); -- register(Material.CRAFTER, CraftCrafter.class, CraftCrafter::new, CrafterBlockEntity::new); -- register(Material.TRIAL_SPAWNER, CraftTrialSpawner.class, CraftTrialSpawner::new, TrialSpawnerBlockEntity::new); -- register(Material.VAULT, CraftVault.class, CraftVault::new, VaultBlockEntity::new); -+ // Paper start - simplify -+ register(BlockEntityType.SIGN, CraftSign.class, CraftSign::new); -+ register(BlockEntityType.HANGING_SIGN, CraftHangingSign.class, CraftHangingSign::new); -+ register(BlockEntityType.SKULL, CraftSkull.class, CraftSkull::new); -+ register(BlockEntityType.COMMAND_BLOCK, CraftCommandBlock.class, CraftCommandBlock::new); -+ register(BlockEntityType.BANNER, CraftBanner.class, CraftBanner::new); -+ register(BlockEntityType.SHULKER_BOX, CraftShulkerBox.class, CraftShulkerBox::new); -+ register(BlockEntityType.BED, CraftBed.class, CraftBed::new); -+ register(BlockEntityType.BEEHIVE, CraftBeehive.class, CraftBeehive::new); -+ register(BlockEntityType.CAMPFIRE, CraftCampfire.class, CraftCampfire::new); -+ register(BlockEntityType.BARREL, CraftBarrel.class, CraftBarrel::new); -+ register(BlockEntityType.BEACON, CraftBeacon.class, CraftBeacon::new); -+ register(BlockEntityType.BELL, CraftBell.class, CraftBell::new); -+ register(BlockEntityType.BLAST_FURNACE, CraftBlastFurnace.class, CraftBlastFurnace::new); -+ register(BlockEntityType.BREWING_STAND, CraftBrewingStand.class, CraftBrewingStand::new); -+ register(BlockEntityType.CHEST, CraftChest.class, CraftChest::new); -+ register(BlockEntityType.CHISELED_BOOKSHELF, CraftChiseledBookshelf.class, CraftChiseledBookshelf::new); -+ register(BlockEntityType.COMPARATOR, CraftComparator.class, CraftComparator::new); -+ register(BlockEntityType.CONDUIT, CraftConduit.class, CraftConduit::new); -+ register(BlockEntityType.DAYLIGHT_DETECTOR, CraftDaylightDetector.class, CraftDaylightDetector::new); -+ register(BlockEntityType.DECORATED_POT, CraftDecoratedPot.class, CraftDecoratedPot::new); -+ register(BlockEntityType.DISPENSER, CraftDispenser.class, CraftDispenser::new); -+ register(BlockEntityType.DROPPER, CraftDropper.class, CraftDropper::new); -+ register(BlockEntityType.ENCHANTING_TABLE, CraftEnchantingTable.class, CraftEnchantingTable::new); -+ register(BlockEntityType.ENDER_CHEST, CraftEnderChest.class, CraftEnderChest::new); -+ register(BlockEntityType.END_GATEWAY, CraftEndGateway.class, CraftEndGateway::new); -+ register(BlockEntityType.END_PORTAL, CraftEndPortal.class, CraftEndPortal::new); -+ register(BlockEntityType.FURNACE, CraftFurnaceFurnace.class, CraftFurnaceFurnace::new); -+ register(BlockEntityType.HOPPER, CraftHopper.class, CraftHopper::new); -+ register(BlockEntityType.JIGSAW, CraftJigsaw.class, CraftJigsaw::new); -+ register(BlockEntityType.JUKEBOX, CraftJukebox.class, CraftJukebox::new); -+ register(BlockEntityType.LECTERN, CraftLectern.class, CraftLectern::new); -+ register(BlockEntityType.PISTON, CraftMovingPiston.class, CraftMovingPiston::new); -+ register(BlockEntityType.SCULK_CATALYST, CraftSculkCatalyst.class, CraftSculkCatalyst::new); -+ register(BlockEntityType.SCULK_SENSOR, CraftSculkSensor.class, CraftSculkSensor::new); -+ register(BlockEntityType.SCULK_SHRIEKER, CraftSculkShrieker.class, CraftSculkShrieker::new); -+ register(BlockEntityType.CALIBRATED_SCULK_SENSOR, CraftCalibratedSculkSensor.class, CraftCalibratedSculkSensor::new); -+ register(BlockEntityType.SMOKER, CraftSmoker.class, CraftSmoker::new); -+ register(BlockEntityType.MOB_SPAWNER, CraftCreatureSpawner.class, CraftCreatureSpawner::new); -+ register(BlockEntityType.STRUCTURE_BLOCK, CraftStructureBlock.class, CraftStructureBlock::new); -+ register(BlockEntityType.BRUSHABLE_BLOCK, CraftBrushableBlock.class, CraftBrushableBlock::new); // note: spigot still uses CraftSuspiciousSand impl for that block type -+ register(BlockEntityType.TRAPPED_CHEST, CraftChest.class, CraftChest::new); -+ register(BlockEntityType.CRAFTER, CraftCrafter.class, CraftCrafter::new); -+ register(BlockEntityType.TRIAL_SPAWNER, CraftTrialSpawner.class, CraftTrialSpawner::new); -+ register(BlockEntityType.VAULT, CraftVault.class, CraftVault::new); -+ // Paper end - } - - private static void register(Material blockType, BlockStateFactory factory) { -@@ -347,30 +185,33 @@ public final class CraftBlockStates { - } - - private static > void register( -- Material blockType, -+ net.minecraft.world.level.block.entity.BlockEntityType blockEntityType, // Paper - Class blockStateType, -- BiFunction blockStateConstructor, -- BiFunction tileEntityConstructor -+ BiFunction blockStateConstructor // Paper - ) { -- CraftBlockStates.register(Collections.singletonList(blockType), blockStateType, blockStateConstructor, tileEntityConstructor); -- } -- -- private static > void register( -- List blockTypes, -- Class blockStateType, -- BiFunction blockStateConstructor, -- BiFunction tileEntityConstructor -- ) { -- BlockStateFactory factory = new BlockEntityStateFactory<>(blockStateType, blockStateConstructor, tileEntityConstructor); -- for (Material blockType : blockTypes) { -- CraftBlockStates.register(blockType, factory); -+ // Paper start -+ BlockStateFactory factory = new BlockEntityStateFactory<>(blockStateType, blockStateConstructor, blockEntityType); // Paper -+ for (net.minecraft.world.level.block.Block block : blockEntityType.validBlocks) { -+ CraftBlockStates.register(CraftBlockType.minecraftToBukkit(block), factory); - } -+ CraftBlockStates.register(blockEntityType, factory); -+ // Paper end - } - - private static BlockStateFactory getFactory(Material material) { - return CraftBlockStates.FACTORIES.getOrDefault(material, CraftBlockStates.DEFAULT_FACTORY); - } - -+ // Paper start -+ private static BlockStateFactory getFactory(Material material, BlockEntityType type) { -+ if (type != null) { -+ return CraftBlockStates.FACTORIES_BY_BLOCK_ENTITY_TYPE.getOrDefault(type, getFactory(material)); -+ } else { -+ return getFactory(material); -+ } -+ } -+ // Paper end -+ - public static Class getBlockStateType(Material material) { - Preconditions.checkNotNull(material, "material is null"); - return CraftBlockStates.getFactory(material).blockStateType; -@@ -386,6 +227,13 @@ public final class CraftBlockStates { - return null; - } - -+ // Paper start -+ public static Class getBlockStateType(BlockEntityType blockEntityType) { -+ Preconditions.checkNotNull(blockEntityType, "blockEntityType is null"); -+ return CraftBlockStates.getFactory(null, blockEntityType).blockStateType; -+ } -+ // Paper end -+ - public static BlockState getBlockState(Block block) { - // Paper start - return CraftBlockStates.getBlockState(block, true); -@@ -453,7 +301,7 @@ public final class CraftBlockStates { - if (world != null && tileEntity == null && CraftBlockStates.isTileEntityOptional(material)) { - factory = CraftBlockStates.DEFAULT_FACTORY; - } else { -- factory = CraftBlockStates.getFactory(material); -+ factory = CraftBlockStates.getFactory(material, tileEntity != null ? tileEntity.getType() : null); // Paper - } - return factory.createBlockState(world, blockPosition, blockData, tileEntity); - } -@@ -472,6 +320,14 @@ public final class CraftBlockStates { - return new CraftBlockState(CraftBlock.at(world, pos), flag); - } - -+ // Paper start -+ @Nullable -+ public static BlockEntityType getBlockEntityType(final Material material) { -+ final BlockStateFactory factory = org.bukkit.craftbukkit.block.CraftBlockStates.FACTORIES.get(material); -+ return factory instanceof final BlockEntityStateFactory blockEntityStateFactory ? blockEntityStateFactory.tileEntityConstructor : null; -+ } -+ // Paper end -+ - private CraftBlockStates() { - } - } -diff --git a/src/test/java/org/bukkit/craftbukkit/block/BlockStateTest.java b/src/test/java/org/bukkit/craftbukkit/block/BlockStateTest.java -index c032daa6957df2ad8b621379e415ad925f5cc162..a9810c88e05ebc1af60c051faa45e50ee183924f 100644 ---- a/src/test/java/org/bukkit/craftbukkit/block/BlockStateTest.java -+++ b/src/test/java/org/bukkit/craftbukkit/block/BlockStateTest.java -@@ -7,6 +7,7 @@ import net.minecraft.core.registries.BuiltInRegistries; - import net.minecraft.world.level.block.Block; - import net.minecraft.world.level.block.EntityBlock; - import net.minecraft.world.level.block.entity.BlockEntity; -+import net.minecraft.world.level.block.entity.BlockEntityType; - import org.bukkit.Material; - import org.bukkit.support.environment.AllFeatures; - import org.junit.jupiter.api.Test; -@@ -42,4 +43,13 @@ public class BlockStateTest { - } - } - } -+ -+ // Paper start -+ @Test -+ public void testBlockEntityTypes() { -+ for (var blockEntityType : BuiltInRegistries.BLOCK_ENTITY_TYPE) { -+ org.junit.jupiter.api.Assertions.assertNotNull(CraftBlockStates.getBlockStateType(blockEntityType)); -+ } -+ } -+ // Paper end - } diff --git a/patches/server/0623-Remove-client-side-code-using-deprecated-for-removal.patch b/patches/server/0623-Remove-client-side-code-using-deprecated-for-removal.patch new file mode 100644 index 000000000000..1a9d2657f14f --- /dev/null +++ b/patches/server/0623-Remove-client-side-code-using-deprecated-for-removal.patch @@ -0,0 +1,30 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Jason Penilla <11360596+jpenilla@users.noreply.github.com> +Date: Fri, 26 Nov 2021 15:09:58 -0800 +Subject: [PATCH] Remove client-side code using deprecated for removal + AccessController + +Fixes warnings on build + +diff --git a/src/main/java/net/minecraft/Util.java b/src/main/java/net/minecraft/Util.java +index bf2833c92eca6491699b4a89410e4e46b5bbe4d1..57223285860f61119b6cf348aa78e59384a04e22 100644 +--- a/src/main/java/net/minecraft/Util.java ++++ b/src/main/java/net/minecraft/Util.java +@@ -1082,16 +1082,7 @@ public class Util { + } + + public void openUri(URI uri) { +- try { +- Process process = AccessController.doPrivileged( +- (PrivilegedExceptionAction)(() -> Runtime.getRuntime().exec(this.getOpenUriArguments(uri))) +- ); +- process.getInputStream().close(); +- process.getErrorStream().close(); +- process.getOutputStream().close(); +- } catch (IOException | PrivilegedActionException var3) { +- Util.LOGGER.error("Couldn't open location '{}'", uri, var3); +- } ++ throw new IllegalStateException("This method is not useful on dedicated servers."); // Paper - Fix warnings on build by removing client-only code + } + + public void openFile(File file) { diff --git a/patches/server/0624-Configurable-feature-seeds.patch b/patches/server/0624-Configurable-feature-seeds.patch deleted file mode 100644 index 27997ba7c0ef..000000000000 --- a/patches/server/0624-Configurable-feature-seeds.patch +++ /dev/null @@ -1,40 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Nassim Jahnke -Date: Tue, 31 Aug 2021 17:05:27 +0200 -Subject: [PATCH] Configurable feature seeds - -Co-authored-by: Thonk <30448663+ExcessiveAmountsOfZombies@users.noreply.github.com> - -diff --git a/src/main/java/co/aikar/timings/TimingsExport.java b/src/main/java/co/aikar/timings/TimingsExport.java -index 49028463ba47e760281545c2f7597e3db8d6c453..7620c72a4c243cbeea245203ce03a97cbfa7d922 100644 ---- a/src/main/java/co/aikar/timings/TimingsExport.java -+++ b/src/main/java/co/aikar/timings/TimingsExport.java -@@ -286,7 +286,7 @@ public class TimingsExport extends Thread { - JSONObject object = new JSONObject(); - for (String key : config.getKeys(false)) { - String fullKey = (parentKey != null ? parentKey + "." + key : key); -- if (fullKey.equals("database") || fullKey.equals("settings.bungeecord-addresses") || TimingsManager.hiddenConfigs.contains(fullKey) || key.startsWith("seed-") || key.equals("worldeditregentempworld")) { -+ if (fullKey.equals("database") || fullKey.equals("settings.bungeecord-addresses") || TimingsManager.hiddenConfigs.contains(fullKey) || key.startsWith("seed-") || key.equals("worldeditregentempworld") || key.equals("feature-seeds")) { - continue; - } - final Object val = config.get(key); -diff --git a/src/main/java/net/minecraft/world/level/chunk/ChunkGenerator.java b/src/main/java/net/minecraft/world/level/chunk/ChunkGenerator.java -index 5adc1952504b26772116b55a5144b7704136edfa..c5dd3aac54aa5936da4bd9f54f0e76ecf8141d27 100644 ---- a/src/main/java/net/minecraft/world/level/chunk/ChunkGenerator.java -+++ b/src/main/java/net/minecraft/world/level/chunk/ChunkGenerator.java -@@ -436,7 +436,14 @@ public abstract class ChunkGenerator { - return (String) optional.orElseGet(placedfeature::toString); - }; - -- seededrandom.setFeatureSeed(i, l1, l); -+ // Paper start - Configurable feature seeds; change populationSeed used in random -+ long featurePopulationSeed = i; -+ final long configFeatureSeed = generatoraccessseed.getMinecraftWorld().paperConfig().featureSeeds.features.getLong(placedfeature.feature()); -+ if (configFeatureSeed != -1) { -+ featurePopulationSeed = seededrandom.setDecorationSeed(configFeatureSeed, blockposition.getX(), blockposition.getZ()); // See seededrandom.setDecorationSeed from above -+ } -+ seededrandom.setFeatureSeed(featurePopulationSeed, l1, l); -+ // Paper end - Configurable feature seeds - - try { - generatoraccessseed.setCurrentlyGenerating(supplier1); diff --git a/patches/server/0624-Fix-Spigot-growth-modifiers.patch b/patches/server/0624-Fix-Spigot-growth-modifiers.patch new file mode 100644 index 000000000000..6a24adf1c833 --- /dev/null +++ b/patches/server/0624-Fix-Spigot-growth-modifiers.patch @@ -0,0 +1,141 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Jason Penilla <11360596+jpenilla@users.noreply.github.com> +Date: Fri, 3 Dec 2021 17:09:24 -0800 +Subject: [PATCH] Fix Spigot growth modifiers + +Fixes kelp modifier changing growth for other crops +Also add growth modifiers for glow berries, mangrove propagules, +torchflower crops and pitcher plant crops +Also fix above-mentioned modifiers from having the reverse effect + +Co-authored-by: Jake Potrebic +Co-authored-by: Noah van der Aa +Co-authored-by: Lulu13022002 <41980282+Lulu13022002@users.noreply.github.com> + +diff --git a/src/main/java/net/minecraft/world/level/block/CaveVinesBlock.java b/src/main/java/net/minecraft/world/level/block/CaveVinesBlock.java +index 437be546cf03b6af0856adabfeaf67bc33ff75d6..c4473c2a778116d48bc3e4796afd901f455070e6 100644 +--- a/src/main/java/net/minecraft/world/level/block/CaveVinesBlock.java ++++ b/src/main/java/net/minecraft/world/level/block/CaveVinesBlock.java +@@ -50,9 +50,18 @@ public class CaveVinesBlock extends GrowingPlantHeadBlock implements CaveVines { + return to.setValue(BERRIES, from.getValue(BERRIES)); + } + ++ // Paper start - Fix Spigot growth modifiers ++ @Override ++ protected BlockState getGrowIntoState(BlockState state, RandomSource random, @javax.annotation.Nullable Level level) { ++ final boolean value = random.nextFloat() < (level != null ? (0.11F * (level.spigotConfig.glowBerryModifier / 100.0F)) : 0.11F); ++ return (BlockState) super.getGrowIntoState(state, random).setValue(CaveVinesBlock.BERRIES, value); ++ } ++ // Paper end - Fix Spigot growth modifiers ++ + @Override + protected BlockState getGrowIntoState(BlockState state, RandomSource random) { +- return super.getGrowIntoState(state, random).setValue(BERRIES, Boolean.valueOf(random.nextFloat() < 0.11F)); ++ // Paper start - Fix Spigot growth modifiers ++ return this.getGrowIntoState(state, random, null); + } + + @Override +diff --git a/src/main/java/net/minecraft/world/level/block/CropBlock.java b/src/main/java/net/minecraft/world/level/block/CropBlock.java +index 79ebc37a779bf6cba66a34a7604f819e95fd86a2..1ada5ed825501666addacf527a513ab7bd4a3a58 100644 +--- a/src/main/java/net/minecraft/world/level/block/CropBlock.java ++++ b/src/main/java/net/minecraft/world/level/block/CropBlock.java +@@ -91,6 +91,10 @@ public class CropBlock extends BushBlock implements BonemealableBlock { + modifier = world.spigotConfig.carrotModifier; + } else if (this == Blocks.POTATOES) { + modifier = world.spigotConfig.potatoModifier; ++ // Paper start - Fix Spigot growth modifiers ++ } else if (this == Blocks.TORCHFLOWER_CROP) { ++ modifier = world.spigotConfig.torchFlowerModifier; ++ // Paper end - Fix Spigot growth modifiers + } else { + modifier = world.spigotConfig.wheatModifier; + } +diff --git a/src/main/java/net/minecraft/world/level/block/GrowingPlantHeadBlock.java b/src/main/java/net/minecraft/world/level/block/GrowingPlantHeadBlock.java +index c527f2d8f594d711d047a2a149efe37caaeb0308..9b424d7661fedf8ee1eb9f3167c62e563f04d4d1 100644 +--- a/src/main/java/net/minecraft/world/level/block/GrowingPlantHeadBlock.java ++++ b/src/main/java/net/minecraft/world/level/block/GrowingPlantHeadBlock.java +@@ -60,12 +60,18 @@ public abstract class GrowingPlantHeadBlock extends GrowingPlantBlock implements + BlockPos blockposition1 = pos.relative(this.growthDirection); + + if (this.canGrowInto(world.getBlockState(blockposition1))) { +- org.bukkit.craftbukkit.event.CraftEventFactory.handleBlockSpreadEvent(world, pos, blockposition1, this.getGrowIntoState(state, world.random)); // CraftBukkit ++ org.bukkit.craftbukkit.event.CraftEventFactory.handleBlockSpreadEvent(world, pos, blockposition1, this.getGrowIntoState(state, world.random, world)); // CraftBukkit // Paper - Fix Spigot growth modifiers + } + } + + } + ++ // Paper start - Fix Spigot growth modifiers ++ protected BlockState getGrowIntoState(BlockState state, RandomSource random, @javax.annotation.Nullable Level level) { ++ return this.getGrowIntoState(state, random); ++ } ++ // Paper end - Fix Spigot growth modifiers ++ + protected BlockState getGrowIntoState(BlockState state, RandomSource random) { + return (BlockState) state.cycle(GrowingPlantHeadBlock.AGE); + } +diff --git a/src/main/java/net/minecraft/world/level/block/MangrovePropaguleBlock.java b/src/main/java/net/minecraft/world/level/block/MangrovePropaguleBlock.java +index 10e2b80b0b6010982258548b5084b9591177b558..6b18db68c8b95480992199126c6063ea728d2314 100644 +--- a/src/main/java/net/minecraft/world/level/block/MangrovePropaguleBlock.java ++++ b/src/main/java/net/minecraft/world/level/block/MangrovePropaguleBlock.java +@@ -123,7 +123,7 @@ public class MangrovePropaguleBlock extends SaplingBlock implements SimpleWaterl + @Override + protected void randomTick(BlockState state, ServerLevel world, BlockPos pos, RandomSource random) { + if (!isHanging(state)) { +- if (random.nextInt(7) == 0) { ++ if (random.nextFloat() < (world.spigotConfig.saplingModifier / (100.0F * 7))) { // Paper - Fix Spigot growth modifiers + this.advanceTree(world, pos, state, random); + } + } else { +diff --git a/src/main/java/net/minecraft/world/level/block/PitcherCropBlock.java b/src/main/java/net/minecraft/world/level/block/PitcherCropBlock.java +index 972d8833127090c01d620cab10b3eca3d3601710..ea6135edc76c06b7cd55930b620dfb05f939e4f6 100644 +--- a/src/main/java/net/minecraft/world/level/block/PitcherCropBlock.java ++++ b/src/main/java/net/minecraft/world/level/block/PitcherCropBlock.java +@@ -132,7 +132,7 @@ public class PitcherCropBlock extends DoublePlantBlock implements BonemealableBl + @Override + public void randomTick(BlockState state, ServerLevel world, BlockPos pos, RandomSource random) { + float f = CropBlock.getGrowthSpeed(this, world, pos); +- boolean bl = random.nextInt((int)(25.0F / f) + 1) == 0; ++ boolean bl = random.nextFloat() < (world.spigotConfig.pitcherPlantModifier / (100.0F * (Math.floor(25.0F / f) + 1))); // Paper - Fix Spigot growth modifiers + if (bl) { + this.grow(world, state, pos, 1); + } +diff --git a/src/main/java/org/spigotmc/SpigotWorldConfig.java b/src/main/java/org/spigotmc/SpigotWorldConfig.java +index 491dd55b2870093184a606efabd251c68cc24719..e76f96a5c48d1eda2f9bbb3e11dd79f23f9ab75c 100644 +--- a/src/main/java/org/spigotmc/SpigotWorldConfig.java ++++ b/src/main/java/org/spigotmc/SpigotWorldConfig.java +@@ -96,6 +96,7 @@ public class SpigotWorldConfig + public int beetrootModifier; + public int carrotModifier; + public int potatoModifier; ++ public int torchFlowerModifier; // Paper + public int wheatModifier; + public int wartModifier; + public int vineModifier; +@@ -106,6 +107,8 @@ public class SpigotWorldConfig + public int twistingVinesModifier; + public int weepingVinesModifier; + public int caveVinesModifier; ++ public int glowBerryModifier; // Paper ++ public int pitcherPlantModifier; // Paper + private int getAndValidateGrowth(String crop) + { + int modifier = this.getInt( "growth." + crop.toLowerCase(java.util.Locale.ENGLISH) + "-modifier", 100 ); +@@ -129,6 +132,7 @@ public class SpigotWorldConfig + this.beetrootModifier = this.getAndValidateGrowth( "Beetroot" ); + this.carrotModifier = this.getAndValidateGrowth( "Carrot" ); + this.potatoModifier = this.getAndValidateGrowth( "Potato" ); ++ this.torchFlowerModifier = this.getAndValidateGrowth("TorchFlower"); // Paper + this.wheatModifier = this.getAndValidateGrowth( "Wheat" ); + this.wartModifier = this.getAndValidateGrowth( "NetherWart" ); + this.vineModifier = this.getAndValidateGrowth( "Vine" ); +@@ -139,6 +143,8 @@ public class SpigotWorldConfig + this.twistingVinesModifier = this.getAndValidateGrowth( "TwistingVines" ); + this.weepingVinesModifier = this.getAndValidateGrowth( "WeepingVines" ); + this.caveVinesModifier = this.getAndValidateGrowth( "CaveVines" ); ++ this.glowBerryModifier = this.getAndValidateGrowth("GlowBerry"); // Paper ++ this.pitcherPlantModifier = this.getAndValidateGrowth("PitcherPlant"); // Paper + } + + public double itemMerge; diff --git a/patches/server/0625-Add-root-admin-user-detection.patch b/patches/server/0625-Add-root-admin-user-detection.patch deleted file mode 100644 index abe2f3b5ae69..000000000000 --- a/patches/server/0625-Add-root-admin-user-detection.patch +++ /dev/null @@ -1,62 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: egg82 -Date: Sat, 11 Sep 2021 22:55:14 +0200 -Subject: [PATCH] Add root/admin user detection - -This patch detects whether or not the server is currently executing as a privileged user and spits out a warning. -The warning serves as a sort-of PSA for newer server admins who don't understand the risks of running as root. -We've seen plenty of bad/malicious plugins hit markets, and there's been a few close-calls with exploits in the past. -Hopefully this helps mitigate some potential damage to servers, even if it is just a warning. - -Co-authored-by: Noah van der Aa - -diff --git a/src/main/java/io/papermc/paper/util/ServerEnvironment.java b/src/main/java/io/papermc/paper/util/ServerEnvironment.java -new file mode 100644 -index 0000000000000000000000000000000000000000..68098dfe716e93aafcca4d8d5b5a81d8648b3654 ---- /dev/null -+++ b/src/main/java/io/papermc/paper/util/ServerEnvironment.java -@@ -0,0 +1,23 @@ -+package io.papermc.paper.util; -+ -+import com.sun.security.auth.module.NTSystem; -+import com.sun.security.auth.module.UnixSystem; -+import java.util.Set; -+import org.apache.commons.lang.SystemUtils; -+ -+public class ServerEnvironment { -+ private static final boolean RUNNING_AS_ROOT_OR_ADMIN; -+ private static final String WINDOWS_HIGH_INTEGRITY_LEVEL = "S-1-16-12288"; -+ -+ static { -+ if (SystemUtils.IS_OS_WINDOWS) { -+ RUNNING_AS_ROOT_OR_ADMIN = Set.of(new NTSystem().getGroupIDs()).contains(WINDOWS_HIGH_INTEGRITY_LEVEL); -+ } else { -+ RUNNING_AS_ROOT_OR_ADMIN = new UnixSystem().getUid() == 0; -+ } -+ } -+ -+ public static boolean userIsRootOrAdmin() { -+ return RUNNING_AS_ROOT_OR_ADMIN; -+ } -+} -diff --git a/src/main/java/net/minecraft/server/dedicated/DedicatedServer.java b/src/main/java/net/minecraft/server/dedicated/DedicatedServer.java -index adbd61c41cc30afa89c6ee3544c562b351304a01..585d3e51b4af87327fc2bc64a49f09732a8c61ab 100644 ---- a/src/main/java/net/minecraft/server/dedicated/DedicatedServer.java -+++ b/src/main/java/net/minecraft/server/dedicated/DedicatedServer.java -@@ -196,6 +196,16 @@ public class DedicatedServer extends MinecraftServer implements ServerInterface - DedicatedServer.LOGGER.warn("To start the server with more ram, launch it as \"java -Xmx1024M -Xms1024M -jar minecraft_server.jar\""); - } - -+ // Paper start - detect running as root -+ if (io.papermc.paper.util.ServerEnvironment.userIsRootOrAdmin()) { -+ DedicatedServer.LOGGER.warn("****************************"); -+ DedicatedServer.LOGGER.warn("YOU ARE RUNNING THIS SERVER AS AN ADMINISTRATIVE OR ROOT USER. THIS IS NOT ADVISED."); -+ DedicatedServer.LOGGER.warn("YOU ARE OPENING YOURSELF UP TO POTENTIAL RISKS WHEN DOING THIS."); -+ DedicatedServer.LOGGER.warn("FOR MORE INFORMATION, SEE https://madelinemiller.dev/blog/root-minecraft-server/"); -+ DedicatedServer.LOGGER.warn("****************************"); -+ } -+ // Paper end - detect running as root -+ - DedicatedServer.LOGGER.info("Loading properties"); - DedicatedServerProperties dedicatedserverproperties = this.settings.getProperties(); - diff --git a/patches/server/0630-Prevent-ContainerOpenersCounter-openCount-from-going.patch b/patches/server/0625-Prevent-ContainerOpenersCounter-openCount-from-going.patch similarity index 100% rename from patches/server/0630-Prevent-ContainerOpenersCounter-openCount-from-going.patch rename to patches/server/0625-Prevent-ContainerOpenersCounter-openCount-from-going.patch diff --git a/patches/server/0626-Add-PlayerItemFrameChangeEvent.patch b/patches/server/0626-Add-PlayerItemFrameChangeEvent.patch new file mode 100644 index 000000000000..b7e908ab6eef --- /dev/null +++ b/patches/server/0626-Add-PlayerItemFrameChangeEvent.patch @@ -0,0 +1,61 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: SamB440 +Date: Mon, 15 Nov 2021 18:10:10 +0000 +Subject: [PATCH] Add PlayerItemFrameChangeEvent + + +diff --git a/src/main/java/net/minecraft/world/entity/decoration/ItemFrame.java b/src/main/java/net/minecraft/world/entity/decoration/ItemFrame.java +index c0deabf9d86d086fbd8cbaac5a02badf5c01c870..30af4cbb17148c247a46c0346419d6c838dbc9d2 100644 +--- a/src/main/java/net/minecraft/world/entity/decoration/ItemFrame.java ++++ b/src/main/java/net/minecraft/world/entity/decoration/ItemFrame.java +@@ -1,6 +1,7 @@ + package net.minecraft.world.entity.decoration; + + import javax.annotation.Nullable; ++import io.papermc.paper.event.player.PlayerItemFrameChangeEvent; // Paper - Add PlayerItemFrameChangeEvent + import net.minecraft.core.BlockPos; + import net.minecraft.core.Direction; + import net.minecraft.core.component.DataComponents; +@@ -166,6 +167,13 @@ public class ItemFrame extends HangingEntity { + return true; + } + // CraftBukkit end ++ // Paper start - Add PlayerItemFrameChangeEvent ++ if (source.getEntity() instanceof Player player) { ++ var event = new PlayerItemFrameChangeEvent((org.bukkit.entity.Player) player.getBukkitEntity(), (org.bukkit.entity.ItemFrame) this.getBukkitEntity(), this.getItem().asBukkitCopy(), PlayerItemFrameChangeEvent.ItemFrameChangeAction.REMOVE); ++ if (!event.callEvent()) return true; // return true here because you aren't cancelling the damage, just the change ++ this.setItem(ItemStack.fromBukkitCopy(event.getItemStack()), false); ++ } ++ // Paper end - Add PlayerItemFrameChangeEvent + this.dropItem(world, source.getEntity(), false); + this.gameEvent(GameEvent.BLOCK_CHANGE, source.getEntity()); + this.playSound(this.getRemoveItemSound(), 1.0F, 1.0F); +@@ -403,7 +411,13 @@ public class ItemFrame extends HangingEntity { + if (worldmap != null && worldmap.isTrackedCountOverLimit(256)) { + return InteractionResult.FAIL; + } else { +- this.setItem(itemstack); ++ // Paper start - Add PlayerItemFrameChangeEvent ++ PlayerItemFrameChangeEvent event = new PlayerItemFrameChangeEvent((org.bukkit.entity.Player) player.getBukkitEntity(), (org.bukkit.entity.ItemFrame) this.getBukkitEntity(), itemstack.asBukkitCopy(), PlayerItemFrameChangeEvent.ItemFrameChangeAction.PLACE); ++ if (!event.callEvent()) { ++ return InteractionResult.FAIL; ++ } ++ this.setItem(ItemStack.fromBukkitCopy(event.getItemStack())); ++ // Paper end - Add PlayerItemFrameChangeEvent + this.gameEvent(GameEvent.BLOCK_CHANGE, player); + itemstack.consume(1, player); + return InteractionResult.SUCCESS; +@@ -412,6 +426,13 @@ public class ItemFrame extends HangingEntity { + return InteractionResult.PASS; + } + } else { ++ // Paper start - Add PlayerItemFrameChangeEvent ++ PlayerItemFrameChangeEvent event = new PlayerItemFrameChangeEvent((org.bukkit.entity.Player) player.getBukkitEntity(), (org.bukkit.entity.ItemFrame) this.getBukkitEntity(), this.getItem().asBukkitCopy(), PlayerItemFrameChangeEvent.ItemFrameChangeAction.ROTATE); ++ if (!event.callEvent()) { ++ return InteractionResult.FAIL; ++ } ++ setItem(ItemStack.fromBukkitCopy(event.getItemStack()), false, false); ++ // Paper end - Add PlayerItemFrameChangeEvent + this.playSound(this.getRotateItemSound(), 1.0F, 1.0F); + this.setRotation(this.getRotation() + 1); + this.gameEvent(GameEvent.BLOCK_CHANGE, player); diff --git a/patches/server/0626-don-t-attempt-to-teleport-dead-entities.patch b/patches/server/0626-don-t-attempt-to-teleport-dead-entities.patch deleted file mode 100644 index 2c5a22066fea..000000000000 --- a/patches/server/0626-don-t-attempt-to-teleport-dead-entities.patch +++ /dev/null @@ -1,19 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: sulu5890 -Date: Sun, 24 Oct 2021 22:48:14 -0500 -Subject: [PATCH] don't attempt to teleport dead entities - - -diff --git a/src/main/java/net/minecraft/world/entity/Entity.java b/src/main/java/net/minecraft/world/entity/Entity.java -index e34260c80d19869c9bec5ba67f7ce7db9d13a1f1..35c0591f9d981f1cf7da4d1dea108a8bb3e46c7a 100644 ---- a/src/main/java/net/minecraft/world/entity/Entity.java -+++ b/src/main/java/net/minecraft/world/entity/Entity.java -@@ -705,7 +705,7 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess - // CraftBukkit start - public void postTick() { - // No clean way to break out of ticking once the entity has been copied to a new world, so instead we move the portalling later in the tick cycle -- if (!(this instanceof ServerPlayer)) { -+ if (!(this instanceof ServerPlayer) && this.isAlive()) { // Paper - don't attempt to teleport dead entities - this.handlePortal(); - } - } diff --git a/patches/server/0627-Optimize-HashMapPalette.patch b/patches/server/0627-Optimize-HashMapPalette.patch new file mode 100644 index 000000000000..a490eb0c2093 --- /dev/null +++ b/patches/server/0627-Optimize-HashMapPalette.patch @@ -0,0 +1,57 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: stonar96 +Date: Sun, 17 Jan 2021 01:11:36 +0100 +Subject: [PATCH] Optimize HashMapPalette + +HashMapPalette uses an instance of CrudeIncrementalIntIdentityHashBiMap +internally. A Palette has a preset maximum size = 1 << bits. +CrudeIncrementalIntIdentityHashBiMap has an initial size but is +automatically resized. The CrudeIncrementalIntIdentityHashBiMap is created +with the maximum size in the constructor of HashMapPalette, with the aim +that it doesn't need to be resized anymore. However, there are two things +that I think Mojang hasn't considered here: +1) The CrudeIncrementalIntIdentityHashBiMap is resized, when its initial +size is reached and not the next time, when a further object is added. +2) HashMapPalette adds objects (unnecessarily) before checking if the +initial size of CrudeIncrementalIntIdentityHashBiMap is reached. +This means to actually avoid resize operations in +CrudeIncrementalIntIdentityHashBiMap, one has to add 2 to the initial size +or add 1 and check the size before adding objects. This commit implements +the second approach. Note that this isn't only an optimization but also +makes async reads of Palettes fail-safe. An async read while the +CrudeIncrementalIntIdentityHashBiMap is resized is fatal and can even lead +to corrupted data. This is also something that Anti-Xray is currently +relying on. + +diff --git a/src/main/java/net/minecraft/world/level/chunk/HashMapPalette.java b/src/main/java/net/minecraft/world/level/chunk/HashMapPalette.java +index ac673cb38755852eef37e915f157f6a702117306..98dbeaf8bde15940e5b5d5d1f13fd4bb32f0a10d 100644 +--- a/src/main/java/net/minecraft/world/level/chunk/HashMapPalette.java ++++ b/src/main/java/net/minecraft/world/level/chunk/HashMapPalette.java +@@ -20,7 +20,7 @@ public class HashMapPalette implements Palette { + } + + public HashMapPalette(IdMap idList, int indexBits, PaletteResize listener) { +- this(idList, indexBits, listener, CrudeIncrementalIntIdentityHashBiMap.create(1 << indexBits)); ++ this(idList, indexBits, listener, CrudeIncrementalIntIdentityHashBiMap.create((1 << indexBits) + 1)); // Paper - Perf: Avoid unnecessary resize operation in CrudeIncrementalIntIdentityHashBiMap + } + + private HashMapPalette(IdMap idList, int indexBits, PaletteResize listener, CrudeIncrementalIntIdentityHashBiMap map) { +@@ -38,10 +38,16 @@ public class HashMapPalette implements Palette { + public int idFor(T object) { + int i = this.values.getId(object); + if (i == -1) { +- i = this.values.add(object); +- if (i >= 1 << this.bits) { ++ // Paper start - Perf: Avoid unnecessary resize operation in CrudeIncrementalIntIdentityHashBiMap and optimize ++ // We use size() instead of the result from add(K) ++ // This avoids adding another object unnecessarily ++ // Without this change, + 2 would be required in the constructor ++ if (this.values.size() >= 1 << this.bits) { + i = this.resizeHandler.onResize(this.bits + 1, object); ++ } else { ++ i = this.values.add(object); + } ++ // Paper end - Perf: Avoid unnecessary resize operation in CrudeIncrementalIntIdentityHashBiMap and optimize + } + + return i; diff --git a/patches/server/0627-Prevent-excessive-velocity-through-repeated-crits.patch b/patches/server/0627-Prevent-excessive-velocity-through-repeated-crits.patch deleted file mode 100644 index 10ce91923a79..000000000000 --- a/patches/server/0627-Prevent-excessive-velocity-through-repeated-crits.patch +++ /dev/null @@ -1,41 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Nassim Jahnke -Date: Thu, 25 Nov 2021 10:25:09 +0100 -Subject: [PATCH] Prevent excessive velocity through repeated crits - - -diff --git a/src/main/java/net/minecraft/world/entity/LivingEntity.java b/src/main/java/net/minecraft/world/entity/LivingEntity.java -index 4132b5dfbc6a7693f0bb923a8b14ded571560030..ee7bb1a01bf027eb7b28e3795950a17e5f686815 100644 ---- a/src/main/java/net/minecraft/world/entity/LivingEntity.java -+++ b/src/main/java/net/minecraft/world/entity/LivingEntity.java -@@ -2773,17 +2773,29 @@ public abstract class LivingEntity extends Entity implements Attackable { - return this.hasEffect(MobEffects.JUMP) ? 0.1F * ((float) this.getEffect(MobEffects.JUMP).getAmplifier() + 1.0F) : 0.0F; - } - -+ protected long lastJumpTime = 0L; // Paper - Prevent excessive velocity through repeated crits - @VisibleForTesting - public void jumpFromGround() { - float f = this.getJumpPower(); - - if (f > 1.0E-5F) { - Vec3 vec3d = this.getDeltaMovement(); -+ // Paper start - Prevent excessive velocity through repeated crits -+ long time = System.nanoTime(); -+ boolean canCrit = true; -+ if (this instanceof net.minecraft.world.entity.player.Player) { -+ canCrit = false; -+ if (time - this.lastJumpTime > (long)(0.250e9)) { -+ this.lastJumpTime = time; -+ canCrit = true; -+ } -+ } -+ // Paper end - Prevent excessive velocity through repeated crits - - this.setDeltaMovement(vec3d.x, (double) f, vec3d.z); - if (this.isSprinting()) { - float f1 = this.getYRot() * 0.017453292F; -- -+ if (canCrit) // Paper - Prevent excessive velocity through repeated crits - this.addDeltaMovement(new Vec3((double) (-Mth.sin(f1)) * 0.2D, 0.0D, (double) Mth.cos(f1) * 0.2D)); - } - diff --git a/patches/server/0628-Fix-ChunkSnapshot-isSectionEmpty-int-and-optimize-Pa.patch b/patches/server/0628-Fix-ChunkSnapshot-isSectionEmpty-int-and-optimize-Pa.patch new file mode 100644 index 000000000000..50968cdc5001 --- /dev/null +++ b/patches/server/0628-Fix-ChunkSnapshot-isSectionEmpty-int-and-optimize-Pa.patch @@ -0,0 +1,44 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Jason Penilla <11360596+jpenilla@users.noreply.github.com> +Date: Thu, 9 Dec 2021 00:08:11 -0800 +Subject: [PATCH] Fix ChunkSnapshot#isSectionEmpty(int) and optimize + PalettedContainer copying by not using codecs + + +diff --git a/src/main/java/org/bukkit/craftbukkit/CraftChunk.java b/src/main/java/org/bukkit/craftbukkit/CraftChunk.java +index b2d85abb6c9c725955d972cd6895440849213fdf..887a17a0833064eb5701222e5fb6f5ccf9511588 100644 +--- a/src/main/java/org/bukkit/craftbukkit/CraftChunk.java ++++ b/src/main/java/org/bukkit/craftbukkit/CraftChunk.java +@@ -338,14 +338,17 @@ public class CraftChunk implements Chunk { + PalettedContainerRO>[] biome = (includeBiome || includeBiomeTempRain) ? new PalettedContainer[cs.length] : null; + + Registry iregistry = this.worldServer.registryAccess().lookupOrThrow(Registries.BIOME); +- Codec>> biomeCodec = PalettedContainer.codecRO(iregistry.asHolderIdMap(), iregistry.holderByNameCodec(), PalettedContainer.Strategy.SECTION_BIOMES, iregistry.getOrThrow(Biomes.PLAINS)); + + for (int i = 0; i < cs.length; i++) { +- CompoundTag data = new CompoundTag(); + +- data.put("block_states", SerializableChunkData.BLOCK_STATE_CODEC.encodeStart(NbtOps.INSTANCE, cs[i].getStates()).getOrThrow()); +- sectionBlockIDs[i] = SerializableChunkData.BLOCK_STATE_CODEC.parse(NbtOps.INSTANCE, data.getCompound("block_states")).getOrThrow(SerializableChunkData.ChunkReadException::new); +- sectionEmpty[i] = cs[i].hasOnlyAir(); ++ // Paper start - Fix ChunkSnapshot#isSectionEmpty(int); and remove codec usage ++ sectionEmpty[i] = cs[i].hasOnlyAir(); // fix sectionEmpty array not being filled ++ if (!sectionEmpty[i]) { ++ sectionBlockIDs[i] = cs[i].getStates().copy(); // use copy instead of round tripping with codecs ++ } else { ++ sectionBlockIDs[i] = CraftChunk.emptyBlockIDs; // use cached instance for empty block sections ++ } ++ // Paper end - Fix ChunkSnapshot#isSectionEmpty(int) + + LevelLightEngine lightengine = this.worldServer.getLightEngine(); + DataLayer skyLightArray = lightengine.getLayerListener(LightLayer.SKY).getDataLayerData(SectionPos.of(this.x, chunk.getSectionYFromSectionIndex(i), this.z)); // SPIGOT-7498: Convert section index +@@ -364,8 +367,7 @@ public class CraftChunk implements Chunk { + } + + if (biome != null) { +- data.put("biomes", biomeCodec.encodeStart(NbtOps.INSTANCE, cs[i].getBiomes()).getOrThrow()); +- biome[i] = biomeCodec.parse(NbtOps.INSTANCE, data.getCompound("biomes")).getOrThrow(SerializableChunkData.ChunkReadException::new); ++ biome[i] = ((PalettedContainer>) cs[i].getBiomes()).copy(); // Paper - Perf: use copy instead of round tripping with codecs + } + } + diff --git a/patches/server/0628-Remove-client-side-code-using-deprecated-for-removal.patch b/patches/server/0628-Remove-client-side-code-using-deprecated-for-removal.patch deleted file mode 100644 index 4aed4f1f3a6a..000000000000 --- a/patches/server/0628-Remove-client-side-code-using-deprecated-for-removal.patch +++ /dev/null @@ -1,30 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Jason Penilla <11360596+jpenilla@users.noreply.github.com> -Date: Fri, 26 Nov 2021 15:09:58 -0800 -Subject: [PATCH] Remove client-side code using deprecated for removal - AccessController - -Fixes warnings on build - -diff --git a/src/main/java/net/minecraft/Util.java b/src/main/java/net/minecraft/Util.java -index 4cf88f6d815d60cfbf8e4ecf9d96d0cfadd0620b..42d7ecfab6f72517904451d9df3f0404b176fdb2 100644 ---- a/src/main/java/net/minecraft/Util.java -+++ b/src/main/java/net/minecraft/Util.java -@@ -1002,16 +1002,7 @@ public class Util { - } - - public void openUri(URI uri) { -- try { -- Process process = AccessController.doPrivileged( -- (PrivilegedExceptionAction)(() -> Runtime.getRuntime().exec(this.getOpenUriArguments(uri))) -- ); -- process.getInputStream().close(); -- process.getErrorStream().close(); -- process.getOutputStream().close(); -- } catch (IOException | PrivilegedActionException var3) { -- Util.LOGGER.error("Couldn't open location '{}'", uri, var3); -- } -+ throw new IllegalStateException("This method is not useful on dedicated servers."); // Paper - Fix warnings on build by removing client-only code - } - - public void openFile(File file) { diff --git a/patches/server/0629-Add-more-Campfire-API.patch b/patches/server/0629-Add-more-Campfire-API.patch new file mode 100644 index 000000000000..c490c151c3c1 --- /dev/null +++ b/patches/server/0629-Add-more-Campfire-API.patch @@ -0,0 +1,111 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: LemonCaramel +Date: Fri, 16 Jul 2021 00:39:03 +0900 +Subject: [PATCH] Add more Campfire API + + +diff --git a/src/main/java/net/minecraft/world/level/block/entity/CampfireBlockEntity.java b/src/main/java/net/minecraft/world/level/block/entity/CampfireBlockEntity.java +index 7fa1aea7942a1bc4d9779a9f8ab020ccd5566923..94072a9b65f69dfc3337907f8573081989467662 100644 +--- a/src/main/java/net/minecraft/world/level/block/entity/CampfireBlockEntity.java ++++ b/src/main/java/net/minecraft/world/level/block/entity/CampfireBlockEntity.java +@@ -46,12 +46,14 @@ public class CampfireBlockEntity extends BlockEntity implements Clearable { + private final NonNullList items; + public final int[] cookingProgress; + public final int[] cookingTime; ++ public final boolean[] stopCooking; // Paper - Add more Campfire API + + public CampfireBlockEntity(BlockPos pos, BlockState state) { + super(BlockEntityType.CAMPFIRE, pos, state); + this.items = NonNullList.withSize(4, ItemStack.EMPTY); + this.cookingProgress = new int[4]; + this.cookingTime = new int[4]; ++ this.stopCooking = new boolean[4]; // Paper - Add more Campfire API + } + + public static void cookTick(ServerLevel world, BlockPos pos, BlockState state, CampfireBlockEntity blockEntity, RecipeManager.CachedCheck recipeMatchGetter) { +@@ -62,7 +64,9 @@ public class CampfireBlockEntity extends BlockEntity implements Clearable { + + if (!itemstack.isEmpty()) { + flag = true; ++ if (!blockEntity.stopCooking[i]) { // Paper - Add more Campfire API + int j = blockEntity.cookingProgress[i]++; ++ } // Paper - Add more Campfire API + + if (blockEntity.cookingProgress[i] >= blockEntity.cookingTime[i]) { + SingleRecipeInput singlerecipeinput = new SingleRecipeInput(itemstack); +@@ -175,6 +179,16 @@ public class CampfireBlockEntity extends BlockEntity implements Clearable { + System.arraycopy(aint, 0, this.cookingTime, 0, Math.min(this.cookingTime.length, aint.length)); + } + ++ // Paper start - Add more Campfire API ++ if (nbt.contains("Paper.StopCooking", org.bukkit.craftbukkit.util.CraftMagicNumbers.NBT.TAG_BYTE_ARRAY)) { ++ byte[] abyte = nbt.getByteArray("Paper.StopCooking"); ++ boolean[] cookingState = new boolean[4]; ++ for (int index = 0; index < abyte.length; index++) { ++ cookingState[index] = abyte[index] == 1; ++ } ++ System.arraycopy(cookingState, 0, this.stopCooking, 0, Math.min(this.stopCooking.length, abyte.length)); ++ } ++ // Paper end - Add more Campfire API + } + + @Override +@@ -183,6 +197,13 @@ public class CampfireBlockEntity extends BlockEntity implements Clearable { + ContainerHelper.saveAllItems(nbt, this.items, true, registries); + nbt.putIntArray("CookingTimes", this.cookingProgress); + nbt.putIntArray("CookingTotalTimes", this.cookingTime); ++ // Paper start - Add more Campfire API ++ byte[] cookingState = new byte[4]; ++ for (int index = 0; index < cookingState.length; index++) { ++ cookingState[index] = (byte) (this.stopCooking[index] ? 1 : 0); ++ } ++ nbt.putByteArray("Paper.StopCooking", cookingState); ++ // Paper end - Add more Campfire API + } + + @Override +diff --git a/src/main/java/org/bukkit/craftbukkit/block/CraftCampfire.java b/src/main/java/org/bukkit/craftbukkit/block/CraftCampfire.java +index 4850cbf2c326f1155e04a204abed2d200c02342d..a776bba2ec51c6aecce98a3abceb2c235522d99d 100644 +--- a/src/main/java/org/bukkit/craftbukkit/block/CraftCampfire.java ++++ b/src/main/java/org/bukkit/craftbukkit/block/CraftCampfire.java +@@ -62,4 +62,40 @@ public class CraftCampfire extends CraftBlockEntityState im + public CraftCampfire copy(Location location) { + return new CraftCampfire(this, location); + } ++ ++ // Paper start ++ @Override ++ public void stopCooking() { ++ for (int i = 0; i < getSnapshot().stopCooking.length; ++i) ++ this.stopCooking(i); ++ } ++ ++ @Override ++ public void startCooking() { ++ for (int i = 0; i < getSnapshot().stopCooking.length; ++i) ++ this.startCooking(i); ++ } ++ ++ @Override ++ public boolean stopCooking(int index) { ++ org.apache.commons.lang.Validate.isTrue(-1 < index && index < 4, "Slot index must be between 0 (incl) to 3 (incl)"); ++ boolean previous = this.isCookingDisabled(index); ++ getSnapshot().stopCooking[index] = true; ++ return previous; ++ } ++ ++ @Override ++ public boolean startCooking(int index) { ++ org.apache.commons.lang.Validate.isTrue(-1 < index && index < 4, "Slot index must be between 0 (incl) to 3 (incl)"); ++ boolean previous = this.isCookingDisabled(index); ++ getSnapshot().stopCooking[index] = false; ++ return previous; ++ } ++ ++ @Override ++ public boolean isCookingDisabled(int index) { ++ org.apache.commons.lang.Validate.isTrue(-1 < index && index < 4, "Slot index must be between 0 (incl) to 3 (incl)"); ++ return getSnapshot().stopCooking[index]; ++ } ++ // Paper end + } diff --git a/patches/server/0629-Fix-Spigot-growth-modifiers.patch b/patches/server/0629-Fix-Spigot-growth-modifiers.patch deleted file mode 100644 index 35160b163d08..000000000000 --- a/patches/server/0629-Fix-Spigot-growth-modifiers.patch +++ /dev/null @@ -1,141 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Jason Penilla <11360596+jpenilla@users.noreply.github.com> -Date: Fri, 3 Dec 2021 17:09:24 -0800 -Subject: [PATCH] Fix Spigot growth modifiers - -Fixes kelp modifier changing growth for other crops -Also add growth modifiers for glow berries, mangrove propagules, -torchflower crops and pitcher plant crops -Also fix above-mentioned modifiers from having the reverse effect - -Co-authored-by: Jake Potrebic -Co-authored-by: Noah van der Aa -Co-authored-by: Lulu13022002 <41980282+Lulu13022002@users.noreply.github.com> - -diff --git a/src/main/java/net/minecraft/world/level/block/CaveVinesBlock.java b/src/main/java/net/minecraft/world/level/block/CaveVinesBlock.java -index 1e7235413fae39dc530eb843f1846f74bb5268b6..635fc086d832c641f840cf36d18cdc0fcc3beef3 100644 ---- a/src/main/java/net/minecraft/world/level/block/CaveVinesBlock.java -+++ b/src/main/java/net/minecraft/world/level/block/CaveVinesBlock.java -@@ -50,9 +50,18 @@ public class CaveVinesBlock extends GrowingPlantHeadBlock implements Bonemealabl - return to.setValue(BERRIES, from.getValue(BERRIES)); - } - -+ // Paper start - Fix Spigot growth modifiers -+ @Override -+ protected BlockState getGrowIntoState(BlockState state, RandomSource random, @javax.annotation.Nullable Level level) { -+ final boolean value = random.nextFloat() < (level != null ? (0.11F * (level.spigotConfig.glowBerryModifier / 100.0F)) : 0.11F); -+ return (BlockState) super.getGrowIntoState(state, random).setValue(CaveVinesBlock.BERRIES, value); -+ } -+ // Paper end - Fix Spigot growth modifiers -+ - @Override - protected BlockState getGrowIntoState(BlockState state, RandomSource random) { -- return super.getGrowIntoState(state, random).setValue(BERRIES, Boolean.valueOf(random.nextFloat() < 0.11F)); -+ // Paper start - Fix Spigot growth modifiers -+ return this.getGrowIntoState(state, random, null); - } - - @Override -diff --git a/src/main/java/net/minecraft/world/level/block/CropBlock.java b/src/main/java/net/minecraft/world/level/block/CropBlock.java -index 73595922dcff8e7a8595fcf033ab238bc4096630..112d2feba5f75a2a873b595617780515945c10e4 100644 ---- a/src/main/java/net/minecraft/world/level/block/CropBlock.java -+++ b/src/main/java/net/minecraft/world/level/block/CropBlock.java -@@ -91,6 +91,10 @@ public class CropBlock extends BushBlock implements BonemealableBlock { - modifier = world.spigotConfig.carrotModifier; - } else if (this == Blocks.POTATOES) { - modifier = world.spigotConfig.potatoModifier; -+ // Paper start - Fix Spigot growth modifiers -+ } else if (this == Blocks.TORCHFLOWER_CROP) { -+ modifier = world.spigotConfig.torchFlowerModifier; -+ // Paper end - Fix Spigot growth modifiers - } else { - modifier = world.spigotConfig.wheatModifier; - } -diff --git a/src/main/java/net/minecraft/world/level/block/GrowingPlantHeadBlock.java b/src/main/java/net/minecraft/world/level/block/GrowingPlantHeadBlock.java -index 164a855d99cf36205bd9fce04640109f20a2b96c..cf05da1c86e3018db11dc079bf50317b6639e5cc 100644 ---- a/src/main/java/net/minecraft/world/level/block/GrowingPlantHeadBlock.java -+++ b/src/main/java/net/minecraft/world/level/block/GrowingPlantHeadBlock.java -@@ -60,12 +60,18 @@ public abstract class GrowingPlantHeadBlock extends GrowingPlantBlock implements - BlockPos blockposition1 = pos.relative(this.growthDirection); - - if (this.canGrowInto(world.getBlockState(blockposition1))) { -- org.bukkit.craftbukkit.event.CraftEventFactory.handleBlockSpreadEvent(world, pos, blockposition1, this.getGrowIntoState(state, world.random)); // CraftBukkit -+ org.bukkit.craftbukkit.event.CraftEventFactory.handleBlockSpreadEvent(world, pos, blockposition1, this.getGrowIntoState(state, world.random, world)); // CraftBukkit // Paper - Fix Spigot growth modifiers - } - } - - } - -+ // Paper start - Fix Spigot growth modifiers -+ protected BlockState getGrowIntoState(BlockState state, RandomSource random, @javax.annotation.Nullable Level level) { -+ return this.getGrowIntoState(state, random); -+ } -+ // Paper end - Fix Spigot growth modifiers -+ - protected BlockState getGrowIntoState(BlockState state, RandomSource random) { - return (BlockState) state.cycle(GrowingPlantHeadBlock.AGE); - } -diff --git a/src/main/java/net/minecraft/world/level/block/MangrovePropaguleBlock.java b/src/main/java/net/minecraft/world/level/block/MangrovePropaguleBlock.java -index fd7f7d58cd159f663f8f474d9c73841c795d2990..b98e17d21d43610fb7a7ce1641c518598ff48cb0 100644 ---- a/src/main/java/net/minecraft/world/level/block/MangrovePropaguleBlock.java -+++ b/src/main/java/net/minecraft/world/level/block/MangrovePropaguleBlock.java -@@ -114,7 +114,7 @@ public class MangrovePropaguleBlock extends SaplingBlock implements SimpleWaterl - @Override - protected void randomTick(BlockState state, ServerLevel world, BlockPos pos, RandomSource random) { - if (!isHanging(state)) { -- if (random.nextInt(7) == 0) { -+ if (random.nextFloat() < (world.spigotConfig.saplingModifier / (100.0F * 7))) { // Paper - Fix Spigot growth modifiers - this.advanceTree(world, pos, state, random); - } - } else { -diff --git a/src/main/java/net/minecraft/world/level/block/PitcherCropBlock.java b/src/main/java/net/minecraft/world/level/block/PitcherCropBlock.java -index c9ed129db2cadd0a33d69993961f43088725c3cb..d06e3892cf42723f8e3f621b5497c5348fa1a715 100644 ---- a/src/main/java/net/minecraft/world/level/block/PitcherCropBlock.java -+++ b/src/main/java/net/minecraft/world/level/block/PitcherCropBlock.java -@@ -123,7 +123,7 @@ public class PitcherCropBlock extends DoublePlantBlock implements BonemealableBl - @Override - public void randomTick(BlockState state, ServerLevel world, BlockPos pos, RandomSource random) { - float f = CropBlock.getGrowthSpeed(this, world, pos); -- boolean bl = random.nextInt((int)(25.0F / f) + 1) == 0; -+ boolean bl = random.nextFloat() < (world.spigotConfig.pitcherPlantModifier / (100.0F * (Math.floor(25.0F / f) + 1))); // Paper - Fix Spigot growth modifiers - if (bl) { - this.grow(world, state, pos, 1); - } -diff --git a/src/main/java/org/spigotmc/SpigotWorldConfig.java b/src/main/java/org/spigotmc/SpigotWorldConfig.java -index 491dd55b2870093184a606efabd251c68cc24719..e76f96a5c48d1eda2f9bbb3e11dd79f23f9ab75c 100644 ---- a/src/main/java/org/spigotmc/SpigotWorldConfig.java -+++ b/src/main/java/org/spigotmc/SpigotWorldConfig.java -@@ -96,6 +96,7 @@ public class SpigotWorldConfig - public int beetrootModifier; - public int carrotModifier; - public int potatoModifier; -+ public int torchFlowerModifier; // Paper - public int wheatModifier; - public int wartModifier; - public int vineModifier; -@@ -106,6 +107,8 @@ public class SpigotWorldConfig - public int twistingVinesModifier; - public int weepingVinesModifier; - public int caveVinesModifier; -+ public int glowBerryModifier; // Paper -+ public int pitcherPlantModifier; // Paper - private int getAndValidateGrowth(String crop) - { - int modifier = this.getInt( "growth." + crop.toLowerCase(java.util.Locale.ENGLISH) + "-modifier", 100 ); -@@ -129,6 +132,7 @@ public class SpigotWorldConfig - this.beetrootModifier = this.getAndValidateGrowth( "Beetroot" ); - this.carrotModifier = this.getAndValidateGrowth( "Carrot" ); - this.potatoModifier = this.getAndValidateGrowth( "Potato" ); -+ this.torchFlowerModifier = this.getAndValidateGrowth("TorchFlower"); // Paper - this.wheatModifier = this.getAndValidateGrowth( "Wheat" ); - this.wartModifier = this.getAndValidateGrowth( "NetherWart" ); - this.vineModifier = this.getAndValidateGrowth( "Vine" ); -@@ -139,6 +143,8 @@ public class SpigotWorldConfig - this.twistingVinesModifier = this.getAndValidateGrowth( "TwistingVines" ); - this.weepingVinesModifier = this.getAndValidateGrowth( "WeepingVines" ); - this.caveVinesModifier = this.getAndValidateGrowth( "CaveVines" ); -+ this.glowBerryModifier = this.getAndValidateGrowth("GlowBerry"); // Paper -+ this.pitcherPlantModifier = this.getAndValidateGrowth("PitcherPlant"); // Paper - } - - public double itemMerge; diff --git a/patches/server/0635-Only-write-chunk-data-to-disk-if-it-serializes-witho.patch b/patches/server/0630-Only-write-chunk-data-to-disk-if-it-serializes-witho.patch similarity index 100% rename from patches/server/0635-Only-write-chunk-data-to-disk-if-it-serializes-witho.patch rename to patches/server/0630-Only-write-chunk-data-to-disk-if-it-serializes-witho.patch diff --git a/patches/server/0631-Add-PlayerItemFrameChangeEvent.patch b/patches/server/0631-Add-PlayerItemFrameChangeEvent.patch deleted file mode 100644 index e04772674000..000000000000 --- a/patches/server/0631-Add-PlayerItemFrameChangeEvent.patch +++ /dev/null @@ -1,58 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: SamB440 -Date: Mon, 15 Nov 2021 18:10:10 +0000 -Subject: [PATCH] Add PlayerItemFrameChangeEvent - - -diff --git a/src/main/java/net/minecraft/world/entity/decoration/ItemFrame.java b/src/main/java/net/minecraft/world/entity/decoration/ItemFrame.java -index fdb6898519acfb27baf25d8bbad2013956c1361f..3c6edc5ea44b7ec15d8fc7a2dca95a11a0d6108a 100644 ---- a/src/main/java/net/minecraft/world/entity/decoration/ItemFrame.java -+++ b/src/main/java/net/minecraft/world/entity/decoration/ItemFrame.java -@@ -1,6 +1,7 @@ - package net.minecraft.world.entity.decoration; - - import javax.annotation.Nullable; -+import io.papermc.paper.event.player.PlayerItemFrameChangeEvent; // Paper - Add PlayerItemFrameChangeEvent - import net.minecraft.core.BlockPos; - import net.minecraft.core.Direction; - import net.minecraft.core.component.DataComponents; -@@ -154,6 +155,13 @@ public class ItemFrame extends HangingEntity { - return true; - } - // CraftBukkit end -+ // Paper start - Add PlayerItemFrameChangeEvent -+ if (source.getEntity() instanceof Player player) { -+ var event = new PlayerItemFrameChangeEvent((org.bukkit.entity.Player) player.getBukkitEntity(), (org.bukkit.entity.ItemFrame) this.getBukkitEntity(), this.getItem().asBukkitCopy(), PlayerItemFrameChangeEvent.ItemFrameChangeAction.REMOVE); -+ if (!event.callEvent()) return true; // return true here because you aren't cancelling the damage, just the change -+ this.setItem(ItemStack.fromBukkitCopy(event.getItemStack()), false); -+ } -+ // Paper end - Add PlayerItemFrameChangeEvent - this.dropItem(source.getEntity(), false); - this.gameEvent(GameEvent.BLOCK_CHANGE, source.getEntity()); - this.playSound(this.getRemoveItemSound(), 1.0F, 1.0F); -@@ -394,11 +402,24 @@ public class ItemFrame extends HangingEntity { - } - } - -- this.setItem(itemstack); -+ // Paper start - Add PlayerItemFrameChangeEvent -+ PlayerItemFrameChangeEvent event = new PlayerItemFrameChangeEvent((org.bukkit.entity.Player) player.getBukkitEntity(), (org.bukkit.entity.ItemFrame) this.getBukkitEntity(), itemstack.asBukkitCopy(), PlayerItemFrameChangeEvent.ItemFrameChangeAction.PLACE); -+ if (!event.callEvent()) { -+ return InteractionResult.FAIL; -+ } -+ this.setItem(ItemStack.fromBukkitCopy(event.getItemStack())); -+ // Paper end - Add PlayerItemFrameChangeEvent - this.gameEvent(GameEvent.BLOCK_CHANGE, player); - itemstack.consume(1, player); - } - } else { -+ // Paper start - Add PlayerItemFrameChangeEvent -+ PlayerItemFrameChangeEvent event = new PlayerItemFrameChangeEvent((org.bukkit.entity.Player) player.getBukkitEntity(), (org.bukkit.entity.ItemFrame) this.getBukkitEntity(), this.getItem().asBukkitCopy(), PlayerItemFrameChangeEvent.ItemFrameChangeAction.ROTATE); -+ if (!event.callEvent()) { -+ return InteractionResult.FAIL; -+ } -+ setItem(ItemStack.fromBukkitCopy(event.getItemStack()), false, false); -+ // Paper end - Add PlayerItemFrameChangeEvent - this.playSound(this.getRotateItemSound(), 1.0F, 1.0F); - this.setRotation(this.getRotation() + 1); - this.gameEvent(GameEvent.BLOCK_CHANGE, player); diff --git a/patches/server/0631-Forward-CraftEntity-in-teleport-command.patch b/patches/server/0631-Forward-CraftEntity-in-teleport-command.patch new file mode 100644 index 000000000000..9d0cbe05951d --- /dev/null +++ b/patches/server/0631-Forward-CraftEntity-in-teleport-command.patch @@ -0,0 +1,35 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Jake Potrebic +Date: Sat, 4 Dec 2021 17:04:47 -0800 +Subject: [PATCH] Forward CraftEntity in teleport command + + +diff --git a/src/main/java/net/minecraft/world/entity/Entity.java b/src/main/java/net/minecraft/world/entity/Entity.java +index d6bbefe3d3b21c6fc9be3cfe3ba304226bab6883..06d7aed2539a0f38fabe5b10c91d8da10c43605f 100644 +--- a/src/main/java/net/minecraft/world/entity/Entity.java ++++ b/src/main/java/net/minecraft/world/entity/Entity.java +@@ -3480,6 +3480,13 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess + } + + public void restoreFrom(Entity original) { ++ // Paper start - Forward CraftEntity in teleport command ++ CraftEntity bukkitEntity = original.bukkitEntity; ++ if (bukkitEntity != null) { ++ bukkitEntity.setHandle(this); ++ this.bukkitEntity = bukkitEntity; ++ } ++ // Paper end - Forward CraftEntity in teleport command + CompoundTag nbttagcompound = original.saveWithoutId(new CompoundTag()); + + nbttagcompound.remove("Dimension"); +@@ -3617,8 +3624,8 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess + entity.restoreFrom(this); + this.removeAfterChangingDimensions(); + // CraftBukkit start - Forward the CraftEntity to the new entity +- this.getBukkitEntity().setHandle(entity); +- entity.bukkitEntity = this.getBukkitEntity(); ++ //this.getBukkitEntity().setHandle(entity); ++ //entity.bukkitEntity = this.getBukkitEntity(); // Paper - forward CraftEntity in teleport command; moved to Entity#restoreFrom + // CraftBukkit end + entity.teleportSetPosition(PositionMoveRotation.of(teleportTarget), teleportTarget.relatives()); + if (this.inWorld) world.addDuringTeleport(entity); // CraftBukkit - Don't spawn the new entity if the current entity isn't spawned diff --git a/patches/server/0637-Improve-scoreboard-entries.patch b/patches/server/0632-Improve-scoreboard-entries.patch similarity index 100% rename from patches/server/0637-Improve-scoreboard-entries.patch rename to patches/server/0632-Improve-scoreboard-entries.patch diff --git a/patches/server/0632-Optimize-HashMapPalette.patch b/patches/server/0632-Optimize-HashMapPalette.patch deleted file mode 100644 index d50159b901f9..000000000000 --- a/patches/server/0632-Optimize-HashMapPalette.patch +++ /dev/null @@ -1,57 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: stonar96 -Date: Sun, 17 Jan 2021 01:11:36 +0100 -Subject: [PATCH] Optimize HashMapPalette - -HashMapPalette uses an instance of CrudeIncrementalIntIdentityHashBiMap -internally. A Palette has a preset maximum size = 1 << bits. -CrudeIncrementalIntIdentityHashBiMap has an initial size but is -automatically resized. The CrudeIncrementalIntIdentityHashBiMap is created -with the maximum size in the constructor of HashMapPalette, with the aim -that it doesn't need to be resized anymore. However, there are two things -that I think Mojang hasn't considered here: -1) The CrudeIncrementalIntIdentityHashBiMap is resized, when its initial -size is reached and not the next time, when a further object is added. -2) HashMapPalette adds objects (unnecessarily) before checking if the -initial size of CrudeIncrementalIntIdentityHashBiMap is reached. -This means to actually avoid resize operations in -CrudeIncrementalIntIdentityHashBiMap, one has to add 2 to the initial size -or add 1 and check the size before adding objects. This commit implements -the second approach. Note that this isn't only an optimization but also -makes async reads of Palettes fail-safe. An async read while the -CrudeIncrementalIntIdentityHashBiMap is resized is fatal and can even lead -to corrupted data. This is also something that Anti-Xray is currently -relying on. - -diff --git a/src/main/java/net/minecraft/world/level/chunk/HashMapPalette.java b/src/main/java/net/minecraft/world/level/chunk/HashMapPalette.java -index f80cde264393f3606bc0c54ba2fd6a467f4bcb5a..c5e1040c239874dcf20b79472bf690ee7f0a9e5f 100644 ---- a/src/main/java/net/minecraft/world/level/chunk/HashMapPalette.java -+++ b/src/main/java/net/minecraft/world/level/chunk/HashMapPalette.java -@@ -20,7 +20,7 @@ public class HashMapPalette implements Palette { - } - - public HashMapPalette(IdMap idList, int indexBits, PaletteResize listener) { -- this(idList, indexBits, listener, CrudeIncrementalIntIdentityHashBiMap.create(1 << indexBits)); -+ this(idList, indexBits, listener, CrudeIncrementalIntIdentityHashBiMap.create((1 << indexBits) + 1)); // Paper - Perf: Avoid unnecessary resize operation in CrudeIncrementalIntIdentityHashBiMap - } - - private HashMapPalette(IdMap idList, int indexBits, PaletteResize listener, CrudeIncrementalIntIdentityHashBiMap map) { -@@ -38,10 +38,16 @@ public class HashMapPalette implements Palette { - public int idFor(T object) { - int i = this.values.getId(object); - if (i == -1) { -- i = this.values.add(object); -- if (i >= 1 << this.bits) { -+ // Paper start - Perf: Avoid unnecessary resize operation in CrudeIncrementalIntIdentityHashBiMap and optimize -+ // We use size() instead of the result from add(K) -+ // This avoids adding another object unnecessarily -+ // Without this change, + 2 would be required in the constructor -+ if (this.values.size() >= 1 << this.bits) { - i = this.resizeHandler.onResize(this.bits + 1, object); -+ } else { -+ i = this.values.add(object); - } -+ // Paper end - Perf: Avoid unnecessary resize operation in CrudeIncrementalIntIdentityHashBiMap and optimize - } - - return i; diff --git a/patches/server/0633-Entity-powdered-snow-API.patch b/patches/server/0633-Entity-powdered-snow-API.patch new file mode 100644 index 000000000000..09c93986c1fd --- /dev/null +++ b/patches/server/0633-Entity-powdered-snow-API.patch @@ -0,0 +1,42 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Jake Potrebic +Date: Sun, 24 Oct 2021 20:58:43 -0700 +Subject: [PATCH] Entity powdered snow API + +== AT == +public net.minecraft.world.entity.monster.Skeleton inPowderSnowTime + +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java +index 1ed01978611cddb2558e441863dadc468bc07c3d..9693656418210957000add9b6670c4bee67f8462 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java +@@ -1100,6 +1100,13 @@ public abstract class CraftEntity implements org.bukkit.entity.Entity { + } + // Paper end - raw entity serialization API + ++ // Paper start - entity powdered snow API ++ @Override ++ public boolean isInPowderedSnow() { ++ return getHandle().isInPowderSnow || getHandle().wasInPowderSnow; // depending on the location in the entity "tick" either could be needed. ++ } ++ // Paper end - entity powdered snow API ++ + // Paper start - missing entity api + @Override + public boolean isInvisible() { // Paper - moved up from LivingEntity +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftSkeleton.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftSkeleton.java +index a0ea54181de6c6685deef265cbe9f66aabbca42b..6f98da9be6aef35e3b5c940188b872459a383c8e 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftSkeleton.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftSkeleton.java +@@ -45,4 +45,11 @@ public class CraftSkeleton extends CraftAbstractSkeleton implements Skeleton { + public SkeletonType getSkeletonType() { + return SkeletonType.NORMAL; + } ++ ++ // Paper start ++ @Override ++ public int inPowderedSnowTime() { ++ return getHandle().inPowderSnowTime; ++ } ++ // Paper end + } diff --git a/patches/server/0633-Fix-ChunkSnapshot-isSectionEmpty-int-and-optimize-Pa.patch b/patches/server/0633-Fix-ChunkSnapshot-isSectionEmpty-int-and-optimize-Pa.patch deleted file mode 100644 index 8f7f6be0804c..000000000000 --- a/patches/server/0633-Fix-ChunkSnapshot-isSectionEmpty-int-and-optimize-Pa.patch +++ /dev/null @@ -1,44 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Jason Penilla <11360596+jpenilla@users.noreply.github.com> -Date: Thu, 9 Dec 2021 00:08:11 -0800 -Subject: [PATCH] Fix ChunkSnapshot#isSectionEmpty(int) and optimize - PalettedContainer copying by not using codecs - - -diff --git a/src/main/java/org/bukkit/craftbukkit/CraftChunk.java b/src/main/java/org/bukkit/craftbukkit/CraftChunk.java -index f1d5c2d423dc015cc7720a4544370895f3cc644b..d6eab2a0fdbafc35efa7ed5b404357391565f4f3 100644 ---- a/src/main/java/org/bukkit/craftbukkit/CraftChunk.java -+++ b/src/main/java/org/bukkit/craftbukkit/CraftChunk.java -@@ -338,14 +338,17 @@ public class CraftChunk implements Chunk { - PalettedContainerRO>[] biome = (includeBiome || includeBiomeTempRain) ? new PalettedContainer[cs.length] : null; - - Registry iregistry = this.worldServer.registryAccess().registryOrThrow(Registries.BIOME); -- Codec>> biomeCodec = PalettedContainer.codecRO(iregistry.asHolderIdMap(), iregistry.holderByNameCodec(), PalettedContainer.Strategy.SECTION_BIOMES, iregistry.getHolderOrThrow(Biomes.PLAINS)); - - for (int i = 0; i < cs.length; i++) { -- CompoundTag data = new CompoundTag(); - -- data.put("block_states", ChunkSerializer.BLOCK_STATE_CODEC.encodeStart(NbtOps.INSTANCE, cs[i].getStates()).getOrThrow()); -- sectionBlockIDs[i] = ChunkSerializer.BLOCK_STATE_CODEC.parse(NbtOps.INSTANCE, data.getCompound("block_states")).getOrThrow(ChunkSerializer.ChunkReadException::new); -- sectionEmpty[i] = cs[i].hasOnlyAir(); -+ // Paper start - Fix ChunkSnapshot#isSectionEmpty(int); and remove codec usage -+ sectionEmpty[i] = cs[i].hasOnlyAir(); // fix sectionEmpty array not being filled -+ if (!sectionEmpty[i]) { -+ sectionBlockIDs[i] = cs[i].getStates().copy(); // use copy instead of round tripping with codecs -+ } else { -+ sectionBlockIDs[i] = CraftChunk.emptyBlockIDs; // use cached instance for empty block sections -+ } -+ // Paper end - Fix ChunkSnapshot#isSectionEmpty(int) - - LevelLightEngine lightengine = this.worldServer.getLightEngine(); - DataLayer skyLightArray = lightengine.getLayerListener(LightLayer.SKY).getDataLayerData(SectionPos.of(this.x, chunk.getSectionYFromSectionIndex(i), this.z)); // SPIGOT-7498: Convert section index -@@ -364,8 +367,7 @@ public class CraftChunk implements Chunk { - } - - if (biome != null) { -- data.put("biomes", biomeCodec.encodeStart(NbtOps.INSTANCE, cs[i].getBiomes()).getOrThrow()); -- biome[i] = biomeCodec.parse(NbtOps.INSTANCE, data.getCompound("biomes")).getOrThrow(ChunkSerializer.ChunkReadException::new); -+ biome[i] = ((PalettedContainer>) cs[i].getBiomes()).copy(); // Paper - Perf: use copy instead of round tripping with codecs - } - } - diff --git a/patches/server/0639-Add-API-for-item-entity-health.patch b/patches/server/0634-Add-API-for-item-entity-health.patch similarity index 100% rename from patches/server/0639-Add-API-for-item-entity-health.patch rename to patches/server/0634-Add-API-for-item-entity-health.patch diff --git a/patches/server/0634-Add-more-Campfire-API.patch b/patches/server/0634-Add-more-Campfire-API.patch deleted file mode 100644 index 012ca1cc7d17..000000000000 --- a/patches/server/0634-Add-more-Campfire-API.patch +++ /dev/null @@ -1,112 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: LemonCaramel -Date: Fri, 16 Jul 2021 00:39:03 +0900 -Subject: [PATCH] Add more Campfire API - - -diff --git a/src/main/java/net/minecraft/world/level/block/entity/CampfireBlockEntity.java b/src/main/java/net/minecraft/world/level/block/entity/CampfireBlockEntity.java -index 0c20a334be4b1c4cf7999826f8d9bff5e36bc2b8..6d58a30a588ec98bc343ab0ab82be82d9cf0618c 100644 ---- a/src/main/java/net/minecraft/world/level/block/entity/CampfireBlockEntity.java -+++ b/src/main/java/net/minecraft/world/level/block/entity/CampfireBlockEntity.java -@@ -46,6 +46,7 @@ public class CampfireBlockEntity extends BlockEntity implements Clearable { - public final int[] cookingProgress; - public final int[] cookingTime; - private final RecipeManager.CachedCheck quickCheck; -+ public final boolean[] stopCooking; // Paper - Add more Campfire API - - public CampfireBlockEntity(BlockPos pos, BlockState state) { - super(BlockEntityType.CAMPFIRE, pos, state); -@@ -53,6 +54,7 @@ public class CampfireBlockEntity extends BlockEntity implements Clearable { - this.cookingProgress = new int[4]; - this.cookingTime = new int[4]; - this.quickCheck = RecipeManager.createCheck(RecipeType.CAMPFIRE_COOKING); -+ this.stopCooking = new boolean[4]; // Paper - Add more Campfire API - } - - public static void cookTick(Level world, BlockPos pos, BlockState state, CampfireBlockEntity campfire) { -@@ -63,7 +65,9 @@ public class CampfireBlockEntity extends BlockEntity implements Clearable { - - if (!itemstack.isEmpty()) { - flag = true; -+ if (!campfire.stopCooking[i]) { // Paper - Add more Campfire API - int j = campfire.cookingProgress[i]++; -+ } // Paper - Add more Campfire API - - if (campfire.cookingProgress[i] >= campfire.cookingTime[i]) { - SingleRecipeInput singlerecipeinput = new SingleRecipeInput(itemstack); -@@ -176,6 +180,16 @@ public class CampfireBlockEntity extends BlockEntity implements Clearable { - System.arraycopy(aint, 0, this.cookingTime, 0, Math.min(this.cookingTime.length, aint.length)); - } - -+ // Paper start - Add more Campfire API -+ if (nbt.contains("Paper.StopCooking", org.bukkit.craftbukkit.util.CraftMagicNumbers.NBT.TAG_BYTE_ARRAY)) { -+ byte[] abyte = nbt.getByteArray("Paper.StopCooking"); -+ boolean[] cookingState = new boolean[4]; -+ for (int index = 0; index < abyte.length; index++) { -+ cookingState[index] = abyte[index] == 1; -+ } -+ System.arraycopy(cookingState, 0, this.stopCooking, 0, Math.min(this.stopCooking.length, abyte.length)); -+ } -+ // Paper end - Add more Campfire API - } - - @Override -@@ -184,6 +198,13 @@ public class CampfireBlockEntity extends BlockEntity implements Clearable { - ContainerHelper.saveAllItems(nbt, this.items, true, registryLookup); - nbt.putIntArray("CookingTimes", this.cookingProgress); - nbt.putIntArray("CookingTotalTimes", this.cookingTime); -+ // Paper start - Add more Campfire API -+ byte[] cookingState = new byte[4]; -+ for (int index = 0; index < cookingState.length; index++) { -+ cookingState[index] = (byte) (this.stopCooking[index] ? 1 : 0); -+ } -+ nbt.putByteArray("Paper.StopCooking", cookingState); -+ // Paper end - Add more Campfire API - } - - @Override -diff --git a/src/main/java/org/bukkit/craftbukkit/block/CraftCampfire.java b/src/main/java/org/bukkit/craftbukkit/block/CraftCampfire.java -index 4850cbf2c326f1155e04a204abed2d200c02342d..a776bba2ec51c6aecce98a3abceb2c235522d99d 100644 ---- a/src/main/java/org/bukkit/craftbukkit/block/CraftCampfire.java -+++ b/src/main/java/org/bukkit/craftbukkit/block/CraftCampfire.java -@@ -62,4 +62,40 @@ public class CraftCampfire extends CraftBlockEntityState im - public CraftCampfire copy(Location location) { - return new CraftCampfire(this, location); - } -+ -+ // Paper start -+ @Override -+ public void stopCooking() { -+ for (int i = 0; i < getSnapshot().stopCooking.length; ++i) -+ this.stopCooking(i); -+ } -+ -+ @Override -+ public void startCooking() { -+ for (int i = 0; i < getSnapshot().stopCooking.length; ++i) -+ this.startCooking(i); -+ } -+ -+ @Override -+ public boolean stopCooking(int index) { -+ org.apache.commons.lang.Validate.isTrue(-1 < index && index < 4, "Slot index must be between 0 (incl) to 3 (incl)"); -+ boolean previous = this.isCookingDisabled(index); -+ getSnapshot().stopCooking[index] = true; -+ return previous; -+ } -+ -+ @Override -+ public boolean startCooking(int index) { -+ org.apache.commons.lang.Validate.isTrue(-1 < index && index < 4, "Slot index must be between 0 (incl) to 3 (incl)"); -+ boolean previous = this.isCookingDisabled(index); -+ getSnapshot().stopCooking[index] = false; -+ return previous; -+ } -+ -+ @Override -+ public boolean isCookingDisabled(int index) { -+ org.apache.commons.lang.Validate.isTrue(-1 < index && index < 4, "Slot index must be between 0 (incl) to 3 (incl)"); -+ return getSnapshot().stopCooking[index]; -+ } -+ // Paper end - } diff --git a/patches/server/0635-Configurable-max-block-light-for-monster-spawning.patch b/patches/server/0635-Configurable-max-block-light-for-monster-spawning.patch new file mode 100644 index 000000000000..8c19486a5f33 --- /dev/null +++ b/patches/server/0635-Configurable-max-block-light-for-monster-spawning.patch @@ -0,0 +1,19 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Nassim Jahnke +Date: Thu, 16 Dec 2021 09:40:39 +0100 +Subject: [PATCH] Configurable max block light for monster spawning + + +diff --git a/src/main/java/net/minecraft/world/entity/monster/Monster.java b/src/main/java/net/minecraft/world/entity/monster/Monster.java +index c49853f25bdf1fbc7ec9700d421c6ddccabae05f..e2de074bbe7bab0e5a7aecc1fae4c5914a203dd4 100644 +--- a/src/main/java/net/minecraft/world/entity/monster/Monster.java ++++ b/src/main/java/net/minecraft/world/entity/monster/Monster.java +@@ -92,7 +92,7 @@ public abstract class Monster extends PathfinderMob implements Enemy { + return false; + } else { + DimensionType dimensionType = world.dimensionType(); +- int i = dimensionType.monsterSpawnBlockLightLimit(); ++ int i = world.getLevel().paperConfig().entities.spawning.monsterSpawnMaxLightLevel.or(dimensionType.monsterSpawnBlockLightLimit()); // Paper - Configurable max block light for monster spawning + if (i < 15 && world.getBrightness(LightLayer.BLOCK, pos) > i) { + return false; + } else { diff --git a/patches/server/0636-Fix-sticky-pistons-and-BlockPistonRetractEvent.patch b/patches/server/0636-Fix-sticky-pistons-and-BlockPistonRetractEvent.patch new file mode 100644 index 000000000000..b239d74e25a7 --- /dev/null +++ b/patches/server/0636-Fix-sticky-pistons-and-BlockPistonRetractEvent.patch @@ -0,0 +1,85 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Jake Potrebic +Date: Wed, 22 Dec 2021 09:51:48 -0800 +Subject: [PATCH] Fix sticky pistons and BlockPistonRetractEvent + +There is an explicit check in the handling code for empty pistons that +prevents sticky pistons from firing the event. However when we look back +at the history we see that this check was originally added so that ONLY +sticky pistons would fire the retract event. I'm not sure why. +https://hub.spigotmc.org/stash/projects/SPIGOT/repos/craftbukkit/commits/1092acbddf07edfa4100bc6824504ac75088e913 + +Over the course of several updates, the meaning of that field appears to +have changed from "is NOT sticky" to "is sticky". So now its having the +opposite effect. Only normal pistons fire the retraction event. And like +all things in CB, it's just been carried around since. + +If we are to believe the history, the correct fix for this issue is to +flip it so it only fires for sticky pistons, but that puts us in a +bind. It's already firing for non-sticky pistons, changing it now would +likely result in breakage. Furthermore, there is little documentation as +to WHY that was ever intended to be the case. + +Instead we opt to remove the check entirely so that the event fires for +all piston types. + +Co-authored-by: Zach Brown +Co-authored-by: Madeline Miller + +diff --git a/src/main/java/net/minecraft/world/level/block/piston/PistonBaseBlock.java b/src/main/java/net/minecraft/world/level/block/piston/PistonBaseBlock.java +index 4973d75b26880e39d42b5ef533896f43a1f07cba..e841fccb8f298ef692677583b468869f56dc722c 100644 +--- a/src/main/java/net/minecraft/world/level/block/piston/PistonBaseBlock.java ++++ b/src/main/java/net/minecraft/world/level/block/piston/PistonBaseBlock.java +@@ -163,15 +163,15 @@ public class PistonBaseBlock extends DirectionalBlock { + } + + // CraftBukkit start +- if (!this.isSticky) { +- org.bukkit.block.Block block = world.getWorld().getBlockAt(pos.getX(), pos.getY(), pos.getZ()); +- BlockPistonRetractEvent event = new BlockPistonRetractEvent(block, ImmutableList.of(), CraftBlock.notchToBlockFace(enumdirection)); +- world.getCraftServer().getPluginManager().callEvent(event); +- +- if (event.isCancelled()) { +- return; +- } +- } ++ // if (!this.isSticky) { // Paper - Fix sticky pistons and BlockPistonRetractEvent; Move further down ++ // org.bukkit.block.Block block = world.getWorld().getBlockAt(pos.getX(), pos.getY(), pos.getZ()); ++ // BlockPistonRetractEvent event = new BlockPistonRetractEvent(block, ImmutableList.of(), CraftBlock.notchToBlockFace(enumdirection)); ++ // world.getCraftServer().getPluginManager().callEvent(event); ++ // ++ // if (event.isCancelled()) { ++ // return; ++ // } ++ // } + // PAIL: checkME - what happened to setTypeAndData? + // CraftBukkit end + world.blockEvent(pos, this, b0, enumdirection.get3DDataValue()); +@@ -248,6 +248,13 @@ public class PistonBaseBlock extends DirectionalBlock { + + BlockState iblockdata2 = (BlockState) ((BlockState) Blocks.MOVING_PISTON.defaultBlockState().setValue(MovingPistonBlock.FACING, enumdirection)).setValue(MovingPistonBlock.TYPE, this.isSticky ? PistonType.STICKY : PistonType.DEFAULT); + ++ // Paper start - Fix sticky pistons and BlockPistonRetractEvent; Move empty piston retract call to fix multiple event fires ++ if (!this.isSticky) { ++ if (!new BlockPistonRetractEvent(CraftBlock.at(world, pos), java.util.Collections.emptyList(), CraftBlock.notchToBlockFace(enumdirection)).callEvent()) { ++ return false; ++ } ++ } ++ // Paper end - Fix sticky pistons and BlockPistonRetractEvent + world.setBlock(pos, iblockdata2, 20); + world.setBlockEntity(MovingPistonBlock.newMovingBlockEntity(pos, iblockdata2, (BlockState) this.defaultBlockState().setValue(PistonBaseBlock.FACING, Direction.from3DDataValue(data & 7)), enumdirection, false, true)); + world.blockUpdated(pos, iblockdata2.getBlock()); +@@ -274,6 +281,13 @@ public class PistonBaseBlock extends DirectionalBlock { + if (type == 1 && !iblockdata3.isAir() && PistonBaseBlock.isPushable(iblockdata3, world, blockposition1, enumdirection.getOpposite(), false, enumdirection) && (iblockdata3.getPistonPushReaction() == PushReaction.NORMAL || iblockdata3.is(Blocks.PISTON) || iblockdata3.is(Blocks.STICKY_PISTON))) { + this.moveBlocks(world, pos, enumdirection, false); + } else { ++ // Paper start - Fix sticky pistons and BlockPistonRetractEvent; fire BlockPistonRetractEvent for sticky pistons retracting nothing (air) ++ if (type == TRIGGER_CONTRACT && iblockdata2.isAir()) { ++ if (!new BlockPistonRetractEvent(CraftBlock.at(world, pos), java.util.Collections.emptyList(), CraftBlock.notchToBlockFace(enumdirection)).callEvent()) { ++ return false; ++ } ++ } ++ // Paper end - Fix sticky pistons and BlockPistonRetractEvent + world.removeBlock(pos.relative(enumdirection), false); + } + } diff --git a/patches/server/0636-Forward-CraftEntity-in-teleport-command.patch b/patches/server/0636-Forward-CraftEntity-in-teleport-command.patch deleted file mode 100644 index 9557e79f3618..000000000000 --- a/patches/server/0636-Forward-CraftEntity-in-teleport-command.patch +++ /dev/null @@ -1,35 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Jake Potrebic -Date: Sat, 4 Dec 2021 17:04:47 -0800 -Subject: [PATCH] Forward CraftEntity in teleport command - - -diff --git a/src/main/java/net/minecraft/world/entity/Entity.java b/src/main/java/net/minecraft/world/entity/Entity.java -index 0d98dcfeab1455931219e498072f7129016f6678..56ce703a3b33e0530eb86389f8290d109d321589 100644 ---- a/src/main/java/net/minecraft/world/entity/Entity.java -+++ b/src/main/java/net/minecraft/world/entity/Entity.java -@@ -3353,6 +3353,13 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess - } - - public void restoreFrom(Entity original) { -+ // Paper start - Forward CraftEntity in teleport command -+ CraftEntity bukkitEntity = original.bukkitEntity; -+ if (bukkitEntity != null) { -+ bukkitEntity.setHandle(this); -+ this.bukkitEntity = bukkitEntity; -+ } -+ // Paper end - Forward CraftEntity in teleport command - CompoundTag nbttagcompound = original.saveWithoutId(new CompoundTag()); - - nbttagcompound.remove("Dimension"); -@@ -3441,8 +3448,8 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess - entity2.restoreFrom(this); - this.removeAfterChangingDimensions(); - // CraftBukkit start - Forward the CraftEntity to the new entity -- this.getBukkitEntity().setHandle(entity2); -- entity2.bukkitEntity = this.getBukkitEntity(); -+ // this.getBukkitEntity().setHandle(entity2); // Paper - forward CraftEntity in teleport command; moved to Entity#restoreFrom -+ // entity2.bukkitEntity = this.getBukkitEntity(); - // CraftBukkit end - } - diff --git a/patches/server/0637-Expose-isFuel-and-canSmelt-methods-to-FurnaceInvento.patch b/patches/server/0637-Expose-isFuel-and-canSmelt-methods-to-FurnaceInvento.patch new file mode 100644 index 000000000000..cfaa29570688 --- /dev/null +++ b/patches/server/0637-Expose-isFuel-and-canSmelt-methods-to-FurnaceInvento.patch @@ -0,0 +1,32 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: BillyGalbreath +Date: Thu, 23 Dec 2021 15:32:50 -0600 +Subject: [PATCH] Expose isFuel and canSmelt methods to FurnaceInventory + + +diff --git a/src/main/java/org/bukkit/craftbukkit/inventory/CraftInventoryFurnace.java b/src/main/java/org/bukkit/craftbukkit/inventory/CraftInventoryFurnace.java +index 29a8cd7667860c4598a556e6ef3af39c731683db..33c970b467675429ad952925830ed334632fd3b6 100644 +--- a/src/main/java/org/bukkit/craftbukkit/inventory/CraftInventoryFurnace.java ++++ b/src/main/java/org/bukkit/craftbukkit/inventory/CraftInventoryFurnace.java +@@ -40,6 +40,21 @@ public class CraftInventoryFurnace extends CraftInventory implements FurnaceInve + this.setItem(0, stack); + } + ++ // Paper start ++ @Override ++ public boolean isFuel(ItemStack stack) { ++ net.minecraft.server.level.ServerLevel world = ((org.bukkit.craftbukkit.CraftWorld) org.bukkit.Bukkit.getWorlds().get(0)).getHandle(); ++ return stack != null && !stack.getType().isEmpty() && world.fuelValues().isFuel(CraftItemStack.asNMSCopy(stack)); ++ } ++ ++ @Override ++ public boolean canSmelt(ItemStack stack) { ++ // data packs are always loaded in the main world ++ net.minecraft.server.level.ServerLevel world = ((org.bukkit.craftbukkit.CraftWorld) org.bukkit.Bukkit.getWorlds().get(0)).getHandle(); ++ return stack != null && !stack.getType().isEmpty() && world.recipeAccess().getRecipeFor(((AbstractFurnaceBlockEntity) this.inventory).recipeType, new net.minecraft.world.item.crafting.SingleRecipeInput(CraftItemStack.asNMSCopy(stack)), world).isPresent(); ++ } ++ // Paper end ++ + @Override + public Furnace getHolder() { + return (Furnace) this.inventory.getOwner(); diff --git a/patches/server/0643-Bucketable-API.patch b/patches/server/0638-Bucketable-API.patch similarity index 100% rename from patches/server/0643-Bucketable-API.patch rename to patches/server/0638-Bucketable-API.patch diff --git a/patches/server/0638-Entity-powdered-snow-API.patch b/patches/server/0638-Entity-powdered-snow-API.patch deleted file mode 100644 index 8cba17f5719f..000000000000 --- a/patches/server/0638-Entity-powdered-snow-API.patch +++ /dev/null @@ -1,42 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Jake Potrebic -Date: Sun, 24 Oct 2021 20:58:43 -0700 -Subject: [PATCH] Entity powdered snow API - -== AT == -public net.minecraft.world.entity.monster.Skeleton inPowderSnowTime - -diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java -index 6fab713531665298d3b03e7960a17ecb1471a6d7..4ed5647101bbace0005b1ebfb824e4aed48e43cb 100644 ---- a/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java -+++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java -@@ -1099,6 +1099,13 @@ public abstract class CraftEntity implements org.bukkit.entity.Entity { - } - // Paper end - raw entity serialization API - -+ // Paper start - entity powdered snow API -+ @Override -+ public boolean isInPowderedSnow() { -+ return getHandle().isInPowderSnow || getHandle().wasInPowderSnow; // depending on the location in the entity "tick" either could be needed. -+ } -+ // Paper end - entity powdered snow API -+ - // Paper start - missing entity api - @Override - public boolean isInvisible() { // Paper - moved up from LivingEntity -diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftSkeleton.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftSkeleton.java -index a0ea54181de6c6685deef265cbe9f66aabbca42b..6f98da9be6aef35e3b5c940188b872459a383c8e 100644 ---- a/src/main/java/org/bukkit/craftbukkit/entity/CraftSkeleton.java -+++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftSkeleton.java -@@ -45,4 +45,11 @@ public class CraftSkeleton extends CraftAbstractSkeleton implements Skeleton { - public SkeletonType getSkeletonType() { - return SkeletonType.NORMAL; - } -+ -+ // Paper start -+ @Override -+ public int inPowderedSnowTime() { -+ return getHandle().inPowderSnowTime; -+ } -+ // Paper end - } diff --git a/patches/server/0639-Validate-usernames.patch b/patches/server/0639-Validate-usernames.patch new file mode 100644 index 000000000000..1845ec8d6014 --- /dev/null +++ b/patches/server/0639-Validate-usernames.patch @@ -0,0 +1,76 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Spottedleaf +Date: Sat, 1 Jan 2022 05:19:37 -0800 +Subject: [PATCH] Validate usernames + + +diff --git a/src/main/java/net/minecraft/server/network/ServerLoginPacketListenerImpl.java b/src/main/java/net/minecraft/server/network/ServerLoginPacketListenerImpl.java +index 1e4b288f20153ce0c91fabf164c5c8320c90ba7d..cb5dd77892283a1aaec45434fb99bb7f08ee5394 100644 +--- a/src/main/java/net/minecraft/server/network/ServerLoginPacketListenerImpl.java ++++ b/src/main/java/net/minecraft/server/network/ServerLoginPacketListenerImpl.java +@@ -90,6 +90,7 @@ public class ServerLoginPacketListenerImpl implements ServerLoginPacketListener, + private final String serverId; + private final boolean transferred; + private ServerPlayer player; // CraftBukkit ++ public boolean iKnowThisMayNotBeTheBestIdeaButPleaseDisableUsernameValidation = false; // Paper - username validation overriding + + public ServerLoginPacketListenerImpl(MinecraftServer server, Connection connection, boolean transferred) { + this.state = ServerLoginPacketListenerImpl.State.HELLO; +@@ -171,7 +172,13 @@ public class ServerLoginPacketListenerImpl implements ServerLoginPacketListener, + @Override + public void handleHello(ServerboundHelloPacket packet) { + Validate.validState(this.state == ServerLoginPacketListenerImpl.State.HELLO, "Unexpected hello packet", new Object[0]); +- Validate.validState(StringUtil.isValidPlayerName(packet.name()), "Invalid characters in username", new Object[0]); ++ // Paper start - Validate usernames ++ if (io.papermc.paper.configuration.GlobalConfiguration.get().proxies.isProxyOnlineMode() ++ && io.papermc.paper.configuration.GlobalConfiguration.get().unsupportedSettings.performUsernameValidation ++ && !this.iKnowThisMayNotBeTheBestIdeaButPleaseDisableUsernameValidation) { ++ Validate.validState(StringUtil.isReasonablePlayerName(packet.name()), "Invalid characters in username", new Object[0]); ++ } ++ // Paper end - Validate usernames + this.requestedUsername = packet.name(); + GameProfile gameprofile = this.server.getSingleplayerProfile(); + +diff --git a/src/main/java/net/minecraft/server/players/PlayerList.java b/src/main/java/net/minecraft/server/players/PlayerList.java +index a62858d3cd83358f2356c15fc102b99c41969a74..bb301d1ce9c1792ead2681c9f3d38bb2079b0112 100644 +--- a/src/main/java/net/minecraft/server/players/PlayerList.java ++++ b/src/main/java/net/minecraft/server/players/PlayerList.java +@@ -627,7 +627,7 @@ public abstract class PlayerList { + + for (int i = 0; i < this.players.size(); ++i) { + entityplayer = (ServerPlayer) this.players.get(i); +- if (entityplayer.getUUID().equals(uuid)) { ++ if (entityplayer.getUUID().equals(uuid) || (io.papermc.paper.configuration.GlobalConfiguration.get().proxies.isProxyOnlineMode() && entityplayer.getGameProfile().getName().equalsIgnoreCase(gameprofile.getName()))) { // Paper - validate usernames + list.add(entityplayer); + } + } +diff --git a/src/main/java/net/minecraft/util/StringUtil.java b/src/main/java/net/minecraft/util/StringUtil.java +index e588bd7ef0616dc88ce4c0feeeabadc29dcaa550..6c33002dc8bbb3759c3156302ab7d1f26ce5e8ee 100644 +--- a/src/main/java/net/minecraft/util/StringUtil.java ++++ b/src/main/java/net/minecraft/util/StringUtil.java +@@ -67,6 +67,25 @@ public class StringUtil { + return name.length() <= 16 && name.chars().filter(c -> c <= 32 || c >= 127).findAny().isEmpty(); + } + ++ // Paper start - Username validation ++ public static boolean isReasonablePlayerName(final String name) { ++ if (name.isEmpty() || name.length() > 16) { ++ return false; ++ } ++ ++ for (int i = 0, len = name.length(); i < len; ++i) { ++ final char c = name.charAt(i); ++ if ((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || (c >= '0' && c <= '9') || (c == '_' || c == '.')) { ++ continue; ++ } ++ ++ return false; ++ } ++ ++ return true; ++ } ++ // Paper end - Username validation ++ + public static String filterText(String string) { + return filterText(string, false); + } diff --git a/patches/server/0640-Configurable-max-block-light-for-monster-spawning.patch b/patches/server/0640-Configurable-max-block-light-for-monster-spawning.patch deleted file mode 100644 index d0fb0003dcf9..000000000000 --- a/patches/server/0640-Configurable-max-block-light-for-monster-spawning.patch +++ /dev/null @@ -1,19 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Nassim Jahnke -Date: Thu, 16 Dec 2021 09:40:39 +0100 -Subject: [PATCH] Configurable max block light for monster spawning - - -diff --git a/src/main/java/net/minecraft/world/entity/monster/Monster.java b/src/main/java/net/minecraft/world/entity/monster/Monster.java -index f73604d762efbac400d40f536ec1782fca584efa..e7bfce0534c7ef3a1480a1082ae8514caf78778b 100644 ---- a/src/main/java/net/minecraft/world/entity/monster/Monster.java -+++ b/src/main/java/net/minecraft/world/entity/monster/Monster.java -@@ -91,7 +91,7 @@ public abstract class Monster extends PathfinderMob implements Enemy { - return false; - } else { - DimensionType dimensionType = world.dimensionType(); -- int i = dimensionType.monsterSpawnBlockLightLimit(); -+ int i = world.getLevel().paperConfig().entities.spawning.monsterSpawnMaxLightLevel.or(dimensionType.monsterSpawnBlockLightLimit()); // Paper - Configurable max block light for monster spawning - if (i < 15 && world.getBrightness(LightLayer.BLOCK, pos) > i) { - return false; - } else { diff --git a/patches/server/0640-Make-water-animal-spawn-height-configurable.patch b/patches/server/0640-Make-water-animal-spawn-height-configurable.patch new file mode 100644 index 000000000000..341b55b1f650 --- /dev/null +++ b/patches/server/0640-Make-water-animal-spawn-height-configurable.patch @@ -0,0 +1,21 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Brokkonaut +Date: Sat, 18 Dec 2021 08:26:55 +0100 +Subject: [PATCH] Make water animal spawn height configurable + + +diff --git a/src/main/java/net/minecraft/world/entity/animal/WaterAnimal.java b/src/main/java/net/minecraft/world/entity/animal/WaterAnimal.java +index 6a8a90059dcc524a0724264c8d604bf39228a650..8c4532a250f8679d729a35c17e9b5bd339264450 100644 +--- a/src/main/java/net/minecraft/world/entity/animal/WaterAnimal.java ++++ b/src/main/java/net/minecraft/world/entity/animal/WaterAnimal.java +@@ -70,6 +70,10 @@ public abstract class WaterAnimal extends PathfinderMob { + ) { + int i = world.getSeaLevel(); + int j = i - 13; ++ // Paper start - Make water animal spawn height configurable ++ i = world.getMinecraftWorld().paperConfig().entities.spawning.wateranimalSpawnHeight.maximum.or(i); ++ j = world.getMinecraftWorld().paperConfig().entities.spawning.wateranimalSpawnHeight.minimum.or(j); ++ // Paper end - Make water animal spawn height configurable + return pos.getY() >= j && pos.getY() <= i && world.getFluidState(pos.below()).is(FluidTags.WATER) && world.getBlockState(pos.above()).is(Blocks.WATER); + } + } diff --git a/patches/server/0641-Expose-vanilla-BiomeProvider-from-WorldInfo.patch b/patches/server/0641-Expose-vanilla-BiomeProvider-from-WorldInfo.patch new file mode 100644 index 000000000000..b09fd4e69919 --- /dev/null +++ b/patches/server/0641-Expose-vanilla-BiomeProvider-from-WorldInfo.patch @@ -0,0 +1,177 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Jason Penilla <11360596+jpenilla@users.noreply.github.com> +Date: Thu, 6 Jan 2022 15:59:06 -0800 +Subject: [PATCH] Expose vanilla BiomeProvider from WorldInfo + + +diff --git a/src/main/java/net/minecraft/server/MinecraftServer.java b/src/main/java/net/minecraft/server/MinecraftServer.java +index 7ab9a9c04f94a68dccb5ec9bfc8171bfc02927a2..8cbc8eeaa8719f8bb136543e80ec85248c90e154 100644 +--- a/src/main/java/net/minecraft/server/MinecraftServer.java ++++ b/src/main/java/net/minecraft/server/MinecraftServer.java +@@ -626,7 +626,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop list = ImmutableList.of(new PhantomSpawner(), new PatrolSpawner(), new CatSpawner(), new VillageSiege(), new WanderingTraderSpawner(iworlddataserver)); + LevelStem worlddimension = (LevelStem) dimensions.getValue(dimensionKey); + +- org.bukkit.generator.WorldInfo worldInfo = new org.bukkit.craftbukkit.generator.CraftWorldInfo(iworlddataserver, worldSession, org.bukkit.World.Environment.getEnvironment(dimension), worlddimension.type().value()); ++ org.bukkit.generator.WorldInfo worldInfo = new org.bukkit.craftbukkit.generator.CraftWorldInfo(iworlddataserver, worldSession, org.bukkit.World.Environment.getEnvironment(dimension), worlddimension.type().value(), worlddimension.generator(), this.registryAccess()); // Paper - Expose vanilla BiomeProvider from WorldInfo + if (biomeProvider == null && gen != null) { + biomeProvider = gen.getDefaultBiomeProvider(worldInfo); + } +diff --git a/src/main/java/net/minecraft/server/level/ServerLevel.java b/src/main/java/net/minecraft/server/level/ServerLevel.java +index 3b3024fcf39266cc6ae61fb77dbffb391dc96c92..2d77e9526917a83987ae0486a669538d5417b781 100644 +--- a/src/main/java/net/minecraft/server/level/ServerLevel.java ++++ b/src/main/java/net/minecraft/server/level/ServerLevel.java +@@ -357,7 +357,7 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe + this.serverLevelData.setWorld(this); + + if (biomeProvider != null) { +- BiomeSource worldChunkManager = new CustomWorldChunkManager(this.getWorld(), biomeProvider, this.server.registryAccess().lookupOrThrow(Registries.BIOME)); ++ BiomeSource worldChunkManager = new CustomWorldChunkManager(this.getWorld(), biomeProvider, this.server.registryAccess().lookupOrThrow(Registries.BIOME), chunkgenerator.getBiomeSource()); // Paper - add vanillaBiomeProvider + if (chunkgenerator instanceof NoiseBasedChunkGenerator cga) { + chunkgenerator = new NoiseBasedChunkGenerator(worldChunkManager, cga.settings); + } else if (chunkgenerator instanceof FlatLevelSource cpf) { +diff --git a/src/main/java/org/bukkit/craftbukkit/CraftServer.java b/src/main/java/org/bukkit/craftbukkit/CraftServer.java +index 1c72862b3167acc05f06b44cd9a0929ad8d2b9c8..6d32505266fef119289bcf6761c1948368238eb9 100644 +--- a/src/main/java/org/bukkit/craftbukkit/CraftServer.java ++++ b/src/main/java/org/bukkit/craftbukkit/CraftServer.java +@@ -1310,7 +1310,7 @@ public final class CraftServer implements Server { + List list = ImmutableList.of(new PhantomSpawner(), new PatrolSpawner(), new CatSpawner(), new VillageSiege(), new WanderingTraderSpawner(worlddata)); + LevelStem worlddimension = iregistry.getValue(actualDimension); + +- WorldInfo worldInfo = new CraftWorldInfo(worlddata, worldSession, creator.environment(), worlddimension.type().value()); ++ WorldInfo worldInfo = new CraftWorldInfo(worlddata, worldSession, creator.environment(), worlddimension.type().value(), worlddimension.generator(), this.getHandle().getServer().registryAccess()); // Paper - Expose vanilla BiomeProvider from WorldInfo + if (biomeProvider == null && generator != null) { + biomeProvider = generator.getDefaultBiomeProvider(worldInfo); + } +diff --git a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java +index 5050c9a9d5369a829f5b47a56b7621de05c9a6de..0ff12d4e74c14b2b0ecf06d720c5e06edb35fcdb 100644 +--- a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java ++++ b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java +@@ -216,6 +216,39 @@ public class CraftWorld extends CraftRegionAccessor implements World { + public int getPlayerCount() { + return world.players().size(); + } ++ ++ @Override ++ public BiomeProvider vanillaBiomeProvider() { ++ net.minecraft.server.level.ServerChunkCache serverCache = this.getHandle().chunkSource; ++ ++ final net.minecraft.world.level.chunk.ChunkGenerator gen = serverCache.getGenerator(); ++ net.minecraft.world.level.biome.BiomeSource biomeSource; ++ if (gen instanceof org.bukkit.craftbukkit.generator.CustomChunkGenerator custom) { ++ biomeSource = custom.getDelegate().getBiomeSource(); ++ } else { ++ biomeSource = gen.getBiomeSource(); ++ } ++ if (biomeSource instanceof org.bukkit.craftbukkit.generator.CustomWorldChunkManager customBiomeSource) { ++ biomeSource = customBiomeSource.vanillaBiomeSource; ++ } ++ final net.minecraft.world.level.biome.BiomeSource finalBiomeSource = biomeSource; ++ final net.minecraft.world.level.biome.Climate.Sampler sampler = serverCache.randomState().sampler(); ++ ++ final List possibleBiomes = finalBiomeSource.possibleBiomes().stream() ++ .map(CraftBiome::minecraftHolderToBukkit) ++ .toList(); ++ return new BiomeProvider() { ++ @Override ++ public Biome getBiome(final org.bukkit.generator.WorldInfo worldInfo, final int x, final int y, final int z) { ++ return CraftBiome.minecraftHolderToBukkit(finalBiomeSource.getNoiseBiome(x >> 2, y >> 2, z >> 2, sampler)); ++ } ++ ++ @Override ++ public List getBiomes(final org.bukkit.generator.WorldInfo worldInfo) { ++ return possibleBiomes; ++ } ++ }; ++ } + // Paper end + + private static final Random rand = new Random(); +diff --git a/src/main/java/org/bukkit/craftbukkit/generator/CraftWorldInfo.java b/src/main/java/org/bukkit/craftbukkit/generator/CraftWorldInfo.java +index 5d655d6cd3e23e0287069f8bdf77601487e862fd..c81455a4ee9a3185f125ebf8cec325f4ed2e501d 100644 +--- a/src/main/java/org/bukkit/craftbukkit/generator/CraftWorldInfo.java ++++ b/src/main/java/org/bukkit/craftbukkit/generator/CraftWorldInfo.java +@@ -17,8 +17,14 @@ public class CraftWorldInfo implements WorldInfo { + private final long seed; + private final int minHeight; + private final int maxHeight; ++ // Paper start ++ private final net.minecraft.world.level.chunk.ChunkGenerator vanillaChunkGenerator; ++ private final net.minecraft.core.RegistryAccess.Frozen registryAccess; + +- public CraftWorldInfo(ServerLevelData worldDataServer, LevelStorageSource.LevelStorageAccess session, World.Environment environment, DimensionType dimensionManager) { ++ public CraftWorldInfo(PrimaryLevelData worldDataServer, LevelStorageSource.LevelStorageAccess session, World.Environment environment, DimensionType dimensionManager, net.minecraft.world.level.chunk.ChunkGenerator chunkGenerator, net.minecraft.core.RegistryAccess.Frozen registryAccess) { ++ this.registryAccess = registryAccess; ++ this.vanillaChunkGenerator = chunkGenerator; ++ // Paper end + this.name = worldDataServer.getLevelName(); + this.uuid = WorldUUID.getUUID(session.levelDirectory.path().toFile()); + this.environment = environment; +@@ -27,15 +33,6 @@ public class CraftWorldInfo implements WorldInfo { + this.maxHeight = dimensionManager.minY() + dimensionManager.height(); + } + +- public CraftWorldInfo(String name, UUID uuid, World.Environment environment, long seed, int minHeight, int maxHeight) { +- this.name = name; +- this.uuid = uuid; +- this.environment = environment; +- this.seed = seed; +- this.minHeight = minHeight; +- this.maxHeight = maxHeight; +- } +- + @Override + public String getName() { + return this.name; +@@ -65,4 +62,34 @@ public class CraftWorldInfo implements WorldInfo { + public int getMaxHeight() { + return this.maxHeight; + } ++ ++ // Paper start ++ @Override ++ public org.bukkit.generator.BiomeProvider vanillaBiomeProvider() { ++ final net.minecraft.world.level.levelgen.RandomState randomState; ++ if (vanillaChunkGenerator instanceof net.minecraft.world.level.levelgen.NoiseBasedChunkGenerator noiseBasedChunkGenerator) { ++ randomState = net.minecraft.world.level.levelgen.RandomState.create(noiseBasedChunkGenerator.generatorSettings().value(), ++ registryAccess.lookupOrThrow(net.minecraft.core.registries.Registries.NOISE), getSeed()); ++ } else { ++ randomState = net.minecraft.world.level.levelgen.RandomState.create(net.minecraft.world.level.levelgen.NoiseGeneratorSettings.dummy(), ++ registryAccess.lookupOrThrow(net.minecraft.core.registries.Registries.NOISE), getSeed()); ++ } ++ ++ final java.util.List possibleBiomes = CraftWorldInfo.this.vanillaChunkGenerator.getBiomeSource().possibleBiomes().stream() ++ .map(biome -> org.bukkit.craftbukkit.block.CraftBiome.minecraftHolderToBukkit(biome)) ++ .toList(); ++ return new org.bukkit.generator.BiomeProvider() { ++ @Override ++ public org.bukkit.block.Biome getBiome(final WorldInfo worldInfo, final int x, final int y, final int z) { ++ return org.bukkit.craftbukkit.block.CraftBiome.minecraftHolderToBukkit( ++ CraftWorldInfo.this.vanillaChunkGenerator.getBiomeSource().getNoiseBiome(x >> 2, y >> 2, z >> 2, randomState.sampler())); ++ } ++ ++ @Override ++ public java.util.List getBiomes(final org.bukkit.generator.WorldInfo worldInfo) { ++ return possibleBiomes; ++ } ++ }; ++ } ++ // Paper end + } +diff --git a/src/main/java/org/bukkit/craftbukkit/generator/CustomWorldChunkManager.java b/src/main/java/org/bukkit/craftbukkit/generator/CustomWorldChunkManager.java +index 0063c4c17d05a77adf81164fb9307a29860cbe12..0bac128d6faff0063b03f595b82deea78d1ae161 100644 +--- a/src/main/java/org/bukkit/craftbukkit/generator/CustomWorldChunkManager.java ++++ b/src/main/java/org/bukkit/craftbukkit/generator/CustomWorldChunkManager.java +@@ -31,7 +31,11 @@ public class CustomWorldChunkManager extends BiomeSource { + return biomeBases; + } + +- public CustomWorldChunkManager(WorldInfo worldInfo, BiomeProvider biomeProvider, Registry registry) { ++ // Paper start - add vanillaBiomeProvider ++ public final BiomeSource vanillaBiomeSource; ++ public CustomWorldChunkManager(WorldInfo worldInfo, BiomeProvider biomeProvider, Registry registry, BiomeSource vanillaBiomeSource) { ++ this.vanillaBiomeSource = vanillaBiomeSource; ++ // Paper end - add vanillaBiomeProvider + this.worldInfo = worldInfo; + this.biomeProvider = biomeProvider; + this.registry = registry; diff --git a/patches/server/0641-Fix-sticky-pistons-and-BlockPistonRetractEvent.patch b/patches/server/0641-Fix-sticky-pistons-and-BlockPistonRetractEvent.patch deleted file mode 100644 index 2ee001a77ae1..000000000000 --- a/patches/server/0641-Fix-sticky-pistons-and-BlockPistonRetractEvent.patch +++ /dev/null @@ -1,85 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Jake Potrebic -Date: Wed, 22 Dec 2021 09:51:48 -0800 -Subject: [PATCH] Fix sticky pistons and BlockPistonRetractEvent - -There is an explicit check in the handling code for empty pistons that -prevents sticky pistons from firing the event. However when we look back -at the history we see that this check was originally added so that ONLY -sticky pistons would fire the retract event. I'm not sure why. -https://hub.spigotmc.org/stash/projects/SPIGOT/repos/craftbukkit/commits/1092acbddf07edfa4100bc6824504ac75088e913 - -Over the course of several updates, the meaning of that field appears to -have changed from "is NOT sticky" to "is sticky". So now its having the -opposite effect. Only normal pistons fire the retraction event. And like -all things in CB, it's just been carried around since. - -If we are to believe the history, the correct fix for this issue is to -flip it so it only fires for sticky pistons, but that puts us in a -bind. It's already firing for non-sticky pistons, changing it now would -likely result in breakage. Furthermore, there is little documentation as -to WHY that was ever intended to be the case. - -Instead we opt to remove the check entirely so that the event fires for -all piston types. - -Co-authored-by: Zach Brown -Co-authored-by: Madeline Miller - -diff --git a/src/main/java/net/minecraft/world/level/block/piston/PistonBaseBlock.java b/src/main/java/net/minecraft/world/level/block/piston/PistonBaseBlock.java -index 2f2c9fb65d4cc8bd40303216e03c5c1956305ff4..e6bfbe2588e0c2a1be14e38d654e889d392ad4db 100644 ---- a/src/main/java/net/minecraft/world/level/block/piston/PistonBaseBlock.java -+++ b/src/main/java/net/minecraft/world/level/block/piston/PistonBaseBlock.java -@@ -160,15 +160,15 @@ public class PistonBaseBlock extends DirectionalBlock { - } - - // CraftBukkit start -- if (!this.isSticky) { -- org.bukkit.block.Block block = world.getWorld().getBlockAt(pos.getX(), pos.getY(), pos.getZ()); -- BlockPistonRetractEvent event = new BlockPistonRetractEvent(block, ImmutableList.of(), CraftBlock.notchToBlockFace(enumdirection)); -- world.getCraftServer().getPluginManager().callEvent(event); -- -- if (event.isCancelled()) { -- return; -- } -- } -+ // if (!this.isSticky) { // Paper - Fix sticky pistons and BlockPistonRetractEvent; Move further down -+ // org.bukkit.block.Block block = world.getWorld().getBlockAt(pos.getX(), pos.getY(), pos.getZ()); -+ // BlockPistonRetractEvent event = new BlockPistonRetractEvent(block, ImmutableList.of(), CraftBlock.notchToBlockFace(enumdirection)); -+ // world.getCraftServer().getPluginManager().callEvent(event); -+ // -+ // if (event.isCancelled()) { -+ // return; -+ // } -+ // } - // PAIL: checkME - what happened to setTypeAndData? - // CraftBukkit end - world.blockEvent(pos, this, b0, enumdirection.get3DDataValue()); -@@ -245,6 +245,13 @@ public class PistonBaseBlock extends DirectionalBlock { - - BlockState iblockdata2 = (BlockState) ((BlockState) Blocks.MOVING_PISTON.defaultBlockState().setValue(MovingPistonBlock.FACING, enumdirection)).setValue(MovingPistonBlock.TYPE, this.isSticky ? PistonType.STICKY : PistonType.DEFAULT); - -+ // Paper start - Fix sticky pistons and BlockPistonRetractEvent; Move empty piston retract call to fix multiple event fires -+ if (!this.isSticky) { -+ if (!new BlockPistonRetractEvent(CraftBlock.at(world, pos), java.util.Collections.emptyList(), CraftBlock.notchToBlockFace(enumdirection)).callEvent()) { -+ return false; -+ } -+ } -+ // Paper end - Fix sticky pistons and BlockPistonRetractEvent - world.setBlock(pos, iblockdata2, 20); - world.setBlockEntity(MovingPistonBlock.newMovingBlockEntity(pos, iblockdata2, (BlockState) this.defaultBlockState().setValue(PistonBaseBlock.FACING, Direction.from3DDataValue(data & 7)), enumdirection, false, true)); - world.blockUpdated(pos, iblockdata2.getBlock()); -@@ -271,6 +278,13 @@ public class PistonBaseBlock extends DirectionalBlock { - if (type == 1 && !iblockdata3.isAir() && PistonBaseBlock.isPushable(iblockdata3, world, blockposition1, enumdirection.getOpposite(), false, enumdirection) && (iblockdata3.getPistonPushReaction() == PushReaction.NORMAL || iblockdata3.is(Blocks.PISTON) || iblockdata3.is(Blocks.STICKY_PISTON))) { - this.moveBlocks(world, pos, enumdirection, false); - } else { -+ // Paper start - Fix sticky pistons and BlockPistonRetractEvent; fire BlockPistonRetractEvent for sticky pistons retracting nothing (air) -+ if (type == TRIGGER_CONTRACT && iblockdata2.isAir()) { -+ if (!new BlockPistonRetractEvent(CraftBlock.at(world, pos), java.util.Collections.emptyList(), CraftBlock.notchToBlockFace(enumdirection)).callEvent()) { -+ return false; -+ } -+ } -+ // Paper end - Fix sticky pistons and BlockPistonRetractEvent - world.removeBlock(pos.relative(enumdirection), false); - } - } diff --git a/patches/server/0647-Add-config-option-for-worlds-affected-by-time-cmd.patch b/patches/server/0642-Add-config-option-for-worlds-affected-by-time-cmd.patch similarity index 100% rename from patches/server/0647-Add-config-option-for-worlds-affected-by-time-cmd.patch rename to patches/server/0642-Add-config-option-for-worlds-affected-by-time-cmd.patch diff --git a/patches/server/0642-Expose-isFuel-and-canSmelt-methods-to-FurnaceInvento.patch b/patches/server/0642-Expose-isFuel-and-canSmelt-methods-to-FurnaceInvento.patch deleted file mode 100644 index 026f79354172..000000000000 --- a/patches/server/0642-Expose-isFuel-and-canSmelt-methods-to-FurnaceInvento.patch +++ /dev/null @@ -1,31 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: BillyGalbreath -Date: Thu, 23 Dec 2021 15:32:50 -0600 -Subject: [PATCH] Expose isFuel and canSmelt methods to FurnaceInventory - - -diff --git a/src/main/java/org/bukkit/craftbukkit/inventory/CraftInventoryFurnace.java b/src/main/java/org/bukkit/craftbukkit/inventory/CraftInventoryFurnace.java -index 29a8cd7667860c4598a556e6ef3af39c731683db..bd370f5c856d75b7210ef26036aedaa859c570be 100644 ---- a/src/main/java/org/bukkit/craftbukkit/inventory/CraftInventoryFurnace.java -+++ b/src/main/java/org/bukkit/craftbukkit/inventory/CraftInventoryFurnace.java -@@ -40,6 +40,20 @@ public class CraftInventoryFurnace extends CraftInventory implements FurnaceInve - this.setItem(0, stack); - } - -+ // Paper start -+ @Override -+ public boolean isFuel(ItemStack stack) { -+ return stack != null && !stack.getType().isEmpty() && AbstractFurnaceBlockEntity.isFuel(CraftItemStack.asNMSCopy(stack)); -+ } -+ -+ @Override -+ public boolean canSmelt(ItemStack stack) { -+ // data packs are always loaded in the main world -+ net.minecraft.server.level.ServerLevel world = ((org.bukkit.craftbukkit.CraftWorld) org.bukkit.Bukkit.getWorlds().get(0)).getHandle(); -+ return stack != null && !stack.getType().isEmpty() && world.getRecipeManager().getRecipeFor(((AbstractFurnaceBlockEntity) this.inventory).recipeType, new net.minecraft.world.item.crafting.SingleRecipeInput(CraftItemStack.asNMSCopy(stack)), world).isPresent(); -+ } -+ // Paper end -+ - @Override - public Furnace getHolder() { - return (Furnace) this.inventory.getOwner(); diff --git a/patches/server/0648-Add-missing-IAE-check-for-PersistentDataContainer-ha.patch b/patches/server/0643-Add-missing-IAE-check-for-PersistentDataContainer-ha.patch similarity index 100% rename from patches/server/0648-Add-missing-IAE-check-for-PersistentDataContainer-ha.patch rename to patches/server/0643-Add-missing-IAE-check-for-PersistentDataContainer-ha.patch diff --git a/patches/server/0644-Multiple-Entries-with-Scoreboards.patch b/patches/server/0644-Multiple-Entries-with-Scoreboards.patch new file mode 100644 index 000000000000..558cc93da58e --- /dev/null +++ b/patches/server/0644-Multiple-Entries-with-Scoreboards.patch @@ -0,0 +1,125 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Cryptite +Date: Tue, 21 Sep 2021 18:17:33 -0500 +Subject: [PATCH] Multiple Entries with Scoreboards + + +diff --git a/src/main/java/net/minecraft/network/protocol/game/ClientboundSetPlayerTeamPacket.java b/src/main/java/net/minecraft/network/protocol/game/ClientboundSetPlayerTeamPacket.java +index 1d0c473442b5c72245c356054440323e3c5d4711..f8fe125f12a6a00899d1d6acfa448be882b81557 100644 +--- a/src/main/java/net/minecraft/network/protocol/game/ClientboundSetPlayerTeamPacket.java ++++ b/src/main/java/net/minecraft/network/protocol/game/ClientboundSetPlayerTeamPacket.java +@@ -58,6 +58,11 @@ public class ClientboundSetPlayerTeamPacket implements Packet players, ClientboundSetPlayerTeamPacket.Action operation) { ++ return new ClientboundSetPlayerTeamPacket(team.getName(), operation == ClientboundSetPlayerTeamPacket.Action.ADD ? 3 : 4, Optional.empty(), players); ++ } ++ // Paper end - Multiple Entries with Scoreboards + private ClientboundSetPlayerTeamPacket(RegistryFriendlyByteBuf buf) { + this.name = buf.readUtf(); + this.method = buf.readByte(); +diff --git a/src/main/java/net/minecraft/server/ServerScoreboard.java b/src/main/java/net/minecraft/server/ServerScoreboard.java +index f1e72463ca11658390f662efbaf3e551c05fe799..8fd4d8ffcc9e1a0fcf83730d26c3bb9bef0f73f2 100644 +--- a/src/main/java/net/minecraft/server/ServerScoreboard.java ++++ b/src/main/java/net/minecraft/server/ServerScoreboard.java +@@ -106,6 +106,25 @@ public class ServerScoreboard extends Scoreboard { + } + } + ++ // Paper start - Multiple Entries with Scoreboards ++ public boolean addPlayersToTeam(java.util.Collection players, PlayerTeam team) { ++ boolean anyAdded = false; ++ for (String playerName : players) { ++ if (super.addPlayerToTeam(playerName, team)) { ++ anyAdded = true; ++ } ++ } ++ ++ if (anyAdded) { ++ this.broadcastAll(ClientboundSetPlayerTeamPacket.createMultiplePlayerPacket(team, players, ClientboundSetPlayerTeamPacket.Action.ADD)); ++ this.setDirty(); ++ return true; ++ } else { ++ return false; ++ } ++ } ++ // Paper end - Multiple Entries with Scoreboards ++ + @Override + public void removePlayerFromTeam(String scoreHolderName, PlayerTeam team) { + super.removePlayerFromTeam(scoreHolderName, team); +@@ -113,6 +132,17 @@ public class ServerScoreboard extends Scoreboard { + this.setDirty(); + } + ++ // Paper start - Multiple Entries with Scoreboards ++ public void removePlayersFromTeam(java.util.Collection players, PlayerTeam team) { ++ for (String playerName : players) { ++ super.removePlayerFromTeam(playerName, team); ++ } ++ ++ this.broadcastAll(ClientboundSetPlayerTeamPacket.createMultiplePlayerPacket(team, players, ClientboundSetPlayerTeamPacket.Action.REMOVE)); ++ this.setDirty(); ++ } ++ // Paper end - Multiple Entries with Scoreboards ++ + @Override + public void onObjectiveAdded(Objective objective) { + super.onObjectiveAdded(objective); +diff --git a/src/main/java/org/bukkit/craftbukkit/scoreboard/CraftTeam.java b/src/main/java/org/bukkit/craftbukkit/scoreboard/CraftTeam.java +index 27219bf2f16aed64c78623d44c3cc84aa9f47065..2b335c750ce5f9ccc2651a8701497ca9b8f46704 100644 +--- a/src/main/java/org/bukkit/craftbukkit/scoreboard/CraftTeam.java ++++ b/src/main/java/org/bukkit/craftbukkit/scoreboard/CraftTeam.java +@@ -229,6 +229,21 @@ final class CraftTeam extends CraftScoreboardComponent implements Team { + scoreboard.board.addPlayerToTeam(entry, this.team); + } + ++ // Paper start - Multiple Entries with Scoreboards ++ @Override ++ public void addEntities(java.util.Collection entities) throws IllegalStateException, IllegalArgumentException { ++ this.addEntries(entities.stream().map(entity -> ((org.bukkit.craftbukkit.entity.CraftEntity) entity).getHandle().getScoreboardName()).toList()); ++ } ++ ++ @Override ++ public void addEntries(java.util.Collection entries) throws IllegalStateException, IllegalArgumentException { ++ Preconditions.checkArgument(entries != null, "Entries cannot be null"); ++ CraftScoreboard scoreboard = this.checkState(); ++ ++ ((net.minecraft.server.ServerScoreboard) scoreboard.board).addPlayersToTeam(entries, this.team); ++ } ++ // Paper end - Multiple Entries with Scoreboards ++ + @Override + public boolean removePlayer(OfflinePlayer player) { + Preconditions.checkArgument(player != null, "OfflinePlayer cannot be null"); +@@ -248,6 +263,28 @@ final class CraftTeam extends CraftScoreboardComponent implements Team { + return true; + } + ++ // Paper start - Multiple Entries with Scoreboards ++ @Override ++ public boolean removeEntities(java.util.Collection entities) throws IllegalStateException, IllegalArgumentException { ++ return this.removeEntries(entities.stream().map(entity -> ((org.bukkit.craftbukkit.entity.CraftEntity) entity).getHandle().getScoreboardName()).toList()); ++ } ++ ++ @Override ++ public boolean removeEntries(java.util.Collection entries) throws IllegalStateException, IllegalArgumentException { ++ Preconditions.checkArgument(entries != null, "Entry cannot be null"); ++ CraftScoreboard scoreboard = this.checkState(); ++ ++ for (String entry : entries) { ++ if (this.team.getPlayers().contains(entry)) { ++ ((net.minecraft.server.ServerScoreboard) scoreboard.board).removePlayersFromTeam(entries, this.team); ++ return true; ++ } ++ } ++ ++ return false; ++ } ++ // Paper end - Multiple Entries with Scoreboards ++ + @Override + public boolean hasPlayer(OfflinePlayer player) throws IllegalArgumentException, IllegalStateException { + Preconditions.checkArgument(player != null, "OfflinePlayer cannot be null"); diff --git a/patches/server/0644-Validate-usernames.patch b/patches/server/0644-Validate-usernames.patch deleted file mode 100644 index 0735bfd8576e..000000000000 --- a/patches/server/0644-Validate-usernames.patch +++ /dev/null @@ -1,76 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Spottedleaf -Date: Sat, 1 Jan 2022 05:19:37 -0800 -Subject: [PATCH] Validate usernames - - -diff --git a/src/main/java/net/minecraft/server/network/ServerLoginPacketListenerImpl.java b/src/main/java/net/minecraft/server/network/ServerLoginPacketListenerImpl.java -index e0a10f1d8bf2c0df66e62bdf2a174ce6a66bbd6d..9c343b579d9735dc59c8c74fde030d981a673c4f 100644 ---- a/src/main/java/net/minecraft/server/network/ServerLoginPacketListenerImpl.java -+++ b/src/main/java/net/minecraft/server/network/ServerLoginPacketListenerImpl.java -@@ -90,6 +90,7 @@ public class ServerLoginPacketListenerImpl implements ServerLoginPacketListener, - private final String serverId; - private final boolean transferred; - private ServerPlayer player; // CraftBukkit -+ public boolean iKnowThisMayNotBeTheBestIdeaButPleaseDisableUsernameValidation = false; // Paper - username validation overriding - - public ServerLoginPacketListenerImpl(MinecraftServer server, Connection connection, boolean transferred) { - this.state = ServerLoginPacketListenerImpl.State.HELLO; -@@ -171,7 +172,13 @@ public class ServerLoginPacketListenerImpl implements ServerLoginPacketListener, - @Override - public void handleHello(ServerboundHelloPacket packet) { - Validate.validState(this.state == ServerLoginPacketListenerImpl.State.HELLO, "Unexpected hello packet", new Object[0]); -- Validate.validState(StringUtil.isValidPlayerName(packet.name()), "Invalid characters in username", new Object[0]); -+ // Paper start - Validate usernames -+ if (io.papermc.paper.configuration.GlobalConfiguration.get().proxies.isProxyOnlineMode() -+ && io.papermc.paper.configuration.GlobalConfiguration.get().unsupportedSettings.performUsernameValidation -+ && !this.iKnowThisMayNotBeTheBestIdeaButPleaseDisableUsernameValidation) { -+ Validate.validState(StringUtil.isReasonablePlayerName(packet.name()), "Invalid characters in username", new Object[0]); -+ } -+ // Paper end - Validate usernames - this.requestedUsername = packet.name(); - GameProfile gameprofile = this.server.getSingleplayerProfile(); - -diff --git a/src/main/java/net/minecraft/server/players/PlayerList.java b/src/main/java/net/minecraft/server/players/PlayerList.java -index c7dc335d6da208c224d94256eab11b2c3c63745f..cff45afb90b232a236683bd569d50aab6b701149 100644 ---- a/src/main/java/net/minecraft/server/players/PlayerList.java -+++ b/src/main/java/net/minecraft/server/players/PlayerList.java -@@ -670,7 +670,7 @@ public abstract class PlayerList { - - for (int i = 0; i < this.players.size(); ++i) { - entityplayer = (ServerPlayer) this.players.get(i); -- if (entityplayer.getUUID().equals(uuid)) { -+ if (entityplayer.getUUID().equals(uuid) || (io.papermc.paper.configuration.GlobalConfiguration.get().proxies.isProxyOnlineMode() && entityplayer.getGameProfile().getName().equalsIgnoreCase(gameprofile.getName()))) { // Paper - validate usernames - list.add(entityplayer); - } - } -diff --git a/src/main/java/net/minecraft/util/StringUtil.java b/src/main/java/net/minecraft/util/StringUtil.java -index d3fc549a08993376c76c4ebebb788fea3f4ddf69..0bd191acb9596d3aa21c337230d26f09d26f6888 100644 ---- a/src/main/java/net/minecraft/util/StringUtil.java -+++ b/src/main/java/net/minecraft/util/StringUtil.java -@@ -67,6 +67,25 @@ public class StringUtil { - return name.length() <= 16 && name.chars().filter(c -> c <= 32 || c >= 127).findAny().isEmpty(); - } - -+ // Paper start - Username validation -+ public static boolean isReasonablePlayerName(final String name) { -+ if (name.isEmpty() || name.length() > 16) { -+ return false; -+ } -+ -+ for (int i = 0, len = name.length(); i < len; ++i) { -+ final char c = name.charAt(i); -+ if ((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || (c >= '0' && c <= '9') || (c == '_' || c == '.')) { -+ continue; -+ } -+ -+ return false; -+ } -+ -+ return true; -+ } -+ // Paper end - Username validation -+ - public static String filterText(String string) { - return filterText(string, false); - } diff --git a/patches/server/0645-Make-water-animal-spawn-height-configurable.patch b/patches/server/0645-Make-water-animal-spawn-height-configurable.patch deleted file mode 100644 index 14f75d9b89e9..000000000000 --- a/patches/server/0645-Make-water-animal-spawn-height-configurable.patch +++ /dev/null @@ -1,21 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Brokkonaut -Date: Sat, 18 Dec 2021 08:26:55 +0100 -Subject: [PATCH] Make water animal spawn height configurable - - -diff --git a/src/main/java/net/minecraft/world/entity/animal/WaterAnimal.java b/src/main/java/net/minecraft/world/entity/animal/WaterAnimal.java -index ad86b129ffdb9c6358f451fbcc0ae000eb81141d..c9cabb061ebc9172647304431cc3fb2593dd47ba 100644 ---- a/src/main/java/net/minecraft/world/entity/animal/WaterAnimal.java -+++ b/src/main/java/net/minecraft/world/entity/animal/WaterAnimal.java -@@ -67,6 +67,10 @@ public abstract class WaterAnimal extends PathfinderMob { - ) { - int i = world.getSeaLevel(); - int j = i - 13; -+ // Paper start - Make water animal spawn height configurable -+ i = world.getMinecraftWorld().paperConfig().entities.spawning.wateranimalSpawnHeight.maximum.or(i); -+ j = world.getMinecraftWorld().paperConfig().entities.spawning.wateranimalSpawnHeight.minimum.or(j); -+ // Paper end - Make water animal spawn height configurable - return pos.getY() >= j && pos.getY() <= i && world.getFluidState(pos.below()).is(FluidTags.WATER) && world.getBlockState(pos.above()).is(Blocks.WATER); - } - } diff --git a/patches/server/0645-Reset-placed-block-on-exception.patch b/patches/server/0645-Reset-placed-block-on-exception.patch new file mode 100644 index 000000000000..327d98e29107 --- /dev/null +++ b/patches/server/0645-Reset-placed-block-on-exception.patch @@ -0,0 +1,39 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Nassim Jahnke +Date: Fri, 7 Jan 2022 11:45:15 +0100 +Subject: [PATCH] Reset placed block on exception + + +diff --git a/src/main/java/net/minecraft/world/item/BlockItem.java b/src/main/java/net/minecraft/world/item/BlockItem.java +index 407f5db0a4b3884440bc49bf4f00d9c035899e86..44cc12a3338b5f0448c88192c8674cd36531db34 100644 +--- a/src/main/java/net/minecraft/world/item/BlockItem.java ++++ b/src/main/java/net/minecraft/world/item/BlockItem.java +@@ -71,6 +71,7 @@ public class BlockItem extends Item { + if (this instanceof PlaceOnWaterBlockItem || this instanceof SolidBucketItem) { + blockstate = org.bukkit.craftbukkit.block.CraftBlockStates.getBlockState(blockactioncontext1.getLevel(), blockactioncontext1.getClickedPos()); + } ++ final org.bukkit.block.BlockState oldBlockstate = blockstate != null ? blockstate : org.bukkit.craftbukkit.block.CraftBlockStates.getBlockState(blockactioncontext1.getLevel(), blockactioncontext1.getClickedPos()); // Paper - Reset placed block on exception + // CraftBukkit end + + if (iblockdata == null) { +@@ -86,8 +87,20 @@ public class BlockItem extends Item { + + if (iblockdata1.is(iblockdata.getBlock())) { + iblockdata1 = this.updateBlockStateFromTag(blockposition, world, itemstack, iblockdata1); ++ // Paper start - Reset placed block on exception ++ try { + this.updateCustomBlockEntityTag(blockposition, world, entityhuman, itemstack, iblockdata1); + BlockItem.updateBlockEntityComponents(world, blockposition, itemstack); ++ } catch (Exception e) { ++ oldBlockstate.update(true, false); ++ if (entityhuman instanceof ServerPlayer player) { ++ org.apache.logging.log4j.LogManager.getLogger().error("Player {} tried placing invalid block", player.getScoreboardName(), e); ++ player.getBukkitEntity().kickPlayer("Packet processing error"); ++ return InteractionResult.FAIL; ++ } ++ throw e; // Rethrow exception if not placed by a player ++ } ++ // Paper end - Reset placed block on exception + iblockdata1.getBlock().setPlacedBy(world, blockposition, iblockdata1, entityhuman, itemstack); + // CraftBukkit start + if (blockstate != null) { diff --git a/patches/server/0646-Add-configurable-height-for-slime-spawn.patch b/patches/server/0646-Add-configurable-height-for-slime-spawn.patch new file mode 100644 index 000000000000..dfc8ecb34980 --- /dev/null +++ b/patches/server/0646-Add-configurable-height-for-slime-spawn.patch @@ -0,0 +1,35 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Doc +Date: Mon, 2 Aug 2021 11:24:39 -0400 +Subject: [PATCH] Add configurable height for slime spawn + + +diff --git a/src/main/java/net/minecraft/world/entity/monster/Slime.java b/src/main/java/net/minecraft/world/entity/monster/Slime.java +index 129f0cbc0469cb2804db6088b53347d88d91f4eb..72346a7e5269c91e3143933ac37e65ad9639b791 100644 +--- a/src/main/java/net/minecraft/world/entity/monster/Slime.java ++++ b/src/main/java/net/minecraft/world/entity/monster/Slime.java +@@ -340,7 +340,11 @@ public class Slime extends Mob implements Enemy { + return checkMobSpawnRules(type, world, spawnReason, pos, random); + } + +- if (world.getBiome(pos).is(BiomeTags.ALLOWS_SURFACE_SLIME_SPAWNS) && pos.getY() > 50 && pos.getY() < 70 && random.nextFloat() < 0.5F && random.nextFloat() < world.getMoonBrightness() && world.getMaxLocalRawBrightness(pos) <= random.nextInt(8)) { ++ // Paper start - Replace rules for Height in Swamp Biome ++ final double maxHeightSwamp = world.getMinecraftWorld().paperConfig().entities.spawning.slimeSpawnHeight.surfaceBiome.maximum; ++ final double minHeightSwamp = world.getMinecraftWorld().paperConfig().entities.spawning.slimeSpawnHeight.surfaceBiome.minimum; ++ if (world.getBiome(pos).is(BiomeTags.ALLOWS_SURFACE_SLIME_SPAWNS) && pos.getY() > minHeightSwamp && pos.getY() < maxHeightSwamp && random.nextFloat() < 0.5F && random.nextFloat() < world.getMoonBrightness() && world.getMaxLocalRawBrightness(pos) <= random.nextInt(8)) { ++ // Paper end - Replace rules for Height in Swamp Biome + return checkMobSpawnRules(type, world, spawnReason, pos, random); + } + +@@ -351,7 +355,10 @@ public class Slime extends Mob implements Enemy { + ChunkPos chunkcoordintpair = new ChunkPos(pos); + boolean flag = world.getMinecraftWorld().paperConfig().entities.spawning.allChunksAreSlimeChunks || WorldgenRandom.seedSlimeChunk(chunkcoordintpair.x, chunkcoordintpair.z, ((WorldGenLevel) world).getSeed(), world.getMinecraftWorld().spigotConfig.slimeSeed).nextInt(10) == 0; // Spigot // Paper + +- if (random.nextInt(10) == 0 && flag && pos.getY() < 40) { ++ // Paper start - Replace rules for Height in Slime Chunks ++ final double maxHeightSlimeChunk = world.getMinecraftWorld().paperConfig().entities.spawning.slimeSpawnHeight.slimeChunk.maximum; ++ if (random.nextInt(10) == 0 && flag && pos.getY() < maxHeightSlimeChunk) { ++ // Paper end - Replace rules for Height in Slime Chunks + return checkMobSpawnRules(type, world, spawnReason, pos, random); + } + } diff --git a/patches/server/0646-Expose-vanilla-BiomeProvider-from-WorldInfo.patch b/patches/server/0646-Expose-vanilla-BiomeProvider-from-WorldInfo.patch deleted file mode 100644 index 0801289d710d..000000000000 --- a/patches/server/0646-Expose-vanilla-BiomeProvider-from-WorldInfo.patch +++ /dev/null @@ -1,177 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Jason Penilla <11360596+jpenilla@users.noreply.github.com> -Date: Thu, 6 Jan 2022 15:59:06 -0800 -Subject: [PATCH] Expose vanilla BiomeProvider from WorldInfo - - -diff --git a/src/main/java/net/minecraft/server/MinecraftServer.java b/src/main/java/net/minecraft/server/MinecraftServer.java -index 56589fee8dd09783e01f2ae290ffacb5dff3f05f..718b653a118a1c64d07efab93192f10dfcbf414e 100644 ---- a/src/main/java/net/minecraft/server/MinecraftServer.java -+++ b/src/main/java/net/minecraft/server/MinecraftServer.java -@@ -611,7 +611,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop list = ImmutableList.of(new PhantomSpawner(), new PatrolSpawner(), new CatSpawner(), new VillageSiege(), new WanderingTraderSpawner(iworlddataserver)); - LevelStem worlddimension = (LevelStem) dimensions.get(dimensionKey); - -- org.bukkit.generator.WorldInfo worldInfo = new org.bukkit.craftbukkit.generator.CraftWorldInfo(iworlddataserver, worldSession, org.bukkit.World.Environment.getEnvironment(dimension), worlddimension.type().value()); -+ org.bukkit.generator.WorldInfo worldInfo = new org.bukkit.craftbukkit.generator.CraftWorldInfo(iworlddataserver, worldSession, org.bukkit.World.Environment.getEnvironment(dimension), worlddimension.type().value(), worlddimension.generator(), this.registryAccess()); // Paper - Expose vanilla BiomeProvider from WorldInfo - if (biomeProvider == null && gen != null) { - biomeProvider = gen.getDefaultBiomeProvider(worldInfo); - } -diff --git a/src/main/java/net/minecraft/server/level/ServerLevel.java b/src/main/java/net/minecraft/server/level/ServerLevel.java -index a473993ea48afd05a884e3ffcbdd15abe74580d2..a87a0c9672b53db0e06292c339b3b1e163901942 100644 ---- a/src/main/java/net/minecraft/server/level/ServerLevel.java -+++ b/src/main/java/net/minecraft/server/level/ServerLevel.java -@@ -369,7 +369,7 @@ public class ServerLevel extends Level implements WorldGenLevel { - this.serverLevelData.setWorld(this); - - if (biomeProvider != null) { -- BiomeSource worldChunkManager = new CustomWorldChunkManager(this.getWorld(), biomeProvider, this.server.registryAccess().registryOrThrow(Registries.BIOME)); -+ BiomeSource worldChunkManager = new CustomWorldChunkManager(this.getWorld(), biomeProvider, this.server.registryAccess().registryOrThrow(Registries.BIOME), chunkgenerator.getBiomeSource()); // Paper - add vanillaBiomeProvider - if (chunkgenerator instanceof NoiseBasedChunkGenerator cga) { - chunkgenerator = new NoiseBasedChunkGenerator(worldChunkManager, cga.settings); - } else if (chunkgenerator instanceof FlatLevelSource cpf) { -diff --git a/src/main/java/org/bukkit/craftbukkit/CraftServer.java b/src/main/java/org/bukkit/craftbukkit/CraftServer.java -index b638ccf0e46eeb375a59a42d6f29edd3f084fa17..bb232cd338f94b03f2add34766927ecae019e388 100644 ---- a/src/main/java/org/bukkit/craftbukkit/CraftServer.java -+++ b/src/main/java/org/bukkit/craftbukkit/CraftServer.java -@@ -1308,7 +1308,7 @@ public final class CraftServer implements Server { - List list = ImmutableList.of(new PhantomSpawner(), new PatrolSpawner(), new CatSpawner(), new VillageSiege(), new WanderingTraderSpawner(worlddata)); - LevelStem worlddimension = iregistry.get(actualDimension); - -- WorldInfo worldInfo = new CraftWorldInfo(worlddata, worldSession, creator.environment(), worlddimension.type().value()); -+ WorldInfo worldInfo = new CraftWorldInfo(worlddata, worldSession, creator.environment(), worlddimension.type().value(), worlddimension.generator(), this.getHandle().getServer().registryAccess()); // Paper - Expose vanilla BiomeProvider from WorldInfo - if (biomeProvider == null && generator != null) { - biomeProvider = generator.getDefaultBiomeProvider(worldInfo); - } -diff --git a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java -index 838ffddba99964748dfe95d68ca93b578bc3292b..bb837f7fa46a7f5926a67ce3f725a328b9bff415 100644 ---- a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java -+++ b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java -@@ -212,6 +212,39 @@ public class CraftWorld extends CraftRegionAccessor implements World { - public int getPlayerCount() { - return world.players().size(); - } -+ -+ @Override -+ public BiomeProvider vanillaBiomeProvider() { -+ net.minecraft.server.level.ServerChunkCache serverCache = this.getHandle().chunkSource; -+ -+ final net.minecraft.world.level.chunk.ChunkGenerator gen = serverCache.getGenerator(); -+ net.minecraft.world.level.biome.BiomeSource biomeSource; -+ if (gen instanceof org.bukkit.craftbukkit.generator.CustomChunkGenerator custom) { -+ biomeSource = custom.getDelegate().getBiomeSource(); -+ } else { -+ biomeSource = gen.getBiomeSource(); -+ } -+ if (biomeSource instanceof org.bukkit.craftbukkit.generator.CustomWorldChunkManager customBiomeSource) { -+ biomeSource = customBiomeSource.vanillaBiomeSource; -+ } -+ final net.minecraft.world.level.biome.BiomeSource finalBiomeSource = biomeSource; -+ final net.minecraft.world.level.biome.Climate.Sampler sampler = serverCache.randomState().sampler(); -+ -+ final List possibleBiomes = finalBiomeSource.possibleBiomes().stream() -+ .map(CraftBiome::minecraftHolderToBukkit) -+ .toList(); -+ return new BiomeProvider() { -+ @Override -+ public Biome getBiome(final org.bukkit.generator.WorldInfo worldInfo, final int x, final int y, final int z) { -+ return CraftBiome.minecraftHolderToBukkit(finalBiomeSource.getNoiseBiome(x >> 2, y >> 2, z >> 2, sampler)); -+ } -+ -+ @Override -+ public List getBiomes(final org.bukkit.generator.WorldInfo worldInfo) { -+ return possibleBiomes; -+ } -+ }; -+ } - // Paper end - - private static final Random rand = new Random(); -diff --git a/src/main/java/org/bukkit/craftbukkit/generator/CraftWorldInfo.java b/src/main/java/org/bukkit/craftbukkit/generator/CraftWorldInfo.java -index 5d655d6cd3e23e0287069f8bdf77601487e862fd..c81455a4ee9a3185f125ebf8cec325f4ed2e501d 100644 ---- a/src/main/java/org/bukkit/craftbukkit/generator/CraftWorldInfo.java -+++ b/src/main/java/org/bukkit/craftbukkit/generator/CraftWorldInfo.java -@@ -17,8 +17,14 @@ public class CraftWorldInfo implements WorldInfo { - private final long seed; - private final int minHeight; - private final int maxHeight; -+ // Paper start -+ private final net.minecraft.world.level.chunk.ChunkGenerator vanillaChunkGenerator; -+ private final net.minecraft.core.RegistryAccess.Frozen registryAccess; - -- public CraftWorldInfo(ServerLevelData worldDataServer, LevelStorageSource.LevelStorageAccess session, World.Environment environment, DimensionType dimensionManager) { -+ public CraftWorldInfo(PrimaryLevelData worldDataServer, LevelStorageSource.LevelStorageAccess session, World.Environment environment, DimensionType dimensionManager, net.minecraft.world.level.chunk.ChunkGenerator chunkGenerator, net.minecraft.core.RegistryAccess.Frozen registryAccess) { -+ this.registryAccess = registryAccess; -+ this.vanillaChunkGenerator = chunkGenerator; -+ // Paper end - this.name = worldDataServer.getLevelName(); - this.uuid = WorldUUID.getUUID(session.levelDirectory.path().toFile()); - this.environment = environment; -@@ -27,15 +33,6 @@ public class CraftWorldInfo implements WorldInfo { - this.maxHeight = dimensionManager.minY() + dimensionManager.height(); - } - -- public CraftWorldInfo(String name, UUID uuid, World.Environment environment, long seed, int minHeight, int maxHeight) { -- this.name = name; -- this.uuid = uuid; -- this.environment = environment; -- this.seed = seed; -- this.minHeight = minHeight; -- this.maxHeight = maxHeight; -- } -- - @Override - public String getName() { - return this.name; -@@ -65,4 +62,34 @@ public class CraftWorldInfo implements WorldInfo { - public int getMaxHeight() { - return this.maxHeight; - } -+ -+ // Paper start -+ @Override -+ public org.bukkit.generator.BiomeProvider vanillaBiomeProvider() { -+ final net.minecraft.world.level.levelgen.RandomState randomState; -+ if (vanillaChunkGenerator instanceof net.minecraft.world.level.levelgen.NoiseBasedChunkGenerator noiseBasedChunkGenerator) { -+ randomState = net.minecraft.world.level.levelgen.RandomState.create(noiseBasedChunkGenerator.generatorSettings().value(), -+ registryAccess.lookupOrThrow(net.minecraft.core.registries.Registries.NOISE), getSeed()); -+ } else { -+ randomState = net.minecraft.world.level.levelgen.RandomState.create(net.minecraft.world.level.levelgen.NoiseGeneratorSettings.dummy(), -+ registryAccess.lookupOrThrow(net.minecraft.core.registries.Registries.NOISE), getSeed()); -+ } -+ -+ final java.util.List possibleBiomes = CraftWorldInfo.this.vanillaChunkGenerator.getBiomeSource().possibleBiomes().stream() -+ .map(biome -> org.bukkit.craftbukkit.block.CraftBiome.minecraftHolderToBukkit(biome)) -+ .toList(); -+ return new org.bukkit.generator.BiomeProvider() { -+ @Override -+ public org.bukkit.block.Biome getBiome(final WorldInfo worldInfo, final int x, final int y, final int z) { -+ return org.bukkit.craftbukkit.block.CraftBiome.minecraftHolderToBukkit( -+ CraftWorldInfo.this.vanillaChunkGenerator.getBiomeSource().getNoiseBiome(x >> 2, y >> 2, z >> 2, randomState.sampler())); -+ } -+ -+ @Override -+ public java.util.List getBiomes(final org.bukkit.generator.WorldInfo worldInfo) { -+ return possibleBiomes; -+ } -+ }; -+ } -+ // Paper end - } -diff --git a/src/main/java/org/bukkit/craftbukkit/generator/CustomWorldChunkManager.java b/src/main/java/org/bukkit/craftbukkit/generator/CustomWorldChunkManager.java -index 0063c4c17d05a77adf81164fb9307a29860cbe12..0bac128d6faff0063b03f595b82deea78d1ae161 100644 ---- a/src/main/java/org/bukkit/craftbukkit/generator/CustomWorldChunkManager.java -+++ b/src/main/java/org/bukkit/craftbukkit/generator/CustomWorldChunkManager.java -@@ -31,7 +31,11 @@ public class CustomWorldChunkManager extends BiomeSource { - return biomeBases; - } - -- public CustomWorldChunkManager(WorldInfo worldInfo, BiomeProvider biomeProvider, Registry registry) { -+ // Paper start - add vanillaBiomeProvider -+ public final BiomeSource vanillaBiomeSource; -+ public CustomWorldChunkManager(WorldInfo worldInfo, BiomeProvider biomeProvider, Registry registry, BiomeSource vanillaBiomeSource) { -+ this.vanillaBiomeSource = vanillaBiomeSource; -+ // Paper end - add vanillaBiomeProvider - this.worldInfo = worldInfo; - this.biomeProvider = biomeProvider; - this.registry = registry; diff --git a/patches/server/0647-Fix-xp-reward-for-baby-zombies.patch b/patches/server/0647-Fix-xp-reward-for-baby-zombies.patch new file mode 100644 index 000000000000..3ec320760f08 --- /dev/null +++ b/patches/server/0647-Fix-xp-reward-for-baby-zombies.patch @@ -0,0 +1,32 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Jake Potrebic +Date: Sun, 16 Jan 2022 10:34:02 -0800 +Subject: [PATCH] Fix xp reward for baby zombies + +The field that tracks the xpReward was not +getting reset if the death was cancelled +so this resets it after each call to +Zombie#getExperienceReward + +diff --git a/src/main/java/net/minecraft/world/entity/monster/Zombie.java b/src/main/java/net/minecraft/world/entity/monster/Zombie.java +index a835ec6e063dd247a008da84446f8647f38d89d4..94b3ba2688676e92d9d093b63d92cab39d5d2f02 100644 +--- a/src/main/java/net/minecraft/world/entity/monster/Zombie.java ++++ b/src/main/java/net/minecraft/world/entity/monster/Zombie.java +@@ -174,11 +174,16 @@ public class Zombie extends Monster { + + @Override + protected int getBaseExperienceReward(ServerLevel world) { ++ final int previousReward = this.xpReward; // Paper - store previous value to reset after calculating XP reward + if (this.isBaby()) { + this.xpReward = (int) ((double) this.xpReward * 2.5D); + } + +- return super.getBaseExperienceReward(world); ++ // Paper start - store previous value to reset after calculating XP reward ++ int reward = super.getBaseExperienceReward(world); ++ this.xpReward = previousReward; ++ return reward; ++ // Paper end - store previous value to reset after calculating XP reward + } + + @Override diff --git a/patches/server/0648-Multi-Block-Change-API-Implementation.patch b/patches/server/0648-Multi-Block-Change-API-Implementation.patch new file mode 100644 index 000000000000..9edd052d01b2 --- /dev/null +++ b/patches/server/0648-Multi-Block-Change-API-Implementation.patch @@ -0,0 +1,62 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Brody Beckwith +Date: Fri, 14 Jan 2022 00:41:11 -0500 +Subject: [PATCH] Multi Block Change API Implementation + + +diff --git a/src/main/java/net/minecraft/network/protocol/game/ClientboundSectionBlocksUpdatePacket.java b/src/main/java/net/minecraft/network/protocol/game/ClientboundSectionBlocksUpdatePacket.java +index 926ff9be3d9e3f5d620e4c7ccb22b9f64865ff8c..1a37654aff9a9c86c9f7af10a1cf721371f0c5ec 100644 +--- a/src/main/java/net/minecraft/network/protocol/game/ClientboundSectionBlocksUpdatePacket.java ++++ b/src/main/java/net/minecraft/network/protocol/game/ClientboundSectionBlocksUpdatePacket.java +@@ -62,6 +62,14 @@ public class ClientboundSectionBlocksUpdatePacket implements Packet blockChanges) { ++ this.sectionPos = sectionPos; ++ this.positions = blockChanges.keySet().toShortArray(); ++ this.states = blockChanges.values().toArray(new BlockState[0]); ++ } ++ // Paper end - Multi Block Change API ++ + private void write(FriendlyByteBuf buf) { + buf.writeLong(this.sectionPos.asLong()); + buf.writeVarInt(this.positions.length); +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java +index ca13d317a5e1c7b7ccda3fdec63e2146562649f6..fe0c355f8203c9bfa30d2ec48392a5a1a3d616ae 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java +@@ -947,6 +947,32 @@ public class CraftPlayer extends CraftHumanEntity implements Player { + this.getHandle().connection.send(packet); + } + ++ // Paper start ++ @Override ++ public void sendMultiBlockChange(final Map blockChanges) { ++ if (this.getHandle().connection == null) return; ++ ++ Map> sectionMap = new HashMap<>(); ++ ++ for (Map.Entry entry : blockChanges.entrySet()) { ++ BlockData blockData = entry.getValue(); ++ BlockPos blockPos = io.papermc.paper.util.MCUtil.toBlockPos(entry.getKey()); ++ SectionPos sectionPos = SectionPos.of(blockPos); ++ ++ it.unimi.dsi.fastutil.shorts.Short2ObjectMap sectionData = sectionMap.computeIfAbsent(sectionPos, key -> new it.unimi.dsi.fastutil.shorts.Short2ObjectArrayMap<>()); ++ sectionData.put(SectionPos.sectionRelativePos(blockPos), ((CraftBlockData) blockData).getState()); ++ } ++ ++ for (Map.Entry> entry : sectionMap.entrySet()) { ++ SectionPos sectionPos = entry.getKey(); ++ it.unimi.dsi.fastutil.shorts.Short2ObjectMap blockData = entry.getValue(); ++ ++ net.minecraft.network.protocol.game.ClientboundSectionBlocksUpdatePacket packet = new net.minecraft.network.protocol.game.ClientboundSectionBlocksUpdatePacket(sectionPos, blockData); ++ this.getHandle().connection.send(packet); ++ } ++ } ++ // Paper end ++ + @Override + public void sendBlockChanges(Collection blocks) { + Preconditions.checkArgument(blocks != null, "blocks must not be null"); diff --git a/patches/server/0649-Fix-NotePlayEvent.patch b/patches/server/0649-Fix-NotePlayEvent.patch new file mode 100644 index 000000000000..cdffcd6f0ff0 --- /dev/null +++ b/patches/server/0649-Fix-NotePlayEvent.patch @@ -0,0 +1,54 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Kieran Wallbanks +Date: Mon, 21 Jun 2021 14:23:50 +0100 +Subject: [PATCH] Fix NotePlayEvent + +== AT == +public org.bukkit.craftbukkit.block.data.CraftBlockData toNMS(Ljava/lang/Enum;Ljava/lang/Class;)Ljava/lang/Enum; + +diff --git a/src/main/java/net/minecraft/world/level/block/NoteBlock.java b/src/main/java/net/minecraft/world/level/block/NoteBlock.java +index 57e13269367a82ec39c2298b20d7595f61326f47..71fd7a467a4cb89cad8d2541366fd4add9115e04 100644 +--- a/src/main/java/net/minecraft/world/level/block/NoteBlock.java ++++ b/src/main/java/net/minecraft/world/level/block/NoteBlock.java +@@ -96,11 +96,12 @@ public class NoteBlock extends Block { + private void playNote(@Nullable Entity entity, BlockState state, Level world, BlockPos pos) { + if (((NoteBlockInstrument) state.getValue(NoteBlock.INSTRUMENT)).worksAboveNoteBlock() || world.getBlockState(pos.above()).isAir()) { + // CraftBukkit start +- org.bukkit.event.block.NotePlayEvent event = org.bukkit.craftbukkit.event.CraftEventFactory.callNotePlayEvent(world, pos, state.getValue(NoteBlock.INSTRUMENT), state.getValue(NoteBlock.NOTE)); +- if (event.isCancelled()) { +- return; +- } ++ // org.bukkit.event.block.NotePlayEvent event = org.bukkit.craftbukkit.event.CraftEventFactory.callNotePlayEvent(world, pos, state.getValue(NoteBlock.INSTRUMENT), state.getValue(NoteBlock.NOTE)); ++ // if (event.isCancelled()) { ++ // return; ++ // } + // CraftBukkit end ++ // Paper - move NotePlayEvent call to fix instrument/note changes; TODO any way to cancel the game event? + world.blockEvent(pos, this, 0, 0); + world.gameEvent(entity, (Holder) GameEvent.NOTE_BLOCK_PLAY, pos); + } +@@ -139,10 +140,14 @@ public class NoteBlock extends Block { + @Override + protected boolean triggerEvent(BlockState state, Level world, BlockPos pos, int type, int data) { + NoteBlockInstrument blockpropertyinstrument = (NoteBlockInstrument) state.getValue(NoteBlock.INSTRUMENT); ++ // Paper start - move NotePlayEvent call to fix instrument/note changes ++ org.bukkit.event.block.NotePlayEvent event = org.bukkit.craftbukkit.event.CraftEventFactory.callNotePlayEvent(world, pos, blockpropertyinstrument, state.getValue(NOTE)); ++ if (event.isCancelled()) return false; ++ // Paper end - move NotePlayEvent call to fix instrument/note changes + float f; + + if (blockpropertyinstrument.isTunable()) { +- int k = (Integer) state.getValue(NoteBlock.NOTE); ++ int k = event.getNote().getId(); // Paper - move NotePlayEvent call to fix instrument/note changes + + f = NoteBlock.getPitchFromNote(k); + world.addParticle(ParticleTypes.NOTE, (double) pos.getX() + 0.5D, (double) pos.getY() + 1.2D, (double) pos.getZ() + 0.5D, (double) k / 24.0D, 0.0D, 0.0D); +@@ -161,7 +166,7 @@ public class NoteBlock extends Block { + + holder = Holder.direct(SoundEvent.createVariableRangeEvent(minecraftkey)); + } else { +- holder = blockpropertyinstrument.getSoundEvent(); ++ holder = org.bukkit.craftbukkit.block.data.CraftBlockData.toNMS(event.getInstrument(), NoteBlockInstrument.class).getSoundEvent(); // Paper - move NotePlayEvent call to fix instrument/note changes + } + + world.playSeededSound((Player) null, (double) pos.getX() + 0.5D, (double) pos.getY() + 0.5D, (double) pos.getZ() + 0.5D, holder, SoundSource.RECORDS, 3.0F, f, world.random.nextLong()); diff --git a/patches/server/0649-Multiple-Entries-with-Scoreboards.patch b/patches/server/0649-Multiple-Entries-with-Scoreboards.patch deleted file mode 100644 index c286ca9e4b95..000000000000 --- a/patches/server/0649-Multiple-Entries-with-Scoreboards.patch +++ /dev/null @@ -1,125 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Cryptite -Date: Tue, 21 Sep 2021 18:17:33 -0500 -Subject: [PATCH] Multiple Entries with Scoreboards - - -diff --git a/src/main/java/net/minecraft/network/protocol/game/ClientboundSetPlayerTeamPacket.java b/src/main/java/net/minecraft/network/protocol/game/ClientboundSetPlayerTeamPacket.java -index 1d0c473442b5c72245c356054440323e3c5d4711..f8fe125f12a6a00899d1d6acfa448be882b81557 100644 ---- a/src/main/java/net/minecraft/network/protocol/game/ClientboundSetPlayerTeamPacket.java -+++ b/src/main/java/net/minecraft/network/protocol/game/ClientboundSetPlayerTeamPacket.java -@@ -58,6 +58,11 @@ public class ClientboundSetPlayerTeamPacket implements Packet players, ClientboundSetPlayerTeamPacket.Action operation) { -+ return new ClientboundSetPlayerTeamPacket(team.getName(), operation == ClientboundSetPlayerTeamPacket.Action.ADD ? 3 : 4, Optional.empty(), players); -+ } -+ // Paper end - Multiple Entries with Scoreboards - private ClientboundSetPlayerTeamPacket(RegistryFriendlyByteBuf buf) { - this.name = buf.readUtf(); - this.method = buf.readByte(); -diff --git a/src/main/java/net/minecraft/server/ServerScoreboard.java b/src/main/java/net/minecraft/server/ServerScoreboard.java -index 65a206c9969d5c7eee6bd185e398244365b5a809..f180001493146ef0d54079a8b2b47ad7decc24ca 100644 ---- a/src/main/java/net/minecraft/server/ServerScoreboard.java -+++ b/src/main/java/net/minecraft/server/ServerScoreboard.java -@@ -106,6 +106,25 @@ public class ServerScoreboard extends Scoreboard { - } - } - -+ // Paper start - Multiple Entries with Scoreboards -+ public boolean addPlayersToTeam(java.util.Collection players, PlayerTeam team) { -+ boolean anyAdded = false; -+ for (String playerName : players) { -+ if (super.addPlayerToTeam(playerName, team)) { -+ anyAdded = true; -+ } -+ } -+ -+ if (anyAdded) { -+ this.broadcastAll(ClientboundSetPlayerTeamPacket.createMultiplePlayerPacket(team, players, ClientboundSetPlayerTeamPacket.Action.ADD)); -+ this.setDirty(); -+ return true; -+ } else { -+ return false; -+ } -+ } -+ // Paper end - Multiple Entries with Scoreboards -+ - @Override - public void removePlayerFromTeam(String scoreHolderName, PlayerTeam team) { - super.removePlayerFromTeam(scoreHolderName, team); -@@ -113,6 +132,17 @@ public class ServerScoreboard extends Scoreboard { - this.setDirty(); - } - -+ // Paper start - Multiple Entries with Scoreboards -+ public void removePlayersFromTeam(java.util.Collection players, PlayerTeam team) { -+ for (String playerName : players) { -+ super.removePlayerFromTeam(playerName, team); -+ } -+ -+ this.broadcastAll(ClientboundSetPlayerTeamPacket.createMultiplePlayerPacket(team, players, ClientboundSetPlayerTeamPacket.Action.REMOVE)); -+ this.setDirty(); -+ } -+ // Paper end - Multiple Entries with Scoreboards -+ - @Override - public void onObjectiveAdded(Objective objective) { - super.onObjectiveAdded(objective); -diff --git a/src/main/java/org/bukkit/craftbukkit/scoreboard/CraftTeam.java b/src/main/java/org/bukkit/craftbukkit/scoreboard/CraftTeam.java -index 27219bf2f16aed64c78623d44c3cc84aa9f47065..2b335c750ce5f9ccc2651a8701497ca9b8f46704 100644 ---- a/src/main/java/org/bukkit/craftbukkit/scoreboard/CraftTeam.java -+++ b/src/main/java/org/bukkit/craftbukkit/scoreboard/CraftTeam.java -@@ -229,6 +229,21 @@ final class CraftTeam extends CraftScoreboardComponent implements Team { - scoreboard.board.addPlayerToTeam(entry, this.team); - } - -+ // Paper start - Multiple Entries with Scoreboards -+ @Override -+ public void addEntities(java.util.Collection entities) throws IllegalStateException, IllegalArgumentException { -+ this.addEntries(entities.stream().map(entity -> ((org.bukkit.craftbukkit.entity.CraftEntity) entity).getHandle().getScoreboardName()).toList()); -+ } -+ -+ @Override -+ public void addEntries(java.util.Collection entries) throws IllegalStateException, IllegalArgumentException { -+ Preconditions.checkArgument(entries != null, "Entries cannot be null"); -+ CraftScoreboard scoreboard = this.checkState(); -+ -+ ((net.minecraft.server.ServerScoreboard) scoreboard.board).addPlayersToTeam(entries, this.team); -+ } -+ // Paper end - Multiple Entries with Scoreboards -+ - @Override - public boolean removePlayer(OfflinePlayer player) { - Preconditions.checkArgument(player != null, "OfflinePlayer cannot be null"); -@@ -248,6 +263,28 @@ final class CraftTeam extends CraftScoreboardComponent implements Team { - return true; - } - -+ // Paper start - Multiple Entries with Scoreboards -+ @Override -+ public boolean removeEntities(java.util.Collection entities) throws IllegalStateException, IllegalArgumentException { -+ return this.removeEntries(entities.stream().map(entity -> ((org.bukkit.craftbukkit.entity.CraftEntity) entity).getHandle().getScoreboardName()).toList()); -+ } -+ -+ @Override -+ public boolean removeEntries(java.util.Collection entries) throws IllegalStateException, IllegalArgumentException { -+ Preconditions.checkArgument(entries != null, "Entry cannot be null"); -+ CraftScoreboard scoreboard = this.checkState(); -+ -+ for (String entry : entries) { -+ if (this.team.getPlayers().contains(entry)) { -+ ((net.minecraft.server.ServerScoreboard) scoreboard.board).removePlayersFromTeam(entries, this.team); -+ return true; -+ } -+ } -+ -+ return false; -+ } -+ // Paper end - Multiple Entries with Scoreboards -+ - @Override - public boolean hasPlayer(OfflinePlayer player) throws IllegalArgumentException, IllegalStateException { - Preconditions.checkArgument(player != null, "OfflinePlayer cannot be null"); diff --git a/patches/server/0650-Freeze-Tick-Lock-API.patch b/patches/server/0650-Freeze-Tick-Lock-API.patch new file mode 100644 index 000000000000..98811e564539 --- /dev/null +++ b/patches/server/0650-Freeze-Tick-Lock-API.patch @@ -0,0 +1,82 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Owen1212055 <23108066+Owen1212055@users.noreply.github.com> +Date: Sun, 26 Dec 2021 20:27:43 -0500 +Subject: [PATCH] Freeze Tick Lock API + + +diff --git a/src/main/java/net/minecraft/world/entity/Entity.java b/src/main/java/net/minecraft/world/entity/Entity.java +index 06d7aed2539a0f38fabe5b10c91d8da10c43605f..4b0b768a4563281ec4ef1a16a3a21c24a1272849 100644 +--- a/src/main/java/net/minecraft/world/entity/Entity.java ++++ b/src/main/java/net/minecraft/world/entity/Entity.java +@@ -414,6 +414,7 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess + private org.bukkit.util.Vector origin; + @javax.annotation.Nullable + private UUID originWorld; ++ public boolean freezeLocked = false; // Paper - Freeze Tick Lock API + + public void setOrigin(@javax.annotation.Nonnull Location location) { + this.origin = location.toVector(); +@@ -759,7 +760,7 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess + this.setRemainingFireTicks(this.remainingFireTicks - 1); + } + +- if (this.getTicksFrozen() > 0) { ++ if (this.getTicksFrozen() > 0 && !freezeLocked) { // Paper - Freeze Tick Lock API + this.setTicksFrozen(0); + this.level().levelEvent((Player) null, 1009, this.blockPosition, 1); + } +@@ -2428,6 +2429,9 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess + if (fromNetherPortal) { + nbttagcompound.putBoolean("Paper.FromNetherPortal", true); + } ++ if (freezeLocked) { ++ nbttagcompound.putBoolean("Paper.FreezeLock", true); ++ } + // Paper end + return nbttagcompound; + } catch (Throwable throwable) { +@@ -2573,6 +2577,9 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess + if (spawnReason == null) { + spawnReason = org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason.DEFAULT; + } ++ if (nbt.contains("Paper.FreezeLock")) { ++ freezeLocked = nbt.getBoolean("Paper.FreezeLock"); ++ } + // Paper end + + } catch (Throwable throwable) { +diff --git a/src/main/java/net/minecraft/world/entity/LivingEntity.java b/src/main/java/net/minecraft/world/entity/LivingEntity.java +index 8e0805bf6d330717f7555fbdb28d416295f8495a..8b454382f59ee36ec6f45ca8445b3f1a956ff668 100644 +--- a/src/main/java/net/minecraft/world/entity/LivingEntity.java ++++ b/src/main/java/net/minecraft/world/entity/LivingEntity.java +@@ -3609,7 +3609,7 @@ public abstract class LivingEntity extends Entity implements Attackable { + this.calculateEntityAnimation(this instanceof FlyingAnimal); + gameprofilerfiller.pop(); + gameprofilerfiller.push("freezing"); +- if (!this.level().isClientSide && !this.isDeadOrDying()) { ++ if (!this.level().isClientSide && !this.isDeadOrDying() && !this.freezeLocked) { // Paper - Freeze Tick Lock API + int i = this.getTicksFrozen(); + + if (this.isInPowderSnow && this.canFreeze()) { +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java +index 9693656418210957000add9b6670c4bee67f8462..4e6afa243d6108cb946a8a7cf96c4036a3c2ac0c 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java +@@ -324,6 +324,17 @@ public abstract class CraftEntity implements org.bukkit.entity.Entity { + return this.getHandle().isFullyFrozen(); + } + ++ // Paper start - Freeze Tick Lock API ++ @Override ++ public boolean isFreezeTickingLocked() { ++ return this.entity.freezeLocked; ++ } ++ ++ @Override ++ public void lockFreezeTicks(boolean locked) { ++ this.entity.freezeLocked = locked; ++ } ++ // Paper end - Freeze Tick Lock API + @Override + public void remove() { + this.entity.pluginRemoved = true; diff --git a/patches/server/0650-Reset-placed-block-on-exception.patch b/patches/server/0650-Reset-placed-block-on-exception.patch deleted file mode 100644 index c5b6f9f135e8..000000000000 --- a/patches/server/0650-Reset-placed-block-on-exception.patch +++ /dev/null @@ -1,39 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Nassim Jahnke -Date: Fri, 7 Jan 2022 11:45:15 +0100 -Subject: [PATCH] Reset placed block on exception - - -diff --git a/src/main/java/net/minecraft/world/item/BlockItem.java b/src/main/java/net/minecraft/world/item/BlockItem.java -index cee3f1200af602b5dfd0b27d05eb01826c5bbb1d..7d76cdc59984b156628273c8357485eb10046007 100644 ---- a/src/main/java/net/minecraft/world/item/BlockItem.java -+++ b/src/main/java/net/minecraft/world/item/BlockItem.java -@@ -77,6 +77,7 @@ public class BlockItem extends Item { - if (this instanceof PlaceOnWaterBlockItem || this instanceof SolidBucketItem) { - blockstate = org.bukkit.craftbukkit.block.CraftBlockStates.getBlockState(blockactioncontext1.getLevel(), blockactioncontext1.getClickedPos()); - } -+ final org.bukkit.block.BlockState oldBlockstate = blockstate != null ? blockstate : org.bukkit.craftbukkit.block.CraftBlockStates.getBlockState(blockactioncontext1.getLevel(), blockactioncontext1.getClickedPos()); // Paper - Reset placed block on exception - // CraftBukkit end - - if (iblockdata == null) { -@@ -92,8 +93,20 @@ public class BlockItem extends Item { - - if (iblockdata1.is(iblockdata.getBlock())) { - iblockdata1 = this.updateBlockStateFromTag(blockposition, world, itemstack, iblockdata1); -+ // Paper start - Reset placed block on exception -+ try { - this.updateCustomBlockEntityTag(blockposition, world, entityhuman, itemstack, iblockdata1); - BlockItem.updateBlockEntityComponents(world, blockposition, itemstack); -+ } catch (Exception e) { -+ oldBlockstate.update(true, false); -+ if (entityhuman instanceof ServerPlayer player) { -+ org.apache.logging.log4j.LogManager.getLogger().error("Player {} tried placing invalid block", player.getScoreboardName(), e); -+ player.getBukkitEntity().kickPlayer("Packet processing error"); -+ return InteractionResult.FAIL; -+ } -+ throw e; // Rethrow exception if not placed by a player -+ } -+ // Paper end - Reset placed block on exception - iblockdata1.getBlock().setPlacedBy(world, blockposition, iblockdata1, entityhuman, itemstack); - // CraftBukkit start - if (blockstate != null) { diff --git a/patches/server/0651-Add-configurable-height-for-slime-spawn.patch b/patches/server/0651-Add-configurable-height-for-slime-spawn.patch deleted file mode 100644 index 5ed7f1549da5..000000000000 --- a/patches/server/0651-Add-configurable-height-for-slime-spawn.patch +++ /dev/null @@ -1,35 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Doc -Date: Mon, 2 Aug 2021 11:24:39 -0400 -Subject: [PATCH] Add configurable height for slime spawn - - -diff --git a/src/main/java/net/minecraft/world/entity/monster/Slime.java b/src/main/java/net/minecraft/world/entity/monster/Slime.java -index b54c30ba73b6ab069c0c7c1cd2b193090da79667..adebb66e550f805f444bec22e9a4dd575a642b43 100644 ---- a/src/main/java/net/minecraft/world/entity/monster/Slime.java -+++ b/src/main/java/net/minecraft/world/entity/monster/Slime.java -@@ -348,7 +348,11 @@ public class Slime extends Mob implements Enemy { - return checkMobSpawnRules(type, world, spawnReason, pos, random); - } - -- if (world.getBiome(pos).is(BiomeTags.ALLOWS_SURFACE_SLIME_SPAWNS) && pos.getY() > 50 && pos.getY() < 70 && random.nextFloat() < 0.5F && random.nextFloat() < world.getMoonBrightness() && world.getMaxLocalRawBrightness(pos) <= random.nextInt(8)) { -+ // Paper start - Replace rules for Height in Swamp Biome -+ final double maxHeightSwamp = world.getMinecraftWorld().paperConfig().entities.spawning.slimeSpawnHeight.surfaceBiome.maximum; -+ final double minHeightSwamp = world.getMinecraftWorld().paperConfig().entities.spawning.slimeSpawnHeight.surfaceBiome.minimum; -+ if (world.getBiome(pos).is(BiomeTags.ALLOWS_SURFACE_SLIME_SPAWNS) && pos.getY() > minHeightSwamp && pos.getY() < maxHeightSwamp && random.nextFloat() < 0.5F && random.nextFloat() < world.getMoonBrightness() && world.getMaxLocalRawBrightness(pos) <= random.nextInt(8)) { -+ // Paper end - Replace rules for Height in Swamp Biome - return checkMobSpawnRules(type, world, spawnReason, pos, random); - } - -@@ -359,7 +363,10 @@ public class Slime extends Mob implements Enemy { - ChunkPos chunkcoordintpair = new ChunkPos(pos); - boolean flag = world.getMinecraftWorld().paperConfig().entities.spawning.allChunksAreSlimeChunks || WorldgenRandom.seedSlimeChunk(chunkcoordintpair.x, chunkcoordintpair.z, ((WorldGenLevel) world).getSeed(), world.getMinecraftWorld().spigotConfig.slimeSeed).nextInt(10) == 0; // Spigot // Paper - -- if (random.nextInt(10) == 0 && flag && pos.getY() < 40) { -+ // Paper start - Replace rules for Height in Slime Chunks -+ final double maxHeightSlimeChunk = world.getMinecraftWorld().paperConfig().entities.spawning.slimeSpawnHeight.slimeChunk.maximum; -+ if (random.nextInt(10) == 0 && flag && pos.getY() < maxHeightSlimeChunk) { -+ // Paper end - Replace rules for Height in Slime Chunks - return checkMobSpawnRules(type, world, spawnReason, pos, random); - } - } diff --git a/patches/server/0656-More-PotionEffectType-API.patch b/patches/server/0651-More-PotionEffectType-API.patch similarity index 100% rename from patches/server/0656-More-PotionEffectType-API.patch rename to patches/server/0651-More-PotionEffectType-API.patch diff --git a/patches/server/0652-Fix-xp-reward-for-baby-zombies.patch b/patches/server/0652-Fix-xp-reward-for-baby-zombies.patch deleted file mode 100644 index a4fec4e0f2f1..000000000000 --- a/patches/server/0652-Fix-xp-reward-for-baby-zombies.patch +++ /dev/null @@ -1,32 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Jake Potrebic -Date: Sun, 16 Jan 2022 10:34:02 -0800 -Subject: [PATCH] Fix xp reward for baby zombies - -The field that tracks the xpReward was not -getting reset if the death was cancelled -so this resets it after each call to -Zombie#getExperienceReward - -diff --git a/src/main/java/net/minecraft/world/entity/monster/Zombie.java b/src/main/java/net/minecraft/world/entity/monster/Zombie.java -index fd304e7d8305877d56de7a38b8664e5a70bf0c33..2280004638fd19ed018cb3e77d53a018b34ec516 100644 ---- a/src/main/java/net/minecraft/world/entity/monster/Zombie.java -+++ b/src/main/java/net/minecraft/world/entity/monster/Zombie.java -@@ -176,11 +176,16 @@ public class Zombie extends Monster { - - @Override - protected int getBaseExperienceReward() { -+ final int previousReward = this.xpReward; // Paper - store previous value to reset after calculating XP reward - if (this.isBaby()) { - this.xpReward = (int) ((double) this.xpReward * 2.5D); - } - -- return super.getBaseExperienceReward(); -+ // Paper start - store previous value to reset after calculating XP reward -+ int reward = super.getBaseExperienceReward(); -+ this.xpReward = previousReward; -+ return reward; -+ // Paper end - store previous value to reset after calculating XP reward - } - - @Override diff --git a/patches/server/0652-Use-a-CHM-for-StructureTemplate.Pallete-cache.patch b/patches/server/0652-Use-a-CHM-for-StructureTemplate.Pallete-cache.patch new file mode 100644 index 000000000000..027285c2b186 --- /dev/null +++ b/patches/server/0652-Use-a-CHM-for-StructureTemplate.Pallete-cache.patch @@ -0,0 +1,20 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Shane Freeder +Date: Mon, 12 Jul 2021 12:28:29 +0100 +Subject: [PATCH] Use a CHM for StructureTemplate.Pallete cache + +fixes a CME due to this collection being shared across threads + +diff --git a/src/main/java/net/minecraft/world/level/levelgen/structure/templatesystem/StructureTemplate.java b/src/main/java/net/minecraft/world/level/levelgen/structure/templatesystem/StructureTemplate.java +index e7900e7e8263cae131dad2427fb2bfdadf5b8d14..b120949667ae0169a667b329b3cabbd79a0a5bda 100644 +--- a/src/main/java/net/minecraft/world/level/levelgen/structure/templatesystem/StructureTemplate.java ++++ b/src/main/java/net/minecraft/world/level/levelgen/structure/templatesystem/StructureTemplate.java +@@ -889,7 +889,7 @@ public class StructureTemplate { + public static final class Palette { + + private final List blocks; +- private final Map> cache = Maps.newHashMap(); ++ private final Map> cache = Maps.newConcurrentMap(); // Paper - Fix CME due to this collection being shared across threads + @Nullable + private List cachedJigsaws; + diff --git a/patches/server/0653-API-for-creating-command-sender-which-forwards-feedb.patch b/patches/server/0653-API-for-creating-command-sender-which-forwards-feedb.patch new file mode 100644 index 000000000000..76a09348afdd --- /dev/null +++ b/patches/server/0653-API-for-creating-command-sender-which-forwards-feedb.patch @@ -0,0 +1,170 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Jason Penilla <11360596+jpenilla@users.noreply.github.com> +Date: Tue, 1 Feb 2022 15:51:55 -0700 +Subject: [PATCH] API for creating command sender which forwards feedback + + +diff --git a/src/main/java/io/papermc/paper/commands/FeedbackForwardingSender.java b/src/main/java/io/papermc/paper/commands/FeedbackForwardingSender.java +new file mode 100644 +index 0000000000000000000000000000000000000000..e3a5f1ec376319bdfda87fa27ae217bff3914292 +--- /dev/null ++++ b/src/main/java/io/papermc/paper/commands/FeedbackForwardingSender.java +@@ -0,0 +1,111 @@ ++package io.papermc.paper.commands; ++ ++import io.papermc.paper.adventure.PaperAdventure; ++import java.util.UUID; ++import java.util.function.Consumer; ++import net.kyori.adventure.audience.MessageType; ++import net.kyori.adventure.identity.Identity; ++import net.kyori.adventure.text.Component; ++import net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer; ++import net.minecraft.commands.CommandSource; ++import net.minecraft.commands.CommandSourceStack; ++import net.minecraft.server.level.ServerLevel; ++import net.minecraft.world.phys.Vec2; ++import net.minecraft.world.phys.Vec3; ++import org.bukkit.command.CommandSender; ++import org.bukkit.craftbukkit.CraftServer; ++import org.bukkit.craftbukkit.command.ServerCommandSender; ++import org.checkerframework.checker.nullness.qual.NonNull; ++import org.checkerframework.checker.nullness.qual.Nullable; ++import org.checkerframework.framework.qual.DefaultQualifier; ++ ++@DefaultQualifier(NonNull.class) ++public final class FeedbackForwardingSender extends ServerCommandSender { ++ private final Consumer feedback; ++ private final CraftServer server; ++ ++ public FeedbackForwardingSender(final Consumer feedback, final CraftServer server) { ++ super(((ServerCommandSender) server.getConsoleSender()).perm); ++ this.server = server; ++ this.feedback = feedback; ++ } ++ ++ @Override ++ public void sendMessage(final String message) { ++ this.sendMessage(LegacyComponentSerializer.legacySection().deserialize(message)); ++ } ++ ++ @Override ++ public void sendMessage(final String... messages) { ++ for (final String message : messages) { ++ this.sendMessage(message); ++ } ++ } ++ ++ @Override ++ public void sendMessage(final Identity identity, final Component message, final MessageType type) { ++ this.feedback.accept(message); ++ } ++ ++ @Override ++ public String getName() { ++ return "FeedbackForwardingSender"; ++ } ++ ++ @Override ++ public Component name() { ++ return Component.text(this.getName()); ++ } ++ ++ @Override ++ public boolean isOp() { ++ return true; ++ } ++ ++ @Override ++ public void setOp(final boolean value) { ++ throw new UnsupportedOperationException("Cannot change operator status of " + this.getClass().getName()); ++ } ++ ++ public CommandSourceStack asVanilla() { ++ final @Nullable ServerLevel overworld = this.server.getServer().overworld(); ++ return new CommandSourceStack( ++ new Source(this), ++ overworld == null ? Vec3.ZERO : Vec3.atLowerCornerOf(overworld.getSharedSpawnPos()), ++ Vec2.ZERO, ++ overworld, ++ 4, ++ this.getName(), ++ net.minecraft.network.chat.Component.literal(this.getName()), ++ this.server.getServer(), ++ null ++ ); ++ } ++ ++ private record Source(FeedbackForwardingSender sender) implements CommandSource { ++ @Override ++ public void sendSystemMessage(final net.minecraft.network.chat.Component message) { ++ this.sender.sendMessage(Identity.nil(), PaperAdventure.asAdventure(message)); ++ } ++ ++ @Override ++ public boolean acceptsSuccess() { ++ return true; ++ } ++ ++ @Override ++ public boolean acceptsFailure() { ++ return true; ++ } ++ ++ @Override ++ public boolean shouldInformAdmins() { ++ return false; ++ } ++ ++ @Override ++ public CommandSender getBukkitSender(final CommandSourceStack stack) { ++ return this.sender; ++ } ++ } ++} +diff --git a/src/main/java/org/bukkit/craftbukkit/CraftServer.java b/src/main/java/org/bukkit/craftbukkit/CraftServer.java +index 6d32505266fef119289bcf6761c1948368238eb9..9de7978c7383f8364feba82e9cd3efbfcce00e3c 100644 +--- a/src/main/java/org/bukkit/craftbukkit/CraftServer.java ++++ b/src/main/java/org/bukkit/craftbukkit/CraftServer.java +@@ -2165,6 +2165,13 @@ public final class CraftServer implements Server { + return this.console.console; + } + ++ // Paper start ++ @Override ++ public CommandSender createCommandSender(final java.util.function.Consumer feedback) { ++ return new io.papermc.paper.commands.FeedbackForwardingSender(feedback, this); ++ } ++ // Paper end ++ + public EntityMetadataStore getEntityMetadata() { + return this.entityMetadata; + } +diff --git a/src/main/java/org/bukkit/craftbukkit/command/ServerCommandSender.java b/src/main/java/org/bukkit/craftbukkit/command/ServerCommandSender.java +index 7f22950ae61436e91a59cd29a345809c42bbe739..1e3091687735b461d3b6a313ab8761127981d3e8 100644 +--- a/src/main/java/org/bukkit/craftbukkit/command/ServerCommandSender.java ++++ b/src/main/java/org/bukkit/craftbukkit/command/ServerCommandSender.java +@@ -12,7 +12,7 @@ import org.bukkit.permissions.PermissionAttachmentInfo; + import org.bukkit.plugin.Plugin; + + public abstract class ServerCommandSender implements CommandSender { +- private final PermissibleBase perm; ++ public final PermissibleBase perm; // Paper + private net.kyori.adventure.pointer.Pointers adventure$pointers; // Paper - implement pointers + + protected ServerCommandSender() { +diff --git a/src/main/java/org/bukkit/craftbukkit/command/VanillaCommandWrapper.java b/src/main/java/org/bukkit/craftbukkit/command/VanillaCommandWrapper.java +index 4e81c26fdbd089961b2577168c716bf29d504d40..a35e2d2f53e8308d51e5a07b34c56d05a707bc14 100644 +--- a/src/main/java/org/bukkit/craftbukkit/command/VanillaCommandWrapper.java ++++ b/src/main/java/org/bukkit/craftbukkit/command/VanillaCommandWrapper.java +@@ -81,6 +81,11 @@ public final class VanillaCommandWrapper extends BukkitCommand { + if (sender instanceof ProxiedCommandSender) { + return ((ProxiedNativeCommandSender) sender).getHandle(); + } ++ // Paper start ++ if (sender instanceof io.papermc.paper.commands.FeedbackForwardingSender feedback) { ++ return feedback.asVanilla(); ++ } ++ // Paper end + + throw new IllegalArgumentException("Cannot make " + sender + " a vanilla command listener"); + } diff --git a/patches/server/0653-Multi-Block-Change-API-Implementation.patch b/patches/server/0653-Multi-Block-Change-API-Implementation.patch deleted file mode 100644 index 740aad5839e3..000000000000 --- a/patches/server/0653-Multi-Block-Change-API-Implementation.patch +++ /dev/null @@ -1,62 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Brody Beckwith -Date: Fri, 14 Jan 2022 00:41:11 -0500 -Subject: [PATCH] Multi Block Change API Implementation - - -diff --git a/src/main/java/net/minecraft/network/protocol/game/ClientboundSectionBlocksUpdatePacket.java b/src/main/java/net/minecraft/network/protocol/game/ClientboundSectionBlocksUpdatePacket.java -index 926ff9be3d9e3f5d620e4c7ccb22b9f64865ff8c..1a37654aff9a9c86c9f7af10a1cf721371f0c5ec 100644 ---- a/src/main/java/net/minecraft/network/protocol/game/ClientboundSectionBlocksUpdatePacket.java -+++ b/src/main/java/net/minecraft/network/protocol/game/ClientboundSectionBlocksUpdatePacket.java -@@ -62,6 +62,14 @@ public class ClientboundSectionBlocksUpdatePacket implements Packet blockChanges) { -+ this.sectionPos = sectionPos; -+ this.positions = blockChanges.keySet().toShortArray(); -+ this.states = blockChanges.values().toArray(new BlockState[0]); -+ } -+ // Paper end - Multi Block Change API -+ - private void write(FriendlyByteBuf buf) { - buf.writeLong(this.sectionPos.asLong()); - buf.writeVarInt(this.positions.length); -diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java -index bc0d63f90e352ad4158d1dcc0f21a34954279095..ef289dba67db65a5e43104a4ca68bc03c8c5e4cb 100644 ---- a/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java -+++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java -@@ -930,6 +930,32 @@ public class CraftPlayer extends CraftHumanEntity implements Player { - this.getHandle().connection.send(packet); - } - -+ // Paper start -+ @Override -+ public void sendMultiBlockChange(final Map blockChanges) { -+ if (this.getHandle().connection == null) return; -+ -+ Map> sectionMap = new HashMap<>(); -+ -+ for (Map.Entry entry : blockChanges.entrySet()) { -+ BlockData blockData = entry.getValue(); -+ BlockPos blockPos = io.papermc.paper.util.MCUtil.toBlockPos(entry.getKey()); -+ SectionPos sectionPos = SectionPos.of(blockPos); -+ -+ it.unimi.dsi.fastutil.shorts.Short2ObjectMap sectionData = sectionMap.computeIfAbsent(sectionPos, key -> new it.unimi.dsi.fastutil.shorts.Short2ObjectArrayMap<>()); -+ sectionData.put(SectionPos.sectionRelativePos(blockPos), ((CraftBlockData) blockData).getState()); -+ } -+ -+ for (Map.Entry> entry : sectionMap.entrySet()) { -+ SectionPos sectionPos = entry.getKey(); -+ it.unimi.dsi.fastutil.shorts.Short2ObjectMap blockData = entry.getValue(); -+ -+ net.minecraft.network.protocol.game.ClientboundSectionBlocksUpdatePacket packet = new net.minecraft.network.protocol.game.ClientboundSectionBlocksUpdatePacket(sectionPos, blockData); -+ this.getHandle().connection.send(packet); -+ } -+ } -+ // Paper end -+ - @Override - public void sendBlockChanges(Collection blocks) { - Preconditions.checkArgument(blocks != null, "blocks must not be null"); diff --git a/patches/server/0654-Add-missing-structure-set-seed-configs.patch b/patches/server/0654-Add-missing-structure-set-seed-configs.patch new file mode 100644 index 000000000000..8677197bdc76 --- /dev/null +++ b/patches/server/0654-Add-missing-structure-set-seed-configs.patch @@ -0,0 +1,399 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Jake Potrebic +Date: Thu, 13 Jan 2022 23:05:53 -0800 +Subject: [PATCH] Add missing structure set seed configs + +The 4 missing structure set seed configs are strongholds, mineshafts, +buried treasure, and ancient cities. + +Strongholds use a ring placement scheme which isn't random so they +utilize the world seed by default, this adds a config to override it +for just generating the ring positions. + +Mineshafts and Buried Treasure structure sets are special cases +where the "salt" that can be defined for them via datapacks has 0 +effect because the difference between the spacing and separation is 1 +which is used as the upper bound in the random with salt. So the random +always returns the same int (0) so the salt has no effect. This adds +seeds/salts to the frequency reducer which has a similar effect. + +Co-authored-by: William Blake Galbreath + +diff --git a/src/main/java/net/minecraft/world/level/chunk/ChunkGenerator.java b/src/main/java/net/minecraft/world/level/chunk/ChunkGenerator.java +index fc8e3edd9734fa7b69f0fc6b4eefd8a704e451cf..31c5f54c90d6e35875f762747f8618e58e2eed91 100644 +--- a/src/main/java/net/minecraft/world/level/chunk/ChunkGenerator.java ++++ b/src/main/java/net/minecraft/world/level/chunk/ChunkGenerator.java +@@ -573,7 +573,7 @@ public abstract class ChunkGenerator { + } + } + +- if (structureplacement.isStructureChunk(placementCalculator, chunkcoordintpair.x, chunkcoordintpair.z)) { ++ if (structureplacement.isStructureChunk(placementCalculator, chunkcoordintpair.x, chunkcoordintpair.z, structureplacement instanceof net.minecraft.world.level.chunk.ChunkGeneratorStructureState.KeyedRandomSpreadStructurePlacement keyed ? keyed.key : null)) { // Paper - Add missing structure set seed configs + if (list.size() == 1) { + this.tryGenerateStructure((StructureSet.StructureSelectionEntry) list.get(0), structureAccessor, registryManager, randomstate, structureTemplateManager, placementCalculator.getLevelSeed(), chunk, chunkcoordintpair, sectionposition); + } else { +diff --git a/src/main/java/net/minecraft/world/level/chunk/ChunkGeneratorStructureState.java b/src/main/java/net/minecraft/world/level/chunk/ChunkGeneratorStructureState.java +index ed779a03e2ce7684428600dac4f2e92ab002f29f..a20520a6bd28bae1cee82258ac49d9753faba2bd 100644 +--- a/src/main/java/net/minecraft/world/level/chunk/ChunkGeneratorStructureState.java ++++ b/src/main/java/net/minecraft/world/level/chunk/ChunkGeneratorStructureState.java +@@ -50,13 +50,14 @@ public class ChunkGeneratorStructureState { + private final Map>> ringPositions = new Object2ObjectArrayMap(); + private boolean hasGeneratedPositions; + private final List> possibleStructureSets; ++ public final SpigotWorldConfig conf; // Paper - Add missing structure set seed configs + + public static ChunkGeneratorStructureState createForFlat(RandomState randomstate, long i, BiomeSource worldchunkmanager, Stream> stream, SpigotWorldConfig conf) { // Spigot + List> list = stream.filter((holder) -> { + return ChunkGeneratorStructureState.hasBiomesForStructureSet((StructureSet) holder.value(), worldchunkmanager); + }).toList(); + +- return new ChunkGeneratorStructureState(randomstate, worldchunkmanager, i, 0L, ChunkGeneratorStructureState.injectSpigot(list, conf)); // Spigot ++ return new ChunkGeneratorStructureState(randomstate, worldchunkmanager, i, 0L, ChunkGeneratorStructureState.injectSpigot(list, conf), conf); // Spigot + } + + public static ChunkGeneratorStructureState createForNormal(RandomState randomstate, long i, BiomeSource worldchunkmanager, HolderLookup holderlookup, SpigotWorldConfig conf) { // Spigot +@@ -64,14 +65,24 @@ public class ChunkGeneratorStructureState { + return ChunkGeneratorStructureState.hasBiomesForStructureSet((StructureSet) holder_c.value(), worldchunkmanager); + }).collect(Collectors.toUnmodifiableList()); + +- return new ChunkGeneratorStructureState(randomstate, worldchunkmanager, i, i, ChunkGeneratorStructureState.injectSpigot(list, conf)); // Spigot ++ return new ChunkGeneratorStructureState(randomstate, worldchunkmanager, i, i, ChunkGeneratorStructureState.injectSpigot(list, conf), conf); // Spigot + } ++ // Paper start - Add missing structure set seed configs; horrible hack because spigot creates a ton of direct Holders which lose track of the identifying key ++ public static final class KeyedRandomSpreadStructurePlacement extends RandomSpreadStructurePlacement { ++ public final net.minecraft.resources.ResourceKey key; ++ public KeyedRandomSpreadStructurePlacement(net.minecraft.resources.ResourceKey key, net.minecraft.core.Vec3i locateOffset, FrequencyReductionMethod frequencyReductionMethod, float frequency, int salt, java.util.Optional exclusionZone, int spacing, int separation, net.minecraft.world.level.levelgen.structure.placement.RandomSpreadType spreadType) { ++ super(locateOffset, frequencyReductionMethod, frequency, salt, exclusionZone, spacing, separation, spreadType); ++ this.key = key; ++ } ++ } ++ // Paper end - Add missing structure set seed configs + + // Spigot start + private static List> injectSpigot(List> list, SpigotWorldConfig conf) { + return list.stream().map((holder) -> { + StructureSet structureset = holder.value(); +- if (structureset.placement() instanceof RandomSpreadStructurePlacement randomConfig) { ++ final Holder newHolder; // Paper - Add missing structure set seed configs ++ if (structureset.placement() instanceof RandomSpreadStructurePlacement randomConfig && holder.unwrapKey().orElseThrow().location().getNamespace().equals(net.minecraft.resources.ResourceLocation.DEFAULT_NAMESPACE)) { // Paper - Add missing structure set seed configs; check namespace cause datapacks could add structure sets with the same path + String name = holder.unwrapKey().orElseThrow().location().getPath(); + int seed = randomConfig.salt; + +@@ -118,11 +129,27 @@ public class ChunkGeneratorStructureState { + case "villages": + seed = conf.villageSeed; + break; ++ // Paper start - Add missing structure set seed configs ++ case "ancient_cities": ++ seed = conf.ancientCitySeed; ++ break; ++ case "trail_ruins": ++ seed = conf.trailRuinsSeed; ++ break; ++ case "trial_chambers": ++ seed = conf.trialChambersSeed; ++ break; ++ // Paper end - Add missing structure set seed configs + } + +- structureset = new StructureSet(structureset.structures(), new RandomSpreadStructurePlacement(randomConfig.locateOffset, randomConfig.frequencyReductionMethod, randomConfig.frequency, seed, randomConfig.exclusionZone, randomConfig.spacing(), randomConfig.separation(), randomConfig.spreadType())); ++ // Paper start - Add missing structure set seed configs ++ structureset = new StructureSet(structureset.structures(), new KeyedRandomSpreadStructurePlacement(holder.unwrapKey().orElseThrow(), randomConfig.locateOffset, randomConfig.frequencyReductionMethod, randomConfig.frequency, seed, randomConfig.exclusionZone, randomConfig.spacing(), randomConfig.separation(), randomConfig.spreadType())); ++ newHolder = Holder.direct(structureset); // I really wish we didn't have to do this here ++ } else { ++ newHolder = holder; + } +- return Holder.direct(structureset); ++ return newHolder; ++ // Paper end - Add missing structure set seed configs + }).collect(Collectors.toUnmodifiableList()); + } + // Spigot end +@@ -139,12 +166,13 @@ public class ChunkGeneratorStructureState { + return stream.anyMatch(set::contains); + } + +- private ChunkGeneratorStructureState(RandomState noiseConfig, BiomeSource biomeSource, long structureSeed, long concentricRingSeed, List> structureSets) { ++ private ChunkGeneratorStructureState(RandomState noiseConfig, BiomeSource biomeSource, long structureSeed, long concentricRingSeed, List> structureSets, SpigotWorldConfig conf) { // Paper - Add missing structure set seed configs + this.randomState = noiseConfig; + this.levelSeed = structureSeed; + this.biomeSource = biomeSource; + this.concentricRingsSeed = concentricRingSeed; + this.possibleStructureSets = structureSets; ++ this.conf = conf; // Paper - Add missing structure set seed configs + } + + public List> possibleStructureSets() { +@@ -198,7 +226,13 @@ public class ChunkGeneratorStructureState { + HolderSet holderset = placement.preferredBiomes(); + RandomSource randomsource = RandomSource.create(); + ++ // Paper start - Add missing structure set seed configs ++ if (this.conf.strongholdSeed != null && structureSetEntry.is(net.minecraft.world.level.levelgen.structure.BuiltinStructureSets.STRONGHOLDS)) { ++ randomsource.setSeed(this.conf.strongholdSeed); ++ } else { ++ // Paper end - Add missing structure set seed configs + randomsource.setSeed(this.concentricRingsSeed); ++ } // Paper - Add missing structure set seed configs + double d0 = randomsource.nextDouble() * Math.PI * 2.0D; + int l = 0; + int i1 = 0; +@@ -275,7 +309,7 @@ public class ChunkGeneratorStructureState { + + for (int l = centerChunkX - chunkCount; l <= centerChunkX + chunkCount; ++l) { + for (int i1 = centerChunkZ - chunkCount; i1 <= centerChunkZ + chunkCount; ++i1) { +- if (structureplacement.isStructureChunk(this, l, i1)) { ++ if (structureplacement.isStructureChunk(this, l, i1, structureplacement instanceof KeyedRandomSpreadStructurePlacement keyed ? keyed.key : null)) { // Paper - Add missing structure set seed configs + return true; + } + } +diff --git a/src/main/java/net/minecraft/world/level/levelgen/structure/StructureCheck.java b/src/main/java/net/minecraft/world/level/levelgen/structure/StructureCheck.java +index 953ab7638f7242b5a11dd1de8786172443a0558c..5f354b333a39b873915bedd57b647355ae5bdf56 100644 +--- a/src/main/java/net/minecraft/world/level/levelgen/structure/StructureCheck.java ++++ b/src/main/java/net/minecraft/world/level/levelgen/structure/StructureCheck.java +@@ -74,6 +74,20 @@ public class StructureCheck { + this.fixerUpper = dataFixer; + } + ++ // Paper start - add missing structure salt configs ++ @Nullable ++ private Integer getSaltOverride(Structure type) { ++ if (this.heightAccessor instanceof net.minecraft.server.level.ServerLevel serverLevel) { ++ if (type instanceof net.minecraft.world.level.levelgen.structure.structures.MineshaftStructure) { ++ return serverLevel.spigotConfig.mineshaftSeed; ++ } else if (type instanceof net.minecraft.world.level.levelgen.structure.structures.BuriedTreasureStructure) { ++ return serverLevel.spigotConfig.buriedTreasureSeed; ++ } ++ } ++ return null; ++ } ++ // Paper end - add missing structure seed configs ++ + public StructureCheckResult checkStart(ChunkPos pos, Structure type, StructurePlacement placement, boolean skipReferencedStructures) { + long l = pos.toLong(); + Object2IntMap object2IntMap = this.loadedChunks.get(l); +@@ -83,7 +97,7 @@ public class StructureCheck { + StructureCheckResult structureCheckResult = this.tryLoadFromStorage(pos, type, skipReferencedStructures, l); + if (structureCheckResult != null) { + return structureCheckResult; +- } else if (!placement.applyAdditionalChunkRestrictions(pos.x, pos.z, this.seed)) { ++ } else if (!placement.applyAdditionalChunkRestrictions(pos.x, pos.z, this.seed, this.getSaltOverride(type))) { // Paper - add missing structure seed configs + return StructureCheckResult.START_NOT_PRESENT; + } else { + boolean bl = this.featureChecks +diff --git a/src/main/java/net/minecraft/world/level/levelgen/structure/placement/StructurePlacement.java b/src/main/java/net/minecraft/world/level/levelgen/structure/placement/StructurePlacement.java +index a4c34e9415632354d33668a38d06453ada4d3c77..cbf13e4f2da6a27619e9bc9a7cd73bb6e69cad2a 100644 +--- a/src/main/java/net/minecraft/world/level/levelgen/structure/placement/StructurePlacement.java ++++ b/src/main/java/net/minecraft/world/level/levelgen/structure/placement/StructurePlacement.java +@@ -79,14 +79,30 @@ public abstract class StructurePlacement { + return this.exclusionZone; + } + ++ @Deprecated @io.papermc.paper.annotation.DoNotUse // Paper - Add missing structure set seed configs + public boolean isStructureChunk(ChunkGeneratorStructureState calculator, int chunkX, int chunkZ) { ++ // Paper start - Add missing structure set seed configs ++ return this.isStructureChunk(calculator, chunkX, chunkZ, null); ++ } ++ public boolean isStructureChunk(ChunkGeneratorStructureState calculator, int chunkX, int chunkZ, @org.jetbrains.annotations.Nullable net.minecraft.resources.ResourceKey structureSetKey) { ++ Integer saltOverride = null; ++ if (structureSetKey != null) { ++ if (structureSetKey == net.minecraft.world.level.levelgen.structure.BuiltinStructureSets.MINESHAFTS) { ++ saltOverride = calculator.conf.mineshaftSeed; ++ } else if (structureSetKey == net.minecraft.world.level.levelgen.structure.BuiltinStructureSets.BURIED_TREASURES) { ++ saltOverride = calculator.conf.buriedTreasureSeed; ++ } ++ } ++ // Paper end - Add missing structure set seed configs + return this.isPlacementChunk(calculator, chunkX, chunkZ) +- && this.applyAdditionalChunkRestrictions(chunkX, chunkZ, calculator.getLevelSeed()) ++ && this.applyAdditionalChunkRestrictions(chunkX, chunkZ, calculator.getLevelSeed(), saltOverride) // Paper - Add missing structure set seed configs + && this.applyInteractionsWithOtherStructures(calculator, chunkX, chunkZ); + } + +- public boolean applyAdditionalChunkRestrictions(int chunkX, int chunkZ, long seed) { +- return !(this.frequency < 1.0F) || this.frequencyReductionMethod.shouldGenerate(seed, this.salt, chunkX, chunkZ, this.frequency); ++ // Paper start - Add missing structure set seed configs ++ public boolean applyAdditionalChunkRestrictions(int chunkX, int chunkZ, long seed, @org.jetbrains.annotations.Nullable Integer saltOverride) { ++ return !(this.frequency < 1.0F) || this.frequencyReductionMethod.shouldGenerate(seed, this.salt, chunkX, chunkZ, this.frequency, saltOverride); ++ // Paper end - Add missing structure set seed configs + } + + public boolean applyInteractionsWithOtherStructures(ChunkGeneratorStructureState calculator, int centerChunkX, int centerChunkZ) { +@@ -101,25 +117,31 @@ public abstract class StructurePlacement { + + public abstract StructurePlacementType type(); + +- private static boolean probabilityReducer(long seed, int salt, int chunkX, int chunkZ, float frequency) { ++ private static boolean probabilityReducer(long seed, int salt, int chunkX, int chunkZ, float frequency, @org.jetbrains.annotations.Nullable Integer saltOverride) { // Paper - Add missing structure set seed configs; ignore here + WorldgenRandom worldgenRandom = new WorldgenRandom(new LegacyRandomSource(0L)); + worldgenRandom.setLargeFeatureWithSalt(seed, salt, chunkX, chunkZ); + return worldgenRandom.nextFloat() < frequency; + } + +- private static boolean legacyProbabilityReducerWithDouble(long seed, int salt, int chunkX, int chunkZ, float frequency) { ++ private static boolean legacyProbabilityReducerWithDouble(long seed, int salt, int chunkX, int chunkZ, float frequency, @org.jetbrains.annotations.Nullable Integer saltOverride) { // Paper - Add missing structure set seed configs + WorldgenRandom worldgenRandom = new WorldgenRandom(new LegacyRandomSource(0L)); ++ if (saltOverride == null) { // Paper - Add missing structure set seed configs + worldgenRandom.setLargeFeatureSeed(seed, chunkX, chunkZ); ++ // Paper start - Add missing structure set seed configs ++ } else { ++ worldgenRandom.setLargeFeatureWithSalt(seed, chunkX, chunkZ, saltOverride); ++ } ++ // Paper end - Add missing structure set seed configs + return worldgenRandom.nextDouble() < (double)frequency; + } + +- private static boolean legacyArbitrarySaltProbabilityReducer(long seed, int salt, int chunkX, int chunkZ, float frequency) { ++ private static boolean legacyArbitrarySaltProbabilityReducer(long seed, int salt, int chunkX, int chunkZ, float frequency, @org.jetbrains.annotations.Nullable Integer saltOverride) { // Paper - Add missing structure set seed configs + WorldgenRandom worldgenRandom = new WorldgenRandom(new LegacyRandomSource(0L)); +- worldgenRandom.setLargeFeatureWithSalt(seed, chunkX, chunkZ, 10387320); ++ worldgenRandom.setLargeFeatureWithSalt(seed, chunkX, chunkZ, saltOverride != null ? saltOverride : HIGHLY_ARBITRARY_RANDOM_SALT); // Paper - Add missing structure set seed configs + return worldgenRandom.nextFloat() < frequency; + } + +- private static boolean legacyPillagerOutpostReducer(long seed, int salt, int chunkX, int chunkZ, float frequency) { ++ private static boolean legacyPillagerOutpostReducer(long seed, int salt, int chunkX, int chunkZ, float frequency, @org.jetbrains.annotations.Nullable Integer saltOverride) { // Paper - Add missing structure set seed configs; ignore here + int i = chunkX >> 4; + int j = chunkZ >> 4; + WorldgenRandom worldgenRandom = new WorldgenRandom(new LegacyRandomSource(0L)); +@@ -147,7 +169,7 @@ public abstract class StructurePlacement { + + @FunctionalInterface + public interface FrequencyReducer { +- boolean shouldGenerate(long seed, int salt, int chunkX, int chunkZ, float chance); ++ boolean shouldGenerate(long seed, int salt, int chunkX, int chunkZ, float chance, @org.jetbrains.annotations.Nullable Integer saltOverride); // Paper - Add missing structure set seed configs + } + + public static enum FrequencyReductionMethod implements StringRepresentable { +@@ -167,8 +189,8 @@ public abstract class StructurePlacement { + this.reducer = generationPredicate; + } + +- public boolean shouldGenerate(long seed, int salt, int chunkX, int chunkZ, float chance) { +- return this.reducer.shouldGenerate(seed, salt, chunkX, chunkZ, chance); ++ public boolean shouldGenerate(long seed, int salt, int chunkX, int chunkZ, float chance, @org.jetbrains.annotations.Nullable Integer saltOverride) { // Paper - Add missing structure set seed configs ++ return this.reducer.shouldGenerate(seed, salt, chunkX, chunkZ, chance, saltOverride); // Paper - Add missing structure set seed configs + } + + @Override +diff --git a/src/main/java/org/spigotmc/SpigotWorldConfig.java b/src/main/java/org/spigotmc/SpigotWorldConfig.java +index e76f96a5c48d1eda2f9bbb3e11dd79f23f9ab75c..2b263246135c85aa225120519e9702a628773935 100644 +--- a/src/main/java/org/spigotmc/SpigotWorldConfig.java ++++ b/src/main/java/org/spigotmc/SpigotWorldConfig.java +@@ -322,6 +322,18 @@ public class SpigotWorldConfig + public int mansionSeed; + public int fossilSeed; + public int portalSeed; ++ // Paper start - add missing structure set configs ++ public int ancientCitySeed; ++ public int trailRuinsSeed; ++ public int trialChambersSeed; ++ public int buriedTreasureSeed; ++ public Integer mineshaftSeed; ++ public Long strongholdSeed; ++ private N getSeed(String path, java.util.function.Function toNumberFunc) { ++ final String value = this.getString(path, "default"); ++ return org.apache.commons.lang3.math.NumberUtils.isParsable(value) ? toNumberFunc.apply(value) : null; ++ } ++ // Paper end + private void initWorldGenSeeds() + { + this.villageSeed = this.getInt( "seed-village", 10387312 ); +@@ -339,6 +351,14 @@ public class SpigotWorldConfig + this.mansionSeed = this.getInt( "seed-mansion", 10387319 ); + this.fossilSeed = this.getInt( "seed-fossil", 14357921 ); + this.portalSeed = this.getInt( "seed-portal", 34222645 ); ++ // Paper start - add missing structure set configs ++ this.ancientCitySeed = this.getInt("seed-ancientcity", 20083232); ++ this.trailRuinsSeed = this.getInt("seed-trailruins", 83469867); ++ this.trialChambersSeed = this.getInt("seed-trialchambers", 94251327); ++ this.buriedTreasureSeed = this.getInt("seed-buriedtreasure", 10387320); // StructurePlacement#HIGHLY_ARBITRARY_RANDOM_SALT ++ this.mineshaftSeed = this.getSeed("seed-mineshaft", Integer::parseInt); ++ this.strongholdSeed = this.getSeed("seed-stronghold", Long::parseLong); ++ // Paper end + this.log( "Custom Map Seeds: Village: " + this.villageSeed + " Desert: " + this.desertSeed + " Igloo: " + this.iglooSeed + " Jungle: " + this.jungleSeed + " Swamp: " + this.swampSeed + " Monument: " + this.monumentSeed + + " Ocean: " + this.oceanSeed + " Shipwreck: " + this.shipwreckSeed + " End City: " + this.endCitySeed + " Slime: " + this.slimeSeed + " Nether: " + this.netherSeed + " Mansion: " + this.mansionSeed + " Fossil: " + this.fossilSeed + " Portal: " + this.portalSeed ); + } +diff --git a/src/test/java/io/papermc/paper/world/structure/StructureSeedConfigTest.java b/src/test/java/io/papermc/paper/world/structure/StructureSeedConfigTest.java +new file mode 100644 +index 0000000000000000000000000000000000000000..cc1fb5ae9e0898735771c6163c9b90658fb61eed +--- /dev/null ++++ b/src/test/java/io/papermc/paper/world/structure/StructureSeedConfigTest.java +@@ -0,0 +1,77 @@ ++package io.papermc.paper.world.structure; ++ ++import io.papermc.paper.configuration.PaperConfigurations; ++import java.io.File; ++import java.lang.reflect.Field; ++import net.minecraft.core.Registry; ++import net.minecraft.core.registries.Registries; ++import net.minecraft.resources.ResourceKey; ++import net.minecraft.resources.ResourceLocation; ++import net.minecraft.world.level.levelgen.structure.BuiltinStructureSets; ++import net.minecraft.world.level.levelgen.structure.StructureSet; ++import net.minecraft.world.level.levelgen.structure.placement.StructurePlacement; ++import org.bukkit.configuration.file.YamlConfiguration; ++import org.bukkit.support.RegistryHelper; ++import org.bukkit.support.environment.AllFeatures; ++import org.jetbrains.annotations.NotNull; ++import org.junit.jupiter.api.Test; ++import org.spigotmc.SpigotConfig; ++import org.spigotmc.SpigotWorldConfig; ++ ++import static org.junit.jupiter.api.Assertions.assertEquals; ++ ++@AllFeatures ++public class StructureSeedConfigTest { ++ ++ @Test ++ public void checkStructureSeedDefaults() throws ReflectiveOperationException { ++ SpigotConfig.config = new YamlConfiguration() { ++ @Override ++ public void save(final @NotNull File file) { ++ // no-op ++ } ++ }; ++ final SpigotWorldConfig config = PaperConfigurations.SPIGOT_WORLD_DEFAULTS.get(); ++ ++ ++ final Registry structureSets = RegistryHelper.getRegistry().lookupOrThrow(Registries.STRUCTURE_SET); ++ for (final ResourceKey setKey : structureSets.registryKeySet()) { ++ assertEquals(ResourceLocation.DEFAULT_NAMESPACE, setKey.location().getNamespace()); ++ final StructureSet set = structureSets.getValueOrThrow(setKey); ++ if (setKey == BuiltinStructureSets.STRONGHOLDS) { // special case due to seed matching world seed ++ assertEquals(0, set.placement().salt); ++ continue; ++ } ++ int salt = switch (setKey.location().getPath()) { ++ case "villages" -> config.villageSeed; ++ case "desert_pyramids" -> config.desertSeed; ++ case "igloos" -> config.iglooSeed; ++ case "jungle_temples" -> config.jungleSeed; ++ case "swamp_huts" -> config.swampSeed; ++ case "pillager_outposts" -> config.outpostSeed; ++ case "ocean_monuments" -> config.monumentSeed; ++ case "woodland_mansions" -> config.mansionSeed; ++ case "buried_treasures" -> config.buriedTreasureSeed; ++ case "mineshafts" -> config.mineshaftSeed == null ? 0 : config.mineshaftSeed; // mineshaft seed is set differently ++ case "ruined_portals" -> config.portalSeed; ++ case "shipwrecks" -> config.shipwreckSeed; ++ case "ocean_ruins" -> config.oceanSeed; ++ case "nether_complexes" -> config.netherSeed; ++ case "nether_fossils" -> config.fossilSeed; ++ case "end_cities" -> config.endCitySeed; ++ case "ancient_cities" -> config.ancientCitySeed; ++ case "trail_ruins" -> config.trailRuinsSeed; ++ case "trial_chambers" -> config.trialChambersSeed; ++ default -> throw new AssertionError("Missing structure set seed in SpigotWorldConfig for " + setKey); ++ }; ++ if (setKey == BuiltinStructureSets.BURIED_TREASURES) { ++ final Field field = StructurePlacement.class.getDeclaredField("HIGHLY_ARBITRARY_RANDOM_SALT"); ++ field.trySetAccessible(); ++ assertEquals(0, set.placement().salt); ++ assertEquals(field.get(null), salt, "Mismatched default seed for " + setKey + ". Should be " + field.get(null)); ++ continue; ++ } ++ assertEquals(set.placement().salt, salt, "Mismatched default seed for " + setKey + ". Should be " + set.placement().salt); ++ } ++ } ++} diff --git a/patches/server/0654-Fix-NotePlayEvent.patch b/patches/server/0654-Fix-NotePlayEvent.patch deleted file mode 100644 index 4a601d8f1be6..000000000000 --- a/patches/server/0654-Fix-NotePlayEvent.patch +++ /dev/null @@ -1,54 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Kieran Wallbanks -Date: Mon, 21 Jun 2021 14:23:50 +0100 -Subject: [PATCH] Fix NotePlayEvent - -== AT == -public org.bukkit.craftbukkit.block.data.CraftBlockData toNMS(Ljava/lang/Enum;Ljava/lang/Class;)Ljava/lang/Enum; - -diff --git a/src/main/java/net/minecraft/world/level/block/NoteBlock.java b/src/main/java/net/minecraft/world/level/block/NoteBlock.java -index 6215f096849f48843d6e25875b8b71f6827ec0a5..f77d51d10e01fc4eaf5516c05c8be0ef7a425893 100644 ---- a/src/main/java/net/minecraft/world/level/block/NoteBlock.java -+++ b/src/main/java/net/minecraft/world/level/block/NoteBlock.java -@@ -94,11 +94,12 @@ public class NoteBlock extends Block { - private void playNote(@Nullable Entity entity, BlockState state, Level world, BlockPos pos) { - if (((NoteBlockInstrument) state.getValue(NoteBlock.INSTRUMENT)).worksAboveNoteBlock() || world.getBlockState(pos.above()).isAir()) { - // CraftBukkit start -- org.bukkit.event.block.NotePlayEvent event = org.bukkit.craftbukkit.event.CraftEventFactory.callNotePlayEvent(world, pos, state.getValue(NoteBlock.INSTRUMENT), state.getValue(NoteBlock.NOTE)); -- if (event.isCancelled()) { -- return; -- } -+ // org.bukkit.event.block.NotePlayEvent event = org.bukkit.craftbukkit.event.CraftEventFactory.callNotePlayEvent(world, pos, state.getValue(NoteBlock.INSTRUMENT), state.getValue(NoteBlock.NOTE)); -+ // if (event.isCancelled()) { -+ // return; -+ // } - // CraftBukkit end -+ // Paper - move NotePlayEvent call to fix instrument/note changes; TODO any way to cancel the game event? - world.blockEvent(pos, this, 0, 0); - world.gameEvent(entity, (Holder) GameEvent.NOTE_BLOCK_PLAY, pos); - } -@@ -138,10 +139,14 @@ public class NoteBlock extends Block { - @Override - protected boolean triggerEvent(BlockState state, Level world, BlockPos pos, int type, int data) { - NoteBlockInstrument blockpropertyinstrument = (NoteBlockInstrument) state.getValue(NoteBlock.INSTRUMENT); -+ // Paper start - move NotePlayEvent call to fix instrument/note changes -+ org.bukkit.event.block.NotePlayEvent event = org.bukkit.craftbukkit.event.CraftEventFactory.callNotePlayEvent(world, pos, blockpropertyinstrument, state.getValue(NOTE)); -+ if (event.isCancelled()) return false; -+ // Paper end - move NotePlayEvent call to fix instrument/note changes - float f; - - if (blockpropertyinstrument.isTunable()) { -- int k = (Integer) state.getValue(NoteBlock.NOTE); -+ int k = event.getNote().getId(); // Paper - move NotePlayEvent call to fix instrument/note changes - - f = NoteBlock.getPitchFromNote(k); - world.addParticle(ParticleTypes.NOTE, (double) pos.getX() + 0.5D, (double) pos.getY() + 1.2D, (double) pos.getZ() + 0.5D, (double) k / 24.0D, 0.0D, 0.0D); -@@ -160,7 +165,7 @@ public class NoteBlock extends Block { - - holder = Holder.direct(SoundEvent.createVariableRangeEvent(minecraftkey)); - } else { -- holder = blockpropertyinstrument.getSoundEvent(); -+ holder = org.bukkit.craftbukkit.block.data.CraftBlockData.toNMS(event.getInstrument(), NoteBlockInstrument.class).getSoundEvent(); // Paper - move NotePlayEvent call to fix instrument/note changes - } - - world.playSeededSound((Player) null, (double) pos.getX() + 0.5D, (double) pos.getY() + 0.5D, (double) pos.getZ() + 0.5D, holder, SoundSource.RECORDS, 3.0F, f, world.random.nextLong()); diff --git a/patches/server/0655-Fix-cancelled-powdered-snow-bucket-placement.patch b/patches/server/0655-Fix-cancelled-powdered-snow-bucket-placement.patch new file mode 100644 index 000000000000..d1a867e6051b --- /dev/null +++ b/patches/server/0655-Fix-cancelled-powdered-snow-bucket-placement.patch @@ -0,0 +1,31 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Jake Potrebic +Date: Fri, 8 Oct 2021 13:12:58 -0700 +Subject: [PATCH] Fix cancelled powdered snow bucket placement + +Cancelling the placement of powdered snow from the powdered +snow bucket didn't revert grass that became snowy because of the +placement. + +diff --git a/src/main/java/net/minecraft/world/item/ItemStack.java b/src/main/java/net/minecraft/world/item/ItemStack.java +index 8c9ae9ac38def29ae4cd8944395e566e434d46d0..a100c7a53b4b1dac0a01ee65418d44297bcdb93f 100644 +--- a/src/main/java/net/minecraft/world/item/ItemStack.java ++++ b/src/main/java/net/minecraft/world/item/ItemStack.java +@@ -425,7 +425,7 @@ public final class ItemStack implements DataComponentHolder { + int oldCount = this.getCount(); + ServerLevel world = (ServerLevel) context.getLevel(); + +- if (!(item instanceof BucketItem || item instanceof SolidBucketItem)) { // if not bucket ++ if (!(item instanceof BucketItem/* || item instanceof SolidBucketItem*/)) { // if not bucket // Paper - Fix cancelled powdered snow bucket placement + world.captureBlockStates = true; + // special case bonemeal + if (item == Items.BONE_MEAL) { +@@ -488,7 +488,7 @@ public final class ItemStack implements DataComponentHolder { + world.capturedBlockStates.clear(); + if (blocks.size() > 1) { + placeEvent = org.bukkit.craftbukkit.event.CraftEventFactory.callBlockMultiPlaceEvent(world, entityhuman, enumhand, blocks, blockposition.getX(), blockposition.getY(), blockposition.getZ()); +- } else if (blocks.size() == 1) { ++ } else if (blocks.size() == 1 && item != Items.POWDER_SNOW_BUCKET) { // Paper - Fix cancelled powdered snow bucket placement + placeEvent = org.bukkit.craftbukkit.event.CraftEventFactory.callBlockPlaceEvent(world, entityhuman, enumhand, blocks.get(0), blockposition.getX(), blockposition.getY(), blockposition.getZ()); + } + diff --git a/patches/server/0655-Freeze-Tick-Lock-API.patch b/patches/server/0655-Freeze-Tick-Lock-API.patch deleted file mode 100644 index 69adb442ee92..000000000000 --- a/patches/server/0655-Freeze-Tick-Lock-API.patch +++ /dev/null @@ -1,82 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Owen1212055 <23108066+Owen1212055@users.noreply.github.com> -Date: Sun, 26 Dec 2021 20:27:43 -0500 -Subject: [PATCH] Freeze Tick Lock API - - -diff --git a/src/main/java/net/minecraft/world/entity/Entity.java b/src/main/java/net/minecraft/world/entity/Entity.java -index 9f40cc481ded9279319d92fe5a0a9278a3f5d9c2..7d9ebc71b794d40246cbd43ff4de5795970e7ebe 100644 ---- a/src/main/java/net/minecraft/world/entity/Entity.java -+++ b/src/main/java/net/minecraft/world/entity/Entity.java -@@ -411,6 +411,7 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess - private org.bukkit.util.Vector origin; - @javax.annotation.Nullable - private UUID originWorld; -+ public boolean freezeLocked = false; // Paper - Freeze Tick Lock API - - public void setOrigin(@javax.annotation.Nonnull Location location) { - this.origin = location.toVector(); -@@ -751,7 +752,7 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess - this.setRemainingFireTicks(this.remainingFireTicks - 1); - } - -- if (this.getTicksFrozen() > 0) { -+ if (this.getTicksFrozen() > 0 && !freezeLocked) { // Paper - Freeze Tick Lock API - this.setTicksFrozen(0); - this.level().levelEvent((Player) null, 1009, this.blockPosition, 1); - } -@@ -2312,6 +2313,9 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess - if (fromNetherPortal) { - nbttagcompound.putBoolean("Paper.FromNetherPortal", true); - } -+ if (freezeLocked) { -+ nbttagcompound.putBoolean("Paper.FreezeLock", true); -+ } - // Paper end - return nbttagcompound; - } catch (Throwable throwable) { -@@ -2456,6 +2460,9 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess - if (spawnReason == null) { - spawnReason = org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason.DEFAULT; - } -+ if (nbt.contains("Paper.FreezeLock")) { -+ freezeLocked = nbt.getBoolean("Paper.FreezeLock"); -+ } - // Paper end - - } catch (Throwable throwable) { -diff --git a/src/main/java/net/minecraft/world/entity/LivingEntity.java b/src/main/java/net/minecraft/world/entity/LivingEntity.java -index ee7bb1a01bf027eb7b28e3795950a17e5f686815..01a6e91f1b8f619e99d9bccde12f6e91e6ab5eb8 100644 ---- a/src/main/java/net/minecraft/world/entity/LivingEntity.java -+++ b/src/main/java/net/minecraft/world/entity/LivingEntity.java -@@ -3497,7 +3497,7 @@ public abstract class LivingEntity extends Entity implements Attackable { - - this.level().getProfiler().pop(); - this.level().getProfiler().push("freezing"); -- if (!this.level().isClientSide && !this.isDeadOrDying()) { -+ if (!this.level().isClientSide && !this.isDeadOrDying() && !this.freezeLocked) { // Paper - Freeze Tick Lock API - int i = this.getTicksFrozen(); - - if (this.isInPowderSnow && this.canFreeze()) { -diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java -index 4ed5647101bbace0005b1ebfb824e4aed48e43cb..4c09f2529dd8eb7ac7d260d177f5292ff2339442 100644 ---- a/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java -+++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java -@@ -323,6 +323,17 @@ public abstract class CraftEntity implements org.bukkit.entity.Entity { - return this.getHandle().isFullyFrozen(); - } - -+ // Paper start - Freeze Tick Lock API -+ @Override -+ public boolean isFreezeTickingLocked() { -+ return this.entity.freezeLocked; -+ } -+ -+ @Override -+ public void lockFreezeTicks(boolean locked) { -+ this.entity.freezeLocked = locked; -+ } -+ // Paper end - Freeze Tick Lock API - @Override - public void remove() { - this.entity.pluginRemoved = true; diff --git a/patches/server/0656-Add-missing-Validate-calls-to-CraftServer-getSpawnLi.patch b/patches/server/0656-Add-missing-Validate-calls-to-CraftServer-getSpawnLi.patch new file mode 100644 index 000000000000..3f4362495aad --- /dev/null +++ b/patches/server/0656-Add-missing-Validate-calls-to-CraftServer-getSpawnLi.patch @@ -0,0 +1,20 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Jason Penilla <11360596+jpenilla@users.noreply.github.com> +Date: Sat, 12 Feb 2022 12:40:50 -0700 +Subject: [PATCH] Add missing Validate calls to CraftServer#getSpawnLimit + +Copies appropriate checks from CraftWorld#getSpawnLimit + +diff --git a/src/main/java/org/bukkit/craftbukkit/CraftServer.java b/src/main/java/org/bukkit/craftbukkit/CraftServer.java +index 9de7978c7383f8364feba82e9cd3efbfcce00e3c..c1bdefbad35fd259e3d90c6e330da14c9d072090 100644 +--- a/src/main/java/org/bukkit/craftbukkit/CraftServer.java ++++ b/src/main/java/org/bukkit/craftbukkit/CraftServer.java +@@ -2342,6 +2342,8 @@ public final class CraftServer implements Server { + @Override + public int getSpawnLimit(SpawnCategory spawnCategory) { + // Paper start - Add mobcaps commands ++ Preconditions.checkArgument(spawnCategory != null, "SpawnCategory cannot be null"); ++ Preconditions.checkArgument(CraftSpawnCategory.isValidForLimits(spawnCategory), "SpawnCategory." + spawnCategory + " does not have a spawn limit."); + return this.getSpawnLimitUnsafe(spawnCategory); + } + public int getSpawnLimitUnsafe(final SpawnCategory spawnCategory) { diff --git a/patches/server/0657-Add-GameEvent-tags.patch b/patches/server/0657-Add-GameEvent-tags.patch new file mode 100644 index 000000000000..0f1b9f52d218 --- /dev/null +++ b/patches/server/0657-Add-GameEvent-tags.patch @@ -0,0 +1,81 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Jake Potrebic +Date: Sun, 3 Jan 2021 20:03:35 -0800 +Subject: [PATCH] Add GameEvent tags + + +diff --git a/src/main/java/io/papermc/paper/CraftGameEventTag.java b/src/main/java/io/papermc/paper/CraftGameEventTag.java +new file mode 100644 +index 0000000000000000000000000000000000000000..874c420e60b6be09c806d64f40cf63663ffddc07 +--- /dev/null ++++ b/src/main/java/io/papermc/paper/CraftGameEventTag.java +@@ -0,0 +1,35 @@ ++package io.papermc.paper; ++ ++import net.minecraft.core.registries.BuiltInRegistries; ++import net.minecraft.core.registries.Registries; ++import net.minecraft.resources.ResourceKey; ++import net.minecraft.tags.TagKey; ++import org.bukkit.GameEvent; ++import org.bukkit.craftbukkit.tag.CraftTag; ++import org.bukkit.craftbukkit.util.CraftNamespacedKey; ++import org.jetbrains.annotations.NotNull; ++ ++import java.util.Collections; ++import java.util.IdentityHashMap; ++import java.util.Map; ++import java.util.Objects; ++import java.util.Set; ++import java.util.stream.Collectors; ++ ++public class CraftGameEventTag extends CraftTag { ++ ++ public CraftGameEventTag(net.minecraft.core.Registry registry, TagKey tag) { ++ super(registry, tag); ++ } ++ ++ private static final Map> KEY_CACHE = Collections.synchronizedMap(new IdentityHashMap<>()); ++ @Override ++ public boolean isTagged(@NotNull GameEvent gameEvent) { ++ return registry.getOrThrow(KEY_CACHE.computeIfAbsent(gameEvent, event -> ResourceKey.create(Registries.GAME_EVENT, CraftNamespacedKey.toMinecraft(event.getKey())))).is(tag); ++ } ++ ++ @Override ++ public @NotNull Set getValues() { ++ return getHandle().stream().map((nms) -> Objects.requireNonNull(GameEvent.getByKey(CraftNamespacedKey.fromMinecraft(BuiltInRegistries.GAME_EVENT.getKey(nms.value()))), nms + " is not a recognized game event")).collect(Collectors.toUnmodifiableSet()); ++ } ++} +diff --git a/src/main/java/org/bukkit/craftbukkit/CraftServer.java b/src/main/java/org/bukkit/craftbukkit/CraftServer.java +index c1bdefbad35fd259e3d90c6e330da14c9d072090..59690263f1cb27f2289b027ffd31c1e1ac4f2e69 100644 +--- a/src/main/java/org/bukkit/craftbukkit/CraftServer.java ++++ b/src/main/java/org/bukkit/craftbukkit/CraftServer.java +@@ -2699,6 +2699,15 @@ public final class CraftServer implements Server { + return (org.bukkit.Tag) new CraftDamageTag(damageRegistry, damageTagKey); + } + } ++ // Paper start ++ case org.bukkit.Tag.REGISTRY_GAME_EVENTS -> { ++ Preconditions.checkArgument(clazz == org.bukkit.GameEvent.class, "Game Event namespace must have GameEvent type"); ++ TagKey gameEventTagKey = TagKey.create(net.minecraft.core.registries.Registries.GAME_EVENT, key); ++ if (net.minecraft.core.registries.BuiltInRegistries.GAME_EVENT.get(gameEventTagKey).isPresent()) { ++ return (org.bukkit.Tag) new io.papermc.paper.CraftGameEventTag(net.minecraft.core.registries.BuiltInRegistries.GAME_EVENT, gameEventTagKey); ++ } ++ } ++ // Paper end + default -> throw new IllegalArgumentException(); + } + +@@ -2736,6 +2745,13 @@ public final class CraftServer implements Server { + net.minecraft.core.Registry damageTags = CraftRegistry.getMinecraftRegistry(Registries.DAMAGE_TYPE); + return damageTags.getTags().map(pair -> (org.bukkit.Tag) new CraftDamageTag(damageTags, pair.key())).collect(ImmutableList.toImmutableList()); + } ++ // Paper start ++ case org.bukkit.Tag.REGISTRY_GAME_EVENTS -> { ++ Preconditions.checkArgument(clazz == org.bukkit.GameEvent.class); ++ net.minecraft.core.Registry gameEvents = net.minecraft.core.registries.BuiltInRegistries.GAME_EVENT; ++ return gameEvents.getTags().map(pair -> (org.bukkit.Tag) new io.papermc.paper.CraftGameEventTag(gameEvents, pair.key())).collect(ImmutableList.toImmutableList()); ++ } ++ // Paper end + default -> throw new IllegalArgumentException(); + } + } diff --git a/patches/server/0657-Use-a-CHM-for-StructureTemplate.Pallete-cache.patch b/patches/server/0657-Use-a-CHM-for-StructureTemplate.Pallete-cache.patch deleted file mode 100644 index 82d25835a60d..000000000000 --- a/patches/server/0657-Use-a-CHM-for-StructureTemplate.Pallete-cache.patch +++ /dev/null @@ -1,20 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Shane Freeder -Date: Mon, 12 Jul 2021 12:28:29 +0100 -Subject: [PATCH] Use a CHM for StructureTemplate.Pallete cache - -fixes a CME due to this collection being shared across threads - -diff --git a/src/main/java/net/minecraft/world/level/levelgen/structure/templatesystem/StructureTemplate.java b/src/main/java/net/minecraft/world/level/levelgen/structure/templatesystem/StructureTemplate.java -index 02b493435509f7a84a2c26050ca0c732ecf00ca0..33564e62e3181d28b18a957e28b8ec5152d8339f 100644 ---- a/src/main/java/net/minecraft/world/level/levelgen/structure/templatesystem/StructureTemplate.java -+++ b/src/main/java/net/minecraft/world/level/levelgen/structure/templatesystem/StructureTemplate.java -@@ -859,7 +859,7 @@ public class StructureTemplate { - public static final class Palette { - - private final List blocks; -- private final Map> cache = Maps.newHashMap(); -+ private final Map> cache = Maps.newConcurrentMap(); // Paper - Fix CME due to this collection being shared across threads - - Palette(List infos) { - this.blocks = infos; diff --git a/patches/server/0658-API-for-creating-command-sender-which-forwards-feedb.patch b/patches/server/0658-API-for-creating-command-sender-which-forwards-feedb.patch deleted file mode 100644 index 477c1e1ddbee..000000000000 --- a/patches/server/0658-API-for-creating-command-sender-which-forwards-feedb.patch +++ /dev/null @@ -1,170 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Jason Penilla <11360596+jpenilla@users.noreply.github.com> -Date: Tue, 1 Feb 2022 15:51:55 -0700 -Subject: [PATCH] API for creating command sender which forwards feedback - - -diff --git a/src/main/java/io/papermc/paper/commands/FeedbackForwardingSender.java b/src/main/java/io/papermc/paper/commands/FeedbackForwardingSender.java -new file mode 100644 -index 0000000000000000000000000000000000000000..e3a5f1ec376319bdfda87fa27ae217bff3914292 ---- /dev/null -+++ b/src/main/java/io/papermc/paper/commands/FeedbackForwardingSender.java -@@ -0,0 +1,111 @@ -+package io.papermc.paper.commands; -+ -+import io.papermc.paper.adventure.PaperAdventure; -+import java.util.UUID; -+import java.util.function.Consumer; -+import net.kyori.adventure.audience.MessageType; -+import net.kyori.adventure.identity.Identity; -+import net.kyori.adventure.text.Component; -+import net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer; -+import net.minecraft.commands.CommandSource; -+import net.minecraft.commands.CommandSourceStack; -+import net.minecraft.server.level.ServerLevel; -+import net.minecraft.world.phys.Vec2; -+import net.minecraft.world.phys.Vec3; -+import org.bukkit.command.CommandSender; -+import org.bukkit.craftbukkit.CraftServer; -+import org.bukkit.craftbukkit.command.ServerCommandSender; -+import org.checkerframework.checker.nullness.qual.NonNull; -+import org.checkerframework.checker.nullness.qual.Nullable; -+import org.checkerframework.framework.qual.DefaultQualifier; -+ -+@DefaultQualifier(NonNull.class) -+public final class FeedbackForwardingSender extends ServerCommandSender { -+ private final Consumer feedback; -+ private final CraftServer server; -+ -+ public FeedbackForwardingSender(final Consumer feedback, final CraftServer server) { -+ super(((ServerCommandSender) server.getConsoleSender()).perm); -+ this.server = server; -+ this.feedback = feedback; -+ } -+ -+ @Override -+ public void sendMessage(final String message) { -+ this.sendMessage(LegacyComponentSerializer.legacySection().deserialize(message)); -+ } -+ -+ @Override -+ public void sendMessage(final String... messages) { -+ for (final String message : messages) { -+ this.sendMessage(message); -+ } -+ } -+ -+ @Override -+ public void sendMessage(final Identity identity, final Component message, final MessageType type) { -+ this.feedback.accept(message); -+ } -+ -+ @Override -+ public String getName() { -+ return "FeedbackForwardingSender"; -+ } -+ -+ @Override -+ public Component name() { -+ return Component.text(this.getName()); -+ } -+ -+ @Override -+ public boolean isOp() { -+ return true; -+ } -+ -+ @Override -+ public void setOp(final boolean value) { -+ throw new UnsupportedOperationException("Cannot change operator status of " + this.getClass().getName()); -+ } -+ -+ public CommandSourceStack asVanilla() { -+ final @Nullable ServerLevel overworld = this.server.getServer().overworld(); -+ return new CommandSourceStack( -+ new Source(this), -+ overworld == null ? Vec3.ZERO : Vec3.atLowerCornerOf(overworld.getSharedSpawnPos()), -+ Vec2.ZERO, -+ overworld, -+ 4, -+ this.getName(), -+ net.minecraft.network.chat.Component.literal(this.getName()), -+ this.server.getServer(), -+ null -+ ); -+ } -+ -+ private record Source(FeedbackForwardingSender sender) implements CommandSource { -+ @Override -+ public void sendSystemMessage(final net.minecraft.network.chat.Component message) { -+ this.sender.sendMessage(Identity.nil(), PaperAdventure.asAdventure(message)); -+ } -+ -+ @Override -+ public boolean acceptsSuccess() { -+ return true; -+ } -+ -+ @Override -+ public boolean acceptsFailure() { -+ return true; -+ } -+ -+ @Override -+ public boolean shouldInformAdmins() { -+ return false; -+ } -+ -+ @Override -+ public CommandSender getBukkitSender(final CommandSourceStack stack) { -+ return this.sender; -+ } -+ } -+} -diff --git a/src/main/java/org/bukkit/craftbukkit/CraftServer.java b/src/main/java/org/bukkit/craftbukkit/CraftServer.java -index bb232cd338f94b03f2add34766927ecae019e388..c0eca359919c55ba7b33520277c124eb54d935d7 100644 ---- a/src/main/java/org/bukkit/craftbukkit/CraftServer.java -+++ b/src/main/java/org/bukkit/craftbukkit/CraftServer.java -@@ -2162,6 +2162,13 @@ public final class CraftServer implements Server { - return this.console.console; - } - -+ // Paper start -+ @Override -+ public CommandSender createCommandSender(final java.util.function.Consumer feedback) { -+ return new io.papermc.paper.commands.FeedbackForwardingSender(feedback, this); -+ } -+ // Paper end -+ - public EntityMetadataStore getEntityMetadata() { - return this.entityMetadata; - } -diff --git a/src/main/java/org/bukkit/craftbukkit/command/ServerCommandSender.java b/src/main/java/org/bukkit/craftbukkit/command/ServerCommandSender.java -index 7f22950ae61436e91a59cd29a345809c42bbe739..1e3091687735b461d3b6a313ab8761127981d3e8 100644 ---- a/src/main/java/org/bukkit/craftbukkit/command/ServerCommandSender.java -+++ b/src/main/java/org/bukkit/craftbukkit/command/ServerCommandSender.java -@@ -12,7 +12,7 @@ import org.bukkit.permissions.PermissionAttachmentInfo; - import org.bukkit.plugin.Plugin; - - public abstract class ServerCommandSender implements CommandSender { -- private final PermissibleBase perm; -+ public final PermissibleBase perm; // Paper - private net.kyori.adventure.pointer.Pointers adventure$pointers; // Paper - implement pointers - - protected ServerCommandSender() { -diff --git a/src/main/java/org/bukkit/craftbukkit/command/VanillaCommandWrapper.java b/src/main/java/org/bukkit/craftbukkit/command/VanillaCommandWrapper.java -index 98d314cd293d462ef109e952f3239e08e14dda59..2ee33c55890fa659f6d251e486264c85d9e89802 100644 ---- a/src/main/java/org/bukkit/craftbukkit/command/VanillaCommandWrapper.java -+++ b/src/main/java/org/bukkit/craftbukkit/command/VanillaCommandWrapper.java -@@ -81,6 +81,11 @@ public final class VanillaCommandWrapper extends BukkitCommand { - if (sender instanceof ProxiedCommandSender) { - return ((ProxiedNativeCommandSender) sender).getHandle(); - } -+ // Paper start -+ if (sender instanceof io.papermc.paper.commands.FeedbackForwardingSender feedback) { -+ return feedback.asVanilla(); -+ } -+ // Paper end - - throw new IllegalArgumentException("Cannot make " + sender + " a vanilla command listener"); - } diff --git a/patches/server/0658-Execute-chunk-tasks-fairly-for-worlds-while-waiting-.patch b/patches/server/0658-Execute-chunk-tasks-fairly-for-worlds-while-waiting-.patch new file mode 100644 index 000000000000..7d399f3858f1 --- /dev/null +++ b/patches/server/0658-Execute-chunk-tasks-fairly-for-worlds-while-waiting-.patch @@ -0,0 +1,37 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Spottedleaf +Date: Tue, 28 Dec 2021 07:19:01 -0800 +Subject: [PATCH] Execute chunk tasks fairly for worlds while waiting for next + tick + +Currently, only the first world would have had tasks executed. +This might result in chunks loading far slower in the nether, +for example. + +diff --git a/src/main/java/net/minecraft/server/MinecraftServer.java b/src/main/java/net/minecraft/server/MinecraftServer.java +index 8cbc8eeaa8719f8bb136543e80ec85248c90e154..3cd23d329fae0a6a4eff36510edc5d9bd27c804e 100644 +--- a/src/main/java/net/minecraft/server/MinecraftServer.java ++++ b/src/main/java/net/minecraft/server/MinecraftServer.java +@@ -1405,6 +1405,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop -Date: Thu, 13 Jan 2022 23:05:53 -0800 -Subject: [PATCH] Add missing structure set seed configs - -The 4 missing structure set seed configs are strongholds, mineshafts, -buried treasure, and ancient cities. - -Strongholds use a ring placement scheme which isn't random so they -utilize the world seed by default, this adds a config to override it -for just generating the ring positions. - -Mineshafts and Buried Treasure structure sets are special cases -where the "salt" that can be defined for them via datapacks has 0 -effect because the difference between the spacing and separation is 1 -which is used as the upper bound in the random with salt. So the random -always returns the same int (0) so the salt has no effect. This adds -seeds/salts to the frequency reducer which has a similar effect. - -Co-authored-by: William Blake Galbreath - -diff --git a/src/main/java/net/minecraft/world/level/chunk/ChunkGenerator.java b/src/main/java/net/minecraft/world/level/chunk/ChunkGenerator.java -index c5dd3aac54aa5936da4bd9f54f0e76ecf8141d27..102de569415ef011dacdca9a6ea8134d0ef62454 100644 ---- a/src/main/java/net/minecraft/world/level/chunk/ChunkGenerator.java -+++ b/src/main/java/net/minecraft/world/level/chunk/ChunkGenerator.java -@@ -573,7 +573,7 @@ public abstract class ChunkGenerator { - } - } - -- if (structureplacement.isStructureChunk(placementCalculator, chunkcoordintpair.x, chunkcoordintpair.z)) { -+ if (structureplacement.isStructureChunk(placementCalculator, chunkcoordintpair.x, chunkcoordintpair.z, structureplacement instanceof net.minecraft.world.level.chunk.ChunkGeneratorStructureState.KeyedRandomSpreadStructurePlacement keyed ? keyed.key : null)) { // Paper - Add missing structure set seed configs - if (list.size() == 1) { - this.tryGenerateStructure((StructureSet.StructureSelectionEntry) list.get(0), structureAccessor, registryManager, randomstate, structureTemplateManager, placementCalculator.getLevelSeed(), chunk, chunkcoordintpair, sectionposition); - } else { -diff --git a/src/main/java/net/minecraft/world/level/chunk/ChunkGeneratorStructureState.java b/src/main/java/net/minecraft/world/level/chunk/ChunkGeneratorStructureState.java -index e6c59f986ae89022bd76463209dfa550a3d4fb59..a6b6e5ea191c0e2cd7a2e4f01b89d8af40a83c1b 100644 ---- a/src/main/java/net/minecraft/world/level/chunk/ChunkGeneratorStructureState.java -+++ b/src/main/java/net/minecraft/world/level/chunk/ChunkGeneratorStructureState.java -@@ -50,13 +50,14 @@ public class ChunkGeneratorStructureState { - private final Map>> ringPositions = new Object2ObjectArrayMap(); - private boolean hasGeneratedPositions; - private final List> possibleStructureSets; -+ public final SpigotWorldConfig conf; // Paper - Add missing structure set seed configs - - public static ChunkGeneratorStructureState createForFlat(RandomState randomstate, long i, BiomeSource worldchunkmanager, Stream> stream, SpigotWorldConfig conf) { // Spigot - List> list = stream.filter((holder) -> { - return ChunkGeneratorStructureState.hasBiomesForStructureSet((StructureSet) holder.value(), worldchunkmanager); - }).toList(); - -- return new ChunkGeneratorStructureState(randomstate, worldchunkmanager, i, 0L, ChunkGeneratorStructureState.injectSpigot(list, conf)); // Spigot -+ return new ChunkGeneratorStructureState(randomstate, worldchunkmanager, i, 0L, ChunkGeneratorStructureState.injectSpigot(list, conf), conf); // Spigot - } - - public static ChunkGeneratorStructureState createForNormal(RandomState randomstate, long i, BiomeSource worldchunkmanager, HolderLookup holderlookup, SpigotWorldConfig conf) { // Spigot -@@ -64,14 +65,24 @@ public class ChunkGeneratorStructureState { - return ChunkGeneratorStructureState.hasBiomesForStructureSet((StructureSet) holder_c.value(), worldchunkmanager); - }).collect(Collectors.toUnmodifiableList()); - -- return new ChunkGeneratorStructureState(randomstate, worldchunkmanager, i, i, ChunkGeneratorStructureState.injectSpigot(list, conf)); // Spigot -+ return new ChunkGeneratorStructureState(randomstate, worldchunkmanager, i, i, ChunkGeneratorStructureState.injectSpigot(list, conf), conf); // Spigot - } -+ // Paper start - Add missing structure set seed configs; horrible hack because spigot creates a ton of direct Holders which lose track of the identifying key -+ public static final class KeyedRandomSpreadStructurePlacement extends RandomSpreadStructurePlacement { -+ public final net.minecraft.resources.ResourceKey key; -+ public KeyedRandomSpreadStructurePlacement(net.minecraft.resources.ResourceKey key, net.minecraft.core.Vec3i locateOffset, FrequencyReductionMethod frequencyReductionMethod, float frequency, int salt, java.util.Optional exclusionZone, int spacing, int separation, net.minecraft.world.level.levelgen.structure.placement.RandomSpreadType spreadType) { -+ super(locateOffset, frequencyReductionMethod, frequency, salt, exclusionZone, spacing, separation, spreadType); -+ this.key = key; -+ } -+ } -+ // Paper end - Add missing structure set seed configs - - // Spigot start - private static List> injectSpigot(List> list, SpigotWorldConfig conf) { - return list.stream().map((holder) -> { - StructureSet structureset = holder.value(); -- if (structureset.placement() instanceof RandomSpreadStructurePlacement randomConfig) { -+ final Holder newHolder; // Paper - Add missing structure set seed configs -+ if (structureset.placement() instanceof RandomSpreadStructurePlacement randomConfig && holder.unwrapKey().orElseThrow().location().getNamespace().equals(net.minecraft.resources.ResourceLocation.DEFAULT_NAMESPACE)) { // Paper - Add missing structure set seed configs; check namespace cause datapacks could add structure sets with the same path - String name = holder.unwrapKey().orElseThrow().location().getPath(); - int seed = randomConfig.salt; - -@@ -118,11 +129,27 @@ public class ChunkGeneratorStructureState { - case "villages": - seed = conf.villageSeed; - break; -+ // Paper start - Add missing structure set seed configs -+ case "ancient_cities": -+ seed = conf.ancientCitySeed; -+ break; -+ case "trail_ruins": -+ seed = conf.trailRuinsSeed; -+ break; -+ case "trial_chambers": -+ seed = conf.trialChambersSeed; -+ break; -+ // Paper end - Add missing structure set seed configs - } - -- structureset = new StructureSet(structureset.structures(), new RandomSpreadStructurePlacement(randomConfig.locateOffset, randomConfig.frequencyReductionMethod, randomConfig.frequency, seed, randomConfig.exclusionZone, randomConfig.spacing(), randomConfig.separation(), randomConfig.spreadType())); -+ // Paper start - Add missing structure set seed configs -+ structureset = new StructureSet(structureset.structures(), new KeyedRandomSpreadStructurePlacement(holder.unwrapKey().orElseThrow(), randomConfig.locateOffset, randomConfig.frequencyReductionMethod, randomConfig.frequency, seed, randomConfig.exclusionZone, randomConfig.spacing(), randomConfig.separation(), randomConfig.spreadType())); -+ newHolder = Holder.direct(structureset); // I really wish we didn't have to do this here -+ } else { -+ newHolder = holder; - } -- return Holder.direct(structureset); -+ return newHolder; -+ // Paper end - Add missing structure set seed configs - }).collect(Collectors.toUnmodifiableList()); - } - // Spigot end -@@ -139,12 +166,13 @@ public class ChunkGeneratorStructureState { - return stream.anyMatch(set::contains); - } - -- private ChunkGeneratorStructureState(RandomState noiseConfig, BiomeSource biomeSource, long structureSeed, long concentricRingSeed, List> structureSets) { -+ private ChunkGeneratorStructureState(RandomState noiseConfig, BiomeSource biomeSource, long structureSeed, long concentricRingSeed, List> structureSets, SpigotWorldConfig conf) { // Paper - Add missing structure set seed configs - this.randomState = noiseConfig; - this.levelSeed = structureSeed; - this.biomeSource = biomeSource; - this.concentricRingsSeed = concentricRingSeed; - this.possibleStructureSets = structureSets; -+ this.conf = conf; // Paper - Add missing structure set seed configs - } - - public List> possibleStructureSets() { -@@ -198,7 +226,13 @@ public class ChunkGeneratorStructureState { - HolderSet holderset = placement.preferredBiomes(); - RandomSource randomsource = RandomSource.create(); - -+ // Paper start - Add missing structure set seed configs -+ if (this.conf.strongholdSeed != null && structureSetEntry.is(net.minecraft.world.level.levelgen.structure.BuiltinStructureSets.STRONGHOLDS)) { -+ randomsource.setSeed(this.conf.strongholdSeed); -+ } else { -+ // Paper end - Add missing structure set seed configs - randomsource.setSeed(this.concentricRingsSeed); -+ } // Paper - Add missing structure set seed configs - double d0 = randomsource.nextDouble() * Math.PI * 2.0D; - int l = 0; - int i1 = 0; -@@ -275,7 +309,7 @@ public class ChunkGeneratorStructureState { - - for (int l = centerChunkX - chunkCount; l <= centerChunkX + chunkCount; ++l) { - for (int i1 = centerChunkZ - chunkCount; i1 <= centerChunkZ + chunkCount; ++i1) { -- if (structureplacement.isStructureChunk(this, l, i1)) { -+ if (structureplacement.isStructureChunk(this, l, i1, structureplacement instanceof KeyedRandomSpreadStructurePlacement keyed ? keyed.key : null)) { // Paper - Add missing structure set seed configs - return true; - } - } -diff --git a/src/main/java/net/minecraft/world/level/levelgen/structure/StructureCheck.java b/src/main/java/net/minecraft/world/level/levelgen/structure/StructureCheck.java -index aae73586265593ee7830fb8dd5c2e3d7560057f0..c6181e14d85d454506534f9bbe856156c0d4a062 100644 ---- a/src/main/java/net/minecraft/world/level/levelgen/structure/StructureCheck.java -+++ b/src/main/java/net/minecraft/world/level/levelgen/structure/StructureCheck.java -@@ -74,6 +74,20 @@ public class StructureCheck { - this.fixerUpper = dataFixer; - } - -+ // Paper start - add missing structure salt configs -+ @Nullable -+ private Integer getSaltOverride(Structure type) { -+ if (this.heightAccessor instanceof net.minecraft.server.level.ServerLevel serverLevel) { -+ if (type instanceof net.minecraft.world.level.levelgen.structure.structures.MineshaftStructure) { -+ return serverLevel.spigotConfig.mineshaftSeed; -+ } else if (type instanceof net.minecraft.world.level.levelgen.structure.structures.BuriedTreasureStructure) { -+ return serverLevel.spigotConfig.buriedTreasureSeed; -+ } -+ } -+ return null; -+ } -+ // Paper end - add missing structure seed configs -+ - public StructureCheckResult checkStart(ChunkPos pos, Structure type, StructurePlacement placement, boolean skipReferencedStructures) { - long l = pos.toLong(); - Object2IntMap object2IntMap = this.loadedChunks.get(l); -@@ -83,7 +97,7 @@ public class StructureCheck { - StructureCheckResult structureCheckResult = this.tryLoadFromStorage(pos, type, skipReferencedStructures, l); - if (structureCheckResult != null) { - return structureCheckResult; -- } else if (!placement.applyAdditionalChunkRestrictions(pos.x, pos.z, this.seed)) { -+ } else if (!placement.applyAdditionalChunkRestrictions(pos.x, pos.z, this.seed, this.getSaltOverride(type))) { // Paper - add missing structure seed configs - return StructureCheckResult.START_NOT_PRESENT; - } else { - boolean bl = this.featureChecks -diff --git a/src/main/java/net/minecraft/world/level/levelgen/structure/placement/StructurePlacement.java b/src/main/java/net/minecraft/world/level/levelgen/structure/placement/StructurePlacement.java -index a4c34e9415632354d33668a38d06453ada4d3c77..cbf13e4f2da6a27619e9bc9a7cd73bb6e69cad2a 100644 ---- a/src/main/java/net/minecraft/world/level/levelgen/structure/placement/StructurePlacement.java -+++ b/src/main/java/net/minecraft/world/level/levelgen/structure/placement/StructurePlacement.java -@@ -79,14 +79,30 @@ public abstract class StructurePlacement { - return this.exclusionZone; - } - -+ @Deprecated @io.papermc.paper.annotation.DoNotUse // Paper - Add missing structure set seed configs - public boolean isStructureChunk(ChunkGeneratorStructureState calculator, int chunkX, int chunkZ) { -+ // Paper start - Add missing structure set seed configs -+ return this.isStructureChunk(calculator, chunkX, chunkZ, null); -+ } -+ public boolean isStructureChunk(ChunkGeneratorStructureState calculator, int chunkX, int chunkZ, @org.jetbrains.annotations.Nullable net.minecraft.resources.ResourceKey structureSetKey) { -+ Integer saltOverride = null; -+ if (structureSetKey != null) { -+ if (structureSetKey == net.minecraft.world.level.levelgen.structure.BuiltinStructureSets.MINESHAFTS) { -+ saltOverride = calculator.conf.mineshaftSeed; -+ } else if (structureSetKey == net.minecraft.world.level.levelgen.structure.BuiltinStructureSets.BURIED_TREASURES) { -+ saltOverride = calculator.conf.buriedTreasureSeed; -+ } -+ } -+ // Paper end - Add missing structure set seed configs - return this.isPlacementChunk(calculator, chunkX, chunkZ) -- && this.applyAdditionalChunkRestrictions(chunkX, chunkZ, calculator.getLevelSeed()) -+ && this.applyAdditionalChunkRestrictions(chunkX, chunkZ, calculator.getLevelSeed(), saltOverride) // Paper - Add missing structure set seed configs - && this.applyInteractionsWithOtherStructures(calculator, chunkX, chunkZ); - } - -- public boolean applyAdditionalChunkRestrictions(int chunkX, int chunkZ, long seed) { -- return !(this.frequency < 1.0F) || this.frequencyReductionMethod.shouldGenerate(seed, this.salt, chunkX, chunkZ, this.frequency); -+ // Paper start - Add missing structure set seed configs -+ public boolean applyAdditionalChunkRestrictions(int chunkX, int chunkZ, long seed, @org.jetbrains.annotations.Nullable Integer saltOverride) { -+ return !(this.frequency < 1.0F) || this.frequencyReductionMethod.shouldGenerate(seed, this.salt, chunkX, chunkZ, this.frequency, saltOverride); -+ // Paper end - Add missing structure set seed configs - } - - public boolean applyInteractionsWithOtherStructures(ChunkGeneratorStructureState calculator, int centerChunkX, int centerChunkZ) { -@@ -101,25 +117,31 @@ public abstract class StructurePlacement { - - public abstract StructurePlacementType type(); - -- private static boolean probabilityReducer(long seed, int salt, int chunkX, int chunkZ, float frequency) { -+ private static boolean probabilityReducer(long seed, int salt, int chunkX, int chunkZ, float frequency, @org.jetbrains.annotations.Nullable Integer saltOverride) { // Paper - Add missing structure set seed configs; ignore here - WorldgenRandom worldgenRandom = new WorldgenRandom(new LegacyRandomSource(0L)); - worldgenRandom.setLargeFeatureWithSalt(seed, salt, chunkX, chunkZ); - return worldgenRandom.nextFloat() < frequency; - } - -- private static boolean legacyProbabilityReducerWithDouble(long seed, int salt, int chunkX, int chunkZ, float frequency) { -+ private static boolean legacyProbabilityReducerWithDouble(long seed, int salt, int chunkX, int chunkZ, float frequency, @org.jetbrains.annotations.Nullable Integer saltOverride) { // Paper - Add missing structure set seed configs - WorldgenRandom worldgenRandom = new WorldgenRandom(new LegacyRandomSource(0L)); -+ if (saltOverride == null) { // Paper - Add missing structure set seed configs - worldgenRandom.setLargeFeatureSeed(seed, chunkX, chunkZ); -+ // Paper start - Add missing structure set seed configs -+ } else { -+ worldgenRandom.setLargeFeatureWithSalt(seed, chunkX, chunkZ, saltOverride); -+ } -+ // Paper end - Add missing structure set seed configs - return worldgenRandom.nextDouble() < (double)frequency; - } - -- private static boolean legacyArbitrarySaltProbabilityReducer(long seed, int salt, int chunkX, int chunkZ, float frequency) { -+ private static boolean legacyArbitrarySaltProbabilityReducer(long seed, int salt, int chunkX, int chunkZ, float frequency, @org.jetbrains.annotations.Nullable Integer saltOverride) { // Paper - Add missing structure set seed configs - WorldgenRandom worldgenRandom = new WorldgenRandom(new LegacyRandomSource(0L)); -- worldgenRandom.setLargeFeatureWithSalt(seed, chunkX, chunkZ, 10387320); -+ worldgenRandom.setLargeFeatureWithSalt(seed, chunkX, chunkZ, saltOverride != null ? saltOverride : HIGHLY_ARBITRARY_RANDOM_SALT); // Paper - Add missing structure set seed configs - return worldgenRandom.nextFloat() < frequency; - } - -- private static boolean legacyPillagerOutpostReducer(long seed, int salt, int chunkX, int chunkZ, float frequency) { -+ private static boolean legacyPillagerOutpostReducer(long seed, int salt, int chunkX, int chunkZ, float frequency, @org.jetbrains.annotations.Nullable Integer saltOverride) { // Paper - Add missing structure set seed configs; ignore here - int i = chunkX >> 4; - int j = chunkZ >> 4; - WorldgenRandom worldgenRandom = new WorldgenRandom(new LegacyRandomSource(0L)); -@@ -147,7 +169,7 @@ public abstract class StructurePlacement { - - @FunctionalInterface - public interface FrequencyReducer { -- boolean shouldGenerate(long seed, int salt, int chunkX, int chunkZ, float chance); -+ boolean shouldGenerate(long seed, int salt, int chunkX, int chunkZ, float chance, @org.jetbrains.annotations.Nullable Integer saltOverride); // Paper - Add missing structure set seed configs - } - - public static enum FrequencyReductionMethod implements StringRepresentable { -@@ -167,8 +189,8 @@ public abstract class StructurePlacement { - this.reducer = generationPredicate; - } - -- public boolean shouldGenerate(long seed, int salt, int chunkX, int chunkZ, float chance) { -- return this.reducer.shouldGenerate(seed, salt, chunkX, chunkZ, chance); -+ public boolean shouldGenerate(long seed, int salt, int chunkX, int chunkZ, float chance, @org.jetbrains.annotations.Nullable Integer saltOverride) { // Paper - Add missing structure set seed configs -+ return this.reducer.shouldGenerate(seed, salt, chunkX, chunkZ, chance, saltOverride); // Paper - Add missing structure set seed configs - } - - @Override -diff --git a/src/main/java/org/spigotmc/SpigotWorldConfig.java b/src/main/java/org/spigotmc/SpigotWorldConfig.java -index e76f96a5c48d1eda2f9bbb3e11dd79f23f9ab75c..2b263246135c85aa225120519e9702a628773935 100644 ---- a/src/main/java/org/spigotmc/SpigotWorldConfig.java -+++ b/src/main/java/org/spigotmc/SpigotWorldConfig.java -@@ -322,6 +322,18 @@ public class SpigotWorldConfig - public int mansionSeed; - public int fossilSeed; - public int portalSeed; -+ // Paper start - add missing structure set configs -+ public int ancientCitySeed; -+ public int trailRuinsSeed; -+ public int trialChambersSeed; -+ public int buriedTreasureSeed; -+ public Integer mineshaftSeed; -+ public Long strongholdSeed; -+ private N getSeed(String path, java.util.function.Function toNumberFunc) { -+ final String value = this.getString(path, "default"); -+ return org.apache.commons.lang3.math.NumberUtils.isParsable(value) ? toNumberFunc.apply(value) : null; -+ } -+ // Paper end - private void initWorldGenSeeds() - { - this.villageSeed = this.getInt( "seed-village", 10387312 ); -@@ -339,6 +351,14 @@ public class SpigotWorldConfig - this.mansionSeed = this.getInt( "seed-mansion", 10387319 ); - this.fossilSeed = this.getInt( "seed-fossil", 14357921 ); - this.portalSeed = this.getInt( "seed-portal", 34222645 ); -+ // Paper start - add missing structure set configs -+ this.ancientCitySeed = this.getInt("seed-ancientcity", 20083232); -+ this.trailRuinsSeed = this.getInt("seed-trailruins", 83469867); -+ this.trialChambersSeed = this.getInt("seed-trialchambers", 94251327); -+ this.buriedTreasureSeed = this.getInt("seed-buriedtreasure", 10387320); // StructurePlacement#HIGHLY_ARBITRARY_RANDOM_SALT -+ this.mineshaftSeed = this.getSeed("seed-mineshaft", Integer::parseInt); -+ this.strongholdSeed = this.getSeed("seed-stronghold", Long::parseLong); -+ // Paper end - this.log( "Custom Map Seeds: Village: " + this.villageSeed + " Desert: " + this.desertSeed + " Igloo: " + this.iglooSeed + " Jungle: " + this.jungleSeed + " Swamp: " + this.swampSeed + " Monument: " + this.monumentSeed - + " Ocean: " + this.oceanSeed + " Shipwreck: " + this.shipwreckSeed + " End City: " + this.endCitySeed + " Slime: " + this.slimeSeed + " Nether: " + this.netherSeed + " Mansion: " + this.mansionSeed + " Fossil: " + this.fossilSeed + " Portal: " + this.portalSeed ); - } -diff --git a/src/test/java/io/papermc/paper/world/structure/StructureSeedConfigTest.java b/src/test/java/io/papermc/paper/world/structure/StructureSeedConfigTest.java -new file mode 100644 -index 0000000000000000000000000000000000000000..c77345f7e0c9bf179b8b35a8b300085f31fd45af ---- /dev/null -+++ b/src/test/java/io/papermc/paper/world/structure/StructureSeedConfigTest.java -@@ -0,0 +1,77 @@ -+package io.papermc.paper.world.structure; -+ -+import io.papermc.paper.configuration.PaperConfigurations; -+import java.io.File; -+import java.lang.reflect.Field; -+import net.minecraft.core.Registry; -+import net.minecraft.core.registries.Registries; -+import net.minecraft.resources.ResourceKey; -+import net.minecraft.resources.ResourceLocation; -+import net.minecraft.world.level.levelgen.structure.BuiltinStructureSets; -+import net.minecraft.world.level.levelgen.structure.StructureSet; -+import net.minecraft.world.level.levelgen.structure.placement.StructurePlacement; -+import org.bukkit.configuration.file.YamlConfiguration; -+import org.bukkit.support.RegistryHelper; -+import org.bukkit.support.environment.AllFeatures; -+import org.jetbrains.annotations.NotNull; -+import org.junit.jupiter.api.Test; -+import org.spigotmc.SpigotConfig; -+import org.spigotmc.SpigotWorldConfig; -+ -+import static org.junit.jupiter.api.Assertions.assertEquals; -+ -+@AllFeatures -+public class StructureSeedConfigTest { -+ -+ @Test -+ public void checkStructureSeedDefaults() throws ReflectiveOperationException { -+ SpigotConfig.config = new YamlConfiguration() { -+ @Override -+ public void save(final @NotNull File file) { -+ // no-op -+ } -+ }; -+ final SpigotWorldConfig config = PaperConfigurations.SPIGOT_WORLD_DEFAULTS.get(); -+ -+ -+ final Registry structureSets = RegistryHelper.getRegistry().registryOrThrow(Registries.STRUCTURE_SET); -+ for (final ResourceKey setKey : structureSets.registryKeySet()) { -+ assertEquals(ResourceLocation.DEFAULT_NAMESPACE, setKey.location().getNamespace()); -+ final StructureSet set = structureSets.getOrThrow(setKey); -+ if (setKey == BuiltinStructureSets.STRONGHOLDS) { // special case due to seed matching world seed -+ assertEquals(0, set.placement().salt); -+ continue; -+ } -+ int salt = switch (setKey.location().getPath()) { -+ case "villages" -> config.villageSeed; -+ case "desert_pyramids" -> config.desertSeed; -+ case "igloos" -> config.iglooSeed; -+ case "jungle_temples" -> config.jungleSeed; -+ case "swamp_huts" -> config.swampSeed; -+ case "pillager_outposts" -> config.outpostSeed; -+ case "ocean_monuments" -> config.monumentSeed; -+ case "woodland_mansions" -> config.mansionSeed; -+ case "buried_treasures" -> config.buriedTreasureSeed; -+ case "mineshafts" -> config.mineshaftSeed == null ? 0 : config.mineshaftSeed; // mineshaft seed is set differently -+ case "ruined_portals" -> config.portalSeed; -+ case "shipwrecks" -> config.shipwreckSeed; -+ case "ocean_ruins" -> config.oceanSeed; -+ case "nether_complexes" -> config.netherSeed; -+ case "nether_fossils" -> config.fossilSeed; -+ case "end_cities" -> config.endCitySeed; -+ case "ancient_cities" -> config.ancientCitySeed; -+ case "trail_ruins" -> config.trailRuinsSeed; -+ case "trial_chambers" -> config.trialChambersSeed; -+ default -> throw new AssertionError("Missing structure set seed in SpigotWorldConfig for " + setKey); -+ }; -+ if (setKey == BuiltinStructureSets.BURIED_TREASURES) { -+ final Field field = StructurePlacement.class.getDeclaredField("HIGHLY_ARBITRARY_RANDOM_SALT"); -+ field.trySetAccessible(); -+ assertEquals(0, set.placement().salt); -+ assertEquals(field.get(null), salt, "Mismatched default seed for " + setKey + ". Should be " + field.get(null)); -+ continue; -+ } -+ assertEquals(set.placement().salt, salt, "Mismatched default seed for " + setKey + ". Should be " + set.placement().salt); -+ } -+ } -+} diff --git a/patches/server/0659-Furnace-RecipesUsed-API.patch b/patches/server/0659-Furnace-RecipesUsed-API.patch new file mode 100644 index 000000000000..5b19cd68c049 --- /dev/null +++ b/patches/server/0659-Furnace-RecipesUsed-API.patch @@ -0,0 +1,48 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Jake Potrebic +Date: Thu, 13 Jan 2022 15:20:47 -0800 +Subject: [PATCH] Furnace RecipesUsed API + + +diff --git a/src/main/java/org/bukkit/craftbukkit/block/CraftFurnace.java b/src/main/java/org/bukkit/craftbukkit/block/CraftFurnace.java +index 7b5f35779ac63b5f9b3a88cc4dcde38147fea2b7..e8d57a9497d545a84955eb3d0240844ae8276c08 100644 +--- a/src/main/java/org/bukkit/craftbukkit/block/CraftFurnace.java ++++ b/src/main/java/org/bukkit/craftbukkit/block/CraftFurnace.java +@@ -103,5 +103,37 @@ public abstract class CraftFurnace extends + snapshot.cookSpeedMultiplier = multiplier; + snapshot.cookingTotalTime = AbstractFurnaceBlockEntity.getTotalCookTime(this.isPlaced() ? this.world.getHandle() : null, snapshot, snapshot.recipeType, snapshot.cookSpeedMultiplier); // Update the snapshot's current total cook time to scale with the newly set multiplier + } ++ ++ @Override ++ public int getRecipeUsedCount(org.bukkit.NamespacedKey furnaceRecipe) { ++ return this.getSnapshot().recipesUsed.getInt(io.papermc.paper.util.MCUtil.toResourceKey(net.minecraft.core.registries.Registries.RECIPE, furnaceRecipe)); ++ } ++ ++ @Override ++ public boolean hasRecipeUsedCount(org.bukkit.NamespacedKey furnaceRecipe) { ++ return this.getSnapshot().recipesUsed.containsKey(io.papermc.paper.util.MCUtil.toResourceKey(net.minecraft.core.registries.Registries.RECIPE, furnaceRecipe)); ++ } ++ ++ @Override ++ public void setRecipeUsedCount(org.bukkit.inventory.CookingRecipe furnaceRecipe, int count) { ++ final var location = io.papermc.paper.util.MCUtil.toResourceKey(net.minecraft.core.registries.Registries.RECIPE, furnaceRecipe.getKey()); ++ java.util.Optional> nmsRecipe = (this.isPlaced() ? this.world.getHandle().recipeAccess() : net.minecraft.server.MinecraftServer.getServer().getRecipeManager()).byKey(location); ++ com.google.common.base.Preconditions.checkArgument(nmsRecipe.isPresent() && nmsRecipe.get().value() instanceof net.minecraft.world.item.crafting.AbstractCookingRecipe, furnaceRecipe.getKey() + " is not recognized as a valid and registered furnace recipe"); ++ if (count > 0) { ++ this.getSnapshot().recipesUsed.put(location, count); ++ } else { ++ this.getSnapshot().recipesUsed.removeInt(location); ++ } ++ } ++ ++ @Override ++ public void setRecipesUsed(java.util.Map, Integer> recipesUsed) { ++ this.getSnapshot().recipesUsed.clear(); ++ recipesUsed.forEach((recipe, integer) -> { ++ if (integer != null) { ++ this.setRecipeUsedCount(recipe, integer); ++ } ++ }); ++ } + // Paper end + } diff --git a/patches/server/0660-Configurable-sculk-sensor-listener-range.patch b/patches/server/0660-Configurable-sculk-sensor-listener-range.patch new file mode 100644 index 000000000000..3dd132435490 --- /dev/null +++ b/patches/server/0660-Configurable-sculk-sensor-listener-range.patch @@ -0,0 +1,106 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Jake Potrebic +Date: Thu, 19 Aug 2021 18:45:42 -0700 +Subject: [PATCH] Configurable sculk sensor listener range + +== AT == +public-f net.minecraft.world.level.gameevent.vibrations.VibrationListener listenerRange + +diff --git a/src/main/java/net/minecraft/world/level/block/entity/CalibratedSculkSensorBlockEntity.java b/src/main/java/net/minecraft/world/level/block/entity/CalibratedSculkSensorBlockEntity.java +index 41ccbee5fc7767a7d5e1cdca0ec7d9a17ee80a90..1d28f117965da22694b12018923a5f1347905085 100644 +--- a/src/main/java/net/minecraft/world/level/block/entity/CalibratedSculkSensorBlockEntity.java ++++ b/src/main/java/net/minecraft/world/level/block/entity/CalibratedSculkSensorBlockEntity.java +@@ -20,6 +20,12 @@ public class CalibratedSculkSensorBlockEntity extends SculkSensorBlockEntity { + public VibrationSystem.User createVibrationUser() { + return new CalibratedSculkSensorBlockEntity.VibrationUser(this.getBlockPos()); + } ++ // Paper start - Configurable sculk sensor listener range ++ @Override ++ protected void saveRangeOverride(final net.minecraft.nbt.CompoundTag nbt) { ++ if (this.rangeOverride != null && this.rangeOverride != 16) nbt.putInt(PAPER_LISTENER_RANGE_NBT_KEY, this.rangeOverride); // only save if it's different from the default ++ } ++ // Paper end - Configurable sculk sensor listener range + + protected class VibrationUser extends SculkSensorBlockEntity.VibrationUser { + public VibrationUser(final BlockPos pos) { +@@ -28,6 +34,7 @@ public class CalibratedSculkSensorBlockEntity extends SculkSensorBlockEntity { + + @Override + public int getListenerRadius() { ++ if (CalibratedSculkSensorBlockEntity.this.rangeOverride != null) return CalibratedSculkSensorBlockEntity.this.rangeOverride; // Paper - Configurable sculk sensor listener range + return 16; + } + +diff --git a/src/main/java/net/minecraft/world/level/block/entity/SculkSensorBlockEntity.java b/src/main/java/net/minecraft/world/level/block/entity/SculkSensorBlockEntity.java +index 070814eb011ab8e02218c91e1cf75be5501c1a0a..28849cf84afcdc0d9fc245fac1a8d769a2db3b68 100644 +--- a/src/main/java/net/minecraft/world/level/block/entity/SculkSensorBlockEntity.java ++++ b/src/main/java/net/minecraft/world/level/block/entity/SculkSensorBlockEntity.java +@@ -26,6 +26,7 @@ public class SculkSensorBlockEntity extends BlockEntity implements GameEventList + private final VibrationSystem.Listener vibrationListener; + private final VibrationSystem.User vibrationUser = this.createVibrationUser(); + public int lastVibrationFrequency; ++ @Nullable public Integer rangeOverride = null; // Paper - Configurable sculk sensor listener range + + protected SculkSensorBlockEntity(BlockEntityType type, BlockPos pos, BlockState state) { + super(type, pos, state); +@@ -52,8 +53,16 @@ public class SculkSensorBlockEntity extends BlockEntity implements GameEventList + .resultOrPartial(string -> LOGGER.error("Failed to parse vibration listener for Sculk Sensor: '{}'", string)) + .ifPresent(listener -> this.vibrationData = listener); + } ++ // Paper start - Configurable sculk sensor listener range ++ if (nbt.contains(PAPER_LISTENER_RANGE_NBT_KEY)) { ++ this.rangeOverride = nbt.getInt(PAPER_LISTENER_RANGE_NBT_KEY); ++ } else { ++ this.rangeOverride = null; ++ } ++ // Paper end - Configurable sculk sensor listener range + } + ++ protected static final String PAPER_LISTENER_RANGE_NBT_KEY = "Paper.ListenerRange"; // Paper - Configurable sculk sensor listener range + @Override + protected void saveAdditional(CompoundTag nbt, HolderLookup.Provider registries) { + super.saveAdditional(nbt, registries); +@@ -63,7 +72,13 @@ public class SculkSensorBlockEntity extends BlockEntity implements GameEventList + .encodeStart(registryOps, this.vibrationData) + .resultOrPartial(string -> LOGGER.error("Failed to encode vibration listener for Sculk Sensor: '{}'", string)) + .ifPresent(listenerNbt -> nbt.put("listener", listenerNbt)); ++ this.saveRangeOverride(nbt); // Paper - Configurable sculk sensor listener range ++ } ++ // Paper start - Configurable sculk sensor listener range ++ protected void saveRangeOverride(CompoundTag nbt) { ++ if (this.rangeOverride != null && this.rangeOverride != VibrationUser.LISTENER_RANGE) nbt.putInt(PAPER_LISTENER_RANGE_NBT_KEY, this.rangeOverride); // only save if it's different from the default + } ++ // Paper end - Configurable sculk sensor listener range + + @Override + public VibrationSystem.Data getVibrationData() { +@@ -100,6 +115,7 @@ public class SculkSensorBlockEntity extends BlockEntity implements GameEventList + + @Override + public int getListenerRadius() { ++ if (SculkSensorBlockEntity.this.rangeOverride != null) return SculkSensorBlockEntity.this.rangeOverride; // Paper - Configurable sculk sensor listener range + return 8; + } + +diff --git a/src/main/java/org/bukkit/craftbukkit/block/CraftSculkSensor.java b/src/main/java/org/bukkit/craftbukkit/block/CraftSculkSensor.java +index 70d85dbfcaae7ee632a4f541334302b46615a254..6ba229afb0219ff229fd794c59d7585f010742ee 100644 +--- a/src/main/java/org/bukkit/craftbukkit/block/CraftSculkSensor.java ++++ b/src/main/java/org/bukkit/craftbukkit/block/CraftSculkSensor.java +@@ -36,4 +36,17 @@ public class CraftSculkSensor extends CraftBlo + public CraftSculkSensor copy(Location location) { + return new CraftSculkSensor<>(this, location); + } ++ ++ // Paper start ++ @Override ++ public int getListenerRange() { ++ return this.getSnapshot().getListener().getListenerRadius(); ++ } ++ ++ @Override ++ public void setListenerRange(int range) { ++ Preconditions.checkArgument(range > 0, "Vibration listener range must be greater than 0"); ++ this.getSnapshot().rangeOverride = range; ++ } ++ // Paper end + } diff --git a/patches/server/0660-Fix-cancelled-powdered-snow-bucket-placement.patch b/patches/server/0660-Fix-cancelled-powdered-snow-bucket-placement.patch deleted file mode 100644 index 08a9dbf2e458..000000000000 --- a/patches/server/0660-Fix-cancelled-powdered-snow-bucket-placement.patch +++ /dev/null @@ -1,31 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Jake Potrebic -Date: Fri, 8 Oct 2021 13:12:58 -0700 -Subject: [PATCH] Fix cancelled powdered snow bucket placement - -Cancelling the placement of powdered snow from the powdered -snow bucket didn't revert grass that became snowy because of the -placement. - -diff --git a/src/main/java/net/minecraft/world/item/ItemStack.java b/src/main/java/net/minecraft/world/item/ItemStack.java -index ad1cb11245108b5cb187b686ed7e6bc769193b52..ec3be7fb4eb4560fe0106ac127f17d7437612157 100644 ---- a/src/main/java/net/minecraft/world/item/ItemStack.java -+++ b/src/main/java/net/minecraft/world/item/ItemStack.java -@@ -422,7 +422,7 @@ public final class ItemStack implements DataComponentHolder { - int oldCount = this.getCount(); - ServerLevel world = (ServerLevel) context.getLevel(); - -- if (!(item instanceof BucketItem || item instanceof SolidBucketItem)) { // if not bucket -+ if (!(item instanceof BucketItem/* || item instanceof SolidBucketItem*/)) { // if not bucket // Paper - Fix cancelled powdered snow bucket placement - world.captureBlockStates = true; - // special case bonemeal - if (item == Items.BONE_MEAL) { -@@ -482,7 +482,7 @@ public final class ItemStack implements DataComponentHolder { - world.capturedBlockStates.clear(); - if (blocks.size() > 1) { - placeEvent = org.bukkit.craftbukkit.event.CraftEventFactory.callBlockMultiPlaceEvent(world, entityhuman, enumhand, blocks, blockposition.getX(), blockposition.getY(), blockposition.getZ()); -- } else if (blocks.size() == 1) { -+ } else if (blocks.size() == 1 && item != Items.POWDER_SNOW_BUCKET) { // Paper - Fix cancelled powdered snow bucket placement - placeEvent = org.bukkit.craftbukkit.event.CraftEventFactory.callBlockPlaceEvent(world, entityhuman, enumhand, blocks.get(0), blockposition.getX(), blockposition.getY(), blockposition.getZ()); - } - diff --git a/patches/server/0661-Add-missing-Validate-calls-to-CraftServer-getSpawnLi.patch b/patches/server/0661-Add-missing-Validate-calls-to-CraftServer-getSpawnLi.patch deleted file mode 100644 index 604f6800ee50..000000000000 --- a/patches/server/0661-Add-missing-Validate-calls-to-CraftServer-getSpawnLi.patch +++ /dev/null @@ -1,20 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Jason Penilla <11360596+jpenilla@users.noreply.github.com> -Date: Sat, 12 Feb 2022 12:40:50 -0700 -Subject: [PATCH] Add missing Validate calls to CraftServer#getSpawnLimit - -Copies appropriate checks from CraftWorld#getSpawnLimit - -diff --git a/src/main/java/org/bukkit/craftbukkit/CraftServer.java b/src/main/java/org/bukkit/craftbukkit/CraftServer.java -index c0eca359919c55ba7b33520277c124eb54d935d7..1a2081267e85ef5c1edd4808fbda5bb442a00252 100644 ---- a/src/main/java/org/bukkit/craftbukkit/CraftServer.java -+++ b/src/main/java/org/bukkit/craftbukkit/CraftServer.java -@@ -2339,6 +2339,8 @@ public final class CraftServer implements Server { - @Override - public int getSpawnLimit(SpawnCategory spawnCategory) { - // Paper start - Add mobcaps commands -+ Preconditions.checkArgument(spawnCategory != null, "SpawnCategory cannot be null"); -+ Preconditions.checkArgument(CraftSpawnCategory.isValidForLimits(spawnCategory), "SpawnCategory." + spawnCategory + " does not have a spawn limit."); - return this.getSpawnLimitUnsafe(spawnCategory); - } - public int getSpawnLimitUnsafe(final SpawnCategory spawnCategory) { diff --git a/patches/server/0666-Add-missing-block-data-API.patch b/patches/server/0661-Add-missing-block-data-API.patch similarity index 100% rename from patches/server/0666-Add-missing-block-data-API.patch rename to patches/server/0661-Add-missing-block-data-API.patch diff --git a/patches/server/0662-Add-GameEvent-tags.patch b/patches/server/0662-Add-GameEvent-tags.patch deleted file mode 100644 index d545e402aeaa..000000000000 --- a/patches/server/0662-Add-GameEvent-tags.patch +++ /dev/null @@ -1,81 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Jake Potrebic -Date: Sun, 3 Jan 2021 20:03:35 -0800 -Subject: [PATCH] Add GameEvent tags - - -diff --git a/src/main/java/io/papermc/paper/CraftGameEventTag.java b/src/main/java/io/papermc/paper/CraftGameEventTag.java -new file mode 100644 -index 0000000000000000000000000000000000000000..e7d9fd2702a1ce96596580fff8f5ee4fd3d22b5b ---- /dev/null -+++ b/src/main/java/io/papermc/paper/CraftGameEventTag.java -@@ -0,0 +1,35 @@ -+package io.papermc.paper; -+ -+import net.minecraft.core.registries.BuiltInRegistries; -+import net.minecraft.core.registries.Registries; -+import net.minecraft.resources.ResourceKey; -+import net.minecraft.tags.TagKey; -+import org.bukkit.GameEvent; -+import org.bukkit.craftbukkit.tag.CraftTag; -+import org.bukkit.craftbukkit.util.CraftNamespacedKey; -+import org.jetbrains.annotations.NotNull; -+ -+import java.util.Collections; -+import java.util.IdentityHashMap; -+import java.util.Map; -+import java.util.Objects; -+import java.util.Set; -+import java.util.stream.Collectors; -+ -+public class CraftGameEventTag extends CraftTag { -+ -+ public CraftGameEventTag(net.minecraft.core.Registry registry, TagKey tag) { -+ super(registry, tag); -+ } -+ -+ private static final Map> KEY_CACHE = Collections.synchronizedMap(new IdentityHashMap<>()); -+ @Override -+ public boolean isTagged(@NotNull GameEvent gameEvent) { -+ return registry.getHolderOrThrow(KEY_CACHE.computeIfAbsent(gameEvent, event -> ResourceKey.create(Registries.GAME_EVENT, CraftNamespacedKey.toMinecraft(event.getKey())))).is(tag); -+ } -+ -+ @Override -+ public @NotNull Set getValues() { -+ return getHandle().stream().map((nms) -> Objects.requireNonNull(GameEvent.getByKey(CraftNamespacedKey.fromMinecraft(BuiltInRegistries.GAME_EVENT.getKey(nms.value()))), nms + " is not a recognized game event")).collect(Collectors.toUnmodifiableSet()); -+ } -+} -diff --git a/src/main/java/org/bukkit/craftbukkit/CraftServer.java b/src/main/java/org/bukkit/craftbukkit/CraftServer.java -index 1a2081267e85ef5c1edd4808fbda5bb442a00252..8589b0218659ae5ddbaac5437316bf59265324dd 100644 ---- a/src/main/java/org/bukkit/craftbukkit/CraftServer.java -+++ b/src/main/java/org/bukkit/craftbukkit/CraftServer.java -@@ -2688,6 +2688,15 @@ public final class CraftServer implements Server { - return (org.bukkit.Tag) new CraftEntityTag(BuiltInRegistries.ENTITY_TYPE, entityTagKey); - } - } -+ // Paper start -+ case org.bukkit.Tag.REGISTRY_GAME_EVENTS -> { -+ Preconditions.checkArgument(clazz == org.bukkit.GameEvent.class, "Game Event namespace must have GameEvent type"); -+ TagKey gameEventTagKey = TagKey.create(net.minecraft.core.registries.Registries.GAME_EVENT, key); -+ if (net.minecraft.core.registries.BuiltInRegistries.GAME_EVENT.getTag(gameEventTagKey).isPresent()) { -+ return (org.bukkit.Tag) new io.papermc.paper.CraftGameEventTag(net.minecraft.core.registries.BuiltInRegistries.GAME_EVENT, gameEventTagKey); -+ } -+ } -+ // Paper end - default -> throw new IllegalArgumentException(); - } - -@@ -2720,6 +2729,13 @@ public final class CraftServer implements Server { - net.minecraft.core.Registry> entityTags = BuiltInRegistries.ENTITY_TYPE; - return entityTags.getTags().map(pair -> (org.bukkit.Tag) new CraftEntityTag(entityTags, pair.getFirst())).collect(ImmutableList.toImmutableList()); - } -+ // Paper start -+ case org.bukkit.Tag.REGISTRY_GAME_EVENTS -> { -+ Preconditions.checkArgument(clazz == org.bukkit.GameEvent.class); -+ net.minecraft.core.Registry gameEvents = net.minecraft.core.registries.BuiltInRegistries.GAME_EVENT; -+ return gameEvents.getTags().map(pair -> (org.bukkit.Tag) new io.papermc.paper.CraftGameEventTag(gameEvents, pair.getFirst())).collect(ImmutableList.toImmutableList()); -+ } -+ // Paper end - default -> throw new IllegalArgumentException(); - } - } diff --git a/patches/server/0662-Option-to-have-default-CustomSpawners-in-custom-worl.patch b/patches/server/0662-Option-to-have-default-CustomSpawners-in-custom-worl.patch new file mode 100644 index 000000000000..622b090c3c00 --- /dev/null +++ b/patches/server/0662-Option-to-have-default-CustomSpawners-in-custom-worl.patch @@ -0,0 +1,32 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Jake Potrebic +Date: Sat, 19 Feb 2022 20:15:41 -0800 +Subject: [PATCH] Option to have default CustomSpawners in custom worlds + +By default, only LevelStem's that specifically match the ResourceKey for +OVERWORLD will have the 5 (currently) impls of CustomSpawner (for +phantoms, wandering traders, etc.). This adds an option to instead of +just looking at the LevelStem key, look at the DimensionType key which +is one level below that. Defaults to off to keep vanilla behavior. + +diff --git a/src/main/java/net/minecraft/server/MinecraftServer.java b/src/main/java/net/minecraft/server/MinecraftServer.java +index 3cd23d329fae0a6a4eff36510edc5d9bd27c804e..6fdc5be323833c4ca2722b695fe790ea2ecc53d5 100644 +--- a/src/main/java/net/minecraft/server/MinecraftServer.java ++++ b/src/main/java/net/minecraft/server/MinecraftServer.java +@@ -646,7 +646,15 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop spawners; ++ if (io.papermc.paper.configuration.GlobalConfiguration.get().misc.useDimensionTypeForCustomSpawners && this.registryAccess().lookupOrThrow(Registries.DIMENSION_TYPE).getResourceKey(worlddimension.type().value()).orElseThrow() == net.minecraft.world.level.dimension.BuiltinDimensionTypes.OVERWORLD) { ++ spawners = list; ++ } else { ++ spawners = Collections.emptyList(); ++ } ++ world = new ServerLevel(this, this.executor, worldSession, iworlddataserver, worldKey, worlddimension, worldloadlistener, flag, j, spawners, true, this.overworld().getRandomSequences(), org.bukkit.World.Environment.getEnvironment(dimension), gen, biomeProvider); ++ // Paper end - option to use the dimension_type to check if spawners should be added + } + + worlddata.setModdedInfo(this.getServerModName(), this.getModdedStatus().shouldReportAsModified()); diff --git a/patches/server/0663-Execute-chunk-tasks-fairly-for-worlds-while-waiting-.patch b/patches/server/0663-Execute-chunk-tasks-fairly-for-worlds-while-waiting-.patch deleted file mode 100644 index f73960e08f60..000000000000 --- a/patches/server/0663-Execute-chunk-tasks-fairly-for-worlds-while-waiting-.patch +++ /dev/null @@ -1,37 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Spottedleaf -Date: Tue, 28 Dec 2021 07:19:01 -0800 -Subject: [PATCH] Execute chunk tasks fairly for worlds while waiting for next - tick - -Currently, only the first world would have had tasks executed. -This might result in chunks loading far slower in the nether, -for example. - -diff --git a/src/main/java/net/minecraft/server/MinecraftServer.java b/src/main/java/net/minecraft/server/MinecraftServer.java -index 718b653a118a1c64d07efab93192f10dfcbf414e..d45f234fe389ade739bb13f824a1f650709220b8 100644 ---- a/src/main/java/net/minecraft/server/MinecraftServer.java -+++ b/src/main/java/net/minecraft/server/MinecraftServer.java -@@ -1369,6 +1369,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop +Date: Tue, 22 Feb 2022 14:21:35 -0800 +Subject: [PATCH] Put world into worldlist before initing the world + +Some parts of legacy conversion will need the overworld +to get the legacy structure data storage + +diff --git a/src/main/java/net/minecraft/server/MinecraftServer.java b/src/main/java/net/minecraft/server/MinecraftServer.java +index 6fdc5be323833c4ca2722b695fe790ea2ecc53d5..88d4021bc4f9ab7c9ee33a5a5c2326b7c628f4a4 100644 +--- a/src/main/java/net/minecraft/server/MinecraftServer.java ++++ b/src/main/java/net/minecraft/server/MinecraftServer.java +@@ -658,9 +658,10 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop +Date: Thu, 7 Oct 2021 14:34:55 -0700 +Subject: [PATCH] Custom Potion Mixes + +== AT == +public-f net.minecraft.server.MinecraftServer potionBrewing + +diff --git a/src/main/java/io/papermc/paper/potion/PaperPotionBrewer.java b/src/main/java/io/papermc/paper/potion/PaperPotionBrewer.java +new file mode 100644 +index 0000000000000000000000000000000000000000..d9390227a2bba4e03aa9ee592ca157127633c41b +--- /dev/null ++++ b/src/main/java/io/papermc/paper/potion/PaperPotionBrewer.java +@@ -0,0 +1,56 @@ ++package io.papermc.paper.potion; ++ ++import com.google.common.base.Preconditions; ++import java.util.Collection; ++import net.minecraft.server.MinecraftServer; ++import org.bukkit.NamespacedKey; ++import org.bukkit.potion.PotionBrewer; ++import org.bukkit.potion.PotionEffect; ++import org.bukkit.potion.PotionType; ++import org.checkerframework.checker.nullness.qual.NonNull; ++import org.checkerframework.framework.qual.DefaultQualifier; ++ ++@DefaultQualifier(NonNull.class) ++public class PaperPotionBrewer implements PotionBrewer { ++ ++ private final MinecraftServer minecraftServer; ++ ++ public PaperPotionBrewer(final MinecraftServer minecraftServer) { ++ this.minecraftServer = minecraftServer; ++ } ++ ++ @Override ++ @Deprecated(forRemoval = true) ++ public Collection getEffects(PotionType type, boolean upgraded, boolean extended) { ++ final org.bukkit.NamespacedKey key = type.getKey(); ++ ++ Preconditions.checkArgument(!key.getKey().startsWith("strong_"), "Strong potion type cannot be used directly, got %s", key); ++ Preconditions.checkArgument(!key.getKey().startsWith("long_"), "Extended potion type cannot be used directly, got %s", key); ++ ++ org.bukkit.NamespacedKey effectiveKey = key; ++ if (upgraded) { ++ effectiveKey = new org.bukkit.NamespacedKey(key.namespace(), "strong_" + key.key()); ++ } else if (extended) { ++ effectiveKey = new org.bukkit.NamespacedKey(key.namespace(), "long_" + key.key()); ++ } ++ ++ final org.bukkit.potion.PotionType effectivePotionType = org.bukkit.Registry.POTION.get(effectiveKey); ++ Preconditions.checkNotNull(type, "Unknown potion type from data " + effectiveKey.asMinimalString()); // Legacy error message in 1.20.4 ++ return effectivePotionType.getPotionEffects(); ++ } ++ ++ @Override ++ public void addPotionMix(final PotionMix potionMix) { ++ this.minecraftServer.potionBrewing().addPotionMix(potionMix); ++ } ++ ++ @Override ++ public void removePotionMix(final NamespacedKey key) { ++ this.minecraftServer.potionBrewing.removePotionMix(key); ++ } ++ ++ @Override ++ public void resetPotionMixes() { ++ this.minecraftServer.potionBrewing = this.minecraftServer.potionBrewing().reload(this.minecraftServer.getWorldData().enabledFeatures()); ++ } ++} +diff --git a/src/main/java/io/papermc/paper/potion/PaperPotionMix.java b/src/main/java/io/papermc/paper/potion/PaperPotionMix.java +new file mode 100644 +index 0000000000000000000000000000000000000000..7ea357ac2f3a93db4ebdf24b5072be7d1cad3e33 +--- /dev/null ++++ b/src/main/java/io/papermc/paper/potion/PaperPotionMix.java +@@ -0,0 +1,21 @@ ++package io.papermc.paper.potion; ++ ++import java.util.function.Predicate; ++import net.minecraft.world.item.ItemStack; ++import org.bukkit.craftbukkit.inventory.CraftItemStack; ++import org.bukkit.craftbukkit.inventory.CraftRecipe; ++import org.bukkit.inventory.RecipeChoice; ++ ++public record PaperPotionMix(ItemStack result, Predicate input, Predicate ingredient) { ++ ++ public PaperPotionMix(PotionMix potionMix) { ++ this(CraftItemStack.asNMSCopy(potionMix.getResult()), convert(potionMix.getInput()), convert(potionMix.getIngredient())); ++ } ++ ++ static Predicate convert(final RecipeChoice choice) { ++ if (choice instanceof PredicateRecipeChoice predicateRecipeChoice) { ++ return stack -> predicateRecipeChoice.test(CraftItemStack.asBukkitCopy(stack)); ++ } ++ return CraftRecipe.toIngredient(choice, true); ++ } ++} +diff --git a/src/main/java/net/minecraft/server/MinecraftServer.java b/src/main/java/net/minecraft/server/MinecraftServer.java +index 88d4021bc4f9ab7c9ee33a5a5c2326b7c628f4a4..a836ea518bc6d8ddd7c6484038d3d712b3fb13cf 100644 +--- a/src/main/java/net/minecraft/server/MinecraftServer.java ++++ b/src/main/java/net/minecraft/server/MinecraftServer.java +@@ -2196,6 +2196,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop containers; + private final List> potionMixes; + private final List> containerMixes; ++ private final it.unimi.dsi.fastutil.objects.Object2ObjectLinkedOpenHashMap customMixes = new it.unimi.dsi.fastutil.objects.Object2ObjectLinkedOpenHashMap<>(); // Paper - Custom Potion Mixes + + PotionBrewing(List potionTypes, List> potionRecipes, List> itemRecipes) { + this.containers = potionTypes; +@@ -27,7 +28,7 @@ public class PotionBrewing { + } + + public boolean isIngredient(ItemStack stack) { +- return this.isContainerIngredient(stack) || this.isPotionIngredient(stack); ++ return this.isContainerIngredient(stack) || this.isPotionIngredient(stack) || this.isCustomIngredient(stack); // Paper - Custom Potion Mixes + } + + private boolean isContainer(ItemStack stack) { +@@ -71,6 +72,11 @@ public class PotionBrewing { + } + + public boolean hasMix(ItemStack input, ItemStack ingredient) { ++ // Paper start - Custom Potion Mixes ++ if (this.hasCustomMix(input, ingredient)) { ++ return true; ++ } ++ // Paper end - Custom Potion Mixes + return this.isContainer(input) && (this.hasContainerMix(input, ingredient) || this.hasPotionMix(input, ingredient)); + } + +@@ -103,6 +109,13 @@ public class PotionBrewing { + if (input.isEmpty()) { + return input; + } else { ++ // Paper start - Custom Potion Mixes ++ for (io.papermc.paper.potion.PaperPotionMix mix : this.customMixes.values()) { ++ if (mix.input().test(input) && mix.ingredient().test(ingredient)) { ++ return mix.result().copy(); ++ } ++ } ++ // Paper end - Custom Potion Mixes + Optional> optional = input.getOrDefault(DataComponents.POTION_CONTENTS, PotionContents.EMPTY).potion(); + if (optional.isEmpty()) { + return input; +@@ -190,6 +203,50 @@ public class PotionBrewing { + builder.addMix(Potions.SLOW_FALLING, Items.REDSTONE, Potions.LONG_SLOW_FALLING); + } + ++ // Paper start - Custom Potion Mixes ++ public boolean isCustomIngredient(ItemStack stack) { ++ for (io.papermc.paper.potion.PaperPotionMix mix : this.customMixes.values()) { ++ if (mix.ingredient().test(stack)) { ++ return true; ++ } ++ } ++ return false; ++ } ++ ++ public boolean isCustomInput(ItemStack stack) { ++ for (io.papermc.paper.potion.PaperPotionMix mix : this.customMixes.values()) { ++ if (mix.input().test(stack)) { ++ return true; ++ } ++ } ++ return false; ++ } ++ ++ private boolean hasCustomMix(ItemStack input, ItemStack ingredient) { ++ for (io.papermc.paper.potion.PaperPotionMix mix : this.customMixes.values()) { ++ if (mix.input().test(input) && mix.ingredient().test(ingredient)) { ++ return true; ++ } ++ } ++ return false; ++ } ++ ++ public void addPotionMix(io.papermc.paper.potion.PotionMix mix) { ++ if (this.customMixes.containsKey(mix.getKey())) { ++ throw new IllegalArgumentException("Duplicate recipe ignored with ID " + mix.getKey()); ++ } ++ this.customMixes.putAndMoveToFirst(mix.getKey(), new io.papermc.paper.potion.PaperPotionMix(mix)); ++ } ++ ++ public boolean removePotionMix(org.bukkit.NamespacedKey key) { ++ return this.customMixes.remove(key) != null; ++ } ++ ++ public PotionBrewing reload(FeatureFlagSet flags) { ++ return bootstrap(flags); ++ } ++ // Paper end - Custom Potion Mixes ++ + public static class Builder { + private final List containers = new ArrayList<>(); + private final List> potionMixes = new ArrayList<>(); +diff --git a/src/main/java/net/minecraft/world/level/block/entity/BrewingStandBlockEntity.java b/src/main/java/net/minecraft/world/level/block/entity/BrewingStandBlockEntity.java +index e167c2834f1b7899a7d11cef782940deeb739a9c..2bafacd7bc56186d9105d2031180f8c4a6940018 100644 +--- a/src/main/java/net/minecraft/world/level/block/entity/BrewingStandBlockEntity.java ++++ b/src/main/java/net/minecraft/world/level/block/entity/BrewingStandBlockEntity.java +@@ -316,12 +316,12 @@ public class BrewingStandBlockEntity extends BaseContainerBlockEntity implements + + @Override + public boolean canPlaceItem(int slot, ItemStack stack) { ++ PotionBrewing potionbrewer = this.level != null ? this.level.potionBrewing() : PotionBrewing.EMPTY; // Paper - move up + if (slot == 3) { +- PotionBrewing potionbrewer = this.level != null ? this.level.potionBrewing() : PotionBrewing.EMPTY; + + return potionbrewer.isIngredient(stack); + } else { +- return slot == 4 ? stack.is(ItemTags.BREWING_FUEL) : (stack.is(Items.POTION) || stack.is(Items.SPLASH_POTION) || stack.is(Items.LINGERING_POTION) || stack.is(Items.GLASS_BOTTLE)) && this.getItem(slot).isEmpty(); ++ return slot == 4 ? stack.is(ItemTags.BREWING_FUEL) : (stack.is(Items.POTION) || stack.is(Items.SPLASH_POTION) || stack.is(Items.LINGERING_POTION) || stack.is(Items.GLASS_BOTTLE) || potionbrewer.isCustomInput(stack)) && this.getItem(slot).isEmpty(); // Paper - Custom Potion Mixes + } + } + +diff --git a/src/main/java/org/bukkit/craftbukkit/CraftServer.java b/src/main/java/org/bukkit/craftbukkit/CraftServer.java +index fcd8b3ce258bdb12a87d41d348ac847d5dc603f8..3b48a8107bf0b4326b86ee08bb54341825be81c1 100644 +--- a/src/main/java/org/bukkit/craftbukkit/CraftServer.java ++++ b/src/main/java/org/bukkit/craftbukkit/CraftServer.java +@@ -311,6 +311,7 @@ public final class CraftServer implements Server { + private final io.papermc.paper.datapack.PaperDatapackManager datapackManager; // Paper + public static Exception excessiveVelEx; // Paper - Velocity warnings + private final io.papermc.paper.logging.SysoutCatcher sysoutCatcher = new io.papermc.paper.logging.SysoutCatcher(); // Paper ++ private final io.papermc.paper.potion.PaperPotionBrewer potionBrewer; // Paper - Custom Potion Mixes + + static { + ConfigurationSerialization.registerClass(CraftOfflinePlayer.class); +@@ -394,6 +395,7 @@ public final class CraftServer implements Server { + if (this.configuration.getBoolean("settings.use-map-color-cache")) { + MapPalette.setMapColorCache(new CraftMapColorCache(this.logger)); + } ++ this.potionBrewer = new io.papermc.paper.potion.PaperPotionBrewer(console); // Paper - custom potion mixes + datapackManager = new io.papermc.paper.datapack.PaperDatapackManager(console.getPackRepository()); // Paper + } + +@@ -3066,5 +3068,9 @@ public final class CraftServer implements Server { + return datapackManager; + } + ++ @Override ++ public io.papermc.paper.potion.PaperPotionBrewer getPotionBrewer() { ++ return this.potionBrewer; ++ } + // Paper end + } +diff --git a/src/main/java/org/bukkit/craftbukkit/inventory/CraftRecipe.java b/src/main/java/org/bukkit/craftbukkit/inventory/CraftRecipe.java +index 6d955c0d2ae166fb39fa38c604729c9a66132352..a30950287646524c4906574d193ec7ce94b4eb34 100644 +--- a/src/main/java/org/bukkit/craftbukkit/inventory/CraftRecipe.java ++++ b/src/main/java/org/bukkit/craftbukkit/inventory/CraftRecipe.java +@@ -25,6 +25,11 @@ public interface CraftRecipe extends Recipe { + } + + default Ingredient toNMS(RecipeChoice bukkit, boolean requireNotEmpty) { ++ // Paper start ++ return toIngredient(bukkit, requireNotEmpty); ++ } ++ static Ingredient toIngredient(RecipeChoice bukkit, boolean requireNotEmpty) { ++ // Paper end + Ingredient stack; + + if (bukkit == null) { diff --git a/patches/server/0664-Furnace-RecipesUsed-API.patch b/patches/server/0664-Furnace-RecipesUsed-API.patch deleted file mode 100644 index d8224fe839a2..000000000000 --- a/patches/server/0664-Furnace-RecipesUsed-API.patch +++ /dev/null @@ -1,48 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Jake Potrebic -Date: Thu, 13 Jan 2022 15:20:47 -0800 -Subject: [PATCH] Furnace RecipesUsed API - - -diff --git a/src/main/java/org/bukkit/craftbukkit/block/CraftFurnace.java b/src/main/java/org/bukkit/craftbukkit/block/CraftFurnace.java -index ddbbf977c8f536a156ff6b2462353f7be5ab5742..677d8bc8fcfbb987378b4fbe1a57935786b612fb 100644 ---- a/src/main/java/org/bukkit/craftbukkit/block/CraftFurnace.java -+++ b/src/main/java/org/bukkit/craftbukkit/block/CraftFurnace.java -@@ -104,5 +104,37 @@ public abstract class CraftFurnace extends - snapshot.cookSpeedMultiplier = multiplier; - snapshot.cookingTotalTime = AbstractFurnaceBlockEntity.getTotalCookTime(this.isPlaced() ? this.world.getHandle() : null, snapshot.recipeType, snapshot, snapshot.cookSpeedMultiplier); // Update the snapshot's current total cook time to scale with the newly set multiplier - } -+ -+ @Override -+ public int getRecipeUsedCount(org.bukkit.NamespacedKey furnaceRecipe) { -+ return this.getSnapshot().getRecipesUsed().getInt(org.bukkit.craftbukkit.util.CraftNamespacedKey.toMinecraft(furnaceRecipe)); -+ } -+ -+ @Override -+ public boolean hasRecipeUsedCount(org.bukkit.NamespacedKey furnaceRecipe) { -+ return this.getSnapshot().getRecipesUsed().containsKey(org.bukkit.craftbukkit.util.CraftNamespacedKey.toMinecraft(furnaceRecipe)); -+ } -+ -+ @Override -+ public void setRecipeUsedCount(org.bukkit.inventory.CookingRecipe furnaceRecipe, int count) { -+ final net.minecraft.resources.ResourceLocation location = org.bukkit.craftbukkit.util.CraftNamespacedKey.toMinecraft(furnaceRecipe.getKey()); -+ java.util.Optional> nmsRecipe = (this.isPlaced() ? this.world.getHandle().getRecipeManager() : net.minecraft.server.MinecraftServer.getServer().getRecipeManager()).byKey(location); -+ com.google.common.base.Preconditions.checkArgument(nmsRecipe.isPresent() && nmsRecipe.get().value() instanceof net.minecraft.world.item.crafting.AbstractCookingRecipe, furnaceRecipe.getKey() + " is not recognized as a valid and registered furnace recipe"); -+ if (count > 0) { -+ this.getSnapshot().getRecipesUsed().put(location, count); -+ } else { -+ this.getSnapshot().getRecipesUsed().removeInt(location); -+ } -+ } -+ -+ @Override -+ public void setRecipesUsed(java.util.Map, Integer> recipesUsed) { -+ this.getSnapshot().getRecipesUsed().clear(); -+ recipesUsed.forEach((recipe, integer) -> { -+ if (integer != null) { -+ this.setRecipeUsedCount(recipe, integer); -+ } -+ }); -+ } - // Paper end - } diff --git a/patches/server/0665-Configurable-sculk-sensor-listener-range.patch b/patches/server/0665-Configurable-sculk-sensor-listener-range.patch deleted file mode 100644 index 99ff902efa5e..000000000000 --- a/patches/server/0665-Configurable-sculk-sensor-listener-range.patch +++ /dev/null @@ -1,106 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Jake Potrebic -Date: Thu, 19 Aug 2021 18:45:42 -0700 -Subject: [PATCH] Configurable sculk sensor listener range - -== AT == -public-f net.minecraft.world.level.gameevent.vibrations.VibrationListener listenerRange - -diff --git a/src/main/java/net/minecraft/world/level/block/entity/CalibratedSculkSensorBlockEntity.java b/src/main/java/net/minecraft/world/level/block/entity/CalibratedSculkSensorBlockEntity.java -index 41ccbee5fc7767a7d5e1cdca0ec7d9a17ee80a90..1d28f117965da22694b12018923a5f1347905085 100644 ---- a/src/main/java/net/minecraft/world/level/block/entity/CalibratedSculkSensorBlockEntity.java -+++ b/src/main/java/net/minecraft/world/level/block/entity/CalibratedSculkSensorBlockEntity.java -@@ -20,6 +20,12 @@ public class CalibratedSculkSensorBlockEntity extends SculkSensorBlockEntity { - public VibrationSystem.User createVibrationUser() { - return new CalibratedSculkSensorBlockEntity.VibrationUser(this.getBlockPos()); - } -+ // Paper start - Configurable sculk sensor listener range -+ @Override -+ protected void saveRangeOverride(final net.minecraft.nbt.CompoundTag nbt) { -+ if (this.rangeOverride != null && this.rangeOverride != 16) nbt.putInt(PAPER_LISTENER_RANGE_NBT_KEY, this.rangeOverride); // only save if it's different from the default -+ } -+ // Paper end - Configurable sculk sensor listener range - - protected class VibrationUser extends SculkSensorBlockEntity.VibrationUser { - public VibrationUser(final BlockPos pos) { -@@ -28,6 +34,7 @@ public class CalibratedSculkSensorBlockEntity extends SculkSensorBlockEntity { - - @Override - public int getListenerRadius() { -+ if (CalibratedSculkSensorBlockEntity.this.rangeOverride != null) return CalibratedSculkSensorBlockEntity.this.rangeOverride; // Paper - Configurable sculk sensor listener range - return 16; - } - -diff --git a/src/main/java/net/minecraft/world/level/block/entity/SculkSensorBlockEntity.java b/src/main/java/net/minecraft/world/level/block/entity/SculkSensorBlockEntity.java -index bbaffdac7c2d3bd646133b36c5d450cba9fc0c07..a943ed46a5f593dfe612c9c797dc961e3e020506 100644 ---- a/src/main/java/net/minecraft/world/level/block/entity/SculkSensorBlockEntity.java -+++ b/src/main/java/net/minecraft/world/level/block/entity/SculkSensorBlockEntity.java -@@ -26,6 +26,7 @@ public class SculkSensorBlockEntity extends BlockEntity implements GameEventList - private final VibrationSystem.Listener vibrationListener; - private final VibrationSystem.User vibrationUser = this.createVibrationUser(); - public int lastVibrationFrequency; -+ @Nullable public Integer rangeOverride = null; // Paper - Configurable sculk sensor listener range - - protected SculkSensorBlockEntity(BlockEntityType type, BlockPos pos, BlockState state) { - super(type, pos, state); -@@ -52,8 +53,16 @@ public class SculkSensorBlockEntity extends BlockEntity implements GameEventList - .resultOrPartial(string -> LOGGER.error("Failed to parse vibration listener for Sculk Sensor: '{}'", string)) - .ifPresent(listener -> this.vibrationData = listener); - } -+ // Paper start - Configurable sculk sensor listener range -+ if (nbt.contains(PAPER_LISTENER_RANGE_NBT_KEY)) { -+ this.rangeOverride = nbt.getInt(PAPER_LISTENER_RANGE_NBT_KEY); -+ } else { -+ this.rangeOverride = null; -+ } -+ // Paper end - Configurable sculk sensor listener range - } - -+ protected static final String PAPER_LISTENER_RANGE_NBT_KEY = "Paper.ListenerRange"; // Paper - Configurable sculk sensor listener range - @Override - protected void saveAdditional(CompoundTag nbt, HolderLookup.Provider registryLookup) { - super.saveAdditional(nbt, registryLookup); -@@ -63,7 +72,13 @@ public class SculkSensorBlockEntity extends BlockEntity implements GameEventList - .encodeStart(registryOps, this.vibrationData) - .resultOrPartial(string -> LOGGER.error("Failed to encode vibration listener for Sculk Sensor: '{}'", string)) - .ifPresent(listenerNbt -> nbt.put("listener", listenerNbt)); -+ this.saveRangeOverride(nbt); // Paper - Configurable sculk sensor listener range -+ } -+ // Paper start - Configurable sculk sensor listener range -+ protected void saveRangeOverride(CompoundTag nbt) { -+ if (this.rangeOverride != null && this.rangeOverride != VibrationUser.LISTENER_RANGE) nbt.putInt(PAPER_LISTENER_RANGE_NBT_KEY, this.rangeOverride); // only save if it's different from the default - } -+ // Paper end - Configurable sculk sensor listener range - - @Override - public VibrationSystem.Data getVibrationData() { -@@ -100,6 +115,7 @@ public class SculkSensorBlockEntity extends BlockEntity implements GameEventList - - @Override - public int getListenerRadius() { -+ if (SculkSensorBlockEntity.this.rangeOverride != null) return SculkSensorBlockEntity.this.rangeOverride; // Paper - Configurable sculk sensor listener range - return 8; - } - -diff --git a/src/main/java/org/bukkit/craftbukkit/block/CraftSculkSensor.java b/src/main/java/org/bukkit/craftbukkit/block/CraftSculkSensor.java -index 70d85dbfcaae7ee632a4f541334302b46615a254..6ba229afb0219ff229fd794c59d7585f010742ee 100644 ---- a/src/main/java/org/bukkit/craftbukkit/block/CraftSculkSensor.java -+++ b/src/main/java/org/bukkit/craftbukkit/block/CraftSculkSensor.java -@@ -36,4 +36,17 @@ public class CraftSculkSensor extends CraftBlo - public CraftSculkSensor copy(Location location) { - return new CraftSculkSensor<>(this, location); - } -+ -+ // Paper start -+ @Override -+ public int getListenerRange() { -+ return this.getSnapshot().getListener().getListenerRadius(); -+ } -+ -+ @Override -+ public void setListenerRange(int range) { -+ Preconditions.checkArgument(range > 0, "Vibration listener range must be greater than 0"); -+ this.getSnapshot().rangeOverride = range; -+ } -+ // Paper end - } diff --git a/patches/server/0665-Force-close-world-loading-screen.patch b/patches/server/0665-Force-close-world-loading-screen.patch new file mode 100644 index 000000000000..376fbf1282cc --- /dev/null +++ b/patches/server/0665-Force-close-world-loading-screen.patch @@ -0,0 +1,32 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Nassim Jahnke +Date: Wed, 2 Mar 2022 09:45:56 +0100 +Subject: [PATCH] Force close world loading screen + +Dead players would be stuck in the world loading screen and other players may +miss messages and similar sent in the join event if chunk loading is slow. +Paper already circumvents falling through the world before chunks are loaded, +so we do not need that. The client only needs the chunk it is currently in to +be loaded to close the loading screen, so we just send an empty one. + +diff --git a/src/main/java/net/minecraft/server/players/PlayerList.java b/src/main/java/net/minecraft/server/players/PlayerList.java +index bb301d1ce9c1792ead2681c9f3d38bb2079b0112..11f86e1b119d20f668e67b83f09137dcb24d4bf1 100644 +--- a/src/main/java/net/minecraft/server/players/PlayerList.java ++++ b/src/main/java/net/minecraft/server/players/PlayerList.java +@@ -406,6 +406,16 @@ public abstract class PlayerList { + } + // Paper end - Configurable player collision + PlayerList.LOGGER.info("{}[{}] logged in with entity id {} at ([{}]{}, {}, {})", player.getName().getString(), s1, player.getId(), worldserver1.serverLevelData.getLevelName(), player.getX(), player.getY(), player.getZ()); ++ // Paper start - Send empty chunk, so players aren't stuck in the world loading screen with our chunk system not sending chunks when dead ++ if (player.isDeadOrDying()) { ++ net.minecraft.core.Holder plains = worldserver1.registryAccess().lookupOrThrow(net.minecraft.core.registries.Registries.BIOME) ++ .getOrThrow(net.minecraft.world.level.biome.Biomes.PLAINS); ++ player.connection.send(new net.minecraft.network.protocol.game.ClientboundLevelChunkWithLightPacket( ++ new net.minecraft.world.level.chunk.EmptyLevelChunk(worldserver1, player.chunkPosition(), plains), ++ worldserver1.getLightEngine(), (java.util.BitSet)null, (java.util.BitSet) null) ++ ); ++ } ++ // Paper end - Send empty chunk + } + + public void updateEntireScoreboard(ServerScoreboard scoreboard, ServerPlayer player) { diff --git a/patches/server/0666-Fix-falling-block-spawn-methods.patch b/patches/server/0666-Fix-falling-block-spawn-methods.patch new file mode 100644 index 000000000000..771588bb1233 --- /dev/null +++ b/patches/server/0666-Fix-falling-block-spawn-methods.patch @@ -0,0 +1,57 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Nassim Jahnke +Date: Fri, 4 Mar 2022 20:35:19 +0100 +Subject: [PATCH] Fix falling block spawn methods + +Restores the API behavior from previous versions of the server +- Do not call API events +- Do not replace the existing block in the world + +== AT == +public net.minecraft.world.entity.item.FallingBlockEntity (Lnet/minecraft/world/level/Level;DDDLnet/minecraft/world/level/block/state/BlockState;)V + +diff --git a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java +index 0ff12d4e74c14b2b0ecf06d720c5e06edb35fcdb..9240460b9ec582ac01d3074c9bc923191bf0ad95 100644 +--- a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java ++++ b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java +@@ -1405,7 +1405,12 @@ public class CraftWorld extends CraftRegionAccessor implements World { + Preconditions.checkArgument(material != null, "Material cannot be null"); + Preconditions.checkArgument(material.isBlock(), "Material.%s must be a block", material); + +- FallingBlockEntity entity = FallingBlockEntity.fall(this.world, BlockPos.containing(location.getX(), location.getY(), location.getZ()), CraftBlockType.bukkitToMinecraft(material).defaultBlockState(), SpawnReason.CUSTOM); ++ // Paper start - restore API behavior for spawning falling blocks ++ FallingBlockEntity entity = new FallingBlockEntity(this.world, location.getX(), location.getY(), location.getZ(), CraftBlockType.bukkitToMinecraft(material).defaultBlockState()); // Paper ++ entity.time = 1; ++ ++ this.world.addFreshEntity(entity, SpawnReason.CUSTOM); ++ // Paper end - restore API behavior for spawning falling blocks + return (FallingBlock) entity.getBukkitEntity(); + } + +@@ -1414,7 +1419,12 @@ public class CraftWorld extends CraftRegionAccessor implements World { + Preconditions.checkArgument(location != null, "Location cannot be null"); + Preconditions.checkArgument(data != null, "BlockData cannot be null"); + +- FallingBlockEntity entity = FallingBlockEntity.fall(this.world, BlockPos.containing(location.getX(), location.getY(), location.getZ()), ((CraftBlockData) data).getState(), SpawnReason.CUSTOM); ++ // Paper start - restore API behavior for spawning falling blocks ++ FallingBlockEntity entity = new FallingBlockEntity(this.world, location.getX(), location.getY(), location.getZ(), ((CraftBlockData) data).getState()); ++ entity.time = 1; ++ ++ this.world.addFreshEntity(entity, SpawnReason.CUSTOM); ++ // Paper end - restore API behavior for spawning falling blocks + return (FallingBlock) entity.getBukkitEntity(); + } + +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftEntityTypes.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftEntityTypes.java +index 42ecef3dbb888ba716b6f63335efca6fb0f27457..b79e72a77178f755957ef391b6444a357bdefbd0 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftEntityTypes.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftEntityTypes.java +@@ -440,7 +440,7 @@ public final class CraftEntityTypes { + register(new EntityTypeData<>(EntityType.TNT, TNTPrimed.class, CraftTNTPrimed::new, spawnData -> new PrimedTnt(spawnData.minecraftWorld(), spawnData.x(), spawnData.y(), spawnData.z(), null))); + register(new EntityTypeData<>(EntityType.FALLING_BLOCK, FallingBlock.class, CraftFallingBlock::new, spawnData -> { + BlockPos pos = BlockPos.containing(spawnData.x(), spawnData.y(), spawnData.z()); +- return FallingBlockEntity.fall(spawnData.minecraftWorld(), pos, spawnData.world().getBlockState(pos)); ++ return new FallingBlockEntity(spawnData.minecraftWorld(), spawnData.x(), spawnData.y(), spawnData.z(), spawnData.world().getBlockState(pos)); // Paper - create falling block entities correctly + })); + register(new EntityTypeData<>(EntityType.FIREWORK_ROCKET, Firework.class, CraftFirework::new, spawnData -> new FireworkRocketEntity(spawnData.minecraftWorld(), spawnData.x(), spawnData.y(), spawnData.z(), net.minecraft.world.item.ItemStack.EMPTY))); + register(new EntityTypeData<>(EntityType.EVOKER_FANGS, EvokerFangs.class, CraftEvokerFangs::new, spawnData -> new net.minecraft.world.entity.projectile.EvokerFangs(spawnData.minecraftWorld(), spawnData.x(), spawnData.y(), spawnData.z(), (float) Math.toRadians(spawnData.yaw()), 0, null))); diff --git a/patches/server/0667-Expose-furnace-minecart-push-values.patch b/patches/server/0667-Expose-furnace-minecart-push-values.patch new file mode 100644 index 000000000000..fb48aabb5f80 --- /dev/null +++ b/patches/server/0667-Expose-furnace-minecart-push-values.patch @@ -0,0 +1,42 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: EpicKnarvik97 +Date: Sat, 5 Mar 2022 20:58:46 +0100 +Subject: [PATCH] Expose furnace minecart push values + +Adds methods for getting and setting a furnace minecart's push values + +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftMinecartFurnace.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftMinecartFurnace.java +index 53042b75b45093535d6572239b34c3ff9a72f648..1be1f6d23f2224d4d8720d40f2e530736b1bae81 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftMinecartFurnace.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftMinecartFurnace.java +@@ -27,6 +27,30 @@ public class CraftMinecartFurnace extends CraftMinecart implements PoweredMineca + this.getHandle().fuel = fuel; + } + ++ // Paper start ++ @Override ++ public double getPushX() { ++ return getHandle().push.x; ++ } ++ ++ @Override ++ public double getPushZ() { ++ return getHandle().push.z; ++ } ++ ++ @Override ++ public void setPushX(double xPush) { ++ final net.minecraft.world.phys.Vec3 push = getHandle().push; ++ getHandle().push = new net.minecraft.world.phys.Vec3(xPush, push.y, push.z); ++ } ++ ++ @Override ++ public void setPushZ(double zPush) { ++ final net.minecraft.world.phys.Vec3 push = getHandle().push; ++ getHandle().push = new net.minecraft.world.phys.Vec3(push.x, push.y, zPush); ++ } ++ // Paper end ++ + @Override + public String toString() { + return "CraftMinecartFurnace"; diff --git a/patches/server/0667-Option-to-have-default-CustomSpawners-in-custom-worl.patch b/patches/server/0667-Option-to-have-default-CustomSpawners-in-custom-worl.patch deleted file mode 100644 index 9093bae5b5c9..000000000000 --- a/patches/server/0667-Option-to-have-default-CustomSpawners-in-custom-worl.patch +++ /dev/null @@ -1,32 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Jake Potrebic -Date: Sat, 19 Feb 2022 20:15:41 -0800 -Subject: [PATCH] Option to have default CustomSpawners in custom worlds - -By default, only LevelStem's that specifically match the ResourceKey for -OVERWORLD will have the 5 (currently) impls of CustomSpawner (for -phantoms, wandering traders, etc.). This adds an option to instead of -just looking at the LevelStem key, look at the DimensionType key which -is one level below that. Defaults to off to keep vanilla behavior. - -diff --git a/src/main/java/net/minecraft/server/MinecraftServer.java b/src/main/java/net/minecraft/server/MinecraftServer.java -index d45f234fe389ade739bb13f824a1f650709220b8..413a37991d1df6314dd9938e5eb5f28f5b69efb8 100644 ---- a/src/main/java/net/minecraft/server/MinecraftServer.java -+++ b/src/main/java/net/minecraft/server/MinecraftServer.java -@@ -631,7 +631,15 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop spawners; -+ if (io.papermc.paper.configuration.GlobalConfiguration.get().misc.useDimensionTypeForCustomSpawners && this.registryAccess().registryOrThrow(Registries.DIMENSION_TYPE).getResourceKey(worlddimension.type().value()).orElseThrow() == net.minecraft.world.level.dimension.BuiltinDimensionTypes.OVERWORLD) { -+ spawners = list; -+ } else { -+ spawners = Collections.emptyList(); -+ } -+ world = new ServerLevel(this, this.executor, worldSession, iworlddataserver, worldKey, worlddimension, worldloadlistener, flag, j, spawners, true, this.overworld().getRandomSequences(), org.bukkit.World.Environment.getEnvironment(dimension), gen, biomeProvider); -+ // Paper end - option to use the dimension_type to check if spawners should be added - } - - worlddata.setModdedInfo(this.getServerModName(), this.getModdedStatus().shouldReportAsModified()); diff --git a/patches/server/0668-Fix-cancelling-ProjectileHitEvent-for-piercing-arrow.patch b/patches/server/0668-Fix-cancelling-ProjectileHitEvent-for-piercing-arrow.patch new file mode 100644 index 000000000000..7a72849b26e5 --- /dev/null +++ b/patches/server/0668-Fix-cancelling-ProjectileHitEvent-for-piercing-arrow.patch @@ -0,0 +1,40 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Jake Potrebic +Date: Sat, 19 Feb 2022 19:05:59 -0800 +Subject: [PATCH] Fix cancelling ProjectileHitEvent for piercing arrows + +Piercing arrows search for multiple entities inside a while +loop that is checking the projectile entity's removed state. +If the hit event is cancelled on the first entity, the event will +be called over and over again inside that while loop until the event +is not cancelled. The solution here, is to make use of an +already-existing field on AbstractArrow for tracking entities hit by +piercing arrows to avoid duplicate damage being applied. + +== AT == +protected net.minecraft.world.entity.projectile.Projectile hitCancelled + +diff --git a/src/main/java/net/minecraft/world/entity/projectile/AbstractArrow.java b/src/main/java/net/minecraft/world/entity/projectile/AbstractArrow.java +index 44bcb1117cfa4d66c500011489ae193a0d1e7d78..75cc3db39c974abab8510af4a633fc6812efc647 100644 +--- a/src/main/java/net/minecraft/world/entity/projectile/AbstractArrow.java ++++ b/src/main/java/net/minecraft/world/entity/projectile/AbstractArrow.java +@@ -344,6 +344,19 @@ public abstract class AbstractArrow extends Projectile { + + } + ++ // Paper start - Fix cancelling ProjectileHitEvent for piercing arrows ++ @Override ++ public ProjectileDeflection preHitTargetOrDeflectSelf(HitResult hitResult) { ++ if (hitResult instanceof EntityHitResult entityHitResult && this.hitCancelled && this.getPierceLevel() > 0) { ++ if (this.piercingIgnoreEntityIds == null) { ++ this.piercingIgnoreEntityIds = new IntOpenHashSet(5); ++ } ++ this.piercingIgnoreEntityIds.add(entityHitResult.getEntity().getId()); ++ } ++ return super.preHitTargetOrDeflectSelf(hitResult); ++ } ++ // Paper end - Fix cancelling ProjectileHitEvent for piercing arrows ++ + @Override + protected double getDefaultGravity() { + return 0.05D; diff --git a/patches/server/0668-Put-world-into-worldlist-before-initing-the-world.patch b/patches/server/0668-Put-world-into-worldlist-before-initing-the-world.patch deleted file mode 100644 index 06960bd55489..000000000000 --- a/patches/server/0668-Put-world-into-worldlist-before-initing-the-world.patch +++ /dev/null @@ -1,41 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Spottedleaf -Date: Tue, 22 Feb 2022 14:21:35 -0800 -Subject: [PATCH] Put world into worldlist before initing the world - -Some parts of legacy conversion will need the overworld -to get the legacy structure data storage - -diff --git a/src/main/java/net/minecraft/server/MinecraftServer.java b/src/main/java/net/minecraft/server/MinecraftServer.java -index 413a37991d1df6314dd9938e5eb5f28f5b69efb8..e7d6f4e6ac41b183c702d5195e4f94136c22b000 100644 ---- a/src/main/java/net/minecraft/server/MinecraftServer.java -+++ b/src/main/java/net/minecraft/server/MinecraftServer.java -@@ -643,9 +643,10 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop -Date: Thu, 7 Oct 2021 14:34:55 -0700 -Subject: [PATCH] Custom Potion Mixes - -== AT == -public-f net.minecraft.server.MinecraftServer potionBrewing - -diff --git a/src/main/java/io/papermc/paper/potion/PaperPotionBrewer.java b/src/main/java/io/papermc/paper/potion/PaperPotionBrewer.java -new file mode 100644 -index 0000000000000000000000000000000000000000..d9390227a2bba4e03aa9ee592ca157127633c41b ---- /dev/null -+++ b/src/main/java/io/papermc/paper/potion/PaperPotionBrewer.java -@@ -0,0 +1,56 @@ -+package io.papermc.paper.potion; -+ -+import com.google.common.base.Preconditions; -+import java.util.Collection; -+import net.minecraft.server.MinecraftServer; -+import org.bukkit.NamespacedKey; -+import org.bukkit.potion.PotionBrewer; -+import org.bukkit.potion.PotionEffect; -+import org.bukkit.potion.PotionType; -+import org.checkerframework.checker.nullness.qual.NonNull; -+import org.checkerframework.framework.qual.DefaultQualifier; -+ -+@DefaultQualifier(NonNull.class) -+public class PaperPotionBrewer implements PotionBrewer { -+ -+ private final MinecraftServer minecraftServer; -+ -+ public PaperPotionBrewer(final MinecraftServer minecraftServer) { -+ this.minecraftServer = minecraftServer; -+ } -+ -+ @Override -+ @Deprecated(forRemoval = true) -+ public Collection getEffects(PotionType type, boolean upgraded, boolean extended) { -+ final org.bukkit.NamespacedKey key = type.getKey(); -+ -+ Preconditions.checkArgument(!key.getKey().startsWith("strong_"), "Strong potion type cannot be used directly, got %s", key); -+ Preconditions.checkArgument(!key.getKey().startsWith("long_"), "Extended potion type cannot be used directly, got %s", key); -+ -+ org.bukkit.NamespacedKey effectiveKey = key; -+ if (upgraded) { -+ effectiveKey = new org.bukkit.NamespacedKey(key.namespace(), "strong_" + key.key()); -+ } else if (extended) { -+ effectiveKey = new org.bukkit.NamespacedKey(key.namespace(), "long_" + key.key()); -+ } -+ -+ final org.bukkit.potion.PotionType effectivePotionType = org.bukkit.Registry.POTION.get(effectiveKey); -+ Preconditions.checkNotNull(type, "Unknown potion type from data " + effectiveKey.asMinimalString()); // Legacy error message in 1.20.4 -+ return effectivePotionType.getPotionEffects(); -+ } -+ -+ @Override -+ public void addPotionMix(final PotionMix potionMix) { -+ this.minecraftServer.potionBrewing().addPotionMix(potionMix); -+ } -+ -+ @Override -+ public void removePotionMix(final NamespacedKey key) { -+ this.minecraftServer.potionBrewing.removePotionMix(key); -+ } -+ -+ @Override -+ public void resetPotionMixes() { -+ this.minecraftServer.potionBrewing = this.minecraftServer.potionBrewing().reload(this.minecraftServer.getWorldData().enabledFeatures()); -+ } -+} -diff --git a/src/main/java/io/papermc/paper/potion/PaperPotionMix.java b/src/main/java/io/papermc/paper/potion/PaperPotionMix.java -new file mode 100644 -index 0000000000000000000000000000000000000000..7ea357ac2f3a93db4ebdf24b5072be7d1cad3e33 ---- /dev/null -+++ b/src/main/java/io/papermc/paper/potion/PaperPotionMix.java -@@ -0,0 +1,21 @@ -+package io.papermc.paper.potion; -+ -+import java.util.function.Predicate; -+import net.minecraft.world.item.ItemStack; -+import org.bukkit.craftbukkit.inventory.CraftItemStack; -+import org.bukkit.craftbukkit.inventory.CraftRecipe; -+import org.bukkit.inventory.RecipeChoice; -+ -+public record PaperPotionMix(ItemStack result, Predicate input, Predicate ingredient) { -+ -+ public PaperPotionMix(PotionMix potionMix) { -+ this(CraftItemStack.asNMSCopy(potionMix.getResult()), convert(potionMix.getInput()), convert(potionMix.getIngredient())); -+ } -+ -+ static Predicate convert(final RecipeChoice choice) { -+ if (choice instanceof PredicateRecipeChoice predicateRecipeChoice) { -+ return stack -> predicateRecipeChoice.test(CraftItemStack.asBukkitCopy(stack)); -+ } -+ return CraftRecipe.toIngredient(choice, true); -+ } -+} -diff --git a/src/main/java/net/minecraft/server/MinecraftServer.java b/src/main/java/net/minecraft/server/MinecraftServer.java -index e7d6f4e6ac41b183c702d5195e4f94136c22b000..94aa901b77b19445a33d5b2b24ba2e947d2a6aef 100644 ---- a/src/main/java/net/minecraft/server/MinecraftServer.java -+++ b/src/main/java/net/minecraft/server/MinecraftServer.java -@@ -2158,6 +2158,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop containers; - private final List> potionMixes; - private final List> containerMixes; -+ private final it.unimi.dsi.fastutil.objects.Object2ObjectLinkedOpenHashMap customMixes = new it.unimi.dsi.fastutil.objects.Object2ObjectLinkedOpenHashMap<>(); // Paper - Custom Potion Mixes - - PotionBrewing(List potionTypes, List> potionRecipes, List> itemRecipes) { - this.containers = potionTypes; -@@ -27,7 +28,7 @@ public class PotionBrewing { - } - - public boolean isIngredient(ItemStack stack) { -- return this.isContainerIngredient(stack) || this.isPotionIngredient(stack); -+ return this.isContainerIngredient(stack) || this.isPotionIngredient(stack) || this.isCustomIngredient(stack); // Paper - Custom Potion Mixes - } - - private boolean isContainer(ItemStack stack) { -@@ -71,6 +72,11 @@ public class PotionBrewing { - } - - public boolean hasMix(ItemStack input, ItemStack ingredient) { -+ // Paper start - Custom Potion Mixes -+ if (this.hasCustomMix(input, ingredient)) { -+ return true; -+ } -+ // Paper end - Custom Potion Mixes - return this.isContainer(input) && (this.hasContainerMix(input, ingredient) || this.hasPotionMix(input, ingredient)); - } - -@@ -103,6 +109,13 @@ public class PotionBrewing { - if (input.isEmpty()) { - return input; - } else { -+ // Paper start - Custom Potion Mixes -+ for (io.papermc.paper.potion.PaperPotionMix mix : this.customMixes.values()) { -+ if (mix.input().test(input) && mix.ingredient().test(ingredient)) { -+ return mix.result().copy(); -+ } -+ } -+ // Paper end - Custom Potion Mixes - Optional> optional = input.getOrDefault(DataComponents.POTION_CONTENTS, PotionContents.EMPTY).potion(); - if (optional.isEmpty()) { - return input; -@@ -190,6 +203,50 @@ public class PotionBrewing { - builder.addMix(Potions.SLOW_FALLING, Items.REDSTONE, Potions.LONG_SLOW_FALLING); - } - -+ // Paper start - Custom Potion Mixes -+ public boolean isCustomIngredient(ItemStack stack) { -+ for (io.papermc.paper.potion.PaperPotionMix mix : this.customMixes.values()) { -+ if (mix.ingredient().test(stack)) { -+ return true; -+ } -+ } -+ return false; -+ } -+ -+ public boolean isCustomInput(ItemStack stack) { -+ for (io.papermc.paper.potion.PaperPotionMix mix : this.customMixes.values()) { -+ if (mix.input().test(stack)) { -+ return true; -+ } -+ } -+ return false; -+ } -+ -+ private boolean hasCustomMix(ItemStack input, ItemStack ingredient) { -+ for (io.papermc.paper.potion.PaperPotionMix mix : this.customMixes.values()) { -+ if (mix.input().test(input) && mix.ingredient().test(ingredient)) { -+ return true; -+ } -+ } -+ return false; -+ } -+ -+ public void addPotionMix(io.papermc.paper.potion.PotionMix mix) { -+ if (this.customMixes.containsKey(mix.getKey())) { -+ throw new IllegalArgumentException("Duplicate recipe ignored with ID " + mix.getKey()); -+ } -+ this.customMixes.putAndMoveToFirst(mix.getKey(), new io.papermc.paper.potion.PaperPotionMix(mix)); -+ } -+ -+ public boolean removePotionMix(org.bukkit.NamespacedKey key) { -+ return this.customMixes.remove(key) != null; -+ } -+ -+ public PotionBrewing reload(FeatureFlagSet flags) { -+ return bootstrap(flags); -+ } -+ // Paper end - Custom Potion Mixes -+ - public static class Builder { - private final List containers = new ArrayList<>(); - private final List> potionMixes = new ArrayList<>(); -diff --git a/src/main/java/net/minecraft/world/level/block/entity/BrewingStandBlockEntity.java b/src/main/java/net/minecraft/world/level/block/entity/BrewingStandBlockEntity.java -index c8f9972ad1c2330908cc840d426f29c20b242ca8..a2fafef89d5354e2cb02f5672810909950a57777 100644 ---- a/src/main/java/net/minecraft/world/level/block/entity/BrewingStandBlockEntity.java -+++ b/src/main/java/net/minecraft/world/level/block/entity/BrewingStandBlockEntity.java -@@ -315,12 +315,12 @@ public class BrewingStandBlockEntity extends BaseContainerBlockEntity implements - - @Override - public boolean canPlaceItem(int slot, ItemStack stack) { -+ PotionBrewing potionbrewer = this.level != null ? this.level.potionBrewing() : PotionBrewing.EMPTY; // Paper - move up - if (slot == 3) { -- PotionBrewing potionbrewer = this.level != null ? this.level.potionBrewing() : PotionBrewing.EMPTY; - - return potionbrewer.isIngredient(stack); - } else { -- return slot == 4 ? stack.is(Items.BLAZE_POWDER) : (stack.is(Items.POTION) || stack.is(Items.SPLASH_POTION) || stack.is(Items.LINGERING_POTION) || stack.is(Items.GLASS_BOTTLE)) && this.getItem(slot).isEmpty(); -+ return slot == 4 ? stack.is(Items.BLAZE_POWDER) : (stack.is(Items.POTION) || stack.is(Items.SPLASH_POTION) || stack.is(Items.LINGERING_POTION) || stack.is(Items.GLASS_BOTTLE) || potionbrewer.isCustomInput(stack)) && this.getItem(slot).isEmpty(); // Paper - Custom Potion Mixes - } - } - -diff --git a/src/main/java/org/bukkit/craftbukkit/CraftServer.java b/src/main/java/org/bukkit/craftbukkit/CraftServer.java -index 74d2f6df5a4cdab8a24ca1769c7b7d98ef87bcfe..566ed56de6fcb4dc64e504310b46295466917eee 100644 ---- a/src/main/java/org/bukkit/craftbukkit/CraftServer.java -+++ b/src/main/java/org/bukkit/craftbukkit/CraftServer.java -@@ -308,6 +308,7 @@ public final class CraftServer implements Server { - private final io.papermc.paper.datapack.PaperDatapackManager datapackManager; // Paper - public static Exception excessiveVelEx; // Paper - Velocity warnings - private final io.papermc.paper.logging.SysoutCatcher sysoutCatcher = new io.papermc.paper.logging.SysoutCatcher(); // Paper -+ private final io.papermc.paper.potion.PaperPotionBrewer potionBrewer; // Paper - Custom Potion Mixes - - static { - ConfigurationSerialization.registerClass(CraftOfflinePlayer.class); -@@ -392,6 +393,7 @@ public final class CraftServer implements Server { - if (this.configuration.getBoolean("settings.use-map-color-cache")) { - MapPalette.setMapColorCache(new CraftMapColorCache(this.logger)); - } -+ this.potionBrewer = new io.papermc.paper.potion.PaperPotionBrewer(console); // Paper - custom potion mixes - datapackManager = new io.papermc.paper.datapack.PaperDatapackManager(console.getPackRepository()); // Paper - } - -@@ -3048,5 +3050,9 @@ public final class CraftServer implements Server { - return datapackManager; - } - -+ @Override -+ public io.papermc.paper.potion.PaperPotionBrewer getPotionBrewer() { -+ return this.potionBrewer; -+ } - // Paper end - } -diff --git a/src/main/java/org/bukkit/craftbukkit/inventory/CraftRecipe.java b/src/main/java/org/bukkit/craftbukkit/inventory/CraftRecipe.java -index 139dff90561ac6c51954c6289918a07aeea13a1b..6ba29875d78ede4aa7978ff689e588f7fed11528 100644 ---- a/src/main/java/org/bukkit/craftbukkit/inventory/CraftRecipe.java -+++ b/src/main/java/org/bukkit/craftbukkit/inventory/CraftRecipe.java -@@ -15,6 +15,11 @@ public interface CraftRecipe extends Recipe { - void addToCraftingManager(); - - default Ingredient toNMS(RecipeChoice bukkit, boolean requireNotEmpty) { -+ // Paper start -+ return toIngredient(bukkit, requireNotEmpty); -+ } -+ static Ingredient toIngredient(RecipeChoice bukkit, boolean requireNotEmpty) { -+ // Paper end - Ingredient stack; - - if (bukkit == null) { diff --git a/patches/server/0669-More-Projectile-API.patch b/patches/server/0669-More-Projectile-API.patch new file mode 100644 index 000000000000..afff65d722f8 --- /dev/null +++ b/patches/server/0669-More-Projectile-API.patch @@ -0,0 +1,932 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Owen1212055 <23108066+Owen1212055@users.noreply.github.com> +Date: Tue, 22 Jun 2021 23:41:11 -0400 +Subject: [PATCH] More Projectile API + +== AT == +public net.minecraft.world.entity.projectile.FishingHook timeUntilLured +public net.minecraft.world.entity.projectile.FishingHook fishAngle +public net.minecraft.world.entity.projectile.ShulkerBullet targetDeltaX +public net.minecraft.world.entity.projectile.ShulkerBullet targetDeltaY +public net.minecraft.world.entity.projectile.ShulkerBullet targetDeltaZ +public net.minecraft.world.entity.projectile.ShulkerBullet currentMoveDirection +public net.minecraft.world.entity.projectile.ShulkerBullet flightSteps +public net.minecraft.world.entity.projectile.AbstractArrow soundEvent +public net.minecraft.world.entity.projectile.AbstractArrow setPickupItemStack(Lnet/minecraft/world/item/ItemStack;)V +public net.minecraft.world.entity.projectile.ThrownTrident dealtDamage +public net.minecraft.world.entity.projectile.Arrow NO_EFFECT_COLOR +public net.minecraft.world.entity.projectile.Projectile hasBeenShot +public net.minecraft.world.entity.projectile.Projectile leftOwner +public net.minecraft.world.entity.projectile.Projectile ownerUUID +public net.minecraft.world.entity.projectile.Projectile preOnHit(Lnet/minecraft/world/phys/HitResult;)V +public net.minecraft.world.entity.projectile.Projectile canHitEntity(Lnet/minecraft/world/entity/Entity;)Z +public net.minecraft.world.entity.projectile.FireworkRocketEntity getDefaultItem()Lnet/minecraft/world/item/ItemStack; +public net.minecraft.world.item.CrossbowItem FIREWORK_POWER + +Co-authored-by: Nassim Jahnke +Co-authored-by: SoSeDiK +Co-authored-by: MelnCat +Co-authored-by: Lulu13022002 <41980282+Lulu13022002@users.noreply.github.com> + +diff --git a/src/main/java/net/minecraft/world/entity/projectile/FishingHook.java b/src/main/java/net/minecraft/world/entity/projectile/FishingHook.java +index 536196a740f607adda2a5ae7f644981ac26bef98..1f95234c0a1457050574aa0f6c4b2a8c91b1f272 100644 +--- a/src/main/java/net/minecraft/world/entity/projectile/FishingHook.java ++++ b/src/main/java/net/minecraft/world/entity/projectile/FishingHook.java +@@ -419,13 +419,18 @@ public class FishingHook extends Projectile { + } + } else { + // CraftBukkit start - logic to modify fishing wait time +- this.timeUntilLured = Mth.nextInt(this.random, this.minWaitTime, this.maxWaitTime); +- this.timeUntilLured -= (this.applyLure) ? (this.lureSpeed >= this.maxWaitTime ? this.timeUntilLured - 1 : this.lureSpeed ) : 0; // Paper - Fix Lure infinite loop ++ this.resetTimeUntilLured(); // Paper - more projectile api - extract time until lured reset logic + // CraftBukkit end + } + } + + } ++ // Paper start - more projectile api - extract time until lured reset logic ++ public void resetTimeUntilLured() { ++ this.timeUntilLured = Mth.nextInt(this.random, this.minWaitTime, this.maxWaitTime); ++ this.timeUntilLured -= (this.applyLure) ? (this.lureSpeed >= this.maxWaitTime ? this.timeUntilLured - 1 : this.lureSpeed ) : 0; // Paper - Fix Lure infinite loop ++ } ++ // Paper end - more projectile api - extract time until lured reset logic + + public boolean calculateOpenWater(BlockPos pos) { + FishingHook.OpenWaterType entityfishinghook_waterposition = FishingHook.OpenWaterType.INVALID; +diff --git a/src/main/java/net/minecraft/world/entity/projectile/Projectile.java b/src/main/java/net/minecraft/world/entity/projectile/Projectile.java +index 0e4ba92d97998937ccb83ebd60aaad3fb68f7546..01684c903930b14a0df3a146176e2b476a374efb 100644 +--- a/src/main/java/net/minecraft/world/entity/projectile/Projectile.java ++++ b/src/main/java/net/minecraft/world/entity/projectile/Projectile.java +@@ -288,7 +288,7 @@ public abstract class Projectile extends Entity implements TraceableEntity { + } + + // CraftBukkit start - call projectile hit event +- protected ProjectileDeflection preHitTargetOrDeflectSelf(HitResult movingobjectposition) { ++ public ProjectileDeflection preHitTargetOrDeflectSelf(HitResult movingobjectposition) { // Paper - protected -> public + org.bukkit.event.entity.ProjectileHitEvent event = org.bukkit.craftbukkit.event.CraftEventFactory.callProjectileHitEvent(this, movingobjectposition); + this.hitCancelled = event != null && event.isCancelled(); + if (movingobjectposition.getType() == HitResult.Type.BLOCK || !this.hitCancelled) { +diff --git a/src/main/java/net/minecraft/world/entity/projectile/ThrownPotion.java b/src/main/java/net/minecraft/world/entity/projectile/ThrownPotion.java +index d6ac07d9d5ee0430a1d91b7084b378aac1d047e5..a486466040a646b8a5a5ff2430cdd25b95b7e20f 100644 +--- a/src/main/java/net/minecraft/world/entity/projectile/ThrownPotion.java ++++ b/src/main/java/net/minecraft/world/entity/projectile/ThrownPotion.java +@@ -103,8 +103,12 @@ public class ThrownPotion extends ThrowableItemProjectile { + @Override + protected void onHit(HitResult hitResult) { + super.onHit(hitResult); ++ // Paper start - More projectile API ++ this.splash(hitResult); ++ } ++ public void splash(@Nullable HitResult hitResult) { ++ // Paper end - More projectile API + Level world = this.level(); +- + if (world instanceof ServerLevel worldserver) { + ItemStack itemstack = this.getItem(); + PotionContents potioncontents = (PotionContents) itemstack.getOrDefault(DataComponents.POTION_CONTENTS, PotionContents.EMPTY); +@@ -116,7 +120,7 @@ public class ThrownPotion extends ThrowableItemProjectile { + if (this.isLingering()) { + showParticles = this.makeAreaOfEffectCloud(potioncontents, hitResult); // CraftBukkit - Pass MovingObjectPosition // Paper + } else { +- showParticles = this.applySplash(worldserver, potioncontents.getAllEffects(), hitResult.getType() == HitResult.Type.ENTITY ? ((EntityHitResult) hitResult).getEntity() : null, hitResult); // CraftBukkit - Pass MovingObjectPosition // Paper ++ showParticles = this.applySplash(worldserver, potioncontents.getAllEffects(), hitResult != null && hitResult.getType() == HitResult.Type.ENTITY ? ((EntityHitResult) hitResult).getEntity() : null, hitResult); // CraftBukkit - Pass MovingObjectPosition // Paper - More projectile API + } + } + +@@ -178,7 +182,7 @@ public class ThrownPotion extends ThrowableItemProjectile { + + } + +- private boolean applySplash(ServerLevel worldserver, Iterable iterable, @Nullable Entity entity, HitResult position) { // CraftBukkit - Pass MovingObjectPosition // Paper - Fix potions splash events ++ private boolean applySplash(ServerLevel worldserver, Iterable iterable, @Nullable Entity entity, @Nullable HitResult position) { // CraftBukkit - Pass MovingObjectPosition // Paper - Fix potions splash events & More projectile API + AABB axisalignedbb = this.getBoundingBox().inflate(4.0D, 2.0D, 4.0D); + List list = worldserver.getEntitiesOfClass(net.minecraft.world.entity.LivingEntity.class, axisalignedbb); + Map affected = new HashMap(); // CraftBukkit +@@ -256,7 +260,7 @@ public class ThrownPotion extends ThrowableItemProjectile { + + } + +- private boolean makeAreaOfEffectCloud(PotionContents potioncontents, HitResult position) { // CraftBukkit - Pass MovingObjectPosition ++ private boolean makeAreaOfEffectCloud(PotionContents potioncontents, @Nullable HitResult position) { // CraftBukkit - Pass MovingObjectPosition // Paper - More projectile API + AreaEffectCloud entityareaeffectcloud = new AreaEffectCloud(this.level(), this.getX(), this.getY(), this.getZ()); + Entity entity = this.getOwner(); + +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/AbstractProjectile.java b/src/main/java/org/bukkit/craftbukkit/entity/AbstractProjectile.java +index 91c2d0b40d3fca86938cd454e1415a4eea3df7c7..de4fb2654c7895cfd83ad694455ee56cb708c2f2 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/AbstractProjectile.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/AbstractProjectile.java +@@ -17,4 +17,65 @@ public abstract class AbstractProjectile extends CraftEntity implements Projecti + @Override + public void setBounce(boolean doesBounce) {} + ++ // Paper start - More projectile API ++ @Override ++ public boolean hasLeftShooter() { ++ return this.getHandle().leftOwner; ++ } ++ ++ @Override ++ public void setHasLeftShooter(boolean leftShooter) { ++ this.getHandle().leftOwner = leftShooter; ++ } ++ ++ @Override ++ public boolean hasBeenShot() { ++ return this.getHandle().hasBeenShot; ++ } ++ ++ @Override ++ public void setHasBeenShot(boolean beenShot) { ++ this.getHandle().hasBeenShot = beenShot; ++ } ++ ++ @Override ++ public boolean canHitEntity(org.bukkit.entity.Entity entity) { ++ return this.getHandle().canHitEntity(((CraftEntity) entity).getHandle()); ++ } ++ ++ @Override ++ public void hitEntity(org.bukkit.entity.Entity entity) { ++ this.getHandle().preHitTargetOrDeflectSelf(new net.minecraft.world.phys.EntityHitResult(((CraftEntity) entity).getHandle())); ++ } ++ ++ @Override ++ public void hitEntity(org.bukkit.entity.Entity entity, org.bukkit.util.Vector vector) { ++ this.getHandle().preHitTargetOrDeflectSelf(new net.minecraft.world.phys.EntityHitResult(((CraftEntity) entity).getHandle(), new net.minecraft.world.phys.Vec3(vector.getX(), vector.getY(), vector.getZ()))); ++ } ++ ++ @Override ++ public net.minecraft.world.entity.projectile.Projectile getHandle() { ++ return (net.minecraft.world.entity.projectile.Projectile) entity; ++ } ++ ++ @Override ++ public final org.bukkit.projectiles.ProjectileSource getShooter() { ++ return this.getHandle().projectileSource; ++ } ++ ++ @Override ++ public final void setShooter(org.bukkit.projectiles.ProjectileSource shooter) { ++ if (shooter instanceof CraftEntity craftEntity) { ++ this.getHandle().setOwner(craftEntity.getHandle()); ++ } else { ++ this.getHandle().setOwner(null); ++ } ++ this.getHandle().projectileSource = shooter; ++ } ++ ++ @Override ++ public java.util.UUID getOwnerUniqueId() { ++ return this.getHandle().ownerUUID; ++ } ++ // Paper end - More projectile API + } +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftAbstractArrow.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftAbstractArrow.java +index 0f85c1f991469b277bba8b40b087f7224b4b3a85..1f30109abd86b76af343eb5eb75ec3db83ef9417 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftAbstractArrow.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftAbstractArrow.java +@@ -59,20 +59,7 @@ public class CraftAbstractArrow extends AbstractProjectile implements AbstractAr + this.getHandle().setCritArrow(critical); + } + +- @Override +- public ProjectileSource getShooter() { +- return this.getHandle().projectileSource; +- } +- +- @Override +- public void setShooter(ProjectileSource shooter) { +- if (shooter instanceof Entity) { +- this.getHandle().setOwner(((CraftEntity) shooter).getHandle()); +- } else { +- this.getHandle().setOwner(null); +- } +- this.getHandle().projectileSource = shooter; +- } ++ // Paper - moved to AbstractProjectile + + @Override + public boolean isInBlock() { +@@ -133,6 +120,7 @@ public class CraftAbstractArrow extends AbstractProjectile implements AbstractAr + + @Override + public ItemStack getWeapon() { ++ if (this.getHandle().getWeaponItem() == null) return null; // Paper - fix NPE + return CraftItemStack.asBukkitCopy(this.getHandle().getWeaponItem()); + } + +@@ -152,4 +140,37 @@ public class CraftAbstractArrow extends AbstractProjectile implements AbstractAr + public String toString() { + return "CraftArrow"; + } ++ ++ // Paper start ++ @Override ++ public CraftItemStack getItemStack() { ++ return CraftItemStack.asCraftMirror(this.getHandle().getPickupItem()); ++ } ++ ++ @Override ++ public void setItemStack(final ItemStack stack) { ++ Preconditions.checkArgument(stack != null, "ItemStack cannot be null"); ++ this.getHandle().setPickupItemStack(CraftItemStack.asNMSCopy(stack)); ++ } ++ ++ @Override ++ public void setLifetimeTicks(int ticks) { ++ this.getHandle().life = ticks; ++ } ++ ++ @Override ++ public int getLifetimeTicks() { ++ return this.getHandle().life; ++ } ++ ++ @Override ++ public org.bukkit.Sound getHitSound() { ++ return org.bukkit.craftbukkit.CraftSound.minecraftToBukkit(this.getHandle().soundEvent); ++ } ++ ++ @Override ++ public void setHitSound(org.bukkit.Sound sound) { ++ this.getHandle().setSoundEvent(org.bukkit.craftbukkit.CraftSound.bukkitToMinecraft(sound)); ++ } ++ // Paper end + } +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftAreaEffectCloud.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftAreaEffectCloud.java +index 6591513bb62226b6f85fd2ef9f6ebe376f0f7362..f9c113dc018702159345240d6d0de85767afa0c3 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftAreaEffectCloud.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftAreaEffectCloud.java +@@ -125,7 +125,7 @@ public class CraftAreaEffectCloud extends CraftEntity implements AreaEffectCloud + + @Override + public Color getColor() { +- return Color.fromRGB(this.getHandle().potionContents.getColor()); ++ return Color.fromRGB(this.getHandle().potionContents.getColor() & 0x00FFFFFF); // Paper - skip alpha channel + } + + @Override +@@ -143,7 +143,7 @@ public class CraftAreaEffectCloud extends CraftEntity implements AreaEffectCloud + this.removeCustomEffect(effect.getType()); + } + this.getHandle().addEffect(CraftPotionUtil.fromBukkit(effect)); +- this.getHandle().updateColor(); ++ // this.getHandle().updateColor(); // Paper - already done above + return true; + } + +@@ -151,7 +151,7 @@ public class CraftAreaEffectCloud extends CraftEntity implements AreaEffectCloud + public void clearCustomEffects() { + PotionContents old = this.getHandle().potionContents; + this.getHandle().setPotionContents(new PotionContents(old.potion(), old.customColor(), List.of(), old.customName())); +- this.getHandle().updateColor(); ++ // this.getHandle().updateColor(); // Paper - already done above + } + + @Override +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftArrow.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftArrow.java +index 199d5836dc787cca54c6b653a4e67573f2f758a2..15d50a284cafc2eb59239ca00926836526f09e06 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftArrow.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftArrow.java +@@ -43,7 +43,7 @@ public class CraftArrow extends CraftAbstractArrow implements Arrow { + this.removeCustomEffect(effect.getType()); + } + this.getHandle().addEffect(CraftPotionUtil.fromBukkit(effect)); +- this.getHandle().updateColor(); ++ // this.getHandle().updateColor(); // Paper - already done above + return true; + } + +@@ -51,7 +51,7 @@ public class CraftArrow extends CraftAbstractArrow implements Arrow { + public void clearCustomEffects() { + PotionContents old = this.getHandle().getPotionContents(); + this.getHandle().setPotionContents(new PotionContents(old.potion(), old.customColor(), List.of(), old.customName())); +- this.getHandle().updateColor(); ++ // this.getHandle().updateColor(); // Paper - already done above + } + + @Override +@@ -117,16 +117,17 @@ public class CraftArrow extends CraftAbstractArrow implements Arrow { + + @Override + public void setColor(Color color) { +- int colorRGB = (color == null) ? -1 : color.asRGB(); ++ int colorRGB = (color == null) ? net.minecraft.world.entity.projectile.Arrow.NO_EFFECT_COLOR : color.asARGB(); // Paper + PotionContents old = this.getHandle().getPotionContents(); + this.getHandle().setPotionContents(new PotionContents(old.potion(), Optional.of(colorRGB), old.customEffects(), old.customName())); + } + + @Override + public Color getColor() { +- if (this.getHandle().getColor() <= -1) { ++ int color = this.getHandle().getColor(); // Paper ++ if (color == net.minecraft.world.entity.projectile.Arrow.NO_EFFECT_COLOR) { // Paper + return null; + } +- return Color.fromRGB(this.getHandle().getColor()); ++ return Color.fromARGB(color); // Paper + } + } +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftEntityTypes.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftEntityTypes.java +index b79e72a77178f755957ef391b6444a357bdefbd0..39ea25d2145acaa7c7b458800adc674a3e73e7c1 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftEntityTypes.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftEntityTypes.java +@@ -442,7 +442,7 @@ public final class CraftEntityTypes { + BlockPos pos = BlockPos.containing(spawnData.x(), spawnData.y(), spawnData.z()); + return new FallingBlockEntity(spawnData.minecraftWorld(), spawnData.x(), spawnData.y(), spawnData.z(), spawnData.world().getBlockState(pos)); // Paper - create falling block entities correctly + })); +- register(new EntityTypeData<>(EntityType.FIREWORK_ROCKET, Firework.class, CraftFirework::new, spawnData -> new FireworkRocketEntity(spawnData.minecraftWorld(), spawnData.x(), spawnData.y(), spawnData.z(), net.minecraft.world.item.ItemStack.EMPTY))); ++ register(new EntityTypeData<>(EntityType.FIREWORK_ROCKET, Firework.class, CraftFirework::new, spawnData -> new FireworkRocketEntity(spawnData.minecraftWorld(), spawnData.x(), spawnData.y(), spawnData.z(), FireworkRocketEntity.getDefaultItem()))); // Paper - pass correct default to rocket for data storage + register(new EntityTypeData<>(EntityType.EVOKER_FANGS, EvokerFangs.class, CraftEvokerFangs::new, spawnData -> new net.minecraft.world.entity.projectile.EvokerFangs(spawnData.minecraftWorld(), spawnData.x(), spawnData.y(), spawnData.z(), (float) Math.toRadians(spawnData.yaw()), 0, null))); + register(new EntityTypeData<>(EntityType.COMMAND_BLOCK_MINECART, CommandMinecart.class, CraftMinecartCommand::new, createMinecart(net.minecraft.world.entity.EntityType.COMMAND_BLOCK_MINECART))); + register(new EntityTypeData<>(EntityType.MINECART, RideableMinecart.class, CraftMinecartRideable::new, createMinecart(net.minecraft.world.entity.EntityType.MINECART))); +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftFireball.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftFireball.java +index 1b084d63bdbb24dad45d28eed1693eb6e26e24dc..43d7bea201a52cfeacf60c75caa28dfd2c4ff164 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftFireball.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftFireball.java +@@ -34,20 +34,7 @@ public class CraftFireball extends AbstractProjectile implements Fireball { + this.getHandle().bukkitYield = yield; + } + +- @Override +- public ProjectileSource getShooter() { +- return this.getHandle().projectileSource; +- } +- +- @Override +- public void setShooter(ProjectileSource shooter) { +- if (shooter instanceof CraftLivingEntity) { +- this.getHandle().setOwner(((CraftLivingEntity) shooter).getHandle()); +- } else { +- this.getHandle().setOwner(null); +- } +- this.getHandle().projectileSource = shooter; +- } ++ // Paper - moved to AbstractProjectile + + @Override + public Vector getDirection() { +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftFirework.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftFirework.java +index c9e15a9d82dee935293b2e7e233f5b9b2d822448..2d54cf6f3d9696c55335f0a2057025e2034d4e13 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftFirework.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftFirework.java +@@ -15,24 +15,26 @@ import org.bukkit.inventory.meta.FireworkMeta; + public class CraftFirework extends CraftProjectile implements Firework { + + private final Random random = new Random(); +- private final CraftItemStack item; ++ //private CraftItemStack item; // Paper - Remove usage, not accurate representation of current item. + + public CraftFirework(CraftServer server, FireworkRocketEntity entity) { + super(server, entity); + +- ItemStack item = this.getHandle().getEntityData().get(FireworkRocketEntity.DATA_ID_FIREWORKS_ITEM); +- +- if (item.isEmpty()) { +- item = new ItemStack(Items.FIREWORK_ROCKET); +- this.getHandle().getEntityData().set(FireworkRocketEntity.DATA_ID_FIREWORKS_ITEM, item); +- } +- +- this.item = CraftItemStack.asCraftMirror(item); +- +- // Ensure the item is a firework... +- if (this.item.getType() != Material.FIREWORK_ROCKET) { +- this.item.setType(Material.FIREWORK_ROCKET); +- } ++ // Paper start - Expose firework item directly ++// ItemStack item = this.getHandle().getEntityData().get(FireworkRocketEntity.DATA_ID_FIREWORKS_ITEM); ++// ++// if (item.isEmpty()) { ++// item = new ItemStack(Items.FIREWORK_ROCKET); ++// this.getHandle().getEntityData().set(FireworkRocketEntity.DATA_ID_FIREWORKS_ITEM, item); ++// } ++// ++// this.item = CraftItemStack.asCraftMirror(item); ++// ++// // Ensure the item is a firework... ++// if (this.item.getType() != Material.FIREWORK_ROCKET) { ++// this.item.setType(Material.FIREWORK_ROCKET); ++// } ++ // Paper end - Expose firework item directly + } + + @Override +@@ -47,12 +49,12 @@ public class CraftFirework extends CraftProjectile implements Firework { + + @Override + public FireworkMeta getFireworkMeta() { +- return (FireworkMeta) this.item.getItemMeta(); ++ return (FireworkMeta) CraftItemStack.getItemMeta(this.getHandle().getEntityData().get(FireworkRocketEntity.DATA_ID_FIREWORKS_ITEM), org.bukkit.inventory.ItemType.FIREWORK_ROCKET); // Paper - Expose firework item directly + } + + @Override + public void setFireworkMeta(FireworkMeta meta) { +- this.item.setItemMeta(meta); ++ applyFireworkEffect(meta); // Paper - Expose firework item directly + + // Copied from EntityFireworks constructor, update firework lifetime/power + this.getHandle().lifetime = 10 * (1 + meta.getPower()) + this.random.nextInt(6) + this.random.nextInt(7); +@@ -136,4 +138,46 @@ public class CraftFirework extends CraftProjectile implements Firework { + return getHandle().spawningEntity; + } + // Paper end ++ // Paper start - Expose firework item directly + manually setting flight ++ @Override ++ public org.bukkit.inventory.ItemStack getItem() { ++ return CraftItemStack.asBukkitCopy(this.getHandle().getItem()); ++ } ++ ++ @Override ++ public void setItem(org.bukkit.inventory.ItemStack itemStack) { ++ FireworkMeta meta = getFireworkMeta(); ++ ItemStack nmsItem = itemStack == null ? FireworkRocketEntity.getDefaultItem() : CraftItemStack.asNMSCopy(itemStack); ++ this.getHandle().getEntityData().set(FireworkRocketEntity.DATA_ID_FIREWORKS_ITEM, nmsItem); ++ ++ applyFireworkEffect(meta); ++ } ++ ++ @Override ++ public int getTicksFlown() { ++ return this.getHandle().life; ++ } ++ ++ @Override ++ public void setTicksFlown(int ticks) { ++ this.getHandle().life = ticks; ++ } ++ ++ @Override ++ public int getTicksToDetonate() { ++ return this.getHandle().lifetime; ++ } ++ ++ @Override ++ public void setTicksToDetonate(int ticks) { ++ this.getHandle().lifetime = ticks; ++ } ++ ++ void applyFireworkEffect(FireworkMeta meta) { ++ ItemStack item = this.getHandle().getItem(); ++ CraftItemStack.applyMetaToItem(item, meta); ++ ++ this.getHandle().getEntityData().set(FireworkRocketEntity.DATA_ID_FIREWORKS_ITEM, item); ++ } ++ // Paper end - Expose firework item directly + manually setting flight + } +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftFishHook.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftFishHook.java +index 6e2f91423371ead9890095cf4b1e2299c4dcba28..9d8f4b7176e60180565e3134a14ecf19060f2621 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftFishHook.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftFishHook.java +@@ -196,4 +196,42 @@ public class CraftFishHook extends CraftProjectile implements FishHook { + public HookState getState() { + return HookState.values()[this.getHandle().currentState.ordinal()]; + } ++ // Paper start - More FishHook API ++ @Override ++ public int getWaitTime() { ++ return this.getHandle().timeUntilLured; ++ } ++ ++ @Override ++ public void setWaitTime(int ticks) { ++ this.getHandle().timeUntilLured = ticks; ++ } ++ ++ @Override ++ public int getTimeUntilBite() { ++ return this.getHandle().timeUntilHooked; ++ } ++ ++ @Override ++ public void setTimeUntilBite(final int ticks) { ++ com.google.common.base.Preconditions.checkArgument(ticks >= 1, "Cannot set time until bite to less than 1 (%s<1)", ticks); ++ final FishingHook hook = this.getHandle(); ++ ++ // Reset the fish angle hook only when this call "enters" the fish into the lure stage. ++ final boolean alreadyInLuringPhase = hook.timeUntilHooked > 0 && hook.timeUntilLured <= 0; ++ if (!alreadyInLuringPhase) { ++ hook.fishAngle = net.minecraft.util.Mth.nextFloat(hook.random, hook.minLureAngle, hook.maxLureAngle); ++ hook.timeUntilLured = 0; ++ } ++ ++ hook.timeUntilHooked = ticks; ++ } ++ ++ @Override ++ public void resetFishingState() { ++ final FishingHook hook = this.getHandle(); ++ hook.resetTimeUntilLured(); ++ hook.timeUntilHooked = 0; // Reset time until hooked, will be repopulated once lured time is ticked down. ++ } ++ // Paper end + } +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftLivingEntity.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftLivingEntity.java +index e148239d4930e5cbb000beed4de386f992f28d88..14b4c3835388d957653ba34444968bb718ce7f68 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftLivingEntity.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftLivingEntity.java +@@ -579,8 +579,15 @@ public class CraftLivingEntity extends CraftEntity implements LivingEntity { + } + + @Override +- @SuppressWarnings("unchecked") + public T launchProjectile(Class projectile, Vector velocity) { ++ // Paper start - launchProjectile consumer ++ return this.launchProjectile(projectile, velocity, null); ++ } ++ ++ @Override ++ @SuppressWarnings("unchecked") ++ public T launchProjectile(Class projectile, Vector velocity, java.util.function.Consumer function) { ++ // Paper end - launchProjectile consumer + Preconditions.checkState(!this.getHandle().generation, "Cannot launch projectile during world generation"); + + net.minecraft.world.level.Level world = ((CraftWorld) this.getWorld()).getHandle(); +@@ -606,7 +613,7 @@ public class CraftLivingEntity extends CraftEntity implements LivingEntity { + } else { + launch = new net.minecraft.world.entity.projectile.Arrow(world, this.getHandle(), new net.minecraft.world.item.ItemStack(net.minecraft.world.item.Items.ARROW), null); + } +- ((net.minecraft.world.entity.projectile.AbstractArrow) launch).shootFromRotation(this.getHandle(), this.getHandle().getXRot(), this.getHandle().getYRot(), 0.0F, 3.0F, 1.0F); // ItemBow ++ ((net.minecraft.world.entity.projectile.AbstractArrow) launch).shootFromRotation(this.getHandle(), this.getHandle().getXRot(), this.getHandle().getYRot(), 0.0F, Trident.class.isAssignableFrom(projectile) ? net.minecraft.world.item.TridentItem.SHOOT_POWER : 3.0F, 1.0F); // ItemBow // Paper - see TridentItem + } else if (ThrownPotion.class.isAssignableFrom(projectile)) { + if (LingeringPotion.class.isAssignableFrom(projectile)) { + launch = new net.minecraft.world.entity.projectile.ThrownPotion(world, this.getHandle(), new net.minecraft.world.item.ItemStack(Items.LINGERING_POTION)); +@@ -663,8 +670,26 @@ public class CraftLivingEntity extends CraftEntity implements LivingEntity { + } else if (Firework.class.isAssignableFrom(projectile)) { + Location location = this.getEyeLocation(); + +- launch = new FireworkRocketEntity(world, net.minecraft.world.item.ItemStack.EMPTY, this.getHandle()); +- launch.moveTo(location.getX(), location.getY(), location.getZ(), location.getYaw(), location.getPitch()); ++ // Paper start - see CrossbowItem ++ launch = new FireworkRocketEntity(world, FireworkRocketEntity.getDefaultItem(), this.getHandle(), location.getX(), location.getY() - 0.15F, location.getZ(), true); // Paper - pass correct default to rocket for data storage & see CrossbowItem for regular launch without elytra boost ++ ++ // Lifted from net.minecraft.world.item.ProjectileWeaponItem.shoot ++ float f2 = /* net.minecraft.world.item.enchantment.EnchantmentHelper.processProjectileSpread((ServerLevel) world, new net.minecraft.world.item.ItemStack(net.minecraft.world.item.Items.CROSSBOW), this.getHandle(), 0.0F); */ 0; // Just shortcut this to 0, no need to do any calculations on a non existing stack ++ int projectileSize = 1; ++ int i = 0; ++ ++ float f3 = projectileSize == 1 ? 0.0F : 2.0F * f2 / (float) (projectileSize - 1); ++ float f4 = (float) ((projectileSize - 1) % 2) * f3 / 2.0F; ++ float f5 = 1.0F; ++ float yaw = f4 + f5 * (float) ((i + 1) / 2) * f3; ++ ++ // Lifted from net.minecraft.world.item.CrossbowItem.shootProjectile ++ Vec3 vec3 = this.getHandle().getUpVector(1.0F); ++ org.joml.Quaternionf quaternionf = new org.joml.Quaternionf().setAngleAxis((double)(yaw * (float) (Math.PI / 180.0)), vec3.x, vec3.y, vec3.z); ++ Vec3 vec32 = this.getHandle().getViewVector(1.0F); ++ org.joml.Vector3f vector3f = vec32.toVector3f().rotate(quaternionf); ++ ((FireworkRocketEntity) launch).shoot((double)vector3f.x(), (double)vector3f.y(), (double)vector3f.z(), net.minecraft.world.item.CrossbowItem.FIREWORK_POWER, 1.0F); ++ // Paper end + } + + Preconditions.checkArgument(launch != null, "Projectile (%s) not supported", projectile.getName()); +@@ -672,6 +697,11 @@ public class CraftLivingEntity extends CraftEntity implements LivingEntity { + if (velocity != null) { + ((T) launch.getBukkitEntity()).setVelocity(velocity); + } ++ // Paper start - launchProjectile consumer ++ if (function != null) { ++ function.accept((T) launch.getBukkitEntity()); ++ } ++ // Paper end - launchProjectile consumer + + world.addFreshEntity(launch); + return (T) launch.getBukkitEntity(); +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftLlamaSpit.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftLlamaSpit.java +index 70cbc6c668c60e9d608ca7013b72f9b916c05c2d..47633f05b4fab1dcabc2117e7645fe6d6949622a 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftLlamaSpit.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftLlamaSpit.java +@@ -20,13 +20,5 @@ public class CraftLlamaSpit extends AbstractProjectile implements LlamaSpit { + return "CraftLlamaSpit"; + } + +- @Override +- public ProjectileSource getShooter() { +- return (this.getHandle().getOwner() != null) ? (ProjectileSource) this.getHandle().getOwner().getBukkitEntity() : null; +- } +- +- @Override +- public void setShooter(ProjectileSource source) { +- this.getHandle().setOwner((source != null) ? ((CraftLivingEntity) source).getHandle() : null); +- } ++ // Paper - moved to AbstractProjectile + } +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftProjectile.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftProjectile.java +index 696fdfa723aa896a67946f862d7c6890f7f7ab17..4f1fa7dec78970bdfc184d3c1f1632dc9d75a574 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftProjectile.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftProjectile.java +@@ -10,20 +10,7 @@ public abstract class CraftProjectile extends AbstractProjectile implements Proj + super(server, entity); + } + +- @Override +- public ProjectileSource getShooter() { +- return this.getHandle().projectileSource; +- } +- +- @Override +- public void setShooter(ProjectileSource shooter) { +- if (shooter instanceof CraftLivingEntity) { +- this.getHandle().setOwner((LivingEntity) ((CraftLivingEntity) shooter).entity); +- } else { +- this.getHandle().setOwner(null); +- } +- this.getHandle().projectileSource = shooter; +- } ++ // Paper - moved to AbstractProjectile + + @Override + public net.minecraft.world.entity.projectile.Projectile getHandle() { +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftShulkerBullet.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftShulkerBullet.java +index d685d09cae5f862c0004f148298c800736d2139e..b3797a43eeee11cb7ae0774d61bd5f195d0aa3ad 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftShulkerBullet.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftShulkerBullet.java +@@ -12,31 +12,56 @@ public class CraftShulkerBullet extends AbstractProjectile implements ShulkerBul + super(server, entity); + } + ++ // Paper - moved to AbstractProjectile ++ ++ @Override ++ public org.bukkit.entity.Entity getTarget() { ++ return this.getHandle().getTarget() != null ? this.getHandle().getTarget().getBukkitEntity() : null; ++ } ++ + @Override +- public ProjectileSource getShooter() { +- return this.getHandle().projectileSource; ++ public void setTarget(org.bukkit.entity.Entity target) { ++ Preconditions.checkState(!this.getHandle().generation, "Cannot set target during world generation"); ++ ++ this.getHandle().setTarget(target == null ? null : ((CraftEntity) target).getHandle()); + } + + @Override +- public void setShooter(ProjectileSource shooter) { +- if (shooter instanceof Entity) { +- this.getHandle().setOwner(((CraftEntity) shooter).getHandle()); +- } else { +- this.getHandle().setOwner(null); ++ public org.bukkit.util.Vector getTargetDelta() { ++ net.minecraft.world.entity.projectile.ShulkerBullet bullet = this.getHandle(); ++ return new org.bukkit.util.Vector(bullet.targetDeltaX, bullet.targetDeltaY, bullet.targetDeltaZ); ++ } ++ ++ @Override ++ public void setTargetDelta(org.bukkit.util.Vector vector) { ++ net.minecraft.world.entity.projectile.ShulkerBullet bullet = this.getHandle(); ++ bullet.targetDeltaX = vector.getX(); ++ bullet.targetDeltaY = vector.getY(); ++ bullet.targetDeltaZ = vector.getZ(); ++ } ++ ++ @Override ++ public org.bukkit.block.BlockFace getCurrentMovementDirection() { ++ net.minecraft.core.Direction dir = this.getHandle().currentMoveDirection; ++ if (dir == null) { ++ return null; // random dir + } +- this.getHandle().projectileSource = shooter; ++ return org.bukkit.craftbukkit.block.CraftBlock.notchToBlockFace(dir); + } + + @Override +- public org.bukkit.entity.Entity getTarget() { +- return this.getHandle().getTarget() != null ? this.getHandle().getTarget().getBukkitEntity() : null; ++ public void setCurrentMovementDirection(org.bukkit.block.BlockFace movementDirection) { ++ this.getHandle().currentMoveDirection = org.bukkit.craftbukkit.block.CraftBlock.blockFaceToNotch(movementDirection); + } + + @Override +- public void setTarget(org.bukkit.entity.Entity target) { +- Preconditions.checkState(!this.getHandle().generation, "Cannot set target during world generation"); ++ public int getFlightSteps() { ++ return this.getHandle().flightSteps; ++ } + +- this.getHandle().setTarget(target == null ? null : ((CraftEntity) target).getHandle()); ++ @Override ++ public void setFlightSteps(int steps) { ++ this.getHandle().flightSteps = steps; + } + + @Override +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftThrownPotion.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftThrownPotion.java +index d67a80161b3e7c1fe02a6ed9d341c00dc7c2847a..f6fa6f1ac50b757dd3bc9a8dee9f6085446182c8 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftThrownPotion.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftThrownPotion.java +@@ -36,11 +36,31 @@ public class CraftThrownPotion extends CraftThrowableProjectile implements Throw + @Override + public void setItem(ItemStack item) { + Preconditions.checkArgument(item != null, "ItemStack cannot be null"); +- Preconditions.checkArgument(item.getType() == Material.LINGERING_POTION || item.getType() == Material.SPLASH_POTION, "ItemStack material must be Material.LINGERING_POTION or Material.SPLASH_POTION but was Material.%s", item.getType()); ++ // Preconditions.checkArgument(item.getType() == Material.LINGERING_POTION || item.getType() == Material.SPLASH_POTION, "ItemStack material must be Material.LINGERING_POTION or Material.SPLASH_POTION but was Material.%s", item.getType()); // Paper - Projectile API ++ org.bukkit.inventory.meta.PotionMeta meta = (item.getType() == Material.LINGERING_POTION || item.getType() == Material.SPLASH_POTION) ? null : this.getPotionMeta(); // Paper - Projectile API + + this.getHandle().setItem(CraftItemStack.asNMSCopy(item)); ++ if (meta != null) this.setPotionMeta(meta); // Paper - Projectile API + } + ++ // Paper start - Projectile API ++ @Override ++ public org.bukkit.inventory.meta.PotionMeta getPotionMeta() { ++ return (org.bukkit.inventory.meta.PotionMeta) CraftItemStack.getItemMeta(this.getHandle().getItem(), org.bukkit.inventory.ItemType.SPLASH_POTION); ++ } ++ ++ @Override ++ public void setPotionMeta(org.bukkit.inventory.meta.PotionMeta meta) { ++ net.minecraft.world.item.ItemStack item = this.getHandle().getItem(); ++ CraftItemStack.applyMetaToItem(item, meta); ++ this.getHandle().setItem(item); // Reset item ++ } ++ ++ @Override ++ public void splash() { ++ this.getHandle().splash(null); ++ } ++ // Paper end + @Override + public net.minecraft.world.entity.projectile.ThrownPotion getHandle() { + return (net.minecraft.world.entity.projectile.ThrownPotion) this.entity; +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftTrident.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftTrident.java +index e374b9f40eddca13b30855d25a2030f8df98138f..4fc893378fb0568ddcffc7593d66df6bfe23f659 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftTrident.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftTrident.java +@@ -53,5 +53,15 @@ public class CraftTrident extends CraftAbstractArrow implements Trident { + com.google.common.base.Preconditions.checkArgument(loyaltyLevel >= 0 && loyaltyLevel <= 127, "The loyalty level has to be between 0 and 127"); + this.getHandle().setLoyalty((byte) loyaltyLevel); + } ++ ++ @Override ++ public boolean hasDealtDamage() { ++ return this.getHandle().dealtDamage; ++ } ++ ++ @Override ++ public void setHasDealtDamage(boolean hasDealtDamage) { ++ this.getHandle().dealtDamage = hasDealtDamage; ++ } + // Paper end + } +diff --git a/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java b/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java +index a9ba62a9d605234d993b6e5330889795099af391..e0418c0c36aa99ea43dbf39fa176ddf8855e0568 100644 +--- a/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java ++++ b/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java +@@ -841,19 +841,19 @@ public class CraftEventFactory { + /** + * PotionSplashEvent + */ +- public static PotionSplashEvent callPotionSplashEvent(net.minecraft.world.entity.projectile.ThrownPotion potion, HitResult position, Map affectedEntities) { ++ public static PotionSplashEvent callPotionSplashEvent(net.minecraft.world.entity.projectile.ThrownPotion potion, @Nullable HitResult position, Map affectedEntities) { // Paper - nullable hitResult + ThrownPotion thrownPotion = (ThrownPotion) potion.getBukkitEntity(); + + Block hitBlock = null; + BlockFace hitFace = null; +- if (position.getType() == HitResult.Type.BLOCK) { ++ if (position != null && position.getType() == HitResult.Type.BLOCK) { // Paper - nullable hitResult + BlockHitResult positionBlock = (BlockHitResult) position; + hitBlock = CraftBlock.at(potion.level(), positionBlock.getBlockPos()); + hitFace = CraftBlock.notchToBlockFace(positionBlock.getDirection()); + } + + org.bukkit.entity.Entity hitEntity = null; +- if (position.getType() == HitResult.Type.ENTITY) { ++ if (position != null && position.getType() == HitResult.Type.ENTITY) { // Paper - nullable hitResult + hitEntity = ((EntityHitResult) position).getEntity().getBukkitEntity(); + } + +@@ -862,20 +862,20 @@ public class CraftEventFactory { + return event; + } + +- public static LingeringPotionSplashEvent callLingeringPotionSplashEvent(net.minecraft.world.entity.projectile.ThrownPotion potion, HitResult position, net.minecraft.world.entity.AreaEffectCloud cloud) { ++ public static LingeringPotionSplashEvent callLingeringPotionSplashEvent(net.minecraft.world.entity.projectile.ThrownPotion potion, @Nullable HitResult position, net.minecraft.world.entity.AreaEffectCloud cloud) { // Paper - nullable hitResult + ThrownPotion thrownPotion = (ThrownPotion) potion.getBukkitEntity(); + AreaEffectCloud effectCloud = (AreaEffectCloud) cloud.getBukkitEntity(); + + Block hitBlock = null; + BlockFace hitFace = null; +- if (position.getType() == HitResult.Type.BLOCK) { ++ if (position != null && position.getType() == HitResult.Type.BLOCK) { // Paper + BlockHitResult positionBlock = (BlockHitResult) position; + hitBlock = CraftBlock.at(potion.level(), positionBlock.getBlockPos()); + hitFace = CraftBlock.notchToBlockFace(positionBlock.getDirection()); + } + + org.bukkit.entity.Entity hitEntity = null; +- if (position.getType() == HitResult.Type.ENTITY) { ++ if (position != null && position.getType() == HitResult.Type.ENTITY) { // Paper + hitEntity = ((EntityHitResult) position).getEntity().getBukkitEntity(); + } + +diff --git a/src/main/java/org/bukkit/craftbukkit/inventory/CraftItemStack.java b/src/main/java/org/bukkit/craftbukkit/inventory/CraftItemStack.java +index 60062ea5b18b95a14c459f2f3a0743c1e1ac0f12..502be683e8b04a9966043c9bee9d9fe793b12ef5 100644 +--- a/src/main/java/org/bukkit/craftbukkit/inventory/CraftItemStack.java ++++ b/src/main/java/org/bukkit/craftbukkit/inventory/CraftItemStack.java +@@ -341,12 +341,23 @@ public final class CraftItemStack extends ItemStack { + public ItemMeta getItemMeta() { + return CraftItemStack.getItemMeta(this.handle); + } ++ // Paper start ++ public static void applyMetaToItem(net.minecraft.world.item.ItemStack itemStack, ItemMeta itemMeta) { ++ final CraftMetaItem.Applicator tag = new CraftMetaItem.Applicator(); ++ ((CraftMetaItem) itemMeta).applyToItem(tag); ++ itemStack.applyComponents(tag.build()); ++ } + + public static ItemMeta getItemMeta(net.minecraft.world.item.ItemStack item) { ++ return getItemMeta(item, null); ++ } ++ public static ItemMeta getItemMeta(net.minecraft.world.item.ItemStack item, org.bukkit.inventory.ItemType metaForType) { ++ // Paper end + if (!CraftItemStack.hasItemMeta(item)) { + return CraftItemFactory.instance().getItemMeta(CraftItemStack.getType(item)); + } + ++ if (metaForType != null) { return ((CraftItemType) metaForType).getItemMeta(item); } // Paper + return ((CraftItemType) CraftItemType.minecraftToBukkitNew(item.getItem())).getItemMeta(item); + } + +diff --git a/src/main/java/org/bukkit/craftbukkit/projectiles/CraftBlockProjectileSource.java b/src/main/java/org/bukkit/craftbukkit/projectiles/CraftBlockProjectileSource.java +index 642f5bf75661eb485558bc227f668e84416f3b5f..76fd4d27730d9139caa67099a6757ea33d681be9 100644 +--- a/src/main/java/org/bukkit/craftbukkit/projectiles/CraftBlockProjectileSource.java ++++ b/src/main/java/org/bukkit/craftbukkit/projectiles/CraftBlockProjectileSource.java +@@ -56,7 +56,15 @@ public class CraftBlockProjectileSource implements BlockProjectileSource { + + @Override + public T launchProjectile(Class projectile, Vector velocity) { ++ // Paper start - launchProjectile consumer ++ return this.launchProjectile(projectile, velocity, null); ++ } ++ ++ @Override ++ public T launchProjectile(Class projectile, Vector velocity, java.util.function.Consumer function) { ++ // Paper end - launchProjectile consumer + Preconditions.checkArgument(this.getBlock().getType() == Material.DISPENSER, "Block is no longer dispenser"); ++ + // Copied from BlockDispenser.dispense() + BlockSource sourceblock = new BlockSource((ServerLevel) this.dispenserBlock.getLevel(), this.dispenserBlock.getBlockPos(), this.dispenserBlock.getBlockState(), this.dispenserBlock); + // Copied from DispenseBehaviorProjectile +@@ -68,7 +76,7 @@ public class CraftBlockProjectileSource implements BlockProjectileSource { + item = Items.SNOWBALL; + } else if (Egg.class.isAssignableFrom(projectile)) { + item = Items.EGG; +- } else if (EnderPearl.class.isAssignableFrom(projectile)) { ++ } else if (false && EnderPearl.class.isAssignableFrom(projectile)) { // Paper - more projectile API - disallow enderpearl, it is not a projectile item + item = Items.ENDER_PEARL; + } else if (ThrownExpBottle.class.isAssignableFrom(projectile)) { + item = Items.EXPERIENCE_BOTTLE; +@@ -83,20 +91,20 @@ public class CraftBlockProjectileSource implements BlockProjectileSource { + item = Items.TIPPED_ARROW; + } else if (SpectralArrow.class.isAssignableFrom(projectile)) { + item = Items.SPECTRAL_ARROW; +- } else { ++ } else if (org.bukkit.entity.Arrow.class.isAssignableFrom(projectile)) { // Paper - more projectile API - disallow trident + item = Items.ARROW; + } + } else if (Fireball.class.isAssignableFrom(projectile)) { +- if (AbstractWindCharge.class.isAssignableFrom(projectile)) { ++ if (org.bukkit.entity.WindCharge.class.isAssignableFrom(projectile)) { // Paper - more projectile API - only allow wind charge not breeze wind charge + item = Items.WIND_CHARGE; +- } else { ++ } else if (org.bukkit.entity.SmallFireball.class.isAssignableFrom(projectile)) { // Paper - more projectile API - only allow firing fire charges. + item = Items.FIRE_CHARGE; + } + } else if (Firework.class.isAssignableFrom(projectile)) { + item = Items.FIREWORK_ROCKET; + } + +- Preconditions.checkArgument(item instanceof ProjectileItem, "Projectile not supported"); ++ Preconditions.checkArgument(item instanceof ProjectileItem, "Projectile '%s' not supported", projectile.getSimpleName()); // Paper - more projectile API - include simple name in exception + + ItemStack itemstack = new ItemStack(item); + ProjectileItem projectileItem = (ProjectileItem) item; +@@ -105,7 +113,7 @@ public class CraftBlockProjectileSource implements BlockProjectileSource { + Position iposition = dispenseConfig.positionFunction().getDispensePosition(sourceblock, enumdirection); + net.minecraft.world.entity.projectile.Projectile launch = projectileItem.asProjectile(world, iposition, itemstack, enumdirection); + +- if (Fireball.class.isAssignableFrom(projectile)) { ++ if (false && Fireball.class.isAssignableFrom(projectile)) { // Paper - more project API - dispensers cannot launch anything but fire charges. + AbstractHurtingProjectile customFireball = null; + if (WitherSkull.class.isAssignableFrom(projectile)) { + launch = customFireball = EntityType.WITHER_SKULL.create(world, EntitySpawnReason.TRIGGERED); +@@ -130,7 +138,7 @@ public class CraftBlockProjectileSource implements BlockProjectileSource { + } + } + +- if (launch instanceof net.minecraft.world.entity.projectile.AbstractArrow arrow) { ++ if (false && launch instanceof net.minecraft.world.entity.projectile.AbstractArrow arrow) { // Paper - more projectile API - this is set by the respective ArrowItem when constructing the projectile + arrow.pickup = net.minecraft.world.entity.projectile.AbstractArrow.Pickup.ALLOWED; + } + launch.projectileSource = this; +@@ -139,6 +147,11 @@ public class CraftBlockProjectileSource implements BlockProjectileSource { + if (velocity != null) { + ((T) launch.getBukkitEntity()).setVelocity(velocity); + } ++ // Paper start ++ if (function != null) { ++ function.accept((T) launch.getBukkitEntity()); ++ } ++ // Paper end + + world.addFreshEntity(launch); + return (T) launch.getBukkitEntity(); diff --git a/patches/server/0670-Fix-swamp-hut-cat-generation-deadlock.patch b/patches/server/0670-Fix-swamp-hut-cat-generation-deadlock.patch new file mode 100644 index 000000000000..24bfbc0dc7c0 --- /dev/null +++ b/patches/server/0670-Fix-swamp-hut-cat-generation-deadlock.patch @@ -0,0 +1,64 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Spottedleaf +Date: Sat, 12 Mar 2022 06:31:13 -0800 +Subject: [PATCH] Fix swamp hut cat generation deadlock + +The worldgen thread will attempt to get structure references +via the world's getChunkAt method, which is fine if the gen is +not cancelled - but if the chunk was unloaded, the call will block +indefinitely. Instead of using the world state, we use the already +supplied ServerLevelAccessor which will always have the chunk available. + +diff --git a/src/main/java/net/minecraft/world/entity/animal/Cat.java b/src/main/java/net/minecraft/world/entity/animal/Cat.java +index f3a991aa878f3b29fdc525ecdde6766efb5e129c..5a86530c65d7d83e4608600a04ffd931bf59dc4a 100644 +--- a/src/main/java/net/minecraft/world/entity/animal/Cat.java ++++ b/src/main/java/net/minecraft/world/entity/animal/Cat.java +@@ -366,7 +366,7 @@ public class Cat extends TamableAnimal implements VariantHolder startsForStructure(ChunkPos pos, Predicate predicate) { +- Map map = this.level.getChunk(pos.x, pos.z, ChunkStatus.STRUCTURE_REFERENCES).getAllReferences(); ++ // Paper start - Fix swamp hut cat generation deadlock ++ return this.startsForStructure(pos, predicate, null); ++ } ++ public List startsForStructure(ChunkPos pos, Predicate predicate, @Nullable ServerLevelAccessor levelAccessor) { ++ Map map = (levelAccessor == null ? this.level : levelAccessor).getChunk(pos.x, pos.z, ChunkStatus.STRUCTURE_REFERENCES).getAllReferences(); ++ // Paper end - Fix swamp hut cat generation deadlock + Builder builder = ImmutableList.builder(); + + for (Entry entry : map.entrySet()) { +@@ -116,10 +121,20 @@ public class StructureManager { + } + + public StructureStart getStructureWithPieceAt(BlockPos pos, Predicate> predicate) { ++ // Paper start - Fix swamp hut cat generation deadlock ++ return this.getStructureWithPieceAt(pos, predicate, null); ++ } ++ ++ public StructureStart getStructureWithPieceAt(BlockPos pos, TagKey tag, @Nullable ServerLevelAccessor levelAccessor) { ++ return this.getStructureWithPieceAt(pos, structure -> structure.is(tag), levelAccessor); ++ } ++ ++ public StructureStart getStructureWithPieceAt(BlockPos pos, Predicate> predicate, @Nullable ServerLevelAccessor levelAccessor) { ++ // Paper end - Fix swamp hut cat generation deadlock + Registry registry = this.registryAccess().lookupOrThrow(Registries.STRUCTURE); + + for (StructureStart structureStart : this.startsForStructure( +- new ChunkPos(pos), structure -> registry.get(registry.getId(structure)).map(predicate::test).orElse(false) ++ new ChunkPos(pos), structure -> registry.get(registry.getId(structure)).map(predicate::test).orElse(false), levelAccessor // Paper - Fix swamp hut cat generation deadlock + )) { + if (this.structureHasPieceAt(pos, structureStart)) { + return structureStart; diff --git a/patches/server/0670-Force-close-world-loading-screen.patch b/patches/server/0670-Force-close-world-loading-screen.patch deleted file mode 100644 index 284d51211569..000000000000 --- a/patches/server/0670-Force-close-world-loading-screen.patch +++ /dev/null @@ -1,32 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Nassim Jahnke -Date: Wed, 2 Mar 2022 09:45:56 +0100 -Subject: [PATCH] Force close world loading screen - -Dead players would be stuck in the world loading screen and other players may -miss messages and similar sent in the join event if chunk loading is slow. -Paper already circumvents falling through the world before chunks are loaded, -so we do not need that. The client only needs the chunk it is currently in to -be loaded to close the loading screen, so we just send an empty one. - -diff --git a/src/main/java/net/minecraft/server/players/PlayerList.java b/src/main/java/net/minecraft/server/players/PlayerList.java -index cff45afb90b232a236683bd569d50aab6b701149..e30ad8c7e85c25f3133bbd23dcbe59ae4c2f8db5 100644 ---- a/src/main/java/net/minecraft/server/players/PlayerList.java -+++ b/src/main/java/net/minecraft/server/players/PlayerList.java -@@ -394,6 +394,16 @@ public abstract class PlayerList { - this.sendActivePlayerEffects(player); - // Paper start - Fire PlayerJoinEvent when Player is actually ready; move vehicle into method so it can be called above - short circuit around that code - this.onPlayerJoinFinish(player, worldserver1, s1); -+ // Paper start - Send empty chunk, so players aren't stuck in the world loading screen with our chunk system not sending chunks when dead -+ if (player.isDeadOrDying()) { -+ net.minecraft.core.Holder plains = worldserver1.registryAccess().registryOrThrow(net.minecraft.core.registries.Registries.BIOME) -+ .getHolderOrThrow(net.minecraft.world.level.biome.Biomes.PLAINS); -+ player.connection.send(new net.minecraft.network.protocol.game.ClientboundLevelChunkWithLightPacket( -+ new net.minecraft.world.level.chunk.EmptyLevelChunk(worldserver1, player.chunkPosition(), plains), -+ worldserver1.getLightEngine(), (java.util.BitSet)null, (java.util.BitSet) null) -+ ); -+ } -+ // Paper end - Send empty chunk - } - private void mountSavedVehicle(ServerPlayer player, ServerLevel worldserver1, Optional optional) { - // Paper end - Fire PlayerJoinEvent when Player is actually ready diff --git a/patches/server/0671-Don-t-allow-vehicle-movement-from-players-while-tele.patch b/patches/server/0671-Don-t-allow-vehicle-movement-from-players-while-tele.patch new file mode 100644 index 000000000000..cb7a4055d9c0 --- /dev/null +++ b/patches/server/0671-Don-t-allow-vehicle-movement-from-players-while-tele.patch @@ -0,0 +1,24 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Spottedleaf +Date: Mon, 14 Mar 2022 12:35:37 -0700 +Subject: [PATCH] Don't allow vehicle movement from players while teleporting + +Bring the vehicle move packet behavior in line with the +regular player move packet. + +diff --git a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java +index 7f1e0c6801a1d8b0857fba9826fc56e30bd41497..9656e15aa15735e9bd6ac4e2ad7aa8aa66140b9a 100644 +--- a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java ++++ b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java +@@ -489,6 +489,11 @@ public class ServerGamePacketListenerImpl extends ServerCommonPacketListenerImpl + this.disconnect((Component) Component.translatable("multiplayer.disconnect.invalid_vehicle_movement"), org.bukkit.event.player.PlayerKickEvent.Cause.INVALID_VEHICLE_MOVEMENT); // Paper - kick event cause + } else if (!this.updateAwaitingTeleport()) { + Entity entity = this.player.getRootVehicle(); ++ // Paper start - Don't allow vehicle movement from players while teleporting ++ if (this.awaitingPositionFromClient != null || this.player.isImmobile() || entity.isRemoved()) { ++ return; ++ } ++ // Paper end - Don't allow vehicle movement from players while teleporting + + if (entity != this.player && entity.getControllingPassenger() == this.player && entity == this.lastVehicle) { + ServerLevel worldserver = this.player.serverLevel(); diff --git a/patches/server/0671-Fix-falling-block-spawn-methods.patch b/patches/server/0671-Fix-falling-block-spawn-methods.patch deleted file mode 100644 index 548b96559bf2..000000000000 --- a/patches/server/0671-Fix-falling-block-spawn-methods.patch +++ /dev/null @@ -1,57 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Nassim Jahnke -Date: Fri, 4 Mar 2022 20:35:19 +0100 -Subject: [PATCH] Fix falling block spawn methods - -Restores the API behavior from previous versions of the server -- Do not call API events -- Do not replace the existing block in the world - -== AT == -public net.minecraft.world.entity.item.FallingBlockEntity (Lnet/minecraft/world/level/Level;DDDLnet/minecraft/world/level/block/state/BlockState;)V - -diff --git a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java -index ba011d3b56ed0e5019da61ccfec687042d863beb..20a954c8637c2cf9cc6c0c823e33a44f668cc4f6 100644 ---- a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java -+++ b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java -@@ -1395,7 +1395,12 @@ public class CraftWorld extends CraftRegionAccessor implements World { - Preconditions.checkArgument(material != null, "Material cannot be null"); - Preconditions.checkArgument(material.isBlock(), "Material.%s must be a block", material); - -- FallingBlockEntity entity = FallingBlockEntity.fall(this.world, BlockPos.containing(location.getX(), location.getY(), location.getZ()), CraftBlockType.bukkitToMinecraft(material).defaultBlockState(), SpawnReason.CUSTOM); -+ // Paper start - restore API behavior for spawning falling blocks -+ FallingBlockEntity entity = new FallingBlockEntity(this.world, location.getX(), location.getY(), location.getZ(), CraftBlockType.bukkitToMinecraft(material).defaultBlockState()); // Paper -+ entity.time = 1; -+ -+ this.world.addFreshEntity(entity, SpawnReason.CUSTOM); -+ // Paper end - restore API behavior for spawning falling blocks - return (FallingBlock) entity.getBukkitEntity(); - } - -@@ -1404,7 +1409,12 @@ public class CraftWorld extends CraftRegionAccessor implements World { - Preconditions.checkArgument(location != null, "Location cannot be null"); - Preconditions.checkArgument(data != null, "BlockData cannot be null"); - -- FallingBlockEntity entity = FallingBlockEntity.fall(this.world, BlockPos.containing(location.getX(), location.getY(), location.getZ()), ((CraftBlockData) data).getState(), SpawnReason.CUSTOM); -+ // Paper start - restore API behavior for spawning falling blocks -+ FallingBlockEntity entity = new FallingBlockEntity(this.world, location.getX(), location.getY(), location.getZ(), ((CraftBlockData) data).getState()); -+ entity.time = 1; -+ -+ this.world.addFreshEntity(entity, SpawnReason.CUSTOM); -+ // Paper end - restore API behavior for spawning falling blocks - return (FallingBlock) entity.getBukkitEntity(); - } - -diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftEntityTypes.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftEntityTypes.java -index 481f0c62fcd4435d655cfe2f94f46262b24a7144..37527713b0afa6db19eefd57aaffcb2fe3544ce6 100644 ---- a/src/main/java/org/bukkit/craftbukkit/entity/CraftEntityTypes.java -+++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftEntityTypes.java -@@ -387,7 +387,7 @@ public final class CraftEntityTypes { - register(new EntityTypeData<>(EntityType.TNT, TNTPrimed.class, CraftTNTPrimed::new, spawnData -> new PrimedTnt(spawnData.minecraftWorld(), spawnData.x(), spawnData.y(), spawnData.z(), null))); - register(new EntityTypeData<>(EntityType.FALLING_BLOCK, FallingBlock.class, CraftFallingBlock::new, spawnData -> { - BlockPos pos = BlockPos.containing(spawnData.x(), spawnData.y(), spawnData.z()); -- return FallingBlockEntity.fall(spawnData.minecraftWorld(), pos, spawnData.world().getBlockState(pos)); -+ return new FallingBlockEntity(spawnData.minecraftWorld(), spawnData.x(), spawnData.y(), spawnData.z(), spawnData.world().getBlockState(pos)); // Paper - create falling block entities correctly - })); - register(new EntityTypeData<>(EntityType.FIREWORK_ROCKET, Firework.class, CraftFirework::new, spawnData -> new FireworkRocketEntity(spawnData.minecraftWorld(), spawnData.x(), spawnData.y(), spawnData.z(), net.minecraft.world.item.ItemStack.EMPTY))); - register(new EntityTypeData<>(EntityType.EVOKER_FANGS, EvokerFangs.class, CraftEvokerFangs::new, spawnData -> new net.minecraft.world.entity.projectile.EvokerFangs(spawnData.minecraftWorld(), spawnData.x(), spawnData.y(), spawnData.z(), (float) Math.toRadians(spawnData.yaw()), 0, null))); diff --git a/patches/server/0672-Expose-furnace-minecart-push-values.patch b/patches/server/0672-Expose-furnace-minecart-push-values.patch deleted file mode 100644 index 6245e14b4e0c..000000000000 --- a/patches/server/0672-Expose-furnace-minecart-push-values.patch +++ /dev/null @@ -1,40 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: EpicKnarvik97 -Date: Sat, 5 Mar 2022 20:58:46 +0100 -Subject: [PATCH] Expose furnace minecart push values - -Adds methods for getting and setting a furnace minecart's push values - -diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftMinecartFurnace.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftMinecartFurnace.java -index 53042b75b45093535d6572239b34c3ff9a72f648..1b41026ab638bb2764b19429706eb0aded5aad12 100644 ---- a/src/main/java/org/bukkit/craftbukkit/entity/CraftMinecartFurnace.java -+++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftMinecartFurnace.java -@@ -27,6 +27,28 @@ public class CraftMinecartFurnace extends CraftMinecart implements PoweredMineca - this.getHandle().fuel = fuel; - } - -+ // Paper start -+ @Override -+ public double getPushX() { -+ return getHandle().xPush; -+ } -+ -+ @Override -+ public double getPushZ() { -+ return getHandle().zPush; -+ } -+ -+ @Override -+ public void setPushX(double xPush) { -+ getHandle().xPush = xPush; -+ } -+ -+ @Override -+ public void setPushZ(double zPush) { -+ getHandle().zPush = zPush; -+ } -+ // Paper end -+ - @Override - public String toString() { - return "CraftMinecartFurnace"; diff --git a/patches/server/0672-Implement-getComputedBiome-API.patch b/patches/server/0672-Implement-getComputedBiome-API.patch new file mode 100644 index 000000000000..dab55a6218f9 --- /dev/null +++ b/patches/server/0672-Implement-getComputedBiome-API.patch @@ -0,0 +1,61 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Jason Penilla <11360596+jpenilla@users.noreply.github.com> +Date: Mon, 14 Mar 2022 22:46:05 -0700 +Subject: [PATCH] Implement getComputedBiome API + + +diff --git a/src/main/java/org/bukkit/craftbukkit/CraftRegionAccessor.java b/src/main/java/org/bukkit/craftbukkit/CraftRegionAccessor.java +index ca35e93239eea09b3d0dc6ef18f58743e633996b..a7748f4b7c5a1630937c702b3fd5fded93793d64 100644 +--- a/src/main/java/org/bukkit/craftbukkit/CraftRegionAccessor.java ++++ b/src/main/java/org/bukkit/craftbukkit/CraftRegionAccessor.java +@@ -77,6 +77,13 @@ public abstract class CraftRegionAccessor implements RegionAccessor { + return CraftBiome.minecraftHolderToBukkit(this.getHandle().getNoiseBiome(x >> 2, y >> 2, z >> 2)); + } + ++ // Paper start ++ @Override ++ public Biome getComputedBiome(int x, int y, int z) { ++ return CraftBiome.minecraftHolderToBukkit(this.getHandle().getBiome(new BlockPos(x, y, z))); ++ } ++ // Paper end ++ + @Override + public void setBiome(Location location, Biome biome) { + this.setBiome(location.getBlockX(), location.getBlockY(), location.getBlockZ(), biome); +diff --git a/src/main/java/org/bukkit/craftbukkit/block/CraftBlock.java b/src/main/java/org/bukkit/craftbukkit/block/CraftBlock.java +index d5b495b5a3ca7f4411d1a700f7149042a16509f1..68fcec085334383808b2117a49220f4d8239220b 100644 +--- a/src/main/java/org/bukkit/craftbukkit/block/CraftBlock.java ++++ b/src/main/java/org/bukkit/craftbukkit/block/CraftBlock.java +@@ -340,6 +340,13 @@ public class CraftBlock implements Block { + return this.getWorld().getBiome(this.getX(), this.getY(), this.getZ()); + } + ++ // Paper start ++ @Override ++ public Biome getComputedBiome() { ++ return this.getWorld().getComputedBiome(this.getX(), this.getY(), this.getZ()); ++ } ++ // Paper end ++ + @Override + public void setBiome(Biome bio) { + this.getWorld().setBiome(this.getX(), this.getY(), this.getZ(), bio); +diff --git a/src/main/java/org/bukkit/craftbukkit/generator/CraftLimitedRegion.java b/src/main/java/org/bukkit/craftbukkit/generator/CraftLimitedRegion.java +index 2c7b64bd7071cb803a042152d497982d753e0b5d..a23269e3bdb83f85a1d08d5f7b54742025223ada 100644 +--- a/src/main/java/org/bukkit/craftbukkit/generator/CraftLimitedRegion.java ++++ b/src/main/java/org/bukkit/craftbukkit/generator/CraftLimitedRegion.java +@@ -166,6 +166,14 @@ public class CraftLimitedRegion extends CraftRegionAccessor implements LimitedRe + return super.getBiome(x, y, z); + } + ++ // Paper start ++ @Override ++ public Biome getComputedBiome(int x, int y, int z) { ++ Preconditions.checkArgument(this.isInRegion(x, y, z), "Coordinates %s, %s, %s are not in the region", x, y, z); ++ return super.getComputedBiome(x, y, z); ++ } ++ // Paper end ++ + @Override + public void setBiome(int x, int y, int z, Holder biomeBase) { + Preconditions.checkArgument(this.isInRegion(x, y, z), "Coordinates %s, %s, %s are not in the region", x, y, z); diff --git a/patches/server/0673-Fix-cancelling-ProjectileHitEvent-for-piercing-arrow.patch b/patches/server/0673-Fix-cancelling-ProjectileHitEvent-for-piercing-arrow.patch deleted file mode 100644 index 4698298019ab..000000000000 --- a/patches/server/0673-Fix-cancelling-ProjectileHitEvent-for-piercing-arrow.patch +++ /dev/null @@ -1,40 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Jake Potrebic -Date: Sat, 19 Feb 2022 19:05:59 -0800 -Subject: [PATCH] Fix cancelling ProjectileHitEvent for piercing arrows - -Piercing arrows search for multiple entities inside a while -loop that is checking the projectile entity's removed state. -If the hit event is cancelled on the first entity, the event will -be called over and over again inside that while loop until the event -is not cancelled. The solution here, is to make use of an -already-existing field on AbstractArrow for tracking entities hit by -piercing arrows to avoid duplicate damage being applied. - -== AT == -protected net.minecraft.world.entity.projectile.Projectile hitCancelled - -diff --git a/src/main/java/net/minecraft/world/entity/projectile/AbstractArrow.java b/src/main/java/net/minecraft/world/entity/projectile/AbstractArrow.java -index 57ebb96707748e90810dc07471f9769f1317df9d..10d30304c8c89b1f2a55be8529035311d1424e44 100644 ---- a/src/main/java/net/minecraft/world/entity/projectile/AbstractArrow.java -+++ b/src/main/java/net/minecraft/world/entity/projectile/AbstractArrow.java -@@ -329,6 +329,19 @@ public abstract class AbstractArrow extends Projectile { - } - } - -+ // Paper start - Fix cancelling ProjectileHitEvent for piercing arrows -+ @Override -+ public ProjectileDeflection preHitTargetOrDeflectSelf(HitResult hitResult) { -+ if (hitResult instanceof EntityHitResult entityHitResult && this.hitCancelled && this.getPierceLevel() > 0) { -+ if (this.piercingIgnoreEntityIds == null) { -+ this.piercingIgnoreEntityIds = new IntOpenHashSet(5); -+ } -+ this.piercingIgnoreEntityIds.add(entityHitResult.getEntity().getId()); -+ } -+ return super.preHitTargetOrDeflectSelf(hitResult); -+ } -+ // Paper end - Fix cancelling ProjectileHitEvent for piercing arrows -+ - @Override - protected double getDefaultGravity() { - return 0.05D; diff --git a/patches/server/0673-Make-some-itemstacks-nonnull.patch b/patches/server/0673-Make-some-itemstacks-nonnull.patch new file mode 100644 index 000000000000..20944d908892 --- /dev/null +++ b/patches/server/0673-Make-some-itemstacks-nonnull.patch @@ -0,0 +1,28 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Jake Potrebic +Date: Tue, 15 Mar 2022 01:38:15 -0700 +Subject: [PATCH] Make some itemstacks nonnull + + +diff --git a/src/main/java/org/bukkit/craftbukkit/inventory/CraftInventoryPlayer.java b/src/main/java/org/bukkit/craftbukkit/inventory/CraftInventoryPlayer.java +index 107fa1d4bd977d17dc062da280dda46eb3c5f81c..e62baea16df017f1e394e3c706157e158066eb93 100644 +--- a/src/main/java/org/bukkit/craftbukkit/inventory/CraftInventoryPlayer.java ++++ b/src/main/java/org/bukkit/craftbukkit/inventory/CraftInventoryPlayer.java +@@ -155,13 +155,13 @@ public class CraftInventoryPlayer extends CraftInventory implements org.bukkit.i + case OFF_HAND: + return this.getItemInOffHand(); + case FEET: +- return this.getBoots(); ++ return java.util.Objects.requireNonNullElseGet(this.getBoots(), () -> new ItemStack(org.bukkit.Material.AIR)); // Paper - make nonnull + case LEGS: +- return this.getLeggings(); ++ return java.util.Objects.requireNonNullElseGet(this.getLeggings(), () -> new ItemStack(org.bukkit.Material.AIR)); // Paper - make nonnull + case CHEST: +- return this.getChestplate(); ++ return java.util.Objects.requireNonNullElseGet(this.getChestplate(), () -> new ItemStack(org.bukkit.Material.AIR)); // Paper - make nonnull + case HEAD: +- return this.getHelmet(); ++ return java.util.Objects.requireNonNullElseGet(this.getHelmet(), () -> new ItemStack(org.bukkit.Material.AIR)); // Paper - make nonnull + default: + throw new IllegalArgumentException("Could not get slot " + slot + " - not a valid slot for PlayerInventory"); + } diff --git a/patches/server/0674-Implement-enchantWithLevels-API.patch b/patches/server/0674-Implement-enchantWithLevels-API.patch new file mode 100644 index 000000000000..45f1f704cacf --- /dev/null +++ b/patches/server/0674-Implement-enchantWithLevels-API.patch @@ -0,0 +1,58 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Jason Penilla <11360596+jpenilla@users.noreply.github.com> +Date: Wed, 16 Mar 2022 20:35:21 -0700 +Subject: [PATCH] Implement enchantWithLevels API + + +diff --git a/src/main/java/org/bukkit/craftbukkit/inventory/CraftItemFactory.java b/src/main/java/org/bukkit/craftbukkit/inventory/CraftItemFactory.java +index ad86ee4372e55c82968fd4fc6a65debab0092028..d9eec6cff3c7c6515f4d61bf1063e7d609d4bcb3 100644 +--- a/src/main/java/org/bukkit/craftbukkit/inventory/CraftItemFactory.java ++++ b/src/main/java/org/bukkit/craftbukkit/inventory/CraftItemFactory.java +@@ -305,4 +305,47 @@ public final class CraftItemFactory implements ItemFactory { + return eggItem == null ? null : new net.minecraft.world.item.ItemStack(eggItem).asBukkitMirror(); + } + // Paper end - old getSpawnEgg API ++ // Paper start - enchantWithLevels API ++ @Override ++ public ItemStack enchantWithLevels(ItemStack itemStack, int levels, boolean allowTreasure, java.util.Random random) { ++ return enchantWithLevels( ++ itemStack, ++ levels, ++ allowTreasure ++ ? Optional.empty() ++ // While IN_ENCHANTING_TABLE is not logically the same as all but TREASURE, the tag is defined as ++ // NON_TREASURE, which does contain all enchantments not in the treasure tag. ++ // Additionally, the allowTreasure boolean is more intended to configure this method to behave like ++ // an enchanting table. ++ : net.minecraft.server.MinecraftServer.getServer().registryAccess().lookupOrThrow(Registries.ENCHANTMENT).get(EnchantmentTags.IN_ENCHANTING_TABLE), ++ random ++ ); ++ } ++ ++ @SuppressWarnings("OptionalUsedAsFieldOrParameterType") ++ private ItemStack enchantWithLevels( ++ ItemStack itemStack, ++ int levels, ++ Optional> possibleEnchantments, ++ java.util.Random random ++ ) { ++ Preconditions.checkArgument(itemStack != null, "Argument 'itemStack' must not be null"); ++ Preconditions.checkArgument(!itemStack.isEmpty(), "Argument 'itemStack' cannot be empty"); ++ Preconditions.checkArgument(levels > 0 && levels <= 30, "Argument 'levels' must be in range [1, 30] (attempted " + levels + ")"); ++ Preconditions.checkArgument(random != null, "Argument 'random' must not be null"); ++ final net.minecraft.world.item.ItemStack internalStack = CraftItemStack.asNMSCopy(itemStack); ++ if (internalStack.isEnchanted()) { ++ internalStack.set(net.minecraft.core.component.DataComponents.ENCHANTMENTS, net.minecraft.world.item.enchantment.ItemEnchantments.EMPTY); ++ } ++ final net.minecraft.core.RegistryAccess registryAccess = net.minecraft.server.MinecraftServer.getServer().registryAccess(); ++ final net.minecraft.world.item.ItemStack enchanted = net.minecraft.world.item.enchantment.EnchantmentHelper.enchantItem( ++ new org.bukkit.craftbukkit.util.RandomSourceWrapper(random), ++ internalStack, ++ levels, ++ registryAccess, ++ possibleEnchantments ++ ); ++ return CraftItemStack.asCraftMirror(enchanted); ++ } ++ // Paper end - enchantWithLevels API + } diff --git a/patches/server/0674-More-Projectile-API.patch b/patches/server/0674-More-Projectile-API.patch deleted file mode 100644 index a58713f20399..000000000000 --- a/patches/server/0674-More-Projectile-API.patch +++ /dev/null @@ -1,929 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Owen1212055 <23108066+Owen1212055@users.noreply.github.com> -Date: Tue, 22 Jun 2021 23:41:11 -0400 -Subject: [PATCH] More Projectile API - -== AT == -public net.minecraft.world.entity.projectile.FishingHook timeUntilLured -public net.minecraft.world.entity.projectile.FishingHook fishAngle -public net.minecraft.world.entity.projectile.ShulkerBullet targetDeltaX -public net.minecraft.world.entity.projectile.ShulkerBullet targetDeltaY -public net.minecraft.world.entity.projectile.ShulkerBullet targetDeltaZ -public net.minecraft.world.entity.projectile.ShulkerBullet currentMoveDirection -public net.minecraft.world.entity.projectile.ShulkerBullet flightSteps -public net.minecraft.world.entity.projectile.AbstractArrow soundEvent -public net.minecraft.world.entity.projectile.AbstractArrow setPickupItemStack(Lnet/minecraft/world/item/ItemStack;)V -public net.minecraft.world.entity.projectile.ThrownTrident dealtDamage -public net.minecraft.world.entity.projectile.Arrow NO_EFFECT_COLOR -public net.minecraft.world.entity.projectile.Projectile hasBeenShot -public net.minecraft.world.entity.projectile.Projectile leftOwner -public net.minecraft.world.entity.projectile.Projectile preOnHit(Lnet/minecraft/world/phys/HitResult;)V -public net.minecraft.world.entity.projectile.Projectile canHitEntity(Lnet/minecraft/world/entity/Entity;)Z -public net.minecraft.world.entity.projectile.FireworkRocketEntity getDefaultItem()Lnet/minecraft/world/item/ItemStack; -public net.minecraft.world.item.CrossbowItem FIREWORK_POWER - -Co-authored-by: Nassim Jahnke -Co-authored-by: SoSeDiK -Co-authored-by: MelnCat -Co-authored-by: Lulu13022002 <41980282+Lulu13022002@users.noreply.github.com> - -diff --git a/src/main/java/net/minecraft/world/entity/projectile/FishingHook.java b/src/main/java/net/minecraft/world/entity/projectile/FishingHook.java -index 5b7734020b496ade3740d92908ad2d399bfd55e6..e70ca1b2e6fbbc1f20e65429298d01b4ebd2dd29 100644 ---- a/src/main/java/net/minecraft/world/entity/projectile/FishingHook.java -+++ b/src/main/java/net/minecraft/world/entity/projectile/FishingHook.java -@@ -414,13 +414,18 @@ public class FishingHook extends Projectile { - } - } else { - // CraftBukkit start - logic to modify fishing wait time -- this.timeUntilLured = Mth.nextInt(this.random, this.minWaitTime, this.maxWaitTime); -- this.timeUntilLured -= (this.applyLure) ? (this.lureSpeed >= this.maxWaitTime ? this.timeUntilLured - 1 : this.lureSpeed ) : 0; // Paper - Fix Lure infinite loop -+ this.resetTimeUntilLured(); // Paper - more projectile api - extract time until lured reset logic - // CraftBukkit end - } - } - - } -+ // Paper start - more projectile api - extract time until lured reset logic -+ public void resetTimeUntilLured() { -+ this.timeUntilLured = Mth.nextInt(this.random, this.minWaitTime, this.maxWaitTime); -+ this.timeUntilLured -= (this.applyLure) ? (this.lureSpeed >= this.maxWaitTime ? this.timeUntilLured - 1 : this.lureSpeed ) : 0; // Paper - Fix Lure infinite loop -+ } -+ // Paper end - more projectile api - extract time until lured reset logic - - public boolean calculateOpenWater(BlockPos pos) { - FishingHook.OpenWaterType entityfishinghook_waterposition = FishingHook.OpenWaterType.INVALID; -diff --git a/src/main/java/net/minecraft/world/entity/projectile/Projectile.java b/src/main/java/net/minecraft/world/entity/projectile/Projectile.java -index d27e17ebf25cd842a943cf82bde05b2248c74414..06ca07edef062f21c51860146086297ca345104d 100644 ---- a/src/main/java/net/minecraft/world/entity/projectile/Projectile.java -+++ b/src/main/java/net/minecraft/world/entity/projectile/Projectile.java -@@ -190,7 +190,7 @@ public abstract class Projectile extends Entity implements TraceableEntity { - } - - // CraftBukkit start - call projectile hit event -- protected ProjectileDeflection preHitTargetOrDeflectSelf(HitResult movingobjectposition) { -+ public ProjectileDeflection preHitTargetOrDeflectSelf(HitResult movingobjectposition) { // Paper - protected -> public - org.bukkit.event.entity.ProjectileHitEvent event = org.bukkit.craftbukkit.event.CraftEventFactory.callProjectileHitEvent(this, movingobjectposition); - this.hitCancelled = event != null && event.isCancelled(); - if (movingobjectposition.getType() == HitResult.Type.BLOCK || !this.hitCancelled) { -diff --git a/src/main/java/net/minecraft/world/entity/projectile/ThrownPotion.java b/src/main/java/net/minecraft/world/entity/projectile/ThrownPotion.java -index cb34cc9443da56c0497c7a0192c8b8363c3426fe..58dc69fe319027c2b9ecfb9caf272368e81081df 100644 ---- a/src/main/java/net/minecraft/world/entity/projectile/ThrownPotion.java -+++ b/src/main/java/net/minecraft/world/entity/projectile/ThrownPotion.java -@@ -102,6 +102,11 @@ public class ThrownPotion extends ThrowableItemProjectile implements ItemSupplie - @Override - protected void onHit(HitResult hitResult) { - super.onHit(hitResult); -+ // Paper start - More projectile API -+ this.splash(hitResult); -+ } -+ public void splash(@Nullable HitResult hitResult) { -+ // Paper end - More projectile API - if (!this.level().isClientSide) { - ItemStack itemstack = this.getItem(); - PotionContents potioncontents = (PotionContents) itemstack.getOrDefault(DataComponents.POTION_CONTENTS, PotionContents.EMPTY); -@@ -113,7 +118,7 @@ public class ThrownPotion extends ThrowableItemProjectile implements ItemSupplie - if (this.isLingering()) { - showParticles = this.makeAreaOfEffectCloud(potioncontents, hitResult); // CraftBukkit - Pass MovingObjectPosition // Paper - } else { -- showParticles = this.applySplash(potioncontents.getAllEffects(), hitResult.getType() == HitResult.Type.ENTITY ? ((EntityHitResult) hitResult).getEntity() : null, hitResult); // CraftBukkit - Pass MovingObjectPosition // Paper -+ showParticles = this.applySplash(potioncontents.getAllEffects(), hitResult != null && hitResult.getType() == HitResult.Type.ENTITY ? ((EntityHitResult) hitResult).getEntity() : null, hitResult); // CraftBukkit - Pass MovingObjectPosition // Paper - More projectile API - } - } - -@@ -175,7 +180,7 @@ public class ThrownPotion extends ThrowableItemProjectile implements ItemSupplie - - } - -- private boolean applySplash(Iterable iterable, @Nullable Entity entity, HitResult position) { // CraftBukkit - Pass MovingObjectPosition // Paper - Fix potions splash events -+ private boolean applySplash(Iterable iterable, @Nullable Entity entity, @Nullable HitResult position) { // CraftBukkit - Pass MovingObjectPosition // Paper - Fix potions splash events & More projectile API - AABB axisalignedbb = this.getBoundingBox().inflate(4.0D, 2.0D, 4.0D); - List list = this.level().getEntitiesOfClass(net.minecraft.world.entity.LivingEntity.class, axisalignedbb); - Map affected = new HashMap(); // CraftBukkit -@@ -253,7 +258,7 @@ public class ThrownPotion extends ThrowableItemProjectile implements ItemSupplie - - } - -- private boolean makeAreaOfEffectCloud(PotionContents potioncontents, HitResult position) { // CraftBukkit - Pass MovingObjectPosition -+ private boolean makeAreaOfEffectCloud(PotionContents potioncontents, @Nullable HitResult position) { // CraftBukkit - Pass MovingObjectPosition // Paper - More projectile API - AreaEffectCloud entityareaeffectcloud = new AreaEffectCloud(this.level(), this.getX(), this.getY(), this.getZ()); - Entity entity = this.getOwner(); - -diff --git a/src/main/java/org/bukkit/craftbukkit/entity/AbstractProjectile.java b/src/main/java/org/bukkit/craftbukkit/entity/AbstractProjectile.java -index 91c2d0b40d3fca86938cd454e1415a4eea3df7c7..de4fb2654c7895cfd83ad694455ee56cb708c2f2 100644 ---- a/src/main/java/org/bukkit/craftbukkit/entity/AbstractProjectile.java -+++ b/src/main/java/org/bukkit/craftbukkit/entity/AbstractProjectile.java -@@ -17,4 +17,65 @@ public abstract class AbstractProjectile extends CraftEntity implements Projecti - @Override - public void setBounce(boolean doesBounce) {} - -+ // Paper start - More projectile API -+ @Override -+ public boolean hasLeftShooter() { -+ return this.getHandle().leftOwner; -+ } -+ -+ @Override -+ public void setHasLeftShooter(boolean leftShooter) { -+ this.getHandle().leftOwner = leftShooter; -+ } -+ -+ @Override -+ public boolean hasBeenShot() { -+ return this.getHandle().hasBeenShot; -+ } -+ -+ @Override -+ public void setHasBeenShot(boolean beenShot) { -+ this.getHandle().hasBeenShot = beenShot; -+ } -+ -+ @Override -+ public boolean canHitEntity(org.bukkit.entity.Entity entity) { -+ return this.getHandle().canHitEntity(((CraftEntity) entity).getHandle()); -+ } -+ -+ @Override -+ public void hitEntity(org.bukkit.entity.Entity entity) { -+ this.getHandle().preHitTargetOrDeflectSelf(new net.minecraft.world.phys.EntityHitResult(((CraftEntity) entity).getHandle())); -+ } -+ -+ @Override -+ public void hitEntity(org.bukkit.entity.Entity entity, org.bukkit.util.Vector vector) { -+ this.getHandle().preHitTargetOrDeflectSelf(new net.minecraft.world.phys.EntityHitResult(((CraftEntity) entity).getHandle(), new net.minecraft.world.phys.Vec3(vector.getX(), vector.getY(), vector.getZ()))); -+ } -+ -+ @Override -+ public net.minecraft.world.entity.projectile.Projectile getHandle() { -+ return (net.minecraft.world.entity.projectile.Projectile) entity; -+ } -+ -+ @Override -+ public final org.bukkit.projectiles.ProjectileSource getShooter() { -+ return this.getHandle().projectileSource; -+ } -+ -+ @Override -+ public final void setShooter(org.bukkit.projectiles.ProjectileSource shooter) { -+ if (shooter instanceof CraftEntity craftEntity) { -+ this.getHandle().setOwner(craftEntity.getHandle()); -+ } else { -+ this.getHandle().setOwner(null); -+ } -+ this.getHandle().projectileSource = shooter; -+ } -+ -+ @Override -+ public java.util.UUID getOwnerUniqueId() { -+ return this.getHandle().ownerUUID; -+ } -+ // Paper end - More projectile API - } -diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftAbstractArrow.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftAbstractArrow.java -index 329ca9c743a7f2feeabbfb769ff9a71f60165006..faa08ad912fa43e7a6c5a2359e23c04c059c5edf 100644 ---- a/src/main/java/org/bukkit/craftbukkit/entity/CraftAbstractArrow.java -+++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftAbstractArrow.java -@@ -58,20 +58,7 @@ public class CraftAbstractArrow extends AbstractProjectile implements AbstractAr - this.getHandle().setCritArrow(critical); - } - -- @Override -- public ProjectileSource getShooter() { -- return this.getHandle().projectileSource; -- } -- -- @Override -- public void setShooter(ProjectileSource shooter) { -- if (shooter instanceof Entity) { -- this.getHandle().setOwner(((CraftEntity) shooter).getHandle()); -- } else { -- this.getHandle().setOwner(null); -- } -- this.getHandle().projectileSource = shooter; -- } -+ // Paper - moved to AbstractProjectile - - @Override - public boolean isInBlock() { -@@ -130,6 +117,7 @@ public class CraftAbstractArrow extends AbstractProjectile implements AbstractAr - - @Override - public ItemStack getWeapon() { -+ if (this.getHandle().getWeaponItem() == null) return null; // Paper - fix NPE - return CraftItemStack.asBukkitCopy(this.getHandle().getWeaponItem()); - } - -@@ -149,4 +137,37 @@ public class CraftAbstractArrow extends AbstractProjectile implements AbstractAr - public String toString() { - return "CraftArrow"; - } -+ -+ // Paper start -+ @Override -+ public CraftItemStack getItemStack() { -+ return CraftItemStack.asCraftMirror(this.getHandle().getPickupItem()); -+ } -+ -+ @Override -+ public void setItemStack(final ItemStack stack) { -+ Preconditions.checkArgument(stack != null, "ItemStack cannot be null"); -+ this.getHandle().setPickupItemStack(CraftItemStack.asNMSCopy(stack)); -+ } -+ -+ @Override -+ public void setLifetimeTicks(int ticks) { -+ this.getHandle().life = ticks; -+ } -+ -+ @Override -+ public int getLifetimeTicks() { -+ return this.getHandle().life; -+ } -+ -+ @Override -+ public org.bukkit.Sound getHitSound() { -+ return org.bukkit.craftbukkit.CraftSound.minecraftToBukkit(this.getHandle().soundEvent); -+ } -+ -+ @Override -+ public void setHitSound(org.bukkit.Sound sound) { -+ this.getHandle().setSoundEvent(org.bukkit.craftbukkit.CraftSound.bukkitToMinecraft(sound)); -+ } -+ // Paper end - } -diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftAreaEffectCloud.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftAreaEffectCloud.java -index 81f5e1d866128af8fb2acc13aca715580fdf9886..88f2a9f310f30a08893f3fa68af13a54cf72fa7f 100644 ---- a/src/main/java/org/bukkit/craftbukkit/entity/CraftAreaEffectCloud.java -+++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftAreaEffectCloud.java -@@ -125,7 +125,7 @@ public class CraftAreaEffectCloud extends CraftEntity implements AreaEffectCloud - - @Override - public Color getColor() { -- return Color.fromRGB(this.getHandle().potionContents.getColor()); -+ return Color.fromRGB(this.getHandle().potionContents.getColor() & 0x00FFFFFF); // Paper - skip alpha channel - } - - @Override -@@ -143,7 +143,7 @@ public class CraftAreaEffectCloud extends CraftEntity implements AreaEffectCloud - this.removeCustomEffect(effect.getType()); - } - this.getHandle().addEffect(CraftPotionUtil.fromBukkit(effect)); -- this.getHandle().updateColor(); -+ // this.getHandle().updateColor(); // Paper - already done above - return true; - } - -@@ -151,7 +151,7 @@ public class CraftAreaEffectCloud extends CraftEntity implements AreaEffectCloud - public void clearCustomEffects() { - PotionContents old = this.getHandle().potionContents; - this.getHandle().setPotionContents(new PotionContents(old.potion(), old.customColor(), List.of())); -- this.getHandle().updateColor(); -+ // this.getHandle().updateColor(); // Paper - already done above - } - - @Override -diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftArrow.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftArrow.java -index 5232fbef0d014edd32a5d18d4a1500ab215313f5..071be344c3265a0cd52b31ffbb02ff7a70bdf231 100644 ---- a/src/main/java/org/bukkit/craftbukkit/entity/CraftArrow.java -+++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftArrow.java -@@ -43,7 +43,7 @@ public class CraftArrow extends CraftAbstractArrow implements Arrow { - this.removeCustomEffect(effect.getType()); - } - this.getHandle().addEffect(CraftPotionUtil.fromBukkit(effect)); -- this.getHandle().updateColor(); -+ // this.getHandle().updateColor(); // Paper - already done above - return true; - } - -@@ -51,7 +51,7 @@ public class CraftArrow extends CraftAbstractArrow implements Arrow { - public void clearCustomEffects() { - PotionContents old = this.getHandle().getPotionContents(); - this.getHandle().setPotionContents(new PotionContents(old.potion(), old.customColor(), List.of())); -- this.getHandle().updateColor(); -+ // this.getHandle().updateColor(); // Paper - already done above - } - - @Override -@@ -117,16 +117,17 @@ public class CraftArrow extends CraftAbstractArrow implements Arrow { - - @Override - public void setColor(Color color) { -- int colorRGB = (color == null) ? -1 : color.asRGB(); -+ int colorRGB = (color == null) ? net.minecraft.world.entity.projectile.Arrow.NO_EFFECT_COLOR : color.asARGB(); // Paper - PotionContents old = this.getHandle().getPotionContents(); - this.getHandle().setPotionContents(new PotionContents(old.potion(), Optional.of(colorRGB), old.customEffects())); - } - - @Override - public Color getColor() { -- if (this.getHandle().getColor() <= -1) { -+ int color = this.getHandle().getColor(); // Paper -+ if (color == net.minecraft.world.entity.projectile.Arrow.NO_EFFECT_COLOR) { // Paper - return null; - } -- return Color.fromRGB(this.getHandle().getColor()); -+ return Color.fromARGB(color); // Paper - } - } -diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftEntityTypes.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftEntityTypes.java -index 37527713b0afa6db19eefd57aaffcb2fe3544ce6..46a4f31e2b6eee6f8dc5f8fccd7de4c48a698b61 100644 ---- a/src/main/java/org/bukkit/craftbukkit/entity/CraftEntityTypes.java -+++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftEntityTypes.java -@@ -389,7 +389,7 @@ public final class CraftEntityTypes { - BlockPos pos = BlockPos.containing(spawnData.x(), spawnData.y(), spawnData.z()); - return new FallingBlockEntity(spawnData.minecraftWorld(), spawnData.x(), spawnData.y(), spawnData.z(), spawnData.world().getBlockState(pos)); // Paper - create falling block entities correctly - })); -- register(new EntityTypeData<>(EntityType.FIREWORK_ROCKET, Firework.class, CraftFirework::new, spawnData -> new FireworkRocketEntity(spawnData.minecraftWorld(), spawnData.x(), spawnData.y(), spawnData.z(), net.minecraft.world.item.ItemStack.EMPTY))); -+ register(new EntityTypeData<>(EntityType.FIREWORK_ROCKET, Firework.class, CraftFirework::new, spawnData -> new FireworkRocketEntity(spawnData.minecraftWorld(), spawnData.x(), spawnData.y(), spawnData.z(), FireworkRocketEntity.getDefaultItem()))); // Paper - pass correct default to rocket for data storage - register(new EntityTypeData<>(EntityType.EVOKER_FANGS, EvokerFangs.class, CraftEvokerFangs::new, spawnData -> new net.minecraft.world.entity.projectile.EvokerFangs(spawnData.minecraftWorld(), spawnData.x(), spawnData.y(), spawnData.z(), (float) Math.toRadians(spawnData.yaw()), 0, null))); - register(new EntityTypeData<>(EntityType.COMMAND_BLOCK_MINECART, CommandMinecart.class, CraftMinecartCommand::new, spawnData -> new MinecartCommandBlock(spawnData.minecraftWorld(), spawnData.x(), spawnData.y(), spawnData.z()))); - register(new EntityTypeData<>(EntityType.MINECART, RideableMinecart.class, CraftMinecartRideable::new, spawnData -> new Minecart(spawnData.minecraftWorld(), spawnData.x(), spawnData.y(), spawnData.z()))); -diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftFireball.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftFireball.java -index 1b084d63bdbb24dad45d28eed1693eb6e26e24dc..43d7bea201a52cfeacf60c75caa28dfd2c4ff164 100644 ---- a/src/main/java/org/bukkit/craftbukkit/entity/CraftFireball.java -+++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftFireball.java -@@ -34,20 +34,7 @@ public class CraftFireball extends AbstractProjectile implements Fireball { - this.getHandle().bukkitYield = yield; - } - -- @Override -- public ProjectileSource getShooter() { -- return this.getHandle().projectileSource; -- } -- -- @Override -- public void setShooter(ProjectileSource shooter) { -- if (shooter instanceof CraftLivingEntity) { -- this.getHandle().setOwner(((CraftLivingEntity) shooter).getHandle()); -- } else { -- this.getHandle().setOwner(null); -- } -- this.getHandle().projectileSource = shooter; -- } -+ // Paper - moved to AbstractProjectile - - @Override - public Vector getDirection() { -diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftFirework.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftFirework.java -index c9e15a9d82dee935293b2e7e233f5b9b2d822448..2d54cf6f3d9696c55335f0a2057025e2034d4e13 100644 ---- a/src/main/java/org/bukkit/craftbukkit/entity/CraftFirework.java -+++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftFirework.java -@@ -15,24 +15,26 @@ import org.bukkit.inventory.meta.FireworkMeta; - public class CraftFirework extends CraftProjectile implements Firework { - - private final Random random = new Random(); -- private final CraftItemStack item; -+ //private CraftItemStack item; // Paper - Remove usage, not accurate representation of current item. - - public CraftFirework(CraftServer server, FireworkRocketEntity entity) { - super(server, entity); - -- ItemStack item = this.getHandle().getEntityData().get(FireworkRocketEntity.DATA_ID_FIREWORKS_ITEM); -- -- if (item.isEmpty()) { -- item = new ItemStack(Items.FIREWORK_ROCKET); -- this.getHandle().getEntityData().set(FireworkRocketEntity.DATA_ID_FIREWORKS_ITEM, item); -- } -- -- this.item = CraftItemStack.asCraftMirror(item); -- -- // Ensure the item is a firework... -- if (this.item.getType() != Material.FIREWORK_ROCKET) { -- this.item.setType(Material.FIREWORK_ROCKET); -- } -+ // Paper start - Expose firework item directly -+// ItemStack item = this.getHandle().getEntityData().get(FireworkRocketEntity.DATA_ID_FIREWORKS_ITEM); -+// -+// if (item.isEmpty()) { -+// item = new ItemStack(Items.FIREWORK_ROCKET); -+// this.getHandle().getEntityData().set(FireworkRocketEntity.DATA_ID_FIREWORKS_ITEM, item); -+// } -+// -+// this.item = CraftItemStack.asCraftMirror(item); -+// -+// // Ensure the item is a firework... -+// if (this.item.getType() != Material.FIREWORK_ROCKET) { -+// this.item.setType(Material.FIREWORK_ROCKET); -+// } -+ // Paper end - Expose firework item directly - } - - @Override -@@ -47,12 +49,12 @@ public class CraftFirework extends CraftProjectile implements Firework { - - @Override - public FireworkMeta getFireworkMeta() { -- return (FireworkMeta) this.item.getItemMeta(); -+ return (FireworkMeta) CraftItemStack.getItemMeta(this.getHandle().getEntityData().get(FireworkRocketEntity.DATA_ID_FIREWORKS_ITEM), org.bukkit.inventory.ItemType.FIREWORK_ROCKET); // Paper - Expose firework item directly - } - - @Override - public void setFireworkMeta(FireworkMeta meta) { -- this.item.setItemMeta(meta); -+ applyFireworkEffect(meta); // Paper - Expose firework item directly - - // Copied from EntityFireworks constructor, update firework lifetime/power - this.getHandle().lifetime = 10 * (1 + meta.getPower()) + this.random.nextInt(6) + this.random.nextInt(7); -@@ -136,4 +138,46 @@ public class CraftFirework extends CraftProjectile implements Firework { - return getHandle().spawningEntity; - } - // Paper end -+ // Paper start - Expose firework item directly + manually setting flight -+ @Override -+ public org.bukkit.inventory.ItemStack getItem() { -+ return CraftItemStack.asBukkitCopy(this.getHandle().getItem()); -+ } -+ -+ @Override -+ public void setItem(org.bukkit.inventory.ItemStack itemStack) { -+ FireworkMeta meta = getFireworkMeta(); -+ ItemStack nmsItem = itemStack == null ? FireworkRocketEntity.getDefaultItem() : CraftItemStack.asNMSCopy(itemStack); -+ this.getHandle().getEntityData().set(FireworkRocketEntity.DATA_ID_FIREWORKS_ITEM, nmsItem); -+ -+ applyFireworkEffect(meta); -+ } -+ -+ @Override -+ public int getTicksFlown() { -+ return this.getHandle().life; -+ } -+ -+ @Override -+ public void setTicksFlown(int ticks) { -+ this.getHandle().life = ticks; -+ } -+ -+ @Override -+ public int getTicksToDetonate() { -+ return this.getHandle().lifetime; -+ } -+ -+ @Override -+ public void setTicksToDetonate(int ticks) { -+ this.getHandle().lifetime = ticks; -+ } -+ -+ void applyFireworkEffect(FireworkMeta meta) { -+ ItemStack item = this.getHandle().getItem(); -+ CraftItemStack.applyMetaToItem(item, meta); -+ -+ this.getHandle().getEntityData().set(FireworkRocketEntity.DATA_ID_FIREWORKS_ITEM, item); -+ } -+ // Paper end - Expose firework item directly + manually setting flight - } -diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftFishHook.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftFishHook.java -index 6e2f91423371ead9890095cf4b1e2299c4dcba28..9d8f4b7176e60180565e3134a14ecf19060f2621 100644 ---- a/src/main/java/org/bukkit/craftbukkit/entity/CraftFishHook.java -+++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftFishHook.java -@@ -196,4 +196,42 @@ public class CraftFishHook extends CraftProjectile implements FishHook { - public HookState getState() { - return HookState.values()[this.getHandle().currentState.ordinal()]; - } -+ // Paper start - More FishHook API -+ @Override -+ public int getWaitTime() { -+ return this.getHandle().timeUntilLured; -+ } -+ -+ @Override -+ public void setWaitTime(int ticks) { -+ this.getHandle().timeUntilLured = ticks; -+ } -+ -+ @Override -+ public int getTimeUntilBite() { -+ return this.getHandle().timeUntilHooked; -+ } -+ -+ @Override -+ public void setTimeUntilBite(final int ticks) { -+ com.google.common.base.Preconditions.checkArgument(ticks >= 1, "Cannot set time until bite to less than 1 (%s<1)", ticks); -+ final FishingHook hook = this.getHandle(); -+ -+ // Reset the fish angle hook only when this call "enters" the fish into the lure stage. -+ final boolean alreadyInLuringPhase = hook.timeUntilHooked > 0 && hook.timeUntilLured <= 0; -+ if (!alreadyInLuringPhase) { -+ hook.fishAngle = net.minecraft.util.Mth.nextFloat(hook.random, hook.minLureAngle, hook.maxLureAngle); -+ hook.timeUntilLured = 0; -+ } -+ -+ hook.timeUntilHooked = ticks; -+ } -+ -+ @Override -+ public void resetFishingState() { -+ final FishingHook hook = this.getHandle(); -+ hook.resetTimeUntilLured(); -+ hook.timeUntilHooked = 0; // Reset time until hooked, will be repopulated once lured time is ticked down. -+ } -+ // Paper end - } -diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftLivingEntity.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftLivingEntity.java -index 0ed2910e64b6efdb4180c5bc23a146aced87c3d9..6350d4729fa3fbd9b15f1150cb4322f1c685044a 100644 ---- a/src/main/java/org/bukkit/craftbukkit/entity/CraftLivingEntity.java -+++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftLivingEntity.java -@@ -570,8 +570,15 @@ public class CraftLivingEntity extends CraftEntity implements LivingEntity { - } - - @Override -- @SuppressWarnings("unchecked") - public T launchProjectile(Class projectile, Vector velocity) { -+ // Paper start - launchProjectile consumer -+ return this.launchProjectile(projectile, velocity, null); -+ } -+ -+ @Override -+ @SuppressWarnings("unchecked") -+ public T launchProjectile(Class projectile, Vector velocity, java.util.function.Consumer function) { -+ // Paper end - launchProjectile consumer - Preconditions.checkState(!this.getHandle().generation, "Cannot launch projectile during world generation"); - - net.minecraft.world.level.Level world = ((CraftWorld) this.getWorld()).getHandle(); -@@ -597,7 +604,7 @@ public class CraftLivingEntity extends CraftEntity implements LivingEntity { - } else { - launch = new net.minecraft.world.entity.projectile.Arrow(world, this.getHandle(), new net.minecraft.world.item.ItemStack(net.minecraft.world.item.Items.ARROW), null); - } -- ((net.minecraft.world.entity.projectile.AbstractArrow) launch).shootFromRotation(this.getHandle(), this.getHandle().getXRot(), this.getHandle().getYRot(), 0.0F, 3.0F, 1.0F); // ItemBow -+ ((net.minecraft.world.entity.projectile.AbstractArrow) launch).shootFromRotation(this.getHandle(), this.getHandle().getXRot(), this.getHandle().getYRot(), 0.0F, Trident.class.isAssignableFrom(projectile) ? net.minecraft.world.item.TridentItem.SHOOT_POWER : 3.0F, 1.0F); // ItemBow // Paper - see TridentItem - } else if (ThrownPotion.class.isAssignableFrom(projectile)) { - if (LingeringPotion.class.isAssignableFrom(projectile)) { - launch = new net.minecraft.world.entity.projectile.ThrownPotion(world, this.getHandle()); -@@ -656,8 +663,26 @@ public class CraftLivingEntity extends CraftEntity implements LivingEntity { - } else if (Firework.class.isAssignableFrom(projectile)) { - Location location = this.getEyeLocation(); - -- launch = new FireworkRocketEntity(world, net.minecraft.world.item.ItemStack.EMPTY, this.getHandle()); -- launch.moveTo(location.getX(), location.getY(), location.getZ(), location.getYaw(), location.getPitch()); -+ // Paper start - see CrossbowItem -+ launch = new FireworkRocketEntity(world, FireworkRocketEntity.getDefaultItem(), this.getHandle(), location.getX(), location.getY() - 0.15F, location.getZ(), true); // Paper - pass correct default to rocket for data storage & see CrossbowItem for regular launch without elytra boost -+ -+ // Lifted from net.minecraft.world.item.ProjectileWeaponItem.shoot -+ float f2 = /* net.minecraft.world.item.enchantment.EnchantmentHelper.processProjectileSpread((ServerLevel) world, new net.minecraft.world.item.ItemStack(net.minecraft.world.item.Items.CROSSBOW), this.getHandle(), 0.0F); */ 0; // Just shortcut this to 0, no need to do any calculations on a non existing stack -+ int projectileSize = 1; -+ int i = 0; -+ -+ float f3 = projectileSize == 1 ? 0.0F : 2.0F * f2 / (float) (projectileSize - 1); -+ float f4 = (float) ((projectileSize - 1) % 2) * f3 / 2.0F; -+ float f5 = 1.0F; -+ float yaw = f4 + f5 * (float) ((i + 1) / 2) * f3; -+ -+ // Lifted from net.minecraft.world.item.CrossbowItem.shootProjectile -+ Vec3 vec3 = this.getHandle().getUpVector(1.0F); -+ org.joml.Quaternionf quaternionf = new org.joml.Quaternionf().setAngleAxis((double)(yaw * (float) (Math.PI / 180.0)), vec3.x, vec3.y, vec3.z); -+ Vec3 vec32 = this.getHandle().getViewVector(1.0F); -+ org.joml.Vector3f vector3f = vec32.toVector3f().rotate(quaternionf); -+ ((FireworkRocketEntity) launch).shoot((double)vector3f.x(), (double)vector3f.y(), (double)vector3f.z(), net.minecraft.world.item.CrossbowItem.FIREWORK_POWER, 1.0F); -+ // Paper end - } - - Preconditions.checkArgument(launch != null, "Projectile (%s) not supported", projectile.getName()); -@@ -665,6 +690,11 @@ public class CraftLivingEntity extends CraftEntity implements LivingEntity { - if (velocity != null) { - ((T) launch.getBukkitEntity()).setVelocity(velocity); - } -+ // Paper start - launchProjectile consumer -+ if (function != null) { -+ function.accept((T) launch.getBukkitEntity()); -+ } -+ // Paper end - launchProjectile consumer - - world.addFreshEntity(launch); - return (T) launch.getBukkitEntity(); -diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftLlamaSpit.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftLlamaSpit.java -index 70cbc6c668c60e9d608ca7013b72f9b916c05c2d..47633f05b4fab1dcabc2117e7645fe6d6949622a 100644 ---- a/src/main/java/org/bukkit/craftbukkit/entity/CraftLlamaSpit.java -+++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftLlamaSpit.java -@@ -20,13 +20,5 @@ public class CraftLlamaSpit extends AbstractProjectile implements LlamaSpit { - return "CraftLlamaSpit"; - } - -- @Override -- public ProjectileSource getShooter() { -- return (this.getHandle().getOwner() != null) ? (ProjectileSource) this.getHandle().getOwner().getBukkitEntity() : null; -- } -- -- @Override -- public void setShooter(ProjectileSource source) { -- this.getHandle().setOwner((source != null) ? ((CraftLivingEntity) source).getHandle() : null); -- } -+ // Paper - moved to AbstractProjectile - } -diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftProjectile.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftProjectile.java -index 696fdfa723aa896a67946f862d7c6890f7f7ab17..4f1fa7dec78970bdfc184d3c1f1632dc9d75a574 100644 ---- a/src/main/java/org/bukkit/craftbukkit/entity/CraftProjectile.java -+++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftProjectile.java -@@ -10,20 +10,7 @@ public abstract class CraftProjectile extends AbstractProjectile implements Proj - super(server, entity); - } - -- @Override -- public ProjectileSource getShooter() { -- return this.getHandle().projectileSource; -- } -- -- @Override -- public void setShooter(ProjectileSource shooter) { -- if (shooter instanceof CraftLivingEntity) { -- this.getHandle().setOwner((LivingEntity) ((CraftLivingEntity) shooter).entity); -- } else { -- this.getHandle().setOwner(null); -- } -- this.getHandle().projectileSource = shooter; -- } -+ // Paper - moved to AbstractProjectile - - @Override - public net.minecraft.world.entity.projectile.Projectile getHandle() { -diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftShulkerBullet.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftShulkerBullet.java -index d685d09cae5f862c0004f148298c800736d2139e..b3797a43eeee11cb7ae0774d61bd5f195d0aa3ad 100644 ---- a/src/main/java/org/bukkit/craftbukkit/entity/CraftShulkerBullet.java -+++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftShulkerBullet.java -@@ -12,31 +12,56 @@ public class CraftShulkerBullet extends AbstractProjectile implements ShulkerBul - super(server, entity); - } - -+ // Paper - moved to AbstractProjectile -+ -+ @Override -+ public org.bukkit.entity.Entity getTarget() { -+ return this.getHandle().getTarget() != null ? this.getHandle().getTarget().getBukkitEntity() : null; -+ } -+ - @Override -- public ProjectileSource getShooter() { -- return this.getHandle().projectileSource; -+ public void setTarget(org.bukkit.entity.Entity target) { -+ Preconditions.checkState(!this.getHandle().generation, "Cannot set target during world generation"); -+ -+ this.getHandle().setTarget(target == null ? null : ((CraftEntity) target).getHandle()); - } - - @Override -- public void setShooter(ProjectileSource shooter) { -- if (shooter instanceof Entity) { -- this.getHandle().setOwner(((CraftEntity) shooter).getHandle()); -- } else { -- this.getHandle().setOwner(null); -+ public org.bukkit.util.Vector getTargetDelta() { -+ net.minecraft.world.entity.projectile.ShulkerBullet bullet = this.getHandle(); -+ return new org.bukkit.util.Vector(bullet.targetDeltaX, bullet.targetDeltaY, bullet.targetDeltaZ); -+ } -+ -+ @Override -+ public void setTargetDelta(org.bukkit.util.Vector vector) { -+ net.minecraft.world.entity.projectile.ShulkerBullet bullet = this.getHandle(); -+ bullet.targetDeltaX = vector.getX(); -+ bullet.targetDeltaY = vector.getY(); -+ bullet.targetDeltaZ = vector.getZ(); -+ } -+ -+ @Override -+ public org.bukkit.block.BlockFace getCurrentMovementDirection() { -+ net.minecraft.core.Direction dir = this.getHandle().currentMoveDirection; -+ if (dir == null) { -+ return null; // random dir - } -- this.getHandle().projectileSource = shooter; -+ return org.bukkit.craftbukkit.block.CraftBlock.notchToBlockFace(dir); - } - - @Override -- public org.bukkit.entity.Entity getTarget() { -- return this.getHandle().getTarget() != null ? this.getHandle().getTarget().getBukkitEntity() : null; -+ public void setCurrentMovementDirection(org.bukkit.block.BlockFace movementDirection) { -+ this.getHandle().currentMoveDirection = org.bukkit.craftbukkit.block.CraftBlock.blockFaceToNotch(movementDirection); - } - - @Override -- public void setTarget(org.bukkit.entity.Entity target) { -- Preconditions.checkState(!this.getHandle().generation, "Cannot set target during world generation"); -+ public int getFlightSteps() { -+ return this.getHandle().flightSteps; -+ } - -- this.getHandle().setTarget(target == null ? null : ((CraftEntity) target).getHandle()); -+ @Override -+ public void setFlightSteps(int steps) { -+ this.getHandle().flightSteps = steps; - } - - @Override -diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftThrownPotion.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftThrownPotion.java -index d67a80161b3e7c1fe02a6ed9d341c00dc7c2847a..f6fa6f1ac50b757dd3bc9a8dee9f6085446182c8 100644 ---- a/src/main/java/org/bukkit/craftbukkit/entity/CraftThrownPotion.java -+++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftThrownPotion.java -@@ -36,11 +36,31 @@ public class CraftThrownPotion extends CraftThrowableProjectile implements Throw - @Override - public void setItem(ItemStack item) { - Preconditions.checkArgument(item != null, "ItemStack cannot be null"); -- Preconditions.checkArgument(item.getType() == Material.LINGERING_POTION || item.getType() == Material.SPLASH_POTION, "ItemStack material must be Material.LINGERING_POTION or Material.SPLASH_POTION but was Material.%s", item.getType()); -+ // Preconditions.checkArgument(item.getType() == Material.LINGERING_POTION || item.getType() == Material.SPLASH_POTION, "ItemStack material must be Material.LINGERING_POTION or Material.SPLASH_POTION but was Material.%s", item.getType()); // Paper - Projectile API -+ org.bukkit.inventory.meta.PotionMeta meta = (item.getType() == Material.LINGERING_POTION || item.getType() == Material.SPLASH_POTION) ? null : this.getPotionMeta(); // Paper - Projectile API - - this.getHandle().setItem(CraftItemStack.asNMSCopy(item)); -+ if (meta != null) this.setPotionMeta(meta); // Paper - Projectile API - } - -+ // Paper start - Projectile API -+ @Override -+ public org.bukkit.inventory.meta.PotionMeta getPotionMeta() { -+ return (org.bukkit.inventory.meta.PotionMeta) CraftItemStack.getItemMeta(this.getHandle().getItem(), org.bukkit.inventory.ItemType.SPLASH_POTION); -+ } -+ -+ @Override -+ public void setPotionMeta(org.bukkit.inventory.meta.PotionMeta meta) { -+ net.minecraft.world.item.ItemStack item = this.getHandle().getItem(); -+ CraftItemStack.applyMetaToItem(item, meta); -+ this.getHandle().setItem(item); // Reset item -+ } -+ -+ @Override -+ public void splash() { -+ this.getHandle().splash(null); -+ } -+ // Paper end - @Override - public net.minecraft.world.entity.projectile.ThrownPotion getHandle() { - return (net.minecraft.world.entity.projectile.ThrownPotion) this.entity; -diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftTrident.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftTrident.java -index e374b9f40eddca13b30855d25a2030f8df98138f..4fc893378fb0568ddcffc7593d66df6bfe23f659 100644 ---- a/src/main/java/org/bukkit/craftbukkit/entity/CraftTrident.java -+++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftTrident.java -@@ -53,5 +53,15 @@ public class CraftTrident extends CraftAbstractArrow implements Trident { - com.google.common.base.Preconditions.checkArgument(loyaltyLevel >= 0 && loyaltyLevel <= 127, "The loyalty level has to be between 0 and 127"); - this.getHandle().setLoyalty((byte) loyaltyLevel); - } -+ -+ @Override -+ public boolean hasDealtDamage() { -+ return this.getHandle().dealtDamage; -+ } -+ -+ @Override -+ public void setHasDealtDamage(boolean hasDealtDamage) { -+ this.getHandle().dealtDamage = hasDealtDamage; -+ } - // Paper end - } -diff --git a/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java b/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java -index 5fc6ef13cdc9df11b0fd2b0baf3cec862ccb5e37..eeb326a115020f571e96f3ec85408950b5eb56fb 100644 ---- a/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java -+++ b/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java -@@ -843,19 +843,19 @@ public class CraftEventFactory { - /** - * PotionSplashEvent - */ -- public static PotionSplashEvent callPotionSplashEvent(net.minecraft.world.entity.projectile.ThrownPotion potion, HitResult position, Map affectedEntities) { -+ public static PotionSplashEvent callPotionSplashEvent(net.minecraft.world.entity.projectile.ThrownPotion potion, @Nullable HitResult position, Map affectedEntities) { // Paper - nullable hitResult - ThrownPotion thrownPotion = (ThrownPotion) potion.getBukkitEntity(); - - Block hitBlock = null; - BlockFace hitFace = null; -- if (position.getType() == HitResult.Type.BLOCK) { -+ if (position != null && position.getType() == HitResult.Type.BLOCK) { // Paper - nullable hitResult - BlockHitResult positionBlock = (BlockHitResult) position; - hitBlock = CraftBlock.at(potion.level(), positionBlock.getBlockPos()); - hitFace = CraftBlock.notchToBlockFace(positionBlock.getDirection()); - } - - org.bukkit.entity.Entity hitEntity = null; -- if (position.getType() == HitResult.Type.ENTITY) { -+ if (position != null && position.getType() == HitResult.Type.ENTITY) { // Paper - nullable hitResult - hitEntity = ((EntityHitResult) position).getEntity().getBukkitEntity(); - } - -@@ -864,20 +864,20 @@ public class CraftEventFactory { - return event; - } - -- public static LingeringPotionSplashEvent callLingeringPotionSplashEvent(net.minecraft.world.entity.projectile.ThrownPotion potion, HitResult position, net.minecraft.world.entity.AreaEffectCloud cloud) { -+ public static LingeringPotionSplashEvent callLingeringPotionSplashEvent(net.minecraft.world.entity.projectile.ThrownPotion potion, @Nullable HitResult position, net.minecraft.world.entity.AreaEffectCloud cloud) { // Paper - nullable hitResult - ThrownPotion thrownPotion = (ThrownPotion) potion.getBukkitEntity(); - AreaEffectCloud effectCloud = (AreaEffectCloud) cloud.getBukkitEntity(); - - Block hitBlock = null; - BlockFace hitFace = null; -- if (position.getType() == HitResult.Type.BLOCK) { -+ if (position != null && position.getType() == HitResult.Type.BLOCK) { // Paper - BlockHitResult positionBlock = (BlockHitResult) position; - hitBlock = CraftBlock.at(potion.level(), positionBlock.getBlockPos()); - hitFace = CraftBlock.notchToBlockFace(positionBlock.getDirection()); - } - - org.bukkit.entity.Entity hitEntity = null; -- if (position.getType() == HitResult.Type.ENTITY) { -+ if (position != null && position.getType() == HitResult.Type.ENTITY) { // Paper - hitEntity = ((EntityHitResult) position).getEntity().getBukkitEntity(); - } - -diff --git a/src/main/java/org/bukkit/craftbukkit/inventory/CraftItemStack.java b/src/main/java/org/bukkit/craftbukkit/inventory/CraftItemStack.java -index e8a455eb5e17bcfcae3f03664f2b47773fbdf37e..08178a88ba7d0881a6c2843eef24a846cf07adb4 100644 ---- a/src/main/java/org/bukkit/craftbukkit/inventory/CraftItemStack.java -+++ b/src/main/java/org/bukkit/craftbukkit/inventory/CraftItemStack.java -@@ -329,12 +329,23 @@ public final class CraftItemStack extends ItemStack { - public ItemMeta getItemMeta() { - return CraftItemStack.getItemMeta(this.handle); - } -+ // Paper start -+ public static void applyMetaToItem(net.minecraft.world.item.ItemStack itemStack, ItemMeta itemMeta) { -+ final CraftMetaItem.Applicator tag = new CraftMetaItem.Applicator(); -+ ((CraftMetaItem) itemMeta).applyToItem(tag); -+ itemStack.applyComponents(tag.build()); -+ } - - public static ItemMeta getItemMeta(net.minecraft.world.item.ItemStack item) { -+ return getItemMeta(item, null); -+ } -+ public static ItemMeta getItemMeta(net.minecraft.world.item.ItemStack item, org.bukkit.inventory.ItemType metaForType) { -+ // Paper end - if (!CraftItemStack.hasItemMeta(item)) { - return CraftItemFactory.instance().getItemMeta(CraftItemStack.getType(item)); - } - -+ if (metaForType != null) { return ((CraftItemType) metaForType).getItemMeta(item); } // Paper - return ((CraftItemType) CraftItemType.minecraftToBukkitNew(item.getItem())).getItemMeta(item); - } - -diff --git a/src/main/java/org/bukkit/craftbukkit/projectiles/CraftBlockProjectileSource.java b/src/main/java/org/bukkit/craftbukkit/projectiles/CraftBlockProjectileSource.java -index 93b9e6b32798e32026492fa3f80de9d4a7b3d9a5..d2329ebf12dbea922fab481cd6822b36682f3461 100644 ---- a/src/main/java/org/bukkit/craftbukkit/projectiles/CraftBlockProjectileSource.java -+++ b/src/main/java/org/bukkit/craftbukkit/projectiles/CraftBlockProjectileSource.java -@@ -55,7 +55,15 @@ public class CraftBlockProjectileSource implements BlockProjectileSource { - - @Override - public T launchProjectile(Class projectile, Vector velocity) { -+ // Paper start - launchProjectile consumer -+ return this.launchProjectile(projectile, velocity, null); -+ } -+ -+ @Override -+ public T launchProjectile(Class projectile, Vector velocity, java.util.function.Consumer function) { -+ // Paper end - launchProjectile consumer - Preconditions.checkArgument(this.getBlock().getType() == Material.DISPENSER, "Block is no longer dispenser"); -+ - // Copied from BlockDispenser.dispense() - BlockSource sourceblock = new BlockSource((ServerLevel) this.dispenserBlock.getLevel(), this.dispenserBlock.getBlockPos(), this.dispenserBlock.getBlockState(), this.dispenserBlock); - // Copied from DispenseBehaviorProjectile -@@ -67,7 +75,7 @@ public class CraftBlockProjectileSource implements BlockProjectileSource { - item = Items.SNOWBALL; - } else if (Egg.class.isAssignableFrom(projectile)) { - item = Items.EGG; -- } else if (EnderPearl.class.isAssignableFrom(projectile)) { -+ } else if (false && EnderPearl.class.isAssignableFrom(projectile)) { // Paper - more projectile API - disallow enderpearl, it is not a projectile item - item = Items.ENDER_PEARL; - } else if (ThrownExpBottle.class.isAssignableFrom(projectile)) { - item = Items.EXPERIENCE_BOTTLE; -@@ -82,20 +90,20 @@ public class CraftBlockProjectileSource implements BlockProjectileSource { - item = Items.TIPPED_ARROW; - } else if (SpectralArrow.class.isAssignableFrom(projectile)) { - item = Items.SPECTRAL_ARROW; -- } else { -+ } else if (org.bukkit.entity.Arrow.class.isAssignableFrom(projectile)) { // Paper - more projectile API - disallow trident - item = Items.ARROW; - } - } else if (Fireball.class.isAssignableFrom(projectile)) { -- if (AbstractWindCharge.class.isAssignableFrom(projectile)) { -+ if (org.bukkit.entity.WindCharge.class.isAssignableFrom(projectile)) { // Paper - more projectile API - only allow wind charge not breeze wind charge - item = Items.WIND_CHARGE; -- } else { -+ } else if (org.bukkit.entity.SmallFireball.class.isAssignableFrom(projectile)) { // Paper - more projectile API - only allow firing fire charges. - item = Items.FIRE_CHARGE; - } - } else if (Firework.class.isAssignableFrom(projectile)) { - item = Items.FIREWORK_ROCKET; - } - -- Preconditions.checkArgument(item instanceof ProjectileItem, "Projectile not supported"); -+ Preconditions.checkArgument(item instanceof ProjectileItem, "Projectile '%s' not supported", projectile.getSimpleName()); // Paper - more projectile API - include simple name in exception - - ItemStack itemstack = new ItemStack(item); - ProjectileItem projectileItem = (ProjectileItem) item; -@@ -104,7 +112,7 @@ public class CraftBlockProjectileSource implements BlockProjectileSource { - Position iposition = dispenseConfig.positionFunction().getDispensePosition(sourceblock, enumdirection); - net.minecraft.world.entity.projectile.Projectile launch = projectileItem.asProjectile(world, iposition, itemstack, enumdirection); - -- if (Fireball.class.isAssignableFrom(projectile)) { -+ if (false && Fireball.class.isAssignableFrom(projectile)) { // Paper - more project API - dispensers cannot launch anything but fire charges. - AbstractHurtingProjectile customFireball = null; - if (WitherSkull.class.isAssignableFrom(projectile)) { - launch = customFireball = EntityType.WITHER_SKULL.create(world); -@@ -129,7 +137,7 @@ public class CraftBlockProjectileSource implements BlockProjectileSource { - } - } - -- if (launch instanceof net.minecraft.world.entity.projectile.AbstractArrow arrow) { -+ if (false && launch instanceof net.minecraft.world.entity.projectile.AbstractArrow arrow) { // Paper - more projectile API - this is set by the respective ArrowItem when constructing the projectile - arrow.pickup = net.minecraft.world.entity.projectile.AbstractArrow.Pickup.ALLOWED; - } - launch.projectileSource = this; -@@ -138,6 +146,11 @@ public class CraftBlockProjectileSource implements BlockProjectileSource { - if (velocity != null) { - ((T) launch.getBukkitEntity()).setVelocity(velocity); - } -+ // Paper start -+ if (function != null) { -+ function.accept((T) launch.getBukkitEntity()); -+ } -+ // Paper end - - world.addFreshEntity(launch); - return (T) launch.getBukkitEntity(); diff --git a/patches/server/0675-Fix-saving-in-unloadWorld.patch b/patches/server/0675-Fix-saving-in-unloadWorld.patch new file mode 100644 index 000000000000..a715afca0ee2 --- /dev/null +++ b/patches/server/0675-Fix-saving-in-unloadWorld.patch @@ -0,0 +1,20 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Philip Kelley +Date: Wed, 16 Mar 2022 12:05:59 +0000 +Subject: [PATCH] Fix saving in unloadWorld + +Change savingDisabled to false to ensure ServerLevel's saving logic gets called when unloadWorld is called with save = true + +diff --git a/src/main/java/org/bukkit/craftbukkit/CraftServer.java b/src/main/java/org/bukkit/craftbukkit/CraftServer.java +index 3b48a8107bf0b4326b86ee08bb54341825be81c1..a504a6458423a997e703e95356dd2058d6c164e2 100644 +--- a/src/main/java/org/bukkit/craftbukkit/CraftServer.java ++++ b/src/main/java/org/bukkit/craftbukkit/CraftServer.java +@@ -1385,7 +1385,7 @@ public final class CraftServer implements Server { + + try { + if (save) { +- handle.save(null, true, true); ++ handle.save(null, true, false); // Paper - Fix saving in unloadWorld + } + + handle.getChunkSource().close(save); diff --git a/patches/server/0675-Fix-swamp-hut-cat-generation-deadlock.patch b/patches/server/0675-Fix-swamp-hut-cat-generation-deadlock.patch deleted file mode 100644 index 6aa68bdd5ceb..000000000000 --- a/patches/server/0675-Fix-swamp-hut-cat-generation-deadlock.patch +++ /dev/null @@ -1,64 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Spottedleaf -Date: Sat, 12 Mar 2022 06:31:13 -0800 -Subject: [PATCH] Fix swamp hut cat generation deadlock - -The worldgen thread will attempt to get structure references -via the world's getChunkAt method, which is fine if the gen is -not cancelled - but if the chunk was unloaded, the call will block -indefinitely. Instead of using the world state, we use the already -supplied ServerLevelAccessor which will always have the chunk available. - -diff --git a/src/main/java/net/minecraft/world/entity/animal/Cat.java b/src/main/java/net/minecraft/world/entity/animal/Cat.java -index f0e5428394f8de8f7ac05b8cbc34826d153eeaef..d44807c16712afd37efdbf434d1afb12a7c3d343 100644 ---- a/src/main/java/net/minecraft/world/entity/animal/Cat.java -+++ b/src/main/java/net/minecraft/world/entity/animal/Cat.java -@@ -357,7 +357,7 @@ public class Cat extends TamableAnimal implements VariantHolder startsForStructure(ChunkPos pos, Predicate predicate) { -- Map map = this.level.getChunk(pos.x, pos.z, ChunkStatus.STRUCTURE_REFERENCES).getAllReferences(); -+ // Paper start - Fix swamp hut cat generation deadlock -+ return this.startsForStructure(pos, predicate, null); -+ } -+ public List startsForStructure(ChunkPos pos, Predicate predicate, @Nullable ServerLevelAccessor levelAccessor) { -+ Map map = (levelAccessor == null ? this.level : levelAccessor).getChunk(pos.x, pos.z, ChunkStatus.STRUCTURE_REFERENCES).getAllReferences(); -+ // Paper end - Fix swamp hut cat generation deadlock - Builder builder = ImmutableList.builder(); - - for (Entry entry : map.entrySet()) { -@@ -116,10 +121,20 @@ public class StructureManager { - } - - public StructureStart getStructureWithPieceAt(BlockPos pos, Predicate> predicate) { -+ // Paper start - Fix swamp hut cat generation deadlock -+ return this.getStructureWithPieceAt(pos, predicate, null); -+ } -+ -+ public StructureStart getStructureWithPieceAt(BlockPos pos, TagKey tag, @Nullable ServerLevelAccessor levelAccessor) { -+ return this.getStructureWithPieceAt(pos, structure -> structure.is(tag), levelAccessor); -+ } -+ -+ public StructureStart getStructureWithPieceAt(BlockPos pos, Predicate> predicate, @Nullable ServerLevelAccessor levelAccessor) { -+ // Paper end - Fix swamp hut cat generation deadlock - Registry registry = this.registryAccess().registryOrThrow(Registries.STRUCTURE); - - for (StructureStart structureStart : this.startsForStructure( -- new ChunkPos(pos), structure -> registry.getHolder(registry.getId(structure)).map(predicate::test).orElse(false) -+ new ChunkPos(pos), structure -> registry.getHolder(registry.getId(structure)).map(predicate::test).orElse(false), levelAccessor // Paper - Fix swamp hut cat generation deadlock - )) { - if (this.structureHasPieceAt(pos, structureStart)) { - return structureStart; diff --git a/patches/server/0676-Buffer-OOB-setBlock-calls.patch b/patches/server/0676-Buffer-OOB-setBlock-calls.patch new file mode 100644 index 000000000000..1b7ba291c41e --- /dev/null +++ b/patches/server/0676-Buffer-OOB-setBlock-calls.patch @@ -0,0 +1,42 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Shane Freeder +Date: Sat, 19 Mar 2022 12:12:22 +0000 +Subject: [PATCH] Buffer OOB setBlock calls + +lets debug mode throw a trace in order to potentially see where +such calls are cascading from easier, but, generally, if you see one setBlock +call, you're gonna see more, and this just potentially causes a flood of logs +which can cause issues for slower terminals, etc. + +We can limit the flood by just allowing one for a single gen region, +we'll also only gen a trace for the first one, I see no real pressing need +to generate more, given that that would *massively* negate this patch otherwise + +diff --git a/src/main/java/net/minecraft/server/level/WorldGenRegion.java b/src/main/java/net/minecraft/server/level/WorldGenRegion.java +index f1725ef766c35aa623ace58fe8bf31fc9b2bb6b3..5bf438bb58833c1df3620e82d3d2b90207366372 100644 +--- a/src/main/java/net/minecraft/server/level/WorldGenRegion.java ++++ b/src/main/java/net/minecraft/server/level/WorldGenRegion.java +@@ -284,6 +284,7 @@ public class WorldGenRegion implements WorldGenLevel { + } + } + ++ private boolean hasSetFarWarned = false; // Paper - Buffer OOB setBlock calls + @Override + public boolean ensureCanWrite(BlockPos pos) { + int i = SectionPos.blockToSectionCoord(pos.getX()); +@@ -303,7 +304,15 @@ public class WorldGenRegion implements WorldGenLevel { + + return true; + } else { ++ // Paper start - Buffer OOB setBlock calls ++ if (!hasSetFarWarned) { + Util.logAndPauseIfInIde("Detected setBlock in a far chunk [" + i + ", " + j + "], pos: " + String.valueOf(pos) + ", status: " + String.valueOf(this.generatingStep.targetStatus()) + (this.currentlyGenerating == null ? "" : ", currently generating: " + (String) this.currentlyGenerating.get())); ++ hasSetFarWarned = true; ++ if (this.getServer() != null && this.getServer().isDebugging()) { ++ io.papermc.paper.util.TraceUtil.dumpTraceForThread("far setBlock call"); ++ } ++ } ++ // Paper end - Buffer OOB setBlock calls + return false; + } + } diff --git a/patches/server/0676-Don-t-allow-vehicle-movement-from-players-while-tele.patch b/patches/server/0676-Don-t-allow-vehicle-movement-from-players-while-tele.patch deleted file mode 100644 index 0ceb6b0ce1ee..000000000000 --- a/patches/server/0676-Don-t-allow-vehicle-movement-from-players-while-tele.patch +++ /dev/null @@ -1,24 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Spottedleaf -Date: Mon, 14 Mar 2022 12:35:37 -0700 -Subject: [PATCH] Don't allow vehicle movement from players while teleporting - -Bring the vehicle move packet behavior in line with the -regular player move packet. - -diff --git a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java -index 1e806fd16ca5f90bdb4f71dea008d7936eb82562..76c9feb5e1389eb929571de74ececd4a4f384453 100644 ---- a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java -+++ b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java -@@ -484,6 +484,11 @@ public class ServerGamePacketListenerImpl extends ServerCommonPacketListenerImpl - this.disconnect((Component) Component.translatable("multiplayer.disconnect.invalid_vehicle_movement"), org.bukkit.event.player.PlayerKickEvent.Cause.INVALID_VEHICLE_MOVEMENT); // Paper - kick event cause - } else if (!this.updateAwaitingTeleport()) { - Entity entity = this.player.getRootVehicle(); -+ // Paper start - Don't allow vehicle movement from players while teleporting -+ if (this.awaitingPositionFromClient != null || this.player.isImmobile() || entity.isRemoved()) { -+ return; -+ } -+ // Paper end - Don't allow vehicle movement from players while teleporting - - if (entity != this.player && entity.getControllingPassenger() == this.player && entity == this.lastVehicle) { - ServerLevel worldserver = this.player.serverLevel(); diff --git a/patches/server/0677-Add-TameableDeathMessageEvent.patch b/patches/server/0677-Add-TameableDeathMessageEvent.patch new file mode 100644 index 000000000000..641b0b0e3757 --- /dev/null +++ b/patches/server/0677-Add-TameableDeathMessageEvent.patch @@ -0,0 +1,24 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Owen1212055 <23108066+Owen1212055@users.noreply.github.com> +Date: Mon, 21 Jun 2021 21:24:45 -0400 +Subject: [PATCH] Add TameableDeathMessageEvent + + +diff --git a/src/main/java/net/minecraft/world/entity/TamableAnimal.java b/src/main/java/net/minecraft/world/entity/TamableAnimal.java +index cd565d1a8dab8d45196e4d29cab3d93a3ca619eb..749ae54ee42229cb32ec5280bc59a88f74fde197 100644 +--- a/src/main/java/net/minecraft/world/entity/TamableAnimal.java ++++ b/src/main/java/net/minecraft/world/entity/TamableAnimal.java +@@ -250,7 +250,12 @@ public abstract class TamableAnimal extends Animal implements OwnableEntity { + if (entityliving instanceof ServerPlayer) { + ServerPlayer entityplayer = (ServerPlayer) entityliving; + +- entityplayer.sendSystemMessage(this.getCombatTracker().getDeathMessage()); ++ // Paper start - Add TameableDeathMessageEvent ++ io.papermc.paper.event.entity.TameableDeathMessageEvent event = new io.papermc.paper.event.entity.TameableDeathMessageEvent((org.bukkit.entity.Tameable) getBukkitEntity(), io.papermc.paper.adventure.PaperAdventure.asAdventure(this.getCombatTracker().getDeathMessage())); ++ if (event.callEvent()) { ++ entityplayer.sendSystemMessage(io.papermc.paper.adventure.PaperAdventure.asVanilla(event.deathMessage())); ++ } ++ // Paper end - Add TameableDeathMessageEvent + } + } + } diff --git a/patches/server/0677-Implement-getComputedBiome-API.patch b/patches/server/0677-Implement-getComputedBiome-API.patch deleted file mode 100644 index 41624673a765..000000000000 --- a/patches/server/0677-Implement-getComputedBiome-API.patch +++ /dev/null @@ -1,61 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Jason Penilla <11360596+jpenilla@users.noreply.github.com> -Date: Mon, 14 Mar 2022 22:46:05 -0700 -Subject: [PATCH] Implement getComputedBiome API - - -diff --git a/src/main/java/org/bukkit/craftbukkit/CraftRegionAccessor.java b/src/main/java/org/bukkit/craftbukkit/CraftRegionAccessor.java -index 5d137f8c42356359701e1bea7525f82c018b502c..f6dd8665b9f6e2a8ff396deba8a021e695bedf7e 100644 ---- a/src/main/java/org/bukkit/craftbukkit/CraftRegionAccessor.java -+++ b/src/main/java/org/bukkit/craftbukkit/CraftRegionAccessor.java -@@ -77,6 +77,13 @@ public abstract class CraftRegionAccessor implements RegionAccessor { - return CraftBiome.minecraftHolderToBukkit(this.getHandle().getNoiseBiome(x >> 2, y >> 2, z >> 2)); - } - -+ // Paper start -+ @Override -+ public Biome getComputedBiome(int x, int y, int z) { -+ return CraftBiome.minecraftHolderToBukkit(this.getHandle().getBiome(new BlockPos(x, y, z))); -+ } -+ // Paper end -+ - @Override - public void setBiome(Location location, Biome biome) { - this.setBiome(location.getBlockX(), location.getBlockY(), location.getBlockZ(), biome); -diff --git a/src/main/java/org/bukkit/craftbukkit/block/CraftBlock.java b/src/main/java/org/bukkit/craftbukkit/block/CraftBlock.java -index 068b3735b6c50a7a2053c7dc39856f728fb7218a..6d10396347b69d9243ab902ecc68ede93fa17b7d 100644 ---- a/src/main/java/org/bukkit/craftbukkit/block/CraftBlock.java -+++ b/src/main/java/org/bukkit/craftbukkit/block/CraftBlock.java -@@ -340,6 +340,13 @@ public class CraftBlock implements Block { - return this.getWorld().getBiome(this.getX(), this.getY(), this.getZ()); - } - -+ // Paper start -+ @Override -+ public Biome getComputedBiome() { -+ return this.getWorld().getComputedBiome(this.getX(), this.getY(), this.getZ()); -+ } -+ // Paper end -+ - @Override - public void setBiome(Biome bio) { - this.getWorld().setBiome(this.getX(), this.getY(), this.getZ(), bio); -diff --git a/src/main/java/org/bukkit/craftbukkit/generator/CraftLimitedRegion.java b/src/main/java/org/bukkit/craftbukkit/generator/CraftLimitedRegion.java -index 7d6ee60b1d3e023e1eabc59eb591c3ae3d8ac043..eaa9ba70b0b80d86eb376a0641420093a7c9dfdb 100644 ---- a/src/main/java/org/bukkit/craftbukkit/generator/CraftLimitedRegion.java -+++ b/src/main/java/org/bukkit/craftbukkit/generator/CraftLimitedRegion.java -@@ -165,6 +165,14 @@ public class CraftLimitedRegion extends CraftRegionAccessor implements LimitedRe - return super.getBiome(x, y, z); - } - -+ // Paper start -+ @Override -+ public Biome getComputedBiome(int x, int y, int z) { -+ Preconditions.checkArgument(this.isInRegion(x, y, z), "Coordinates %s, %s, %s are not in the region", x, y, z); -+ return super.getComputedBiome(x, y, z); -+ } -+ // Paper end -+ - @Override - public void setBiome(int x, int y, int z, Holder biomeBase) { - Preconditions.checkArgument(this.isInRegion(x, y, z), "Coordinates %s, %s, %s are not in the region", x, y, z); diff --git a/patches/server/0678-Fix-new-block-data-for-EntityChangeBlockEvent.patch b/patches/server/0678-Fix-new-block-data-for-EntityChangeBlockEvent.patch new file mode 100644 index 000000000000..fce021dda2b9 --- /dev/null +++ b/patches/server/0678-Fix-new-block-data-for-EntityChangeBlockEvent.patch @@ -0,0 +1,215 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: SoSeDiK +Date: Mon, 21 Mar 2022 20:00:53 +0200 +Subject: [PATCH] Fix new block data for EntityChangeBlockEvent + +Also standardizes how to handle EntityChangeBlockEvent before a removeBlock or destroyBlock +call. Always use 'state.getFluidState().createLegacyBlock()' to get the new state instead of +just using the 'air' state. + +Also fixes the new block data for EntityBreakDoorEvent (a sub-event from +EntityChangeBlockEvent) + +Co-authored-by: Lulu13022002 <41980282+Lulu13022002@users.noreply.github.com> +Co-authored-by: Jake Potrebic + +diff --git a/src/main/java/net/minecraft/world/entity/ai/behavior/HarvestFarmland.java b/src/main/java/net/minecraft/world/entity/ai/behavior/HarvestFarmland.java +index 6a7052cd6ec88ed4aaea2fef0ebc750060d1dec0..2ade08d1466660ee1787fa97908002ef56389712 100644 +--- a/src/main/java/net/minecraft/world/entity/ai/behavior/HarvestFarmland.java ++++ b/src/main/java/net/minecraft/world/entity/ai/behavior/HarvestFarmland.java +@@ -108,7 +108,7 @@ public class HarvestFarmland extends Behavior { + Block block1 = world.getBlockState(this.aboveFarmlandPos.below()).getBlock(); + + if (block instanceof CropBlock && ((CropBlock) block).isMaxAge(iblockdata)) { +- if (CraftEventFactory.callEntityChangeBlockEvent(entity, this.aboveFarmlandPos, Blocks.AIR.defaultBlockState())) { // CraftBukkit ++ if (CraftEventFactory.callEntityChangeBlockEvent(entity, this.aboveFarmlandPos, iblockdata.getFluidState().createLegacyBlock())) { // CraftBukkit // Paper - fix wrong block state + world.destroyBlock(this.aboveFarmlandPos, true, entity); + } // CraftBukkit + } +diff --git a/src/main/java/net/minecraft/world/entity/ai/goal/BreakDoorGoal.java b/src/main/java/net/minecraft/world/entity/ai/goal/BreakDoorGoal.java +index e2aa6d46375cbc4f4b694888c4f9750eb26e4940..6827426e6e9706909265f84bf97b5fa7105a7fea 100644 +--- a/src/main/java/net/minecraft/world/entity/ai/goal/BreakDoorGoal.java ++++ b/src/main/java/net/minecraft/world/entity/ai/goal/BreakDoorGoal.java +@@ -73,7 +73,7 @@ public class BreakDoorGoal extends DoorInteractGoal { + + if (this.breakTime == this.getDoorBreakTime() && this.isValidDifficulty(this.mob.level().getDifficulty())) { + // CraftBukkit start +- if (org.bukkit.craftbukkit.event.CraftEventFactory.callEntityBreakDoorEvent(this.mob, this.doorPos).isCancelled()) { ++ if (org.bukkit.craftbukkit.event.CraftEventFactory.callEntityBreakDoorEvent(this.mob, this.doorPos, this.mob.level().getFluidState(this.doorPos).createLegacyBlock()).isCancelled()) { // Paper - fix wrong block state + this.start(); + return; + } +diff --git a/src/main/java/net/minecraft/world/entity/ai/goal/EatBlockGoal.java b/src/main/java/net/minecraft/world/entity/ai/goal/EatBlockGoal.java +index dc3602002e60d64cbbe9504c22282a9e65da2c9d..9e6f946e6d2878aa3fa8abe0f6fa4770d18676d3 100644 +--- a/src/main/java/net/minecraft/world/entity/ai/goal/EatBlockGoal.java ++++ b/src/main/java/net/minecraft/world/entity/ai/goal/EatBlockGoal.java +@@ -67,8 +67,9 @@ public class EatBlockGoal extends Goal { + if (this.eatAnimationTick == this.adjustedTickDelay(4)) { + BlockPos blockposition = this.mob.blockPosition(); + +- if (EatBlockGoal.IS_TALL_GRASS.test(this.level.getBlockState(blockposition))) { +- if (CraftEventFactory.callEntityChangeBlockEvent(this.mob, blockposition, Blocks.AIR.defaultBlockState(), !getServerLevel(this.level).getGameRules().getBoolean(GameRules.RULE_MOBGRIEFING))) { // CraftBukkit ++ final BlockState blockState = this.level.getBlockState(blockposition); // Paper - fix wrong block state ++ if (EatBlockGoal.IS_TALL_GRASS.test(blockState)) { // Paper - fix wrong block state ++ if (CraftEventFactory.callEntityChangeBlockEvent(this.mob, blockposition, blockState.getFluidState().createLegacyBlock(), !getServerLevel(this.level).getGameRules().getBoolean(GameRules.RULE_MOBGRIEFING))) { // CraftBukkit // Paper - fix wrong block state + this.level.destroyBlock(blockposition, false); + } + +@@ -77,7 +78,7 @@ public class EatBlockGoal extends Goal { + BlockPos blockposition1 = blockposition.below(); + + if (this.level.getBlockState(blockposition1).is(Blocks.GRASS_BLOCK)) { +- if (CraftEventFactory.callEntityChangeBlockEvent(this.mob, blockposition1, Blocks.AIR.defaultBlockState(), !getServerLevel(this.level).getGameRules().getBoolean(GameRules.RULE_MOBGRIEFING))) { // CraftBukkit ++ if (CraftEventFactory.callEntityChangeBlockEvent(this.mob, blockposition1, Blocks.DIRT.defaultBlockState(), !getServerLevel(this.level).getGameRules().getBoolean(GameRules.RULE_MOBGRIEFING))) { // CraftBukkit // Paper - Fix wrong block state + this.level.levelEvent(2001, blockposition1, Block.getId(Blocks.GRASS_BLOCK.defaultBlockState())); + this.level.setBlock(blockposition1, Blocks.DIRT.defaultBlockState(), 2); + } +diff --git a/src/main/java/net/minecraft/world/entity/animal/Rabbit.java b/src/main/java/net/minecraft/world/entity/animal/Rabbit.java +index a2bbb79f2999e719b8c80d33e530391ec3d1d2d2..8cc6022507c97af62fb2b4455198bc35744137c9 100644 +--- a/src/main/java/net/minecraft/world/entity/animal/Rabbit.java ++++ b/src/main/java/net/minecraft/world/entity/animal/Rabbit.java +@@ -580,7 +580,7 @@ public class Rabbit extends Animal implements VariantHolder { + + if (i == 0) { + // CraftBukkit start +- if (!CraftEventFactory.callEntityChangeBlockEvent(this.rabbit, blockposition, Blocks.AIR.defaultBlockState())) { ++ if (!CraftEventFactory.callEntityChangeBlockEvent(this.rabbit, blockposition, iblockdata.getFluidState().createLegacyBlock())) { // Paper - fix wrong block state + return; + } + // CraftBukkit end +diff --git a/src/main/java/net/minecraft/world/entity/boss/wither/WitherBoss.java b/src/main/java/net/minecraft/world/entity/boss/wither/WitherBoss.java +index b145023308e6a2823d83db97ff2d79c38b709ef9..e6b18d7f8922cb42acb9e40bef2f71a56aea8646 100644 +--- a/src/main/java/net/minecraft/world/entity/boss/wither/WitherBoss.java ++++ b/src/main/java/net/minecraft/world/entity/boss/wither/WitherBoss.java +@@ -375,7 +375,7 @@ public class WitherBoss extends Monster implements RangedAttackMob { + + if (WitherBoss.canDestroy(iblockdata)) { + // CraftBukkit start +- if (!CraftEventFactory.callEntityChangeBlockEvent(this, blockposition, Blocks.AIR.defaultBlockState())) { ++ if (!CraftEventFactory.callEntityChangeBlockEvent(this, blockposition, iblockdata.getFluidState().createLegacyBlock())) { // Paper - fix wrong block state + continue; + } + // CraftBukkit end +diff --git a/src/main/java/net/minecraft/world/entity/monster/EnderMan.java b/src/main/java/net/minecraft/world/entity/monster/EnderMan.java +index 0e8349aa6cb23860a4dd884e730ac8f22d422205..48dcd2bc12ce1d08cc5195bff5460dc0dd9902d3 100644 +--- a/src/main/java/net/minecraft/world/entity/monster/EnderMan.java ++++ b/src/main/java/net/minecraft/world/entity/monster/EnderMan.java +@@ -552,7 +552,7 @@ public class EnderMan extends Monster implements NeutralMob { + boolean flag = movingobjectpositionblock.getBlockPos().equals(blockposition); + + if (iblockdata.is(BlockTags.ENDERMAN_HOLDABLE) && flag) { +- if (CraftEventFactory.callEntityChangeBlockEvent(this.enderman, blockposition, Blocks.AIR.defaultBlockState())) { // CraftBukkit - Place event ++ if (CraftEventFactory.callEntityChangeBlockEvent(this.enderman, blockposition, iblockdata.getFluidState().createLegacyBlock())) { // CraftBukkit - Place event // Paper - fix wrong block state + world.removeBlock(blockposition, false); + world.gameEvent((Holder) GameEvent.BLOCK_DESTROY, blockposition, GameEvent.Context.of(this.enderman, iblockdata)); + this.enderman.setCarriedBlock(iblockdata.getBlock().defaultBlockState()); +diff --git a/src/main/java/net/minecraft/world/entity/monster/Ravager.java b/src/main/java/net/minecraft/world/entity/monster/Ravager.java +index c661ae4e5c07494c7de852cc8d01f0f9839c1590..c96fbfe448b3e7b722a8db0e1688276776abd94e 100644 +--- a/src/main/java/net/minecraft/world/entity/monster/Ravager.java ++++ b/src/main/java/net/minecraft/world/entity/monster/Ravager.java +@@ -165,7 +165,7 @@ public class Ravager extends Raider { + + if (block instanceof LeavesBlock) { + // CraftBukkit start +- if (!CraftEventFactory.callEntityChangeBlockEvent(this, blockposition, net.minecraft.world.level.block.Blocks.AIR.defaultBlockState())) { ++ if (!CraftEventFactory.callEntityChangeBlockEvent(this, blockposition, iblockdata.getFluidState().createLegacyBlock())) { // Paper - fix wrong block state + continue; + } + // CraftBukkit end +diff --git a/src/main/java/net/minecraft/world/entity/monster/Silverfish.java b/src/main/java/net/minecraft/world/entity/monster/Silverfish.java +index 52d8ea3e40cdb01eab428f5d3d945c0c9f6088ce..ff65cb8ea5233f2dd159f42ad53bc9d300cd604f 100644 +--- a/src/main/java/net/minecraft/world/entity/monster/Silverfish.java ++++ b/src/main/java/net/minecraft/world/entity/monster/Silverfish.java +@@ -165,7 +165,8 @@ public class Silverfish extends Monster { + + if (block instanceof InfestedBlock) { + // CraftBukkit start +- if (!CraftEventFactory.callEntityChangeBlockEvent(this.silverfish, blockposition1, net.minecraft.world.level.block.Blocks.AIR.defaultBlockState())) { ++ BlockState afterState = getServerLevel(world).getGameRules().getBoolean(GameRules.RULE_MOBGRIEFING) ? iblockdata.getFluidState().createLegacyBlock() : ((InfestedBlock) block).hostStateByInfested(world.getBlockState(blockposition1)); // Paper - fix wrong block state ++ if (!CraftEventFactory.callEntityChangeBlockEvent(this.silverfish, blockposition1, afterState)) { // Paper - fix wrong block state + continue; + } + // CraftBukkit end +diff --git a/src/main/java/net/minecraft/world/entity/projectile/ThrownPotion.java b/src/main/java/net/minecraft/world/entity/projectile/ThrownPotion.java +index a486466040a646b8a5a5ff2430cdd25b95b7e20f..e78eef3b6fbcd657f9dd180df4cb2eeb55d0814f 100644 +--- a/src/main/java/net/minecraft/world/entity/projectile/ThrownPotion.java ++++ b/src/main/java/net/minecraft/world/entity/projectile/ThrownPotion.java +@@ -294,7 +294,7 @@ public class ThrownPotion extends ThrowableItemProjectile { + + if (iblockdata.is(BlockTags.FIRE)) { + // CraftBukkit start +- if (CraftEventFactory.callEntityChangeBlockEvent(this, pos, Blocks.AIR.defaultBlockState())) { ++ if (CraftEventFactory.callEntityChangeBlockEvent(this, pos, iblockdata.getFluidState().createLegacyBlock())) { // Paper - fix wrong block state + this.level().destroyBlock(pos, false, this); + } + // CraftBukkit end +diff --git a/src/main/java/net/minecraft/world/level/block/ChorusFlowerBlock.java b/src/main/java/net/minecraft/world/level/block/ChorusFlowerBlock.java +index fae574f8617bada96469a2eb1349eaa061f0450f..6d0d13e70a82c4db7848e1007f5b6d670dd5acad 100644 +--- a/src/main/java/net/minecraft/world/level/block/ChorusFlowerBlock.java ++++ b/src/main/java/net/minecraft/world/level/block/ChorusFlowerBlock.java +@@ -286,7 +286,7 @@ public class ChorusFlowerBlock extends Block { + if (world instanceof ServerLevel worldserver) { + if (projectile.mayInteract(worldserver, blockposition) && projectile.mayBreak(worldserver)) { + // CraftBukkit +- if (!CraftEventFactory.callEntityChangeBlockEvent(projectile, blockposition, Blocks.AIR.defaultBlockState())) { ++ if (!CraftEventFactory.callEntityChangeBlockEvent(projectile, blockposition, state.getFluidState().createLegacyBlock())) { // Paper - fix wrong block state + return; + } + // CraftBukkit end +diff --git a/src/main/java/net/minecraft/world/level/block/PointedDripstoneBlock.java b/src/main/java/net/minecraft/world/level/block/PointedDripstoneBlock.java +index 48503f5ba8bccb42ae3b5324a5f65f6c23a8e479..bd38a0a73d543a85bb5c6d50219f5438ce194df3 100644 +--- a/src/main/java/net/minecraft/world/level/block/PointedDripstoneBlock.java ++++ b/src/main/java/net/minecraft/world/level/block/PointedDripstoneBlock.java +@@ -137,7 +137,7 @@ public class PointedDripstoneBlock extends Block implements Fallable, SimpleWate + + if (projectile.mayInteract(worldserver, blockposition) && projectile.mayBreak(worldserver) && projectile instanceof ThrownTrident && projectile.getDeltaMovement().length() > 0.6D) { + // CraftBukkit start +- if (!org.bukkit.craftbukkit.event.CraftEventFactory.callEntityChangeBlockEvent(projectile, blockposition, Blocks.AIR.defaultBlockState())) { ++ if (!org.bukkit.craftbukkit.event.CraftEventFactory.callEntityChangeBlockEvent(projectile, blockposition, state.getFluidState().createLegacyBlock())) { // Paper - fix wrong block state + return; + } + // CraftBukkit end +diff --git a/src/main/java/net/minecraft/world/level/block/TntBlock.java b/src/main/java/net/minecraft/world/level/block/TntBlock.java +index d256b0f3998028709334dd6c394d184f2c36efce..2cbe2053dd5d0bcdcd89de69762c77b400b8697a 100644 +--- a/src/main/java/net/minecraft/world/level/block/TntBlock.java ++++ b/src/main/java/net/minecraft/world/level/block/TntBlock.java +@@ -158,7 +158,7 @@ public class TntBlock extends Block { + + if (projectile.isOnFire() && projectile.mayInteract(worldserver, blockposition)) { + // CraftBukkit start +- if (!org.bukkit.craftbukkit.event.CraftEventFactory.callEntityChangeBlockEvent(projectile, blockposition, Blocks.AIR.defaultBlockState()) || !CraftEventFactory.callTNTPrimeEvent(world, blockposition, PrimeCause.PROJECTILE, projectile, null)) { ++ if (!org.bukkit.craftbukkit.event.CraftEventFactory.callEntityChangeBlockEvent(projectile, blockposition, state.getFluidState().createLegacyBlock()) || !CraftEventFactory.callTNTPrimeEvent(world, blockposition, PrimeCause.PROJECTILE, projectile, null)) { // Paper - fix wrong block state + return; + } + // CraftBukkit end +diff --git a/src/main/java/net/minecraft/world/level/block/WaterlilyBlock.java b/src/main/java/net/minecraft/world/level/block/WaterlilyBlock.java +index 674d710ff88db5eced9e017284d1b7ec7a4fe7cd..72320c6099a4b26235bab68570e7b7efad84740f 100644 +--- a/src/main/java/net/minecraft/world/level/block/WaterlilyBlock.java ++++ b/src/main/java/net/minecraft/world/level/block/WaterlilyBlock.java +@@ -37,7 +37,7 @@ public class WaterlilyBlock extends BushBlock { + super.entityInside(state, world, pos, entity); + if (world instanceof ServerLevel && entity instanceof AbstractBoat) { + // CraftBukkit start +- if (!CraftEventFactory.callEntityChangeBlockEvent(entity, pos, Blocks.AIR.defaultBlockState())) { ++ if (!CraftEventFactory.callEntityChangeBlockEvent(entity, pos, state.getFluidState().createLegacyBlock())) { // Paper - fix wrong block state + return; + } + // CraftBukkit end +diff --git a/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java b/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java +index e0418c0c36aa99ea43dbf39fa176ddf8855e0568..f54a4c263eeb4df40c72c20c3c480ce74718a718 100644 +--- a/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java ++++ b/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java +@@ -1378,11 +1378,11 @@ public class CraftEventFactory { + return event; + } + +- public static EntityBreakDoorEvent callEntityBreakDoorEvent(Entity entity, BlockPos pos) { ++ public static EntityBreakDoorEvent callEntityBreakDoorEvent(Entity entity, BlockPos pos, net.minecraft.world.level.block.state.BlockState newState) { // Paper + org.bukkit.entity.Entity entity1 = entity.getBukkitEntity(); + Block block = CraftBlock.at(entity.level(), pos); + +- EntityBreakDoorEvent event = new EntityBreakDoorEvent((LivingEntity) entity1, block); ++ EntityBreakDoorEvent event = new EntityBreakDoorEvent((LivingEntity) entity1, block, newState.createCraftBlockData()); // Paper + entity1.getServer().getPluginManager().callEvent(event); + + return event; diff --git a/patches/server/0678-Make-some-itemstacks-nonnull.patch b/patches/server/0678-Make-some-itemstacks-nonnull.patch deleted file mode 100644 index 4679d4a859ad..000000000000 --- a/patches/server/0678-Make-some-itemstacks-nonnull.patch +++ /dev/null @@ -1,28 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Jake Potrebic -Date: Tue, 15 Mar 2022 01:38:15 -0700 -Subject: [PATCH] Make some itemstacks nonnull - - -diff --git a/src/main/java/org/bukkit/craftbukkit/inventory/CraftInventoryPlayer.java b/src/main/java/org/bukkit/craftbukkit/inventory/CraftInventoryPlayer.java -index 972fe4237461f07f78b60845b2ebfefb06698ded..9d74577af071954e1e37201a96368c1360076209 100644 ---- a/src/main/java/org/bukkit/craftbukkit/inventory/CraftInventoryPlayer.java -+++ b/src/main/java/org/bukkit/craftbukkit/inventory/CraftInventoryPlayer.java -@@ -155,13 +155,13 @@ public class CraftInventoryPlayer extends CraftInventory implements org.bukkit.i - case OFF_HAND: - return this.getItemInOffHand(); - case FEET: -- return this.getBoots(); -+ return java.util.Objects.requireNonNullElseGet(this.getBoots(), () -> new ItemStack(org.bukkit.Material.AIR)); // Paper - make nonnull - case LEGS: -- return this.getLeggings(); -+ return java.util.Objects.requireNonNullElseGet(this.getLeggings(), () -> new ItemStack(org.bukkit.Material.AIR)); // Paper - make nonnull - case CHEST: -- return this.getChestplate(); -+ return java.util.Objects.requireNonNullElseGet(this.getChestplate(), () -> new ItemStack(org.bukkit.Material.AIR)); // Paper - make nonnull - case HEAD: -- return this.getHelmet(); -+ return java.util.Objects.requireNonNullElseGet(this.getHelmet(), () -> new ItemStack(org.bukkit.Material.AIR)); // Paper - make nonnull - default: - throw new IllegalArgumentException("Not implemented. This is a bug"); - } diff --git a/patches/server/0679-Implement-enchantWithLevels-API.patch b/patches/server/0679-Implement-enchantWithLevels-API.patch deleted file mode 100644 index 23f8df750399..000000000000 --- a/patches/server/0679-Implement-enchantWithLevels-API.patch +++ /dev/null @@ -1,58 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Jason Penilla <11360596+jpenilla@users.noreply.github.com> -Date: Wed, 16 Mar 2022 20:35:21 -0700 -Subject: [PATCH] Implement enchantWithLevels API - - -diff --git a/src/main/java/org/bukkit/craftbukkit/inventory/CraftItemFactory.java b/src/main/java/org/bukkit/craftbukkit/inventory/CraftItemFactory.java -index 803a19063c03627dbea79cb1c395ae35aaef2834..fef91dbede067f1ab99a9c7d463a2c55cc6cae3a 100644 ---- a/src/main/java/org/bukkit/craftbukkit/inventory/CraftItemFactory.java -+++ b/src/main/java/org/bukkit/craftbukkit/inventory/CraftItemFactory.java -@@ -302,4 +302,47 @@ public final class CraftItemFactory implements ItemFactory { - return eggItem == null ? null : new net.minecraft.world.item.ItemStack(eggItem).asBukkitMirror(); - } - // Paper end - old getSpawnEgg API -+ // Paper start - enchantWithLevels API -+ @Override -+ public ItemStack enchantWithLevels(ItemStack itemStack, int levels, boolean allowTreasure, java.util.Random random) { -+ return enchantWithLevels( -+ itemStack, -+ levels, -+ allowTreasure -+ ? Optional.empty() -+ // While IN_ENCHANTING_TABLE is not logically the same as all but TREASURE, the tag is defined as -+ // NON_TREASURE, which does contain all enchantments not in the treasure tag. -+ // Additionally, the allowTreasure boolean is more intended to configure this method to behave like -+ // an enchanting table. -+ : net.minecraft.server.MinecraftServer.getServer().registryAccess().registryOrThrow(Registries.ENCHANTMENT).getTag(EnchantmentTags.IN_ENCHANTING_TABLE), -+ random -+ ); -+ } -+ -+ @SuppressWarnings("OptionalUsedAsFieldOrParameterType") -+ private ItemStack enchantWithLevels( -+ ItemStack itemStack, -+ int levels, -+ Optional> possibleEnchantments, -+ java.util.Random random -+ ) { -+ Preconditions.checkArgument(itemStack != null, "Argument 'itemStack' must not be null"); -+ Preconditions.checkArgument(!itemStack.isEmpty(), "Argument 'itemStack' cannot be empty"); -+ Preconditions.checkArgument(levels > 0 && levels <= 30, "Argument 'levels' must be in range [1, 30] (attempted " + levels + ")"); -+ Preconditions.checkArgument(random != null, "Argument 'random' must not be null"); -+ final net.minecraft.world.item.ItemStack internalStack = CraftItemStack.asNMSCopy(itemStack); -+ if (internalStack.isEnchanted()) { -+ internalStack.set(net.minecraft.core.component.DataComponents.ENCHANTMENTS, net.minecraft.world.item.enchantment.ItemEnchantments.EMPTY); -+ } -+ final net.minecraft.core.RegistryAccess registryAccess = net.minecraft.server.MinecraftServer.getServer().registryAccess(); -+ final net.minecraft.world.item.ItemStack enchanted = net.minecraft.world.item.enchantment.EnchantmentHelper.enchantItem( -+ new org.bukkit.craftbukkit.util.RandomSourceWrapper(random), -+ internalStack, -+ levels, -+ registryAccess, -+ possibleEnchantments -+ ); -+ return CraftItemStack.asCraftMirror(enchanted); -+ } -+ // Paper end - enchantWithLevels API - } diff --git a/patches/server/0679-fix-player-loottables-running-when-mob-loot-gamerule.patch b/patches/server/0679-fix-player-loottables-running-when-mob-loot-gamerule.patch new file mode 100644 index 000000000000..7a7f077e66c0 --- /dev/null +++ b/patches/server/0679-fix-player-loottables-running-when-mob-loot-gamerule.patch @@ -0,0 +1,25 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Jake Potrebic +Date: Tue, 22 Mar 2022 09:50:40 -0700 +Subject: [PATCH] fix player loottables running when mob loot gamerule is false + + +diff --git a/src/main/java/net/minecraft/server/level/ServerPlayer.java b/src/main/java/net/minecraft/server/level/ServerPlayer.java +index f72ab5e4e743cb0758ebca28e81f97c143c91b42..d723adaa7b1df4a1d5067298536b303992ac2c52 100644 +--- a/src/main/java/net/minecraft/server/level/ServerPlayer.java ++++ b/src/main/java/net/minecraft/server/level/ServerPlayer.java +@@ -1218,12 +1218,14 @@ public class ServerPlayer extends net.minecraft.world.entity.player.Player { + } + } + } ++ if (this.shouldDropLoot() && this.serverLevel().getGameRules().getBoolean(GameRules.RULE_DOMOBLOOT)) { // Paper - fix player loottables running when mob loot gamerule is false + // SPIGOT-5071: manually add player loot tables (SPIGOT-5195 - ignores keepInventory rule) + this.dropFromLootTable(this.serverLevel(), damageSource, this.lastHurtByPlayerTime > 0); + this.dropCustomDeathLoot(this.serverLevel(), damageSource, flag); + + loot.addAll(this.drops); + this.drops.clear(); // SPIGOT-5188: make sure to clear ++ } // Paper - fix player loottables running when mob loot gamerule is false + + Component defaultMessage = this.getCombatTracker().getDeathMessage(); + diff --git a/patches/server/0680-Ensure-entity-passenger-world-matches-ridden-entity.patch b/patches/server/0680-Ensure-entity-passenger-world-matches-ridden-entity.patch new file mode 100644 index 000000000000..e85a7e401966 --- /dev/null +++ b/patches/server/0680-Ensure-entity-passenger-world-matches-ridden-entity.patch @@ -0,0 +1,20 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Spottedleaf +Date: Thu, 31 Mar 2022 05:11:37 -0700 +Subject: [PATCH] Ensure entity passenger world matches ridden entity + +Bad plugins doing this would cause some obvious problems... + +diff --git a/src/main/java/net/minecraft/world/entity/Entity.java b/src/main/java/net/minecraft/world/entity/Entity.java +index 4b0b768a4563281ec4ef1a16a3a21c24a1272849..dcd6cb204d5d19da17664e446da7f3edef104b80 100644 +--- a/src/main/java/net/minecraft/world/entity/Entity.java ++++ b/src/main/java/net/minecraft/world/entity/Entity.java +@@ -2802,7 +2802,7 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess + } + + public boolean startRiding(Entity entity, boolean force) { +- if (entity == this.vehicle) { ++ if (entity == this.vehicle || entity.level != this.level) { // Paper - Ensure entity passenger world matches ridden entity (bad plugins) + return false; + } else if (!entity.couldAcceptPassenger()) { + return false; diff --git a/patches/server/0680-Fix-saving-in-unloadWorld.patch b/patches/server/0680-Fix-saving-in-unloadWorld.patch deleted file mode 100644 index 5536c9b21abb..000000000000 --- a/patches/server/0680-Fix-saving-in-unloadWorld.patch +++ /dev/null @@ -1,20 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Philip Kelley -Date: Wed, 16 Mar 2022 12:05:59 +0000 -Subject: [PATCH] Fix saving in unloadWorld - -Change savingDisabled to false to ensure ServerLevel's saving logic gets called when unloadWorld is called with save = true - -diff --git a/src/main/java/org/bukkit/craftbukkit/CraftServer.java b/src/main/java/org/bukkit/craftbukkit/CraftServer.java -index 566ed56de6fcb4dc64e504310b46295466917eee..183d9672ad60e2b7db48c283b1ca863df01ad658 100644 ---- a/src/main/java/org/bukkit/craftbukkit/CraftServer.java -+++ b/src/main/java/org/bukkit/craftbukkit/CraftServer.java -@@ -1383,7 +1383,7 @@ public final class CraftServer implements Server { - - try { - if (save) { -- handle.save(null, true, true); -+ handle.save(null, true, false); // Paper - Fix saving in unloadWorld - } - - handle.getChunkSource().close(save); diff --git a/patches/server/0681-Buffer-OOB-setBlock-calls.patch b/patches/server/0681-Buffer-OOB-setBlock-calls.patch deleted file mode 100644 index 7bc7bd4823c3..000000000000 --- a/patches/server/0681-Buffer-OOB-setBlock-calls.patch +++ /dev/null @@ -1,42 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Shane Freeder -Date: Sat, 19 Mar 2022 12:12:22 +0000 -Subject: [PATCH] Buffer OOB setBlock calls - -lets debug mode throw a trace in order to potentially see where -such calls are cascading from easier, but, generally, if you see one setBlock -call, you're gonna see more, and this just potentially causes a flood of logs -which can cause issues for slower terminals, etc. - -We can limit the flood by just allowing one for a single gen region, -we'll also only gen a trace for the first one, I see no real pressing need -to generate more, given that that would *massively* negate this patch otherwise - -diff --git a/src/main/java/net/minecraft/server/level/WorldGenRegion.java b/src/main/java/net/minecraft/server/level/WorldGenRegion.java -index 421f146ea9c35b852251c0ddb29856c13e11aef3..13229388ddce668061a34c787ab9586d41854d8a 100644 ---- a/src/main/java/net/minecraft/server/level/WorldGenRegion.java -+++ b/src/main/java/net/minecraft/server/level/WorldGenRegion.java -@@ -284,6 +284,7 @@ public class WorldGenRegion implements WorldGenLevel { - } - } - -+ private boolean hasSetFarWarned = false; // Paper - Buffer OOB setBlock calls - @Override - public boolean ensureCanWrite(BlockPos pos) { - int i = SectionPos.blockToSectionCoord(pos.getX()); -@@ -303,7 +304,15 @@ public class WorldGenRegion implements WorldGenLevel { - - return true; - } else { -+ // Paper start - Buffer OOB setBlock calls -+ if (!hasSetFarWarned) { - Util.logAndPauseIfInIde("Detected setBlock in a far chunk [" + i + ", " + j + "], pos: " + String.valueOf(pos) + ", status: " + String.valueOf(this.generatingStep.targetStatus()) + (this.currentlyGenerating == null ? "" : ", currently generating: " + (String) this.currentlyGenerating.get())); -+ hasSetFarWarned = true; -+ if (this.getServer() != null && this.getServer().isDebugging()) { -+ io.papermc.paper.util.TraceUtil.dumpTraceForThread("far setBlock call"); -+ } -+ } -+ // Paper end - Buffer OOB setBlock calls - return false; - } - } diff --git a/patches/server/0681-Cache-resource-keys-and-optimize-reference-Holder-ta.patch b/patches/server/0681-Cache-resource-keys-and-optimize-reference-Holder-ta.patch new file mode 100644 index 000000000000..263f5852b348 --- /dev/null +++ b/patches/server/0681-Cache-resource-keys-and-optimize-reference-Holder-ta.patch @@ -0,0 +1,66 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Jake Potrebic +Date: Sun, 20 Mar 2022 22:06:47 -0700 +Subject: [PATCH] Cache resource keys and optimize reference Holder tags set + +TagKeys are always interned, so we can use a reference hash set for them + +diff --git a/src/main/java/net/minecraft/core/Holder.java b/src/main/java/net/minecraft/core/Holder.java +index 94671ea227b59a8f820425c401712e6aeb8b2b10..e91c4e26c25980645941ca8fbdcc3a9d02e31063 100644 +--- a/src/main/java/net/minecraft/core/Holder.java ++++ b/src/main/java/net/minecraft/core/Holder.java +@@ -230,7 +230,7 @@ public interface Holder { + } + + void bindTags(Collection> tags) { +- this.tags = Set.copyOf(tags); ++ this.tags = java.util.Collections.unmodifiableSet(new it.unimi.dsi.fastutil.objects.ReferenceOpenHashSet<>(tags)); // Paper + } + + @Override +diff --git a/src/main/java/org/bukkit/craftbukkit/block/CraftBiome.java b/src/main/java/org/bukkit/craftbukkit/block/CraftBiome.java +index 95b956802f83b583a823fcd24808363775a56842..33d2e89ac40465b0c4633f9c51378b80f7c397a9 100644 +--- a/src/main/java/org/bukkit/craftbukkit/block/CraftBiome.java ++++ b/src/main/java/org/bukkit/craftbukkit/block/CraftBiome.java +@@ -3,6 +3,7 @@ package org.bukkit.craftbukkit.block; + import com.google.common.base.Preconditions; + import net.minecraft.core.Holder; + import net.minecraft.core.registries.Registries; ++import net.minecraft.resources.ResourceKey; + import org.bukkit.Registry; + import org.bukkit.block.Biome; + import org.bukkit.craftbukkit.CraftRegistry; +@@ -27,13 +28,14 @@ public class CraftBiome { + return CraftBiome.minecraftToBukkit(minecraft.value()); + } + ++ private static final java.util.Map> BIOME_KEY_CACHE = java.util.Collections.synchronizedMap(new java.util.EnumMap<>(Biome.class)); // Paper + public static net.minecraft.world.level.biome.Biome bukkitToMinecraft(Biome bukkit) { + if (bukkit == null || bukkit == Biome.CUSTOM) { + return null; + } + + return CraftRegistry.getMinecraftRegistry(Registries.BIOME) +- .getOptional(CraftNamespacedKey.toMinecraft(bukkit.getKey())).orElseThrow(); ++ .getOptional(BIOME_KEY_CACHE.computeIfAbsent(bukkit, b -> ResourceKey.create(Registries.BIOME, CraftNamespacedKey.toMinecraft(b.getKey())))).orElseThrow(); + } + + public static Holder bukkitToMinecraftHolder(Biome bukkit) { +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftEntityType.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftEntityType.java +index 6cf8af0c85231de9955282d4abaa0607ec9a195c..d230cbc26f61d8ac5880825aca4dfab197c20401 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftEntityType.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftEntityType.java +@@ -25,11 +25,11 @@ public class CraftEntityType { + return bukkit; + } + ++ private static final java.util.Map>> KEY_CACHE = java.util.Collections.synchronizedMap(new java.util.EnumMap<>(EntityType.class)); // Paper + public static net.minecraft.world.entity.EntityType bukkitToMinecraft(EntityType bukkit) { + Preconditions.checkArgument(bukkit != null); +- + return CraftRegistry.getMinecraftRegistry(Registries.ENTITY_TYPE) +- .getOptional(CraftNamespacedKey.toMinecraft(bukkit.getKey())).orElseThrow(); ++ .getOptional(KEY_CACHE.computeIfAbsent(bukkit, type -> net.minecraft.resources.ResourceKey.create(Registries.ENTITY_TYPE, CraftNamespacedKey.toMinecraft(type.getKey())))).orElseThrow(); + } + + public static Holder> bukkitToMinecraftHolder(EntityType bukkit) { diff --git a/patches/server/0682-Add-TameableDeathMessageEvent.patch b/patches/server/0682-Add-TameableDeathMessageEvent.patch deleted file mode 100644 index 1531307bd4a7..000000000000 --- a/patches/server/0682-Add-TameableDeathMessageEvent.patch +++ /dev/null @@ -1,24 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Owen1212055 <23108066+Owen1212055@users.noreply.github.com> -Date: Mon, 21 Jun 2021 21:24:45 -0400 -Subject: [PATCH] Add TameableDeathMessageEvent - - -diff --git a/src/main/java/net/minecraft/world/entity/TamableAnimal.java b/src/main/java/net/minecraft/world/entity/TamableAnimal.java -index 7b7bc1a205dfacbe5709011b6b6799e75af9e4cc..4c5b0b1a71ec5ba0e8dcd5ef52bf667e9bb30be9 100644 ---- a/src/main/java/net/minecraft/world/entity/TamableAnimal.java -+++ b/src/main/java/net/minecraft/world/entity/TamableAnimal.java -@@ -241,7 +241,12 @@ public abstract class TamableAnimal extends Animal implements OwnableEntity { - @Override - public void die(DamageSource damageSource) { - if (!this.level().isClientSide && this.level().getGameRules().getBoolean(GameRules.RULE_SHOWDEATHMESSAGES) && this.getOwner() instanceof ServerPlayer) { -- this.getOwner().sendSystemMessage(this.getCombatTracker().getDeathMessage()); -+ // Paper start - Add TameableDeathMessageEvent -+ io.papermc.paper.event.entity.TameableDeathMessageEvent event = new io.papermc.paper.event.entity.TameableDeathMessageEvent((org.bukkit.entity.Tameable) getBukkitEntity(), io.papermc.paper.adventure.PaperAdventure.asAdventure(this.getCombatTracker().getDeathMessage())); -+ if (event.callEvent()) { -+ this.getOwner().sendSystemMessage(io.papermc.paper.adventure.PaperAdventure.asVanilla(event.deathMessage())); -+ } -+ // Paper end - Add TameableDeathMessageEvent - } - - super.die(damageSource); diff --git a/patches/server/0682-Allow-changing-the-EnderDragon-podium.patch b/patches/server/0682-Allow-changing-the-EnderDragon-podium.patch new file mode 100644 index 000000000000..5cbce2b4ecc6 --- /dev/null +++ b/patches/server/0682-Allow-changing-the-EnderDragon-podium.patch @@ -0,0 +1,142 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Doc +Date: Sun, 3 Apr 2022 11:31:42 -0400 +Subject: [PATCH] Allow changing the EnderDragon podium + + +diff --git a/src/main/java/net/minecraft/world/entity/boss/enderdragon/EnderDragon.java b/src/main/java/net/minecraft/world/entity/boss/enderdragon/EnderDragon.java +index 25d2226c2a5dda411a9e35f7a0e3ab183110c227..38456d3901e495e4c401cff0de7ae38544c1b2a7 100644 +--- a/src/main/java/net/minecraft/world/entity/boss/enderdragon/EnderDragon.java ++++ b/src/main/java/net/minecraft/world/entity/boss/enderdragon/EnderDragon.java +@@ -104,6 +104,10 @@ public class EnderDragon extends Mob implements Enemy { + private final int[] nodeAdjacency; + private final BinaryHeap openSet; + private final Explosion explosionSource; // CraftBukkit - reusable source for CraftTNTPrimed.getSource() ++ // Paper start - Allow changing the EnderDragon podium ++ @Nullable ++ private BlockPos podium; ++ // Paper end - Allow changing the EnderDragon podium + + public EnderDragon(EntityType entitytypes, Level world) { + super(EntityType.ENDER_DRAGON, world); +@@ -143,6 +147,19 @@ public class EnderDragon extends Mob implements Enemy { + return Mob.createMobAttributes().add(Attributes.MAX_HEALTH, 200.0D); + } + ++ // Paper start - Allow changing the EnderDragon podium ++ public BlockPos getPodium() { ++ if (this.podium == null) { ++ return EndPodiumFeature.getLocation(this.getFightOrigin()); ++ } ++ return this.podium; ++ } ++ ++ public void setPodium(@Nullable BlockPos blockPos) { ++ this.podium = blockPos; ++ } ++ // Paper end - Allow changing the EnderDragon podium ++ + @Override + public boolean isFlapping() { + float f = Mth.cos(this.flapTime * 6.2831855F); +@@ -988,7 +1005,7 @@ public class EnderDragon extends Mob implements Enemy { + vec3d = this.getViewVector(tickDelta); + } + } else { +- BlockPos blockposition = this.level().getHeightmapPos(Heightmap.Types.MOTION_BLOCKING_NO_LEAVES, EndPodiumFeature.getLocation(this.fightOrigin)); ++ BlockPos blockposition = this.level().getHeightmapPos(Heightmap.Types.MOTION_BLOCKING_NO_LEAVES, this.getPodium()); // Paper - Allow changing the EnderDragon podium + + f1 = Math.max((float) Math.sqrt(blockposition.distToCenterSqr(this.position())) / 4.0F, 1.0F); + float f3 = 6.0F / f1; +diff --git a/src/main/java/net/minecraft/world/entity/boss/enderdragon/phases/DragonDeathPhase.java b/src/main/java/net/minecraft/world/entity/boss/enderdragon/phases/DragonDeathPhase.java +index a897c994423d7d624df6ff3a67789cc2436f0417..29f4acc2943ce009088c61bb32aed330f6e2c051 100644 +--- a/src/main/java/net/minecraft/world/entity/boss/enderdragon/phases/DragonDeathPhase.java ++++ b/src/main/java/net/minecraft/world/entity/boss/enderdragon/phases/DragonDeathPhase.java +@@ -42,7 +42,7 @@ public class DragonDeathPhase extends AbstractDragonPhaseInstance { + public void doServerTick(ServerLevel world) { + this.time++; + if (this.targetLocation == null) { +- BlockPos blockPos = world.getHeightmapPos(Heightmap.Types.MOTION_BLOCKING, EndPodiumFeature.getLocation(this.dragon.getFightOrigin())); ++ BlockPos blockPos = world.getHeightmapPos(Heightmap.Types.MOTION_BLOCKING, this.dragon.getPodium()); // Paper - Allow changing the EnderDragon podium + this.targetLocation = Vec3.atBottomCenterOf(blockPos); + } + +diff --git a/src/main/java/net/minecraft/world/entity/boss/enderdragon/phases/DragonHoldingPatternPhase.java b/src/main/java/net/minecraft/world/entity/boss/enderdragon/phases/DragonHoldingPatternPhase.java +index 2db996f3528c65f5d719cbcfb8ae587ff59c14c1..8e39cf181993ff284a2b0429577de0c307f3ef16 100644 +--- a/src/main/java/net/minecraft/world/entity/boss/enderdragon/phases/DragonHoldingPatternPhase.java ++++ b/src/main/java/net/minecraft/world/entity/boss/enderdragon/phases/DragonHoldingPatternPhase.java +@@ -53,7 +53,7 @@ public class DragonHoldingPatternPhase extends AbstractDragonPhaseInstance { + + private void findNewTarget(ServerLevel world) { + if (this.currentPath != null && this.currentPath.isDone()) { +- BlockPos blockPos = world.getHeightmapPos(Heightmap.Types.MOTION_BLOCKING_NO_LEAVES, EndPodiumFeature.getLocation(this.dragon.getFightOrigin())); ++ BlockPos blockPos = world.getHeightmapPos(Heightmap.Types.MOTION_BLOCKING_NO_LEAVES, this.dragon.getPodium()); // Paper - Allow changing the EnderDragon podium + int i = this.dragon.getDragonFight() == null ? 0 : this.dragon.getDragonFight().getCrystalsAlive(); + if (this.dragon.getRandom().nextInt(i + 3) == 0) { + this.dragon.getPhaseManager().setPhase(EnderDragonPhase.LANDING_APPROACH); +diff --git a/src/main/java/net/minecraft/world/entity/boss/enderdragon/phases/DragonLandingApproachPhase.java b/src/main/java/net/minecraft/world/entity/boss/enderdragon/phases/DragonLandingApproachPhase.java +index 4c28d3c3d601b5316d79c8474d106800abc8e95b..1c4250cc61b770707ad25c0e93caeacbcb0a80b0 100644 +--- a/src/main/java/net/minecraft/world/entity/boss/enderdragon/phases/DragonLandingApproachPhase.java ++++ b/src/main/java/net/minecraft/world/entity/boss/enderdragon/phases/DragonLandingApproachPhase.java +@@ -52,7 +52,7 @@ public class DragonLandingApproachPhase extends AbstractDragonPhaseInstance { + private void findNewTarget(ServerLevel world) { + if (this.currentPath == null || this.currentPath.isDone()) { + int i = this.dragon.findClosestNode(); +- BlockPos blockPos = world.getHeightmapPos(Heightmap.Types.MOTION_BLOCKING_NO_LEAVES, EndPodiumFeature.getLocation(this.dragon.getFightOrigin())); ++ BlockPos blockPos = world.getHeightmapPos(Heightmap.Types.MOTION_BLOCKING_NO_LEAVES, this.dragon.getPodium()); // Paper - Allow changing the EnderDragon podium + Player player = world.getNearestPlayer(NEAR_EGG_TARGETING, this.dragon, (double)blockPos.getX(), (double)blockPos.getY(), (double)blockPos.getZ()); + int j; + if (player != null) { +diff --git a/src/main/java/net/minecraft/world/entity/boss/enderdragon/phases/DragonLandingPhase.java b/src/main/java/net/minecraft/world/entity/boss/enderdragon/phases/DragonLandingPhase.java +index 789df0e05d0cf0df0f66bffc98f587a31770ebea..cfcd3f91517832d26c1177c3715cfa8ebe675193 100644 +--- a/src/main/java/net/minecraft/world/entity/boss/enderdragon/phases/DragonLandingPhase.java ++++ b/src/main/java/net/minecraft/world/entity/boss/enderdragon/phases/DragonLandingPhase.java +@@ -42,7 +42,7 @@ public class DragonLandingPhase extends AbstractDragonPhaseInstance { + public void doServerTick(ServerLevel world) { + if (this.targetLocation == null) { + this.targetLocation = Vec3.atBottomCenterOf( +- world.getHeightmapPos(Heightmap.Types.MOTION_BLOCKING_NO_LEAVES, EndPodiumFeature.getLocation(this.dragon.getFightOrigin())) ++ world.getHeightmapPos(Heightmap.Types.MOTION_BLOCKING_NO_LEAVES, this.dragon.getPodium()) // Paper - Allow changing the EnderDragon podium + ); + } + +diff --git a/src/main/java/net/minecraft/world/entity/boss/enderdragon/phases/DragonTakeoffPhase.java b/src/main/java/net/minecraft/world/entity/boss/enderdragon/phases/DragonTakeoffPhase.java +index f942ea2ad87d4ae12921578f7b869f064c8db7b0..5c5b03f612621ec4efd92b78601032b84e6f3c28 100644 +--- a/src/main/java/net/minecraft/world/entity/boss/enderdragon/phases/DragonTakeoffPhase.java ++++ b/src/main/java/net/minecraft/world/entity/boss/enderdragon/phases/DragonTakeoffPhase.java +@@ -24,7 +24,7 @@ public class DragonTakeoffPhase extends AbstractDragonPhaseInstance { + @Override + public void doServerTick(ServerLevel world) { + if (!this.firstTick && this.currentPath != null) { +- BlockPos blockPos = world.getHeightmapPos(Heightmap.Types.MOTION_BLOCKING_NO_LEAVES, EndPodiumFeature.getLocation(this.dragon.getFightOrigin())); ++ BlockPos blockPos = world.getHeightmapPos(Heightmap.Types.MOTION_BLOCKING_NO_LEAVES, this.dragon.getPodium()); // Paper - Allow changing the EnderDragon podium + if (!blockPos.closerToCenterThan(this.dragon.position(), 10.0)) { + this.dragon.getPhaseManager().setPhase(EnderDragonPhase.HOLDING_PATTERN); + } +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftEnderDragon.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftEnderDragon.java +index 25b3d889a1742c347e60725df8d6f6c1cee264c7..7b7b89e67d53ed70efae714192c5fa32977f3d9c 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftEnderDragon.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftEnderDragon.java +@@ -73,4 +73,22 @@ public class CraftEnderDragon extends CraftMob implements EnderDragon, CraftEnem + public int getDeathAnimationTicks() { + return this.getHandle().dragonDeathTime; + } ++ ++ // Paper start - Allow changing the EnderDragon podium ++ @Override ++ public org.bukkit.Location getPodium() { ++ net.minecraft.core.BlockPos blockPosOrigin = this.getHandle().getPodium(); ++ return new org.bukkit.Location(getWorld(), blockPosOrigin.getX(), blockPosOrigin.getY(), blockPosOrigin.getZ()); ++ } ++ ++ @Override ++ public void setPodium(org.bukkit.Location location) { ++ if (location == null) { ++ this.getHandle().setPodium(null); ++ } else { ++ org.apache.commons.lang.Validate.isTrue(location.getWorld() == null || location.getWorld().equals(getWorld()), "You cannot set a podium in a different world to where the dragon is"); ++ this.getHandle().setPodium(io.papermc.paper.util.MCUtil.toBlockPos(location)); ++ } ++ } ++ // Paper end - Allow changing the EnderDragon podium + } diff --git a/patches/server/0683-Fix-NBT-pieces-overriding-a-block-entity-during-worl.patch b/patches/server/0683-Fix-NBT-pieces-overriding-a-block-entity-during-worl.patch new file mode 100644 index 000000000000..f885dbe8b41b --- /dev/null +++ b/patches/server/0683-Fix-NBT-pieces-overriding-a-block-entity-during-worl.patch @@ -0,0 +1,40 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: etil2jz +Date: Sat, 2 Apr 2022 23:29:24 +0200 +Subject: [PATCH] Fix NBT pieces overriding a block entity during worldgen + deadlock + +By checking if the world passed into StructureTemplate's placeInWorld +is not a WorldGenRegion, we can bypass the deadlock entirely. +See https://bugs.mojang.com/browse/MC-246262 + +diff --git a/src/main/java/net/minecraft/world/level/levelgen/structure/templatesystem/StructureTemplate.java b/src/main/java/net/minecraft/world/level/levelgen/structure/templatesystem/StructureTemplate.java +index b120949667ae0169a667b329b3cabbd79a0a5bda..734f511d197bc6bf2b02588069eb02c0224781f5 100644 +--- a/src/main/java/net/minecraft/world/level/levelgen/structure/templatesystem/StructureTemplate.java ++++ b/src/main/java/net/minecraft/world/level/levelgen/structure/templatesystem/StructureTemplate.java +@@ -303,7 +303,11 @@ public class StructureTemplate { + + if (definedstructure_blockinfo.nbt != null) { + tileentity = world.getBlockEntity(blockposition2); +- Clearable.tryClear(tileentity); ++ // Paper start - Fix NBT pieces overriding a block entity during worldgen deadlock ++ if (!(world instanceof net.minecraft.world.level.WorldGenLevel)) { ++ Clearable.tryClear(tileentity); ++ } ++ // Paper end - Fix NBT pieces overriding a block entity during worldgen deadlock + world.setBlock(blockposition2, Blocks.BARRIER.defaultBlockState(), 20); + } + // CraftBukkit start +@@ -430,7 +434,11 @@ public class StructureTemplate { + if (pair1.getSecond() != null) { + tileentity = world.getBlockEntity(blockposition6); + if (tileentity != null) { +- tileentity.setChanged(); ++ // Paper start - Fix NBT pieces overriding a block entity during worldgen deadlock ++ if (!(world instanceof net.minecraft.world.level.WorldGenLevel)) { ++ tileentity.setChanged(); ++ } ++ // Paper end - Fix NBT pieces overriding a block entity during worldgen deadlock + } + } + } diff --git a/patches/server/0683-Fix-new-block-data-for-EntityChangeBlockEvent.patch b/patches/server/0683-Fix-new-block-data-for-EntityChangeBlockEvent.patch deleted file mode 100644 index 6dae9bf4a8ef..000000000000 --- a/patches/server/0683-Fix-new-block-data-for-EntityChangeBlockEvent.patch +++ /dev/null @@ -1,215 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: SoSeDiK -Date: Mon, 21 Mar 2022 20:00:53 +0200 -Subject: [PATCH] Fix new block data for EntityChangeBlockEvent - -Also standardizes how to handle EntityChangeBlockEvent before a removeBlock or destroyBlock -call. Always use 'state.getFluidState().createLegacyBlock()' to get the new state instead of -just using the 'air' state. - -Also fixes the new block data for EntityBreakDoorEvent (a sub-event from -EntityChangeBlockEvent) - -Co-authored-by: Lulu13022002 <41980282+Lulu13022002@users.noreply.github.com> -Co-authored-by: Jake Potrebic - -diff --git a/src/main/java/net/minecraft/world/entity/ai/behavior/HarvestFarmland.java b/src/main/java/net/minecraft/world/entity/ai/behavior/HarvestFarmland.java -index 6a7052cd6ec88ed4aaea2fef0ebc750060d1dec0..2ade08d1466660ee1787fa97908002ef56389712 100644 ---- a/src/main/java/net/minecraft/world/entity/ai/behavior/HarvestFarmland.java -+++ b/src/main/java/net/minecraft/world/entity/ai/behavior/HarvestFarmland.java -@@ -108,7 +108,7 @@ public class HarvestFarmland extends Behavior { - Block block1 = world.getBlockState(this.aboveFarmlandPos.below()).getBlock(); - - if (block instanceof CropBlock && ((CropBlock) block).isMaxAge(iblockdata)) { -- if (CraftEventFactory.callEntityChangeBlockEvent(entity, this.aboveFarmlandPos, Blocks.AIR.defaultBlockState())) { // CraftBukkit -+ if (CraftEventFactory.callEntityChangeBlockEvent(entity, this.aboveFarmlandPos, iblockdata.getFluidState().createLegacyBlock())) { // CraftBukkit // Paper - fix wrong block state - world.destroyBlock(this.aboveFarmlandPos, true, entity); - } // CraftBukkit - } -diff --git a/src/main/java/net/minecraft/world/entity/ai/goal/BreakDoorGoal.java b/src/main/java/net/minecraft/world/entity/ai/goal/BreakDoorGoal.java -index 4253b3b1263a7ae5a2f5f3a34674dfea615a81ea..784a894688f98f9d0368a36d456c5c94e1ee3695 100644 ---- a/src/main/java/net/minecraft/world/entity/ai/goal/BreakDoorGoal.java -+++ b/src/main/java/net/minecraft/world/entity/ai/goal/BreakDoorGoal.java -@@ -72,7 +72,7 @@ public class BreakDoorGoal extends DoorInteractGoal { - - if (this.breakTime == this.getDoorBreakTime() && this.isValidDifficulty(this.mob.level().getDifficulty())) { - // CraftBukkit start -- if (org.bukkit.craftbukkit.event.CraftEventFactory.callEntityBreakDoorEvent(this.mob, this.doorPos).isCancelled()) { -+ if (org.bukkit.craftbukkit.event.CraftEventFactory.callEntityBreakDoorEvent(this.mob, this.doorPos, this.mob.level().getFluidState(this.doorPos).createLegacyBlock()).isCancelled()) { // Paper - fix wrong block state - this.start(); - return; - } -diff --git a/src/main/java/net/minecraft/world/entity/ai/goal/EatBlockGoal.java b/src/main/java/net/minecraft/world/entity/ai/goal/EatBlockGoal.java -index f4bc556e245179d0a4710e5255dd289aaafdceb7..d802985f1431be4332c07f0dab88feebedea4ce2 100644 ---- a/src/main/java/net/minecraft/world/entity/ai/goal/EatBlockGoal.java -+++ b/src/main/java/net/minecraft/world/entity/ai/goal/EatBlockGoal.java -@@ -67,8 +67,9 @@ public class EatBlockGoal extends Goal { - if (this.eatAnimationTick == this.adjustedTickDelay(4)) { - BlockPos blockposition = this.mob.blockPosition(); - -- if (EatBlockGoal.IS_TALL_GRASS.test(this.level.getBlockState(blockposition))) { -- if (CraftEventFactory.callEntityChangeBlockEvent(this.mob, blockposition, Blocks.AIR.defaultBlockState(), !this.level.getGameRules().getBoolean(GameRules.RULE_MOBGRIEFING))) { // CraftBukkit -+ final BlockState blockState = this.level.getBlockState(blockposition); // Paper - fix wrong block state -+ if (EatBlockGoal.IS_TALL_GRASS.test(blockState)) { // Paper - fix wrong block state -+ if (CraftEventFactory.callEntityChangeBlockEvent(this.mob, blockposition, blockState.getFluidState().createLegacyBlock(), !this.level.getGameRules().getBoolean(GameRules.RULE_MOBGRIEFING))) { // CraftBukkit // Paper - fix wrong block state - this.level.destroyBlock(blockposition, false); - } - -@@ -77,7 +78,7 @@ public class EatBlockGoal extends Goal { - BlockPos blockposition1 = blockposition.below(); - - if (this.level.getBlockState(blockposition1).is(Blocks.GRASS_BLOCK)) { -- if (CraftEventFactory.callEntityChangeBlockEvent(this.mob, blockposition1, Blocks.AIR.defaultBlockState(), !this.level.getGameRules().getBoolean(GameRules.RULE_MOBGRIEFING))) { // CraftBukkit -+ if (CraftEventFactory.callEntityChangeBlockEvent(this.mob, blockposition1, Blocks.DIRT.defaultBlockState(), !this.level.getGameRules().getBoolean(GameRules.RULE_MOBGRIEFING))) { // CraftBukkit // Paper - Fix wrong block state - this.level.levelEvent(2001, blockposition1, Block.getId(Blocks.GRASS_BLOCK.defaultBlockState())); - this.level.setBlock(blockposition1, Blocks.DIRT.defaultBlockState(), 2); - } -diff --git a/src/main/java/net/minecraft/world/entity/animal/Rabbit.java b/src/main/java/net/minecraft/world/entity/animal/Rabbit.java -index e5bd3039ef698fcea80a270b6111c2a663e5075c..0109c8ed8bf6a053674456fa4473934e028ca418 100644 ---- a/src/main/java/net/minecraft/world/entity/animal/Rabbit.java -+++ b/src/main/java/net/minecraft/world/entity/animal/Rabbit.java -@@ -581,7 +581,7 @@ public class Rabbit extends Animal implements VariantHolder { - - if (i == 0) { - // CraftBukkit start -- if (!CraftEventFactory.callEntityChangeBlockEvent(this.rabbit, blockposition, Blocks.AIR.defaultBlockState())) { -+ if (!CraftEventFactory.callEntityChangeBlockEvent(this.rabbit, blockposition, iblockdata.getFluidState().createLegacyBlock())) { // Paper - fix wrong block state - return; - } - // CraftBukkit end -diff --git a/src/main/java/net/minecraft/world/entity/boss/wither/WitherBoss.java b/src/main/java/net/minecraft/world/entity/boss/wither/WitherBoss.java -index 3cd4a744c3e3aeba90f342de9dea67ef2f3de626..1b49090a466bc74d9e5f2815314955b6dfbb83dc 100644 ---- a/src/main/java/net/minecraft/world/entity/boss/wither/WitherBoss.java -+++ b/src/main/java/net/minecraft/world/entity/boss/wither/WitherBoss.java -@@ -377,7 +377,7 @@ public class WitherBoss extends Monster implements PowerableMob, RangedAttackMob - - if (WitherBoss.canDestroy(iblockdata)) { - // CraftBukkit start -- if (!CraftEventFactory.callEntityChangeBlockEvent(this, blockposition, Blocks.AIR.defaultBlockState())) { -+ if (!CraftEventFactory.callEntityChangeBlockEvent(this, blockposition, iblockdata.getFluidState().createLegacyBlock())) { // Paper - fix wrong block state - continue; - } - // CraftBukkit end -diff --git a/src/main/java/net/minecraft/world/entity/monster/EnderMan.java b/src/main/java/net/minecraft/world/entity/monster/EnderMan.java -index 7caa5469a4daa5d0c569f446ff2d597a9f10e8ac..828c51477cd8f35d591367b30bf4feef6a250292 100644 ---- a/src/main/java/net/minecraft/world/entity/monster/EnderMan.java -+++ b/src/main/java/net/minecraft/world/entity/monster/EnderMan.java -@@ -565,7 +565,7 @@ public class EnderMan extends Monster implements NeutralMob { - boolean flag = movingobjectpositionblock.getBlockPos().equals(blockposition); - - if (iblockdata.is(BlockTags.ENDERMAN_HOLDABLE) && flag) { -- if (CraftEventFactory.callEntityChangeBlockEvent(this.enderman, blockposition, Blocks.AIR.defaultBlockState())) { // CraftBukkit - Place event -+ if (CraftEventFactory.callEntityChangeBlockEvent(this.enderman, blockposition, iblockdata.getFluidState().createLegacyBlock())) { // CraftBukkit - Place event // Paper - fix wrong block state - world.removeBlock(blockposition, false); - world.gameEvent((Holder) GameEvent.BLOCK_DESTROY, blockposition, GameEvent.Context.of(this.enderman, iblockdata)); - this.enderman.setCarriedBlock(iblockdata.getBlock().defaultBlockState()); -diff --git a/src/main/java/net/minecraft/world/entity/monster/Ravager.java b/src/main/java/net/minecraft/world/entity/monster/Ravager.java -index aa4111eef22546f8aa630228c51ef5761c9b373b..212d341425c0f93bba0376de69bea61ffcf4dbd6 100644 ---- a/src/main/java/net/minecraft/world/entity/monster/Ravager.java -+++ b/src/main/java/net/minecraft/world/entity/monster/Ravager.java -@@ -151,7 +151,7 @@ public class Ravager extends Raider { - - if (block instanceof LeavesBlock) { - // CraftBukkit start -- if (!CraftEventFactory.callEntityChangeBlockEvent(this, blockposition, net.minecraft.world.level.block.Blocks.AIR.defaultBlockState())) { -+ if (!CraftEventFactory.callEntityChangeBlockEvent(this, blockposition, iblockdata.getFluidState().createLegacyBlock())) { // Paper - fix wrong block state - continue; - } - // CraftBukkit end -diff --git a/src/main/java/net/minecraft/world/entity/monster/Silverfish.java b/src/main/java/net/minecraft/world/entity/monster/Silverfish.java -index 9ff42b0ae2b82dc3092e38e1439d89b4ab554b17..860e385fc83f9787dca92efe35d21fd69c4dd635 100644 ---- a/src/main/java/net/minecraft/world/entity/monster/Silverfish.java -+++ b/src/main/java/net/minecraft/world/entity/monster/Silverfish.java -@@ -162,7 +162,8 @@ public class Silverfish extends Monster { - - if (block instanceof InfestedBlock) { - // CraftBukkit start -- if (!CraftEventFactory.callEntityChangeBlockEvent(this.silverfish, blockposition1, net.minecraft.world.level.block.Blocks.AIR.defaultBlockState())) { -+ BlockState afterState = world.getGameRules().getBoolean(GameRules.RULE_MOBGRIEFING) ? iblockdata.getFluidState().createLegacyBlock() : ((InfestedBlock) block).hostStateByInfested(world.getBlockState(blockposition1)); // Paper - fix wrong block state -+ if (!CraftEventFactory.callEntityChangeBlockEvent(this.silverfish, blockposition1, afterState)) { // Paper - fix wrong block state - continue; - } - // CraftBukkit end -diff --git a/src/main/java/net/minecraft/world/entity/projectile/ThrownPotion.java b/src/main/java/net/minecraft/world/entity/projectile/ThrownPotion.java -index 58dc69fe319027c2b9ecfb9caf272368e81081df..5c6cb752f61c3f3c2960a337173fb7dfe86cc1d3 100644 ---- a/src/main/java/net/minecraft/world/entity/projectile/ThrownPotion.java -+++ b/src/main/java/net/minecraft/world/entity/projectile/ThrownPotion.java -@@ -292,7 +292,7 @@ public class ThrownPotion extends ThrowableItemProjectile implements ItemSupplie - - if (iblockdata.is(BlockTags.FIRE)) { - // CraftBukkit start -- if (CraftEventFactory.callEntityChangeBlockEvent(this, pos, Blocks.AIR.defaultBlockState())) { -+ if (CraftEventFactory.callEntityChangeBlockEvent(this, pos, iblockdata.getFluidState().createLegacyBlock())) { // Paper - fix wrong block state - this.level().destroyBlock(pos, false, this); - } - // CraftBukkit end -diff --git a/src/main/java/net/minecraft/world/level/block/ChorusFlowerBlock.java b/src/main/java/net/minecraft/world/level/block/ChorusFlowerBlock.java -index bb1487dd2c8e4374f75a102ab5e17ce9b2d31114..6709cb6b657a8612781c2fe4dd76ee38f329c5ba 100644 ---- a/src/main/java/net/minecraft/world/level/block/ChorusFlowerBlock.java -+++ b/src/main/java/net/minecraft/world/level/block/ChorusFlowerBlock.java -@@ -284,7 +284,7 @@ public class ChorusFlowerBlock extends Block { - - if (!world.isClientSide && projectile.mayInteract(world, blockposition) && projectile.mayBreak(world)) { - // CraftBukkit -- if (!CraftEventFactory.callEntityChangeBlockEvent(projectile, blockposition, Blocks.AIR.defaultBlockState())) { -+ if (!CraftEventFactory.callEntityChangeBlockEvent(projectile, blockposition, state.getFluidState().createLegacyBlock())) { // Paper - fix wrong block state - return; - } - // CraftBukkit end -diff --git a/src/main/java/net/minecraft/world/level/block/PointedDripstoneBlock.java b/src/main/java/net/minecraft/world/level/block/PointedDripstoneBlock.java -index ec9190abe3edf7c3845156bb967dddf6ae7c30ff..95cb7492ac691a8e8aa9894f701b802a7eda5446 100644 ---- a/src/main/java/net/minecraft/world/level/block/PointedDripstoneBlock.java -+++ b/src/main/java/net/minecraft/world/level/block/PointedDripstoneBlock.java -@@ -134,7 +134,7 @@ public class PointedDripstoneBlock extends Block implements Fallable, SimpleWate - - if (projectile.mayInteract(world, blockposition) && projectile.mayBreak(world) && projectile instanceof ThrownTrident && projectile.getDeltaMovement().length() > 0.6D) { - // CraftBukkit start -- if (!org.bukkit.craftbukkit.event.CraftEventFactory.callEntityChangeBlockEvent(projectile, blockposition, Blocks.AIR.defaultBlockState())) { -+ if (!org.bukkit.craftbukkit.event.CraftEventFactory.callEntityChangeBlockEvent(projectile, blockposition, state.getFluidState().createLegacyBlock())) { // Paper - fix wrong block state - return; - } - // CraftBukkit end -diff --git a/src/main/java/net/minecraft/world/level/block/TntBlock.java b/src/main/java/net/minecraft/world/level/block/TntBlock.java -index 4896ddca849646135ae101236e534ab8f59bd617..a3525ae6d83591664e1456f20d9732a8de0ec326 100644 ---- a/src/main/java/net/minecraft/world/level/block/TntBlock.java -+++ b/src/main/java/net/minecraft/world/level/block/TntBlock.java -@@ -158,7 +158,7 @@ public class TntBlock extends Block { - - if (projectile.isOnFire() && projectile.mayInteract(world, blockposition)) { - // CraftBukkit start -- if (!org.bukkit.craftbukkit.event.CraftEventFactory.callEntityChangeBlockEvent(projectile, blockposition, Blocks.AIR.defaultBlockState()) || !CraftEventFactory.callTNTPrimeEvent(world, blockposition, PrimeCause.PROJECTILE, projectile, null)) { -+ if (!org.bukkit.craftbukkit.event.CraftEventFactory.callEntityChangeBlockEvent(projectile, blockposition, state.getFluidState().createLegacyBlock()) || !CraftEventFactory.callTNTPrimeEvent(world, blockposition, PrimeCause.PROJECTILE, projectile, null)) { // Paper - fix wrong block state - return; - } - // CraftBukkit end -diff --git a/src/main/java/net/minecraft/world/level/block/WaterlilyBlock.java b/src/main/java/net/minecraft/world/level/block/WaterlilyBlock.java -index edc20745649b0837f1371c8d29e71fc0c8e5528f..932831bb5632ead5850842fc77192c841571162e 100644 ---- a/src/main/java/net/minecraft/world/level/block/WaterlilyBlock.java -+++ b/src/main/java/net/minecraft/world/level/block/WaterlilyBlock.java -@@ -37,7 +37,7 @@ public class WaterlilyBlock extends BushBlock { - if (!new io.papermc.paper.event.entity.EntityInsideBlockEvent(entity.getBukkitEntity(), org.bukkit.craftbukkit.block.CraftBlock.at(world, pos)).callEvent()) { return; } // Paper - Add EntityInsideBlockEvent - if (world instanceof ServerLevel && entity instanceof Boat) { - // CraftBukkit start -- if (!CraftEventFactory.callEntityChangeBlockEvent(entity, pos, Blocks.AIR.defaultBlockState())) { -+ if (!CraftEventFactory.callEntityChangeBlockEvent(entity, pos, state.getFluidState().createLegacyBlock())) { // Paper - fix wrong block state - return; - } - // CraftBukkit end -diff --git a/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java b/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java -index eeb326a115020f571e96f3ec85408950b5eb56fb..ba1599b733946bef468a9b006411f8827844e791 100644 ---- a/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java -+++ b/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java -@@ -1380,11 +1380,11 @@ public class CraftEventFactory { - return event; - } - -- public static EntityBreakDoorEvent callEntityBreakDoorEvent(Entity entity, BlockPos pos) { -+ public static EntityBreakDoorEvent callEntityBreakDoorEvent(Entity entity, BlockPos pos, net.minecraft.world.level.block.state.BlockState newState) { // Paper - org.bukkit.entity.Entity entity1 = entity.getBukkitEntity(); - Block block = CraftBlock.at(entity.level(), pos); - -- EntityBreakDoorEvent event = new EntityBreakDoorEvent((LivingEntity) entity1, block); -+ EntityBreakDoorEvent event = new EntityBreakDoorEvent((LivingEntity) entity1, block, newState.createCraftBlockData()); // Paper - entity1.getServer().getPluginManager().callEvent(event); - - return event; diff --git a/patches/server/0684-Prevent-tile-entity-copies-loading-chunks.patch b/patches/server/0684-Prevent-tile-entity-copies-loading-chunks.patch new file mode 100644 index 000000000000..d690eb728839 --- /dev/null +++ b/patches/server/0684-Prevent-tile-entity-copies-loading-chunks.patch @@ -0,0 +1,24 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Shane Freeder +Date: Wed, 13 Apr 2022 08:25:42 +0100 +Subject: [PATCH] Prevent tile entity copies loading chunks + + +diff --git a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java +index 9656e15aa15735e9bd6ac4e2ad7aa8aa66140b9a..bc8f9bd50de3894e6262e13ed55252c98f22ed8a 100644 +--- a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java ++++ b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java +@@ -3245,7 +3245,12 @@ public class ServerGamePacketListenerImpl extends ServerCommonPacketListenerImpl + BlockPos blockposition = BlockEntity.getPosFromTag(customdata.getUnsafe()); + + if (this.player.level().isLoaded(blockposition)) { +- BlockEntity tileentity = this.player.level().getBlockEntity(blockposition); ++ // Paper start - Prevent tile entity copies loading chunks ++ BlockEntity tileentity = null; ++ if (this.player.distanceToSqr(blockposition.getX(), blockposition.getY(), blockposition.getZ()) < 32 * 32 && this.player.serverLevel().isLoadedAndInBounds(blockposition)) { ++ tileentity = this.player.level().getBlockEntity(blockposition); ++ } ++ // Paper end - Prevent tile entity copies loading chunks + + if (tileentity != null) { + tileentity.saveToItem(itemstack, this.player.level().registryAccess()); diff --git a/patches/server/0684-fix-player-loottables-running-when-mob-loot-gamerule.patch b/patches/server/0684-fix-player-loottables-running-when-mob-loot-gamerule.patch deleted file mode 100644 index ad38317db74d..000000000000 --- a/patches/server/0684-fix-player-loottables-running-when-mob-loot-gamerule.patch +++ /dev/null @@ -1,25 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Jake Potrebic -Date: Tue, 22 Mar 2022 09:50:40 -0700 -Subject: [PATCH] fix player loottables running when mob loot gamerule is false - - -diff --git a/src/main/java/net/minecraft/server/level/ServerPlayer.java b/src/main/java/net/minecraft/server/level/ServerPlayer.java -index 25ca370c0b19eca529fc2c980c50e899013f0897..f31eb944465e9011d8aad398eb60bafb44203ad5 100644 ---- a/src/main/java/net/minecraft/server/level/ServerPlayer.java -+++ b/src/main/java/net/minecraft/server/level/ServerPlayer.java -@@ -959,12 +959,14 @@ public class ServerPlayer extends net.minecraft.world.entity.player.Player { - } - } - } -+ if (this.shouldDropLoot() && this.level().getGameRules().getBoolean(GameRules.RULE_DOMOBLOOT)) { // Paper - fix player loottables running when mob loot gamerule is false - // SPIGOT-5071: manually add player loot tables (SPIGOT-5195 - ignores keepInventory rule) - this.dropFromLootTable(damageSource, this.lastHurtByPlayerTime > 0); - this.dropCustomDeathLoot(this.serverLevel(), damageSource, flag); - - loot.addAll(this.drops); - this.drops.clear(); // SPIGOT-5188: make sure to clear -+ } // Paper - fix player loottables running when mob loot gamerule is false - - Component defaultMessage = this.getCombatTracker().getDeathMessage(); - diff --git a/patches/server/0685-Ensure-entity-passenger-world-matches-ridden-entity.patch b/patches/server/0685-Ensure-entity-passenger-world-matches-ridden-entity.patch deleted file mode 100644 index ba6ce8567a41..000000000000 --- a/patches/server/0685-Ensure-entity-passenger-world-matches-ridden-entity.patch +++ /dev/null @@ -1,20 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Spottedleaf -Date: Thu, 31 Mar 2022 05:11:37 -0700 -Subject: [PATCH] Ensure entity passenger world matches ridden entity - -Bad plugins doing this would cause some obvious problems... - -diff --git a/src/main/java/net/minecraft/world/entity/Entity.java b/src/main/java/net/minecraft/world/entity/Entity.java -index 7d9ebc71b794d40246cbd43ff4de5795970e7ebe..3bb7e0f6a02b9b1726b8c878271d89a9c6d56c12 100644 ---- a/src/main/java/net/minecraft/world/entity/Entity.java -+++ b/src/main/java/net/minecraft/world/entity/Entity.java -@@ -2687,7 +2687,7 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess - } - - public boolean startRiding(Entity entity, boolean force) { -- if (entity == this.vehicle) { -+ if (entity == this.vehicle || entity.level != this.level) { // Paper - Ensure entity passenger world matches ridden entity (bad plugins) - return false; - } else if (!entity.couldAcceptPassenger()) { - return false; diff --git a/patches/server/0685-Use-username-instead-of-display-name-in-PlayerList-g.patch b/patches/server/0685-Use-username-instead-of-display-name-in-PlayerList-g.patch new file mode 100644 index 000000000000..3cb43bf44945 --- /dev/null +++ b/patches/server/0685-Use-username-instead-of-display-name-in-PlayerList-g.patch @@ -0,0 +1,20 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Doc +Date: Fri, 15 Apr 2022 17:40:30 -0400 +Subject: [PATCH] Use username instead of display name in + PlayerList#getPlayerStats + + +diff --git a/src/main/java/net/minecraft/server/players/PlayerList.java b/src/main/java/net/minecraft/server/players/PlayerList.java +index 11f86e1b119d20f668e67b83f09137dcb24d4bf1..6409463c9834a660f2a2e2b6bbfac8b3beb80db7 100644 +--- a/src/main/java/net/minecraft/server/players/PlayerList.java ++++ b/src/main/java/net/minecraft/server/players/PlayerList.java +@@ -1352,7 +1352,7 @@ public abstract class PlayerList { + // CraftBukkit start + public ServerStatsCounter getPlayerStats(ServerPlayer entityhuman) { + ServerStatsCounter serverstatisticmanager = entityhuman.getStats(); +- return serverstatisticmanager == null ? this.getPlayerStats(entityhuman.getUUID(), entityhuman.getDisplayName().getString()) : serverstatisticmanager; ++ return serverstatisticmanager == null ? this.getPlayerStats(entityhuman.getUUID(), entityhuman.getGameProfile().getName()) : serverstatisticmanager; // Paper - use username and not display name + } + + public ServerStatsCounter getPlayerStats(UUID uuid, String displayName) { diff --git a/patches/server/0686-Cache-resource-keys-and-optimize-reference-Holder-ta.patch b/patches/server/0686-Cache-resource-keys-and-optimize-reference-Holder-ta.patch deleted file mode 100644 index ddb3a70fde20..000000000000 --- a/patches/server/0686-Cache-resource-keys-and-optimize-reference-Holder-ta.patch +++ /dev/null @@ -1,66 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Jake Potrebic -Date: Sun, 20 Mar 2022 22:06:47 -0700 -Subject: [PATCH] Cache resource keys and optimize reference Holder tags set - -TagKeys are always interned, so we can use a reference hash set for them - -diff --git a/src/main/java/net/minecraft/core/Holder.java b/src/main/java/net/minecraft/core/Holder.java -index d7bbf60ba94ecd85f991a0c5c70c34fdb00ec9d5..4d2231868b786da9071c3dff2c073b478a486e8a 100644 ---- a/src/main/java/net/minecraft/core/Holder.java -+++ b/src/main/java/net/minecraft/core/Holder.java -@@ -221,7 +221,7 @@ public interface Holder { - } - - void bindTags(Collection> tags) { -- this.tags = Set.copyOf(tags); -+ this.tags = java.util.Collections.unmodifiableSet(new it.unimi.dsi.fastutil.objects.ReferenceOpenHashSet<>(tags)); // Paper - } - - @Override -diff --git a/src/main/java/org/bukkit/craftbukkit/block/CraftBiome.java b/src/main/java/org/bukkit/craftbukkit/block/CraftBiome.java -index 95b956802f83b583a823fcd24808363775a56842..33d2e89ac40465b0c4633f9c51378b80f7c397a9 100644 ---- a/src/main/java/org/bukkit/craftbukkit/block/CraftBiome.java -+++ b/src/main/java/org/bukkit/craftbukkit/block/CraftBiome.java -@@ -3,6 +3,7 @@ package org.bukkit.craftbukkit.block; - import com.google.common.base.Preconditions; - import net.minecraft.core.Holder; - import net.minecraft.core.registries.Registries; -+import net.minecraft.resources.ResourceKey; - import org.bukkit.Registry; - import org.bukkit.block.Biome; - import org.bukkit.craftbukkit.CraftRegistry; -@@ -27,13 +28,14 @@ public class CraftBiome { - return CraftBiome.minecraftToBukkit(minecraft.value()); - } - -+ private static final java.util.Map> BIOME_KEY_CACHE = java.util.Collections.synchronizedMap(new java.util.EnumMap<>(Biome.class)); // Paper - public static net.minecraft.world.level.biome.Biome bukkitToMinecraft(Biome bukkit) { - if (bukkit == null || bukkit == Biome.CUSTOM) { - return null; - } - - return CraftRegistry.getMinecraftRegistry(Registries.BIOME) -- .getOptional(CraftNamespacedKey.toMinecraft(bukkit.getKey())).orElseThrow(); -+ .getOptional(BIOME_KEY_CACHE.computeIfAbsent(bukkit, b -> ResourceKey.create(Registries.BIOME, CraftNamespacedKey.toMinecraft(b.getKey())))).orElseThrow(); - } - - public static Holder bukkitToMinecraftHolder(Biome bukkit) { -diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftEntityType.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftEntityType.java -index fd1aaf8e18d6e3425639b60ce21c5aaf36e0b42a..266b616419a47f518a43b990cc7cbb4516beda03 100644 ---- a/src/main/java/org/bukkit/craftbukkit/entity/CraftEntityType.java -+++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftEntityType.java -@@ -24,11 +24,11 @@ public class CraftEntityType { - return bukkit; - } - -+ private static final java.util.Map>> KEY_CACHE = java.util.Collections.synchronizedMap(new java.util.EnumMap<>(EntityType.class)); // Paper - public static net.minecraft.world.entity.EntityType bukkitToMinecraft(EntityType bukkit) { - Preconditions.checkArgument(bukkit != null); -- - return CraftRegistry.getMinecraftRegistry(Registries.ENTITY_TYPE) -- .getOptional(CraftNamespacedKey.toMinecraft(bukkit.getKey())).orElseThrow(); -+ .getOptional(KEY_CACHE.computeIfAbsent(bukkit, type -> net.minecraft.resources.ResourceKey.create(Registries.ENTITY_TYPE, CraftNamespacedKey.toMinecraft(type.getKey())))).orElseThrow(); - } - - public static String bukkitToString(EntityType bukkit) { diff --git a/patches/server/0686-Expand-PlayerItemDamageEvent.patch b/patches/server/0686-Expand-PlayerItemDamageEvent.patch new file mode 100644 index 000000000000..9659c386596a --- /dev/null +++ b/patches/server/0686-Expand-PlayerItemDamageEvent.patch @@ -0,0 +1,23 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: HexedHero <6012891+HexedHero@users.noreply.github.com> +Date: Sun, 10 Apr 2022 06:26:32 +0100 +Subject: [PATCH] Expand PlayerItemDamageEvent + + +diff --git a/src/main/java/net/minecraft/world/item/ItemStack.java b/src/main/java/net/minecraft/world/item/ItemStack.java +index a100c7a53b4b1dac0a01ee65418d44297bcdb93f..bcb3a45166e5dd75dd727adf92304b3a75399c8d 100644 +--- a/src/main/java/net/minecraft/world/item/ItemStack.java ++++ b/src/main/java/net/minecraft/world/item/ItemStack.java +@@ -693,10 +693,11 @@ public final class ItemStack implements DataComponentHolder { + } + + public void hurtAndBreak(int amount, ServerLevel world, @Nullable LivingEntity player, Consumer breakCallback) { // Paper - Add EntityDamageItemEvent ++ int originalDamage = amount; // Paper - Expand PlayerItemDamageEvent + int j = this.processDurabilityChange(amount, world, player); + // CraftBukkit start + if (player instanceof final ServerPlayer serverPlayer) { // Paper - Add EntityDamageItemEvent +- PlayerItemDamageEvent event = new PlayerItemDamageEvent(serverPlayer.getBukkitEntity(), CraftItemStack.asCraftMirror(this), j); // Paper - Add EntityDamageItemEvent ++ PlayerItemDamageEvent event = new PlayerItemDamageEvent(serverPlayer.getBukkitEntity(), CraftItemStack.asCraftMirror(this), j, originalDamage); // Paper - Add EntityDamageItemEvent + event.getPlayer().getServer().getPluginManager().callEvent(event); + + if (j != event.getDamage() || event.isCancelled()) { diff --git a/patches/server/0687-Allow-changing-the-EnderDragon-podium.patch b/patches/server/0687-Allow-changing-the-EnderDragon-podium.patch deleted file mode 100644 index 4c615ac13206..000000000000 --- a/patches/server/0687-Allow-changing-the-EnderDragon-podium.patch +++ /dev/null @@ -1,151 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Doc -Date: Sun, 3 Apr 2022 11:31:42 -0400 -Subject: [PATCH] Allow changing the EnderDragon podium - - -diff --git a/src/main/java/net/minecraft/world/entity/boss/enderdragon/EnderDragon.java b/src/main/java/net/minecraft/world/entity/boss/enderdragon/EnderDragon.java -index a2cde7b1b316e43382cb1639ffccf29d89f5ebfc..6306f7925ac4852d8eed7508a11764c636dd7d36 100644 ---- a/src/main/java/net/minecraft/world/entity/boss/enderdragon/EnderDragon.java -+++ b/src/main/java/net/minecraft/world/entity/boss/enderdragon/EnderDragon.java -@@ -104,6 +104,10 @@ public class EnderDragon extends Mob implements Enemy { - private final int[] nodeAdjacency; - private final BinaryHeap openSet; - private final Explosion explosionSource; // CraftBukkit - reusable source for CraftTNTPrimed.getSource() -+ // Paper start - Allow changing the EnderDragon podium -+ @Nullable -+ private BlockPos podium; -+ // Paper end - Allow changing the EnderDragon podium - - public EnderDragon(EntityType entitytypes, Level world) { - super(EntityType.ENDER_DRAGON, world); -@@ -144,6 +148,19 @@ public class EnderDragon extends Mob implements Enemy { - return Mob.createMobAttributes().add(Attributes.MAX_HEALTH, 200.0D); - } - -+ // Paper start - Allow changing the EnderDragon podium -+ public BlockPos getPodium() { -+ if (this.podium == null) { -+ return EndPodiumFeature.getLocation(this.getFightOrigin()); -+ } -+ return this.podium; -+ } -+ -+ public void setPodium(@Nullable BlockPos blockPos) { -+ this.podium = blockPos; -+ } -+ // Paper end - Allow changing the EnderDragon podium -+ - @Override - public boolean isFlapping() { - float f = Mth.cos(this.flapTime * 6.2831855F); -@@ -1009,7 +1026,7 @@ public class EnderDragon extends Mob implements Enemy { - d0 = segment2[1] - segment1[1]; - } - } else { -- BlockPos blockposition = this.level().getHeightmapPos(Heightmap.Types.MOTION_BLOCKING_NO_LEAVES, EndPodiumFeature.getLocation(this.fightOrigin)); -+ BlockPos blockposition = this.level().getHeightmapPos(Heightmap.Types.MOTION_BLOCKING_NO_LEAVES, this.getPodium()); // Paper - Allow changing the EnderDragon podium - double d1 = Math.max(Math.sqrt(blockposition.distToCenterSqr(this.position())) / 4.0D, 1.0D); - - d0 = (double) segmentOffset / d1; -@@ -1036,7 +1053,7 @@ public class EnderDragon extends Mob implements Enemy { - vec3d = this.getViewVector(tickDelta); - } - } else { -- BlockPos blockposition = this.level().getHeightmapPos(Heightmap.Types.MOTION_BLOCKING_NO_LEAVES, EndPodiumFeature.getLocation(this.fightOrigin)); -+ BlockPos blockposition = this.level().getHeightmapPos(Heightmap.Types.MOTION_BLOCKING_NO_LEAVES, this.getPodium()); // Paper - Allow changing the EnderDragon podium - - f1 = Math.max((float) Math.sqrt(blockposition.distToCenterSqr(this.position())) / 4.0F, 1.0F); - float f3 = 6.0F / f1; -diff --git a/src/main/java/net/minecraft/world/entity/boss/enderdragon/phases/DragonDeathPhase.java b/src/main/java/net/minecraft/world/entity/boss/enderdragon/phases/DragonDeathPhase.java -index 803d227281d70606691eed95c4b10a27ca5d1125..5663f2ff1eba4a5e00c76c9d735cb553faae6a04 100644 ---- a/src/main/java/net/minecraft/world/entity/boss/enderdragon/phases/DragonDeathPhase.java -+++ b/src/main/java/net/minecraft/world/entity/boss/enderdragon/phases/DragonDeathPhase.java -@@ -43,7 +43,7 @@ public class DragonDeathPhase extends AbstractDragonPhaseInstance { - if (this.targetLocation == null) { - BlockPos blockPos = this.dragon - .level() -- .getHeightmapPos(Heightmap.Types.MOTION_BLOCKING, EndPodiumFeature.getLocation(this.dragon.getFightOrigin())); -+ .getHeightmapPos(Heightmap.Types.MOTION_BLOCKING, this.dragon.getPodium()); // Paper - Allow changing the EnderDragon podium - this.targetLocation = Vec3.atBottomCenterOf(blockPos); - } - -diff --git a/src/main/java/net/minecraft/world/entity/boss/enderdragon/phases/DragonHoldingPatternPhase.java b/src/main/java/net/minecraft/world/entity/boss/enderdragon/phases/DragonHoldingPatternPhase.java -index 707ef45ccd7fbcbe1947c8941846277f19ee54c9..ddf668205a7cb29b9018bf9eea49667b5fd2d471 100644 ---- a/src/main/java/net/minecraft/world/entity/boss/enderdragon/phases/DragonHoldingPatternPhase.java -+++ b/src/main/java/net/minecraft/world/entity/boss/enderdragon/phases/DragonHoldingPatternPhase.java -@@ -54,7 +54,7 @@ public class DragonHoldingPatternPhase extends AbstractDragonPhaseInstance { - if (this.currentPath != null && this.currentPath.isDone()) { - BlockPos blockPos = this.dragon - .level() -- .getHeightmapPos(Heightmap.Types.MOTION_BLOCKING_NO_LEAVES, new BlockPos(EndPodiumFeature.getLocation(this.dragon.getFightOrigin()))); -+ .getHeightmapPos(Heightmap.Types.MOTION_BLOCKING_NO_LEAVES, new BlockPos(this.dragon.getPodium())); // Paper - Allow changing the EnderDragon podium - int i = this.dragon.getDragonFight() == null ? 0 : this.dragon.getDragonFight().getCrystalsAlive(); - if (this.dragon.getRandom().nextInt(i + 3) == 0) { - this.dragon.getPhaseManager().setPhase(EnderDragonPhase.LANDING_APPROACH); -diff --git a/src/main/java/net/minecraft/world/entity/boss/enderdragon/phases/DragonLandingApproachPhase.java b/src/main/java/net/minecraft/world/entity/boss/enderdragon/phases/DragonLandingApproachPhase.java -index e731d0f74692615ce5f42f690e36bd906a39c1dd..557de8a8e21e7f049b6acf27b4ec927ef5a9f9cb 100644 ---- a/src/main/java/net/minecraft/world/entity/boss/enderdragon/phases/DragonLandingApproachPhase.java -+++ b/src/main/java/net/minecraft/world/entity/boss/enderdragon/phases/DragonLandingApproachPhase.java -@@ -53,7 +53,7 @@ public class DragonLandingApproachPhase extends AbstractDragonPhaseInstance { - int i = this.dragon.findClosestNode(); - BlockPos blockPos = this.dragon - .level() -- .getHeightmapPos(Heightmap.Types.MOTION_BLOCKING_NO_LEAVES, EndPodiumFeature.getLocation(this.dragon.getFightOrigin())); -+ .getHeightmapPos(Heightmap.Types.MOTION_BLOCKING_NO_LEAVES, this.dragon.getPodium()); // Paper - Allow changing the EnderDragon podium - Player player = this.dragon - .level() - .getNearestPlayer(NEAR_EGG_TARGETING, this.dragon, (double)blockPos.getX(), (double)blockPos.getY(), (double)blockPos.getZ()); -diff --git a/src/main/java/net/minecraft/world/entity/boss/enderdragon/phases/DragonLandingPhase.java b/src/main/java/net/minecraft/world/entity/boss/enderdragon/phases/DragonLandingPhase.java -index d913147d692e7e58bec4fac44c7e93a1822e8f65..3b960060f152d0352c2f8cdc1c71543cd7fa0dbd 100644 ---- a/src/main/java/net/minecraft/world/entity/boss/enderdragon/phases/DragonLandingPhase.java -+++ b/src/main/java/net/minecraft/world/entity/boss/enderdragon/phases/DragonLandingPhase.java -@@ -41,7 +41,7 @@ public class DragonLandingPhase extends AbstractDragonPhaseInstance { - public void doServerTick() { - if (this.targetLocation == null) { - this.targetLocation = Vec3.atBottomCenterOf( -- this.dragon.level().getHeightmapPos(Heightmap.Types.MOTION_BLOCKING_NO_LEAVES, EndPodiumFeature.getLocation(this.dragon.getFightOrigin())) -+ this.dragon.level().getHeightmapPos(Heightmap.Types.MOTION_BLOCKING_NO_LEAVES, this.dragon.getPodium()) // Paper - Allow changing the EnderDragon podium - ); - } - -diff --git a/src/main/java/net/minecraft/world/entity/boss/enderdragon/phases/DragonTakeoffPhase.java b/src/main/java/net/minecraft/world/entity/boss/enderdragon/phases/DragonTakeoffPhase.java -index 8d66284eb96cfc0392c211842e87875a095c3ca2..718bf877179f85ee3f0de384ca3a8aaebaa067a5 100644 ---- a/src/main/java/net/minecraft/world/entity/boss/enderdragon/phases/DragonTakeoffPhase.java -+++ b/src/main/java/net/minecraft/world/entity/boss/enderdragon/phases/DragonTakeoffPhase.java -@@ -25,7 +25,7 @@ public class DragonTakeoffPhase extends AbstractDragonPhaseInstance { - if (!this.firstTick && this.currentPath != null) { - BlockPos blockPos = this.dragon - .level() -- .getHeightmapPos(Heightmap.Types.MOTION_BLOCKING_NO_LEAVES, EndPodiumFeature.getLocation(this.dragon.getFightOrigin())); -+ .getHeightmapPos(Heightmap.Types.MOTION_BLOCKING_NO_LEAVES, this.dragon.getPodium()); // Paper - Allow changing the EnderDragon podium - if (!blockPos.closerToCenterThan(this.dragon.position(), 10.0)) { - this.dragon.getPhaseManager().setPhase(EnderDragonPhase.HOLDING_PATTERN); - } -diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftEnderDragon.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftEnderDragon.java -index 25b3d889a1742c347e60725df8d6f6c1cee264c7..7b7b89e67d53ed70efae714192c5fa32977f3d9c 100644 ---- a/src/main/java/org/bukkit/craftbukkit/entity/CraftEnderDragon.java -+++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftEnderDragon.java -@@ -73,4 +73,22 @@ public class CraftEnderDragon extends CraftMob implements EnderDragon, CraftEnem - public int getDeathAnimationTicks() { - return this.getHandle().dragonDeathTime; - } -+ -+ // Paper start - Allow changing the EnderDragon podium -+ @Override -+ public org.bukkit.Location getPodium() { -+ net.minecraft.core.BlockPos blockPosOrigin = this.getHandle().getPodium(); -+ return new org.bukkit.Location(getWorld(), blockPosOrigin.getX(), blockPosOrigin.getY(), blockPosOrigin.getZ()); -+ } -+ -+ @Override -+ public void setPodium(org.bukkit.Location location) { -+ if (location == null) { -+ this.getHandle().setPodium(null); -+ } else { -+ org.apache.commons.lang.Validate.isTrue(location.getWorld() == null || location.getWorld().equals(getWorld()), "You cannot set a podium in a different world to where the dragon is"); -+ this.getHandle().setPodium(io.papermc.paper.util.MCUtil.toBlockPos(location)); -+ } -+ } -+ // Paper end - Allow changing the EnderDragon podium - } diff --git a/patches/server/0687-WorldCreator-keepSpawnLoaded.patch b/patches/server/0687-WorldCreator-keepSpawnLoaded.patch new file mode 100644 index 000000000000..6712b0e0a361 --- /dev/null +++ b/patches/server/0687-WorldCreator-keepSpawnLoaded.patch @@ -0,0 +1,19 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Shane Freeder +Date: Sat, 3 Jul 2021 21:18:28 +0100 +Subject: [PATCH] WorldCreator#keepSpawnLoaded + + +diff --git a/src/main/java/org/bukkit/craftbukkit/CraftServer.java b/src/main/java/org/bukkit/craftbukkit/CraftServer.java +index a504a6458423a997e703e95356dd2058d6c164e2..6ba912570ea66c3b0747beb4c168e351904cc31e 100644 +--- a/src/main/java/org/bukkit/craftbukkit/CraftServer.java ++++ b/src/main/java/org/bukkit/craftbukkit/CraftServer.java +@@ -1328,7 +1328,7 @@ public final class CraftServer implements Server { + } + + // If set to not keep spawn in memory (changed from default) then adjust rule accordingly +- if (!creator.keepSpawnInMemory()) { ++ if (creator.keepSpawnLoaded() == net.kyori.adventure.util.TriState.FALSE) { // Paper + worlddata.getGameRules().getRule(GameRules.RULE_SPAWN_CHUNK_RADIUS).set(0, null); + } + ServerLevel internal = (ServerLevel) new ServerLevel(this.console, this.console.executor, worldSession, worlddata, worldKey, worlddimension, this.getServer().progressListenerFactory.create(worlddata.getGameRules().getInt(GameRules.RULE_SPAWN_CHUNK_RADIUS)), diff --git a/patches/server/0693-Fix-CME-in-CraftPersistentDataTypeRegistry.patch b/patches/server/0688-Fix-CME-in-CraftPersistentDataTypeRegistry.patch similarity index 100% rename from patches/server/0693-Fix-CME-in-CraftPersistentDataTypeRegistry.patch rename to patches/server/0688-Fix-CME-in-CraftPersistentDataTypeRegistry.patch diff --git a/patches/server/0688-Fix-NBT-pieces-overriding-a-block-entity-during-worl.patch b/patches/server/0688-Fix-NBT-pieces-overriding-a-block-entity-during-worl.patch deleted file mode 100644 index 265f2bbdbb06..000000000000 --- a/patches/server/0688-Fix-NBT-pieces-overriding-a-block-entity-during-worl.patch +++ /dev/null @@ -1,40 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: etil2jz -Date: Sat, 2 Apr 2022 23:29:24 +0200 -Subject: [PATCH] Fix NBT pieces overriding a block entity during worldgen - deadlock - -By checking if the world passed into StructureTemplate's placeInWorld -is not a WorldGenRegion, we can bypass the deadlock entirely. -See https://bugs.mojang.com/browse/MC-246262 - -diff --git a/src/main/java/net/minecraft/world/level/levelgen/structure/templatesystem/StructureTemplate.java b/src/main/java/net/minecraft/world/level/levelgen/structure/templatesystem/StructureTemplate.java -index 33564e62e3181d28b18a957e28b8ec5152d8339f..cf8258e8d46ca7286a66c38fa24af369bd9a279f 100644 ---- a/src/main/java/net/minecraft/world/level/levelgen/structure/templatesystem/StructureTemplate.java -+++ b/src/main/java/net/minecraft/world/level/levelgen/structure/templatesystem/StructureTemplate.java -@@ -279,7 +279,11 @@ public class StructureTemplate { - - if (definedstructure_blockinfo.nbt != null) { - tileentity = world.getBlockEntity(blockposition2); -- Clearable.tryClear(tileentity); -+ // Paper start - Fix NBT pieces overriding a block entity during worldgen deadlock -+ if (!(world instanceof net.minecraft.world.level.WorldGenLevel)) { -+ Clearable.tryClear(tileentity); -+ } -+ // Paper end - Fix NBT pieces overriding a block entity during worldgen deadlock - world.setBlock(blockposition2, Blocks.BARRIER.defaultBlockState(), 20); - } - // CraftBukkit start -@@ -406,7 +410,11 @@ public class StructureTemplate { - if (pair1.getSecond() != null) { - tileentity = world.getBlockEntity(blockposition6); - if (tileentity != null) { -- tileentity.setChanged(); -+ // Paper start - Fix NBT pieces overriding a block entity during worldgen deadlock -+ if (!(world instanceof net.minecraft.world.level.WorldGenLevel)) { -+ tileentity.setChanged(); -+ } -+ // Paper end - Fix NBT pieces overriding a block entity during worldgen deadlock - } - } - } diff --git a/patches/server/0689-Prevent-tile-entity-copies-loading-chunks.patch b/patches/server/0689-Prevent-tile-entity-copies-loading-chunks.patch deleted file mode 100644 index 50f78f59ad8f..000000000000 --- a/patches/server/0689-Prevent-tile-entity-copies-loading-chunks.patch +++ /dev/null @@ -1,24 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Shane Freeder -Date: Wed, 13 Apr 2022 08:25:42 +0100 -Subject: [PATCH] Prevent tile entity copies loading chunks - - -diff --git a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java -index 76c9feb5e1389eb929571de74ececd4a4f384453..7c1a94980cf49d37be442e1b62de36d4395bcf2a 100644 ---- a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java -+++ b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java -@@ -3200,7 +3200,12 @@ public class ServerGamePacketListenerImpl extends ServerCommonPacketListenerImpl - BlockPos blockposition = BlockEntity.getPosFromTag(customdata.getUnsafe()); - - if (this.player.level().isLoaded(blockposition)) { -- BlockEntity tileentity = this.player.level().getBlockEntity(blockposition); -+ // Paper start - Prevent tile entity copies loading chunks -+ BlockEntity tileentity = null; -+ if (this.player.distanceToSqr(blockposition.getX(), blockposition.getY(), blockposition.getZ()) < 32 * 32 && this.player.serverLevel().isLoadedAndInBounds(blockposition)) { -+ tileentity = this.player.level().getBlockEntity(blockposition); -+ } -+ // Paper end - Prevent tile entity copies loading chunks - - if (tileentity != null) { - tileentity.saveToItem(itemstack, this.player.level().registryAccess()); diff --git a/patches/server/0689-Trigger-bee_nest_destroyed-trigger-in-the-correct-pl.patch b/patches/server/0689-Trigger-bee_nest_destroyed-trigger-in-the-correct-pl.patch new file mode 100644 index 000000000000..3ecc56a3325f --- /dev/null +++ b/patches/server/0689-Trigger-bee_nest_destroyed-trigger-in-the-correct-pl.patch @@ -0,0 +1,54 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Jake Potrebic +Date: Wed, 2 Feb 2022 13:50:06 -0800 +Subject: [PATCH] Trigger bee_nest_destroyed trigger in the correct place + + +diff --git a/src/main/java/net/minecraft/server/level/ServerPlayerGameMode.java b/src/main/java/net/minecraft/server/level/ServerPlayerGameMode.java +index e000a918230187f6841b03b7b0dd73687f3cc15e..5c3e5c348e6fececccd8097355f423b9e7ad982b 100644 +--- a/src/main/java/net/minecraft/server/level/ServerPlayerGameMode.java ++++ b/src/main/java/net/minecraft/server/level/ServerPlayerGameMode.java +@@ -427,12 +427,16 @@ public class ServerPlayerGameMode { + block.destroy(this.level, pos, iblockdata1); + } + ++ ItemStack mainHandStack = null; // Paper - Trigger bee_nest_destroyed trigger in the correct place ++ boolean isCorrectTool = false; // Paper - Trigger bee_nest_destroyed trigger in the correct place + if (this.isCreative()) { + // return true; // CraftBukkit + } else { + ItemStack itemstack = this.player.getMainHandItem(); + ItemStack itemstack1 = itemstack.copy(); + boolean flag1 = this.player.hasCorrectToolForDrops(iblockdata1); ++ mainHandStack = itemstack1; // Paper - Trigger bee_nest_destroyed trigger in the correct place ++ isCorrectTool = flag1; // Paper - Trigger bee_nest_destroyed trigger in the correct place + + itemstack.mineBlock(this.level, iblockdata1, pos, this.player); + if (flag && flag1 && event.isDropItems()) { // CraftBukkit - Check if block should drop items +@@ -453,6 +457,13 @@ public class ServerPlayerGameMode { + if (flag && event != null) { + iblockdata.getBlock().popExperience(this.level, pos, event.getExpToDrop(), this.player); // Paper + } ++ // Paper start - Trigger bee_nest_destroyed trigger in the correct place (check impls of block#playerDestroy) ++ if (mainHandStack != null) { ++ if (flag && isCorrectTool && event.isDropItems() && block instanceof net.minecraft.world.level.block.BeehiveBlock && tileentity instanceof net.minecraft.world.level.block.entity.BeehiveBlockEntity beehiveBlockEntity) { // simulates the guard on block#playerDestroy above ++ CriteriaTriggers.BEE_NEST_DESTROYED.trigger(player, iblockdata, mainHandStack, beehiveBlockEntity.getOccupantCount()); ++ } ++ } ++ // Paper end - Trigger bee_nest_destroyed trigger in the correct place + + return true; + // CraftBukkit end +diff --git a/src/main/java/net/minecraft/world/level/block/BeehiveBlock.java b/src/main/java/net/minecraft/world/level/block/BeehiveBlock.java +index 20f705c90a0a96321cfe29c0cf564013dcccd18f..d5ed53c37bba79f84b60d616887cd5176e124f10 100644 +--- a/src/main/java/net/minecraft/world/level/block/BeehiveBlock.java ++++ b/src/main/java/net/minecraft/world/level/block/BeehiveBlock.java +@@ -103,7 +103,7 @@ public class BeehiveBlock extends BaseEntityBlock { + this.angerNearbyBees(world, pos); + } + +- CriteriaTriggers.BEE_NEST_DESTROYED.trigger((ServerPlayer) player, state, tool, tileentitybeehive.getOccupantCount()); ++ // CriteriaTriggers.BEE_NEST_DESTROYED.trigger((ServerPlayer) player, state, tool, tileentitybeehive.getOccupantCount()); // Paper - Trigger bee_nest_destroyed trigger in the correct place; moved until after items are dropped + } + + } diff --git a/patches/server/0690-Add-EntityDyeEvent-and-CollarColorable-interface.patch b/patches/server/0690-Add-EntityDyeEvent-and-CollarColorable-interface.patch new file mode 100644 index 000000000000..d50c55e4e37b --- /dev/null +++ b/patches/server/0690-Add-EntityDyeEvent-and-CollarColorable-interface.patch @@ -0,0 +1,43 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Jake Potrebic +Date: Fri, 18 Mar 2022 21:15:55 -0700 +Subject: [PATCH] Add EntityDyeEvent and CollarColorable interface + + +diff --git a/src/main/java/net/minecraft/world/entity/animal/Cat.java b/src/main/java/net/minecraft/world/entity/animal/Cat.java +index 5a86530c65d7d83e4608600a04ffd931bf59dc4a..bffc7c21727a6e5ff13a498aa51f6797e4d6d596 100644 +--- a/src/main/java/net/minecraft/world/entity/animal/Cat.java ++++ b/src/main/java/net/minecraft/world/entity/animal/Cat.java +@@ -387,6 +387,13 @@ public class Cat extends TamableAnimal implements VariantHolder -Date: Fri, 15 Apr 2022 17:40:30 -0400 -Subject: [PATCH] Use username instead of display name in - PlayerList#getPlayerStats - - -diff --git a/src/main/java/net/minecraft/server/players/PlayerList.java b/src/main/java/net/minecraft/server/players/PlayerList.java -index 06bcc9974b4a63c174c51df332ff9ddd7eb6a159..0bded2c55d6c826e2f3dcb89995712c92348a3c7 100644 ---- a/src/main/java/net/minecraft/server/players/PlayerList.java -+++ b/src/main/java/net/minecraft/server/players/PlayerList.java -@@ -1394,7 +1394,7 @@ public abstract class PlayerList { - // CraftBukkit start - public ServerStatsCounter getPlayerStats(ServerPlayer entityhuman) { - ServerStatsCounter serverstatisticmanager = entityhuman.getStats(); -- return serverstatisticmanager == null ? this.getPlayerStats(entityhuman.getUUID(), entityhuman.getDisplayName().getString()) : serverstatisticmanager; -+ return serverstatisticmanager == null ? this.getPlayerStats(entityhuman.getUUID(), entityhuman.getGameProfile().getName()) : serverstatisticmanager; // Paper - use username and not display name - } - - public ServerStatsCounter getPlayerStats(UUID uuid, String displayName) { diff --git a/patches/server/0691-Expand-PlayerItemDamageEvent.patch b/patches/server/0691-Expand-PlayerItemDamageEvent.patch deleted file mode 100644 index fac811b9d874..000000000000 --- a/patches/server/0691-Expand-PlayerItemDamageEvent.patch +++ /dev/null @@ -1,23 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: HexedHero <6012891+HexedHero@users.noreply.github.com> -Date: Sun, 10 Apr 2022 06:26:32 +0100 -Subject: [PATCH] Expand PlayerItemDamageEvent - - -diff --git a/src/main/java/net/minecraft/world/item/ItemStack.java b/src/main/java/net/minecraft/world/item/ItemStack.java -index ec3be7fb4eb4560fe0106ac127f17d7437612157..bee59df6a8f30416f94c1a4fbd5e2629336e842f 100644 ---- a/src/main/java/net/minecraft/world/item/ItemStack.java -+++ b/src/main/java/net/minecraft/world/item/ItemStack.java -@@ -650,10 +650,11 @@ public final class ItemStack implements DataComponentHolder { - if (this.isDamageableItem()) { - if (player == null || !player.hasInfiniteMaterials()) { - if (amount > 0) { -+ int originalDamage = amount; // Paper - Expand PlayerItemDamageEvent - amount = EnchantmentHelper.processDurabilityChange(world, this, amount); - // CraftBukkit start - if (player instanceof ServerPlayer serverPlayer) { // Paper - Add EntityDamageItemEvent -- PlayerItemDamageEvent event = new PlayerItemDamageEvent(serverPlayer.getBukkitEntity(), CraftItemStack.asCraftMirror(this), amount); // Paper - Add EntityDamageItemEvent -+ PlayerItemDamageEvent event = new PlayerItemDamageEvent(serverPlayer.getBukkitEntity(), CraftItemStack.asCraftMirror(this), amount, originalDamage); // Paper - Add EntityDamageItemEvent & Expand PlayerItemDamageEvent - event.getPlayer().getServer().getPluginManager().callEvent(event); - - if (amount != event.getDamage() || event.isCancelled()) { diff --git a/patches/server/0691-Fire-CauldronLevelChange-on-initial-fill.patch b/patches/server/0691-Fire-CauldronLevelChange-on-initial-fill.patch new file mode 100644 index 000000000000..86520e0647a9 --- /dev/null +++ b/patches/server/0691-Fire-CauldronLevelChange-on-initial-fill.patch @@ -0,0 +1,122 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Jake Potrebic +Date: Tue, 29 Mar 2022 13:46:23 -0700 +Subject: [PATCH] Fire CauldronLevelChange on initial fill + +Also don't fire level events or game events if stalactite +drip is cancelled + +diff --git a/src/main/java/net/minecraft/core/cauldron/CauldronInteraction.java b/src/main/java/net/minecraft/core/cauldron/CauldronInteraction.java +index 637fef0d3cabc867cf861b507b90221e27a711d1..df76185d42075834a39c79515917e03beb938a06 100644 +--- a/src/main/java/net/minecraft/core/cauldron/CauldronInteraction.java ++++ b/src/main/java/net/minecraft/core/cauldron/CauldronInteraction.java +@@ -74,7 +74,7 @@ public interface CauldronInteraction { + if (potioncontents != null && potioncontents.is(Potions.WATER)) { + if (!world.isClientSide) { + // CraftBukkit start +- if (!LayeredCauldronBlock.changeLevel(iblockdata, world, blockposition, Blocks.WATER_CAULDRON.defaultBlockState(), entityhuman, CauldronLevelChangeEvent.ChangeReason.BOTTLE_EMPTY)) { ++ if (!LayeredCauldronBlock.changeLevel(iblockdata, world, blockposition, Blocks.WATER_CAULDRON.defaultBlockState(), entityhuman, CauldronLevelChangeEvent.ChangeReason.BOTTLE_EMPTY, false)) { // Paper - Call CauldronLevelChangeEvent + return InteractionResult.SUCCESS; + } + // CraftBukkit end +@@ -129,7 +129,7 @@ public interface CauldronInteraction { + if (potioncontents != null && potioncontents.is(Potions.WATER)) { + if (!world.isClientSide) { + // CraftBukkit start +- if (!LayeredCauldronBlock.changeLevel(iblockdata, world, blockposition, iblockdata.cycle(LayeredCauldronBlock.LEVEL), entityhuman, CauldronLevelChangeEvent.ChangeReason.BOTTLE_EMPTY)) { ++ if (!LayeredCauldronBlock.changeLevel(iblockdata, world, blockposition, iblockdata.cycle(LayeredCauldronBlock.LEVEL), entityhuman, CauldronLevelChangeEvent.ChangeReason.BOTTLE_EMPTY, false)) { // Paper - Call CauldronLevelChangeEvent + return InteractionResult.SUCCESS; + } + // CraftBukkit end +@@ -215,7 +215,7 @@ public interface CauldronInteraction { + } else { + if (!world.isClientSide) { + // CraftBukkit start +- if (!LayeredCauldronBlock.changeLevel(state, world, pos, Blocks.CAULDRON.defaultBlockState(), player, CauldronLevelChangeEvent.ChangeReason.BUCKET_FILL)) { ++ if (!LayeredCauldronBlock.changeLevel(state, world, pos, Blocks.CAULDRON.defaultBlockState(), player, CauldronLevelChangeEvent.ChangeReason.BUCKET_FILL, false)) { // Paper - Call CauldronLevelChangeEvent + return InteractionResult.SUCCESS; + } + // CraftBukkit end +@@ -236,7 +236,7 @@ public interface CauldronInteraction { + static InteractionResult emptyBucket(Level world, BlockPos pos, Player player, InteractionHand hand, ItemStack stack, BlockState state, SoundEvent soundEvent) { + if (!world.isClientSide) { + // CraftBukkit start +- if (!LayeredCauldronBlock.changeLevel(state, world, pos, state, player, CauldronLevelChangeEvent.ChangeReason.BUCKET_EMPTY)) { ++ if (!LayeredCauldronBlock.changeLevel(state, world, pos, state, player, CauldronLevelChangeEvent.ChangeReason.BUCKET_EMPTY, false)) { // Paper - Call CauldronLevelChangeEvent + return InteractionResult.SUCCESS; + } + // CraftBukkit end +diff --git a/src/main/java/net/minecraft/world/level/block/CauldronBlock.java b/src/main/java/net/minecraft/world/level/block/CauldronBlock.java +index 8d5ad9848d9652b4fd7179c425c47a683ee169ef..c9968934f4ecaa8d81e545f279b3001c7b1ce545 100644 +--- a/src/main/java/net/minecraft/world/level/block/CauldronBlock.java ++++ b/src/main/java/net/minecraft/world/level/block/CauldronBlock.java +@@ -44,9 +44,19 @@ public class CauldronBlock extends AbstractCauldronBlock { + public void handlePrecipitation(BlockState state, Level world, BlockPos pos, Biome.Precipitation precipitation) { + if (CauldronBlock.shouldHandlePrecipitation(world, precipitation)) { + if (precipitation == Biome.Precipitation.RAIN) { ++ // Paper start - Call CauldronLevelChangeEvent ++ if (!LayeredCauldronBlock.changeLevel(state, world, pos, Blocks.WATER_CAULDRON.defaultBlockState(), null, CauldronLevelChangeEvent.ChangeReason.NATURAL_FILL, false)) { // avoid duplicate game event ++ return; ++ } ++ // Paper end - Call CauldronLevelChangeEvent + world.setBlockAndUpdate(pos, Blocks.WATER_CAULDRON.defaultBlockState()); + world.gameEvent((Entity) null, (Holder) GameEvent.BLOCK_CHANGE, pos); + } else if (precipitation == Biome.Precipitation.SNOW) { ++ // Paper start - Call CauldronLevelChangeEvent ++ if (!LayeredCauldronBlock.changeLevel(state, world, pos, Blocks.POWDER_SNOW_CAULDRON.defaultBlockState(), null, CauldronLevelChangeEvent.ChangeReason.NATURAL_FILL, false)) { // avoid duplicate game event ++ return; ++ } ++ // Paper end - Call CauldronLevelChangeEvent + world.setBlockAndUpdate(pos, Blocks.POWDER_SNOW_CAULDRON.defaultBlockState()); + world.gameEvent((Entity) null, (Holder) GameEvent.BLOCK_CHANGE, pos); + } +@@ -65,11 +75,19 @@ public class CauldronBlock extends AbstractCauldronBlock { + + if (fluid == Fluids.WATER) { + iblockdata1 = Blocks.WATER_CAULDRON.defaultBlockState(); +- LayeredCauldronBlock.changeLevel(state, world, pos, iblockdata1, null, CauldronLevelChangeEvent.ChangeReason.NATURAL_FILL); // CraftBukkit ++ // Paper start - Call CauldronLevelChangeEvent; don't send level event or game event if cancelled ++ if (!LayeredCauldronBlock.changeLevel(state, world, pos, iblockdata1, null, CauldronLevelChangeEvent.ChangeReason.NATURAL_FILL)) { // CraftBukkit ++ return; ++ } ++ // Paper end - Call CauldronLevelChangeEvent + world.levelEvent(1047, pos, 0); + } else if (fluid == Fluids.LAVA) { + iblockdata1 = Blocks.LAVA_CAULDRON.defaultBlockState(); +- LayeredCauldronBlock.changeLevel(state, world, pos, iblockdata1, null, CauldronLevelChangeEvent.ChangeReason.NATURAL_FILL); // CraftBukkit ++ // Paper start - Call CauldronLevelChangeEvent; don't send level event or game event if cancelled ++ if (!LayeredCauldronBlock.changeLevel(state, world, pos, iblockdata1, null, CauldronLevelChangeEvent.ChangeReason.NATURAL_FILL)) { // CraftBukkit ++ return; ++ } ++ // Paper end - Call CauldronLevelChangeEvent + world.levelEvent(1046, pos, 0); + } + +diff --git a/src/main/java/net/minecraft/world/level/block/LayeredCauldronBlock.java b/src/main/java/net/minecraft/world/level/block/LayeredCauldronBlock.java +index 4ab73a083eba2ad3e12526af0a0dbcfba5cf6c14..806d18689126d0a971665a33d7fc91102ec76db5 100644 +--- a/src/main/java/net/minecraft/world/level/block/LayeredCauldronBlock.java ++++ b/src/main/java/net/minecraft/world/level/block/LayeredCauldronBlock.java +@@ -107,7 +107,13 @@ public class LayeredCauldronBlock extends AbstractCauldronBlock { + } + + // CraftBukkit start +- public static boolean changeLevel(BlockState iblockdata, Level world, BlockPos blockposition, BlockState newBlock, Entity entity, CauldronLevelChangeEvent.ChangeReason reason) { ++ // Paper start - Call CauldronLevelChangeEvent ++ public static boolean changeLevel(BlockState iblockdata, Level world, BlockPos blockposition, BlockState newBlock, @javax.annotation.Nullable Entity entity, CauldronLevelChangeEvent.ChangeReason reason) { // Paper - entity is nullable ++ return changeLevel(iblockdata, world, blockposition, newBlock, entity, reason, true); ++ } ++ ++ public static boolean changeLevel(BlockState iblockdata, Level world, BlockPos blockposition, BlockState newBlock, @javax.annotation.Nullable Entity entity, CauldronLevelChangeEvent.ChangeReason reason, boolean sendGameEvent) { // Paper - entity is nullable ++ // Paper end - Call CauldronLevelChangeEvent + CraftBlockState newState = CraftBlockStates.getBlockState(world, blockposition); + newState.setData(newBlock); + +@@ -120,7 +126,7 @@ public class LayeredCauldronBlock extends AbstractCauldronBlock { + return false; + } + newState.update(true); +- world.gameEvent((Holder) GameEvent.BLOCK_CHANGE, blockposition, GameEvent.Context.of(newBlock)); ++ if (sendGameEvent) world.gameEvent((Holder) GameEvent.BLOCK_CHANGE, blockposition, GameEvent.Context.of(newBlock)); // Paper - Call CauldronLevelChangeEvent + return true; + } + // CraftBukkit end diff --git a/patches/server/0692-WorldCreator-keepSpawnLoaded.patch b/patches/server/0692-WorldCreator-keepSpawnLoaded.patch deleted file mode 100644 index 6369ed67aad2..000000000000 --- a/patches/server/0692-WorldCreator-keepSpawnLoaded.patch +++ /dev/null @@ -1,19 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Shane Freeder -Date: Sat, 3 Jul 2021 21:18:28 +0100 -Subject: [PATCH] WorldCreator#keepSpawnLoaded - - -diff --git a/src/main/java/org/bukkit/craftbukkit/CraftServer.java b/src/main/java/org/bukkit/craftbukkit/CraftServer.java -index 183d9672ad60e2b7db48c283b1ca863df01ad658..cfdfb81bc81382b71131c037ebd56f24573d4e34 100644 ---- a/src/main/java/org/bukkit/craftbukkit/CraftServer.java -+++ b/src/main/java/org/bukkit/craftbukkit/CraftServer.java -@@ -1326,7 +1326,7 @@ public final class CraftServer implements Server { - } - - // If set to not keep spawn in memory (changed from default) then adjust rule accordingly -- if (!creator.keepSpawnInMemory()) { -+ if (creator.keepSpawnLoaded() == net.kyori.adventure.util.TriState.FALSE) { // Paper - worlddata.getGameRules().getRule(GameRules.RULE_SPAWN_CHUNK_RADIUS).set(0, null); - } - ServerLevel internal = (ServerLevel) new ServerLevel(this.console, this.console.executor, worldSession, worlddata, worldKey, worlddimension, this.getServer().progressListenerFactory.create(worlddata.getGameRules().getInt(GameRules.RULE_SPAWN_CHUNK_RADIUS)), diff --git a/patches/server/0692-fix-powder-snow-cauldrons-not-turning-to-water.patch b/patches/server/0692-fix-powder-snow-cauldrons-not-turning-to-water.patch new file mode 100644 index 000000000000..201c323a3539 --- /dev/null +++ b/patches/server/0692-fix-powder-snow-cauldrons-not-turning-to-water.patch @@ -0,0 +1,45 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Jake Potrebic +Date: Thu, 30 Dec 2021 14:02:13 -0800 +Subject: [PATCH] fix powder snow cauldrons not turning to water + +Powder snow cauldrons should turn to water when +extinguishing an entity + +diff --git a/src/main/java/net/minecraft/world/level/block/LayeredCauldronBlock.java b/src/main/java/net/minecraft/world/level/block/LayeredCauldronBlock.java +index 806d18689126d0a971665a33d7fc91102ec76db5..7dd6b7c0ea472cfbc7ece55bc64bc5d85be4a6c0 100644 +--- a/src/main/java/net/minecraft/world/level/block/LayeredCauldronBlock.java ++++ b/src/main/java/net/minecraft/world/level/block/LayeredCauldronBlock.java +@@ -73,7 +73,7 @@ public class LayeredCauldronBlock extends AbstractCauldronBlock { + // CraftBukkit start - moved down + // entity.clearFire(); + if (entity.mayInteract(worldserver, pos)) { +- if (this.handleEntityOnFireInside(state, world, pos, entity)) { ++ if (this.handleEntityOnFireInsideWithEvent(state, world, pos, entity)) { // Paper - fix powdered snow cauldron extinguishing entities + entity.clearFire(); + } + // CraftBukkit end +@@ -84,6 +84,7 @@ public class LayeredCauldronBlock extends AbstractCauldronBlock { + } + + // CraftBukkit start ++ @io.papermc.paper.annotation.DoNotUse @Deprecated // Paper - fix powdered snow cauldron extinguishing entities; use #handleEntityOnFireInsideWithEvent + private boolean handleEntityOnFireInside(BlockState iblockdata, Level world, BlockPos blockposition, Entity entity) { + if (this.precipitationType == Biome.Precipitation.SNOW) { + return LayeredCauldronBlock.lowerFillLevel((BlockState) Blocks.WATER_CAULDRON.defaultBlockState().setValue(LayeredCauldronBlock.LEVEL, (Integer) iblockdata.getValue(LayeredCauldronBlock.LEVEL)), world, blockposition, entity, CauldronLevelChangeEvent.ChangeReason.EXTINGUISH); +@@ -93,6 +94,15 @@ public class LayeredCauldronBlock extends AbstractCauldronBlock { + } + + } ++ // Paper start - fix powdered snow cauldron extinguishing entities ++ protected boolean handleEntityOnFireInsideWithEvent(BlockState state, Level world, BlockPos pos, Entity entity) { ++ if (this.precipitationType == Biome.Precipitation.SNOW) { ++ return LayeredCauldronBlock.lowerFillLevel((BlockState) Blocks.WATER_CAULDRON.defaultBlockState().setValue(LayeredCauldronBlock.LEVEL, (Integer) state.getValue(LayeredCauldronBlock.LEVEL)), world, pos, entity, CauldronLevelChangeEvent.ChangeReason.EXTINGUISH); ++ } else { ++ return LayeredCauldronBlock.lowerFillLevel(state, world, pos, entity, CauldronLevelChangeEvent.ChangeReason.EXTINGUISH); ++ } ++ } ++ // Paper end - fix powdered snow cauldron extinguishing entities + + public static void lowerFillLevel(BlockState state, Level world, BlockPos pos) { + // CraftBukkit start diff --git a/patches/server/0693-Add-PlayerStopUsingItemEvent.patch b/patches/server/0693-Add-PlayerStopUsingItemEvent.patch new file mode 100644 index 000000000000..2e1c8f29d000 --- /dev/null +++ b/patches/server/0693-Add-PlayerStopUsingItemEvent.patch @@ -0,0 +1,18 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: u9g +Date: Tue, 3 May 2022 20:41:37 -0400 +Subject: [PATCH] Add PlayerStopUsingItemEvent + + +diff --git a/src/main/java/net/minecraft/world/entity/LivingEntity.java b/src/main/java/net/minecraft/world/entity/LivingEntity.java +index 8b454382f59ee36ec6f45ca8445b3f1a956ff668..786ac2127bc743cf2a33776314a4b5c197f35538 100644 +--- a/src/main/java/net/minecraft/world/entity/LivingEntity.java ++++ b/src/main/java/net/minecraft/world/entity/LivingEntity.java +@@ -4202,6 +4202,7 @@ public abstract class LivingEntity extends Entity implements Attackable { + + public void releaseUsingItem() { + if (!this.useItem.isEmpty()) { ++ if (this instanceof ServerPlayer) new io.papermc.paper.event.player.PlayerStopUsingItemEvent((Player) getBukkitEntity(), useItem.asBukkitMirror(), getTicksUsingItem()).callEvent(); // Paper - Add PlayerStopUsingItemEvent + this.useItem.releaseUsing(this.level(), this, this.getUseItemRemainingTicks()); + if (this.useItem.useOnRelease()) { + this.updatingUsingItem(); diff --git a/patches/server/0694-Don-t-tick-markers.patch b/patches/server/0694-Don-t-tick-markers.patch new file mode 100644 index 000000000000..e5c290a988cc --- /dev/null +++ b/patches/server/0694-Don-t-tick-markers.patch @@ -0,0 +1,36 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Noah van der Aa +Date: Fri, 7 Jan 2022 11:58:26 +0100 +Subject: [PATCH] Don't tick markers + +Fixes https://github.com/PaperMC/Paper/issues/7276 and https://github.com/PaperMC/Paper/issues/8118 +by using a config option that, when set to false, does not add markers to the entity +tick list at all and ignores them in Spigot's activation range checks. The entity tick +list is only used in the tick and tickPassenger methods, so we can safely not add the +markers to it. When the config option is set to true, markers are ticked as normal. + +diff --git a/src/main/java/io/papermc/paper/command/subcommands/EntityCommand.java b/src/main/java/io/papermc/paper/command/subcommands/EntityCommand.java +index 67fcba634f8183bb33834ac3b2c3dcfb8d87129e..777b789fdcdf297309cfb36fc7f77e3fdb6327ca 100644 +--- a/src/main/java/io/papermc/paper/command/subcommands/EntityCommand.java ++++ b/src/main/java/io/papermc/paper/command/subcommands/EntityCommand.java +@@ -109,7 +109,7 @@ public final class EntityCommand implements PaperSubcommand { + ChunkPos chunk = e.chunkPosition(); + info.left++; + info.right.put(chunk, info.right.getOrDefault(chunk, 0) + 1); +- if (!chunkProviderServer.isPositionTicking(e)) { ++ if (!chunkProviderServer.isPositionTicking(e) || (e instanceof net.minecraft.world.entity.Marker && !world.paperConfig().entities.markers.tick)) { // Paper - Configurable marker ticking + nonEntityTicking.merge(key, 1, Integer::sum); + } + }); +diff --git a/src/main/java/net/minecraft/server/level/ServerLevel.java b/src/main/java/net/minecraft/server/level/ServerLevel.java +index 2d77e9526917a83987ae0486a669538d5417b781..f4b1b5f1903015b3c4650186466c8183560c9de0 100644 +--- a/src/main/java/net/minecraft/server/level/ServerLevel.java ++++ b/src/main/java/net/minecraft/server/level/ServerLevel.java +@@ -2219,6 +2219,7 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe + } + + public void onTickingStart(Entity entity) { ++ if (entity instanceof net.minecraft.world.entity.Marker && !paperConfig().entities.markers.tick) return; // Paper - Configurable marker ticking + ServerLevel.this.entityTickList.add(entity); + } + diff --git a/patches/server/0694-Trigger-bee_nest_destroyed-trigger-in-the-correct-pl.patch b/patches/server/0694-Trigger-bee_nest_destroyed-trigger-in-the-correct-pl.patch deleted file mode 100644 index ed6f34442741..000000000000 --- a/patches/server/0694-Trigger-bee_nest_destroyed-trigger-in-the-correct-pl.patch +++ /dev/null @@ -1,54 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Jake Potrebic -Date: Wed, 2 Feb 2022 13:50:06 -0800 -Subject: [PATCH] Trigger bee_nest_destroyed trigger in the correct place - - -diff --git a/src/main/java/net/minecraft/server/level/ServerPlayerGameMode.java b/src/main/java/net/minecraft/server/level/ServerPlayerGameMode.java -index 073cf184a0e7af41048ae67a9b17b4cdfcc43c35..4d024956156aefde7df308642dfd0a40779e0633 100644 ---- a/src/main/java/net/minecraft/server/level/ServerPlayerGameMode.java -+++ b/src/main/java/net/minecraft/server/level/ServerPlayerGameMode.java -@@ -429,12 +429,16 @@ public class ServerPlayerGameMode { - block.destroy(this.level, pos, iblockdata1); - } - -+ ItemStack mainHandStack = null; // Paper - Trigger bee_nest_destroyed trigger in the correct place -+ boolean isCorrectTool = false; // Paper - Trigger bee_nest_destroyed trigger in the correct place - if (this.isCreative()) { - // return true; // CraftBukkit - } else { - ItemStack itemstack = this.player.getMainHandItem(); - ItemStack itemstack1 = itemstack.copy(); - boolean flag1 = this.player.hasCorrectToolForDrops(iblockdata1); -+ mainHandStack = itemstack1; // Paper - Trigger bee_nest_destroyed trigger in the correct place -+ isCorrectTool = flag1; // Paper - Trigger bee_nest_destroyed trigger in the correct place - - itemstack.mineBlock(this.level, iblockdata1, pos, this.player); - if (flag && flag1 && event.isDropItems()) { // CraftBukkit - Check if block should drop items -@@ -455,6 +459,13 @@ public class ServerPlayerGameMode { - if (flag && event != null) { - iblockdata.getBlock().popExperience(this.level, pos, event.getExpToDrop(), this.player); // Paper - } -+ // Paper start - Trigger bee_nest_destroyed trigger in the correct place (check impls of block#playerDestroy) -+ if (mainHandStack != null) { -+ if (flag && isCorrectTool && event.isDropItems() && block instanceof net.minecraft.world.level.block.BeehiveBlock && tileentity instanceof net.minecraft.world.level.block.entity.BeehiveBlockEntity beehiveBlockEntity) { // simulates the guard on block#playerDestroy above -+ CriteriaTriggers.BEE_NEST_DESTROYED.trigger(player, iblockdata, mainHandStack, beehiveBlockEntity.getOccupantCount()); -+ } -+ } -+ // Paper end - Trigger bee_nest_destroyed trigger in the correct place - - return true; - // CraftBukkit end -diff --git a/src/main/java/net/minecraft/world/level/block/BeehiveBlock.java b/src/main/java/net/minecraft/world/level/block/BeehiveBlock.java -index 8d6736003934c5958f600660bdee58b386c39da4..c75227b2ea165dcd65c203e60157ac7cdebd4bc6 100644 ---- a/src/main/java/net/minecraft/world/level/block/BeehiveBlock.java -+++ b/src/main/java/net/minecraft/world/level/block/BeehiveBlock.java -@@ -95,7 +95,7 @@ public class BeehiveBlock extends BaseEntityBlock { - this.angerNearbyBees(world, pos); - } - -- CriteriaTriggers.BEE_NEST_DESTROYED.trigger((ServerPlayer) player, state, tool, tileentitybeehive.getOccupantCount()); -+ // CriteriaTriggers.BEE_NEST_DESTROYED.trigger((ServerPlayer) player, state, tool, tileentitybeehive.getOccupantCount()); // Paper - Trigger bee_nest_destroyed trigger in the correct place; moved until after items are dropped - } - - } diff --git a/patches/server/0695-Add-EntityDyeEvent-and-CollarColorable-interface.patch b/patches/server/0695-Add-EntityDyeEvent-and-CollarColorable-interface.patch deleted file mode 100644 index b73fc3193a41..000000000000 --- a/patches/server/0695-Add-EntityDyeEvent-and-CollarColorable-interface.patch +++ /dev/null @@ -1,43 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Jake Potrebic -Date: Fri, 18 Mar 2022 21:15:55 -0700 -Subject: [PATCH] Add EntityDyeEvent and CollarColorable interface - - -diff --git a/src/main/java/net/minecraft/world/entity/animal/Cat.java b/src/main/java/net/minecraft/world/entity/animal/Cat.java -index d44807c16712afd37efdbf434d1afb12a7c3d343..2ed442c8d36f285420cf84a956e90b6036384ce0 100644 ---- a/src/main/java/net/minecraft/world/entity/animal/Cat.java -+++ b/src/main/java/net/minecraft/world/entity/animal/Cat.java -@@ -378,6 +378,13 @@ public class Cat extends TamableAnimal implements VariantHolder +Date: Sun, 5 Dec 2021 14:58:17 -0500 +Subject: [PATCH] Expand FallingBlock API + +- add auto expire setting +- add setter for block data +- add accessors for block state + +== AT == +public net.minecraft.world.entity.item.FallingBlockEntity blockState + +Co-authored-by: Lukas Planz + +diff --git a/src/main/java/net/minecraft/world/entity/item/FallingBlockEntity.java b/src/main/java/net/minecraft/world/entity/item/FallingBlockEntity.java +index 72abeb4f37b70094498ed3b18e8f73346ba0ead0..0ecda05a98046938546fe7bc6cf2590c886add41 100644 +--- a/src/main/java/net/minecraft/world/entity/item/FallingBlockEntity.java ++++ b/src/main/java/net/minecraft/world/entity/item/FallingBlockEntity.java +@@ -71,6 +71,7 @@ public class FallingBlockEntity extends Entity { + public CompoundTag blockData; + public boolean forceTickAfterTeleportToDuplicate; + protected static final EntityDataAccessor DATA_START_POS = SynchedEntityData.defineId(FallingBlockEntity.class, EntityDataSerializers.BLOCK_POS); ++ public boolean autoExpire = true; // Paper - Expand FallingBlock API + + public FallingBlockEntity(EntityType type, Level world) { + super(type, world); +@@ -191,7 +192,7 @@ public class FallingBlockEntity extends Entity { + } + + if (!this.onGround() && !flag1) { +- if (this.time > 100 && (blockposition.getY() <= this.level().getMinY() || blockposition.getY() > this.level().getMaxY()) || this.time > 600) { ++ if ((this.time > 100 && autoExpire) && (blockposition.getY() <= this.level().getMinY() || blockposition.getY() > this.level().getMaxY()) || (this.time > 600 && autoExpire)) { // Paper - Expand FallingBlock API + if (this.dropItem && worldserver.getGameRules().getBoolean(GameRules.RULE_DOENTITYDROPS)) { + this.spawnAtLocation(worldserver, (ItemLike) block); + } +@@ -338,6 +339,7 @@ public class FallingBlockEntity extends Entity { + } + + nbt.putBoolean("CancelDrop", this.cancelDrop); ++ if (!autoExpire) {nbt.putBoolean("Paper.AutoExpire", false);} // Paper - Expand FallingBlock API + } + + @Override +@@ -365,6 +367,11 @@ public class FallingBlockEntity extends Entity { + this.blockState = Blocks.SAND.defaultBlockState(); + } + ++ // Paper start - Expand FallingBlock API ++ if (nbt.contains("Paper.AutoExpire")) { ++ this.autoExpire = nbt.getBoolean("Paper.AutoExpire"); ++ } ++ // Paper end - Expand FallingBlock API + } + + public void setHurtsEntities(float fallHurtAmount, int fallHurtMax) { +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftFallingBlock.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftFallingBlock.java +index a7a3f74b846112d752fe04162b30805961457b11..1359d25a32b4a5d5e8e68ce737bd19f7b5afaf69 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftFallingBlock.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftFallingBlock.java +@@ -33,6 +33,31 @@ public class CraftFallingBlock extends CraftEntity implements FallingBlock { + public BlockData getBlockData() { + return CraftBlockData.fromData(this.getHandle().getBlockState()); + } ++ // Paper start - Expand FallingBlock API ++ @Override ++ public void setBlockData(final BlockData blockData) { ++ Preconditions.checkArgument(blockData != null, "blockData"); ++ final net.minecraft.world.level.block.state.BlockState oldState = this.getHandle().blockState, newState = ((CraftBlockData) blockData).getState(); ++ this.getHandle().blockState = newState; ++ this.getHandle().blockData = null; ++ ++ if (oldState != newState) this.update(); ++ } ++ ++ @Override ++ public org.bukkit.block.BlockState getBlockState() { ++ return org.bukkit.craftbukkit.block.CraftBlockStates.getBlockState(this.getHandle().blockState, this.getHandle().blockData); ++ } ++ ++ @Override ++ public void setBlockState(final org.bukkit.block.BlockState blockState) { ++ Preconditions.checkArgument(blockState != null, "blockState"); ++ // Calls #update if needed, the block data compound tag is not synced with the client and hence can be mutated after the sync with clients. ++ // The call also clears any potential old block data. ++ this.setBlockData(blockState.getBlockData()); ++ if (blockState instanceof final org.bukkit.craftbukkit.block.CraftBlockEntityState tileEntity) this.getHandle().blockData = tileEntity.getSnapshotNBT(); ++ } ++ // Paper end - Expand FallingBlock API + + @Override + public boolean getDropItem() { +@@ -101,4 +126,15 @@ public class CraftFallingBlock extends CraftEntity implements FallingBlock { + this.setHurtEntities(true); + } + } ++ // Paper start - Expand FallingBlock API ++ @Override ++ public boolean doesAutoExpire() { ++ return this.getHandle().autoExpire; ++ } ++ ++ @Override ++ public void shouldAutoExpire(boolean autoExpires) { ++ this.getHandle().autoExpire = autoExpires; ++ } ++ // Paper end - Expand FallingBlock API + } diff --git a/patches/server/0696-Add-support-for-Proxy-Protocol.patch b/patches/server/0696-Add-support-for-Proxy-Protocol.patch new file mode 100644 index 000000000000..5d35c10858ca --- /dev/null +++ b/patches/server/0696-Add-support-for-Proxy-Protocol.patch @@ -0,0 +1,65 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: PanSzelescik +Date: Thu, 7 Apr 2022 16:13:39 +0200 +Subject: [PATCH] Add support for Proxy Protocol + + +diff --git a/build.gradle.kts b/build.gradle.kts +index 38585b7f0b8e1e287b37820924a1b0d464fe9e99..25001d6cf4f70bd01ab304625b49ec45f5b1f525 100644 +--- a/build.gradle.kts ++++ b/build.gradle.kts +@@ -28,6 +28,7 @@ dependencies { + log4jPlugins.annotationProcessorConfigurationName("org.apache.logging.log4j:log4j-core:2.19.0") // Paper - Needed to generate meta for our Log4j plugins + runtimeOnly(log4jPlugins.output) + alsoShade(log4jPlugins.output) ++ implementation("io.netty:netty-codec-haproxy:4.1.97.Final") // Paper - Add support for proxy protocol + // Paper end + implementation("org.apache.logging.log4j:log4j-iostreams:2.22.1") // Paper - remove exclusion + implementation("org.ow2.asm:asm-commons:9.7.1") +diff --git a/src/main/java/net/minecraft/server/network/ServerConnectionListener.java b/src/main/java/net/minecraft/server/network/ServerConnectionListener.java +index c63c194c44646e6bc1a59426552787011fc2ced5..c62df32af11636ad408b584fcc590590ce4fb0d0 100644 +--- a/src/main/java/net/minecraft/server/network/ServerConnectionListener.java ++++ b/src/main/java/net/minecraft/server/network/ServerConnectionListener.java +@@ -104,6 +104,12 @@ public class ServerConnectionListener { + ServerConnectionListener.LOGGER.info("Using default channel type"); + } + ++ // Paper start - Warn people with console access that HAProxy is in use. ++ if (io.papermc.paper.configuration.GlobalConfiguration.get().proxies.proxyProtocol) { ++ ServerConnectionListener.LOGGER.warn("Using HAProxy, please ensure the server port is adequately firewalled."); ++ } ++ // Paper end - Warn people with console access that HAProxy is in use. ++ + this.channels.add(((ServerBootstrap) ((ServerBootstrap) (new ServerBootstrap()).channel(oclass)).childHandler(new ChannelInitializer() { + protected void initChannel(Channel channel) { + try { +@@ -123,6 +129,29 @@ public class ServerConnectionListener { + Connection object = j > 0 ? new RateKickingConnection(j) : new Connection(PacketFlow.SERVERBOUND); // CraftBukkit - decompile error + + //ServerConnectionListener.this.connections.add(object); // Paper ++ // Paper start - Add support for Proxy Protocol ++ if (io.papermc.paper.configuration.GlobalConfiguration.get().proxies.proxyProtocol) { ++ channel.pipeline().addAfter("timeout", "haproxy-decoder", new io.netty.handler.codec.haproxy.HAProxyMessageDecoder()); ++ channel.pipeline().addAfter("haproxy-decoder", "haproxy-handler", new ChannelInboundHandlerAdapter() { ++ @Override ++ public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception { ++ if (msg instanceof io.netty.handler.codec.haproxy.HAProxyMessage message) { ++ if (message.command() == io.netty.handler.codec.haproxy.HAProxyCommand.PROXY) { ++ String realaddress = message.sourceAddress(); ++ int realport = message.sourcePort(); ++ ++ SocketAddress socketaddr = new java.net.InetSocketAddress(realaddress, realport); ++ ++ Connection connection = (Connection) channel.pipeline().get("packet_handler"); ++ connection.address = socketaddr; ++ } ++ } else { ++ super.channelRead(ctx, msg); ++ } ++ } ++ }); ++ } ++ // Paper end - Add support for proxy protocol + pending.add(object); // Paper - prevent blocking on adding a new connection while the server is ticking + ((Connection) object).configurePacketHandler(channelpipeline); + ((Connection) object).setListenerForServerboundHandshake(new ServerHandshakePacketListenerImpl(ServerConnectionListener.this.server, (Connection) object)); diff --git a/patches/server/0696-Fire-CauldronLevelChange-on-initial-fill.patch b/patches/server/0696-Fire-CauldronLevelChange-on-initial-fill.patch deleted file mode 100644 index 07f836b363bc..000000000000 --- a/patches/server/0696-Fire-CauldronLevelChange-on-initial-fill.patch +++ /dev/null @@ -1,122 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Jake Potrebic -Date: Tue, 29 Mar 2022 13:46:23 -0700 -Subject: [PATCH] Fire CauldronLevelChange on initial fill - -Also don't fire level events or game events if stalactite -drip is cancelled - -diff --git a/src/main/java/net/minecraft/core/cauldron/CauldronInteraction.java b/src/main/java/net/minecraft/core/cauldron/CauldronInteraction.java -index 9ebd5310ee358acd83123a30415d9f7eb6aa36d9..f301c20e808b77cb3fcffd9a7c8102928306456e 100644 ---- a/src/main/java/net/minecraft/core/cauldron/CauldronInteraction.java -+++ b/src/main/java/net/minecraft/core/cauldron/CauldronInteraction.java -@@ -81,7 +81,7 @@ public interface CauldronInteraction { - if (potioncontents != null && potioncontents.is(Potions.WATER)) { - if (!world.isClientSide) { - // CraftBukkit start -- if (!LayeredCauldronBlock.changeLevel(iblockdata, world, blockposition, Blocks.WATER_CAULDRON.defaultBlockState(), entityhuman, CauldronLevelChangeEvent.ChangeReason.BOTTLE_EMPTY)) { -+ if (!LayeredCauldronBlock.changeLevel(iblockdata, world, blockposition, Blocks.WATER_CAULDRON.defaultBlockState(), entityhuman, CauldronLevelChangeEvent.ChangeReason.BOTTLE_EMPTY, false)) { // Paper - Call CauldronLevelChangeEvent - return ItemInteractionResult.SUCCESS; - } - // CraftBukkit end -@@ -136,7 +136,7 @@ public interface CauldronInteraction { - if (potioncontents != null && potioncontents.is(Potions.WATER)) { - if (!world.isClientSide) { - // CraftBukkit start -- if (!LayeredCauldronBlock.changeLevel(iblockdata, world, blockposition, iblockdata.cycle(LayeredCauldronBlock.LEVEL), entityhuman, CauldronLevelChangeEvent.ChangeReason.BOTTLE_EMPTY)) { -+ if (!LayeredCauldronBlock.changeLevel(iblockdata, world, blockposition, iblockdata.cycle(LayeredCauldronBlock.LEVEL), entityhuman, CauldronLevelChangeEvent.ChangeReason.BOTTLE_EMPTY, false)) { // Paper - Call CauldronLevelChangeEvent - return ItemInteractionResult.SUCCESS; - } - // CraftBukkit end -@@ -222,7 +222,7 @@ public interface CauldronInteraction { - } else { - if (!world.isClientSide) { - // CraftBukkit start -- if (!LayeredCauldronBlock.changeLevel(state, world, pos, Blocks.CAULDRON.defaultBlockState(), player, CauldronLevelChangeEvent.ChangeReason.BUCKET_FILL)) { -+ if (!LayeredCauldronBlock.changeLevel(state, world, pos, Blocks.CAULDRON.defaultBlockState(), player, CauldronLevelChangeEvent.ChangeReason.BUCKET_FILL, false)) { // Paper - Call CauldronLevelChangeEvent - return ItemInteractionResult.SUCCESS; - } - // CraftBukkit end -@@ -243,7 +243,7 @@ public interface CauldronInteraction { - static ItemInteractionResult emptyBucket(Level world, BlockPos pos, Player player, InteractionHand hand, ItemStack stack, BlockState state, SoundEvent soundEvent) { - if (!world.isClientSide) { - // CraftBukkit start -- if (!LayeredCauldronBlock.changeLevel(state, world, pos, state, player, CauldronLevelChangeEvent.ChangeReason.BUCKET_EMPTY)) { -+ if (!LayeredCauldronBlock.changeLevel(state, world, pos, state, player, CauldronLevelChangeEvent.ChangeReason.BUCKET_EMPTY, false)) { // Paper - Call CauldronLevelChangeEvent - return ItemInteractionResult.SUCCESS; - } - // CraftBukkit end -diff --git a/src/main/java/net/minecraft/world/level/block/CauldronBlock.java b/src/main/java/net/minecraft/world/level/block/CauldronBlock.java -index 8d5ad9848d9652b4fd7179c425c47a683ee169ef..c9968934f4ecaa8d81e545f279b3001c7b1ce545 100644 ---- a/src/main/java/net/minecraft/world/level/block/CauldronBlock.java -+++ b/src/main/java/net/minecraft/world/level/block/CauldronBlock.java -@@ -44,9 +44,19 @@ public class CauldronBlock extends AbstractCauldronBlock { - public void handlePrecipitation(BlockState state, Level world, BlockPos pos, Biome.Precipitation precipitation) { - if (CauldronBlock.shouldHandlePrecipitation(world, precipitation)) { - if (precipitation == Biome.Precipitation.RAIN) { -+ // Paper start - Call CauldronLevelChangeEvent -+ if (!LayeredCauldronBlock.changeLevel(state, world, pos, Blocks.WATER_CAULDRON.defaultBlockState(), null, CauldronLevelChangeEvent.ChangeReason.NATURAL_FILL, false)) { // avoid duplicate game event -+ return; -+ } -+ // Paper end - Call CauldronLevelChangeEvent - world.setBlockAndUpdate(pos, Blocks.WATER_CAULDRON.defaultBlockState()); - world.gameEvent((Entity) null, (Holder) GameEvent.BLOCK_CHANGE, pos); - } else if (precipitation == Biome.Precipitation.SNOW) { -+ // Paper start - Call CauldronLevelChangeEvent -+ if (!LayeredCauldronBlock.changeLevel(state, world, pos, Blocks.POWDER_SNOW_CAULDRON.defaultBlockState(), null, CauldronLevelChangeEvent.ChangeReason.NATURAL_FILL, false)) { // avoid duplicate game event -+ return; -+ } -+ // Paper end - Call CauldronLevelChangeEvent - world.setBlockAndUpdate(pos, Blocks.POWDER_SNOW_CAULDRON.defaultBlockState()); - world.gameEvent((Entity) null, (Holder) GameEvent.BLOCK_CHANGE, pos); - } -@@ -65,11 +75,19 @@ public class CauldronBlock extends AbstractCauldronBlock { - - if (fluid == Fluids.WATER) { - iblockdata1 = Blocks.WATER_CAULDRON.defaultBlockState(); -- LayeredCauldronBlock.changeLevel(state, world, pos, iblockdata1, null, CauldronLevelChangeEvent.ChangeReason.NATURAL_FILL); // CraftBukkit -+ // Paper start - Call CauldronLevelChangeEvent; don't send level event or game event if cancelled -+ if (!LayeredCauldronBlock.changeLevel(state, world, pos, iblockdata1, null, CauldronLevelChangeEvent.ChangeReason.NATURAL_FILL)) { // CraftBukkit -+ return; -+ } -+ // Paper end - Call CauldronLevelChangeEvent - world.levelEvent(1047, pos, 0); - } else if (fluid == Fluids.LAVA) { - iblockdata1 = Blocks.LAVA_CAULDRON.defaultBlockState(); -- LayeredCauldronBlock.changeLevel(state, world, pos, iblockdata1, null, CauldronLevelChangeEvent.ChangeReason.NATURAL_FILL); // CraftBukkit -+ // Paper start - Call CauldronLevelChangeEvent; don't send level event or game event if cancelled -+ if (!LayeredCauldronBlock.changeLevel(state, world, pos, iblockdata1, null, CauldronLevelChangeEvent.ChangeReason.NATURAL_FILL)) { // CraftBukkit -+ return; -+ } -+ // Paper end - Call CauldronLevelChangeEvent - world.levelEvent(1046, pos, 0); - } - -diff --git a/src/main/java/net/minecraft/world/level/block/LayeredCauldronBlock.java b/src/main/java/net/minecraft/world/level/block/LayeredCauldronBlock.java -index 7c67efa6e344870b764eb39d5508190349e2e911..b222ac8e5b966c515be2aca86083f5640853cd29 100644 ---- a/src/main/java/net/minecraft/world/level/block/LayeredCauldronBlock.java -+++ b/src/main/java/net/minecraft/world/level/block/LayeredCauldronBlock.java -@@ -102,7 +102,13 @@ public class LayeredCauldronBlock extends AbstractCauldronBlock { - } - - // CraftBukkit start -- public static boolean changeLevel(BlockState iblockdata, Level world, BlockPos blockposition, BlockState newBlock, Entity entity, CauldronLevelChangeEvent.ChangeReason reason) { -+ // Paper start - Call CauldronLevelChangeEvent -+ public static boolean changeLevel(BlockState iblockdata, Level world, BlockPos blockposition, BlockState newBlock, @javax.annotation.Nullable Entity entity, CauldronLevelChangeEvent.ChangeReason reason) { // Paper - entity is nullable -+ return changeLevel(iblockdata, world, blockposition, newBlock, entity, reason, true); -+ } -+ -+ public static boolean changeLevel(BlockState iblockdata, Level world, BlockPos blockposition, BlockState newBlock, @javax.annotation.Nullable Entity entity, CauldronLevelChangeEvent.ChangeReason reason, boolean sendGameEvent) { // Paper - entity is nullable -+ // Paper end - Call CauldronLevelChangeEvent - CraftBlockState newState = CraftBlockStates.getBlockState(world, blockposition); - newState.setData(newBlock); - -@@ -115,7 +121,7 @@ public class LayeredCauldronBlock extends AbstractCauldronBlock { - return false; - } - newState.update(true); -- world.gameEvent((Holder) GameEvent.BLOCK_CHANGE, blockposition, GameEvent.Context.of(newBlock)); -+ if (sendGameEvent) world.gameEvent((Holder) GameEvent.BLOCK_CHANGE, blockposition, GameEvent.Context.of(newBlock)); // Paper - Call CauldronLevelChangeEvent - return true; - } - // CraftBukkit end diff --git a/patches/server/0702-Fix-OfflinePlayer-getBedSpawnLocation.patch b/patches/server/0697-Fix-OfflinePlayer-getBedSpawnLocation.patch similarity index 100% rename from patches/server/0702-Fix-OfflinePlayer-getBedSpawnLocation.patch rename to patches/server/0697-Fix-OfflinePlayer-getBedSpawnLocation.patch diff --git a/patches/server/0697-fix-powder-snow-cauldrons-not-turning-to-water.patch b/patches/server/0697-fix-powder-snow-cauldrons-not-turning-to-water.patch deleted file mode 100644 index f277b93aa1cb..000000000000 --- a/patches/server/0697-fix-powder-snow-cauldrons-not-turning-to-water.patch +++ /dev/null @@ -1,45 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Jake Potrebic -Date: Thu, 30 Dec 2021 14:02:13 -0800 -Subject: [PATCH] fix powder snow cauldrons not turning to water - -Powder snow cauldrons should turn to water when -extinguishing an entity - -diff --git a/src/main/java/net/minecraft/world/level/block/LayeredCauldronBlock.java b/src/main/java/net/minecraft/world/level/block/LayeredCauldronBlock.java -index b222ac8e5b966c515be2aca86083f5640853cd29..fa5366961861370c2366e6f0ff026a6d65128316 100644 ---- a/src/main/java/net/minecraft/world/level/block/LayeredCauldronBlock.java -+++ b/src/main/java/net/minecraft/world/level/block/LayeredCauldronBlock.java -@@ -70,7 +70,7 @@ public class LayeredCauldronBlock extends AbstractCauldronBlock { - if (!world.isClientSide && entity.isOnFire() && this.isEntityInsideContent(state, pos, entity)) { - // CraftBukkit start - if (entity.mayInteract(world, pos)) { -- if (!LayeredCauldronBlock.lowerFillLevel(state, world, pos, entity, CauldronLevelChangeEvent.ChangeReason.EXTINGUISH)) { -+ if (!this.handleEntityOnFireInsideWithEvent(state, world, pos, entity)) { // Paper - fix powdered snow cauldron extinguishing entities - return; - } - } -@@ -80,6 +80,7 @@ public class LayeredCauldronBlock extends AbstractCauldronBlock { - - } - -+ @io.papermc.paper.annotation.DoNotUse @Deprecated // Paper - fix powdered snow cauldron extinguishing entities; use #handleEntityOnFireInsideWithEvent - private void handleEntityOnFireInside(BlockState state, Level world, BlockPos pos) { - if (this.precipitationType == Biome.Precipitation.SNOW) { - LayeredCauldronBlock.lowerFillLevel((BlockState) Blocks.WATER_CAULDRON.defaultBlockState().setValue(LayeredCauldronBlock.LEVEL, (Integer) state.getValue(LayeredCauldronBlock.LEVEL)), world, pos); -@@ -88,6 +89,15 @@ public class LayeredCauldronBlock extends AbstractCauldronBlock { - } - - } -+ // Paper start - fix powdered snow cauldron extinguishing entities -+ protected boolean handleEntityOnFireInsideWithEvent(BlockState state, Level world, BlockPos pos, Entity entity) { -+ if (this.precipitationType == Biome.Precipitation.SNOW) { -+ return LayeredCauldronBlock.lowerFillLevel((BlockState) Blocks.WATER_CAULDRON.defaultBlockState().setValue(LayeredCauldronBlock.LEVEL, (Integer) state.getValue(LayeredCauldronBlock.LEVEL)), world, pos, entity, CauldronLevelChangeEvent.ChangeReason.EXTINGUISH); -+ } else { -+ return LayeredCauldronBlock.lowerFillLevel(state, world, pos, entity, CauldronLevelChangeEvent.ChangeReason.EXTINGUISH); -+ } -+ } -+ // Paper end - fix powdered snow cauldron extinguishing entities - - public static void lowerFillLevel(BlockState state, Level world, BlockPos pos) { - // CraftBukkit start diff --git a/patches/server/0698-Add-PlayerStopUsingItemEvent.patch b/patches/server/0698-Add-PlayerStopUsingItemEvent.patch deleted file mode 100644 index b27685e2dc80..000000000000 --- a/patches/server/0698-Add-PlayerStopUsingItemEvent.patch +++ /dev/null @@ -1,18 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: u9g -Date: Tue, 3 May 2022 20:41:37 -0400 -Subject: [PATCH] Add PlayerStopUsingItemEvent - - -diff --git a/src/main/java/net/minecraft/world/entity/LivingEntity.java b/src/main/java/net/minecraft/world/entity/LivingEntity.java -index 01a6e91f1b8f619e99d9bccde12f6e91e6ab5eb8..5383d231748f2d30b2f2bf3ce07d3667e4d828e9 100644 ---- a/src/main/java/net/minecraft/world/entity/LivingEntity.java -+++ b/src/main/java/net/minecraft/world/entity/LivingEntity.java -@@ -4076,6 +4076,7 @@ public abstract class LivingEntity extends Entity implements Attackable { - - public void releaseUsingItem() { - if (!this.useItem.isEmpty()) { -+ if (this instanceof ServerPlayer) new io.papermc.paper.event.player.PlayerStopUsingItemEvent((Player) getBukkitEntity(), useItem.asBukkitMirror(), getTicksUsingItem()).callEvent(); // Paper - Add PlayerStopUsingItemEvent - this.useItem.releaseUsing(this.level(), this, this.getUseItemRemainingTicks()); - if (this.useItem.useOnRelease()) { - this.updatingUsingItem(); diff --git a/patches/server/0703-Fix-FurnaceInventory-for-smokers-and-blast-furnaces.patch b/patches/server/0698-Fix-FurnaceInventory-for-smokers-and-blast-furnaces.patch similarity index 100% rename from patches/server/0703-Fix-FurnaceInventory-for-smokers-and-blast-furnaces.patch rename to patches/server/0698-Fix-FurnaceInventory-for-smokers-and-blast-furnaces.patch diff --git a/patches/server/0699-Don-t-tick-markers.patch b/patches/server/0699-Don-t-tick-markers.patch deleted file mode 100644 index 5ab34da747b2..000000000000 --- a/patches/server/0699-Don-t-tick-markers.patch +++ /dev/null @@ -1,36 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Noah van der Aa -Date: Fri, 7 Jan 2022 11:58:26 +0100 -Subject: [PATCH] Don't tick markers - -Fixes https://github.com/PaperMC/Paper/issues/7276 and https://github.com/PaperMC/Paper/issues/8118 -by using a config option that, when set to false, does not add markers to the entity -tick list at all and ignores them in Spigot's activation range checks. The entity tick -list is only used in the tick and tickPassenger methods, so we can safely not add the -markers to it. When the config option is set to true, markers are ticked as normal. - -diff --git a/src/main/java/io/papermc/paper/command/subcommands/EntityCommand.java b/src/main/java/io/papermc/paper/command/subcommands/EntityCommand.java -index 67fcba634f8183bb33834ac3b2c3dcfb8d87129e..777b789fdcdf297309cfb36fc7f77e3fdb6327ca 100644 ---- a/src/main/java/io/papermc/paper/command/subcommands/EntityCommand.java -+++ b/src/main/java/io/papermc/paper/command/subcommands/EntityCommand.java -@@ -109,7 +109,7 @@ public final class EntityCommand implements PaperSubcommand { - ChunkPos chunk = e.chunkPosition(); - info.left++; - info.right.put(chunk, info.right.getOrDefault(chunk, 0) + 1); -- if (!chunkProviderServer.isPositionTicking(e)) { -+ if (!chunkProviderServer.isPositionTicking(e) || (e instanceof net.minecraft.world.entity.Marker && !world.paperConfig().entities.markers.tick)) { // Paper - Configurable marker ticking - nonEntityTicking.merge(key, 1, Integer::sum); - } - }); -diff --git a/src/main/java/net/minecraft/server/level/ServerLevel.java b/src/main/java/net/minecraft/server/level/ServerLevel.java -index a87a0c9672b53db0e06292c339b3b1e163901942..f4ab0ce684ae2a83a02848c4d18062e1b27377ce 100644 ---- a/src/main/java/net/minecraft/server/level/ServerLevel.java -+++ b/src/main/java/net/minecraft/server/level/ServerLevel.java -@@ -2162,6 +2162,7 @@ public class ServerLevel extends Level implements WorldGenLevel { - } - - public void onTickingStart(Entity entity) { -+ if (entity instanceof net.minecraft.world.entity.Marker && !paperConfig().entities.markers.tick) return; // Paper - Configurable marker ticking - ServerLevel.this.entityTickList.add(entity); - } - diff --git a/patches/server/0699-Sanitize-sent-BlockEntity-NBT.patch b/patches/server/0699-Sanitize-sent-BlockEntity-NBT.patch new file mode 100644 index 000000000000..60a49fa4ef1e --- /dev/null +++ b/patches/server/0699-Sanitize-sent-BlockEntity-NBT.patch @@ -0,0 +1,50 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Owen1212055 <23108066+Owen1212055@users.noreply.github.com> +Date: Fri, 3 Dec 2021 16:55:50 -0500 +Subject: [PATCH] Sanitize sent BlockEntity NBT + + +diff --git a/src/main/java/net/minecraft/network/protocol/game/ClientboundBlockEntityDataPacket.java b/src/main/java/net/minecraft/network/protocol/game/ClientboundBlockEntityDataPacket.java +index 4f3ba61f13dbe5773034a19e749b7c4f5dc3d291..5d3e739d28d394ed59fe0003245cc55ac62e6087 100644 +--- a/src/main/java/net/minecraft/network/protocol/game/ClientboundBlockEntityDataPacket.java ++++ b/src/main/java/net/minecraft/network/protocol/game/ClientboundBlockEntityDataPacket.java +@@ -29,7 +29,7 @@ public class ClientboundBlockEntityDataPacket implements Packet nbtGetter) { + RegistryAccess registryAccess = blockEntity.getLevel().registryAccess(); +- return new ClientboundBlockEntityDataPacket(blockEntity.getBlockPos(), blockEntity.getType(), nbtGetter.apply(blockEntity, registryAccess)); ++ return new ClientboundBlockEntityDataPacket(blockEntity.getBlockPos(), blockEntity.getType(), blockEntity.sanitizeSentNbt(nbtGetter.apply(blockEntity, registryAccess))); // Paper - Sanitize sent data + } + + public static ClientboundBlockEntityDataPacket create(BlockEntity blockEntity) { +diff --git a/src/main/java/net/minecraft/network/protocol/game/ClientboundLevelChunkPacketData.java b/src/main/java/net/minecraft/network/protocol/game/ClientboundLevelChunkPacketData.java +index ac900dfdc5c90e9e60d47efa734be8f0a5b20dca..ec1cb034d840633240f2b379b09f7d2f1c8971a5 100644 +--- a/src/main/java/net/minecraft/network/protocol/game/ClientboundLevelChunkPacketData.java ++++ b/src/main/java/net/minecraft/network/protocol/game/ClientboundLevelChunkPacketData.java +@@ -154,6 +154,7 @@ public class ClientboundLevelChunkPacketData { + CompoundTag compoundTag = blockEntity.getUpdateTag(blockEntity.getLevel().registryAccess()); + BlockPos blockPos = blockEntity.getBlockPos(); + int i = SectionPos.sectionRelative(blockPos.getX()) << 4 | SectionPos.sectionRelative(blockPos.getZ()); ++ blockEntity.sanitizeSentNbt(compoundTag); // Paper - Sanitize sent data + return new ClientboundLevelChunkPacketData.BlockEntityInfo(i, blockPos.getY(), blockEntity.getType(), compoundTag.isEmpty() ? null : compoundTag); + } + } +diff --git a/src/main/java/net/minecraft/world/level/block/entity/BlockEntity.java b/src/main/java/net/minecraft/world/level/block/entity/BlockEntity.java +index 09b85c4caa4ebaae1e8c2910b090c40a039a1be7..645a7ec0709cbd3c0cfbf75f7b8622a67515f74c 100644 +--- a/src/main/java/net/minecraft/world/level/block/entity/BlockEntity.java ++++ b/src/main/java/net/minecraft/world/level/block/entity/BlockEntity.java +@@ -390,6 +390,14 @@ public abstract class BlockEntity { + } + // CraftBukkit end + ++ // Paper start - Sanitize sent data ++ public CompoundTag sanitizeSentNbt(CompoundTag tag) { ++ tag.remove("PublicBukkitValues"); ++ ++ return tag; ++ } ++ // Paper end - Sanitize sent data ++ + private static class ComponentHelper { + + public static final Codec COMPONENTS_CODEC = DataComponentMap.CODEC.optionalFieldOf("components", DataComponentMap.EMPTY).codec(); diff --git a/patches/server/0700-Disable-component-selector-resolving-in-books-by-def.patch b/patches/server/0700-Disable-component-selector-resolving-in-books-by-def.patch new file mode 100644 index 000000000000..7231198032aa --- /dev/null +++ b/patches/server/0700-Disable-component-selector-resolving-in-books-by-def.patch @@ -0,0 +1,19 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Nassim Jahnke +Date: Thu, 2 Jun 2022 20:35:58 +0200 +Subject: [PATCH] Disable component selector resolving in books by default + + +diff --git a/src/main/java/net/minecraft/world/item/WrittenBookItem.java b/src/main/java/net/minecraft/world/item/WrittenBookItem.java +index a282c1cbbdf8e5ebac547b45a58116f9b4b2d49e..0c54100fb72b79e0eb4bad8f6851648e084d9260 100644 +--- a/src/main/java/net/minecraft/world/item/WrittenBookItem.java ++++ b/src/main/java/net/minecraft/world/item/WrittenBookItem.java +@@ -41,7 +41,7 @@ public class WrittenBookItem extends Item { + + public static boolean resolveBookComponents(ItemStack book, CommandSourceStack commandSource, @Nullable Player player) { + WrittenBookContent writtenBookContent = book.get(DataComponents.WRITTEN_BOOK_CONTENT); +- if (writtenBookContent != null && !writtenBookContent.resolved()) { ++ if (io.papermc.paper.configuration.GlobalConfiguration.get().itemValidation.resolveSelectorsInBooks && writtenBookContent != null && !writtenBookContent.resolved()) { // Paper - Disable component selector resolving in books by default + WrittenBookContent writtenBookContent2 = writtenBookContent.resolve(commandSource, player); + if (writtenBookContent2 != null) { + book.set(DataComponents.WRITTEN_BOOK_CONTENT, writtenBookContent2); diff --git a/patches/server/0700-Expand-FallingBlock-API.patch b/patches/server/0700-Expand-FallingBlock-API.patch deleted file mode 100644 index 84c782114275..000000000000 --- a/patches/server/0700-Expand-FallingBlock-API.patch +++ /dev/null @@ -1,107 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Owen1212055 <23108066+Owen1212055@users.noreply.github.com> -Date: Sun, 5 Dec 2021 14:58:17 -0500 -Subject: [PATCH] Expand FallingBlock API - -- add auto expire setting -- add setter for block data -- add accessors for block state - -== AT == -public net.minecraft.world.entity.item.FallingBlockEntity blockState - -Co-authored-by: Lukas Planz - -diff --git a/src/main/java/net/minecraft/world/entity/item/FallingBlockEntity.java b/src/main/java/net/minecraft/world/entity/item/FallingBlockEntity.java -index 01ac7bb0ef8ab13e7c4b5b56b768b7c0a642b300..caf9273cfafbbaaf8dc95498927383e608773665 100644 ---- a/src/main/java/net/minecraft/world/entity/item/FallingBlockEntity.java -+++ b/src/main/java/net/minecraft/world/entity/item/FallingBlockEntity.java -@@ -71,6 +71,7 @@ public class FallingBlockEntity extends Entity { - public CompoundTag blockData; - public boolean forceTickAfterTeleportToDuplicate; - protected static final EntityDataAccessor DATA_START_POS = SynchedEntityData.defineId(FallingBlockEntity.class, EntityDataSerializers.BLOCK_POS); -+ public boolean autoExpire = true; // Paper - Expand FallingBlock API - - public FallingBlockEntity(EntityType type, Level world) { - super(type, world); -@@ -176,7 +177,7 @@ public class FallingBlockEntity extends Entity { - } - - if (!this.onGround() && !flag1) { -- if (!this.level().isClientSide && (this.time > 100 && (blockposition.getY() <= this.level().getMinBuildHeight() || blockposition.getY() > this.level().getMaxBuildHeight()) || this.time > 600)) { -+ if (!this.level().isClientSide && ((this.time > 100 && autoExpire) && (blockposition.getY() <= this.level().getMinBuildHeight() || blockposition.getY() > this.level().getMaxBuildHeight()) || (this.time > 600 && autoExpire))) { // Paper - Expand FallingBlock API - if (this.dropItem && this.level().getGameRules().getBoolean(GameRules.RULE_DOENTITYDROPS)) { - this.spawnAtLocation((ItemLike) block); - } -@@ -322,6 +323,7 @@ public class FallingBlockEntity extends Entity { - } - - nbt.putBoolean("CancelDrop", this.cancelDrop); -+ if (!autoExpire) {nbt.putBoolean("Paper.AutoExpire", false);} // Paper - Expand FallingBlock API - } - - @Override -@@ -349,6 +351,11 @@ public class FallingBlockEntity extends Entity { - this.blockState = Blocks.SAND.defaultBlockState(); - } - -+ // Paper start - Expand FallingBlock API -+ if (nbt.contains("Paper.AutoExpire")) { -+ this.autoExpire = nbt.getBoolean("Paper.AutoExpire"); -+ } -+ // Paper end - Expand FallingBlock API - } - - public void setHurtsEntities(float fallHurtAmount, int fallHurtMax) { -diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftFallingBlock.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftFallingBlock.java -index a7a3f74b846112d752fe04162b30805961457b11..1359d25a32b4a5d5e8e68ce737bd19f7b5afaf69 100644 ---- a/src/main/java/org/bukkit/craftbukkit/entity/CraftFallingBlock.java -+++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftFallingBlock.java -@@ -33,6 +33,31 @@ public class CraftFallingBlock extends CraftEntity implements FallingBlock { - public BlockData getBlockData() { - return CraftBlockData.fromData(this.getHandle().getBlockState()); - } -+ // Paper start - Expand FallingBlock API -+ @Override -+ public void setBlockData(final BlockData blockData) { -+ Preconditions.checkArgument(blockData != null, "blockData"); -+ final net.minecraft.world.level.block.state.BlockState oldState = this.getHandle().blockState, newState = ((CraftBlockData) blockData).getState(); -+ this.getHandle().blockState = newState; -+ this.getHandle().blockData = null; -+ -+ if (oldState != newState) this.update(); -+ } -+ -+ @Override -+ public org.bukkit.block.BlockState getBlockState() { -+ return org.bukkit.craftbukkit.block.CraftBlockStates.getBlockState(this.getHandle().blockState, this.getHandle().blockData); -+ } -+ -+ @Override -+ public void setBlockState(final org.bukkit.block.BlockState blockState) { -+ Preconditions.checkArgument(blockState != null, "blockState"); -+ // Calls #update if needed, the block data compound tag is not synced with the client and hence can be mutated after the sync with clients. -+ // The call also clears any potential old block data. -+ this.setBlockData(blockState.getBlockData()); -+ if (blockState instanceof final org.bukkit.craftbukkit.block.CraftBlockEntityState tileEntity) this.getHandle().blockData = tileEntity.getSnapshotNBT(); -+ } -+ // Paper end - Expand FallingBlock API - - @Override - public boolean getDropItem() { -@@ -101,4 +126,15 @@ public class CraftFallingBlock extends CraftEntity implements FallingBlock { - this.setHurtEntities(true); - } - } -+ // Paper start - Expand FallingBlock API -+ @Override -+ public boolean doesAutoExpire() { -+ return this.getHandle().autoExpire; -+ } -+ -+ @Override -+ public void shouldAutoExpire(boolean autoExpires) { -+ this.getHandle().autoExpire = autoExpires; -+ } -+ // Paper end - Expand FallingBlock API - } diff --git a/patches/server/0701-Add-support-for-Proxy-Protocol.patch b/patches/server/0701-Add-support-for-Proxy-Protocol.patch deleted file mode 100644 index 8ad330c63d02..000000000000 --- a/patches/server/0701-Add-support-for-Proxy-Protocol.patch +++ /dev/null @@ -1,65 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: PanSzelescik -Date: Thu, 7 Apr 2022 16:13:39 +0200 -Subject: [PATCH] Add support for Proxy Protocol - - -diff --git a/build.gradle.kts b/build.gradle.kts -index 6c3ed9e685473d7f555ae0e34fb9d4f3873f109a..ec9ae97b4a624bc208a778e63d0f0b86285167a6 100644 ---- a/build.gradle.kts -+++ b/build.gradle.kts -@@ -28,6 +28,7 @@ dependencies { - log4jPlugins.annotationProcessorConfigurationName("org.apache.logging.log4j:log4j-core:2.19.0") // Paper - Needed to generate meta for our Log4j plugins - runtimeOnly(log4jPlugins.output) - alsoShade(log4jPlugins.output) -+ implementation("io.netty:netty-codec-haproxy:4.1.97.Final") // Paper - Add support for proxy protocol - // Paper end - implementation("org.apache.logging.log4j:log4j-iostreams:2.22.1") // Paper - remove exclusion - implementation("org.ow2.asm:asm-commons:9.7.1") -diff --git a/src/main/java/net/minecraft/server/network/ServerConnectionListener.java b/src/main/java/net/minecraft/server/network/ServerConnectionListener.java -index c63c194c44646e6bc1a59426552787011fc2ced5..c62df32af11636ad408b584fcc590590ce4fb0d0 100644 ---- a/src/main/java/net/minecraft/server/network/ServerConnectionListener.java -+++ b/src/main/java/net/minecraft/server/network/ServerConnectionListener.java -@@ -104,6 +104,12 @@ public class ServerConnectionListener { - ServerConnectionListener.LOGGER.info("Using default channel type"); - } - -+ // Paper start - Warn people with console access that HAProxy is in use. -+ if (io.papermc.paper.configuration.GlobalConfiguration.get().proxies.proxyProtocol) { -+ ServerConnectionListener.LOGGER.warn("Using HAProxy, please ensure the server port is adequately firewalled."); -+ } -+ // Paper end - Warn people with console access that HAProxy is in use. -+ - this.channels.add(((ServerBootstrap) ((ServerBootstrap) (new ServerBootstrap()).channel(oclass)).childHandler(new ChannelInitializer() { - protected void initChannel(Channel channel) { - try { -@@ -123,6 +129,29 @@ public class ServerConnectionListener { - Connection object = j > 0 ? new RateKickingConnection(j) : new Connection(PacketFlow.SERVERBOUND); // CraftBukkit - decompile error - - //ServerConnectionListener.this.connections.add(object); // Paper -+ // Paper start - Add support for Proxy Protocol -+ if (io.papermc.paper.configuration.GlobalConfiguration.get().proxies.proxyProtocol) { -+ channel.pipeline().addAfter("timeout", "haproxy-decoder", new io.netty.handler.codec.haproxy.HAProxyMessageDecoder()); -+ channel.pipeline().addAfter("haproxy-decoder", "haproxy-handler", new ChannelInboundHandlerAdapter() { -+ @Override -+ public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception { -+ if (msg instanceof io.netty.handler.codec.haproxy.HAProxyMessage message) { -+ if (message.command() == io.netty.handler.codec.haproxy.HAProxyCommand.PROXY) { -+ String realaddress = message.sourceAddress(); -+ int realport = message.sourcePort(); -+ -+ SocketAddress socketaddr = new java.net.InetSocketAddress(realaddress, realport); -+ -+ Connection connection = (Connection) channel.pipeline().get("packet_handler"); -+ connection.address = socketaddr; -+ } -+ } else { -+ super.channelRead(ctx, msg); -+ } -+ } -+ }); -+ } -+ // Paper end - Add support for proxy protocol - pending.add(object); // Paper - prevent blocking on adding a new connection while the server is ticking - ((Connection) object).configurePacketHandler(channelpipeline); - ((Connection) object).setListenerForServerboundHandshake(new ServerHandshakePacketListenerImpl(ServerConnectionListener.this.server, (Connection) object)); diff --git a/patches/server/0701-Prevent-entity-loading-causing-async-lookups.patch b/patches/server/0701-Prevent-entity-loading-causing-async-lookups.patch new file mode 100644 index 000000000000..93303844469a --- /dev/null +++ b/patches/server/0701-Prevent-entity-loading-causing-async-lookups.patch @@ -0,0 +1,81 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Owen1212055 <23108066+Owen1212055@users.noreply.github.com> +Date: Sun, 6 Mar 2022 11:09:09 -0500 +Subject: [PATCH] Prevent entity loading causing async lookups + + +diff --git a/src/main/java/net/minecraft/world/entity/Entity.java b/src/main/java/net/minecraft/world/entity/Entity.java +index dcd6cb204d5d19da17664e446da7f3edef104b80..736b8bb59631a4e6f8639d84715a7d8cf9dbb775 100644 +--- a/src/main/java/net/minecraft/world/entity/Entity.java ++++ b/src/main/java/net/minecraft/world/entity/Entity.java +@@ -724,6 +724,7 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess + ProfilerFiller gameprofilerfiller = Profiler.get(); + + gameprofilerfiller.push("entityBaseTick"); ++ if (firstTick && this instanceof net.minecraft.world.entity.NeutralMob neutralMob) neutralMob.tickInitialPersistentAnger(level); // Paper - Prevent entity loading causing async lookups + this.inBlockState = null; + if (this.isPassenger() && this.getVehicle().isRemoved()) { + this.stopRiding(); +diff --git a/src/main/java/net/minecraft/world/entity/NeutralMob.java b/src/main/java/net/minecraft/world/entity/NeutralMob.java +index bf577b06707ff197f13f0b5e16620c09d4a69fa8..053d947c4cc00096dae422df36fb8351b3266215 100644 +--- a/src/main/java/net/minecraft/world/entity/NeutralMob.java ++++ b/src/main/java/net/minecraft/world/entity/NeutralMob.java +@@ -45,24 +45,11 @@ public interface NeutralMob { + UUID uuid = nbt.getUUID("AngryAt"); + + this.setPersistentAngerTarget(uuid); +- Entity entity = ((ServerLevel) world).getEntity(uuid); +- +- if (entity != null) { +- if (entity instanceof Mob) { +- Mob entityinsentient = (Mob) entity; +- +- this.setTarget(entityinsentient, EntityTargetEvent.TargetReason.UNKNOWN, false); // CraftBukkit +- this.setLastHurtByMob(entityinsentient); +- } +- +- if (entity instanceof Player) { +- Player entityhuman = (Player) entity; +- +- this.setTarget(entityhuman, EntityTargetEvent.TargetReason.UNKNOWN, false); // CraftBukkit +- this.setLastHurtByPlayer(entityhuman); +- } +- +- } ++ // Paper - Prevent entity loading causing async lookups; Moved diff to separate method ++ // If this entity already survived its first tick, e.g. is loaded and ticked in sync, actively ++ // tick the initial persistent anger. ++ // If not, let the first tick on the baseTick call the method later down the line. ++ if (this instanceof Entity entity && !entity.firstTick) this.tickInitialPersistentAnger(world); + } + } + } +@@ -136,4 +123,28 @@ public interface NeutralMob { + + @Nullable + LivingEntity getTarget(); ++ ++ // Paper start - Prevent entity loading causing async lookups ++ // Update last hurt when ticking ++ default void tickInitialPersistentAnger(Level level) { ++ UUID target = getPersistentAngerTarget(); ++ if (target == null) { ++ return; ++ } ++ ++ Entity entity = ((ServerLevel) level).getEntity(target); ++ ++ if (entity != null) { ++ if (entity instanceof Mob mob) { ++ this.setTarget(mob, EntityTargetEvent.TargetReason.UNKNOWN, false); // CraftBukkit ++ this.setLastHurtByMob(mob); ++ } ++ ++ if (entity instanceof Player player) { ++ this.setTarget(player, EntityTargetEvent.TargetReason.UNKNOWN, false); // CraftBukkit ++ this.setLastHurtByPlayer(player); ++ } ++ } ++ } ++ // Paper end - Prevent entity loading causing async lookups + } diff --git a/patches/server/0702-Throw-exception-on-world-create-while-being-ticked.patch b/patches/server/0702-Throw-exception-on-world-create-while-being-ticked.patch new file mode 100644 index 000000000000..75ec8c003cea --- /dev/null +++ b/patches/server/0702-Throw-exception-on-world-create-while-being-ticked.patch @@ -0,0 +1,78 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Jake Potrebic +Date: Tue, 22 Mar 2022 12:44:30 -0700 +Subject: [PATCH] Throw exception on world create while being ticked + +There are no plans to support creating worlds while worlds are +being ticked themselvess. + +diff --git a/src/main/java/net/minecraft/server/MinecraftServer.java b/src/main/java/net/minecraft/server/MinecraftServer.java +index a836ea518bc6d8ddd7c6484038d3d712b3fb13cf..aa9a4a0f35d0200777bdc83520ba82a970bfa900 100644 +--- a/src/main/java/net/minecraft/server/MinecraftServer.java ++++ b/src/main/java/net/minecraft/server/MinecraftServer.java +@@ -327,6 +327,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop S spin(Function serverFactory) { + AtomicReference atomicreference = new AtomicReference(); +@@ -1627,7 +1628,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop 0; // Paper - BlockPhysicsEvent +@@ -1687,6 +1690,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop(this.worlds.values()); + } + ++ @Override ++ public boolean isTickingWorlds() { ++ return console.isIteratingOverLevels; ++ } ++ + public DedicatedPlayerList getHandle() { + return this.playerList; + } +@@ -1182,6 +1187,7 @@ public final class CraftServer implements Server { + @Override + public World createWorld(WorldCreator creator) { + Preconditions.checkState(this.console.getAllLevels().iterator().hasNext(), "Cannot create additional worlds on STARTUP"); ++ //Preconditions.checkState(!this.console.isIteratingOverLevels, "Cannot create a world while worlds are being ticked"); // Paper - Cat - Temp disable. We'll see how this goes. + Preconditions.checkArgument(creator != null, "WorldCreator cannot be null"); + + String name = creator.name(); +@@ -1358,6 +1364,7 @@ public final class CraftServer implements Server { + + @Override + public boolean unloadWorld(World world, boolean save) { ++ //Preconditions.checkState(!this.console.isIteratingOverLevels, "Cannot unload a world while worlds are being ticked"); // Paper - Cat - Temp disable. We'll see how this goes. + if (world == null) { + return false; + } diff --git a/patches/server/0708-Dont-resent-entity-on-art-update.patch b/patches/server/0703-Dont-resent-entity-on-art-update.patch similarity index 100% rename from patches/server/0708-Dont-resent-entity-on-art-update.patch rename to patches/server/0703-Dont-resent-entity-on-art-update.patch diff --git a/patches/server/0704-Add-WardenAngerChangeEvent.patch b/patches/server/0704-Add-WardenAngerChangeEvent.patch new file mode 100644 index 000000000000..ca2817ab51ee --- /dev/null +++ b/patches/server/0704-Add-WardenAngerChangeEvent.patch @@ -0,0 +1,39 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: nopjar +Date: Sun, 12 Jun 2022 02:26:04 +0200 +Subject: [PATCH] Add WardenAngerChangeEvent + + +diff --git a/src/main/java/net/minecraft/world/entity/monster/warden/AngerManagement.java b/src/main/java/net/minecraft/world/entity/monster/warden/AngerManagement.java +index 27e52ee93ab591e97f37705de64cb5b62c06e253..3d792957f27fd4bdfad8d76262a8e2a2012bf35f 100644 +--- a/src/main/java/net/minecraft/world/entity/monster/warden/AngerManagement.java ++++ b/src/main/java/net/minecraft/world/entity/monster/warden/AngerManagement.java +@@ -146,7 +146,7 @@ public class AngerManagement { + + public int increaseAnger(Entity entity, int amount) { + boolean bl = !this.angerBySuspect.containsKey(entity); +- int i = this.angerBySuspect.computeInt(entity, (suspect, anger) -> Math.min(150, (anger == null ? 0 : anger) + amount)); ++ int i = this.angerBySuspect.computeInt(entity, (suspect, anger) -> Math.min(150, (anger == null ? 0 : anger) + amount)); // Paper - diff on change (Warden#increaseAngerAt WardenAngerChangeEvent) + if (bl) { + int j = this.angerByUuid.removeInt(entity.getUUID()); + i += j; +diff --git a/src/main/java/net/minecraft/world/entity/monster/warden/Warden.java b/src/main/java/net/minecraft/world/entity/monster/warden/Warden.java +index 71311d30459d495c57e6fcf0115e4f34a232f1ad..6180019da58b19d2595da508aed3196af922d587 100644 +--- a/src/main/java/net/minecraft/world/entity/monster/warden/Warden.java ++++ b/src/main/java/net/minecraft/world/entity/monster/warden/Warden.java +@@ -482,6 +482,15 @@ public class Warden extends Monster implements VibrationSystem { + @VisibleForTesting + public void increaseAngerAt(@Nullable Entity entity, int amount, boolean listening) { + if (!this.isNoAi() && this.canTargetEntity(entity)) { ++ // Paper start - Add WardenAngerChangeEvent ++ int activeAnger = this.angerManagement.getActiveAnger(entity); ++ io.papermc.paper.event.entity.WardenAngerChangeEvent event = new io.papermc.paper.event.entity.WardenAngerChangeEvent((org.bukkit.entity.Warden) this.getBukkitEntity(), entity.getBukkitEntity(), activeAnger, Math.min(150, activeAnger + amount)); ++ this.level().getCraftServer().getPluginManager().callEvent(event); ++ if (event.isCancelled()) { ++ return; ++ } ++ amount = event.getNewAnger() - activeAnger; ++ // Paper end - Add WardenAngerChangeEvent + WardenAi.setDigCooldown(this); + boolean flag1 = !(this.getTarget() instanceof Player); + int j = this.angerManagement.increaseAnger(entity, amount); diff --git a/patches/server/0704-Sanitize-sent-BlockEntity-NBT.patch b/patches/server/0704-Sanitize-sent-BlockEntity-NBT.patch deleted file mode 100644 index 152674ac0661..000000000000 --- a/patches/server/0704-Sanitize-sent-BlockEntity-NBT.patch +++ /dev/null @@ -1,50 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Owen1212055 <23108066+Owen1212055@users.noreply.github.com> -Date: Fri, 3 Dec 2021 16:55:50 -0500 -Subject: [PATCH] Sanitize sent BlockEntity NBT - - -diff --git a/src/main/java/net/minecraft/network/protocol/game/ClientboundBlockEntityDataPacket.java b/src/main/java/net/minecraft/network/protocol/game/ClientboundBlockEntityDataPacket.java -index 4f3ba61f13dbe5773034a19e749b7c4f5dc3d291..5d3e739d28d394ed59fe0003245cc55ac62e6087 100644 ---- a/src/main/java/net/minecraft/network/protocol/game/ClientboundBlockEntityDataPacket.java -+++ b/src/main/java/net/minecraft/network/protocol/game/ClientboundBlockEntityDataPacket.java -@@ -29,7 +29,7 @@ public class ClientboundBlockEntityDataPacket implements Packet nbtGetter) { - RegistryAccess registryAccess = blockEntity.getLevel().registryAccess(); -- return new ClientboundBlockEntityDataPacket(blockEntity.getBlockPos(), blockEntity.getType(), nbtGetter.apply(blockEntity, registryAccess)); -+ return new ClientboundBlockEntityDataPacket(blockEntity.getBlockPos(), blockEntity.getType(), blockEntity.sanitizeSentNbt(nbtGetter.apply(blockEntity, registryAccess))); // Paper - Sanitize sent data - } - - public static ClientboundBlockEntityDataPacket create(BlockEntity blockEntity) { -diff --git a/src/main/java/net/minecraft/network/protocol/game/ClientboundLevelChunkPacketData.java b/src/main/java/net/minecraft/network/protocol/game/ClientboundLevelChunkPacketData.java -index ac900dfdc5c90e9e60d47efa734be8f0a5b20dca..ec1cb034d840633240f2b379b09f7d2f1c8971a5 100644 ---- a/src/main/java/net/minecraft/network/protocol/game/ClientboundLevelChunkPacketData.java -+++ b/src/main/java/net/minecraft/network/protocol/game/ClientboundLevelChunkPacketData.java -@@ -154,6 +154,7 @@ public class ClientboundLevelChunkPacketData { - CompoundTag compoundTag = blockEntity.getUpdateTag(blockEntity.getLevel().registryAccess()); - BlockPos blockPos = blockEntity.getBlockPos(); - int i = SectionPos.sectionRelative(blockPos.getX()) << 4 | SectionPos.sectionRelative(blockPos.getZ()); -+ blockEntity.sanitizeSentNbt(compoundTag); // Paper - Sanitize sent data - return new ClientboundLevelChunkPacketData.BlockEntityInfo(i, blockPos.getY(), blockEntity.getType(), compoundTag.isEmpty() ? null : compoundTag); - } - } -diff --git a/src/main/java/net/minecraft/world/level/block/entity/BlockEntity.java b/src/main/java/net/minecraft/world/level/block/entity/BlockEntity.java -index cbb777f499a4e8a447153c04d09c0c71321c663c..41f43d7d12a47563360f48a793e63db8cf92ac69 100644 ---- a/src/main/java/net/minecraft/world/level/block/entity/BlockEntity.java -+++ b/src/main/java/net/minecraft/world/level/block/entity/BlockEntity.java -@@ -394,6 +394,14 @@ public abstract class BlockEntity { - } - // CraftBukkit end - -+ // Paper start - Sanitize sent data -+ public CompoundTag sanitizeSentNbt(CompoundTag tag) { -+ tag.remove("PublicBukkitValues"); -+ -+ return tag; -+ } -+ // Paper end - Sanitize sent data -+ - private static class ComponentHelper { - - public static final Codec COMPONENTS_CODEC = DataComponentMap.CODEC.optionalFieldOf("components", DataComponentMap.EMPTY).codec(); diff --git a/patches/server/0705-Add-option-for-strict-advancement-dimension-checks.patch b/patches/server/0705-Add-option-for-strict-advancement-dimension-checks.patch new file mode 100644 index 000000000000..ca19dca53fc2 --- /dev/null +++ b/patches/server/0705-Add-option-for-strict-advancement-dimension-checks.patch @@ -0,0 +1,42 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Jake Potrebic +Date: Sun, 12 Jun 2022 11:47:24 -0700 +Subject: [PATCH] Add option for strict advancement dimension checks + +Craftbukkit attempts to translate worlds that use the +same generation as the Overworld, The Nether, or The End +to use those dimensions when checking the `changed_dimension` +criteria trigger, or whether to trigger the `NETHER_TRAVEL` +distance trigger. This adds a config option to ignore that +and use the exact dimension key of the worlds involved. + +diff --git a/src/main/java/net/minecraft/advancements/critereon/LocationPredicate.java b/src/main/java/net/minecraft/advancements/critereon/LocationPredicate.java +index 01b8f7024fbc965bc6a7f97f79ba3dec964ef769..801823d003a8e28a13fe90db4604cd0938899c6d 100644 +--- a/src/main/java/net/minecraft/advancements/critereon/LocationPredicate.java ++++ b/src/main/java/net/minecraft/advancements/critereon/LocationPredicate.java +@@ -44,7 +44,7 @@ public record LocationPredicate( + public boolean matches(ServerLevel world, double x, double y, double z) { + if (this.position.isPresent() && !this.position.get().matches(x, y, z)) { + return false; +- } else if (this.dimension.isPresent() && this.dimension.get() != world.dimension()) { ++ } else if (this.dimension.isPresent() && this.dimension.get() != (io.papermc.paper.configuration.GlobalConfiguration.get().misc.strictAdvancementDimensionCheck ? world.dimension() : org.bukkit.craftbukkit.util.CraftDimensionUtil.getMainDimensionKey(world))) { // Paper - Add option for strict advancement dimension checks + return false; + } else { + BlockPos blockPos = BlockPos.containing(x, y, z); +diff --git a/src/main/java/net/minecraft/server/level/ServerPlayer.java b/src/main/java/net/minecraft/server/level/ServerPlayer.java +index d723adaa7b1df4a1d5067298536b303992ac2c52..312225a15261f2e80fbf6133c75c567574ade181 100644 +--- a/src/main/java/net/minecraft/server/level/ServerPlayer.java ++++ b/src/main/java/net/minecraft/server/level/ServerPlayer.java +@@ -1655,6 +1655,12 @@ public class ServerPlayer extends net.minecraft.world.entity.player.Player { + ResourceKey maindimensionkey = CraftDimensionUtil.getMainDimensionKey(origin); + ResourceKey maindimensionkey1 = CraftDimensionUtil.getMainDimensionKey(this.level()); + ++ // Paper start - Add option for strict advancement dimension checks ++ if (io.papermc.paper.configuration.GlobalConfiguration.get().misc.strictAdvancementDimensionCheck) { ++ maindimensionkey = resourcekey; ++ maindimensionkey1 = resourcekey1; ++ } ++ // Paper end - Add option for strict advancement dimension checks + CriteriaTriggers.CHANGED_DIMENSION.trigger(this, maindimensionkey, maindimensionkey1); + if (maindimensionkey != resourcekey || maindimensionkey1 != resourcekey1) { + CriteriaTriggers.CHANGED_DIMENSION.trigger(this, resourcekey, resourcekey1); diff --git a/patches/server/0705-Disable-component-selector-resolving-in-books-by-def.patch b/patches/server/0705-Disable-component-selector-resolving-in-books-by-def.patch deleted file mode 100644 index 796fcc7f2a84..000000000000 --- a/patches/server/0705-Disable-component-selector-resolving-in-books-by-def.patch +++ /dev/null @@ -1,19 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Nassim Jahnke -Date: Thu, 2 Jun 2022 20:35:58 +0200 -Subject: [PATCH] Disable component selector resolving in books by default - - -diff --git a/src/main/java/net/minecraft/world/item/WrittenBookItem.java b/src/main/java/net/minecraft/world/item/WrittenBookItem.java -index 46383a9f8bd1075ea56847f5fd8437df0a5d4941..0a5182c52fe2fef05dabbeb5ed13487f9175efbe 100644 ---- a/src/main/java/net/minecraft/world/item/WrittenBookItem.java -+++ b/src/main/java/net/minecraft/world/item/WrittenBookItem.java -@@ -54,7 +54,7 @@ public class WrittenBookItem extends Item { - - public static boolean resolveBookComponents(ItemStack book, CommandSourceStack commandSource, @Nullable Player player) { - WrittenBookContent writtenBookContent = book.get(DataComponents.WRITTEN_BOOK_CONTENT); -- if (writtenBookContent != null && !writtenBookContent.resolved()) { -+ if (io.papermc.paper.configuration.GlobalConfiguration.get().itemValidation.resolveSelectorsInBooks && writtenBookContent != null && !writtenBookContent.resolved()) { // Paper - Disable component selector resolving in books by default - WrittenBookContent writtenBookContent2 = writtenBookContent.resolve(commandSource, player); - if (writtenBookContent2 != null) { - book.set(DataComponents.WRITTEN_BOOK_CONTENT, writtenBookContent2); diff --git a/patches/server/0706-Add-missing-important-BlockStateListPopulator-method.patch b/patches/server/0706-Add-missing-important-BlockStateListPopulator-method.patch new file mode 100644 index 000000000000..e0b94dc4ac0d --- /dev/null +++ b/patches/server/0706-Add-missing-important-BlockStateListPopulator-method.patch @@ -0,0 +1,73 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Owen1212055 <23108066+Owen1212055@users.noreply.github.com> +Date: Sun, 12 Jun 2022 13:25:52 -0400 +Subject: [PATCH] Add missing important BlockStateListPopulator methods + +Without these methods it causes exceptions due to these being used by certain feature generators. + +diff --git a/src/main/java/org/bukkit/craftbukkit/util/BlockStateListPopulator.java b/src/main/java/org/bukkit/craftbukkit/util/BlockStateListPopulator.java +index 072d105f05f3b535d53cfbf8538d1716f9cfcd0e..4d6d637188ef4010a71ea2eb6ea0310aea820511 100644 +--- a/src/main/java/org/bukkit/craftbukkit/util/BlockStateListPopulator.java ++++ b/src/main/java/org/bukkit/craftbukkit/util/BlockStateListPopulator.java +@@ -129,7 +129,7 @@ public class BlockStateListPopulator extends DummyGeneratorAccess { + + @Override + public boolean isFluidAtPosition(BlockPos pos, Predicate state) { +- return this.world.isFluidAtPosition(pos, state); ++ return state.test(this.getFluidState(pos)); // Paper - fix + } + + @Override +@@ -152,4 +152,33 @@ public class BlockStateListPopulator extends DummyGeneratorAccess { + public long nextSubTickCount() { + return this.world.nextSubTickCount(); + } ++ ++ // Paper start ++ @Override ++ public java.util.Optional getBlockEntity(BlockPos pos, net.minecraft.world.level.block.entity.BlockEntityType type) { ++ BlockEntity tileentity = this.getBlockEntity(pos); ++ ++ return tileentity != null && tileentity.getType() == type ? (java.util.Optional) java.util.Optional.of(tileentity) : java.util.Optional.empty(); ++ } ++ ++ @Override ++ public BlockPos getHeightmapPos(net.minecraft.world.level.levelgen.Heightmap.Types heightmap, BlockPos pos) { ++ return world.getHeightmapPos(heightmap, pos); ++ } ++ ++ @Override ++ public int getHeight(net.minecraft.world.level.levelgen.Heightmap.Types heightmap, int x, int z) { ++ return world.getHeight(heightmap, x, z); ++ } ++ ++ @Override ++ public int getRawBrightness(BlockPos pos, int ambientDarkness) { ++ return world.getRawBrightness(pos, ambientDarkness); ++ } ++ ++ @Override ++ public int getBrightness(net.minecraft.world.level.LightLayer type, BlockPos pos) { ++ return world.getBrightness(type, pos); ++ } ++ // Paper end + } +diff --git a/src/main/java/org/bukkit/craftbukkit/util/DummyGeneratorAccess.java b/src/main/java/org/bukkit/craftbukkit/util/DummyGeneratorAccess.java +index 4705aed1dd98378c146bf9e346df1a17f719ad36..daf3c26fce7569761951bfd5594c6726d854a9ff 100644 +--- a/src/main/java/org/bukkit/craftbukkit/util/DummyGeneratorAccess.java ++++ b/src/main/java/org/bukkit/craftbukkit/util/DummyGeneratorAccess.java +@@ -258,4 +258,14 @@ public class DummyGeneratorAccess implements WorldGenLevel { + public boolean destroyBlock(BlockPos pos, boolean drop, Entity breakingEntity, int maxUpdateDepth) { + return false; // SPIGOT-6515 + } ++ ++ // Paper start - add more methods ++ public void scheduleTick(BlockPos pos, Fluid fluid, int delay) {} ++ ++ @Override ++ public void scheduleTick(BlockPos pos, Block block, int delay, net.minecraft.world.ticks.TickPriority priority) {} ++ ++ @Override ++ public void scheduleTick(BlockPos pos, Fluid fluid, int delay, net.minecraft.world.ticks.TickPriority priority) {} ++ // Paper end - add more methods + } diff --git a/patches/server/0706-Prevent-entity-loading-causing-async-lookups.patch b/patches/server/0706-Prevent-entity-loading-causing-async-lookups.patch deleted file mode 100644 index 52647af7c4fa..000000000000 --- a/patches/server/0706-Prevent-entity-loading-causing-async-lookups.patch +++ /dev/null @@ -1,81 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Owen1212055 <23108066+Owen1212055@users.noreply.github.com> -Date: Sun, 6 Mar 2022 11:09:09 -0500 -Subject: [PATCH] Prevent entity loading causing async lookups - - -diff --git a/src/main/java/net/minecraft/world/entity/Entity.java b/src/main/java/net/minecraft/world/entity/Entity.java -index 3bb7e0f6a02b9b1726b8c878271d89a9c6d56c12..de483cc77931fcb1d8bb9b1bcfd6606706490fba 100644 ---- a/src/main/java/net/minecraft/world/entity/Entity.java -+++ b/src/main/java/net/minecraft/world/entity/Entity.java -@@ -714,6 +714,7 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess - - public void baseTick() { - this.level().getProfiler().push("entityBaseTick"); -+ if (firstTick && this instanceof net.minecraft.world.entity.NeutralMob neutralMob) neutralMob.tickInitialPersistentAnger(level); // Paper - Prevent entity loading causing async lookups - this.inBlockState = null; - if (this.isPassenger() && this.getVehicle().isRemoved()) { - this.stopRiding(); -diff --git a/src/main/java/net/minecraft/world/entity/NeutralMob.java b/src/main/java/net/minecraft/world/entity/NeutralMob.java -index 3b08905ddc3c2506779ce0eac1a53a4d89442ddc..b55633bf4e26dc00e27ab28c7739c274e48b74c3 100644 ---- a/src/main/java/net/minecraft/world/entity/NeutralMob.java -+++ b/src/main/java/net/minecraft/world/entity/NeutralMob.java -@@ -45,24 +45,11 @@ public interface NeutralMob { - UUID uuid = nbt.getUUID("AngryAt"); - - this.setPersistentAngerTarget(uuid); -- Entity entity = ((ServerLevel) world).getEntity(uuid); -- -- if (entity != null) { -- if (entity instanceof Mob) { -- Mob entityinsentient = (Mob) entity; -- -- this.setTarget(entityinsentient, EntityTargetEvent.TargetReason.UNKNOWN, false); // CraftBukkit -- this.setLastHurtByMob(entityinsentient); -- } -- -- if (entity instanceof Player) { -- Player entityhuman = (Player) entity; -- -- this.setTarget(entityhuman, EntityTargetEvent.TargetReason.UNKNOWN, false); // CraftBukkit -- this.setLastHurtByPlayer(entityhuman); -- } -- -- } -+ // Paper - Prevent entity loading causing async lookups; Moved diff to separate method -+ // If this entity already survived its first tick, e.g. is loaded and ticked in sync, actively -+ // tick the initial persistent anger. -+ // If not, let the first tick on the baseTick call the method later down the line. -+ if (this instanceof Entity entity && !entity.firstTick) this.tickInitialPersistentAnger(world); - } - } - } -@@ -136,4 +123,28 @@ public interface NeutralMob { - - @Nullable - LivingEntity getTarget(); -+ -+ // Paper start - Prevent entity loading causing async lookups -+ // Update last hurt when ticking -+ default void tickInitialPersistentAnger(Level level) { -+ UUID target = getPersistentAngerTarget(); -+ if (target == null) { -+ return; -+ } -+ -+ Entity entity = ((ServerLevel) level).getEntity(target); -+ -+ if (entity != null) { -+ if (entity instanceof Mob mob) { -+ this.setTarget(mob, EntityTargetEvent.TargetReason.UNKNOWN, false); // CraftBukkit -+ this.setLastHurtByMob(mob); -+ } -+ -+ if (entity instanceof Player player) { -+ this.setTarget(player, EntityTargetEvent.TargetReason.UNKNOWN, false); // CraftBukkit -+ this.setLastHurtByPlayer(player); -+ } -+ } -+ } -+ // Paper end - Prevent entity loading causing async lookups - } diff --git a/patches/server/0707-Nameable-Banner-API.patch b/patches/server/0707-Nameable-Banner-API.patch new file mode 100644 index 000000000000..aab360dbed11 --- /dev/null +++ b/patches/server/0707-Nameable-Banner-API.patch @@ -0,0 +1,50 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Owen1212055 <23108066+Owen1212055@users.noreply.github.com> +Date: Thu, 7 Apr 2022 17:49:25 -0400 +Subject: [PATCH] Nameable Banner API + + +diff --git a/src/main/java/net/minecraft/world/level/block/entity/BannerBlockEntity.java b/src/main/java/net/minecraft/world/level/block/entity/BannerBlockEntity.java +index 01521f38871d821762ff7ac2d39927f8c6b861bf..98bc87fe5d153cc4927f7e1b4a02f61d9dd019a0 100644 +--- a/src/main/java/net/minecraft/world/level/block/entity/BannerBlockEntity.java ++++ b/src/main/java/net/minecraft/world/level/block/entity/BannerBlockEntity.java +@@ -29,7 +29,7 @@ public class BannerBlockEntity extends BlockEntity implements Nameable { + public static final int MAX_PATTERNS = 6; + private static final String TAG_PATTERNS = "patterns"; + @Nullable +- private Component name; ++ public Component name; // Paper - public + public DyeColor baseColor; + private BannerPatternLayers patterns; + +diff --git a/src/main/java/org/bukkit/craftbukkit/block/CraftBanner.java b/src/main/java/org/bukkit/craftbukkit/block/CraftBanner.java +index 5635230fc288fe5280bf785b42c862b8f111afb0..afed8bdb9bd6a135e9b5f7bd9bfc61964cb240f7 100644 +--- a/src/main/java/org/bukkit/craftbukkit/block/CraftBanner.java ++++ b/src/main/java/org/bukkit/craftbukkit/block/CraftBanner.java +@@ -112,4 +112,26 @@ public class CraftBanner extends CraftBlockEntityState implem + public CraftBanner copy(Location location) { + return new CraftBanner(this, location); + } ++ ++ // Paper start ++ @Override ++ public net.kyori.adventure.text.Component customName() { ++ return io.papermc.paper.adventure.PaperAdventure.asAdventure(this.getSnapshot().getCustomName()); ++ } ++ ++ @Override ++ public void customName(net.kyori.adventure.text.Component customName) { ++ this.getSnapshot().name = io.papermc.paper.adventure.PaperAdventure.asVanilla(customName); ++ } ++ ++ @Override ++ public String getCustomName() { ++ return net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer.legacySection().serializeOrNull(this.customName()); ++ } ++ ++ @Override ++ public void setCustomName(String name) { ++ this.customName(net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer.legacySection().deserializeOrNull(name)); ++ } ++ // Paper end + } diff --git a/patches/server/0707-Throw-exception-on-world-create-while-being-ticked.patch b/patches/server/0707-Throw-exception-on-world-create-while-being-ticked.patch deleted file mode 100644 index 8d6f8d9e1def..000000000000 --- a/patches/server/0707-Throw-exception-on-world-create-while-being-ticked.patch +++ /dev/null @@ -1,78 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Jake Potrebic -Date: Tue, 22 Mar 2022 12:44:30 -0700 -Subject: [PATCH] Throw exception on world create while being ticked - -There are no plans to support creating worlds while worlds are -being ticked themselvess. - -diff --git a/src/main/java/net/minecraft/server/MinecraftServer.java b/src/main/java/net/minecraft/server/MinecraftServer.java -index 94aa901b77b19445a33d5b2b24ba2e947d2a6aef..11b9eb8d7d5f9ad736f2c6784c5d9e745a355093 100644 ---- a/src/main/java/net/minecraft/server/MinecraftServer.java -+++ b/src/main/java/net/minecraft/server/MinecraftServer.java -@@ -317,6 +317,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop S spin(Function serverFactory) { - AtomicReference atomicreference = new AtomicReference(); -@@ -1580,7 +1581,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop 0; // Paper - BlockPhysicsEvent -@@ -1647,6 +1650,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop(this.worlds.values()); - } - -+ @Override -+ public boolean isTickingWorlds() { -+ return console.isIteratingOverLevels; -+ } -+ - public DedicatedPlayerList getHandle() { - return this.playerList; - } -@@ -1180,6 +1185,7 @@ public final class CraftServer implements Server { - @Override - public World createWorld(WorldCreator creator) { - Preconditions.checkState(this.console.getAllLevels().iterator().hasNext(), "Cannot create additional worlds on STARTUP"); -+ //Preconditions.checkState(!this.console.isIteratingOverLevels, "Cannot create a world while worlds are being ticked"); // Paper - Cat - Temp disable. We'll see how this goes. - Preconditions.checkArgument(creator != null, "WorldCreator cannot be null"); - - String name = creator.name(); -@@ -1356,6 +1362,7 @@ public final class CraftServer implements Server { - - @Override - public boolean unloadWorld(World world, boolean save) { -+ //Preconditions.checkState(!this.console.isIteratingOverLevels, "Cannot unload a world while worlds are being ticked"); // Paper - Cat - Temp disable. We'll see how this goes. - if (world == null) { - return false; - } diff --git a/patches/server/0708-Don-t-broadcast-messages-to-command-blocks.patch b/patches/server/0708-Don-t-broadcast-messages-to-command-blocks.patch new file mode 100644 index 000000000000..428c5de1aa25 --- /dev/null +++ b/patches/server/0708-Don-t-broadcast-messages-to-command-blocks.patch @@ -0,0 +1,34 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Jake Potrebic +Date: Thu, 16 Jun 2022 14:22:56 -0700 +Subject: [PATCH] Don't broadcast messages to command blocks + +Previously the broadcast method would update the last output +in command blocks, and if called asynchronously, would throw +an error + +diff --git a/src/main/java/net/minecraft/world/level/BaseCommandBlock.java b/src/main/java/net/minecraft/world/level/BaseCommandBlock.java +index a0e59b236dff1f861a0e987764a77ee203504412..5cb39f95bd2d45b6c18554605f01d2ebf6473428 100644 +--- a/src/main/java/net/minecraft/world/level/BaseCommandBlock.java ++++ b/src/main/java/net/minecraft/world/level/BaseCommandBlock.java +@@ -178,6 +178,7 @@ public abstract class BaseCommandBlock implements CommandSource { + @Override + public void sendSystemMessage(Component message) { + if (this.trackOutput) { ++ org.spigotmc.AsyncCatcher.catchOp("sendSystemMessage to a command block"); // Paper - Don't broadcast messages to command blocks + SimpleDateFormat simpledateformat = BaseCommandBlock.TIME_FORMAT; + Date date = new Date(); + +diff --git a/src/main/java/org/bukkit/craftbukkit/CraftServer.java b/src/main/java/org/bukkit/craftbukkit/CraftServer.java +index 2d16e96c7c329cde6369f1d5b739f60f1776bb4b..3deb58f3cd2e29b51944ac81cb3e0e52ec496242 100644 +--- a/src/main/java/org/bukkit/craftbukkit/CraftServer.java ++++ b/src/main/java/org/bukkit/craftbukkit/CraftServer.java +@@ -1928,7 +1928,7 @@ public final class CraftServer implements Server { + // Paper end + Set recipients = new HashSet<>(); + for (Permissible permissible : this.getPluginManager().getPermissionSubscriptions(permission)) { +- if (permissible instanceof CommandSender && permissible.hasPermission(permission)) { ++ if (permissible instanceof CommandSender && !(permissible instanceof org.bukkit.command.BlockCommandSender) && permissible.hasPermission(permission)) { // Paper - Don't broadcast messages to command blocks + recipients.add((CommandSender) permissible); + } + } diff --git a/patches/server/0709-Add-WardenAngerChangeEvent.patch b/patches/server/0709-Add-WardenAngerChangeEvent.patch deleted file mode 100644 index 41f4251548ef..000000000000 --- a/patches/server/0709-Add-WardenAngerChangeEvent.patch +++ /dev/null @@ -1,39 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: nopjar -Date: Sun, 12 Jun 2022 02:26:04 +0200 -Subject: [PATCH] Add WardenAngerChangeEvent - - -diff --git a/src/main/java/net/minecraft/world/entity/monster/warden/AngerManagement.java b/src/main/java/net/minecraft/world/entity/monster/warden/AngerManagement.java -index 27e52ee93ab591e97f37705de64cb5b62c06e253..3d792957f27fd4bdfad8d76262a8e2a2012bf35f 100644 ---- a/src/main/java/net/minecraft/world/entity/monster/warden/AngerManagement.java -+++ b/src/main/java/net/minecraft/world/entity/monster/warden/AngerManagement.java -@@ -146,7 +146,7 @@ public class AngerManagement { - - public int increaseAnger(Entity entity, int amount) { - boolean bl = !this.angerBySuspect.containsKey(entity); -- int i = this.angerBySuspect.computeInt(entity, (suspect, anger) -> Math.min(150, (anger == null ? 0 : anger) + amount)); -+ int i = this.angerBySuspect.computeInt(entity, (suspect, anger) -> Math.min(150, (anger == null ? 0 : anger) + amount)); // Paper - diff on change (Warden#increaseAngerAt WardenAngerChangeEvent) - if (bl) { - int j = this.angerByUuid.removeInt(entity.getUUID()); - i += j; -diff --git a/src/main/java/net/minecraft/world/entity/monster/warden/Warden.java b/src/main/java/net/minecraft/world/entity/monster/warden/Warden.java -index aaa4b5d90e23b09f87847aa52c36548c94ddf548..38bf417a9ad4647f4af24d969f3bf4fed9c4bad7 100644 ---- a/src/main/java/net/minecraft/world/entity/monster/warden/Warden.java -+++ b/src/main/java/net/minecraft/world/entity/monster/warden/Warden.java -@@ -479,6 +479,15 @@ public class Warden extends Monster implements VibrationSystem { - @VisibleForTesting - public void increaseAngerAt(@Nullable Entity entity, int amount, boolean listening) { - if (!this.isNoAi() && this.canTargetEntity(entity)) { -+ // Paper start - Add WardenAngerChangeEvent -+ int activeAnger = this.angerManagement.getActiveAnger(entity); -+ io.papermc.paper.event.entity.WardenAngerChangeEvent event = new io.papermc.paper.event.entity.WardenAngerChangeEvent((org.bukkit.entity.Warden) this.getBukkitEntity(), entity.getBukkitEntity(), activeAnger, Math.min(150, activeAnger + amount)); -+ this.level().getCraftServer().getPluginManager().callEvent(event); -+ if (event.isCancelled()) { -+ return; -+ } -+ amount = event.getNewAnger() - activeAnger; -+ // Paper end - Add WardenAngerChangeEvent - WardenAi.setDigCooldown(this); - boolean flag1 = !(this.getTarget() instanceof Player); - int j = this.angerManagement.increaseAnger(entity, amount); diff --git a/patches/server/0709-Prevent-empty-items-from-being-added-to-world.patch b/patches/server/0709-Prevent-empty-items-from-being-added-to-world.patch new file mode 100644 index 000000000000..4b6241c7701f --- /dev/null +++ b/patches/server/0709-Prevent-empty-items-from-being-added-to-world.patch @@ -0,0 +1,20 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Owen1212055 <23108066+Owen1212055@users.noreply.github.com> +Date: Wed, 15 Jun 2022 21:52:57 -0400 +Subject: [PATCH] Prevent empty items from being added to world + +The previous solution caused a bunch of bandaid fixes inorder to resolve edge cases where minecraft/the api might spawn items that are air. +Just simply prevent them from being added to the world instead. + +diff --git a/src/main/java/net/minecraft/server/level/ServerLevel.java b/src/main/java/net/minecraft/server/level/ServerLevel.java +index f4b1b5f1903015b3c4650186466c8183560c9de0..f3f93e8cbc2a5c9d0a3841ec7de010477bfd976a 100644 +--- a/src/main/java/net/minecraft/server/level/ServerLevel.java ++++ b/src/main/java/net/minecraft/server/level/ServerLevel.java +@@ -1200,6 +1200,7 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe + // WorldServer.LOGGER.warn("Tried to add entity {} but it was marked as removed already", EntityTypes.getKey(entity.getType())); // CraftBukkit + return false; + } else { ++ if (entity instanceof net.minecraft.world.entity.item.ItemEntity itemEntity && itemEntity.getItem().isEmpty()) return false; // Paper - Prevent empty items from being added + // Paper start - capture all item additions to the world + if (captureDrops != null && entity instanceof net.minecraft.world.entity.item.ItemEntity) { + captureDrops.add((net.minecraft.world.entity.item.ItemEntity) entity); diff --git a/patches/server/0710-Add-option-for-strict-advancement-dimension-checks.patch b/patches/server/0710-Add-option-for-strict-advancement-dimension-checks.patch deleted file mode 100644 index 488f84c88426..000000000000 --- a/patches/server/0710-Add-option-for-strict-advancement-dimension-checks.patch +++ /dev/null @@ -1,42 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Jake Potrebic -Date: Sun, 12 Jun 2022 11:47:24 -0700 -Subject: [PATCH] Add option for strict advancement dimension checks - -Craftbukkit attempts to translate worlds that use the -same generation as the Overworld, The Nether, or The End -to use those dimensions when checking the `changed_dimension` -criteria trigger, or whether to trigger the `NETHER_TRAVEL` -distance trigger. This adds a config option to ignore that -and use the exact dimension key of the worlds involved. - -diff --git a/src/main/java/net/minecraft/advancements/critereon/LocationPredicate.java b/src/main/java/net/minecraft/advancements/critereon/LocationPredicate.java -index 01b8f7024fbc965bc6a7f97f79ba3dec964ef769..801823d003a8e28a13fe90db4604cd0938899c6d 100644 ---- a/src/main/java/net/minecraft/advancements/critereon/LocationPredicate.java -+++ b/src/main/java/net/minecraft/advancements/critereon/LocationPredicate.java -@@ -44,7 +44,7 @@ public record LocationPredicate( - public boolean matches(ServerLevel world, double x, double y, double z) { - if (this.position.isPresent() && !this.position.get().matches(x, y, z)) { - return false; -- } else if (this.dimension.isPresent() && this.dimension.get() != world.dimension()) { -+ } else if (this.dimension.isPresent() && this.dimension.get() != (io.papermc.paper.configuration.GlobalConfiguration.get().misc.strictAdvancementDimensionCheck ? world.dimension() : org.bukkit.craftbukkit.util.CraftDimensionUtil.getMainDimensionKey(world))) { // Paper - Add option for strict advancement dimension checks - return false; - } else { - BlockPos blockPos = BlockPos.containing(x, y, z); -diff --git a/src/main/java/net/minecraft/server/level/ServerPlayer.java b/src/main/java/net/minecraft/server/level/ServerPlayer.java -index f31eb944465e9011d8aad398eb60bafb44203ad5..ad3896dd524acb573adffdfb38b13dd699539cef 100644 ---- a/src/main/java/net/minecraft/server/level/ServerPlayer.java -+++ b/src/main/java/net/minecraft/server/level/ServerPlayer.java -@@ -1387,6 +1387,12 @@ public class ServerPlayer extends net.minecraft.world.entity.player.Player { - ResourceKey maindimensionkey = CraftDimensionUtil.getMainDimensionKey(origin); - ResourceKey maindimensionkey1 = CraftDimensionUtil.getMainDimensionKey(this.level()); - -+ // Paper start - Add option for strict advancement dimension checks -+ if (io.papermc.paper.configuration.GlobalConfiguration.get().misc.strictAdvancementDimensionCheck) { -+ maindimensionkey = resourcekey; -+ maindimensionkey1 = resourcekey1; -+ } -+ // Paper end - Add option for strict advancement dimension checks - CriteriaTriggers.CHANGED_DIMENSION.trigger(this, maindimensionkey, maindimensionkey1); - if (maindimensionkey != resourcekey || maindimensionkey1 != resourcekey1) { - CriteriaTriggers.CHANGED_DIMENSION.trigger(this, resourcekey, resourcekey1); diff --git a/patches/server/0715-Fix-CCE-for-SplashPotion-and-LingeringPotion-spawnin.patch b/patches/server/0710-Fix-CCE-for-SplashPotion-and-LingeringPotion-spawnin.patch similarity index 100% rename from patches/server/0715-Fix-CCE-for-SplashPotion-and-LingeringPotion-spawnin.patch rename to patches/server/0710-Fix-CCE-for-SplashPotion-and-LingeringPotion-spawnin.patch diff --git a/patches/server/0711-Add-Player-getFishHook.patch b/patches/server/0711-Add-Player-getFishHook.patch new file mode 100644 index 000000000000..b2f8e7f0eb41 --- /dev/null +++ b/patches/server/0711-Add-Player-getFishHook.patch @@ -0,0 +1,26 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: u9g +Date: Tue, 14 Jun 2022 19:36:10 -0400 +Subject: [PATCH] Add Player#getFishHook + + +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftHumanEntity.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftHumanEntity.java +index 1e49eae80730aa9d2e49cd92759d899deb49fb90..df9d02eb1ffc3cc669e835e2c08d951283871db3 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftHumanEntity.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftHumanEntity.java +@@ -160,6 +160,15 @@ public class CraftHumanEntity extends CraftLivingEntity implements HumanEntity { + return new Location(worldServer.getWorld(), bed.getX(), bed.getY(), bed.getZ()); + } + // Paper end ++ // Paper start ++ @Override ++ public org.bukkit.entity.FishHook getFishHook() { ++ if (getHandle().fishing == null) { ++ return null; ++ } ++ return (org.bukkit.entity.FishHook) getHandle().fishing.getBukkitEntity(); ++ } ++ // Paper end + @Override + public boolean sleep(Location location, boolean force) { + Preconditions.checkArgument(location != null, "Location cannot be null"); diff --git a/patches/server/0711-Add-missing-important-BlockStateListPopulator-method.patch b/patches/server/0711-Add-missing-important-BlockStateListPopulator-method.patch deleted file mode 100644 index ecf565ebf68a..000000000000 --- a/patches/server/0711-Add-missing-important-BlockStateListPopulator-method.patch +++ /dev/null @@ -1,73 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Owen1212055 <23108066+Owen1212055@users.noreply.github.com> -Date: Sun, 12 Jun 2022 13:25:52 -0400 -Subject: [PATCH] Add missing important BlockStateListPopulator methods - -Without these methods it causes exceptions due to these being used by certain feature generators. - -diff --git a/src/main/java/org/bukkit/craftbukkit/util/BlockStateListPopulator.java b/src/main/java/org/bukkit/craftbukkit/util/BlockStateListPopulator.java -index ffe6881d93153838cd23f125980b832e6fd1d0eb..f5cbe9ae5802fa48e57092b1e5ca8a5f5f69ee60 100644 ---- a/src/main/java/org/bukkit/craftbukkit/util/BlockStateListPopulator.java -+++ b/src/main/java/org/bukkit/craftbukkit/util/BlockStateListPopulator.java -@@ -129,7 +129,7 @@ public class BlockStateListPopulator extends DummyGeneratorAccess { - - @Override - public boolean isFluidAtPosition(BlockPos pos, Predicate state) { -- return this.world.isFluidAtPosition(pos, state); -+ return state.test(this.getFluidState(pos)); // Paper - fix - } - - @Override -@@ -152,4 +152,33 @@ public class BlockStateListPopulator extends DummyGeneratorAccess { - public long nextSubTickCount() { - return this.world.nextSubTickCount(); - } -+ -+ // Paper start -+ @Override -+ public java.util.Optional getBlockEntity(BlockPos pos, net.minecraft.world.level.block.entity.BlockEntityType type) { -+ BlockEntity tileentity = this.getBlockEntity(pos); -+ -+ return tileentity != null && tileentity.getType() == type ? (java.util.Optional) java.util.Optional.of(tileentity) : java.util.Optional.empty(); -+ } -+ -+ @Override -+ public BlockPos getHeightmapPos(net.minecraft.world.level.levelgen.Heightmap.Types heightmap, BlockPos pos) { -+ return world.getHeightmapPos(heightmap, pos); -+ } -+ -+ @Override -+ public int getHeight(net.minecraft.world.level.levelgen.Heightmap.Types heightmap, int x, int z) { -+ return world.getHeight(heightmap, x, z); -+ } -+ -+ @Override -+ public int getRawBrightness(BlockPos pos, int ambientDarkness) { -+ return world.getRawBrightness(pos, ambientDarkness); -+ } -+ -+ @Override -+ public int getBrightness(net.minecraft.world.level.LightLayer type, BlockPos pos) { -+ return world.getBrightness(type, pos); -+ } -+ // Paper end - } -diff --git a/src/main/java/org/bukkit/craftbukkit/util/DummyGeneratorAccess.java b/src/main/java/org/bukkit/craftbukkit/util/DummyGeneratorAccess.java -index 4705aed1dd98378c146bf9e346df1a17f719ad36..daf3c26fce7569761951bfd5594c6726d854a9ff 100644 ---- a/src/main/java/org/bukkit/craftbukkit/util/DummyGeneratorAccess.java -+++ b/src/main/java/org/bukkit/craftbukkit/util/DummyGeneratorAccess.java -@@ -258,4 +258,14 @@ public class DummyGeneratorAccess implements WorldGenLevel { - public boolean destroyBlock(BlockPos pos, boolean drop, Entity breakingEntity, int maxUpdateDepth) { - return false; // SPIGOT-6515 - } -+ -+ // Paper start - add more methods -+ public void scheduleTick(BlockPos pos, Fluid fluid, int delay) {} -+ -+ @Override -+ public void scheduleTick(BlockPos pos, Block block, int delay, net.minecraft.world.ticks.TickPriority priority) {} -+ -+ @Override -+ public void scheduleTick(BlockPos pos, Fluid fluid, int delay, net.minecraft.world.ticks.TickPriority priority) {} -+ // Paper end - add more methods - } diff --git a/patches/server/0717-Do-not-sync-load-chunk-for-dynamic-game-event-listen.patch b/patches/server/0712-Do-not-sync-load-chunk-for-dynamic-game-event-listen.patch similarity index 100% rename from patches/server/0717-Do-not-sync-load-chunk-for-dynamic-game-event-listen.patch rename to patches/server/0712-Do-not-sync-load-chunk-for-dynamic-game-event-listen.patch diff --git a/patches/server/0712-Nameable-Banner-API.patch b/patches/server/0712-Nameable-Banner-API.patch deleted file mode 100644 index da3d804d8f27..000000000000 --- a/patches/server/0712-Nameable-Banner-API.patch +++ /dev/null @@ -1,50 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Owen1212055 <23108066+Owen1212055@users.noreply.github.com> -Date: Thu, 7 Apr 2022 17:49:25 -0400 -Subject: [PATCH] Nameable Banner API - - -diff --git a/src/main/java/net/minecraft/world/level/block/entity/BannerBlockEntity.java b/src/main/java/net/minecraft/world/level/block/entity/BannerBlockEntity.java -index 925608bfa9c848ed6285de5e35d60aa66e12004a..60c26076e7acf869fa0e20fdc14eeec341387d99 100644 ---- a/src/main/java/net/minecraft/world/level/block/entity/BannerBlockEntity.java -+++ b/src/main/java/net/minecraft/world/level/block/entity/BannerBlockEntity.java -@@ -29,7 +29,7 @@ public class BannerBlockEntity extends BlockEntity implements Nameable { - public static final int MAX_PATTERNS = 6; - private static final String TAG_PATTERNS = "patterns"; - @Nullable -- private Component name; -+ public Component name; // Paper - public - public DyeColor baseColor; - private BannerPatternLayers patterns; - -diff --git a/src/main/java/org/bukkit/craftbukkit/block/CraftBanner.java b/src/main/java/org/bukkit/craftbukkit/block/CraftBanner.java -index 5635230fc288fe5280bf785b42c862b8f111afb0..afed8bdb9bd6a135e9b5f7bd9bfc61964cb240f7 100644 ---- a/src/main/java/org/bukkit/craftbukkit/block/CraftBanner.java -+++ b/src/main/java/org/bukkit/craftbukkit/block/CraftBanner.java -@@ -112,4 +112,26 @@ public class CraftBanner extends CraftBlockEntityState implem - public CraftBanner copy(Location location) { - return new CraftBanner(this, location); - } -+ -+ // Paper start -+ @Override -+ public net.kyori.adventure.text.Component customName() { -+ return io.papermc.paper.adventure.PaperAdventure.asAdventure(this.getSnapshot().getCustomName()); -+ } -+ -+ @Override -+ public void customName(net.kyori.adventure.text.Component customName) { -+ this.getSnapshot().name = io.papermc.paper.adventure.PaperAdventure.asVanilla(customName); -+ } -+ -+ @Override -+ public String getCustomName() { -+ return net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer.legacySection().serializeOrNull(this.customName()); -+ } -+ -+ @Override -+ public void setCustomName(String name) { -+ this.customName(net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer.legacySection().deserializeOrNull(name)); -+ } -+ // Paper end - } diff --git a/patches/server/0713-Add-various-missing-EntityDropItemEvent-calls.patch b/patches/server/0713-Add-various-missing-EntityDropItemEvent-calls.patch new file mode 100644 index 000000000000..9b07c23cf818 --- /dev/null +++ b/patches/server/0713-Add-various-missing-EntityDropItemEvent-calls.patch @@ -0,0 +1,109 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Jake Potrebic +Date: Tue, 20 Jul 2021 21:35:47 -0700 +Subject: [PATCH] Add various missing EntityDropItemEvent calls + + +diff --git a/src/main/java/net/minecraft/world/entity/Entity.java b/src/main/java/net/minecraft/world/entity/Entity.java +index 736b8bb59631a4e6f8639d84715a7d8cf9dbb775..0de6c90c195439564810036c90f31e8296538666 100644 +--- a/src/main/java/net/minecraft/world/entity/Entity.java ++++ b/src/main/java/net/minecraft/world/entity/Entity.java +@@ -2672,6 +2672,14 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess + stack.setCount(0); // Paper - destroy this item - if this ever leaks due to game bugs, ensure it doesn't dupe + + entityitem.setDefaultPickUpDelay(); ++ // Paper start - Call EntityDropItemEvent ++ return this.spawnAtLocation(world, entityitem); ++ } ++ } ++ @Nullable ++ public ItemEntity spawnAtLocation(ServerLevel world, ItemEntity entityitem) { ++ { ++ // Paper end - Call EntityDropItemEvent + // CraftBukkit start + EntityDropItemEvent event = new EntityDropItemEvent(this.getBukkitEntity(), (org.bukkit.entity.Item) entityitem.getBukkitEntity()); + Bukkit.getPluginManager().callEvent(event); +diff --git a/src/main/java/net/minecraft/world/entity/animal/Dolphin.java b/src/main/java/net/minecraft/world/entity/animal/Dolphin.java +index dde1ccca98f58200910334160f0f79eb00dd2388..5af4d590a9b0f17ba53c6959d9c18bd1269878a4 100644 +--- a/src/main/java/net/minecraft/world/entity/animal/Dolphin.java ++++ b/src/main/java/net/minecraft/world/entity/animal/Dolphin.java +@@ -604,7 +604,7 @@ public class Dolphin extends AgeableWaterCreature { + float f2 = 0.02F * Dolphin.this.random.nextFloat(); + + entityitem.setDeltaMovement((double) (0.3F * -Mth.sin(Dolphin.this.getYRot() * 0.017453292F) * Mth.cos(Dolphin.this.getXRot() * 0.017453292F) + Mth.cos(f1) * f2), (double) (0.3F * Mth.sin(Dolphin.this.getXRot() * 0.017453292F) * 1.5F), (double) (0.3F * Mth.cos(Dolphin.this.getYRot() * 0.017453292F) * Mth.cos(Dolphin.this.getXRot() * 0.017453292F) + Mth.sin(f1) * f2)); +- Dolphin.this.level().addFreshEntity(entityitem); ++ Dolphin.this.spawnAtLocation(getServerLevel(Dolphin.this), entityitem); // Paper - Call EntityDropItemEvent + } + } + } +diff --git a/src/main/java/net/minecraft/world/entity/animal/Fox.java b/src/main/java/net/minecraft/world/entity/animal/Fox.java +index 205aefd38a185fa411ff17cfb0155769de8fc2fd..67ea374ae3c66af434b4aadbe702a44d230fbe09 100644 +--- a/src/main/java/net/minecraft/world/entity/animal/Fox.java ++++ b/src/main/java/net/minecraft/world/entity/animal/Fox.java +@@ -492,14 +492,14 @@ public class Fox extends Animal implements VariantHolder { + entityitem.setPickUpDelay(40); + entityitem.setThrower(this); + this.playSound(SoundEvents.FOX_SPIT, 1.0F, 1.0F); +- this.level().addFreshEntity(entityitem); ++ this.spawnAtLocation((net.minecraft.server.level.ServerLevel) this.level(), entityitem); // Paper - Call EntityDropItemEvent + } + } + + private void dropItemStack(ItemStack stack) { + ItemEntity entityitem = new ItemEntity(this.level(), this.getX(), this.getY(), this.getZ(), stack); + +- this.level().addFreshEntity(entityitem); ++ this.spawnAtLocation((net.minecraft.server.level.ServerLevel) this.level(), entityitem); // Paper - Call EntityDropItemEvent + } + + @Override +diff --git a/src/main/java/net/minecraft/world/entity/animal/goat/Goat.java b/src/main/java/net/minecraft/world/entity/animal/goat/Goat.java +index 14e02f9b0169db8388c515a68315ad5cc3f13d22..14b47d6fa189f2a666b12ef7e7708d204c2b0452 100644 +--- a/src/main/java/net/minecraft/world/entity/animal/goat/Goat.java ++++ b/src/main/java/net/minecraft/world/entity/animal/goat/Goat.java +@@ -365,8 +365,7 @@ public class Goat extends Animal { + double d2 = (double) Mth.randomBetween(this.random, -0.2F, 0.2F); + ItemEntity entityitem = new ItemEntity(this.level(), vec3d.x(), vec3d.y(), vec3d.z(), itemstack, d0, d1, d2); + +- this.level().addFreshEntity(entityitem); +- return true; ++ return this.spawnAtLocation((net.minecraft.server.level.ServerLevel) this.level(), entityitem) != null; // Paper - Call EntityDropItemEvent + } + } + +diff --git a/src/main/java/net/minecraft/world/entity/animal/sniffer/Sniffer.java b/src/main/java/net/minecraft/world/entity/animal/sniffer/Sniffer.java +index 5e586f174ff4b610a2584f28c9ffdd445ad58bea..014e1ea1603bc7a7b42ae7ff7d12e5a41f331d2f 100644 +--- a/src/main/java/net/minecraft/world/entity/animal/sniffer/Sniffer.java ++++ b/src/main/java/net/minecraft/world/entity/animal/sniffer/Sniffer.java +@@ -345,8 +345,9 @@ public class Sniffer extends Animal { + + entityitem.setDefaultPickUpDelay(); + this.finalizeSpawnChildFromBreeding(world, other, (AgeableMob) null); ++ if (this.spawnAtLocation(world, entityitem) != null) { // Paper - Call EntityDropItemEvent + this.playSound(SoundEvents.SNIFFER_EGG_PLOP, 1.0F, (this.random.nextFloat() - this.random.nextFloat()) * 0.2F + 0.5F); +- world.addFreshEntity(entityitem); ++ } // Paper - Call EntityDropItemEvent + } + + @Override +diff --git a/src/main/java/net/minecraft/world/item/ItemUtils.java b/src/main/java/net/minecraft/world/item/ItemUtils.java +index f4fc17e029407be5df3cfe2dcb6fb2f88234b066..0c4074ed8b4fd9d6fcb838e8843d66f6f286ed5d 100644 +--- a/src/main/java/net/minecraft/world/item/ItemUtils.java ++++ b/src/main/java/net/minecraft/world/item/ItemUtils.java +@@ -41,7 +41,15 @@ public class ItemUtils { + public static void onContainerDestroyed(ItemEntity itemEntity, Iterable contents) { + Level level = itemEntity.level(); + if (!level.isClientSide) { +- contents.forEach(stack -> level.addFreshEntity(new ItemEntity(level, itemEntity.getX(), itemEntity.getY(), itemEntity.getZ(), stack))); ++ // Paper start - call EntityDropItemEvent ++ contents.forEach(stack -> { ++ ItemEntity droppedItem = new ItemEntity(level, itemEntity.getX(), itemEntity.getY(), itemEntity.getZ(), stack); ++ org.bukkit.event.entity.EntityDropItemEvent event = new org.bukkit.event.entity.EntityDropItemEvent(itemEntity.getBukkitEntity(), (org.bukkit.entity.Item) droppedItem.getBukkitEntity()); ++ if (event.callEvent()) { ++ level.addFreshEntity(droppedItem); ++ } ++ }); ++ // Paper end - call EntityDropItemEvent + } + } + } diff --git a/patches/server/0713-Don-t-broadcast-messages-to-command-blocks.patch b/patches/server/0713-Don-t-broadcast-messages-to-command-blocks.patch deleted file mode 100644 index 6c746cf029f7..000000000000 --- a/patches/server/0713-Don-t-broadcast-messages-to-command-blocks.patch +++ /dev/null @@ -1,34 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Jake Potrebic -Date: Thu, 16 Jun 2022 14:22:56 -0700 -Subject: [PATCH] Don't broadcast messages to command blocks - -Previously the broadcast method would update the last output -in command blocks, and if called asynchronously, would throw -an error - -diff --git a/src/main/java/net/minecraft/world/level/BaseCommandBlock.java b/src/main/java/net/minecraft/world/level/BaseCommandBlock.java -index 8c2dcc4134d96351cee75773214f3f47e71533e9..e6bfcc50cdf728216084bc00a5bb8b6b3b8f72e4 100644 ---- a/src/main/java/net/minecraft/world/level/BaseCommandBlock.java -+++ b/src/main/java/net/minecraft/world/level/BaseCommandBlock.java -@@ -178,6 +178,7 @@ public abstract class BaseCommandBlock implements CommandSource { - @Override - public void sendSystemMessage(Component message) { - if (this.trackOutput) { -+ org.spigotmc.AsyncCatcher.catchOp("sendSystemMessage to a command block"); // Paper - Don't broadcast messages to command blocks - SimpleDateFormat simpledateformat = BaseCommandBlock.TIME_FORMAT; - Date date = new Date(); - -diff --git a/src/main/java/org/bukkit/craftbukkit/CraftServer.java b/src/main/java/org/bukkit/craftbukkit/CraftServer.java -index 8f8674ccee58f1910ffc3926d42913048da1810e..3c5d44499e94bd19e4058eb2bf2d9b5d5890f980 100644 ---- a/src/main/java/org/bukkit/craftbukkit/CraftServer.java -+++ b/src/main/java/org/bukkit/craftbukkit/CraftServer.java -@@ -1925,7 +1925,7 @@ public final class CraftServer implements Server { - // Paper end - Set recipients = new HashSet<>(); - for (Permissible permissible : this.getPluginManager().getPermissionSubscriptions(permission)) { -- if (permissible instanceof CommandSender && permissible.hasPermission(permission)) { -+ if (permissible instanceof CommandSender && !(permissible instanceof org.bukkit.command.BlockCommandSender) && permissible.hasPermission(permission)) { // Paper - Don't broadcast messages to command blocks - recipients.add((CommandSender) permissible); - } - } diff --git a/patches/server/0714-Fix-Bee-flower-NPE.patch b/patches/server/0714-Fix-Bee-flower-NPE.patch new file mode 100644 index 000000000000..d84bd3a355c0 --- /dev/null +++ b/patches/server/0714-Fix-Bee-flower-NPE.patch @@ -0,0 +1,19 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Jake Potrebic +Date: Wed, 6 Jul 2022 14:59:38 -0700 +Subject: [PATCH] Fix Bee flower NPE + + +diff --git a/src/main/java/net/minecraft/world/entity/animal/Bee.java b/src/main/java/net/minecraft/world/entity/animal/Bee.java +index 048a357546c8f5ad5dbb86e2e1ada2730a52d061..42276acfeadec6e7aa9a91d3f446f4fedb04829d 100644 +--- a/src/main/java/net/minecraft/world/entity/animal/Bee.java ++++ b/src/main/java/net/minecraft/world/entity/animal/Bee.java +@@ -940,7 +940,7 @@ public class Bee extends Animal implements NeutralMob, FlyingAnimal { + Bee.this.dropFlower(); + this.pollinating = false; + Bee.this.remainingCooldownBeforeLocatingNewFlower = 200; +- } else { ++ } else if (Bee.this.savedFlowerPos != null) { // Paper - add null check since API can manipulate this + Vec3 vec3d = Vec3.atBottomCenterOf(Bee.this.savedFlowerPos).add(0.0D, 0.6000000238418579D, 0.0D); + + if (vec3d.distanceTo(Bee.this.position()) > 1.0D) { diff --git a/patches/server/0714-Prevent-empty-items-from-being-added-to-world.patch b/patches/server/0714-Prevent-empty-items-from-being-added-to-world.patch deleted file mode 100644 index 53f557979168..000000000000 --- a/patches/server/0714-Prevent-empty-items-from-being-added-to-world.patch +++ /dev/null @@ -1,20 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Owen1212055 <23108066+Owen1212055@users.noreply.github.com> -Date: Wed, 15 Jun 2022 21:52:57 -0400 -Subject: [PATCH] Prevent empty items from being added to world - -The previous solution caused a bunch of bandaid fixes inorder to resolve edge cases where minecraft/the api might spawn items that are air. -Just simply prevent them from being added to the world instead. - -diff --git a/src/main/java/net/minecraft/server/level/ServerLevel.java b/src/main/java/net/minecraft/server/level/ServerLevel.java -index 492e8a2ff5618c45bfffa3284f03be14ff162ce8..b88a7f580b4138ceb262f3d398e78fdc2e6b018c 100644 ---- a/src/main/java/net/minecraft/server/level/ServerLevel.java -+++ b/src/main/java/net/minecraft/server/level/ServerLevel.java -@@ -1235,6 +1235,7 @@ public class ServerLevel extends Level implements WorldGenLevel { - // WorldServer.LOGGER.warn("Tried to add entity {} but it was marked as removed already", EntityTypes.getKey(entity.getType())); // CraftBukkit - return false; - } else { -+ if (entity instanceof net.minecraft.world.entity.item.ItemEntity itemEntity && itemEntity.getItem().isEmpty()) return false; // Paper - Prevent empty items from being added - // Paper start - capture all item additions to the world - if (captureDrops != null && entity instanceof net.minecraft.world.entity.item.ItemEntity) { - captureDrops.add((net.minecraft.world.entity.item.ItemEntity) entity); diff --git a/patches/server/0715-More-Teleport-API.patch b/patches/server/0715-More-Teleport-API.patch new file mode 100644 index 000000000000..e567640ee1d3 --- /dev/null +++ b/patches/server/0715-More-Teleport-API.patch @@ -0,0 +1,266 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Owen1212055 <23108066+Owen1212055@users.noreply.github.com> +Date: Sun, 5 Sep 2021 12:15:59 -0400 +Subject: [PATCH] More Teleport API + + +diff --git a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java +index bc8f9bd50de3894e6262e13ed55252c98f22ed8a..4d64eedfbe5b967572b7140ddfb55efa1ccc3650 100644 +--- a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java ++++ b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java +@@ -1585,11 +1585,17 @@ public class ServerGamePacketListenerImpl extends ServerCommonPacketListenerImpl + return true; // CraftBukkit - Return event status + } + +- PlayerTeleportEvent event = new PlayerTeleportEvent(player, from.clone(), to.clone(), cause); ++ // Paper start - Teleport API ++ Set relativeFlags = java.util.EnumSet.noneOf(io.papermc.paper.entity.TeleportFlag.Relative.class); ++ for (Relative relativeArgument : set) { ++ relativeFlags.add(org.bukkit.craftbukkit.entity.CraftPlayer.toApiRelativeFlag(relativeArgument)); ++ } ++ PlayerTeleportEvent event = new PlayerTeleportEvent(player, from.clone(), to.clone(), cause, java.util.Set.copyOf(relativeFlags)); ++ // Paper end - Teleport API + this.cserver.getPluginManager().callEvent(event); + + if (event.isCancelled() || !to.equals(event.getTo())) { +- set = Collections.emptySet(); // Can't relative teleport ++ // set = Collections.emptySet(); // Can't relative teleport // Paper - Teleport API; Now you can! + to = event.isCancelled() ? event.getFrom() : event.getTo(); + positionmoverotation = new PositionMoveRotation(CraftLocation.toVec3D(to), Vec3.ZERO, to.getYaw(), to.getPitch()); + } +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java +index 4e6afa243d6108cb946a8a7cf96c4036a3c2ac0c..a314e401ee8a306dc12a2d98a3d400ae611a6e5a 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java +@@ -222,15 +222,36 @@ public abstract class CraftEntity implements org.bukkit.entity.Entity { + + @Override + public boolean teleport(Location location, TeleportCause cause) { ++ // Paper start - Teleport passenger API ++ return teleport(location, cause, new io.papermc.paper.entity.TeleportFlag[0]); ++ } ++ ++ @Override ++ public boolean teleport(Location location, TeleportCause cause, io.papermc.paper.entity.TeleportFlag... flags) { ++ // Paper end + Preconditions.checkArgument(location != null, "location cannot be null"); + location.checkFinite(); ++ // Paper start - Teleport passenger API ++ Set flagSet = Set.of(flags); ++ boolean dismount = !flagSet.contains(io.papermc.paper.entity.TeleportFlag.EntityState.RETAIN_VEHICLE); ++ boolean ignorePassengers = flagSet.contains(io.papermc.paper.entity.TeleportFlag.EntityState.RETAIN_PASSENGERS); ++ // Don't allow teleporting between worlds while keeping passengers ++ if (flagSet.contains(io.papermc.paper.entity.TeleportFlag.EntityState.RETAIN_PASSENGERS) && this.entity.isVehicle() && location.getWorld() != this.getWorld()) { ++ return false; ++ } ++ ++ // Don't allow to teleport between worlds if remaining on vehicle ++ if (!dismount && this.entity.isPassenger() && location.getWorld() != this.getWorld()) { ++ return false; ++ } ++ // Paper end + +- if (this.entity.isVehicle() || this.entity.isRemoved()) { ++ if ((!ignorePassengers && this.entity.isVehicle()) || this.entity.isRemoved()) { // Paper - Teleport passenger API + return false; + } + + // If this entity is riding another entity, we must dismount before teleporting. +- this.entity.stopRiding(); ++ if (dismount) this.entity.stopRiding(); // Paper - Teleport passenger API + + // Let the server handle cross world teleports + if (location.getWorld() != null && !location.getWorld().equals(this.getWorld())) { +@@ -976,6 +997,39 @@ public abstract class CraftEntity implements org.bukkit.entity.Entity { + return CraftEntity.perm; + } + ++ // Paper start - more teleport API / async chunk API ++ @Override ++ public java.util.concurrent.CompletableFuture teleportAsync(final Location location, final TeleportCause cause, final io.papermc.paper.entity.TeleportFlag... teleportFlags) { ++ Preconditions.checkArgument(location != null, "location"); ++ location.checkFinite(); ++ Location locationClone = location.clone(); // clone so we don't need to worry about mutations after this call. ++ ++ net.minecraft.server.level.ServerLevel world = ((CraftWorld)locationClone.getWorld()).getHandle(); ++ java.util.concurrent.CompletableFuture ret = new java.util.concurrent.CompletableFuture<>(); ++ ++ world.loadChunksForMoveAsync(getHandle().getBoundingBoxAt(locationClone.getX(), locationClone.getY(), locationClone.getZ()), ++ this instanceof CraftPlayer ? ca.spottedleaf.concurrentutil.util.Priority.HIGHER : ca.spottedleaf.concurrentutil.util.Priority.NORMAL, (list) -> { ++ net.minecraft.server.level.ServerChunkCache chunkProviderServer = world.getChunkSource(); ++ for (net.minecraft.world.level.chunk.ChunkAccess chunk : list) { ++ chunkProviderServer.addTicketAtLevel(net.minecraft.server.level.TicketType.POST_TELEPORT, chunk.getPos(), 33, CraftEntity.this.getEntityId()); ++ } ++ net.minecraft.server.MinecraftServer.getServer().scheduleOnMain(() -> { ++ try { ++ ret.complete(CraftEntity.this.teleport(locationClone, cause, teleportFlags) ? Boolean.TRUE : Boolean.FALSE); ++ } catch (Throwable throwable) { ++ if (throwable instanceof ThreadDeath) { ++ throw (ThreadDeath)throwable; ++ } ++ net.minecraft.server.MinecraftServer.LOGGER.error("Failed to teleport entity " + CraftEntity.this, throwable); ++ ret.completeExceptionally(throwable); ++ } ++ }); ++ }); ++ ++ return ret; ++ } ++ // Paper end - more teleport API / async chunk API ++ + // Spigot start + private final org.bukkit.entity.Entity.Spigot spigot = new org.bukkit.entity.Entity.Spigot() + { +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java +index fe0c355f8203c9bfa30d2ec48392a5a1a3d616ae..4c4b8c14b41816173466232113252ef1ba2bb2ee 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java +@@ -1303,13 +1303,96 @@ public class CraftPlayer extends CraftHumanEntity implements Player { + + @Override + public void setRotation(float yaw, float pitch) { +- throw new UnsupportedOperationException("Cannot set rotation of players. Consider teleporting instead."); ++ // Paper start - Teleport API ++ if (this.getHandle().connection == null) return; ++ this.getHandle().forceSetRotation(yaw, pitch); ++ // Paper end - Teleportation API + } + + @Override + public boolean teleport(Location location, PlayerTeleportEvent.TeleportCause cause) { ++ // Paper start - Teleport API ++ return this.teleport(location, cause, new io.papermc.paper.entity.TeleportFlag[0]); ++ } ++ ++ @Override ++ public void lookAt(@NotNull org.bukkit.entity.Entity entity, @NotNull io.papermc.paper.entity.LookAnchor playerAnchor, @NotNull io.papermc.paper.entity.LookAnchor entityAnchor) { ++ this.getHandle().lookAt(toNmsAnchor(playerAnchor), ((CraftEntity) entity).getHandle(), toNmsAnchor(entityAnchor)); ++ } ++ ++ @Override ++ public void lookAt(double x, double y, double z, @NotNull io.papermc.paper.entity.LookAnchor playerAnchor) { ++ this.getHandle().lookAt(toNmsAnchor(playerAnchor), new net.minecraft.world.phys.Vec3(x, y, z)); ++ } ++ ++ public static net.minecraft.commands.arguments.EntityAnchorArgument.Anchor toNmsAnchor(io.papermc.paper.entity.LookAnchor nmsAnchor) { ++ return switch (nmsAnchor) { ++ case EYES -> net.minecraft.commands.arguments.EntityAnchorArgument.Anchor.EYES; ++ case FEET -> net.minecraft.commands.arguments.EntityAnchorArgument.Anchor.FEET; ++ }; ++ } ++ ++ public static io.papermc.paper.entity.LookAnchor toApiAnchor(net.minecraft.commands.arguments.EntityAnchorArgument.Anchor playerAnchor) { ++ return switch (playerAnchor) { ++ case EYES -> io.papermc.paper.entity.LookAnchor.EYES; ++ case FEET -> io.papermc.paper.entity.LookAnchor.FEET; ++ }; ++ } ++ ++ public static net.minecraft.world.entity.Relative toNmsRelativeFlag(io.papermc.paper.entity.TeleportFlag.Relative apiFlag) { ++ return switch (apiFlag) { ++ case X -> net.minecraft.world.entity.Relative.X; ++ case Y -> net.minecraft.world.entity.Relative.Y; ++ case Z -> net.minecraft.world.entity.Relative.Z; ++ case PITCH -> net.minecraft.world.entity.Relative.X_ROT; ++ case YAW -> net.minecraft.world.entity.Relative.Y_ROT; ++ }; ++ } ++ ++ public static io.papermc.paper.entity.TeleportFlag.Relative toApiRelativeFlag(net.minecraft.world.entity.Relative nmsFlag) { ++ return switch (nmsFlag) { ++ case X -> io.papermc.paper.entity.TeleportFlag.Relative.X; ++ case Y -> io.papermc.paper.entity.TeleportFlag.Relative.Y; ++ case Z -> io.papermc.paper.entity.TeleportFlag.Relative.Z; ++ case X_ROT -> io.papermc.paper.entity.TeleportFlag.Relative.PITCH; ++ case Y_ROT -> io.papermc.paper.entity.TeleportFlag.Relative.YAW; ++ default -> throw new RuntimeException("not yet"); // TODO figure out what to do with new flags ++ }; ++ } ++ ++ @Override ++ public boolean teleport(Location location, org.bukkit.event.player.PlayerTeleportEvent.TeleportCause cause, io.papermc.paper.entity.TeleportFlag... flags) { ++ Set relativeArguments; ++ Set allFlags; ++ if (flags.length == 0) { ++ relativeArguments = Set.of(); ++ allFlags = Set.of(); ++ } else { ++ relativeArguments = java.util.EnumSet.noneOf(io.papermc.paper.entity.TeleportFlag.Relative.class); ++ allFlags = new HashSet<>(); ++ for (io.papermc.paper.entity.TeleportFlag flag : flags) { ++ if (flag instanceof final io.papermc.paper.entity.TeleportFlag.Relative relativeTeleportFlag) { ++ relativeArguments.add(relativeTeleportFlag); ++ } ++ allFlags.add(flag); ++ } ++ } ++ boolean dismount = !allFlags.contains(io.papermc.paper.entity.TeleportFlag.EntityState.RETAIN_VEHICLE); ++ boolean ignorePassengers = allFlags.contains(io.papermc.paper.entity.TeleportFlag.EntityState.RETAIN_PASSENGERS); ++ // Paper end - Teleport API + Preconditions.checkArgument(location != null, "location"); + Preconditions.checkArgument(location.getWorld() != null, "location.world"); ++ // Paper start - Teleport passenger API ++ // Don't allow teleporting between worlds while keeping passengers ++ if (ignorePassengers && entity.isVehicle() && location.getWorld() != this.getWorld()) { ++ return false; ++ } ++ ++ // Don't allow to teleport between worlds if remaining on vehicle ++ if (!dismount && entity.isPassenger() && location.getWorld() != this.getWorld()) { ++ return false; ++ } ++ // Paper end + location.checkFinite(); + + ServerPlayer entity = this.getHandle(); +@@ -1322,7 +1405,7 @@ public class CraftPlayer extends CraftHumanEntity implements Player { + return false; + } + +- if (entity.isVehicle()) { ++ if (entity.isVehicle() && !ignorePassengers) { // Paper - Teleport API + return false; + } + +@@ -1331,7 +1414,7 @@ public class CraftPlayer extends CraftHumanEntity implements Player { + // To = Players new Location if Teleport is Successful + Location to = location; + // Create & Call the Teleport Event. +- PlayerTeleportEvent event = new PlayerTeleportEvent(this, from, to, cause); ++ PlayerTeleportEvent event = new PlayerTeleportEvent(this, from, to, cause, Set.copyOf(relativeArguments)); // Paper - Teleport API + this.server.getPluginManager().callEvent(event); + + // Return False to inform the Plugin that the Teleport was unsuccessful/cancelled. +@@ -1340,7 +1423,7 @@ public class CraftPlayer extends CraftHumanEntity implements Player { + } + + // If this player is riding another entity, we must dismount before teleporting. +- entity.stopRiding(); ++ if (dismount) entity.stopRiding(); // Paper - Teleport API + + // SPIGOT-5509: Wakeup, similar to riding + if (this.isSleeping()) { +@@ -1356,13 +1439,21 @@ public class CraftPlayer extends CraftHumanEntity implements Player { + ServerLevel toWorld = ((CraftWorld) to.getWorld()).getHandle(); + + // Close any foreign inventory +- if (this.getHandle().containerMenu != this.getHandle().inventoryMenu) { ++ if (this.getHandle().containerMenu != this.getHandle().inventoryMenu && !allFlags.contains(io.papermc.paper.entity.TeleportFlag.EntityState.RETAIN_OPEN_INVENTORY)) { // Paper + this.getHandle().closeContainer(org.bukkit.event.inventory.InventoryCloseEvent.Reason.TELEPORT); // Paper - Inventory close reason + } + + // Check if the fromWorld and toWorld are the same. + if (fromWorld == toWorld) { +- entity.connection.teleport(to); ++ // Paper start - Teleport API ++ final Set nms = java.util.EnumSet.noneOf(net.minecraft.world.entity.Relative.class); ++ for (final io.papermc.paper.entity.TeleportFlag.Relative bukkit : relativeArguments) { ++ nms.add(toNmsRelativeFlag(bukkit)); ++ } ++ entity.connection.internalTeleport(new net.minecraft.world.entity.PositionMoveRotation( ++ io.papermc.paper.util.MCUtil.toVec3(to), net.minecraft.world.phys.Vec3.ZERO, to.getYaw(), to.getPitch() ++ ), nms); ++ // Paper end - Teleport API + } else { + entity.portalProcess = null; // SPIGOT-7785: there is no need to carry this over as it contains the old world/location and we might run into trouble if there is a portal in the same spot in both worlds + // The respawn reason should never be used if the passed location is non null. diff --git a/patches/server/0716-Add-EntityPortalReadyEvent.patch b/patches/server/0716-Add-EntityPortalReadyEvent.patch new file mode 100644 index 000000000000..46f19b8cc87a --- /dev/null +++ b/patches/server/0716-Add-EntityPortalReadyEvent.patch @@ -0,0 +1,25 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Jake Potrebic +Date: Wed, 12 May 2021 04:30:42 -0700 +Subject: [PATCH] Add EntityPortalReadyEvent + + +diff --git a/src/main/java/net/minecraft/world/level/block/NetherPortalBlock.java b/src/main/java/net/minecraft/world/level/block/NetherPortalBlock.java +index c00507fa7af579263caed67dafc2ea9eba09512b..fb361eac03c16ecee02219ff0524cce2292c7976 100644 +--- a/src/main/java/net/minecraft/world/level/block/NetherPortalBlock.java ++++ b/src/main/java/net/minecraft/world/level/block/NetherPortalBlock.java +@@ -141,6 +141,14 @@ public class NetherPortalBlock extends Block implements Portal { + // CraftBukkit start + ResourceKey resourcekey = world.getTypeKey() == LevelStem.NETHER ? Level.OVERWORLD : Level.NETHER; + ServerLevel worldserver1 = world.getServer().getLevel(resourcekey); ++ // Paper start - Add EntityPortalReadyEvent ++ io.papermc.paper.event.entity.EntityPortalReadyEvent portalReadyEvent = new io.papermc.paper.event.entity.EntityPortalReadyEvent(entity.getBukkitEntity(), worldserver1 == null ? null : worldserver1.getWorld(), org.bukkit.PortalType.NETHER); ++ if (!portalReadyEvent.callEvent()) { ++ entity.portalProcess = null; ++ return null; ++ } ++ worldserver1 = portalReadyEvent.getTargetWorld() == null ? null : ((org.bukkit.craftbukkit.CraftWorld) portalReadyEvent.getTargetWorld()).getHandle(); ++ // Paper end - Add EntityPortalReadyEvent + + if (worldserver1 == null) { + return new TeleportTransition(PlayerTeleportEvent.TeleportCause.NETHER_PORTAL); // always fire event in case plugins wish to change it diff --git a/patches/server/0716-Add-Player-getFishHook.patch b/patches/server/0716-Add-Player-getFishHook.patch deleted file mode 100644 index 99c85d33b3ac..000000000000 --- a/patches/server/0716-Add-Player-getFishHook.patch +++ /dev/null @@ -1,26 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: u9g -Date: Tue, 14 Jun 2022 19:36:10 -0400 -Subject: [PATCH] Add Player#getFishHook - - -diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftHumanEntity.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftHumanEntity.java -index 0222b6c68112551336f17a722fc3399898cdc7bb..049db909fbd8610ebb688d948f5d03c97ab23495 100644 ---- a/src/main/java/org/bukkit/craftbukkit/entity/CraftHumanEntity.java -+++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftHumanEntity.java -@@ -160,6 +160,15 @@ public class CraftHumanEntity extends CraftLivingEntity implements HumanEntity { - return new Location(worldServer.getWorld(), bed.getX(), bed.getY(), bed.getZ()); - } - // Paper end -+ // Paper start -+ @Override -+ public org.bukkit.entity.FishHook getFishHook() { -+ if (getHandle().fishing == null) { -+ return null; -+ } -+ return (org.bukkit.entity.FishHook) getHandle().fishing.getBukkitEntity(); -+ } -+ // Paper end - @Override - public boolean sleep(Location location, boolean force) { - Preconditions.checkArgument(location != null, "Location cannot be null"); diff --git a/patches/server/0717-Don-t-use-level-random-in-entity-constructors.patch b/patches/server/0717-Don-t-use-level-random-in-entity-constructors.patch new file mode 100644 index 000000000000..83efb1446fbc --- /dev/null +++ b/patches/server/0717-Don-t-use-level-random-in-entity-constructors.patch @@ -0,0 +1,41 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Jake Potrebic +Date: Sun, 10 Jul 2022 14:13:22 -0700 +Subject: [PATCH] Don't use level random in entity constructors + +Paper makes the entity random thread-safe +and constructing an entity off the main thread +should be supported. Some entities (for whatever +reason) use the level's random in some places. + +diff --git a/src/main/java/net/minecraft/world/entity/item/ItemEntity.java b/src/main/java/net/minecraft/world/entity/item/ItemEntity.java +index 586257fe5c9f5cddd0ed164254f46777c6e71d66..d555fd0b200c012f30ed0c0ec09a37b25a737b76 100644 +--- a/src/main/java/net/minecraft/world/entity/item/ItemEntity.java ++++ b/src/main/java/net/minecraft/world/entity/item/ItemEntity.java +@@ -72,7 +72,12 @@ public class ItemEntity extends Entity implements TraceableEntity { + } + + public ItemEntity(Level world, double x, double y, double z, ItemStack stack) { +- this(world, x, y, z, stack, world.random.nextDouble() * 0.2D - 0.1D, 0.2D, world.random.nextDouble() * 0.2D - 0.1D); ++ // Paper start - Don't use level random in entity constructors (to make them thread-safe) ++ this(EntityType.ITEM, world); ++ this.setPos(x, y, z); ++ this.setDeltaMovement(this.random.nextDouble() * 0.2D - 0.1D, 0.2D, this.random.nextDouble() * 0.2D - 0.1D); ++ this.setItem(stack); ++ // Paper end - Don't use level random in entity constructors + } + + public ItemEntity(Level world, double x, double y, double z, ItemStack stack, double velocityX, double velocityY, double velocityZ) { +diff --git a/src/main/java/net/minecraft/world/entity/item/PrimedTnt.java b/src/main/java/net/minecraft/world/entity/item/PrimedTnt.java +index 45c224198135e48f94dc72312c805bf451bf7b0e..de87483600e55d88176fe25db621bbd3e464729f 100644 +--- a/src/main/java/net/minecraft/world/entity/item/PrimedTnt.java ++++ b/src/main/java/net/minecraft/world/entity/item/PrimedTnt.java +@@ -68,7 +68,7 @@ public class PrimedTnt extends Entity implements TraceableEntity { + public PrimedTnt(Level world, double x, double y, double z, @Nullable LivingEntity igniter) { + this(EntityType.TNT, world); + this.setPos(x, y, z); +- double d3 = world.random.nextDouble() * 6.2831854820251465D; ++ double d3 = this.random.nextDouble() * 6.2831854820251465D; // Paper - Don't use level random in entity constructors + + this.setDeltaMovement(-Math.sin(d3) * 0.02D, 0.20000000298023224D, -Math.cos(d3) * 0.02D); + this.setFuse(80); diff --git a/patches/server/0718-Add-various-missing-EntityDropItemEvent-calls.patch b/patches/server/0718-Add-various-missing-EntityDropItemEvent-calls.patch deleted file mode 100644 index 2f42aa11cb2e..000000000000 --- a/patches/server/0718-Add-various-missing-EntityDropItemEvent-calls.patch +++ /dev/null @@ -1,109 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Jake Potrebic -Date: Tue, 20 Jul 2021 21:35:47 -0700 -Subject: [PATCH] Add various missing EntityDropItemEvent calls - - -diff --git a/src/main/java/net/minecraft/world/entity/Entity.java b/src/main/java/net/minecraft/world/entity/Entity.java -index 8b0959050cd5e0ef24347632c3b4184eadf565e6..73a69c35bca89ab82da39149a2a7cb86d4878e3c 100644 ---- a/src/main/java/net/minecraft/world/entity/Entity.java -+++ b/src/main/java/net/minecraft/world/entity/Entity.java -@@ -2557,6 +2557,14 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess - stack.setCount(0); // Paper - destroy this item - if this ever leaks due to game bugs, ensure it doesn't dupe - - entityitem.setDefaultPickUpDelay(); -+ // Paper start - Call EntityDropItemEvent -+ return this.spawnAtLocation(entityitem); -+ } -+ } -+ @Nullable -+ public ItemEntity spawnAtLocation(ItemEntity entityitem) { -+ { -+ // Paper end - Call EntityDropItemEvent - // CraftBukkit start - EntityDropItemEvent event = new EntityDropItemEvent(this.getBukkitEntity(), (org.bukkit.entity.Item) entityitem.getBukkitEntity()); - Bukkit.getPluginManager().callEvent(event); -diff --git a/src/main/java/net/minecraft/world/entity/animal/Dolphin.java b/src/main/java/net/minecraft/world/entity/animal/Dolphin.java -index 08823651b0a81b15bc33f86c96d6cc1dc72770f4..da45cc62985f8b67cdfeffc21cb33837db673555 100644 ---- a/src/main/java/net/minecraft/world/entity/animal/Dolphin.java -+++ b/src/main/java/net/minecraft/world/entity/animal/Dolphin.java -@@ -576,7 +576,7 @@ public class Dolphin extends WaterAnimal { - float f2 = 0.02F * Dolphin.this.random.nextFloat(); - - entityitem.setDeltaMovement((double) (0.3F * -Mth.sin(Dolphin.this.getYRot() * 0.017453292F) * Mth.cos(Dolphin.this.getXRot() * 0.017453292F) + Mth.cos(f1) * f2), (double) (0.3F * Mth.sin(Dolphin.this.getXRot() * 0.017453292F) * 1.5F), (double) (0.3F * Mth.cos(Dolphin.this.getYRot() * 0.017453292F) * Mth.cos(Dolphin.this.getXRot() * 0.017453292F) + Mth.sin(f1) * f2)); -- Dolphin.this.level().addFreshEntity(entityitem); -+ Dolphin.this.spawnAtLocation(entityitem); // Paper - Call EntityDropItemEvent - } - } - } -diff --git a/src/main/java/net/minecraft/world/entity/animal/Fox.java b/src/main/java/net/minecraft/world/entity/animal/Fox.java -index e46c8231ee318eda0512afbb6343b426b4838643..3265b3b5aede517b6fd8bb836947795bf8881350 100644 ---- a/src/main/java/net/minecraft/world/entity/animal/Fox.java -+++ b/src/main/java/net/minecraft/world/entity/animal/Fox.java -@@ -507,14 +507,14 @@ public class Fox extends Animal implements VariantHolder { - entityitem.setPickUpDelay(40); - entityitem.setThrower(this); - this.playSound(SoundEvents.FOX_SPIT, 1.0F, 1.0F); -- this.level().addFreshEntity(entityitem); -+ this.spawnAtLocation(entityitem); // Paper - Call EntityDropItemEvent - } - } - - private void dropItemStack(ItemStack stack) { - ItemEntity entityitem = new ItemEntity(this.level(), this.getX(), this.getY(), this.getZ(), stack); - -- this.level().addFreshEntity(entityitem); -+ this.spawnAtLocation(entityitem); // Paper - Call EntityDropItemEvent - } - - @Override -diff --git a/src/main/java/net/minecraft/world/entity/animal/goat/Goat.java b/src/main/java/net/minecraft/world/entity/animal/goat/Goat.java -index 3b2cf9ca8447321d64ffdb4fdb9569d736d63dbb..923806900ef6248576e71260d40e9caf2c8943e8 100644 ---- a/src/main/java/net/minecraft/world/entity/animal/goat/Goat.java -+++ b/src/main/java/net/minecraft/world/entity/animal/goat/Goat.java -@@ -361,8 +361,7 @@ public class Goat extends Animal { - double d2 = (double) Mth.randomBetween(this.random, -0.2F, 0.2F); - ItemEntity entityitem = new ItemEntity(this.level(), vec3d.x(), vec3d.y(), vec3d.z(), itemstack, d0, d1, d2); - -- this.level().addFreshEntity(entityitem); -- return true; -+ return this.spawnAtLocation(entityitem) != null; // Paper - Call EntityDropItemEvent - } - } - -diff --git a/src/main/java/net/minecraft/world/entity/animal/sniffer/Sniffer.java b/src/main/java/net/minecraft/world/entity/animal/sniffer/Sniffer.java -index 94df94f7acc121201966955a09fb336b0a98e599..d34d8fe70379dcad9540739ec0ae1c94f01fc46b 100644 ---- a/src/main/java/net/minecraft/world/entity/animal/sniffer/Sniffer.java -+++ b/src/main/java/net/minecraft/world/entity/animal/sniffer/Sniffer.java -@@ -350,8 +350,9 @@ public class Sniffer extends Animal { - - entityitem.setDefaultPickUpDelay(); - this.finalizeSpawnChildFromBreeding(world, other, (AgeableMob) null); -+ if (this.spawnAtLocation(entityitem) != null) { // Paper - Call EntityDropItemEvent - this.playSound(SoundEvents.SNIFFER_EGG_PLOP, 1.0F, (this.random.nextFloat() - this.random.nextFloat()) * 0.2F + 0.5F); -- world.addFreshEntity(entityitem); -+ } // Paper - Call EntityDropItemEvent - } - - @Override -diff --git a/src/main/java/net/minecraft/world/item/ItemUtils.java b/src/main/java/net/minecraft/world/item/ItemUtils.java -index 66f88f44eb74dfbdafe0d6257dc1ef46238aaa92..4901f0d89ae2472bce7f242d9529236674f5d134 100644 ---- a/src/main/java/net/minecraft/world/item/ItemUtils.java -+++ b/src/main/java/net/minecraft/world/item/ItemUtils.java -@@ -41,7 +41,15 @@ public class ItemUtils { - public static void onContainerDestroyed(ItemEntity itemEntity, Iterable contents) { - Level level = itemEntity.level(); - if (!level.isClientSide) { -- contents.forEach(stack -> level.addFreshEntity(new ItemEntity(level, itemEntity.getX(), itemEntity.getY(), itemEntity.getZ(), stack))); -+ // Paper start - call EntityDropItemEvent -+ contents.forEach(stack -> { -+ ItemEntity droppedItem = new ItemEntity(level, itemEntity.getX(), itemEntity.getY(), itemEntity.getZ(), stack); -+ org.bukkit.event.entity.EntityDropItemEvent event = new org.bukkit.event.entity.EntityDropItemEvent(itemEntity.getBukkitEntity(), (org.bukkit.entity.Item) droppedItem.getBukkitEntity()); -+ if (event.callEvent()) { -+ level.addFreshEntity(droppedItem); -+ } -+ }); -+ // Paper end - call EntityDropItemEvent - } - } - } diff --git a/patches/server/0718-Send-block-entities-after-destroy-prediction.patch b/patches/server/0718-Send-block-entities-after-destroy-prediction.patch new file mode 100644 index 000000000000..6fcd859761f1 --- /dev/null +++ b/patches/server/0718-Send-block-entities-after-destroy-prediction.patch @@ -0,0 +1,91 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Owen1212055 <23108066+Owen1212055@users.noreply.github.com> +Date: Sat, 25 Jun 2022 19:45:20 -0400 +Subject: [PATCH] Send block entities after destroy prediction + +Minecraft's prediction system does not handle block entities, so if we are manually sending block entities during +block breaking we need to set it after the prediction is finished. This fixes block entities not showing when cancelling the BlockBreakEvent. + +diff --git a/src/main/java/net/minecraft/server/level/ServerPlayerGameMode.java b/src/main/java/net/minecraft/server/level/ServerPlayerGameMode.java +index 5c3e5c348e6fececccd8097355f423b9e7ad982b..064a7a3e1c4d192010e072a5e985a54135748d87 100644 +--- a/src/main/java/net/minecraft/server/level/ServerPlayerGameMode.java ++++ b/src/main/java/net/minecraft/server/level/ServerPlayerGameMode.java +@@ -61,6 +61,8 @@ public class ServerPlayerGameMode { + private BlockPos delayedDestroyPos; + private int delayedTickStart; + private int lastSentState; ++ public boolean captureSentBlockEntities = false; // Paper - Send block entities after destroy prediction ++ public boolean capturedBlockEntity = false; // Paper - Send block entities after destroy prediction + + public ServerPlayerGameMode(ServerPlayer player) { + this.gameModeForPlayer = GameType.DEFAULT_MODE; +@@ -191,10 +193,7 @@ public class ServerPlayerGameMode { + this.player.connection.send(new ClientboundBlockUpdatePacket(pos, this.level.getBlockState(pos))); + this.debugLogging(pos, false, sequence, "may not interact"); + // Update any tile entity data for this block +- BlockEntity tileentity = this.level.getBlockEntity(pos); +- if (tileentity != null) { +- this.player.connection.send(tileentity.getUpdatePacket()); +- } ++ capturedBlockEntity = true; // Paper - Send block entities after destroy prediction + // CraftBukkit end + return; + } +@@ -205,10 +204,7 @@ public class ServerPlayerGameMode { + // Let the client know the block still exists + this.player.connection.send(new ClientboundBlockUpdatePacket(this.level, pos)); + // Update any tile entity data for this block +- BlockEntity tileentity = this.level.getBlockEntity(pos); +- if (tileentity != null) { +- this.player.connection.send(tileentity.getUpdatePacket()); +- } ++ capturedBlockEntity = true; // Paper - Send block entities after destroy prediction + return; + } + // CraftBukkit end +@@ -393,10 +389,12 @@ public class ServerPlayerGameMode { + } + + // Update any tile entity data for this block ++ if (!captureSentBlockEntities) { // Paper - Send block entities after destroy prediction + BlockEntity tileentity = this.level.getBlockEntity(pos); + if (tileentity != null) { + this.player.connection.send(tileentity.getUpdatePacket()); + } ++ } else {capturedBlockEntity = true;} // Paper - Send block entities after destroy prediction + return false; + } + } +diff --git a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java +index 4d64eedfbe5b967572b7140ddfb55efa1ccc3650..b48966424fb8e937552c0e7bffaedaefc63ef77f 100644 +--- a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java ++++ b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java +@@ -1723,8 +1723,28 @@ public class ServerGamePacketListenerImpl extends ServerCommonPacketListenerImpl + return; + } + // Paper end - Don't allow digging into unloaded chunks ++ // Paper start - Send block entities after destroy prediction ++ this.player.gameMode.capturedBlockEntity = false; ++ this.player.gameMode.captureSentBlockEntities = true; ++ // Paper end - Send block entities after destroy prediction + this.player.gameMode.handleBlockBreakAction(blockposition, packetplayinblockdig_enumplayerdigtype, packet.getDirection(), this.player.level().getMaxY(), packet.getSequence()); + this.player.connection.ackBlockChangesUpTo(packet.getSequence()); ++ // Paper start - Send block entities after destroy prediction ++ this.player.gameMode.captureSentBlockEntities = false; ++ // If a block entity was modified speedup the block change ack to avoid the block entity ++ // being overriden. ++ if (this.player.gameMode.capturedBlockEntity) { ++ // manually tick ++ this.send(new ClientboundBlockChangedAckPacket(this.ackBlockChangesUpTo)); ++ this.player.connection.ackBlockChangesUpTo = -1; ++ ++ this.player.gameMode.capturedBlockEntity = false; ++ BlockEntity tileentity = this.player.level().getBlockEntity(blockposition); ++ if (tileentity != null) { ++ this.player.connection.send(tileentity.getUpdatePacket()); ++ } ++ } ++ // Paper end - Send block entities after destroy prediction + return; + default: + throw new IllegalArgumentException("Invalid player action"); diff --git a/patches/server/0719-Fix-Bee-flower-NPE.patch b/patches/server/0719-Fix-Bee-flower-NPE.patch deleted file mode 100644 index db6e1b0ad944..000000000000 --- a/patches/server/0719-Fix-Bee-flower-NPE.patch +++ /dev/null @@ -1,19 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Jake Potrebic -Date: Wed, 6 Jul 2022 14:59:38 -0700 -Subject: [PATCH] Fix Bee flower NPE - - -diff --git a/src/main/java/net/minecraft/world/entity/animal/Bee.java b/src/main/java/net/minecraft/world/entity/animal/Bee.java -index f933654b66f7474dc071da5f10cf1684fdac367a..1b3978f4ea7e8491e0c0cb6de23c141f44fab414 100644 ---- a/src/main/java/net/minecraft/world/entity/animal/Bee.java -+++ b/src/main/java/net/minecraft/world/entity/animal/Bee.java -@@ -805,7 +805,7 @@ public class Bee extends Animal implements NeutralMob, FlyingAnimal { - ++this.pollinatingTicks; - if (this.pollinatingTicks > 600) { - Bee.this.savedFlowerPos = null; -- } else { -+ } else if (Bee.this.savedFlowerPos != null) { // Paper - add null check since API can manipulate this - Vec3 vec3d = Vec3.atBottomCenterOf(Bee.this.savedFlowerPos).add(0.0D, 0.6000000238418579D, 0.0D); - - if (vec3d.distanceTo(Bee.this.position()) > 1.0D) { diff --git a/patches/server/0719-Warn-on-plugins-accessing-faraway-chunks.patch b/patches/server/0719-Warn-on-plugins-accessing-faraway-chunks.patch new file mode 100644 index 000000000000..2b2ca909d024 --- /dev/null +++ b/patches/server/0719-Warn-on-plugins-accessing-faraway-chunks.patch @@ -0,0 +1,96 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Owen1212055 <23108066+Owen1212055@users.noreply.github.com> +Date: Fri, 29 Jul 2022 12:35:19 -0400 +Subject: [PATCH] Warn on plugins accessing faraway chunks + + +diff --git a/src/main/java/net/minecraft/world/level/Level.java b/src/main/java/net/minecraft/world/level/Level.java +index 9afc0eaaca5ab7b6445d90ce53e31a6ae76f8848..f0c2187a92de633a1d4cc7e71ff62cbe30ce8774 100644 +--- a/src/main/java/net/minecraft/world/level/Level.java ++++ b/src/main/java/net/minecraft/world/level/Level.java +@@ -337,7 +337,7 @@ public abstract class Level implements LevelAccessor, AutoCloseable { + } + + private static boolean isInWorldBoundsHorizontal(BlockPos pos) { +- return pos.getX() >= -30000000 && pos.getZ() >= -30000000 && pos.getX() < 30000000 && pos.getZ() < 30000000; ++ return pos.getX() >= -30000000 && pos.getZ() >= -30000000 && pos.getX() < 30000000 && pos.getZ() < 30000000; // Diff on change warnUnsafeChunk() + } + + private static boolean isOutsideSpawnableHeight(int y) { +diff --git a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java +index 9240460b9ec582ac01d3074c9bc923191bf0ad95..c0f5e4497e1ffc93f56fc2b5748bcf76569e8c3a 100644 +--- a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java ++++ b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java +@@ -309,9 +309,24 @@ public class CraftWorld extends CraftRegionAccessor implements World { + public boolean setSpawnLocation(int x, int y, int z) { + return this.setSpawnLocation(x, y, z, 0.0F); + } ++ // Paper start ++ private static void warnUnsafeChunk(String reason, int x, int z) { ++ // if any chunk coord is outside of 30 million blocks ++ if (x > 1875000 || z > 1875000 || x < -1875000 || z < -1875000) { ++ Plugin plugin = io.papermc.paper.util.StackWalkerUtil.getFirstPluginCaller(); ++ if (plugin != null) { ++ plugin.getLogger().warning("Plugin is %s at (%s, %s), this might cause issues.".formatted(reason, x, z)); ++ } ++ if (net.minecraft.server.MinecraftServer.getServer().isDebugging()) { ++ io.papermc.paper.util.TraceUtil.dumpTraceForThread("Dangerous chunk retrieval"); ++ } ++ } ++ } ++ // Paper end + + @Override + public Chunk getChunkAt(int x, int z) { ++ warnUnsafeChunk("getting a faraway chunk", x, z); // Paper + // Paper start - add ticket to hold chunk for a little while longer if plugin accesses it + net.minecraft.world.level.chunk.LevelChunk chunk = this.world.getChunkSource().getChunkAtIfLoadedImmediately(x, z); + if (chunk == null) { +@@ -419,6 +434,7 @@ public class CraftWorld extends CraftRegionAccessor implements World { + if (!unloadChunk0(x, z, false)) { + return false; + } ++ warnUnsafeChunk("regenerating a faraway chunk", x, z); // Paper + + final long chunkKey = ChunkCoordIntPair.pair(x, z); + world.getChunkProvider().unloadQueue.remove(chunkKey); +@@ -492,6 +508,7 @@ public class CraftWorld extends CraftRegionAccessor implements World { + @Override + public boolean loadChunk(int x, int z, boolean generate) { + org.spigotmc.AsyncCatcher.catchOp("chunk load"); // Spigot ++ warnUnsafeChunk("loading a faraway chunk", x, z); // Paper + ChunkAccess chunk = this.world.getChunkSource().getChunk(x, z, generate || isChunkGenerated(x, z) ? ChunkStatus.FULL : ChunkStatus.EMPTY, true); // Paper + + // If generate = false, but the chunk already exists, we will get this back. +@@ -524,6 +541,7 @@ public class CraftWorld extends CraftRegionAccessor implements World { + + @Override + public boolean addPluginChunkTicket(int x, int z, Plugin plugin) { ++ warnUnsafeChunk("adding a faraway chunk ticket", x, z); // Paper + Preconditions.checkArgument(plugin != null, "null plugin"); + Preconditions.checkArgument(plugin.isEnabled(), "plugin is not enabled"); + +@@ -624,6 +642,7 @@ public class CraftWorld extends CraftRegionAccessor implements World { + + @Override + public void setChunkForceLoaded(int x, int z, boolean forced) { ++ warnUnsafeChunk("forceloading a faraway chunk", x, z); // Paper + this.getHandle().setChunkForced(x, z, forced); + } + +@@ -958,6 +977,7 @@ public class CraftWorld extends CraftRegionAccessor implements World { + + @Override + public int getHighestBlockYAt(int x, int z, org.bukkit.HeightMap heightMap) { ++ warnUnsafeChunk("getting a faraway chunk", x >> 4, z >> 4); // Paper + // Transient load for this tick + return this.world.getChunk(x >> 4, z >> 4).getHeight(CraftHeightMap.toNMS(heightMap), x, z); + } +@@ -2358,6 +2378,7 @@ public class CraftWorld extends CraftRegionAccessor implements World { + // Spigot end + // Paper start + public java.util.concurrent.CompletableFuture getChunkAtAsync(int x, int z, boolean gen, boolean urgent) { ++ warnUnsafeChunk("getting a faraway chunk async", x, z); // Paper + if (Bukkit.isPrimaryThread()) { + net.minecraft.world.level.chunk.LevelChunk immediate = this.world.getChunkSource().getChunkAtIfLoadedImmediately(x, z); + if (immediate != null) { diff --git a/patches/server/0720-Custom-Chat-Completion-Suggestions-API.patch b/patches/server/0720-Custom-Chat-Completion-Suggestions-API.patch new file mode 100644 index 000000000000..6dba5504c2bb --- /dev/null +++ b/patches/server/0720-Custom-Chat-Completion-Suggestions-API.patch @@ -0,0 +1,35 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Owen1212055 <23108066+Owen1212055@users.noreply.github.com> +Date: Sat, 30 Jul 2022 11:23:05 -0400 +Subject: [PATCH] Custom Chat Completion Suggestions API + + +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java +index 4c4b8c14b41816173466232113252ef1ba2bb2ee..2aaf9e87a7322392d713bd869b0bdddae88db7ff 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java +@@ -708,6 +708,24 @@ public class CraftPlayer extends CraftHumanEntity implements Player { + } + // Paper end - Add sendOpLevel API + ++ // Paper start - custom chat completions API ++ @Override ++ public void addAdditionalChatCompletions(@NotNull Collection completions) { ++ this.getHandle().connection.send(new net.minecraft.network.protocol.game.ClientboundCustomChatCompletionsPacket( ++ net.minecraft.network.protocol.game.ClientboundCustomChatCompletionsPacket.Action.ADD, ++ new ArrayList<>(completions) ++ )); ++ } ++ ++ @Override ++ public void removeAdditionalChatCompletions(@NotNull Collection completions) { ++ this.getHandle().connection.send(new net.minecraft.network.protocol.game.ClientboundCustomChatCompletionsPacket( ++ net.minecraft.network.protocol.game.ClientboundCustomChatCompletionsPacket.Action.REMOVE, ++ new ArrayList<>(completions) ++ )); ++ } ++ // Paper end - custom chat completions API ++ + @Override + public void setCompassTarget(Location loc) { + Preconditions.checkArgument(loc != null, "Location cannot be null"); diff --git a/patches/server/0720-Fix-Spigot-Config-not-using-commands.spam-exclusions.patch b/patches/server/0720-Fix-Spigot-Config-not-using-commands.spam-exclusions.patch deleted file mode 100644 index 67b7b9467ec4..000000000000 --- a/patches/server/0720-Fix-Spigot-Config-not-using-commands.spam-exclusions.patch +++ /dev/null @@ -1,19 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Doc -Date: Sun, 17 Jul 2022 11:49:43 -0400 -Subject: [PATCH] Fix Spigot Config not using commands.spam-exclusions - - -diff --git a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java -index 7c1a94980cf49d37be442e1b62de36d4395bcf2a..4fa49daf58723827366f611e006e77448cc73a0b 100644 ---- a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java -+++ b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java -@@ -2383,7 +2383,7 @@ public class ServerGamePacketListenerImpl extends ServerCommonPacketListenerImpl - } - // Spigot end - // this.chatSpamTickCount += 20; -- if (this.chatSpamTickCount.addAndGet(20) > 200 && !this.server.getPlayerList().isOp(this.player.getGameProfile()) && !this.server.isSingleplayerOwner(this.player.getGameProfile())) { -+ if (counted && this.chatSpamTickCount.addAndGet(20) > 200 && !this.server.getPlayerList().isOp(this.player.getGameProfile()) && !this.server.isSingleplayerOwner(this.player.getGameProfile())) { // Paper - exclude from SpigotConfig.spamExclusions - // CraftBukkit end - this.disconnect((Component) Component.translatable("disconnect.spam"), org.bukkit.event.player.PlayerKickEvent.Cause.SPAM); // Paper - kick event cause - } diff --git a/patches/server/0721-Add-and-fix-missing-BlockFadeEvents.patch b/patches/server/0721-Add-and-fix-missing-BlockFadeEvents.patch new file mode 100644 index 000000000000..1ac5d1e5ab42 --- /dev/null +++ b/patches/server/0721-Add-and-fix-missing-BlockFadeEvents.patch @@ -0,0 +1,71 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Owen1212055 <23108066+Owen1212055@users.noreply.github.com> +Date: Thu, 21 Jul 2022 12:07:54 -0400 +Subject: [PATCH] Add and fix missing BlockFadeEvents + +Beyond calling the BlockFadeEvent in more places, this patch also aims +to pass the proper replacement state to the event, specifically for +potentially waterlogged block states fading. + +Co-authored-by: Lulu13022002 <41980282+Lulu13022002@users.noreply.github.com> + +diff --git a/src/main/java/net/minecraft/world/level/block/FrogspawnBlock.java b/src/main/java/net/minecraft/world/level/block/FrogspawnBlock.java +index 34be6b349722240e99f91d28067578aa0b4bd1fe..4f53f25d7565eb930f6ae27ca24a6aa0cca571a2 100644 +--- a/src/main/java/net/minecraft/world/level/block/FrogspawnBlock.java ++++ b/src/main/java/net/minecraft/world/level/block/FrogspawnBlock.java +@@ -102,6 +102,11 @@ public class FrogspawnBlock extends Block { + } + + private void hatchFrogspawn(ServerLevel world, BlockPos pos, RandomSource random) { ++ // Paper start - Call BlockFadeEvent ++ if (org.bukkit.craftbukkit.event.CraftEventFactory.callBlockFadeEvent(world, pos, Blocks.AIR.defaultBlockState()).isCancelled()) { ++ return; ++ } ++ // Paper end - Call BlockFadeEvent + this.destroyBlock(world, pos); + world.playSound(null, pos, SoundEvents.FROGSPAWN_HATCH, SoundSource.BLOCKS, 1.0F, 1.0F); + this.spawnTadpoles(world, pos, random); +diff --git a/src/main/java/net/minecraft/world/level/block/ScaffoldingBlock.java b/src/main/java/net/minecraft/world/level/block/ScaffoldingBlock.java +index fc49230d6b2c13b009724d28081f5e77537d5ab8..2d3f425778302490dd3654d487cfa3cfed6fb9e8 100644 +--- a/src/main/java/net/minecraft/world/level/block/ScaffoldingBlock.java ++++ b/src/main/java/net/minecraft/world/level/block/ScaffoldingBlock.java +@@ -103,7 +103,7 @@ public class ScaffoldingBlock extends Block implements SimpleWaterloggedBlock { + int i = ScaffoldingBlock.getDistance(world, pos); + BlockState iblockdata1 = (BlockState) ((BlockState) state.setValue(ScaffoldingBlock.DISTANCE, i)).setValue(ScaffoldingBlock.BOTTOM, this.isBottom(world, pos, i)); + +- if ((Integer) iblockdata1.getValue(ScaffoldingBlock.DISTANCE) == 7 && !org.bukkit.craftbukkit.event.CraftEventFactory.callBlockFadeEvent(world, pos, Blocks.AIR.defaultBlockState()).isCancelled()) { // CraftBukkit - BlockFadeEvent ++ if ((Integer) iblockdata1.getValue(ScaffoldingBlock.DISTANCE) == 7 && !org.bukkit.craftbukkit.event.CraftEventFactory.callBlockFadeEvent(world, pos, iblockdata1.getFluidState().createLegacyBlock()).isCancelled()) { // CraftBukkit - BlockFadeEvent // Paper - fix wrong block state + if ((Integer) state.getValue(ScaffoldingBlock.DISTANCE) == 7) { + FallingBlockEntity.fall(world, pos, iblockdata1); + } else { +diff --git a/src/main/java/net/minecraft/world/level/block/SnifferEggBlock.java b/src/main/java/net/minecraft/world/level/block/SnifferEggBlock.java +index 4f8224841865f956aaa969ab7f543c80b3c24319..2df28caefff893f319924ae5fd376c8aeb0a2158 100644 +--- a/src/main/java/net/minecraft/world/level/block/SnifferEggBlock.java ++++ b/src/main/java/net/minecraft/world/level/block/SnifferEggBlock.java +@@ -61,12 +61,26 @@ public class SnifferEggBlock extends Block { + return this.getHatchLevel(state) == 2; + } + ++ // Paper start - Call BlockFadeEvent ++ private void rescheduleTick(ServerLevel world, BlockPos pos) { ++ int baseDelay = hatchBoost(world, pos) ? BOOSTED_HATCH_TIME_TICKS : REGULAR_HATCH_TIME_TICKS; ++ world.scheduleTick(pos, this, (baseDelay / 3) + world.random.nextInt(RANDOM_HATCH_OFFSET_TICKS)); ++ // reschedule to avoid being stuck here and behave like the other calls (see #onPlace) ++ } ++ // Paper end - Call BlockFadeEvent ++ + @Override + public void tick(BlockState state, ServerLevel world, BlockPos pos, RandomSource random) { + if (!this.isReadyToHatch(state)) { + world.playSound(null, pos, SoundEvents.SNIFFER_EGG_CRACK, SoundSource.BLOCKS, 0.7F, 0.9F + random.nextFloat() * 0.2F); + world.setBlock(pos, state.setValue(HATCH, Integer.valueOf(this.getHatchLevel(state) + 1)), 2); + } else { ++ // Paper start - Call BlockFadeEvent ++ if (org.bukkit.craftbukkit.event.CraftEventFactory.callBlockFadeEvent(world, pos, state.getFluidState().createLegacyBlock()).isCancelled()) { ++ this.rescheduleTick(world, pos); ++ return; ++ } ++ // Paper end - Call BlockFadeEvent + world.playSound(null, pos, SoundEvents.SNIFFER_EGG_HATCH, SoundSource.BLOCKS, 0.7F, 0.9F + random.nextFloat() * 0.2F); + world.destroyBlock(pos, false); + Sniffer sniffer = EntityType.SNIFFER.create(world, EntitySpawnReason.BREEDING); diff --git a/patches/server/0721-More-Teleport-API.patch b/patches/server/0721-More-Teleport-API.patch deleted file mode 100644 index 3465cde97df5..000000000000 --- a/patches/server/0721-More-Teleport-API.patch +++ /dev/null @@ -1,269 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Owen1212055 <23108066+Owen1212055@users.noreply.github.com> -Date: Sun, 5 Sep 2021 12:15:59 -0400 -Subject: [PATCH] More Teleport API - - -diff --git a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java -index 4fa49daf58723827366f611e006e77448cc73a0b..f05297a2b349734b9700a0e977ee9b2bba030d65 100644 ---- a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java -+++ b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java -@@ -1565,11 +1565,17 @@ public class ServerGamePacketListenerImpl extends ServerCommonPacketListenerImpl - return true; // CraftBukkit - Return event status - } - -- PlayerTeleportEvent event = new PlayerTeleportEvent(player, from.clone(), to.clone(), cause); -+ // Paper start - Teleport API -+ Set relativeFlags = java.util.EnumSet.noneOf(io.papermc.paper.entity.TeleportFlag.Relative.class); -+ for (RelativeMovement relativeArgument : set) { -+ relativeFlags.add(org.bukkit.craftbukkit.entity.CraftPlayer.toApiRelativeFlag(relativeArgument)); -+ } -+ PlayerTeleportEvent event = new PlayerTeleportEvent(player, from.clone(), to.clone(), cause, java.util.Set.copyOf(relativeFlags)); -+ // Paper end - Teleport API - this.cserver.getPluginManager().callEvent(event); - - if (event.isCancelled() || !to.equals(event.getTo())) { -- set = Collections.emptySet(); // Can't relative teleport -+ // set = Collections.emptySet(); // Can't relative teleport // Paper - Teleport API; Now you can! - to = event.isCancelled() ? event.getFrom() : event.getTo(); - d0 = to.getX(); - d1 = to.getY(); -diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java -index 4c09f2529dd8eb7ac7d260d177f5292ff2339442..94051ae8ea93ab144f3767345b1cda0438d2afc6 100644 ---- a/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java -+++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java -@@ -221,15 +221,36 @@ public abstract class CraftEntity implements org.bukkit.entity.Entity { - - @Override - public boolean teleport(Location location, TeleportCause cause) { -+ // Paper start - Teleport passenger API -+ return teleport(location, cause, new io.papermc.paper.entity.TeleportFlag[0]); -+ } -+ -+ @Override -+ public boolean teleport(Location location, TeleportCause cause, io.papermc.paper.entity.TeleportFlag... flags) { -+ // Paper end - Preconditions.checkArgument(location != null, "location cannot be null"); - location.checkFinite(); -+ // Paper start - Teleport passenger API -+ Set flagSet = Set.of(flags); -+ boolean dismount = !flagSet.contains(io.papermc.paper.entity.TeleportFlag.EntityState.RETAIN_VEHICLE); -+ boolean ignorePassengers = flagSet.contains(io.papermc.paper.entity.TeleportFlag.EntityState.RETAIN_PASSENGERS); -+ // Don't allow teleporting between worlds while keeping passengers -+ if (flagSet.contains(io.papermc.paper.entity.TeleportFlag.EntityState.RETAIN_PASSENGERS) && this.entity.isVehicle() && location.getWorld() != this.getWorld()) { -+ return false; -+ } -+ -+ // Don't allow to teleport between worlds if remaining on vehicle -+ if (!dismount && this.entity.isPassenger() && location.getWorld() != this.getWorld()) { -+ return false; -+ } -+ // Paper end - -- if (this.entity.isVehicle() || this.entity.isRemoved()) { -+ if ((!ignorePassengers && this.entity.isVehicle()) || this.entity.isRemoved()) { // Paper - Teleport passenger API - return false; - } - - // If this entity is riding another entity, we must dismount before teleporting. -- this.entity.stopRiding(); -+ if (dismount) this.entity.stopRiding(); // Paper - Teleport passenger API - - // Let the server handle cross world teleports - if (location.getWorld() != null && !location.getWorld().equals(this.getWorld())) { -@@ -975,6 +996,39 @@ public abstract class CraftEntity implements org.bukkit.entity.Entity { - return CraftEntity.perm; - } - -+ // Paper start - more teleport API / async chunk API -+ @Override -+ public java.util.concurrent.CompletableFuture teleportAsync(final Location location, final TeleportCause cause, final io.papermc.paper.entity.TeleportFlag... teleportFlags) { -+ Preconditions.checkArgument(location != null, "location"); -+ location.checkFinite(); -+ Location locationClone = location.clone(); // clone so we don't need to worry about mutations after this call. -+ -+ net.minecraft.server.level.ServerLevel world = ((CraftWorld)locationClone.getWorld()).getHandle(); -+ java.util.concurrent.CompletableFuture ret = new java.util.concurrent.CompletableFuture<>(); -+ -+ world.loadChunksForMoveAsync(getHandle().getBoundingBoxAt(locationClone.getX(), locationClone.getY(), locationClone.getZ()), -+ this instanceof CraftPlayer ? ca.spottedleaf.concurrentutil.executor.standard.PrioritisedExecutor.Priority.HIGHER : ca.spottedleaf.concurrentutil.executor.standard.PrioritisedExecutor.Priority.NORMAL, (list) -> { -+ net.minecraft.server.level.ServerChunkCache chunkProviderServer = world.getChunkSource(); -+ for (net.minecraft.world.level.chunk.ChunkAccess chunk : list) { -+ chunkProviderServer.addTicketAtLevel(net.minecraft.server.level.TicketType.POST_TELEPORT, chunk.getPos(), 33, CraftEntity.this.getEntityId()); -+ } -+ net.minecraft.server.MinecraftServer.getServer().scheduleOnMain(() -> { -+ try { -+ ret.complete(CraftEntity.this.teleport(locationClone, cause, teleportFlags) ? Boolean.TRUE : Boolean.FALSE); -+ } catch (Throwable throwable) { -+ if (throwable instanceof ThreadDeath) { -+ throw (ThreadDeath)throwable; -+ } -+ net.minecraft.server.MinecraftServer.LOGGER.error("Failed to teleport entity " + CraftEntity.this, throwable); -+ ret.completeExceptionally(throwable); -+ } -+ }); -+ }); -+ -+ return ret; -+ } -+ // Paper end - more teleport API / async chunk API -+ - // Spigot start - private final org.bukkit.entity.Entity.Spigot spigot = new org.bukkit.entity.Entity.Spigot() - { -diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java -index ef289dba67db65a5e43104a4ca68bc03c8c5e4cb..7f47005c08cccbcb8cd5ac56157c4e0d0dfe10b0 100644 ---- a/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java -+++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java -@@ -1286,13 +1286,101 @@ public class CraftPlayer extends CraftHumanEntity implements Player { - - @Override - public void setRotation(float yaw, float pitch) { -- throw new UnsupportedOperationException("Cannot set rotation of players. Consider teleporting instead."); -+ // Paper start - Teleport API -+ Location targetLocation = this.getEyeLocation(); -+ targetLocation.setYaw(yaw); -+ targetLocation.setPitch(pitch); -+ -+ org.bukkit.util.Vector direction = targetLocation.getDirection(); -+ direction.multiply(9999999); // We need to move the target block.. FAR out -+ targetLocation.add(direction); -+ this.lookAt(targetLocation, io.papermc.paper.entity.LookAnchor.EYES); -+ // Paper end - } - - @Override - public boolean teleport(Location location, PlayerTeleportEvent.TeleportCause cause) { -+ // Paper start - Teleport API -+ return this.teleport(location, cause, new io.papermc.paper.entity.TeleportFlag[0]); -+ } -+ -+ @Override -+ public void lookAt(@NotNull org.bukkit.entity.Entity entity, @NotNull io.papermc.paper.entity.LookAnchor playerAnchor, @NotNull io.papermc.paper.entity.LookAnchor entityAnchor) { -+ this.getHandle().lookAt(toNmsAnchor(playerAnchor), ((CraftEntity) entity).getHandle(), toNmsAnchor(entityAnchor)); -+ } -+ -+ @Override -+ public void lookAt(double x, double y, double z, @NotNull io.papermc.paper.entity.LookAnchor playerAnchor) { -+ this.getHandle().lookAt(toNmsAnchor(playerAnchor), new net.minecraft.world.phys.Vec3(x, y, z)); -+ } -+ -+ public static net.minecraft.commands.arguments.EntityAnchorArgument.Anchor toNmsAnchor(io.papermc.paper.entity.LookAnchor nmsAnchor) { -+ return switch (nmsAnchor) { -+ case EYES -> net.minecraft.commands.arguments.EntityAnchorArgument.Anchor.EYES; -+ case FEET -> net.minecraft.commands.arguments.EntityAnchorArgument.Anchor.FEET; -+ }; -+ } -+ -+ public static io.papermc.paper.entity.LookAnchor toApiAnchor(net.minecraft.commands.arguments.EntityAnchorArgument.Anchor playerAnchor) { -+ return switch (playerAnchor) { -+ case EYES -> io.papermc.paper.entity.LookAnchor.EYES; -+ case FEET -> io.papermc.paper.entity.LookAnchor.FEET; -+ }; -+ } -+ -+ public static net.minecraft.world.entity.RelativeMovement toNmsRelativeFlag(io.papermc.paper.entity.TeleportFlag.Relative apiFlag) { -+ return switch (apiFlag) { -+ case X -> net.minecraft.world.entity.RelativeMovement.X; -+ case Y -> net.minecraft.world.entity.RelativeMovement.Y; -+ case Z -> net.minecraft.world.entity.RelativeMovement.Z; -+ case PITCH -> net.minecraft.world.entity.RelativeMovement.X_ROT; -+ case YAW -> net.minecraft.world.entity.RelativeMovement.Y_ROT; -+ }; -+ } -+ -+ public static io.papermc.paper.entity.TeleportFlag.Relative toApiRelativeFlag(net.minecraft.world.entity.RelativeMovement nmsFlag) { -+ return switch (nmsFlag) { -+ case X -> io.papermc.paper.entity.TeleportFlag.Relative.X; -+ case Y -> io.papermc.paper.entity.TeleportFlag.Relative.Y; -+ case Z -> io.papermc.paper.entity.TeleportFlag.Relative.Z; -+ case X_ROT -> io.papermc.paper.entity.TeleportFlag.Relative.PITCH; -+ case Y_ROT -> io.papermc.paper.entity.TeleportFlag.Relative.YAW; -+ }; -+ } -+ -+ @Override -+ public boolean teleport(Location location, org.bukkit.event.player.PlayerTeleportEvent.TeleportCause cause, io.papermc.paper.entity.TeleportFlag... flags) { -+ Set relativeArguments; -+ Set allFlags; -+ if (flags.length == 0) { -+ relativeArguments = Set.of(); -+ allFlags = Set.of(); -+ } else { -+ relativeArguments = java.util.EnumSet.noneOf(io.papermc.paper.entity.TeleportFlag.Relative.class); -+ allFlags = new HashSet<>(); -+ for (io.papermc.paper.entity.TeleportFlag flag : flags) { -+ if (flag instanceof final io.papermc.paper.entity.TeleportFlag.Relative relativeTeleportFlag) { -+ relativeArguments.add(relativeTeleportFlag); -+ } -+ allFlags.add(flag); -+ } -+ } -+ boolean dismount = !allFlags.contains(io.papermc.paper.entity.TeleportFlag.EntityState.RETAIN_VEHICLE); -+ boolean ignorePassengers = allFlags.contains(io.papermc.paper.entity.TeleportFlag.EntityState.RETAIN_PASSENGERS); -+ // Paper end - Teleport API - Preconditions.checkArgument(location != null, "location"); - Preconditions.checkArgument(location.getWorld() != null, "location.world"); -+ // Paper start - Teleport passenger API -+ // Don't allow teleporting between worlds while keeping passengers -+ if (ignorePassengers && entity.isVehicle() && location.getWorld() != this.getWorld()) { -+ return false; -+ } -+ -+ // Don't allow to teleport between worlds if remaining on vehicle -+ if (!dismount && entity.isPassenger() && location.getWorld() != this.getWorld()) { -+ return false; -+ } -+ // Paper end - location.checkFinite(); - - ServerPlayer entity = this.getHandle(); -@@ -1305,7 +1393,7 @@ public class CraftPlayer extends CraftHumanEntity implements Player { - return false; - } - -- if (entity.isVehicle()) { -+ if (entity.isVehicle() && !ignorePassengers) { // Paper - Teleport API - return false; - } - -@@ -1314,7 +1402,7 @@ public class CraftPlayer extends CraftHumanEntity implements Player { - // To = Players new Location if Teleport is Successful - Location to = location; - // Create & Call the Teleport Event. -- PlayerTeleportEvent event = new PlayerTeleportEvent(this, from, to, cause); -+ PlayerTeleportEvent event = new PlayerTeleportEvent(this, from, to, cause, Set.copyOf(relativeArguments)); // Paper - Teleport API - this.server.getPluginManager().callEvent(event); - - // Return False to inform the Plugin that the Teleport was unsuccessful/cancelled. -@@ -1323,7 +1411,7 @@ public class CraftPlayer extends CraftHumanEntity implements Player { - } - - // If this player is riding another entity, we must dismount before teleporting. -- entity.stopRiding(); -+ if (dismount) entity.stopRiding(); // Paper - Teleport API - - // SPIGOT-5509: Wakeup, similar to riding - if (this.isSleeping()) { -@@ -1339,13 +1427,19 @@ public class CraftPlayer extends CraftHumanEntity implements Player { - ServerLevel toWorld = ((CraftWorld) to.getWorld()).getHandle(); - - // Close any foreign inventory -- if (this.getHandle().containerMenu != this.getHandle().inventoryMenu) { -+ if (this.getHandle().containerMenu != this.getHandle().inventoryMenu && !allFlags.contains(io.papermc.paper.entity.TeleportFlag.EntityState.RETAIN_OPEN_INVENTORY)) { // Paper - this.getHandle().closeContainer(org.bukkit.event.inventory.InventoryCloseEvent.Reason.TELEPORT); // Paper - Inventory close reason - } - - // Check if the fromWorld and toWorld are the same. - if (fromWorld == toWorld) { -- entity.connection.teleport(to); -+ // Paper start - Teleport API -+ final Set nms = java.util.EnumSet.noneOf(net.minecraft.world.entity.RelativeMovement.class); -+ for (final io.papermc.paper.entity.TeleportFlag.Relative bukkit : relativeArguments) { -+ nms.add(toNmsRelativeFlag(bukkit)); -+ } -+ entity.connection.internalTeleport(to.getX(), to.getY(), to.getZ(), to.getYaw(), to.getPitch(), nms); -+ // Paper end - Teleport API - } else { - entity.portalProcess = null; // SPIGOT-7785: there is no need to carry this over as it contains the old world/location and we might run into trouble if there is a portal in the same spot in both worlds - // The respawn reason should never be used if the passed location is non null. diff --git a/patches/server/0722-Add-EntityPortalReadyEvent.patch b/patches/server/0722-Add-EntityPortalReadyEvent.patch deleted file mode 100644 index 56705093a314..000000000000 --- a/patches/server/0722-Add-EntityPortalReadyEvent.patch +++ /dev/null @@ -1,25 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Jake Potrebic -Date: Wed, 12 May 2021 04:30:42 -0700 -Subject: [PATCH] Add EntityPortalReadyEvent - - -diff --git a/src/main/java/net/minecraft/world/level/block/NetherPortalBlock.java b/src/main/java/net/minecraft/world/level/block/NetherPortalBlock.java -index e2969b49494c55c3705312361ee8083b05ef569e..417a9ab28d247d5fbb3f1097fdeccab7ad2a793b 100644 ---- a/src/main/java/net/minecraft/world/level/block/NetherPortalBlock.java -+++ b/src/main/java/net/minecraft/world/level/block/NetherPortalBlock.java -@@ -133,6 +133,14 @@ public class NetherPortalBlock extends Block implements Portal { - // CraftBukkit start - ResourceKey resourcekey = world.getTypeKey() == LevelStem.NETHER ? Level.OVERWORLD : Level.NETHER; - ServerLevel worldserver1 = world.getServer().getLevel(resourcekey); -+ // Paper start - Add EntityPortalReadyEvent -+ io.papermc.paper.event.entity.EntityPortalReadyEvent portalReadyEvent = new io.papermc.paper.event.entity.EntityPortalReadyEvent(entity.getBukkitEntity(), worldserver1 == null ? null : worldserver1.getWorld(), org.bukkit.PortalType.NETHER); -+ if (!portalReadyEvent.callEvent()) { -+ entity.portalProcess = null; -+ return null; -+ } -+ worldserver1 = portalReadyEvent.getTargetWorld() == null ? null : ((org.bukkit.craftbukkit.CraftWorld) portalReadyEvent.getTargetWorld()).getHandle(); -+ // Paper end - Add EntityPortalReadyEvent - - if (worldserver1 == null) { - return new DimensionTransition(PlayerTeleportEvent.TeleportCause.NETHER_PORTAL); // always fire event in case plugins wish to change it diff --git a/patches/server/0722-Collision-API.patch b/patches/server/0722-Collision-API.patch new file mode 100644 index 000000000000..6f4b6366a8dd --- /dev/null +++ b/patches/server/0722-Collision-API.patch @@ -0,0 +1,48 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Owen1212055 <23108066+Owen1212055@users.noreply.github.com> +Date: Wed, 6 Oct 2021 20:10:44 -0400 +Subject: [PATCH] Collision API + + +diff --git a/src/main/java/org/bukkit/craftbukkit/CraftRegionAccessor.java b/src/main/java/org/bukkit/craftbukkit/CraftRegionAccessor.java +index a7748f4b7c5a1630937c702b3fd5fded93793d64..a93a879117ee1eb06842242aa03f757a4a676946 100644 +--- a/src/main/java/org/bukkit/craftbukkit/CraftRegionAccessor.java ++++ b/src/main/java/org/bukkit/craftbukkit/CraftRegionAccessor.java +@@ -549,5 +549,12 @@ public abstract class CraftRegionAccessor implements RegionAccessor { + + return this.getHandle().clip(new net.minecraft.world.level.ClipContext(start, end, net.minecraft.world.level.ClipContext.Block.COLLIDER, net.minecraft.world.level.ClipContext.Fluid.NONE, net.minecraft.world.phys.shapes.CollisionContext.empty())).getType() == net.minecraft.world.phys.HitResult.Type.MISS; + } ++ ++ @Override ++ public boolean hasCollisionsIn(@org.jetbrains.annotations.NotNull org.bukkit.util.BoundingBox boundingBox) { ++ net.minecraft.world.phys.AABB aabb = new net.minecraft.world.phys.AABB(boundingBox.getMinX(), boundingBox.getMinY(), boundingBox.getMinZ(), boundingBox.getMaxX(), boundingBox.getMaxY(), boundingBox.getMaxZ()); ++ ++ return !this.getHandle().noCollision(aabb); ++ } + // Paper end + } +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java +index a314e401ee8a306dc12a2d98a3d400ae611a6e5a..01513cd747a0e18e3839e83bf5795aadb7d1e0e9 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java +@@ -1194,4 +1194,20 @@ public abstract class CraftEntity implements org.bukkit.entity.Entity { + return this.getHandle().noPhysics; + } + // Paper end - missing entity api ++ ++ // Paper start - Collision API ++ @Override ++ public boolean collidesAt(@org.jetbrains.annotations.NotNull Location location) { ++ net.minecraft.world.phys.AABB aabb = this.getHandle().getBoundingBoxAt(location.getX(), location.getY(), location.getZ()); ++ ++ return !this.getHandle().level().noCollision(this.getHandle(), aabb); ++ } ++ ++ @Override ++ public boolean wouldCollideUsing(@org.jetbrains.annotations.NotNull BoundingBox boundingBox) { ++ net.minecraft.world.phys.AABB aabb = new AABB(boundingBox.getMinX(), boundingBox.getMinY(), boundingBox.getMinZ(), boundingBox.getMaxX(), boundingBox.getMaxY(), boundingBox.getMaxZ()); ++ ++ return !this.getHandle().level().noCollision(this.getHandle(), aabb); ++ } ++ // Paper end - Collision API + } diff --git a/patches/server/0723-Don-t-use-level-random-in-entity-constructors.patch b/patches/server/0723-Don-t-use-level-random-in-entity-constructors.patch deleted file mode 100644 index ec5bc5264e56..000000000000 --- a/patches/server/0723-Don-t-use-level-random-in-entity-constructors.patch +++ /dev/null @@ -1,41 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Jake Potrebic -Date: Sun, 10 Jul 2022 14:13:22 -0700 -Subject: [PATCH] Don't use level random in entity constructors - -Paper makes the entity random thread-safe -and constructing an entity off the main thread -should be supported. Some entities (for whatever -reason) use the level's random in some places. - -diff --git a/src/main/java/net/minecraft/world/entity/item/ItemEntity.java b/src/main/java/net/minecraft/world/entity/item/ItemEntity.java -index df90d5b934f41f5d8c232e93830d6690b6ccf401..446c319524183d6a1b4d0e6f0613a8db690677da 100644 ---- a/src/main/java/net/minecraft/world/entity/item/ItemEntity.java -+++ b/src/main/java/net/minecraft/world/entity/item/ItemEntity.java -@@ -72,7 +72,12 @@ public class ItemEntity extends Entity implements TraceableEntity { - } - - public ItemEntity(Level world, double x, double y, double z, ItemStack stack) { -- this(world, x, y, z, stack, world.random.nextDouble() * 0.2D - 0.1D, 0.2D, world.random.nextDouble() * 0.2D - 0.1D); -+ // Paper start - Don't use level random in entity constructors (to make them thread-safe) -+ this(EntityType.ITEM, world); -+ this.setPos(x, y, z); -+ this.setDeltaMovement(this.random.nextDouble() * 0.2D - 0.1D, 0.2D, this.random.nextDouble() * 0.2D - 0.1D); -+ this.setItem(stack); -+ // Paper end - Don't use level random in entity constructors - } - - public ItemEntity(Level world, double x, double y, double z, ItemStack stack, double velocityX, double velocityY, double velocityZ) { -diff --git a/src/main/java/net/minecraft/world/entity/item/PrimedTnt.java b/src/main/java/net/minecraft/world/entity/item/PrimedTnt.java -index 47f2a480f6a4b15e55cedbfa8a58459d33516a97..42bd2d9a1528b6210e4dfb56233062fd97c9743b 100644 ---- a/src/main/java/net/minecraft/world/entity/item/PrimedTnt.java -+++ b/src/main/java/net/minecraft/world/entity/item/PrimedTnt.java -@@ -62,7 +62,7 @@ public class PrimedTnt extends Entity implements TraceableEntity { - public PrimedTnt(Level world, double x, double y, double z, @Nullable LivingEntity igniter) { - this(EntityType.TNT, world); - this.setPos(x, y, z); -- double d3 = world.random.nextDouble() * 6.2831854820251465D; -+ double d3 = this.random.nextDouble() * 6.2831854820251465D; // Paper - Don't use level random in entity constructors - - this.setDeltaMovement(-Math.sin(d3) * 0.02D, 0.20000000298023224D, -Math.cos(d3) * 0.02D); - this.setFuse(80); diff --git a/patches/server/0723-Fix-suggest-command-message-for-brigadier-syntax-exc.patch b/patches/server/0723-Fix-suggest-command-message-for-brigadier-syntax-exc.patch new file mode 100644 index 000000000000..34f5fa3e3d28 --- /dev/null +++ b/patches/server/0723-Fix-suggest-command-message-for-brigadier-syntax-exc.patch @@ -0,0 +1,20 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: chickeneer +Date: Mon, 1 Aug 2022 20:13:02 -0500 +Subject: [PATCH] Fix suggest command message for brigadier syntax exceptions + +This is a bug accidentally introduced in upstream CB + +diff --git a/src/main/java/net/minecraft/commands/Commands.java b/src/main/java/net/minecraft/commands/Commands.java +index bee79fab7f8195e14f6bd22d9cd59bfc704bf903..fd12046fab797fd845ad8521f94147480dfba5da 100644 +--- a/src/main/java/net/minecraft/commands/Commands.java ++++ b/src/main/java/net/minecraft/commands/Commands.java +@@ -391,7 +391,7 @@ public class Commands { + if (commandsyntaxexception.getInput() != null && commandsyntaxexception.getCursor() >= 0) { + int i = Math.min(commandsyntaxexception.getInput().length(), commandsyntaxexception.getCursor()); + MutableComponent ichatmutablecomponent = Component.empty().withStyle(ChatFormatting.GRAY).withStyle((chatmodifier) -> { +- return chatmodifier.withClickEvent(new ClickEvent(ClickEvent.Action.SUGGEST_COMMAND, label)); // CraftBukkit ++ return chatmodifier.withClickEvent(new ClickEvent(ClickEvent.Action.SUGGEST_COMMAND, "/" + label)); // CraftBukkit // Paper + }); + + if (i > 10) { diff --git a/patches/server/0724-Block-Ticking-API.patch b/patches/server/0724-Block-Ticking-API.patch new file mode 100644 index 000000000000..c2a5c23d6ce8 --- /dev/null +++ b/patches/server/0724-Block-Ticking-API.patch @@ -0,0 +1,63 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Owen1212055 <23108066+Owen1212055@users.noreply.github.com> +Date: Sun, 26 Dec 2021 13:23:46 -0500 +Subject: [PATCH] Block Ticking API + + +diff --git a/src/main/java/org/bukkit/craftbukkit/block/CraftBlock.java b/src/main/java/org/bukkit/craftbukkit/block/CraftBlock.java +index 68fcec085334383808b2117a49220f4d8239220b..7afb933782cac7e1ad621e80211c13066c680ad7 100644 +--- a/src/main/java/org/bukkit/craftbukkit/block/CraftBlock.java ++++ b/src/main/java/org/bukkit/craftbukkit/block/CraftBlock.java +@@ -78,6 +78,12 @@ public class CraftBlock implements Block { + return this.world.getBlockState(this.position); + } + ++ // Paper start ++ public net.minecraft.world.level.material.FluidState getNMSFluid() { ++ return this.world.getFluidState(this.position); ++ } ++ // Paper end ++ + public BlockPos getPosition() { + return this.position; + } +@@ -709,5 +715,23 @@ public class CraftBlock implements Block { + public boolean isValidTool(ItemStack itemStack) { + return getDrops(itemStack).size() != 0; + } ++ ++ @Override ++ public void tick() { ++ final ServerLevel level = this.world.getMinecraftWorld(); ++ this.getNMS().tick(level, this.position, level.random); ++ } ++ ++ ++ @Override ++ public void fluidTick() { ++ this.getNMSFluid().tick(this.world.getMinecraftWorld(), this.position, this.getNMS()); ++ } ++ ++ @Override ++ public void randomTick() { ++ final ServerLevel level = this.world.getMinecraftWorld(); ++ this.getNMS().randomTick(level, this.position, level.random); ++ } + // Paper end + } +diff --git a/src/main/java/org/bukkit/craftbukkit/block/data/CraftBlockData.java b/src/main/java/org/bukkit/craftbukkit/block/data/CraftBlockData.java +index a17c1d1651d4d36c40ef97c1cf0b1e0d61f53418..3ec64c995dcb59a758741e32b886925983a8be56 100644 +--- a/src/main/java/org/bukkit/craftbukkit/block/data/CraftBlockData.java ++++ b/src/main/java/org/bukkit/craftbukkit/block/data/CraftBlockData.java +@@ -756,4 +756,11 @@ public class CraftBlockData implements BlockData { + return speed; + } + // Paper end - destroy speed API ++ ++ // Paper start - Block tick API ++ @Override ++ public boolean isRandomlyTicked() { ++ return this.state.isRandomlyTicking(); ++ } ++ // Paper end - Block tick API + } diff --git a/patches/server/0724-Send-block-entities-after-destroy-prediction.patch b/patches/server/0724-Send-block-entities-after-destroy-prediction.patch deleted file mode 100644 index c039e444e14f..000000000000 --- a/patches/server/0724-Send-block-entities-after-destroy-prediction.patch +++ /dev/null @@ -1,91 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Owen1212055 <23108066+Owen1212055@users.noreply.github.com> -Date: Sat, 25 Jun 2022 19:45:20 -0400 -Subject: [PATCH] Send block entities after destroy prediction - -Minecraft's prediction system does not handle block entities, so if we are manually sending block entities during -block breaking we need to set it after the prediction is finished. This fixes block entities not showing when cancelling the BlockBreakEvent. - -diff --git a/src/main/java/net/minecraft/server/level/ServerPlayerGameMode.java b/src/main/java/net/minecraft/server/level/ServerPlayerGameMode.java -index 4d024956156aefde7df308642dfd0a40779e0633..6abecaac8407b992d208a9108e11fd4954a4106f 100644 ---- a/src/main/java/net/minecraft/server/level/ServerPlayerGameMode.java -+++ b/src/main/java/net/minecraft/server/level/ServerPlayerGameMode.java -@@ -63,6 +63,8 @@ public class ServerPlayerGameMode { - private BlockPos delayedDestroyPos; - private int delayedTickStart; - private int lastSentState; -+ public boolean captureSentBlockEntities = false; // Paper - Send block entities after destroy prediction -+ public boolean capturedBlockEntity = false; // Paper - Send block entities after destroy prediction - - public ServerPlayerGameMode(ServerPlayer player) { - this.gameModeForPlayer = GameType.DEFAULT_MODE; -@@ -193,10 +195,7 @@ public class ServerPlayerGameMode { - this.player.connection.send(new ClientboundBlockUpdatePacket(pos, this.level.getBlockState(pos))); - this.debugLogging(pos, false, sequence, "may not interact"); - // Update any tile entity data for this block -- BlockEntity tileentity = this.level.getBlockEntity(pos); -- if (tileentity != null) { -- this.player.connection.send(tileentity.getUpdatePacket()); -- } -+ capturedBlockEntity = true; // Paper - Send block entities after destroy prediction - // CraftBukkit end - return; - } -@@ -207,10 +206,7 @@ public class ServerPlayerGameMode { - // Let the client know the block still exists - this.player.connection.send(new ClientboundBlockUpdatePacket(this.level, pos)); - // Update any tile entity data for this block -- BlockEntity tileentity = this.level.getBlockEntity(pos); -- if (tileentity != null) { -- this.player.connection.send(tileentity.getUpdatePacket()); -- } -+ capturedBlockEntity = true; // Paper - Send block entities after destroy prediction - return; - } - // CraftBukkit end -@@ -395,10 +391,12 @@ public class ServerPlayerGameMode { - } - - // Update any tile entity data for this block -+ if (!captureSentBlockEntities) { // Paper - Send block entities after destroy prediction - BlockEntity tileentity = this.level.getBlockEntity(pos); - if (tileentity != null) { - this.player.connection.send(tileentity.getUpdatePacket()); - } -+ } else {capturedBlockEntity = true;} // Paper - Send block entities after destroy prediction - return false; - } - } -diff --git a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java -index f05297a2b349734b9700a0e977ee9b2bba030d65..ca32e5aa6e77ca1bab886e7b6a778ec931ac4e4c 100644 ---- a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java -+++ b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java -@@ -1711,8 +1711,28 @@ public class ServerGamePacketListenerImpl extends ServerCommonPacketListenerImpl - return; - } - // Paper end - Don't allow digging into unloaded chunks -+ // Paper start - Send block entities after destroy prediction -+ this.player.gameMode.capturedBlockEntity = false; -+ this.player.gameMode.captureSentBlockEntities = true; -+ // Paper end - Send block entities after destroy prediction - this.player.gameMode.handleBlockBreakAction(blockposition, packetplayinblockdig_enumplayerdigtype, packet.getDirection(), this.player.level().getMaxBuildHeight(), packet.getSequence()); - this.player.connection.ackBlockChangesUpTo(packet.getSequence()); -+ // Paper start - Send block entities after destroy prediction -+ this.player.gameMode.captureSentBlockEntities = false; -+ // If a block entity was modified speedup the block change ack to avoid the block entity -+ // being overriden. -+ if (this.player.gameMode.capturedBlockEntity) { -+ // manually tick -+ this.send(new ClientboundBlockChangedAckPacket(this.ackBlockChangesUpTo)); -+ this.player.connection.ackBlockChangesUpTo = -1; -+ -+ this.player.gameMode.capturedBlockEntity = false; -+ BlockEntity tileentity = this.player.level().getBlockEntity(blockposition); -+ if (tileentity != null) { -+ this.player.connection.send(tileentity.getUpdatePacket()); -+ } -+ } -+ // Paper end - Send block entities after destroy prediction - return; - default: - throw new IllegalArgumentException("Invalid player action"); diff --git a/patches/server/0725-Add-Velocity-IP-Forwarding-Support.patch b/patches/server/0725-Add-Velocity-IP-Forwarding-Support.patch new file mode 100644 index 000000000000..d5eb9544e551 --- /dev/null +++ b/patches/server/0725-Add-Velocity-IP-Forwarding-Support.patch @@ -0,0 +1,242 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Andrew Steinborn +Date: Mon, 8 Oct 2018 14:36:14 -0400 +Subject: [PATCH] Add Velocity IP Forwarding Support + +While Velocity supports BungeeCord-style IP forwarding, it is not secure. Users +have a lot of problems setting up firewalls or setting up plugins like IPWhitelist. +Further, the BungeeCord IP forwarding protocol still retains essentially its original +form, when there is brand new support for custom login plugin messages in 1.13. + +Velocity's modern IP forwarding uses an HMAC-SHA256 code to ensure authenticity +of messages, is packed into a binary format that is smaller than BungeeCord's +forwarding, and is integrated into the Minecraft login process by using the 1.13 +login plugin message packet. + +diff --git a/src/main/java/com/destroystokyo/paper/proxy/VelocityProxy.java b/src/main/java/com/destroystokyo/paper/proxy/VelocityProxy.java +new file mode 100644 +index 0000000000000000000000000000000000000000..1b797955357612a4319452de7461ba044cbb88d6 +--- /dev/null ++++ b/src/main/java/com/destroystokyo/paper/proxy/VelocityProxy.java +@@ -0,0 +1,86 @@ ++package com.destroystokyo.paper.proxy; ++ ++import io.papermc.paper.configuration.GlobalConfiguration; ++import com.google.common.net.InetAddresses; ++import com.mojang.authlib.GameProfile; ++import com.mojang.authlib.properties.Property; ++import java.net.InetAddress; ++import java.security.InvalidKeyException; ++import java.security.MessageDigest; ++import java.security.NoSuchAlgorithmException; ++import java.util.UUID; ++import javax.crypto.Mac; ++import javax.crypto.spec.SecretKeySpec; ++import net.minecraft.network.FriendlyByteBuf; ++import net.minecraft.network.protocol.login.custom.CustomQueryPayload; ++import net.minecraft.resources.ResourceLocation; ++import net.minecraft.world.entity.player.ProfilePublicKey; ++ ++/** ++ * While Velocity supports BungeeCord-style IP forwarding, it is not secure. Users ++ * have a lot of problems setting up firewalls or setting up plugins like IPWhitelist. ++ * Further, the BungeeCord IP forwarding protocol still retains essentially its original ++ * form, when there is brand-new support for custom login plugin messages in 1.13. ++ *

    ++ * Velocity's modern IP forwarding uses an HMAC-SHA256 code to ensure authenticity ++ * of messages, is packed into a binary format that is smaller than BungeeCord's ++ * forwarding, and is integrated into the Minecraft login process by using the 1.13 ++ * login plugin message packet. ++ */ ++public class VelocityProxy { ++ private static final int SUPPORTED_FORWARDING_VERSION = 1; ++ public static final int MODERN_FORWARDING_WITH_KEY = 2; ++ public static final int MODERN_FORWARDING_WITH_KEY_V2 = 3; ++ public static final int MODERN_LAZY_SESSION = 4; ++ public static final byte MAX_SUPPORTED_FORWARDING_VERSION = MODERN_LAZY_SESSION; ++ public static final ResourceLocation PLAYER_INFO_CHANNEL = ResourceLocation.fromNamespaceAndPath("velocity", "player_info"); ++ ++ public static boolean checkIntegrity(final FriendlyByteBuf buf) { ++ final byte[] signature = new byte[32]; ++ buf.readBytes(signature); ++ ++ final byte[] data = new byte[buf.readableBytes()]; ++ buf.getBytes(buf.readerIndex(), data); ++ ++ try { ++ final Mac mac = Mac.getInstance("HmacSHA256"); ++ mac.init(new SecretKeySpec(GlobalConfiguration.get().proxies.velocity.secret.getBytes(java.nio.charset.StandardCharsets.UTF_8), "HmacSHA256")); ++ final byte[] mySignature = mac.doFinal(data); ++ if (!MessageDigest.isEqual(signature, mySignature)) { ++ return false; ++ } ++ } catch (final InvalidKeyException | NoSuchAlgorithmException e) { ++ throw new AssertionError(e); ++ } ++ ++ return true; ++ } ++ ++ public static InetAddress readAddress(final FriendlyByteBuf buf) { ++ return InetAddresses.forString(buf.readUtf(Short.MAX_VALUE)); ++ } ++ ++ public static GameProfile createProfile(final FriendlyByteBuf buf) { ++ final GameProfile profile = new GameProfile(buf.readUUID(), buf.readUtf(16)); ++ readProperties(buf, profile); ++ return profile; ++ } ++ ++ private static void readProperties(final FriendlyByteBuf buf, final GameProfile profile) { ++ final int properties = buf.readVarInt(); ++ for (int i1 = 0; i1 < properties; i1++) { ++ final String name = buf.readUtf(Short.MAX_VALUE); ++ final String value = buf.readUtf(Short.MAX_VALUE); ++ final String signature = buf.readBoolean() ? buf.readUtf(Short.MAX_VALUE) : null; ++ profile.getProperties().put(name, new Property(name, value, signature)); ++ } ++ } ++ ++ public static ProfilePublicKey.Data readForwardedKey(FriendlyByteBuf buf) { ++ return new ProfilePublicKey.Data(buf); ++ } ++ ++ public static UUID readSignerUuidOrElse(FriendlyByteBuf buf, UUID orElse) { ++ return buf.readBoolean() ? buf.readUUID() : orElse; ++ } ++} +diff --git a/src/main/java/net/minecraft/server/dedicated/DedicatedServer.java b/src/main/java/net/minecraft/server/dedicated/DedicatedServer.java +index ac7fc2497b860a143ef08498f5f477a373a29be7..4e47611f85f3db6a638d0fc6a0dd712b245cf229 100644 +--- a/src/main/java/net/minecraft/server/dedicated/DedicatedServer.java ++++ b/src/main/java/net/minecraft/server/dedicated/DedicatedServer.java +@@ -288,13 +288,20 @@ public class DedicatedServer extends MinecraftServer implements ServerInterface + this.server.enablePlugins(org.bukkit.plugin.PluginLoadOrder.STARTUP); + // CraftBukkit end + ++ // Paper start - Add Velocity IP Forwarding Support ++ boolean usingProxy = org.spigotmc.SpigotConfig.bungee || io.papermc.paper.configuration.GlobalConfiguration.get().proxies.velocity.enabled; ++ String proxyFlavor = (io.papermc.paper.configuration.GlobalConfiguration.get().proxies.velocity.enabled) ? "Velocity" : "BungeeCord"; ++ String proxyLink = (io.papermc.paper.configuration.GlobalConfiguration.get().proxies.velocity.enabled) ? "https://docs.papermc.io/velocity/security" : "http://www.spigotmc.org/wiki/firewall-guide/"; ++ // Paper end - Add Velocity IP Forwarding Support + if (!this.usesAuthentication()) { + DedicatedServer.LOGGER.warn("**** SERVER IS RUNNING IN OFFLINE/INSECURE MODE!"); + DedicatedServer.LOGGER.warn("The server will make no attempt to authenticate usernames. Beware."); + // Spigot start +- if (org.spigotmc.SpigotConfig.bungee) { +- DedicatedServer.LOGGER.warn("Whilst this makes it possible to use BungeeCord, unless access to your server is properly restricted, it also opens up the ability for hackers to connect with any username they choose."); +- DedicatedServer.LOGGER.warn("Please see http://www.spigotmc.org/wiki/firewall-guide/ for further information."); ++ // Paper start - Add Velocity IP Forwarding Support ++ if (usingProxy) { ++ DedicatedServer.LOGGER.warn("Whilst this makes it possible to use " + proxyFlavor + ", unless access to your server is properly restricted, it also opens up the ability for hackers to connect with any username they choose."); ++ DedicatedServer.LOGGER.warn("Please see " + proxyLink + " for further information."); ++ // Paper end - Add Velocity IP Forwarding Support + } else { + DedicatedServer.LOGGER.warn("While this makes the game possible to play without internet access, it also opens up the ability for hackers to connect with any username they choose."); + } +diff --git a/src/main/java/net/minecraft/server/network/ServerLoginPacketListenerImpl.java b/src/main/java/net/minecraft/server/network/ServerLoginPacketListenerImpl.java +index cb5dd77892283a1aaec45434fb99bb7f08ee5394..4a89b73d972f366e70f4d2bd96c6ee413593baba 100644 +--- a/src/main/java/net/minecraft/server/network/ServerLoginPacketListenerImpl.java ++++ b/src/main/java/net/minecraft/server/network/ServerLoginPacketListenerImpl.java +@@ -91,6 +91,7 @@ public class ServerLoginPacketListenerImpl implements ServerLoginPacketListener, + private final boolean transferred; + private ServerPlayer player; // CraftBukkit + public boolean iKnowThisMayNotBeTheBestIdeaButPleaseDisableUsernameValidation = false; // Paper - username validation overriding ++ private int velocityLoginMessageId = -1; // Paper - Add Velocity IP Forwarding Support + + public ServerLoginPacketListenerImpl(MinecraftServer server, Connection connection, boolean transferred) { + this.state = ServerLoginPacketListenerImpl.State.HELLO; +@@ -189,6 +190,16 @@ public class ServerLoginPacketListenerImpl implements ServerLoginPacketListener, + this.state = ServerLoginPacketListenerImpl.State.KEY; + this.connection.send(new ClientboundHelloPacket("", this.server.getKeyPair().getPublic().getEncoded(), this.challenge, true)); + } else { ++ // Paper start - Add Velocity IP Forwarding Support ++ if (io.papermc.paper.configuration.GlobalConfiguration.get().proxies.velocity.enabled) { ++ this.velocityLoginMessageId = java.util.concurrent.ThreadLocalRandom.current().nextInt(); ++ net.minecraft.network.FriendlyByteBuf buf = new net.minecraft.network.FriendlyByteBuf(io.netty.buffer.Unpooled.buffer()); ++ buf.writeByte(com.destroystokyo.paper.proxy.VelocityProxy.MAX_SUPPORTED_FORWARDING_VERSION); ++ net.minecraft.network.protocol.login.ClientboundCustomQueryPacket packet1 = new net.minecraft.network.protocol.login.ClientboundCustomQueryPacket(this.velocityLoginMessageId, new net.minecraft.network.protocol.login.ClientboundCustomQueryPacket.PlayerInfoChannelPayload(com.destroystokyo.paper.proxy.VelocityProxy.PLAYER_INFO_CHANNEL, buf)); ++ this.connection.send(packet1); ++ return; ++ } ++ // Paper end - Add Velocity IP Forwarding Support + // CraftBukkit start + // Paper start - Cache authenticator threads + authenticatorPool.execute(new Runnable() { +@@ -341,6 +352,12 @@ public class ServerLoginPacketListenerImpl implements ServerLoginPacketListener, + + // CraftBukkit start + private GameProfile callPlayerPreLoginEvents(GameProfile gameprofile) throws Exception { // Paper - Add more fields to AsyncPlayerPreLoginEvent ++ // Paper start - Add Velocity IP Forwarding Support ++ if (ServerLoginPacketListenerImpl.this.velocityLoginMessageId == -1 && io.papermc.paper.configuration.GlobalConfiguration.get().proxies.velocity.enabled) { ++ disconnect("This server requires you to connect with Velocity."); ++ return gameprofile; ++ } ++ // Paper end - Add Velocity IP Forwarding Support + String playerName = gameprofile.getName(); + java.net.InetAddress address = ((java.net.InetSocketAddress) this.connection.getRemoteAddress()).getAddress(); + java.util.UUID uniqueId = gameprofile.getId(); +@@ -386,6 +403,51 @@ public class ServerLoginPacketListenerImpl implements ServerLoginPacketListener, + + @Override + public void handleCustomQueryPacket(ServerboundCustomQueryAnswerPacket packet) { ++ // Paper start - Add Velocity IP Forwarding Support ++ if (io.papermc.paper.configuration.GlobalConfiguration.get().proxies.velocity.enabled && packet.transactionId() == this.velocityLoginMessageId) { ++ ServerboundCustomQueryAnswerPacket.QueryAnswerPayload payload = (ServerboundCustomQueryAnswerPacket.QueryAnswerPayload)packet.payload(); ++ if (payload == null) { ++ this.disconnect("This server requires you to connect with Velocity."); ++ return; ++ } ++ ++ net.minecraft.network.FriendlyByteBuf buf = payload.buffer; ++ ++ if (!com.destroystokyo.paper.proxy.VelocityProxy.checkIntegrity(buf)) { ++ this.disconnect("Unable to verify player details"); ++ return; ++ } ++ ++ int version = buf.readVarInt(); ++ if (version > com.destroystokyo.paper.proxy.VelocityProxy.MAX_SUPPORTED_FORWARDING_VERSION) { ++ throw new IllegalStateException("Unsupported forwarding version " + version + ", wanted upto " + com.destroystokyo.paper.proxy.VelocityProxy.MAX_SUPPORTED_FORWARDING_VERSION); ++ } ++ ++ java.net.SocketAddress listening = this.connection.getRemoteAddress(); ++ int port = 0; ++ if (listening instanceof java.net.InetSocketAddress) { ++ port = ((java.net.InetSocketAddress) listening).getPort(); ++ } ++ this.connection.address = new java.net.InetSocketAddress(com.destroystokyo.paper.proxy.VelocityProxy.readAddress(buf), port); ++ ++ this.authenticatedProfile = com.destroystokyo.paper.proxy.VelocityProxy.createProfile(buf); ++ ++ //TODO Update handling for lazy sessions, might not even have to do anything? ++ ++ // Proceed with login ++ authenticatorPool.execute(() -> { ++ try { ++ final GameProfile gameprofile = this.callPlayerPreLoginEvents(this.authenticatedProfile); ++ ServerLoginPacketListenerImpl.LOGGER.info("UUID of player {} is {}", gameprofile.getName(), gameprofile.getId()); ++ ServerLoginPacketListenerImpl.this.startClientVerification(gameprofile); ++ } catch (Exception ex) { ++ disconnect("Failed to verify username!"); ++ server.server.getLogger().log(java.util.logging.Level.WARNING, "Exception verifying " + this.authenticatedProfile.getName(), ex); ++ } ++ }); ++ return; ++ } ++ // Paper end - Add Velocity IP Forwarding Support + this.disconnect(ServerCommonPacketListenerImpl.DISCONNECT_UNEXPECTED_QUERY); + } + +diff --git a/src/main/java/org/bukkit/craftbukkit/CraftServer.java b/src/main/java/org/bukkit/craftbukkit/CraftServer.java +index 3deb58f3cd2e29b51944ac81cb3e0e52ec496242..b84dc11212aba4c06375cdbefa91c09d86a2b957 100644 +--- a/src/main/java/org/bukkit/craftbukkit/CraftServer.java ++++ b/src/main/java/org/bukkit/craftbukkit/CraftServer.java +@@ -848,7 +848,7 @@ public final class CraftServer implements Server { + @Override + public long getConnectionThrottle() { + // Spigot Start - Automatically set connection throttle for bungee configurations +- if (org.spigotmc.SpigotConfig.bungee) { ++ if (org.spigotmc.SpigotConfig.bungee || io.papermc.paper.configuration.GlobalConfiguration.get().proxies.velocity.enabled) { // Paper - Add Velocity IP Forwarding Support + return -1; + } else { + return this.configuration.getInt("settings.connection-throttle"); diff --git a/patches/server/0725-Warn-on-plugins-accessing-faraway-chunks.patch b/patches/server/0725-Warn-on-plugins-accessing-faraway-chunks.patch deleted file mode 100644 index 1b42817dd995..000000000000 --- a/patches/server/0725-Warn-on-plugins-accessing-faraway-chunks.patch +++ /dev/null @@ -1,96 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Owen1212055 <23108066+Owen1212055@users.noreply.github.com> -Date: Fri, 29 Jul 2022 12:35:19 -0400 -Subject: [PATCH] Warn on plugins accessing faraway chunks - - -diff --git a/src/main/java/net/minecraft/world/level/Level.java b/src/main/java/net/minecraft/world/level/Level.java -index f800975068135fb86b025254db6f88f406cf0b65..63beee706d820bba5fb93db0bf53b4a6ecd403a1 100644 ---- a/src/main/java/net/minecraft/world/level/Level.java -+++ b/src/main/java/net/minecraft/world/level/Level.java -@@ -339,7 +339,7 @@ public abstract class Level implements LevelAccessor, AutoCloseable { - } - - private static boolean isInWorldBoundsHorizontal(BlockPos pos) { -- return pos.getX() >= -30000000 && pos.getZ() >= -30000000 && pos.getX() < 30000000 && pos.getZ() < 30000000; -+ return pos.getX() >= -30000000 && pos.getZ() >= -30000000 && pos.getX() < 30000000 && pos.getZ() < 30000000; // Diff on change warnUnsafeChunk() - } - - private static boolean isOutsideSpawnableHeight(int y) { -diff --git a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java -index 20a954c8637c2cf9cc6c0c823e33a44f668cc4f6..58cd4ecd5582c01b836814f4151df2538b93143c 100644 ---- a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java -+++ b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java -@@ -305,9 +305,24 @@ public class CraftWorld extends CraftRegionAccessor implements World { - public boolean setSpawnLocation(int x, int y, int z) { - return this.setSpawnLocation(x, y, z, 0.0F); - } -+ // Paper start -+ private static void warnUnsafeChunk(String reason, int x, int z) { -+ // if any chunk coord is outside of 30 million blocks -+ if (x > 1875000 || z > 1875000 || x < -1875000 || z < -1875000) { -+ Plugin plugin = io.papermc.paper.util.StackWalkerUtil.getFirstPluginCaller(); -+ if (plugin != null) { -+ plugin.getLogger().warning("Plugin is %s at (%s, %s), this might cause issues.".formatted(reason, x, z)); -+ } -+ if (net.minecraft.server.MinecraftServer.getServer().isDebugging()) { -+ io.papermc.paper.util.TraceUtil.dumpTraceForThread("Dangerous chunk retrieval"); -+ } -+ } -+ } -+ // Paper end - - @Override - public Chunk getChunkAt(int x, int z) { -+ warnUnsafeChunk("getting a faraway chunk", x, z); // Paper - // Paper start - add ticket to hold chunk for a little while longer if plugin accesses it - net.minecraft.world.level.chunk.LevelChunk chunk = this.world.getChunkSource().getChunkAtIfLoadedImmediately(x, z); - if (chunk == null) { -@@ -413,6 +428,7 @@ public class CraftWorld extends CraftRegionAccessor implements World { - if (!unloadChunk0(x, z, false)) { - return false; - } -+ warnUnsafeChunk("regenerating a faraway chunk", x, z); // Paper - - final long chunkKey = ChunkCoordIntPair.pair(x, z); - world.getChunkProvider().unloadQueue.remove(chunkKey); -@@ -486,6 +502,7 @@ public class CraftWorld extends CraftRegionAccessor implements World { - @Override - public boolean loadChunk(int x, int z, boolean generate) { - org.spigotmc.AsyncCatcher.catchOp("chunk load"); // Spigot -+ warnUnsafeChunk("loading a faraway chunk", x, z); // Paper - ChunkAccess chunk = this.world.getChunkSource().getChunk(x, z, generate || isChunkGenerated(x, z) ? ChunkStatus.FULL : ChunkStatus.EMPTY, true); // Paper - - // If generate = false, but the chunk already exists, we will get this back. -@@ -518,6 +535,7 @@ public class CraftWorld extends CraftRegionAccessor implements World { - - @Override - public boolean addPluginChunkTicket(int x, int z, Plugin plugin) { -+ warnUnsafeChunk("adding a faraway chunk ticket", x, z); // Paper - Preconditions.checkArgument(plugin != null, "null plugin"); - Preconditions.checkArgument(plugin.isEnabled(), "plugin is not enabled"); - -@@ -618,6 +636,7 @@ public class CraftWorld extends CraftRegionAccessor implements World { - - @Override - public void setChunkForceLoaded(int x, int z, boolean forced) { -+ warnUnsafeChunk("forceloading a faraway chunk", x, z); // Paper - this.getHandle().setChunkForced(x, z, forced); - } - -@@ -948,6 +967,7 @@ public class CraftWorld extends CraftRegionAccessor implements World { - - @Override - public int getHighestBlockYAt(int x, int z, org.bukkit.HeightMap heightMap) { -+ warnUnsafeChunk("getting a faraway chunk", x >> 4, z >> 4); // Paper - // Transient load for this tick - return this.world.getChunk(x >> 4, z >> 4).getHeight(CraftHeightMap.toNMS(heightMap), x, z); - } -@@ -2345,6 +2365,7 @@ public class CraftWorld extends CraftRegionAccessor implements World { - // Spigot end - // Paper start - public java.util.concurrent.CompletableFuture getChunkAtAsync(int x, int z, boolean gen, boolean urgent) { -+ warnUnsafeChunk("getting a faraway chunk async", x, z); // Paper - if (Bukkit.isPrimaryThread()) { - net.minecraft.world.level.chunk.LevelChunk immediate = this.world.getChunkSource().getChunkAtIfLoadedImmediately(x, z); - if (immediate != null) { diff --git a/patches/server/0726-Add-NamespacedKey-biome-methods.patch b/patches/server/0726-Add-NamespacedKey-biome-methods.patch new file mode 100644 index 000000000000..015b2064d994 --- /dev/null +++ b/patches/server/0726-Add-NamespacedKey-biome-methods.patch @@ -0,0 +1,31 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Josh Roy <10731363+JRoy@users.noreply.github.com> +Date: Sun, 14 Aug 2022 12:23:11 -0400 +Subject: [PATCH] Add NamespacedKey biome methods + +Co-authored-by: Thonk <30448663+ExcessiveAmountsOfZombies@users.noreply.github.com> + +diff --git a/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java b/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java +index 1ab160c3d042be43df3bd19d095534b91c4c2f71..9f4124485dac3d029ec8247b64098042aa1a48d2 100644 +--- a/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java ++++ b/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java +@@ -587,6 +587,19 @@ public final class CraftMagicNumbers implements UnsafeValues { + var supplier = net.minecraft.world.entity.ai.attributes.DefaultAttributes.getSupplier((net.minecraft.world.entity.EntityType) net.minecraft.core.registries.BuiltInRegistries.ENTITY_TYPE.getValue(CraftNamespacedKey.toMinecraft(bukkitEntityKey))); + return new io.papermc.paper.attribute.UnmodifiableAttributeMap(supplier); + } ++ ++ @Override ++ public org.bukkit.NamespacedKey getBiomeKey(org.bukkit.RegionAccessor accessor, int x, int y, int z) { ++ org.bukkit.craftbukkit.CraftRegionAccessor cra = (org.bukkit.craftbukkit.CraftRegionAccessor) accessor; ++ return org.bukkit.craftbukkit.util.CraftNamespacedKey.fromMinecraft(cra.getHandle().registryAccess().lookupOrThrow(net.minecraft.core.registries.Registries.BIOME).getKey(cra.getHandle().getBiome(new net.minecraft.core.BlockPos(x, y, z)).value())); ++ } ++ ++ @Override ++ public void setBiomeKey(org.bukkit.RegionAccessor accessor, int x, int y, int z, org.bukkit.NamespacedKey biomeKey) { ++ org.bukkit.craftbukkit.CraftRegionAccessor cra = (org.bukkit.craftbukkit.CraftRegionAccessor) accessor; ++ net.minecraft.core.Holder biomeBase = cra.getHandle().registryAccess().lookupOrThrow(net.minecraft.core.registries.Registries.BIOME).getOrThrow(net.minecraft.resources.ResourceKey.create(net.minecraft.core.registries.Registries.BIOME, org.bukkit.craftbukkit.util.CraftNamespacedKey.toMinecraft(biomeKey))); ++ cra.setBiome(x, y, z, biomeBase); ++ } + // Paper end + + /** diff --git a/patches/server/0726-Custom-Chat-Completion-Suggestions-API.patch b/patches/server/0726-Custom-Chat-Completion-Suggestions-API.patch deleted file mode 100644 index 13ea62b5e66c..000000000000 --- a/patches/server/0726-Custom-Chat-Completion-Suggestions-API.patch +++ /dev/null @@ -1,35 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Owen1212055 <23108066+Owen1212055@users.noreply.github.com> -Date: Sat, 30 Jul 2022 11:23:05 -0400 -Subject: [PATCH] Custom Chat Completion Suggestions API - - -diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java -index 7f47005c08cccbcb8cd5ac56157c4e0d0dfe10b0..3374dcd3b1f22b6c1803b9580c4cea6552b6afb1 100644 ---- a/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java -+++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java -@@ -691,6 +691,24 @@ public class CraftPlayer extends CraftHumanEntity implements Player { - } - // Paper end - Add sendOpLevel API - -+ // Paper start - custom chat completions API -+ @Override -+ public void addAdditionalChatCompletions(@NotNull Collection completions) { -+ this.getHandle().connection.send(new net.minecraft.network.protocol.game.ClientboundCustomChatCompletionsPacket( -+ net.minecraft.network.protocol.game.ClientboundCustomChatCompletionsPacket.Action.ADD, -+ new ArrayList<>(completions) -+ )); -+ } -+ -+ @Override -+ public void removeAdditionalChatCompletions(@NotNull Collection completions) { -+ this.getHandle().connection.send(new net.minecraft.network.protocol.game.ClientboundCustomChatCompletionsPacket( -+ net.minecraft.network.protocol.game.ClientboundCustomChatCompletionsPacket.Action.REMOVE, -+ new ArrayList<>(completions) -+ )); -+ } -+ // Paper end - custom chat completions API -+ - @Override - public void setCompassTarget(Location loc) { - Preconditions.checkArgument(loc != null, "Location cannot be null"); diff --git a/patches/server/0727-Add-and-fix-missing-BlockFadeEvents.patch b/patches/server/0727-Add-and-fix-missing-BlockFadeEvents.patch deleted file mode 100644 index a889bba44742..000000000000 --- a/patches/server/0727-Add-and-fix-missing-BlockFadeEvents.patch +++ /dev/null @@ -1,71 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Owen1212055 <23108066+Owen1212055@users.noreply.github.com> -Date: Thu, 21 Jul 2022 12:07:54 -0400 -Subject: [PATCH] Add and fix missing BlockFadeEvents - -Beyond calling the BlockFadeEvent in more places, this patch also aims -to pass the proper replacement state to the event, specifically for -potentially waterlogged block states fading. - -Co-authored-by: Lulu13022002 <41980282+Lulu13022002@users.noreply.github.com> - -diff --git a/src/main/java/net/minecraft/world/level/block/FrogspawnBlock.java b/src/main/java/net/minecraft/world/level/block/FrogspawnBlock.java -index 669234bca9fa50548447f77dc5f314df8d9dd7c9..f2d4264743b6070f36adb66d00a3de0a72b86846 100644 ---- a/src/main/java/net/minecraft/world/level/block/FrogspawnBlock.java -+++ b/src/main/java/net/minecraft/world/level/block/FrogspawnBlock.java -@@ -92,6 +92,11 @@ public class FrogspawnBlock extends Block { - } - - private void hatchFrogspawn(ServerLevel world, BlockPos pos, RandomSource random) { -+ // Paper start - Call BlockFadeEvent -+ if (org.bukkit.craftbukkit.event.CraftEventFactory.callBlockFadeEvent(world, pos, Blocks.AIR.defaultBlockState()).isCancelled()) { -+ return; -+ } -+ // Paper end - Call BlockFadeEvent - this.destroyBlock(world, pos); - world.playSound(null, pos, SoundEvents.FROGSPAWN_HATCH, SoundSource.BLOCKS, 1.0F, 1.0F); - this.spawnTadpoles(world, pos, random); -diff --git a/src/main/java/net/minecraft/world/level/block/ScaffoldingBlock.java b/src/main/java/net/minecraft/world/level/block/ScaffoldingBlock.java -index 580c77eeaf88083f2aed2e46e6c57dc1fcf9564c..f4cccbc134b33758cd553a29b9f8e9967517500d 100644 ---- a/src/main/java/net/minecraft/world/level/block/ScaffoldingBlock.java -+++ b/src/main/java/net/minecraft/world/level/block/ScaffoldingBlock.java -@@ -103,7 +103,7 @@ public class ScaffoldingBlock extends Block implements SimpleWaterloggedBlock { - int i = ScaffoldingBlock.getDistance(world, pos); - BlockState iblockdata1 = (BlockState) ((BlockState) state.setValue(ScaffoldingBlock.DISTANCE, i)).setValue(ScaffoldingBlock.BOTTOM, this.isBottom(world, pos, i)); - -- if ((Integer) iblockdata1.getValue(ScaffoldingBlock.DISTANCE) == 7 && !org.bukkit.craftbukkit.event.CraftEventFactory.callBlockFadeEvent(world, pos, Blocks.AIR.defaultBlockState()).isCancelled()) { // CraftBukkit - BlockFadeEvent -+ if ((Integer) iblockdata1.getValue(ScaffoldingBlock.DISTANCE) == 7 && !org.bukkit.craftbukkit.event.CraftEventFactory.callBlockFadeEvent(world, pos, iblockdata1.getFluidState().createLegacyBlock()).isCancelled()) { // CraftBukkit - BlockFadeEvent // Paper - fix wrong block state - if ((Integer) state.getValue(ScaffoldingBlock.DISTANCE) == 7) { - FallingBlockEntity.fall(world, pos, iblockdata1); - } else { -diff --git a/src/main/java/net/minecraft/world/level/block/SnifferEggBlock.java b/src/main/java/net/minecraft/world/level/block/SnifferEggBlock.java -index 14a5cd54820243f4ca59857b0d67c1e86457d590..f53808e200bd83ab80954ec5c1e9c14250302be8 100644 ---- a/src/main/java/net/minecraft/world/level/block/SnifferEggBlock.java -+++ b/src/main/java/net/minecraft/world/level/block/SnifferEggBlock.java -@@ -60,12 +60,26 @@ public class SnifferEggBlock extends Block { - return this.getHatchLevel(state) == 2; - } - -+ // Paper start - Call BlockFadeEvent -+ private void rescheduleTick(ServerLevel world, BlockPos pos) { -+ int baseDelay = hatchBoost(world, pos) ? BOOSTED_HATCH_TIME_TICKS : REGULAR_HATCH_TIME_TICKS; -+ world.scheduleTick(pos, this, (baseDelay / 3) + world.random.nextInt(RANDOM_HATCH_OFFSET_TICKS)); -+ // reschedule to avoid being stuck here and behave like the other calls (see #onPlace) -+ } -+ // Paper end - Call BlockFadeEvent -+ - @Override - public void tick(BlockState state, ServerLevel world, BlockPos pos, RandomSource random) { - if (!this.isReadyToHatch(state)) { - world.playSound(null, pos, SoundEvents.SNIFFER_EGG_CRACK, SoundSource.BLOCKS, 0.7F, 0.9F + random.nextFloat() * 0.2F); - world.setBlock(pos, state.setValue(HATCH, Integer.valueOf(this.getHatchLevel(state) + 1)), 2); - } else { -+ // Paper start - Call BlockFadeEvent -+ if (org.bukkit.craftbukkit.event.CraftEventFactory.callBlockFadeEvent(world, pos, state.getFluidState().createLegacyBlock()).isCancelled()) { -+ this.rescheduleTick(world, pos); -+ return; -+ } -+ // Paper end - Call BlockFadeEvent - world.playSound(null, pos, SoundEvents.SNIFFER_EGG_HATCH, SoundSource.BLOCKS, 0.7F, 0.9F + random.nextFloat() * 0.2F); - world.destroyBlock(pos, false); - Sniffer sniffer = EntityType.SNIFFER.create(world); diff --git a/patches/server/0727-Fix-plugin-loggers-on-server-shutdown.patch b/patches/server/0727-Fix-plugin-loggers-on-server-shutdown.patch new file mode 100644 index 000000000000..7f6f4755d14f --- /dev/null +++ b/patches/server/0727-Fix-plugin-loggers-on-server-shutdown.patch @@ -0,0 +1,67 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Jos=C3=A9=20Miguel=20Moreno?= +Date: Sat, 5 Jun 2021 13:45:15 +0200 +Subject: [PATCH] Fix plugin loggers on server shutdown + + +diff --git a/src/main/java/io/papermc/paper/log/CustomLogManager.java b/src/main/java/io/papermc/paper/log/CustomLogManager.java +new file mode 100644 +index 0000000000000000000000000000000000000000..c1d3bac79bb8b4796c013ff4472f75dcd79602dc +--- /dev/null ++++ b/src/main/java/io/papermc/paper/log/CustomLogManager.java +@@ -0,0 +1,26 @@ ++package io.papermc.paper.log; ++ ++import java.util.logging.LogManager; ++ ++public class CustomLogManager extends LogManager { ++ private static CustomLogManager instance; ++ ++ public CustomLogManager() { ++ instance = this; ++ } ++ ++ @Override ++ public void reset() { ++ // Ignore calls to this method ++ } ++ ++ private void superReset() { ++ super.reset(); ++ } ++ ++ public static void forceReset() { ++ if (instance != null) { ++ instance.superReset(); ++ } ++ } ++} +diff --git a/src/main/java/net/minecraft/server/MinecraftServer.java b/src/main/java/net/minecraft/server/MinecraftServer.java +index aa9a4a0f35d0200777bdc83520ba82a970bfa900..cb40a2d855e7ac8c1c8b4ec679fd057a31f3fc6f 100644 +--- a/src/main/java/net/minecraft/server/MinecraftServer.java ++++ b/src/main/java/net/minecraft/server/MinecraftServer.java +@@ -1277,6 +1277,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop -Date: Wed, 6 Oct 2021 20:10:44 -0400 -Subject: [PATCH] Collision API - - -diff --git a/src/main/java/org/bukkit/craftbukkit/CraftRegionAccessor.java b/src/main/java/org/bukkit/craftbukkit/CraftRegionAccessor.java -index f6dd8665b9f6e2a8ff396deba8a021e695bedf7e..3c1937b43b6834ae0ffdd8d22ac32a776bc7fb64 100644 ---- a/src/main/java/org/bukkit/craftbukkit/CraftRegionAccessor.java -+++ b/src/main/java/org/bukkit/craftbukkit/CraftRegionAccessor.java -@@ -543,5 +543,12 @@ public abstract class CraftRegionAccessor implements RegionAccessor { - - return this.getHandle().clip(new net.minecraft.world.level.ClipContext(start, end, net.minecraft.world.level.ClipContext.Block.COLLIDER, net.minecraft.world.level.ClipContext.Fluid.NONE, net.minecraft.world.phys.shapes.CollisionContext.empty())).getType() == net.minecraft.world.phys.HitResult.Type.MISS; - } -+ -+ @Override -+ public boolean hasCollisionsIn(@org.jetbrains.annotations.NotNull org.bukkit.util.BoundingBox boundingBox) { -+ net.minecraft.world.phys.AABB aabb = new net.minecraft.world.phys.AABB(boundingBox.getMinX(), boundingBox.getMinY(), boundingBox.getMinZ(), boundingBox.getMaxX(), boundingBox.getMaxY(), boundingBox.getMaxZ()); -+ -+ return !this.getHandle().noCollision(aabb); -+ } - // Paper end - } -diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java -index 94051ae8ea93ab144f3767345b1cda0438d2afc6..f950102a324d07aeba260bfa82fe88728f2362e5 100644 ---- a/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java -+++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java -@@ -1193,4 +1193,20 @@ public abstract class CraftEntity implements org.bukkit.entity.Entity { - return this.getHandle().noPhysics; - } - // Paper end - missing entity api -+ -+ // Paper start - Collision API -+ @Override -+ public boolean collidesAt(@org.jetbrains.annotations.NotNull Location location) { -+ net.minecraft.world.phys.AABB aabb = this.getHandle().getBoundingBoxAt(location.getX(), location.getY(), location.getZ()); -+ -+ return !this.getHandle().level().noCollision(this.getHandle(), aabb); -+ } -+ -+ @Override -+ public boolean wouldCollideUsing(@org.jetbrains.annotations.NotNull BoundingBox boundingBox) { -+ net.minecraft.world.phys.AABB aabb = new AABB(boundingBox.getMinX(), boundingBox.getMinY(), boundingBox.getMinZ(), boundingBox.getMaxX(), boundingBox.getMaxY(), boundingBox.getMaxZ()); -+ -+ return !this.getHandle().level().noCollision(this.getHandle(), aabb); -+ } -+ // Paper end - Collision API - } diff --git a/patches/server/0728-Stop-large-look-changes-from-crashing-the-server.patch b/patches/server/0728-Stop-large-look-changes-from-crashing-the-server.patch new file mode 100644 index 000000000000..2c6afde2fa22 --- /dev/null +++ b/patches/server/0728-Stop-large-look-changes-from-crashing-the-server.patch @@ -0,0 +1,74 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: MWHunter +Date: Wed, 24 Aug 2022 09:54:11 -0400 +Subject: [PATCH] Stop large look changes from crashing the server + +Co-authored-by: Jaren Knodel + +diff --git a/src/main/java/net/minecraft/world/entity/LivingEntity.java b/src/main/java/net/minecraft/world/entity/LivingEntity.java +index 786ac2127bc743cf2a33776314a4b5c197f35538..b367f6e329f44801c6f0d34f447497093d86ae3b 100644 +--- a/src/main/java/net/minecraft/world/entity/LivingEntity.java ++++ b/src/main/java/net/minecraft/world/entity/LivingEntity.java +@@ -3270,37 +3270,15 @@ public abstract class LivingEntity extends Entity implements Attackable { + gameprofilerfiller.pop(); + gameprofilerfiller.push("rangeChecks"); + +- while (this.getYRot() - this.yRotO < -180.0F) { +- this.yRotO -= 360.0F; +- } +- +- while (this.getYRot() - this.yRotO >= 180.0F) { +- this.yRotO += 360.0F; +- } ++ // Paper start - stop large pitch and yaw changes from crashing the server ++ this.yRotO += Math.round((this.getYRot() - this.yRotO) / 360.0F) * 360.0F; + +- while (this.yBodyRot - this.yBodyRotO < -180.0F) { +- this.yBodyRotO -= 360.0F; +- } ++ this.yBodyRotO += Math.round((this.yBodyRot - this.yBodyRotO) / 360.0F) * 360.0F; + +- while (this.yBodyRot - this.yBodyRotO >= 180.0F) { +- this.yBodyRotO += 360.0F; +- } +- +- while (this.getXRot() - this.xRotO < -180.0F) { +- this.xRotO -= 360.0F; +- } ++ this.xRotO += Math.round((this.getXRot() - this.xRotO) / 360.0F) * 360.0F; + +- while (this.getXRot() - this.xRotO >= 180.0F) { +- this.xRotO += 360.0F; +- } +- +- while (this.yHeadRot - this.yHeadRotO < -180.0F) { +- this.yHeadRotO -= 360.0F; +- } +- +- while (this.yHeadRot - this.yHeadRotO >= 180.0F) { +- this.yHeadRotO += 360.0F; +- } ++ this.yHeadRotO += Math.round((this.yHeadRot - this.yHeadRotO) / 360.0F) * 360.0F; ++ // Paper end + + gameprofilerfiller.pop(); + this.animStep += f2; +diff --git a/src/main/java/net/minecraft/world/entity/projectile/Projectile.java b/src/main/java/net/minecraft/world/entity/projectile/Projectile.java +index 01684c903930b14a0df3a146176e2b476a374efb..bf7f81d99af300e89834981eab32568a3747d270 100644 +--- a/src/main/java/net/minecraft/world/entity/projectile/Projectile.java ++++ b/src/main/java/net/minecraft/world/entity/projectile/Projectile.java +@@ -428,13 +428,7 @@ public abstract class Projectile extends Entity implements TraceableEntity { + } + + protected static float lerpRotation(float prevRot, float newRot) { +- while (newRot - prevRot < -180.0F) { +- prevRot -= 360.0F; +- } +- +- while (newRot - prevRot >= 180.0F) { +- prevRot += 360.0F; +- } ++ prevRot += Math.round((newRot - prevRot) / 360.0F) * 360.0F; // Paper - stop large look changes from crashing the server + + return Mth.lerp(0.2F, prevRot, newRot); + } diff --git a/patches/server/0729-Fire-EntityChangeBlockEvent-in-more-places.patch b/patches/server/0729-Fire-EntityChangeBlockEvent-in-more-places.patch new file mode 100644 index 000000000000..1d46619357f1 --- /dev/null +++ b/patches/server/0729-Fire-EntityChangeBlockEvent-in-more-places.patch @@ -0,0 +1,344 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Jake Potrebic +Date: Mon, 9 Aug 2021 20:45:46 -0700 +Subject: [PATCH] Fire EntityChangeBlockEvent in more places + +Co-authored-by: ChristopheG <61288881+chrisgdt@users.noreply.github.com> +Co-authored-by: maxcom1 <46265094+maxcom1@users.noreply.github.com> + +diff --git a/src/main/java/net/minecraft/world/effect/WeavingMobEffect.java b/src/main/java/net/minecraft/world/effect/WeavingMobEffect.java +index e311f94918fb03e9d202cbae71b0909ea3219180..b751748196b458c8a89d512fdd9f9632d25e8be8 100644 +--- a/src/main/java/net/minecraft/world/effect/WeavingMobEffect.java ++++ b/src/main/java/net/minecraft/world/effect/WeavingMobEffect.java +@@ -25,11 +25,11 @@ class WeavingMobEffect extends MobEffect { + @Override + public void onMobRemoved(ServerLevel world, LivingEntity entity, int amplifier, Entity.RemovalReason reason) { + if (reason == Entity.RemovalReason.KILLED && (entity instanceof Player || world.getGameRules().getBoolean(GameRules.RULE_MOBGRIEFING))) { +- this.spawnCobwebsRandomlyAround(world, entity.getRandom(), entity.blockPosition()); ++ this.spawnCobwebsRandomlyAround(world, entity.getRandom(), entity.blockPosition(), entity); // Paper - Fire EntityChangeBlockEvent in more places + } + } + +- private void spawnCobwebsRandomlyAround(ServerLevel world, RandomSource random, BlockPos pos) { ++ private void spawnCobwebsRandomlyAround(ServerLevel world, RandomSource random, BlockPos pos, LivingEntity entity) { // Paper - Fire EntityChangeBlockEvent in more places + Set set = Sets.newHashSet(); + int i = this.maxCobwebs.applyAsInt(random); + +@@ -46,6 +46,7 @@ class WeavingMobEffect extends MobEffect { + } + + for (BlockPos blockPos3 : set) { ++ if (!org.bukkit.craftbukkit.event.CraftEventFactory.callEntityChangeBlockEvent(entity, blockPos3, Blocks.COBWEB.defaultBlockState())) continue; // Paper - Fire EntityChangeBlockEvent in more places + world.setBlock(blockPos3, Blocks.COBWEB.defaultBlockState(), 3); + world.levelEvent(3018, blockPos3, 0); + } +diff --git a/src/main/java/net/minecraft/world/entity/LightningBolt.java b/src/main/java/net/minecraft/world/entity/LightningBolt.java +index 2b09e9efad198ff596d88f97d703a8a00c108608..152ecd38814089333b8d61538297ce720756d2c3 100644 +--- a/src/main/java/net/minecraft/world/entity/LightningBolt.java ++++ b/src/main/java/net/minecraft/world/entity/LightningBolt.java +@@ -98,7 +98,7 @@ public class LightningBolt extends Entity { + } + + this.powerLightningRod(); +- LightningBolt.clearCopperOnLightningStrike(this.level(), this.getStrikePosition()); ++ LightningBolt.clearCopperOnLightningStrike(this.level(), this.getStrikePosition(), this); // Paper - Call EntityChangeBlockEvent + this.gameEvent(GameEvent.LIGHTNING_STRIKE); + } + } +@@ -202,7 +202,7 @@ public class LightningBolt extends Entity { + + } + +- private static void clearCopperOnLightningStrike(Level world, BlockPos pos) { ++ private static void clearCopperOnLightningStrike(Level world, BlockPos pos, Entity lightning) { // Paper - Call EntityChangeBlockEvent + BlockState iblockdata = world.getBlockState(pos); + BlockPos blockposition1; + BlockState iblockdata1; +@@ -216,24 +216,29 @@ public class LightningBolt extends Entity { + } + + if (iblockdata1.getBlock() instanceof WeatheringCopper) { +- world.setBlockAndUpdate(blockposition1, WeatheringCopper.getFirst(world.getBlockState(blockposition1))); ++ // Paper start - Call EntityChangeBlockEvent ++ BlockState newBlock = WeatheringCopper.getFirst(world.getBlockState(blockposition1)); ++ if (CraftEventFactory.callEntityChangeBlockEvent(lightning, blockposition1, newBlock)) { ++ world.setBlockAndUpdate(blockposition1, newBlock); ++ } ++ // Paper end - Call EntityChangeBlockEvent + BlockPos.MutableBlockPos blockposition_mutableblockposition = pos.mutable(); + int i = world.random.nextInt(3) + 3; + + for (int j = 0; j < i; ++j) { + int k = world.random.nextInt(8) + 1; + +- LightningBolt.randomWalkCleaningCopper(world, blockposition1, blockposition_mutableblockposition, k); ++ LightningBolt.randomWalkCleaningCopper(world, blockposition1, blockposition_mutableblockposition, k, lightning); // Paper - transmit LightningBolt instance to call EntityChangeBlockEvent + } + + } + } + +- private static void randomWalkCleaningCopper(Level world, BlockPos pos, BlockPos.MutableBlockPos mutablePos, int count) { ++ private static void randomWalkCleaningCopper(Level world, BlockPos pos, BlockPos.MutableBlockPos mutablePos, int count, Entity lightning) { // Paper - transmit LightningBolt instance to call EntityChangeBlockEvent + mutablePos.set(pos); + + for (int j = 0; j < count; ++j) { +- Optional optional = LightningBolt.randomStepCleaningCopper(world, mutablePos); ++ Optional optional = LightningBolt.randomStepCleaningCopper(world, mutablePos, lightning); // Paper - transmit LightningBolt instance to call EntityChangeBlockEvent + + if (optional.isEmpty()) { + break; +@@ -244,7 +249,7 @@ public class LightningBolt extends Entity { + + } + +- private static Optional randomStepCleaningCopper(Level world, BlockPos pos) { ++ private static Optional randomStepCleaningCopper(Level world, BlockPos pos, Entity lightning) { // Paper - transmit LightningBolt instance to call EntityChangeBlockEvent + Iterator iterator = BlockPos.randomInCube(world.random, 10, pos, 1).iterator(); + + BlockPos blockposition1; +@@ -261,6 +266,7 @@ public class LightningBolt extends Entity { + + BlockPos blockposition1Final = blockposition1; // CraftBukkit - decompile error + WeatheringCopper.getPrevious(iblockdata).ifPresent((iblockdata1) -> { ++ if (CraftEventFactory.callEntityChangeBlockEvent(lightning, blockposition1Final, iblockdata1)) // Paper - call EntityChangeBlockEvent + world.setBlockAndUpdate(blockposition1Final, iblockdata1); // CraftBukkit - decompile error + }); + world.levelEvent(3002, blockposition1, -1); +diff --git a/src/main/java/net/minecraft/world/item/AxeItem.java b/src/main/java/net/minecraft/world/item/AxeItem.java +index 74c9966093377b67e31b50483c2f24b70734faf6..abff08f2d61014944235ffe2f5494a718a28cc10 100644 +--- a/src/main/java/net/minecraft/world/item/AxeItem.java ++++ b/src/main/java/net/minecraft/world/item/AxeItem.java +@@ -67,6 +67,11 @@ public class AxeItem extends DiggerItem { + return InteractionResult.PASS; + } else { + ItemStack itemStack = context.getItemInHand(); ++ // Paper start - EntityChangeBlockEvent ++ if (!org.bukkit.craftbukkit.event.CraftEventFactory.callEntityChangeBlockEvent(player, blockPos, optional.get())) { ++ return InteractionResult.PASS; ++ } ++ // Paper end + if (player instanceof ServerPlayer) { + CriteriaTriggers.ITEM_USED_ON_BLOCK.trigger((ServerPlayer)player, blockPos, itemStack); + } +diff --git a/src/main/java/net/minecraft/world/item/EnderEyeItem.java b/src/main/java/net/minecraft/world/item/EnderEyeItem.java +index 08cbf02bba3633a84cce90c202d13f2beb5b88a2..c71a426c47e0ebc57ecb8c9c1d171737a084ccab 100644 +--- a/src/main/java/net/minecraft/world/item/EnderEyeItem.java ++++ b/src/main/java/net/minecraft/world/item/EnderEyeItem.java +@@ -45,6 +45,11 @@ public class EnderEyeItem extends Item { + return InteractionResult.SUCCESS; + } else { + BlockState iblockdata1 = (BlockState) iblockdata.setValue(EndPortalFrameBlock.HAS_EYE, true); ++ // Paper start ++ if (!org.bukkit.craftbukkit.event.CraftEventFactory.callEntityChangeBlockEvent(context.getPlayer(), blockposition, iblockdata1)) { ++ return InteractionResult.PASS; ++ } ++ // Paper end + + Block.pushEntitiesUp(iblockdata, iblockdata1, world, blockposition); + world.setBlock(blockposition, iblockdata1, 2); +diff --git a/src/main/java/net/minecraft/world/item/HoneycombItem.java b/src/main/java/net/minecraft/world/item/HoneycombItem.java +index 6c0fe41692c9d1fa50a4f421eb4735860a9ae0e9..d7924825823b2bf79ca3a26272de11ff8d2ec74c 100644 +--- a/src/main/java/net/minecraft/world/item/HoneycombItem.java ++++ b/src/main/java/net/minecraft/world/item/HoneycombItem.java +@@ -74,6 +74,14 @@ public class HoneycombItem extends Item implements SignApplicator { + return getWaxed(blockState).map(state -> { + Player player = context.getPlayer(); + ItemStack itemStack = context.getItemInHand(); ++ // Paper start - EntityChangeBlockEvent ++ if (!org.bukkit.craftbukkit.event.CraftEventFactory.callEntityChangeBlockEvent(player, blockPos, state)) { ++ if (!player.isCreative()) { ++ player.containerMenu.sendAllDataToRemote(); ++ } ++ return InteractionResult.PASS; ++ } ++ // Paper end + if (player instanceof ServerPlayer serverPlayer) { + CriteriaTriggers.ITEM_USED_ON_BLOCK.trigger(serverPlayer, blockPos, itemStack); + } +diff --git a/src/main/java/net/minecraft/world/item/PotionItem.java b/src/main/java/net/minecraft/world/item/PotionItem.java +index 1c8fd643c32226d72d51abc869a49e256dd2432b..f19bd2c25d3c84d9f16cad38ac5c32736f0f3a8d 100644 +--- a/src/main/java/net/minecraft/world/item/PotionItem.java ++++ b/src/main/java/net/minecraft/world/item/PotionItem.java +@@ -42,6 +42,12 @@ public class PotionItem extends Item { + PotionContents potionContents = itemStack.getOrDefault(DataComponents.POTION_CONTENTS, PotionContents.EMPTY); + BlockState blockState = level.getBlockState(blockPos); + if (context.getClickedFace() != Direction.DOWN && blockState.is(BlockTags.CONVERTABLE_TO_MUD) && potionContents.is(Potions.WATER)) { ++ // Paper start ++ if (!org.bukkit.craftbukkit.event.CraftEventFactory.callEntityChangeBlockEvent(player, blockPos, Blocks.MUD.defaultBlockState())) { ++ player.containerMenu.sendAllDataToRemote(); ++ return InteractionResult.PASS; ++ } ++ // Paper end + level.playSound(null, blockPos, SoundEvents.GENERIC_SPLASH, SoundSource.BLOCKS, 1.0F, 1.0F); + player.setItemInHand(context.getHand(), ItemUtils.createFilledResult(itemStack, player, new ItemStack(Items.GLASS_BOTTLE))); + player.awardStat(Stats.ITEM_USED.get(itemStack.getItem())); +diff --git a/src/main/java/net/minecraft/world/item/ShovelItem.java b/src/main/java/net/minecraft/world/item/ShovelItem.java +index c0377341227e8f4f1f7b1448580b3c7bc1b65f48..55c18f182166f4905d623d6f5e909eefd5ed2483 100644 +--- a/src/main/java/net/minecraft/world/item/ShovelItem.java ++++ b/src/main/java/net/minecraft/world/item/ShovelItem.java +@@ -46,20 +46,29 @@ public class ShovelItem extends DiggerItem { + Player player = context.getPlayer(); + BlockState blockState2 = FLATTENABLES.get(blockState.getBlock()); + BlockState blockState3 = null; ++ Runnable afterAction = null; // Paper + if (blockState2 != null && level.getBlockState(blockPos.above()).isAir()) { +- level.playSound(player, blockPos, SoundEvents.SHOVEL_FLATTEN, SoundSource.BLOCKS, 1.0F, 1.0F); ++ afterAction = () -> level.playSound(player, blockPos, SoundEvents.SHOVEL_FLATTEN, SoundSource.BLOCKS, 1.0F, 1.0F); // Paper + blockState3 = blockState2; + } else if (blockState.getBlock() instanceof CampfireBlock && blockState.getValue(CampfireBlock.LIT)) { ++ afterAction = () -> { // Paper + if (!level.isClientSide()) { + level.levelEvent(null, 1009, blockPos, 0); + } + + CampfireBlock.dowse(context.getPlayer(), level, blockPos, blockState); ++ }; // Paper + blockState3 = blockState.setValue(CampfireBlock.LIT, Boolean.valueOf(false)); + } + + if (blockState3 != null) { + if (!level.isClientSide) { ++ // Paper start ++ if (!org.bukkit.craftbukkit.event.CraftEventFactory.callEntityChangeBlockEvent(context.getPlayer(), blockPos, blockState3)) { ++ return InteractionResult.PASS; ++ } ++ afterAction.run(); ++ // Paper end + level.setBlock(blockPos, blockState3, 11); + level.gameEvent(GameEvent.BLOCK_CHANGE, blockPos, GameEvent.Context.of(player, blockState3)); + if (player != null) { +diff --git a/src/main/java/net/minecraft/world/level/block/CakeBlock.java b/src/main/java/net/minecraft/world/level/block/CakeBlock.java +index 7629b9212ded7155e94f67ca429f62271e5f5aa0..648c2510beb162e73aed236a3169d0bbb8fc5050 100644 +--- a/src/main/java/net/minecraft/world/level/block/CakeBlock.java ++++ b/src/main/java/net/minecraft/world/level/block/CakeBlock.java +@@ -66,6 +66,12 @@ public class CakeBlock extends Block { + if (block instanceof CandleBlock) { + CandleBlock candleblock = (CandleBlock) block; + ++ // Paper start - call change block event ++ if (!org.bukkit.craftbukkit.event.CraftEventFactory.callEntityChangeBlockEvent(player, pos, CandleCakeBlock.byCandle(candleblock))) { ++ player.containerMenu.sendAllDataToRemote(); // update inv because candle could decrease ++ return InteractionResult.TRY_WITH_EMPTY_HAND; ++ } ++ // Paper end - call change block event + stack.consume(1, player); + world.playSound((Player) null, pos, SoundEvents.CAKE_ADD_CANDLE, SoundSource.BLOCKS, 1.0F, 1.0F); + world.setBlockAndUpdate(pos, CandleCakeBlock.byCandle(candleblock)); +@@ -97,6 +103,14 @@ public class CakeBlock extends Block { + if (!player.canEat(false)) { + return InteractionResult.PASS; + } else { ++ // Paper start - call change block event ++ int i = state.getValue(CakeBlock.BITES); ++ final BlockState newState = i < MAX_BITES ? state.setValue(CakeBlock.BITES, i + 1) : world.getFluidState(pos).createLegacyBlock(); ++ if (!org.bukkit.craftbukkit.event.CraftEventFactory.callEntityChangeBlockEvent(player, pos, newState)) { ++ ((net.minecraft.server.level.ServerPlayer) player).getBukkitEntity().sendHealthUpdate(); ++ return InteractionResult.PASS; // return a non-consume result to cake blocks don't drop their candles ++ } ++ // Paper end - call change block event + player.awardStat(Stats.EAT_CAKE_SLICE); + // CraftBukkit start + // entityhuman.getFoodData().eat(2, 0.1F); +@@ -110,7 +124,7 @@ public class CakeBlock extends Block { + + ((net.minecraft.server.level.ServerPlayer) player).getBukkitEntity().sendHealthUpdate(); + // CraftBukkit end +- int i = (Integer) state.getValue(CakeBlock.BITES); ++ // Paper - move up + + world.gameEvent((Entity) player, (Holder) GameEvent.EAT, pos); + if (i < 6) { +diff --git a/src/main/java/net/minecraft/world/level/block/ComposterBlock.java b/src/main/java/net/minecraft/world/level/block/ComposterBlock.java +index 2bcd246a26dad560cf69682b29983d320ebf1884..05b2eab26e2dc8e143e9fff2dcec40cfe927a197 100644 +--- a/src/main/java/net/minecraft/world/level/block/ComposterBlock.java ++++ b/src/main/java/net/minecraft/world/level/block/ComposterBlock.java +@@ -243,6 +243,11 @@ public class ComposterBlock extends Block implements WorldlyContainerHolder { + if (i < 8 && ComposterBlock.COMPOSTABLES.containsKey(stack.getItem())) { + if (i < 7 && !world.isClientSide) { + BlockState iblockdata1 = ComposterBlock.addItem(player, state, world, pos, stack); ++ // Paper start - handle cancelled events ++ if (iblockdata1 == null) { ++ return InteractionResult.PASS; ++ } ++ // Paper end + + world.levelEvent(1500, pos, state != iblockdata1 ? 1 : 0); + player.awardStat(Stats.ITEM_USED.get(stack.getItem())); +@@ -273,11 +278,16 @@ public class ComposterBlock extends Block implements WorldlyContainerHolder { + if (i < 7 && ComposterBlock.COMPOSTABLES.containsKey(stack.getItem())) { + // CraftBukkit start + double rand = world.getRandom().nextDouble(); +- BlockState iblockdata1 = ComposterBlock.addItem(user, state, DummyGeneratorAccess.INSTANCE, pos, stack, rand); +- if (state == iblockdata1 || !org.bukkit.craftbukkit.event.CraftEventFactory.callEntityChangeBlockEvent(user, pos, iblockdata1)) { ++ BlockState iblockdata1 = null; // Paper ++ if (false && (state == iblockdata1 || !org.bukkit.craftbukkit.event.CraftEventFactory.callEntityChangeBlockEvent(user, pos, iblockdata1))) { // Paper - move event call into addItem + return state; + } + iblockdata1 = ComposterBlock.addItem(user, state, world, pos, stack, rand); ++ // Paper start - handle cancelled events ++ if (iblockdata1 == null) { ++ return state; ++ } ++ // Paper end + // CraftBukkit end + + stack.shrink(1); +@@ -318,11 +328,13 @@ public class ComposterBlock extends Block implements WorldlyContainerHolder { + return iblockdata1; + } + ++ @Nullable // Paper + static BlockState addItem(@Nullable Entity user, BlockState state, LevelAccessor world, BlockPos pos, ItemStack stack) { + // CraftBukkit start + return ComposterBlock.addItem(user, state, world, pos, stack, world.getRandom().nextDouble()); + } + ++ @Nullable // Paper - make it nullable + static BlockState addItem(@Nullable Entity entity, BlockState iblockdata, LevelAccessor generatoraccess, BlockPos blockposition, ItemStack itemstack, double rand) { + // CraftBukkit end + int i = (Integer) iblockdata.getValue(ComposterBlock.LEVEL); +@@ -333,6 +345,11 @@ public class ComposterBlock extends Block implements WorldlyContainerHolder { + } else { + int j = i + 1; + BlockState iblockdata1 = (BlockState) iblockdata.setValue(ComposterBlock.LEVEL, j); ++ // Paper start - move the EntityChangeBlockEvent here to avoid conflict later for the compost events ++ if (entity != null && !org.bukkit.craftbukkit.event.CraftEventFactory.callEntityChangeBlockEvent(entity, blockposition, iblockdata1)) { ++ return null; ++ } ++ // Paper end + + generatoraccess.setBlock(blockposition, iblockdata1, 3); + generatoraccess.gameEvent((Holder) GameEvent.BLOCK_CHANGE, blockposition, GameEvent.Context.of(entity, iblockdata1)); +diff --git a/src/main/java/net/minecraft/world/level/block/entity/BeehiveBlockEntity.java b/src/main/java/net/minecraft/world/level/block/entity/BeehiveBlockEntity.java +index 8224d5bddb7f7893dee09222a673af54791abcfa..f8828dc8334af19d7b2118e8cf34d94be5e09ee8 100644 +--- a/src/main/java/net/minecraft/world/level/block/entity/BeehiveBlockEntity.java ++++ b/src/main/java/net/minecraft/world/level/block/entity/BeehiveBlockEntity.java +@@ -260,7 +260,13 @@ public class BeehiveBlockEntity extends BlockEntity { + --j; + } + +- world.setBlockAndUpdate(blockposition, (BlockState) iblockdata.setValue(BeehiveBlock.HONEY_LEVEL, i + j)); ++ // Paper start - Fire EntityChangeBlockEvent in more places ++ BlockState newBlockState = iblockdata.setValue(BeehiveBlock.HONEY_LEVEL, i + j); ++ ++ if (org.bukkit.craftbukkit.event.CraftEventFactory.callEntityChangeBlockEvent(entitybee, blockposition, newBlockState)) { ++ world.setBlockAndUpdate(blockposition, newBlockState); ++ } ++ // Paper end - Fire EntityChangeBlockEvent in more places + } + } + } +diff --git a/src/main/java/org/bukkit/craftbukkit/util/DummyGeneratorAccess.java b/src/main/java/org/bukkit/craftbukkit/util/DummyGeneratorAccess.java +index daf3c26fce7569761951bfd5594c6726d854a9ff..e8a73d34dbb372581b03018aade170a31c266099 100644 +--- a/src/main/java/org/bukkit/craftbukkit/util/DummyGeneratorAccess.java ++++ b/src/main/java/org/bukkit/craftbukkit/util/DummyGeneratorAccess.java +@@ -120,7 +120,7 @@ public class DummyGeneratorAccess implements WorldGenLevel { + + @Override + public void gameEvent(Holder event, Vec3 emitterPos, GameEvent.Context emitter) { +- // Used by BlockComposter ++ // Used by ComposterBlock + } + + @Override diff --git a/patches/server/0729-Fix-suggest-command-message-for-brigadier-syntax-exc.patch b/patches/server/0729-Fix-suggest-command-message-for-brigadier-syntax-exc.patch deleted file mode 100644 index f661ad8722b9..000000000000 --- a/patches/server/0729-Fix-suggest-command-message-for-brigadier-syntax-exc.patch +++ /dev/null @@ -1,20 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: chickeneer -Date: Mon, 1 Aug 2022 20:13:02 -0500 -Subject: [PATCH] Fix suggest command message for brigadier syntax exceptions - -This is a bug accidentally introduced in upstream CB - -diff --git a/src/main/java/net/minecraft/commands/Commands.java b/src/main/java/net/minecraft/commands/Commands.java -index 64656e69dbeb6a1cf399ca143a2d7e0a1ee85957..96ca1af97c1f63776b88bc428874742a5114564f 100644 ---- a/src/main/java/net/minecraft/commands/Commands.java -+++ b/src/main/java/net/minecraft/commands/Commands.java -@@ -386,7 +386,7 @@ public class Commands { - if (commandsyntaxexception.getInput() != null && commandsyntaxexception.getCursor() >= 0) { - int i = Math.min(commandsyntaxexception.getInput().length(), commandsyntaxexception.getCursor()); - MutableComponent ichatmutablecomponent = Component.empty().withStyle(ChatFormatting.GRAY).withStyle((chatmodifier) -> { -- return chatmodifier.withClickEvent(new ClickEvent(ClickEvent.Action.SUGGEST_COMMAND, label)); // CraftBukkit -+ return chatmodifier.withClickEvent(new ClickEvent(ClickEvent.Action.SUGGEST_COMMAND, "/" + label)); // CraftBukkit // Paper - }); - - if (i > 10) { diff --git a/patches/server/0730-Block-Ticking-API.patch b/patches/server/0730-Block-Ticking-API.patch deleted file mode 100644 index 9e5247f35f95..000000000000 --- a/patches/server/0730-Block-Ticking-API.patch +++ /dev/null @@ -1,63 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Owen1212055 <23108066+Owen1212055@users.noreply.github.com> -Date: Sun, 26 Dec 2021 13:23:46 -0500 -Subject: [PATCH] Block Ticking API - - -diff --git a/src/main/java/org/bukkit/craftbukkit/block/CraftBlock.java b/src/main/java/org/bukkit/craftbukkit/block/CraftBlock.java -index 6d10396347b69d9243ab902ecc68ede93fa17b7d..af219df5267589300f0ad1d30fa5c81a1f50234f 100644 ---- a/src/main/java/org/bukkit/craftbukkit/block/CraftBlock.java -+++ b/src/main/java/org/bukkit/craftbukkit/block/CraftBlock.java -@@ -78,6 +78,12 @@ public class CraftBlock implements Block { - return this.world.getBlockState(this.position); - } - -+ // Paper start -+ public net.minecraft.world.level.material.FluidState getNMSFluid() { -+ return this.world.getFluidState(this.position); -+ } -+ // Paper end -+ - public BlockPos getPosition() { - return this.position; - } -@@ -709,5 +715,23 @@ public class CraftBlock implements Block { - public boolean isValidTool(ItemStack itemStack) { - return getDrops(itemStack).size() != 0; - } -+ -+ @Override -+ public void tick() { -+ final ServerLevel level = this.world.getMinecraftWorld(); -+ this.getNMS().tick(level, this.position, level.random); -+ } -+ -+ -+ @Override -+ public void fluidTick() { -+ this.getNMSFluid().tick(this.world.getMinecraftWorld(), this.position); -+ } -+ -+ @Override -+ public void randomTick() { -+ final ServerLevel level = this.world.getMinecraftWorld(); -+ this.getNMS().randomTick(level, this.position, level.random); -+ } - // Paper end - } -diff --git a/src/main/java/org/bukkit/craftbukkit/block/data/CraftBlockData.java b/src/main/java/org/bukkit/craftbukkit/block/data/CraftBlockData.java -index 33869e725c3b3f2120fa36ca468019a78321e862..64ddd6d1c40dc91b6e7fc3118403415bb4533d97 100644 ---- a/src/main/java/org/bukkit/craftbukkit/block/data/CraftBlockData.java -+++ b/src/main/java/org/bukkit/craftbukkit/block/data/CraftBlockData.java -@@ -752,4 +752,11 @@ public class CraftBlockData implements BlockData { - return speed; - } - // Paper end - destroy speed API -+ -+ // Paper start - Block tick API -+ @Override -+ public boolean isRandomlyTicked() { -+ return this.state.isRandomlyTicking(); -+ } -+ // Paper end - Block tick API - } diff --git a/patches/server/0730-Missing-eating-regain-reason.patch b/patches/server/0730-Missing-eating-regain-reason.patch new file mode 100644 index 000000000000..38626b635277 --- /dev/null +++ b/patches/server/0730-Missing-eating-regain-reason.patch @@ -0,0 +1,45 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Lulu13022002 <41980282+Lulu13022002@users.noreply.github.com> +Date: Fri, 5 Aug 2022 12:16:51 +0200 +Subject: [PATCH] Missing eating regain reason + + +diff --git a/src/main/java/net/minecraft/world/entity/animal/Cat.java b/src/main/java/net/minecraft/world/entity/animal/Cat.java +index bffc7c21727a6e5ff13a498aa51f6797e4d6d596..471d5727b964922d8e898be9e1d5c30f9d3bac97 100644 +--- a/src/main/java/net/minecraft/world/entity/animal/Cat.java ++++ b/src/main/java/net/minecraft/world/entity/animal/Cat.java +@@ -407,7 +407,7 @@ public class Cat extends TamableAnimal implements VariantHolder 0.0F) { +- this.heal(f); ++ this.heal(f, org.bukkit.event.entity.EntityRegainHealthEvent.RegainReason.EATING); // Paper - Add missing regain reason + flag = true; + } + diff --git a/patches/server/0731-Add-Velocity-IP-Forwarding-Support.patch b/patches/server/0731-Add-Velocity-IP-Forwarding-Support.patch deleted file mode 100644 index 72ee3dd6febe..000000000000 --- a/patches/server/0731-Add-Velocity-IP-Forwarding-Support.patch +++ /dev/null @@ -1,242 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Andrew Steinborn -Date: Mon, 8 Oct 2018 14:36:14 -0400 -Subject: [PATCH] Add Velocity IP Forwarding Support - -While Velocity supports BungeeCord-style IP forwarding, it is not secure. Users -have a lot of problems setting up firewalls or setting up plugins like IPWhitelist. -Further, the BungeeCord IP forwarding protocol still retains essentially its original -form, when there is brand new support for custom login plugin messages in 1.13. - -Velocity's modern IP forwarding uses an HMAC-SHA256 code to ensure authenticity -of messages, is packed into a binary format that is smaller than BungeeCord's -forwarding, and is integrated into the Minecraft login process by using the 1.13 -login plugin message packet. - -diff --git a/src/main/java/com/destroystokyo/paper/proxy/VelocityProxy.java b/src/main/java/com/destroystokyo/paper/proxy/VelocityProxy.java -new file mode 100644 -index 0000000000000000000000000000000000000000..1b797955357612a4319452de7461ba044cbb88d6 ---- /dev/null -+++ b/src/main/java/com/destroystokyo/paper/proxy/VelocityProxy.java -@@ -0,0 +1,86 @@ -+package com.destroystokyo.paper.proxy; -+ -+import io.papermc.paper.configuration.GlobalConfiguration; -+import com.google.common.net.InetAddresses; -+import com.mojang.authlib.GameProfile; -+import com.mojang.authlib.properties.Property; -+import java.net.InetAddress; -+import java.security.InvalidKeyException; -+import java.security.MessageDigest; -+import java.security.NoSuchAlgorithmException; -+import java.util.UUID; -+import javax.crypto.Mac; -+import javax.crypto.spec.SecretKeySpec; -+import net.minecraft.network.FriendlyByteBuf; -+import net.minecraft.network.protocol.login.custom.CustomQueryPayload; -+import net.minecraft.resources.ResourceLocation; -+import net.minecraft.world.entity.player.ProfilePublicKey; -+ -+/** -+ * While Velocity supports BungeeCord-style IP forwarding, it is not secure. Users -+ * have a lot of problems setting up firewalls or setting up plugins like IPWhitelist. -+ * Further, the BungeeCord IP forwarding protocol still retains essentially its original -+ * form, when there is brand-new support for custom login plugin messages in 1.13. -+ *

    -+ * Velocity's modern IP forwarding uses an HMAC-SHA256 code to ensure authenticity -+ * of messages, is packed into a binary format that is smaller than BungeeCord's -+ * forwarding, and is integrated into the Minecraft login process by using the 1.13 -+ * login plugin message packet. -+ */ -+public class VelocityProxy { -+ private static final int SUPPORTED_FORWARDING_VERSION = 1; -+ public static final int MODERN_FORWARDING_WITH_KEY = 2; -+ public static final int MODERN_FORWARDING_WITH_KEY_V2 = 3; -+ public static final int MODERN_LAZY_SESSION = 4; -+ public static final byte MAX_SUPPORTED_FORWARDING_VERSION = MODERN_LAZY_SESSION; -+ public static final ResourceLocation PLAYER_INFO_CHANNEL = ResourceLocation.fromNamespaceAndPath("velocity", "player_info"); -+ -+ public static boolean checkIntegrity(final FriendlyByteBuf buf) { -+ final byte[] signature = new byte[32]; -+ buf.readBytes(signature); -+ -+ final byte[] data = new byte[buf.readableBytes()]; -+ buf.getBytes(buf.readerIndex(), data); -+ -+ try { -+ final Mac mac = Mac.getInstance("HmacSHA256"); -+ mac.init(new SecretKeySpec(GlobalConfiguration.get().proxies.velocity.secret.getBytes(java.nio.charset.StandardCharsets.UTF_8), "HmacSHA256")); -+ final byte[] mySignature = mac.doFinal(data); -+ if (!MessageDigest.isEqual(signature, mySignature)) { -+ return false; -+ } -+ } catch (final InvalidKeyException | NoSuchAlgorithmException e) { -+ throw new AssertionError(e); -+ } -+ -+ return true; -+ } -+ -+ public static InetAddress readAddress(final FriendlyByteBuf buf) { -+ return InetAddresses.forString(buf.readUtf(Short.MAX_VALUE)); -+ } -+ -+ public static GameProfile createProfile(final FriendlyByteBuf buf) { -+ final GameProfile profile = new GameProfile(buf.readUUID(), buf.readUtf(16)); -+ readProperties(buf, profile); -+ return profile; -+ } -+ -+ private static void readProperties(final FriendlyByteBuf buf, final GameProfile profile) { -+ final int properties = buf.readVarInt(); -+ for (int i1 = 0; i1 < properties; i1++) { -+ final String name = buf.readUtf(Short.MAX_VALUE); -+ final String value = buf.readUtf(Short.MAX_VALUE); -+ final String signature = buf.readBoolean() ? buf.readUtf(Short.MAX_VALUE) : null; -+ profile.getProperties().put(name, new Property(name, value, signature)); -+ } -+ } -+ -+ public static ProfilePublicKey.Data readForwardedKey(FriendlyByteBuf buf) { -+ return new ProfilePublicKey.Data(buf); -+ } -+ -+ public static UUID readSignerUuidOrElse(FriendlyByteBuf buf, UUID orElse) { -+ return buf.readBoolean() ? buf.readUUID() : orElse; -+ } -+} -diff --git a/src/main/java/net/minecraft/server/dedicated/DedicatedServer.java b/src/main/java/net/minecraft/server/dedicated/DedicatedServer.java -index 585d3e51b4af87327fc2bc64a49f09732a8c61ab..aa39bdb0a4ba8fedf5052ea9700afa7d4d2a4300 100644 ---- a/src/main/java/net/minecraft/server/dedicated/DedicatedServer.java -+++ b/src/main/java/net/minecraft/server/dedicated/DedicatedServer.java -@@ -291,13 +291,20 @@ public class DedicatedServer extends MinecraftServer implements ServerInterface - this.server.enablePlugins(org.bukkit.plugin.PluginLoadOrder.STARTUP); - // CraftBukkit end - -+ // Paper start - Add Velocity IP Forwarding Support -+ boolean usingProxy = org.spigotmc.SpigotConfig.bungee || io.papermc.paper.configuration.GlobalConfiguration.get().proxies.velocity.enabled; -+ String proxyFlavor = (io.papermc.paper.configuration.GlobalConfiguration.get().proxies.velocity.enabled) ? "Velocity" : "BungeeCord"; -+ String proxyLink = (io.papermc.paper.configuration.GlobalConfiguration.get().proxies.velocity.enabled) ? "https://docs.papermc.io/velocity/security" : "http://www.spigotmc.org/wiki/firewall-guide/"; -+ // Paper end - Add Velocity IP Forwarding Support - if (!this.usesAuthentication()) { - DedicatedServer.LOGGER.warn("**** SERVER IS RUNNING IN OFFLINE/INSECURE MODE!"); - DedicatedServer.LOGGER.warn("The server will make no attempt to authenticate usernames. Beware."); - // Spigot start -- if (org.spigotmc.SpigotConfig.bungee) { -- DedicatedServer.LOGGER.warn("Whilst this makes it possible to use BungeeCord, unless access to your server is properly restricted, it also opens up the ability for hackers to connect with any username they choose."); -- DedicatedServer.LOGGER.warn("Please see http://www.spigotmc.org/wiki/firewall-guide/ for further information."); -+ // Paper start - Add Velocity IP Forwarding Support -+ if (usingProxy) { -+ DedicatedServer.LOGGER.warn("Whilst this makes it possible to use " + proxyFlavor + ", unless access to your server is properly restricted, it also opens up the ability for hackers to connect with any username they choose."); -+ DedicatedServer.LOGGER.warn("Please see " + proxyLink + " for further information."); -+ // Paper end - Add Velocity IP Forwarding Support - } else { - DedicatedServer.LOGGER.warn("While this makes the game possible to play without internet access, it also opens up the ability for hackers to connect with any username they choose."); - } -diff --git a/src/main/java/net/minecraft/server/network/ServerLoginPacketListenerImpl.java b/src/main/java/net/minecraft/server/network/ServerLoginPacketListenerImpl.java -index 9c343b579d9735dc59c8c74fde030d981a673c4f..35faa10f3b82504ae9d3f923fc04c5a99c1a624a 100644 ---- a/src/main/java/net/minecraft/server/network/ServerLoginPacketListenerImpl.java -+++ b/src/main/java/net/minecraft/server/network/ServerLoginPacketListenerImpl.java -@@ -91,6 +91,7 @@ public class ServerLoginPacketListenerImpl implements ServerLoginPacketListener, - private final boolean transferred; - private ServerPlayer player; // CraftBukkit - public boolean iKnowThisMayNotBeTheBestIdeaButPleaseDisableUsernameValidation = false; // Paper - username validation overriding -+ private int velocityLoginMessageId = -1; // Paper - Add Velocity IP Forwarding Support - - public ServerLoginPacketListenerImpl(MinecraftServer server, Connection connection, boolean transferred) { - this.state = ServerLoginPacketListenerImpl.State.HELLO; -@@ -189,6 +190,16 @@ public class ServerLoginPacketListenerImpl implements ServerLoginPacketListener, - this.state = ServerLoginPacketListenerImpl.State.KEY; - this.connection.send(new ClientboundHelloPacket("", this.server.getKeyPair().getPublic().getEncoded(), this.challenge, true)); - } else { -+ // Paper start - Add Velocity IP Forwarding Support -+ if (io.papermc.paper.configuration.GlobalConfiguration.get().proxies.velocity.enabled) { -+ this.velocityLoginMessageId = java.util.concurrent.ThreadLocalRandom.current().nextInt(); -+ net.minecraft.network.FriendlyByteBuf buf = new net.minecraft.network.FriendlyByteBuf(io.netty.buffer.Unpooled.buffer()); -+ buf.writeByte(com.destroystokyo.paper.proxy.VelocityProxy.MAX_SUPPORTED_FORWARDING_VERSION); -+ net.minecraft.network.protocol.login.ClientboundCustomQueryPacket packet1 = new net.minecraft.network.protocol.login.ClientboundCustomQueryPacket(this.velocityLoginMessageId, new net.minecraft.network.protocol.login.ClientboundCustomQueryPacket.PlayerInfoChannelPayload(com.destroystokyo.paper.proxy.VelocityProxy.PLAYER_INFO_CHANNEL, buf)); -+ this.connection.send(packet1); -+ return; -+ } -+ // Paper end - Add Velocity IP Forwarding Support - // CraftBukkit start - // Paper start - Cache authenticator threads - authenticatorPool.execute(new Runnable() { -@@ -341,6 +352,12 @@ public class ServerLoginPacketListenerImpl implements ServerLoginPacketListener, - - // CraftBukkit start - private GameProfile callPlayerPreLoginEvents(GameProfile gameprofile) throws Exception { // Paper - Add more fields to AsyncPlayerPreLoginEvent -+ // Paper start - Add Velocity IP Forwarding Support -+ if (ServerLoginPacketListenerImpl.this.velocityLoginMessageId == -1 && io.papermc.paper.configuration.GlobalConfiguration.get().proxies.velocity.enabled) { -+ disconnect("This server requires you to connect with Velocity."); -+ return gameprofile; -+ } -+ // Paper end - Add Velocity IP Forwarding Support - String playerName = gameprofile.getName(); - java.net.InetAddress address = ((java.net.InetSocketAddress) this.connection.getRemoteAddress()).getAddress(); - java.util.UUID uniqueId = gameprofile.getId(); -@@ -386,6 +403,51 @@ public class ServerLoginPacketListenerImpl implements ServerLoginPacketListener, - - @Override - public void handleCustomQueryPacket(ServerboundCustomQueryAnswerPacket packet) { -+ // Paper start - Add Velocity IP Forwarding Support -+ if (io.papermc.paper.configuration.GlobalConfiguration.get().proxies.velocity.enabled && packet.transactionId() == this.velocityLoginMessageId) { -+ ServerboundCustomQueryAnswerPacket.QueryAnswerPayload payload = (ServerboundCustomQueryAnswerPacket.QueryAnswerPayload)packet.payload(); -+ if (payload == null) { -+ this.disconnect("This server requires you to connect with Velocity."); -+ return; -+ } -+ -+ net.minecraft.network.FriendlyByteBuf buf = payload.buffer; -+ -+ if (!com.destroystokyo.paper.proxy.VelocityProxy.checkIntegrity(buf)) { -+ this.disconnect("Unable to verify player details"); -+ return; -+ } -+ -+ int version = buf.readVarInt(); -+ if (version > com.destroystokyo.paper.proxy.VelocityProxy.MAX_SUPPORTED_FORWARDING_VERSION) { -+ throw new IllegalStateException("Unsupported forwarding version " + version + ", wanted upto " + com.destroystokyo.paper.proxy.VelocityProxy.MAX_SUPPORTED_FORWARDING_VERSION); -+ } -+ -+ java.net.SocketAddress listening = this.connection.getRemoteAddress(); -+ int port = 0; -+ if (listening instanceof java.net.InetSocketAddress) { -+ port = ((java.net.InetSocketAddress) listening).getPort(); -+ } -+ this.connection.address = new java.net.InetSocketAddress(com.destroystokyo.paper.proxy.VelocityProxy.readAddress(buf), port); -+ -+ this.authenticatedProfile = com.destroystokyo.paper.proxy.VelocityProxy.createProfile(buf); -+ -+ //TODO Update handling for lazy sessions, might not even have to do anything? -+ -+ // Proceed with login -+ authenticatorPool.execute(() -> { -+ try { -+ final GameProfile gameprofile = this.callPlayerPreLoginEvents(this.authenticatedProfile); -+ ServerLoginPacketListenerImpl.LOGGER.info("UUID of player {} is {}", gameprofile.getName(), gameprofile.getId()); -+ ServerLoginPacketListenerImpl.this.startClientVerification(gameprofile); -+ } catch (Exception ex) { -+ disconnect("Failed to verify username!"); -+ server.server.getLogger().log(java.util.logging.Level.WARNING, "Exception verifying " + this.authenticatedProfile.getName(), ex); -+ } -+ }); -+ return; -+ } -+ // Paper end - Add Velocity IP Forwarding Support - this.disconnect(ServerCommonPacketListenerImpl.DISCONNECT_UNEXPECTED_QUERY); - } - -diff --git a/src/main/java/org/bukkit/craftbukkit/CraftServer.java b/src/main/java/org/bukkit/craftbukkit/CraftServer.java -index 3c5d44499e94bd19e4058eb2bf2d9b5d5890f980..96e7bad6620ff6dc17a80af3febcbc4e1623944e 100644 ---- a/src/main/java/org/bukkit/craftbukkit/CraftServer.java -+++ b/src/main/java/org/bukkit/craftbukkit/CraftServer.java -@@ -846,7 +846,7 @@ public final class CraftServer implements Server { - @Override - public long getConnectionThrottle() { - // Spigot Start - Automatically set connection throttle for bungee configurations -- if (org.spigotmc.SpigotConfig.bungee) { -+ if (org.spigotmc.SpigotConfig.bungee || io.papermc.paper.configuration.GlobalConfiguration.get().proxies.velocity.enabled) { // Paper - Add Velocity IP Forwarding Support - return -1; - } else { - return this.configuration.getInt("settings.connection-throttle"); diff --git a/patches/server/0731-Missing-effect-cause.patch b/patches/server/0731-Missing-effect-cause.patch new file mode 100644 index 000000000000..6157121022e5 --- /dev/null +++ b/patches/server/0731-Missing-effect-cause.patch @@ -0,0 +1,19 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Lulu13022002 <41980282+Lulu13022002@users.noreply.github.com> +Date: Tue, 16 Aug 2022 19:44:55 +0200 +Subject: [PATCH] Missing effect cause + + +diff --git a/src/main/java/net/minecraft/world/entity/animal/axolotl/Axolotl.java b/src/main/java/net/minecraft/world/entity/animal/axolotl/Axolotl.java +index 068d96cd0bceec79f14fbf3289eeb81533d1ba22..31b10cd404b672d7ce21c2107d8f83e32de26ef4 100644 +--- a/src/main/java/net/minecraft/world/entity/animal/axolotl/Axolotl.java ++++ b/src/main/java/net/minecraft/world/entity/animal/axolotl/Axolotl.java +@@ -424,7 +424,7 @@ public class Axolotl extends Animal implements VariantHolder, B + player.addEffect(new MobEffectInstance(MobEffects.REGENERATION, j, 0), this, org.bukkit.event.entity.EntityPotionEffectEvent.Cause.AXOLOTL); // CraftBukkit + } + +- player.removeEffect(MobEffects.DIG_SLOWDOWN); ++ player.removeEffect(MobEffects.DIG_SLOWDOWN, org.bukkit.event.entity.EntityPotionEffectEvent.Cause.AXOLOTL); // Paper - Add missing effect cause + } + + @Override diff --git a/patches/server/0732-Add-NamespacedKey-biome-methods.patch b/patches/server/0732-Add-NamespacedKey-biome-methods.patch deleted file mode 100644 index 1c393cb89c32..000000000000 --- a/patches/server/0732-Add-NamespacedKey-biome-methods.patch +++ /dev/null @@ -1,33 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Josh Roy <10731363+JRoy@users.noreply.github.com> -Date: Sun, 14 Aug 2022 12:23:11 -0400 -Subject: [PATCH] Add NamespacedKey biome methods - -Co-authored-by: Thonk <30448663+ExcessiveAmountsOfZombies@users.noreply.github.com> - -diff --git a/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java b/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java -index c10273445c4b5ef089f86fc08a944da69d708244..72c2f70d22c5ac920f13b11badac404dbb15c055 100644 ---- a/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java -+++ b/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java -@@ -584,6 +584,21 @@ public final class CraftMagicNumbers implements UnsafeValues { - } - // Paper end - -+ // Paper start - namespaced key biome methods -+ @Override -+ public org.bukkit.NamespacedKey getBiomeKey(org.bukkit.RegionAccessor accessor, int x, int y, int z) { -+ org.bukkit.craftbukkit.CraftRegionAccessor cra = (org.bukkit.craftbukkit.CraftRegionAccessor) accessor; -+ return org.bukkit.craftbukkit.util.CraftNamespacedKey.fromMinecraft(cra.getHandle().registryAccess().registryOrThrow(net.minecraft.core.registries.Registries.BIOME).getKey(cra.getHandle().getBiome(new net.minecraft.core.BlockPos(x, y, z)).value())); -+ } -+ -+ @Override -+ public void setBiomeKey(org.bukkit.RegionAccessor accessor, int x, int y, int z, org.bukkit.NamespacedKey biomeKey) { -+ org.bukkit.craftbukkit.CraftRegionAccessor cra = (org.bukkit.craftbukkit.CraftRegionAccessor) accessor; -+ net.minecraft.core.Holder biomeBase = cra.getHandle().registryAccess().registryOrThrow(net.minecraft.core.registries.Registries.BIOME).getHolderOrThrow(net.minecraft.resources.ResourceKey.create(net.minecraft.core.registries.Registries.BIOME, org.bukkit.craftbukkit.util.CraftNamespacedKey.toMinecraft(biomeKey))); -+ cra.setBiome(x, y, z, biomeBase); -+ } -+ // Paper end - namespaced key biome methods -+ - @Override - public String get(Class aClass, String s) { - if (aClass == Enchantment.class) { diff --git a/patches/server/0738-Added-byte-array-serialization-deserialization-for-P.patch b/patches/server/0732-Added-byte-array-serialization-deserialization-for-P.patch similarity index 100% rename from patches/server/0738-Added-byte-array-serialization-deserialization-for-P.patch rename to patches/server/0732-Added-byte-array-serialization-deserialization-for-P.patch diff --git a/patches/server/0733-Call-BlockPhysicsEvent-more-often.patch b/patches/server/0733-Call-BlockPhysicsEvent-more-often.patch new file mode 100644 index 000000000000..ad24091f87da --- /dev/null +++ b/patches/server/0733-Call-BlockPhysicsEvent-more-often.patch @@ -0,0 +1,32 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Lulu13022002 <41980282+Lulu13022002@users.noreply.github.com> +Date: Sun, 7 Aug 2022 22:16:36 +0200 +Subject: [PATCH] Call BlockPhysicsEvent more often + + +diff --git a/src/main/java/net/minecraft/world/level/redstone/CollectingNeighborUpdater.java b/src/main/java/net/minecraft/world/level/redstone/CollectingNeighborUpdater.java +index 5821c802ec880501df025fcd3fbbd98242ed952c..0587f4e5083a6c890a11642284a9c16fb2eb2fe1 100644 +--- a/src/main/java/net/minecraft/world/level/redstone/CollectingNeighborUpdater.java ++++ b/src/main/java/net/minecraft/world/level/redstone/CollectingNeighborUpdater.java +@@ -135,7 +135,20 @@ public class CollectingNeighborUpdater implements NeighborUpdater { + orientation = this.orientation.withFront(direction); + } + +- NeighborUpdater.executeUpdate(world, blockState, blockPos, this.sourceBlock, orientation, false); ++ // Paper start - Call BlockPhysicsEvent ++ try { ++ org.bukkit.event.block.BlockPhysicsEvent event = new org.bukkit.event.block.BlockPhysicsEvent( ++ org.bukkit.craftbukkit.block.CraftBlock.at(world, blockPos), ++ org.bukkit.craftbukkit.block.data.CraftBlockData.fromData(blockState), ++ org.bukkit.craftbukkit.block.CraftBlock.at(world, this.sourcePos)); ++ ++ if (event.callEvent()) { // continue to check for adjacent block (increase idx) ++ NeighborUpdater.executeUpdate(world, blockState, blockPos, this.sourceBlock, orientation, false); ++ } ++ } catch (StackOverflowError ex) { ++ world.lastPhysicsProblem = blockPos; ++ } ++ // Paper end - Call BlockPhysicsEvent + if (this.idx < NeighborUpdater.UPDATE_ORDER.length && NeighborUpdater.UPDATE_ORDER[this.idx] == this.skipDirection) { + this.idx++; + } diff --git a/patches/server/0733-Fix-plugin-loggers-on-server-shutdown.patch b/patches/server/0733-Fix-plugin-loggers-on-server-shutdown.patch deleted file mode 100644 index 4a610485e692..000000000000 --- a/patches/server/0733-Fix-plugin-loggers-on-server-shutdown.patch +++ /dev/null @@ -1,67 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: =?UTF-8?q?Jos=C3=A9=20Miguel=20Moreno?= -Date: Sat, 5 Jun 2021 13:45:15 +0200 -Subject: [PATCH] Fix plugin loggers on server shutdown - - -diff --git a/src/main/java/io/papermc/paper/log/CustomLogManager.java b/src/main/java/io/papermc/paper/log/CustomLogManager.java -new file mode 100644 -index 0000000000000000000000000000000000000000..c1d3bac79bb8b4796c013ff4472f75dcd79602dc ---- /dev/null -+++ b/src/main/java/io/papermc/paper/log/CustomLogManager.java -@@ -0,0 +1,26 @@ -+package io.papermc.paper.log; -+ -+import java.util.logging.LogManager; -+ -+public class CustomLogManager extends LogManager { -+ private static CustomLogManager instance; -+ -+ public CustomLogManager() { -+ instance = this; -+ } -+ -+ @Override -+ public void reset() { -+ // Ignore calls to this method -+ } -+ -+ private void superReset() { -+ super.reset(); -+ } -+ -+ public static void forceReset() { -+ if (instance != null) { -+ instance.superReset(); -+ } -+ } -+} -diff --git a/src/main/java/net/minecraft/server/MinecraftServer.java b/src/main/java/net/minecraft/server/MinecraftServer.java -index 11b9eb8d7d5f9ad736f2c6784c5d9e745a355093..bef05e09c654794405832ad75c3471f63399dfee 100644 ---- a/src/main/java/net/minecraft/server/MinecraftServer.java -+++ b/src/main/java/net/minecraft/server/MinecraftServer.java -@@ -1237,6 +1237,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop +Date: Sun, 18 Sep 2022 06:33:17 +0100 +Subject: [PATCH] Configurable chat thread limit + +By default, spigot shifts chat over to an unbounded thread pool, +on a normal server, this really offers no gains, the creation of a thread +on submitting to the pool on these servers eats more time vs just running it in +the netty pipeline, however, on servers using plugins which do work in here, there +could be some overall benefits to moving this stuff outside of the pipeline. + +In general, this patch does two things: +1) Exposes the core size for the pool, this allows for ensuring that a number of threads +sit around in the pool, mitigating the need for creating new threads; This IS however +caveated, the ThreadPoolExecutor will ONLY create core threads as they're needed, it +just won't allow for us to dip back under the # of core threads, this can potentially +be mitigated by calling prestartCoreThread, however, I'm not sure if there is much justification +for this +2) Exposes a max size for the pool, as stated, by default this is unbounded, for most +servers limiting the size of the pool is going to have 0 effects given how fast chat +is actually processed, this is honestly really just exposed for the misnomers or people +who just wanna ensure that this won't grow over a specific size if chat gets stupidly active + +diff --git a/src/main/java/io/papermc/paper/configuration/GlobalConfiguration.java b/src/main/java/io/papermc/paper/configuration/GlobalConfiguration.java +index c3fe4481dd35f80815716e48beeeb07b1f51e30b..56798215644d8bca1695856b3a941e8089f49e48 100644 +--- a/src/main/java/io/papermc/paper/configuration/GlobalConfiguration.java ++++ b/src/main/java/io/papermc/paper/configuration/GlobalConfiguration.java +@@ -302,7 +302,18 @@ public class GlobalConfiguration extends ConfigurationPart { + + @PostProcess + private void postProcess() { +- // TODO: fill in separate patch ++ //noinspection ConstantConditions ++ if (net.minecraft.server.MinecraftServer.getServer() == null) return; // In testing env, this will be null here ++ int _chatExecutorMaxSize = (this.chatExecutorMaxSize <= 0) ? Integer.MAX_VALUE : this.chatExecutorMaxSize; // This is somewhat dumb, but, this is the default, do we cap this?; ++ int _chatExecutorCoreSize = Math.max(this.chatExecutorCoreSize, 0); ++ ++ if (_chatExecutorMaxSize < _chatExecutorCoreSize) { ++ _chatExecutorMaxSize = _chatExecutorCoreSize; ++ } ++ ++ java.util.concurrent.ThreadPoolExecutor executor = (java.util.concurrent.ThreadPoolExecutor) net.minecraft.server.MinecraftServer.getServer().chatExecutor; ++ executor.setCorePoolSize(_chatExecutorCoreSize); ++ executor.setMaximumPoolSize(_chatExecutorMaxSize); + } + } + public int maxJoinsPerTick = 5; diff --git a/patches/server/0734-Stop-large-look-changes-from-crashing-the-server.patch b/patches/server/0734-Stop-large-look-changes-from-crashing-the-server.patch deleted file mode 100644 index 1346524b9191..000000000000 --- a/patches/server/0734-Stop-large-look-changes-from-crashing-the-server.patch +++ /dev/null @@ -1,74 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: MWHunter -Date: Wed, 24 Aug 2022 09:54:11 -0400 -Subject: [PATCH] Stop large look changes from crashing the server - -Co-authored-by: Jaren Knodel - -diff --git a/src/main/java/net/minecraft/world/entity/LivingEntity.java b/src/main/java/net/minecraft/world/entity/LivingEntity.java -index 5383d231748f2d30b2f2bf3ce07d3667e4d828e9..e6d20887572817099cb863515978d3f06926be3d 100644 ---- a/src/main/java/net/minecraft/world/entity/LivingEntity.java -+++ b/src/main/java/net/minecraft/world/entity/LivingEntity.java -@@ -3158,37 +3158,15 @@ public abstract class LivingEntity extends Entity implements Attackable { - this.level().getProfiler().pop(); - this.level().getProfiler().push("rangeChecks"); - -- while (this.getYRot() - this.yRotO < -180.0F) { -- this.yRotO -= 360.0F; -- } -- -- while (this.getYRot() - this.yRotO >= 180.0F) { -- this.yRotO += 360.0F; -- } -+ // Paper start - stop large pitch and yaw changes from crashing the server -+ this.yRotO += Math.round((this.getYRot() - this.yRotO) / 360.0F) * 360.0F; - -- while (this.yBodyRot - this.yBodyRotO < -180.0F) { -- this.yBodyRotO -= 360.0F; -- } -+ this.yBodyRotO += Math.round((this.yBodyRot - this.yBodyRotO) / 360.0F) * 360.0F; - -- while (this.yBodyRot - this.yBodyRotO >= 180.0F) { -- this.yBodyRotO += 360.0F; -- } -- -- while (this.getXRot() - this.xRotO < -180.0F) { -- this.xRotO -= 360.0F; -- } -+ this.xRotO += Math.round((this.getXRot() - this.xRotO) / 360.0F) * 360.0F; - -- while (this.getXRot() - this.xRotO >= 180.0F) { -- this.xRotO += 360.0F; -- } -- -- while (this.yHeadRot - this.yHeadRotO < -180.0F) { -- this.yHeadRotO -= 360.0F; -- } -- -- while (this.yHeadRot - this.yHeadRotO >= 180.0F) { -- this.yHeadRotO += 360.0F; -- } -+ this.yHeadRotO += Math.round((this.yHeadRot - this.yHeadRotO) / 360.0F) * 360.0F; -+ // Paper end - - this.level().getProfiler().pop(); - this.animStep += f2; -diff --git a/src/main/java/net/minecraft/world/entity/projectile/Projectile.java b/src/main/java/net/minecraft/world/entity/projectile/Projectile.java -index 06ca07edef062f21c51860146086297ca345104d..63ffa02f820d88f865ae604712edcf2ac13f0bff 100644 ---- a/src/main/java/net/minecraft/world/entity/projectile/Projectile.java -+++ b/src/main/java/net/minecraft/world/entity/projectile/Projectile.java -@@ -313,13 +313,7 @@ public abstract class Projectile extends Entity implements TraceableEntity { - } - - protected static float lerpRotation(float prevRot, float newRot) { -- while (newRot - prevRot < -180.0F) { -- prevRot -= 360.0F; -- } -- -- while (newRot - prevRot >= 180.0F) { -- prevRot += 360.0F; -- } -+ prevRot += Math.round((newRot - prevRot) / 360.0F) * 360.0F; // Paper - stop large look changes from crashing the server - - return Mth.lerp(0.2F, prevRot, newRot); - } diff --git a/patches/server/0735-Fire-EntityChangeBlockEvent-in-more-places.patch b/patches/server/0735-Fire-EntityChangeBlockEvent-in-more-places.patch deleted file mode 100644 index bafa064971a4..000000000000 --- a/patches/server/0735-Fire-EntityChangeBlockEvent-in-more-places.patch +++ /dev/null @@ -1,344 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Jake Potrebic -Date: Mon, 9 Aug 2021 20:45:46 -0700 -Subject: [PATCH] Fire EntityChangeBlockEvent in more places - -Co-authored-by: ChristopheG <61288881+chrisgdt@users.noreply.github.com> -Co-authored-by: maxcom1 <46265094+maxcom1@users.noreply.github.com> - -diff --git a/src/main/java/net/minecraft/world/effect/WeavingMobEffect.java b/src/main/java/net/minecraft/world/effect/WeavingMobEffect.java -index 6a84eaeddd0d7f050053c8aa0659d6811192aad4..1b2d41a6c6f149701c8a78f3d345f45069f1f857 100644 ---- a/src/main/java/net/minecraft/world/effect/WeavingMobEffect.java -+++ b/src/main/java/net/minecraft/world/effect/WeavingMobEffect.java -@@ -25,11 +25,11 @@ class WeavingMobEffect extends MobEffect { - @Override - public void onMobRemoved(LivingEntity entity, int amplifier, Entity.RemovalReason reason) { - if (reason == Entity.RemovalReason.KILLED && (entity instanceof Player || entity.level().getGameRules().getBoolean(GameRules.RULE_MOBGRIEFING))) { -- this.spawnCobwebsRandomlyAround(entity.level(), entity.getRandom(), entity.getOnPos()); -+ this.spawnCobwebsRandomlyAround(entity, entity.level(), entity.getRandom(), entity.getOnPos()); // Paper - Fire EntityChangeBlockEvent in more places - } - } - -- private void spawnCobwebsRandomlyAround(Level world, RandomSource random, BlockPos pos) { -+ private void spawnCobwebsRandomlyAround(LivingEntity entity, Level world, RandomSource random, BlockPos pos) { // Paper - Fire EntityChangeBlockEvent in more places - Set set = Sets.newHashSet(); - int i = this.maxCobwebs.applyAsInt(random); - -@@ -46,6 +46,7 @@ class WeavingMobEffect extends MobEffect { - } - - for (BlockPos blockPos3 : set) { -+ if (!org.bukkit.craftbukkit.event.CraftEventFactory.callEntityChangeBlockEvent(entity, blockPos3, Blocks.COBWEB.defaultBlockState())) continue; // Paper - Fire EntityChangeBlockEvent in more places - world.setBlock(blockPos3, Blocks.COBWEB.defaultBlockState(), 3); - world.levelEvent(3018, blockPos3, 0); - } -diff --git a/src/main/java/net/minecraft/world/entity/LightningBolt.java b/src/main/java/net/minecraft/world/entity/LightningBolt.java -index bf5015c4bb68e5c46313bab1e59c0a4d45053b73..0471d9c85af02133f99cca4e181b83b58a3f1abc 100644 ---- a/src/main/java/net/minecraft/world/entity/LightningBolt.java -+++ b/src/main/java/net/minecraft/world/entity/LightningBolt.java -@@ -99,7 +99,7 @@ public class LightningBolt extends Entity { - } - - this.powerLightningRod(); -- LightningBolt.clearCopperOnLightningStrike(this.level(), this.getStrikePosition()); -+ LightningBolt.clearCopperOnLightningStrike(this.level(), this.getStrikePosition(), this); // Paper - Call EntityChangeBlockEvent - this.gameEvent(GameEvent.LIGHTNING_STRIKE); - } - } -@@ -193,7 +193,7 @@ public class LightningBolt extends Entity { - } - } - -- private static void clearCopperOnLightningStrike(Level world, BlockPos pos) { -+ private static void clearCopperOnLightningStrike(Level world, BlockPos pos, Entity lightning) { // Paper - Call EntityChangeBlockEvent - BlockState iblockdata = world.getBlockState(pos); - BlockPos blockposition1; - BlockState iblockdata1; -@@ -207,24 +207,29 @@ public class LightningBolt extends Entity { - } - - if (iblockdata1.getBlock() instanceof WeatheringCopper) { -- world.setBlockAndUpdate(blockposition1, WeatheringCopper.getFirst(world.getBlockState(blockposition1))); -+ // Paper start - Call EntityChangeBlockEvent -+ BlockState newBlock = WeatheringCopper.getFirst(world.getBlockState(blockposition1)); -+ if (CraftEventFactory.callEntityChangeBlockEvent(lightning, blockposition1, newBlock)) { -+ world.setBlockAndUpdate(blockposition1, newBlock); -+ } -+ // Paper end - Call EntityChangeBlockEvent - BlockPos.MutableBlockPos blockposition_mutableblockposition = pos.mutable(); - int i = world.random.nextInt(3) + 3; - - for (int j = 0; j < i; ++j) { - int k = world.random.nextInt(8) + 1; - -- LightningBolt.randomWalkCleaningCopper(world, blockposition1, blockposition_mutableblockposition, k); -+ LightningBolt.randomWalkCleaningCopper(world, blockposition1, blockposition_mutableblockposition, k, lightning); // Paper - transmit LightningBolt instance to call EntityChangeBlockEvent - } - - } - } - -- private static void randomWalkCleaningCopper(Level world, BlockPos pos, BlockPos.MutableBlockPos mutablePos, int count) { -+ private static void randomWalkCleaningCopper(Level world, BlockPos pos, BlockPos.MutableBlockPos mutablePos, int count, Entity lightning) { // Paper - transmit LightningBolt instance to call EntityChangeBlockEvent - mutablePos.set(pos); - - for (int j = 0; j < count; ++j) { -- Optional optional = LightningBolt.randomStepCleaningCopper(world, mutablePos); -+ Optional optional = LightningBolt.randomStepCleaningCopper(world, mutablePos, lightning); // Paper - transmit LightningBolt instance to call EntityChangeBlockEvent - - if (optional.isEmpty()) { - break; -@@ -235,7 +240,7 @@ public class LightningBolt extends Entity { - - } - -- private static Optional randomStepCleaningCopper(Level world, BlockPos pos) { -+ private static Optional randomStepCleaningCopper(Level world, BlockPos pos, Entity lightning) { // Paper - transmit LightningBolt instance to call EntityChangeBlockEvent - Iterator iterator = BlockPos.randomInCube(world.random, 10, pos, 1).iterator(); - - BlockPos blockposition1; -@@ -252,6 +257,7 @@ public class LightningBolt extends Entity { - - BlockPos blockposition1Final = blockposition1; // CraftBukkit - decompile error - WeatheringCopper.getPrevious(iblockdata).ifPresent((iblockdata1) -> { -+ if (CraftEventFactory.callEntityChangeBlockEvent(lightning, blockposition1Final, iblockdata1)) // Paper - call EntityChangeBlockEvent - world.setBlockAndUpdate(blockposition1Final, iblockdata1); // CraftBukkit - decompile error - }); - world.levelEvent(3002, blockposition1, -1); -diff --git a/src/main/java/net/minecraft/world/item/AxeItem.java b/src/main/java/net/minecraft/world/item/AxeItem.java -index c227784544e52ee2c75ab1a2181e0b0ba7246f4b..f005ea92449cf5e249ff64f4791c3bc6b8635528 100644 ---- a/src/main/java/net/minecraft/world/item/AxeItem.java -+++ b/src/main/java/net/minecraft/world/item/AxeItem.java -@@ -65,6 +65,11 @@ public class AxeItem extends DiggerItem { - return InteractionResult.PASS; - } else { - ItemStack itemStack = context.getItemInHand(); -+ // Paper start - EntityChangeBlockEvent -+ if (!org.bukkit.craftbukkit.event.CraftEventFactory.callEntityChangeBlockEvent(player, blockPos, optional.get())) { -+ return InteractionResult.PASS; -+ } -+ // Paper end - if (player instanceof ServerPlayer) { - CriteriaTriggers.ITEM_USED_ON_BLOCK.trigger((ServerPlayer)player, blockPos, itemStack); - } -diff --git a/src/main/java/net/minecraft/world/item/EnderEyeItem.java b/src/main/java/net/minecraft/world/item/EnderEyeItem.java -index 391579b515c5a07066f82b33c4f9ef8ee1d05530..d8ce44a180f848f4c9c04967470c4359af979b2f 100644 ---- a/src/main/java/net/minecraft/world/item/EnderEyeItem.java -+++ b/src/main/java/net/minecraft/world/item/EnderEyeItem.java -@@ -46,6 +46,11 @@ public class EnderEyeItem extends Item { - return InteractionResult.SUCCESS; - } else { - BlockState iblockdata1 = (BlockState) iblockdata.setValue(EndPortalFrameBlock.HAS_EYE, true); -+ // Paper start -+ if (!org.bukkit.craftbukkit.event.CraftEventFactory.callEntityChangeBlockEvent(context.getPlayer(), blockposition, iblockdata1)) { -+ return InteractionResult.PASS; -+ } -+ // Paper end - - Block.pushEntitiesUp(iblockdata, iblockdata1, world, blockposition); - world.setBlock(blockposition, iblockdata1, 2); -diff --git a/src/main/java/net/minecraft/world/item/HoneycombItem.java b/src/main/java/net/minecraft/world/item/HoneycombItem.java -index decabf6fccaca3d1bfeba679ac71677d33315f5e..14d37bf64af719eae3ea154ea7f952cc27712b57 100644 ---- a/src/main/java/net/minecraft/world/item/HoneycombItem.java -+++ b/src/main/java/net/minecraft/world/item/HoneycombItem.java -@@ -74,6 +74,14 @@ public class HoneycombItem extends Item implements SignApplicator { - return getWaxed(blockState).map(state -> { - Player player = context.getPlayer(); - ItemStack itemStack = context.getItemInHand(); -+ // Paper start - EntityChangeBlockEvent -+ if (!org.bukkit.craftbukkit.event.CraftEventFactory.callEntityChangeBlockEvent(player, blockPos, state)) { -+ if (!player.isCreative()) { -+ player.containerMenu.sendAllDataToRemote(); -+ } -+ return InteractionResult.PASS; -+ } -+ // Paper end - if (player instanceof ServerPlayer) { - CriteriaTriggers.ITEM_USED_ON_BLOCK.trigger((ServerPlayer)player, blockPos, itemStack); - } -diff --git a/src/main/java/net/minecraft/world/item/PotionItem.java b/src/main/java/net/minecraft/world/item/PotionItem.java -index 92fa6523f2bba105a74fff228e36e58666ed56ae..d147b24bb57b322d3f5f4a9eb8cfef2acdd9e0f5 100644 ---- a/src/main/java/net/minecraft/world/item/PotionItem.java -+++ b/src/main/java/net/minecraft/world/item/PotionItem.java -@@ -109,6 +109,12 @@ public class PotionItem extends Item { - BlockState iblockdata = world.getBlockState(blockposition); - - if (context.getClickedFace() != Direction.DOWN && iblockdata.is(BlockTags.CONVERTABLE_TO_MUD) && potioncontents.is(Potions.WATER)) { -+ // Paper start -+ if (!org.bukkit.craftbukkit.event.CraftEventFactory.callEntityChangeBlockEvent(entityhuman, blockposition, Blocks.MUD.defaultBlockState())) { -+ entityhuman.containerMenu.sendAllDataToRemote(); -+ return InteractionResult.PASS; -+ } -+ // Paper end - world.playSound((Player) null, blockposition, SoundEvents.GENERIC_SPLASH, SoundSource.BLOCKS, 1.0F, 1.0F); - entityhuman.setItemInHand(context.getHand(), ItemUtils.createFilledResult(itemstack, entityhuman, new ItemStack(Items.GLASS_BOTTLE))); - entityhuman.awardStat(Stats.ITEM_USED.get(itemstack.getItem())); -diff --git a/src/main/java/net/minecraft/world/item/ShovelItem.java b/src/main/java/net/minecraft/world/item/ShovelItem.java -index c0e96284bb470353c0e54ad4e0c2205017913e61..24f6a158e4759aac3be8da4cf5e0d40bd295355b 100644 ---- a/src/main/java/net/minecraft/world/item/ShovelItem.java -+++ b/src/main/java/net/minecraft/world/item/ShovelItem.java -@@ -46,20 +46,29 @@ public class ShovelItem extends DiggerItem { - Player player = context.getPlayer(); - BlockState blockState2 = FLATTENABLES.get(blockState.getBlock()); - BlockState blockState3 = null; -+ Runnable afterAction = null; // Paper - if (blockState2 != null && level.getBlockState(blockPos.above()).isAir()) { -- level.playSound(player, blockPos, SoundEvents.SHOVEL_FLATTEN, SoundSource.BLOCKS, 1.0F, 1.0F); -+ afterAction = () -> level.playSound(player, blockPos, SoundEvents.SHOVEL_FLATTEN, SoundSource.BLOCKS, 1.0F, 1.0F); // Paper - blockState3 = blockState2; - } else if (blockState.getBlock() instanceof CampfireBlock && blockState.getValue(CampfireBlock.LIT)) { -+ afterAction = () -> { // Paper - if (!level.isClientSide()) { - level.levelEvent(null, 1009, blockPos, 0); - } - - CampfireBlock.dowse(context.getPlayer(), level, blockPos, blockState); -+ }; // Paper - blockState3 = blockState.setValue(CampfireBlock.LIT, Boolean.valueOf(false)); - } - - if (blockState3 != null) { - if (!level.isClientSide) { -+ // Paper start -+ if (!org.bukkit.craftbukkit.event.CraftEventFactory.callEntityChangeBlockEvent(context.getPlayer(), blockPos, blockState3)) { -+ return InteractionResult.PASS; -+ } -+ afterAction.run(); -+ // Paper end - level.setBlock(blockPos, blockState3, 11); - level.gameEvent(GameEvent.BLOCK_CHANGE, blockPos, GameEvent.Context.of(player, blockState3)); - if (player != null) { -diff --git a/src/main/java/net/minecraft/world/level/block/CakeBlock.java b/src/main/java/net/minecraft/world/level/block/CakeBlock.java -index d72f4a01ca07df8a9678b3ca4707e5363e482283..43e306b5ef00b39923c1597f212b4a07fb95f1ca 100644 ---- a/src/main/java/net/minecraft/world/level/block/CakeBlock.java -+++ b/src/main/java/net/minecraft/world/level/block/CakeBlock.java -@@ -65,6 +65,12 @@ public class CakeBlock extends Block { - if (block instanceof CandleBlock) { - CandleBlock candleblock = (CandleBlock) block; - -+ // Paper start - call change block event -+ if (!org.bukkit.craftbukkit.event.CraftEventFactory.callEntityChangeBlockEvent(player, pos, CandleCakeBlock.byCandle(candleblock))) { -+ player.containerMenu.sendAllDataToRemote(); // update inv because candle could decrease -+ return ItemInteractionResult.PASS_TO_DEFAULT_BLOCK_INTERACTION; -+ } -+ // Paper end - call change block event - stack.consume(1, player); - world.playSound((Player) null, pos, SoundEvents.CAKE_ADD_CANDLE, SoundSource.BLOCKS, 1.0F, 1.0F); - world.setBlockAndUpdate(pos, CandleCakeBlock.byCandle(candleblock)); -@@ -96,6 +102,14 @@ public class CakeBlock extends Block { - if (!player.canEat(false)) { - return InteractionResult.PASS; - } else { -+ // Paper start - call change block event -+ int i = state.getValue(CakeBlock.BITES); -+ final BlockState newState = i < MAX_BITES ? state.setValue(CakeBlock.BITES, i + 1) : world.getFluidState(pos).createLegacyBlock(); -+ if (!org.bukkit.craftbukkit.event.CraftEventFactory.callEntityChangeBlockEvent(player, pos, newState)) { -+ ((net.minecraft.server.level.ServerPlayer) player).getBukkitEntity().sendHealthUpdate(); -+ return InteractionResult.PASS; // return a non-consume result to cake blocks don't drop their candles -+ } -+ // Paper end - call change block event - player.awardStat(Stats.EAT_CAKE_SLICE); - // CraftBukkit start - // entityhuman.getFoodData().eat(2, 0.1F); -@@ -109,7 +123,7 @@ public class CakeBlock extends Block { - - ((net.minecraft.server.level.ServerPlayer) player).getBukkitEntity().sendHealthUpdate(); - // CraftBukkit end -- int i = (Integer) state.getValue(CakeBlock.BITES); -+ // Paper - move up - - world.gameEvent((Entity) player, (Holder) GameEvent.EAT, pos); - if (i < 6) { -diff --git a/src/main/java/net/minecraft/world/level/block/ComposterBlock.java b/src/main/java/net/minecraft/world/level/block/ComposterBlock.java -index 804adb5ed92dfcf4c29c756dd95d7164150a9666..19fa8a9f935e9063497f8c0bd7909036fa0af2b7 100644 ---- a/src/main/java/net/minecraft/world/level/block/ComposterBlock.java -+++ b/src/main/java/net/minecraft/world/level/block/ComposterBlock.java -@@ -239,6 +239,11 @@ public class ComposterBlock extends Block implements WorldlyContainerHolder { - if (i < 8 && ComposterBlock.COMPOSTABLES.containsKey(stack.getItem())) { - if (i < 7 && !world.isClientSide) { - BlockState iblockdata1 = ComposterBlock.addItem(player, state, world, pos, stack); -+ // Paper start - handle cancelled events -+ if (iblockdata1 == null) { -+ return ItemInteractionResult.SKIP_DEFAULT_BLOCK_INTERACTION; -+ } -+ // Paper end - - world.levelEvent(1500, pos, state != iblockdata1 ? 1 : 0); - player.awardStat(Stats.ITEM_USED.get(stack.getItem())); -@@ -269,11 +274,16 @@ public class ComposterBlock extends Block implements WorldlyContainerHolder { - if (i < 7 && ComposterBlock.COMPOSTABLES.containsKey(stack.getItem())) { - // CraftBukkit start - double rand = world.getRandom().nextDouble(); -- BlockState iblockdata1 = ComposterBlock.addItem(user, state, DummyGeneratorAccess.INSTANCE, pos, stack, rand); -- if (state == iblockdata1 || !org.bukkit.craftbukkit.event.CraftEventFactory.callEntityChangeBlockEvent(user, pos, iblockdata1)) { -+ BlockState iblockdata1 = null; // Paper -+ if (false && (state == iblockdata1 || !org.bukkit.craftbukkit.event.CraftEventFactory.callEntityChangeBlockEvent(user, pos, iblockdata1))) { // Paper - move event call into addItem - return state; - } - iblockdata1 = ComposterBlock.addItem(user, state, world, pos, stack, rand); -+ // Paper start - handle cancelled events -+ if (iblockdata1 == null) { -+ return state; -+ } -+ // Paper end - // CraftBukkit end - - stack.shrink(1); -@@ -314,11 +324,13 @@ public class ComposterBlock extends Block implements WorldlyContainerHolder { - return iblockdata1; - } - -+ @Nullable // Paper - static BlockState addItem(@Nullable Entity user, BlockState state, LevelAccessor world, BlockPos pos, ItemStack stack) { - // CraftBukkit start - return ComposterBlock.addItem(user, state, world, pos, stack, world.getRandom().nextDouble()); - } - -+ @Nullable // Paper - make it nullable - static BlockState addItem(@Nullable Entity entity, BlockState iblockdata, LevelAccessor generatoraccess, BlockPos blockposition, ItemStack itemstack, double rand) { - // CraftBukkit end - int i = (Integer) iblockdata.getValue(ComposterBlock.LEVEL); -@@ -329,6 +341,11 @@ public class ComposterBlock extends Block implements WorldlyContainerHolder { - } else { - int j = i + 1; - BlockState iblockdata1 = (BlockState) iblockdata.setValue(ComposterBlock.LEVEL, j); -+ // Paper start - move the EntityChangeBlockEvent here to avoid conflict later for the compost events -+ if (entity != null && !org.bukkit.craftbukkit.event.CraftEventFactory.callEntityChangeBlockEvent(entity, blockposition, iblockdata1)) { -+ return null; -+ } -+ // Paper end - - generatoraccess.setBlock(blockposition, iblockdata1, 3); - generatoraccess.gameEvent((Holder) GameEvent.BLOCK_CHANGE, blockposition, GameEvent.Context.of(entity, iblockdata1)); -diff --git a/src/main/java/net/minecraft/world/level/block/entity/BeehiveBlockEntity.java b/src/main/java/net/minecraft/world/level/block/entity/BeehiveBlockEntity.java -index ef8a0236ab4fb648c4bb2a8cfc90e3cefe8f9f1d..1a3d8755c8f6a7cfe06069e2082d8147aaaff097 100644 ---- a/src/main/java/net/minecraft/world/level/block/entity/BeehiveBlockEntity.java -+++ b/src/main/java/net/minecraft/world/level/block/entity/BeehiveBlockEntity.java -@@ -259,7 +259,13 @@ public class BeehiveBlockEntity extends BlockEntity { - --j; - } - -- world.setBlockAndUpdate(blockposition, (BlockState) iblockdata.setValue(BeehiveBlock.HONEY_LEVEL, i + j)); -+ // Paper start - Fire EntityChangeBlockEvent in more places -+ BlockState newBlockState = iblockdata.setValue(BeehiveBlock.HONEY_LEVEL, i + j); -+ -+ if (org.bukkit.craftbukkit.event.CraftEventFactory.callEntityChangeBlockEvent(entitybee, blockposition, newBlockState)) { -+ world.setBlockAndUpdate(blockposition, newBlockState); -+ } -+ // Paper end - Fire EntityChangeBlockEvent in more places - } - } - } -diff --git a/src/main/java/org/bukkit/craftbukkit/util/DummyGeneratorAccess.java b/src/main/java/org/bukkit/craftbukkit/util/DummyGeneratorAccess.java -index daf3c26fce7569761951bfd5594c6726d854a9ff..e8a73d34dbb372581b03018aade170a31c266099 100644 ---- a/src/main/java/org/bukkit/craftbukkit/util/DummyGeneratorAccess.java -+++ b/src/main/java/org/bukkit/craftbukkit/util/DummyGeneratorAccess.java -@@ -120,7 +120,7 @@ public class DummyGeneratorAccess implements WorldGenLevel { - - @Override - public void gameEvent(Holder event, Vec3 emitterPos, GameEvent.Context emitter) { -- // Used by BlockComposter -+ // Used by ComposterBlock - } - - @Override diff --git a/patches/server/0741-Mitigate-effects-of-WorldCreator-keepSpawnLoaded-ret.patch b/patches/server/0735-Mitigate-effects-of-WorldCreator-keepSpawnLoaded-ret.patch similarity index 100% rename from patches/server/0741-Mitigate-effects-of-WorldCreator-keepSpawnLoaded-ret.patch rename to patches/server/0735-Mitigate-effects-of-WorldCreator-keepSpawnLoaded-ret.patch diff --git a/patches/server/0736-Missing-eating-regain-reason.patch b/patches/server/0736-Missing-eating-regain-reason.patch deleted file mode 100644 index af804e161b36..000000000000 --- a/patches/server/0736-Missing-eating-regain-reason.patch +++ /dev/null @@ -1,45 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Lulu13022002 <41980282+Lulu13022002@users.noreply.github.com> -Date: Fri, 5 Aug 2022 12:16:51 +0200 -Subject: [PATCH] Missing eating regain reason - - -diff --git a/src/main/java/net/minecraft/world/entity/animal/Cat.java b/src/main/java/net/minecraft/world/entity/animal/Cat.java -index 2ed442c8d36f285420cf84a956e90b6036384ce0..23d4dcc82115fd1a0a77565a0472304042d5f12d 100644 ---- a/src/main/java/net/minecraft/world/entity/animal/Cat.java -+++ b/src/main/java/net/minecraft/world/entity/animal/Cat.java -@@ -398,7 +398,7 @@ public class Cat extends TamableAnimal implements VariantHolder 0.0F) { -- this.heal(f); -+ this.heal(f, org.bukkit.event.entity.EntityRegainHealthEvent.RegainReason.EATING); // Paper - Add missing regain reason - flag = true; - } - diff --git a/patches/server/0736-fix-Jigsaw-block-kicking-user.patch b/patches/server/0736-fix-Jigsaw-block-kicking-user.patch new file mode 100644 index 000000000000..749d498f5799 --- /dev/null +++ b/patches/server/0736-fix-Jigsaw-block-kicking-user.patch @@ -0,0 +1,24 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Legitimoose +Date: Wed, 28 Sep 2022 22:45:49 -0700 +Subject: [PATCH] fix Jigsaw block kicking user + + +diff --git a/src/main/java/net/minecraft/world/level/block/entity/JigsawBlockEntity.java b/src/main/java/net/minecraft/world/level/block/entity/JigsawBlockEntity.java +index 42300114e3baf31fd26090fca3b497c8157d4bb9..6410cd7a7324bb04e9285a5b090b1675a5ffa16f 100644 +--- a/src/main/java/net/minecraft/world/level/block/entity/JigsawBlockEntity.java ++++ b/src/main/java/net/minecraft/world/level/block/entity/JigsawBlockEntity.java +@@ -131,7 +131,12 @@ public class JigsawBlockEntity extends BlockEntity { + public void generate(ServerLevel world, int maxDepth, boolean keepJigsaws) { + BlockPos blockPos = this.getBlockPos().relative(this.getBlockState().getValue(JigsawBlock.ORIENTATION).front()); + Registry registry = world.registryAccess().lookupOrThrow(Registries.TEMPLATE_POOL); +- Holder holder = registry.getOrThrow(this.pool); ++ // Paper start - Replace getHolderOrThrow with a null check ++ Holder holder = registry.get(this.pool).orElse(null); ++ if (holder == null) { ++ return; ++ } ++ // Paper end - Replace getHolderOrThrow with a null check + JigsawPlacement.generateJigsaw(world, holder, this.target, maxDepth, blockPos, keepJigsaws); + } + diff --git a/patches/server/0737-Missing-effect-cause.patch b/patches/server/0737-Missing-effect-cause.patch deleted file mode 100644 index e24048113b1d..000000000000 --- a/patches/server/0737-Missing-effect-cause.patch +++ /dev/null @@ -1,58 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Lulu13022002 <41980282+Lulu13022002@users.noreply.github.com> -Date: Tue, 16 Aug 2022 19:44:55 +0200 -Subject: [PATCH] Missing effect cause - - -diff --git a/src/main/java/net/minecraft/world/entity/animal/axolotl/Axolotl.java b/src/main/java/net/minecraft/world/entity/animal/axolotl/Axolotl.java -index 3dec72f407ca4af53ac5fe32f05326638af05474..01a0731e92d39c8718538244e34a271fb8717fc2 100644 ---- a/src/main/java/net/minecraft/world/entity/animal/axolotl/Axolotl.java -+++ b/src/main/java/net/minecraft/world/entity/animal/axolotl/Axolotl.java -@@ -401,7 +401,7 @@ public class Axolotl extends Animal implements LerpingModel, VariantHolder +Date: Thu, 29 Sep 2022 16:25:50 -0700 +Subject: [PATCH] use BlockFormEvent for mud converting into clay + + +diff --git a/src/main/java/net/minecraft/world/level/block/PointedDripstoneBlock.java b/src/main/java/net/minecraft/world/level/block/PointedDripstoneBlock.java +index bd38a0a73d543a85bb5c6d50219f5438ce194df3..53cea36ec931de89e0060613acf87beb51dc16ec 100644 +--- a/src/main/java/net/minecraft/world/level/block/PointedDripstoneBlock.java ++++ b/src/main/java/net/minecraft/world/level/block/PointedDripstoneBlock.java +@@ -219,10 +219,13 @@ public class PointedDripstoneBlock extends Block implements Fallable, SimpleWate + if (((PointedDripstoneBlock.FluidInfo) optional.get()).sourceState.is(Blocks.MUD) && fluidtype == Fluids.WATER) { + BlockState iblockdata1 = Blocks.CLAY.defaultBlockState(); + +- world.setBlockAndUpdate(((PointedDripstoneBlock.FluidInfo) optional.get()).pos, iblockdata1); ++ // Paper start - Call BlockFormEvent ++ if (org.bukkit.craftbukkit.event.CraftEventFactory.handleBlockFormEvent(world, ((PointedDripstoneBlock.FluidInfo) optional.get()).pos, iblockdata1)) { + Block.pushEntitiesUp(((PointedDripstoneBlock.FluidInfo) optional.get()).sourceState, iblockdata1, world, ((PointedDripstoneBlock.FluidInfo) optional.get()).pos); + world.gameEvent((Holder) GameEvent.BLOCK_CHANGE, ((PointedDripstoneBlock.FluidInfo) optional.get()).pos, GameEvent.Context.of(iblockdata1)); + world.levelEvent(1504, blockposition1, 0); ++ } ++ // Paper end - Call BlockFormEvent + } else { + BlockPos blockposition2 = PointedDripstoneBlock.findFillableCauldronBelowStalactiteTip(world, blockposition1, fluidtype); + diff --git a/patches/server/0744-Add-getDrops-to-BlockState.patch b/patches/server/0738-Add-getDrops-to-BlockState.patch similarity index 100% rename from patches/server/0744-Add-getDrops-to-BlockState.patch rename to patches/server/0738-Add-getDrops-to-BlockState.patch diff --git a/patches/server/0739-Call-BlockPhysicsEvent-more-often.patch b/patches/server/0739-Call-BlockPhysicsEvent-more-often.patch deleted file mode 100644 index 1839fbe711c4..000000000000 --- a/patches/server/0739-Call-BlockPhysicsEvent-more-often.patch +++ /dev/null @@ -1,32 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Lulu13022002 <41980282+Lulu13022002@users.noreply.github.com> -Date: Sun, 7 Aug 2022 22:16:36 +0200 -Subject: [PATCH] Call BlockPhysicsEvent more often - - -diff --git a/src/main/java/net/minecraft/world/level/redstone/CollectingNeighborUpdater.java b/src/main/java/net/minecraft/world/level/redstone/CollectingNeighborUpdater.java -index 457a963ee6ebd3cf5c3831f6660e3850335af49f..106af2b2c7ff72c7549975aef75cdcff8d9a7d97 100644 ---- a/src/main/java/net/minecraft/world/level/redstone/CollectingNeighborUpdater.java -+++ b/src/main/java/net/minecraft/world/level/redstone/CollectingNeighborUpdater.java -@@ -119,7 +119,20 @@ public class CollectingNeighborUpdater implements NeighborUpdater { - public boolean runNext(Level world) { - BlockPos blockPos = this.sourcePos.relative(NeighborUpdater.UPDATE_ORDER[this.idx++]); - BlockState blockState = world.getBlockState(blockPos); -- NeighborUpdater.executeUpdate(world, blockState, blockPos, this.sourceBlock, this.sourcePos, false); -+ // Paper start - Call BlockPhysicsEvent -+ try { -+ org.bukkit.event.block.BlockPhysicsEvent event = new org.bukkit.event.block.BlockPhysicsEvent( -+ org.bukkit.craftbukkit.block.CraftBlock.at(world, blockPos), -+ org.bukkit.craftbukkit.block.data.CraftBlockData.fromData(blockState), -+ org.bukkit.craftbukkit.block.CraftBlock.at(world, this.sourcePos)); -+ -+ if (event.callEvent()) { // continue to check for adjacent block (increase idx) -+ NeighborUpdater.executeUpdate(world, blockState, blockPos, this.sourceBlock, this.sourcePos, false); -+ } -+ } catch (StackOverflowError ex) { -+ world.lastPhysicsProblem = blockPos; -+ } -+ // Paper end - Call BlockPhysicsEvent - if (this.idx < NeighborUpdater.UPDATE_ORDER.length && NeighborUpdater.UPDATE_ORDER[this.idx] == this.skipDirection) { - this.idx++; - } diff --git a/patches/server/0739-Fix-a-bunch-of-vanilla-bugs.patch b/patches/server/0739-Fix-a-bunch-of-vanilla-bugs.patch new file mode 100644 index 000000000000..bd4227733382 --- /dev/null +++ b/patches/server/0739-Fix-a-bunch-of-vanilla-bugs.patch @@ -0,0 +1,372 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Jake Potrebic +Date: Mon, 11 Jul 2022 11:56:41 -0700 +Subject: [PATCH] Fix a bunch of vanilla bugs + +https://bugs.mojang.com/browse/MC-253721 + wrong msg for opping multiple players + +https://bugs.mojang.com/browse/MC-248588 + respect mob griefing gamerule for draining water cauldrons + +https://bugs.mojang.com/browse/MC-244739 + play goat eating sound for last item in stack + +https://bugs.mojang.com/browse/MC-243057 + ignore furnace fuel slot in recipe book click + +https://bugs.mojang.com/browse/MC-147659 + Some witch huts spawn the incorrect cat + Note: Marked as Won't Fix, makes 0 sense + +https://bugs.mojang.com/browse/MC-179072 + Creepers do not defuse when switching from Survival to Creative/Spectator + +https://bugs.mojang.com/browse/MC-259571 + Fix changeGameModeForPlayer to use gameModeForPlayer + +https://bugs.mojang.com/browse/MC-262422 + Fix lightning being able to hit spectators + +https://bugs.mojang.com/browse/MC-263999 + Fix mobs breaking doors not spawning block break particles + +https://bugs.mojang.com/browse/MC-210802 + Fixes sheep eating blocks outside of ticking range + +https://bugs.mojang.com/browse/MC-123848 + Fixes item frames dropping items above when pointing down + +https://bugs.mojang.com/browse/MC-174630 + Fix secondary beacon effect remaining after switching effect + +https://bugs.mojang.com/browse/MC-153086 + Fix the beacon deactivation sound always playing when broken + +https://bugs.mojang.com/browse/MC-200092 + Fix yaw being ignored for a player's first spawn pos + +https://bugs.mojang.com/browse/MC-158900 + Fix error when joining after tempban expired + +https://bugs.mojang.com/browse/MC-99075 + Fix inventory desync within spawn protected area + +https://bugs.mojang.com/browse/MC-273635 + Fix TrialSpawner forgets assigned mob when placed by player + +== AT == +public net/minecraft/world/entity/Mob leashInfoTag +public net/minecraft/server/level/ChunkMap anyPlayerCloseEnoughForSpawning(Lnet/minecraft/world/level/ChunkPos;)Z + +Co-authored-by: William Blake Galbreath +Co-authored-by: Spottedleaf + +diff --git a/src/main/java/net/minecraft/server/commands/DeOpCommands.java b/src/main/java/net/minecraft/server/commands/DeOpCommands.java +index 0283f26151488d715dc823a0008c9a37ef6740fb..d98447e58233745665f0833196226077d972cc2a 100644 +--- a/src/main/java/net/minecraft/server/commands/DeOpCommands.java ++++ b/src/main/java/net/minecraft/server/commands/DeOpCommands.java +@@ -35,7 +35,7 @@ public class DeOpCommands { + if (playerList.isOp(gameProfile)) { + playerList.deop(gameProfile); + i++; +- source.sendSuccess(() -> Component.translatable("commands.deop.success", targets.iterator().next().getName()), true); ++ source.sendSuccess(() -> Component.translatable("commands.deop.success", gameProfile.getName()), true); // Paper - fixes MC-253721 + } + } + +diff --git a/src/main/java/net/minecraft/server/commands/OpCommand.java b/src/main/java/net/minecraft/server/commands/OpCommand.java +index 6854ca4d4fec2b4fa541c3fabf63787665572609..e7b444a10b244828827b3c66c53465206ea8e0ec 100644 +--- a/src/main/java/net/minecraft/server/commands/OpCommand.java ++++ b/src/main/java/net/minecraft/server/commands/OpCommand.java +@@ -46,7 +46,7 @@ public class OpCommand { + if (!playerList.isOp(gameProfile)) { + playerList.op(gameProfile); + i++; +- source.sendSuccess(() -> Component.translatable("commands.op.success", targets.iterator().next().getName()), true); ++ source.sendSuccess(() -> Component.translatable("commands.op.success", gameProfile.getName()), true); // Paper - fixes MC-253721 + } + } + +diff --git a/src/main/java/net/minecraft/server/level/ServerLevel.java b/src/main/java/net/minecraft/server/level/ServerLevel.java +index f3f93e8cbc2a5c9d0a3841ec7de010477bfd976a..d01f42aad003c7b0ea5700d32109eed4a264fa4c 100644 +--- a/src/main/java/net/minecraft/server/level/ServerLevel.java ++++ b/src/main/java/net/minecraft/server/level/ServerLevel.java +@@ -748,7 +748,7 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe + } else { + AABB axisalignedbb = AABB.encapsulatingFullBlocks(blockposition1, blockposition1.atY(this.getMaxY() + 1)).inflate(3.0D); + List list = this.getEntitiesOfClass(LivingEntity.class, axisalignedbb, (entityliving) -> { +- return entityliving != null && entityliving.isAlive() && this.canSeeSky(entityliving.blockPosition()); ++ return entityliving != null && entityliving.isAlive() && this.canSeeSky(entityliving.blockPosition()) && !entityliving.isSpectator(); // Paper - Fix lightning being able to hit spectators (MC-262422) + }); + + if (!list.isEmpty()) { +diff --git a/src/main/java/net/minecraft/server/level/ServerPlayerGameMode.java b/src/main/java/net/minecraft/server/level/ServerPlayerGameMode.java +index 064a7a3e1c4d192010e072a5e985a54135748d87..a706f0855fdf88cc9aece3ba00ef574b9cd8bd11 100644 +--- a/src/main/java/net/minecraft/server/level/ServerPlayerGameMode.java ++++ b/src/main/java/net/minecraft/server/level/ServerPlayerGameMode.java +@@ -91,7 +91,7 @@ public class ServerPlayerGameMode { + return event; // Paper - Expand PlayerGameModeChangeEvent + } + // CraftBukkit end +- this.setGameModeForPlayer(gameMode, this.previousGameModeForPlayer); ++ this.setGameModeForPlayer(gameMode, this.gameModeForPlayer); // Paper - Fix MC-259571 + this.player.onUpdateAbilities(); + this.player.server.getPlayerList().broadcastAll(new ClientboundPlayerInfoUpdatePacket(ClientboundPlayerInfoUpdatePacket.Action.UPDATE_GAME_MODE, this.player), this.player); // CraftBukkit + this.level.updateSleepingPlayerList(); +diff --git a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java +index b48966424fb8e937552c0e7bffaedaefc63ef77f..79eceb995f92b3f3d7b695dc6d2a0a4a824ce871 100644 +--- a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java ++++ b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java +@@ -1831,7 +1831,7 @@ public class ServerGamePacketListenerImpl extends ServerCommonPacketListenerImpl + this.player.swing(enumhand, true); + } + } +- } ++ } else { this.player.containerMenu.sendAllDataToRemote(); } // Paper - Fix inventory desync; MC-99075 + } else { + MutableComponent ichatmutablecomponent1 = Component.translatable("build.tooHigh", i).withStyle(ChatFormatting.RED); + +diff --git a/src/main/java/net/minecraft/server/players/PlayerList.java b/src/main/java/net/minecraft/server/players/PlayerList.java +index 6409463c9834a660f2a2e2b6bbfac8b3beb80db7..9b01ef9e9b00274f1213c006a4cad0f75e412e13 100644 +--- a/src/main/java/net/minecraft/server/players/PlayerList.java ++++ b/src/main/java/net/minecraft/server/players/PlayerList.java +@@ -260,7 +260,7 @@ public abstract class PlayerList { + } + if (optional.isEmpty() || invalidPlayerWorld[0]) { + // Paper end - reset to main world spawn if first spawn or invalid world +- player.moveTo(player.adjustSpawnLocation(worldserver1, worldserver1.getSharedSpawnPos()).getBottomCenter(), 0.0F, 0.0F); ++ player.moveTo(player.adjustSpawnLocation(worldserver1, worldserver1.getSharedSpawnPos()).getBottomCenter(), worldserver1.getSharedSpawnAngle(), 0.0F); // Paper - MC-200092 - fix first spawn pos yaw being ignored + } + // Paper end - Entity#getEntitySpawnReason + player.setServerLevel(worldserver1); +@@ -660,8 +660,10 @@ public abstract class PlayerList { + Player player = entity.getBukkitEntity(); + PlayerLoginEvent event = new PlayerLoginEvent(player, loginlistener.connection.hostname, ((java.net.InetSocketAddress) socketaddress).getAddress(), ((java.net.InetSocketAddress) loginlistener.connection.channel.remoteAddress()).getAddress()); + +- if (this.bans.isBanned(gameprofile)) { +- UserBanListEntry gameprofilebanentry = (UserBanListEntry) this.bans.get(gameprofile); ++ // Paper start - Fix MC-158900 ++ UserBanListEntry gameprofilebanentry; ++ if (this.bans.isBanned(gameprofile) && (gameprofilebanentry = this.bans.get(gameprofile)) != null) { ++ // Paper end - Fix MC-158900 + + ichatmutablecomponent = Component.translatable("multiplayer.disconnect.banned.reason", gameprofilebanentry.getReason()); + if (gameprofilebanentry.getExpires() != null) { +diff --git a/src/main/java/net/minecraft/world/entity/ai/goal/BreakDoorGoal.java b/src/main/java/net/minecraft/world/entity/ai/goal/BreakDoorGoal.java +index 6827426e6e9706909265f84bf97b5fa7105a7fea..7324da6b7dd2623ce394e3827ff77ef684a3b98b 100644 +--- a/src/main/java/net/minecraft/world/entity/ai/goal/BreakDoorGoal.java ++++ b/src/main/java/net/minecraft/world/entity/ai/goal/BreakDoorGoal.java +@@ -78,9 +78,10 @@ public class BreakDoorGoal extends DoorInteractGoal { + return; + } + // CraftBukkit end ++ final net.minecraft.world.level.block.state.BlockState oldState = this.mob.level().getBlockState(this.doorPos); // Paper - fix MC-263999 + this.mob.level().removeBlock(this.doorPos, false); + this.mob.level().levelEvent(1021, this.doorPos, 0); +- this.mob.level().levelEvent(2001, this.doorPos, Block.getId(this.mob.level().getBlockState(this.doorPos))); ++ this.mob.level().levelEvent(2001, this.doorPos, Block.getId(oldState)); // Paper - fix MC-263999 + } + + } +diff --git a/src/main/java/net/minecraft/world/entity/ai/goal/EatBlockGoal.java b/src/main/java/net/minecraft/world/entity/ai/goal/EatBlockGoal.java +index 9e6f946e6d2878aa3fa8abe0f6fa4770d18676d3..32bb591371fe78ba10a2bc52389ef33978cbc0eb 100644 +--- a/src/main/java/net/minecraft/world/entity/ai/goal/EatBlockGoal.java ++++ b/src/main/java/net/minecraft/world/entity/ai/goal/EatBlockGoal.java +@@ -31,6 +31,11 @@ public class EatBlockGoal extends Goal { + + @Override + public boolean canUse() { ++ // Paper start - Fix MC-210802 ++ if (!((net.minecraft.server.level.ServerLevel) this.level).chunkSource.chunkMap.anyPlayerCloseEnoughForSpawning(this.mob.chunkPosition())) { ++ return false; ++ } ++ // Paper end + if (this.mob.getRandom().nextInt(this.mob.isBaby() ? 50 : 1000) != 0) { + return false; + } else { +diff --git a/src/main/java/net/minecraft/world/entity/ai/goal/SwellGoal.java b/src/main/java/net/minecraft/world/entity/ai/goal/SwellGoal.java +index ef71b3ef4444c05b4211de87e1c8ec52cbe3e72a..137ec75ee803789deb7b1ca93dd9369c9af362b9 100644 +--- a/src/main/java/net/minecraft/world/entity/ai/goal/SwellGoal.java ++++ b/src/main/java/net/minecraft/world/entity/ai/goal/SwellGoal.java +@@ -21,6 +21,13 @@ public class SwellGoal extends Goal { + return this.creeper.getSwellDir() > 0 || livingEntity != null && this.creeper.distanceToSqr(livingEntity) < 9.0; + } + ++ // Paper start - Fix MC-179072 ++ @Override ++ public boolean canContinueToUse() { ++ return !net.minecraft.world.entity.EntitySelector.NO_CREATIVE_OR_SPECTATOR.test(this.creeper.getTarget()) && canUse(); ++ } ++ // Paper end ++ + @Override + public void start() { + this.creeper.getNavigation().stop(); +diff --git a/src/main/java/net/minecraft/world/entity/animal/goat/Goat.java b/src/main/java/net/minecraft/world/entity/animal/goat/Goat.java +index 14b47d6fa189f2a666b12ef7e7708d204c2b0452..4c6dc427b90012b0945e073dd905dc7e8d1bec82 100644 +--- a/src/main/java/net/minecraft/world/entity/animal/goat/Goat.java ++++ b/src/main/java/net/minecraft/world/entity/animal/goat/Goat.java +@@ -247,9 +247,10 @@ public class Goat extends Animal { + player.setItemInHand(hand, itemstack1); + return InteractionResult.SUCCESS; + } else { ++ boolean isFood = this.isFood(itemstack); // Paper - track before stack is possibly decreased to 0 (Fixes MC-244739) + InteractionResult enuminteractionresult = super.mobInteract(player, hand); + +- if (enuminteractionresult.consumesAction() && this.isFood(itemstack)) { ++ if (enuminteractionresult.consumesAction() && isFood) { // Paper + this.playEatingSound(); + } + +diff --git a/src/main/java/net/minecraft/world/entity/decoration/ItemFrame.java b/src/main/java/net/minecraft/world/entity/decoration/ItemFrame.java +index 30af4cbb17148c247a46c0346419d6c838dbc9d2..d431ee93cd7e87a24ff4079288facd089053d725 100644 +--- a/src/main/java/net/minecraft/world/entity/decoration/ItemFrame.java ++++ b/src/main/java/net/minecraft/world/entity/decoration/ItemFrame.java +@@ -272,6 +272,14 @@ public class ItemFrame extends HangingEntity { + return (ItemStack) this.getEntityData().get(ItemFrame.DATA_ITEM); + } + ++ // Paper start - Fix MC-123848 (spawn item frame drops above block) ++ @Nullable ++ @Override ++ public net.minecraft.world.entity.item.ItemEntity spawnAtLocation(ServerLevel serverLevel, ItemStack stack) { ++ return this.spawnAtLocation(serverLevel, stack, this.getDirection() == Direction.DOWN ? -0.6F : 0.0F); ++ } ++ // Paper end ++ + @Nullable + public MapId getFramedMapId(ItemStack stack) { + return (MapId) stack.get(DataComponents.MAP_ID); +diff --git a/src/main/java/net/minecraft/world/entity/npc/CatSpawner.java b/src/main/java/net/minecraft/world/entity/npc/CatSpawner.java +index 15a1ea01917ce57c2457094186dde937c21ffb22..b0236c7bf9441aa84d3795ffed05dd6099f29636 100644 +--- a/src/main/java/net/minecraft/world/entity/npc/CatSpawner.java ++++ b/src/main/java/net/minecraft/world/entity/npc/CatSpawner.java +@@ -82,8 +82,8 @@ public class CatSpawner implements CustomSpawner { + if (cat == null) { + return 0; + } else { ++ cat.moveTo(pos, 0.0F, 0.0F); // Paper - move up - Fix MC-147659 + cat.finalizeSpawn(world, world.getCurrentDifficultyAt(pos), EntitySpawnReason.NATURAL, null); +- cat.moveTo(pos, 0.0F, 0.0F); + world.addFreshEntityWithPassengers(cat); + return 1; + } +diff --git a/src/main/java/net/minecraft/world/inventory/BeaconMenu.java b/src/main/java/net/minecraft/world/inventory/BeaconMenu.java +index cad0b581c992edc5cd312a727695a443e26e96d8..9db647cfbd3f9c884465cf3d3a1b911d46a3da58 100644 +--- a/src/main/java/net/minecraft/world/inventory/BeaconMenu.java ++++ b/src/main/java/net/minecraft/world/inventory/BeaconMenu.java +@@ -164,6 +164,11 @@ public class BeaconMenu extends AbstractContainerMenu { + // Paper end - Add PlayerChangeBeaconEffectEvent + + public void updateEffects(Optional> primary, Optional> secondary) { ++ // Paper start - fix MC-174630 - validate secondary power ++ if (secondary.isPresent() && secondary.get() != net.minecraft.world.effect.MobEffects.REGENERATION && (primary.isPresent() && secondary.get() != primary.get())) { ++ secondary = Optional.empty(); ++ } ++ // Paper end + if (this.paymentSlot.hasItem()) { + // Paper start - Add PlayerChangeBeaconEffectEvent + io.papermc.paper.event.player.PlayerChangeBeaconEffectEvent event = new io.papermc.paper.event.player.PlayerChangeBeaconEffectEvent((org.bukkit.entity.Player) this.player.player.getBukkitEntity(), convert(primary), convert(secondary), this.access.getLocation().getBlock()); +diff --git a/src/main/java/net/minecraft/world/level/block/LayeredCauldronBlock.java b/src/main/java/net/minecraft/world/level/block/LayeredCauldronBlock.java +index 7dd6b7c0ea472cfbc7ece55bc64bc5d85be4a6c0..6dcb571e9f35fbae724be69dc113b0c33eca63b3 100644 +--- a/src/main/java/net/minecraft/world/level/block/LayeredCauldronBlock.java ++++ b/src/main/java/net/minecraft/world/level/block/LayeredCauldronBlock.java +@@ -72,7 +72,7 @@ public class LayeredCauldronBlock extends AbstractCauldronBlock { + if (entity.isOnFire() && this.isEntityInsideContent(state, pos, entity)) { + // CraftBukkit start - moved down + // entity.clearFire(); +- if (entity.mayInteract(worldserver, pos)) { ++ if ((entity instanceof net.minecraft.world.entity.player.Player || worldserver.getGameRules().getBoolean(net.minecraft.world.level.GameRules.RULE_MOBGRIEFING)) && entity.mayInteract(worldserver, pos)) { // Paper - Fixes MC-248588 + if (this.handleEntityOnFireInsideWithEvent(state, world, pos, entity)) { // Paper - fix powdered snow cauldron extinguishing entities + entity.clearFire(); + } +diff --git a/src/main/java/net/minecraft/world/level/block/entity/AbstractFurnaceBlockEntity.java b/src/main/java/net/minecraft/world/level/block/entity/AbstractFurnaceBlockEntity.java +index 9c1267df7057caa3500c7a9e6c705ea58c2b5e11..4d36aa195332c2ff6fa7bc5fff61ff7dc80a3fd5 100644 +--- a/src/main/java/net/minecraft/world/level/block/entity/AbstractFurnaceBlockEntity.java ++++ b/src/main/java/net/minecraft/world/level/block/entity/AbstractFurnaceBlockEntity.java +@@ -531,13 +531,10 @@ public abstract class AbstractFurnaceBlockEntity extends BaseContainerBlockEntit + + @Override + public void fillStackedContents(StackedItemContents finder) { +- Iterator iterator = this.items.iterator(); +- +- while (iterator.hasNext()) { +- ItemStack itemstack = (ItemStack) iterator.next(); +- +- finder.accountStack(itemstack); +- } ++ // Paper start - don't account fuel stack (fixes MC-243057) ++ finder.accountStack(this.items.get(SLOT_INPUT)); ++ finder.accountStack(this.items.get(SLOT_RESULT)); ++ // Paper end + + } + } +diff --git a/src/main/java/net/minecraft/world/level/block/entity/BeaconBlockEntity.java b/src/main/java/net/minecraft/world/level/block/entity/BeaconBlockEntity.java +index 8aab6f68f576fb022eb59798585e264f5aafbc69..edd6017937a7f20a1b43fa15204ec130b524b52b 100644 +--- a/src/main/java/net/minecraft/world/level/block/entity/BeaconBlockEntity.java ++++ b/src/main/java/net/minecraft/world/level/block/entity/BeaconBlockEntity.java +@@ -295,7 +295,11 @@ public class BeaconBlockEntity extends BlockEntity implements MenuProvider, Name + org.bukkit.block.Block block = org.bukkit.craftbukkit.block.CraftBlock.at(level, worldPosition); + new io.papermc.paper.event.block.BeaconDeactivatedEvent(block).callEvent(); + // Paper end - beacon activation/deactivation events ++ // Paper start - fix MC-153086 ++ if (this.levels > 0 && !this.beamSections.isEmpty()) { + BeaconBlockEntity.playSound(this.level, this.worldPosition, SoundEvents.BEACON_DEACTIVATE); ++ } ++ // Paper end + super.setRemoved(); + } + +diff --git a/src/main/java/net/minecraft/world/level/block/entity/trialspawner/TrialSpawnerData.java b/src/main/java/net/minecraft/world/level/block/entity/trialspawner/TrialSpawnerData.java +index e64a30577e9000e5c4d22fd3d9cf8a9381c5c459..a49f83784f85f5420091692aae588ef067aa5fcd 100644 +--- a/src/main/java/net/minecraft/world/level/block/entity/trialspawner/TrialSpawnerData.java ++++ b/src/main/java/net/minecraft/world/level/block/entity/trialspawner/TrialSpawnerData.java +@@ -101,9 +101,9 @@ public class TrialSpawnerData { + this.ejectingLootTable = rewardLootTable; + } + +- public void reset() { ++ public void reset(TrialSpawner logic) { // Paper - Fix TrialSpawner forgets assigned mob; MC-273635 + this.currentMobs.clear(); +- this.nextSpawnData = Optional.empty(); ++ if (!logic.getConfig().spawnPotentialsDefinition().isEmpty()) this.nextSpawnData = Optional.empty(); // Paper - Fix TrialSpawner forgets assigned mob; MC-273635 + this.resetStatistics(); + } + +diff --git a/src/main/java/net/minecraft/world/level/block/entity/trialspawner/TrialSpawnerState.java b/src/main/java/net/minecraft/world/level/block/entity/trialspawner/TrialSpawnerState.java +index 83cdeee5e2ce115ff696a5afc5465dc4301779b9..192ee216b617d3533f592e62719b6d75d20b5a96 100644 +--- a/src/main/java/net/minecraft/world/level/block/entity/trialspawner/TrialSpawnerState.java ++++ b/src/main/java/net/minecraft/world/level/block/entity/trialspawner/TrialSpawnerState.java +@@ -145,7 +145,7 @@ public enum TrialSpawnerState implements StringRepresentable { + yield ACTIVE; + } else if (trialSpawnerData.isCooldownFinished(world)) { + logic.removeOminous(world, pos); +- trialSpawnerData.reset(); ++ trialSpawnerData.reset(logic); // Paper - Fix TrialSpawner forgets assigned mob; MC-273635 + yield WAITING_FOR_PLAYERS; + } else { + yield this; +diff --git a/src/main/java/net/minecraft/world/level/portal/TeleportTransition.java b/src/main/java/net/minecraft/world/level/portal/TeleportTransition.java +index cf27b0d6a9fe53b9f91090db4740776b335a2e9b..7d5909431f98f7e8b84d740bba9c044fec6d8e96 100644 +--- a/src/main/java/net/minecraft/world/level/portal/TeleportTransition.java ++++ b/src/main/java/net/minecraft/world/level/portal/TeleportTransition.java +@@ -53,7 +53,7 @@ public record TeleportTransition(ServerLevel newLevel, Vec3 position, Vec3 delta + } + + public TeleportTransition(ServerLevel worldserver, Entity entity, TeleportTransition.PostTeleportTransition teleporttransition_a, PlayerTeleportEvent.TeleportCause cause) { +- this(worldserver, findAdjustedSharedSpawnPos(worldserver, entity), Vec3.ZERO, 0.0F, 0.0F, false, false, Set.of(), teleporttransition_a, cause); ++ this(worldserver, findAdjustedSharedSpawnPos(worldserver, entity), Vec3.ZERO, worldserver.getSharedSpawnAngle(), 0.0F, false, false, Set.of(), teleporttransition_a, cause); // Paper - MC-200092 - fix first spawn pos yaw being ignored + // CraftBukkit end + } + +@@ -69,7 +69,7 @@ public record TeleportTransition(ServerLevel newLevel, Vec3 position, Vec3 delta + } + + public static TeleportTransition missingRespawnBlock(ServerLevel world, Entity entity, TeleportTransition.PostTeleportTransition postDimensionTransition) { +- return new TeleportTransition(world, findAdjustedSharedSpawnPos(world, entity), Vec3.ZERO, 0.0F, 0.0F, true, false, Set.of(), postDimensionTransition); ++ return new TeleportTransition(world, findAdjustedSharedSpawnPos(world, entity), Vec3.ZERO, world.getSharedSpawnAngle(), 0.0F, true, false, Set.of(), postDimensionTransition); // Paper - MC-200092 - fix spawn pos yaw being ignored + } + + private static Vec3 findAdjustedSharedSpawnPos(ServerLevel world, Entity entity) { diff --git a/patches/server/0740-Configurable-chat-thread-limit.patch b/patches/server/0740-Configurable-chat-thread-limit.patch deleted file mode 100644 index 882f700d25f6..000000000000 --- a/patches/server/0740-Configurable-chat-thread-limit.patch +++ /dev/null @@ -1,47 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Shane Freeder -Date: Sun, 18 Sep 2022 06:33:17 +0100 -Subject: [PATCH] Configurable chat thread limit - -By default, spigot shifts chat over to an unbounded thread pool, -on a normal server, this really offers no gains, the creation of a thread -on submitting to the pool on these servers eats more time vs just running it in -the netty pipeline, however, on servers using plugins which do work in here, there -could be some overall benefits to moving this stuff outside of the pipeline. - -In general, this patch does two things: -1) Exposes the core size for the pool, this allows for ensuring that a number of threads -sit around in the pool, mitigating the need for creating new threads; This IS however -caveated, the ThreadPoolExecutor will ONLY create core threads as they're needed, it -just won't allow for us to dip back under the # of core threads, this can potentially -be mitigated by calling prestartCoreThread, however, I'm not sure if there is much justification -for this -2) Exposes a max size for the pool, as stated, by default this is unbounded, for most -servers limiting the size of the pool is going to have 0 effects given how fast chat -is actually processed, this is honestly really just exposed for the misnomers or people -who just wanna ensure that this won't grow over a specific size if chat gets stupidly active - -diff --git a/src/main/java/io/papermc/paper/configuration/GlobalConfiguration.java b/src/main/java/io/papermc/paper/configuration/GlobalConfiguration.java -index b92bfd89e32becde2e7630c6116c16f8a4f6614a..73e8a524925ed6f2580d3bd01616646fabafda78 100644 ---- a/src/main/java/io/papermc/paper/configuration/GlobalConfiguration.java -+++ b/src/main/java/io/papermc/paper/configuration/GlobalConfiguration.java -@@ -303,7 +303,18 @@ public class GlobalConfiguration extends ConfigurationPart { - - @PostProcess - private void postProcess() { -- // TODO: fill in separate patch -+ //noinspection ConstantConditions -+ if (net.minecraft.server.MinecraftServer.getServer() == null) return; // In testing env, this will be null here -+ int _chatExecutorMaxSize = (this.chatExecutorMaxSize <= 0) ? Integer.MAX_VALUE : this.chatExecutorMaxSize; // This is somewhat dumb, but, this is the default, do we cap this?; -+ int _chatExecutorCoreSize = Math.max(this.chatExecutorCoreSize, 0); -+ -+ if (_chatExecutorMaxSize < _chatExecutorCoreSize) { -+ _chatExecutorMaxSize = _chatExecutorCoreSize; -+ } -+ -+ java.util.concurrent.ThreadPoolExecutor executor = (java.util.concurrent.ThreadPoolExecutor) net.minecraft.server.MinecraftServer.getServer().chatExecutor; -+ executor.setCorePoolSize(_chatExecutorCoreSize); -+ executor.setMaximumPoolSize(_chatExecutorMaxSize); - } - } - public int maxJoinsPerTick = 5; diff --git a/patches/server/0740-Remove-unnecessary-onTrackingStart-during-navigation.patch b/patches/server/0740-Remove-unnecessary-onTrackingStart-during-navigation.patch new file mode 100644 index 000000000000..ad0baf535fa1 --- /dev/null +++ b/patches/server/0740-Remove-unnecessary-onTrackingStart-during-navigation.patch @@ -0,0 +1,28 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Nassim Jahnke +Date: Mon, 3 Oct 2022 20:48:19 +0200 +Subject: [PATCH] Remove unnecessary onTrackingStart during navigation warning + + +diff --git a/src/main/java/net/minecraft/server/level/ServerLevel.java b/src/main/java/net/minecraft/server/level/ServerLevel.java +index d01f42aad003c7b0ea5700d32109eed4a264fa4c..45e61a08152517a61260e662764d8bb0335537e3 100644 +--- a/src/main/java/net/minecraft/server/level/ServerLevel.java ++++ b/src/main/java/net/minecraft/server/level/ServerLevel.java +@@ -2237,7 +2237,7 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe + } + + if (entity instanceof Mob entityinsentient) { +- if (ServerLevel.this.isUpdatingNavigations) { ++ if (false && ServerLevel.this.isUpdatingNavigations) { // Paper - Remove unnecessary onTrackingStart during navigation warning + String s = "onTrackingStart called during navigation iteration"; + + Util.logAndPauseIfInIde("onTrackingStart called during navigation iteration", new IllegalStateException("onTrackingStart called during navigation iteration")); +@@ -2317,7 +2317,7 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe + } + + if (entity instanceof Mob entityinsentient) { +- if (ServerLevel.this.isUpdatingNavigations) { ++ if (false && ServerLevel.this.isUpdatingNavigations) { // Paper - Remove unnecessary onTrackingStart during navigation warning + String s = "onTrackingStart called during navigation iteration"; + + Util.logAndPauseIfInIde("onTrackingStart called during navigation iteration", new IllegalStateException("onTrackingStart called during navigation iteration")); diff --git a/patches/server/0741-Fix-custom-piglin-loved-items.patch b/patches/server/0741-Fix-custom-piglin-loved-items.patch new file mode 100644 index 000000000000..358bd47f1e72 --- /dev/null +++ b/patches/server/0741-Fix-custom-piglin-loved-items.patch @@ -0,0 +1,21 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Jake Potrebic +Date: Mon, 4 Jul 2022 21:50:44 -0700 +Subject: [PATCH] Fix custom piglin loved items + +Upstream didn't modify the isLovedItem check in wantsToPickup +so piglins never actually tried to pickup interestItems + +diff --git a/src/main/java/net/minecraft/world/entity/monster/piglin/PiglinAi.java b/src/main/java/net/minecraft/world/entity/monster/piglin/PiglinAi.java +index 4f3048615a34fc2c067e09aec76af94cde6a74e0..5c26beef2d3f3d4afa51950ddeb7089989218462 100644 +--- a/src/main/java/net/minecraft/world/entity/monster/piglin/PiglinAi.java ++++ b/src/main/java/net/minecraft/world/entity/monster/piglin/PiglinAi.java +@@ -405,7 +405,7 @@ public class PiglinAi { + } else { + boolean flag = piglin.canAddToInventory(stack); + +- return stack.is(Items.GOLD_NUGGET) ? flag : (PiglinAi.isFood(stack) ? !PiglinAi.hasEatenRecently(piglin) && flag : (!PiglinAi.isLovedItem(stack) ? piglin.canReplaceCurrentItem(stack) : PiglinAi.isNotHoldingLovedItemInOffHand(piglin) && flag)); ++ return stack.is(Items.GOLD_NUGGET) ? flag : (PiglinAi.isFood(stack) ? !PiglinAi.hasEatenRecently(piglin) && flag : (!PiglinAi.isLovedItem(stack, piglin) ? piglin.canReplaceCurrentItem(stack) : PiglinAi.isNotHoldingLovedItemInOffHand(piglin) && flag)); // Paper - upstream missed isLovedItem check + } + } + diff --git a/patches/server/0742-EntityPickupItemEvent-fixes.patch b/patches/server/0742-EntityPickupItemEvent-fixes.patch new file mode 100644 index 000000000000..839a01673d30 --- /dev/null +++ b/patches/server/0742-EntityPickupItemEvent-fixes.patch @@ -0,0 +1,73 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Jake Potrebic +Date: Mon, 4 Jul 2022 21:45:36 -0700 +Subject: [PATCH] EntityPickupItemEvent fixes + +Fixes double firing of the event in PiglinAi + +Fixes cancelling the event for piglins still triggering the +advancement trigger + +Fires the event when a Raider tries to pick up a raid banner +to become raid leader. + +diff --git a/src/main/java/net/minecraft/world/entity/monster/piglin/Piglin.java b/src/main/java/net/minecraft/world/entity/monster/piglin/Piglin.java +index 15a49e3541c8b45db5e472a64fa0cb94c5a72f67..e04d2c5e75dc774fe893a552474fdb8045c32693 100644 +--- a/src/main/java/net/minecraft/world/entity/monster/piglin/Piglin.java ++++ b/src/main/java/net/minecraft/world/entity/monster/piglin/Piglin.java +@@ -429,7 +429,7 @@ public class Piglin extends AbstractPiglin implements CrossbowAttackMob, Invento + + @Override + protected void pickUpItem(ServerLevel world, ItemEntity itemEntity) { +- this.onItemPickup(itemEntity); ++ // this.onItemPickup(itemEntity); // Paper - EntityPickupItemEvent fixes; call in PiglinAi#pickUpItem after EntityPickupItemEvent is fired + PiglinAi.pickUpItem(world, this, itemEntity); + } + +diff --git a/src/main/java/net/minecraft/world/entity/monster/piglin/PiglinAi.java b/src/main/java/net/minecraft/world/entity/monster/piglin/PiglinAi.java +index 5c26beef2d3f3d4afa51950ddeb7089989218462..e283b1296c1e831376bfe9491cbf02ed4b3fffe4 100644 +--- a/src/main/java/net/minecraft/world/entity/monster/piglin/PiglinAi.java ++++ b/src/main/java/net/minecraft/world/entity/monster/piglin/PiglinAi.java +@@ -244,11 +244,16 @@ public class PiglinAi { + ItemStack itemstack; + + // CraftBukkit start +- if (itemEntity.getItem().is(Items.GOLD_NUGGET) && !org.bukkit.craftbukkit.event.CraftEventFactory.callEntityPickupItemEvent(piglin, itemEntity, 0, false).isCancelled()) { ++ // Paper start - EntityPickupItemEvent fixes; fix event firing twice ++ if (itemEntity.getItem().is(Items.GOLD_NUGGET)/* && !org.bukkit.craftbukkit.event.CraftEventFactory.callEntityPickupItemEvent(piglin, itemEntity, 0, false).isCancelled()*/) { // Paper ++ if (!org.bukkit.craftbukkit.event.CraftEventFactory.callEntityPickupItemEvent(piglin, itemEntity, 0, false).isCancelled()) return; ++ piglin.onItemPickup(itemEntity); // Paper - moved from Piglin#pickUpItem - call prior to item entity modification ++ // Paper end + piglin.take(itemEntity, itemEntity.getItem().getCount()); + itemstack = itemEntity.getItem(); + itemEntity.discard(EntityRemoveEvent.Cause.PICKUP); // CraftBukkit - add Bukkit remove cause + } else if (!org.bukkit.craftbukkit.event.CraftEventFactory.callEntityPickupItemEvent(piglin, itemEntity, itemEntity.getItem().getCount() - 1, false).isCancelled()) { ++ piglin.onItemPickup(itemEntity); // Paper - EntityPickupItemEvent fixes; moved from Piglin#pickUpItem - call prior to item entity modification + piglin.take(itemEntity, 1); + itemstack = PiglinAi.removeOneItemFromItemEntity(itemEntity); + } else { +@@ -263,7 +268,7 @@ public class PiglinAi { + } else if (PiglinAi.isFood(itemstack) && !PiglinAi.hasEatenRecently(piglin)) { + PiglinAi.eat(piglin); + } else { +- boolean flag = !piglin.equipItemIfPossible(world, itemstack, itemEntity).equals(ItemStack.EMPTY); // CraftBukkit ++ boolean flag = !piglin.equipItemIfPossible(world, itemstack, null).equals(ItemStack.EMPTY); // CraftBukkit // Paper - pass null item entity to prevent duplicate pickup item event call - called above. + + if (!flag) { + PiglinAi.putInInventory(piglin, itemstack); +diff --git a/src/main/java/net/minecraft/world/entity/raid/Raider.java b/src/main/java/net/minecraft/world/entity/raid/Raider.java +index 45375ccdcf730732dd915304dea2f523807eedd6..ab132041982df2a701e4baea8195873f31b4a5fb 100644 +--- a/src/main/java/net/minecraft/world/entity/raid/Raider.java ++++ b/src/main/java/net/minecraft/world/entity/raid/Raider.java +@@ -228,6 +228,11 @@ public abstract class Raider extends PatrollingMonster { + boolean flag = this.hasActiveRaid() && this.getCurrentRaid().getLeader(this.getWave()) != null; + + if (this.hasActiveRaid() && !flag && ItemStack.matches(itemstack, Raid.getOminousBannerInstance(this.registryAccess().lookupOrThrow(Registries.BANNER_PATTERN)))) { ++ // Paper start - EntityPickupItemEvent fixes ++ if (org.bukkit.craftbukkit.event.CraftEventFactory.callEntityPickupItemEvent(this, itemEntity, 0, false).isCancelled()) { ++ return; ++ } ++ // Paper end - EntityPickupItemEvent fixes + EquipmentSlot enumitemslot = EquipmentSlot.HEAD; + ItemStack itemstack1 = this.getItemBySlot(enumitemslot); + double d0 = (double) this.getEquipmentDropChance(enumitemslot); diff --git a/patches/server/0742-fix-Jigsaw-block-kicking-user.patch b/patches/server/0742-fix-Jigsaw-block-kicking-user.patch deleted file mode 100644 index 6e1aef94c824..000000000000 --- a/patches/server/0742-fix-Jigsaw-block-kicking-user.patch +++ /dev/null @@ -1,24 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Legitimoose -Date: Wed, 28 Sep 2022 22:45:49 -0700 -Subject: [PATCH] fix Jigsaw block kicking user - - -diff --git a/src/main/java/net/minecraft/world/level/block/entity/JigsawBlockEntity.java b/src/main/java/net/minecraft/world/level/block/entity/JigsawBlockEntity.java -index 0083602f8c8826e247fecbdb5cdb6548ff6180ce..675f6b2932e036d2eedf065fd2db4bf9f0e4250d 100644 ---- a/src/main/java/net/minecraft/world/level/block/entity/JigsawBlockEntity.java -+++ b/src/main/java/net/minecraft/world/level/block/entity/JigsawBlockEntity.java -@@ -137,7 +137,12 @@ public class JigsawBlockEntity extends BlockEntity { - public void generate(ServerLevel world, int maxDepth, boolean keepJigsaws) { - BlockPos blockPos = this.getBlockPos().relative(this.getBlockState().getValue(JigsawBlock.ORIENTATION).front()); - Registry registry = world.registryAccess().registryOrThrow(Registries.TEMPLATE_POOL); -- Holder holder = registry.getHolderOrThrow(this.pool); -+ // Paper start - Replace getHolderOrThrow with a null check -+ Holder holder = registry.getHolder(this.pool).orElse(null); -+ if (holder == null) { -+ return; -+ } -+ // Paper end - Replace getHolderOrThrow with a null check - JigsawPlacement.generateJigsaw(world, holder, this.target, maxDepth, blockPos, keepJigsaws); - } - diff --git a/patches/server/0743-Correctly-handle-interactions-with-items-on-cooldown.patch b/patches/server/0743-Correctly-handle-interactions-with-items-on-cooldown.patch new file mode 100644 index 000000000000..a86affadb8ee --- /dev/null +++ b/patches/server/0743-Correctly-handle-interactions-with-items-on-cooldown.patch @@ -0,0 +1,60 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Jake Potrebic +Date: Thu, 16 Jun 2022 21:57:02 -0700 +Subject: [PATCH] Correctly handle interactions with items on cooldown + + +diff --git a/src/main/java/net/minecraft/server/level/ServerPlayerGameMode.java b/src/main/java/net/minecraft/server/level/ServerPlayerGameMode.java +index a706f0855fdf88cc9aece3ba00ef574b9cd8bd11..2aee9c2fbe38076317a3de7c3fdbd6988b64b389 100644 +--- a/src/main/java/net/minecraft/server/level/ServerPlayerGameMode.java ++++ b/src/main/java/net/minecraft/server/level/ServerPlayerGameMode.java +@@ -520,6 +520,7 @@ public class ServerPlayerGameMode { + BlockPos blockposition = hitResult.getBlockPos(); + BlockState iblockdata = world.getBlockState(blockposition); + boolean cancelledBlock = false; ++ boolean cancelledItem = false; // Paper - correctly handle items on cooldown + + if (!iblockdata.getBlock().isEnabled(world.enabledFeatures())) { + return InteractionResult.FAIL; +@@ -529,10 +530,10 @@ public class ServerPlayerGameMode { + } + + if (player.getCooldowns().isOnCooldown(stack)) { +- cancelledBlock = true; ++ cancelledItem = true; // Paper - correctly handle items on cooldown + } + +- PlayerInteractEvent event = CraftEventFactory.callPlayerInteractEvent(player, Action.RIGHT_CLICK_BLOCK, blockposition, hitResult.getDirection(), stack, cancelledBlock, hand, hitResult.getLocation()); ++ PlayerInteractEvent event = CraftEventFactory.callPlayerInteractEvent(player, Action.RIGHT_CLICK_BLOCK, blockposition, hitResult.getDirection(), stack, cancelledBlock, cancelledItem, hand, hitResult.getLocation()); // Paper - correctly handle items on cooldown + this.firedInteract = true; + this.interactResult = event.useItemInHand() == Event.Result.DENY; + this.interactPosition = blockposition.immutable(); +diff --git a/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java b/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java +index f54a4c263eeb4df40c72c20c3c480ce74718a718..ea0241de1a7ef64059cddcb43c41f56b91901897 100644 +--- a/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java ++++ b/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java +@@ -555,6 +555,12 @@ public class CraftEventFactory { + } + + public static PlayerInteractEvent callPlayerInteractEvent(net.minecraft.world.entity.player.Player who, Action action, BlockPos position, Direction direction, ItemStack itemstack, boolean cancelledBlock, InteractionHand hand, Vec3 targetPos) { ++ // Paper start - cancelledItem param ++ return CraftEventFactory.callPlayerInteractEvent(who, action, position, direction, itemstack, cancelledBlock, false, hand, targetPos); ++ } ++ ++ public static PlayerInteractEvent callPlayerInteractEvent(net.minecraft.world.entity.player.Player who, Action action, BlockPos position, Direction direction, ItemStack itemstack, boolean cancelledBlock, boolean cancelledItem, InteractionHand hand, Vec3 targetPos) { ++ // Paper end - cancelledItem param + Player player = (who == null) ? null : (Player) who.getBukkitEntity(); + CraftItemStack itemInHand = CraftItemStack.asCraftMirror(itemstack); + +@@ -589,6 +595,11 @@ public class CraftEventFactory { + if (cancelledBlock) { + event.setUseInteractedBlock(Event.Result.DENY); + } ++ // Paper start ++ if (cancelledItem) { ++ event.setUseItemInHand(Result.DENY); ++ } ++ // Paper end + craftServer.getPluginManager().callEvent(event); + + return event; diff --git a/patches/server/0743-use-BlockFormEvent-for-mud-converting-into-clay.patch b/patches/server/0743-use-BlockFormEvent-for-mud-converting-into-clay.patch deleted file mode 100644 index 8653ffd78625..000000000000 --- a/patches/server/0743-use-BlockFormEvent-for-mud-converting-into-clay.patch +++ /dev/null @@ -1,25 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Legitimoose -Date: Thu, 29 Sep 2022 16:25:50 -0700 -Subject: [PATCH] use BlockFormEvent for mud converting into clay - - -diff --git a/src/main/java/net/minecraft/world/level/block/PointedDripstoneBlock.java b/src/main/java/net/minecraft/world/level/block/PointedDripstoneBlock.java -index 95cb7492ac691a8e8aa9894f701b802a7eda5446..a2bd54dae4b0460d200f6d5300194a7ef5a28830 100644 ---- a/src/main/java/net/minecraft/world/level/block/PointedDripstoneBlock.java -+++ b/src/main/java/net/minecraft/world/level/block/PointedDripstoneBlock.java -@@ -215,10 +215,13 @@ public class PointedDripstoneBlock extends Block implements Fallable, SimpleWate - if (((PointedDripstoneBlock.FluidInfo) optional.get()).sourceState.is(Blocks.MUD) && fluidtype == Fluids.WATER) { - BlockState iblockdata1 = Blocks.CLAY.defaultBlockState(); - -- world.setBlockAndUpdate(((PointedDripstoneBlock.FluidInfo) optional.get()).pos, iblockdata1); -+ // Paper start - Call BlockFormEvent -+ if (org.bukkit.craftbukkit.event.CraftEventFactory.handleBlockFormEvent(world, ((PointedDripstoneBlock.FluidInfo) optional.get()).pos, iblockdata1)) { - Block.pushEntitiesUp(((PointedDripstoneBlock.FluidInfo) optional.get()).sourceState, iblockdata1, world, ((PointedDripstoneBlock.FluidInfo) optional.get()).pos); - world.gameEvent((Holder) GameEvent.BLOCK_CHANGE, ((PointedDripstoneBlock.FluidInfo) optional.get()).pos, GameEvent.Context.of(iblockdata1)); - world.levelEvent(1504, blockposition1, 0); -+ } -+ // Paper end - Call BlockFormEvent - } else { - BlockPos blockposition2 = PointedDripstoneBlock.findFillableCauldronBelowStalactiteTip(world, blockposition1, fluidtype); - diff --git a/patches/server/0744-Add-PlayerInventorySlotChangeEvent.patch b/patches/server/0744-Add-PlayerInventorySlotChangeEvent.patch new file mode 100644 index 000000000000..cca34b90d083 --- /dev/null +++ b/patches/server/0744-Add-PlayerInventorySlotChangeEvent.patch @@ -0,0 +1,65 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Jakub Zacek +Date: Sun, 24 Apr 2022 22:56:59 +0200 +Subject: [PATCH] Add PlayerInventorySlotChangeEvent + + +diff --git a/src/main/java/net/minecraft/server/level/ServerPlayer.java b/src/main/java/net/minecraft/server/level/ServerPlayer.java +index 312225a15261f2e80fbf6133c75c567574ade181..04e3c75c9abfaccb6d2d59d234e5169258b77553 100644 +--- a/src/main/java/net/minecraft/server/level/ServerPlayer.java ++++ b/src/main/java/net/minecraft/server/level/ServerPlayer.java +@@ -381,6 +381,25 @@ public class ServerPlayer extends net.minecraft.world.entity.player.Player { + + } + } ++ // Paper start - Add PlayerInventorySlotChangeEvent ++ @Override ++ public void slotChanged(AbstractContainerMenu handler, int slotId, ItemStack oldStack, ItemStack stack) { ++ Slot slot = handler.getSlot(slotId); ++ if (!(slot instanceof ResultSlot)) { ++ if (slot.container == ServerPlayer.this.getInventory()) { ++ if (io.papermc.paper.event.player.PlayerInventorySlotChangeEvent.getHandlerList().getRegisteredListeners().length == 0) { ++ CriteriaTriggers.INVENTORY_CHANGED.trigger(ServerPlayer.this, ServerPlayer.this.getInventory(), stack); ++ return; ++ } ++ io.papermc.paper.event.player.PlayerInventorySlotChangeEvent event = new io.papermc.paper.event.player.PlayerInventorySlotChangeEvent(ServerPlayer.this.getBukkitEntity(), slotId, CraftItemStack.asBukkitCopy(oldStack), CraftItemStack.asBukkitCopy(stack)); ++ event.callEvent(); ++ if (event.shouldTriggerAdvancements()) { ++ CriteriaTriggers.INVENTORY_CHANGED.trigger(ServerPlayer.this, ServerPlayer.this.getInventory(), stack); ++ } ++ } ++ } ++ } ++ // Paper end - Add PlayerInventorySlotChangeEvent + + @Override + public void dataChanged(AbstractContainerMenu handler, int property, int value) {} +diff --git a/src/main/java/net/minecraft/world/inventory/AbstractContainerMenu.java b/src/main/java/net/minecraft/world/inventory/AbstractContainerMenu.java +index 3b92fb173f623a05ae99c86d98f2ecdf907f58c4..d830504d08c9de92797c432a868c1ee9dfc46a91 100644 +--- a/src/main/java/net/minecraft/world/inventory/AbstractContainerMenu.java ++++ b/src/main/java/net/minecraft/world/inventory/AbstractContainerMenu.java +@@ -330,7 +330,7 @@ public abstract class AbstractContainerMenu { + while (iterator.hasNext()) { + ContainerListener icrafting = (ContainerListener) iterator.next(); + +- icrafting.slotChanged(this, slot, itemstack2); ++ icrafting.slotChanged(this, slot, itemstack1, itemstack2); // Paper - Add PlayerInventorySlotChangeEvent + } + } + +diff --git a/src/main/java/net/minecraft/world/inventory/ContainerListener.java b/src/main/java/net/minecraft/world/inventory/ContainerListener.java +index 0e19cc55646625bf32a354d3df2dc2d6bcff96f4..eba024c9aadef8902aacccea1584ffbee6c04e44 100644 +--- a/src/main/java/net/minecraft/world/inventory/ContainerListener.java ++++ b/src/main/java/net/minecraft/world/inventory/ContainerListener.java +@@ -5,5 +5,11 @@ import net.minecraft.world.item.ItemStack; + public interface ContainerListener { + void slotChanged(AbstractContainerMenu handler, int slotId, ItemStack stack); + ++ // Paper start - Add PlayerInventorySlotChangeEvent ++ default void slotChanged(AbstractContainerMenu handler, int slotId, ItemStack oldStack, ItemStack stack) { ++ slotChanged(handler, slotId, stack); ++ } ++ // Paper end - Add PlayerInventorySlotChangeEvent ++ + void dataChanged(AbstractContainerMenu handler, int property, int value); + } diff --git a/patches/server/0745-Elder-Guardian-appearance-API.patch b/patches/server/0745-Elder-Guardian-appearance-API.patch new file mode 100644 index 000000000000..b7e5065d43d0 --- /dev/null +++ b/patches/server/0745-Elder-Guardian-appearance-API.patch @@ -0,0 +1,24 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: SoSeDiK +Date: Tue, 11 Oct 2022 20:38:47 +0300 +Subject: [PATCH] Elder Guardian appearance API + + +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java +index 2aaf9e87a7322392d713bd869b0bdddae88db7ff..dc09ddfb8e695606fe4dfd2594d75ab312ee9b78 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java +@@ -3322,6 +3322,13 @@ public class CraftPlayer extends CraftHumanEntity implements Player { + } + // Paper end + ++ // Paper start ++ @Override ++ public void showElderGuardian(boolean silent) { ++ if (getHandle().connection != null) getHandle().connection.send(new ClientboundGameEventPacket(ClientboundGameEventPacket.GUARDIAN_ELDER_EFFECT, silent ? 0F : 1F)); ++ } ++ // Paper end ++ + public Player.Spigot spigot() + { + return this.spigot; diff --git a/patches/server/0745-Fix-a-bunch-of-vanilla-bugs.patch b/patches/server/0745-Fix-a-bunch-of-vanilla-bugs.patch deleted file mode 100644 index 7813123f4940..000000000000 --- a/patches/server/0745-Fix-a-bunch-of-vanilla-bugs.patch +++ /dev/null @@ -1,425 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Jake Potrebic -Date: Mon, 11 Jul 2022 11:56:41 -0700 -Subject: [PATCH] Fix a bunch of vanilla bugs - -https://bugs.mojang.com/browse/MC-253721 - wrong msg for opping multiple players - -https://bugs.mojang.com/browse/MC-248588 - respect mob griefing gamerule for draining water cauldrons - -https://bugs.mojang.com/browse/MC-244739 - play goat eating sound for last item in stack - -https://bugs.mojang.com/browse/MC-243057 - ignore furnace fuel slot in recipe book click - -https://bugs.mojang.com/browse/MC-147659 - Some witch huts spawn the incorrect cat - Note: Marked as Won't Fix, makes 0 sense - -https://bugs.mojang.com/browse/MC-179072 - Creepers do not defuse when switching from Survival to Creative/Spectator - -https://bugs.mojang.com/browse/MC-259571 - Fix changeGameModeForPlayer to use gameModeForPlayer - -https://bugs.mojang.com/browse/MC-262422 - Fix lightning being able to hit spectators - -https://bugs.mojang.com/browse/MC-224454 - Fix mobs attempting to pathfind through azalea blocks - -https://bugs.mojang.com/browse/MC-263999 - Fix mobs breaking doors not spawning block break particles - -https://bugs.mojang.com/browse/MC-210802 - Fixes sheep eating blocks outside of ticking range - -https://bugs.mojang.com/browse/MC-123848 - Fixes item frames dropping items above when pointing down - -https://bugs.mojang.com/browse/MC-174630 - Fix secondary beacon effect remaining after switching effect - -https://bugs.mojang.com/browse/MC-153086 - Fix the beacon deactivation sound always playing when broken - -https://bugs.mojang.com/browse/MC-200092 - Fix yaw being ignored for a player's first spawn pos - -https://bugs.mojang.com/browse/MC-158900 - Fix error when joining after tempban expired - -https://bugs.mojang.com/browse/MC-99075 - Fix inventory desync within spawn protected area - -https://bugs.mojang.com/browse/MC-273635 - Fix TrialSpawner forgets assigned mob when placed by player - -== AT == -public net/minecraft/world/entity/Mob leashInfoTag - -Co-authored-by: William Blake Galbreath -Co-authored-by: Spottedleaf - -diff --git a/src/main/java/net/minecraft/server/commands/DeOpCommands.java b/src/main/java/net/minecraft/server/commands/DeOpCommands.java -index 0283f26151488d715dc823a0008c9a37ef6740fb..d98447e58233745665f0833196226077d972cc2a 100644 ---- a/src/main/java/net/minecraft/server/commands/DeOpCommands.java -+++ b/src/main/java/net/minecraft/server/commands/DeOpCommands.java -@@ -35,7 +35,7 @@ public class DeOpCommands { - if (playerList.isOp(gameProfile)) { - playerList.deop(gameProfile); - i++; -- source.sendSuccess(() -> Component.translatable("commands.deop.success", targets.iterator().next().getName()), true); -+ source.sendSuccess(() -> Component.translatable("commands.deop.success", gameProfile.getName()), true); // Paper - fixes MC-253721 - } - } - -diff --git a/src/main/java/net/minecraft/server/commands/OpCommand.java b/src/main/java/net/minecraft/server/commands/OpCommand.java -index 6854ca4d4fec2b4fa541c3fabf63787665572609..e7b444a10b244828827b3c66c53465206ea8e0ec 100644 ---- a/src/main/java/net/minecraft/server/commands/OpCommand.java -+++ b/src/main/java/net/minecraft/server/commands/OpCommand.java -@@ -46,7 +46,7 @@ public class OpCommand { - if (!playerList.isOp(gameProfile)) { - playerList.op(gameProfile); - i++; -- source.sendSuccess(() -> Component.translatable("commands.op.success", targets.iterator().next().getName()), true); -+ source.sendSuccess(() -> Component.translatable("commands.op.success", gameProfile.getName()), true); // Paper - fixes MC-253721 - } - } - -diff --git a/src/main/java/net/minecraft/server/level/ChunkMap.java b/src/main/java/net/minecraft/server/level/ChunkMap.java -index 071e9ef3680c5dc492c6142ccd05f6788ebc3035..61fda6927f060cdf8bcfddaaa08bbbe2c514c630 100644 ---- a/src/main/java/net/minecraft/server/level/ChunkMap.java -+++ b/src/main/java/net/minecraft/server/level/ChunkMap.java -@@ -1027,7 +1027,7 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider - // CraftBukkit end - } - -- boolean anyPlayerCloseEnoughForSpawning(ChunkPos pos) { -+ public boolean anyPlayerCloseEnoughForSpawning(ChunkPos pos) { // Paper - public - // Spigot start - return this.anyPlayerCloseEnoughForSpawning(pos, false); - } -diff --git a/src/main/java/net/minecraft/server/level/ServerLevel.java b/src/main/java/net/minecraft/server/level/ServerLevel.java -index b88a7f580b4138ceb262f3d398e78fdc2e6b018c..1edad85fc5c199dcab66497fa758e48dd14aec8c 100644 ---- a/src/main/java/net/minecraft/server/level/ServerLevel.java -+++ b/src/main/java/net/minecraft/server/level/ServerLevel.java -@@ -777,7 +777,7 @@ public class ServerLevel extends Level implements WorldGenLevel { - } else { - AABB axisalignedbb = AABB.encapsulatingFullBlocks(blockposition1, new BlockPos(blockposition1.atY(this.getMaxBuildHeight()))).inflate(3.0D); - List list = this.getEntitiesOfClass(LivingEntity.class, axisalignedbb, (entityliving) -> { -- return entityliving != null && entityliving.isAlive() && this.canSeeSky(entityliving.blockPosition()); -+ return entityliving != null && entityliving.isAlive() && this.canSeeSky(entityliving.blockPosition()) && !entityliving.isSpectator(); // Paper - Fix lightning being able to hit spectators (MC-262422) - }); - - if (!list.isEmpty()) { -diff --git a/src/main/java/net/minecraft/server/level/ServerPlayerGameMode.java b/src/main/java/net/minecraft/server/level/ServerPlayerGameMode.java -index 6abecaac8407b992d208a9108e11fd4954a4106f..03d89f326d320c5d778c3d1e2db7d6b88753faec 100644 ---- a/src/main/java/net/minecraft/server/level/ServerPlayerGameMode.java -+++ b/src/main/java/net/minecraft/server/level/ServerPlayerGameMode.java -@@ -93,7 +93,7 @@ public class ServerPlayerGameMode { - return event; // Paper - Expand PlayerGameModeChangeEvent - } - // CraftBukkit end -- this.setGameModeForPlayer(gameMode, this.previousGameModeForPlayer); -+ this.setGameModeForPlayer(gameMode, this.gameModeForPlayer); // Paper - Fix MC-259571 - this.player.onUpdateAbilities(); - this.player.server.getPlayerList().broadcastAll(new ClientboundPlayerInfoUpdatePacket(ClientboundPlayerInfoUpdatePacket.Action.UPDATE_GAME_MODE, this.player), this.player); // CraftBukkit - this.level.updateSleepingPlayerList(); -diff --git a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java -index ca32e5aa6e77ca1bab886e7b6a778ec931ac4e4c..28808ffc6e486f7dc01be370c9eb249dc1f7ea46 100644 ---- a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java -+++ b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java -@@ -1814,7 +1814,7 @@ public class ServerGamePacketListenerImpl extends ServerCommonPacketListenerImpl - } else if (enuminteractionresult.shouldSwing() && !this.player.gameMode.interactResult) { // Paper - Call interact event - this.player.swing(enumhand, true); - } -- } -+ } else { this.player.containerMenu.sendAllDataToRemote(); } // Paper - Fix inventory desync; MC-99075 - } else { - MutableComponent ichatmutablecomponent1 = Component.translatable("build.tooHigh", i - 1).withStyle(ChatFormatting.RED); - -diff --git a/src/main/java/net/minecraft/server/players/PlayerList.java b/src/main/java/net/minecraft/server/players/PlayerList.java -index 03572023ae7f061bf34fed8ac27852e08d6412c0..38b9ab369e25e4b718418375f76e782283b48411 100644 ---- a/src/main/java/net/minecraft/server/players/PlayerList.java -+++ b/src/main/java/net/minecraft/server/players/PlayerList.java -@@ -260,7 +260,7 @@ public abstract class PlayerList { - } - if (optional.isEmpty() || invalidPlayerWorld[0]) { - // Paper end - reset to main world spawn if first spawn or invalid world -- player.moveTo(player.adjustSpawnLocation(worldserver1, worldserver1.getSharedSpawnPos()).getBottomCenter(), 0.0F, 0.0F); -+ player.moveTo(player.adjustSpawnLocation(worldserver1, worldserver1.getSharedSpawnPos()).getBottomCenter(), worldserver1.getSharedSpawnAngle(), 0.0F); // Paper - MC-200092 - fix first spawn pos yaw being ignored - } - // Paper end - Entity#getEntitySpawnReason - player.setServerLevel(worldserver1); -@@ -703,8 +703,10 @@ public abstract class PlayerList { - Player player = entity.getBukkitEntity(); - PlayerLoginEvent event = new PlayerLoginEvent(player, loginlistener.connection.hostname, ((java.net.InetSocketAddress) socketaddress).getAddress(), ((java.net.InetSocketAddress) loginlistener.connection.channel.remoteAddress()).getAddress()); - -- if (this.bans.isBanned(gameprofile)) { -- UserBanListEntry gameprofilebanentry = (UserBanListEntry) this.bans.get(gameprofile); -+ // Paper start - Fix MC-158900 -+ UserBanListEntry gameprofilebanentry; -+ if (this.bans.isBanned(gameprofile) && (gameprofilebanentry = this.bans.get(gameprofile)) != null) { -+ // Paper end - Fix MC-158900 - - ichatmutablecomponent = Component.translatable("multiplayer.disconnect.banned.reason", gameprofilebanentry.getReason()); - if (gameprofilebanentry.getExpires() != null) { -diff --git a/src/main/java/net/minecraft/world/entity/ai/goal/BreakDoorGoal.java b/src/main/java/net/minecraft/world/entity/ai/goal/BreakDoorGoal.java -index 784a894688f98f9d0368a36d456c5c94e1ee3695..a85885ee51df585fa11ae9f8fcd67ff2a71c5a18 100644 ---- a/src/main/java/net/minecraft/world/entity/ai/goal/BreakDoorGoal.java -+++ b/src/main/java/net/minecraft/world/entity/ai/goal/BreakDoorGoal.java -@@ -77,9 +77,10 @@ public class BreakDoorGoal extends DoorInteractGoal { - return; - } - // CraftBukkit end -+ final net.minecraft.world.level.block.state.BlockState oldState = this.mob.level().getBlockState(this.doorPos); // Paper - fix MC-263999 - this.mob.level().removeBlock(this.doorPos, false); - this.mob.level().levelEvent(1021, this.doorPos, 0); -- this.mob.level().levelEvent(2001, this.doorPos, Block.getId(this.mob.level().getBlockState(this.doorPos))); -+ this.mob.level().levelEvent(2001, this.doorPos, Block.getId(oldState)); // Paper - fix MC-263999 - } - - } -diff --git a/src/main/java/net/minecraft/world/entity/ai/goal/EatBlockGoal.java b/src/main/java/net/minecraft/world/entity/ai/goal/EatBlockGoal.java -index d802985f1431be4332c07f0dab88feebedea4ce2..4e2c23ccdf4e4a4d65b291dbe20952bae1838bff 100644 ---- a/src/main/java/net/minecraft/world/entity/ai/goal/EatBlockGoal.java -+++ b/src/main/java/net/minecraft/world/entity/ai/goal/EatBlockGoal.java -@@ -31,6 +31,11 @@ public class EatBlockGoal extends Goal { - - @Override - public boolean canUse() { -+ // Paper start - Fix MC-210802 -+ if (!((net.minecraft.server.level.ServerLevel) this.level).chunkSource.chunkMap.anyPlayerCloseEnoughForSpawning(this.mob.chunkPosition())) { -+ return false; -+ } -+ // Paper end - if (this.mob.getRandom().nextInt(this.mob.isBaby() ? 50 : 1000) != 0) { - return false; - } else { -diff --git a/src/main/java/net/minecraft/world/entity/ai/goal/SwellGoal.java b/src/main/java/net/minecraft/world/entity/ai/goal/SwellGoal.java -index ef71b3ef4444c05b4211de87e1c8ec52cbe3e72a..137ec75ee803789deb7b1ca93dd9369c9af362b9 100644 ---- a/src/main/java/net/minecraft/world/entity/ai/goal/SwellGoal.java -+++ b/src/main/java/net/minecraft/world/entity/ai/goal/SwellGoal.java -@@ -21,6 +21,13 @@ public class SwellGoal extends Goal { - return this.creeper.getSwellDir() > 0 || livingEntity != null && this.creeper.distanceToSqr(livingEntity) < 9.0; - } - -+ // Paper start - Fix MC-179072 -+ @Override -+ public boolean canContinueToUse() { -+ return !net.minecraft.world.entity.EntitySelector.NO_CREATIVE_OR_SPECTATOR.test(this.creeper.getTarget()) && canUse(); -+ } -+ // Paper end -+ - @Override - public void start() { - this.creeper.getNavigation().stop(); -diff --git a/src/main/java/net/minecraft/world/entity/animal/goat/Goat.java b/src/main/java/net/minecraft/world/entity/animal/goat/Goat.java -index 923806900ef6248576e71260d40e9caf2c8943e8..02e49c7ae5e120302b6479cf3e3934b9217eebf0 100644 ---- a/src/main/java/net/minecraft/world/entity/animal/goat/Goat.java -+++ b/src/main/java/net/minecraft/world/entity/animal/goat/Goat.java -@@ -243,9 +243,10 @@ public class Goat extends Animal { - player.setItemInHand(hand, itemstack1); - return InteractionResult.sidedSuccess(this.level().isClientSide); - } else { -+ boolean isFood = this.isFood(itemstack); // Paper - track before stack is possibly decreased to 0 (Fixes MC-244739) - InteractionResult enuminteractionresult = super.mobInteract(player, hand); - -- if (enuminteractionresult.consumesAction() && this.isFood(itemstack)) { -+ if (enuminteractionresult.consumesAction() && isFood) { // Paper - this.level().playSound((Player) null, (Entity) this, this.getEatingSound(itemstack), SoundSource.NEUTRAL, 1.0F, Mth.randomBetween(this.level().random, 0.8F, 1.2F)); - } - -diff --git a/src/main/java/net/minecraft/world/entity/decoration/ItemFrame.java b/src/main/java/net/minecraft/world/entity/decoration/ItemFrame.java -index 3c6edc5ea44b7ec15d8fc7a2dca95a11a0d6108a..ba4e0ad7c7c2731d61ec7f60f19d7e9ec31a28ba 100644 ---- a/src/main/java/net/minecraft/world/entity/decoration/ItemFrame.java -+++ b/src/main/java/net/minecraft/world/entity/decoration/ItemFrame.java -@@ -260,6 +260,14 @@ public class ItemFrame extends HangingEntity { - return (ItemStack) this.getEntityData().get(ItemFrame.DATA_ITEM); - } - -+ // Paper start - Fix MC-123848 (spawn item frame drops above block) -+ @Nullable -+ @Override -+ public net.minecraft.world.entity.item.ItemEntity spawnAtLocation(ItemStack stack) { -+ return this.spawnAtLocation(stack, getDirection().equals(Direction.DOWN) ? -0.6F : 0.0F); -+ } -+ // Paper end -+ - @Nullable - public MapId getFramedMapId(ItemStack itemstack) { - return (MapId) itemstack.get(DataComponents.MAP_ID); -diff --git a/src/main/java/net/minecraft/world/entity/npc/CatSpawner.java b/src/main/java/net/minecraft/world/entity/npc/CatSpawner.java -index 08dcc94c9eca9a5fd61055f05b7737ba5840b5bf..e0e5046c84941a8d17e18c177f3daea9cb631940 100644 ---- a/src/main/java/net/minecraft/world/entity/npc/CatSpawner.java -+++ b/src/main/java/net/minecraft/world/entity/npc/CatSpawner.java -@@ -82,8 +82,8 @@ public class CatSpawner implements CustomSpawner { - if (cat == null) { - return 0; - } else { -+ cat.moveTo(pos, 0.0F, 0.0F); // Paper - move up - Fix MC-147659 - cat.finalizeSpawn(world, world.getCurrentDifficultyAt(pos), MobSpawnType.NATURAL, null); -- cat.moveTo(pos, 0.0F, 0.0F); - world.addFreshEntityWithPassengers(cat); - return 1; - } -diff --git a/src/main/java/net/minecraft/world/inventory/BeaconMenu.java b/src/main/java/net/minecraft/world/inventory/BeaconMenu.java -index 396559c281eee9e8c677cb222721414e8d9e12a2..8b0ebf659f6b219ce2a5d10b0d79f9b89b47c911 100644 ---- a/src/main/java/net/minecraft/world/inventory/BeaconMenu.java -+++ b/src/main/java/net/minecraft/world/inventory/BeaconMenu.java -@@ -178,6 +178,11 @@ public class BeaconMenu extends AbstractContainerMenu { - // Paper end - Add PlayerChangeBeaconEffectEvent - - public void updateEffects(Optional> primary, Optional> secondary) { -+ // Paper start - fix MC-174630 - validate secondary power -+ if (secondary.isPresent() && secondary.get() != net.minecraft.world.effect.MobEffects.REGENERATION && (primary.isPresent() && secondary.get() != primary.get())) { -+ secondary = Optional.empty(); -+ } -+ // Paper end - if (this.paymentSlot.hasItem()) { - // Paper start - Add PlayerChangeBeaconEffectEvent - io.papermc.paper.event.player.PlayerChangeBeaconEffectEvent event = new io.papermc.paper.event.player.PlayerChangeBeaconEffectEvent((org.bukkit.entity.Player) this.player.player.getBukkitEntity(), convert(primary), convert(secondary), this.access.getLocation().getBlock()); -diff --git a/src/main/java/net/minecraft/world/level/block/AzaleaBlock.java b/src/main/java/net/minecraft/world/level/block/AzaleaBlock.java -index cd90129be2cf8095abd80528e5de3bbe05022a9d..fad69dfc20574ab23634b14252b50929cca75b21 100644 ---- a/src/main/java/net/minecraft/world/level/block/AzaleaBlock.java -+++ b/src/main/java/net/minecraft/world/level/block/AzaleaBlock.java -@@ -51,4 +51,11 @@ public class AzaleaBlock extends BushBlock implements BonemealableBlock { - public void performBonemeal(ServerLevel world, RandomSource random, BlockPos pos, BlockState state) { - TreeGrower.AZALEA.growTree(world, world.getChunkSource().getGenerator(), pos, state, random); - } -+ -+ // Paper start - Fix MC-224454 -+ @Override -+ public boolean isPathfindable(BlockState state, net.minecraft.world.level.pathfinder.PathComputationType type) { -+ return false; -+ } -+ // Paper end - } -diff --git a/src/main/java/net/minecraft/world/level/block/LayeredCauldronBlock.java b/src/main/java/net/minecraft/world/level/block/LayeredCauldronBlock.java -index fa5366961861370c2366e6f0ff026a6d65128316..a5c7c2d24498c66159316a4f92677625975ce5ca 100644 ---- a/src/main/java/net/minecraft/world/level/block/LayeredCauldronBlock.java -+++ b/src/main/java/net/minecraft/world/level/block/LayeredCauldronBlock.java -@@ -69,7 +69,7 @@ public class LayeredCauldronBlock extends AbstractCauldronBlock { - if (!new io.papermc.paper.event.entity.EntityInsideBlockEvent(entity.getBukkitEntity(), org.bukkit.craftbukkit.block.CraftBlock.at(world, pos)).callEvent()) { return; } // Paper - Add EntityInsideBlockEvent - if (!world.isClientSide && entity.isOnFire() && this.isEntityInsideContent(state, pos, entity)) { - // CraftBukkit start -- if (entity.mayInteract(world, pos)) { -+ if ((entity instanceof net.minecraft.world.entity.player.Player || world.getGameRules().getBoolean(net.minecraft.world.level.GameRules.RULE_MOBGRIEFING)) && entity.mayInteract(world, pos)) { // Paper - Fixes MC-248588 - if (!this.handleEntityOnFireInsideWithEvent(state, world, pos, entity)) { // Paper - fix powdered snow cauldron extinguishing entities - return; - } -diff --git a/src/main/java/net/minecraft/world/level/block/entity/AbstractFurnaceBlockEntity.java b/src/main/java/net/minecraft/world/level/block/entity/AbstractFurnaceBlockEntity.java -index aa59a45bcbe652996eddb80a3187618b7f6d1234..f1a7054a0ee1842e78338d8984f151b24c352849 100644 ---- a/src/main/java/net/minecraft/world/level/block/entity/AbstractFurnaceBlockEntity.java -+++ b/src/main/java/net/minecraft/world/level/block/entity/AbstractFurnaceBlockEntity.java -@@ -648,13 +648,10 @@ public abstract class AbstractFurnaceBlockEntity extends BaseContainerBlockEntit - - @Override - public void fillStackedContents(StackedContents finder) { -- Iterator iterator = this.items.iterator(); -- -- while (iterator.hasNext()) { -- ItemStack itemstack = (ItemStack) iterator.next(); -- -- finder.accountStack(itemstack); -- } -+ // Paper start - don't account fuel stack (fixes MC-243057) -+ finder.accountStack(this.items.get(SLOT_INPUT)); -+ finder.accountStack(this.items.get(SLOT_RESULT)); -+ // Paper end - - } - } -diff --git a/src/main/java/net/minecraft/world/level/block/entity/BeaconBlockEntity.java b/src/main/java/net/minecraft/world/level/block/entity/BeaconBlockEntity.java -index e124f040386e130aebd7135434c4f06d130d28f6..f8d432ef21e59796da4b11c9748ba151c54e5e04 100644 ---- a/src/main/java/net/minecraft/world/level/block/entity/BeaconBlockEntity.java -+++ b/src/main/java/net/minecraft/world/level/block/entity/BeaconBlockEntity.java -@@ -295,7 +295,11 @@ public class BeaconBlockEntity extends BlockEntity implements MenuProvider, Name - org.bukkit.block.Block block = org.bukkit.craftbukkit.block.CraftBlock.at(level, worldPosition); - new io.papermc.paper.event.block.BeaconDeactivatedEvent(block).callEvent(); - // Paper end - beacon activation/deactivation events -+ // Paper start - fix MC-153086 -+ if (this.levels > 0 && !this.beamSections.isEmpty()) { - BeaconBlockEntity.playSound(this.level, this.worldPosition, SoundEvents.BEACON_DEACTIVATE); -+ } -+ // Paper end - super.setRemoved(); - } - -diff --git a/src/main/java/net/minecraft/world/level/block/entity/trialspawner/TrialSpawnerData.java b/src/main/java/net/minecraft/world/level/block/entity/trialspawner/TrialSpawnerData.java -index 055f4b87c01ee7ecf7d2a111b72cc5aa85d9fbe8..6684ded7135f943f8cea954b417f596369215357 100644 ---- a/src/main/java/net/minecraft/world/level/block/entity/trialspawner/TrialSpawnerData.java -+++ b/src/main/java/net/minecraft/world/level/block/entity/trialspawner/TrialSpawnerData.java -@@ -100,13 +100,13 @@ public class TrialSpawnerData { - this.ejectingLootTable = rewardLootTable; - } - -- public void reset() { -+ public void reset(TrialSpawner logic) { // Paper - Fix TrialSpawner forgets assigned mob; MC-273635 - this.detectedPlayers.clear(); - this.totalMobsSpawned = 0; - this.nextMobSpawnsAt = 0L; - this.cooldownEndsAt = 0L; - this.currentMobs.clear(); -- this.nextSpawnData = Optional.empty(); -+ if (!logic.getConfig().spawnPotentialsDefinition().isEmpty()) this.nextSpawnData = Optional.empty(); // Paper - Fix TrialSpawner forgets assigned mob; MC-273635 - } - - public boolean hasMobToSpawn(TrialSpawner logic, RandomSource random) { -diff --git a/src/main/java/net/minecraft/world/level/block/entity/trialspawner/TrialSpawnerState.java b/src/main/java/net/minecraft/world/level/block/entity/trialspawner/TrialSpawnerState.java -index ffabcfaef8defe932473dee515f05220921904e0..005aea8a747e9cbbc352b8b57c64b84ec71ad321 100644 ---- a/src/main/java/net/minecraft/world/level/block/entity/trialspawner/TrialSpawnerState.java -+++ b/src/main/java/net/minecraft/world/level/block/entity/trialspawner/TrialSpawnerState.java -@@ -68,7 +68,7 @@ public enum TrialSpawnerState implements StringRepresentable { - case INACTIVE -> trialSpawnerData.getOrCreateDisplayEntity(logic, world, WAITING_FOR_PLAYERS) == null ? this : WAITING_FOR_PLAYERS; - case WAITING_FOR_PLAYERS -> { - if (!logic.canSpawnInLevel(world)) { -- trialSpawnerData.reset(); -+ trialSpawnerData.reset(logic); // Paper - Fix TrialSpawner forgets assigned mob; MC-273635 - yield this; - } else if (!trialSpawnerData.hasMobToSpawn(logic, world.random)) { - yield INACTIVE; -@@ -79,7 +79,7 @@ public enum TrialSpawnerState implements StringRepresentable { - } - case ACTIVE -> { - if (!logic.canSpawnInLevel(world)) { -- trialSpawnerData.reset(); -+ trialSpawnerData.reset(logic); // Paper - Fix TrialSpawner forgets assigned mob; MC-273635 - yield WAITING_FOR_PLAYERS; - } else if (!trialSpawnerData.hasMobToSpawn(logic, world.random)) { - yield INACTIVE; -@@ -145,7 +145,7 @@ public enum TrialSpawnerState implements StringRepresentable { - yield ACTIVE; - } else if (trialSpawnerData.isCooldownFinished(world)) { - logic.removeOminous(world, pos); -- trialSpawnerData.reset(); -+ trialSpawnerData.reset(logic); // Paper - Fix TrialSpawner forgets assigned mob; MC-273635 - yield WAITING_FOR_PLAYERS; - } else { - yield this; -diff --git a/src/main/java/net/minecraft/world/level/portal/DimensionTransition.java b/src/main/java/net/minecraft/world/level/portal/DimensionTransition.java -index 1595a379875abc718659f6b1ce7c64c6383b1bc8..788f79dc38012595b385ee6a449daa0bccf0079a 100644 ---- a/src/main/java/net/minecraft/world/level/portal/DimensionTransition.java -+++ b/src/main/java/net/minecraft/world/level/portal/DimensionTransition.java -@@ -39,7 +39,7 @@ public record DimensionTransition(ServerLevel newLevel, Vec3 pos, Vec3 speed, fl - } - - public DimensionTransition(ServerLevel worldserver, Entity entity, DimensionTransition.PostDimensionTransition dimensiontransition_a, PlayerTeleportEvent.TeleportCause cause) { -- this(worldserver, findAdjustedSharedSpawnPos(worldserver, entity), Vec3.ZERO, 0.0F, 0.0F, false, dimensiontransition_a, cause); -+ this(worldserver, findAdjustedSharedSpawnPos(worldserver, entity), Vec3.ZERO, worldserver.getSharedSpawnAngle(), 0.0F, false, dimensiontransition_a, cause); // Paper - MC-200092 - fix spawn pos yaw being ignored - // CraftBukkit end - } - -@@ -55,7 +55,7 @@ public record DimensionTransition(ServerLevel newLevel, Vec3 pos, Vec3 speed, fl - } - - public static DimensionTransition missingRespawnBlock(ServerLevel world, Entity entity, DimensionTransition.PostDimensionTransition postDimensionTransition) { -- return new DimensionTransition(world, findAdjustedSharedSpawnPos(world, entity), Vec3.ZERO, 0.0F, 0.0F, true, postDimensionTransition); -+ return new DimensionTransition(world, findAdjustedSharedSpawnPos(world, entity), Vec3.ZERO, world.getSharedSpawnAngle(), 0.0F, true, postDimensionTransition); // Paper - MC-200092 - fix spawn pos yaw being ignored - } - - private static Vec3 findAdjustedSharedSpawnPos(ServerLevel world, Entity entity) { diff --git a/patches/server/0746-Add-entity-knockback-API.patch b/patches/server/0746-Add-entity-knockback-API.patch new file mode 100644 index 000000000000..71f4427274ab --- /dev/null +++ b/patches/server/0746-Add-entity-knockback-API.patch @@ -0,0 +1,23 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: MelnCat +Date: Sun, 16 Oct 2022 12:10:17 -0700 +Subject: [PATCH] Add entity knockback API + + +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftLivingEntity.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftLivingEntity.java +index 14b4c3835388d957653ba34444968bb718ce7f68..4fa19ddb1414282020e118eea298d57d2bf42754 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftLivingEntity.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftLivingEntity.java +@@ -1146,4 +1146,12 @@ public class CraftLivingEntity extends CraftEntity implements LivingEntity { + throw new UnsupportedOperationException("Cannot set the hurt direction on a non player"); + } + // Paper end - hurt direction API ++ ++ // Paper start - knockback API ++ @Override ++ public void knockback(final double strength, final double directionX, final double directionZ) { ++ Preconditions.checkArgument(strength > 0, "Knockback strength must be > 0"); ++ this.getHandle().knockback(strength, directionX, directionZ); ++ }; ++ // Paper end - knockback API + } diff --git a/patches/server/0746-Remove-unnecessary-onTrackingStart-during-navigation.patch b/patches/server/0746-Remove-unnecessary-onTrackingStart-during-navigation.patch deleted file mode 100644 index 104738383c0a..000000000000 --- a/patches/server/0746-Remove-unnecessary-onTrackingStart-during-navigation.patch +++ /dev/null @@ -1,28 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Nassim Jahnke -Date: Mon, 3 Oct 2022 20:48:19 +0200 -Subject: [PATCH] Remove unnecessary onTrackingStart during navigation warning - - -diff --git a/src/main/java/net/minecraft/server/level/ServerLevel.java b/src/main/java/net/minecraft/server/level/ServerLevel.java -index d3707c3fabc09068f3b07edebe9a333c153dfab5..4bf6281a75597072b19658208e4447c4d1ee8ba2 100644 ---- a/src/main/java/net/minecraft/server/level/ServerLevel.java -+++ b/src/main/java/net/minecraft/server/level/ServerLevel.java -@@ -2186,7 +2186,7 @@ public class ServerLevel extends Level implements WorldGenLevel { - } - - if (entity instanceof Mob entityinsentient) { -- if (ServerLevel.this.isUpdatingNavigations) { -+ if (false && ServerLevel.this.isUpdatingNavigations) { // Paper - Remove unnecessary onTrackingStart during navigation warning - String s = "onTrackingStart called during navigation iteration"; - - Util.logAndPauseIfInIde("onTrackingStart called during navigation iteration", new IllegalStateException("onTrackingStart called during navigation iteration")); -@@ -2266,7 +2266,7 @@ public class ServerLevel extends Level implements WorldGenLevel { - } - - if (entity instanceof Mob entityinsentient) { -- if (ServerLevel.this.isUpdatingNavigations) { -+ if (false && ServerLevel.this.isUpdatingNavigations) { // Paper - Remove unnecessary onTrackingStart during navigation warning - String s = "onTrackingStart called during navigation iteration"; - - Util.logAndPauseIfInIde("onTrackingStart called during navigation iteration", new IllegalStateException("onTrackingStart called during navigation iteration")); diff --git a/patches/server/0747-Detect-headless-JREs.patch b/patches/server/0747-Detect-headless-JREs.patch new file mode 100644 index 000000000000..e680b6146f85 --- /dev/null +++ b/patches/server/0747-Detect-headless-JREs.patch @@ -0,0 +1,51 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Noah van der Aa +Date: Sat, 22 Oct 2022 14:47:45 +0200 +Subject: [PATCH] Detect headless JREs + +Crashes caused by the missing AWT dependency come up in the support channels fairly often. +This patch detects the missing dependency and stops the server with a clear error message, +containing a link to instructions on how to install a non-headless JRE. + +diff --git a/src/main/java/io/papermc/paper/util/ServerEnvironment.java b/src/main/java/io/papermc/paper/util/ServerEnvironment.java +index 68098dfe716e93aafcca4d8d5b5a81d8648b3654..2b7070e0cefa7cf0777df159693750fea14e800b 100644 +--- a/src/main/java/io/papermc/paper/util/ServerEnvironment.java ++++ b/src/main/java/io/papermc/paper/util/ServerEnvironment.java +@@ -20,4 +20,14 @@ public class ServerEnvironment { + public static boolean userIsRootOrAdmin() { + return RUNNING_AS_ROOT_OR_ADMIN; + } ++ ++ public static String awtDependencyCheck() { ++ try { ++ new java.awt.Color(0); ++ } catch (UnsatisfiedLinkError e) { ++ return e.getClass().getName() + ": " + e.getMessage(); ++ } ++ ++ return null; ++ } + } +diff --git a/src/main/java/net/minecraft/server/Main.java b/src/main/java/net/minecraft/server/Main.java +index cf071610ed662c4a309cc26ee73a74fa490d846f..47cd7c66ac37efebf2f63c49d78dd8fe44a70ef8 100644 +--- a/src/main/java/net/minecraft/server/Main.java ++++ b/src/main/java/net/minecraft/server/Main.java +@@ -167,6 +167,18 @@ public class Main { + return; + } + ++ // Paper start - Detect headless JRE ++ String awtException = io.papermc.paper.util.ServerEnvironment.awtDependencyCheck(); ++ if (awtException != null) { ++ Main.LOGGER.error("You are using a headless JRE distribution."); ++ Main.LOGGER.error("This distribution is missing certain graphic libraries that the Minecraft server needs to function."); ++ Main.LOGGER.error("For instructions on how to install the non-headless JRE, see https://docs.papermc.io/misc/java-install"); ++ Main.LOGGER.error(""); ++ Main.LOGGER.error(awtException); ++ return; ++ } ++ // Paper end - Detect headless JRE ++ + org.spigotmc.SpigotConfig.disabledAdvancements = spigotConfiguration.getStringList("advancements.disabled"); // Paper - fix SPIGOT-5885, must be set early in init + // Paper start - fix SPIGOT-5824 + File file; diff --git a/patches/server/0747-Fix-custom-piglin-loved-items.patch b/patches/server/0747-Fix-custom-piglin-loved-items.patch deleted file mode 100644 index a09a51f7ea71..000000000000 --- a/patches/server/0747-Fix-custom-piglin-loved-items.patch +++ /dev/null @@ -1,21 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Jake Potrebic -Date: Mon, 4 Jul 2022 21:50:44 -0700 -Subject: [PATCH] Fix custom piglin loved items - -Upstream didn't modify the isLovedItem check in wantsToPickup -so piglins never actually tried to pickup interestItems - -diff --git a/src/main/java/net/minecraft/world/entity/monster/piglin/PiglinAi.java b/src/main/java/net/minecraft/world/entity/monster/piglin/PiglinAi.java -index d601bff8e8f62af78791ad357b51b92faf04e55f..d3d50ec0f4466464c048449d8a844569c447d59b 100644 ---- a/src/main/java/net/minecraft/world/entity/monster/piglin/PiglinAi.java -+++ b/src/main/java/net/minecraft/world/entity/monster/piglin/PiglinAi.java -@@ -403,7 +403,7 @@ public class PiglinAi { - } else { - boolean flag = piglin.canAddToInventory(stack); - -- return stack.is(Items.GOLD_NUGGET) ? flag : (PiglinAi.isFood(stack) ? !PiglinAi.hasEatenRecently(piglin) && flag : (!PiglinAi.isLovedItem(stack) ? piglin.canReplaceCurrentItem(stack) : PiglinAi.isNotHoldingLovedItemInOffHand(piglin) && flag)); -+ return stack.is(Items.GOLD_NUGGET) ? flag : (PiglinAi.isFood(stack) ? !PiglinAi.hasEatenRecently(piglin) && flag : (!PiglinAi.isLovedItem(stack, piglin) ? piglin.canReplaceCurrentItem(stack) : PiglinAi.isNotHoldingLovedItemInOffHand(piglin) && flag)); // Paper - upstream missed isLovedItem check - } - } - diff --git a/patches/server/0748-EntityPickupItemEvent-fixes.patch b/patches/server/0748-EntityPickupItemEvent-fixes.patch deleted file mode 100644 index 91c0eac860d2..000000000000 --- a/patches/server/0748-EntityPickupItemEvent-fixes.patch +++ /dev/null @@ -1,73 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Jake Potrebic -Date: Mon, 4 Jul 2022 21:45:36 -0700 -Subject: [PATCH] EntityPickupItemEvent fixes - -Fixes double firing of the event in PiglinAi - -Fixes cancelling the event for piglins still triggering the -advancement trigger - -Fires the event when a Raider tries to pick up a raid banner -to become raid leader. - -diff --git a/src/main/java/net/minecraft/world/entity/monster/piglin/Piglin.java b/src/main/java/net/minecraft/world/entity/monster/piglin/Piglin.java -index d2dfa49e124460f4762b950f9ded106d2ec15dc2..bc58323801ee16fe9b63c21332144ec002a902f2 100644 ---- a/src/main/java/net/minecraft/world/entity/monster/piglin/Piglin.java -+++ b/src/main/java/net/minecraft/world/entity/monster/piglin/Piglin.java -@@ -412,7 +412,7 @@ public class Piglin extends AbstractPiglin implements CrossbowAttackMob, Invento - - @Override - protected void pickUpItem(ItemEntity item) { -- this.onItemPickup(item); -+ // this.onItemPickup(item); // Paper - EntityPickupItemEvent fixes; call in PiglinAi#pickUpItem after EntityPickupItemEvent is fired - PiglinAi.pickUpItem(this, item); - } - -diff --git a/src/main/java/net/minecraft/world/entity/monster/piglin/PiglinAi.java b/src/main/java/net/minecraft/world/entity/monster/piglin/PiglinAi.java -index d3d50ec0f4466464c048449d8a844569c447d59b..b9810a1f6ac91ae9631dd1ebc225f009d91b7845 100644 ---- a/src/main/java/net/minecraft/world/entity/monster/piglin/PiglinAi.java -+++ b/src/main/java/net/minecraft/world/entity/monster/piglin/PiglinAi.java -@@ -242,11 +242,16 @@ public class PiglinAi { - ItemStack itemstack; - - // CraftBukkit start -- if (drop.getItem().is(Items.GOLD_NUGGET) && !org.bukkit.craftbukkit.event.CraftEventFactory.callEntityPickupItemEvent(piglin, drop, 0, false).isCancelled()) { -+ // Paper start - EntityPickupItemEvent fixes; fix event firing twice -+ if (drop.getItem().is(Items.GOLD_NUGGET) /* && !org.bukkit.craftbukkit.event.CraftEventFactory.callEntityPickupItemEvent(piglin, drop, 0, false).isCancelled() */) { -+ if (org.bukkit.craftbukkit.event.CraftEventFactory.callEntityPickupItemEvent(piglin, drop, 0, false).isCancelled()) return; -+ piglin.onItemPickup(drop); // Paper - moved from Piglin#pickUpItem - call prior to item entity modification -+ // Paper end - EntityPickupItemEvent fixes - piglin.take(drop, drop.getItem().getCount()); - itemstack = drop.getItem(); - drop.discard(EntityRemoveEvent.Cause.PICKUP); // CraftBukkit - add Bukkit remove cause - } else if (!org.bukkit.craftbukkit.event.CraftEventFactory.callEntityPickupItemEvent(piglin, drop, drop.getItem().getCount() - 1, false).isCancelled()) { -+ piglin.onItemPickup(drop); // Paper - EntityPickupItemEvent fixes; moved from Piglin#pickUpItem - call prior to item entity modification - piglin.take(drop, 1); - itemstack = PiglinAi.removeOneItemFromItemEntity(drop); - } else { -@@ -261,7 +266,7 @@ public class PiglinAi { - } else if (PiglinAi.isFood(itemstack) && !PiglinAi.hasEatenRecently(piglin)) { - PiglinAi.eat(piglin); - } else { -- boolean flag = !piglin.equipItemIfPossible(itemstack, drop).equals(ItemStack.EMPTY); // CraftBukkit -+ boolean flag = !piglin.equipItemIfPossible(itemstack, null).equals(ItemStack.EMPTY); // CraftBukkit // Paper - pass null item entity to prevent duplicate pickup item event call - called above. - - if (!flag) { - PiglinAi.putInInventory(piglin, itemstack); -diff --git a/src/main/java/net/minecraft/world/entity/raid/Raider.java b/src/main/java/net/minecraft/world/entity/raid/Raider.java -index 174d246b0a4d0fc9d769aad08da627ca8487bdf2..bbf21ea433f9e3963aac0ede597ed8d3c8e50ed8 100644 ---- a/src/main/java/net/minecraft/world/entity/raid/Raider.java -+++ b/src/main/java/net/minecraft/world/entity/raid/Raider.java -@@ -225,6 +225,11 @@ public abstract class Raider extends PatrollingMonster { - boolean flag = this.hasActiveRaid() && this.getCurrentRaid().getLeader(this.getWave()) != null; - - if (this.hasActiveRaid() && !flag && ItemStack.matches(itemstack, Raid.getLeaderBannerInstance(this.registryAccess().lookupOrThrow(Registries.BANNER_PATTERN)))) { -+ // Paper start - EntityPickupItemEvent fixes -+ if (org.bukkit.craftbukkit.event.CraftEventFactory.callEntityPickupItemEvent(this, item, 0, false).isCancelled()) { -+ return; -+ } -+ // Paper end - EntityPickupItemEvent fixes - EquipmentSlot enumitemslot = EquipmentSlot.HEAD; - ItemStack itemstack1 = this.getItemBySlot(enumitemslot); - double d0 = (double) this.getEquipmentDropChance(enumitemslot); diff --git a/patches/server/0748-fix-entity-vehicle-collision-event-not-called.patch b/patches/server/0748-fix-entity-vehicle-collision-event-not-called.patch new file mode 100644 index 000000000000..9b38b3208643 --- /dev/null +++ b/patches/server/0748-fix-entity-vehicle-collision-event-not-called.patch @@ -0,0 +1,27 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: lukas81298 +Date: Tue, 12 Jan 2021 14:41:38 +0100 +Subject: [PATCH] fix entity vehicle collision event not called + + +diff --git a/src/main/java/net/minecraft/world/entity/vehicle/AbstractMinecart.java b/src/main/java/net/minecraft/world/entity/vehicle/AbstractMinecart.java +index ee7350e19a86ffa115e4bce6b186a2422951e89b..d8fcd6d1edec1f31a861fab4b86cbeb15ddc799d 100644 +--- a/src/main/java/net/minecraft/world/entity/vehicle/AbstractMinecart.java ++++ b/src/main/java/net/minecraft/world/entity/vehicle/AbstractMinecart.java +@@ -157,7 +157,15 @@ public abstract class AbstractMinecart extends VehicleEntity { + + @Override + public boolean canCollideWith(Entity other) { +- return AbstractBoat.canVehicleCollide(this, other); ++ // Paper start - fix VehicleEntityCollisionEvent not called when colliding with player ++ boolean collides = AbstractBoat.canVehicleCollide(this, other); ++ if (!collides) { ++ return false; ++ } ++ org.bukkit.event.vehicle.VehicleEntityCollisionEvent collisionEvent = new org.bukkit.event.vehicle.VehicleEntityCollisionEvent((org.bukkit.entity.Vehicle) getBukkitEntity(), other.getBukkitEntity()); ++ ++ return collisionEvent.callEvent(); ++ // Paper end - fix VehicleEntityCollisionEvent not called when colliding with player + } + + @Override diff --git a/patches/server/0749-Add-EntityToggleSitEvent.patch b/patches/server/0749-Add-EntityToggleSitEvent.patch new file mode 100644 index 000000000000..7bab6af93d78 --- /dev/null +++ b/patches/server/0749-Add-EntityToggleSitEvent.patch @@ -0,0 +1,100 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: KyGuy2002 +Date: Fri, 11 Mar 2022 15:33:10 +0000 +Subject: [PATCH] Add EntityToggleSitEvent + + +diff --git a/src/main/java/net/minecraft/world/entity/TamableAnimal.java b/src/main/java/net/minecraft/world/entity/TamableAnimal.java +index 749ae54ee42229cb32ec5280bc59a88f74fde197..f6a253e063f4a2cf78a036e44431806a0ba270d9 100644 +--- a/src/main/java/net/minecraft/world/entity/TamableAnimal.java ++++ b/src/main/java/net/minecraft/world/entity/TamableAnimal.java +@@ -87,7 +87,7 @@ public abstract class TamableAnimal extends Animal implements OwnableEntity { + } + + this.orderedToSit = nbt.getBoolean("Sitting"); +- this.setInSittingPose(this.orderedToSit); ++ this.setInSittingPose(this.orderedToSit, false); // Paper - Add EntityToggleSitEvent + } + + @Override +@@ -167,6 +167,12 @@ public abstract class TamableAnimal extends Animal implements OwnableEntity { + } + + public void setInSittingPose(boolean inSittingPose) { ++ // Paper start - Add EntityToggleSitEvent ++ this.setInSittingPose(inSittingPose, true); ++ } ++ public void setInSittingPose(boolean inSittingPose, boolean callEvent) { ++ if (callEvent && !new io.papermc.paper.event.entity.EntityToggleSitEvent(this.getBukkitEntity(), inSittingPose).callEvent()) return; ++ // Paper end - Add EntityToggleSitEvent + byte b0 = (Byte) this.entityData.get(TamableAnimal.DATA_FLAGS_ID); + + if (inSittingPose) { +diff --git a/src/main/java/net/minecraft/world/entity/animal/Fox.java b/src/main/java/net/minecraft/world/entity/animal/Fox.java +index 67ea374ae3c66af434b4aadbe702a44d230fbe09..a9a8ebb2cebe668628d5bdb33fa1399e0ab1e08b 100644 +--- a/src/main/java/net/minecraft/world/entity/animal/Fox.java ++++ b/src/main/java/net/minecraft/world/entity/animal/Fox.java +@@ -419,7 +419,7 @@ public class Fox extends Animal implements VariantHolder { + + this.setSleeping(nbt.getBoolean("Sleeping")); + this.setVariant(Fox.Variant.byName(nbt.getString("Type"))); +- this.setSitting(nbt.getBoolean("Sitting")); ++ this.setSitting(nbt.getBoolean("Sitting"), false); // Paper - Add EntityToggleSitEvent + this.setIsCrouching(nbt.getBoolean("Crouching")); + if (this.level() instanceof ServerLevel) { + this.setTargetGoals(); +@@ -432,6 +432,12 @@ public class Fox extends Animal implements VariantHolder { + } + + public void setSitting(boolean sitting) { ++ // Paper start - Add EntityToggleSitEvent ++ this.setSitting(sitting, true); ++ } ++ public void setSitting(boolean sitting, boolean fireEvent) { ++ if (fireEvent && !new io.papermc.paper.event.entity.EntityToggleSitEvent(this.getBukkitEntity(), sitting).callEvent()) return; ++ // Paper end - Add EntityToggleSitEvent + this.setFlag(1, sitting); + } + +diff --git a/src/main/java/net/minecraft/world/entity/animal/Panda.java b/src/main/java/net/minecraft/world/entity/animal/Panda.java +index 4f04170b3ec4ff59358e10ccfd0799af3ab590c3..b654bec0fbe903fac24f3bb99399455bf367c68a 100644 +--- a/src/main/java/net/minecraft/world/entity/animal/Panda.java ++++ b/src/main/java/net/minecraft/world/entity/animal/Panda.java +@@ -134,6 +134,7 @@ public class Panda extends Animal { + } + + public void sit(boolean sitting) { ++ if (!new io.papermc.paper.event.entity.EntityToggleSitEvent(this.getBukkitEntity(), sitting).callEvent()) return; // Paper - Add EntityToggleSitEvent + this.setFlag(8, sitting); + } + +diff --git a/src/main/java/net/minecraft/world/entity/animal/camel/Camel.java b/src/main/java/net/minecraft/world/entity/animal/camel/Camel.java +index b6baa40dac6d6bf40c4ee7ee75b8a8e6f60733a1..c99d37a40c63726c11980adccc67d09fd5132885 100644 +--- a/src/main/java/net/minecraft/world/entity/animal/camel/Camel.java ++++ b/src/main/java/net/minecraft/world/entity/animal/camel/Camel.java +@@ -572,7 +572,7 @@ public class Camel extends AbstractHorse { + } + + public void sitDown() { +- if (!this.isCamelSitting()) { ++ if (!this.isCamelSitting() && new io.papermc.paper.event.entity.EntityToggleSitEvent(this.getBukkitEntity(), true).callEvent()) { // Paper - Add EntityToggleSitEvent + this.makeSound(SoundEvents.CAMEL_SIT); + this.setPose(Pose.SITTING); + this.gameEvent(GameEvent.ENTITY_ACTION); +@@ -581,7 +581,7 @@ public class Camel extends AbstractHorse { + } + + public void standUp() { +- if (this.isCamelSitting()) { ++ if (this.isCamelSitting() && new io.papermc.paper.event.entity.EntityToggleSitEvent(this.getBukkitEntity(), false).callEvent()) { // Paper - Add EntityToggleSitEvent + this.makeSound(SoundEvents.CAMEL_STAND); + this.setPose(Pose.STANDING); + this.gameEvent(GameEvent.ENTITY_ACTION); +@@ -590,6 +590,7 @@ public class Camel extends AbstractHorse { + } + + public void standUpInstantly() { ++ if (this.isCamelSitting() && !new io.papermc.paper.event.entity.EntityToggleSitEvent(this.getBukkitEntity(), false).callEvent()) return; // Paper - Add EntityToggleSitEvent + this.setPose(Pose.STANDING); + this.gameEvent(GameEvent.ENTITY_ACTION); + this.resetLastPoseChangeTickToFullStand(this.level().getGameTime()); diff --git a/patches/server/0749-Correctly-handle-interactions-with-items-on-cooldown.patch b/patches/server/0749-Correctly-handle-interactions-with-items-on-cooldown.patch deleted file mode 100644 index b7fa362a4bce..000000000000 --- a/patches/server/0749-Correctly-handle-interactions-with-items-on-cooldown.patch +++ /dev/null @@ -1,60 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Jake Potrebic -Date: Thu, 16 Jun 2022 21:57:02 -0700 -Subject: [PATCH] Correctly handle interactions with items on cooldown - - -diff --git a/src/main/java/net/minecraft/server/level/ServerPlayerGameMode.java b/src/main/java/net/minecraft/server/level/ServerPlayerGameMode.java -index 03d89f326d320c5d778c3d1e2db7d6b88753faec..717d015dd4637dd9d568b751be1dc1046b0a0560 100644 ---- a/src/main/java/net/minecraft/server/level/ServerPlayerGameMode.java -+++ b/src/main/java/net/minecraft/server/level/ServerPlayerGameMode.java -@@ -514,6 +514,7 @@ public class ServerPlayerGameMode { - BlockPos blockposition = hitResult.getBlockPos(); - BlockState iblockdata = world.getBlockState(blockposition); - boolean cancelledBlock = false; -+ boolean cancelledItem = false; // Paper - correctly handle items on cooldown - - if (!iblockdata.getBlock().isEnabled(world.enabledFeatures())) { - return InteractionResult.FAIL; -@@ -523,10 +524,10 @@ public class ServerPlayerGameMode { - } - - if (player.getCooldowns().isOnCooldown(stack.getItem())) { -- cancelledBlock = true; -+ cancelledItem = true; // Paper - correctly handle items on cooldown - } - -- PlayerInteractEvent event = CraftEventFactory.callPlayerInteractEvent(player, Action.RIGHT_CLICK_BLOCK, blockposition, hitResult.getDirection(), stack, cancelledBlock, hand, hitResult.getLocation()); -+ PlayerInteractEvent event = CraftEventFactory.callPlayerInteractEvent(player, Action.RIGHT_CLICK_BLOCK, blockposition, hitResult.getDirection(), stack, cancelledBlock, cancelledItem, hand, hitResult.getLocation()); // Paper - correctly handle items on cooldown - this.firedInteract = true; - this.interactResult = event.useItemInHand() == Event.Result.DENY; - this.interactPosition = blockposition.immutable(); -diff --git a/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java b/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java -index ba1599b733946bef468a9b006411f8827844e791..b6cca72b4785d5cf009077c81c1cca718d8cfe28 100644 ---- a/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java -+++ b/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java -@@ -556,6 +556,12 @@ public class CraftEventFactory { - } - - public static PlayerInteractEvent callPlayerInteractEvent(net.minecraft.world.entity.player.Player who, Action action, BlockPos position, Direction direction, ItemStack itemstack, boolean cancelledBlock, InteractionHand hand, Vec3 targetPos) { -+ // Paper start - cancelledItem param -+ return CraftEventFactory.callPlayerInteractEvent(who, action, position, direction, itemstack, cancelledBlock, false, hand, targetPos); -+ } -+ -+ public static PlayerInteractEvent callPlayerInteractEvent(net.minecraft.world.entity.player.Player who, Action action, BlockPos position, Direction direction, ItemStack itemstack, boolean cancelledBlock, boolean cancelledItem, InteractionHand hand, Vec3 targetPos) { -+ // Paper end - cancelledItem param - Player player = (who == null) ? null : (Player) who.getBukkitEntity(); - CraftItemStack itemInHand = CraftItemStack.asCraftMirror(itemstack); - -@@ -590,6 +596,11 @@ public class CraftEventFactory { - if (cancelledBlock) { - event.setUseInteractedBlock(Event.Result.DENY); - } -+ // Paper start -+ if (cancelledItem) { -+ event.setUseItemInHand(Result.DENY); -+ } -+ // Paper end - craftServer.getPluginManager().callEvent(event); - - return event; diff --git a/patches/server/0750-Add-PlayerInventorySlotChangeEvent.patch b/patches/server/0750-Add-PlayerInventorySlotChangeEvent.patch deleted file mode 100644 index 0f6a75faa355..000000000000 --- a/patches/server/0750-Add-PlayerInventorySlotChangeEvent.patch +++ /dev/null @@ -1,65 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Jakub Zacek -Date: Sun, 24 Apr 2022 22:56:59 +0200 -Subject: [PATCH] Add PlayerInventorySlotChangeEvent - - -diff --git a/src/main/java/net/minecraft/server/level/ServerPlayer.java b/src/main/java/net/minecraft/server/level/ServerPlayer.java -index ad3896dd524acb573adffdfb38b13dd699539cef..3dbaa3c6b75a0f8bfdef42d210f2ac6f560cde3d 100644 ---- a/src/main/java/net/minecraft/server/level/ServerPlayer.java -+++ b/src/main/java/net/minecraft/server/level/ServerPlayer.java -@@ -352,6 +352,25 @@ public class ServerPlayer extends net.minecraft.world.entity.player.Player { - - } - } -+ // Paper start - Add PlayerInventorySlotChangeEvent -+ @Override -+ public void slotChanged(AbstractContainerMenu handler, int slotId, ItemStack oldStack, ItemStack stack) { -+ Slot slot = handler.getSlot(slotId); -+ if (!(slot instanceof ResultSlot)) { -+ if (slot.container == ServerPlayer.this.getInventory()) { -+ if (io.papermc.paper.event.player.PlayerInventorySlotChangeEvent.getHandlerList().getRegisteredListeners().length == 0) { -+ CriteriaTriggers.INVENTORY_CHANGED.trigger(ServerPlayer.this, ServerPlayer.this.getInventory(), stack); -+ return; -+ } -+ io.papermc.paper.event.player.PlayerInventorySlotChangeEvent event = new io.papermc.paper.event.player.PlayerInventorySlotChangeEvent(ServerPlayer.this.getBukkitEntity(), slotId, CraftItemStack.asBukkitCopy(oldStack), CraftItemStack.asBukkitCopy(stack)); -+ event.callEvent(); -+ if (event.shouldTriggerAdvancements()) { -+ CriteriaTriggers.INVENTORY_CHANGED.trigger(ServerPlayer.this, ServerPlayer.this.getInventory(), stack); -+ } -+ } -+ } -+ } -+ // Paper end - Add PlayerInventorySlotChangeEvent - - @Override - public void dataChanged(AbstractContainerMenu handler, int property, int value) {} -diff --git a/src/main/java/net/minecraft/world/inventory/AbstractContainerMenu.java b/src/main/java/net/minecraft/world/inventory/AbstractContainerMenu.java -index b8d8aad81f54f7f43c01da075e63ec9173750bec..f7b9849819c185cd89533aca1f6d34398ffc1077 100644 ---- a/src/main/java/net/minecraft/world/inventory/AbstractContainerMenu.java -+++ b/src/main/java/net/minecraft/world/inventory/AbstractContainerMenu.java -@@ -302,7 +302,7 @@ public abstract class AbstractContainerMenu { - while (iterator.hasNext()) { - ContainerListener icrafting = (ContainerListener) iterator.next(); - -- icrafting.slotChanged(this, slot, itemstack2); -+ icrafting.slotChanged(this, slot, itemstack1, itemstack2); // Paper - Add PlayerInventorySlotChangeEvent - } - } - -diff --git a/src/main/java/net/minecraft/world/inventory/ContainerListener.java b/src/main/java/net/minecraft/world/inventory/ContainerListener.java -index 0e19cc55646625bf32a354d3df2dc2d6bcff96f4..eba024c9aadef8902aacccea1584ffbee6c04e44 100644 ---- a/src/main/java/net/minecraft/world/inventory/ContainerListener.java -+++ b/src/main/java/net/minecraft/world/inventory/ContainerListener.java -@@ -5,5 +5,11 @@ import net.minecraft.world.item.ItemStack; - public interface ContainerListener { - void slotChanged(AbstractContainerMenu handler, int slotId, ItemStack stack); - -+ // Paper start - Add PlayerInventorySlotChangeEvent -+ default void slotChanged(AbstractContainerMenu handler, int slotId, ItemStack oldStack, ItemStack stack) { -+ slotChanged(handler, slotId, stack); -+ } -+ // Paper end - Add PlayerInventorySlotChangeEvent -+ - void dataChanged(AbstractContainerMenu handler, int property, int value); - } diff --git a/patches/server/0750-Add-fire-tick-delay-option.patch b/patches/server/0750-Add-fire-tick-delay-option.patch new file mode 100644 index 000000000000..7db107dee97d --- /dev/null +++ b/patches/server/0750-Add-fire-tick-delay-option.patch @@ -0,0 +1,34 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: VytskaLT +Date: Wed, 22 Jun 2022 14:34:28 +0300 +Subject: [PATCH] Add fire-tick-delay option + + +diff --git a/src/main/java/net/minecraft/world/level/block/FireBlock.java b/src/main/java/net/minecraft/world/level/block/FireBlock.java +index 065d6164b5c9d65d20e7790c607d77e9ad70dfef..0e5a47ab235d99e6cb1468905f791c2c59ac0082 100644 +--- a/src/main/java/net/minecraft/world/level/block/FireBlock.java ++++ b/src/main/java/net/minecraft/world/level/block/FireBlock.java +@@ -171,7 +171,7 @@ public class FireBlock extends BaseFireBlock { + + @Override + protected void tick(BlockState state, ServerLevel world, BlockPos pos, RandomSource random) { +- world.scheduleTick(pos, (Block) this, FireBlock.getFireTickDelay(world.random)); ++ world.scheduleTick(pos, (Block) this, FireBlock.getFireTickDelay(world)); // Paper - Add fire-tick-delay option + if (world.getGameRules().getBoolean(GameRules.RULE_DOFIRETICK)) { + if (!state.canSurvive(world, pos)) { + this.fireExtinguished(world, pos); // CraftBukkit - invalid place location +@@ -372,11 +372,11 @@ public class FireBlock extends BaseFireBlock { + protected void onPlace(BlockState iblockdata, Level world, BlockPos blockposition, BlockState iblockdata1, boolean flag, UseOnContext context) { + super.onPlace(iblockdata, world, blockposition, iblockdata1, flag, context); + // CraftBukkit end +- world.scheduleTick(blockposition, (Block) this, FireBlock.getFireTickDelay(world.random)); ++ world.scheduleTick(blockposition, (Block) this, FireBlock.getFireTickDelay(world)); // Paper - Add fire-tick-delay option + } + +- private static int getFireTickDelay(RandomSource random) { +- return 30 + random.nextInt(10); ++ private static int getFireTickDelay(Level world) { // Paper - Add fire-tick-delay option ++ return world.paperConfig().environment.fireTickDelay + world.random.nextInt(10); // Paper - Add fire-tick-delay option + } + + @Override diff --git a/patches/server/0757-Add-Moving-Piston-API.patch b/patches/server/0751-Add-Moving-Piston-API.patch similarity index 100% rename from patches/server/0757-Add-Moving-Piston-API.patch rename to patches/server/0751-Add-Moving-Piston-API.patch diff --git a/patches/server/0751-Elder-Guardian-appearance-API.patch b/patches/server/0751-Elder-Guardian-appearance-API.patch deleted file mode 100644 index fd1cef6a094f..000000000000 --- a/patches/server/0751-Elder-Guardian-appearance-API.patch +++ /dev/null @@ -1,24 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: SoSeDiK -Date: Tue, 11 Oct 2022 20:38:47 +0300 -Subject: [PATCH] Elder Guardian appearance API - - -diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java -index 3374dcd3b1f22b6c1803b9580c4cea6552b6afb1..1e563ed8f7e85d68682e679b27b0f6100eb10aea 100644 ---- a/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java -+++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java -@@ -3298,6 +3298,13 @@ public class CraftPlayer extends CraftHumanEntity implements Player { - } - // Paper end - -+ // Paper start -+ @Override -+ public void showElderGuardian(boolean silent) { -+ if (getHandle().connection != null) getHandle().connection.send(new ClientboundGameEventPacket(ClientboundGameEventPacket.GUARDIAN_ELDER_EFFECT, silent ? 0F : 1F)); -+ } -+ // Paper end -+ - public Player.Spigot spigot() - { - return this.spigot; diff --git a/patches/server/0752-Add-entity-knockback-API.patch b/patches/server/0752-Add-entity-knockback-API.patch deleted file mode 100644 index b50878265dbb..000000000000 --- a/patches/server/0752-Add-entity-knockback-API.patch +++ /dev/null @@ -1,23 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: MelnCat -Date: Sun, 16 Oct 2022 12:10:17 -0700 -Subject: [PATCH] Add entity knockback API - - -diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftLivingEntity.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftLivingEntity.java -index 877e3d20edba3fe2ea6b51d0b54ec8893bd112ed..2a7fffa71106327d0ffee632408d45faaec76702 100644 ---- a/src/main/java/org/bukkit/craftbukkit/entity/CraftLivingEntity.java -+++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftLivingEntity.java -@@ -1127,4 +1127,12 @@ public class CraftLivingEntity extends CraftEntity implements LivingEntity { - throw new UnsupportedOperationException("Cannot set the hurt direction on a non player"); - } - // Paper end - hurt direction API -+ -+ // Paper start - knockback API -+ @Override -+ public void knockback(final double strength, final double directionX, final double directionZ) { -+ Preconditions.checkArgument(strength > 0, "Knockback strength must be > 0"); -+ this.getHandle().knockback(strength, directionX, directionZ); -+ }; -+ // Paper end - knockback API - } diff --git a/patches/server/0752-Ignore-impossible-spawn-tick.patch b/patches/server/0752-Ignore-impossible-spawn-tick.patch new file mode 100644 index 000000000000..ee2750f36df9 --- /dev/null +++ b/patches/server/0752-Ignore-impossible-spawn-tick.patch @@ -0,0 +1,18 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: dannyball710 +Date: Sat, 12 Feb 2022 23:42:48 +0800 +Subject: [PATCH] Ignore impossible spawn tick + + +diff --git a/src/main/java/net/minecraft/world/level/BaseSpawner.java b/src/main/java/net/minecraft/world/level/BaseSpawner.java +index 1b6ec72f59504d2f420d6d5dcbed4d3b9115e3d8..7de66aa435dd36899b80f4ecc64480680e474d94 100644 +--- a/src/main/java/net/minecraft/world/level/BaseSpawner.java ++++ b/src/main/java/net/minecraft/world/level/BaseSpawner.java +@@ -84,6 +84,7 @@ public abstract class BaseSpawner { + } + + public void serverTick(ServerLevel world, BlockPos pos) { ++ if (spawnCount <= 0 || maxNearbyEntities <= 0) return; // Paper - Ignore impossible spawn tick + // Paper start - Configurable mob spawner tick rate + if (spawnDelay > 0 && --tickDelay > 0) return; + tickDelay = world.paperConfig().tickRates.mobSpawner; diff --git a/patches/server/0753-Detect-headless-JREs.patch b/patches/server/0753-Detect-headless-JREs.patch deleted file mode 100644 index 5ba28913a820..000000000000 --- a/patches/server/0753-Detect-headless-JREs.patch +++ /dev/null @@ -1,51 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Noah van der Aa -Date: Sat, 22 Oct 2022 14:47:45 +0200 -Subject: [PATCH] Detect headless JREs - -Crashes caused by the missing AWT dependency come up in the support channels fairly often. -This patch detects the missing dependency and stops the server with a clear error message, -containing a link to instructions on how to install a non-headless JRE. - -diff --git a/src/main/java/io/papermc/paper/util/ServerEnvironment.java b/src/main/java/io/papermc/paper/util/ServerEnvironment.java -index 68098dfe716e93aafcca4d8d5b5a81d8648b3654..2b7070e0cefa7cf0777df159693750fea14e800b 100644 ---- a/src/main/java/io/papermc/paper/util/ServerEnvironment.java -+++ b/src/main/java/io/papermc/paper/util/ServerEnvironment.java -@@ -20,4 +20,14 @@ public class ServerEnvironment { - public static boolean userIsRootOrAdmin() { - return RUNNING_AS_ROOT_OR_ADMIN; - } -+ -+ public static String awtDependencyCheck() { -+ try { -+ new java.awt.Color(0); -+ } catch (UnsatisfiedLinkError e) { -+ return e.getClass().getName() + ": " + e.getMessage(); -+ } -+ -+ return null; -+ } - } -diff --git a/src/main/java/net/minecraft/server/Main.java b/src/main/java/net/minecraft/server/Main.java -index 467b8ca8007c5d3a7b72b88e3a979bdf09f1a283..13e1a914d4523f1c192db2a9a1ee6522e0ee27da 100644 ---- a/src/main/java/net/minecraft/server/Main.java -+++ b/src/main/java/net/minecraft/server/Main.java -@@ -167,6 +167,18 @@ public class Main { - return; - } - -+ // Paper start - Detect headless JRE -+ String awtException = io.papermc.paper.util.ServerEnvironment.awtDependencyCheck(); -+ if (awtException != null) { -+ Main.LOGGER.error("You are using a headless JRE distribution."); -+ Main.LOGGER.error("This distribution is missing certain graphic libraries that the Minecraft server needs to function."); -+ Main.LOGGER.error("For instructions on how to install the non-headless JRE, see https://docs.papermc.io/misc/java-install"); -+ Main.LOGGER.error(""); -+ Main.LOGGER.error(awtException); -+ return; -+ } -+ // Paper end - Detect headless JRE -+ - org.spigotmc.SpigotConfig.disabledAdvancements = spigotConfiguration.getStringList("advancements.disabled"); // Paper - fix SPIGOT-5885, must be set early in init - // Paper start - fix SPIGOT-5824 - File file; diff --git a/patches/server/0759-Fix-EntityArgument-and-EntitySelectorParser-permissi.patch b/patches/server/0753-Fix-EntityArgument-and-EntitySelectorParser-permissi.patch similarity index 100% rename from patches/server/0759-Fix-EntityArgument-and-EntitySelectorParser-permissi.patch rename to patches/server/0753-Fix-EntityArgument-and-EntitySelectorParser-permissi.patch diff --git a/patches/server/0754-Fix-EntityCombustEvent-cancellation-cant-fully-preve.patch b/patches/server/0754-Fix-EntityCombustEvent-cancellation-cant-fully-preve.patch new file mode 100644 index 000000000000..1c5db8b76dc5 --- /dev/null +++ b/patches/server/0754-Fix-EntityCombustEvent-cancellation-cant-fully-preve.patch @@ -0,0 +1,37 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Denery +Date: Mon, 31 Oct 2022 14:20:52 +0300 +Subject: [PATCH] Fix EntityCombustEvent cancellation cant fully prevent + entities from being set on fire + + +diff --git a/src/main/java/net/minecraft/world/entity/Entity.java b/src/main/java/net/minecraft/world/entity/Entity.java +index 0de6c90c195439564810036c90f31e8296538666..8b2b0cf8318f06f0fef59908ece650fb54b6a6b3 100644 +--- a/src/main/java/net/minecraft/world/entity/Entity.java ++++ b/src/main/java/net/minecraft/world/entity/Entity.java +@@ -3320,6 +3320,10 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess + pluginManager.callEvent(entityCombustEvent); + if (!entityCombustEvent.isCancelled()) { + this.igniteForSeconds(entityCombustEvent.getDuration(), false); ++ // Paper start - fix EntityCombustEvent cancellation ++ } else { ++ this.setRemainingFireTicks(this.remainingFireTicks - 1); ++ // Paper end - fix EntityCombustEvent cancellation + } + // CraftBukkit end + } +diff --git a/src/main/java/net/minecraft/world/level/block/BaseFireBlock.java b/src/main/java/net/minecraft/world/level/block/BaseFireBlock.java +index c8ca41cd81a72f9bff40f5c1b3bfc1189bf51f98..0cf4133849ed8ff6d4038cc41ede9d3645b31da1 100644 +--- a/src/main/java/net/minecraft/world/level/block/BaseFireBlock.java ++++ b/src/main/java/net/minecraft/world/level/block/BaseFireBlock.java +@@ -145,6 +145,10 @@ public abstract class BaseFireBlock extends Block { + + if (!event.isCancelled()) { + entity.igniteForSeconds(event.getDuration(), false); ++ // Paper start - fix EntityCombustEvent cancellation ++ } else { ++ entity.setRemainingFireTicks(entity.getRemainingFireTicks() - 1); ++ // Paper end - fix EntityCombustEvent cancellation + } + // CraftBukkit end + } diff --git a/patches/server/0754-fix-entity-vehicle-collision-event-not-called.patch b/patches/server/0754-fix-entity-vehicle-collision-event-not-called.patch deleted file mode 100644 index e2f922a2a17c..000000000000 --- a/patches/server/0754-fix-entity-vehicle-collision-event-not-called.patch +++ /dev/null @@ -1,27 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: lukas81298 -Date: Tue, 12 Jan 2021 14:41:38 +0100 -Subject: [PATCH] fix entity vehicle collision event not called - - -diff --git a/src/main/java/net/minecraft/world/entity/vehicle/AbstractMinecart.java b/src/main/java/net/minecraft/world/entity/vehicle/AbstractMinecart.java -index 93634fb01962ca7a35026e3c365f2a7f6b258a39..a38ce400550893f63640e3bb5bb801ab40f06266 100644 ---- a/src/main/java/net/minecraft/world/entity/vehicle/AbstractMinecart.java -+++ b/src/main/java/net/minecraft/world/entity/vehicle/AbstractMinecart.java -@@ -165,7 +165,15 @@ public abstract class AbstractMinecart extends VehicleEntity { - - @Override - public boolean canCollideWith(Entity other) { -- return Boat.canVehicleCollide(this, other); -+ // Paper start - fix VehicleEntityCollisionEvent not called when colliding with player -+ boolean collides = Boat.canVehicleCollide(this, other); -+ if (!collides) { -+ return false; -+ } -+ org.bukkit.event.vehicle.VehicleEntityCollisionEvent collisionEvent = new org.bukkit.event.vehicle.VehicleEntityCollisionEvent((org.bukkit.entity.Vehicle) getBukkitEntity(), other.getBukkitEntity()); -+ -+ return collisionEvent.callEvent(); -+ // Paper end - fix VehicleEntityCollisionEvent not called when colliding with player - } - - @Override diff --git a/patches/server/0755-Add-EntityToggleSitEvent.patch b/patches/server/0755-Add-EntityToggleSitEvent.patch deleted file mode 100644 index 0ed0b9556227..000000000000 --- a/patches/server/0755-Add-EntityToggleSitEvent.patch +++ /dev/null @@ -1,100 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: KyGuy2002 -Date: Fri, 11 Mar 2022 15:33:10 +0000 -Subject: [PATCH] Add EntityToggleSitEvent - - -diff --git a/src/main/java/net/minecraft/world/entity/TamableAnimal.java b/src/main/java/net/minecraft/world/entity/TamableAnimal.java -index 4c5b0b1a71ec5ba0e8dcd5ef52bf667e9bb30be9..87c669517baa923ffa7392e400e7344e81fc9406 100644 ---- a/src/main/java/net/minecraft/world/entity/TamableAnimal.java -+++ b/src/main/java/net/minecraft/world/entity/TamableAnimal.java -@@ -86,7 +86,7 @@ public abstract class TamableAnimal extends Animal implements OwnableEntity { - } - - this.orderedToSit = nbt.getBoolean("Sitting"); -- this.setInSittingPose(this.orderedToSit); -+ this.setInSittingPose(this.orderedToSit, false); // Paper - Add EntityToggleSitEvent - } - - @Override -@@ -166,6 +166,12 @@ public abstract class TamableAnimal extends Animal implements OwnableEntity { - } - - public void setInSittingPose(boolean inSittingPose) { -+ // Paper start - Add EntityToggleSitEvent -+ this.setInSittingPose(inSittingPose, true); -+ } -+ public void setInSittingPose(boolean inSittingPose, boolean callEvent) { -+ if (callEvent && !new io.papermc.paper.event.entity.EntityToggleSitEvent(this.getBukkitEntity(), inSittingPose).callEvent()) return; -+ // Paper end - Add EntityToggleSitEvent - byte b0 = (Byte) this.entityData.get(TamableAnimal.DATA_FLAGS_ID); - - if (inSittingPose) { -diff --git a/src/main/java/net/minecraft/world/entity/animal/Fox.java b/src/main/java/net/minecraft/world/entity/animal/Fox.java -index 3265b3b5aede517b6fd8bb836947795bf8881350..9a0adf65d4d54852301a91b6fe444e4c5a139f5d 100644 ---- a/src/main/java/net/minecraft/world/entity/animal/Fox.java -+++ b/src/main/java/net/minecraft/world/entity/animal/Fox.java -@@ -432,7 +432,7 @@ public class Fox extends Animal implements VariantHolder { - - this.setSleeping(nbt.getBoolean("Sleeping")); - this.setVariant(Fox.Type.byName(nbt.getString("Type"))); -- this.setSitting(nbt.getBoolean("Sitting")); -+ this.setSitting(nbt.getBoolean("Sitting"), false); // Paper - Add EntityToggleSitEvent - this.setIsCrouching(nbt.getBoolean("Crouching")); - if (this.level() instanceof ServerLevel) { - this.setTargetGoals(); -@@ -445,6 +445,12 @@ public class Fox extends Animal implements VariantHolder { - } - - public void setSitting(boolean sitting) { -+ // Paper start - Add EntityToggleSitEvent -+ this.setSitting(sitting, true); -+ } -+ public void setSitting(boolean sitting, boolean fireEvent) { -+ if (fireEvent && !new io.papermc.paper.event.entity.EntityToggleSitEvent(this.getBukkitEntity(), sitting).callEvent()) return; -+ // Paper end - Add EntityToggleSitEvent - this.setFlag(1, sitting); - } - -diff --git a/src/main/java/net/minecraft/world/entity/animal/Panda.java b/src/main/java/net/minecraft/world/entity/animal/Panda.java -index 7b3d5322611990406028e59b1409907291e27b21..293d6891948e99ac9bd741008f7dcbc5fc1a2e3d 100644 ---- a/src/main/java/net/minecraft/world/entity/animal/Panda.java -+++ b/src/main/java/net/minecraft/world/entity/animal/Panda.java -@@ -145,6 +145,7 @@ public class Panda extends Animal { - } - - public void sit(boolean sitting) { -+ if (!new io.papermc.paper.event.entity.EntityToggleSitEvent(this.getBukkitEntity(), sitting).callEvent()) return; // Paper - Add EntityToggleSitEvent - this.setFlag(8, sitting); - } - -diff --git a/src/main/java/net/minecraft/world/entity/animal/camel/Camel.java b/src/main/java/net/minecraft/world/entity/animal/camel/Camel.java -index aecd17953dc117d369885d4c8d7b5c7c3a0f8f0f..0388b09e1c4f03958384680ed487792a54007463 100644 ---- a/src/main/java/net/minecraft/world/entity/animal/camel/Camel.java -+++ b/src/main/java/net/minecraft/world/entity/animal/camel/Camel.java -@@ -570,7 +570,7 @@ public class Camel extends AbstractHorse implements PlayerRideableJumping, Saddl - } - - public void sitDown() { -- if (!this.isCamelSitting()) { -+ if (!this.isCamelSitting() && new io.papermc.paper.event.entity.EntityToggleSitEvent(this.getBukkitEntity(), true).callEvent()) { // Paper - Add EntityToggleSitEvent - this.makeSound(SoundEvents.CAMEL_SIT); - this.setPose(Pose.SITTING); - this.gameEvent(GameEvent.ENTITY_ACTION); -@@ -579,7 +579,7 @@ public class Camel extends AbstractHorse implements PlayerRideableJumping, Saddl - } - - public void standUp() { -- if (this.isCamelSitting()) { -+ if (this.isCamelSitting() && new io.papermc.paper.event.entity.EntityToggleSitEvent(this.getBukkitEntity(), false).callEvent()) { // Paper - Add EntityToggleSitEvent - this.makeSound(SoundEvents.CAMEL_STAND); - this.setPose(Pose.STANDING); - this.gameEvent(GameEvent.ENTITY_ACTION); -@@ -588,6 +588,7 @@ public class Camel extends AbstractHorse implements PlayerRideableJumping, Saddl - } - - public void standUpInstantly() { -+ if (this.isCamelSitting() && !new io.papermc.paper.event.entity.EntityToggleSitEvent(this.getBukkitEntity(), false).callEvent()) return; // Paper - Add EntityToggleSitEvent - this.setPose(Pose.STANDING); - this.gameEvent(GameEvent.ENTITY_ACTION); - this.resetLastPoseChangeTickToFullStand(this.level().getGameTime()); diff --git a/patches/server/0755-Add-PrePlayerAttackEntityEvent.patch b/patches/server/0755-Add-PrePlayerAttackEntityEvent.patch new file mode 100644 index 000000000000..5a925374e58a --- /dev/null +++ b/patches/server/0755-Add-PrePlayerAttackEntityEvent.patch @@ -0,0 +1,30 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Owen1212055 <23108066+Owen1212055@users.noreply.github.com> +Date: Sun, 18 Sep 2022 13:10:18 -0400 +Subject: [PATCH] Add PrePlayerAttackEntityEvent + + +diff --git a/src/main/java/net/minecraft/world/entity/player/Player.java b/src/main/java/net/minecraft/world/entity/player/Player.java +index b8edbd23d547d7189ec64c5d3a8cd1d51859ce23..30e0a5fe3f9bd85d2b702c2c877c5682ed35d461 100644 +--- a/src/main/java/net/minecraft/world/entity/player/Player.java ++++ b/src/main/java/net/minecraft/world/entity/player/Player.java +@@ -1222,8 +1222,17 @@ public abstract class Player extends LivingEntity { + } + + public void attack(Entity target) { +- if (target.isAttackable()) { +- if (!target.skipAttackInteraction(this)) { ++ // Paper start - PlayerAttackEntityEvent ++ boolean willAttack = target.isAttackable() && !target.skipAttackInteraction(this); // Vanilla logic ++ io.papermc.paper.event.player.PrePlayerAttackEntityEvent playerAttackEntityEvent = new io.papermc.paper.event.player.PrePlayerAttackEntityEvent( ++ (org.bukkit.entity.Player) this.getBukkitEntity(), ++ target.getBukkitEntity(), ++ willAttack ++ ); ++ ++ if (playerAttackEntityEvent.callEvent() && willAttack) { // Logic moved to willAttack local variable. ++ { ++ // Paper end - PlayerAttackEntityEvent + float f = this.isAutoSpinAttack() ? this.autoSpinAttackDmg : (float) this.getAttributeValue(Attributes.ATTACK_DAMAGE); + ItemStack itemstack = this.getWeaponItem(); + DamageSource damagesource = (DamageSource) Optional.ofNullable(itemstack.getItem().getDamageSource(this)).orElse(this.damageSources().playerAttack(this)); diff --git a/patches/server/0756-Add-fire-tick-delay-option.patch b/patches/server/0756-Add-fire-tick-delay-option.patch deleted file mode 100644 index 2ad454d1c6f5..000000000000 --- a/patches/server/0756-Add-fire-tick-delay-option.patch +++ /dev/null @@ -1,34 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: VytskaLT -Date: Wed, 22 Jun 2022 14:34:28 +0300 -Subject: [PATCH] Add fire-tick-delay option - - -diff --git a/src/main/java/net/minecraft/world/level/block/FireBlock.java b/src/main/java/net/minecraft/world/level/block/FireBlock.java -index 9db6df5f28be559a324ead2fcfbe189eac076e2e..0a77a470d78f68e8397f29f298e7f52fbd7ba9a2 100644 ---- a/src/main/java/net/minecraft/world/level/block/FireBlock.java -+++ b/src/main/java/net/minecraft/world/level/block/FireBlock.java -@@ -171,7 +171,7 @@ public class FireBlock extends BaseFireBlock { - - @Override - protected void tick(BlockState state, ServerLevel world, BlockPos pos, RandomSource random) { -- world.scheduleTick(pos, (Block) this, FireBlock.getFireTickDelay(world.random)); -+ world.scheduleTick(pos, (Block) this, FireBlock.getFireTickDelay(world)); // Paper - Add fire-tick-delay option - if (world.getGameRules().getBoolean(GameRules.RULE_DOFIRETICK)) { - if (!state.canSurvive(world, pos)) { - this.fireExtinguished(world, pos); // CraftBukkit - invalid place location -@@ -372,11 +372,11 @@ public class FireBlock extends BaseFireBlock { - protected void onPlace(BlockState iblockdata, Level world, BlockPos blockposition, BlockState iblockdata1, boolean flag, UseOnContext context) { - super.onPlace(iblockdata, world, blockposition, iblockdata1, flag, context); - // CraftBukkit end -- world.scheduleTick(blockposition, (Block) this, FireBlock.getFireTickDelay(world.random)); -+ world.scheduleTick(blockposition, (Block) this, FireBlock.getFireTickDelay(world)); // Paper - Add fire-tick-delay option - } - -- private static int getFireTickDelay(RandomSource random) { -- return 30 + random.nextInt(10); -+ private static int getFireTickDelay(Level world) { // Paper - Add fire-tick-delay option -+ return world.paperConfig().environment.fireTickDelay + world.random.nextInt(10); // Paper - Add fire-tick-delay option - } - - @Override diff --git a/patches/server/0756-ensure-reset-EnderDragon-boss-event-name.patch b/patches/server/0756-ensure-reset-EnderDragon-boss-event-name.patch new file mode 100644 index 000000000000..4c5db1221b02 --- /dev/null +++ b/patches/server/0756-ensure-reset-EnderDragon-boss-event-name.patch @@ -0,0 +1,39 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Jake Potrebic +Date: Sat, 12 Nov 2022 10:08:58 -0800 +Subject: [PATCH] ensure reset EnderDragon boss event name + +Fix MC-257487 + +diff --git a/src/main/java/net/minecraft/world/level/dimension/end/EndDragonFight.java b/src/main/java/net/minecraft/world/level/dimension/end/EndDragonFight.java +index bc6426c04ac1fd19949d587d2b7061895db0893b..39b1d6ee10d4a10ab4fb339621ca80f27d383324 100644 +--- a/src/main/java/net/minecraft/world/level/dimension/end/EndDragonFight.java ++++ b/src/main/java/net/minecraft/world/level/dimension/end/EndDragonFight.java +@@ -74,6 +74,7 @@ public class EndDragonFight { + private static final int GATEWAY_DISTANCE = 96; + public static final int DRAGON_SPAWN_Y = 128; + private final Predicate validPlayer; ++ private static final Component DEFAULT_BOSS_EVENT_NAME = Component.translatable("entity.minecraft.ender_dragon"); // Paper - ensure reset EnderDragon boss event name + public final ServerBossEvent dragonEvent; + public final ServerLevel level; + private final BlockPos origin; +@@ -102,7 +103,7 @@ public class EndDragonFight { + } + + public EndDragonFight(ServerLevel world, long gatewaysSeed, EndDragonFight.Data data, BlockPos origin) { +- this.dragonEvent = (ServerBossEvent) (new ServerBossEvent(Component.translatable("entity.minecraft.ender_dragon"), BossEvent.BossBarColor.PINK, BossEvent.BossBarOverlay.PROGRESS)).setPlayBossMusic(true).setCreateWorldFog(true); ++ this.dragonEvent = (ServerBossEvent) (new ServerBossEvent(DEFAULT_BOSS_EVENT_NAME, BossEvent.BossBarColor.PINK, BossEvent.BossBarOverlay.PROGRESS)).setPlayBossMusic(true).setCreateWorldFog(true); // Paper - ensure reset EnderDragon boss event name + this.gateways = new ObjectArrayList(); + this.ticksSinceLastPlayerScan = 21; + this.skipArenaLoadedCheck = false; +@@ -506,6 +507,10 @@ public class EndDragonFight { + this.ticksSinceDragonSeen = 0; + if (dragon.hasCustomName()) { + this.dragonEvent.setName(dragon.getDisplayName()); ++ // Paper start - ensure reset EnderDragon boss event name ++ } else { ++ this.dragonEvent.setName(DEFAULT_BOSS_EVENT_NAME); ++ // Paper end - ensure reset EnderDragon boss event name + } + } + diff --git a/patches/server/0757-Add-Player-Warden-Warning-API.patch b/patches/server/0757-Add-Player-Warden-Warning-API.patch new file mode 100644 index 000000000000..08e5c3298180 --- /dev/null +++ b/patches/server/0757-Add-Player-Warden-Warning-API.patch @@ -0,0 +1,57 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: dawon +Date: Sat, 15 Oct 2022 00:46:57 +0200 +Subject: [PATCH] Add Player Warden Warning API + +== AT == +public net.minecraft.server.level.ServerPlayer wardenSpawnTracker +public net.minecraft.world.entity.monster.warden.WardenSpawnTracker ticksSinceLastWarning +public net.minecraft.world.entity.monster.warden.WardenSpawnTracker cooldownTicks +public net.minecraft.world.entity.monster.warden.WardenSpawnTracker increaseWarningLevel()V + +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java +index dc09ddfb8e695606fe4dfd2594d75ab312ee9b78..9f31ea3cb0b5dac2c50be0f51ad002e593d6fb5c 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java +@@ -3327,6 +3327,41 @@ public class CraftPlayer extends CraftHumanEntity implements Player { + public void showElderGuardian(boolean silent) { + if (getHandle().connection != null) getHandle().connection.send(new ClientboundGameEventPacket(ClientboundGameEventPacket.GUARDIAN_ELDER_EFFECT, silent ? 0F : 1F)); + } ++ ++ @Override ++ public int getWardenWarningCooldown() { ++ return this.getHandle().wardenSpawnTracker.cooldownTicks; ++ } ++ ++ @Override ++ public void setWardenWarningCooldown(int cooldown) { ++ this.getHandle().wardenSpawnTracker.cooldownTicks = Math.max(cooldown, 0); ++ } ++ ++ @Override ++ public int getWardenTimeSinceLastWarning() { ++ return this.getHandle().wardenSpawnTracker.ticksSinceLastWarning; ++ } ++ ++ @Override ++ public void setWardenTimeSinceLastWarning(int time) { ++ this.getHandle().wardenSpawnTracker.ticksSinceLastWarning = time; ++ } ++ ++ @Override ++ public int getWardenWarningLevel() { ++ return this.getHandle().wardenSpawnTracker.getWarningLevel(); ++ } ++ ++ @Override ++ public void setWardenWarningLevel(int warningLevel) { ++ this.getHandle().wardenSpawnTracker.setWarningLevel(warningLevel); ++ } ++ ++ @Override ++ public void increaseWardenWarningLevel() { ++ this.getHandle().wardenSpawnTracker.increaseWarningLevel(); ++ } + // Paper end + + public Player.Spigot spigot() diff --git a/patches/server/0758-Ignore-impossible-spawn-tick.patch b/patches/server/0758-Ignore-impossible-spawn-tick.patch deleted file mode 100644 index f046102ceb4d..000000000000 --- a/patches/server/0758-Ignore-impossible-spawn-tick.patch +++ /dev/null @@ -1,18 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: dannyball710 -Date: Sat, 12 Feb 2022 23:42:48 +0800 -Subject: [PATCH] Ignore impossible spawn tick - - -diff --git a/src/main/java/net/minecraft/world/level/BaseSpawner.java b/src/main/java/net/minecraft/world/level/BaseSpawner.java -index 8efc06d29c62fa2be8515ed3359d52a6d4b807d2..f57e1b78204dff661ad5d3ee93a88a00330af2dc 100644 ---- a/src/main/java/net/minecraft/world/level/BaseSpawner.java -+++ b/src/main/java/net/minecraft/world/level/BaseSpawner.java -@@ -84,6 +84,7 @@ public abstract class BaseSpawner { - } - - public void serverTick(ServerLevel world, BlockPos pos) { -+ if (spawnCount <= 0 || maxNearbyEntities <= 0) return; // Paper - Ignore impossible spawn tick - // Paper start - Configurable mob spawner tick rate - if (spawnDelay > 0 && --tickDelay > 0) return; - tickDelay = world.paperConfig().tickRates.mobSpawner; diff --git a/patches/server/0758-More-vanilla-friendly-methods-to-update-trades.patch b/patches/server/0758-More-vanilla-friendly-methods-to-update-trades.patch new file mode 100644 index 000000000000..b3901a0ae782 --- /dev/null +++ b/patches/server/0758-More-vanilla-friendly-methods-to-update-trades.patch @@ -0,0 +1,75 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Lulu13022002 <41980282+Lulu13022002@users.noreply.github.com> +Date: Sun, 16 Oct 2022 16:12:49 +0200 +Subject: [PATCH] More vanilla friendly methods to update trades + + +diff --git a/src/main/java/net/minecraft/world/entity/npc/Villager.java b/src/main/java/net/minecraft/world/entity/npc/Villager.java +index f51078e4b9e6267fa43795d009f5d149b86acb5a..a573aa4d387ad3a4e1017890f2b50b83a3c27ff4 100644 +--- a/src/main/java/net/minecraft/world/entity/npc/Villager.java ++++ b/src/main/java/net/minecraft/world/entity/npc/Villager.java +@@ -895,6 +895,12 @@ public class Villager extends AbstractVillager implements ReputationEventHandler + + @Override + protected void updateTrades() { ++ // Paper start - More vanilla friendly methods to update trades ++ updateTrades(TRADES_PER_LEVEL); ++ } ++ ++ public boolean updateTrades(int amount) { ++ // Paper end - More vanilla friendly methods to update trades + VillagerData villagerdata = this.getVillagerData(); + Int2ObjectMap int2objectmap; + +@@ -912,9 +918,11 @@ public class Villager extends AbstractVillager implements ReputationEventHandler + if (avillagertrades_imerchantrecipeoption != null) { + MerchantOffers merchantrecipelist = this.getOffers(); + +- this.addOffersFromItemListings(merchantrecipelist, avillagertrades_imerchantrecipeoption, 2); ++ this.addOffersFromItemListings(merchantrecipelist, avillagertrades_imerchantrecipeoption, amount); // Paper - More vanilla friendly methods to update trades ++ return true; // Paper - More vanilla friendly methods to update trades + } + } ++ return false; // Paper - More vanilla friendly methods to update trades + } + + public void gossip(ServerLevel world, Villager villager, long time) { +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftVillager.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftVillager.java +index 45c44c46edee9f46b8e197f1f54ea2779bf1184c..8e895d6f84f7d84b219f2424909dd42e5f08dec4 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftVillager.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftVillager.java +@@ -98,6 +98,34 @@ public class CraftVillager extends CraftAbstractVillager implements Villager { + } + + // Paper start ++ @Override ++ public boolean increaseLevel(int amount) { ++ Preconditions.checkArgument(amount > 0, "Level earned must be positive"); ++ int supposedFinalLevel = this.getVillagerLevel() + amount; ++ Preconditions.checkArgument(net.minecraft.world.entity.npc.VillagerData.MIN_VILLAGER_LEVEL <= supposedFinalLevel && supposedFinalLevel <= net.minecraft.world.entity.npc.VillagerData.MAX_VILLAGER_LEVEL, ++ "Final level reached after the donation (%d) must be between [%d, %d]".formatted(supposedFinalLevel, net.minecraft.world.entity.npc.VillagerData.MIN_VILLAGER_LEVEL, net.minecraft.world.entity.npc.VillagerData.MAX_VILLAGER_LEVEL)); ++ ++ it.unimi.dsi.fastutil.ints.Int2ObjectMap trades = ++ net.minecraft.world.entity.npc.VillagerTrades.TRADES.get(this.getHandle().getVillagerData().getProfession()); ++ ++ if (trades == null || trades.isEmpty()) { ++ this.getHandle().setVillagerData(this.getHandle().getVillagerData().setLevel(supposedFinalLevel)); ++ return false; ++ } ++ ++ while (amount > 0) { ++ this.getHandle().increaseMerchantCareer(); ++ amount--; ++ } ++ return true; ++ } ++ ++ @Override ++ public boolean addTrades(int amount) { ++ Preconditions.checkArgument(amount > 0, "Number of trades unlocked must be positive"); ++ return this.getHandle().updateTrades(amount); ++ } ++ + @Override + public int getRestocksToday() { + return getHandle().numberOfRestocksToday; diff --git a/patches/server/0765-Add-paper-dumplisteners-command.patch b/patches/server/0759-Add-paper-dumplisteners-command.patch similarity index 100% rename from patches/server/0765-Add-paper-dumplisteners-command.patch rename to patches/server/0759-Add-paper-dumplisteners-command.patch diff --git a/patches/server/0760-Fix-EntityCombustEvent-cancellation-cant-fully-preve.patch b/patches/server/0760-Fix-EntityCombustEvent-cancellation-cant-fully-preve.patch deleted file mode 100644 index cd0b3b2cc751..000000000000 --- a/patches/server/0760-Fix-EntityCombustEvent-cancellation-cant-fully-preve.patch +++ /dev/null @@ -1,37 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Denery -Date: Mon, 31 Oct 2022 14:20:52 +0300 -Subject: [PATCH] Fix EntityCombustEvent cancellation cant fully prevent - entities from being set on fire - - -diff --git a/src/main/java/net/minecraft/world/entity/Entity.java b/src/main/java/net/minecraft/world/entity/Entity.java -index 180a86c419cad9873c53a49c8881ced647a753e0..1aa3396a33e24f3849725769fe58b57374ee7611 100644 ---- a/src/main/java/net/minecraft/world/entity/Entity.java -+++ b/src/main/java/net/minecraft/world/entity/Entity.java -@@ -3193,6 +3193,10 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess - pluginManager.callEvent(entityCombustEvent); - if (!entityCombustEvent.isCancelled()) { - this.igniteForSeconds(entityCombustEvent.getDuration(), false); -+ // Paper start - fix EntityCombustEvent cancellation -+ } else { -+ this.setRemainingFireTicks(this.remainingFireTicks - 1); -+ // Paper end - fix EntityCombustEvent cancellation - } - // CraftBukkit end - } -diff --git a/src/main/java/net/minecraft/world/level/block/BaseFireBlock.java b/src/main/java/net/minecraft/world/level/block/BaseFireBlock.java -index 0c5409af685ef1f251db3d9f9e21295c82a1e02a..8b5918dc07f17ae5001c03dc743128fd9520b819 100644 ---- a/src/main/java/net/minecraft/world/level/block/BaseFireBlock.java -+++ b/src/main/java/net/minecraft/world/level/block/BaseFireBlock.java -@@ -135,6 +135,10 @@ public abstract class BaseFireBlock extends Block { - - if (!event.isCancelled()) { - entity.igniteForSeconds(event.getDuration(), false); -+ // Paper start - fix EntityCombustEvent cancellation -+ } else { -+ entity.setRemainingFireTicks(entity.getRemainingFireTicks() - 1); -+ // Paper end - fix EntityCombustEvent cancellation - } - // CraftBukkit end - } diff --git a/patches/server/0760-check-global-player-list-where-appropriate.patch b/patches/server/0760-check-global-player-list-where-appropriate.patch new file mode 100644 index 000000000000..393c169165f2 --- /dev/null +++ b/patches/server/0760-check-global-player-list-where-appropriate.patch @@ -0,0 +1,85 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Jake Potrebic +Date: Tue, 22 Nov 2022 13:16:01 -0800 +Subject: [PATCH] check global player list where appropriate + +Makes certain entities check all players when searching for a player +instead of just checking players in their world. + +diff --git a/src/main/java/net/minecraft/server/level/ServerLevel.java b/src/main/java/net/minecraft/server/level/ServerLevel.java +index 45e61a08152517a61260e662764d8bb0335537e3..b81d814619e4175f42aee397811b07cae420c2e8 100644 +--- a/src/main/java/net/minecraft/server/level/ServerLevel.java ++++ b/src/main/java/net/minecraft/server/level/ServerLevel.java +@@ -2353,4 +2353,12 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe + entity.updateDynamicGameEventListener(DynamicGameEventListener::move); + } + } ++ ++ // Paper start - check global player list where appropriate ++ @Override ++ @Nullable ++ public Player getGlobalPlayerByUUID(UUID uuid) { ++ return this.server.getPlayerList().getPlayer(uuid); ++ } ++ // Paper end - check global player list where appropriate + } +diff --git a/src/main/java/net/minecraft/world/entity/LivingEntity.java b/src/main/java/net/minecraft/world/entity/LivingEntity.java +index b367f6e329f44801c6f0d34f447497093d86ae3b..5330f6315cecfa6afd04b711a5b8656717cb5ede 100644 +--- a/src/main/java/net/minecraft/world/entity/LivingEntity.java ++++ b/src/main/java/net/minecraft/world/entity/LivingEntity.java +@@ -3865,7 +3865,7 @@ public abstract class LivingEntity extends Entity implements Attackable { + } + + public void onItemPickup(ItemEntity item) { +- Entity entity = item.getOwner(); ++ Entity entity = item.thrower != null ? this.level().getGlobalPlayerByUUID(item.thrower) : null; // Paper - check global player list where appropriate + + if (entity instanceof ServerPlayer) { + CriteriaTriggers.THROWN_ITEM_PICKED_UP_BY_ENTITY.trigger((ServerPlayer) entity, item.getItem(), this); +diff --git a/src/main/java/net/minecraft/world/entity/monster/ZombieVillager.java b/src/main/java/net/minecraft/world/entity/monster/ZombieVillager.java +index 533cb2eff3d56e7e8a70aba5e1047250e192bf2c..18c19e4b675000aacb74344909fc104964231008 100644 +--- a/src/main/java/net/minecraft/world/entity/monster/ZombieVillager.java ++++ b/src/main/java/net/minecraft/world/entity/monster/ZombieVillager.java +@@ -262,7 +262,7 @@ public class ZombieVillager extends Zombie implements VillagerDataHolder { + entityvillager.finalizeSpawn(world, world.getCurrentDifficultyAt(entityvillager.blockPosition()), EntitySpawnReason.CONVERSION, (SpawnGroupData) null); + entityvillager.refreshBrain(world); + if (this.conversionStarter != null) { +- Player entityhuman = world.getPlayerByUUID(this.conversionStarter); ++ Player entityhuman = world.getGlobalPlayerByUUID(this.conversionStarter); // Paper - check global player list where appropriate + + if (entityhuman instanceof ServerPlayer) { + CriteriaTriggers.CURED_ZOMBIE_VILLAGER.trigger((ServerPlayer) entityhuman, this, entityvillager); +diff --git a/src/main/java/net/minecraft/world/level/EntityGetter.java b/src/main/java/net/minecraft/world/level/EntityGetter.java +index dac8305f1c897e6f82a2dde67c5b1b6b8b649b19..e185a33b5b1f8e8e0a0e666b24ba3e9186a8a7ff 100644 +--- a/src/main/java/net/minecraft/world/level/EntityGetter.java ++++ b/src/main/java/net/minecraft/world/level/EntityGetter.java +@@ -165,4 +165,11 @@ public interface EntityGetter { + + return null; + } ++ ++ // Paper start - check global player list where appropriate ++ @Nullable ++ default Player getGlobalPlayerByUUID(UUID uuid) { ++ return this.getPlayerByUUID(uuid); ++ } ++ // Paper end - check global player list where appropriate + } +diff --git a/src/main/java/net/minecraft/world/level/block/entity/SculkShriekerBlockEntity.java b/src/main/java/net/minecraft/world/level/block/entity/SculkShriekerBlockEntity.java +index ea53c25c350c0cf8e0360ea409cd1f69a62054a8..275721d8b3d653b38af505dde30396c0b7b6a3da 100644 +--- a/src/main/java/net/minecraft/world/level/block/entity/SculkShriekerBlockEntity.java ++++ b/src/main/java/net/minecraft/world/level/block/entity/SculkShriekerBlockEntity.java +@@ -105,6 +105,13 @@ public class SculkShriekerBlockEntity extends BlockEntity implements GameEventLi + + @Nullable + public static ServerPlayer tryGetPlayer(@Nullable Entity entity) { ++ // Paper start - check global player list where appropriate; ensure level is the same for sculk events ++ final ServerPlayer player = tryGetPlayer0(entity); ++ return player != null && player.level() == entity.level() ? player : null; ++ } ++ @Nullable ++ private static ServerPlayer tryGetPlayer0(@Nullable Entity entity) { ++ // Paper end - check global player list where appropriate + if (entity instanceof ServerPlayer) { + return (ServerPlayer)entity; + } else { diff --git a/patches/server/0761-Add-PrePlayerAttackEntityEvent.patch b/patches/server/0761-Add-PrePlayerAttackEntityEvent.patch deleted file mode 100644 index b0f3a0789006..000000000000 --- a/patches/server/0761-Add-PrePlayerAttackEntityEvent.patch +++ /dev/null @@ -1,30 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Owen1212055 <23108066+Owen1212055@users.noreply.github.com> -Date: Sun, 18 Sep 2022 13:10:18 -0400 -Subject: [PATCH] Add PrePlayerAttackEntityEvent - - -diff --git a/src/main/java/net/minecraft/world/entity/player/Player.java b/src/main/java/net/minecraft/world/entity/player/Player.java -index fab56040ecf496e74f583ec5d6c6c9861cfa4e13..ad334f149fe1b44d4ebe48489dcd2811ff1b5cd0 100644 ---- a/src/main/java/net/minecraft/world/entity/player/Player.java -+++ b/src/main/java/net/minecraft/world/entity/player/Player.java -@@ -1257,8 +1257,17 @@ public abstract class Player extends LivingEntity { - } - - public void attack(Entity target) { -- if (target.isAttackable()) { -- if (!target.skipAttackInteraction(this)) { -+ // Paper start - PlayerAttackEntityEvent -+ boolean willAttack = target.isAttackable() && !target.skipAttackInteraction(this); // Vanilla logic -+ io.papermc.paper.event.player.PrePlayerAttackEntityEvent playerAttackEntityEvent = new io.papermc.paper.event.player.PrePlayerAttackEntityEvent( -+ (org.bukkit.entity.Player) this.getBukkitEntity(), -+ target.getBukkitEntity(), -+ willAttack -+ ); -+ -+ if (playerAttackEntityEvent.callEvent() && willAttack) { // Logic moved to willAttack local variable. -+ { -+ // Paper end - PlayerAttackEntityEvent - float f = this.isAutoSpinAttack() ? this.autoSpinAttackDmg : (float) this.getAttributeValue(Attributes.ATTACK_DAMAGE); - ItemStack itemstack = this.getWeaponItem(); - DamageSource damagesource = this.damageSources().playerAttack(this); diff --git a/patches/server/0761-Fix-async-entity-add-due-to-fungus-trees.patch b/patches/server/0761-Fix-async-entity-add-due-to-fungus-trees.patch new file mode 100644 index 000000000000..009feeb37af8 --- /dev/null +++ b/patches/server/0761-Fix-async-entity-add-due-to-fungus-trees.patch @@ -0,0 +1,35 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Jake Potrebic +Date: Fri, 18 Mar 2022 21:30:00 -0700 +Subject: [PATCH] Fix async entity add due to fungus trees + + +diff --git a/src/main/java/net/minecraft/server/level/WorldGenRegion.java b/src/main/java/net/minecraft/server/level/WorldGenRegion.java +index 5bf438bb58833c1df3620e82d3d2b90207366372..2e72e92762877b28dd908711671e1dfb933de9b0 100644 +--- a/src/main/java/net/minecraft/server/level/WorldGenRegion.java ++++ b/src/main/java/net/minecraft/server/level/WorldGenRegion.java +@@ -237,6 +237,7 @@ public class WorldGenRegion implements WorldGenLevel { + if (iblockdata.isAir()) { + return false; + } else { ++ if (drop) LOGGER.warn("Potential async entity add during worldgen", new Throwable()); // Paper - Fix async entity add due to fungus trees; log when this happens + if (false) { // CraftBukkit - SPIGOT-6833: Do not drop during world generation + BlockEntity tileentity = iblockdata.hasBlockEntity() ? this.getBlockEntity(pos) : null; + +diff --git a/src/main/java/org/bukkit/craftbukkit/CraftRegionAccessor.java b/src/main/java/org/bukkit/craftbukkit/CraftRegionAccessor.java +index a93a879117ee1eb06842242aa03f757a4a676946..4c234e887c42b27754ed8f05f2000d9309274427 100644 +--- a/src/main/java/org/bukkit/craftbukkit/CraftRegionAccessor.java ++++ b/src/main/java/org/bukkit/craftbukkit/CraftRegionAccessor.java +@@ -261,10 +261,10 @@ public abstract class CraftRegionAccessor implements RegionAccessor { + ((ChorusFlowerBlock) Blocks.CHORUS_FLOWER).generatePlant(access, pos, random, 8); + return true; + case CRIMSON_FUNGUS: +- gen = TreeFeatures.CRIMSON_FUNGUS_PLANTED; ++ gen = this.isNormalWorld() ? TreeFeatures.CRIMSON_FUNGUS_PLANTED : TreeFeatures.CRIMSON_FUNGUS; // Paper - Fix async entity add due to fungus trees; if world gen, don't use planted version + break; + case WARPED_FUNGUS: +- gen = TreeFeatures.WARPED_FUNGUS_PLANTED; ++ gen = this.isNormalWorld() ? TreeFeatures.WARPED_FUNGUS_PLANTED : TreeFeatures.WARPED_FUNGUS; // Paper - Fix async entity add due to fungus trees; if world gen, don't use planted version + break; + case AZALEA: + gen = TreeFeatures.AZALEA_TREE; diff --git a/patches/server/0762-ItemStack-damage-API.patch b/patches/server/0762-ItemStack-damage-API.patch new file mode 100644 index 000000000000..55e7f06af162 --- /dev/null +++ b/patches/server/0762-ItemStack-damage-API.patch @@ -0,0 +1,121 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Jake Potrebic +Date: Sun, 8 May 2022 13:35:45 -0700 +Subject: [PATCH] ItemStack damage API + +Adds methods notify clients about item breaks and +to simulate damage done to an itemstack and all +the logic associated with damaging them + +== AT == +public net.minecraft.world.entity.LivingEntity entityEventForEquipmentBreak(Lnet/minecraft/world/entity/EquipmentSlot;)B + +diff --git a/src/main/java/net/minecraft/world/item/ItemStack.java b/src/main/java/net/minecraft/world/item/ItemStack.java +index bcb3a45166e5dd75dd727adf92304b3a75399c8d..90a55f00c36903d52630c51bf69322973a2b5274 100644 +--- a/src/main/java/net/minecraft/world/item/ItemStack.java ++++ b/src/main/java/net/minecraft/world/item/ItemStack.java +@@ -693,8 +693,13 @@ public final class ItemStack implements DataComponentHolder { + } + + public void hurtAndBreak(int amount, ServerLevel world, @Nullable LivingEntity player, Consumer breakCallback) { // Paper - Add EntityDamageItemEvent ++ // Paper start - add force boolean overload ++ this.hurtAndBreak(amount, world, player, breakCallback, false); ++ } ++ public void hurtAndBreak(int amount, ServerLevel world, @Nullable LivingEntity player, Consumer breakCallback, boolean force) { // Paper - Add EntityDamageItemEvent ++ // Paper end + int originalDamage = amount; // Paper - Expand PlayerItemDamageEvent +- int j = this.processDurabilityChange(amount, world, player); ++ int j = this.processDurabilityChange(amount, world, player, force); // Paper + // CraftBukkit start + if (player instanceof final ServerPlayer serverPlayer) { // Paper - Add EntityDamageItemEvent + PlayerItemDamageEvent event = new PlayerItemDamageEvent(serverPlayer.getBukkitEntity(), CraftItemStack.asCraftMirror(this), j, originalDamage); // Paper - Add EntityDamageItemEvent +@@ -726,7 +731,12 @@ public final class ItemStack implements DataComponentHolder { + } + + private int processDurabilityChange(int baseDamage, ServerLevel world, @Nullable LivingEntity player) { // Paper - Add EntityDamageItemEvent +- return !this.isDamageableItem() ? 0 : (player instanceof ServerPlayer && player.hasInfiniteMaterials() ? 0 : (baseDamage > 0 ? EnchantmentHelper.processDurabilityChange(world, this, baseDamage) : baseDamage)); // Paper - Add EntityDamageItemEvent ++ // Paper start - itemstack damage api ++ return processDurabilityChange(baseDamage, world, player, false); ++ } ++ private int processDurabilityChange(int baseDamage, ServerLevel world, @Nullable LivingEntity player, boolean force) { ++ return !this.isDamageableItem() ? 0 : (player instanceof ServerPlayer && player.hasInfiniteMaterials() && !force ? 0 : (baseDamage > 0 ? EnchantmentHelper.processDurabilityChange(world, this, baseDamage) : baseDamage)); // Paper - Add EntityDamageItemEvent ++ // Paper end - itemstack damage api + } + + private void applyDamage(int damage, @Nullable LivingEntity player, Consumer breakCallback) { // Paper - Add EntityDamageItemEvent +@@ -766,6 +776,11 @@ public final class ItemStack implements DataComponentHolder { + } + + public void hurtAndBreak(int amount, LivingEntity entity, EquipmentSlot slot) { ++ // Paper start - add param to skip infinite mats check ++ this.hurtAndBreak(amount, entity, slot, false); ++ } ++ public void hurtAndBreak(int amount, LivingEntity entity, EquipmentSlot slot, boolean force) { ++ // Paper end - add param to skip infinite mats check + Level world = entity.level(); + + if (world instanceof ServerLevel worldserver) { +@@ -778,8 +793,8 @@ public final class ItemStack implements DataComponentHolder { + } + + this.hurtAndBreak(amount, worldserver, entity, (item) -> { // Paper - Add EntityDamageItemEvent +- entity.onEquippedItemBroken(item, slot); +- }); ++ if (slot != null) entity.onEquippedItemBroken(item, slot); // Paper - itemstack damage API - do not process entity related callbacks when damaging from API ++ }, force); // Paper - itemstack damage API + } + + } +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftLivingEntity.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftLivingEntity.java +index 4fa19ddb1414282020e118eea298d57d2bf42754..1ceaa081231a617bd87331b308c24d9c7a8dcf2b 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftLivingEntity.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftLivingEntity.java +@@ -1154,4 +1154,48 @@ public class CraftLivingEntity extends CraftEntity implements LivingEntity { + this.getHandle().knockback(strength, directionX, directionZ); + }; + // Paper end - knockback API ++ ++ // Paper start - ItemStack damage API ++ public void broadcastSlotBreak(final org.bukkit.inventory.EquipmentSlot slot) { ++ this.getHandle().level().broadcastEntityEvent(this.getHandle(), net.minecraft.world.entity.LivingEntity.entityEventForEquipmentBreak(org.bukkit.craftbukkit.CraftEquipmentSlot.getNMS(slot))); ++ } ++ ++ @Override ++ public void broadcastSlotBreak(final org.bukkit.inventory.EquipmentSlot slot, final Collection players) { ++ if (players.isEmpty()) { ++ return; ++ } ++ final net.minecraft.network.protocol.game.ClientboundEntityEventPacket packet = new net.minecraft.network.protocol.game.ClientboundEntityEventPacket( ++ this.getHandle(), ++ net.minecraft.world.entity.LivingEntity.entityEventForEquipmentBreak(org.bukkit.craftbukkit.CraftEquipmentSlot.getNMS(slot)) ++ ); ++ players.forEach(player -> ((CraftPlayer) player).getHandle().connection.send(packet)); ++ } ++ ++ @Override ++ public ItemStack damageItemStack(ItemStack stack, final int amount) { ++ final net.minecraft.world.item.ItemStack nmsStack; ++ if (stack instanceof final CraftItemStack craftItemStack) { ++ if (craftItemStack.handle == null || craftItemStack.handle.isEmpty()) { ++ return stack; ++ } ++ nmsStack = craftItemStack.handle; ++ } else { ++ nmsStack = CraftItemStack.asNMSCopy(stack); ++ stack = CraftItemStack.asCraftMirror(nmsStack); // mirror to capture changes in hurt logic & events ++ } ++ this.damageItemStack0(nmsStack, amount, null); ++ return stack; ++ } ++ ++ @Override ++ public void damageItemStack(final org.bukkit.inventory.EquipmentSlot slot, final int amount) { ++ final net.minecraft.world.entity.EquipmentSlot nmsSlot = org.bukkit.craftbukkit.CraftEquipmentSlot.getNMS(slot); ++ this.damageItemStack0(this.getHandle().getItemBySlot(nmsSlot), amount, nmsSlot); ++ } ++ ++ private void damageItemStack0(final net.minecraft.world.item.ItemStack nmsStack, final int amount, final net.minecraft.world.entity.EquipmentSlot slot) { ++ nmsStack.hurtAndBreak(amount, this.getHandle(), slot, true); ++ } ++ // Paper end - ItemStack damage API + } diff --git a/patches/server/0762-ensure-reset-EnderDragon-boss-event-name.patch b/patches/server/0762-ensure-reset-EnderDragon-boss-event-name.patch deleted file mode 100644 index e2a769583def..000000000000 --- a/patches/server/0762-ensure-reset-EnderDragon-boss-event-name.patch +++ /dev/null @@ -1,39 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Jake Potrebic -Date: Sat, 12 Nov 2022 10:08:58 -0800 -Subject: [PATCH] ensure reset EnderDragon boss event name - -Fix MC-257487 - -diff --git a/src/main/java/net/minecraft/world/level/dimension/end/EndDragonFight.java b/src/main/java/net/minecraft/world/level/dimension/end/EndDragonFight.java -index 98d1c097fdbbc8080624f365634e0812a5eea5ac..bf39f006efef529db697ed4dccbd1657a2673b79 100644 ---- a/src/main/java/net/minecraft/world/level/dimension/end/EndDragonFight.java -+++ b/src/main/java/net/minecraft/world/level/dimension/end/EndDragonFight.java -@@ -73,6 +73,7 @@ public class EndDragonFight { - private static final int GATEWAY_DISTANCE = 96; - public static final int DRAGON_SPAWN_Y = 128; - private final Predicate validPlayer; -+ private static final Component DEFAULT_BOSS_EVENT_NAME = Component.translatable("entity.minecraft.ender_dragon"); // Paper - ensure reset EnderDragon boss event name - public final ServerBossEvent dragonEvent; - public final ServerLevel level; - private final BlockPos origin; -@@ -101,7 +102,7 @@ public class EndDragonFight { - } - - public EndDragonFight(ServerLevel world, long gatewaysSeed, EndDragonFight.Data data, BlockPos origin) { -- this.dragonEvent = (ServerBossEvent) (new ServerBossEvent(Component.translatable("entity.minecraft.ender_dragon"), BossEvent.BossBarColor.PINK, BossEvent.BossBarOverlay.PROGRESS)).setPlayBossMusic(true).setCreateWorldFog(true); -+ this.dragonEvent = (ServerBossEvent) (new ServerBossEvent(DEFAULT_BOSS_EVENT_NAME, BossEvent.BossBarColor.PINK, BossEvent.BossBarOverlay.PROGRESS)).setPlayBossMusic(true).setCreateWorldFog(true); // Paper - ensure reset EnderDragon boss event name - this.gateways = new ObjectArrayList(); - this.ticksSinceLastPlayerScan = 21; - this.skipArenaLoadedCheck = false; -@@ -505,6 +506,10 @@ public class EndDragonFight { - this.ticksSinceDragonSeen = 0; - if (dragon.hasCustomName()) { - this.dragonEvent.setName(dragon.getDisplayName()); -+ // Paper start - ensure reset EnderDragon boss event name -+ } else { -+ this.dragonEvent.setName(DEFAULT_BOSS_EVENT_NAME); -+ // Paper end - ensure reset EnderDragon boss event name - } - } - diff --git a/patches/server/0763-Add-Player-Warden-Warning-API.patch b/patches/server/0763-Add-Player-Warden-Warning-API.patch deleted file mode 100644 index bbb7bff753d6..000000000000 --- a/patches/server/0763-Add-Player-Warden-Warning-API.patch +++ /dev/null @@ -1,57 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: dawon -Date: Sat, 15 Oct 2022 00:46:57 +0200 -Subject: [PATCH] Add Player Warden Warning API - -== AT == -public net.minecraft.server.level.ServerPlayer wardenSpawnTracker -public net.minecraft.world.entity.monster.warden.WardenSpawnTracker ticksSinceLastWarning -public net.minecraft.world.entity.monster.warden.WardenSpawnTracker cooldownTicks -public net.minecraft.world.entity.monster.warden.WardenSpawnTracker increaseWarningLevel()V - -diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java -index 1e563ed8f7e85d68682e679b27b0f6100eb10aea..922ec82d566fd5ac0b40ed95629e63be3d1bf264 100644 ---- a/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java -+++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java -@@ -3303,6 +3303,41 @@ public class CraftPlayer extends CraftHumanEntity implements Player { - public void showElderGuardian(boolean silent) { - if (getHandle().connection != null) getHandle().connection.send(new ClientboundGameEventPacket(ClientboundGameEventPacket.GUARDIAN_ELDER_EFFECT, silent ? 0F : 1F)); - } -+ -+ @Override -+ public int getWardenWarningCooldown() { -+ return this.getHandle().wardenSpawnTracker.cooldownTicks; -+ } -+ -+ @Override -+ public void setWardenWarningCooldown(int cooldown) { -+ this.getHandle().wardenSpawnTracker.cooldownTicks = Math.max(cooldown, 0); -+ } -+ -+ @Override -+ public int getWardenTimeSinceLastWarning() { -+ return this.getHandle().wardenSpawnTracker.ticksSinceLastWarning; -+ } -+ -+ @Override -+ public void setWardenTimeSinceLastWarning(int time) { -+ this.getHandle().wardenSpawnTracker.ticksSinceLastWarning = time; -+ } -+ -+ @Override -+ public int getWardenWarningLevel() { -+ return this.getHandle().wardenSpawnTracker.getWarningLevel(); -+ } -+ -+ @Override -+ public void setWardenWarningLevel(int warningLevel) { -+ this.getHandle().wardenSpawnTracker.setWarningLevel(warningLevel); -+ } -+ -+ @Override -+ public void increaseWardenWarningLevel() { -+ this.getHandle().wardenSpawnTracker.increaseWarningLevel(); -+ } - // Paper end - - public Player.Spigot spigot() diff --git a/patches/server/0763-Friction-API.patch b/patches/server/0763-Friction-API.patch new file mode 100644 index 000000000000..8a1f01205303 --- /dev/null +++ b/patches/server/0763-Friction-API.patch @@ -0,0 +1,245 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Noah van der Aa +Date: Wed, 15 Sep 2021 20:44:22 +0200 +Subject: [PATCH] Friction API + + +diff --git a/src/main/java/net/minecraft/world/entity/LivingEntity.java b/src/main/java/net/minecraft/world/entity/LivingEntity.java +index 5330f6315cecfa6afd04b711a5b8656717cb5ede..8b0a764984f886b711cb337a7f70608167916bf3 100644 +--- a/src/main/java/net/minecraft/world/entity/LivingEntity.java ++++ b/src/main/java/net/minecraft/world/entity/LivingEntity.java +@@ -296,6 +296,7 @@ public abstract class LivingEntity extends Entity implements Attackable { + public boolean bukkitPickUpLoot; + public org.bukkit.craftbukkit.entity.CraftLivingEntity getBukkitLivingEntity() { return (org.bukkit.craftbukkit.entity.CraftLivingEntity) super.getBukkitEntity(); } // Paper + public boolean silentDeath = false; // Paper - mark entity as dying silently for cancellable death event ++ public net.kyori.adventure.util.TriState frictionState = net.kyori.adventure.util.TriState.NOT_SET; // Paper - Friction API + + @Override + public float getBukkitYaw() { +@@ -722,7 +723,7 @@ public abstract class LivingEntity extends Entity implements Attackable { + } + + public boolean shouldDiscardFriction() { +- return this.discardFriction; ++ return !this.frictionState.toBooleanOrElse(!this.discardFriction); // Paper - Friction API + } + + public void setDiscardFriction(boolean noDrag) { +@@ -796,6 +797,11 @@ public abstract class LivingEntity extends Entity implements Attackable { + + @Override + public void addAdditionalSaveData(CompoundTag nbt) { ++ // Paper start - Friction API ++ if (this.frictionState != net.kyori.adventure.util.TriState.NOT_SET) { ++ nbt.putString("Paper.FrictionState", this.frictionState.toString()); ++ } ++ // Paper end - Friction API + nbt.putFloat("Health", this.getHealth()); + nbt.putShort("HurtTime", (short) this.hurtTime); + nbt.putInt("HurtByTimestamp", this.lastHurtByMobTimestamp); +@@ -839,6 +845,16 @@ public abstract class LivingEntity extends Entity implements Attackable { + } + this.internalSetAbsorptionAmount(absorptionAmount); + // Paper end - Check for NaN ++ // Paper start - Friction API ++ if (nbt.contains("Paper.FrictionState")) { ++ String fs = nbt.getString("Paper.FrictionState"); ++ try { ++ frictionState = net.kyori.adventure.util.TriState.valueOf(fs); ++ } catch (Exception ignored) { ++ LOGGER.error("Unknown friction state " + fs + " for " + this); ++ } ++ } ++ // Paper end - Friction API + if (nbt.contains("attributes", 9) && this.level() != null && !this.level().isClientSide) { + this.getAttributes().load(nbt.getList("attributes", 10)); + } +diff --git a/src/main/java/net/minecraft/world/entity/item/ItemEntity.java b/src/main/java/net/minecraft/world/entity/item/ItemEntity.java +index d555fd0b200c012f30ed0c0ec09a37b25a737b76..7a6d51020d9c6be33b4c34c0d608559589d5b390 100644 +--- a/src/main/java/net/minecraft/world/entity/item/ItemEntity.java ++++ b/src/main/java/net/minecraft/world/entity/item/ItemEntity.java +@@ -63,6 +63,7 @@ public class ItemEntity extends Entity implements TraceableEntity { + private int lastTick = MinecraftServer.currentTick - 1; // CraftBukkit + public boolean canMobPickup = true; // Paper - Item#canEntityPickup + private int despawnRate = -1; // Paper - Alternative item-despawn-rate ++ public net.kyori.adventure.util.TriState frictionState = net.kyori.adventure.util.TriState.NOT_SET; // Paper - Friction API + + public ItemEntity(EntityType type, Level world) { + super(type, world); +@@ -186,7 +187,11 @@ public class ItemEntity extends Entity implements TraceableEntity { + this.applyEffectsFromBlocks(); + float f = 0.98F; + +- if (this.onGround()) { ++ // Paper start - Friction API ++ if (frictionState == net.kyori.adventure.util.TriState.FALSE) { ++ f = 1F; ++ } else if (this.onGround()) { ++ // Paper end - Friction API + f = this.level().getBlockState(this.getBlockPosBelowThatAffectsMyMovement()).getBlock().getFriction() * 0.98F; + } + +@@ -409,6 +414,11 @@ public class ItemEntity extends Entity implements TraceableEntity { + + @Override + public void addAdditionalSaveData(CompoundTag nbt) { ++ // Paper start - Friction API ++ if (this.frictionState != net.kyori.adventure.util.TriState.NOT_SET) { ++ nbt.putString("Paper.FrictionState", this.frictionState.toString()); ++ } ++ // Paper end - Friction API + nbt.putShort("Health", (short) this.health); + nbt.putShort("Age", (short) this.age); + nbt.putShort("PickupDelay", (short) this.pickupDelay); +@@ -451,6 +461,17 @@ public class ItemEntity extends Entity implements TraceableEntity { + this.setItem(ItemStack.EMPTY); + } + ++ // Paper start - Friction API ++ if (nbt.contains("Paper.FrictionState")) { ++ String fs = nbt.getString("Paper.FrictionState"); ++ try { ++ frictionState = net.kyori.adventure.util.TriState.valueOf(fs); ++ } catch (Exception ignored) { ++ com.mojang.logging.LogUtils.getLogger().error("Unknown friction state " + fs + " for " + this); ++ } ++ } ++ // Paper end - Friction API ++ + if (this.getItem().isEmpty()) { + this.discard(null); // CraftBukkit - add Bukkit remove cause + } +diff --git a/src/main/java/net/minecraft/world/entity/vehicle/AbstractMinecart.java b/src/main/java/net/minecraft/world/entity/vehicle/AbstractMinecart.java +index d8fcd6d1edec1f31a861fab4b86cbeb15ddc799d..d277f56fef882313d6d21f636fafae2f26630ad7 100644 +--- a/src/main/java/net/minecraft/world/entity/vehicle/AbstractMinecart.java ++++ b/src/main/java/net/minecraft/world/entity/vehicle/AbstractMinecart.java +@@ -93,6 +93,7 @@ public abstract class AbstractMinecart extends VehicleEntity { + private double flyingZ = 0.95; + public double maxSpeed = 0.4D; + // CraftBukkit end ++ public net.kyori.adventure.util.TriState frictionState = net.kyori.adventure.util.TriState.NOT_SET; // Paper - Friction API + + protected AbstractMinecart(EntityType type, Level world) { + super(type, world); +@@ -552,6 +553,16 @@ public abstract class AbstractMinecart extends VehicleEntity { + + this.flipped = nbt.getBoolean("FlippedRotation"); + this.firstTick = nbt.getBoolean("HasTicked"); ++ // Paper start - Friction API ++ if (nbt.contains("Paper.FrictionState")) { ++ String fs = nbt.getString("Paper.FrictionState"); ++ try { ++ frictionState = net.kyori.adventure.util.TriState.valueOf(fs); ++ } catch (Exception ignored) { ++ com.mojang.logging.LogUtils.getLogger().error("Unknown friction state " + fs + " for " + this); ++ } ++ } ++ // Paper end - Friction API + } + + @Override +@@ -564,6 +575,12 @@ public abstract class AbstractMinecart extends VehicleEntity { + + nbt.putBoolean("FlippedRotation", this.flipped); + nbt.putBoolean("HasTicked", this.firstTick); ++ ++ // Paper start - Friction API ++ if (this.frictionState != net.kyori.adventure.util.TriState.NOT_SET) { ++ nbt.putString("Paper.FrictionState", this.frictionState.toString()); ++ } ++ // Paper end - Friction API + } + + @Override +diff --git a/src/main/java/net/minecraft/world/entity/vehicle/NewMinecartBehavior.java b/src/main/java/net/minecraft/world/entity/vehicle/NewMinecartBehavior.java +index 00b0004940339dc105fb95f813bd35b16f7a9fb4..a8718ee94cd6b9a20bd1e9a49d58d39e6f3f2a7a 100644 +--- a/src/main/java/net/minecraft/world/entity/vehicle/NewMinecartBehavior.java ++++ b/src/main/java/net/minecraft/world/entity/vehicle/NewMinecartBehavior.java +@@ -548,6 +548,7 @@ public class NewMinecartBehavior extends MinecartBehavior { + + @Override + public double getSlowdownFactor() { ++ if (this.minecart.frictionState == net.kyori.adventure.util.TriState.FALSE) return 1; // Paper + return this.minecart.isVehicle() || !this.minecart.slowWhenEmpty ? 0.997D : 0.975D; // CraftBukkit - add !this.slowWhenEmpty + } + +diff --git a/src/main/java/net/minecraft/world/entity/vehicle/OldMinecartBehavior.java b/src/main/java/net/minecraft/world/entity/vehicle/OldMinecartBehavior.java +index cf871c174091139c8ad1affb84f98fcd74b60dee..23cbafcc12f6e5f5755215a72879a6cab306ad18 100644 +--- a/src/main/java/net/minecraft/world/entity/vehicle/OldMinecartBehavior.java ++++ b/src/main/java/net/minecraft/world/entity/vehicle/OldMinecartBehavior.java +@@ -522,6 +522,7 @@ public class OldMinecartBehavior extends MinecartBehavior { + + @Override + public double getSlowdownFactor() { ++ if (this.minecart.frictionState == net.kyori.adventure.util.TriState.FALSE) return 1; // Paper + return this.minecart.isVehicle() || !this.minecart.slowWhenEmpty ? 0.997D : 0.96D; // CraftBukkit - add !this.slowWhenEmpty + } + } +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftItem.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftItem.java +index 1a291dd8a287db30e71dcb315599fc4b038764c4..30d62ee4d5cd2ddacb8783b5bbbf475d592b3e02 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftItem.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftItem.java +@@ -99,6 +99,18 @@ public class CraftItem extends CraftEntity implements Item { + this.getHandle().age = willAge ? 0 : NO_AGE_TIME; + } + ++ @org.jetbrains.annotations.NotNull ++ @Override ++ public net.kyori.adventure.util.TriState getFrictionState() { ++ return this.getHandle().frictionState; ++ } ++ ++ @Override ++ public void setFrictionState(@org.jetbrains.annotations.NotNull net.kyori.adventure.util.TriState state) { ++ java.util.Objects.requireNonNull(state, "state may not be null"); ++ this.getHandle().frictionState = state; ++ } ++ + @Override + public int getHealth() { + return this.getHandle().health; +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftLivingEntity.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftLivingEntity.java +index 1ceaa081231a617bd87331b308c24d9c7a8dcf2b..2fd4a3068d86a37cc18c9203448823c53d590ffb 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftLivingEntity.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftLivingEntity.java +@@ -1198,4 +1198,17 @@ public class CraftLivingEntity extends CraftEntity implements LivingEntity { + nmsStack.hurtAndBreak(amount, this.getHandle(), slot, true); + } + // Paper end - ItemStack damage API ++ // Paper start - friction API ++ @org.jetbrains.annotations.NotNull ++ @Override ++ public net.kyori.adventure.util.TriState getFrictionState() { ++ return this.getHandle().frictionState; ++ } ++ ++ @Override ++ public void setFrictionState(@org.jetbrains.annotations.NotNull net.kyori.adventure.util.TriState state) { ++ java.util.Objects.requireNonNull(state, "state may not be null"); ++ this.getHandle().frictionState = state; ++ } ++ // Paper end - friction API + } +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftMinecart.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftMinecart.java +index d35c1a10e58932b19c8053c5dacdc25fd7f22e8c..ab9d06a9a4951a5b8aa14d47818a3850433e92b8 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftMinecart.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftMinecart.java +@@ -137,4 +137,18 @@ public abstract class CraftMinecart extends CraftVehicle implements Minecart { + public int getDisplayBlockOffset() { + return this.getHandle().getDisplayOffset(); + } ++ ++ // Paper start - Friction API ++ @org.jetbrains.annotations.NotNull ++ @Override ++ public net.kyori.adventure.util.TriState getFrictionState() { ++ return this.getHandle().frictionState; ++ } ++ ++ @Override ++ public void setFrictionState(@org.jetbrains.annotations.NotNull net.kyori.adventure.util.TriState state) { ++ java.util.Objects.requireNonNull(state, "state may not be null"); ++ this.getHandle().frictionState = state; ++ } ++ // Paper end - Friction API + } diff --git a/patches/server/0764-Ability-to-control-player-s-insomnia-and-phantoms.patch b/patches/server/0764-Ability-to-control-player-s-insomnia-and-phantoms.patch new file mode 100644 index 000000000000..0b482955d51f --- /dev/null +++ b/patches/server/0764-Ability-to-control-player-s-insomnia-and-phantoms.patch @@ -0,0 +1,67 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Jan Villim +Date: Sat, 22 Jan 2022 17:56:19 +0100 +Subject: [PATCH] Ability to control player's insomnia and phantoms + + +diff --git a/src/main/java/net/minecraft/world/entity/EntitySelector.java b/src/main/java/net/minecraft/world/entity/EntitySelector.java +index 721321a19ce056f82de2bef44a8791dc9d5b3418..6bf691fcc6486bde73bae30eff09142802c29eda 100644 +--- a/src/main/java/net/minecraft/world/entity/EntitySelector.java ++++ b/src/main/java/net/minecraft/world/entity/EntitySelector.java +@@ -27,7 +27,18 @@ public final class EntitySelector { + }; + public static final Predicate CAN_BE_COLLIDED_WITH = EntitySelector.NO_SPECTATORS.and(Entity::canBeCollidedWith); + public static final Predicate CAN_BE_PICKED = EntitySelector.NO_SPECTATORS.and(Entity::isPickable); +- public static Predicate IS_INSOMNIAC = (player) -> net.minecraft.util.Mth.clamp(((net.minecraft.server.level.ServerPlayer) player).getStats().getValue(net.minecraft.stats.Stats.CUSTOM.get(net.minecraft.stats.Stats.TIME_SINCE_REST)), 1, Integer.MAX_VALUE) >= 72000; // Paper - Add phantom creative and insomniac controls ++ // Paper start - Ability to control player's insomnia and phantoms ++ public static Predicate IS_INSOMNIAC = (player) -> { ++ net.minecraft.server.level.ServerPlayer serverPlayer = (net.minecraft.server.level.ServerPlayer) player; ++ int playerInsomniaTicks = serverPlayer.level().paperConfig().entities.behavior.playerInsomniaStartTicks; ++ ++ if (playerInsomniaTicks <= 0) { ++ return false; ++ } ++ ++ return net.minecraft.util.Mth.clamp(serverPlayer.getStats().getValue(net.minecraft.stats.Stats.CUSTOM.get(net.minecraft.stats.Stats.TIME_SINCE_REST)), 1, Integer.MAX_VALUE) >= playerInsomniaTicks; ++ }; ++ // Paper end - Ability to control player's insomnia and phantoms + + private EntitySelector() {} + // Paper start - Affects Spawning API +diff --git a/src/main/java/net/minecraft/world/level/levelgen/PhantomSpawner.java b/src/main/java/net/minecraft/world/level/levelgen/PhantomSpawner.java +index 7d407a7597f3ae576ac7e94bc2eb96d9dcd78dd3..021221da5d0315f6e371380a705ac6b3f6ac18d3 100644 +--- a/src/main/java/net/minecraft/world/level/levelgen/PhantomSpawner.java ++++ b/src/main/java/net/minecraft/world/level/levelgen/PhantomSpawner.java +@@ -32,13 +32,22 @@ public class PhantomSpawner implements CustomSpawner { + } else if (!world.getGameRules().getBoolean(GameRules.RULE_DOINSOMNIA)) { + return 0; + } else { ++ // Paper start - Ability to control player's insomnia and phantoms ++ if (world.paperConfig().entities.behavior.phantomsSpawnAttemptMaxSeconds <= 0) { ++ return 0; ++ } ++ // Paper end - Ability to control player's insomnia and phantoms + RandomSource randomsource = world.random; + + --this.nextTick; + if (this.nextTick > 0) { + return 0; + } else { +- this.nextTick += (60 + randomsource.nextInt(60)) * 20; ++ // Paper start - Ability to control player's insomnia and phantoms ++ int spawnAttemptMinSeconds = world.paperConfig().entities.behavior.phantomsSpawnAttemptMinSeconds; ++ int spawnAttemptMaxSeconds = world.paperConfig().entities.behavior.phantomsSpawnAttemptMaxSeconds; ++ this.nextTick += (spawnAttemptMinSeconds + randomsource.nextInt(spawnAttemptMaxSeconds - spawnAttemptMinSeconds + 1)) * 20; ++ // Paper end - Ability to control player's insomnia and phantoms + if (world.getSkyDarken() < 5 && world.dimensionType().hasSkyLight()) { + return 0; + } else { +@@ -59,7 +68,7 @@ public class PhantomSpawner implements CustomSpawner { + int j = Mth.clamp(serverstatisticmanager.getValue(Stats.CUSTOM.get(Stats.TIME_SINCE_REST)), 1, Integer.MAX_VALUE); + boolean flag2 = true; + +- if (randomsource.nextInt(j) >= 72000) { ++ if (randomsource.nextInt(j) >= world.paperConfig().entities.behavior.playerInsomniaStartTicks) { // Paper - Ability to control player's insomnia and phantoms + BlockPos blockposition1 = blockposition.above(20 + randomsource.nextInt(15)).east(-10 + randomsource.nextInt(21)).south(-10 + randomsource.nextInt(21)); + BlockState iblockdata = world.getBlockState(blockposition1); + FluidState fluid = world.getFluidState(blockposition1); diff --git a/patches/server/0764-More-vanilla-friendly-methods-to-update-trades.patch b/patches/server/0764-More-vanilla-friendly-methods-to-update-trades.patch deleted file mode 100644 index 5c30d5f82cb8..000000000000 --- a/patches/server/0764-More-vanilla-friendly-methods-to-update-trades.patch +++ /dev/null @@ -1,75 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Lulu13022002 <41980282+Lulu13022002@users.noreply.github.com> -Date: Sun, 16 Oct 2022 16:12:49 +0200 -Subject: [PATCH] More vanilla friendly methods to update trades - - -diff --git a/src/main/java/net/minecraft/world/entity/npc/Villager.java b/src/main/java/net/minecraft/world/entity/npc/Villager.java -index 9d5a5a7fff7f75871e167f83edb0e9d5348748d7..393588661c41b490ee6bce2f687962f7ddeff7d4 100644 ---- a/src/main/java/net/minecraft/world/entity/npc/Villager.java -+++ b/src/main/java/net/minecraft/world/entity/npc/Villager.java -@@ -921,6 +921,12 @@ public class Villager extends AbstractVillager implements ReputationEventHandler - - @Override - protected void updateTrades() { -+ // Paper start - More vanilla friendly methods to update trades -+ updateTrades(TRADES_PER_LEVEL); -+ } -+ -+ public boolean updateTrades(int amount) { -+ // Paper end - More vanilla friendly methods to update trades - VillagerData villagerdata = this.getVillagerData(); - Int2ObjectMap int2objectmap; - -@@ -938,9 +944,11 @@ public class Villager extends AbstractVillager implements ReputationEventHandler - if (avillagertrades_imerchantrecipeoption != null) { - MerchantOffers merchantrecipelist = this.getOffers(); - -- this.addOffersFromItemListings(merchantrecipelist, avillagertrades_imerchantrecipeoption, 2); -+ this.addOffersFromItemListings(merchantrecipelist, avillagertrades_imerchantrecipeoption, amount); // Paper - More vanilla friendly methods to update trades -+ return true; // Paper - More vanilla friendly methods to update trades - } - } -+ return false; // Paper - More vanilla friendly methods to update trades - } - - public void gossip(ServerLevel world, Villager villager, long time) { -diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftVillager.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftVillager.java -index 52312bec840322d32ea845f0bd64eb3ca1380854..bd2987fa1fb194a581567134ed980e8fc043f435 100644 ---- a/src/main/java/org/bukkit/craftbukkit/entity/CraftVillager.java -+++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftVillager.java -@@ -97,6 +97,34 @@ public class CraftVillager extends CraftAbstractVillager implements Villager { - } - - // Paper start -+ @Override -+ public boolean increaseLevel(int amount) { -+ Preconditions.checkArgument(amount > 0, "Level earned must be positive"); -+ int supposedFinalLevel = this.getVillagerLevel() + amount; -+ Preconditions.checkArgument(net.minecraft.world.entity.npc.VillagerData.MIN_VILLAGER_LEVEL <= supposedFinalLevel && supposedFinalLevel <= net.minecraft.world.entity.npc.VillagerData.MAX_VILLAGER_LEVEL, -+ "Final level reached after the donation (%d) must be between [%d, %d]".formatted(supposedFinalLevel, net.minecraft.world.entity.npc.VillagerData.MIN_VILLAGER_LEVEL, net.minecraft.world.entity.npc.VillagerData.MAX_VILLAGER_LEVEL)); -+ -+ it.unimi.dsi.fastutil.ints.Int2ObjectMap trades = -+ net.minecraft.world.entity.npc.VillagerTrades.TRADES.get(this.getHandle().getVillagerData().getProfession()); -+ -+ if (trades == null || trades.isEmpty()) { -+ this.getHandle().setVillagerData(this.getHandle().getVillagerData().setLevel(supposedFinalLevel)); -+ return false; -+ } -+ -+ while (amount > 0) { -+ this.getHandle().increaseMerchantCareer(); -+ amount--; -+ } -+ return true; -+ } -+ -+ @Override -+ public boolean addTrades(int amount) { -+ Preconditions.checkArgument(amount > 0, "Number of trades unlocked must be positive"); -+ return this.getHandle().updateTrades(amount); -+ } -+ - @Override - public int getRestocksToday() { - return getHandle().numberOfRestocksToday; diff --git a/patches/server/0765-Fix-premature-player-kicks-on-shutdown.patch b/patches/server/0765-Fix-premature-player-kicks-on-shutdown.patch new file mode 100644 index 000000000000..5a0d9d65c0bd --- /dev/null +++ b/patches/server/0765-Fix-premature-player-kicks-on-shutdown.patch @@ -0,0 +1,61 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Shane Freeder +Date: Thu, 11 Apr 2024 16:37:44 +0100 +Subject: [PATCH] Fix premature player kicks on shutdown + +When the server is stopping, the default execution handler method will throw a +RejectedExecutionException in order to prevent further execution, this causes +us to lose the actual kick reason. To mitigate this, we'll use a seperate marked +class in order to gracefully ignore these. + +diff --git a/src/main/java/io/papermc/paper/util/ServerStopRejectedExecutionException.java b/src/main/java/io/papermc/paper/util/ServerStopRejectedExecutionException.java +new file mode 100644 +index 0000000000000000000000000000000000000000..2c5cd77103c5a33d4349ab6b9ee2d8378bb60eb4 +--- /dev/null ++++ b/src/main/java/io/papermc/paper/util/ServerStopRejectedExecutionException.java +@@ -0,0 +1,20 @@ ++package io.papermc.paper.util; ++ ++import java.util.concurrent.RejectedExecutionException; ++ ++public class ServerStopRejectedExecutionException extends RejectedExecutionException { ++ public ServerStopRejectedExecutionException() { ++ } ++ ++ public ServerStopRejectedExecutionException(final String message) { ++ super(message); ++ } ++ ++ public ServerStopRejectedExecutionException(final String message, final Throwable cause) { ++ super(message, cause); ++ } ++ ++ public ServerStopRejectedExecutionException(final Throwable cause) { ++ super(cause); ++ } ++} +diff --git a/src/main/java/net/minecraft/network/Connection.java b/src/main/java/net/minecraft/network/Connection.java +index 4d9f1fc884050993287adfa4578a87da710623fb..a8dfe7a4b3d01bf75587be078f471d1ef1d7a667 100644 +--- a/src/main/java/net/minecraft/network/Connection.java ++++ b/src/main/java/net/minecraft/network/Connection.java +@@ -285,6 +285,7 @@ public class Connection extends SimpleChannelInboundHandler> { + Connection.genericsFtw(packet, packetlistener); + } catch (RunningOnDifferentThreadException cancelledpackethandleexception) { + ; ++ } catch (io.papermc.paper.util.ServerStopRejectedExecutionException ignored) { // Paper - do not prematurely disconnect players on stop + } catch (RejectedExecutionException rejectedexecutionexception) { + this.disconnect((Component) Component.translatable("multiplayer.disconnect.server_shutdown")); + } catch (ClassCastException classcastexception) { +diff --git a/src/main/java/net/minecraft/server/MinecraftServer.java b/src/main/java/net/minecraft/server/MinecraftServer.java +index cb40a2d855e7ac8c1c8b4ec679fd057a31f3fc6f..8f7a808b0c89953a7f2932e68e2c85f9330469f2 100644 +--- a/src/main/java/net/minecraft/server/MinecraftServer.java ++++ b/src/main/java/net/minecraft/server/MinecraftServer.java +@@ -2127,7 +2127,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop +Date: Fri, 14 Jan 2022 10:20:40 -0800 +Subject: [PATCH] Sync offhand slot in menus + +Menus don't add slots for the offhand, so on sendAllDataToRemote calls the +offhand slot isn't sent. This is not correct because you *can* put stuff into the offhand +by pressing the offhand swap item + +diff --git a/src/main/java/net/minecraft/server/level/ServerPlayer.java b/src/main/java/net/minecraft/server/level/ServerPlayer.java +index 04e3c75c9abfaccb6d2d59d234e5169258b77553..25b1e8bec23465f0e9a17f156bdff7fe716db84c 100644 +--- a/src/main/java/net/minecraft/server/level/ServerPlayer.java ++++ b/src/main/java/net/minecraft/server/level/ServerPlayer.java +@@ -350,6 +350,13 @@ public class ServerPlayer extends net.minecraft.world.entity.player.Player { + + } + ++ // Paper start - Sync offhand slot in menus ++ @Override ++ public void sendOffHandSlotChange() { ++ ServerPlayer.this.connection.send(new ClientboundContainerSetSlotPacket(ServerPlayer.this.inventoryMenu.containerId, ServerPlayer.this.inventoryMenu.incrementStateId(), net.minecraft.world.inventory.InventoryMenu.SHIELD_SLOT, ServerPlayer.this.inventoryMenu.getSlot(net.minecraft.world.inventory.InventoryMenu.SHIELD_SLOT).getItem().copy())); ++ } ++ // Paper end - Sync offhand slot in menus ++ + @Override + public void sendSlotChange(AbstractContainerMenu handler, int slot, ItemStack stack) { + ServerPlayer.this.connection.send(new ClientboundContainerSetSlotPacket(handler.containerId, handler.incrementStateId(), slot, stack)); +diff --git a/src/main/java/net/minecraft/world/inventory/AbstractContainerMenu.java b/src/main/java/net/minecraft/world/inventory/AbstractContainerMenu.java +index d830504d08c9de92797c432a868c1ee9dfc46a91..fc23e2dc42e907d5f8dc134a06102cc3e7fde515 100644 +--- a/src/main/java/net/minecraft/world/inventory/AbstractContainerMenu.java ++++ b/src/main/java/net/minecraft/world/inventory/AbstractContainerMenu.java +@@ -228,6 +228,7 @@ public abstract class AbstractContainerMenu { + + if (this.synchronizer != null) { + this.synchronizer.sendInitialData(this, this.remoteSlots, this.remoteCarried, this.remoteDataSlots.toIntArray()); ++ this.synchronizer.sendOffHandSlotChange(); // Paper - Sync offhand slot in menus; update player's offhand since the offhand slot is not added to the slots for menus but can be changed by swapping from a menu slot + } + + } +diff --git a/src/main/java/net/minecraft/world/inventory/ContainerSynchronizer.java b/src/main/java/net/minecraft/world/inventory/ContainerSynchronizer.java +index ff4fa86f9408e83e505f7e27692d3423f8570c48..a45ef5fcffc05e4e30801b73e82d29c6dbf5b8fd 100644 +--- a/src/main/java/net/minecraft/world/inventory/ContainerSynchronizer.java ++++ b/src/main/java/net/minecraft/world/inventory/ContainerSynchronizer.java +@@ -6,6 +6,7 @@ import net.minecraft.world.item.ItemStack; + public interface ContainerSynchronizer { + void sendInitialData(AbstractContainerMenu handler, NonNullList stacks, ItemStack cursorStack, int[] properties); + ++ default void sendOffHandSlotChange() {} // Paper - Sync offhand slot in menus + void sendSlotChange(AbstractContainerMenu handler, int slot, ItemStack stack); + + void sendCarriedChange(AbstractContainerMenu handler, ItemStack stack); diff --git a/patches/server/0766-check-global-player-list-where-appropriate.patch b/patches/server/0766-check-global-player-list-where-appropriate.patch deleted file mode 100644 index f777294a4901..000000000000 --- a/patches/server/0766-check-global-player-list-where-appropriate.patch +++ /dev/null @@ -1,85 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Jake Potrebic -Date: Tue, 22 Nov 2022 13:16:01 -0800 -Subject: [PATCH] check global player list where appropriate - -Makes certain entities check all players when searching for a player -instead of just checking players in their world. - -diff --git a/src/main/java/net/minecraft/server/level/ServerLevel.java b/src/main/java/net/minecraft/server/level/ServerLevel.java -index 4bf6281a75597072b19658208e4447c4d1ee8ba2..688f9f13ae06337250e2e9ac2ddf9ad90d049f9b 100644 ---- a/src/main/java/net/minecraft/server/level/ServerLevel.java -+++ b/src/main/java/net/minecraft/server/level/ServerLevel.java -@@ -2302,4 +2302,12 @@ public class ServerLevel extends Level implements WorldGenLevel { - entity.updateDynamicGameEventListener(DynamicGameEventListener::move); - } - } -+ -+ // Paper start - check global player list where appropriate -+ @Override -+ @Nullable -+ public Player getGlobalPlayerByUUID(UUID uuid) { -+ return this.server.getPlayerList().getPlayer(uuid); -+ } -+ // Paper end - check global player list where appropriate - } -diff --git a/src/main/java/net/minecraft/world/entity/LivingEntity.java b/src/main/java/net/minecraft/world/entity/LivingEntity.java -index e6d20887572817099cb863515978d3f06926be3d..f662a791e8cb045903a0de29778e5f30892fb766 100644 ---- a/src/main/java/net/minecraft/world/entity/LivingEntity.java -+++ b/src/main/java/net/minecraft/world/entity/LivingEntity.java -@@ -3723,7 +3723,7 @@ public abstract class LivingEntity extends Entity implements Attackable { - } - - public void onItemPickup(ItemEntity item) { -- Entity entity = item.getOwner(); -+ Entity entity = item.thrower != null ? this.level().getGlobalPlayerByUUID(item.thrower) : null; // Paper - check global player list where appropriate - - if (entity instanceof ServerPlayer) { - CriteriaTriggers.THROWN_ITEM_PICKED_UP_BY_ENTITY.trigger((ServerPlayer) entity, item.getItem(), this); -diff --git a/src/main/java/net/minecraft/world/entity/monster/ZombieVillager.java b/src/main/java/net/minecraft/world/entity/monster/ZombieVillager.java -index f8c733961015ace508bfe14fd61d5188ca9d551b..e0dabbf6d7a87b8722769c78ef0d2ba4353ed2cb 100644 ---- a/src/main/java/net/minecraft/world/entity/monster/ZombieVillager.java -+++ b/src/main/java/net/minecraft/world/entity/monster/ZombieVillager.java -@@ -269,7 +269,7 @@ public class ZombieVillager extends Zombie implements VillagerDataHolder { - entityvillager.finalizeSpawn(world, world.getCurrentDifficultyAt(entityvillager.blockPosition()), MobSpawnType.CONVERSION, (SpawnGroupData) null); - entityvillager.refreshBrain(world); - if (this.conversionStarter != null) { -- Player entityhuman = world.getPlayerByUUID(this.conversionStarter); -+ Player entityhuman = world.getGlobalPlayerByUUID(this.conversionStarter); // Paper - check global player list where appropriate - - if (entityhuman instanceof ServerPlayer) { - CriteriaTriggers.CURED_ZOMBIE_VILLAGER.trigger((ServerPlayer) entityhuman, this, entityvillager); -diff --git a/src/main/java/net/minecraft/world/level/EntityGetter.java b/src/main/java/net/minecraft/world/level/EntityGetter.java -index d465fb01af4c8610f83ecb9c68b83127cf7e95ae..bd20bea7f76a7307f1698fb2dfef37125032d166 100644 ---- a/src/main/java/net/minecraft/world/level/EntityGetter.java -+++ b/src/main/java/net/minecraft/world/level/EntityGetter.java -@@ -237,4 +237,11 @@ public interface EntityGetter { - - return null; - } -+ -+ // Paper start - check global player list where appropriate -+ @Nullable -+ default Player getGlobalPlayerByUUID(UUID uuid) { -+ return this.getPlayerByUUID(uuid); -+ } -+ // Paper end - check global player list where appropriate - } -diff --git a/src/main/java/net/minecraft/world/level/block/entity/SculkShriekerBlockEntity.java b/src/main/java/net/minecraft/world/level/block/entity/SculkShriekerBlockEntity.java -index dd86f5ec5b2051aeea4e19ff97146362b1e8d019..0c6561e53db1b82f70dec00729d4d8a70c8fd4a1 100644 ---- a/src/main/java/net/minecraft/world/level/block/entity/SculkShriekerBlockEntity.java -+++ b/src/main/java/net/minecraft/world/level/block/entity/SculkShriekerBlockEntity.java -@@ -105,6 +105,13 @@ public class SculkShriekerBlockEntity extends BlockEntity implements GameEventLi - - @Nullable - public static ServerPlayer tryGetPlayer(@Nullable Entity entity) { -+ // Paper start - check global player list where appropriate; ensure level is the same for sculk events -+ final ServerPlayer player = tryGetPlayer0(entity); -+ return player != null && player.level() == entity.level() ? player : null; -+ } -+ @Nullable -+ private static ServerPlayer tryGetPlayer0(@Nullable Entity entity) { -+ // Paper end - check global player list where appropriate - if (entity instanceof ServerPlayer) { - return (ServerPlayer)entity; - } else { diff --git a/patches/server/0767-Fix-async-entity-add-due-to-fungus-trees.patch b/patches/server/0767-Fix-async-entity-add-due-to-fungus-trees.patch deleted file mode 100644 index 2a7830d9526f..000000000000 --- a/patches/server/0767-Fix-async-entity-add-due-to-fungus-trees.patch +++ /dev/null @@ -1,35 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Jake Potrebic -Date: Fri, 18 Mar 2022 21:30:00 -0700 -Subject: [PATCH] Fix async entity add due to fungus trees - - -diff --git a/src/main/java/net/minecraft/server/level/WorldGenRegion.java b/src/main/java/net/minecraft/server/level/WorldGenRegion.java -index 13229388ddce668061a34c787ab9586d41854d8a..682c8cfbd917c086072f1756861a340800ea40da 100644 ---- a/src/main/java/net/minecraft/server/level/WorldGenRegion.java -+++ b/src/main/java/net/minecraft/server/level/WorldGenRegion.java -@@ -237,6 +237,7 @@ public class WorldGenRegion implements WorldGenLevel { - if (iblockdata.isAir()) { - return false; - } else { -+ if (drop) LOGGER.warn("Potential async entity add during worldgen", new Throwable()); // Paper - Fix async entity add due to fungus trees; log when this happens - if (false) { // CraftBukkit - SPIGOT-6833: Do not drop during world generation - BlockEntity tileentity = iblockdata.hasBlockEntity() ? this.getBlockEntity(pos) : null; - -diff --git a/src/main/java/org/bukkit/craftbukkit/CraftRegionAccessor.java b/src/main/java/org/bukkit/craftbukkit/CraftRegionAccessor.java -index 3c1937b43b6834ae0ffdd8d22ac32a776bc7fb64..a14d3e6c43b94c543790571b13808713444a239f 100644 ---- a/src/main/java/org/bukkit/craftbukkit/CraftRegionAccessor.java -+++ b/src/main/java/org/bukkit/craftbukkit/CraftRegionAccessor.java -@@ -261,10 +261,10 @@ public abstract class CraftRegionAccessor implements RegionAccessor { - ((ChorusFlowerBlock) Blocks.CHORUS_FLOWER).generatePlant(access, pos, random, 8); - return true; - case CRIMSON_FUNGUS: -- gen = TreeFeatures.CRIMSON_FUNGUS_PLANTED; -+ gen = this.isNormalWorld() ? TreeFeatures.CRIMSON_FUNGUS_PLANTED : TreeFeatures.CRIMSON_FUNGUS; // Paper - Fix async entity add due to fungus trees; if world gen, don't use planted version - break; - case WARPED_FUNGUS: -- gen = TreeFeatures.WARPED_FUNGUS_PLANTED; -+ gen = this.isNormalWorld() ? TreeFeatures.WARPED_FUNGUS_PLANTED : TreeFeatures.WARPED_FUNGUS; // Paper - Fix async entity add due to fungus trees; if world gen, don't use planted version - break; - case AZALEA: - gen = TreeFeatures.AZALEA_TREE; diff --git a/patches/server/0767-Player-Entity-Tracking-Events.patch b/patches/server/0767-Player-Entity-Tracking-Events.patch new file mode 100644 index 000000000000..fc9f07d3da2f --- /dev/null +++ b/patches/server/0767-Player-Entity-Tracking-Events.patch @@ -0,0 +1,42 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Yannick Lamprecht +Date: Wed, 30 Mar 2022 18:16:52 +0200 +Subject: [PATCH] Player Entity Tracking Events + + +diff --git a/src/main/java/net/minecraft/server/level/ChunkMap.java b/src/main/java/net/minecraft/server/level/ChunkMap.java +index 51a6735b35e73175680e61c2d67d4adbedf305c9..8b5d11aceb77135c917c3581f4db792ef4b647ec 100644 +--- a/src/main/java/net/minecraft/server/level/ChunkMap.java ++++ b/src/main/java/net/minecraft/server/level/ChunkMap.java +@@ -1586,7 +1586,11 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider + // CraftBukkit end + if (flag) { + if (this.seenBy.add(player.connection)) { ++ // Paper start - entity tracking events ++ if (io.papermc.paper.event.player.PlayerTrackEntityEvent.getHandlerList().getRegisteredListeners().length == 0 || new io.papermc.paper.event.player.PlayerTrackEntityEvent(player.getBukkitEntity(), this.entity.getBukkitEntity()).callEvent()) { + this.serverEntity.addPairing(player); ++ } ++ // Paper end - entity tracking events + } + } else if (this.seenBy.remove(player.connection)) { + this.serverEntity.removePairing(player); +diff --git a/src/main/java/net/minecraft/world/entity/Entity.java b/src/main/java/net/minecraft/world/entity/Entity.java +index 8b2b0cf8318f06f0fef59908ece650fb54b6a6b3..429cdb83252b417c810d529125fc7343dca7990d 100644 +--- a/src/main/java/net/minecraft/world/entity/Entity.java ++++ b/src/main/java/net/minecraft/world/entity/Entity.java +@@ -4071,7 +4071,14 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess + + public void startSeenByPlayer(ServerPlayer player) {} + +- public void stopSeenByPlayer(ServerPlayer player) {} ++ // Paper start - entity tracking events ++ public void stopSeenByPlayer(ServerPlayer player) { ++ // Since this event cannot be cancelled, we should call it here to catch all "un-tracks" ++ if (io.papermc.paper.event.player.PlayerUntrackEntityEvent.getHandlerList().getRegisteredListeners().length > 0) { ++ new io.papermc.paper.event.player.PlayerUntrackEntityEvent(player.getBukkitEntity(), this.getBukkitEntity()).callEvent(); ++ } ++ } ++ // Paper end - entity tracking events + + public float rotate(Rotation rotation) { + float f = Mth.wrapDegrees(this.getYRot()); diff --git a/patches/server/0768-ItemStack-damage-API.patch b/patches/server/0768-ItemStack-damage-API.patch deleted file mode 100644 index 1233d5fea9c0..000000000000 --- a/patches/server/0768-ItemStack-damage-API.patch +++ /dev/null @@ -1,107 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Jake Potrebic -Date: Sun, 8 May 2022 13:35:45 -0700 -Subject: [PATCH] ItemStack damage API - -Adds methods notify clients about item breaks and -to simulate damage done to an itemstack and all -the logic associated with damaging them - -== AT == -public net.minecraft.world.entity.LivingEntity entityEventForEquipmentBreak(Lnet/minecraft/world/entity/EquipmentSlot;)B - -diff --git a/src/main/java/net/minecraft/world/item/ItemStack.java b/src/main/java/net/minecraft/world/item/ItemStack.java -index bee59df6a8f30416f94c1a4fbd5e2629336e842f..e64cc91b416bbbefe6aadf1c6b685346cf258ab4 100644 ---- a/src/main/java/net/minecraft/world/item/ItemStack.java -+++ b/src/main/java/net/minecraft/world/item/ItemStack.java -@@ -647,8 +647,13 @@ public final class ItemStack implements DataComponentHolder { - } - - public void hurtAndBreak(int amount, ServerLevel world, @Nullable LivingEntity player, Consumer breakCallback) { // Paper - Add EntityDamageItemEvent -+ // Paper start - add param to skip infinite mats check -+ this.hurtAndBreak(amount, world, player, breakCallback, false); -+ } -+ public void hurtAndBreak(int amount, ServerLevel world, @Nullable LivingEntity player, Consumer breakCallback, boolean force) { -+ // Paper end - add param to skip infinite mats check - if (this.isDamageableItem()) { -- if (player == null || !player.hasInfiniteMaterials()) { -+ if (player == null || !player.hasInfiniteMaterials() || force) { // Paper - if (amount > 0) { - int originalDamage = amount; // Paper - Expand PlayerItemDamageEvent - amount = EnchantmentHelper.processDurabilityChange(world, this, amount); -@@ -704,6 +709,11 @@ public final class ItemStack implements DataComponentHolder { - } - - public void hurtAndBreak(int amount, LivingEntity entity, EquipmentSlot slot) { -+ // Paper start - add param to skip infinite mats check -+ this.hurtAndBreak(amount, entity, slot, false); -+ } -+ public void hurtAndBreak(int amount, LivingEntity entity, EquipmentSlot slot, boolean force) { -+ // Paper end - add param to skip infinite mats check - Level world = entity.level(); - - if (world instanceof ServerLevel worldserver) { -@@ -716,8 +726,8 @@ public final class ItemStack implements DataComponentHolder { - } - - this.hurtAndBreak(amount, worldserver, entity, (item) -> { // Paper - Add EntityDamageItemEvent -- entity.onEquippedItemBroken(item, slot); -- }); -+ if (slot != null) entity.onEquippedItemBroken(item, slot); // Paper - itemstack damage API - do not process entity related callbacks when damaging from API -+ }, force); // Paper - itemstack damage API - } - - } -diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftLivingEntity.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftLivingEntity.java -index 2a7fffa71106327d0ffee632408d45faaec76702..2332f40fed84510741e63073aaf0a18aab85303e 100644 ---- a/src/main/java/org/bukkit/craftbukkit/entity/CraftLivingEntity.java -+++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftLivingEntity.java -@@ -1135,4 +1135,48 @@ public class CraftLivingEntity extends CraftEntity implements LivingEntity { - this.getHandle().knockback(strength, directionX, directionZ); - }; - // Paper end - knockback API -+ -+ // Paper start - ItemStack damage API -+ public void broadcastSlotBreak(final org.bukkit.inventory.EquipmentSlot slot) { -+ this.getHandle().level().broadcastEntityEvent(this.getHandle(), net.minecraft.world.entity.LivingEntity.entityEventForEquipmentBreak(org.bukkit.craftbukkit.CraftEquipmentSlot.getNMS(slot))); -+ } -+ -+ @Override -+ public void broadcastSlotBreak(final org.bukkit.inventory.EquipmentSlot slot, final Collection players) { -+ if (players.isEmpty()) { -+ return; -+ } -+ final net.minecraft.network.protocol.game.ClientboundEntityEventPacket packet = new net.minecraft.network.protocol.game.ClientboundEntityEventPacket( -+ this.getHandle(), -+ net.minecraft.world.entity.LivingEntity.entityEventForEquipmentBreak(org.bukkit.craftbukkit.CraftEquipmentSlot.getNMS(slot)) -+ ); -+ players.forEach(player -> ((CraftPlayer) player).getHandle().connection.send(packet)); -+ } -+ -+ @Override -+ public ItemStack damageItemStack(ItemStack stack, final int amount) { -+ final net.minecraft.world.item.ItemStack nmsStack; -+ if (stack instanceof final CraftItemStack craftItemStack) { -+ if (craftItemStack.handle == null || craftItemStack.handle.isEmpty()) { -+ return stack; -+ } -+ nmsStack = craftItemStack.handle; -+ } else { -+ nmsStack = CraftItemStack.asNMSCopy(stack); -+ stack = CraftItemStack.asCraftMirror(nmsStack); // mirror to capture changes in hurt logic & events -+ } -+ this.damageItemStack0(nmsStack, amount, null); -+ return stack; -+ } -+ -+ @Override -+ public void damageItemStack(final org.bukkit.inventory.EquipmentSlot slot, final int amount) { -+ final net.minecraft.world.entity.EquipmentSlot nmsSlot = org.bukkit.craftbukkit.CraftEquipmentSlot.getNMS(slot); -+ this.damageItemStack0(this.getHandle().getItemBySlot(nmsSlot), amount, nmsSlot); -+ } -+ -+ private void damageItemStack0(final net.minecraft.world.item.ItemStack nmsStack, final int amount, final net.minecraft.world.entity.EquipmentSlot slot) { -+ nmsStack.hurtAndBreak(amount, this.getHandle(), slot, true); -+ } -+ // Paper end - ItemStack damage API - } diff --git a/patches/server/0774-Limit-pet-look-distance.patch b/patches/server/0768-Limit-pet-look-distance.patch similarity index 100% rename from patches/server/0774-Limit-pet-look-distance.patch rename to patches/server/0768-Limit-pet-look-distance.patch diff --git a/patches/server/0769-Friction-API.patch b/patches/server/0769-Friction-API.patch deleted file mode 100644 index dc735c46bb67..000000000000 --- a/patches/server/0769-Friction-API.patch +++ /dev/null @@ -1,156 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Noah van der Aa -Date: Wed, 15 Sep 2021 20:44:22 +0200 -Subject: [PATCH] Friction API - - -diff --git a/src/main/java/net/minecraft/world/entity/LivingEntity.java b/src/main/java/net/minecraft/world/entity/LivingEntity.java -index f662a791e8cb045903a0de29778e5f30892fb766..2bb61084b628582ded44926d7697ee26d0bb1e8e 100644 ---- a/src/main/java/net/minecraft/world/entity/LivingEntity.java -+++ b/src/main/java/net/minecraft/world/entity/LivingEntity.java -@@ -285,6 +285,7 @@ public abstract class LivingEntity extends Entity implements Attackable { - public boolean bukkitPickUpLoot; - public org.bukkit.craftbukkit.entity.CraftLivingEntity getBukkitLivingEntity() { return (org.bukkit.craftbukkit.entity.CraftLivingEntity) super.getBukkitEntity(); } // Paper - public boolean silentDeath = false; // Paper - mark entity as dying silently for cancellable death event -+ public net.kyori.adventure.util.TriState frictionState = net.kyori.adventure.util.TriState.NOT_SET; // Paper - Friction API - - @Override - public float getBukkitYaw() { -@@ -706,7 +707,7 @@ public abstract class LivingEntity extends Entity implements Attackable { - } - - public boolean shouldDiscardFriction() { -- return this.discardFriction; -+ return !this.frictionState.toBooleanOrElse(!this.discardFriction); // Paper - Friction API - } - - public void setDiscardFriction(boolean noDrag) { -@@ -774,6 +775,11 @@ public abstract class LivingEntity extends Entity implements Attackable { - - @Override - public void addAdditionalSaveData(CompoundTag nbt) { -+ // Paper start - Friction API -+ if (this.frictionState != net.kyori.adventure.util.TriState.NOT_SET) { -+ nbt.putString("Paper.FrictionState", this.frictionState.toString()); -+ } -+ // Paper end - Friction API - nbt.putFloat("Health", this.getHealth()); - nbt.putShort("HurtTime", (short) this.hurtTime); - nbt.putInt("HurtByTimestamp", this.lastHurtByMobTimestamp); -@@ -817,6 +823,16 @@ public abstract class LivingEntity extends Entity implements Attackable { - } - this.internalSetAbsorptionAmount(absorptionAmount); - // Paper end - Check for NaN -+ // Paper start - Friction API -+ if (nbt.contains("Paper.FrictionState")) { -+ String fs = nbt.getString("Paper.FrictionState"); -+ try { -+ frictionState = net.kyori.adventure.util.TriState.valueOf(fs); -+ } catch (Exception ignored) { -+ LOGGER.error("Unknown friction state " + fs + " for " + this); -+ } -+ } -+ // Paper end - Friction API - if (nbt.contains("attributes", 9) && this.level() != null && !this.level().isClientSide) { - this.getAttributes().load(nbt.getList("attributes", 10)); - } -diff --git a/src/main/java/net/minecraft/world/entity/item/ItemEntity.java b/src/main/java/net/minecraft/world/entity/item/ItemEntity.java -index 446c319524183d6a1b4d0e6f0613a8db690677da..cbfb07bdf8d5e2e5a462835184be2d47e59d506c 100644 ---- a/src/main/java/net/minecraft/world/entity/item/ItemEntity.java -+++ b/src/main/java/net/minecraft/world/entity/item/ItemEntity.java -@@ -63,6 +63,7 @@ public class ItemEntity extends Entity implements TraceableEntity { - private int lastTick = MinecraftServer.currentTick - 1; // CraftBukkit - public boolean canMobPickup = true; // Paper - Item#canEntityPickup - private int despawnRate = -1; // Paper - Alternative item-despawn-rate -+ public net.kyori.adventure.util.TriState frictionState = net.kyori.adventure.util.TriState.NOT_SET; // Paper - Friction API - - public ItemEntity(EntityType type, Level world) { - super(type, world); -@@ -185,7 +186,11 @@ public class ItemEntity extends Entity implements TraceableEntity { - this.move(MoverType.SELF, this.getDeltaMovement()); - float f = 0.98F; - -- if (this.onGround()) { -+ // Paper start - Friction API -+ if (frictionState == net.kyori.adventure.util.TriState.FALSE) { -+ f = 1F; -+ } else if (this.onGround()) { -+ // Paper end - Friction API - f = this.level().getBlockState(this.getBlockPosBelowThatAffectsMyMovement()).getBlock().getFriction() * 0.98F; - } - -@@ -393,6 +398,11 @@ public class ItemEntity extends Entity implements TraceableEntity { - - @Override - public void addAdditionalSaveData(CompoundTag nbt) { -+ // Paper start - Friction API -+ if (this.frictionState != net.kyori.adventure.util.TriState.NOT_SET) { -+ nbt.putString("Paper.FrictionState", this.frictionState.toString()); -+ } -+ // Paper end - Friction API - nbt.putShort("Health", (short) this.health); - nbt.putShort("Age", (short) this.age); - nbt.putShort("PickupDelay", (short) this.pickupDelay); -@@ -435,6 +445,17 @@ public class ItemEntity extends Entity implements TraceableEntity { - this.setItem(ItemStack.EMPTY); - } - -+ // Paper start - Friction API -+ if (nbt.contains("Paper.FrictionState")) { -+ String fs = nbt.getString("Paper.FrictionState"); -+ try { -+ frictionState = net.kyori.adventure.util.TriState.valueOf(fs); -+ } catch (Exception ignored) { -+ com.mojang.logging.LogUtils.getLogger().error("Unknown friction state " + fs + " for " + this); -+ } -+ } -+ // Paper end - Friction API -+ - if (this.getItem().isEmpty()) { - this.discard(null); // CraftBukkit - add Bukkit remove cause - } -diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftItem.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftItem.java -index 1a291dd8a287db30e71dcb315599fc4b038764c4..30d62ee4d5cd2ddacb8783b5bbbf475d592b3e02 100644 ---- a/src/main/java/org/bukkit/craftbukkit/entity/CraftItem.java -+++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftItem.java -@@ -99,6 +99,18 @@ public class CraftItem extends CraftEntity implements Item { - this.getHandle().age = willAge ? 0 : NO_AGE_TIME; - } - -+ @org.jetbrains.annotations.NotNull -+ @Override -+ public net.kyori.adventure.util.TriState getFrictionState() { -+ return this.getHandle().frictionState; -+ } -+ -+ @Override -+ public void setFrictionState(@org.jetbrains.annotations.NotNull net.kyori.adventure.util.TriState state) { -+ java.util.Objects.requireNonNull(state, "state may not be null"); -+ this.getHandle().frictionState = state; -+ } -+ - @Override - public int getHealth() { - return this.getHandle().health; -diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftLivingEntity.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftLivingEntity.java -index 2332f40fed84510741e63073aaf0a18aab85303e..516ea1ec9ae5069c3c0e4708f62164a91960b627 100644 ---- a/src/main/java/org/bukkit/craftbukkit/entity/CraftLivingEntity.java -+++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftLivingEntity.java -@@ -1179,4 +1179,17 @@ public class CraftLivingEntity extends CraftEntity implements LivingEntity { - nmsStack.hurtAndBreak(amount, this.getHandle(), slot, true); - } - // Paper end - ItemStack damage API -+ // Paper start - friction API -+ @org.jetbrains.annotations.NotNull -+ @Override -+ public net.kyori.adventure.util.TriState getFrictionState() { -+ return this.getHandle().frictionState; -+ } -+ -+ @Override -+ public void setFrictionState(@org.jetbrains.annotations.NotNull net.kyori.adventure.util.TriState state) { -+ java.util.Objects.requireNonNull(state, "state may not be null"); -+ this.getHandle().frictionState = state; -+ } -+ // Paper end - friction API - } diff --git a/patches/server/0769-fix-Instruments.patch b/patches/server/0769-fix-Instruments.patch new file mode 100644 index 000000000000..cfa8db1be6d9 --- /dev/null +++ b/patches/server/0769-fix-Instruments.patch @@ -0,0 +1,57 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Jake Potrebic +Date: Fri, 9 Dec 2022 01:47:23 -0800 +Subject: [PATCH] fix Instruments + +properly handle Player#playNote + +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java +index 9f31ea3cb0b5dac2c50be0f51ad002e593d6fb5c..f41d97bdb046747a6540dc22cbdc322d764f1e0a 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java +@@ -785,7 +785,10 @@ public class CraftPlayer extends CraftHumanEntity implements Player { + Sound instrumentSound = instrument.getSound(); + if (instrumentSound == null) return; + +- float pitch = note.getPitch(); ++ // Paper start - use correct pitch (modeled off of NoteBlock) ++ final net.minecraft.world.level.block.state.properties.NoteBlockInstrument noteBlockInstrument = CraftBlockData.toNMS(instrument, net.minecraft.world.level.block.state.properties.NoteBlockInstrument.class); ++ final float pitch = noteBlockInstrument.isTunable() ? note.getPitch() : 1.0f; ++ // Paper end + this.getHandle().connection.send(new ClientboundSoundPacket(CraftSound.bukkitToMinecraftHolder(instrumentSound), net.minecraft.sounds.SoundSource.RECORDS, loc.getBlockX(), loc.getBlockY(), loc.getBlockZ(), 3.0f, pitch, this.getHandle().getRandom().nextLong())); + } + +diff --git a/src/test/java/io/papermc/paper/block/InstrumentSoundTest.java b/src/test/java/io/papermc/paper/block/InstrumentSoundTest.java +new file mode 100644 +index 0000000000000000000000000000000000000000..cd718ed01ba5d448cdf0a2b6a39dc7ef2337f70d +--- /dev/null ++++ b/src/test/java/io/papermc/paper/block/InstrumentSoundTest.java +@@ -0,0 +1,28 @@ ++package io.papermc.paper.block; ++ ++import java.util.Arrays; ++import java.util.stream.Stream; ++import net.minecraft.world.level.block.state.properties.NoteBlockInstrument; ++import org.bukkit.Instrument; ++import org.bukkit.craftbukkit.CraftSound; ++import org.bukkit.craftbukkit.block.data.CraftBlockData; ++import org.bukkit.support.environment.AllFeatures; ++import org.junit.jupiter.params.ParameterizedTest; ++import org.junit.jupiter.params.provider.MethodSource; ++ ++import static org.junit.jupiter.api.Assertions.assertEquals; ++ ++@AllFeatures ++class InstrumentSoundTest { ++ ++ static Stream bukkitInstruments() { ++ return Arrays.stream(Instrument.values()).filter(i -> i.getSound() != null); ++ } ++ ++ @ParameterizedTest ++ @MethodSource("bukkitInstruments") ++ void checkInstrumentSound(final Instrument bukkit) { ++ final NoteBlockInstrument nms = CraftBlockData.toNMS(bukkit, NoteBlockInstrument.class); ++ assertEquals(nms.getSoundEvent(), CraftSound.bukkitToMinecraftHolder(bukkit.getSound())); ++ } ++} diff --git a/patches/server/0770-Ability-to-control-player-s-insomnia-and-phantoms.patch b/patches/server/0770-Ability-to-control-player-s-insomnia-and-phantoms.patch deleted file mode 100644 index 03be2495a7bb..000000000000 --- a/patches/server/0770-Ability-to-control-player-s-insomnia-and-phantoms.patch +++ /dev/null @@ -1,67 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Jan Villim -Date: Sat, 22 Jan 2022 17:56:19 +0100 -Subject: [PATCH] Ability to control player's insomnia and phantoms - - -diff --git a/src/main/java/net/minecraft/world/entity/EntitySelector.java b/src/main/java/net/minecraft/world/entity/EntitySelector.java -index 302decdccd37c5579473c8fc33adda3956be7603..dca7b99e97f21bf6cfae6ee69eeac95d0bcf6863 100644 ---- a/src/main/java/net/minecraft/world/entity/EntitySelector.java -+++ b/src/main/java/net/minecraft/world/entity/EntitySelector.java -@@ -28,7 +28,18 @@ public final class EntitySelector { - return !entity.isSpectator(); - }; - public static final Predicate CAN_BE_COLLIDED_WITH = EntitySelector.NO_SPECTATORS.and(Entity::canBeCollidedWith); -- public static Predicate IS_INSOMNIAC = (player) -> net.minecraft.util.Mth.clamp(((net.minecraft.server.level.ServerPlayer) player).getStats().getValue(net.minecraft.stats.Stats.CUSTOM.get(net.minecraft.stats.Stats.TIME_SINCE_REST)), 1, Integer.MAX_VALUE) >= 72000; // Paper - Add phantom creative and insomniac controls -+ // Paper start - Ability to control player's insomnia and phantoms -+ public static Predicate IS_INSOMNIAC = (player) -> { -+ net.minecraft.server.level.ServerPlayer serverPlayer = (net.minecraft.server.level.ServerPlayer) player; -+ int playerInsomniaTicks = serverPlayer.level().paperConfig().entities.behavior.playerInsomniaStartTicks; -+ -+ if (playerInsomniaTicks <= 0) { -+ return false; -+ } -+ -+ return net.minecraft.util.Mth.clamp(serverPlayer.getStats().getValue(net.minecraft.stats.Stats.CUSTOM.get(net.minecraft.stats.Stats.TIME_SINCE_REST)), 1, Integer.MAX_VALUE) >= playerInsomniaTicks; -+ }; -+ // Paper end - Ability to control player's insomnia and phantoms - - private EntitySelector() {} - // Paper start - Affects Spawning API -diff --git a/src/main/java/net/minecraft/world/level/levelgen/PhantomSpawner.java b/src/main/java/net/minecraft/world/level/levelgen/PhantomSpawner.java -index f74d41e57570a40cd5ce4da3076f3210b6594a63..1b1b475ca27e799e251d6f8a8c9fe1a4fd8bae83 100644 ---- a/src/main/java/net/minecraft/world/level/levelgen/PhantomSpawner.java -+++ b/src/main/java/net/minecraft/world/level/levelgen/PhantomSpawner.java -@@ -32,13 +32,22 @@ public class PhantomSpawner implements CustomSpawner { - } else if (!world.getGameRules().getBoolean(GameRules.RULE_DOINSOMNIA)) { - return 0; - } else { -+ // Paper start - Ability to control player's insomnia and phantoms -+ if (world.paperConfig().entities.behavior.phantomsSpawnAttemptMaxSeconds <= 0) { -+ return 0; -+ } -+ // Paper end - Ability to control player's insomnia and phantoms - RandomSource randomsource = world.random; - - --this.nextTick; - if (this.nextTick > 0) { - return 0; - } else { -- this.nextTick += (60 + randomsource.nextInt(60)) * 20; -+ // Paper start - Ability to control player's insomnia and phantoms -+ int spawnAttemptMinSeconds = world.paperConfig().entities.behavior.phantomsSpawnAttemptMinSeconds; -+ int spawnAttemptMaxSeconds = world.paperConfig().entities.behavior.phantomsSpawnAttemptMaxSeconds; -+ this.nextTick += (spawnAttemptMinSeconds + randomsource.nextInt(spawnAttemptMaxSeconds - spawnAttemptMinSeconds + 1)) * 20; -+ // Paper end - Ability to control player's insomnia and phantoms - if (world.getSkyDarken() < 5 && world.dimensionType().hasSkyLight()) { - return 0; - } else { -@@ -59,7 +68,7 @@ public class PhantomSpawner implements CustomSpawner { - int j = Mth.clamp(serverstatisticmanager.getValue(Stats.CUSTOM.get(Stats.TIME_SINCE_REST)), 1, Integer.MAX_VALUE); - boolean flag2 = true; - -- if (randomsource.nextInt(j) >= 72000) { -+ if (randomsource.nextInt(j) >= world.paperConfig().entities.behavior.playerInsomniaStartTicks) { // Paper - Ability to control player's insomnia and phantoms - BlockPos blockposition1 = blockposition.above(20 + randomsource.nextInt(15)).east(-10 + randomsource.nextInt(21)).south(-10 + randomsource.nextInt(21)); - BlockState iblockdata = world.getBlockState(blockposition1); - FluidState fluid = world.getFluidState(blockposition1); diff --git a/patches/server/0770-Improve-inlining-for-some-hot-BlockBehavior-and-Flui.patch b/patches/server/0770-Improve-inlining-for-some-hot-BlockBehavior-and-Flui.patch new file mode 100644 index 000000000000..6825374465a7 --- /dev/null +++ b/patches/server/0770-Improve-inlining-for-some-hot-BlockBehavior-and-Flui.patch @@ -0,0 +1,78 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Spottedleaf +Date: Mon, 6 Jul 2020 20:46:50 -0700 +Subject: [PATCH] Improve inlining for some hot BlockBehavior and FluidState + methods + + +diff --git a/src/main/java/net/minecraft/world/level/block/state/BlockBehaviour.java b/src/main/java/net/minecraft/world/level/block/state/BlockBehaviour.java +index 83a3c877f2969549ea154ad86687e96fdf34d881..0665ca48fe2f8ab1ce1c0306b11be19b06445f74 100644 +--- a/src/main/java/net/minecraft/world/level/block/state/BlockBehaviour.java ++++ b/src/main/java/net/minecraft/world/level/block/state/BlockBehaviour.java +@@ -969,15 +969,15 @@ public abstract class BlockBehaviour implements FeatureElement { + return this.shapeExceedsCube; // Paper - moved into shape cache init + } + +- public boolean useShapeForLightOcclusion() { ++ public final boolean useShapeForLightOcclusion() { // Paper - Perf: Final for inlining + return this.useShapeForLightOcclusion; + } + +- public int getLightEmission() { ++ public final int getLightEmission() { // Paper - Perf: Final for inlining + return this.lightEmission; + } + +- public boolean isAir() { ++ public final boolean isAir() { // Paper - Perf: Final for inlining + return this.isAir; + } + +@@ -1055,7 +1055,7 @@ public abstract class BlockBehaviour implements FeatureElement { + return this.solidRender; + } + +- public boolean canOcclude() { ++ public final boolean canOcclude() { // Paper - Perf: Final for inlining + return this.canOcclude; + } + +@@ -1276,11 +1276,11 @@ public abstract class BlockBehaviour implements FeatureElement { + return this.getBlock().builtInRegistryHolder().is(key); + } + +- public FluidState getFluidState() { ++ public final FluidState getFluidState() { // Paper - Perf: Final for inlining + return this.fluidState; + } + +- public boolean isRandomlyTicking() { ++ public final boolean isRandomlyTicking() { // Paper - Perf: Final for inlining + return this.isRandomlyTicking; + } + +diff --git a/src/main/java/net/minecraft/world/level/material/FluidState.java b/src/main/java/net/minecraft/world/level/material/FluidState.java +index 8310cb22e2e75d8b2cca32bfe076fdafabb1b0ec..87adfe152abd1b8b4d547034576883c5d1cdf134 100644 +--- a/src/main/java/net/minecraft/world/level/material/FluidState.java ++++ b/src/main/java/net/minecraft/world/level/material/FluidState.java +@@ -26,9 +26,11 @@ public final class FluidState extends StateHolder { + public static final Codec CODEC = codec(BuiltInRegistries.FLUID.byNameCodec(), Fluid::defaultFluidState).stable(); + public static final int AMOUNT_MAX = 9; + public static final int AMOUNT_FULL = 8; ++ protected final boolean isEmpty; // Paper - Perf: moved from isEmpty() + + public FluidState(Fluid fluid, Reference2ObjectArrayMap, Comparable> propertyMap, MapCodec codec) { + super(fluid, propertyMap, codec); ++ this.isEmpty = fluid.isEmpty(); // Paper - Perf: moved from isEmpty() + } + + public Fluid getType() { +@@ -44,7 +46,7 @@ public final class FluidState extends StateHolder { + } + + public boolean isEmpty() { +- return this.getType().isEmpty(); ++ return this.isEmpty; // Paper - Perf: moved into constructor + } + + public float getHeight(BlockGetter world, BlockPos pos) { diff --git a/patches/server/0771-Add-BlockLockCheckEvent.patch b/patches/server/0771-Add-BlockLockCheckEvent.patch new file mode 100644 index 000000000000..b06f734eb217 --- /dev/null +++ b/patches/server/0771-Add-BlockLockCheckEvent.patch @@ -0,0 +1,70 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Jake Potrebic +Date: Sat, 21 May 2022 20:59:45 -0700 +Subject: [PATCH] Add BlockLockCheckEvent + + +diff --git a/src/main/java/net/minecraft/world/level/block/entity/BaseContainerBlockEntity.java b/src/main/java/net/minecraft/world/level/block/entity/BaseContainerBlockEntity.java +index 4f397dd8e5d333cb2561d03d20511a9194314d1c..1f29b2419914ca9257db6553f01b7e7ec49bfc18 100644 +--- a/src/main/java/net/minecraft/world/level/block/entity/BaseContainerBlockEntity.java ++++ b/src/main/java/net/minecraft/world/level/block/entity/BaseContainerBlockEntity.java +@@ -73,17 +73,44 @@ public abstract class BaseContainerBlockEntity extends BlockEntity implements Co + protected abstract Component getDefaultName(); + + public boolean canOpen(Player player) { +- return BaseContainerBlockEntity.canUnlock(player, this.lockKey, this.getDisplayName()); ++ return BaseContainerBlockEntity.canUnlock(player, this.lockKey, this.getDisplayName(), this); // Paper - Add BlockLockCheckEvent + } + ++ @Deprecated @io.papermc.paper.annotation.DoNotUse // Paper - Add BlockLockCheckEvent + public static boolean canUnlock(Player player, LockCode lock, Component containerName) { ++ // Paper start - Add BlockLockCheckEvent ++ return canUnlock(player, lock, containerName, null); ++ } ++ public static boolean canUnlock(Player player, LockCode lock, Component containerName, @Nullable BlockEntity blockEntity) { ++ if (player instanceof net.minecraft.server.level.ServerPlayer serverPlayer && blockEntity != null && blockEntity.getLevel() != null && blockEntity.getLevel().getBlockEntity(blockEntity.getBlockPos()) == blockEntity) { ++ final org.bukkit.block.Block block = org.bukkit.craftbukkit.block.CraftBlock.at(blockEntity.getLevel(), blockEntity.getBlockPos()); ++ net.kyori.adventure.text.Component lockedMessage = net.kyori.adventure.text.Component.translatable("container.isLocked", io.papermc.paper.adventure.PaperAdventure.asAdventure(containerName)); ++ net.kyori.adventure.sound.Sound lockedSound = net.kyori.adventure.sound.Sound.sound(org.bukkit.Sound.BLOCK_CHEST_LOCKED, net.kyori.adventure.sound.Sound.Source.BLOCK, 1.0F, 1.0F); ++ final io.papermc.paper.event.block.BlockLockCheckEvent event = new io.papermc.paper.event.block.BlockLockCheckEvent(block, serverPlayer.getBukkitEntity(), lockedMessage, lockedSound); ++ event.callEvent(); ++ if (event.getResult() == org.bukkit.event.Event.Result.ALLOW) { ++ return true; ++ } else if (event.getResult() == org.bukkit.event.Event.Result.DENY || (!player.isSpectator() && !lock.unlocksWith(event.isUsingCustomKeyItemStack() ? org.bukkit.craftbukkit.inventory.CraftItemStack.asNMSCopy(event.getKeyItem()) : player.getMainHandItem()))) { ++ if (event.getLockedMessage() != null) { ++ event.getPlayer().sendActionBar(event.getLockedMessage()); ++ } ++ if (event.getLockedSound() != null) { ++ event.getPlayer().playSound(event.getLockedSound()); ++ } ++ return false; ++ } else { ++ return true; ++ } ++ } else { // logic below is replaced by logic above ++ // Paper end - Add BlockLockCheckEvent + if (!player.isSpectator() && !lock.unlocksWith(player.getMainHandItem())) { +- player.displayClientMessage(Component.translatable("container.isLocked", containerName), true); ++ player.displayClientMessage(Component.translatable("container.isLocked", containerName), true); // Paper - diff on change + player.playNotifySound(SoundEvents.CHEST_LOCKED, SoundSource.BLOCKS, 1.0F, 1.0F); + return false; + } else { + return true; + } ++ } // Paper - Add BlockLockCheckEvent + } + + protected abstract NonNullList getItems(); +diff --git a/src/main/java/net/minecraft/world/level/block/entity/BeaconBlockEntity.java b/src/main/java/net/minecraft/world/level/block/entity/BeaconBlockEntity.java +index edd6017937a7f20a1b43fa15204ec130b524b52b..2a7d896dd9a02acf6e3596e2e2e7ed50f4b88377 100644 +--- a/src/main/java/net/minecraft/world/level/block/entity/BeaconBlockEntity.java ++++ b/src/main/java/net/minecraft/world/level/block/entity/BeaconBlockEntity.java +@@ -472,7 +472,7 @@ public class BeaconBlockEntity extends BlockEntity implements MenuProvider, Name + @Nullable + @Override + public AbstractContainerMenu createMenu(int syncId, Inventory playerInventory, Player player) { +- return BaseContainerBlockEntity.canUnlock(player, this.lockKey, this.getDisplayName()) ? new BeaconMenu(syncId, playerInventory, this.dataAccess, ContainerLevelAccess.create(this.level, this.getBlockPos())) : null; ++ return BaseContainerBlockEntity.canUnlock(player, this.lockKey, this.getDisplayName(), this) ? new BeaconMenu(syncId, playerInventory, this.dataAccess, ContainerLevelAccess.create(this.level, this.getBlockPos())) : null; // Paper - Add BlockLockCheckEvent + } + + @Override diff --git a/patches/server/0771-Fix-premature-player-kicks-on-shutdown.patch b/patches/server/0771-Fix-premature-player-kicks-on-shutdown.patch deleted file mode 100644 index 034840341e4f..000000000000 --- a/patches/server/0771-Fix-premature-player-kicks-on-shutdown.patch +++ /dev/null @@ -1,61 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Shane Freeder -Date: Thu, 11 Apr 2024 16:37:44 +0100 -Subject: [PATCH] Fix premature player kicks on shutdown - -When the server is stopping, the default execution handler method will throw a -RejectedExecutionException in order to prevent further execution, this causes -us to lose the actual kick reason. To mitigate this, we'll use a seperate marked -class in order to gracefully ignore these. - -diff --git a/src/main/java/io/papermc/paper/util/ServerStopRejectedExecutionException.java b/src/main/java/io/papermc/paper/util/ServerStopRejectedExecutionException.java -new file mode 100644 -index 0000000000000000000000000000000000000000..2c5cd77103c5a33d4349ab6b9ee2d8378bb60eb4 ---- /dev/null -+++ b/src/main/java/io/papermc/paper/util/ServerStopRejectedExecutionException.java -@@ -0,0 +1,20 @@ -+package io.papermc.paper.util; -+ -+import java.util.concurrent.RejectedExecutionException; -+ -+public class ServerStopRejectedExecutionException extends RejectedExecutionException { -+ public ServerStopRejectedExecutionException() { -+ } -+ -+ public ServerStopRejectedExecutionException(final String message) { -+ super(message); -+ } -+ -+ public ServerStopRejectedExecutionException(final String message, final Throwable cause) { -+ super(message, cause); -+ } -+ -+ public ServerStopRejectedExecutionException(final Throwable cause) { -+ super(cause); -+ } -+} -diff --git a/src/main/java/net/minecraft/network/Connection.java b/src/main/java/net/minecraft/network/Connection.java -index 4d9f1fc884050993287adfa4578a87da710623fb..a8dfe7a4b3d01bf75587be078f471d1ef1d7a667 100644 ---- a/src/main/java/net/minecraft/network/Connection.java -+++ b/src/main/java/net/minecraft/network/Connection.java -@@ -285,6 +285,7 @@ public class Connection extends SimpleChannelInboundHandler> { - Connection.genericsFtw(packet, packetlistener); - } catch (RunningOnDifferentThreadException cancelledpackethandleexception) { - ; -+ } catch (io.papermc.paper.util.ServerStopRejectedExecutionException ignored) { // Paper - do not prematurely disconnect players on stop - } catch (RejectedExecutionException rejectedexecutionexception) { - this.disconnect((Component) Component.translatable("multiplayer.disconnect.server_shutdown")); - } catch (ClassCastException classcastexception) { -diff --git a/src/main/java/net/minecraft/server/MinecraftServer.java b/src/main/java/net/minecraft/server/MinecraftServer.java -index bef05e09c654794405832ad75c3471f63399dfee..2cc22a9a5483c62cdf64870f5ce62b33018bef06 100644 ---- a/src/main/java/net/minecraft/server/MinecraftServer.java -+++ b/src/main/java/net/minecraft/server/MinecraftServer.java -@@ -2091,7 +2091,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop +Date: Wed, 19 Oct 2022 23:31:53 +0200 +Subject: [PATCH] Add Sneaking API for Entities + + +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java +index 01513cd747a0e18e3839e83bf5795aadb7d1e0e9..8a715159917950091e8efd48eaca6a54488fb896 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java +@@ -875,6 +875,18 @@ public abstract class CraftEntity implements org.bukkit.entity.Entity { + return Pose.values()[this.getHandle().getPose().ordinal()]; + } + ++ // Paper start ++ @Override ++ public void setSneaking(boolean sneak) { ++ this.getHandle().setShiftKeyDown(sneak); ++ } ++ ++ @Override ++ public boolean isSneaking() { ++ return this.getHandle().isShiftKeyDown(); ++ } ++ // Paper end ++ + @Override + public SpawnCategory getSpawnCategory() { + return CraftSpawnCategory.toBukkit(this.getHandle().getType().getCategory()); diff --git a/patches/server/0772-Sync-offhand-slot-in-menus.patch b/patches/server/0772-Sync-offhand-slot-in-menus.patch deleted file mode 100644 index f613fba84b9e..000000000000 --- a/patches/server/0772-Sync-offhand-slot-in-menus.patch +++ /dev/null @@ -1,51 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Jake Potrebic -Date: Fri, 14 Jan 2022 10:20:40 -0800 -Subject: [PATCH] Sync offhand slot in menus - -Menus don't add slots for the offhand, so on sendAllDataToRemote calls the -offhand slot isn't sent. This is not correct because you *can* put stuff into the offhand -by pressing the offhand swap item - -diff --git a/src/main/java/net/minecraft/server/level/ServerPlayer.java b/src/main/java/net/minecraft/server/level/ServerPlayer.java -index 3dbaa3c6b75a0f8bfdef42d210f2ac6f560cde3d..96dff63230e8cda1f5e548d914c119bd64b5ac33 100644 ---- a/src/main/java/net/minecraft/server/level/ServerPlayer.java -+++ b/src/main/java/net/minecraft/server/level/ServerPlayer.java -@@ -321,6 +321,13 @@ public class ServerPlayer extends net.minecraft.world.entity.player.Player { - - } - -+ // Paper start - Sync offhand slot in menus -+ @Override -+ public void sendOffHandSlotChange() { -+ ServerPlayer.this.connection.send(new ClientboundContainerSetSlotPacket(ServerPlayer.this.inventoryMenu.containerId, ServerPlayer.this.inventoryMenu.incrementStateId(), net.minecraft.world.inventory.InventoryMenu.SHIELD_SLOT, ServerPlayer.this.inventoryMenu.getSlot(net.minecraft.world.inventory.InventoryMenu.SHIELD_SLOT).getItem().copy())); -+ } -+ // Paper end - Sync offhand slot in menus -+ - @Override - public void sendSlotChange(AbstractContainerMenu handler, int slot, ItemStack stack) { - ServerPlayer.this.connection.send(new ClientboundContainerSetSlotPacket(handler.containerId, handler.incrementStateId(), slot, stack)); -diff --git a/src/main/java/net/minecraft/world/inventory/AbstractContainerMenu.java b/src/main/java/net/minecraft/world/inventory/AbstractContainerMenu.java -index f7b9849819c185cd89533aca1f6d34398ffc1077..0e380151b038e2133013eb7d73621cf247b5b954 100644 ---- a/src/main/java/net/minecraft/world/inventory/AbstractContainerMenu.java -+++ b/src/main/java/net/minecraft/world/inventory/AbstractContainerMenu.java -@@ -200,6 +200,7 @@ public abstract class AbstractContainerMenu { - - if (this.synchronizer != null) { - this.synchronizer.sendInitialData(this, this.remoteSlots, this.remoteCarried, this.remoteDataSlots.toIntArray()); -+ this.synchronizer.sendOffHandSlotChange(); // Paper - Sync offhand slot in menus; update player's offhand since the offhand slot is not added to the slots for menus but can be changed by swapping from a menu slot - } - - } -diff --git a/src/main/java/net/minecraft/world/inventory/ContainerSynchronizer.java b/src/main/java/net/minecraft/world/inventory/ContainerSynchronizer.java -index ff4fa86f9408e83e505f7e27692d3423f8570c48..a45ef5fcffc05e4e30801b73e82d29c6dbf5b8fd 100644 ---- a/src/main/java/net/minecraft/world/inventory/ContainerSynchronizer.java -+++ b/src/main/java/net/minecraft/world/inventory/ContainerSynchronizer.java -@@ -6,6 +6,7 @@ import net.minecraft.world.item.ItemStack; - public interface ContainerSynchronizer { - void sendInitialData(AbstractContainerMenu handler, NonNullList stacks, ItemStack cursorStack, int[] properties); - -+ default void sendOffHandSlotChange() {} // Paper - Sync offhand slot in menus - void sendSlotChange(AbstractContainerMenu handler, int slot, ItemStack stack); - - void sendCarriedChange(AbstractContainerMenu handler, ItemStack stack); diff --git a/patches/server/0773-Improve-logging-and-errors.patch b/patches/server/0773-Improve-logging-and-errors.patch new file mode 100644 index 000000000000..1388f4a8b57a --- /dev/null +++ b/patches/server/0773-Improve-logging-and-errors.patch @@ -0,0 +1,104 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Jake Potrebic +Date: Wed, 14 Dec 2022 15:52:11 -0800 +Subject: [PATCH] Improve logging and errors + +Co-authored-by: Jason Penilla <11360596+jpenilla@users.noreply.github.com> + +diff --git a/src/main/java/net/minecraft/advancements/AdvancementTree.java b/src/main/java/net/minecraft/advancements/AdvancementTree.java +index 2665170b8391a77d6b3fb7ae7b5ccfc0be65acd7..e00d4e0896c0163c43d79af63338de67c7cb0dc4 100644 +--- a/src/main/java/net/minecraft/advancements/AdvancementTree.java ++++ b/src/main/java/net/minecraft/advancements/AdvancementTree.java +@@ -35,7 +35,7 @@ public class AdvancementTree { + this.remove(advancementnode1); + } + +- AdvancementTree.LOGGER.info("Forgot about advancement {}", advancement.holder()); ++ AdvancementTree.LOGGER.debug("Forgot about advancement {}", advancement.holder()); // Paper - Improve logging and errors + this.nodes.remove(advancement.holder().id()); + if (advancement.parent() == null) { + this.roots.remove(advancement); +@@ -77,7 +77,7 @@ public class AdvancementTree { + } + } + +- // AdvancementTree.LOGGER.info("Loaded {} advancements", this.nodes.size()); // CraftBukkit - moved to AdvancementDataWorld#reload ++ // AdvancementTree.LOGGER.info("Loaded {} advancements", this.nodes.size()); // CraftBukkit - moved to AdvancementDataWorld#reload // Paper - Improve logging and errors; you say it was moved... but it wasn't :) it should be moved however, since this is called when the API creates an advancement + } + + private boolean tryInsert(AdvancementHolder advancement) { +diff --git a/src/main/java/net/minecraft/server/ServerAdvancementManager.java b/src/main/java/net/minecraft/server/ServerAdvancementManager.java +index 0828deee077a08db8e3fe5c870f9c242b4c550cf..5c8cf225d936617b6c8d265527a19bdfa371a93b 100644 +--- a/src/main/java/net/minecraft/server/ServerAdvancementManager.java ++++ b/src/main/java/net/minecraft/server/ServerAdvancementManager.java +@@ -53,6 +53,7 @@ public class ServerAdvancementManager extends SimpleJsonResourceReloadListener directoryStream = Files.newDirectoryStream(path)) { + for (Path path2 : directoryStream) { + String string = path2.getFileName().toString(); ++ // Paper start - Improve logging and errors ++ if (!Files.isDirectory(path2)) { ++ LOGGER.error("Invalid directory entry: {} in {}.", string, this.root, new java.nio.file.NotDirectoryException(string)); ++ continue; ++ } ++ // Paper end - Improve logging and errors + if (ResourceLocation.isValidNamespace(string)) { + set.add(string); + } else { +diff --git a/src/main/java/org/bukkit/craftbukkit/legacy/CraftLegacy.java b/src/main/java/org/bukkit/craftbukkit/legacy/CraftLegacy.java +index 46787d68eadc789c3409c0845f3fb0be713c70cc..51ae8eddadc87b143b93521a3cef374f1e3a24dc 100644 +--- a/src/main/java/org/bukkit/craftbukkit/legacy/CraftLegacy.java ++++ b/src/main/java/org/bukkit/craftbukkit/legacy/CraftLegacy.java +@@ -44,6 +44,7 @@ import org.bukkit.material.MaterialData; + */ + @Deprecated + public final class CraftLegacy { ++ private static final org.slf4j.Logger LOGGER = com.mojang.logging.LogUtils.getLogger(); // Paper - Improve logging and errors + + private static final Map SPAWN_EGGS = new HashMap<>(); + private static final Set whitelistedStates = new HashSet<>(Arrays.asList("explode", "check_decay", "decayable", "facing")); +@@ -264,7 +265,7 @@ public final class CraftLegacy { + } + + static { +- System.err.println("Initializing Legacy Material Support. Unless you have legacy plugins and/or data this is a bug!"); ++ LOGGER.warn("Initializing Legacy Material Support. Unless you have legacy plugins and/or data this is a bug!"); // Paper - Improve logging and errors; doesn't need to be an error + if (MinecraftServer.getServer() != null && MinecraftServer.getServer().isDebugging()) { + new Exception().printStackTrace(); + } diff --git a/patches/server/0773-Player-Entity-Tracking-Events.patch b/patches/server/0773-Player-Entity-Tracking-Events.patch deleted file mode 100644 index 62b6d040226b..000000000000 --- a/patches/server/0773-Player-Entity-Tracking-Events.patch +++ /dev/null @@ -1,42 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Yannick Lamprecht -Date: Wed, 30 Mar 2022 18:16:52 +0200 -Subject: [PATCH] Player Entity Tracking Events - - -diff --git a/src/main/java/net/minecraft/server/level/ChunkMap.java b/src/main/java/net/minecraft/server/level/ChunkMap.java -index 61fda6927f060cdf8bcfddaaa08bbbe2c514c630..dca6087dc4e1c177c3dfdae01f140cf80c179803 100644 ---- a/src/main/java/net/minecraft/server/level/ChunkMap.java -+++ b/src/main/java/net/minecraft/server/level/ChunkMap.java -@@ -1533,7 +1533,11 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider - // CraftBukkit end - if (flag) { - if (this.seenBy.add(player.connection)) { -+ // Paper start - entity tracking events -+ if (io.papermc.paper.event.player.PlayerTrackEntityEvent.getHandlerList().getRegisteredListeners().length == 0 || new io.papermc.paper.event.player.PlayerTrackEntityEvent(player.getBukkitEntity(), this.entity.getBukkitEntity()).callEvent()) { - this.serverEntity.addPairing(player); -+ } -+ // Paper end - entity tracking events - } - } else if (this.seenBy.remove(player.connection)) { - this.serverEntity.removePairing(player); -diff --git a/src/main/java/net/minecraft/world/entity/Entity.java b/src/main/java/net/minecraft/world/entity/Entity.java -index 3170f7246f55799fd5783e747920940ab33c3e40..d5df4ddb33600479e6edb93a18db9400ffc6c699 100644 ---- a/src/main/java/net/minecraft/world/entity/Entity.java -+++ b/src/main/java/net/minecraft/world/entity/Entity.java -@@ -3870,7 +3870,14 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess - - public void startSeenByPlayer(ServerPlayer player) {} - -- public void stopSeenByPlayer(ServerPlayer player) {} -+ // Paper start - entity tracking events -+ public void stopSeenByPlayer(ServerPlayer player) { -+ // Since this event cannot be cancelled, we should call it here to catch all "un-tracks" -+ if (io.papermc.paper.event.player.PlayerUntrackEntityEvent.getHandlerList().getRegisteredListeners().length > 0) { -+ new io.papermc.paper.event.player.PlayerUntrackEntityEvent(player.getBukkitEntity(), this.getBukkitEntity()).callEvent(); -+ } -+ } -+ // Paper end - entity tracking events - - public float rotate(Rotation rotation) { - float f = Mth.wrapDegrees(this.getYRot()); diff --git a/patches/server/0774-Improve-PortalEvents.patch b/patches/server/0774-Improve-PortalEvents.patch new file mode 100644 index 000000000000..af78ad7bc060 --- /dev/null +++ b/patches/server/0774-Improve-PortalEvents.patch @@ -0,0 +1,103 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Jake Potrebic +Date: Thu, 15 Dec 2022 10:33:39 -0800 +Subject: [PATCH] Improve PortalEvents + + +diff --git a/src/main/java/net/minecraft/server/level/ServerPlayer.java b/src/main/java/net/minecraft/server/level/ServerPlayer.java +index 25b1e8bec23465f0e9a17f156bdff7fe716db84c..f05a9fd321a4af28e9771bbf39d73f80dd4160c9 100644 +--- a/src/main/java/net/minecraft/server/level/ServerPlayer.java ++++ b/src/main/java/net/minecraft/server/level/ServerPlayer.java +@@ -1561,7 +1561,7 @@ public class ServerPlayer extends net.minecraft.world.entity.player.Player { + + Location enter = this.getBukkitEntity().getLocation(); + PositionMoveRotation absolutePosition = PositionMoveRotation.calculateAbsolute(PositionMoveRotation.of(this), PositionMoveRotation.of(teleportTarget), teleportTarget.relatives()); +- Location exit = (worldserver == null) ? null : CraftLocation.toBukkit(absolutePosition.position(), worldserver.getWorld(), absolutePosition.yRot(), absolutePosition.xRot()); ++ Location exit = /* (worldserver == null) ? null : // Paper - always non-null */CraftLocation.toBukkit(absolutePosition.position(), worldserver.getWorld(), absolutePosition.yRot(), absolutePosition.xRot()); + PlayerTeleportEvent tpEvent = new PlayerTeleportEvent(this.getBukkitEntity(), enter, exit, teleportTarget.cause()); + // Paper start - gateway-specific teleport event + if (this.portalProcess != null && this.portalProcess.isSamePortal(((net.minecraft.world.level.block.EndGatewayBlock) net.minecraft.world.level.block.Blocks.END_GATEWAY)) && this.serverLevel().getBlockEntity(this.portalProcess.getEntryPosition()) instanceof net.minecraft.world.level.block.entity.TheEndGatewayBlockEntity theEndGatewayBlockEntity) { +diff --git a/src/main/java/net/minecraft/world/entity/Entity.java b/src/main/java/net/minecraft/world/entity/Entity.java +index 429cdb83252b417c810d529125fc7343dca7990d..411ffaf842841709eaef48776e2c6ce4f524af05 100644 +--- a/src/main/java/net/minecraft/world/entity/Entity.java ++++ b/src/main/java/net/minecraft/world/entity/Entity.java +@@ -3742,7 +3742,15 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess + org.bukkit.entity.Entity bukkitEntity = entity.getBukkitEntity(); + Location enter = bukkitEntity.getLocation(); + +- EntityPortalEvent event = new EntityPortalEvent(bukkitEntity, enter, exit, searchRadius, true, creationRadius); ++ // Paper start ++ final org.bukkit.PortalType portalType = switch (cause) { ++ case END_PORTAL -> org.bukkit.PortalType.ENDER; ++ case NETHER_PORTAL -> org.bukkit.PortalType.NETHER; ++ case END_GATEWAY -> org.bukkit.PortalType.END_GATEWAY; // not actually used yet ++ default -> org.bukkit.PortalType.CUSTOM; ++ }; ++ EntityPortalEvent event = new EntityPortalEvent(bukkitEntity, enter, exit, searchRadius, true, creationRadius, portalType); ++ // Paper end + event.getEntity().getServer().getPluginManager().callEvent(event); + if (event.isCancelled() || event.getTo() == null || event.getTo().getWorld() == null || !entity.isAlive()) { + return null; +diff --git a/src/main/java/net/minecraft/world/level/block/EndGatewayBlock.java b/src/main/java/net/minecraft/world/level/block/EndGatewayBlock.java +index bb4800c60ac05f2db8821737b2b884ea99b64799..a7a21f071161fb7e73a046717d2462f871ab653c 100644 +--- a/src/main/java/net/minecraft/world/level/block/EndGatewayBlock.java ++++ b/src/main/java/net/minecraft/world/level/block/EndGatewayBlock.java +@@ -94,6 +94,10 @@ public class EndGatewayBlock extends BaseEntityBlock implements Portal { + protected void entityInside(BlockState state, Level world, BlockPos pos, Entity entity) { + if (!new io.papermc.paper.event.entity.EntityInsideBlockEvent(entity.getBukkitEntity(), org.bukkit.craftbukkit.block.CraftBlock.at(world, pos)).callEvent()) { return; } // Paper - Add EntityInsideBlockEvent + if (entity.canUsePortal(false)) { ++ // Paper start - call EntityPortalEnterEvent ++ org.bukkit.event.entity.EntityPortalEnterEvent event = new org.bukkit.event.entity.EntityPortalEnterEvent(entity.getBukkitEntity(), new org.bukkit.Location(world.getWorld(), pos.getX(), pos.getY(), pos.getZ()), org.bukkit.PortalType.END_GATEWAY); // Paper - add portal type ++ if (!event.callEvent()) return; ++ // Paper end - call EntityPortalEnterEvent + BlockEntity tileentity = world.getBlockEntity(pos); + + if (!world.isClientSide && tileentity instanceof TheEndGatewayBlockEntity) { +diff --git a/src/main/java/net/minecraft/world/level/block/EndPortalBlock.java b/src/main/java/net/minecraft/world/level/block/EndPortalBlock.java +index 6ed6c2123ed4c54f191ed8cf6da72109fb95eb69..4aa14f975e1ceedf3d4a427e0daefb58b12fcafe 100644 +--- a/src/main/java/net/minecraft/world/level/block/EndPortalBlock.java ++++ b/src/main/java/net/minecraft/world/level/block/EndPortalBlock.java +@@ -71,8 +71,9 @@ public class EndPortalBlock extends BaseEntityBlock implements Portal { + if (!new io.papermc.paper.event.entity.EntityInsideBlockEvent(entity.getBukkitEntity(), org.bukkit.craftbukkit.block.CraftBlock.at(world, pos)).callEvent()) { return; } // Paper - Add EntityInsideBlockEvent + if (entity.canUsePortal(false)) { + // CraftBukkit start - Entity in portal +- EntityPortalEnterEvent event = new EntityPortalEnterEvent(entity.getBukkitEntity(), new org.bukkit.Location(world.getWorld(), pos.getX(), pos.getY(), pos.getZ())); ++ EntityPortalEnterEvent event = new EntityPortalEnterEvent(entity.getBukkitEntity(), new org.bukkit.Location(world.getWorld(), pos.getX(), pos.getY(), pos.getZ()), org.bukkit.PortalType.ENDER); // Paper - add portal type + world.getCraftServer().getPluginManager().callEvent(event); ++ if (event.isCancelled()) return; // Paper - make cancellable + // CraftBukkit end + if (!world.isClientSide && world.dimension() == Level.END && entity instanceof ServerPlayer) { + ServerPlayer entityplayer = (ServerPlayer) entity; +@@ -95,7 +96,7 @@ public class EndPortalBlock extends BaseEntityBlock implements Portal { + ServerLevel worldserver1 = world.getServer().getLevel(resourcekey); + + if (worldserver1 == null) { +- return new TeleportTransition(PlayerTeleportEvent.TeleportCause.END_PORTAL); // CraftBukkit- always fire event in case plugins wish to change it ++ return null; // Paper - keep previous behavior of not firing PlayerTeleportEvent if the target world doesn't exist + } else { + boolean flag = resourcekey == Level.END; + BlockPos blockposition1 = flag ? ServerLevel.END_SPAWN_POINT : worldserver1.getSharedSpawnPos(); +diff --git a/src/main/java/net/minecraft/world/level/block/NetherPortalBlock.java b/src/main/java/net/minecraft/world/level/block/NetherPortalBlock.java +index fb361eac03c16ecee02219ff0524cce2292c7976..2b31bf586c1c0bd393d2aa8d0b6635dd9f22f21c 100644 +--- a/src/main/java/net/minecraft/world/level/block/NetherPortalBlock.java ++++ b/src/main/java/net/minecraft/world/level/block/NetherPortalBlock.java +@@ -118,8 +118,9 @@ public class NetherPortalBlock extends Block implements Portal { + if (!new io.papermc.paper.event.entity.EntityInsideBlockEvent(entity.getBukkitEntity(), org.bukkit.craftbukkit.block.CraftBlock.at(world, pos)).callEvent()) { return; } // Paper - Add EntityInsideBlockEvent + if (entity.canUsePortal(false)) { + // CraftBukkit start - Entity in portal +- EntityPortalEnterEvent event = new EntityPortalEnterEvent(entity.getBukkitEntity(), new org.bukkit.Location(world.getWorld(), pos.getX(), pos.getY(), pos.getZ())); ++ EntityPortalEnterEvent event = new EntityPortalEnterEvent(entity.getBukkitEntity(), new org.bukkit.Location(world.getWorld(), pos.getX(), pos.getY(), pos.getZ()), org.bukkit.PortalType.NETHER); // Paper - add portal type + world.getCraftServer().getPluginManager().callEvent(event); ++ if (event.isCancelled()) return; // Paper - make cancellable + // CraftBukkit end + entity.setAsInsidePortal(this, pos); + } +@@ -151,7 +152,7 @@ public class NetherPortalBlock extends Block implements Portal { + // Paper end - Add EntityPortalReadyEvent + + if (worldserver1 == null) { +- return new TeleportTransition(PlayerTeleportEvent.TeleportCause.NETHER_PORTAL); // always fire event in case plugins wish to change it ++ return null; // Paper - keep previous behavior of not firing PlayerTeleportEvent if the target world doesn't exist + } else { + boolean flag = worldserver1.getTypeKey() == LevelStem.NETHER; + // CraftBukkit end diff --git a/patches/server/0775-Add-config-option-for-spider-worldborder-climbing.patch b/patches/server/0775-Add-config-option-for-spider-worldborder-climbing.patch new file mode 100644 index 000000000000..6d4efc38b662 --- /dev/null +++ b/patches/server/0775-Add-config-option-for-spider-worldborder-climbing.patch @@ -0,0 +1,19 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: BillyGalbreath +Date: Thu, 27 Oct 2022 15:35:47 +0200 +Subject: [PATCH] Add config option for spider worldborder climbing + + +diff --git a/src/main/java/net/minecraft/world/entity/monster/Spider.java b/src/main/java/net/minecraft/world/entity/monster/Spider.java +index ba4dfa26f4d54abe9ecbacfe1f409a34fb769579..6c2d4c2163cf299c0943af21d4dc367b5677c089 100644 +--- a/src/main/java/net/minecraft/world/entity/monster/Spider.java ++++ b/src/main/java/net/minecraft/world/entity/monster/Spider.java +@@ -82,7 +82,7 @@ public class Spider extends Monster { + public void tick() { + super.tick(); + if (!this.level().isClientSide) { +- this.setClimbing(this.horizontalCollision); ++ this.setClimbing(this.horizontalCollision && (this.level().paperConfig().entities.behavior.allowSpiderWorldBorderClimbing || !(ca.spottedleaf.moonrise.patches.collisions.CollisionUtil.isCollidingWithBorder(this.level().getWorldBorder(), this.getBoundingBox().inflate(ca.spottedleaf.moonrise.patches.collisions.CollisionUtil.COLLISION_EPSILON)) && this.level().getWorldBorder().isInsideCloseToBorder(this, this.getBoundingBox())))); // Paper - Add config option for spider worldborder climbing (Inflate by +EPSILON as collision will just barely place us outside border) + } + + } diff --git a/patches/server/0775-fix-Instruments.patch b/patches/server/0775-fix-Instruments.patch deleted file mode 100644 index 41359cbf136c..000000000000 --- a/patches/server/0775-fix-Instruments.patch +++ /dev/null @@ -1,57 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Jake Potrebic -Date: Fri, 9 Dec 2022 01:47:23 -0800 -Subject: [PATCH] fix Instruments - -properly handle Player#playNote - -diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java -index 922ec82d566fd5ac0b40ed95629e63be3d1bf264..111b90f97f631369acfb76278da26de94a4740bf 100644 ---- a/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java -+++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java -@@ -768,7 +768,10 @@ public class CraftPlayer extends CraftHumanEntity implements Player { - Sound instrumentSound = instrument.getSound(); - if (instrumentSound == null) return; - -- float pitch = note.getPitch(); -+ // Paper start - use correct pitch (modeled off of NoteBlock) -+ final net.minecraft.world.level.block.state.properties.NoteBlockInstrument noteBlockInstrument = CraftBlockData.toNMS(instrument, net.minecraft.world.level.block.state.properties.NoteBlockInstrument.class); -+ final float pitch = noteBlockInstrument.isTunable() ? note.getPitch() : 1.0f; -+ // Paper end - this.getHandle().connection.send(new ClientboundSoundPacket(CraftSound.bukkitToMinecraftHolder(instrumentSound), net.minecraft.sounds.SoundSource.RECORDS, loc.getBlockX(), loc.getBlockY(), loc.getBlockZ(), 3.0f, pitch, this.getHandle().getRandom().nextLong())); - } - -diff --git a/src/test/java/io/papermc/paper/block/InstrumentSoundTest.java b/src/test/java/io/papermc/paper/block/InstrumentSoundTest.java -new file mode 100644 -index 0000000000000000000000000000000000000000..cd718ed01ba5d448cdf0a2b6a39dc7ef2337f70d ---- /dev/null -+++ b/src/test/java/io/papermc/paper/block/InstrumentSoundTest.java -@@ -0,0 +1,28 @@ -+package io.papermc.paper.block; -+ -+import java.util.Arrays; -+import java.util.stream.Stream; -+import net.minecraft.world.level.block.state.properties.NoteBlockInstrument; -+import org.bukkit.Instrument; -+import org.bukkit.craftbukkit.CraftSound; -+import org.bukkit.craftbukkit.block.data.CraftBlockData; -+import org.bukkit.support.environment.AllFeatures; -+import org.junit.jupiter.params.ParameterizedTest; -+import org.junit.jupiter.params.provider.MethodSource; -+ -+import static org.junit.jupiter.api.Assertions.assertEquals; -+ -+@AllFeatures -+class InstrumentSoundTest { -+ -+ static Stream bukkitInstruments() { -+ return Arrays.stream(Instrument.values()).filter(i -> i.getSound() != null); -+ } -+ -+ @ParameterizedTest -+ @MethodSource("bukkitInstruments") -+ void checkInstrumentSound(final Instrument bukkit) { -+ final NoteBlockInstrument nms = CraftBlockData.toNMS(bukkit, NoteBlockInstrument.class); -+ assertEquals(nms.getSoundEvent(), CraftSound.bukkitToMinecraftHolder(bukkit.getSound())); -+ } -+} diff --git a/patches/server/0776-Add-missing-SpigotConfig-logCommands-check.patch b/patches/server/0776-Add-missing-SpigotConfig-logCommands-check.patch new file mode 100644 index 000000000000..73e77aed1118 --- /dev/null +++ b/patches/server/0776-Add-missing-SpigotConfig-logCommands-check.patch @@ -0,0 +1,31 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: NonSwag +Date: Thu, 8 Dec 2022 20:25:05 +0100 +Subject: [PATCH] Add missing SpigotConfig logCommands check + +Co-authored-by: david + +diff --git a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java +index 345d5069d0ebb8ad74158334fd230d0ea64b909d..af8a513c2692b71ad56abce24048b61eb78d41c4 100644 +--- a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java ++++ b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java +@@ -2078,7 +2078,9 @@ public class ServerGamePacketListenerImpl extends ServerCommonPacketListenerImpl + private void performUnsignedChatCommand(String command) { + // CraftBukkit start + String command1 = "/" + command; ++ if (org.spigotmc.SpigotConfig.logCommands) { // Paper - Add missing SpigotConfig logCommands check + ServerGamePacketListenerImpl.LOGGER.info(this.player.getScoreboardName() + " issued server command: " + command1); ++ } + + PlayerCommandPreprocessEvent event = new PlayerCommandPreprocessEvent(this.getCraftPlayer(), command1, new LazyPlayerSet(this.server)); + this.cserver.getPluginManager().callEvent(event); +@@ -2118,7 +2120,9 @@ public class ServerGamePacketListenerImpl extends ServerCommonPacketListenerImpl + private void performSignedChatCommand(ServerboundChatCommandSignedPacket packet, LastSeenMessages lastSeenMessages) { + // CraftBukkit start + String command = "/" + packet.command(); ++ if (org.spigotmc.SpigotConfig.logCommands) { // Paper - Add missing SpigotConfig logCommands check + ServerGamePacketListenerImpl.LOGGER.info(this.player.getScoreboardName() + " issued server command: " + command); ++ } // Paper - Add missing SpigotConfig logCommands check + + PlayerCommandPreprocessEvent event = new PlayerCommandPreprocessEvent(this.getCraftPlayer(), command, new LazyPlayerSet(this.server)); + this.cserver.getPluginManager().callEvent(event); diff --git a/patches/server/0776-Improve-inlining-for-some-hot-BlockBehavior-and-Flui.patch b/patches/server/0776-Improve-inlining-for-some-hot-BlockBehavior-and-Flui.patch deleted file mode 100644 index 420107d5c38d..000000000000 --- a/patches/server/0776-Improve-inlining-for-some-hot-BlockBehavior-and-Flui.patch +++ /dev/null @@ -1,78 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Spottedleaf -Date: Mon, 6 Jul 2020 20:46:50 -0700 -Subject: [PATCH] Improve inlining for some hot BlockBehavior and FluidState - methods - - -diff --git a/src/main/java/net/minecraft/world/level/block/state/BlockBehaviour.java b/src/main/java/net/minecraft/world/level/block/state/BlockBehaviour.java -index 46dd499c2023ec482ae7204d2894fb4100d9233b..a8bec3c405732e5863cf717b1fe948d00837bed2 100644 ---- a/src/main/java/net/minecraft/world/level/block/state/BlockBehaviour.java -+++ b/src/main/java/net/minecraft/world/level/block/state/BlockBehaviour.java -@@ -907,15 +907,15 @@ public abstract class BlockBehaviour implements FeatureElement { - return this.shapeExceedsCube; // Paper - moved into shape cache init - } - -- public boolean useShapeForLightOcclusion() { -+ public final boolean useShapeForLightOcclusion() { // Paper - Perf: Final for inlining - return this.useShapeForLightOcclusion; - } - -- public int getLightEmission() { -+ public final int getLightEmission() { // Paper - Perf: Final for inlining - return this.lightEmission; - } - -- public boolean isAir() { -+ public final boolean isAir() { // Paper - Perf: Final for inlining - return this.isAir; - } - -@@ -999,7 +999,7 @@ public abstract class BlockBehaviour implements FeatureElement { - } - } - -- public boolean canOcclude() { -+ public final boolean canOcclude() { // Paper - Perf: Final for inlining - return this.canOcclude; - } - -@@ -1215,11 +1215,11 @@ public abstract class BlockBehaviour implements FeatureElement { - return this.getBlock().builtInRegistryHolder().is(key); - } - -- public FluidState getFluidState() { -+ public final FluidState getFluidState() { // Paper - Perf: Final for inlining - return this.fluidState; - } - -- public boolean isRandomlyTicking() { -+ public final boolean isRandomlyTicking() { // Paper - Perf: Final for inlining - return this.isRandomlyTicking; - } - -diff --git a/src/main/java/net/minecraft/world/level/material/FluidState.java b/src/main/java/net/minecraft/world/level/material/FluidState.java -index 42370e5e6628ebf8216c01521af859706b08834b..14bb12d2a0066e8b020f2e0e670a7a5c74633623 100644 ---- a/src/main/java/net/minecraft/world/level/material/FluidState.java -+++ b/src/main/java/net/minecraft/world/level/material/FluidState.java -@@ -25,9 +25,11 @@ public final class FluidState extends StateHolder { - public static final Codec CODEC = codec(BuiltInRegistries.FLUID.byNameCodec(), Fluid::defaultFluidState).stable(); - public static final int AMOUNT_MAX = 9; - public static final int AMOUNT_FULL = 8; -+ protected final boolean isEmpty; // Paper - Perf: moved from isEmpty() - - public FluidState(Fluid fluid, Reference2ObjectArrayMap, Comparable> propertyMap, MapCodec codec) { - super(fluid, propertyMap, codec); -+ this.isEmpty = fluid.isEmpty(); // Paper - Perf: moved from isEmpty() - } - - public Fluid getType() { -@@ -43,7 +45,7 @@ public final class FluidState extends StateHolder { - } - - public boolean isEmpty() { -- return this.getType().isEmpty(); -+ return this.isEmpty; // Paper - Perf: moved into constructor - } - - public float getHeight(BlockGetter world, BlockPos pos) { diff --git a/patches/server/0784-Fix-NPE-on-Allay-stopDancing-while-not-dancing.patch b/patches/server/0777-Fix-NPE-on-Allay-stopDancing-while-not-dancing.patch similarity index 100% rename from patches/server/0784-Fix-NPE-on-Allay-stopDancing-while-not-dancing.patch rename to patches/server/0777-Fix-NPE-on-Allay-stopDancing-while-not-dancing.patch diff --git a/patches/server/0778-Add-BlockLockCheckEvent.patch b/patches/server/0778-Add-BlockLockCheckEvent.patch deleted file mode 100644 index 31fba739ae23..000000000000 --- a/patches/server/0778-Add-BlockLockCheckEvent.patch +++ /dev/null @@ -1,70 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Jake Potrebic -Date: Sat, 21 May 2022 20:59:45 -0700 -Subject: [PATCH] Add BlockLockCheckEvent - - -diff --git a/src/main/java/net/minecraft/world/level/block/entity/BaseContainerBlockEntity.java b/src/main/java/net/minecraft/world/level/block/entity/BaseContainerBlockEntity.java -index bf9ae460bdfb247456b895f026f6a7a2e162c5f5..2ddf349fde5b310ec3f74baee1f3d33e09d5286c 100644 ---- a/src/main/java/net/minecraft/world/level/block/entity/BaseContainerBlockEntity.java -+++ b/src/main/java/net/minecraft/world/level/block/entity/BaseContainerBlockEntity.java -@@ -73,17 +73,44 @@ public abstract class BaseContainerBlockEntity extends BlockEntity implements Co - protected abstract Component getDefaultName(); - - public boolean canOpen(Player player) { -- return BaseContainerBlockEntity.canUnlock(player, this.lockKey, this.getDisplayName()); -+ return BaseContainerBlockEntity.canUnlock(player, this.lockKey, this.getDisplayName(), this); // Paper - Add BlockLockCheckEvent - } - -+ @Deprecated @io.papermc.paper.annotation.DoNotUse // Paper - Add BlockLockCheckEvent - public static boolean canUnlock(Player player, LockCode lock, Component containerName) { -+ // Paper start - Add BlockLockCheckEvent -+ return canUnlock(player, lock, containerName, null); -+ } -+ public static boolean canUnlock(Player player, LockCode lock, Component containerName, @Nullable BlockEntity blockEntity) { -+ if (player instanceof net.minecraft.server.level.ServerPlayer serverPlayer && blockEntity != null && blockEntity.getLevel() != null && blockEntity.getLevel().getBlockEntity(blockEntity.getBlockPos()) == blockEntity) { -+ final org.bukkit.block.Block block = org.bukkit.craftbukkit.block.CraftBlock.at(blockEntity.getLevel(), blockEntity.getBlockPos()); -+ net.kyori.adventure.text.Component lockedMessage = net.kyori.adventure.text.Component.translatable("container.isLocked", io.papermc.paper.adventure.PaperAdventure.asAdventure(containerName)); -+ net.kyori.adventure.sound.Sound lockedSound = net.kyori.adventure.sound.Sound.sound(org.bukkit.Sound.BLOCK_CHEST_LOCKED, net.kyori.adventure.sound.Sound.Source.BLOCK, 1.0F, 1.0F); -+ final io.papermc.paper.event.block.BlockLockCheckEvent event = new io.papermc.paper.event.block.BlockLockCheckEvent(block, serverPlayer.getBukkitEntity(), lockedMessage, lockedSound); -+ event.callEvent(); -+ if (event.getResult() == org.bukkit.event.Event.Result.ALLOW) { -+ return true; -+ } else if (event.getResult() == org.bukkit.event.Event.Result.DENY || (!player.isSpectator() && !lock.unlocksWith(event.isUsingCustomKeyItemStack() ? org.bukkit.craftbukkit.inventory.CraftItemStack.asNMSCopy(event.getKeyItem()) : player.getMainHandItem()))) { -+ if (event.getLockedMessage() != null) { -+ event.getPlayer().sendActionBar(event.getLockedMessage()); -+ } -+ if (event.getLockedSound() != null) { -+ event.getPlayer().playSound(event.getLockedSound()); -+ } -+ return false; -+ } else { -+ return true; -+ } -+ } else { // logic below is replaced by logic above -+ // Paper end - Add BlockLockCheckEvent - if (!player.isSpectator() && !lock.unlocksWith(player.getMainHandItem())) { -- player.displayClientMessage(Component.translatable("container.isLocked", containerName), true); -+ player.displayClientMessage(Component.translatable("container.isLocked", containerName), true); // Paper - diff on change - player.playNotifySound(SoundEvents.CHEST_LOCKED, SoundSource.BLOCKS, 1.0F, 1.0F); - return false; - } else { - return true; - } -+ } // Paper - Add BlockLockCheckEvent - } - - protected abstract NonNullList getItems(); -diff --git a/src/main/java/net/minecraft/world/level/block/entity/BeaconBlockEntity.java b/src/main/java/net/minecraft/world/level/block/entity/BeaconBlockEntity.java -index f8d432ef21e59796da4b11c9748ba151c54e5e04..b6633ca1ee73ef0f8a220992a2e0424e67dd9758 100644 ---- a/src/main/java/net/minecraft/world/level/block/entity/BeaconBlockEntity.java -+++ b/src/main/java/net/minecraft/world/level/block/entity/BeaconBlockEntity.java -@@ -472,7 +472,7 @@ public class BeaconBlockEntity extends BlockEntity implements MenuProvider, Name - @Nullable - @Override - public AbstractContainerMenu createMenu(int syncId, Inventory playerInventory, Player player) { -- return BaseContainerBlockEntity.canUnlock(player, this.lockKey, this.getDisplayName()) ? new BeaconMenu(syncId, playerInventory, this.dataAccess, ContainerLevelAccess.create(this.level, this.getBlockPos())) : null; -+ return BaseContainerBlockEntity.canUnlock(player, this.lockKey, this.getDisplayName(), this) ? new BeaconMenu(syncId, playerInventory, this.dataAccess, ContainerLevelAccess.create(this.level, this.getBlockPos())) : null; // Paper - Add BlockLockCheckEvent - } - - @Override diff --git a/patches/server/0778-Flying-Fall-Damage.patch b/patches/server/0778-Flying-Fall-Damage.patch new file mode 100644 index 000000000000..edf295a42023 --- /dev/null +++ b/patches/server/0778-Flying-Fall-Damage.patch @@ -0,0 +1,51 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: TreyRuffy +Date: Fri, 27 May 2022 02:26:08 -0600 +Subject: [PATCH] Flying Fall Damage + + +diff --git a/src/main/java/net/minecraft/world/entity/player/Player.java b/src/main/java/net/minecraft/world/entity/player/Player.java +index 30e0a5fe3f9bd85d2b702c2c877c5682ed35d461..aca888c2f02b09ac6739bdc81b194c4527dd69f5 100644 +--- a/src/main/java/net/minecraft/world/entity/player/Player.java ++++ b/src/main/java/net/minecraft/world/entity/player/Player.java +@@ -196,6 +196,7 @@ public abstract class Player extends LivingEntity { + private boolean ignoreFallDamageFromCurrentImpulse; + private int currentImpulseContextResetGraceTime; + public boolean affectsSpawning = true; // Paper - Affects Spawning API ++ public net.kyori.adventure.util.TriState flyingFallDamage = net.kyori.adventure.util.TriState.NOT_SET; // Paper - flying fall damage + + // CraftBukkit start + public boolean fauxSleeping; +@@ -1651,7 +1652,7 @@ public abstract class Player extends LivingEntity { + + @Override + public boolean causeFallDamage(float fallDistance, float damageMultiplier, DamageSource damageSource) { +- if (this.abilities.mayfly) { ++ if (this.abilities.mayfly && !this.flyingFallDamage.toBooleanOrElse(false)) { // Paper - flying fall damage + return false; + } else { + if (fallDistance >= 2.0F) { +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java +index f41d97bdb046747a6540dc22cbdc322d764f1e0a..3b6bd9a4b3c9767cdc13cda75e0be6d9aed0b672 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java +@@ -2605,6 +2605,19 @@ public class CraftPlayer extends CraftHumanEntity implements Player { + this.getHandle().onUpdateAbilities(); + } + ++ // Paper start - flying fall damage ++ @Override ++ public void setFlyingFallDamage(@NotNull net.kyori.adventure.util.TriState flyingFallDamage) { ++ getHandle().flyingFallDamage = flyingFallDamage; ++ } ++ ++ @NotNull ++ @Override ++ public net.kyori.adventure.util.TriState hasFlyingFallDamage() { ++ return getHandle().flyingFallDamage; ++ } ++ // Paper end - flying fall damage ++ + @Override + public int getNoDamageTicks() { + if (this.getHandle().spawnInvulnerableTime > 0) { diff --git a/patches/server/0779-Add-Sneaking-API-for-Entities.patch b/patches/server/0779-Add-Sneaking-API-for-Entities.patch deleted file mode 100644 index aaee22c0564a..000000000000 --- a/patches/server/0779-Add-Sneaking-API-for-Entities.patch +++ /dev/null @@ -1,29 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: dawon -Date: Wed, 19 Oct 2022 23:31:53 +0200 -Subject: [PATCH] Add Sneaking API for Entities - - -diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java -index f950102a324d07aeba260bfa82fe88728f2362e5..ac513d3162a0794f226abc80bff21c799fe5802c 100644 ---- a/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java -+++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java -@@ -874,6 +874,18 @@ public abstract class CraftEntity implements org.bukkit.entity.Entity { - return Pose.values()[this.getHandle().getPose().ordinal()]; - } - -+ // Paper start -+ @Override -+ public void setSneaking(boolean sneak) { -+ this.getHandle().setShiftKeyDown(sneak); -+ } -+ -+ @Override -+ public boolean isSneaking() { -+ return this.getHandle().isShiftKeyDown(); -+ } -+ // Paper end -+ - @Override - public SpawnCategory getSpawnCategory() { - return CraftSpawnCategory.toBukkit(this.getHandle().getType().getCategory()); diff --git a/patches/server/0779-Expose-pre-collision-moving-velocity-to-VehicleBlock.patch b/patches/server/0779-Expose-pre-collision-moving-velocity-to-VehicleBlock.patch new file mode 100644 index 000000000000..3af6c787dbfa --- /dev/null +++ b/patches/server/0779-Expose-pre-collision-moving-velocity-to-VehicleBlock.patch @@ -0,0 +1,28 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: SoSeDiK +Date: Tue, 11 Oct 2022 23:30:32 +0300 +Subject: [PATCH] Expose pre-collision moving velocity to + VehicleBlockCollisionEvent + + +diff --git a/src/main/java/net/minecraft/world/entity/Entity.java b/src/main/java/net/minecraft/world/entity/Entity.java +index 411ffaf842841709eaef48776e2c6ce4f524af05..5340d6d911a7e586468f2b7e2e74b3386d1dc72d 100644 +--- a/src/main/java/net/minecraft/world/entity/Entity.java ++++ b/src/main/java/net/minecraft/world/entity/Entity.java +@@ -968,6 +968,7 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess + } + + public void move(MoverType type, Vec3 movement) { ++ final Vec3 originalMovement = movement; // Paper - Expose pre-collision velocity + if (this.noPhysics) { + this.setPos(this.getX() + movement.x, this.getY() + movement.y, this.getZ() + movement.z); + } else { +@@ -1059,7 +1060,7 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess + } + + if (!bl.getType().isAir()) { +- VehicleBlockCollisionEvent event = new VehicleBlockCollisionEvent(vehicle, bl); ++ VehicleBlockCollisionEvent event = new VehicleBlockCollisionEvent(vehicle, bl, org.bukkit.craftbukkit.util.CraftVector.toBukkit(originalMovement)); // Paper - Expose pre-collision velocity + this.level.getCraftServer().getPluginManager().callEvent(event); + } + } diff --git a/patches/server/0780-Improve-logging-and-errors.patch b/patches/server/0780-Improve-logging-and-errors.patch deleted file mode 100644 index d2ccc7abedb4..000000000000 --- a/patches/server/0780-Improve-logging-and-errors.patch +++ /dev/null @@ -1,117 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Jake Potrebic -Date: Wed, 14 Dec 2022 15:52:11 -0800 -Subject: [PATCH] Improve logging and errors - -Co-authored-by: Jason Penilla <11360596+jpenilla@users.noreply.github.com> - -diff --git a/src/main/java/net/minecraft/advancements/AdvancementTree.java b/src/main/java/net/minecraft/advancements/AdvancementTree.java -index 2665170b8391a77d6b3fb7ae7b5ccfc0be65acd7..e00d4e0896c0163c43d79af63338de67c7cb0dc4 100644 ---- a/src/main/java/net/minecraft/advancements/AdvancementTree.java -+++ b/src/main/java/net/minecraft/advancements/AdvancementTree.java -@@ -35,7 +35,7 @@ public class AdvancementTree { - this.remove(advancementnode1); - } - -- AdvancementTree.LOGGER.info("Forgot about advancement {}", advancement.holder()); -+ AdvancementTree.LOGGER.debug("Forgot about advancement {}", advancement.holder()); // Paper - Improve logging and errors - this.nodes.remove(advancement.holder().id()); - if (advancement.parent() == null) { - this.roots.remove(advancement); -@@ -77,7 +77,7 @@ public class AdvancementTree { - } - } - -- // AdvancementTree.LOGGER.info("Loaded {} advancements", this.nodes.size()); // CraftBukkit - moved to AdvancementDataWorld#reload -+ // AdvancementTree.LOGGER.info("Loaded {} advancements", this.nodes.size()); // CraftBukkit - moved to AdvancementDataWorld#reload // Paper - Improve logging and errors; you say it was moved... but it wasn't :) it should be moved however, since this is called when the API creates an advancement - } - - private boolean tryInsert(AdvancementHolder advancement) { -diff --git a/src/main/java/net/minecraft/server/ServerAdvancementManager.java b/src/main/java/net/minecraft/server/ServerAdvancementManager.java -index 3255578c8c37d977bb6e89d27194a4dbff822014..2172e23b22b411fe79e930128bd43ac07becb620 100644 ---- a/src/main/java/net/minecraft/server/ServerAdvancementManager.java -+++ b/src/main/java/net/minecraft/server/ServerAdvancementManager.java -@@ -69,6 +69,7 @@ public class ServerAdvancementManager extends SimpleJsonResourceReloadListener { - AdvancementTree advancementtree = new AdvancementTree(); - - advancementtree.addAll(this.advancements.values()); -+ LOGGER.info("Loaded {} advancements", advancementtree.nodes().size()); // Paper - Improve logging and errors; moved from AdvancementTree#addAll - Iterator iterator = advancementtree.roots().iterator(); - - while (iterator.hasNext()) { -diff --git a/src/main/java/net/minecraft/server/dedicated/DedicatedServer.java b/src/main/java/net/minecraft/server/dedicated/DedicatedServer.java -index aa39bdb0a4ba8fedf5052ea9700afa7d4d2a4300..b4af03c4bdd1ce0861f36c3b75fc7e89d701c46a 100644 ---- a/src/main/java/net/minecraft/server/dedicated/DedicatedServer.java -+++ b/src/main/java/net/minecraft/server/dedicated/DedicatedServer.java -@@ -282,6 +282,7 @@ public class DedicatedServer extends MinecraftServer implements ServerInterface - DedicatedServer.LOGGER.warn("**** FAILED TO BIND TO PORT!"); - DedicatedServer.LOGGER.warn("The exception was: {}", ioexception.toString()); - DedicatedServer.LOGGER.warn("Perhaps a server is already running on that port?"); -+ if (true) throw new IllegalStateException("Failed to bind to port", ioexception); // Paper - Propagate failed to bind to port error - return false; - } - -diff --git a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java -index 28808ffc6e486f7dc01be370c9eb249dc1f7ea46..6b869ff834af1e02f55683b3399d40bee0518bf1 100644 ---- a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java -+++ b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java -@@ -3398,7 +3398,7 @@ public class ServerGamePacketListenerImpl extends ServerCommonPacketListenerImpl - - this.resetPlayerChatState(remotechatsession_a.validate(this.player.getGameProfile(), signaturevalidator)); - } catch (ProfilePublicKey.ValidationException profilepublickey_b) { -- ServerGamePacketListenerImpl.LOGGER.error("Failed to validate profile key: {}", profilepublickey_b.getMessage()); -+ // ServerGamePacketListenerImpl.LOGGER.error("Failed to validate profile key: {}", profilepublickey_b.getMessage()); // Paper - Improve logging and errors - this.disconnect(profilepublickey_b.getComponent(), profilepublickey_b.kickCause); // Paper - kick event causes - } - -diff --git a/src/main/java/net/minecraft/server/packs/PathPackResources.java b/src/main/java/net/minecraft/server/packs/PathPackResources.java -index ed1d3d30404dfc6303e746f31295b6b1aa40f204..8d7565eda4536e8aa42dd3dcbcfac38cbf16020a 100644 ---- a/src/main/java/net/minecraft/server/packs/PathPackResources.java -+++ b/src/main/java/net/minecraft/server/packs/PathPackResources.java -@@ -103,6 +103,12 @@ public class PathPackResources extends AbstractPackResources { - try (DirectoryStream directoryStream = Files.newDirectoryStream(path)) { - for (Path path2 : directoryStream) { - String string = path2.getFileName().toString(); -+ // Paper start - Improve logging and errors -+ if (!Files.isDirectory(path2)) { -+ LOGGER.error("Invalid directory entry: {} in {}.", string, this.root, new java.nio.file.NotDirectoryException(string)); -+ continue; -+ } -+ // Paper end - Improve logging and errors - if (ResourceLocation.isValidNamespace(string)) { - set.add(string); - } else { -diff --git a/src/main/java/net/minecraft/world/item/crafting/RecipeManager.java b/src/main/java/net/minecraft/world/item/crafting/RecipeManager.java -index 3270bb6fe1c8b08411e6cad14d48cec886085ee7..407f3c1938b5b5d893b09705fe4930dbdafa3c8e 100644 ---- a/src/main/java/net/minecraft/world/item/crafting/RecipeManager.java -+++ b/src/main/java/net/minecraft/world/item/crafting/RecipeManager.java -@@ -79,7 +79,7 @@ public class RecipeManager extends SimpleJsonResourceReloadListener { - this.byType = LinkedHashMultimap.create(builder.build()); - this.byName = Maps.newHashMap(com_google_common_collect_immutablemap_builder.build()); - // CraftBukkit end -- RecipeManager.LOGGER.info("Loaded {} recipes", this.byType.size()); -+ RecipeManager.LOGGER.info("Loaded {} recipes", this.byName.size()); // Paper - Improve logging and errors; log correct number of recipes - } - - // CraftBukkit start -diff --git a/src/main/java/org/bukkit/craftbukkit/legacy/CraftLegacy.java b/src/main/java/org/bukkit/craftbukkit/legacy/CraftLegacy.java -index b2812cc8d35074fdcff88beef088d9f63ebbe1a8..dc591702d1ad41209bb80e8d05f4ca11f20816f2 100644 ---- a/src/main/java/org/bukkit/craftbukkit/legacy/CraftLegacy.java -+++ b/src/main/java/org/bukkit/craftbukkit/legacy/CraftLegacy.java -@@ -44,6 +44,7 @@ import org.bukkit.material.MaterialData; - */ - @Deprecated - public final class CraftLegacy { -+ private static final org.slf4j.Logger LOGGER = com.mojang.logging.LogUtils.getLogger(); // Paper - Improve logging and errors - - private static final Map SPAWN_EGGS = new HashMap<>(); - private static final Set whitelistedStates = new HashSet<>(Arrays.asList("explode", "check_decay", "decayable", "facing")); -@@ -264,7 +265,7 @@ public final class CraftLegacy { - } - - static { -- System.err.println("Initializing Legacy Material Support. Unless you have legacy plugins and/or data this is a bug!"); -+ LOGGER.warn("Initializing Legacy Material Support. Unless you have legacy plugins and/or data this is a bug!"); // Paper - Improve logging and errors; doesn't need to be an error - if (MinecraftServer.getServer() != null && MinecraftServer.getServer().isDebugging()) { - new Exception().printStackTrace(); - } diff --git a/patches/server/0780-config-for-disabling-entity-tag-tags.patch b/patches/server/0780-config-for-disabling-entity-tag-tags.patch new file mode 100644 index 000000000000..59c33843cef1 --- /dev/null +++ b/patches/server/0780-config-for-disabling-entity-tag-tags.patch @@ -0,0 +1,27 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Jake Potrebic +Date: Wed, 15 Sep 2021 14:52:42 -0700 +Subject: [PATCH] config for disabling entity tag tags + + +diff --git a/src/main/java/net/minecraft/world/entity/EntityType.java b/src/main/java/net/minecraft/world/entity/EntityType.java +index 989b766bc166141a391e0a7c1a5eb815e20acfac..64dc0bd1900575e40ac72a98c6df371223bd244c 100644 +--- a/src/main/java/net/minecraft/world/entity/EntityType.java ++++ b/src/main/java/net/minecraft/world/entity/EntityType.java +@@ -569,6 +569,16 @@ public class EntityType implements FeatureElement, EntityTypeT + + if (minecraftserver != null && entity != null) { + if (world.isClientSide || !entity.onlyOpCanSetNbt() || player != null && minecraftserver.getPlayerList().isOp(player.getGameProfile())) { ++ // Paper start - filter out protected tags ++ if (player == null || !player.getBukkitEntity().hasPermission("minecraft.nbt.place")) { ++ nbt = nbt.update((compound) -> { ++ for (net.minecraft.commands.arguments.NbtPathArgument.NbtPath tag : world.paperConfig().entities.spawning.filteredEntityTagNbtPaths) { ++ tag.remove(compound); ++ } ++ }); ++ } ++ // Paper end - filter out protected tags ++ + nbt.loadInto(entity); + } + } diff --git a/patches/server/0781-Improve-PortalEvents.patch b/patches/server/0781-Improve-PortalEvents.patch deleted file mode 100644 index 641c38d3dbd1..000000000000 --- a/patches/server/0781-Improve-PortalEvents.patch +++ /dev/null @@ -1,118 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Jake Potrebic -Date: Thu, 15 Dec 2022 10:33:39 -0800 -Subject: [PATCH] Improve PortalEvents - - -diff --git a/src/main/java/net/minecraft/server/level/ServerPlayer.java b/src/main/java/net/minecraft/server/level/ServerPlayer.java -index 96dff63230e8cda1f5e548d914c119bd64b5ac33..7270b6fa96bae937663c0fea77887e21fbd0eb57 100644 ---- a/src/main/java/net/minecraft/server/level/ServerPlayer.java -+++ b/src/main/java/net/minecraft/server/level/ServerPlayer.java -@@ -1340,7 +1340,7 @@ public class ServerPlayer extends net.minecraft.world.entity.player.Player { - } - // CraftBukkit start - Location enter = this.getBukkitEntity().getLocation(); -- Location exit = (worldserver == null) ? null : CraftLocation.toBukkit(teleportTarget.pos(), worldserver.getWorld(), teleportTarget.yRot(), teleportTarget.xRot()); -+ Location exit =/* (worldserver == null) ? null : // Paper - always non-null */CraftLocation.toBukkit(teleportTarget.pos(), worldserver.getWorld(), teleportTarget.yRot(), teleportTarget.xRot()); - PlayerTeleportEvent tpEvent = new PlayerTeleportEvent(this.getBukkitEntity(), enter, exit, teleportTarget.cause()); - Bukkit.getServer().getPluginManager().callEvent(tpEvent); - if (tpEvent.isCancelled() || tpEvent.getTo() == null) { -diff --git a/src/main/java/net/minecraft/world/entity/Entity.java b/src/main/java/net/minecraft/world/entity/Entity.java -index d5df4ddb33600479e6edb93a18db9400ffc6c699..9958be86d769b9fd17b6d24d0c4f1e96b65dfb08 100644 ---- a/src/main/java/net/minecraft/world/entity/Entity.java -+++ b/src/main/java/net/minecraft/world/entity/Entity.java -@@ -3529,7 +3529,15 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess - org.bukkit.entity.Entity bukkitEntity = entity.getBukkitEntity(); - Location enter = bukkitEntity.getLocation(); - -- EntityPortalEvent event = new EntityPortalEvent(bukkitEntity, enter, exit, searchRadius, true, creationRadius); -+ // Paper start -+ final org.bukkit.PortalType portalType = switch (cause) { -+ case END_PORTAL -> org.bukkit.PortalType.ENDER; -+ case NETHER_PORTAL -> org.bukkit.PortalType.NETHER; -+ case END_GATEWAY -> org.bukkit.PortalType.END_GATEWAY; // not actually used yet -+ default -> org.bukkit.PortalType.CUSTOM; -+ }; -+ EntityPortalEvent event = new EntityPortalEvent(bukkitEntity, enter, exit, searchRadius, true, creationRadius, portalType); -+ // Paper end - event.getEntity().getServer().getPluginManager().callEvent(event); - if (event.isCancelled() || event.getTo() == null || event.getTo().getWorld() == null || !entity.isAlive()) { - return null; -diff --git a/src/main/java/net/minecraft/world/level/block/EndGatewayBlock.java b/src/main/java/net/minecraft/world/level/block/EndGatewayBlock.java -index 11486419dd98a013c7387d3d73f322a95a18c574..3f5bb5c9ceb5b31fcc9ef0a7a6157e1e1cb2a09f 100644 ---- a/src/main/java/net/minecraft/world/level/block/EndGatewayBlock.java -+++ b/src/main/java/net/minecraft/world/level/block/EndGatewayBlock.java -@@ -92,6 +92,10 @@ public class EndGatewayBlock extends BaseEntityBlock implements Portal { - protected void entityInside(BlockState state, Level world, BlockPos pos, Entity entity) { - if (!new io.papermc.paper.event.entity.EntityInsideBlockEvent(entity.getBukkitEntity(), org.bukkit.craftbukkit.block.CraftBlock.at(world, pos)).callEvent()) { return; } // Paper - Add EntityInsideBlockEvent - if (entity.canUsePortal(false)) { -+ // Paper start - call EntityPortalEnterEvent -+ org.bukkit.event.entity.EntityPortalEnterEvent event = new org.bukkit.event.entity.EntityPortalEnterEvent(entity.getBukkitEntity(), new org.bukkit.Location(world.getWorld(), pos.getX(), pos.getY(), pos.getZ()), org.bukkit.PortalType.END_GATEWAY); // Paper - add portal type -+ if (!event.callEvent()) return; -+ // Paper end - call EntityPortalEnterEvent - BlockEntity tileentity = world.getBlockEntity(pos); - - if (!world.isClientSide && tileentity instanceof TheEndGatewayBlockEntity) { -diff --git a/src/main/java/net/minecraft/world/level/block/EndPortalBlock.java b/src/main/java/net/minecraft/world/level/block/EndPortalBlock.java -index 28fba1448309805fc3d687de6bc8454d2c85fcd3..a35a426cc7778a51523f26057b5d61b8a3e23d5d 100644 ---- a/src/main/java/net/minecraft/world/level/block/EndPortalBlock.java -+++ b/src/main/java/net/minecraft/world/level/block/EndPortalBlock.java -@@ -66,8 +66,9 @@ public class EndPortalBlock extends BaseEntityBlock implements Portal { - if (!new io.papermc.paper.event.entity.EntityInsideBlockEvent(entity.getBukkitEntity(), org.bukkit.craftbukkit.block.CraftBlock.at(world, pos)).callEvent()) { return; } // Paper - Add EntityInsideBlockEvent - if (entity.canUsePortal(false) && Shapes.joinIsNotEmpty(Shapes.create(entity.getBoundingBox().move((double) (-pos.getX()), (double) (-pos.getY()), (double) (-pos.getZ()))), state.getShape(world, pos), BooleanOp.AND)) { - // CraftBukkit start - Entity in portal -- EntityPortalEnterEvent event = new EntityPortalEnterEvent(entity.getBukkitEntity(), new org.bukkit.Location(world.getWorld(), pos.getX(), pos.getY(), pos.getZ())); -+ EntityPortalEnterEvent event = new EntityPortalEnterEvent(entity.getBukkitEntity(), new org.bukkit.Location(world.getWorld(), pos.getX(), pos.getY(), pos.getZ()), org.bukkit.PortalType.ENDER); // Paper - add portal type - world.getCraftServer().getPluginManager().callEvent(event); -+ if (event.isCancelled()) return; // Paper - make cancellable - // CraftBukkit end - if (!world.isClientSide && world.dimension() == Level.END && entity instanceof ServerPlayer) { - ServerPlayer entityplayer = (ServerPlayer) entity; -@@ -90,7 +91,7 @@ public class EndPortalBlock extends BaseEntityBlock implements Portal { - ServerLevel worldserver1 = world.getServer().getLevel(resourcekey); - - if (worldserver1 == null) { -- return new DimensionTransition(PlayerTeleportEvent.TeleportCause.END_PORTAL); // CraftBukkit- always fire event in case plugins wish to change it -+ return null; // Paper - keep previous behavior of not firing PlayerTeleportEvent if the target world doesn't exist - } else { - boolean flag = resourcekey == Level.END; - BlockPos blockposition1 = flag ? ServerLevel.END_SPAWN_POINT : worldserver1.getSharedSpawnPos(); -diff --git a/src/main/java/net/minecraft/world/level/block/NetherPortalBlock.java b/src/main/java/net/minecraft/world/level/block/NetherPortalBlock.java -index 417a9ab28d247d5fbb3f1097fdeccab7ad2a793b..0fdbcab175b51a8b77646e0e4a267d987b133a35 100644 ---- a/src/main/java/net/minecraft/world/level/block/NetherPortalBlock.java -+++ b/src/main/java/net/minecraft/world/level/block/NetherPortalBlock.java -@@ -110,8 +110,9 @@ public class NetherPortalBlock extends Block implements Portal { - if (!new io.papermc.paper.event.entity.EntityInsideBlockEvent(entity.getBukkitEntity(), org.bukkit.craftbukkit.block.CraftBlock.at(world, pos)).callEvent()) { return; } // Paper - Add EntityInsideBlockEvent - if (entity.canUsePortal(false)) { - // CraftBukkit start - Entity in portal -- EntityPortalEnterEvent event = new EntityPortalEnterEvent(entity.getBukkitEntity(), new org.bukkit.Location(world.getWorld(), pos.getX(), pos.getY(), pos.getZ())); -+ EntityPortalEnterEvent event = new EntityPortalEnterEvent(entity.getBukkitEntity(), new org.bukkit.Location(world.getWorld(), pos.getX(), pos.getY(), pos.getZ()), org.bukkit.PortalType.NETHER); // Paper - add portal type - world.getCraftServer().getPluginManager().callEvent(event); -+ if (event.isCancelled()) return; // Paper - make cancellable - // CraftBukkit end - entity.setAsInsidePortal(this, pos); - } -@@ -143,7 +144,7 @@ public class NetherPortalBlock extends Block implements Portal { - // Paper end - Add EntityPortalReadyEvent - - if (worldserver1 == null) { -- return new DimensionTransition(PlayerTeleportEvent.TeleportCause.NETHER_PORTAL); // always fire event in case plugins wish to change it -+ return null; // Paper - keep previous behavior of not firing PlayerTeleportEvent if the target world doesn't exist - } else { - boolean flag = worldserver1.getTypeKey() == LevelStem.NETHER; - // CraftBukkit end -diff --git a/src/main/java/net/minecraft/world/level/portal/DimensionTransition.java b/src/main/java/net/minecraft/world/level/portal/DimensionTransition.java -index 788f79dc38012595b385ee6a449daa0bccf0079a..36c8735312c885eb153f4ffdf0f2a5495e9c9f65 100644 ---- a/src/main/java/net/minecraft/world/level/portal/DimensionTransition.java -+++ b/src/main/java/net/minecraft/world/level/portal/DimensionTransition.java -@@ -15,9 +15,7 @@ public record DimensionTransition(ServerLevel newLevel, Vec3 pos, Vec3 speed, fl - this(newLevel, pos, speed, yRot, xRot, missingRespawnBlock, postDimensionTransition, PlayerTeleportEvent.TeleportCause.UNKNOWN); - } - -- public DimensionTransition(PlayerTeleportEvent.TeleportCause cause) { -- this(null, Vec3.ZERO, Vec3.ZERO, 0.0F, 0.0F, false, DO_NOTHING, cause); -- } -+ // Paper - remove unused constructor (for safety) - // CraftBukkit end - - public static final DimensionTransition.PostDimensionTransition DO_NOTHING = (entity) -> { diff --git a/patches/server/0781-Use-single-player-info-update-packet-on-join.patch b/patches/server/0781-Use-single-player-info-update-packet-on-join.patch new file mode 100644 index 000000000000..271efab27a44 --- /dev/null +++ b/patches/server/0781-Use-single-player-info-update-packet-on-join.patch @@ -0,0 +1,51 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Jake Potrebic +Date: Sun, 8 Jan 2023 17:38:28 -0800 +Subject: [PATCH] Use single player info update packet on join + + +diff --git a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java +index af8a513c2692b71ad56abce24048b61eb78d41c4..5802bca38fdeccbc1353990ecb425034f86e8332 100644 +--- a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java ++++ b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java +@@ -3486,7 +3486,7 @@ public class ServerGamePacketListenerImpl extends ServerCommonPacketListenerImpl + this.signedMessageDecoder = session.createMessageDecoder(this.player.getUUID()); + this.chatMessageChain.append(() -> { + this.player.setChatSession(session); +- this.server.getPlayerList().broadcastAll(new ClientboundPlayerInfoUpdatePacket(EnumSet.of(ClientboundPlayerInfoUpdatePacket.Action.INITIALIZE_CHAT), List.of(this.player))); ++ this.server.getPlayerList().broadcastAll(new ClientboundPlayerInfoUpdatePacket(EnumSet.of(ClientboundPlayerInfoUpdatePacket.Action.INITIALIZE_CHAT), List.of(this.player)), this.player); // Paper - Use single player info update packet on join + }); + } + +diff --git a/src/main/java/net/minecraft/server/players/PlayerList.java b/src/main/java/net/minecraft/server/players/PlayerList.java +index 9b01ef9e9b00274f1213c006a4cad0f75e412e13..cbba549176b3acfc25ec42c2935b31ab2176e0fa 100644 +--- a/src/main/java/net/minecraft/server/players/PlayerList.java ++++ b/src/main/java/net/minecraft/server/players/PlayerList.java +@@ -364,6 +364,7 @@ public abstract class PlayerList { + // CraftBukkit start - sendAll above replaced with this loop + ClientboundPlayerInfoUpdatePacket packet = ClientboundPlayerInfoUpdatePacket.createPlayerInitializing(List.of(player)); + ++ final List onlinePlayers = Lists.newArrayListWithExpectedSize(this.players.size() - 1); // Paper - Use single player info update packet on join + for (int i = 0; i < this.players.size(); ++i) { + ServerPlayer entityplayer1 = (ServerPlayer) this.players.get(i); + +@@ -371,12 +372,17 @@ public abstract class PlayerList { + entityplayer1.connection.send(packet); + } + +- if (!bukkitPlayer.canSee(entityplayer1.getBukkitEntity())) { ++ if (entityplayer1 == player || !bukkitPlayer.canSee(entityplayer1.getBukkitEntity())) { // Paper - Use single player info update packet on join; Don't include joining player + continue; + } + +- player.connection.send(ClientboundPlayerInfoUpdatePacket.createPlayerInitializing(List.of(entityplayer1))); ++ onlinePlayers.add(entityplayer1); // Paper - Use single player info update packet on join + } ++ // Paper start - Use single player info update packet on join ++ if (!onlinePlayers.isEmpty()) { ++ player.connection.send(ClientboundPlayerInfoUpdatePacket.createPlayerInitializing(onlinePlayers)); ++ } ++ // Paper end - Use single player info update packet on join + player.sentListPacket = true; + player.supressTrackerForLogin = false; // Paper - Fire PlayerJoinEvent when Player is actually ready + ((ServerLevel)player.level()).getChunkSource().chunkMap.addEntity(player); // Paper - Fire PlayerJoinEvent when Player is actually ready; track entity now diff --git a/patches/server/0782-Add-config-option-for-spider-worldborder-climbing.patch b/patches/server/0782-Add-config-option-for-spider-worldborder-climbing.patch deleted file mode 100644 index 71e393975351..000000000000 --- a/patches/server/0782-Add-config-option-for-spider-worldborder-climbing.patch +++ /dev/null @@ -1,19 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: BillyGalbreath -Date: Thu, 27 Oct 2022 15:35:47 +0200 -Subject: [PATCH] Add config option for spider worldborder climbing - - -diff --git a/src/main/java/net/minecraft/world/entity/monster/Spider.java b/src/main/java/net/minecraft/world/entity/monster/Spider.java -index c80019f0c9f814c5259b4d3ec2d8a85669dc728f..f91ea9ac5a0d0d3bae5d1eb0c409f4f9c4e5a62b 100644 ---- a/src/main/java/net/minecraft/world/entity/monster/Spider.java -+++ b/src/main/java/net/minecraft/world/entity/monster/Spider.java -@@ -82,7 +82,7 @@ public class Spider extends Monster { - public void tick() { - super.tick(); - if (!this.level().isClientSide) { -- this.setClimbing(this.horizontalCollision); -+ this.setClimbing(this.horizontalCollision && (this.level().paperConfig().entities.behavior.allowSpiderWorldBorderClimbing || !(ca.spottedleaf.moonrise.patches.collisions.CollisionUtil.isCollidingWithBorder(this.level().getWorldBorder(), this.getBoundingBox().inflate(ca.spottedleaf.moonrise.patches.collisions.CollisionUtil.COLLISION_EPSILON)) && this.level().getWorldBorder().isInsideCloseToBorder(this, this.getBoundingBox())))); // Paper - Add config option for spider worldborder climbing (Inflate by +EPSILON as collision will just barely place us outside border) - } - - } diff --git a/patches/server/0782-Correctly-shrink-items-during-EntityResurrectEvent.patch b/patches/server/0782-Correctly-shrink-items-during-EntityResurrectEvent.patch new file mode 100644 index 000000000000..b778104720fc --- /dev/null +++ b/patches/server/0782-Correctly-shrink-items-during-EntityResurrectEvent.patch @@ -0,0 +1,36 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Bjarne Koll +Date: Tue, 10 Jan 2023 21:06:42 +0100 +Subject: [PATCH] Correctly shrink items during EntityResurrectEvent + +The EntityResurrectEvent logic is supposed to locate a totem of undying +in any of the interaction slots of the player inventory and then, if the +called EntityResurrectEvent is not cancelled, shrink that item by 1, +usually reducing it to zero. + +For this, the logic iterates over the items in the interaction slots and +breaks out the loop if a totem of undying was found. +However, even if no totem of undying was found, the iteration item stack +variable remains as a refernce to the last interaction slot probed. + +Plugins uncancelling a EntityResurrectEvent, which is published +pre-cancelled to listeners if no totem of undying could be found, +would hence cause the server logic to shrink completely unrelated items +found in, at the writing of this patch, the players off hand slot. + +This patch corrects this behaviour by only shrinking the item if a totem +of undying was found and the event was called uncancelled. + +diff --git a/src/main/java/net/minecraft/world/entity/LivingEntity.java b/src/main/java/net/minecraft/world/entity/LivingEntity.java +index 8b0a764984f886b711cb337a7f70608167916bf3..9ef5c617cb05f08758105772b9124f6c318b5c17 100644 +--- a/src/main/java/net/minecraft/world/entity/LivingEntity.java ++++ b/src/main/java/net/minecraft/world/entity/LivingEntity.java +@@ -1672,7 +1672,7 @@ public abstract class LivingEntity extends Entity implements Attackable { + this.level().getCraftServer().getPluginManager().callEvent(event); + + if (!event.isCancelled()) { +- if (!itemstack1.isEmpty()) { ++ if (!itemstack1.isEmpty() && itemstack != null) { // Paper - only reduce item if actual totem was found + itemstack1.shrink(1); + } + if (itemstack != null && this instanceof ServerPlayer) { diff --git a/patches/server/0783-Add-missing-SpigotConfig-logCommands-check.patch b/patches/server/0783-Add-missing-SpigotConfig-logCommands-check.patch deleted file mode 100644 index 9c86e0000a17..000000000000 --- a/patches/server/0783-Add-missing-SpigotConfig-logCommands-check.patch +++ /dev/null @@ -1,31 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: NonSwag -Date: Thu, 8 Dec 2022 20:25:05 +0100 -Subject: [PATCH] Add missing SpigotConfig logCommands check - -Co-authored-by: david - -diff --git a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java -index 6b869ff834af1e02f55683b3399d40bee0518bf1..a2a7d499a68841ecd76ba029298094993a82bf39 100644 ---- a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java -+++ b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java -@@ -2057,7 +2057,9 @@ public class ServerGamePacketListenerImpl extends ServerCommonPacketListenerImpl - private void performUnsignedChatCommand(String command) { - // CraftBukkit start - String command1 = "/" + command; -+ if (org.spigotmc.SpigotConfig.logCommands) { // Paper - Add missing SpigotConfig logCommands check - ServerGamePacketListenerImpl.LOGGER.info(this.player.getScoreboardName() + " issued server command: " + command1); -+ } - - PlayerCommandPreprocessEvent event = new PlayerCommandPreprocessEvent(this.getCraftPlayer(), command1, new LazyPlayerSet(this.server)); - this.cserver.getPluginManager().callEvent(event); -@@ -2097,7 +2099,9 @@ public class ServerGamePacketListenerImpl extends ServerCommonPacketListenerImpl - private void performSignedChatCommand(ServerboundChatCommandSignedPacket packet, LastSeenMessages lastSeenMessages) { - // CraftBukkit start - String command = "/" + packet.command(); -+ if (org.spigotmc.SpigotConfig.logCommands) { // Paper - Add missing SpigotConfig logCommands check - ServerGamePacketListenerImpl.LOGGER.info(this.player.getScoreboardName() + " issued server command: " + command); -+ } // Paper - Add missing SpigotConfig logCommands check - - PlayerCommandPreprocessEvent event = new PlayerCommandPreprocessEvent(this.getCraftPlayer(), command, new LazyPlayerSet(this.server)); - this.cserver.getPluginManager().callEvent(event); diff --git a/patches/server/0783-Win-Screen-API.patch b/patches/server/0783-Win-Screen-API.patch new file mode 100644 index 000000000000..89c5499a4ee7 --- /dev/null +++ b/patches/server/0783-Win-Screen-API.patch @@ -0,0 +1,38 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Lama06 +Date: Sat, 21 Jan 2023 13:53:23 +0100 +Subject: [PATCH] Win Screen API + +== AT == +public net.minecraft.server.level.ServerPlayer seenCredits + +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java +index 3b6bd9a4b3c9767cdc13cda75e0be6d9aed0b672..3419024a58a3c68b160f16350c72d1250c10d4dd 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java +@@ -1322,6 +1322,25 @@ public class CraftPlayer extends CraftHumanEntity implements Player { + this.getHandle().connection.send(packet); + } + ++ // Paper start ++ @Override ++ public void showWinScreen() { ++ if (getHandle().connection == null) return; ++ var packet = new ClientboundGameEventPacket(ClientboundGameEventPacket.WIN_GAME, 1); ++ getHandle().connection.send(packet); ++ } ++ ++ @Override ++ public boolean hasSeenWinScreen() { ++ return getHandle().seenCredits; ++ } ++ ++ @Override ++ public void setHasSeenWinScreen(boolean hasSeenWinScreen) { ++ getHandle().seenCredits = hasSeenWinScreen; ++ } ++ // Paper end ++ + @Override + public void setRotation(float yaw, float pitch) { + // Paper start - Teleport API diff --git a/patches/server/0784-Remove-CraftItemStack-setAmount-null-assignment.patch b/patches/server/0784-Remove-CraftItemStack-setAmount-null-assignment.patch new file mode 100644 index 000000000000..aa0acd564ff5 --- /dev/null +++ b/patches/server/0784-Remove-CraftItemStack-setAmount-null-assignment.patch @@ -0,0 +1,30 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Josh Roy +Date: Mon, 23 Jan 2023 19:19:01 -0500 +Subject: [PATCH] Remove CraftItemStack#setAmount null assignment + +This creates a problem with Paper's item serialization +api where deserialized items, which are internally +created as a CraftItemStack, will be completely lost if +#setAmount(0) is invoked (since the underlying handle +is set to null), while a regular Bukkit ItemStack +simply sets the amount field to zero, retaining the +item's data. + +Vanilla treats items with zero amounts the same as items +with less than zero amounts, so this code doesn't create +a problem with operations on the vanilla ItemStack. + +diff --git a/src/main/java/org/bukkit/craftbukkit/inventory/CraftItemStack.java b/src/main/java/org/bukkit/craftbukkit/inventory/CraftItemStack.java +index 502be683e8b04a9966043c9bee9d9fe793b12ef5..134db8c2dd72d0651fc889cc8931e7c971f62deb 100644 +--- a/src/main/java/org/bukkit/craftbukkit/inventory/CraftItemStack.java ++++ b/src/main/java/org/bukkit/craftbukkit/inventory/CraftItemStack.java +@@ -198,7 +198,7 @@ public final class CraftItemStack extends ItemStack { + } + + this.handle.setCount(amount); +- if (amount == 0) { ++ if (false && amount == 0) { // Paper - remove CraftItemStack#setAmount null assignment + this.handle = null; + } + } diff --git a/patches/server/0785-Fix-force-opening-enchantment-tables.patch b/patches/server/0785-Fix-force-opening-enchantment-tables.patch new file mode 100644 index 000000000000..6ddc05bd3fd8 --- /dev/null +++ b/patches/server/0785-Fix-force-opening-enchantment-tables.patch @@ -0,0 +1,30 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Jake Potrebic +Date: Thu, 26 Jan 2023 16:19:26 -0800 +Subject: [PATCH] Fix force-opening enchantment tables + + +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftHumanEntity.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftHumanEntity.java +index df9d02eb1ffc3cc669e835e2c08d951283871db3..04fe27a84eb240f8e9bb0ed5b21fd60cfed619ad 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftHumanEntity.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftHumanEntity.java +@@ -411,7 +411,18 @@ public class CraftHumanEntity extends CraftLivingEntity implements HumanEntity { + + // If there isn't an enchant table we can force create one, won't be very useful though. + BlockPos pos = CraftLocation.toBlockPosition(location); +- this.getHandle().openMenu(Blocks.ENCHANTING_TABLE.defaultBlockState().getMenuProvider(this.getHandle().level(), pos)); ++ // Paper start ++ MenuProvider menuProvider = Blocks.ENCHANTING_TABLE.defaultBlockState().getMenuProvider(this.getHandle().level(), pos); ++ if (menuProvider == null) { ++ if (!force) { ++ return null; ++ } ++ menuProvider = new net.minecraft.world.SimpleMenuProvider((syncId, inventory, player) -> { ++ return new net.minecraft.world.inventory.EnchantmentMenu(syncId, inventory, net.minecraft.world.inventory.ContainerLevelAccess.create(this.getHandle().level(), pos)); ++ }, Component.translatable("container.enchant")); ++ } ++ this.getHandle().openMenu(menuProvider); ++ // Paper end + + if (force) { + this.getHandle().containerMenu.checkReachable = false; diff --git a/patches/server/0785-Flying-Fall-Damage.patch b/patches/server/0785-Flying-Fall-Damage.patch deleted file mode 100644 index cee20a07daae..000000000000 --- a/patches/server/0785-Flying-Fall-Damage.patch +++ /dev/null @@ -1,51 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: TreyRuffy -Date: Fri, 27 May 2022 02:26:08 -0600 -Subject: [PATCH] Flying Fall Damage - - -diff --git a/src/main/java/net/minecraft/world/entity/player/Player.java b/src/main/java/net/minecraft/world/entity/player/Player.java -index ad334f149fe1b44d4ebe48489dcd2811ff1b5cd0..950ce40d268d89ff3c503116081db6c9ccd65329 100644 ---- a/src/main/java/net/minecraft/world/entity/player/Player.java -+++ b/src/main/java/net/minecraft/world/entity/player/Player.java -@@ -196,6 +196,7 @@ public abstract class Player extends LivingEntity { - private boolean ignoreFallDamageFromCurrentImpulse; - private int currentImpulseContextResetGraceTime; - public boolean affectsSpawning = true; // Paper - Affects Spawning API -+ public net.kyori.adventure.util.TriState flyingFallDamage = net.kyori.adventure.util.TriState.NOT_SET; // Paper - flying fall damage - - // CraftBukkit start - public boolean fauxSleeping; -@@ -1693,7 +1694,7 @@ public abstract class Player extends LivingEntity { - - @Override - public boolean causeFallDamage(float fallDistance, float damageMultiplier, DamageSource damageSource) { -- if (this.abilities.mayfly) { -+ if (this.abilities.mayfly && !this.flyingFallDamage.toBooleanOrElse(false)) { // Paper - flying fall damage - return false; - } else { - if (fallDistance >= 2.0F) { -diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java -index 111b90f97f631369acfb76278da26de94a4740bf..fdf6c3f1f8f79dd20e1363ef472f97fe98c9799e 100644 ---- a/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java -+++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java -@@ -2581,6 +2581,19 @@ public class CraftPlayer extends CraftHumanEntity implements Player { - this.getHandle().onUpdateAbilities(); - } - -+ // Paper start - flying fall damage -+ @Override -+ public void setFlyingFallDamage(@NotNull net.kyori.adventure.util.TriState flyingFallDamage) { -+ getHandle().flyingFallDamage = flyingFallDamage; -+ } -+ -+ @NotNull -+ @Override -+ public net.kyori.adventure.util.TriState hasFlyingFallDamage() { -+ return getHandle().flyingFallDamage; -+ } -+ // Paper end - flying fall damage -+ - @Override - public int getNoDamageTicks() { - if (this.getHandle().spawnInvulnerableTime > 0) { diff --git a/patches/server/0786-Add-Entity-Body-Yaw-API.patch b/patches/server/0786-Add-Entity-Body-Yaw-API.patch new file mode 100644 index 000000000000..2a926f1388a2 --- /dev/null +++ b/patches/server/0786-Add-Entity-Body-Yaw-API.patch @@ -0,0 +1,65 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: TheTuso +Date: Thu, 2 Feb 2023 16:40:41 +0100 +Subject: [PATCH] Add Entity Body Yaw API + + +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java +index 8a715159917950091e8efd48eaca6a54488fb896..4209434ff066670000dadb0c59fea297f03300f4 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java +@@ -1184,6 +1184,33 @@ public abstract class CraftEntity implements org.bukkit.entity.Entity { + } + // Paper end - entity powdered snow API + ++ // Paper start - entity body yaw API ++ @Override ++ public double getX() { ++ return this.entity.getX(); ++ } ++ ++ @Override ++ public double getY() { ++ return this.entity.getY(); ++ } ++ ++ @Override ++ public double getZ() { ++ return this.entity.getZ(); ++ } ++ ++ @Override ++ public float getPitch() { ++ return this.entity.getXRot(); ++ } ++ ++ @Override ++ public float getYaw() { ++ return this.entity.getBukkitYaw(); ++ } ++ // Paper end - entity body yaw API ++ + // Paper start - missing entity api + @Override + public boolean isInvisible() { // Paper - moved up from LivingEntity +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftLivingEntity.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftLivingEntity.java +index 2fd4a3068d86a37cc18c9203448823c53d590ffb..203e06fdff824ba67dce0e026f7ea5b746d7f258 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftLivingEntity.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftLivingEntity.java +@@ -1211,4 +1211,16 @@ public class CraftLivingEntity extends CraftEntity implements LivingEntity { + this.getHandle().frictionState = state; + } + // Paper end - friction API ++ ++ // Paper start - body yaw API ++ @Override ++ public float getBodyYaw() { ++ return this.getHandle().getVisualRotationYInDegrees(); ++ } ++ ++ @Override ++ public void setBodyYaw(final float bodyYaw) { ++ this.getHandle().setYBodyRot(bodyYaw); ++ } ++ // Paper end - body yaw API + } diff --git a/patches/server/0786-Expose-pre-collision-moving-velocity-to-VehicleBlock.patch b/patches/server/0786-Expose-pre-collision-moving-velocity-to-VehicleBlock.patch deleted file mode 100644 index 80118a649946..000000000000 --- a/patches/server/0786-Expose-pre-collision-moving-velocity-to-VehicleBlock.patch +++ /dev/null @@ -1,28 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: SoSeDiK -Date: Tue, 11 Oct 2022 23:30:32 +0300 -Subject: [PATCH] Expose pre-collision moving velocity to - VehicleBlockCollisionEvent - - -diff --git a/src/main/java/net/minecraft/world/entity/Entity.java b/src/main/java/net/minecraft/world/entity/Entity.java -index 303f3656be5e9049cd195030c457df9a7c718b66..34e175b28f7c9120b58fc8e2b65ca978c7f301b5 100644 ---- a/src/main/java/net/minecraft/world/entity/Entity.java -+++ b/src/main/java/net/minecraft/world/entity/Entity.java -@@ -942,6 +942,7 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess - } - - public void move(MoverType movementType, Vec3 movement) { -+ final Vec3 originalMovement = movement; // Paper - Expose pre-collision velocity - if (this.noPhysics) { - this.setPos(this.getX() + movement.x, this.getY() + movement.y, this.getZ() + movement.z); - } else { -@@ -1026,7 +1027,7 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess - } - - if (!bl.getType().isAir()) { -- VehicleBlockCollisionEvent event = new VehicleBlockCollisionEvent(vehicle, bl); -+ VehicleBlockCollisionEvent event = new VehicleBlockCollisionEvent(vehicle, bl, org.bukkit.craftbukkit.util.CraftVector.toBukkit(originalMovement)); // Paper - Expose pre-collision velocity - this.level.getCraftServer().getPluginManager().callEvent(event); - } - } diff --git a/patches/server/0787-Fix-MC-157464-Prevent-sleeping-villagers-moving-towa.patch b/patches/server/0787-Fix-MC-157464-Prevent-sleeping-villagers-moving-towa.patch new file mode 100644 index 000000000000..9c1f7e2d4757 --- /dev/null +++ b/patches/server/0787-Fix-MC-157464-Prevent-sleeping-villagers-moving-towa.patch @@ -0,0 +1,24 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Warrior <50800980+Warriorrrr@users.noreply.github.com> +Date: Mon, 27 Feb 2023 19:16:07 +0100 +Subject: [PATCH] Fix MC-157464 Prevent sleeping villagers moving towards food + +Fixes sleeping villagers moving to nearby food by adding an !isSleeping predicate + +Relevant links: +https://bugs.mojang.com/browse/MC-157464 +https://github.com/PaperMC/Paper/issues/8569 + +diff --git a/src/main/java/net/minecraft/world/entity/ai/behavior/VillagerGoalPackages.java b/src/main/java/net/minecraft/world/entity/ai/behavior/VillagerGoalPackages.java +index 1c5cec4e3dbf2daf9e06d8d7dfb30b3f7744a1f5..bb65d46967cb04f611b3c9c97d5732cfb21ede9b 100644 +--- a/src/main/java/net/minecraft/world/entity/ai/behavior/VillagerGoalPackages.java ++++ b/src/main/java/net/minecraft/world/entity/ai/behavior/VillagerGoalPackages.java +@@ -38,7 +38,7 @@ public class VillagerGoalPackages { + Pair.of(1, new MoveToTargetSink()), + Pair.of(2, PoiCompetitorScan.create()), + Pair.of(3, new LookAndFollowTradingPlayerSink(speed)), +- Pair.of(5, GoToWantedItem.create(speed, false, 4)), ++ Pair.of(5, GoToWantedItem.create(villager -> !villager.isSleeping(), speed, false, 4)), // Paper - Fix MC-157464 + Pair.of( + 6, AcquirePoi.create(profession.acquirableJobSite(), MemoryModuleType.JOB_SITE, MemoryModuleType.POTENTIAL_JOB_SITE, true, Optional.empty()) + ), diff --git a/patches/server/0787-config-for-disabling-entity-tag-tags.patch b/patches/server/0787-config-for-disabling-entity-tag-tags.patch deleted file mode 100644 index aabc1e1a31b1..000000000000 --- a/patches/server/0787-config-for-disabling-entity-tag-tags.patch +++ /dev/null @@ -1,27 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Jake Potrebic -Date: Wed, 15 Sep 2021 14:52:42 -0700 -Subject: [PATCH] config for disabling entity tag tags - - -diff --git a/src/main/java/net/minecraft/world/entity/EntityType.java b/src/main/java/net/minecraft/world/entity/EntityType.java -index c0062c8f83641ff30e79a309c0bb9930ba4b422a..5fb3279342506611882b5780cfbee0371919c93c 100644 ---- a/src/main/java/net/minecraft/world/entity/EntityType.java -+++ b/src/main/java/net/minecraft/world/entity/EntityType.java -@@ -502,6 +502,16 @@ public class EntityType implements FeatureElement, EntityTypeT - - if (minecraftserver != null && entity != null) { - if (world.isClientSide || !entity.onlyOpCanSetNbt() || player != null && minecraftserver.getPlayerList().isOp(player.getGameProfile())) { -+ // Paper start - filter out protected tags -+ if (player == null || !player.getBukkitEntity().hasPermission("minecraft.nbt.place")) { -+ nbt = nbt.update((compound) -> { -+ for (net.minecraft.commands.arguments.NbtPathArgument.NbtPath tag : world.paperConfig().entities.spawning.filteredEntityTagNbtPaths) { -+ tag.remove(compound); -+ } -+ }); -+ } -+ // Paper end - filter out protected tags -+ - nbt.loadInto(entity); - } - } diff --git a/patches/server/0788-Add-EntityFertilizeEggEvent.patch b/patches/server/0788-Add-EntityFertilizeEggEvent.patch new file mode 100644 index 000000000000..ea7e33d8b0e8 --- /dev/null +++ b/patches/server/0788-Add-EntityFertilizeEggEvent.patch @@ -0,0 +1,103 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Lulu13022002 <41980282+Lulu13022002@users.noreply.github.com> +Date: Fri, 24 Jun 2022 12:39:34 +0200 +Subject: [PATCH] Add EntityFertilizeEggEvent + + +diff --git a/src/main/java/net/minecraft/world/entity/animal/Turtle.java b/src/main/java/net/minecraft/world/entity/animal/Turtle.java +index ed7f5eb9b3b700c2f817d61ee0bf8a6952731510..9a9ecc3e2c176c6d9700c4c585250b9780b7629b 100644 +--- a/src/main/java/net/minecraft/world/entity/animal/Turtle.java ++++ b/src/main/java/net/minecraft/world/entity/animal/Turtle.java +@@ -448,6 +448,10 @@ public class Turtle extends Animal { + if (entityplayer == null && this.partner.getLoveCause() != null) { + entityplayer = this.partner.getLoveCause(); + } ++ // Paper start - Add EntityFertilizeEggEvent event ++ io.papermc.paper.event.entity.EntityFertilizeEggEvent event = org.bukkit.craftbukkit.event.CraftEventFactory.callEntityFertilizeEggEvent(this.animal, this.partner); ++ if (event.isCancelled()) return; ++ // Paper end - Add EntityFertilizeEggEvent event + + if (entityplayer != null) { + entityplayer.awardStat(Stats.ANIMALS_BRED); +@@ -462,7 +466,7 @@ public class Turtle extends Animal { + RandomSource randomsource = this.animal.getRandom(); + + if (getServerLevel((Level) this.level).getGameRules().getBoolean(GameRules.RULE_DOMOBLOOT)) { +- this.level.addFreshEntity(new ExperienceOrb(this.level, this.animal.getX(), this.animal.getY(), this.animal.getZ(), randomsource.nextInt(7) + 1, org.bukkit.entity.ExperienceOrb.SpawnReason.BREED, entityplayer)); // Paper; ++ if (event.getExperience() > 0) this.level.addFreshEntity(new ExperienceOrb(this.level, this.animal.getX(), this.animal.getY(), this.animal.getZ(), event.getExperience(), org.bukkit.entity.ExperienceOrb.SpawnReason.BREED, entityplayer)); // Paper - Add EntityFertilizeEggEvent event + } + + } +diff --git a/src/main/java/net/minecraft/world/entity/animal/frog/Frog.java b/src/main/java/net/minecraft/world/entity/animal/frog/Frog.java +index 100be2ed533450eda32d9c4eb9eb1067846d1516..36846ba6b6c7494c745ebd8b221479a9d02ff318 100644 +--- a/src/main/java/net/minecraft/world/entity/animal/frog/Frog.java ++++ b/src/main/java/net/minecraft/world/entity/animal/frog/Frog.java +@@ -270,7 +270,12 @@ public class Frog extends Animal implements VariantHolder> { + + @Override + public void spawnChildFromBreeding(ServerLevel world, Animal other) { +- this.finalizeSpawnChildFromBreeding(world, other, null); ++ // Paper start - Add EntityFertilizeEggEvent event ++ final io.papermc.paper.event.entity.EntityFertilizeEggEvent result = org.bukkit.craftbukkit.event.CraftEventFactory.callEntityFertilizeEggEvent(this, other); ++ if (result.isCancelled()) return; ++ ++ this.finalizeSpawnChildFromBreeding(world, other, null, result.getExperience()); // Paper - use craftbukkit call that takes experience amount ++ // Paper end - Add EntityFertilizeEggEvent event + this.getBrain().setMemory(MemoryModuleType.IS_PREGNANT, Unit.INSTANCE); + } + +diff --git a/src/main/java/net/minecraft/world/entity/animal/sniffer/Sniffer.java b/src/main/java/net/minecraft/world/entity/animal/sniffer/Sniffer.java +index 014e1ea1603bc7a7b42ae7ff7d12e5a41f331d2f..af2f6e690fc51d319b77d081466c2dc7a1d8fe19 100644 +--- a/src/main/java/net/minecraft/world/entity/animal/sniffer/Sniffer.java ++++ b/src/main/java/net/minecraft/world/entity/animal/sniffer/Sniffer.java +@@ -340,11 +340,16 @@ public class Sniffer extends Animal { + + @Override + public void spawnChildFromBreeding(ServerLevel world, Animal other) { ++ // Paper start - Add EntityFertilizeEggEvent event ++ final io.papermc.paper.event.entity.EntityFertilizeEggEvent result = org.bukkit.craftbukkit.event.CraftEventFactory.callEntityFertilizeEggEvent(this, other); ++ if (result.isCancelled()) return; ++ // Paper end - Add EntityFertilizeEggEvent event ++ + ItemStack itemstack = new ItemStack(Items.SNIFFER_EGG); + ItemEntity entityitem = new ItemEntity(world, this.position().x(), this.position().y(), this.position().z(), itemstack); + + entityitem.setDefaultPickUpDelay(); +- this.finalizeSpawnChildFromBreeding(world, other, (AgeableMob) null); ++ this.finalizeSpawnChildFromBreeding(world, other, (AgeableMob) null, result.getExperience()); // Paper - Add EntityFertilizeEggEvent event + if (this.spawnAtLocation(world, entityitem) != null) { // Paper - Call EntityDropItemEvent + this.playSound(SoundEvents.SNIFFER_EGG_PLOP, 1.0F, (this.random.nextFloat() - this.random.nextFloat()) * 0.2F + 0.5F); + } // Paper - Call EntityDropItemEvent +diff --git a/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java b/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java +index ea0241de1a7ef64059cddcb43c41f56b91901897..d63b9c666cf6eb1de114c5c89867b8d233267c9e 100644 +--- a/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java ++++ b/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java +@@ -2195,4 +2195,28 @@ public class CraftEventFactory { + return event.callEvent(); + } + // Paper end ++ ++ // Paper start - add EntityFertilizeEggEvent ++ /** ++ * Calls the {@link io.papermc.paper.event.entity.EntityFertilizeEggEvent}. ++ * If the event is cancelled, this method also resets the love on both the {@code breeding} and {@code other} entity. ++ * ++ * @param breeding the entity on which #spawnChildFromBreeding was called. ++ * @param other the partner of the entity. ++ * @return the event after it was called. The instance may be used to retrieve the experience of the event. ++ */ ++ public static io.papermc.paper.event.entity.EntityFertilizeEggEvent callEntityFertilizeEggEvent(Animal breeding, Animal other) { ++ ServerPlayer serverPlayer = breeding.getLoveCause(); ++ if (serverPlayer == null) serverPlayer = other.getLoveCause(); ++ final int experience = breeding.getRandom().nextInt(7) + 1; // From Animal#spawnChildFromBreeding(ServerLevel, Animal) ++ ++ final io.papermc.paper.event.entity.EntityFertilizeEggEvent event = new io.papermc.paper.event.entity.EntityFertilizeEggEvent((LivingEntity) breeding.getBukkitEntity(), (LivingEntity) other.getBukkitEntity(), serverPlayer == null ? null : serverPlayer.getBukkitEntity(), breeding.breedItem == null ? null : CraftItemStack.asCraftMirror(breeding.breedItem).clone(), experience); ++ if (!event.callEvent()) { ++ breeding.resetLove(); ++ other.resetLove(); // stop the pathfinding to avoid infinite loop ++ } ++ ++ return event; ++ } ++ // Paper end - add EntityFertilizeEggEvent + } diff --git a/patches/server/0788-Use-single-player-info-update-packet-on-join.patch b/patches/server/0788-Use-single-player-info-update-packet-on-join.patch deleted file mode 100644 index bbf87853109c..000000000000 --- a/patches/server/0788-Use-single-player-info-update-packet-on-join.patch +++ /dev/null @@ -1,51 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Jake Potrebic -Date: Sun, 8 Jan 2023 17:38:28 -0800 -Subject: [PATCH] Use single player info update packet on join - - -diff --git a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java -index a2a7d499a68841ecd76ba029298094993a82bf39..a6f58438badb0bdeab67706f4a8e519b202623fe 100644 ---- a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java -+++ b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java -@@ -3436,7 +3436,7 @@ public class ServerGamePacketListenerImpl extends ServerCommonPacketListenerImpl - this.signedMessageDecoder = session.createMessageDecoder(this.player.getUUID()); - this.chatMessageChain.append(() -> { - this.player.setChatSession(session); -- this.server.getPlayerList().broadcastAll(new ClientboundPlayerInfoUpdatePacket(EnumSet.of(ClientboundPlayerInfoUpdatePacket.Action.INITIALIZE_CHAT), List.of(this.player))); -+ this.server.getPlayerList().broadcastAll(new ClientboundPlayerInfoUpdatePacket(EnumSet.of(ClientboundPlayerInfoUpdatePacket.Action.INITIALIZE_CHAT), List.of(this.player)), this.player); // Paper - Use single player info update packet on join - }); - } - -diff --git a/src/main/java/net/minecraft/server/players/PlayerList.java b/src/main/java/net/minecraft/server/players/PlayerList.java -index 38b9ab369e25e4b718418375f76e782283b48411..bd67245393f512264db774e0b855db0ce925a3f4 100644 ---- a/src/main/java/net/minecraft/server/players/PlayerList.java -+++ b/src/main/java/net/minecraft/server/players/PlayerList.java -@@ -361,6 +361,7 @@ public abstract class PlayerList { - // CraftBukkit start - sendAll above replaced with this loop - ClientboundPlayerInfoUpdatePacket packet = ClientboundPlayerInfoUpdatePacket.createPlayerInitializing(List.of(player)); - -+ final List onlinePlayers = Lists.newArrayListWithExpectedSize(this.players.size() - 1); // Paper - Use single player info update packet on join - for (int i = 0; i < this.players.size(); ++i) { - ServerPlayer entityplayer1 = (ServerPlayer) this.players.get(i); - -@@ -368,12 +369,17 @@ public abstract class PlayerList { - entityplayer1.connection.send(packet); - } - -- if (!bukkitPlayer.canSee(entityplayer1.getBukkitEntity())) { -+ if (entityplayer1 == player || !bukkitPlayer.canSee(entityplayer1.getBukkitEntity())) { // Paper - Use single player info update packet on join; Don't include joining player - continue; - } - -- player.connection.send(ClientboundPlayerInfoUpdatePacket.createPlayerInitializing(List.of(entityplayer1))); -+ onlinePlayers.add(entityplayer1); // Paper - Use single player info update packet on join - } -+ // Paper start - Use single player info update packet on join -+ if (!onlinePlayers.isEmpty()) { -+ player.connection.send(ClientboundPlayerInfoUpdatePacket.createPlayerInitializing(onlinePlayers)); -+ } -+ // Paper end - Use single player info update packet on join - player.sentListPacket = true; - player.supressTrackerForLogin = false; // Paper - Fire PlayerJoinEvent when Player is actually ready - ((ServerLevel)player.level()).getChunkSource().chunkMap.addEntity(player); // Paper - Fire PlayerJoinEvent when Player is actually ready; track entity now diff --git a/patches/server/0789-Correctly-shrink-items-during-EntityResurrectEvent.patch b/patches/server/0789-Correctly-shrink-items-during-EntityResurrectEvent.patch deleted file mode 100644 index e7b31c3642a8..000000000000 --- a/patches/server/0789-Correctly-shrink-items-during-EntityResurrectEvent.patch +++ /dev/null @@ -1,36 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Bjarne Koll -Date: Tue, 10 Jan 2023 21:06:42 +0100 -Subject: [PATCH] Correctly shrink items during EntityResurrectEvent - -The EntityResurrectEvent logic is supposed to locate a totem of undying -in any of the interaction slots of the player inventory and then, if the -called EntityResurrectEvent is not cancelled, shrink that item by 1, -usually reducing it to zero. - -For this, the logic iterates over the items in the interaction slots and -breaks out the loop if a totem of undying was found. -However, even if no totem of undying was found, the iteration item stack -variable remains as a refernce to the last interaction slot probed. - -Plugins uncancelling a EntityResurrectEvent, which is published -pre-cancelled to listeners if no totem of undying could be found, -would hence cause the server logic to shrink completely unrelated items -found in, at the writing of this patch, the players off hand slot. - -This patch corrects this behaviour by only shrinking the item if a totem -of undying was found and the event was called uncancelled. - -diff --git a/src/main/java/net/minecraft/world/entity/LivingEntity.java b/src/main/java/net/minecraft/world/entity/LivingEntity.java -index 2bb61084b628582ded44926d7697ee26d0bb1e8e..9e58a85a7de53b17fa149ae0b4951baa351d99db 100644 ---- a/src/main/java/net/minecraft/world/entity/LivingEntity.java -+++ b/src/main/java/net/minecraft/world/entity/LivingEntity.java -@@ -1632,7 +1632,7 @@ public abstract class LivingEntity extends Entity implements Attackable { - this.level().getCraftServer().getPluginManager().callEvent(event); - - if (!event.isCancelled()) { -- if (!itemstack1.isEmpty()) { -+ if (!itemstack1.isEmpty() && itemstack != null) { // Paper - only reduce item if actual totem was found - itemstack1.shrink(1); - } - if (itemstack != null && this instanceof ServerPlayer) { diff --git a/patches/server/0789-Fix-HumanEntity-drop-not-updating-the-client-inv.patch b/patches/server/0789-Fix-HumanEntity-drop-not-updating-the-client-inv.patch new file mode 100644 index 000000000000..21d4d0ad650a --- /dev/null +++ b/patches/server/0789-Fix-HumanEntity-drop-not-updating-the-client-inv.patch @@ -0,0 +1,30 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Jake Potrebic +Date: Sun, 10 Oct 2021 18:18:01 -0700 +Subject: [PATCH] Fix HumanEntity#drop not updating the client inv + +== AT == +public net.minecraft.server.level.ServerPlayer containerSynchronizer + +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftHumanEntity.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftHumanEntity.java +index 04fe27a84eb240f8e9bb0ed5b21fd60cfed619ad..7dddf4dd090fcd9e86b147d7e4ddeaa99800713e 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftHumanEntity.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftHumanEntity.java +@@ -782,8 +782,15 @@ public class CraftHumanEntity extends CraftLivingEntity implements HumanEntity { + // Paper end + @Override + public boolean dropItem(boolean dropAll) { +- if (!(this.getHandle() instanceof ServerPlayer)) return false; +- return ((ServerPlayer) this.getHandle()).drop(dropAll); ++ // Paper start - Fix HumanEntity#drop not updating the client inv ++ if (!(this.getHandle() instanceof ServerPlayer player)) return false; ++ boolean success = player.drop(dropAll); ++ if (!success) return false; ++ final net.minecraft.world.entity.player.Inventory inv = player.getInventory(); ++ final java.util.OptionalInt optionalSlot = player.containerMenu.findSlot(inv, inv.selected); ++ optionalSlot.ifPresent(slot -> player.containerSynchronizer.sendSlotChange(player.containerMenu, slot, inv.getSelected())); ++ return true; ++ // Paper end - Fix HumanEntity#drop not updating the client inv + } + + @Override diff --git a/patches/server/0790-Add-CompostItemEvent-and-EntityCompostItemEvent.patch b/patches/server/0790-Add-CompostItemEvent-and-EntityCompostItemEvent.patch new file mode 100644 index 000000000000..2e105236eec0 --- /dev/null +++ b/patches/server/0790-Add-CompostItemEvent-and-EntityCompostItemEvent.patch @@ -0,0 +1,45 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Noah van der Aa +Date: Sun, 8 Aug 2021 19:56:02 +0200 +Subject: [PATCH] Add CompostItemEvent and EntityCompostItemEvent + + +diff --git a/src/main/java/net/minecraft/world/level/block/ComposterBlock.java b/src/main/java/net/minecraft/world/level/block/ComposterBlock.java +index 05b2eab26e2dc8e143e9fff2dcec40cfe927a197..db837b250fc35af5b528bf973b3b07f63e79bc46 100644 +--- a/src/main/java/net/minecraft/world/level/block/ComposterBlock.java ++++ b/src/main/java/net/minecraft/world/level/block/ComposterBlock.java +@@ -340,7 +340,21 @@ public class ComposterBlock extends Block implements WorldlyContainerHolder { + int i = (Integer) iblockdata.getValue(ComposterBlock.LEVEL); + float f = ComposterBlock.COMPOSTABLES.getFloat(itemstack.getItem()); + +- if ((i != 0 || f <= 0.0F) && rand >= (double) f) { ++ // Paper start - Add CompostItemEvent and EntityCompostItemEvent ++ boolean willRaiseLevel = !((i != 0 || f <= 0.0F) && rand >= (double) f); ++ final io.papermc.paper.event.block.CompostItemEvent event; ++ if (entity == null) { ++ event = new io.papermc.paper.event.block.CompostItemEvent(org.bukkit.craftbukkit.block.CraftBlock.at(generatoraccess, blockposition), itemstack.getBukkitStack(), willRaiseLevel); ++ } else { ++ event = new io.papermc.paper.event.entity.EntityCompostItemEvent(entity.getBukkitEntity(), org.bukkit.craftbukkit.block.CraftBlock.at(generatoraccess, blockposition), itemstack.getBukkitStack(), willRaiseLevel); ++ } ++ if (!event.callEvent()) { // check for cancellation of entity event (non entity event can't be cancelled cause of hoppers) ++ return null; ++ } ++ willRaiseLevel = event.willRaiseLevel(); ++ ++ if (!willRaiseLevel) { ++ // Paper end - Add CompostItemEvent and EntityCompostItemEvent + return iblockdata; + } else { + int j = i + 1; +@@ -489,6 +503,11 @@ public class ComposterBlock extends Block implements WorldlyContainerHolder { + if (!itemstack.isEmpty()) { + this.changed = true; + BlockState iblockdata = ComposterBlock.addItem((Entity) null, this.state, this.level, this.pos, itemstack); ++ // Paper start - Add CompostItemEvent and EntityCompostItemEvent ++ if (iblockdata == null) { ++ return; ++ } ++ // Paper end - Add CompostItemEvent and EntityCompostItemEvent + + this.level.levelEvent(1500, this.pos, iblockdata != this.state ? 1 : 0); + this.removeItemNoUpdate(0); diff --git a/patches/server/0790-Win-Screen-API.patch b/patches/server/0790-Win-Screen-API.patch deleted file mode 100644 index 567b2236d934..000000000000 --- a/patches/server/0790-Win-Screen-API.patch +++ /dev/null @@ -1,38 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Lama06 -Date: Sat, 21 Jan 2023 13:53:23 +0100 -Subject: [PATCH] Win Screen API - -== AT == -public net.minecraft.server.level.ServerPlayer seenCredits - -diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java -index fdf6c3f1f8f79dd20e1363ef472f97fe98c9799e..06a3ccb90f23fc357e5cbc6e9173baab4b218955 100644 ---- a/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java -+++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java -@@ -1305,6 +1305,25 @@ public class CraftPlayer extends CraftHumanEntity implements Player { - this.getHandle().connection.send(packet); - } - -+ // Paper start -+ @Override -+ public void showWinScreen() { -+ if (getHandle().connection == null) return; -+ var packet = new ClientboundGameEventPacket(ClientboundGameEventPacket.WIN_GAME, 1); -+ getHandle().connection.send(packet); -+ } -+ -+ @Override -+ public boolean hasSeenWinScreen() { -+ return getHandle().seenCredits; -+ } -+ -+ @Override -+ public void setHasSeenWinScreen(boolean hasSeenWinScreen) { -+ getHandle().seenCredits = hasSeenWinScreen; -+ } -+ // Paper end -+ - @Override - public void setRotation(float yaw, float pitch) { - // Paper start - Teleport API diff --git a/patches/server/0791-Correctly-handle-ArmorStand-invisibility.patch b/patches/server/0791-Correctly-handle-ArmorStand-invisibility.patch new file mode 100644 index 000000000000..4c4c5048be7c --- /dev/null +++ b/patches/server/0791-Correctly-handle-ArmorStand-invisibility.patch @@ -0,0 +1,25 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Jake Potrebic +Date: Sun, 5 Mar 2023 14:38:21 -0800 +Subject: [PATCH] Correctly handle ArmorStand invisibility + + +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftArmorStand.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftArmorStand.java +index c8713200d946b0fdd74b60d0c2c136c8226389e0..184fe8257e5ffb0ef090ffa2833786a4db8b59ea 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftArmorStand.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftArmorStand.java +@@ -152,6 +152,14 @@ public class CraftArmorStand extends CraftLivingEntity implements ArmorStand { + this.getHandle().noPhysics = !gravity; + } + ++ // Paper start - Armor Stand has its own invisible field ++ @Override ++ public void setInvisible(final boolean invisible) { ++ this.getHandle().setInvisible(invisible); ++ super.setInvisible(invisible); ++ } ++ // Paper end ++ + @Override + public boolean isVisible() { + return !this.getHandle().isInvisible(); diff --git a/patches/server/0791-Remove-CraftItemStack-setAmount-null-assignment.patch b/patches/server/0791-Remove-CraftItemStack-setAmount-null-assignment.patch deleted file mode 100644 index 5b5c421ff26c..000000000000 --- a/patches/server/0791-Remove-CraftItemStack-setAmount-null-assignment.patch +++ /dev/null @@ -1,30 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Josh Roy -Date: Mon, 23 Jan 2023 19:19:01 -0500 -Subject: [PATCH] Remove CraftItemStack#setAmount null assignment - -This creates a problem with Paper's item serialization -api where deserialized items, which are internally -created as a CraftItemStack, will be completely lost if -#setAmount(0) is invoked (since the underlying handle -is set to null), while a regular Bukkit ItemStack -simply sets the amount field to zero, retaining the -item's data. - -Vanilla treats items with zero amounts the same as items -with less than zero amounts, so this code doesn't create -a problem with operations on the vanilla ItemStack. - -diff --git a/src/main/java/org/bukkit/craftbukkit/inventory/CraftItemStack.java b/src/main/java/org/bukkit/craftbukkit/inventory/CraftItemStack.java -index 08178a88ba7d0881a6c2843eef24a846cf07adb4..4d29c34e221b749b6972c7ed79ac1f86da999ed7 100644 ---- a/src/main/java/org/bukkit/craftbukkit/inventory/CraftItemStack.java -+++ b/src/main/java/org/bukkit/craftbukkit/inventory/CraftItemStack.java -@@ -186,7 +186,7 @@ public final class CraftItemStack extends ItemStack { - } - - this.handle.setCount(amount); -- if (amount == 0) { -+ if (false && amount == 0) { // Paper - remove CraftItemStack#setAmount null assignment - this.handle = null; - } - } diff --git a/patches/server/0792-Fix-advancement-triggers-for-entity-damage.patch b/patches/server/0792-Fix-advancement-triggers-for-entity-damage.patch new file mode 100644 index 000000000000..c7075265f1fd --- /dev/null +++ b/patches/server/0792-Fix-advancement-triggers-for-entity-damage.patch @@ -0,0 +1,46 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Jake Potrebic +Date: Thu, 16 Mar 2023 10:04:17 +0100 +Subject: [PATCH] Fix advancement triggers for entity damage + +Changes the Interaction entity's trigger to use the vanilla +generic damage source + +Fixes a couple places where the original damage and modified damage +were passed in the reverse order to the advancement triggers + +diff --git a/src/main/java/net/minecraft/world/entity/Interaction.java b/src/main/java/net/minecraft/world/entity/Interaction.java +index 821bb93e1b055ba38fafe3b7079d79aa062ebe8a..221d73676fe2fd240a47cf312c1179e049298cac 100644 +--- a/src/main/java/net/minecraft/world/entity/Interaction.java ++++ b/src/main/java/net/minecraft/world/entity/Interaction.java +@@ -159,7 +159,7 @@ public class Interaction extends Entity implements Attackable, Targeting { + // CraftBukkit end + this.attack = new Interaction.PlayerAction(entityhuman.getUUID(), this.level().getGameTime()); + if (entityhuman instanceof ServerPlayer entityplayer) { +- CriteriaTriggers.PLAYER_HURT_ENTITY.trigger(entityplayer, this, source, (float) event.getFinalDamage(), 1.0F, false); // CraftBukkit ++ CriteriaTriggers.PLAYER_HURT_ENTITY.trigger(entityplayer, this, entityhuman.damageSources().generic(), 1.0F, (float) event.getFinalDamage(), false); // CraftBukkit // Paper - use correct source and fix taken/dealt param order + } + + return !this.getResponse(); +diff --git a/src/main/java/net/minecraft/world/entity/LivingEntity.java b/src/main/java/net/minecraft/world/entity/LivingEntity.java +index 9ef5c617cb05f08758105772b9124f6c318b5c17..24245cfb160dc990b3661388c2f95b9383f98428 100644 +--- a/src/main/java/net/minecraft/world/entity/LivingEntity.java ++++ b/src/main/java/net/minecraft/world/entity/LivingEntity.java +@@ -2463,7 +2463,7 @@ public abstract class LivingEntity extends Entity implements Attackable { + // Duplicate triggers if blocking + if (event.getDamage(DamageModifier.BLOCKING) < 0) { + if (this instanceof ServerPlayer) { +- CriteriaTriggers.ENTITY_HURT_PLAYER.trigger((ServerPlayer) this, damagesource, f, originalDamage, true); ++ CriteriaTriggers.ENTITY_HURT_PLAYER.trigger((ServerPlayer) this, damagesource, originalDamage, f, true); // Paper - fix taken/dealt param order + f2 = (float) -event.getDamage(DamageModifier.BLOCKING); + if (f2 > 0.0F && f2 < 3.4028235E37F) { + ((ServerPlayer) this).awardStat(Stats.DAMAGE_BLOCKED_BY_SHIELD, Math.round(originalDamage * 10.0F)); +@@ -2471,7 +2471,7 @@ public abstract class LivingEntity extends Entity implements Attackable { + } + + if (damagesource.getEntity() instanceof ServerPlayer) { +- CriteriaTriggers.PLAYER_HURT_ENTITY.trigger((ServerPlayer) damagesource.getEntity(), this, damagesource, f, originalDamage, true); ++ CriteriaTriggers.PLAYER_HURT_ENTITY.trigger((ServerPlayer) damagesource.getEntity(), this, damagesource, originalDamage, f, true); // Paper - fix taken/dealt param order + } + + return true; diff --git a/patches/server/0792-Fix-force-opening-enchantment-tables.patch b/patches/server/0792-Fix-force-opening-enchantment-tables.patch deleted file mode 100644 index 89326fc719cd..000000000000 --- a/patches/server/0792-Fix-force-opening-enchantment-tables.patch +++ /dev/null @@ -1,30 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Jake Potrebic -Date: Thu, 26 Jan 2023 16:19:26 -0800 -Subject: [PATCH] Fix force-opening enchantment tables - - -diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftHumanEntity.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftHumanEntity.java -index 049db909fbd8610ebb688d948f5d03c97ab23495..1ff114f792ca12bc5408cdd9ab0ad23c3158b99c 100644 ---- a/src/main/java/org/bukkit/craftbukkit/entity/CraftHumanEntity.java -+++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftHumanEntity.java -@@ -411,7 +411,18 @@ public class CraftHumanEntity extends CraftLivingEntity implements HumanEntity { - - // If there isn't an enchant table we can force create one, won't be very useful though. - BlockPos pos = CraftLocation.toBlockPosition(location); -- this.getHandle().openMenu(Blocks.ENCHANTING_TABLE.defaultBlockState().getMenuProvider(this.getHandle().level(), pos)); -+ // Paper start -+ MenuProvider menuProvider = Blocks.ENCHANTING_TABLE.defaultBlockState().getMenuProvider(this.getHandle().level(), pos); -+ if (menuProvider == null) { -+ if (!force) { -+ return null; -+ } -+ menuProvider = new net.minecraft.world.SimpleMenuProvider((syncId, inventory, player) -> { -+ return new net.minecraft.world.inventory.EnchantmentMenu(syncId, inventory, net.minecraft.world.inventory.ContainerLevelAccess.create(this.getHandle().level(), pos)); -+ }, Component.translatable("container.enchant")); -+ } -+ this.getHandle().openMenu(menuProvider); -+ // Paper end - - if (force) { - this.getHandle().containerMenu.checkReachable = false; diff --git a/patches/server/0793-Add-Entity-Body-Yaw-API.patch b/patches/server/0793-Add-Entity-Body-Yaw-API.patch deleted file mode 100644 index c1be71053d18..000000000000 --- a/patches/server/0793-Add-Entity-Body-Yaw-API.patch +++ /dev/null @@ -1,65 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: TheTuso -Date: Thu, 2 Feb 2023 16:40:41 +0100 -Subject: [PATCH] Add Entity Body Yaw API - - -diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java -index ac513d3162a0794f226abc80bff21c799fe5802c..7c7501b4b21530d0641774f64e87d7d1ca71a33c 100644 ---- a/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java -+++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java -@@ -1183,6 +1183,33 @@ public abstract class CraftEntity implements org.bukkit.entity.Entity { - } - // Paper end - entity powdered snow API - -+ // Paper start - entity body yaw API -+ @Override -+ public double getX() { -+ return this.entity.getX(); -+ } -+ -+ @Override -+ public double getY() { -+ return this.entity.getY(); -+ } -+ -+ @Override -+ public double getZ() { -+ return this.entity.getZ(); -+ } -+ -+ @Override -+ public float getPitch() { -+ return this.entity.getXRot(); -+ } -+ -+ @Override -+ public float getYaw() { -+ return this.entity.getBukkitYaw(); -+ } -+ // Paper end - entity body yaw API -+ - // Paper start - missing entity api - @Override - public boolean isInvisible() { // Paper - moved up from LivingEntity -diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftLivingEntity.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftLivingEntity.java -index 516ea1ec9ae5069c3c0e4708f62164a91960b627..a50803a9b41cf3c0b081eb6b786f952dd0ed284f 100644 ---- a/src/main/java/org/bukkit/craftbukkit/entity/CraftLivingEntity.java -+++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftLivingEntity.java -@@ -1192,4 +1192,16 @@ public class CraftLivingEntity extends CraftEntity implements LivingEntity { - this.getHandle().frictionState = state; - } - // Paper end - friction API -+ -+ // Paper start - body yaw API -+ @Override -+ public float getBodyYaw() { -+ return this.getHandle().getVisualRotationYInDegrees(); -+ } -+ -+ @Override -+ public void setBodyYaw(final float bodyYaw) { -+ this.getHandle().setYBodyRot(bodyYaw); -+ } -+ // Paper end - body yaw API - } diff --git a/patches/server/0793-Fix-text-display-error-on-spawn.patch b/patches/server/0793-Fix-text-display-error-on-spawn.patch new file mode 100644 index 000000000000..d783ac8eee58 --- /dev/null +++ b/patches/server/0793-Fix-text-display-error-on-spawn.patch @@ -0,0 +1,19 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Nassim Jahnke +Date: Thu, 16 Mar 2023 16:27:50 +0100 +Subject: [PATCH] Fix text display error on spawn + + +diff --git a/src/main/java/net/minecraft/world/entity/Display.java b/src/main/java/net/minecraft/world/entity/Display.java +index d7d940e6b397e52cb7d78bc6f5fc26e8096f9228..e6cbf4506c75046a89fad778e138b448fb4a29a9 100644 +--- a/src/main/java/net/minecraft/world/entity/Display.java ++++ b/src/main/java/net/minecraft/world/entity/Display.java +@@ -903,7 +903,7 @@ public abstract class Display extends Entity { + b = loadFlag(b, nbt, "default_background", (byte)4); + Optional optional = Display.TextDisplay.Align.CODEC + .decode(NbtOps.INSTANCE, nbt.get("alignment")) +- .resultOrPartial(Util.prefix("Display entity", Display.LOGGER::error)) ++ .result() // Paper - Hide text display error on spawn + .map(Pair::getFirst); + if (optional.isPresent()) { + b = switch ((Display.TextDisplay.Align)optional.get()) { diff --git a/patches/server/0794-Fix-MC-157464-Prevent-sleeping-villagers-moving-towa.patch b/patches/server/0794-Fix-MC-157464-Prevent-sleeping-villagers-moving-towa.patch deleted file mode 100644 index 962917dab8fd..000000000000 --- a/patches/server/0794-Fix-MC-157464-Prevent-sleeping-villagers-moving-towa.patch +++ /dev/null @@ -1,24 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Warrior <50800980+Warriorrrr@users.noreply.github.com> -Date: Mon, 27 Feb 2023 19:16:07 +0100 -Subject: [PATCH] Fix MC-157464 Prevent sleeping villagers moving towards food - -Fixes sleeping villagers moving to nearby food by adding an !isSleeping predicate - -Relevant links: -https://bugs.mojang.com/browse/MC-157464 -https://github.com/PaperMC/Paper/issues/8569 - -diff --git a/src/main/java/net/minecraft/world/entity/ai/behavior/VillagerGoalPackages.java b/src/main/java/net/minecraft/world/entity/ai/behavior/VillagerGoalPackages.java -index 5a18bf854792339fcd54e32f8053d014f3d195a5..f000a6c1e61198e6dd06ae5f084d12fdf309f50a 100644 ---- a/src/main/java/net/minecraft/world/entity/ai/behavior/VillagerGoalPackages.java -+++ b/src/main/java/net/minecraft/world/entity/ai/behavior/VillagerGoalPackages.java -@@ -38,7 +38,7 @@ public class VillagerGoalPackages { - Pair.of(1, new MoveToTargetSink()), - Pair.of(2, PoiCompetitorScan.create()), - Pair.of(3, new LookAndFollowTradingPlayerSink(speed)), -- Pair.of(5, GoToWantedItem.create(speed, false, 4)), -+ Pair.of(5, GoToWantedItem.create(villager -> !villager.isSleeping(), speed, false, 4)), // Paper - Fix MC-157464 - Pair.of( - 6, AcquirePoi.create(profession.acquirableJobSite(), MemoryModuleType.JOB_SITE, MemoryModuleType.POTENTIAL_JOB_SITE, true, Optional.empty()) - ), diff --git a/patches/server/0794-Fix-inventories-returning-null-Locations.patch b/patches/server/0794-Fix-inventories-returning-null-Locations.patch new file mode 100644 index 000000000000..1a65689fadbb --- /dev/null +++ b/patches/server/0794-Fix-inventories-returning-null-Locations.patch @@ -0,0 +1,61 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Jake Potrebic +Date: Wed, 15 Mar 2023 18:29:45 -0700 +Subject: [PATCH] Fix inventories returning null Locations + +Wandering Trader, AbstractHorse, Beacon and Composter inventories returned null locations +when a block or entity location is readily available + +Co-authored-by: Lukas Planz + +diff --git a/src/main/java/net/minecraft/world/SimpleContainer.java b/src/main/java/net/minecraft/world/SimpleContainer.java +index 13a32ef8f86b678dcce83c41c78b47d9aab9f16b..b0963c534afe5be164701cb283f1849e32ae5a86 100644 +--- a/src/main/java/net/minecraft/world/SimpleContainer.java ++++ b/src/main/java/net/minecraft/world/SimpleContainer.java +@@ -63,6 +63,16 @@ public class SimpleContainer implements Container, StackedContentsCompatible { + + @Override + public Location getLocation() { ++ // Paper start - Fix inventories returning null Locations ++ // When the block inventory does not have a tile state that implements getLocation, e. g. composters ++ if (this.bukkitOwner instanceof org.bukkit.inventory.BlockInventoryHolder blockInventoryHolder) { ++ return blockInventoryHolder.getBlock().getLocation(); ++ } ++ // When the bukkit owner is a bukkit entity, but does not implement Container itself, e. g. horses ++ if (this.bukkitOwner instanceof org.bukkit.entity.Entity entity) { ++ return entity.getLocation(); ++ } ++ // Paper end - Fix inventories returning null Locations + return null; + } + +diff --git a/src/main/java/net/minecraft/world/inventory/BeaconMenu.java b/src/main/java/net/minecraft/world/inventory/BeaconMenu.java +index 9db647cfbd3f9c884465cf3d3a1b911d46a3da58..c0dfa04511110be366ee5b0bd75efc51afcce2e4 100644 +--- a/src/main/java/net/minecraft/world/inventory/BeaconMenu.java ++++ b/src/main/java/net/minecraft/world/inventory/BeaconMenu.java +@@ -52,6 +52,12 @@ public class BeaconMenu extends AbstractContainerMenu { + public int getMaxStackSize() { + return 1; + } ++ // Paper start - Fix inventories returning null Locations ++ @Override ++ public org.bukkit.Location getLocation() { ++ return context.getLocation(); ++ } ++ // Paper end - Fix inventories returning null Locations + }; + checkContainerDataCount(propertyDelegate, 3); + this.beaconData = propertyDelegate; +diff --git a/src/main/java/net/minecraft/world/inventory/MerchantContainer.java b/src/main/java/net/minecraft/world/inventory/MerchantContainer.java +index 7cc96b62f6bacdb44a37d74db214bd0e11c4d503..9140fab07aab32065f7a3b5d13dd17d61dc6d646 100644 +--- a/src/main/java/net/minecraft/world/inventory/MerchantContainer.java ++++ b/src/main/java/net/minecraft/world/inventory/MerchantContainer.java +@@ -65,7 +65,7 @@ public class MerchantContainer implements Container { + + @Override + public Location getLocation() { +- return (this.merchant instanceof Villager) ? ((Villager) this.merchant).getBukkitEntity().getLocation() : null; ++ return (this.merchant instanceof AbstractVillager) ? ((AbstractVillager) this.merchant).getBukkitEntity().getLocation() : null; // Paper - Fix inventories returning null Locations + } + // CraftBukkit end + diff --git a/patches/server/0795-Add-EntityFertilizeEggEvent.patch b/patches/server/0795-Add-EntityFertilizeEggEvent.patch deleted file mode 100644 index 07253994cb05..000000000000 --- a/patches/server/0795-Add-EntityFertilizeEggEvent.patch +++ /dev/null @@ -1,103 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Lulu13022002 <41980282+Lulu13022002@users.noreply.github.com> -Date: Fri, 24 Jun 2022 12:39:34 +0200 -Subject: [PATCH] Add EntityFertilizeEggEvent - - -diff --git a/src/main/java/net/minecraft/world/entity/animal/Turtle.java b/src/main/java/net/minecraft/world/entity/animal/Turtle.java -index 34e6bf677a9f4548f3febe6d57e6af9602530271..2e5ef2a680e294b49f29e8d7ba8bd0ed023c393c 100644 ---- a/src/main/java/net/minecraft/world/entity/animal/Turtle.java -+++ b/src/main/java/net/minecraft/world/entity/animal/Turtle.java -@@ -441,6 +441,10 @@ public class Turtle extends Animal { - if (entityplayer == null && this.partner.getLoveCause() != null) { - entityplayer = this.partner.getLoveCause(); - } -+ // Paper start - Add EntityFertilizeEggEvent event -+ io.papermc.paper.event.entity.EntityFertilizeEggEvent event = org.bukkit.craftbukkit.event.CraftEventFactory.callEntityFertilizeEggEvent(this.animal, this.partner); -+ if (event.isCancelled()) return; -+ // Paper end - Add EntityFertilizeEggEvent event - - if (entityplayer != null) { - entityplayer.awardStat(Stats.ANIMALS_BRED); -@@ -455,7 +459,7 @@ public class Turtle extends Animal { - RandomSource randomsource = this.animal.getRandom(); - - if (this.level.getGameRules().getBoolean(GameRules.RULE_DOMOBLOOT)) { -- this.level.addFreshEntity(new ExperienceOrb(this.level, this.animal.getX(), this.animal.getY(), this.animal.getZ(), randomsource.nextInt(7) + 1, org.bukkit.entity.ExperienceOrb.SpawnReason.BREED, entityplayer)); // Paper; -+ if (event.getExperience() > 0) this.level.addFreshEntity(new ExperienceOrb(this.level, this.animal.getX(), this.animal.getY(), this.animal.getZ(), event.getExperience(), org.bukkit.entity.ExperienceOrb.SpawnReason.BREED, entityplayer)); // Paper - Add EntityFertilizeEggEvent event - } - - } -diff --git a/src/main/java/net/minecraft/world/entity/animal/frog/Frog.java b/src/main/java/net/minecraft/world/entity/animal/frog/Frog.java -index a7af50ebc17abd829a7254c03785206da9624060..816977990639ec0559b652fc9666afd5046f0a5d 100644 ---- a/src/main/java/net/minecraft/world/entity/animal/frog/Frog.java -+++ b/src/main/java/net/minecraft/world/entity/animal/frog/Frog.java -@@ -261,7 +261,12 @@ public class Frog extends Animal implements VariantHolder> { - - @Override - public void spawnChildFromBreeding(ServerLevel world, Animal other) { -- this.finalizeSpawnChildFromBreeding(world, other, null); -+ // Paper start - Add EntityFertilizeEggEvent event -+ final io.papermc.paper.event.entity.EntityFertilizeEggEvent result = org.bukkit.craftbukkit.event.CraftEventFactory.callEntityFertilizeEggEvent(this, other); -+ if (result.isCancelled()) return; -+ -+ this.finalizeSpawnChildFromBreeding(world, other, null, result.getExperience()); // Paper - use craftbukkit call that takes experience amount -+ // Paper end - Add EntityFertilizeEggEvent event - this.getBrain().setMemory(MemoryModuleType.IS_PREGNANT, Unit.INSTANCE); - } - -diff --git a/src/main/java/net/minecraft/world/entity/animal/sniffer/Sniffer.java b/src/main/java/net/minecraft/world/entity/animal/sniffer/Sniffer.java -index d34d8fe70379dcad9540739ec0ae1c94f01fc46b..fadd341ff398886a4da102eefa1beb95a63bbd6d 100644 ---- a/src/main/java/net/minecraft/world/entity/animal/sniffer/Sniffer.java -+++ b/src/main/java/net/minecraft/world/entity/animal/sniffer/Sniffer.java -@@ -345,11 +345,16 @@ public class Sniffer extends Animal { - - @Override - public void spawnChildFromBreeding(ServerLevel world, Animal other) { -+ // Paper start - Add EntityFertilizeEggEvent event -+ final io.papermc.paper.event.entity.EntityFertilizeEggEvent result = org.bukkit.craftbukkit.event.CraftEventFactory.callEntityFertilizeEggEvent(this, other); -+ if (result.isCancelled()) return; -+ // Paper end - Add EntityFertilizeEggEvent event -+ - ItemStack itemstack = new ItemStack(Items.SNIFFER_EGG); - ItemEntity entityitem = new ItemEntity(world, this.position().x(), this.position().y(), this.position().z(), itemstack); - - entityitem.setDefaultPickUpDelay(); -- this.finalizeSpawnChildFromBreeding(world, other, (AgeableMob) null); -+ this.finalizeSpawnChildFromBreeding(world, other, (AgeableMob) null, result.getExperience()); // Paper - Add EntityFertilizeEggEvent event - if (this.spawnAtLocation(entityitem) != null) { // Paper - Call EntityDropItemEvent - this.playSound(SoundEvents.SNIFFER_EGG_PLOP, 1.0F, (this.random.nextFloat() - this.random.nextFloat()) * 0.2F + 0.5F); - } // Paper - Call EntityDropItemEvent -diff --git a/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java b/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java -index b6cca72b4785d5cf009077c81c1cca718d8cfe28..a0b387ddf3cc1e3473c6b35175ac8b68c717cfbe 100644 ---- a/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java -+++ b/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java -@@ -2197,4 +2197,28 @@ public class CraftEventFactory { - return event.callEvent(); - } - // Paper end -+ -+ // Paper start - add EntityFertilizeEggEvent -+ /** -+ * Calls the {@link io.papermc.paper.event.entity.EntityFertilizeEggEvent}. -+ * If the event is cancelled, this method also resets the love on both the {@code breeding} and {@code other} entity. -+ * -+ * @param breeding the entity on which #spawnChildFromBreeding was called. -+ * @param other the partner of the entity. -+ * @return the event after it was called. The instance may be used to retrieve the experience of the event. -+ */ -+ public static io.papermc.paper.event.entity.EntityFertilizeEggEvent callEntityFertilizeEggEvent(Animal breeding, Animal other) { -+ ServerPlayer serverPlayer = breeding.getLoveCause(); -+ if (serverPlayer == null) serverPlayer = other.getLoveCause(); -+ final int experience = breeding.getRandom().nextInt(7) + 1; // From Animal#spawnChildFromBreeding(ServerLevel, Animal) -+ -+ final io.papermc.paper.event.entity.EntityFertilizeEggEvent event = new io.papermc.paper.event.entity.EntityFertilizeEggEvent((LivingEntity) breeding.getBukkitEntity(), (LivingEntity) other.getBukkitEntity(), serverPlayer == null ? null : serverPlayer.getBukkitEntity(), breeding.breedItem == null ? null : CraftItemStack.asCraftMirror(breeding.breedItem).clone(), experience); -+ if (!event.callEvent()) { -+ breeding.resetLove(); -+ other.resetLove(); // stop the pathfinding to avoid infinite loop -+ } -+ -+ return event; -+ } -+ // Paper end - add EntityFertilizeEggEvent - } diff --git a/patches/server/0795-Add-Shearable-API.patch b/patches/server/0795-Add-Shearable-API.patch new file mode 100644 index 000000000000..302cec90c208 --- /dev/null +++ b/patches/server/0795-Add-Shearable-API.patch @@ -0,0 +1,135 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Owen1212055 <23108066+Owen1212055@users.noreply.github.com> +Date: Sun, 17 Oct 2021 15:39:48 -0400 +Subject: [PATCH] Add Shearable API + + +diff --git a/src/main/java/io/papermc/paper/entity/PaperShearable.java b/src/main/java/io/papermc/paper/entity/PaperShearable.java +new file mode 100644 +index 0000000000000000000000000000000000000000..b02e2f2ea4f83615897cb4c66be8b29948097815 +--- /dev/null ++++ b/src/main/java/io/papermc/paper/entity/PaperShearable.java +@@ -0,0 +1,25 @@ ++package io.papermc.paper.entity; ++ ++import io.papermc.paper.adventure.PaperAdventure; ++import net.kyori.adventure.sound.Sound; ++import net.minecraft.server.level.ServerLevel; ++import net.minecraft.world.entity.Shearable; ++import net.minecraft.world.item.ItemStack; ++import net.minecraft.world.item.Items; ++import org.jetbrains.annotations.NotNull; ++ ++public interface PaperShearable extends io.papermc.paper.entity.Shearable { ++ ++ Shearable getHandle(); ++ ++ @Override ++ default boolean readyToBeSheared() { ++ return this.getHandle().readyForShearing(); ++ } ++ ++ @Override ++ default void shear(@NotNull Sound.Source source) { ++ if (!(this.getHandle().level() instanceof final ServerLevel serverLevel)) return; ++ this.getHandle().shear(serverLevel, PaperAdventure.asVanilla(source), new ItemStack(Items.SHEARS)); ++ } ++} +diff --git a/src/main/java/net/minecraft/world/entity/Shearable.java b/src/main/java/net/minecraft/world/entity/Shearable.java +index a3095eee48d8b87a35ad35da9c8a2a9ca20c92bc..35076593f3ccd651295ae1fc9bcf8256c19672dd 100644 +--- a/src/main/java/net/minecraft/world/entity/Shearable.java ++++ b/src/main/java/net/minecraft/world/entity/Shearable.java +@@ -8,4 +8,5 @@ public interface Shearable { + void shear(ServerLevel world, SoundSource shearedSoundCategory, ItemStack shears); + + boolean readyForShearing(); ++ net.minecraft.world.level.Level level(); // Shearable API - expose default level needed for shearing. + } +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftBogged.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftBogged.java +index 0139e85c0751564bb4d2847b7b2e48f75fee9e53..e8e4704304504e69c7964dcd4df8ce5db9e92bf6 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftBogged.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftBogged.java +@@ -4,7 +4,7 @@ import org.bukkit.craftbukkit.CraftServer; + import org.bukkit.entity.Bogged; + import org.bukkit.entity.Skeleton; + +-public class CraftBogged extends CraftAbstractSkeleton implements Bogged { ++public class CraftBogged extends CraftAbstractSkeleton implements Bogged, io.papermc.paper.entity.PaperShearable { // Paper - Shear API + + public CraftBogged(CraftServer server, net.minecraft.world.entity.monster.Bogged entity) { + super(server, entity); +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftMushroomCow.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftMushroomCow.java +index 901b751c856dea677d5238db1ee70401a9d7bba5..bfe39c7a9d2910bee9b40cf5ab4c154711d5cbb0 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftMushroomCow.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftMushroomCow.java +@@ -14,7 +14,7 @@ import org.bukkit.entity.MushroomCow; + import org.bukkit.potion.PotionEffect; + import org.bukkit.potion.PotionEffectType; + +-public class CraftMushroomCow extends CraftCow implements MushroomCow { ++public class CraftMushroomCow extends CraftCow implements MushroomCow, io.papermc.paper.entity.PaperShearable { // Paper + public CraftMushroomCow(CraftServer server, net.minecraft.world.entity.animal.MushroomCow entity) { + super(server, entity); + } +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftSheep.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftSheep.java +index 030bf7b6312799231d0b614ba5c84fec23c276e3..37291d7ad9fdf0fe78894f82a418f40bb581f58b 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftSheep.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftSheep.java +@@ -4,7 +4,7 @@ import org.bukkit.DyeColor; + import org.bukkit.craftbukkit.CraftServer; + import org.bukkit.entity.Sheep; + +-public class CraftSheep extends CraftAnimals implements Sheep { ++public class CraftSheep extends CraftAnimals implements Sheep, io.papermc.paper.entity.PaperShearable { // Paper + public CraftSheep(CraftServer server, net.minecraft.world.entity.animal.Sheep entity) { + super(server, entity); + } +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftSnowman.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftSnowman.java +index 1e9807b8f468742d208f817e22d7625106fc1b58..4ce2373ff71c3c1b8951646e057587a3ab09e145 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftSnowman.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftSnowman.java +@@ -4,7 +4,7 @@ import net.minecraft.world.entity.animal.SnowGolem; + import org.bukkit.craftbukkit.CraftServer; + import org.bukkit.entity.Snowman; + +-public class CraftSnowman extends CraftGolem implements Snowman, com.destroystokyo.paper.entity.CraftRangedEntity { // Paper ++public class CraftSnowman extends CraftGolem implements Snowman, com.destroystokyo.paper.entity.CraftRangedEntity, io.papermc.paper.entity.PaperShearable { // Paper + public CraftSnowman(CraftServer server, SnowGolem entity) { + super(server, entity); + } +diff --git a/src/test/java/io/papermc/paper/entity/ShearableTest.java b/src/test/java/io/papermc/paper/entity/ShearableTest.java +new file mode 100644 +index 0000000000000000000000000000000000000000..709b90f0cd01a4508d44f2e971f5bf9785d78ae5 +--- /dev/null ++++ b/src/test/java/io/papermc/paper/entity/ShearableTest.java +@@ -0,0 +1,30 @@ ++package io.papermc.paper.entity; ++ ++import com.destroystokyo.paper.entity.ai.MobGoalHelper; ++import io.github.classgraph.ClassGraph; ++import io.github.classgraph.ScanResult; ++import java.util.List; ++import net.minecraft.world.entity.Mob; ++import net.minecraft.world.entity.Shearable; ++import org.bukkit.support.environment.Normal; ++import org.junit.jupiter.api.Assertions; ++import org.junit.jupiter.params.ParameterizedTest; ++import org.junit.jupiter.params.provider.MethodSource; ++ ++@Normal ++class ShearableTest { ++ ++ static List> nmsShearables() { ++ try (final ScanResult result = new ClassGraph().enableClassInfo().whitelistPackages("net.minecraft.world.entity").scan()) { ++ return result.getClassesImplementing(Shearable.class.getName()).loadClasses(Shearable.class); ++ } ++ } ++ ++ @SuppressWarnings("unchecked") ++ @ParameterizedTest ++ @MethodSource("nmsShearables") ++ void ensureImplementsShearable(final Class shearableNmsClass) { ++ final Class bukkitClass = MobGoalHelper.toBukkitClass((Class) shearableNmsClass); ++ Assertions.assertTrue(io.papermc.paper.entity.Shearable.class.isAssignableFrom(bukkitClass), bukkitClass.getName() + " does not implement Shearable"); ++ } ++} diff --git a/patches/server/0796-Fix-HumanEntity-drop-not-updating-the-client-inv.patch b/patches/server/0796-Fix-HumanEntity-drop-not-updating-the-client-inv.patch deleted file mode 100644 index b804a77828d0..000000000000 --- a/patches/server/0796-Fix-HumanEntity-drop-not-updating-the-client-inv.patch +++ /dev/null @@ -1,30 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Jake Potrebic -Date: Sun, 10 Oct 2021 18:18:01 -0700 -Subject: [PATCH] Fix HumanEntity#drop not updating the client inv - -== AT == -public net.minecraft.server.level.ServerPlayer containerSynchronizer - -diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftHumanEntity.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftHumanEntity.java -index 1ff114f792ca12bc5408cdd9ab0ad23c3158b99c..b782cc64426a058881947ed62316c1cb8d332037 100644 ---- a/src/main/java/org/bukkit/craftbukkit/entity/CraftHumanEntity.java -+++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftHumanEntity.java -@@ -759,8 +759,15 @@ public class CraftHumanEntity extends CraftLivingEntity implements HumanEntity { - // Paper end - @Override - public boolean dropItem(boolean dropAll) { -- if (!(this.getHandle() instanceof ServerPlayer)) return false; -- return ((ServerPlayer) this.getHandle()).drop(dropAll); -+ // Paper start - Fix HumanEntity#drop not updating the client inv -+ if (!(this.getHandle() instanceof ServerPlayer player)) return false; -+ boolean success = player.drop(dropAll); -+ if (!success) return false; -+ final net.minecraft.world.entity.player.Inventory inv = player.getInventory(); -+ final java.util.OptionalInt optionalSlot = player.containerMenu.findSlot(inv, inv.selected); -+ optionalSlot.ifPresent(slot -> player.containerSynchronizer.sendSlotChange(player.containerMenu, slot, inv.getSelected())); -+ return true; -+ // Paper end - Fix HumanEntity#drop not updating the client inv - } - - @Override diff --git a/patches/server/0803-Fix-SpawnEggMeta-get-setSpawnedType.patch b/patches/server/0796-Fix-SpawnEggMeta-get-setSpawnedType.patch similarity index 100% rename from patches/server/0803-Fix-SpawnEggMeta-get-setSpawnedType.patch rename to patches/server/0796-Fix-SpawnEggMeta-get-setSpawnedType.patch diff --git a/patches/server/0797-Add-CompostItemEvent-and-EntityCompostItemEvent.patch b/patches/server/0797-Add-CompostItemEvent-and-EntityCompostItemEvent.patch deleted file mode 100644 index 85321a4a5ba1..000000000000 --- a/patches/server/0797-Add-CompostItemEvent-and-EntityCompostItemEvent.patch +++ /dev/null @@ -1,45 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Noah van der Aa -Date: Sun, 8 Aug 2021 19:56:02 +0200 -Subject: [PATCH] Add CompostItemEvent and EntityCompostItemEvent - - -diff --git a/src/main/java/net/minecraft/world/level/block/ComposterBlock.java b/src/main/java/net/minecraft/world/level/block/ComposterBlock.java -index 19fa8a9f935e9063497f8c0bd7909036fa0af2b7..d3d12f9114173f4971f95d7ef895a4374705bd3f 100644 ---- a/src/main/java/net/minecraft/world/level/block/ComposterBlock.java -+++ b/src/main/java/net/minecraft/world/level/block/ComposterBlock.java -@@ -336,7 +336,21 @@ public class ComposterBlock extends Block implements WorldlyContainerHolder { - int i = (Integer) iblockdata.getValue(ComposterBlock.LEVEL); - float f = ComposterBlock.COMPOSTABLES.getFloat(itemstack.getItem()); - -- if ((i != 0 || f <= 0.0F) && rand >= (double) f) { -+ // Paper start - Add CompostItemEvent and EntityCompostItemEvent -+ boolean willRaiseLevel = !((i != 0 || f <= 0.0F) && rand >= (double) f); -+ final io.papermc.paper.event.block.CompostItemEvent event; -+ if (entity == null) { -+ event = new io.papermc.paper.event.block.CompostItemEvent(org.bukkit.craftbukkit.block.CraftBlock.at(generatoraccess, blockposition), itemstack.getBukkitStack(), willRaiseLevel); -+ } else { -+ event = new io.papermc.paper.event.entity.EntityCompostItemEvent(entity.getBukkitEntity(), org.bukkit.craftbukkit.block.CraftBlock.at(generatoraccess, blockposition), itemstack.getBukkitStack(), willRaiseLevel); -+ } -+ if (!event.callEvent()) { // check for cancellation of entity event (non entity event can't be cancelled cause of hoppers) -+ return null; -+ } -+ willRaiseLevel = event.willRaiseLevel(); -+ -+ if (!willRaiseLevel) { -+ // Paper end - Add CompostItemEvent and EntityCompostItemEvent - return iblockdata; - } else { - int j = i + 1; -@@ -485,6 +499,11 @@ public class ComposterBlock extends Block implements WorldlyContainerHolder { - if (!itemstack.isEmpty()) { - this.changed = true; - BlockState iblockdata = ComposterBlock.addItem((Entity) null, this.state, this.level, this.pos, itemstack); -+ // Paper start - Add CompostItemEvent and EntityCompostItemEvent -+ if (iblockdata == null) { -+ return; -+ } -+ // Paper end - Add CompostItemEvent and EntityCompostItemEvent - - this.level.levelEvent(1500, this.pos, iblockdata != this.state ? 1 : 0); - this.removeItemNoUpdate(0); diff --git a/patches/server/0797-Fix-crash-relating-to-bad-recipes-in-furnace-like-ti.patch b/patches/server/0797-Fix-crash-relating-to-bad-recipes-in-furnace-like-ti.patch new file mode 100644 index 000000000000..72035f8c0e63 --- /dev/null +++ b/patches/server/0797-Fix-crash-relating-to-bad-recipes-in-furnace-like-ti.patch @@ -0,0 +1,19 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Jake Potrebic +Date: Sun, 19 Mar 2023 20:36:22 -0700 +Subject: [PATCH] Fix crash relating to bad recipes in furnace-like tile + entities + + +diff --git a/src/main/java/net/minecraft/world/level/block/entity/AbstractFurnaceBlockEntity.java b/src/main/java/net/minecraft/world/level/block/entity/AbstractFurnaceBlockEntity.java +index 4d36aa195332c2ff6fa7bc5fff61ff7dc80a3fd5..b1067a3add5dc0cfa853b02b5b556d6d67e2932a 100644 +--- a/src/main/java/net/minecraft/world/level/block/entity/AbstractFurnaceBlockEntity.java ++++ b/src/main/java/net/minecraft/world/level/block/entity/AbstractFurnaceBlockEntity.java +@@ -499,6 +499,7 @@ public abstract class AbstractFurnaceBlockEntity extends BaseContainerBlockEntit + Entry>> entry = (Entry) objectiterator.next(); + + worldserver.recipeAccess().byKey(entry.getKey()).ifPresent((recipeholder) -> { // CraftBukkit - decompile error ++ if (!(recipeholder.value() instanceof AbstractCookingRecipe)) return; // Paper - don't process non-cooking recipes + list.add(recipeholder); + AbstractFurnaceBlockEntity.createExperience(worldserver, vec3d, entry.getIntValue(), ((AbstractCookingRecipe) recipeholder.value()).experience(), blockposition, entityplayer, itemstack, amount); // CraftBukkit + }); diff --git a/patches/server/0798-Correctly-handle-ArmorStand-invisibility.patch b/patches/server/0798-Correctly-handle-ArmorStand-invisibility.patch deleted file mode 100644 index 34b66d686a11..000000000000 --- a/patches/server/0798-Correctly-handle-ArmorStand-invisibility.patch +++ /dev/null @@ -1,25 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Jake Potrebic -Date: Sun, 5 Mar 2023 14:38:21 -0800 -Subject: [PATCH] Correctly handle ArmorStand invisibility - - -diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftArmorStand.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftArmorStand.java -index 1087840331f68ffe79e79f6493137b2b894832f9..9fe85d6f807e64cf02d8e1921672e3196f6d606f 100644 ---- a/src/main/java/org/bukkit/craftbukkit/entity/CraftArmorStand.java -+++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftArmorStand.java -@@ -152,6 +152,14 @@ public class CraftArmorStand extends CraftLivingEntity implements ArmorStand { - this.getHandle().noPhysics = !gravity; - } - -+ // Paper start - Armor Stand has its own invisible field -+ @Override -+ public void setInvisible(final boolean invisible) { -+ this.getHandle().setInvisible(invisible); -+ super.setInvisible(invisible); -+ } -+ // Paper end -+ - @Override - public boolean isVisible() { - return !this.getHandle().isInvisible(); diff --git a/patches/server/0798-Treat-sequence-violations-like-they-should-be.patch b/patches/server/0798-Treat-sequence-violations-like-they-should-be.patch new file mode 100644 index 000000000000..2a7a2399bc2b --- /dev/null +++ b/patches/server/0798-Treat-sequence-violations-like-they-should-be.patch @@ -0,0 +1,18 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Shane Freeder +Date: Thu, 30 Mar 2023 03:13:58 +0100 +Subject: [PATCH] Treat sequence violations like they should be + + +diff --git a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java +index 5802bca38fdeccbc1353990ecb425034f86e8332..3e9247f662afde4dcca900b69b6a78140f9566fa 100644 +--- a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java ++++ b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java +@@ -1997,6 +1997,7 @@ public class ServerGamePacketListenerImpl extends ServerCommonPacketListenerImpl + + public void ackBlockChangesUpTo(int sequence) { + if (sequence < 0) { ++ this.disconnect(Component.literal("Expected packet sequence nr >= 0"), org.bukkit.event.player.PlayerKickEvent.Cause.ILLEGAL_ACTION); // Paper - Treat sequence violations like they should be + throw new IllegalArgumentException("Expected packet sequence nr >= 0"); + } else { + this.ackBlockChangesUpTo = Math.max(sequence, this.ackBlockChangesUpTo); diff --git a/patches/server/0799-Fix-advancement-triggers-for-entity-damage.patch b/patches/server/0799-Fix-advancement-triggers-for-entity-damage.patch deleted file mode 100644 index 94c53ab61cd8..000000000000 --- a/patches/server/0799-Fix-advancement-triggers-for-entity-damage.patch +++ /dev/null @@ -1,46 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Jake Potrebic -Date: Thu, 16 Mar 2023 10:04:17 +0100 -Subject: [PATCH] Fix advancement triggers for entity damage - -Changes the Interaction entity's trigger to use the vanilla -generic damage source - -Fixes a couple places where the original damage and modified damage -were passed in the reverse order to the advancement triggers - -diff --git a/src/main/java/net/minecraft/world/entity/Interaction.java b/src/main/java/net/minecraft/world/entity/Interaction.java -index 2ebbf7954dc5e0d6c9d53327d05b725eec310086..c5bd2e90ad74ba08910f65a2e07b6f76435df10f 100644 ---- a/src/main/java/net/minecraft/world/entity/Interaction.java -+++ b/src/main/java/net/minecraft/world/entity/Interaction.java -@@ -156,7 +156,7 @@ public class Interaction extends Entity implements Attackable, Targeting { - // CraftBukkit end - this.attack = new Interaction.PlayerAction(entityhuman.getUUID(), this.level().getGameTime()); - if (entityhuman instanceof ServerPlayer entityplayer) { -- CriteriaTriggers.PLAYER_HURT_ENTITY.trigger(entityplayer, this, source, (float) event.getFinalDamage(), 1.0F, false); // CraftBukkit -+ CriteriaTriggers.PLAYER_HURT_ENTITY.trigger(entityplayer, this, entityhuman.damageSources().generic(), 1.0F, (float) event.getFinalDamage(), false); // CraftBukkit // Paper - use correct source and fix taken/dealt param order - } - - return !this.getResponse(); -diff --git a/src/main/java/net/minecraft/world/entity/LivingEntity.java b/src/main/java/net/minecraft/world/entity/LivingEntity.java -index 9e58a85a7de53b17fa149ae0b4951baa351d99db..4e96a65396b687d2823f2229744f5d448ba87512 100644 ---- a/src/main/java/net/minecraft/world/entity/LivingEntity.java -+++ b/src/main/java/net/minecraft/world/entity/LivingEntity.java -@@ -2388,7 +2388,7 @@ public abstract class LivingEntity extends Entity implements Attackable { - // Duplicate triggers if blocking - if (event.getDamage(DamageModifier.BLOCKING) < 0) { - if (this instanceof ServerPlayer) { -- CriteriaTriggers.ENTITY_HURT_PLAYER.trigger((ServerPlayer) this, damagesource, f, originalDamage, true); -+ CriteriaTriggers.ENTITY_HURT_PLAYER.trigger((ServerPlayer) this, damagesource, originalDamage, f, true); // Paper - fix taken/dealt param order - f2 = (float) -event.getDamage(DamageModifier.BLOCKING); - if (f2 > 0.0F && f2 < 3.4028235E37F) { - ((ServerPlayer) this).awardStat(Stats.DAMAGE_BLOCKED_BY_SHIELD, Math.round(originalDamage * 10.0F)); -@@ -2396,7 +2396,7 @@ public abstract class LivingEntity extends Entity implements Attackable { - } - - if (damagesource.getEntity() instanceof ServerPlayer) { -- CriteriaTriggers.PLAYER_HURT_ENTITY.trigger((ServerPlayer) damagesource.getEntity(), this, damagesource, f, originalDamage, true); -+ CriteriaTriggers.PLAYER_HURT_ENTITY.trigger((ServerPlayer) damagesource.getEntity(), this, damagesource, originalDamage, f, true); // Paper - fix taken/dealt param order - } - - return true; diff --git a/patches/server/0799-Prevent-causing-expired-keys-from-impacting-new-join.patch b/patches/server/0799-Prevent-causing-expired-keys-from-impacting-new-join.patch new file mode 100644 index 000000000000..7a5bcdc7de65 --- /dev/null +++ b/patches/server/0799-Prevent-causing-expired-keys-from-impacting-new-join.patch @@ -0,0 +1,61 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Shane Freeder +Date: Mon, 3 Apr 2023 08:55:52 +0100 +Subject: [PATCH] Prevent causing expired keys from impacting new joins + + +diff --git a/src/main/java/net/minecraft/network/protocol/game/ClientboundPlayerInfoUpdatePacket.java b/src/main/java/net/minecraft/network/protocol/game/ClientboundPlayerInfoUpdatePacket.java +index d0b2a8b5ded71a9a41753f4addea1c49826b34a3..29b465fc1dc50e0e84ddb889c5303e80fe662874 100644 +--- a/src/main/java/net/minecraft/network/protocol/game/ClientboundPlayerInfoUpdatePacket.java ++++ b/src/main/java/net/minecraft/network/protocol/game/ClientboundPlayerInfoUpdatePacket.java +@@ -114,7 +114,15 @@ public class ClientboundPlayerInfoUpdatePacket implements Packet serialized.chatSession = buf.readNullable(RemoteChatSession.Data::read), +- (buf, entry) -> buf.writeNullable(entry.chatSession, RemoteChatSession.Data::write) ++ // Paper start - Prevent causing expired keys from impacting new joins ++ (buf, entry) -> { ++ RemoteChatSession.Data chatSession = entry.chatSession; ++ if (chatSession != null && chatSession.profilePublicKey().hasExpired()) { ++ chatSession = null; ++ } ++ buf.writeNullable(chatSession, RemoteChatSession.Data::write); ++ } ++ // Paper end - Prevent causing expired keys from impacting new joins + ), + UPDATE_GAME_MODE((serialized, buf) -> serialized.gameMode = GameType.byId(buf.readVarInt()), (buf, entry) -> buf.writeVarInt(entry.gameMode().getId())), + UPDATE_LISTED((serialized, buf) -> serialized.listed = buf.readBoolean(), (buf, entry) -> buf.writeBoolean(entry.listed())), +diff --git a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java +index 3e9247f662afde4dcca900b69b6a78140f9566fa..919b0ffb69720b3edeb9d25c7f41291a09976505 100644 +--- a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java ++++ b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java +@@ -305,6 +305,7 @@ public class ServerGamePacketListenerImpl extends ServerCommonPacketListenerImpl + private boolean receivedMovementThisTick; + @Nullable + private RemoteChatSession chatSession; ++ private boolean hasLoggedExpiry = false; // Paper - Prevent causing expired keys from impacting new joins + private SignedMessageChain.Decoder signedMessageDecoder; + private final LastSeenMessagesValidator lastSeenMessages = new LastSeenMessagesValidator(20); + private final MessageSignatureCache messageSignatureCache = MessageSignatureCache.createDefault(); +@@ -401,6 +402,13 @@ public class ServerGamePacketListenerImpl extends ServerCommonPacketListenerImpl + this.disconnect((Component) Component.translatable("multiplayer.disconnect.idling"), org.bukkit.event.player.PlayerKickEvent.Cause.IDLING); // Paper - kick event cause + } + ++ // Paper start - Prevent causing expired keys from impacting new joins ++ if (!hasLoggedExpiry && this.chatSession != null && this.chatSession.profilePublicKey().data().hasExpired()) { ++ LOGGER.info("Player profile key for {} has expired!", this.player.getName().getString()); ++ hasLoggedExpiry = true; ++ } ++ // Paper end - Prevent causing expired keys from impacting new joins ++ + } + + private int getMaximumFlyingTicks(Entity vehicle) { +@@ -3484,6 +3492,7 @@ public class ServerGamePacketListenerImpl extends ServerCommonPacketListenerImpl + + private void resetPlayerChatState(RemoteChatSession session) { + this.chatSession = session; ++ this.hasLoggedExpiry = false; // Paper - Prevent causing expired keys from impacting new joins + this.signedMessageDecoder = session.createMessageDecoder(this.player.getUUID()); + this.chatMessageChain.append(() -> { + this.player.setChatSession(session); diff --git a/patches/server/0800-Fix-text-display-error-on-spawn.patch b/patches/server/0800-Fix-text-display-error-on-spawn.patch deleted file mode 100644 index 7f64cfe6d999..000000000000 --- a/patches/server/0800-Fix-text-display-error-on-spawn.patch +++ /dev/null @@ -1,19 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Nassim Jahnke -Date: Thu, 16 Mar 2023 16:27:50 +0100 -Subject: [PATCH] Fix text display error on spawn - - -diff --git a/src/main/java/net/minecraft/world/entity/Display.java b/src/main/java/net/minecraft/world/entity/Display.java -index bd56721485b3b4ac81c97d60fc2a83bfc20977c0..a658943669cdfd49f66ba713505d11b33306ed86 100644 ---- a/src/main/java/net/minecraft/world/entity/Display.java -+++ b/src/main/java/net/minecraft/world/entity/Display.java -@@ -892,7 +892,7 @@ public abstract class Display extends Entity { - b = loadFlag(b, nbt, "default_background", (byte)4); - Optional optional = Display.TextDisplay.Align.CODEC - .decode(NbtOps.INSTANCE, nbt.get("alignment")) -- .resultOrPartial(Util.prefix("Display entity", Display.LOGGER::error)) -+ .result() // Paper - Hide text display error on spawn - .map(Pair::getFirst); - if (optional.isPresent()) { - b = switch ((Display.TextDisplay.Align)optional.get()) { diff --git a/patches/server/0800-Prevent-GameEvents-being-fired-from-unloaded-chunks.patch b/patches/server/0800-Prevent-GameEvents-being-fired-from-unloaded-chunks.patch new file mode 100644 index 000000000000..5ddc60ef9150 --- /dev/null +++ b/patches/server/0800-Prevent-GameEvents-being-fired-from-unloaded-chunks.patch @@ -0,0 +1,22 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Shane Freeder +Date: Wed, 5 Apr 2023 20:15:47 +0100 +Subject: [PATCH] Prevent GameEvents being fired from unloaded chunks + + +diff --git a/src/main/java/net/minecraft/server/level/ServerLevel.java b/src/main/java/net/minecraft/server/level/ServerLevel.java +index b81d814619e4175f42aee397811b07cae420c2e8..e9096a138175448279a03a55043982b6d83fbe12 100644 +--- a/src/main/java/net/minecraft/server/level/ServerLevel.java ++++ b/src/main/java/net/minecraft/server/level/ServerLevel.java +@@ -1355,6 +1355,11 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe + + @Override + public void gameEvent(Holder event, Vec3 emitterPos, GameEvent.Context emitter) { ++ // Paper start - Prevent GameEvents being fired from unloaded chunks ++ if (this.getChunkIfLoadedImmediately((Mth.floor(emitterPos.x) >> 4), (Mth.floor(emitterPos.z) >> 4)) == null) { ++ return; ++ } ++ // Paper end - Prevent GameEvents being fired from unloaded chunks + this.gameEventDispatcher.post(event, emitterPos, emitter); + } + diff --git a/patches/server/0801-Fix-inventories-returning-null-Locations.patch b/patches/server/0801-Fix-inventories-returning-null-Locations.patch deleted file mode 100644 index 23413d6b0304..000000000000 --- a/patches/server/0801-Fix-inventories-returning-null-Locations.patch +++ /dev/null @@ -1,61 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Jake Potrebic -Date: Wed, 15 Mar 2023 18:29:45 -0700 -Subject: [PATCH] Fix inventories returning null Locations - -Wandering Trader, AbstractHorse, Beacon and Composter inventories returned null locations -when a block or entity location is readily available - -Co-authored-by: Lukas Planz - -diff --git a/src/main/java/net/minecraft/world/SimpleContainer.java b/src/main/java/net/minecraft/world/SimpleContainer.java -index c26161784359ea167e11de8aa58eda3b4851059c..6632cf24ebe6d147950a1fdb876660937da86b73 100644 ---- a/src/main/java/net/minecraft/world/SimpleContainer.java -+++ b/src/main/java/net/minecraft/world/SimpleContainer.java -@@ -63,6 +63,16 @@ public class SimpleContainer implements Container, StackedContentsCompatible { - - @Override - public Location getLocation() { -+ // Paper start - Fix inventories returning null Locations -+ // When the block inventory does not have a tile state that implements getLocation, e. g. composters -+ if (this.bukkitOwner instanceof org.bukkit.inventory.BlockInventoryHolder blockInventoryHolder) { -+ return blockInventoryHolder.getBlock().getLocation(); -+ } -+ // When the bukkit owner is a bukkit entity, but does not implement Container itself, e. g. horses -+ if (this.bukkitOwner instanceof org.bukkit.entity.Entity entity) { -+ return entity.getLocation(); -+ } -+ // Paper end - Fix inventories returning null Locations - return null; - } - -diff --git a/src/main/java/net/minecraft/world/inventory/BeaconMenu.java b/src/main/java/net/minecraft/world/inventory/BeaconMenu.java -index 8b0ebf659f6b219ce2a5d10b0d79f9b89b47c911..a735aeeb59f79154ce797c6e2f5600305f46d217 100644 ---- a/src/main/java/net/minecraft/world/inventory/BeaconMenu.java -+++ b/src/main/java/net/minecraft/world/inventory/BeaconMenu.java -@@ -52,6 +52,12 @@ public class BeaconMenu extends AbstractContainerMenu { - public int getMaxStackSize() { - return 1; - } -+ // Paper start - Fix inventories returning null Locations -+ @Override -+ public org.bukkit.Location getLocation() { -+ return context.getLocation(); -+ } -+ // Paper end - Fix inventories returning null Locations - }; - checkContainerDataCount(propertyDelegate, 3); - this.beaconData = propertyDelegate; -diff --git a/src/main/java/net/minecraft/world/inventory/MerchantContainer.java b/src/main/java/net/minecraft/world/inventory/MerchantContainer.java -index 7cc96b62f6bacdb44a37d74db214bd0e11c4d503..9140fab07aab32065f7a3b5d13dd17d61dc6d646 100644 ---- a/src/main/java/net/minecraft/world/inventory/MerchantContainer.java -+++ b/src/main/java/net/minecraft/world/inventory/MerchantContainer.java -@@ -65,7 +65,7 @@ public class MerchantContainer implements Container { - - @Override - public Location getLocation() { -- return (this.merchant instanceof Villager) ? ((Villager) this.merchant).getBukkitEntity().getLocation() : null; -+ return (this.merchant instanceof AbstractVillager) ? ((AbstractVillager) this.merchant).getBukkitEntity().getLocation() : null; // Paper - Fix inventories returning null Locations - } - // CraftBukkit end - diff --git a/patches/server/0801-Use-array-for-gamerule-storage.patch b/patches/server/0801-Use-array-for-gamerule-storage.patch new file mode 100644 index 000000000000..efb6cfeb5035 --- /dev/null +++ b/patches/server/0801-Use-array-for-gamerule-storage.patch @@ -0,0 +1,52 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Paul Sauve +Date: Sun, 9 May 2021 16:49:49 -0500 +Subject: [PATCH] Use array for gamerule storage + + +diff --git a/src/main/java/net/minecraft/world/level/GameRules.java b/src/main/java/net/minecraft/world/level/GameRules.java +index 09299e45552eb998fd02123c3921c0653f85083d..4ae47c2c5a6bcfbf932d000a80974463e2d3818d 100644 +--- a/src/main/java/net/minecraft/world/level/GameRules.java ++++ b/src/main/java/net/minecraft/world/level/GameRules.java +@@ -129,6 +129,7 @@ public class GameRules { + })); + private final Map, GameRules.Value> rules; + private final FeatureFlagSet enabledFeatures; ++ private final GameRules.Value[] gameruleArray; // Paper - Perf: Use array for gamerule storage + + private static > GameRules.Key register(String name, GameRules.Category category, GameRules.Type type) { + GameRules.Key gamerules_gamerulekey = new GameRules.Key<>(name, category); +@@ -161,10 +162,21 @@ public class GameRules { + private GameRules(Map, GameRules.Value> rules, FeatureFlagSet enabledFeatures) { + this.rules = rules; + this.enabledFeatures = enabledFeatures; ++ ++ // Paper start - Perf: Use array for gamerule storage ++ int arraySize = GameRules.Key.lastGameRuleIndex + 1; ++ GameRules.Value[] values = new GameRules.Value[arraySize]; ++ ++ for (Entry, GameRules.Value> entry : rules.entrySet()) { ++ values[entry.getKey().gameRuleIndex] = entry.getValue(); ++ } ++ ++ this.gameruleArray = values; ++ // Paper end - Perf: Use array for gamerule storage + } + + public > T getRule(GameRules.Key key) { +- T t0 = (T) this.rules.get(key); // CraftBukkit - decompile error ++ T t0 = key == null ? null : (T) this.gameruleArray[key.gameRuleIndex]; // Paper - Perf: Use array for gamerule storage + + if (t0 == null) { + throw new IllegalArgumentException("Tried to access invalid game rule"); +@@ -232,6 +244,10 @@ public class GameRules { + } + + public static final class Key> { ++ // Paper start - Perf: Use array for gamerule storage ++ public static int lastGameRuleIndex = 0; ++ public final int gameRuleIndex = lastGameRuleIndex++; ++ // Paper end - Perf: Use array for gamerule storage + + final String id; + private final GameRules.Category category; diff --git a/patches/server/0802-Add-Shearable-API.patch b/patches/server/0802-Add-Shearable-API.patch deleted file mode 100644 index 3cf9cf301552..000000000000 --- a/patches/server/0802-Add-Shearable-API.patch +++ /dev/null @@ -1,121 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Owen1212055 <23108066+Owen1212055@users.noreply.github.com> -Date: Sun, 17 Oct 2021 15:39:48 -0400 -Subject: [PATCH] Add Shearable API - - -diff --git a/src/main/java/io/papermc/paper/entity/PaperShearable.java b/src/main/java/io/papermc/paper/entity/PaperShearable.java -new file mode 100644 -index 0000000000000000000000000000000000000000..bcf254e3c81cf1e401bddc850fb24ad29dcc127c ---- /dev/null -+++ b/src/main/java/io/papermc/paper/entity/PaperShearable.java -@@ -0,0 +1,21 @@ -+package io.papermc.paper.entity; -+ -+import io.papermc.paper.adventure.PaperAdventure; -+import net.kyori.adventure.sound.Sound; -+import net.minecraft.world.entity.Shearable; -+import org.jetbrains.annotations.NotNull; -+ -+public interface PaperShearable extends io.papermc.paper.entity.Shearable { -+ -+ Shearable getHandle(); -+ -+ @Override -+ default boolean readyToBeSheared() { -+ return this.getHandle().readyForShearing(); -+ } -+ -+ @Override -+ default void shear(@NotNull Sound.Source source) { -+ this.getHandle().shear(PaperAdventure.asVanilla(source)); -+ } -+} -diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftBogged.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftBogged.java -index 0139e85c0751564bb4d2847b7b2e48f75fee9e53..e8e4704304504e69c7964dcd4df8ce5db9e92bf6 100644 ---- a/src/main/java/org/bukkit/craftbukkit/entity/CraftBogged.java -+++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftBogged.java -@@ -4,7 +4,7 @@ import org.bukkit.craftbukkit.CraftServer; - import org.bukkit.entity.Bogged; - import org.bukkit.entity.Skeleton; - --public class CraftBogged extends CraftAbstractSkeleton implements Bogged { -+public class CraftBogged extends CraftAbstractSkeleton implements Bogged, io.papermc.paper.entity.PaperShearable { // Paper - Shear API - - public CraftBogged(CraftServer server, net.minecraft.world.entity.monster.Bogged entity) { - super(server, entity); -diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftMushroomCow.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftMushroomCow.java -index 986f7b18112ef183de3bbff269a92bf6ac945477..9cc81bcccbf1141f66fedada1359b7c0dfa8e22a 100644 ---- a/src/main/java/org/bukkit/craftbukkit/entity/CraftMushroomCow.java -+++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftMushroomCow.java -@@ -14,7 +14,7 @@ import org.bukkit.entity.MushroomCow; - import org.bukkit.potion.PotionEffect; - import org.bukkit.potion.PotionEffectType; - --public class CraftMushroomCow extends CraftCow implements MushroomCow { -+public class CraftMushroomCow extends CraftCow implements MushroomCow, io.papermc.paper.entity.PaperShearable { // Paper - public CraftMushroomCow(CraftServer server, net.minecraft.world.entity.animal.MushroomCow entity) { - super(server, entity); - } -diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftSheep.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftSheep.java -index 030bf7b6312799231d0b614ba5c84fec23c276e3..37291d7ad9fdf0fe78894f82a418f40bb581f58b 100644 ---- a/src/main/java/org/bukkit/craftbukkit/entity/CraftSheep.java -+++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftSheep.java -@@ -4,7 +4,7 @@ import org.bukkit.DyeColor; - import org.bukkit.craftbukkit.CraftServer; - import org.bukkit.entity.Sheep; - --public class CraftSheep extends CraftAnimals implements Sheep { -+public class CraftSheep extends CraftAnimals implements Sheep, io.papermc.paper.entity.PaperShearable { // Paper - public CraftSheep(CraftServer server, net.minecraft.world.entity.animal.Sheep entity) { - super(server, entity); - } -diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftSnowman.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftSnowman.java -index 1e9807b8f468742d208f817e22d7625106fc1b58..4ce2373ff71c3c1b8951646e057587a3ab09e145 100644 ---- a/src/main/java/org/bukkit/craftbukkit/entity/CraftSnowman.java -+++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftSnowman.java -@@ -4,7 +4,7 @@ import net.minecraft.world.entity.animal.SnowGolem; - import org.bukkit.craftbukkit.CraftServer; - import org.bukkit.entity.Snowman; - --public class CraftSnowman extends CraftGolem implements Snowman, com.destroystokyo.paper.entity.CraftRangedEntity { // Paper -+public class CraftSnowman extends CraftGolem implements Snowman, com.destroystokyo.paper.entity.CraftRangedEntity, io.papermc.paper.entity.PaperShearable { // Paper - public CraftSnowman(CraftServer server, SnowGolem entity) { - super(server, entity); - } -diff --git a/src/test/java/io/papermc/paper/entity/ShearableTest.java b/src/test/java/io/papermc/paper/entity/ShearableTest.java -new file mode 100644 -index 0000000000000000000000000000000000000000..709b90f0cd01a4508d44f2e971f5bf9785d78ae5 ---- /dev/null -+++ b/src/test/java/io/papermc/paper/entity/ShearableTest.java -@@ -0,0 +1,30 @@ -+package io.papermc.paper.entity; -+ -+import com.destroystokyo.paper.entity.ai.MobGoalHelper; -+import io.github.classgraph.ClassGraph; -+import io.github.classgraph.ScanResult; -+import java.util.List; -+import net.minecraft.world.entity.Mob; -+import net.minecraft.world.entity.Shearable; -+import org.bukkit.support.environment.Normal; -+import org.junit.jupiter.api.Assertions; -+import org.junit.jupiter.params.ParameterizedTest; -+import org.junit.jupiter.params.provider.MethodSource; -+ -+@Normal -+class ShearableTest { -+ -+ static List> nmsShearables() { -+ try (final ScanResult result = new ClassGraph().enableClassInfo().whitelistPackages("net.minecraft.world.entity").scan()) { -+ return result.getClassesImplementing(Shearable.class.getName()).loadClasses(Shearable.class); -+ } -+ } -+ -+ @SuppressWarnings("unchecked") -+ @ParameterizedTest -+ @MethodSource("nmsShearables") -+ void ensureImplementsShearable(final Class shearableNmsClass) { -+ final Class bukkitClass = MobGoalHelper.toBukkitClass((Class) shearableNmsClass); -+ Assertions.assertTrue(io.papermc.paper.entity.Shearable.class.isAssignableFrom(bukkitClass), bukkitClass.getName() + " does not implement Shearable"); -+ } -+} diff --git a/patches/server/0802-Fix-a-couple-of-upstream-bed-issues.patch b/patches/server/0802-Fix-a-couple-of-upstream-bed-issues.patch new file mode 100644 index 000000000000..76cfa7bc0d17 --- /dev/null +++ b/patches/server/0802-Fix-a-couple-of-upstream-bed-issues.patch @@ -0,0 +1,33 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Jake Potrebic +Date: Sun, 9 Apr 2023 21:11:58 -0700 +Subject: [PATCH] Fix a couple of upstream bed issues + +Upstream incorrectly skipped explosion logic if +the bed was occupied and added a "feature" where +if you set your spawn in a respawn anchor world +but then replaced it with a bed, you could respawn +at the bed in that world. + +diff --git a/src/main/java/net/minecraft/world/level/block/BedBlock.java b/src/main/java/net/minecraft/world/level/block/BedBlock.java +index b09fa473426a6c7e006a071827e5a68560d6c685..c02c4834ace843633b77fb43eeadd3ddc7b1f743 100644 +--- a/src/main/java/net/minecraft/world/level/block/BedBlock.java ++++ b/src/main/java/net/minecraft/world/level/block/BedBlock.java +@@ -109,6 +109,7 @@ public class BedBlock extends HorizontalDirectionalBlock implements EntityBlock + world.explode((Entity) null, world.damageSources().badRespawnPointExplosion(vec3d), (ExplosionDamageCalculator) null, vec3d, 5.0F, true, Level.ExplosionInteraction.BLOCK); + return InteractionResult.SUCCESS_SERVER; + } else if ((Boolean) state.getValue(BedBlock.OCCUPIED)) { ++ if (!BedBlock.canSetSpawn(world)) return this.explodeBed(state, world, pos); // Paper - check explode first + if (!this.kickVillagerOutOfBed(world, pos)) { + player.displayClientMessage(Component.translatable("block.minecraft.bed.occupied"), true); + } +@@ -166,8 +167,7 @@ public class BedBlock extends HorizontalDirectionalBlock implements EntityBlock + // CraftBukkit end + + public static boolean canSetSpawn(Level world) { +- // CraftBukkit - moved world and biome check into EntityHuman +- return true || world.dimensionType().bedWorks(); ++ return world.dimensionType().bedWorks(); // Paper - actually check if the bed works + } + + private boolean kickVillagerOutOfBed(Level world, BlockPos pos) { diff --git a/patches/server/0803-Fix-demo-flag-not-enabling-demo-mode.patch b/patches/server/0803-Fix-demo-flag-not-enabling-demo-mode.patch new file mode 100644 index 000000000000..1a725719655c --- /dev/null +++ b/patches/server/0803-Fix-demo-flag-not-enabling-demo-mode.patch @@ -0,0 +1,22 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Warrior <50800980+Warriorrrr@users.noreply.github.com> +Date: Fri, 7 Apr 2023 20:11:17 +0200 +Subject: [PATCH] Fix demo flag not enabling demo mode + +https://github.com/PaperMC/Paper/issues/9046 + +diff --git a/src/main/java/net/minecraft/server/Main.java b/src/main/java/net/minecraft/server/Main.java +index 47cd7c66ac37efebf2f63c49d78dd8fe44a70ef8..fc6ce3485dc890f5105a37fe3e344a1460867556 100644 +--- a/src/main/java/net/minecraft/server/Main.java ++++ b/src/main/java/net/minecraft/server/Main.java +@@ -325,7 +325,9 @@ public class Main { + + /* + dedicatedserver1.setPort((Integer) optionset.valueOf(optionspec11)); +- dedicatedserver1.setDemo(optionset.has(optionspec2)); ++ */ ++ dedicatedserver1.setDemo(optionset.has("demo")); // Paper ++ /* + dedicatedserver1.setId((String) optionset.valueOf(optionspec12)); + */ + boolean flag2 = !optionset.has("nogui") && !optionset.nonOptionArguments().contains("nogui"); diff --git a/patches/server/0804-Add-Mob-Experience-reward-API.patch b/patches/server/0804-Add-Mob-Experience-reward-API.patch new file mode 100644 index 000000000000..9e59c458f81e --- /dev/null +++ b/patches/server/0804-Add-Mob-Experience-reward-API.patch @@ -0,0 +1,22 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: GodOfPro <1387ilia@gmail.com> +Date: Tue, 11 Apr 2023 16:31:39 +0430 +Subject: [PATCH] Add Mob Experience reward API + + +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftMob.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftMob.java +index e226a99d00c990a4ca4f21b93fcae7a556e01dbb..95d7015a61098d1d22a501124d6bb8fba1516fe3 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftMob.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftMob.java +@@ -168,4 +168,11 @@ public abstract class CraftMob extends CraftLivingEntity implements Mob { + this.getHandle().setAggressive(aggressive); + } + // Paper end ++ ++ // Paper start ++ @Override ++ public int getPossibleExperienceReward() { ++ return getHandle().getExperienceReward((net.minecraft.server.level.ServerLevel) this.getHandle().level(), null); ++ } ++ // Paper end + } diff --git a/patches/server/0804-Fix-crash-relating-to-bad-recipes-in-furnace-like-ti.patch b/patches/server/0804-Fix-crash-relating-to-bad-recipes-in-furnace-like-ti.patch deleted file mode 100644 index d90e59989716..000000000000 --- a/patches/server/0804-Fix-crash-relating-to-bad-recipes-in-furnace-like-ti.patch +++ /dev/null @@ -1,19 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Jake Potrebic -Date: Sun, 19 Mar 2023 20:36:22 -0700 -Subject: [PATCH] Fix crash relating to bad recipes in furnace-like tile - entities - - -diff --git a/src/main/java/net/minecraft/world/level/block/entity/AbstractFurnaceBlockEntity.java b/src/main/java/net/minecraft/world/level/block/entity/AbstractFurnaceBlockEntity.java -index f1a7054a0ee1842e78338d8984f151b24c352849..65ab9b22f724877b68f4f25aad2831e2cb080b19 100644 ---- a/src/main/java/net/minecraft/world/level/block/entity/AbstractFurnaceBlockEntity.java -+++ b/src/main/java/net/minecraft/world/level/block/entity/AbstractFurnaceBlockEntity.java -@@ -616,6 +616,7 @@ public abstract class AbstractFurnaceBlockEntity extends BaseContainerBlockEntit - Entry entry = (Entry) objectiterator.next(); - - worldserver.getRecipeManager().byKey((ResourceLocation) entry.getKey()).ifPresent((recipeholder) -> { -+ if (!(recipeholder.value() instanceof AbstractCookingRecipe)) return; // Paper - don't process non-cooking recipes - list.add(recipeholder); - AbstractFurnaceBlockEntity.createExperience(worldserver, vec3d, entry.getIntValue(), ((AbstractCookingRecipe) recipeholder.value()).getExperience(), blockposition, entityplayer, itemstack, amount); // CraftBukkit - }); diff --git a/patches/server/0805-Break-redstone-on-top-of-trap-doors-early.patch b/patches/server/0805-Break-redstone-on-top-of-trap-doors-early.patch new file mode 100644 index 000000000000..ded460b32b52 --- /dev/null +++ b/patches/server/0805-Break-redstone-on-top-of-trap-doors-early.patch @@ -0,0 +1,40 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Spottedleaf +Date: Mon, 1 May 2023 18:31:26 -0700 +Subject: [PATCH] Break redstone on top of trap doors early + +This logic hooks into the neighbour update which should be invoked +as a result of redstone powering the trap door. + +diff --git a/src/main/java/net/minecraft/world/level/block/TrapDoorBlock.java b/src/main/java/net/minecraft/world/level/block/TrapDoorBlock.java +index a7d52f2f9c324c5af979fc39b3662ebc880e36fb..872e52e13293a99d45f93d90d8fa7f6aa99d1f3a 100644 +--- a/src/main/java/net/minecraft/world/level/block/TrapDoorBlock.java ++++ b/src/main/java/net/minecraft/world/level/block/TrapDoorBlock.java +@@ -157,7 +157,26 @@ public class TrapDoorBlock extends HorizontalDirectionalBlock implements SimpleW + flag1 = eventRedstone.getNewCurrent() > 0; + } + // CraftBukkit end +- if ((Boolean) state.getValue(TrapDoorBlock.OPEN) != flag1) { ++ // Paper start - break redstone on trapdoors early ++ boolean open = (Boolean) state.getValue(TrapDoorBlock.OPEN) != flag1; ++ // note: this must run before any state for this block/its neighborus are written to the world ++ // we allow the redstone event to fire so that plugins can block ++ if (flag1 && open) { // if we are now powered and it caused the trap door to open ++ // in this case, first check for the redstone on top first ++ BlockPos abovePos = pos.above(); ++ BlockState above = world.getBlockState(abovePos); ++ if (above.getBlock() instanceof RedStoneWireBlock) { ++ world.setBlock(abovePos, Blocks.AIR.defaultBlockState(), Block.UPDATE_CLIENTS | Block.UPDATE_NEIGHBORS); ++ Block.popResource(world, abovePos, new net.minecraft.world.item.ItemStack(net.minecraft.world.item.Items.REDSTONE)); ++ // now check that this didn't change our state ++ if (world.getBlockState(pos) != state) { ++ // our state was changed, so we cannot propagate this update ++ return; ++ } ++ } ++ } ++ if (open) { ++ // Paper end - break redstone on trapdoors early + state = (BlockState) state.setValue(TrapDoorBlock.OPEN, flag1); + this.playSound((Player) null, world, pos, flag1); + } diff --git a/patches/server/0805-Treat-sequence-violations-like-they-should-be.patch b/patches/server/0805-Treat-sequence-violations-like-they-should-be.patch deleted file mode 100644 index e0fce8efcebe..000000000000 --- a/patches/server/0805-Treat-sequence-violations-like-they-should-be.patch +++ /dev/null @@ -1,18 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Shane Freeder -Date: Thu, 30 Mar 2023 03:13:58 +0100 -Subject: [PATCH] Treat sequence violations like they should be - - -diff --git a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java -index a6f58438badb0bdeab67706f4a8e519b202623fe..5a710de60a57c8af4eb083e17f03e801858dc2d9 100644 ---- a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java -+++ b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java -@@ -1976,6 +1976,7 @@ public class ServerGamePacketListenerImpl extends ServerCommonPacketListenerImpl - - public void ackBlockChangesUpTo(int sequence) { - if (sequence < 0) { -+ this.disconnect(Component.literal("Expected packet sequence nr >= 0"), org.bukkit.event.player.PlayerKickEvent.Cause.ILLEGAL_ACTION); // Paper - Treat sequence violations like they should be - throw new IllegalArgumentException("Expected packet sequence nr >= 0"); - } else { - this.ackBlockChangesUpTo = Math.max(sequence, this.ackBlockChangesUpTo); diff --git a/patches/server/0813-Avoid-Lazy-Initialization-for-Enum-Fields.patch b/patches/server/0806-Avoid-Lazy-Initialization-for-Enum-Fields.patch similarity index 100% rename from patches/server/0813-Avoid-Lazy-Initialization-for-Enum-Fields.patch rename to patches/server/0806-Avoid-Lazy-Initialization-for-Enum-Fields.patch diff --git a/patches/server/0806-Prevent-causing-expired-keys-from-impacting-new-join.patch b/patches/server/0806-Prevent-causing-expired-keys-from-impacting-new-join.patch deleted file mode 100644 index ee811c8ba644..000000000000 --- a/patches/server/0806-Prevent-causing-expired-keys-from-impacting-new-join.patch +++ /dev/null @@ -1,61 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Shane Freeder -Date: Mon, 3 Apr 2023 08:55:52 +0100 -Subject: [PATCH] Prevent causing expired keys from impacting new joins - - -diff --git a/src/main/java/net/minecraft/network/protocol/game/ClientboundPlayerInfoUpdatePacket.java b/src/main/java/net/minecraft/network/protocol/game/ClientboundPlayerInfoUpdatePacket.java -index 68c062cbaa030d62d97c9c003651f8fc17a00a6b..6247a21c9c391abf1f6db3482c659593e4f29355 100644 ---- a/src/main/java/net/minecraft/network/protocol/game/ClientboundPlayerInfoUpdatePacket.java -+++ b/src/main/java/net/minecraft/network/protocol/game/ClientboundPlayerInfoUpdatePacket.java -@@ -113,7 +113,15 @@ public class ClientboundPlayerInfoUpdatePacket implements Packet serialized.chatSession = buf.readNullable(RemoteChatSession.Data::read), -- (buf, entry) -> buf.writeNullable(entry.chatSession, RemoteChatSession.Data::write) -+ // Paper start - Prevent causing expired keys from impacting new joins -+ (buf, entry) -> { -+ RemoteChatSession.Data chatSession = entry.chatSession; -+ if (chatSession != null && chatSession.profilePublicKey().hasExpired()) { -+ chatSession = null; -+ } -+ buf.writeNullable(chatSession, RemoteChatSession.Data::write); -+ } -+ // Paper end - Prevent causing expired keys from impacting new joins - ), - UPDATE_GAME_MODE((serialized, buf) -> serialized.gameMode = GameType.byId(buf.readVarInt()), (buf, entry) -> buf.writeVarInt(entry.gameMode().getId())), - UPDATE_LISTED((serialized, buf) -> serialized.listed = buf.readBoolean(), (buf, entry) -> buf.writeBoolean(entry.listed())), -diff --git a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java -index 5a710de60a57c8af4eb083e17f03e801858dc2d9..db9c04d74475e00983cc9df74d615e4b68f83688 100644 ---- a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java -+++ b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java -@@ -295,6 +295,7 @@ public class ServerGamePacketListenerImpl extends ServerCommonPacketListenerImpl - private int knownMovePacketCount; - @Nullable - private RemoteChatSession chatSession; -+ private boolean hasLoggedExpiry = false; // Paper - Prevent causing expired keys from impacting new joins - private SignedMessageChain.Decoder signedMessageDecoder; - private final LastSeenMessagesValidator lastSeenMessages = new LastSeenMessagesValidator(20); - private final MessageSignatureCache messageSignatureCache = MessageSignatureCache.createDefault(); -@@ -402,6 +403,13 @@ public class ServerGamePacketListenerImpl extends ServerCommonPacketListenerImpl - this.disconnect((Component) Component.translatable("multiplayer.disconnect.idling"), org.bukkit.event.player.PlayerKickEvent.Cause.IDLING); // Paper - kick event cause - } - -+ // Paper start - Prevent causing expired keys from impacting new joins -+ if (!hasLoggedExpiry && this.chatSession != null && this.chatSession.profilePublicKey().data().hasExpired()) { -+ LOGGER.info("Player profile key for {} has expired!", this.player.getName().getString()); -+ hasLoggedExpiry = true; -+ } -+ // Paper end - Prevent causing expired keys from impacting new joins -+ - } - - private int getMaximumFlyingTicks(Entity vehicle) { -@@ -3434,6 +3442,7 @@ public class ServerGamePacketListenerImpl extends ServerCommonPacketListenerImpl - - private void resetPlayerChatState(RemoteChatSession session) { - this.chatSession = session; -+ this.hasLoggedExpiry = false; // Paper - Prevent causing expired keys from impacting new joins - this.signedMessageDecoder = session.createMessageDecoder(this.player.getUUID()); - this.chatMessageChain.append(() -> { - this.player.setChatSession(session); diff --git a/patches/server/0814-More-accurate-isInOpenWater-impl.patch b/patches/server/0807-More-accurate-isInOpenWater-impl.patch similarity index 100% rename from patches/server/0814-More-accurate-isInOpenWater-impl.patch rename to patches/server/0807-More-accurate-isInOpenWater-impl.patch diff --git a/patches/server/0807-Prevent-GameEvents-being-fired-from-unloaded-chunks.patch b/patches/server/0807-Prevent-GameEvents-being-fired-from-unloaded-chunks.patch deleted file mode 100644 index 330616349839..000000000000 --- a/patches/server/0807-Prevent-GameEvents-being-fired-from-unloaded-chunks.patch +++ /dev/null @@ -1,22 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Shane Freeder -Date: Wed, 5 Apr 2023 20:15:47 +0100 -Subject: [PATCH] Prevent GameEvents being fired from unloaded chunks - - -diff --git a/src/main/java/net/minecraft/server/level/ServerLevel.java b/src/main/java/net/minecraft/server/level/ServerLevel.java -index 07f7752651095abdd9e9408a419aed7d78173d3a..fd4d1cab675b7b423ac0fdf9a8c9f472ddc68c36 100644 ---- a/src/main/java/net/minecraft/server/level/ServerLevel.java -+++ b/src/main/java/net/minecraft/server/level/ServerLevel.java -@@ -1372,6 +1372,11 @@ public class ServerLevel extends Level implements WorldGenLevel { - - @Override - public void gameEvent(Holder event, Vec3 emitterPos, GameEvent.Context emitter) { -+ // Paper start - Prevent GameEvents being fired from unloaded chunks -+ if (this.getChunkIfLoadedImmediately((Mth.floor(emitterPos.x) >> 4), (Mth.floor(emitterPos.z) >> 4)) == null) { -+ return; -+ } -+ // Paper end - Prevent GameEvents being fired from unloaded chunks - this.gameEventDispatcher.post(event, emitterPos, emitter); - } - diff --git a/patches/server/0808-Expand-PlayerItemMendEvent.patch b/patches/server/0808-Expand-PlayerItemMendEvent.patch new file mode 100644 index 000000000000..93c7b9d1e22f --- /dev/null +++ b/patches/server/0808-Expand-PlayerItemMendEvent.patch @@ -0,0 +1,67 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Jake Potrebic +Date: Thu, 20 Jan 2022 18:11:20 -0800 +Subject: [PATCH] Expand PlayerItemMendEvent + + +diff --git a/src/main/java/net/minecraft/world/entity/ExperienceOrb.java b/src/main/java/net/minecraft/world/entity/ExperienceOrb.java +index 3a7af27bb1ce0cbe56bd3760cd400083daf98d4c..bf0838f574fa3fb9654e087d602b8d380bd7fb28 100644 +--- a/src/main/java/net/minecraft/world/entity/ExperienceOrb.java ++++ b/src/main/java/net/minecraft/world/entity/ExperienceOrb.java +@@ -358,7 +358,10 @@ public class ExperienceOrb extends Entity { + int j = EnchantmentHelper.modifyDurabilityToRepairFromXp(player.serverLevel(), itemstack, amount); + int k = Math.min(j, itemstack.getDamageValue()); + // CraftBukkit start +- org.bukkit.event.player.PlayerItemMendEvent event = CraftEventFactory.callPlayerItemMendEvent(player, this, itemstack, optional.get().inSlot(), k); ++ // Paper start - mending event ++ final int consumedExperience = k > 0 ? k * amount / j : 0; ++ org.bukkit.event.player.PlayerItemMendEvent event = CraftEventFactory.callPlayerItemMendEvent(player, this, itemstack, optional.get().inSlot(), k, consumedExperience); ++ // Paper end - mending event + k = event.getRepairAmount(); + if (event.isCancelled()) { + return amount; +@@ -367,7 +370,7 @@ public class ExperienceOrb extends Entity { + + itemstack.setDamageValue(itemstack.getDamageValue() - k); + if (k > 0) { +- int l = amount - k * amount / j; ++ int l = amount - k * amount / j; // Paper - diff on change - expand PlayerMendEvents + + if (l > 0) { + // this.value = l; // CraftBukkit - update exp value of orb for PlayerItemMendEvent calls // Paper - the value field should not be mutated here because it doesn't take "count" into account +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java +index 3419024a58a3c68b160f16350c72d1250c10d4dd..d48a0d67853fb17cc73bba94c9b6660d2689c8fc 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java +@@ -1877,11 +1877,12 @@ public class CraftPlayer extends CraftHumanEntity implements Player { + handle.serverLevel(), itemstack, amount + ); + int i = Math.min(possibleDurabilityFromXp, itemstack.getDamageValue()); +- org.bukkit.event.player.PlayerItemMendEvent event = org.bukkit.craftbukkit.event.CraftEventFactory.callPlayerItemMendEvent(handle, orb, itemstack, stackEntry.get().inSlot(), i); ++ final int consumedExperience = i * amount / possibleDurabilityFromXp; // Paper - taken from ExperienceOrb#repairPlayerItems ++ org.bukkit.event.player.PlayerItemMendEvent event = org.bukkit.craftbukkit.event.CraftEventFactory.callPlayerItemMendEvent(handle, orb, itemstack, stackEntry.get().inSlot(), i, consumedExperience); + i = event.getRepairAmount(); + orb.discard(org.bukkit.event.entity.EntityRemoveEvent.Cause.DESPAWN); + if (!event.isCancelled()) { +- amount -= i * amount / possibleDurabilityFromXp; ++ amount -= consumedExperience; // Use previously computed variable to reduce diff on change. + itemstack.setDamageValue(itemstack.getDamageValue() - i); + } + } +diff --git a/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java b/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java +index d63b9c666cf6eb1de114c5c89867b8d233267c9e..dd0d19faccbc49b5f9718e2df9f004bb17a9ef7e 100644 +--- a/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java ++++ b/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java +@@ -1284,10 +1284,10 @@ public class CraftEventFactory { + return event; + } + +- public static PlayerItemMendEvent callPlayerItemMendEvent(net.minecraft.world.entity.player.Player entity, net.minecraft.world.entity.ExperienceOrb orb, net.minecraft.world.item.ItemStack nmsMendedItem, net.minecraft.world.entity.EquipmentSlot slot, int repairAmount) { ++ public static PlayerItemMendEvent callPlayerItemMendEvent(net.minecraft.world.entity.player.Player entity, net.minecraft.world.entity.ExperienceOrb orb, net.minecraft.world.item.ItemStack nmsMendedItem, net.minecraft.world.entity.EquipmentSlot slot, int repairAmount, int consumedExperience) { // Paper - Expand PlayerItemMendEvent + Player player = (Player) entity.getBukkitEntity(); + org.bukkit.inventory.ItemStack bukkitStack = CraftItemStack.asCraftMirror(nmsMendedItem); +- PlayerItemMendEvent event = new PlayerItemMendEvent(player, bukkitStack, CraftEquipmentSlot.getSlot(slot), (ExperienceOrb) orb.getBukkitEntity(), repairAmount); ++ PlayerItemMendEvent event = new PlayerItemMendEvent(player, bukkitStack, CraftEquipmentSlot.getSlot(slot), (ExperienceOrb) orb.getBukkitEntity(), repairAmount, consumedExperience); // Paper - Expand PlayerItemMendEvent + Bukkit.getPluginManager().callEvent(event); + return event; + } diff --git a/patches/server/0808-Use-array-for-gamerule-storage.patch b/patches/server/0808-Use-array-for-gamerule-storage.patch deleted file mode 100644 index 194ff8f15b08..000000000000 --- a/patches/server/0808-Use-array-for-gamerule-storage.patch +++ /dev/null @@ -1,63 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Paul Sauve -Date: Sun, 9 May 2021 16:49:49 -0500 -Subject: [PATCH] Use array for gamerule storage - - -diff --git a/src/main/java/net/minecraft/world/level/GameRules.java b/src/main/java/net/minecraft/world/level/GameRules.java -index 51e560d7856f230c5aa2dc32706c3a4996720aa5..89e327bc3a45879fe68887c7aadb077f31a770eb 100644 ---- a/src/main/java/net/minecraft/world/level/GameRules.java -+++ b/src/main/java/net/minecraft/world/level/GameRules.java -@@ -122,6 +122,7 @@ public class GameRules { - worldserver.setDefaultSpawnPos(worldserver.getSharedSpawnPos(), worldserver.getSharedSpawnAngle()); - })); - private final Map, GameRules.Value> rules; -+ private final GameRules.Value[] gameruleArray; // Paper - Perf: Use array for gamerule storage - - private static > GameRules.Key register(String name, GameRules.Category category, GameRules.Type type) { - GameRules.Key gamerules_gamerulekey = new GameRules.Key<>(name, category); -@@ -140,17 +141,30 @@ public class GameRules { - } - - public GameRules() { -- this.rules = (Map) GameRules.GAME_RULE_TYPES.entrySet().stream().collect(ImmutableMap.toImmutableMap(Entry::getKey, (entry) -> { -+ // Paper start - Perf: Use array for gamerule storage -+ this((Map) GameRules.GAME_RULE_TYPES.entrySet().stream().collect(ImmutableMap.toImmutableMap(Entry::getKey, (entry) -> { - return ((GameRules.Type) entry.getValue()).createRule(); -- })); -+ }))); -+ // Paper end - Perf: Use array for gamerule storage - } - - private GameRules(Map, GameRules.Value> rules) { - this.rules = rules; -+ -+ // Paper start - Perf: Use array for gamerule storage -+ int arraySize = rules.keySet().stream().mapToInt(key -> key.gameRuleIndex).max().orElse(-1) + 1; -+ GameRules.Value[] values = new GameRules.Value[arraySize]; -+ -+ for (Entry, GameRules.Value> entry : rules.entrySet()) { -+ values[entry.getKey().gameRuleIndex] = entry.getValue(); -+ } -+ -+ this.gameruleArray = values; -+ // Paper end - Perf: Use array for gamerule storage - } - - public > T getRule(GameRules.Key key) { -- return (T) this.rules.get(key); // CraftBukkit - decompile error -+ return key == null ? null : (T) this.gameruleArray[key.gameRuleIndex]; // Paper - Perf: Use array for gamerule storage - } - - public CompoundTag createTag() { -@@ -209,6 +223,10 @@ public class GameRules { - } - - public static final class Key> { -+ // Paper start - Perf: Use array for gamerule storage -+ private static int lastGameRuleIndex = 0; -+ public final int gameRuleIndex = lastGameRuleIndex++; -+ // Paper end - Perf: Use array for gamerule storage - - final String id; - private final GameRules.Category category; diff --git a/patches/server/0809-Fix-a-couple-of-upstream-bed-issues.patch b/patches/server/0809-Fix-a-couple-of-upstream-bed-issues.patch deleted file mode 100644 index 665db524c5e1..000000000000 --- a/patches/server/0809-Fix-a-couple-of-upstream-bed-issues.patch +++ /dev/null @@ -1,33 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Jake Potrebic -Date: Sun, 9 Apr 2023 21:11:58 -0700 -Subject: [PATCH] Fix a couple of upstream bed issues - -Upstream incorrectly skipped explosion logic if -the bed was occupied and added a "feature" where -if you set your spawn in a respawn anchor world -but then replaced it with a bed, you could respawn -at the bed in that world. - -diff --git a/src/main/java/net/minecraft/world/level/block/BedBlock.java b/src/main/java/net/minecraft/world/level/block/BedBlock.java -index 18b9a62613c08eb5bf63ae26565b0e91e1f44d39..85d598c3354ee62f0fd1b26e485e0084967c0380 100644 ---- a/src/main/java/net/minecraft/world/level/block/BedBlock.java -+++ b/src/main/java/net/minecraft/world/level/block/BedBlock.java -@@ -107,6 +107,7 @@ public class BedBlock extends HorizontalDirectionalBlock implements EntityBlock - world.explode((Entity) null, world.damageSources().badRespawnPointExplosion(vec3d), (ExplosionDamageCalculator) null, vec3d, 5.0F, true, Level.ExplosionInteraction.BLOCK); - return InteractionResult.SUCCESS; - } else if ((Boolean) state.getValue(BedBlock.OCCUPIED)) { -+ if (!BedBlock.canSetSpawn(world)) return this.explodeBed(state, world, pos); // Paper - check explode first - if (!this.kickVillagerOutOfBed(world, pos)) { - player.displayClientMessage(Component.translatable("block.minecraft.bed.occupied"), true); - } -@@ -164,8 +165,7 @@ public class BedBlock extends HorizontalDirectionalBlock implements EntityBlock - // CraftBukkit end - - public static boolean canSetSpawn(Level world) { -- // CraftBukkit - moved world and biome check into EntityHuman -- return true || world.dimensionType().bedWorks(); -+ return world.dimensionType().bedWorks(); // Paper - actually check if the bed works - } - - private boolean kickVillagerOutOfBed(Level world, BlockPos pos) { diff --git a/patches/server/0809-Refresh-ProjectileSource-for-projectiles.patch b/patches/server/0809-Refresh-ProjectileSource-for-projectiles.patch new file mode 100644 index 000000000000..4e706befacd8 --- /dev/null +++ b/patches/server/0809-Refresh-ProjectileSource-for-projectiles.patch @@ -0,0 +1,81 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Jake Potrebic +Date: Tue, 30 May 2023 12:59:10 -0700 +Subject: [PATCH] Refresh ProjectileSource for projectiles + +Makes sure the value returned by Projectile#getShooter in +the API matches the owner UUID specified in the entity nbt. +Previously, after the entity reloaded, Projectile#getShooter +would return null, while the entity still had an owner. + +Also fixes setting the shooter/owner to null actually +clearing the owner. + +Co-authored-by: Warrior <50800980+Warriorrrr@users.noreply.github.com> + +diff --git a/src/main/java/net/minecraft/world/entity/Entity.java b/src/main/java/net/minecraft/world/entity/Entity.java +index 5340d6d911a7e586468f2b7e2e74b3386d1dc72d..90b47da5c6df05565b522636e08c4ebb7165c351 100644 +--- a/src/main/java/net/minecraft/world/entity/Entity.java ++++ b/src/main/java/net/minecraft/world/entity/Entity.java +@@ -393,6 +393,7 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess + public boolean inWorld = false; + public boolean generation; + public int maxAirTicks = this.getDefaultMaxAirSupply(); // CraftBukkit - SPIGOT-6907: re-implement LivingEntity#setMaximumAir() ++ @Nullable // Paper - Refresh ProjectileSource for projectiles + public org.bukkit.projectiles.ProjectileSource projectileSource; // For projectiles only + public boolean lastDamageCancelled; // SPIGOT-5339, SPIGOT-6252, SPIGOT-6777: Keep track if the event was canceled + public boolean persistentInvisibility = false; +diff --git a/src/main/java/net/minecraft/world/entity/projectile/Projectile.java b/src/main/java/net/minecraft/world/entity/projectile/Projectile.java +index bf7f81d99af300e89834981eab32568a3747d270..52d539d54113c4dcd2d8d748ae0abf6dec5bfc72 100644 +--- a/src/main/java/net/minecraft/world/entity/projectile/Projectile.java ++++ b/src/main/java/net/minecraft/world/entity/projectile/Projectile.java +@@ -63,17 +63,35 @@ public abstract class Projectile extends Entity implements TraceableEntity { + this.ownerUUID = entity.getUUID(); + this.cachedOwner = entity; + } +- this.projectileSource = (entity != null && entity.getBukkitEntity() instanceof ProjectileSource) ? (ProjectileSource) entity.getBukkitEntity() : null; // CraftBukkit +- ++ // Paper start - Refresh ProjectileSource for projectiles ++ else { ++ this.ownerUUID = null; ++ this.cachedOwner = null; ++ this.projectileSource = null; ++ } ++ // Paper end - Refresh ProjectileSource for projectiles ++ this.refreshProjectileSource(false); // Paper ++ } ++ // Paper start - Refresh ProjectileSource for projectiles ++ public void refreshProjectileSource(boolean fillCache) { ++ if (fillCache) { ++ this.getOwner(); ++ } ++ if (this.cachedOwner != null && !this.cachedOwner.isRemoved() && this.projectileSource == null && this.cachedOwner.getBukkitEntity() instanceof ProjectileSource projSource) { ++ this.projectileSource = projSource; ++ } + } ++ // Paper end - Refresh ProjectileSource for projectiles + + @Nullable + @Override + public Entity getOwner() { + if (this.cachedOwner != null && !this.cachedOwner.isRemoved()) { ++ this.refreshProjectileSource(false); // Paper - Refresh ProjectileSource for projectiles + return this.cachedOwner; + } else if (this.ownerUUID != null) { + this.cachedOwner = this.findOwner(this.ownerUUID); ++ this.refreshProjectileSource(false); // Paper - Refresh ProjectileSource for projectiles + return this.cachedOwner; + } else { + return null; +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/AbstractProjectile.java b/src/main/java/org/bukkit/craftbukkit/entity/AbstractProjectile.java +index de4fb2654c7895cfd83ad694455ee56cb708c2f2..591af9d0d2fdc9953415979fc97a4a00afd85885 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/AbstractProjectile.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/AbstractProjectile.java +@@ -60,6 +60,7 @@ public abstract class AbstractProjectile extends CraftEntity implements Projecti + + @Override + public final org.bukkit.projectiles.ProjectileSource getShooter() { ++ this.getHandle().refreshProjectileSource(true); // Paper - Refresh ProjectileSource for projectiles + return this.getHandle().projectileSource; + } + diff --git a/patches/server/0817-Add-transient-modifier-API.patch b/patches/server/0810-Add-transient-modifier-API.patch similarity index 100% rename from patches/server/0817-Add-transient-modifier-API.patch rename to patches/server/0810-Add-transient-modifier-API.patch diff --git a/patches/server/0810-Fix-demo-flag-not-enabling-demo-mode.patch b/patches/server/0810-Fix-demo-flag-not-enabling-demo-mode.patch deleted file mode 100644 index d64dd23e3758..000000000000 --- a/patches/server/0810-Fix-demo-flag-not-enabling-demo-mode.patch +++ /dev/null @@ -1,22 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Warrior <50800980+Warriorrrr@users.noreply.github.com> -Date: Fri, 7 Apr 2023 20:11:17 +0200 -Subject: [PATCH] Fix demo flag not enabling demo mode - -https://github.com/PaperMC/Paper/issues/9046 - -diff --git a/src/main/java/net/minecraft/server/Main.java b/src/main/java/net/minecraft/server/Main.java -index 13e1a914d4523f1c192db2a9a1ee6522e0ee27da..c33f85b570f159ab465b5a10a8044a81f2797f43 100644 ---- a/src/main/java/net/minecraft/server/Main.java -+++ b/src/main/java/net/minecraft/server/Main.java -@@ -325,7 +325,9 @@ public class Main { - - /* - dedicatedserver1.setPort((Integer) optionset.valueOf(optionspec11)); -- dedicatedserver1.setDemo(optionset.has(optionspec2)); -+ */ -+ dedicatedserver1.setDemo(optionset.has("demo")); // Paper -+ /* - dedicatedserver1.setId((String) optionset.valueOf(optionspec12)); - */ - boolean flag2 = !optionset.has("nogui") && !optionset.nonOptionArguments().contains("nogui"); diff --git a/patches/server/0811-Add-Mob-Experience-reward-API.patch b/patches/server/0811-Add-Mob-Experience-reward-API.patch deleted file mode 100644 index f82f765bebe5..000000000000 --- a/patches/server/0811-Add-Mob-Experience-reward-API.patch +++ /dev/null @@ -1,30 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: GodOfPro <1387ilia@gmail.com> -Date: Tue, 11 Apr 2023 16:31:39 +0430 -Subject: [PATCH] Add Mob Experience reward API - - -diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftMob.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftMob.java -index 921594a78ea511337434b29b5bc1a037eb30992c..deb66c04abefb4a88521483db1612e494bd27164 100644 ---- a/src/main/java/org/bukkit/craftbukkit/entity/CraftMob.java -+++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftMob.java -@@ -1,6 +1,7 @@ - package org.bukkit.craftbukkit.entity; - - import com.google.common.base.Preconditions; -+import net.minecraft.server.level.ServerLevel; - import net.minecraft.sounds.SoundEvent; - import org.bukkit.Sound; - import org.bukkit.craftbukkit.CraftLootTable; -@@ -167,4 +168,11 @@ public abstract class CraftMob extends CraftLivingEntity implements Mob { - this.getHandle().setAggressive(aggressive); - } - // Paper end -+ -+ // Paper start -+ @Override -+ public int getPossibleExperienceReward() { -+ return getHandle().getExperienceReward((ServerLevel) this.getHandle().level(), null); -+ } -+ // Paper end - } diff --git a/patches/server/0811-Fix-block-place-logic.patch b/patches/server/0811-Fix-block-place-logic.patch new file mode 100644 index 000000000000..4f22f03cabff --- /dev/null +++ b/patches/server/0811-Fix-block-place-logic.patch @@ -0,0 +1,49 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Lulu13022002 <41980282+Lulu13022002@users.noreply.github.com> +Date: Mon, 3 Apr 2023 18:46:49 +0200 +Subject: [PATCH] Fix block place logic + +Fix several issues when a player interact with a block: +* the place sound isn't played for the dispensed shulker block +* desync of the jukebox blocks between bukkit handler and the vanilla interaction +* poi can desync when the BlockPhysicsEvent is cancelled + +diff --git a/src/main/java/net/minecraft/world/item/BlockItem.java b/src/main/java/net/minecraft/world/item/BlockItem.java +index 44cc12a3338b5f0448c88192c8674cd36531db34..d59120f0304823361cc4112f5583323945df4229 100644 +--- a/src/main/java/net/minecraft/world/item/BlockItem.java ++++ b/src/main/java/net/minecraft/world/item/BlockItem.java +@@ -122,7 +122,7 @@ public class BlockItem extends Item { + + SoundType soundeffecttype = iblockdata1.getSoundType(); + +- // world.playSound(entityhuman, blockposition, this.getPlaceSound(iblockdata1), SoundCategory.BLOCKS, (soundeffecttype.getVolume() + 1.0F) / 2.0F, soundeffecttype.getPitch() * 0.8F); ++ if (entityhuman == null) world.playSound(entityhuman, blockposition, this.getPlaceSound(iblockdata1), net.minecraft.sounds.SoundSource.BLOCKS, (soundeffecttype.getVolume() + 1.0F) / 2.0F, soundeffecttype.getPitch() * 0.8F); // Paper - Fix block place logic; reintroduce this for the dispenser (i.e the shulker) + world.gameEvent((Holder) GameEvent.BLOCK_PLACE, blockposition, GameEvent.Context.of(entityhuman, iblockdata1)); + itemstack.consume(1, entityhuman); + return InteractionResult.SUCCESS; +diff --git a/src/main/java/net/minecraft/world/level/Level.java b/src/main/java/net/minecraft/world/level/Level.java +index f0c2187a92de633a1d4cc7e71ff62cbe30ce8774..18c011c1943867dbc4abee338b03b9be499876dd 100644 +--- a/src/main/java/net/minecraft/world/level/Level.java ++++ b/src/main/java/net/minecraft/world/level/Level.java +@@ -550,17 +550,18 @@ public abstract class Level implements LevelAccessor, AutoCloseable { + // CraftBukkit start + iblockdata1.updateIndirectNeighbourShapes(this, blockposition, k, j - 1); // Don't call an event for the old block to limit event spam + CraftWorld world = ((ServerLevel) this).getWorld(); ++ boolean cancelledUpdates = false; // Paper - Fix block place logic + if (world != null && ((ServerLevel)this).hasPhysicsEvent) { // Paper - BlockPhysicsEvent + BlockPhysicsEvent event = new BlockPhysicsEvent(world.getBlockAt(blockposition.getX(), blockposition.getY(), blockposition.getZ()), CraftBlockData.fromData(iblockdata)); + this.getCraftServer().getPluginManager().callEvent(event); + +- if (event.isCancelled()) { +- return; +- } ++ cancelledUpdates = event.isCancelled(); // Paper - Fix block place logic + } + // CraftBukkit end ++ if (!cancelledUpdates) { // Paper - Fix block place logic + iblockdata.updateNeighbourShapes(this, blockposition, k, j - 1); + iblockdata.updateIndirectNeighbourShapes(this, blockposition, k, j - 1); ++ } // Paper - Fix block place logic + } + + // CraftBukkit start - SPIGOT-5710 diff --git a/patches/server/0812-Break-redstone-on-top-of-trap-doors-early.patch b/patches/server/0812-Break-redstone-on-top-of-trap-doors-early.patch deleted file mode 100644 index 11c427e8082f..000000000000 --- a/patches/server/0812-Break-redstone-on-top-of-trap-doors-early.patch +++ /dev/null @@ -1,40 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Spottedleaf -Date: Mon, 1 May 2023 18:31:26 -0700 -Subject: [PATCH] Break redstone on top of trap doors early - -This logic hooks into the neighbour update which should be invoked -as a result of redstone powering the trap door. - -diff --git a/src/main/java/net/minecraft/world/level/block/TrapDoorBlock.java b/src/main/java/net/minecraft/world/level/block/TrapDoorBlock.java -index 82e38b01446d35aa9745be6ff3c7647b46379682..336fcf4af0ffb416b5595a9e65172f36cc36aaa3 100644 ---- a/src/main/java/net/minecraft/world/level/block/TrapDoorBlock.java -+++ b/src/main/java/net/minecraft/world/level/block/TrapDoorBlock.java -@@ -153,7 +153,26 @@ public class TrapDoorBlock extends HorizontalDirectionalBlock implements SimpleW - flag1 = eventRedstone.getNewCurrent() > 0; - } - // CraftBukkit end -- if ((Boolean) state.getValue(TrapDoorBlock.OPEN) != flag1) { -+ // Paper start - break redstone on trapdoors early -+ boolean open = (Boolean) state.getValue(TrapDoorBlock.OPEN) != flag1; -+ // note: this must run before any state for this block/its neighborus are written to the world -+ // we allow the redstone event to fire so that plugins can block -+ if (flag1 && open) { // if we are now powered and it caused the trap door to open -+ // in this case, first check for the redstone on top first -+ BlockPos abovePos = pos.above(); -+ BlockState above = world.getBlockState(abovePos); -+ if (above.getBlock() instanceof RedStoneWireBlock) { -+ world.setBlock(abovePos, Blocks.AIR.defaultBlockState(), Block.UPDATE_CLIENTS | Block.UPDATE_NEIGHBORS); -+ Block.popResource(world, abovePos, new net.minecraft.world.item.ItemStack(net.minecraft.world.item.Items.REDSTONE)); -+ // now check that this didn't change our state -+ if (world.getBlockState(pos) != state) { -+ // our state was changed, so we cannot propagate this update -+ return; -+ } -+ } -+ } -+ if (open) { -+ // Paper end - break redstone on trapdoors early - state = (BlockState) state.setValue(TrapDoorBlock.OPEN, flag1); - this.playSound((Player) null, world, pos, flag1); - } diff --git a/patches/server/0812-Fix-spigot-sound-playing-for-BlockItem-ItemStacks.patch b/patches/server/0812-Fix-spigot-sound-playing-for-BlockItem-ItemStacks.patch new file mode 100644 index 000000000000..69e6a10e5784 --- /dev/null +++ b/patches/server/0812-Fix-spigot-sound-playing-for-BlockItem-ItemStacks.patch @@ -0,0 +1,23 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Owen1212055 <23108066+Owen1212055@users.noreply.github.com> +Date: Thu, 8 Jun 2023 20:23:13 -0400 +Subject: [PATCH] Fix spigot sound playing for BlockItem ItemStacks + + +diff --git a/src/main/java/net/minecraft/world/item/ItemStack.java b/src/main/java/net/minecraft/world/item/ItemStack.java +index 90a55f00c36903d52630c51bf69322973a2b5274..1b7d797dac1ff7ee945cf4ef8c6861475a31903d 100644 +--- a/src/main/java/net/minecraft/world/item/ItemStack.java ++++ b/src/main/java/net/minecraft/world/item/ItemStack.java +@@ -577,7 +577,11 @@ public final class ItemStack implements DataComponentHolder { + + // SPIGOT-1288 - play sound stripped from ItemBlock + if (this.item instanceof BlockItem) { +- SoundType soundeffecttype = ((BlockItem) this.item).getBlock().defaultBlockState().getSoundType(); // TODO: not strictly correct, however currently only affects decorated pots ++ // Paper start - Fix spigot sound playing for BlockItem ItemStacks ++ BlockPos position = new net.minecraft.world.item.context.BlockPlaceContext(context).getClickedPos(); ++ net.minecraft.world.level.block.state.BlockState blockData = world.getBlockState(position); ++ SoundType soundeffecttype = blockData.getSoundType(); ++ // Paper end - Fix spigot sound playing for BlockItem ItemStacks + world.playSound(entityhuman, blockposition, soundeffecttype.getPlaceSound(), SoundSource.BLOCKS, (soundeffecttype.getVolume() + 1.0F) / 2.0F, soundeffecttype.getPitch() * 0.8F); + } + diff --git a/patches/server/0813-Call-BlockGrowEvent-for-missing-blocks.patch b/patches/server/0813-Call-BlockGrowEvent-for-missing-blocks.patch new file mode 100644 index 000000000000..d48638c26137 --- /dev/null +++ b/patches/server/0813-Call-BlockGrowEvent-for-missing-blocks.patch @@ -0,0 +1,39 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Lulu13022002 <41980282+Lulu13022002@users.noreply.github.com> +Date: Fri, 9 Jun 2023 13:04:42 +0200 +Subject: [PATCH] Call BlockGrowEvent for missing blocks + +Call the event for pitcher crops and sniffer egg + +diff --git a/src/main/java/net/minecraft/world/level/block/PitcherCropBlock.java b/src/main/java/net/minecraft/world/level/block/PitcherCropBlock.java +index ea6135edc76c06b7cd55930b620dfb05f939e4f6..640e439524cc1e5960ba99f8bd555c7dee8edbf5 100644 +--- a/src/main/java/net/minecraft/world/level/block/PitcherCropBlock.java ++++ b/src/main/java/net/minecraft/world/level/block/PitcherCropBlock.java +@@ -142,7 +142,7 @@ public class PitcherCropBlock extends DoublePlantBlock implements BonemealableBl + int i = Math.min(state.getValue(AGE) + amount, 4); + if (this.canGrow(world, pos, state, i)) { + BlockState blockState = state.setValue(AGE, Integer.valueOf(i)); +- world.setBlock(pos, blockState, 2); ++ if (!org.bukkit.craftbukkit.event.CraftEventFactory.handleBlockGrowEvent(world, pos, blockState, 2)) return; // Paper + if (isDouble(i)) { + world.setBlock(pos.above(), blockState.setValue(HALF, DoubleBlockHalf.UPPER), 3); + } +diff --git a/src/main/java/net/minecraft/world/level/block/SnifferEggBlock.java b/src/main/java/net/minecraft/world/level/block/SnifferEggBlock.java +index 2df28caefff893f319924ae5fd376c8aeb0a2158..c6b7cfd78bc0c4cb64eada507876c293541890f4 100644 +--- a/src/main/java/net/minecraft/world/level/block/SnifferEggBlock.java ++++ b/src/main/java/net/minecraft/world/level/block/SnifferEggBlock.java +@@ -72,8 +72,13 @@ public class SnifferEggBlock extends Block { + @Override + public void tick(BlockState state, ServerLevel world, BlockPos pos, RandomSource random) { + if (!this.isReadyToHatch(state)) { ++ // Paper start ++ if (!org.bukkit.craftbukkit.event.CraftEventFactory.handleBlockGrowEvent(world, pos, state.setValue(HATCH, Integer.valueOf(this.getHatchLevel(state) + 1)), 2)) { ++ this.rescheduleTick(world, pos); ++ return; ++ } ++ // Paper end + world.playSound(null, pos, SoundEvents.SNIFFER_EGG_CRACK, SoundSource.BLOCKS, 0.7F, 0.9F + random.nextFloat() * 0.2F); +- world.setBlock(pos, state.setValue(HATCH, Integer.valueOf(this.getHatchLevel(state) + 1)), 2); + } else { + // Paper start - Call BlockFadeEvent + if (org.bukkit.craftbukkit.event.CraftEventFactory.callBlockFadeEvent(world, pos, state.getFluidState().createLegacyBlock()).isCancelled()) { diff --git a/patches/server/0814-Don-t-enforce-icanhasbukkit-default-if-alias-block-e.patch b/patches/server/0814-Don-t-enforce-icanhasbukkit-default-if-alias-block-e.patch new file mode 100644 index 000000000000..4cc150197bde --- /dev/null +++ b/patches/server/0814-Don-t-enforce-icanhasbukkit-default-if-alias-block-e.patch @@ -0,0 +1,23 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: TheMeinerLP +Date: Tue, 13 Jun 2023 16:10:59 +0200 +Subject: [PATCH] Don't enforce icanhasbukkit default if alias block exists + + +diff --git a/src/main/java/org/bukkit/craftbukkit/CraftServer.java b/src/main/java/org/bukkit/craftbukkit/CraftServer.java +index b84dc11212aba4c06375cdbefa91c09d86a2b957..a7b79c9ec51f037287fad609c29b240e2071f2f8 100644 +--- a/src/main/java/org/bukkit/craftbukkit/CraftServer.java ++++ b/src/main/java/org/bukkit/craftbukkit/CraftServer.java +@@ -358,7 +358,11 @@ public final class CraftServer implements Server { + } + this.commandsConfiguration = YamlConfiguration.loadConfiguration(this.getCommandsConfigFile()); + this.commandsConfiguration.options().copyDefaults(true); +- this.commandsConfiguration.setDefaults(YamlConfiguration.loadConfiguration(new InputStreamReader(this.getClass().getClassLoader().getResourceAsStream("configurations/commands.yml"), Charsets.UTF_8))); ++ // Paper start - don't enforce icanhasbukkit default if alias block exists ++ final YamlConfiguration commandsDefaults = YamlConfiguration.loadConfiguration(new InputStreamReader(this.getClass().getClassLoader().getResourceAsStream("configurations/commands.yml"), Charsets.UTF_8)); ++ if (this.commandsConfiguration.contains("aliases")) commandsDefaults.set("aliases", null); ++ this.commandsConfiguration.setDefaults(commandsDefaults); ++ // Paper end - don't enforce icanhasbukkit default if alias block exists + this.saveCommandsConfig(); + + // Migrate aliases from old file and add previously implicit $1- to pass all arguments diff --git a/patches/server/0815-Expand-PlayerItemMendEvent.patch b/patches/server/0815-Expand-PlayerItemMendEvent.patch deleted file mode 100644 index 289fae7adb7f..000000000000 --- a/patches/server/0815-Expand-PlayerItemMendEvent.patch +++ /dev/null @@ -1,67 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Jake Potrebic -Date: Thu, 20 Jan 2022 18:11:20 -0800 -Subject: [PATCH] Expand PlayerItemMendEvent - - -diff --git a/src/main/java/net/minecraft/world/entity/ExperienceOrb.java b/src/main/java/net/minecraft/world/entity/ExperienceOrb.java -index a758b2456acac23095fe4619ae10300a034cb460..a58ff67052fb5f33782f8b5c83465ec03ef1d073 100644 ---- a/src/main/java/net/minecraft/world/entity/ExperienceOrb.java -+++ b/src/main/java/net/minecraft/world/entity/ExperienceOrb.java -@@ -354,7 +354,10 @@ public class ExperienceOrb extends Entity { - int j = EnchantmentHelper.modifyDurabilityToRepairFromXp(player.serverLevel(), itemstack, amount); - int k = Math.min(j, itemstack.getDamageValue()); - // CraftBukkit start -- org.bukkit.event.player.PlayerItemMendEvent event = CraftEventFactory.callPlayerItemMendEvent(player, this, itemstack, optional.get().inSlot(), k); -+ // Paper start - mending event -+ final int consumedExperience = k > 0 ? k * amount / j : 0; -+ org.bukkit.event.player.PlayerItemMendEvent event = CraftEventFactory.callPlayerItemMendEvent(player, this, itemstack, optional.get().inSlot(), k, consumedExperience); -+ // Paper end - mending event - k = event.getRepairAmount(); - if (event.isCancelled()) { - return amount; -@@ -363,7 +366,7 @@ public class ExperienceOrb extends Entity { - - itemstack.setDamageValue(itemstack.getDamageValue() - k); - if (k > 0) { -- int l = amount - k * amount / j; -+ int l = amount - k * amount / j; // Paper - diff on change - expand PlayerMendEvents - - if (l > 0) { - // this.value = l; // CraftBukkit - update exp value of orb for PlayerItemMendEvent calls // Paper - the value field should not be mutated here because it doesn't take "count" into account -diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java -index 06a3ccb90f23fc357e5cbc6e9173baab4b218955..3945a6ab723deee3ec3ebb5fbc726ce16bbca411 100644 ---- a/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java -+++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java -@@ -1853,11 +1853,12 @@ public class CraftPlayer extends CraftHumanEntity implements Player { - handle.serverLevel(), itemstack, amount - ); - int i = Math.min(possibleDurabilityFromXp, itemstack.getDamageValue()); -- org.bukkit.event.player.PlayerItemMendEvent event = org.bukkit.craftbukkit.event.CraftEventFactory.callPlayerItemMendEvent(handle, orb, itemstack, stackEntry.get().inSlot(), i); -+ final int consumedExperience = i * amount / possibleDurabilityFromXp; // Paper - taken from ExperienceOrb#repairPlayerItems -+ org.bukkit.event.player.PlayerItemMendEvent event = org.bukkit.craftbukkit.event.CraftEventFactory.callPlayerItemMendEvent(handle, orb, itemstack, stackEntry.get().inSlot(), i, consumedExperience); - i = event.getRepairAmount(); - orb.discard(org.bukkit.event.entity.EntityRemoveEvent.Cause.DESPAWN); - if (!event.isCancelled()) { -- amount -= i * amount / possibleDurabilityFromXp; -+ amount -= consumedExperience; // Use previously computed variable to reduce diff on change. - itemstack.setDamageValue(itemstack.getDamageValue() - i); - } - } -diff --git a/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java b/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java -index a0b387ddf3cc1e3473c6b35175ac8b68c717cfbe..9586837f25464396c0695e87fa278c91ca90183a 100644 ---- a/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java -+++ b/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java -@@ -1286,10 +1286,10 @@ public class CraftEventFactory { - return event; - } - -- public static PlayerItemMendEvent callPlayerItemMendEvent(net.minecraft.world.entity.player.Player entity, net.minecraft.world.entity.ExperienceOrb orb, net.minecraft.world.item.ItemStack nmsMendedItem, net.minecraft.world.entity.EquipmentSlot slot, int repairAmount) { -+ public static PlayerItemMendEvent callPlayerItemMendEvent(net.minecraft.world.entity.player.Player entity, net.minecraft.world.entity.ExperienceOrb orb, net.minecraft.world.item.ItemStack nmsMendedItem, net.minecraft.world.entity.EquipmentSlot slot, int repairAmount, int consumedExperience) { // Paper - Expand PlayerItemMendEvent - Player player = (Player) entity.getBukkitEntity(); - org.bukkit.inventory.ItemStack bukkitStack = CraftItemStack.asCraftMirror(nmsMendedItem); -- PlayerItemMendEvent event = new PlayerItemMendEvent(player, bukkitStack, CraftEquipmentSlot.getSlot(slot), (ExperienceOrb) orb.getBukkitEntity(), repairAmount); -+ PlayerItemMendEvent event = new PlayerItemMendEvent(player, bukkitStack, CraftEquipmentSlot.getSlot(slot), (ExperienceOrb) orb.getBukkitEntity(), repairAmount, consumedExperience); // Paper - Expand PlayerItemMendEvent - Bukkit.getPluginManager().callEvent(event); - return event; - } diff --git a/patches/server/0822-fix-MapLike-spam-for-missing-key-selector.patch b/patches/server/0815-fix-MapLike-spam-for-missing-key-selector.patch similarity index 100% rename from patches/server/0822-fix-MapLike-spam-for-missing-key-selector.patch rename to patches/server/0815-fix-MapLike-spam-for-missing-key-selector.patch diff --git a/patches/server/0823-Fix-sniffer-removeExploredLocation.patch b/patches/server/0816-Fix-sniffer-removeExploredLocation.patch similarity index 100% rename from patches/server/0823-Fix-sniffer-removeExploredLocation.patch rename to patches/server/0816-Fix-sniffer-removeExploredLocation.patch diff --git a/patches/server/0816-Refresh-ProjectileSource-for-projectiles.patch b/patches/server/0816-Refresh-ProjectileSource-for-projectiles.patch deleted file mode 100644 index 59deace23461..000000000000 --- a/patches/server/0816-Refresh-ProjectileSource-for-projectiles.patch +++ /dev/null @@ -1,85 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Jake Potrebic -Date: Tue, 30 May 2023 12:59:10 -0700 -Subject: [PATCH] Refresh ProjectileSource for projectiles - -Makes sure the value returned by Projectile#getShooter in -the API matches the owner UUID specified in the entity nbt. -Previously, after the entity reloaded, Projectile#getShooter -would return null, while the entity still had an owner. - -Also fixes setting the shooter/owner to null actually -clearing the owner. - -Co-authored-by: Warrior <50800980+Warriorrrr@users.noreply.github.com> - -diff --git a/src/main/java/net/minecraft/world/entity/Entity.java b/src/main/java/net/minecraft/world/entity/Entity.java -index 34e175b28f7c9120b58fc8e2b65ca978c7f301b5..cd362a91ad31dbae94fdb5a8c71839576f397ea1 100644 ---- a/src/main/java/net/minecraft/world/entity/Entity.java -+++ b/src/main/java/net/minecraft/world/entity/Entity.java -@@ -390,6 +390,7 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess - public boolean inWorld = false; - public boolean generation; - public int maxAirTicks = this.getDefaultMaxAirSupply(); // CraftBukkit - SPIGOT-6907: re-implement LivingEntity#setMaximumAir() -+ @Nullable // Paper - Refresh ProjectileSource for projectiles - public org.bukkit.projectiles.ProjectileSource projectileSource; // For projectiles only - public boolean lastDamageCancelled; // SPIGOT-5339, SPIGOT-6252, SPIGOT-6777: Keep track if the event was canceled - public boolean persistentInvisibility = false; -diff --git a/src/main/java/net/minecraft/world/entity/projectile/Projectile.java b/src/main/java/net/minecraft/world/entity/projectile/Projectile.java -index 63ffa02f820d88f865ae604712edcf2ac13f0bff..f2bdd95a6ae77400742d87bcae35c09fb8b047ba 100644 ---- a/src/main/java/net/minecraft/world/entity/projectile/Projectile.java -+++ b/src/main/java/net/minecraft/world/entity/projectile/Projectile.java -@@ -57,14 +57,31 @@ public abstract class Projectile extends Entity implements TraceableEntity { - this.ownerUUID = entity.getUUID(); - this.cachedOwner = entity; - } -- this.projectileSource = (entity != null && entity.getBukkitEntity() instanceof ProjectileSource) ? (ProjectileSource) entity.getBukkitEntity() : null; // CraftBukkit -- -+ // Paper start - Refresh ProjectileSource for projectiles -+ else { -+ this.ownerUUID = null; -+ this.cachedOwner = null; -+ this.projectileSource = null; -+ } -+ // Paper end - Refresh ProjectileSource for projectiles -+ this.refreshProjectileSource(false); // Paper -+ } -+ // Paper start - Refresh ProjectileSource for projectiles -+ public void refreshProjectileSource(boolean fillCache) { -+ if (fillCache) { -+ this.getOwner(); -+ } -+ if (this.cachedOwner != null && !this.cachedOwner.isRemoved() && this.projectileSource == null && this.cachedOwner.getBukkitEntity() instanceof ProjectileSource projSource) { -+ this.projectileSource = projSource; -+ } - } -+ // Paper end - Refresh ProjectileSource for projectiles - - @Nullable - @Override - public Entity getOwner() { - if (this.cachedOwner != null && !this.cachedOwner.isRemoved()) { -+ this.refreshProjectileSource(false); // Paper - Refresh ProjectileSource for projectiles - return this.cachedOwner; - } else { - if (this.ownerUUID != null) { -@@ -74,6 +91,7 @@ public abstract class Projectile extends Entity implements TraceableEntity { - ServerLevel worldserver = (ServerLevel) world; - - this.cachedOwner = worldserver.getEntity(this.ownerUUID); -+ this.refreshProjectileSource(false); // Paper - Refresh ProjectileSource for projectiles - return this.cachedOwner; - } - } -diff --git a/src/main/java/org/bukkit/craftbukkit/entity/AbstractProjectile.java b/src/main/java/org/bukkit/craftbukkit/entity/AbstractProjectile.java -index de4fb2654c7895cfd83ad694455ee56cb708c2f2..591af9d0d2fdc9953415979fc97a4a00afd85885 100644 ---- a/src/main/java/org/bukkit/craftbukkit/entity/AbstractProjectile.java -+++ b/src/main/java/org/bukkit/craftbukkit/entity/AbstractProjectile.java -@@ -60,6 +60,7 @@ public abstract class AbstractProjectile extends CraftEntity implements Projecti - - @Override - public final org.bukkit.projectiles.ProjectileSource getShooter() { -+ this.getHandle().refreshProjectileSource(true); // Paper - Refresh ProjectileSource for projectiles - return this.getHandle().projectileSource; - } - diff --git a/patches/server/0817-Add-method-to-remove-all-active-potion-effects.patch b/patches/server/0817-Add-method-to-remove-all-active-potion-effects.patch new file mode 100644 index 000000000000..e730bcf1d4b4 --- /dev/null +++ b/patches/server/0817-Add-method-to-remove-all-active-potion-effects.patch @@ -0,0 +1,24 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Jake Potrebic +Date: Sat, 17 Jun 2023 13:17:25 -0700 +Subject: [PATCH] Add method to remove all active potion effects + + +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftLivingEntity.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftLivingEntity.java +index 203e06fdff824ba67dce0e026f7ea5b746d7f258..a62d17b72c675120b447e625cb3dc437681bdf20 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftLivingEntity.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftLivingEntity.java +@@ -573,6 +573,13 @@ public class CraftLivingEntity extends CraftEntity implements LivingEntity { + return effects; + } + ++ // Paper start - LivingEntity#clearActivePotionEffects(); ++ @Override ++ public boolean clearActivePotionEffects() { ++ return this.getHandle().removeAllEffects(EntityPotionEffectEvent.Cause.PLUGIN); ++ } ++ // Paper end ++ + @Override + public T launchProjectile(Class projectile) { + return this.launchProjectile(projectile, null); diff --git a/patches/server/0818-Add-event-for-player-editing-sign.patch b/patches/server/0818-Add-event-for-player-editing-sign.patch new file mode 100644 index 000000000000..50295978391f --- /dev/null +++ b/patches/server/0818-Add-event-for-player-editing-sign.patch @@ -0,0 +1,93 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: by77er +Date: Mon, 12 Jun 2023 12:56:46 -0400 +Subject: [PATCH] Add event for player editing sign + + +diff --git a/src/main/java/net/minecraft/world/item/ItemStack.java b/src/main/java/net/minecraft/world/item/ItemStack.java +index 1b7d797dac1ff7ee945cf4ef8c6861475a31903d..f81b3e050f21ce10ea86892d9a6bee9a42561514 100644 +--- a/src/main/java/net/minecraft/world/item/ItemStack.java ++++ b/src/main/java/net/minecraft/world/item/ItemStack.java +@@ -556,7 +556,7 @@ public final class ItemStack implements DataComponentHolder { + try { + if (world.getBlockEntity(SignItem.openSign) instanceof SignBlockEntity tileentitysign) { + if (world.getBlockState(SignItem.openSign).getBlock() instanceof SignBlock blocksign) { +- blocksign.openTextEdit(entityhuman, tileentitysign, true, org.bukkit.event.player.PlayerSignOpenEvent.Cause.PLACE); // Craftbukkit ++ blocksign.openTextEdit(entityhuman, tileentitysign, true, io.papermc.paper.event.player.PlayerOpenSignEvent.Cause.PLACE); // Craftbukkit // Paper - Add PlayerOpenSignEvent + } + } + } finally { +diff --git a/src/main/java/net/minecraft/world/level/block/SignBlock.java b/src/main/java/net/minecraft/world/level/block/SignBlock.java +index 725a207f80651939c1d793f8aa4ba27e8e4da568..b212fe323f048dab40c0ccbb9327c9fc73b9e03a 100644 +--- a/src/main/java/net/minecraft/world/level/block/SignBlock.java ++++ b/src/main/java/net/minecraft/world/level/block/SignBlock.java +@@ -140,7 +140,7 @@ public abstract class SignBlock extends BaseEntityBlock implements SimpleWaterlo + } else if (flag1) { + return InteractionResult.SUCCESS_SERVER; + } else if (!this.otherPlayerIsEditingSign(player, tileentitysign) && player.mayBuild() && this.hasEditableText(player, tileentitysign, flag)) { +- this.openTextEdit(player, tileentitysign, flag, org.bukkit.event.player.PlayerSignOpenEvent.Cause.INTERACT); // CraftBukkit ++ this.openTextEdit(player, tileentitysign, flag, io.papermc.paper.event.player.PlayerOpenSignEvent.Cause.INTERACT); // Paper - Add PlayerOpenSignEvent + return InteractionResult.SUCCESS_SERVER; + } else { + return InteractionResult.PASS; +@@ -185,16 +185,33 @@ public abstract class SignBlock extends BaseEntityBlock implements SimpleWaterlo + return blockpropertywood; + } + ++ @io.papermc.paper.annotation.DoNotUse @Deprecated // Paper - Add PlayerOpenSignEvent + public void openTextEdit(Player player, SignBlockEntity blockEntity, boolean front) { +- // Craftbukkit start +- this.openTextEdit(player, blockEntity, front, org.bukkit.event.player.PlayerSignOpenEvent.Cause.UNKNOWN); +- } +- +- public void openTextEdit(Player entityhuman, SignBlockEntity tileentitysign, boolean flag, org.bukkit.event.player.PlayerSignOpenEvent.Cause cause) { +- if (!org.bukkit.craftbukkit.event.CraftEventFactory.callPlayerSignOpenEvent(entityhuman, tileentitysign, flag, cause)) { ++ // Paper start - Add PlayerOpenSignEvent ++ this.openTextEdit(player, blockEntity, front, io.papermc.paper.event.player.PlayerOpenSignEvent.Cause.UNKNOWN); ++ } ++ public void openTextEdit(Player entityhuman, SignBlockEntity tileentitysign, boolean flag, io.papermc.paper.event.player.PlayerOpenSignEvent.Cause cause) { ++ org.bukkit.entity.Player bukkitPlayer = (org.bukkit.entity.Player) entityhuman.getBukkitEntity(); ++ org.bukkit.block.Block bukkitBlock = org.bukkit.craftbukkit.block.CraftBlock.at(tileentitysign.getLevel(), tileentitysign.getBlockPos()); ++ org.bukkit.craftbukkit.block.CraftSign bukkitSign = (org.bukkit.craftbukkit.block.CraftSign) org.bukkit.craftbukkit.block.CraftBlockStates.getBlockState(bukkitBlock); ++ io.papermc.paper.event.player.PlayerOpenSignEvent event = new io.papermc.paper.event.player.PlayerOpenSignEvent( ++ bukkitPlayer, ++ bukkitSign, ++ flag ? org.bukkit.block.sign.Side.FRONT : org.bukkit.block.sign.Side.BACK, ++ cause); ++ if (!event.callEvent()) return; ++ if (org.bukkit.event.player.PlayerSignOpenEvent.getHandlerList().getRegisteredListeners().length > 0) { ++ final org.bukkit.event.player.PlayerSignOpenEvent.Cause legacyCause = switch (cause) { ++ case PLACE -> org.bukkit.event.player.PlayerSignOpenEvent.Cause.PLACE; ++ case PLUGIN -> org.bukkit.event.player.PlayerSignOpenEvent.Cause.PLUGIN; ++ case INTERACT -> org.bukkit.event.player.PlayerSignOpenEvent.Cause.INTERACT; ++ case UNKNOWN -> org.bukkit.event.player.PlayerSignOpenEvent.Cause.UNKNOWN; ++ }; ++ if (!org.bukkit.craftbukkit.event.CraftEventFactory.callPlayerSignOpenEvent(entityhuman, tileentitysign, flag, legacyCause)) { ++ // Paper end - Add PlayerOpenSignEvent + return; + } +- // Craftbukkit end ++ } // Paper - Add PlayerOpenSignEvent + tileentitysign.setAllowedPlayerEditor(entityhuman.getUUID()); + entityhuman.openTextEdit(tileentitysign, flag); + } +diff --git a/src/main/java/org/bukkit/craftbukkit/block/CraftSign.java b/src/main/java/org/bukkit/craftbukkit/block/CraftSign.java +index a12702cdf36c75572e661b5b5758270f5058c181..8303343ecca91076839f4436d6b3a3bf4739c2fd 100644 +--- a/src/main/java/org/bukkit/craftbukkit/block/CraftSign.java ++++ b/src/main/java/org/bukkit/craftbukkit/block/CraftSign.java +@@ -168,9 +168,15 @@ public class CraftSign extends CraftBlockEntityState< + Preconditions.checkArgument(sign.isPlaced(), "Sign must be placed"); + Preconditions.checkArgument(sign.getWorld() == player.getWorld(), "Sign must be in same world as Player"); + ++ // Paper start - Add PlayerOpenSignEvent ++ io.papermc.paper.event.player.PlayerOpenSignEvent event = new io.papermc.paper.event.player.PlayerOpenSignEvent((Player) player, sign, side, io.papermc.paper.event.player.PlayerOpenSignEvent.Cause.PLUGIN); ++ if (!event.callEvent()) return; ++ if (PlayerSignOpenEvent.getHandlerList().getRegisteredListeners().length > 0) { ++ // Paper end - Add PlayerOpenSignEvent + if (!CraftEventFactory.callPlayerSignOpenEvent(player, sign, side, PlayerSignOpenEvent.Cause.PLUGIN)) { + return; + } ++ } // Paper - Add PlayerOpenSignEvent + + SignBlockEntity handle = ((CraftSign) sign).getTileEntity(); + handle.setAllowedPlayerEditor(player.getUniqueId()); diff --git a/patches/server/0818-Fix-block-place-logic.patch b/patches/server/0818-Fix-block-place-logic.patch deleted file mode 100644 index accea4ee7c0b..000000000000 --- a/patches/server/0818-Fix-block-place-logic.patch +++ /dev/null @@ -1,49 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Lulu13022002 <41980282+Lulu13022002@users.noreply.github.com> -Date: Mon, 3 Apr 2023 18:46:49 +0200 -Subject: [PATCH] Fix block place logic - -Fix several issues when a player interact with a block: -* the place sound isn't played for the dispensed shulker block -* desync of the jukebox blocks between bukkit handler and the vanilla interaction -* poi can desync when the BlockPhysicsEvent is cancelled - -diff --git a/src/main/java/net/minecraft/world/item/BlockItem.java b/src/main/java/net/minecraft/world/item/BlockItem.java -index 7d76cdc59984b156628273c8357485eb10046007..7180996027f70aef7afe32fb2adfce6431429401 100644 ---- a/src/main/java/net/minecraft/world/item/BlockItem.java -+++ b/src/main/java/net/minecraft/world/item/BlockItem.java -@@ -128,7 +128,7 @@ public class BlockItem extends Item { - - SoundType soundeffecttype = iblockdata1.getSoundType(); - -- // world.playSound(entityhuman, blockposition, this.getPlaceSound(iblockdata1), SoundCategory.BLOCKS, (soundeffecttype.getVolume() + 1.0F) / 2.0F, soundeffecttype.getPitch() * 0.8F); -+ if (entityhuman == null) world.playSound(entityhuman, blockposition, this.getPlaceSound(iblockdata1), net.minecraft.sounds.SoundSource.BLOCKS, (soundeffecttype.getVolume() + 1.0F) / 2.0F, soundeffecttype.getPitch() * 0.8F); // Paper - Fix block place logic; reintroduce this for the dispenser (i.e the shulker) - world.gameEvent((Holder) GameEvent.BLOCK_PLACE, blockposition, GameEvent.Context.of(entityhuman, iblockdata1)); - itemstack.consume(1, entityhuman); - return InteractionResult.sidedSuccess(world.isClientSide); -diff --git a/src/main/java/net/minecraft/world/level/Level.java b/src/main/java/net/minecraft/world/level/Level.java -index cc92d2e8b77c75da1d8b850c3bc251e8ac221c24..0a3e56302470f239d4840e4e32d2a0ce4611ff65 100644 ---- a/src/main/java/net/minecraft/world/level/Level.java -+++ b/src/main/java/net/minecraft/world/level/Level.java -@@ -552,17 +552,18 @@ public abstract class Level implements LevelAccessor, AutoCloseable { - // CraftBukkit start - iblockdata1.updateIndirectNeighbourShapes(this, blockposition, k, j - 1); // Don't call an event for the old block to limit event spam - CraftWorld world = ((ServerLevel) this).getWorld(); -+ boolean cancelledUpdates = false; // Paper - Fix block place logic - if (world != null && ((ServerLevel)this).hasPhysicsEvent) { // Paper - BlockPhysicsEvent - BlockPhysicsEvent event = new BlockPhysicsEvent(world.getBlockAt(blockposition.getX(), blockposition.getY(), blockposition.getZ()), CraftBlockData.fromData(iblockdata)); - this.getCraftServer().getPluginManager().callEvent(event); - -- if (event.isCancelled()) { -- return; -- } -+ cancelledUpdates = event.isCancelled(); // Paper - Fix block place logic - } - // CraftBukkit end -+ if (!cancelledUpdates) { // Paper - Fix block place logic - iblockdata.updateNeighbourShapes(this, blockposition, k, j - 1); - iblockdata.updateIndirectNeighbourShapes(this, blockposition, k, j - 1); -+ } // Paper - Fix block place logic - } - - // CraftBukkit start - SPIGOT-5710 diff --git a/patches/server/0819-Fix-spigot-sound-playing-for-BlockItem-ItemStacks.patch b/patches/server/0819-Fix-spigot-sound-playing-for-BlockItem-ItemStacks.patch deleted file mode 100644 index c44ec5216d03..000000000000 --- a/patches/server/0819-Fix-spigot-sound-playing-for-BlockItem-ItemStacks.patch +++ /dev/null @@ -1,23 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Owen1212055 <23108066+Owen1212055@users.noreply.github.com> -Date: Thu, 8 Jun 2023 20:23:13 -0400 -Subject: [PATCH] Fix spigot sound playing for BlockItem ItemStacks - - -diff --git a/src/main/java/net/minecraft/world/item/ItemStack.java b/src/main/java/net/minecraft/world/item/ItemStack.java -index e64cc91b416bbbefe6aadf1c6b685346cf258ab4..276858d23be2f9443cc364046a55b9c9a88bf4ea 100644 ---- a/src/main/java/net/minecraft/world/item/ItemStack.java -+++ b/src/main/java/net/minecraft/world/item/ItemStack.java -@@ -571,7 +571,11 @@ public final class ItemStack implements DataComponentHolder { - - // SPIGOT-1288 - play sound stripped from ItemBlock - if (this.item instanceof BlockItem) { -- SoundType soundeffecttype = ((BlockItem) this.item).getBlock().defaultBlockState().getSoundType(); // TODO: not strictly correct, however currently only affects decorated pots -+ // Paper start - Fix spigot sound playing for BlockItem ItemStacks -+ BlockPos position = new net.minecraft.world.item.context.BlockPlaceContext(context).getClickedPos(); -+ net.minecraft.world.level.block.state.BlockState blockData = world.getBlockState(position); -+ SoundType soundeffecttype = blockData.getSoundType(); -+ // Paper end - Fix spigot sound playing for BlockItem ItemStacks - world.playSound(entityhuman, blockposition, soundeffecttype.getPlaceSound(), SoundSource.BLOCKS, (soundeffecttype.getVolume() + 1.0F) / 2.0F, soundeffecttype.getPitch() * 0.8F); - } - diff --git a/patches/server/0819-Only-tick-item-frames-if-players-can-see-it.patch b/patches/server/0819-Only-tick-item-frames-if-players-can-see-it.patch new file mode 100644 index 000000000000..c7c6cd499f71 --- /dev/null +++ b/patches/server/0819-Only-tick-item-frames-if-players-can-see-it.patch @@ -0,0 +1,20 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Redned +Date: Mon, 19 Jun 2023 15:45:53 -0500 +Subject: [PATCH] Only tick item frames if players can see it + +In the event that an item frame cannot be seen by any players, ticking the item frame every tick is unnecessary. This can be a very hot section of the entity tracker when lots of item frames are present on a server, so this reduces the logic which speeds it up. + +diff --git a/src/main/java/net/minecraft/server/level/ServerEntity.java b/src/main/java/net/minecraft/server/level/ServerEntity.java +index bc0f1aa61e68d2a8638d89c10bc5c71922d057f9..3cdcc4f44608d24550f2a8c6f3f5ce675d7777c5 100644 +--- a/src/main/java/net/minecraft/server/level/ServerEntity.java ++++ b/src/main/java/net/minecraft/server/level/ServerEntity.java +@@ -116,7 +116,7 @@ public class ServerEntity { + + Entity entity = this.entity; + +- if (entity instanceof ItemFrame entityitemframe) { ++ if (!this.trackedPlayers.isEmpty() && entity instanceof ItemFrame entityitemframe) { // Paper - Perf: Only tick item frames if players can see it + if (true || this.tickCount % 10 == 0) { // CraftBukkit - Moved below, should always enter this block + ItemStack itemstack = entityitemframe.getItem(); + diff --git a/patches/server/0820-Call-BlockGrowEvent-for-missing-blocks.patch b/patches/server/0820-Call-BlockGrowEvent-for-missing-blocks.patch deleted file mode 100644 index 895a528de8ee..000000000000 --- a/patches/server/0820-Call-BlockGrowEvent-for-missing-blocks.patch +++ /dev/null @@ -1,39 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Lulu13022002 <41980282+Lulu13022002@users.noreply.github.com> -Date: Fri, 9 Jun 2023 13:04:42 +0200 -Subject: [PATCH] Call BlockGrowEvent for missing blocks - -Call the event for pitcher crops and sniffer egg - -diff --git a/src/main/java/net/minecraft/world/level/block/PitcherCropBlock.java b/src/main/java/net/minecraft/world/level/block/PitcherCropBlock.java -index d06e3892cf42723f8e3f621b5497c5348fa1a715..5f1ec3f69295760b7b8097916c82cbf9ddd49700 100644 ---- a/src/main/java/net/minecraft/world/level/block/PitcherCropBlock.java -+++ b/src/main/java/net/minecraft/world/level/block/PitcherCropBlock.java -@@ -133,7 +133,7 @@ public class PitcherCropBlock extends DoublePlantBlock implements BonemealableBl - int i = Math.min(state.getValue(AGE) + amount, 4); - if (this.canGrow(world, pos, state, i)) { - BlockState blockState = state.setValue(AGE, Integer.valueOf(i)); -- world.setBlock(pos, blockState, 2); -+ if (!org.bukkit.craftbukkit.event.CraftEventFactory.handleBlockGrowEvent(world, pos, blockState, 2)) return; // Paper - if (isDouble(i)) { - world.setBlock(pos.above(), blockState.setValue(HALF, DoubleBlockHalf.UPPER), 3); - } -diff --git a/src/main/java/net/minecraft/world/level/block/SnifferEggBlock.java b/src/main/java/net/minecraft/world/level/block/SnifferEggBlock.java -index f53808e200bd83ab80954ec5c1e9c14250302be8..b943384eb6a4612993556036f0d3beec6939a559 100644 ---- a/src/main/java/net/minecraft/world/level/block/SnifferEggBlock.java -+++ b/src/main/java/net/minecraft/world/level/block/SnifferEggBlock.java -@@ -71,8 +71,13 @@ public class SnifferEggBlock extends Block { - @Override - public void tick(BlockState state, ServerLevel world, BlockPos pos, RandomSource random) { - if (!this.isReadyToHatch(state)) { -+ // Paper start -+ if (!org.bukkit.craftbukkit.event.CraftEventFactory.handleBlockGrowEvent(world, pos, state.setValue(HATCH, Integer.valueOf(this.getHatchLevel(state) + 1)), 2)) { -+ this.rescheduleTick(world, pos); -+ return; -+ } -+ // Paper end - world.playSound(null, pos, SoundEvents.SNIFFER_EGG_CRACK, SoundSource.BLOCKS, 0.7F, 0.9F + random.nextFloat() * 0.2F); -- world.setBlock(pos, state.setValue(HATCH, Integer.valueOf(this.getHatchLevel(state) + 1)), 2); - } else { - // Paper start - Call BlockFadeEvent - if (org.bukkit.craftbukkit.event.CraftEventFactory.callBlockFadeEvent(world, pos, state.getFluidState().createLegacyBlock()).isCancelled()) { diff --git a/patches/server/0820-Fix-cmd-permission-levels-for-command-blocks.patch b/patches/server/0820-Fix-cmd-permission-levels-for-command-blocks.patch new file mode 100644 index 000000000000..aa96a27db68b --- /dev/null +++ b/patches/server/0820-Fix-cmd-permission-levels-for-command-blocks.patch @@ -0,0 +1,90 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Jake Potrebic +Date: Mon, 24 Jan 2022 15:32:02 -0800 +Subject: [PATCH] Fix cmd permission levels for command blocks + + +diff --git a/src/main/java/net/minecraft/commands/CommandSourceStack.java b/src/main/java/net/minecraft/commands/CommandSourceStack.java +index f31c5d665678c3163ed4469f8e9d395b890c1bbe..fc0c60b22844ed010aede2fa125b9fa440d3de80 100644 +--- a/src/main/java/net/minecraft/commands/CommandSourceStack.java ++++ b/src/main/java/net/minecraft/commands/CommandSourceStack.java +@@ -206,10 +206,29 @@ public class CommandSourceStack implements ExecutionCommandSource= level; + } + ++ // Paper start - Fix permission levels for command blocks ++ private boolean forceRespectPermissionLevel() { ++ return this.source == CommandSource.NULL || (this.source instanceof final net.minecraft.world.level.BaseCommandBlock commandBlock && commandBlock.getLevel().paperConfig().commandBlocks.forceFollowPermLevel); ++ } ++ // Paper end - Fix permission levels for command blocks ++ + // CraftBukkit start + public boolean hasPermission(int i, String bukkitPermission) { +- // World is null when loading functions +- return ((this.getLevel() == null || !this.getLevel().getCraftServer().ignoreVanillaPermissions) && this.permissionLevel >= i) || this.getBukkitSender().hasPermission(bukkitPermission); ++ // Paper start - Fix permission levels for command blocks ++ final java.util.function.BooleanSupplier hasBukkitPerm = () -> this.source == CommandSource.NULL /*treat NULL as having all bukkit perms*/ || this.getBukkitSender().hasPermission(bukkitPermission); // lazily check bukkit perms to the benefit of custom permission setups ++ // if the server is null, we must check the vanilla perm level system ++ // if ignoreVanillaPermissions is true, we can skip vanilla perms and just run the bukkit perm check ++ //noinspection ConstantValue ++ if (this.getServer() == null || !this.getServer().server.ignoreVanillaPermissions) { // server & level are null for command function loading ++ final boolean hasPermLevel = this.permissionLevel >= i; ++ if (this.forceRespectPermissionLevel()) { // NULL CommandSource and command blocks (if setting is enabled) should always pass the vanilla perm check ++ return hasPermLevel && hasBukkitPerm.getAsBoolean(); ++ } else { // otherwise check vanilla perm first then bukkit perm, matching upstream behavior ++ return hasPermLevel || hasBukkitPerm.getAsBoolean(); ++ } ++ } ++ return hasBukkitPerm.getAsBoolean(); ++ // Paper end - Fix permission levels for command blocks + } + // CraftBukkit end + +diff --git a/src/main/java/net/minecraft/commands/Commands.java b/src/main/java/net/minecraft/commands/Commands.java +index fd12046fab797fd845ad8521f94147480dfba5da..fe9f638db3525893beed565ef9b7ac2fc76318bd 100644 +--- a/src/main/java/net/minecraft/commands/Commands.java ++++ b/src/main/java/net/minecraft/commands/Commands.java +@@ -300,16 +300,7 @@ public class Commands { + String[] args = command.split(" "); + if (args.length == 0) return; // Paper - empty commands shall not be dispatched + +- String cmd = args[0]; +- if (cmd.startsWith("minecraft:")) cmd = cmd.substring("minecraft:".length()); +- if (cmd.startsWith("bukkit:")) cmd = cmd.substring("bukkit:".length()); +- +- // Block disallowed commands +- if (cmd.equalsIgnoreCase("stop") || cmd.equalsIgnoreCase("kick") || cmd.equalsIgnoreCase("op") +- || cmd.equalsIgnoreCase("deop") || cmd.equalsIgnoreCase("ban") || cmd.equalsIgnoreCase("ban-ip") +- || cmd.equalsIgnoreCase("pardon") || cmd.equalsIgnoreCase("pardon-ip") || cmd.equalsIgnoreCase("reload")) { +- return; +- } ++ // Paper - Fix permission levels for command blocks + + // Handle vanilla commands; + if (sender.getLevel().getCraftServer().getCommandBlockOverride(args[0])) { +diff --git a/src/main/java/net/minecraft/world/entity/vehicle/MinecartCommandBlock.java b/src/main/java/net/minecraft/world/entity/vehicle/MinecartCommandBlock.java +index d7321275cf963ba4fd838226c7f705ea0f2e1ac6..56bae8ea1e7b115e85a701209f3badbd29912b28 100644 +--- a/src/main/java/net/minecraft/world/entity/vehicle/MinecartCommandBlock.java ++++ b/src/main/java/net/minecraft/world/entity/vehicle/MinecartCommandBlock.java +@@ -133,7 +133,7 @@ public class MinecartCommandBlock extends AbstractMinecart { + + @Override + public CommandSourceStack createCommandSourceStack() { +- return new CommandSourceStack(this, MinecartCommandBlock.this.position(), MinecartCommandBlock.this.getRotationVector(), this.getLevel(), 2, this.getName().getString(), MinecartCommandBlock.this.getDisplayName(), this.getLevel().getServer(), MinecartCommandBlock.this); ++ return new CommandSourceStack(this, MinecartCommandBlock.this.position(), MinecartCommandBlock.this.getRotationVector(), this.getLevel(), this.getLevel().paperConfig().commandBlocks.permissionsLevel, this.getName().getString(), MinecartCommandBlock.this.getDisplayName(), this.getLevel().getServer(), MinecartCommandBlock.this); // Paper - configurable command block perm level + } + + @Override +diff --git a/src/main/java/net/minecraft/world/level/block/entity/CommandBlockEntity.java b/src/main/java/net/minecraft/world/level/block/entity/CommandBlockEntity.java +index faa0a3248217fb27dae5b918099f8a63eee6f586..a02c5feb8f2ffe6bb6070650a45762476e1ff9ac 100644 +--- a/src/main/java/net/minecraft/world/level/block/entity/CommandBlockEntity.java ++++ b/src/main/java/net/minecraft/world/level/block/entity/CommandBlockEntity.java +@@ -58,7 +58,7 @@ public class CommandBlockEntity extends BlockEntity { + public CommandSourceStack createCommandSourceStack() { + Direction enumdirection = (Direction) CommandBlockEntity.this.getBlockState().getValue(CommandBlock.FACING); + +- return new CommandSourceStack(this, Vec3.atCenterOf(CommandBlockEntity.this.worldPosition), new Vec2(0.0F, enumdirection.toYRot()), this.getLevel(), 2, this.getName().getString(), this.getName(), this.getLevel().getServer(), (Entity) null); ++ return new CommandSourceStack(this, Vec3.atCenterOf(CommandBlockEntity.this.worldPosition), new Vec2(0.0F, enumdirection.toYRot()), this.getLevel(), this.getLevel().paperConfig().commandBlocks.permissionsLevel, this.getName().getString(), this.getName(), this.getLevel().getServer(), (Entity) null); // Paper - configurable command block perm level + } + + @Override diff --git a/patches/server/0821-Add-option-to-disable-block-updates.patch b/patches/server/0821-Add-option-to-disable-block-updates.patch new file mode 100644 index 000000000000..0ea835ecc917 --- /dev/null +++ b/patches/server/0821-Add-option-to-disable-block-updates.patch @@ -0,0 +1,179 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Boy +Date: Sun, 18 Jun 2023 17:45:33 +0200 +Subject: [PATCH] Add option to disable block updates + + +diff --git a/src/main/java/net/minecraft/world/level/block/ChorusPlantBlock.java b/src/main/java/net/minecraft/world/level/block/ChorusPlantBlock.java +index bf2790fae091ab9d7f4ec4b2ab0f103190c31984..5d3ca961b325a39efcd6b398a27f9a4d300b35e5 100644 +--- a/src/main/java/net/minecraft/world/level/block/ChorusPlantBlock.java ++++ b/src/main/java/net/minecraft/world/level/block/ChorusPlantBlock.java +@@ -38,6 +38,7 @@ public class ChorusPlantBlock extends PipeBlock { + + @Override + public BlockState getStateForPlacement(BlockPlaceContext ctx) { ++ if (io.papermc.paper.configuration.GlobalConfiguration.get().blockUpdates.disableChorusPlantUpdates) return this.defaultBlockState(); // Paper - add option to disable block updates + return getStateWithConnections(ctx.getLevel(), ctx.getClickedPos(), this.defaultBlockState()); + } + +@@ -68,6 +69,7 @@ public class ChorusPlantBlock extends PipeBlock { + BlockState neighborState, + RandomSource random + ) { ++ if (io.papermc.paper.configuration.GlobalConfiguration.get().blockUpdates.disableChorusPlantUpdates) return state; // Paper - add option to disable block updates + if (!state.canSurvive(world, pos)) { + tickView.scheduleTick(pos, this, 1); + return super.updateShape(state, world, tickView, pos, direction, neighborPos, neighborState, random); +@@ -79,6 +81,7 @@ public class ChorusPlantBlock extends PipeBlock { + + @Override + protected void tick(BlockState state, ServerLevel world, BlockPos pos, RandomSource random) { ++ if (io.papermc.paper.configuration.GlobalConfiguration.get().blockUpdates.disableChorusPlantUpdates) return; // Paper - add option to disable block updates + if (!state.canSurvive(world, pos)) { + world.destroyBlock(pos, true); + } +@@ -86,6 +89,7 @@ public class ChorusPlantBlock extends PipeBlock { + + @Override + protected boolean canSurvive(BlockState state, LevelReader world, BlockPos pos) { ++ if (io.papermc.paper.configuration.GlobalConfiguration.get().blockUpdates.disableChorusPlantUpdates) return true; // Paper - add option to disable block updates + BlockState blockState = world.getBlockState(pos.below()); + boolean bl = !world.getBlockState(pos.above()).isAir() && !blockState.isAir(); + +diff --git a/src/main/java/net/minecraft/world/level/block/HugeMushroomBlock.java b/src/main/java/net/minecraft/world/level/block/HugeMushroomBlock.java +index 0e0441646bd21a14a8bea576e5400ce937ebea01..437ad7051bad2d7dccc1a9ae764bdc5dcbe88612 100644 +--- a/src/main/java/net/minecraft/world/level/block/HugeMushroomBlock.java ++++ b/src/main/java/net/minecraft/world/level/block/HugeMushroomBlock.java +@@ -45,6 +45,7 @@ public class HugeMushroomBlock extends Block { + + @Override + public BlockState getStateForPlacement(BlockPlaceContext ctx) { ++ if (io.papermc.paper.configuration.GlobalConfiguration.get().blockUpdates.disableMushroomBlockUpdates) return this.defaultBlockState(); // Paper - add option to disable block updates + BlockGetter blockGetter = ctx.getLevel(); + BlockPos blockPos = ctx.getClickedPos(); + return this.defaultBlockState() +@@ -67,6 +68,7 @@ public class HugeMushroomBlock extends Block { + BlockState neighborState, + RandomSource random + ) { ++ if (io.papermc.paper.configuration.GlobalConfiguration.get().blockUpdates.disableMushroomBlockUpdates) return state; // Paper - add option to disable block updates + return neighborState.is(this) + ? state.setValue(PROPERTY_BY_DIRECTION.get(direction), Boolean.valueOf(false)) + : super.updateShape(state, world, tickView, pos, direction, neighborPos, neighborState, random); +@@ -74,6 +76,7 @@ public class HugeMushroomBlock extends Block { + + @Override + protected BlockState rotate(BlockState state, Rotation rotation) { ++ if (io.papermc.paper.configuration.GlobalConfiguration.get().blockUpdates.disableMushroomBlockUpdates) return state; // Paper - add option to disable block updates + return state.setValue(PROPERTY_BY_DIRECTION.get(rotation.rotate(Direction.NORTH)), state.getValue(NORTH)) + .setValue(PROPERTY_BY_DIRECTION.get(rotation.rotate(Direction.SOUTH)), state.getValue(SOUTH)) + .setValue(PROPERTY_BY_DIRECTION.get(rotation.rotate(Direction.EAST)), state.getValue(EAST)) +@@ -84,6 +87,7 @@ public class HugeMushroomBlock extends Block { + + @Override + protected BlockState mirror(BlockState state, Mirror mirror) { ++ if (io.papermc.paper.configuration.GlobalConfiguration.get().blockUpdates.disableMushroomBlockUpdates) return state; // Paper - add option to disable block updates + return state.setValue(PROPERTY_BY_DIRECTION.get(mirror.mirror(Direction.NORTH)), state.getValue(NORTH)) + .setValue(PROPERTY_BY_DIRECTION.get(mirror.mirror(Direction.SOUTH)), state.getValue(SOUTH)) + .setValue(PROPERTY_BY_DIRECTION.get(mirror.mirror(Direction.EAST)), state.getValue(EAST)) +diff --git a/src/main/java/net/minecraft/world/level/block/NoteBlock.java b/src/main/java/net/minecraft/world/level/block/NoteBlock.java +index 71fd7a467a4cb89cad8d2541366fd4add9115e04..6582db84c5307257f16c321453491cf24e40c9c7 100644 +--- a/src/main/java/net/minecraft/world/level/block/NoteBlock.java ++++ b/src/main/java/net/minecraft/world/level/block/NoteBlock.java +@@ -68,11 +68,13 @@ public class NoteBlock extends Block { + + @Override + public BlockState getStateForPlacement(BlockPlaceContext ctx) { ++ if (io.papermc.paper.configuration.GlobalConfiguration.get().blockUpdates.disableNoteblockUpdates) return this.defaultBlockState(); // Paper - place without considering instrument + return this.setInstrument(ctx.getLevel(), ctx.getClickedPos(), this.defaultBlockState()); + } + + @Override + protected BlockState updateShape(BlockState state, LevelReader world, ScheduledTickAccess tickView, BlockPos pos, Direction direction, BlockPos neighborPos, BlockState neighborState, RandomSource random) { ++ if (io.papermc.paper.configuration.GlobalConfiguration.get().blockUpdates.disableNoteblockUpdates) return state; // Paper - prevent noteblock instrument from updating + boolean flag = direction.getAxis() == Direction.Axis.Y; + + return flag ? this.setInstrument(world, pos, state) : super.updateShape(state, world, tickView, pos, direction, neighborPos, neighborState, random); +@@ -80,6 +82,7 @@ public class NoteBlock extends Block { + + @Override + protected void neighborChanged(BlockState state, Level world, BlockPos pos, Block sourceBlock, @Nullable Orientation wireOrientation, boolean notify) { ++ if (io.papermc.paper.configuration.GlobalConfiguration.get().blockUpdates.disableNoteblockUpdates) return; // Paper - prevent noteblock powered-state from updating + boolean flag1 = world.hasNeighborSignal(pos); + + if (flag1 != (Boolean) state.getValue(NoteBlock.POWERED)) { +@@ -116,7 +119,7 @@ public class NoteBlock extends Block { + @Override + protected InteractionResult useWithoutItem(BlockState state, Level world, BlockPos pos, Player player, BlockHitResult hit) { + if (!world.isClientSide) { +- state = (BlockState) state.cycle(NoteBlock.NOTE); ++ if (!io.papermc.paper.configuration.GlobalConfiguration.get().blockUpdates.disableNoteblockUpdates) state = (BlockState) state.cycle(NoteBlock.NOTE); // Paper - prevent noteblock note from updating + world.setBlock(pos, state, 3); + this.playNote(player, state, world, pos); + player.awardStat(Stats.TUNE_NOTEBLOCK); +diff --git a/src/main/java/net/minecraft/world/level/block/TripWireBlock.java b/src/main/java/net/minecraft/world/level/block/TripWireBlock.java +index f079e5a9aa098225acf09ed9b4aa7ddbc2381270..74cce7874809dcbce2718ec3840bb6bb3127e871 100644 +--- a/src/main/java/net/minecraft/world/level/block/TripWireBlock.java ++++ b/src/main/java/net/minecraft/world/level/block/TripWireBlock.java +@@ -68,6 +68,7 @@ public class TripWireBlock extends Block { + + @Override + public BlockState getStateForPlacement(BlockPlaceContext ctx) { ++ if (io.papermc.paper.configuration.GlobalConfiguration.get().blockUpdates.disableTripwireUpdates) return this.defaultBlockState(); // Paper - place tripwire without updating + Level world = ctx.getLevel(); + BlockPos blockposition = ctx.getClickedPos(); + +@@ -76,11 +77,13 @@ public class TripWireBlock extends Block { + + @Override + protected BlockState updateShape(BlockState state, LevelReader world, ScheduledTickAccess tickView, BlockPos pos, Direction direction, BlockPos neighborPos, BlockState neighborState, RandomSource random) { ++ if (io.papermc.paper.configuration.GlobalConfiguration.get().blockUpdates.disableTripwireUpdates) return state; // Paper - prevent tripwire from updating + return direction.getAxis().isHorizontal() ? (BlockState) state.setValue((Property) TripWireBlock.PROPERTY_BY_DIRECTION.get(direction), this.shouldConnectTo(neighborState, direction)) : super.updateShape(state, world, tickView, pos, direction, neighborPos, neighborState, random); + } + + @Override + protected void onPlace(BlockState state, Level world, BlockPos pos, BlockState oldState, boolean notify) { ++ if (io.papermc.paper.configuration.GlobalConfiguration.get().blockUpdates.disableTripwireUpdates) return; // Paper - prevent adjacent tripwires from updating + if (!oldState.is(state.getBlock())) { + this.updateSource(world, pos, state); + } +@@ -88,6 +91,7 @@ public class TripWireBlock extends Block { + + @Override + protected void onRemove(BlockState state, Level world, BlockPos pos, BlockState newState, boolean moved) { ++ if (io.papermc.paper.configuration.GlobalConfiguration.get().blockUpdates.disableTripwireUpdates) return; // Paper - prevent adjacent tripwires from updating + if (!moved && !state.is(newState.getBlock())) { + this.updateSource(world, pos, (BlockState) state.setValue(TripWireBlock.POWERED, true)); + } +@@ -95,6 +99,7 @@ public class TripWireBlock extends Block { + + @Override + public BlockState playerWillDestroy(Level world, BlockPos pos, BlockState state, Player player) { ++ if (io.papermc.paper.configuration.GlobalConfiguration.get().blockUpdates.disableTripwireUpdates) return state; // Paper - prevent disarming tripwires + if (!world.isClientSide && !player.getMainHandItem().isEmpty() && player.getMainHandItem().is(Items.SHEARS)) { + world.setBlock(pos, (BlockState) state.setValue(TripWireBlock.DISARMED, true), 4); + world.gameEvent((Entity) player, (Holder) GameEvent.SHEAR, pos); +@@ -104,6 +109,7 @@ public class TripWireBlock extends Block { + } + + private void updateSource(Level world, BlockPos pos, BlockState state) { ++ if (io.papermc.paper.configuration.GlobalConfiguration.get().blockUpdates.disableTripwireUpdates) return; // Paper - prevent adjacent tripwires from updating + Direction[] aenumdirection = new Direction[]{Direction.SOUTH, Direction.WEST}; + int i = aenumdirection.length; + int j = 0; +@@ -141,6 +147,7 @@ public class TripWireBlock extends Block { + + @Override + protected void entityInside(BlockState state, Level world, BlockPos pos, Entity entity) { ++ if (io.papermc.paper.configuration.GlobalConfiguration.get().blockUpdates.disableTripwireUpdates) return; // Paper - prevent tripwires from detecting collision + if (!new io.papermc.paper.event.entity.EntityInsideBlockEvent(entity.getBukkitEntity(), org.bukkit.craftbukkit.block.CraftBlock.at(world, pos)).callEvent()) { return; } // Paper - Add EntityInsideBlockEvent + if (!world.isClientSide) { + if (!(Boolean) state.getValue(TripWireBlock.POWERED)) { +@@ -151,6 +158,7 @@ public class TripWireBlock extends Block { + + @Override + protected void tick(BlockState state, ServerLevel world, BlockPos pos, RandomSource random) { ++ if (io.papermc.paper.configuration.GlobalConfiguration.get().blockUpdates.disableTripwireUpdates) return; // Paper - prevent tripwire pressed check + if ((Boolean) world.getBlockState(pos).getValue(TripWireBlock.POWERED)) { + this.checkPressed(world, pos); + } diff --git a/patches/server/0821-Don-t-enforce-icanhasbukkit-default-if-alias-block-e.patch b/patches/server/0821-Don-t-enforce-icanhasbukkit-default-if-alias-block-e.patch deleted file mode 100644 index 0eb4dc7f6982..000000000000 --- a/patches/server/0821-Don-t-enforce-icanhasbukkit-default-if-alias-block-e.patch +++ /dev/null @@ -1,23 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: TheMeinerLP -Date: Tue, 13 Jun 2023 16:10:59 +0200 -Subject: [PATCH] Don't enforce icanhasbukkit default if alias block exists - - -diff --git a/src/main/java/org/bukkit/craftbukkit/CraftServer.java b/src/main/java/org/bukkit/craftbukkit/CraftServer.java -index d0deaa80b404043b8cb3dbc390fd5ec3bff2630b..622fe949819ec80737da21305e1a1e0c46480a63 100644 ---- a/src/main/java/org/bukkit/craftbukkit/CraftServer.java -+++ b/src/main/java/org/bukkit/craftbukkit/CraftServer.java -@@ -355,7 +355,11 @@ public final class CraftServer implements Server { - } - this.commandsConfiguration = YamlConfiguration.loadConfiguration(this.getCommandsConfigFile()); - this.commandsConfiguration.options().copyDefaults(true); -- this.commandsConfiguration.setDefaults(YamlConfiguration.loadConfiguration(new InputStreamReader(this.getClass().getClassLoader().getResourceAsStream("configurations/commands.yml"), Charsets.UTF_8))); -+ // Paper start - don't enforce icanhasbukkit default if alias block exists -+ final YamlConfiguration commandsDefaults = YamlConfiguration.loadConfiguration(new InputStreamReader(this.getClass().getClassLoader().getResourceAsStream("configurations/commands.yml"), Charsets.UTF_8)); -+ if (this.commandsConfiguration.contains("aliases")) commandsDefaults.set("aliases", null); -+ this.commandsConfiguration.setDefaults(commandsDefaults); -+ // Paper end - don't enforce icanhasbukkit default if alias block exists - this.saveCommandsConfig(); - - // Migrate aliases from old file and add previously implicit $1- to pass all arguments diff --git a/patches/server/0822-Call-missing-BlockDispenseEvent.patch b/patches/server/0822-Call-missing-BlockDispenseEvent.patch new file mode 100644 index 000000000000..c8d68a1ed8c7 --- /dev/null +++ b/patches/server/0822-Call-missing-BlockDispenseEvent.patch @@ -0,0 +1,88 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Lulu13022002 <41980282+Lulu13022002@users.noreply.github.com> +Date: Sat, 29 Oct 2022 15:41:56 +0200 +Subject: [PATCH] Call missing BlockDispenseEvent + + +diff --git a/src/main/java/net/minecraft/core/dispenser/DispenseItemBehavior.java b/src/main/java/net/minecraft/core/dispenser/DispenseItemBehavior.java +index 5793569ae8a088f21b0d8d6771a5099b1e88be09..f8f570a97789ab16e57774f233506a289277d5d9 100644 +--- a/src/main/java/net/minecraft/core/dispenser/DispenseItemBehavior.java ++++ b/src/main/java/net/minecraft/core/dispenser/DispenseItemBehavior.java +@@ -768,6 +768,13 @@ public interface DispenseItemBehavior { + this.setSuccess(true); + if (iblockdata.is(Blocks.RESPAWN_ANCHOR)) { + if ((Integer) iblockdata.getValue(RespawnAnchorBlock.CHARGE) != 4) { ++ // Paper start - Call missing BlockDispenseEvent ++ ItemStack result = org.bukkit.craftbukkit.event.CraftEventFactory.handleBlockDispenseEvent(pointer, blockposition, stack, this); ++ if (result != null) { ++ this.setSuccess(false); ++ return result; ++ } ++ // Paper end - Call missing BlockDispenseEvent + RespawnAnchorBlock.charge((Entity) null, worldserver, blockposition, iblockdata); + stack.shrink(1); + } else { +@@ -845,6 +852,13 @@ public interface DispenseItemBehavior { + Optional optional = HoneycombItem.getWaxed(iblockdata); + + if (optional.isPresent()) { ++ // Paper start - Call missing BlockDispenseEvent ++ ItemStack result = org.bukkit.craftbukkit.event.CraftEventFactory.handleBlockDispenseEvent(pointer, blockposition, stack, this); ++ if (result != null) { ++ this.setSuccess(false); ++ return result; ++ } ++ // Paper end - Call missing BlockDispenseEvent + worldserver.setBlockAndUpdate(blockposition, (BlockState) optional.get()); + worldserver.levelEvent(3003, blockposition, 0); + stack.shrink(1); +@@ -872,6 +886,12 @@ public interface DispenseItemBehavior { + if (!worldserver.getBlockState(blockposition1).is(BlockTags.CONVERTABLE_TO_MUD)) { + return this.defaultDispenseItemBehavior.dispense(pointer, stack); + } else { ++ // Paper start - Call missing BlockDispenseEvent ++ ItemStack result = org.bukkit.craftbukkit.event.CraftEventFactory.handleBlockDispenseEvent(pointer, blockposition1, stack, this); ++ if (result != null) { ++ return result; ++ } ++ // Paper end - Call missing BlockDispenseEvent + if (!worldserver.isClientSide) { + for (int k = 0; k < 5; ++k) { + worldserver.sendParticles(ParticleTypes.SPLASH, (double) blockposition.getX() + worldserver.random.nextDouble(), (double) (blockposition.getY() + 1), (double) blockposition.getZ() + worldserver.random.nextDouble(), 1, 0.0D, 0.0D, 0.0D, 1.0D); +diff --git a/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java b/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java +index dd0d19faccbc49b5f9718e2df9f004bb17a9ef7e..38c8f69dff0abe62fc405a9fbb29c80d45aa675f 100644 +--- a/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java ++++ b/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java +@@ -2196,6 +2196,32 @@ public class CraftEventFactory { + } + // Paper end + ++ // Paper start - Call missing BlockDispenseEvent ++ @Nullable ++ public static ItemStack handleBlockDispenseEvent(net.minecraft.core.dispenser.BlockSource pointer, BlockPos to, ItemStack itemStack, net.minecraft.core.dispenser.DispenseItemBehavior instance) { ++ org.bukkit.block.Block bukkitBlock = CraftBlock.at(pointer.level(), pointer.pos()); ++ CraftItemStack craftItem = CraftItemStack.asCraftMirror(itemStack.copyWithCount(1)); ++ ++ org.bukkit.event.block.BlockDispenseEvent event = new org.bukkit.event.block.BlockDispenseEvent(bukkitBlock, craftItem.clone(), CraftVector.toBukkit(to)); ++ if (!net.minecraft.world.level.block.DispenserBlock.eventFired) { ++ if (!event.callEvent()) { ++ return itemStack; ++ } ++ } ++ ++ if (!event.getItem().equals(craftItem)) { ++ // Chain to handler for new item ++ ItemStack eventStack = CraftItemStack.asNMSCopy(event.getItem()); ++ net.minecraft.core.dispenser.DispenseItemBehavior itemBehavior = net.minecraft.world.level.block.DispenserBlock.DISPENSER_REGISTRY.get(eventStack.getItem()); ++ if (itemBehavior != net.minecraft.core.dispenser.DispenseItemBehavior.NOOP && itemBehavior != instance) { ++ itemBehavior.dispense(pointer, eventStack); ++ return itemStack; ++ } ++ } ++ return null; ++ } ++ // Paper end - Call missing BlockDispenseEvent ++ + // Paper start - add EntityFertilizeEggEvent + /** + * Calls the {@link io.papermc.paper.event.entity.EntityFertilizeEggEvent}. diff --git a/patches/server/0823-Don-t-load-chunks-for-supporting-block-checks.patch b/patches/server/0823-Don-t-load-chunks-for-supporting-block-checks.patch new file mode 100644 index 000000000000..4984d9ef4c64 --- /dev/null +++ b/patches/server/0823-Don-t-load-chunks-for-supporting-block-checks.patch @@ -0,0 +1,19 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Shane Freeder +Date: Wed, 5 Jul 2023 23:11:53 +0100 +Subject: [PATCH] Don't load chunks for supporting block checks + + +diff --git a/src/main/java/net/minecraft/world/entity/Entity.java b/src/main/java/net/minecraft/world/entity/Entity.java +index 90b47da5c6df05565b522636e08c4ebb7165c351..a8bb9caaf0db01d8b7cfb18e5f162c6eeada5d6a 100644 +--- a/src/main/java/net/minecraft/world/entity/Entity.java ++++ b/src/main/java/net/minecraft/world/entity/Entity.java +@@ -1228,7 +1228,7 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess + } + + protected BlockPos getOnPos(float offset) { +- if (this.mainSupportingBlockPos.isPresent()) { ++ if (this.mainSupportingBlockPos.isPresent() && this.level().getChunkIfLoadedImmediately(this.mainSupportingBlockPos.get()) != null) { // Paper - ensure no loads + BlockPos blockposition = (BlockPos) this.mainSupportingBlockPos.get(); + + if (offset <= 1.0E-5F) { diff --git a/patches/server/0824-Add-method-to-remove-all-active-potion-effects.patch b/patches/server/0824-Add-method-to-remove-all-active-potion-effects.patch deleted file mode 100644 index 6c4676b8f748..000000000000 --- a/patches/server/0824-Add-method-to-remove-all-active-potion-effects.patch +++ /dev/null @@ -1,24 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Jake Potrebic -Date: Sat, 17 Jun 2023 13:17:25 -0700 -Subject: [PATCH] Add method to remove all active potion effects - - -diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftLivingEntity.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftLivingEntity.java -index 4ede706367d00965ac75a6ac95877e862d464f74..fb6465bbb2a8bb7597c15d7ac8375f696b897e43 100644 ---- a/src/main/java/org/bukkit/craftbukkit/entity/CraftLivingEntity.java -+++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftLivingEntity.java -@@ -564,6 +564,13 @@ public class CraftLivingEntity extends CraftEntity implements LivingEntity { - return effects; - } - -+ // Paper start - LivingEntity#clearActivePotionEffects(); -+ @Override -+ public boolean clearActivePotionEffects() { -+ return this.getHandle().removeAllEffects(EntityPotionEffectEvent.Cause.PLUGIN); -+ } -+ // Paper end -+ - @Override - public T launchProjectile(Class projectile) { - return this.launchProjectile(projectile, null); diff --git a/patches/server/0824-Optimize-player-lookups-for-beacons.patch b/patches/server/0824-Optimize-player-lookups-for-beacons.patch new file mode 100644 index 000000000000..7e1285d8913a --- /dev/null +++ b/patches/server/0824-Optimize-player-lookups-for-beacons.patch @@ -0,0 +1,36 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Spottedleaf +Date: Thu, 6 Jul 2023 20:17:37 -0700 +Subject: [PATCH] Optimize player lookups for beacons + +For larger ranges, it's better to iterate over the player list +than the entity slices. + +diff --git a/src/main/java/net/minecraft/world/level/block/entity/BeaconBlockEntity.java b/src/main/java/net/minecraft/world/level/block/entity/BeaconBlockEntity.java +index 2a7d896dd9a02acf6e3596e2e2e7ed50f4b88377..0e0d178f2793ab014358f534c8dc53218b89f083 100644 +--- a/src/main/java/net/minecraft/world/level/block/entity/BeaconBlockEntity.java ++++ b/src/main/java/net/minecraft/world/level/block/entity/BeaconBlockEntity.java +@@ -333,7 +333,22 @@ public class BeaconBlockEntity extends BlockEntity implements MenuProvider, Name + double d0 = blockEntity != null ? blockEntity.getEffectRange() : (i * 10 + 10); // Paper - Custom beacon ranges + + AABB axisalignedbb = (new AABB(blockposition)).inflate(d0).expandTowards(0.0D, (double) world.getHeight(), 0.0D); +- List list = world.getEntitiesOfClass(Player.class, axisalignedbb); ++ // Paper start - Perf: optimize player lookup for beacons ++ List list; ++ if (d0 <= 128.0) { ++ list = world.getEntitiesOfClass(Player.class, axisalignedbb); ++ } else { ++ list = new java.util.ArrayList<>(); ++ for (Player player : world.players()) { ++ if (player.isSpectator()) { ++ continue; ++ } ++ if (player.getBoundingBox().intersects(axisalignedbb)) { ++ list.add(player); ++ } ++ } ++ } ++ // Paper end - Perf: optimize player lookup for beacons + + return list; + } diff --git a/patches/server/0825-Fix-incorrect-crafting-result-amount-for-fireworks.patch b/patches/server/0825-Fix-incorrect-crafting-result-amount-for-fireworks.patch deleted file mode 100644 index ed5525cfda57..000000000000 --- a/patches/server/0825-Fix-incorrect-crafting-result-amount-for-fireworks.patch +++ /dev/null @@ -1,20 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Redned -Date: Mon, 12 Jun 2023 11:54:12 -0500 -Subject: [PATCH] Fix incorrect crafting result amount for fireworks - -Although vanilla does not specifically call this method anywhere, this fixes a bug where the result using the Bukkit API returns the wrong amount. - -diff --git a/src/main/java/net/minecraft/world/item/crafting/FireworkRocketRecipe.java b/src/main/java/net/minecraft/world/item/crafting/FireworkRocketRecipe.java -index e2a3a4fc2fff21b926a74ed11389333165180fe7..93c2268ea1be1727c2939d5730427e24d4e03e2f 100644 ---- a/src/main/java/net/minecraft/world/item/crafting/FireworkRocketRecipe.java -+++ b/src/main/java/net/minecraft/world/item/crafting/FireworkRocketRecipe.java -@@ -77,7 +77,7 @@ public class FireworkRocketRecipe extends CustomRecipe { - - @Override - public ItemStack getResultItem(HolderLookup.Provider registriesLookup) { -- return new ItemStack(Items.FIREWORK_ROCKET); -+ return new ItemStack(Items.FIREWORK_ROCKET, 3); // Paper - Fix incorrect crafting result amount - } - - @Override diff --git a/patches/server/0825-More-Sign-Block-API.patch b/patches/server/0825-More-Sign-Block-API.patch new file mode 100644 index 000000000000..70558b001027 --- /dev/null +++ b/patches/server/0825-More-Sign-Block-API.patch @@ -0,0 +1,62 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Jake Potrebic +Date: Fri, 23 Jun 2023 12:16:28 -0700 +Subject: [PATCH] More Sign Block API + +Co-authored-by: SoSeDiK + +diff --git a/src/main/java/net/minecraft/world/level/block/entity/SignBlockEntity.java b/src/main/java/net/minecraft/world/level/block/entity/SignBlockEntity.java +index a34c2fc6ac1418a89d789b8945ca3dad57f64151..8ac19e1e052e73ff3fd09089bb8e3fd687390ee4 100644 +--- a/src/main/java/net/minecraft/world/level/block/entity/SignBlockEntity.java ++++ b/src/main/java/net/minecraft/world/level/block/entity/SignBlockEntity.java +@@ -68,12 +68,17 @@ public class SignBlockEntity extends BlockEntity { + } + + public boolean isFacingFrontText(net.minecraft.world.entity.player.Player player) { ++ // Paper start - More Sign Block API ++ return this.isFacingFrontText(player.getX(), player.getZ()); ++ } ++ public boolean isFacingFrontText(double x, double z) { ++ // Paper end - More Sign Block API + Block block = this.getBlockState().getBlock(); + + if (block instanceof SignBlock blocksign) { + Vec3 vec3d = blocksign.getSignHitboxCenterPosition(this.getBlockState()); +- double d0 = player.getX() - ((double) this.getBlockPos().getX() + vec3d.x); +- double d1 = player.getZ() - ((double) this.getBlockPos().getZ() + vec3d.z); ++ double d0 = x - ((double) this.getBlockPos().getX() + vec3d.x); // Paper - More Sign Block API ++ double d1 = z - ((double) this.getBlockPos().getZ() + vec3d.z); // Paper - More Sign Block API + float f = blocksign.getYRotationDegrees(this.getBlockState()); + float f1 = (float) (Mth.atan2(d1, d0) * 57.2957763671875D) - 90.0F; + +diff --git a/src/main/java/org/bukkit/craftbukkit/block/CraftSign.java b/src/main/java/org/bukkit/craftbukkit/block/CraftSign.java +index 8303343ecca91076839f4436d6b3a3bf4739c2fd..e3c333d26dea9af9dd51e1662f81f1d472ab0233 100644 +--- a/src/main/java/org/bukkit/craftbukkit/block/CraftSign.java ++++ b/src/main/java/org/bukkit/craftbukkit/block/CraftSign.java +@@ -198,6 +198,26 @@ public class CraftSign extends CraftBlockEntityState< + } + // Paper end + ++ // Paper start - More Sign Block API ++ @Override ++ public java.util.UUID getAllowedEditorUniqueId() { ++ this.ensureNoWorldGeneration(); ++ return this.getTileEntity().getPlayerWhoMayEdit(); ++ } ++ ++ @Override ++ public void setAllowedEditorUniqueId(java.util.UUID uuid) { ++ this.ensureNoWorldGeneration(); ++ this.getTileEntity().setAllowedPlayerEditor(uuid); ++ } ++ ++ @Override ++ public Side getInteractableSideFor(final double x, final double z) { ++ this.requirePlaced(); ++ return this.getSnapshot().isFacingFrontText(x, z) ? Side.FRONT : Side.BACK; ++ } ++ // Paper end - More Sign Block API ++ + public static Component[] sanitizeLines(String[] lines) { + Component[] components = new Component[4]; + diff --git a/patches/server/0826-Add-event-for-player-editing-sign.patch b/patches/server/0826-Add-event-for-player-editing-sign.patch deleted file mode 100644 index df5567a67f1a..000000000000 --- a/patches/server/0826-Add-event-for-player-editing-sign.patch +++ /dev/null @@ -1,93 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: by77er -Date: Mon, 12 Jun 2023 12:56:46 -0400 -Subject: [PATCH] Add event for player editing sign - - -diff --git a/src/main/java/net/minecraft/world/item/ItemStack.java b/src/main/java/net/minecraft/world/item/ItemStack.java -index 276858d23be2f9443cc364046a55b9c9a88bf4ea..10b3e41d589d480046e15a957902c2751b731cec 100644 ---- a/src/main/java/net/minecraft/world/item/ItemStack.java -+++ b/src/main/java/net/minecraft/world/item/ItemStack.java -@@ -550,7 +550,7 @@ public final class ItemStack implements DataComponentHolder { - try { - if (world.getBlockEntity(SignItem.openSign) instanceof SignBlockEntity tileentitysign) { - if (world.getBlockState(SignItem.openSign).getBlock() instanceof SignBlock blocksign) { -- blocksign.openTextEdit(entityhuman, tileentitysign, true, org.bukkit.event.player.PlayerSignOpenEvent.Cause.PLACE); // Craftbukkit -+ blocksign.openTextEdit(entityhuman, tileentitysign, true, io.papermc.paper.event.player.PlayerOpenSignEvent.Cause.PLACE); // Paper - Add PlayerOpenSignEvent - } - } - } finally { -diff --git a/src/main/java/net/minecraft/world/level/block/SignBlock.java b/src/main/java/net/minecraft/world/level/block/SignBlock.java -index e00bfb839a2512f018d874976d6f9607877fc2af..73874cd18a5b335e895ea0b2fefbd521209afe08 100644 ---- a/src/main/java/net/minecraft/world/level/block/SignBlock.java -+++ b/src/main/java/net/minecraft/world/level/block/SignBlock.java -@@ -139,7 +139,7 @@ public abstract class SignBlock extends BaseEntityBlock implements SimpleWaterlo - } else if (flag1) { - return InteractionResult.SUCCESS; - } else if (!this.otherPlayerIsEditingSign(player, tileentitysign) && player.mayBuild() && this.hasEditableText(player, tileentitysign, flag)) { -- this.openTextEdit(player, tileentitysign, flag, org.bukkit.event.player.PlayerSignOpenEvent.Cause.INTERACT); // CraftBukkit -+ this.openTextEdit(player, tileentitysign, flag, io.papermc.paper.event.player.PlayerOpenSignEvent.Cause.INTERACT); // Paper - Add PlayerOpenSignEvent - return InteractionResult.SUCCESS; - } else { - return InteractionResult.PASS; -@@ -184,16 +184,33 @@ public abstract class SignBlock extends BaseEntityBlock implements SimpleWaterlo - return blockpropertywood; - } - -+ @io.papermc.paper.annotation.DoNotUse @Deprecated // Paper - Add PlayerOpenSignEvent - public void openTextEdit(Player player, SignBlockEntity blockEntity, boolean front) { -- // Craftbukkit start -- this.openTextEdit(player, blockEntity, front, org.bukkit.event.player.PlayerSignOpenEvent.Cause.UNKNOWN); -- } -- -- public void openTextEdit(Player entityhuman, SignBlockEntity tileentitysign, boolean flag, org.bukkit.event.player.PlayerSignOpenEvent.Cause cause) { -- if (!org.bukkit.craftbukkit.event.CraftEventFactory.callPlayerSignOpenEvent(entityhuman, tileentitysign, flag, cause)) { -+ // Paper start - Add PlayerOpenSignEvent -+ this.openTextEdit(player, blockEntity, front, io.papermc.paper.event.player.PlayerOpenSignEvent.Cause.UNKNOWN); -+ } -+ public void openTextEdit(Player entityhuman, SignBlockEntity tileentitysign, boolean flag, io.papermc.paper.event.player.PlayerOpenSignEvent.Cause cause) { -+ org.bukkit.entity.Player bukkitPlayer = (org.bukkit.entity.Player) entityhuman.getBukkitEntity(); -+ org.bukkit.block.Block bukkitBlock = org.bukkit.craftbukkit.block.CraftBlock.at(tileentitysign.getLevel(), tileentitysign.getBlockPos()); -+ org.bukkit.craftbukkit.block.CraftSign bukkitSign = (org.bukkit.craftbukkit.block.CraftSign) org.bukkit.craftbukkit.block.CraftBlockStates.getBlockState(bukkitBlock); -+ io.papermc.paper.event.player.PlayerOpenSignEvent event = new io.papermc.paper.event.player.PlayerOpenSignEvent( -+ bukkitPlayer, -+ bukkitSign, -+ flag ? org.bukkit.block.sign.Side.FRONT : org.bukkit.block.sign.Side.BACK, -+ cause); -+ if (!event.callEvent()) return; -+ if (org.bukkit.event.player.PlayerSignOpenEvent.getHandlerList().getRegisteredListeners().length > 0) { -+ final org.bukkit.event.player.PlayerSignOpenEvent.Cause legacyCause = switch (cause) { -+ case PLACE -> org.bukkit.event.player.PlayerSignOpenEvent.Cause.PLACE; -+ case PLUGIN -> org.bukkit.event.player.PlayerSignOpenEvent.Cause.PLUGIN; -+ case INTERACT -> org.bukkit.event.player.PlayerSignOpenEvent.Cause.INTERACT; -+ case UNKNOWN -> org.bukkit.event.player.PlayerSignOpenEvent.Cause.UNKNOWN; -+ }; -+ if (!org.bukkit.craftbukkit.event.CraftEventFactory.callPlayerSignOpenEvent(entityhuman, tileentitysign, flag, legacyCause)) { -+ // Paper end - Add PlayerOpenSignEvent - return; - } -- // Craftbukkit end -+ } // Paper - Add PlayerOpenSignEvent - tileentitysign.setAllowedPlayerEditor(entityhuman.getUUID()); - entityhuman.openTextEdit(tileentitysign, flag); - } -diff --git a/src/main/java/org/bukkit/craftbukkit/block/CraftSign.java b/src/main/java/org/bukkit/craftbukkit/block/CraftSign.java -index a12702cdf36c75572e661b5b5758270f5058c181..8303343ecca91076839f4436d6b3a3bf4739c2fd 100644 ---- a/src/main/java/org/bukkit/craftbukkit/block/CraftSign.java -+++ b/src/main/java/org/bukkit/craftbukkit/block/CraftSign.java -@@ -168,9 +168,15 @@ public class CraftSign extends CraftBlockEntityState< - Preconditions.checkArgument(sign.isPlaced(), "Sign must be placed"); - Preconditions.checkArgument(sign.getWorld() == player.getWorld(), "Sign must be in same world as Player"); - -+ // Paper start - Add PlayerOpenSignEvent -+ io.papermc.paper.event.player.PlayerOpenSignEvent event = new io.papermc.paper.event.player.PlayerOpenSignEvent((Player) player, sign, side, io.papermc.paper.event.player.PlayerOpenSignEvent.Cause.PLUGIN); -+ if (!event.callEvent()) return; -+ if (PlayerSignOpenEvent.getHandlerList().getRegisteredListeners().length > 0) { -+ // Paper end - Add PlayerOpenSignEvent - if (!CraftEventFactory.callPlayerSignOpenEvent(player, sign, side, PlayerSignOpenEvent.Cause.PLUGIN)) { - return; - } -+ } // Paper - Add PlayerOpenSignEvent - - SignBlockEntity handle = ((CraftSign) sign).getTileEntity(); - handle.setAllowedPlayerEditor(player.getUniqueId()); diff --git a/patches/server/0826-fix-item-meta-for-tadpole-buckets.patch b/patches/server/0826-fix-item-meta-for-tadpole-buckets.patch new file mode 100644 index 000000000000..04c4b6a87380 --- /dev/null +++ b/patches/server/0826-fix-item-meta-for-tadpole-buckets.patch @@ -0,0 +1,63 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Jake Potrebic +Date: Tue, 11 Jul 2023 11:22:30 -0700 +Subject: [PATCH] fix item meta for tadpole buckets + + +diff --git a/src/main/java/org/bukkit/craftbukkit/inventory/CraftItemMetas.java b/src/main/java/org/bukkit/craftbukkit/inventory/CraftItemMetas.java +index 4f5568707d725195ee19b65274454fec8bf99d64..d29f4dd9808e2001c79535d663ca77d6840f6092 100644 +--- a/src/main/java/org/bukkit/craftbukkit/inventory/CraftItemMetas.java ++++ b/src/main/java/org/bukkit/craftbukkit/inventory/CraftItemMetas.java +@@ -265,7 +265,7 @@ public final class CraftItemMetas { + if (itemType == ItemType.SUSPICIOUS_STEW) { + return CraftItemMetas.asType(CraftItemMetas.SUSPICIOUS_STEW_META_DATA); + } +- if (itemType == ItemType.COD_BUCKET || itemType == ItemType.PUFFERFISH_BUCKET ++ if (itemType == ItemType.COD_BUCKET || itemType == ItemType.PUFFERFISH_BUCKET || itemType == ItemType.TADPOLE_BUCKET // Paper + || itemType == ItemType.SALMON_BUCKET || itemType == ItemType.ITEM_FRAME + || itemType == ItemType.GLOW_ITEM_FRAME || itemType == ItemType.PAINTING) { + return CraftItemMetas.asType(CraftItemMetas.ENTITY_TAG_META_DATA); +diff --git a/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaEntityTag.java b/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaEntityTag.java +index 27af7ca9d62bdb4a24be5af139c181d7bc271ba5..3ff0340c40e9dc9a6e690de15ccade7a0c4e8f02 100644 +--- a/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaEntityTag.java ++++ b/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaEntityTag.java +@@ -19,6 +19,7 @@ public class CraftMetaEntityTag extends CraftMetaItem { + Material.COD_BUCKET, + Material.PUFFERFISH_BUCKET, + Material.SALMON_BUCKET, ++ Material.TADPOLE_BUCKET, // Paper + Material.ITEM_FRAME, + Material.GLOW_ITEM_FRAME, + Material.PAINTING +diff --git a/src/test/java/org/bukkit/craftbukkit/inventory/ItemMetaTest.java b/src/test/java/org/bukkit/craftbukkit/inventory/ItemMetaTest.java +index 50faaaa48dffcaf53823caed1e3f7263cd5c441f..ba5c958f322dc34baff3c9d1b99741a4ffeee135 100644 +--- a/src/test/java/org/bukkit/craftbukkit/inventory/ItemMetaTest.java ++++ b/src/test/java/org/bukkit/craftbukkit/inventory/ItemMetaTest.java +@@ -209,6 +209,27 @@ public class ItemMetaTest { + } + } + ++ // Paper start - check entity tag metas ++ private static final java.util.Set> ENTITY_TAG_METAS = java.util.Set.of( ++ CraftMetaEntityTag.class, ++ CraftMetaTropicalFishBucket.class, ++ CraftMetaAxolotlBucket.class ++ ); ++ @Test ++ public void testEntityTagMeta() { ++ for (final Item item : BuiltInRegistries.ITEM) { ++ if (item instanceof net.minecraft.world.item.HangingEntityItem || item instanceof net.minecraft.world.item.MobBucketItem) { ++ ItemStack stack = new ItemStack(CraftItemType.minecraftToBukkit(item)); ++ assertTrue(ENTITY_TAG_METAS.contains(stack.getItemMeta().getClass()), "missing entity tag meta handling for " + item); ++ stack = CraftItemStack.asNewCraftStack(net.minecraft.world.item.Items.STONE); ++ stack.editMeta(meta -> meta.displayName(net.kyori.adventure.text.Component.text("hello"))); ++ stack.setType(CraftItemType.minecraftToBukkit(item)); ++ assertTrue(ENTITY_TAG_METAS.contains(stack.getItemMeta().getClass()), "missing entity tag meta handling for " + item); ++ } ++ } ++ } ++ // Paper end ++ + @Test + public void testEachExtraData() { + final List providers = Arrays.asList( diff --git a/patches/server/0827-Fix-BanList-API.patch b/patches/server/0827-Fix-BanList-API.patch new file mode 100644 index 000000000000..63be621a49f6 --- /dev/null +++ b/patches/server/0827-Fix-BanList-API.patch @@ -0,0 +1,351 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Jake Potrebic +Date: Tue, 4 Jul 2023 11:27:10 -0700 +Subject: [PATCH] Fix BanList API + + +diff --git a/src/main/java/org/bukkit/craftbukkit/CraftOfflinePlayer.java b/src/main/java/org/bukkit/craftbukkit/CraftOfflinePlayer.java +index 30c1c203022c2ed777dcddfd68ef2e3752c62ea1..2c2c4db31a746b4eb853dc04c6b3e5631bbfa034 100644 +--- a/src/main/java/org/bukkit/craftbukkit/CraftOfflinePlayer.java ++++ b/src/main/java/org/bukkit/craftbukkit/CraftOfflinePlayer.java +@@ -115,17 +115,17 @@ public class CraftOfflinePlayer implements OfflinePlayer, ConfigurationSerializa + } + + @Override +- public BanEntry ban(String reason, Date expires, String source) { ++ public BanEntry ban(String reason, Date expires, String source) { // Paper - fix ban list API + return ((ProfileBanList) this.server.getBanList(BanList.Type.PROFILE)).addBan(this.getPlayerProfile(), reason, expires, source); + } + + @Override +- public BanEntry ban(String reason, Instant expires, String source) { ++ public BanEntry ban(String reason, Instant expires, String source) { // Paper - fix ban list API + return ((ProfileBanList) this.server.getBanList(BanList.Type.PROFILE)).addBan(this.getPlayerProfile(), reason, expires, source); + } + + @Override +- public BanEntry ban(String reason, Duration duration, String source) { ++ public BanEntry ban(String reason, Duration duration, String source) { // Paper - fix ban list API + return ((ProfileBanList) this.server.getBanList(BanList.Type.PROFILE)).addBan(this.getPlayerProfile(), reason, duration, source); + } + +diff --git a/src/main/java/org/bukkit/craftbukkit/ban/CraftProfileBanEntry.java b/src/main/java/org/bukkit/craftbukkit/ban/CraftProfileBanEntry.java +index 13e5e44b069121e51b9486c445902937f1d6c6d8..4a37c8172b42b10472bb90c9310c7ae3eeaa3481 100644 +--- a/src/main/java/org/bukkit/craftbukkit/ban/CraftProfileBanEntry.java ++++ b/src/main/java/org/bukkit/craftbukkit/ban/CraftProfileBanEntry.java +@@ -9,7 +9,7 @@ import org.bukkit.BanEntry; + import org.bukkit.craftbukkit.profile.CraftPlayerProfile; + import org.bukkit.profile.PlayerProfile; + +-public final class CraftProfileBanEntry implements BanEntry { ++public final class CraftProfileBanEntry implements BanEntry { // Paper + private static final Date minorDate = Date.from(Instant.parse("1899-12-31T04:00:00Z")); + private final UserBanList list; + private final GameProfile profile; +@@ -33,8 +33,8 @@ public final class CraftProfileBanEntry implements BanEntry { + } + + @Override +- public PlayerProfile getBanTarget() { +- return new CraftPlayerProfile(this.profile); ++ public com.destroystokyo.paper.profile.PlayerProfile getBanTarget() { // Paper ++ return new com.destroystokyo.paper.profile.CraftPlayerProfile(this.profile); // Paper + } + + @Override +diff --git a/src/main/java/org/bukkit/craftbukkit/ban/CraftProfileBanList.java b/src/main/java/org/bukkit/craftbukkit/ban/CraftProfileBanList.java +index 172202accf4448a933fcf1ff820316c7910dd7f7..50ee7656580d386db473c054f5c5ec57bb2b1424 100644 +--- a/src/main/java/org/bukkit/craftbukkit/ban/CraftProfileBanList.java ++++ b/src/main/java/org/bukkit/craftbukkit/ban/CraftProfileBanList.java +@@ -24,42 +24,80 @@ public class CraftProfileBanList implements ProfileBanList { + } + + @Override +- public BanEntry getBanEntry(String target) { ++ public BanEntry getBanEntry(String target) { // Paper + Preconditions.checkArgument(target != null, "Target cannot be null"); + + return this.getBanEntry(CraftProfileBanList.getProfile(target)); + } + + @Override +- public BanEntry getBanEntry(PlayerProfile target) { ++ public BanEntry getBanEntry(PlayerProfile target) { // Paper + Preconditions.checkArgument(target != null, "Target cannot be null"); + +- return this.getBanEntry(((CraftPlayerProfile) target).buildGameProfile()); ++ return this.getBanEntry(((com.destroystokyo.paper.profile.SharedPlayerProfile) target).buildGameProfile()); // Paper ++ } ++ // Paper start - fix ban list API ++ @Override ++ public BanEntry getBanEntry(final com.destroystokyo.paper.profile.PlayerProfile target) { ++ Preconditions.checkArgument(target != null, "target cannot be null"); ++ ++ return this.getBanEntry(((com.destroystokyo.paper.profile.SharedPlayerProfile) target).buildGameProfile()); ++ } ++ ++ @Override ++ public BanEntry addBan(final com.destroystokyo.paper.profile.PlayerProfile target, final String reason, final Date expires, final String source) { ++ Preconditions.checkArgument(target != null, "PlayerProfile cannot be null"); ++ Preconditions.checkArgument(target.getId() != null, "The PlayerProfile UUID cannot be null"); ++ ++ return this.addBan(((com.destroystokyo.paper.profile.SharedPlayerProfile) target).buildGameProfile(), reason, expires, source); ++ } ++ ++ @Override ++ public boolean isBanned(final com.destroystokyo.paper.profile.PlayerProfile target) { ++ return this.isBanned((com.destroystokyo.paper.profile.SharedPlayerProfile) target); ++ } ++ ++ @Override ++ public void pardon(final com.destroystokyo.paper.profile.PlayerProfile target) { ++ this.pardon((com.destroystokyo.paper.profile.SharedPlayerProfile) target); + } + + @Override +- public BanEntry addBan(String target, String reason, Date expires, String source) { ++ public BanEntry addBan(final com.destroystokyo.paper.profile.PlayerProfile target, final String reason, final Instant expires, final String source) { ++ Date date = expires != null ? Date.from(expires) : null; ++ return this.addBan(target, reason, date, source); ++ } ++ ++ @Override ++ public BanEntry addBan(final com.destroystokyo.paper.profile.PlayerProfile target, final String reason, final Duration duration, final String source) { ++ Instant instant = duration != null ? Instant.now().plus(duration) : null; ++ return this.addBan(target, reason, instant, source); ++ } ++ // Paper end - fix ban list API ++ ++ @Override ++ public BanEntry addBan(String target, String reason, Date expires, String source) { // Paper - fix ban list API + Preconditions.checkArgument(target != null, "Ban target cannot be null"); + + return this.addBan(CraftProfileBanList.getProfileByName(target), reason, expires, source); + } + + @Override +- public BanEntry addBan(PlayerProfile target, String reason, Date expires, String source) { ++ public BanEntry addBan(PlayerProfile target, String reason, Date expires, String source) { // Paper - fix ban list API + Preconditions.checkArgument(target != null, "PlayerProfile cannot be null"); + Preconditions.checkArgument(target.getUniqueId() != null, "The PlayerProfile UUID cannot be null"); + +- return this.addBan(((CraftPlayerProfile) target).buildGameProfile(), reason, expires, source); ++ return this.addBan(((com.destroystokyo.paper.profile.SharedPlayerProfile) target).buildGameProfile(), reason, expires, source); // Paper + } + + @Override +- public BanEntry addBan(PlayerProfile target, String reason, Instant expires, String source) { ++ public BanEntry addBan(PlayerProfile target, String reason, Instant expires, String source) { // Paper - fix ban list API + Date date = expires != null ? Date.from(expires) : null; + return this.addBan(target, reason, date, source); + } + + @Override +- public BanEntry addBan(PlayerProfile target, String reason, Duration duration, String source) { ++ public BanEntry addBan(PlayerProfile target, String reason, Duration duration, String source) { // Paper - fix ban list API + Instant instant = duration != null ? Instant.now().plus(duration) : null; + return this.addBan(target, reason, instant, source); + } +@@ -76,8 +114,8 @@ public class CraftProfileBanList implements ProfileBanList { + } + + @Override +- public Set> getEntries() { +- ImmutableSet.Builder> builder = ImmutableSet.builder(); ++ public Set> getEntries() { // Paper ++ ImmutableSet.Builder> builder = ImmutableSet.builder(); // Paper + for (UserBanListEntry entry : this.list.getEntries()) { + GameProfile profile = entry.getUser(); + builder.add(new CraftProfileBanEntry(profile, entry, this.list)); +@@ -88,9 +126,14 @@ public class CraftProfileBanList implements ProfileBanList { + + @Override + public boolean isBanned(PlayerProfile target) { ++ // Paper start ++ return this.isBanned((com.destroystokyo.paper.profile.SharedPlayerProfile) target); ++ } ++ private boolean isBanned(com.destroystokyo.paper.profile.SharedPlayerProfile target) { ++ // Paper end + Preconditions.checkArgument(target != null, "Target cannot be null"); + +- return this.isBanned(((CraftPlayerProfile) target).buildGameProfile()); ++ return this.isBanned(target.buildGameProfile()); // Paper + } + + @Override +@@ -102,9 +145,14 @@ public class CraftProfileBanList implements ProfileBanList { + + @Override + public void pardon(PlayerProfile target) { ++ // Paper start ++ this.pardon((com.destroystokyo.paper.profile.SharedPlayerProfile) target); ++ } ++ private void pardon(com.destroystokyo.paper.profile.SharedPlayerProfile target) { ++ // Paper end + Preconditions.checkArgument(target != null, "Target cannot be null"); + +- this.pardon(((CraftPlayerProfile) target).buildGameProfile()); ++ this.pardon(target.buildGameProfile()); // Paper + } + + @Override +@@ -114,7 +162,7 @@ public class CraftProfileBanList implements ProfileBanList { + this.pardon(CraftProfileBanList.getProfile(target)); + } + +- public BanEntry getBanEntry(GameProfile profile) { ++ public BanEntry getBanEntry(GameProfile profile) { // Paper + if (profile == null) { + return null; + } +@@ -127,7 +175,7 @@ public class CraftProfileBanList implements ProfileBanList { + return new CraftProfileBanEntry(profile, entry, this.list); + } + +- public BanEntry addBan(GameProfile profile, String reason, Date expires, String source) { ++ public BanEntry addBan(GameProfile profile, String reason, Date expires, String source) { // Paper + if (profile == null) { + return null; + } +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java +index d48a0d67853fb17cc73bba94c9b6660d2689c8fc..3ddf3b893605f5763b1f685ad951b1ccdd4a44ce 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java +@@ -1771,23 +1771,23 @@ public class CraftPlayer extends CraftHumanEntity implements Player { + } + + @Override +- public BanEntry ban(String reason, Date expires, String source) { ++ public BanEntry ban(String reason, Date expires, String source) { // Paper - fix ban list API + return this.ban(reason, expires, source, true); + } + + @Override +- public BanEntry ban(String reason, Instant expires, String source) { ++ public BanEntry ban(String reason, Instant expires, String source) { // Paper - fix ban list API + return this.ban(reason, expires != null ? Date.from(expires) : null, source); + } + + @Override +- public BanEntry ban(String reason, Duration duration, String source) { ++ public BanEntry ban(String reason, Duration duration, String source) { // Paper - fix ban list API + return this.ban(reason, duration != null ? Instant.now().plus(duration) : null, source); + } + + @Override +- public BanEntry ban(String reason, Date expires, String source, boolean kickPlayer) { +- BanEntry banEntry = ((ProfileBanList) this.server.getBanList(BanList.Type.PROFILE)).addBan(this.getPlayerProfile(), reason, expires, source); ++ public BanEntry ban(String reason, Date expires, String source, boolean kickPlayer) { // Paper - fix ban list API ++ BanEntry banEntry = ((ProfileBanList) this.server.getBanList(BanList.Type.PROFILE)).addBan(this.getPlayerProfile(), reason, expires, source); // Paper - fix ban list API + if (kickPlayer) { + this.kickPlayer(reason); + } +@@ -1795,12 +1795,12 @@ public class CraftPlayer extends CraftHumanEntity implements Player { + } + + @Override +- public BanEntry ban(String reason, Instant instant, String source, boolean kickPlayer) { ++ public BanEntry ban(String reason, Instant instant, String source, boolean kickPlayer) { // Paper - fix ban list API + return this.ban(reason, instant != null ? Date.from(instant) : null, source, kickPlayer); + } + + @Override +- public BanEntry ban(String reason, Duration duration, String source, boolean kickPlayer) { ++ public BanEntry ban(String reason, Duration duration, String source, boolean kickPlayer) { // Paper - fix ban list API + return this.ban(reason, duration != null ? Instant.now().plus(duration) : null, source, kickPlayer); + } + +diff --git a/src/main/java/org/bukkit/craftbukkit/profile/CraftPlayerProfile.java b/src/main/java/org/bukkit/craftbukkit/profile/CraftPlayerProfile.java +index d9cc76d7e60001957c0f59fdc32d016f3589aa08..f58dec12326734c61f4bc2298a87fb38f1ac1dc4 100644 +--- a/src/main/java/org/bukkit/craftbukkit/profile/CraftPlayerProfile.java ++++ b/src/main/java/org/bukkit/craftbukkit/profile/CraftPlayerProfile.java +@@ -31,7 +31,7 @@ import org.bukkit.profile.PlayerTextures; + import org.jetbrains.annotations.ApiStatus; + + @SerializableAs("PlayerProfile") +-public final class CraftPlayerProfile implements PlayerProfile, com.destroystokyo.paper.profile.SharedPlayerProfile { // Paper ++public final class CraftPlayerProfile implements PlayerProfile, com.destroystokyo.paper.profile.SharedPlayerProfile, com.destroystokyo.paper.profile.PlayerProfile { // Paper + + @Nonnull + public static GameProfile validateSkullProfile(@Nonnull GameProfile gameProfile) { +@@ -162,7 +162,7 @@ public final class CraftPlayerProfile implements PlayerProfile, com.destroystoky + } + + @Override +- public CompletableFuture update() { ++ public CompletableFuture update() { // Paper - have to remove generic to avoid clashing between bukkit.PlayerProfile and paper.PlayerProfile + return CompletableFuture.supplyAsync(this::getUpdatedProfile, Util.PROFILE_EXECUTOR); // Paper - don't submit BLOCKING PROFILE LOOKUPS to the world gen thread + } + +@@ -321,4 +321,71 @@ public final class CraftPlayerProfile implements PlayerProfile, com.destroystoky + // Paper - diff on change + return profile; + } ++ ++ // Paper start - This must implement our PlayerProfile so generic casts succeed from cb.CraftPlayerProfile to paper.PlayerProfile ++ // The methods don't actually have to be implemented, because the profile should immediately be cast to SharedPlayerProfile ++ @Override ++ public String setName(final String name) { ++ throw new UnsupportedOperationException("Do not cast to com.destroystokyo.paper.profile.PlayerProfile"); ++ } ++ ++ @Override ++ public UUID getId() { ++ throw new UnsupportedOperationException("Do not cast to com.destroystokyo.paper.profile.PlayerProfile"); ++ } ++ ++ @Override ++ public UUID setId(final UUID uuid) { ++ throw new UnsupportedOperationException("Do not cast to com.destroystokyo.paper.profile.PlayerProfile"); ++ } ++ ++ @Override ++ public java.util.Set getProperties() { ++ throw new UnsupportedOperationException("Do not cast to com.destroystokyo.paper.profile.PlayerProfile"); ++ } ++ ++ @Override ++ public boolean hasProperty(final String property) { ++ throw new UnsupportedOperationException("Do not cast to com.destroystokyo.paper.profile.PlayerProfile"); ++ } ++ ++ @Override ++ public void setProperty(final com.destroystokyo.paper.profile.ProfileProperty property) { ++ throw new UnsupportedOperationException("Do not cast to com.destroystokyo.paper.profile.PlayerProfile"); ++ } ++ ++ @Override ++ public void setProperties(final java.util.Collection properties) { ++ throw new UnsupportedOperationException("Do not cast to com.destroystokyo.paper.profile.PlayerProfile"); ++ } ++ ++ @Override ++ public void clearProperties() { ++ throw new UnsupportedOperationException("Do not cast to com.destroystokyo.paper.profile.PlayerProfile"); ++ } ++ ++ @Override ++ public boolean completeFromCache() { ++ throw new UnsupportedOperationException("Do not cast to com.destroystokyo.paper.profile.PlayerProfile"); ++ } ++ ++ @Override ++ public boolean completeFromCache(final boolean onlineMode) { ++ throw new UnsupportedOperationException("Do not cast to com.destroystokyo.paper.profile.PlayerProfile"); ++ } ++ ++ @Override ++ public boolean completeFromCache(final boolean lookupUUID, final boolean onlineMode) { ++ throw new UnsupportedOperationException("Do not cast to com.destroystokyo.paper.profile.PlayerProfile"); ++ } ++ ++ @Override ++ public boolean complete(final boolean textures) { ++ throw new UnsupportedOperationException("Do not cast to com.destroystokyo.paper.profile.PlayerProfile"); ++ } ++ ++ @Override ++ public boolean complete(final boolean textures, final boolean onlineMode) { ++ throw new UnsupportedOperationException("Do not cast to com.destroystokyo.paper.profile.PlayerProfile"); ++ } + } diff --git a/patches/server/0827-Only-tick-item-frames-if-players-can-see-it.patch b/patches/server/0827-Only-tick-item-frames-if-players-can-see-it.patch deleted file mode 100644 index bf73ba4a5c3d..000000000000 --- a/patches/server/0827-Only-tick-item-frames-if-players-can-see-it.patch +++ /dev/null @@ -1,20 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Redned -Date: Mon, 19 Jun 2023 15:45:53 -0500 -Subject: [PATCH] Only tick item frames if players can see it - -In the event that an item frame cannot be seen by any players, ticking the item frame every tick is unnecessary. This can be a very hot section of the entity tracker when lots of item frames are present on a server, so this reduces the logic which speeds it up. - -diff --git a/src/main/java/net/minecraft/server/level/ServerEntity.java b/src/main/java/net/minecraft/server/level/ServerEntity.java -index f3f93710846ce0f6d53845e0b49331646a4e8332..f010be9605d7458add7e5693ff473fabf679c938 100644 ---- a/src/main/java/net/minecraft/server/level/ServerEntity.java -+++ b/src/main/java/net/minecraft/server/level/ServerEntity.java -@@ -112,7 +112,7 @@ public class ServerEntity { - - Entity entity = this.entity; - -- if (entity instanceof ItemFrame entityitemframe) { -+ if (!this.trackedPlayers.isEmpty() && entity instanceof ItemFrame entityitemframe) { // Paper - Perf: Only tick item frames if players can see it - if (true || this.tickCount % 10 == 0) { // CraftBukkit - Moved below, should always enter this block - ItemStack itemstack = entityitemframe.getItem(); - diff --git a/patches/server/0828-Determine-lava-and-water-fluid-explosion-resistance-.patch b/patches/server/0828-Determine-lava-and-water-fluid-explosion-resistance-.patch new file mode 100644 index 000000000000..4f2b8e09cd7b --- /dev/null +++ b/patches/server/0828-Determine-lava-and-water-fluid-explosion-resistance-.patch @@ -0,0 +1,36 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: galacticwarrior9 +Date: Thu, 13 Jul 2023 21:32:13 +0100 +Subject: [PATCH] Determine lava and water fluid explosion resistance by their + block explosion resistance + +When selecting which explosion resistance to use for lava and water, vanilla selects the highest value between their block explosion resistance and fluid explosion resistance. + +Problems emerge when we want to reduce the explosion resistance of water or lava, since the fluid explosion resistance is hardcoded to return 100.0F and can't be changed by a plugin. This simply makes the fluid explosion resistance the same as the block explosion resistance, which allows plugin to change the value. Since both are the same in vanilla, this has no side effects on servers that do not need to do this. + +diff --git a/src/main/java/net/minecraft/world/level/material/LavaFluid.java b/src/main/java/net/minecraft/world/level/material/LavaFluid.java +index a27ceed2cac6c16eb4e5f2959db9e482e67e9de6..884db3e64cb22ed765beec8f11ea309fcf810207 100644 +--- a/src/main/java/net/minecraft/world/level/material/LavaFluid.java ++++ b/src/main/java/net/minecraft/world/level/material/LavaFluid.java +@@ -233,7 +233,7 @@ public abstract class LavaFluid extends FlowingFluid { + + @Override + protected float getExplosionResistance() { +- return 100.0F; ++ return Blocks.LAVA.getExplosionResistance(); // Paper - Get explosion resistance from actual block + } + + @Override +diff --git a/src/main/java/net/minecraft/world/level/material/WaterFluid.java b/src/main/java/net/minecraft/world/level/material/WaterFluid.java +index 0a7c05c08bf8c6c331b91e399dc4103a91dc20fe..552925ba47c7475e2e1ec2ded0966f28ed3e50a5 100644 +--- a/src/main/java/net/minecraft/world/level/material/WaterFluid.java ++++ b/src/main/java/net/minecraft/world/level/material/WaterFluid.java +@@ -126,7 +126,7 @@ public abstract class WaterFluid extends FlowingFluid { + + @Override + protected float getExplosionResistance() { +- return 100.0F; ++ return Blocks.WATER.getExplosionResistance(); // Paper - Get explosion resistance from actual block + } + + @Override diff --git a/patches/server/0828-Fix-cmd-permission-levels-for-command-blocks.patch b/patches/server/0828-Fix-cmd-permission-levels-for-command-blocks.patch deleted file mode 100644 index 5167eee08c9c..000000000000 --- a/patches/server/0828-Fix-cmd-permission-levels-for-command-blocks.patch +++ /dev/null @@ -1,90 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Jake Potrebic -Date: Mon, 24 Jan 2022 15:32:02 -0800 -Subject: [PATCH] Fix cmd permission levels for command blocks - - -diff --git a/src/main/java/net/minecraft/commands/CommandSourceStack.java b/src/main/java/net/minecraft/commands/CommandSourceStack.java -index f3c83bb20a73b489f1fb6bacb69388902b1b6fe7..3c0d2332207ba638faaaa4280bce18c334a01271 100644 ---- a/src/main/java/net/minecraft/commands/CommandSourceStack.java -+++ b/src/main/java/net/minecraft/commands/CommandSourceStack.java -@@ -204,10 +204,29 @@ public class CommandSourceStack implements ExecutionCommandSource= level; - } - -+ // Paper start - Fix permission levels for command blocks -+ private boolean forceRespectPermissionLevel() { -+ return this.source == CommandSource.NULL || (this.source instanceof final net.minecraft.world.level.BaseCommandBlock commandBlock && commandBlock.getLevel().paperConfig().commandBlocks.forceFollowPermLevel); -+ } -+ // Paper end - Fix permission levels for command blocks -+ - // CraftBukkit start - public boolean hasPermission(int i, String bukkitPermission) { -- // World is null when loading functions -- return ((this.getLevel() == null || !this.getLevel().getCraftServer().ignoreVanillaPermissions) && this.permissionLevel >= i) || this.getBukkitSender().hasPermission(bukkitPermission); -+ // Paper start - Fix permission levels for command blocks -+ final java.util.function.BooleanSupplier hasBukkitPerm = () -> this.source == CommandSource.NULL /*treat NULL as having all bukkit perms*/ || this.getBukkitSender().hasPermission(bukkitPermission); // lazily check bukkit perms to the benefit of custom permission setups -+ // if the server is null, we must check the vanilla perm level system -+ // if ignoreVanillaPermissions is true, we can skip vanilla perms and just run the bukkit perm check -+ //noinspection ConstantValue -+ if (this.getServer() == null || !this.getServer().server.ignoreVanillaPermissions) { // server & level are null for command function loading -+ final boolean hasPermLevel = this.permissionLevel >= i; -+ if (this.forceRespectPermissionLevel()) { // NULL CommandSource and command blocks (if setting is enabled) should always pass the vanilla perm check -+ return hasPermLevel && hasBukkitPerm.getAsBoolean(); -+ } else { // otherwise check vanilla perm first then bukkit perm, matching upstream behavior -+ return hasPermLevel || hasBukkitPerm.getAsBoolean(); -+ } -+ } -+ return hasBukkitPerm.getAsBoolean(); -+ // Paper end - Fix permission levels for command blocks - } - // CraftBukkit end - -diff --git a/src/main/java/net/minecraft/commands/Commands.java b/src/main/java/net/minecraft/commands/Commands.java -index 96ca1af97c1f63776b88bc428874742a5114564f..7b86e064564d3a285e3971fd08ea6168bac0720e 100644 ---- a/src/main/java/net/minecraft/commands/Commands.java -+++ b/src/main/java/net/minecraft/commands/Commands.java -@@ -295,16 +295,7 @@ public class Commands { - String[] args = command.split(" "); - if (args.length == 0) return; // Paper - empty commands shall not be dispatched - -- String cmd = args[0]; -- if (cmd.startsWith("minecraft:")) cmd = cmd.substring("minecraft:".length()); -- if (cmd.startsWith("bukkit:")) cmd = cmd.substring("bukkit:".length()); -- -- // Block disallowed commands -- if (cmd.equalsIgnoreCase("stop") || cmd.equalsIgnoreCase("kick") || cmd.equalsIgnoreCase("op") -- || cmd.equalsIgnoreCase("deop") || cmd.equalsIgnoreCase("ban") || cmd.equalsIgnoreCase("ban-ip") -- || cmd.equalsIgnoreCase("pardon") || cmd.equalsIgnoreCase("pardon-ip") || cmd.equalsIgnoreCase("reload")) { -- return; -- } -+ // Paper - Fix permission levels for command blocks - - // Handle vanilla commands; - if (sender.getLevel().getCraftServer().getCommandBlockOverride(args[0])) { -diff --git a/src/main/java/net/minecraft/world/entity/vehicle/MinecartCommandBlock.java b/src/main/java/net/minecraft/world/entity/vehicle/MinecartCommandBlock.java -index c671b26d7dc70bac1f4bf9a68a264b25865cf7da..83ef2c3e7b06152b9e68f90002c35e77f148347d 100644 ---- a/src/main/java/net/minecraft/world/entity/vehicle/MinecartCommandBlock.java -+++ b/src/main/java/net/minecraft/world/entity/vehicle/MinecartCommandBlock.java -@@ -136,7 +136,7 @@ public class MinecartCommandBlock extends AbstractMinecart { - - @Override - public CommandSourceStack createCommandSourceStack() { -- return new CommandSourceStack(this, MinecartCommandBlock.this.position(), MinecartCommandBlock.this.getRotationVector(), this.getLevel(), 2, this.getName().getString(), MinecartCommandBlock.this.getDisplayName(), this.getLevel().getServer(), MinecartCommandBlock.this); -+ return new CommandSourceStack(this, MinecartCommandBlock.this.position(), MinecartCommandBlock.this.getRotationVector(), this.getLevel(), this.getLevel().paperConfig().commandBlocks.permissionsLevel, this.getName().getString(), MinecartCommandBlock.this.getDisplayName(), this.getLevel().getServer(), MinecartCommandBlock.this); // Paper - configurable command block perm level - } - - @Override -diff --git a/src/main/java/net/minecraft/world/level/block/entity/CommandBlockEntity.java b/src/main/java/net/minecraft/world/level/block/entity/CommandBlockEntity.java -index 4c7d142dc653f3aca82e0563b6ba2e020721eb9b..6c484f3f8f37a19992ebbfe30aba399cac21acfe 100644 ---- a/src/main/java/net/minecraft/world/level/block/entity/CommandBlockEntity.java -+++ b/src/main/java/net/minecraft/world/level/block/entity/CommandBlockEntity.java -@@ -58,7 +58,7 @@ public class CommandBlockEntity extends BlockEntity { - public CommandSourceStack createCommandSourceStack() { - Direction enumdirection = (Direction) CommandBlockEntity.this.getBlockState().getValue(CommandBlock.FACING); - -- return new CommandSourceStack(this, Vec3.atCenterOf(CommandBlockEntity.this.worldPosition), new Vec2(0.0F, enumdirection.toYRot()), this.getLevel(), 2, this.getName().getString(), this.getName(), this.getLevel().getServer(), (Entity) null); -+ return new CommandSourceStack(this, Vec3.atCenterOf(CommandBlockEntity.this.worldPosition), new Vec2(0.0F, enumdirection.toYRot()), this.getLevel(), this.getLevel().paperConfig().commandBlocks.permissionsLevel, this.getName().getString(), this.getName(), this.getLevel().getServer(), (Entity) null); // Paper - configurable command block perm level - } - - @Override diff --git a/patches/server/0829-Add-option-to-disable-block-updates.patch b/patches/server/0829-Add-option-to-disable-block-updates.patch deleted file mode 100644 index 8fe599472edd..000000000000 --- a/patches/server/0829-Add-option-to-disable-block-updates.patch +++ /dev/null @@ -1,179 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Boy -Date: Sun, 18 Jun 2023 17:45:33 +0200 -Subject: [PATCH] Add option to disable block updates - - -diff --git a/src/main/java/net/minecraft/world/level/block/ChorusPlantBlock.java b/src/main/java/net/minecraft/world/level/block/ChorusPlantBlock.java -index 06f0e5afdab6274154213d82ab3278e08c4ba1b7..8569ad43985fa60d5196ae4bc21646406d8b2adc 100644 ---- a/src/main/java/net/minecraft/world/level/block/ChorusPlantBlock.java -+++ b/src/main/java/net/minecraft/world/level/block/ChorusPlantBlock.java -@@ -38,6 +38,7 @@ public class ChorusPlantBlock extends PipeBlock { - - @Override - public BlockState getStateForPlacement(BlockPlaceContext ctx) { -+ if (io.papermc.paper.configuration.GlobalConfiguration.get().blockUpdates.disableChorusPlantUpdates) return this.defaultBlockState(); // Paper - add option to disable block updates - return getStateWithConnections(ctx.getLevel(), ctx.getClickedPos(), this.defaultBlockState()); - } - -@@ -59,6 +60,7 @@ public class ChorusPlantBlock extends PipeBlock { - - @Override - protected BlockState updateShape(BlockState state, Direction direction, BlockState neighborState, LevelAccessor world, BlockPos pos, BlockPos neighborPos) { -+ if (io.papermc.paper.configuration.GlobalConfiguration.get().blockUpdates.disableChorusPlantUpdates) return state; // Paper - add option to disable block updates - if (!state.canSurvive(world, pos)) { - world.scheduleTick(pos, this, 1); - return super.updateShape(state, direction, neighborState, world, pos, neighborPos); -@@ -70,6 +72,7 @@ public class ChorusPlantBlock extends PipeBlock { - - @Override - protected void tick(BlockState state, ServerLevel world, BlockPos pos, RandomSource random) { -+ if (io.papermc.paper.configuration.GlobalConfiguration.get().blockUpdates.disableChorusPlantUpdates) return; // Paper - add option to disable block updates - if (!state.canSurvive(world, pos)) { - world.destroyBlock(pos, true); - } -@@ -77,6 +80,7 @@ public class ChorusPlantBlock extends PipeBlock { - - @Override - protected boolean canSurvive(BlockState state, LevelReader world, BlockPos pos) { -+ if (io.papermc.paper.configuration.GlobalConfiguration.get().blockUpdates.disableChorusPlantUpdates) return true; // Paper - add option to disable block updates - BlockState blockState = world.getBlockState(pos.below()); - boolean bl = !world.getBlockState(pos.above()).isAir() && !blockState.isAir(); - -diff --git a/src/main/java/net/minecraft/world/level/block/HugeMushroomBlock.java b/src/main/java/net/minecraft/world/level/block/HugeMushroomBlock.java -index 3f45058eee0c78f50c119da5da4c7d2af0251a27..d16aad72c90bbde158b845759eef227546d12e24 100644 ---- a/src/main/java/net/minecraft/world/level/block/HugeMushroomBlock.java -+++ b/src/main/java/net/minecraft/world/level/block/HugeMushroomBlock.java -@@ -43,6 +43,7 @@ public class HugeMushroomBlock extends Block { - - @Override - public BlockState getStateForPlacement(BlockPlaceContext ctx) { -+ if (io.papermc.paper.configuration.GlobalConfiguration.get().blockUpdates.disableMushroomBlockUpdates) return this.defaultBlockState(); // Paper - add option to disable block updates - BlockGetter blockGetter = ctx.getLevel(); - BlockPos blockPos = ctx.getClickedPos(); - return this.defaultBlockState() -@@ -56,6 +57,7 @@ public class HugeMushroomBlock extends Block { - - @Override - protected BlockState updateShape(BlockState state, Direction direction, BlockState neighborState, LevelAccessor world, BlockPos pos, BlockPos neighborPos) { -+ if (io.papermc.paper.configuration.GlobalConfiguration.get().blockUpdates.disableMushroomBlockUpdates) return state; // Paper - add option to disable block updates - return neighborState.is(this) - ? state.setValue(PROPERTY_BY_DIRECTION.get(direction), Boolean.valueOf(false)) - : super.updateShape(state, direction, neighborState, world, pos, neighborPos); -@@ -63,6 +65,7 @@ public class HugeMushroomBlock extends Block { - - @Override - protected BlockState rotate(BlockState state, Rotation rotation) { -+ if (io.papermc.paper.configuration.GlobalConfiguration.get().blockUpdates.disableMushroomBlockUpdates) return state; // Paper - add option to disable block updates - return state.setValue(PROPERTY_BY_DIRECTION.get(rotation.rotate(Direction.NORTH)), state.getValue(NORTH)) - .setValue(PROPERTY_BY_DIRECTION.get(rotation.rotate(Direction.SOUTH)), state.getValue(SOUTH)) - .setValue(PROPERTY_BY_DIRECTION.get(rotation.rotate(Direction.EAST)), state.getValue(EAST)) -@@ -73,6 +76,7 @@ public class HugeMushroomBlock extends Block { - - @Override - protected BlockState mirror(BlockState state, Mirror mirror) { -+ if (io.papermc.paper.configuration.GlobalConfiguration.get().blockUpdates.disableMushroomBlockUpdates) return state; // Paper - add option to disable block updates - return state.setValue(PROPERTY_BY_DIRECTION.get(mirror.mirror(Direction.NORTH)), state.getValue(NORTH)) - .setValue(PROPERTY_BY_DIRECTION.get(mirror.mirror(Direction.SOUTH)), state.getValue(SOUTH)) - .setValue(PROPERTY_BY_DIRECTION.get(mirror.mirror(Direction.EAST)), state.getValue(EAST)) -diff --git a/src/main/java/net/minecraft/world/level/block/NoteBlock.java b/src/main/java/net/minecraft/world/level/block/NoteBlock.java -index f77d51d10e01fc4eaf5516c05c8be0ef7a425893..1d82cfe7af0dc42f88901fb0c44896771fdf8a93 100644 ---- a/src/main/java/net/minecraft/world/level/block/NoteBlock.java -+++ b/src/main/java/net/minecraft/world/level/block/NoteBlock.java -@@ -66,11 +66,13 @@ public class NoteBlock extends Block { - - @Override - public BlockState getStateForPlacement(BlockPlaceContext ctx) { -+ if (io.papermc.paper.configuration.GlobalConfiguration.get().blockUpdates.disableNoteblockUpdates) return this.defaultBlockState(); // Paper - place without considering instrument - return this.setInstrument(ctx.getLevel(), ctx.getClickedPos(), this.defaultBlockState()); - } - - @Override - protected BlockState updateShape(BlockState state, Direction direction, BlockState neighborState, LevelAccessor world, BlockPos pos, BlockPos neighborPos) { -+ if (io.papermc.paper.configuration.GlobalConfiguration.get().blockUpdates.disableNoteblockUpdates) return state; // Paper - prevent noteblock instrument from updating - boolean flag = direction.getAxis() == Direction.Axis.Y; - - return flag ? this.setInstrument(world, pos, state) : super.updateShape(state, direction, neighborState, world, pos, neighborPos); -@@ -78,6 +80,7 @@ public class NoteBlock extends Block { - - @Override - protected void neighborChanged(BlockState state, Level world, BlockPos pos, Block sourceBlock, BlockPos sourcePos, boolean notify) { -+ if (io.papermc.paper.configuration.GlobalConfiguration.get().blockUpdates.disableNoteblockUpdates) return; // Paper - prevent noteblock powered-state from updating - boolean flag1 = world.hasNeighborSignal(pos); - - if (flag1 != (Boolean) state.getValue(NoteBlock.POWERED)) { -@@ -116,7 +119,7 @@ public class NoteBlock extends Block { - if (world.isClientSide) { - return InteractionResult.SUCCESS; - } else { -- state = (BlockState) state.cycle(NoteBlock.NOTE); -+ if (!io.papermc.paper.configuration.GlobalConfiguration.get().blockUpdates.disableNoteblockUpdates) state = (BlockState) state.cycle(NoteBlock.NOTE); // Paper - prevent noteblock note from updating - world.setBlock(pos, state, 3); - this.playNote(player, state, world, pos); - player.awardStat(Stats.TUNE_NOTEBLOCK); -diff --git a/src/main/java/net/minecraft/world/level/block/TripWireBlock.java b/src/main/java/net/minecraft/world/level/block/TripWireBlock.java -index e032d8907045c653c3dd449f65e93e40fd0bb6be..6fe5be785423a35b6ff4e6206ca281b66845b979 100644 ---- a/src/main/java/net/minecraft/world/level/block/TripWireBlock.java -+++ b/src/main/java/net/minecraft/world/level/block/TripWireBlock.java -@@ -67,6 +67,7 @@ public class TripWireBlock extends Block { - - @Override - public BlockState getStateForPlacement(BlockPlaceContext ctx) { -+ if (io.papermc.paper.configuration.GlobalConfiguration.get().blockUpdates.disableTripwireUpdates) return this.defaultBlockState(); // Paper - place tripwire without updating - Level world = ctx.getLevel(); - BlockPos blockposition = ctx.getClickedPos(); - -@@ -75,11 +76,13 @@ public class TripWireBlock extends Block { - - @Override - protected BlockState updateShape(BlockState state, Direction direction, BlockState neighborState, LevelAccessor world, BlockPos pos, BlockPos neighborPos) { -+ if (io.papermc.paper.configuration.GlobalConfiguration.get().blockUpdates.disableTripwireUpdates) return state; // Paper - prevent tripwire from updating - return direction.getAxis().isHorizontal() ? (BlockState) state.setValue((Property) TripWireBlock.PROPERTY_BY_DIRECTION.get(direction), this.shouldConnectTo(neighborState, direction)) : super.updateShape(state, direction, neighborState, world, pos, neighborPos); - } - - @Override - protected void onPlace(BlockState state, Level world, BlockPos pos, BlockState oldState, boolean notify) { -+ if (io.papermc.paper.configuration.GlobalConfiguration.get().blockUpdates.disableTripwireUpdates) return; // Paper - prevent adjacent tripwires from updating - if (!oldState.is(state.getBlock())) { - this.updateSource(world, pos, state); - } -@@ -87,6 +90,7 @@ public class TripWireBlock extends Block { - - @Override - protected void onRemove(BlockState state, Level world, BlockPos pos, BlockState newState, boolean moved) { -+ if (io.papermc.paper.configuration.GlobalConfiguration.get().blockUpdates.disableTripwireUpdates) return; // Paper - prevent adjacent tripwires from updating - if (!moved && !state.is(newState.getBlock())) { - this.updateSource(world, pos, (BlockState) state.setValue(TripWireBlock.POWERED, true)); - } -@@ -94,6 +98,7 @@ public class TripWireBlock extends Block { - - @Override - public BlockState playerWillDestroy(Level world, BlockPos pos, BlockState state, Player player) { -+ if (io.papermc.paper.configuration.GlobalConfiguration.get().blockUpdates.disableTripwireUpdates) return state; // Paper - prevent disarming tripwires - if (!world.isClientSide && !player.getMainHandItem().isEmpty() && player.getMainHandItem().is(Items.SHEARS)) { - world.setBlock(pos, (BlockState) state.setValue(TripWireBlock.DISARMED, true), 4); - world.gameEvent((Entity) player, (Holder) GameEvent.SHEAR, pos); -@@ -103,6 +108,7 @@ public class TripWireBlock extends Block { - } - - private void updateSource(Level world, BlockPos pos, BlockState state) { -+ if (io.papermc.paper.configuration.GlobalConfiguration.get().blockUpdates.disableTripwireUpdates) return; // Paper - prevent adjacent tripwires from updating - Direction[] aenumdirection = new Direction[]{Direction.SOUTH, Direction.WEST}; - int i = aenumdirection.length; - int j = 0; -@@ -135,6 +141,7 @@ public class TripWireBlock extends Block { - - @Override - protected void entityInside(BlockState state, Level world, BlockPos pos, Entity entity) { -+ if (io.papermc.paper.configuration.GlobalConfiguration.get().blockUpdates.disableTripwireUpdates) return; // Paper - prevent tripwires from detecting collision - if (!new io.papermc.paper.event.entity.EntityInsideBlockEvent(entity.getBukkitEntity(), org.bukkit.craftbukkit.block.CraftBlock.at(world, pos)).callEvent()) { return; } // Paper - Add EntityInsideBlockEvent - if (!world.isClientSide) { - if (!(Boolean) state.getValue(TripWireBlock.POWERED)) { -@@ -145,6 +152,7 @@ public class TripWireBlock extends Block { - - @Override - protected void tick(BlockState state, ServerLevel world, BlockPos pos, RandomSource random) { -+ if (io.papermc.paper.configuration.GlobalConfiguration.get().blockUpdates.disableTripwireUpdates) return; // Paper - prevent tripwire pressed check - if ((Boolean) world.getBlockState(pos).getValue(TripWireBlock.POWERED)) { - this.checkPressed(world, pos); - } diff --git a/patches/server/0829-Fix-possible-NPE-on-painting-creation.patch b/patches/server/0829-Fix-possible-NPE-on-painting-creation.patch new file mode 100644 index 000000000000..d5fa31bd07e2 --- /dev/null +++ b/patches/server/0829-Fix-possible-NPE-on-painting-creation.patch @@ -0,0 +1,34 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Jake Potrebic +Date: Sat, 24 Jun 2023 09:42:53 -0700 +Subject: [PATCH] Fix possible NPE on painting creation + + +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftEntityTypes.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftEntityTypes.java +index 39ea25d2145acaa7c7b458800adc674a3e73e7c1..8715d8e790b6735610e2f880f8c1f88a89967f21 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftEntityTypes.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftEntityTypes.java +@@ -361,8 +361,13 @@ public final class CraftEntityTypes { + // Hanging + register(new EntityTypeData<>(EntityType.PAINTING, Painting.class, CraftPainting::new, createHanging(Painting.class, (spawnData, hangingData) -> { + if (spawnData.normalWorld && hangingData.randomize()) { +- return net.minecraft.world.entity.decoration.Painting.create(spawnData.minecraftWorld(), hangingData.position(), hangingData.direction()).orElse(null); +- } else { ++ // Paper start - if randomizeData fails, force it ++ final net.minecraft.world.entity.decoration.Painting entity = net.minecraft.world.entity.decoration.Painting.create(spawnData.minecraftWorld(), hangingData.position(), hangingData.direction()).orElse(null); ++ if (entity != null) { ++ return entity; ++ } ++ } /*else*/ { ++ // Paper end - if randomizeData fails, force it + net.minecraft.world.entity.decoration.Painting entity = new net.minecraft.world.entity.decoration.Painting(net.minecraft.world.entity.EntityType.PAINTING, spawnData.minecraftWorld()); + entity.absMoveTo(spawnData.x(), spawnData.y(), spawnData.z(), spawnData.yaw(), spawnData.pitch()); + entity.setDirection(hangingData.direction()); +@@ -523,6 +528,7 @@ public final class CraftEntityTypes { + AABB bb = (ItemFrame.class.isAssignableFrom(clazz)) + ? net.minecraft.world.entity.decoration.ItemFrame.calculateBoundingBoxStatic(pos, CraftBlock.blockFaceToNotch(dir).getOpposite()) + : net.minecraft.world.entity.decoration.Painting.calculateBoundingBoxStatic(pos, CraftBlock.blockFaceToNotch(dir).getOpposite(), width, height); ++ if (!spawnData.world.noCollision(bb)) continue; // Paper - add collision check + List list = spawnData.world().getEntities(null, bb); + for (Iterator it = list.iterator(); !taken && it.hasNext(); ) { + net.minecraft.world.entity.Entity e = it.next(); diff --git a/patches/server/0830-Call-missing-BlockDispenseEvent.patch b/patches/server/0830-Call-missing-BlockDispenseEvent.patch deleted file mode 100644 index ca534e461773..000000000000 --- a/patches/server/0830-Call-missing-BlockDispenseEvent.patch +++ /dev/null @@ -1,88 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Lulu13022002 <41980282+Lulu13022002@users.noreply.github.com> -Date: Sat, 29 Oct 2022 15:41:56 +0200 -Subject: [PATCH] Call missing BlockDispenseEvent - - -diff --git a/src/main/java/net/minecraft/core/dispenser/DispenseItemBehavior.java b/src/main/java/net/minecraft/core/dispenser/DispenseItemBehavior.java -index 96db0b1041a4c0f054d4f3f2bdced960b119664e..78951f50188528718cdb3dbbaabe3f9f2760ffe3 100644 ---- a/src/main/java/net/minecraft/core/dispenser/DispenseItemBehavior.java -+++ b/src/main/java/net/minecraft/core/dispenser/DispenseItemBehavior.java -@@ -867,6 +867,13 @@ public interface DispenseItemBehavior { - this.setSuccess(true); - if (iblockdata.is(Blocks.RESPAWN_ANCHOR)) { - if ((Integer) iblockdata.getValue(RespawnAnchorBlock.CHARGE) != 4) { -+ // Paper start - Call missing BlockDispenseEvent -+ ItemStack result = org.bukkit.craftbukkit.event.CraftEventFactory.handleBlockDispenseEvent(pointer, blockposition, stack, this); -+ if (result != null) { -+ this.setSuccess(false); -+ return result; -+ } -+ // Paper end - Call missing BlockDispenseEvent - RespawnAnchorBlock.charge((Entity) null, worldserver, blockposition, iblockdata); - stack.shrink(1); - } else { -@@ -944,6 +951,13 @@ public interface DispenseItemBehavior { - Optional optional = HoneycombItem.getWaxed(iblockdata); - - if (optional.isPresent()) { -+ // Paper start - Call missing BlockDispenseEvent -+ ItemStack result = org.bukkit.craftbukkit.event.CraftEventFactory.handleBlockDispenseEvent(pointer, blockposition, stack, this); -+ if (result != null) { -+ this.setSuccess(false); -+ return result; -+ } -+ // Paper end - Call missing BlockDispenseEvent - worldserver.setBlockAndUpdate(blockposition, (BlockState) optional.get()); - worldserver.levelEvent(3003, blockposition, 0); - stack.shrink(1); -@@ -971,6 +985,12 @@ public interface DispenseItemBehavior { - if (!worldserver.getBlockState(blockposition1).is(BlockTags.CONVERTABLE_TO_MUD)) { - return this.defaultDispenseItemBehavior.dispense(pointer, stack); - } else { -+ // Paper start - Call missing BlockDispenseEvent -+ ItemStack result = org.bukkit.craftbukkit.event.CraftEventFactory.handleBlockDispenseEvent(pointer, blockposition1, stack, this); -+ if (result != null) { -+ return result; -+ } -+ // Paper end - Call missing BlockDispenseEvent - if (!worldserver.isClientSide) { - for (int k = 0; k < 5; ++k) { - worldserver.sendParticles(ParticleTypes.SPLASH, (double) blockposition.getX() + worldserver.random.nextDouble(), (double) (blockposition.getY() + 1), (double) blockposition.getZ() + worldserver.random.nextDouble(), 1, 0.0D, 0.0D, 0.0D, 1.0D); -diff --git a/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java b/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java -index 9586837f25464396c0695e87fa278c91ca90183a..9b3023fdda9d08c0f5489542f644e2ea311af191 100644 ---- a/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java -+++ b/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java -@@ -2198,6 +2198,32 @@ public class CraftEventFactory { - } - // Paper end - -+ // Paper start - Call missing BlockDispenseEvent -+ @Nullable -+ public static ItemStack handleBlockDispenseEvent(net.minecraft.core.dispenser.BlockSource pointer, BlockPos to, ItemStack itemStack, net.minecraft.core.dispenser.DispenseItemBehavior instance) { -+ org.bukkit.block.Block bukkitBlock = CraftBlock.at(pointer.level(), pointer.pos()); -+ CraftItemStack craftItem = CraftItemStack.asCraftMirror(itemStack.copyWithCount(1)); -+ -+ org.bukkit.event.block.BlockDispenseEvent event = new org.bukkit.event.block.BlockDispenseEvent(bukkitBlock, craftItem.clone(), CraftVector.toBukkit(to)); -+ if (!net.minecraft.world.level.block.DispenserBlock.eventFired) { -+ if (!event.callEvent()) { -+ return itemStack; -+ } -+ } -+ -+ if (!event.getItem().equals(craftItem)) { -+ // Chain to handler for new item -+ ItemStack eventStack = CraftItemStack.asNMSCopy(event.getItem()); -+ net.minecraft.core.dispenser.DispenseItemBehavior itemBehavior = net.minecraft.world.level.block.DispenserBlock.DISPENSER_REGISTRY.get(eventStack.getItem()); -+ if (itemBehavior != net.minecraft.core.dispenser.DispenseItemBehavior.NOOP && itemBehavior != instance) { -+ itemBehavior.dispense(pointer, eventStack); -+ return itemStack; -+ } -+ } -+ return null; -+ } -+ // Paper end - Call missing BlockDispenseEvent -+ - // Paper start - add EntityFertilizeEggEvent - /** - * Calls the {@link io.papermc.paper.event.entity.EntityFertilizeEggEvent}. diff --git a/patches/server/0830-Only-set-despawnTimer-for-Wandering-Traders-spawned-.patch b/patches/server/0830-Only-set-despawnTimer-for-Wandering-Traders-spawned-.patch new file mode 100644 index 000000000000..c481798890f8 --- /dev/null +++ b/patches/server/0830-Only-set-despawnTimer-for-Wandering-Traders-spawned-.patch @@ -0,0 +1,33 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Jason Penilla <11360596+jpenilla@users.noreply.github.com> +Date: Fri, 19 Mar 2021 16:07:21 -0700 +Subject: [PATCH] Only set despawnTimer for Wandering Traders spawned by + WanderingTraderSpawner + + +diff --git a/src/main/java/net/minecraft/world/entity/npc/WanderingTrader.java b/src/main/java/net/minecraft/world/entity/npc/WanderingTrader.java +index a65fba5621c067c453858efb7fee64cbee1e7916..1e77cce428d9e53142aaa2cf780b7f862d536eca 100644 +--- a/src/main/java/net/minecraft/world/entity/npc/WanderingTrader.java ++++ b/src/main/java/net/minecraft/world/entity/npc/WanderingTrader.java +@@ -69,7 +69,7 @@ public class WanderingTrader extends net.minecraft.world.entity.npc.AbstractVill + + public WanderingTrader(EntityType type, Level world) { + super(type, world); +- this.setDespawnDelay(48000); // CraftBukkit - set default from MobSpawnerTrader ++ //this.setDespawnDelay(48000); // CraftBukkit - set default from MobSpawnerTrader // Paper - move back to MobSpawnerTrader - Vanilla behavior is that only traders spawned by it have this value set. + } + + @Override +diff --git a/src/main/java/net/minecraft/world/entity/npc/WanderingTraderSpawner.java b/src/main/java/net/minecraft/world/entity/npc/WanderingTraderSpawner.java +index 08a3c7140867f339dd99a95094ed0fd8ff344fca..a728dcbf956f108f01c966c7531449a506a14a87 100644 +--- a/src/main/java/net/minecraft/world/entity/npc/WanderingTraderSpawner.java ++++ b/src/main/java/net/minecraft/world/entity/npc/WanderingTraderSpawner.java +@@ -120,7 +120,7 @@ public class WanderingTraderSpawner implements CustomSpawner { + return false; + } + +- WanderingTrader entityvillagertrader = (WanderingTrader) EntityType.WANDERING_TRADER.spawn(world, blockposition2, EntitySpawnReason.EVENT, org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason.NATURAL); // CraftBukkit ++ WanderingTrader entityvillagertrader = (WanderingTrader) EntityType.WANDERING_TRADER.spawn(world, trader -> trader.setDespawnDelay(48000), blockposition2, EntitySpawnReason.EVENT, false, false, org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason.NATURAL); // CraftBukkit // Paper - set despawnTimer before spawn events called + + if (entityvillagertrader != null) { + for (int i = 0; i < 2; ++i) { diff --git a/patches/server/0831-Don-t-load-chunks-for-supporting-block-checks.patch b/patches/server/0831-Don-t-load-chunks-for-supporting-block-checks.patch deleted file mode 100644 index db2c15729d3c..000000000000 --- a/patches/server/0831-Don-t-load-chunks-for-supporting-block-checks.patch +++ /dev/null @@ -1,19 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Shane Freeder -Date: Wed, 5 Jul 2023 23:11:53 +0100 -Subject: [PATCH] Don't load chunks for supporting block checks - - -diff --git a/src/main/java/net/minecraft/world/entity/Entity.java b/src/main/java/net/minecraft/world/entity/Entity.java -index cd362a91ad31dbae94fdb5a8c71839576f397ea1..8e252b930247293e0fbcf350111403ee716cfffa 100644 ---- a/src/main/java/net/minecraft/world/entity/Entity.java -+++ b/src/main/java/net/minecraft/world/entity/Entity.java -@@ -1185,7 +1185,7 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess - } - - protected BlockPos getOnPos(float offset) { -- if (this.mainSupportingBlockPos.isPresent()) { -+ if (this.mainSupportingBlockPos.isPresent() && this.level().getChunkIfLoadedImmediately(this.mainSupportingBlockPos.get()) != null) { // Paper - ensure no loads - BlockPos blockposition = (BlockPos) this.mainSupportingBlockPos.get(); - - if (offset <= 1.0E-5F) { diff --git a/patches/server/0831-ExperienceOrb-should-call-EntitySpawnEvent.patch b/patches/server/0831-ExperienceOrb-should-call-EntitySpawnEvent.patch new file mode 100644 index 000000000000..40dec00abbb1 --- /dev/null +++ b/patches/server/0831-ExperienceOrb-should-call-EntitySpawnEvent.patch @@ -0,0 +1,20 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Golfing8 +Date: Mon, 8 May 2023 09:18:17 -0400 +Subject: [PATCH] ExperienceOrb should call EntitySpawnEvent + + +diff --git a/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java b/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java +index 38c8f69dff0abe62fc405a9fbb29c80d45aa675f..449380c97dc332ecd43e308fcf110c9548930600 100644 +--- a/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java ++++ b/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java +@@ -744,7 +744,8 @@ public class CraftEventFactory { + // Spigot start - SPIGOT-7523: Merge after spawn event and only merge if the event was not cancelled (gets checked above) + if (entity instanceof net.minecraft.world.entity.ExperienceOrb xp) { + double radius = world.spigotConfig.expMerge; +- if (radius > 0) { ++ event = CraftEventFactory.callEntitySpawnEvent(entity); // Call spawn event for ExperienceOrb entities ++ if (radius > 0 && !event.isCancelled() && !entity.isRemoved()) { + // Paper start - Maximum exp value when merging; Whole section has been tweaked, see comments for specifics + final long maxValue = world.paperConfig().entities.behavior.experienceMergeMaxValue; + final boolean mergeUnconditionally = maxValue <= 0; diff --git a/patches/server/0840-Make-Amethyst-throw-both-Spread-and-Grow-Events.patch b/patches/server/0832-Make-Amethyst-throw-both-Spread-and-Grow-Events.patch similarity index 100% rename from patches/server/0840-Make-Amethyst-throw-both-Spread-and-Grow-Events.patch rename to patches/server/0832-Make-Amethyst-throw-both-Spread-and-Grow-Events.patch diff --git a/patches/server/0832-Optimize-player-lookups-for-beacons.patch b/patches/server/0832-Optimize-player-lookups-for-beacons.patch deleted file mode 100644 index 1c203c23a643..000000000000 --- a/patches/server/0832-Optimize-player-lookups-for-beacons.patch +++ /dev/null @@ -1,36 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Spottedleaf -Date: Thu, 6 Jul 2023 20:17:37 -0700 -Subject: [PATCH] Optimize player lookups for beacons - -For larger ranges, it's better to iterate over the player list -than the entity slices. - -diff --git a/src/main/java/net/minecraft/world/level/block/entity/BeaconBlockEntity.java b/src/main/java/net/minecraft/world/level/block/entity/BeaconBlockEntity.java -index b6633ca1ee73ef0f8a220992a2e0424e67dd9758..814e70f558d7a6186233da0ff86c94c95d390e09 100644 ---- a/src/main/java/net/minecraft/world/level/block/entity/BeaconBlockEntity.java -+++ b/src/main/java/net/minecraft/world/level/block/entity/BeaconBlockEntity.java -@@ -333,7 +333,22 @@ public class BeaconBlockEntity extends BlockEntity implements MenuProvider, Name - double d0 = blockEntity != null ? blockEntity.getEffectRange() : (i * 10 + 10); // Paper - Custom beacon ranges - - AABB axisalignedbb = (new AABB(blockposition)).inflate(d0).expandTowards(0.0D, (double) world.getHeight(), 0.0D); -- List list = world.getEntitiesOfClass(Player.class, axisalignedbb); -+ // Paper start - Perf: optimize player lookup for beacons -+ List list; -+ if (d0 <= 128.0) { -+ list = world.getEntitiesOfClass(Player.class, axisalignedbb); -+ } else { -+ list = new java.util.ArrayList<>(); -+ for (Player player : world.players()) { -+ if (player.isSpectator()) { -+ continue; -+ } -+ if (player.getBoundingBox().intersects(axisalignedbb)) { -+ list.add(player); -+ } -+ } -+ } -+ // Paper end - Perf: optimize player lookup for beacons - - return list; - } diff --git a/patches/server/0841-Add-whitelist-events.patch b/patches/server/0833-Add-whitelist-events.patch similarity index 100% rename from patches/server/0841-Add-whitelist-events.patch rename to patches/server/0833-Add-whitelist-events.patch diff --git a/patches/server/0833-More-Sign-Block-API.patch b/patches/server/0833-More-Sign-Block-API.patch deleted file mode 100644 index 3be6f4e3539f..000000000000 --- a/patches/server/0833-More-Sign-Block-API.patch +++ /dev/null @@ -1,62 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Jake Potrebic -Date: Fri, 23 Jun 2023 12:16:28 -0700 -Subject: [PATCH] More Sign Block API - -Co-authored-by: SoSeDiK - -diff --git a/src/main/java/net/minecraft/world/level/block/entity/SignBlockEntity.java b/src/main/java/net/minecraft/world/level/block/entity/SignBlockEntity.java -index bfe8029852385875af4ebe73c63e688f61042021..3070cd2b588f5a69fd8c0d3551e16251680d8c27 100644 ---- a/src/main/java/net/minecraft/world/level/block/entity/SignBlockEntity.java -+++ b/src/main/java/net/minecraft/world/level/block/entity/SignBlockEntity.java -@@ -68,12 +68,17 @@ public class SignBlockEntity extends BlockEntity implements CommandSource { // C - } - - public boolean isFacingFrontText(net.minecraft.world.entity.player.Player player) { -+ // Paper start - More Sign Block API -+ return this.isFacingFrontText(player.getX(), player.getZ()); -+ } -+ public boolean isFacingFrontText(double x, double z) { -+ // Paper end - More Sign Block API - Block block = this.getBlockState().getBlock(); - - if (block instanceof SignBlock blocksign) { - Vec3 vec3d = blocksign.getSignHitboxCenterPosition(this.getBlockState()); -- double d0 = player.getX() - ((double) this.getBlockPos().getX() + vec3d.x); -- double d1 = player.getZ() - ((double) this.getBlockPos().getZ() + vec3d.z); -+ double d0 = x - ((double) this.getBlockPos().getX() + vec3d.x); // Paper - More Sign Block API -+ double d1 = z - ((double) this.getBlockPos().getZ() + vec3d.z); // Paper - More Sign Block API - float f = blocksign.getYRotationDegrees(this.getBlockState()); - float f1 = (float) (Mth.atan2(d1, d0) * 57.2957763671875D) - 90.0F; - -diff --git a/src/main/java/org/bukkit/craftbukkit/block/CraftSign.java b/src/main/java/org/bukkit/craftbukkit/block/CraftSign.java -index 8303343ecca91076839f4436d6b3a3bf4739c2fd..e3c333d26dea9af9dd51e1662f81f1d472ab0233 100644 ---- a/src/main/java/org/bukkit/craftbukkit/block/CraftSign.java -+++ b/src/main/java/org/bukkit/craftbukkit/block/CraftSign.java -@@ -198,6 +198,26 @@ public class CraftSign extends CraftBlockEntityState< - } - // Paper end - -+ // Paper start - More Sign Block API -+ @Override -+ public java.util.UUID getAllowedEditorUniqueId() { -+ this.ensureNoWorldGeneration(); -+ return this.getTileEntity().getPlayerWhoMayEdit(); -+ } -+ -+ @Override -+ public void setAllowedEditorUniqueId(java.util.UUID uuid) { -+ this.ensureNoWorldGeneration(); -+ this.getTileEntity().setAllowedPlayerEditor(uuid); -+ } -+ -+ @Override -+ public Side getInteractableSideFor(final double x, final double z) { -+ this.requirePlaced(); -+ return this.getSnapshot().isFacingFrontText(x, z) ? Side.FRONT : Side.BACK; -+ } -+ // Paper end - More Sign Block API -+ - public static Component[] sanitizeLines(String[] lines) { - Component[] components = new Component[4]; - diff --git a/patches/server/0834-Implement-PlayerFailMoveEvent.patch b/patches/server/0834-Implement-PlayerFailMoveEvent.patch new file mode 100644 index 000000000000..1c34e55f29cb --- /dev/null +++ b/patches/server/0834-Implement-PlayerFailMoveEvent.patch @@ -0,0 +1,111 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Moulberry +Date: Wed, 26 Jul 2023 20:13:31 +0800 +Subject: [PATCH] Implement PlayerFailMoveEvent + + +diff --git a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java +index 919b0ffb69720b3edeb9d25c7f41291a09976505..134d9fbdb757094a4624325c0ad8f32efadd61b6 100644 +--- a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java ++++ b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java +@@ -1271,8 +1271,8 @@ public class ServerGamePacketListenerImpl extends ServerCommonPacketListenerImpl + double d0 = ServerGamePacketListenerImpl.clampHorizontal(packet.getX(this.player.getX())); final double toX = d0; // Paper - OBFHELPER + double d1 = ServerGamePacketListenerImpl.clampVertical(packet.getY(this.player.getY())); final double toY = d1; // Paper - OBFHELPER + double d2 = ServerGamePacketListenerImpl.clampHorizontal(packet.getZ(this.player.getZ())); final double toZ = d2; // Paper - OBFHELPER +- float f = Mth.wrapDegrees(packet.getYRot(this.player.getYRot())); +- float f1 = Mth.wrapDegrees(packet.getXRot(this.player.getXRot())); ++ float f = Mth.wrapDegrees(packet.getYRot(this.player.getYRot())); final float toYaw = f; // Paper - OBFHELPER ++ float f1 = Mth.wrapDegrees(packet.getXRot(this.player.getXRot())); final float toPitch = f1; // Paper - OBFHELPER + + if (this.player.isPassenger()) { + this.player.absMoveTo(this.player.getX(), this.player.getY(), this.player.getZ(), f, f1); +@@ -1339,8 +1339,14 @@ public class ServerGamePacketListenerImpl extends ServerCommonPacketListenerImpl + } + // Paper start - Prevent moving into unloaded chunks + if (this.player.level().paperConfig().chunks.preventMovingIntoUnloadedChunks && (this.player.getX() != toX || this.player.getZ() != toZ) && !worldserver.areChunksLoadedForMove(this.player.getBoundingBox().expandTowards(new Vec3(toX, toY, toZ).subtract(this.player.position())))) { +- this.internalTeleport(PositionMoveRotation.of(this.player), Collections.emptySet()); +- return; ++ // Paper start - Add fail move event ++ io.papermc.paper.event.player.PlayerFailMoveEvent event = fireFailMove(io.papermc.paper.event.player.PlayerFailMoveEvent.FailReason.MOVED_INTO_UNLOADED_CHUNK, ++ toX, toY, toZ, toYaw, toPitch, false); ++ if (!event.isAllowed()) { ++ this.internalTeleport(PositionMoveRotation.of(this.player), Collections.emptySet()); ++ return; ++ } ++ // Paper end - Add fail move event + } + // Paper end - Prevent moving into unloaded chunks + +@@ -1349,9 +1355,16 @@ public class ServerGamePacketListenerImpl extends ServerCommonPacketListenerImpl + + if (d10 - d9 > Math.max(f2, Math.pow((double) (org.spigotmc.SpigotConfig.movedTooQuicklyMultiplier * (float) i * speed), 2))) { + // CraftBukkit end +- ServerGamePacketListenerImpl.LOGGER.warn("{} moved too quickly! {},{},{}", new Object[]{this.player.getName().getString(), d6, d7, d8}); +- this.teleport(this.player.getX(), this.player.getY(), this.player.getZ(), this.player.getYRot(), this.player.getXRot()); +- return; ++ // Paper start - Add fail move event ++ io.papermc.paper.event.player.PlayerFailMoveEvent event = fireFailMove(io.papermc.paper.event.player.PlayerFailMoveEvent.FailReason.MOVED_TOO_QUICKLY, ++ toX, toY, toZ, toYaw, toPitch, true); ++ if (!event.isAllowed()) { ++ if (event.getLogWarning()) ++ ServerGamePacketListenerImpl.LOGGER.warn("{} moved too quickly! {},{},{}", new Object[]{this.player.getName().getString(), d6, d7, d8}); ++ this.teleport(this.player.getX(), this.player.getY(), this.player.getZ(), this.player.getYRot(), this.player.getXRot()); ++ return; ++ } ++ // Paper end - Add fail move event + } + } + } +@@ -1413,14 +1426,31 @@ public class ServerGamePacketListenerImpl extends ServerCommonPacketListenerImpl + + d8 = d2 - this.player.getZ(); + d10 = d6 * d6 + d7 * d7 + d8 * d8; +- boolean flag3 = false; ++ boolean movedWrongly = false; // Paper - Add fail move event; rename + + if (!this.player.isChangingDimension() && d10 > org.spigotmc.SpigotConfig.movedWronglyThreshold && !this.player.isSleeping() && !this.player.gameMode.isCreative() && this.player.gameMode.getGameModeForPlayer() != GameType.SPECTATOR) { // Spigot +- flag3 = true; ++ // Paper start - Add fail move event ++ io.papermc.paper.event.player.PlayerFailMoveEvent event = fireFailMove(io.papermc.paper.event.player.PlayerFailMoveEvent.FailReason.MOVED_WRONGLY, ++ toX, toY, toZ, toYaw, toPitch, true); ++ if (!event.isAllowed()) { ++ movedWrongly = true; ++ if (event.getLogWarning()) ++ // Paper end + ServerGamePacketListenerImpl.LOGGER.warn("{} moved wrongly!", this.player.getName().getString()); ++ } // Paper + } + +- if (!this.player.noPhysics && !this.player.isSleeping() && (flag3 && worldserver.noCollision(this.player, axisalignedbb) || this.isPlayerCollidingWithAnythingNew(worldserver, axisalignedbb, d0, d1, d2))) { ++ // Paper start - Add fail move event ++ boolean teleportBack = !this.player.noPhysics && !this.player.isSleeping() && (movedWrongly && worldserver.noCollision(this.player, axisalignedbb) || this.isPlayerCollidingWithAnythingNew(worldserver, axisalignedbb, d0, d1, d2)); ++ if (teleportBack) { ++ io.papermc.paper.event.player.PlayerFailMoveEvent event = fireFailMove(io.papermc.paper.event.player.PlayerFailMoveEvent.FailReason.CLIPPED_INTO_BLOCK, ++ toX, toY, toZ, toYaw, toPitch, false); ++ if (event.isAllowed()) { ++ teleportBack = false; ++ } ++ } ++ if (teleportBack) { ++ // Paper end - Add fail move event + this.internalTeleport(d3, d4, d5, f, f1); // CraftBukkit - SPIGOT-1807: Don't call teleport event, when the client thinks the player is falling, because the chunks are not loaded on the client yet. + this.player.doCheckFallDamage(this.player.getX() - d3, this.player.getY() - d4, this.player.getZ() - d5, packet.isOnGround()); + } else { +@@ -3534,4 +3564,17 @@ public class ServerGamePacketListenerImpl extends ServerCommonPacketListenerImpl + + InteractionResult run(ServerPlayer player, Entity entity, InteractionHand hand); + } ++ ++ // Paper start - Add fail move event ++ private io.papermc.paper.event.player.PlayerFailMoveEvent fireFailMove(io.papermc.paper.event.player.PlayerFailMoveEvent.FailReason failReason, ++ double toX, double toY, double toZ, float toYaw, float toPitch, boolean logWarning) { ++ Player player = this.getCraftPlayer(); ++ Location from = new Location(player.getWorld(), this.lastPosX, this.lastPosY, this.lastPosZ, this.lastYaw, this.lastPitch); ++ Location to = new Location(player.getWorld(), toX, toY, toZ, toYaw, toPitch); ++ io.papermc.paper.event.player.PlayerFailMoveEvent event = new io.papermc.paper.event.player.PlayerFailMoveEvent(player, failReason, ++ false, logWarning, from, to); ++ event.callEvent(); ++ return event; ++ } ++ // Paper end - Add fail move event + } diff --git a/patches/server/0834-fix-item-meta-for-tadpole-buckets.patch b/patches/server/0834-fix-item-meta-for-tadpole-buckets.patch deleted file mode 100644 index 6c9509e2cfaa..000000000000 --- a/patches/server/0834-fix-item-meta-for-tadpole-buckets.patch +++ /dev/null @@ -1,63 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Jake Potrebic -Date: Tue, 11 Jul 2023 11:22:30 -0700 -Subject: [PATCH] fix item meta for tadpole buckets - - -diff --git a/src/main/java/org/bukkit/craftbukkit/inventory/CraftItemMetas.java b/src/main/java/org/bukkit/craftbukkit/inventory/CraftItemMetas.java -index c9fbc01be0b0e7fd1cafb091d06496f4ba1e7c2c..a4c4ba0d02f9a072236ce86c1e98e2c60b059cb8 100644 ---- a/src/main/java/org/bukkit/craftbukkit/inventory/CraftItemMetas.java -+++ b/src/main/java/org/bukkit/craftbukkit/inventory/CraftItemMetas.java -@@ -263,7 +263,7 @@ public final class CraftItemMetas { - if (itemType == ItemType.SUSPICIOUS_STEW) { - return CraftItemMetas.asType(CraftItemMetas.SUSPICIOUS_STEW_META_DATA); - } -- if (itemType == ItemType.COD_BUCKET || itemType == ItemType.PUFFERFISH_BUCKET -+ if (itemType == ItemType.COD_BUCKET || itemType == ItemType.PUFFERFISH_BUCKET || itemType == ItemType.TADPOLE_BUCKET // Paper - || itemType == ItemType.SALMON_BUCKET || itemType == ItemType.ITEM_FRAME - || itemType == ItemType.GLOW_ITEM_FRAME || itemType == ItemType.PAINTING) { - return CraftItemMetas.asType(CraftItemMetas.ENTITY_TAG_META_DATA); -diff --git a/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaEntityTag.java b/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaEntityTag.java -index 27af7ca9d62bdb4a24be5af139c181d7bc271ba5..3ff0340c40e9dc9a6e690de15ccade7a0c4e8f02 100644 ---- a/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaEntityTag.java -+++ b/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaEntityTag.java -@@ -19,6 +19,7 @@ public class CraftMetaEntityTag extends CraftMetaItem { - Material.COD_BUCKET, - Material.PUFFERFISH_BUCKET, - Material.SALMON_BUCKET, -+ Material.TADPOLE_BUCKET, // Paper - Material.ITEM_FRAME, - Material.GLOW_ITEM_FRAME, - Material.PAINTING -diff --git a/src/test/java/org/bukkit/craftbukkit/inventory/ItemMetaTest.java b/src/test/java/org/bukkit/craftbukkit/inventory/ItemMetaTest.java -index 50faaaa48dffcaf53823caed1e3f7263cd5c441f..ba5c958f322dc34baff3c9d1b99741a4ffeee135 100644 ---- a/src/test/java/org/bukkit/craftbukkit/inventory/ItemMetaTest.java -+++ b/src/test/java/org/bukkit/craftbukkit/inventory/ItemMetaTest.java -@@ -209,6 +209,27 @@ public class ItemMetaTest { - } - } - -+ // Paper start - check entity tag metas -+ private static final java.util.Set> ENTITY_TAG_METAS = java.util.Set.of( -+ CraftMetaEntityTag.class, -+ CraftMetaTropicalFishBucket.class, -+ CraftMetaAxolotlBucket.class -+ ); -+ @Test -+ public void testEntityTagMeta() { -+ for (final Item item : BuiltInRegistries.ITEM) { -+ if (item instanceof net.minecraft.world.item.HangingEntityItem || item instanceof net.minecraft.world.item.MobBucketItem) { -+ ItemStack stack = new ItemStack(CraftItemType.minecraftToBukkit(item)); -+ assertTrue(ENTITY_TAG_METAS.contains(stack.getItemMeta().getClass()), "missing entity tag meta handling for " + item); -+ stack = CraftItemStack.asNewCraftStack(net.minecraft.world.item.Items.STONE); -+ stack.editMeta(meta -> meta.displayName(net.kyori.adventure.text.Component.text("hello"))); -+ stack.setType(CraftItemType.minecraftToBukkit(item)); -+ assertTrue(ENTITY_TAG_METAS.contains(stack.getItemMeta().getClass()), "missing entity tag meta handling for " + item); -+ } -+ } -+ } -+ // Paper end -+ - @Test - public void testEachExtraData() { - final List providers = Arrays.asList( diff --git a/patches/server/0835-Fix-BanList-API.patch b/patches/server/0835-Fix-BanList-API.patch deleted file mode 100644 index 352a89b454ba..000000000000 --- a/patches/server/0835-Fix-BanList-API.patch +++ /dev/null @@ -1,351 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Jake Potrebic -Date: Tue, 4 Jul 2023 11:27:10 -0700 -Subject: [PATCH] Fix BanList API - - -diff --git a/src/main/java/org/bukkit/craftbukkit/CraftOfflinePlayer.java b/src/main/java/org/bukkit/craftbukkit/CraftOfflinePlayer.java -index 30c1c203022c2ed777dcddfd68ef2e3752c62ea1..2c2c4db31a746b4eb853dc04c6b3e5631bbfa034 100644 ---- a/src/main/java/org/bukkit/craftbukkit/CraftOfflinePlayer.java -+++ b/src/main/java/org/bukkit/craftbukkit/CraftOfflinePlayer.java -@@ -115,17 +115,17 @@ public class CraftOfflinePlayer implements OfflinePlayer, ConfigurationSerializa - } - - @Override -- public BanEntry ban(String reason, Date expires, String source) { -+ public BanEntry ban(String reason, Date expires, String source) { // Paper - fix ban list API - return ((ProfileBanList) this.server.getBanList(BanList.Type.PROFILE)).addBan(this.getPlayerProfile(), reason, expires, source); - } - - @Override -- public BanEntry ban(String reason, Instant expires, String source) { -+ public BanEntry ban(String reason, Instant expires, String source) { // Paper - fix ban list API - return ((ProfileBanList) this.server.getBanList(BanList.Type.PROFILE)).addBan(this.getPlayerProfile(), reason, expires, source); - } - - @Override -- public BanEntry ban(String reason, Duration duration, String source) { -+ public BanEntry ban(String reason, Duration duration, String source) { // Paper - fix ban list API - return ((ProfileBanList) this.server.getBanList(BanList.Type.PROFILE)).addBan(this.getPlayerProfile(), reason, duration, source); - } - -diff --git a/src/main/java/org/bukkit/craftbukkit/ban/CraftProfileBanEntry.java b/src/main/java/org/bukkit/craftbukkit/ban/CraftProfileBanEntry.java -index 13e5e44b069121e51b9486c445902937f1d6c6d8..4a37c8172b42b10472bb90c9310c7ae3eeaa3481 100644 ---- a/src/main/java/org/bukkit/craftbukkit/ban/CraftProfileBanEntry.java -+++ b/src/main/java/org/bukkit/craftbukkit/ban/CraftProfileBanEntry.java -@@ -9,7 +9,7 @@ import org.bukkit.BanEntry; - import org.bukkit.craftbukkit.profile.CraftPlayerProfile; - import org.bukkit.profile.PlayerProfile; - --public final class CraftProfileBanEntry implements BanEntry { -+public final class CraftProfileBanEntry implements BanEntry { // Paper - private static final Date minorDate = Date.from(Instant.parse("1899-12-31T04:00:00Z")); - private final UserBanList list; - private final GameProfile profile; -@@ -33,8 +33,8 @@ public final class CraftProfileBanEntry implements BanEntry { - } - - @Override -- public PlayerProfile getBanTarget() { -- return new CraftPlayerProfile(this.profile); -+ public com.destroystokyo.paper.profile.PlayerProfile getBanTarget() { // Paper -+ return new com.destroystokyo.paper.profile.CraftPlayerProfile(this.profile); // Paper - } - - @Override -diff --git a/src/main/java/org/bukkit/craftbukkit/ban/CraftProfileBanList.java b/src/main/java/org/bukkit/craftbukkit/ban/CraftProfileBanList.java -index 172202accf4448a933fcf1ff820316c7910dd7f7..50ee7656580d386db473c054f5c5ec57bb2b1424 100644 ---- a/src/main/java/org/bukkit/craftbukkit/ban/CraftProfileBanList.java -+++ b/src/main/java/org/bukkit/craftbukkit/ban/CraftProfileBanList.java -@@ -24,42 +24,80 @@ public class CraftProfileBanList implements ProfileBanList { - } - - @Override -- public BanEntry getBanEntry(String target) { -+ public BanEntry getBanEntry(String target) { // Paper - Preconditions.checkArgument(target != null, "Target cannot be null"); - - return this.getBanEntry(CraftProfileBanList.getProfile(target)); - } - - @Override -- public BanEntry getBanEntry(PlayerProfile target) { -+ public BanEntry getBanEntry(PlayerProfile target) { // Paper - Preconditions.checkArgument(target != null, "Target cannot be null"); - -- return this.getBanEntry(((CraftPlayerProfile) target).buildGameProfile()); -+ return this.getBanEntry(((com.destroystokyo.paper.profile.SharedPlayerProfile) target).buildGameProfile()); // Paper -+ } -+ // Paper start - fix ban list API -+ @Override -+ public BanEntry getBanEntry(final com.destroystokyo.paper.profile.PlayerProfile target) { -+ Preconditions.checkArgument(target != null, "target cannot be null"); -+ -+ return this.getBanEntry(((com.destroystokyo.paper.profile.SharedPlayerProfile) target).buildGameProfile()); -+ } -+ -+ @Override -+ public BanEntry addBan(final com.destroystokyo.paper.profile.PlayerProfile target, final String reason, final Date expires, final String source) { -+ Preconditions.checkArgument(target != null, "PlayerProfile cannot be null"); -+ Preconditions.checkArgument(target.getId() != null, "The PlayerProfile UUID cannot be null"); -+ -+ return this.addBan(((com.destroystokyo.paper.profile.SharedPlayerProfile) target).buildGameProfile(), reason, expires, source); -+ } -+ -+ @Override -+ public boolean isBanned(final com.destroystokyo.paper.profile.PlayerProfile target) { -+ return this.isBanned((com.destroystokyo.paper.profile.SharedPlayerProfile) target); -+ } -+ -+ @Override -+ public void pardon(final com.destroystokyo.paper.profile.PlayerProfile target) { -+ this.pardon((com.destroystokyo.paper.profile.SharedPlayerProfile) target); - } - - @Override -- public BanEntry addBan(String target, String reason, Date expires, String source) { -+ public BanEntry addBan(final com.destroystokyo.paper.profile.PlayerProfile target, final String reason, final Instant expires, final String source) { -+ Date date = expires != null ? Date.from(expires) : null; -+ return this.addBan(target, reason, date, source); -+ } -+ -+ @Override -+ public BanEntry addBan(final com.destroystokyo.paper.profile.PlayerProfile target, final String reason, final Duration duration, final String source) { -+ Instant instant = duration != null ? Instant.now().plus(duration) : null; -+ return this.addBan(target, reason, instant, source); -+ } -+ // Paper end - fix ban list API -+ -+ @Override -+ public BanEntry addBan(String target, String reason, Date expires, String source) { // Paper - fix ban list API - Preconditions.checkArgument(target != null, "Ban target cannot be null"); - - return this.addBan(CraftProfileBanList.getProfileByName(target), reason, expires, source); - } - - @Override -- public BanEntry addBan(PlayerProfile target, String reason, Date expires, String source) { -+ public BanEntry addBan(PlayerProfile target, String reason, Date expires, String source) { // Paper - fix ban list API - Preconditions.checkArgument(target != null, "PlayerProfile cannot be null"); - Preconditions.checkArgument(target.getUniqueId() != null, "The PlayerProfile UUID cannot be null"); - -- return this.addBan(((CraftPlayerProfile) target).buildGameProfile(), reason, expires, source); -+ return this.addBan(((com.destroystokyo.paper.profile.SharedPlayerProfile) target).buildGameProfile(), reason, expires, source); // Paper - } - - @Override -- public BanEntry addBan(PlayerProfile target, String reason, Instant expires, String source) { -+ public BanEntry addBan(PlayerProfile target, String reason, Instant expires, String source) { // Paper - fix ban list API - Date date = expires != null ? Date.from(expires) : null; - return this.addBan(target, reason, date, source); - } - - @Override -- public BanEntry addBan(PlayerProfile target, String reason, Duration duration, String source) { -+ public BanEntry addBan(PlayerProfile target, String reason, Duration duration, String source) { // Paper - fix ban list API - Instant instant = duration != null ? Instant.now().plus(duration) : null; - return this.addBan(target, reason, instant, source); - } -@@ -76,8 +114,8 @@ public class CraftProfileBanList implements ProfileBanList { - } - - @Override -- public Set> getEntries() { -- ImmutableSet.Builder> builder = ImmutableSet.builder(); -+ public Set> getEntries() { // Paper -+ ImmutableSet.Builder> builder = ImmutableSet.builder(); // Paper - for (UserBanListEntry entry : this.list.getEntries()) { - GameProfile profile = entry.getUser(); - builder.add(new CraftProfileBanEntry(profile, entry, this.list)); -@@ -88,9 +126,14 @@ public class CraftProfileBanList implements ProfileBanList { - - @Override - public boolean isBanned(PlayerProfile target) { -+ // Paper start -+ return this.isBanned((com.destroystokyo.paper.profile.SharedPlayerProfile) target); -+ } -+ private boolean isBanned(com.destroystokyo.paper.profile.SharedPlayerProfile target) { -+ // Paper end - Preconditions.checkArgument(target != null, "Target cannot be null"); - -- return this.isBanned(((CraftPlayerProfile) target).buildGameProfile()); -+ return this.isBanned(target.buildGameProfile()); // Paper - } - - @Override -@@ -102,9 +145,14 @@ public class CraftProfileBanList implements ProfileBanList { - - @Override - public void pardon(PlayerProfile target) { -+ // Paper start -+ this.pardon((com.destroystokyo.paper.profile.SharedPlayerProfile) target); -+ } -+ private void pardon(com.destroystokyo.paper.profile.SharedPlayerProfile target) { -+ // Paper end - Preconditions.checkArgument(target != null, "Target cannot be null"); - -- this.pardon(((CraftPlayerProfile) target).buildGameProfile()); -+ this.pardon(target.buildGameProfile()); // Paper - } - - @Override -@@ -114,7 +162,7 @@ public class CraftProfileBanList implements ProfileBanList { - this.pardon(CraftProfileBanList.getProfile(target)); - } - -- public BanEntry getBanEntry(GameProfile profile) { -+ public BanEntry getBanEntry(GameProfile profile) { // Paper - if (profile == null) { - return null; - } -@@ -127,7 +175,7 @@ public class CraftProfileBanList implements ProfileBanList { - return new CraftProfileBanEntry(profile, entry, this.list); - } - -- public BanEntry addBan(GameProfile profile, String reason, Date expires, String source) { -+ public BanEntry addBan(GameProfile profile, String reason, Date expires, String source) { // Paper - if (profile == null) { - return null; - } -diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java -index 3945a6ab723deee3ec3ebb5fbc726ce16bbca411..62914bde6f6b045e6b3682581899d756d1b272f1 100644 ---- a/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java -+++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java -@@ -1747,23 +1747,23 @@ public class CraftPlayer extends CraftHumanEntity implements Player { - } - - @Override -- public BanEntry ban(String reason, Date expires, String source) { -+ public BanEntry ban(String reason, Date expires, String source) { // Paper - fix ban list API - return this.ban(reason, expires, source, true); - } - - @Override -- public BanEntry ban(String reason, Instant expires, String source) { -+ public BanEntry ban(String reason, Instant expires, String source) { // Paper - fix ban list API - return this.ban(reason, expires != null ? Date.from(expires) : null, source); - } - - @Override -- public BanEntry ban(String reason, Duration duration, String source) { -+ public BanEntry ban(String reason, Duration duration, String source) { // Paper - fix ban list API - return this.ban(reason, duration != null ? Instant.now().plus(duration) : null, source); - } - - @Override -- public BanEntry ban(String reason, Date expires, String source, boolean kickPlayer) { -- BanEntry banEntry = ((ProfileBanList) this.server.getBanList(BanList.Type.PROFILE)).addBan(this.getPlayerProfile(), reason, expires, source); -+ public BanEntry ban(String reason, Date expires, String source, boolean kickPlayer) { // Paper - fix ban list API -+ BanEntry banEntry = ((ProfileBanList) this.server.getBanList(BanList.Type.PROFILE)).addBan(this.getPlayerProfile(), reason, expires, source); // Paper - fix ban list API - if (kickPlayer) { - this.kickPlayer(reason); - } -@@ -1771,12 +1771,12 @@ public class CraftPlayer extends CraftHumanEntity implements Player { - } - - @Override -- public BanEntry ban(String reason, Instant instant, String source, boolean kickPlayer) { -+ public BanEntry ban(String reason, Instant instant, String source, boolean kickPlayer) { // Paper - fix ban list API - return this.ban(reason, instant != null ? Date.from(instant) : null, source, kickPlayer); - } - - @Override -- public BanEntry ban(String reason, Duration duration, String source, boolean kickPlayer) { -+ public BanEntry ban(String reason, Duration duration, String source, boolean kickPlayer) { // Paper - fix ban list API - return this.ban(reason, duration != null ? Instant.now().plus(duration) : null, source, kickPlayer); - } - -diff --git a/src/main/java/org/bukkit/craftbukkit/profile/CraftPlayerProfile.java b/src/main/java/org/bukkit/craftbukkit/profile/CraftPlayerProfile.java -index d9cc76d7e60001957c0f59fdc32d016f3589aa08..f58dec12326734c61f4bc2298a87fb38f1ac1dc4 100644 ---- a/src/main/java/org/bukkit/craftbukkit/profile/CraftPlayerProfile.java -+++ b/src/main/java/org/bukkit/craftbukkit/profile/CraftPlayerProfile.java -@@ -31,7 +31,7 @@ import org.bukkit.profile.PlayerTextures; - import org.jetbrains.annotations.ApiStatus; - - @SerializableAs("PlayerProfile") --public final class CraftPlayerProfile implements PlayerProfile, com.destroystokyo.paper.profile.SharedPlayerProfile { // Paper -+public final class CraftPlayerProfile implements PlayerProfile, com.destroystokyo.paper.profile.SharedPlayerProfile, com.destroystokyo.paper.profile.PlayerProfile { // Paper - - @Nonnull - public static GameProfile validateSkullProfile(@Nonnull GameProfile gameProfile) { -@@ -162,7 +162,7 @@ public final class CraftPlayerProfile implements PlayerProfile, com.destroystoky - } - - @Override -- public CompletableFuture update() { -+ public CompletableFuture update() { // Paper - have to remove generic to avoid clashing between bukkit.PlayerProfile and paper.PlayerProfile - return CompletableFuture.supplyAsync(this::getUpdatedProfile, Util.PROFILE_EXECUTOR); // Paper - don't submit BLOCKING PROFILE LOOKUPS to the world gen thread - } - -@@ -321,4 +321,71 @@ public final class CraftPlayerProfile implements PlayerProfile, com.destroystoky - // Paper - diff on change - return profile; - } -+ -+ // Paper start - This must implement our PlayerProfile so generic casts succeed from cb.CraftPlayerProfile to paper.PlayerProfile -+ // The methods don't actually have to be implemented, because the profile should immediately be cast to SharedPlayerProfile -+ @Override -+ public String setName(final String name) { -+ throw new UnsupportedOperationException("Do not cast to com.destroystokyo.paper.profile.PlayerProfile"); -+ } -+ -+ @Override -+ public UUID getId() { -+ throw new UnsupportedOperationException("Do not cast to com.destroystokyo.paper.profile.PlayerProfile"); -+ } -+ -+ @Override -+ public UUID setId(final UUID uuid) { -+ throw new UnsupportedOperationException("Do not cast to com.destroystokyo.paper.profile.PlayerProfile"); -+ } -+ -+ @Override -+ public java.util.Set getProperties() { -+ throw new UnsupportedOperationException("Do not cast to com.destroystokyo.paper.profile.PlayerProfile"); -+ } -+ -+ @Override -+ public boolean hasProperty(final String property) { -+ throw new UnsupportedOperationException("Do not cast to com.destroystokyo.paper.profile.PlayerProfile"); -+ } -+ -+ @Override -+ public void setProperty(final com.destroystokyo.paper.profile.ProfileProperty property) { -+ throw new UnsupportedOperationException("Do not cast to com.destroystokyo.paper.profile.PlayerProfile"); -+ } -+ -+ @Override -+ public void setProperties(final java.util.Collection properties) { -+ throw new UnsupportedOperationException("Do not cast to com.destroystokyo.paper.profile.PlayerProfile"); -+ } -+ -+ @Override -+ public void clearProperties() { -+ throw new UnsupportedOperationException("Do not cast to com.destroystokyo.paper.profile.PlayerProfile"); -+ } -+ -+ @Override -+ public boolean completeFromCache() { -+ throw new UnsupportedOperationException("Do not cast to com.destroystokyo.paper.profile.PlayerProfile"); -+ } -+ -+ @Override -+ public boolean completeFromCache(final boolean onlineMode) { -+ throw new UnsupportedOperationException("Do not cast to com.destroystokyo.paper.profile.PlayerProfile"); -+ } -+ -+ @Override -+ public boolean completeFromCache(final boolean lookupUUID, final boolean onlineMode) { -+ throw new UnsupportedOperationException("Do not cast to com.destroystokyo.paper.profile.PlayerProfile"); -+ } -+ -+ @Override -+ public boolean complete(final boolean textures) { -+ throw new UnsupportedOperationException("Do not cast to com.destroystokyo.paper.profile.PlayerProfile"); -+ } -+ -+ @Override -+ public boolean complete(final boolean textures, final boolean onlineMode) { -+ throw new UnsupportedOperationException("Do not cast to com.destroystokyo.paper.profile.PlayerProfile"); -+ } - } diff --git a/patches/server/0835-Folia-scheduler-and-owned-region-API.patch b/patches/server/0835-Folia-scheduler-and-owned-region-API.patch new file mode 100644 index 000000000000..2ee4d8a5b522 --- /dev/null +++ b/patches/server/0835-Folia-scheduler-and-owned-region-API.patch @@ -0,0 +1,1364 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Spottedleaf +Date: Sat, 17 Jun 2023 11:52:52 +0200 +Subject: [PATCH] Folia scheduler and owned region API + +Pulling Folia API to Paper is primarily intended for plugins +that want to target both Paper and Folia without unnecessary +compatibility layers. + +Add both a location based scheduler, an entity based scheduler, +and a global region scheduler. + +Owned region API may be useful for plugins which want to perform +operations over large areas outside of the buffer zone provided +by the regionaliser, as it is not guaranteed that anything +outside of the buffer zone is owned. Then, the plugins may use +the schedulers depending on the result of the ownership check. + +diff --git a/src/main/java/io/papermc/paper/plugin/manager/PaperPluginInstanceManager.java b/src/main/java/io/papermc/paper/plugin/manager/PaperPluginInstanceManager.java +index d2dee700f2c5cc7d6a272e751a933901fe7a55b6..834b85f24df023642f8abf7213fe578ac8c17a3e 100644 +--- a/src/main/java/io/papermc/paper/plugin/manager/PaperPluginInstanceManager.java ++++ b/src/main/java/io/papermc/paper/plugin/manager/PaperPluginInstanceManager.java +@@ -263,6 +263,22 @@ class PaperPluginInstanceManager { + + pluginName + " (Is it up to date?)", ex, plugin); // Paper + } + ++ // Paper start - Folia schedulers ++ try { ++ this.server.getGlobalRegionScheduler().cancelTasks(plugin); ++ } catch (Throwable ex) { ++ this.handlePluginException("Error occurred (in the plugin loader) while cancelling global tasks for " ++ + pluginName + " (Is it up to date?)", ex, plugin); // Paper ++ } ++ ++ try { ++ this.server.getAsyncScheduler().cancelTasks(plugin); ++ } catch (Throwable ex) { ++ this.handlePluginException("Error occurred (in the plugin loader) while cancelling async tasks for " ++ + pluginName + " (Is it up to date?)", ex, plugin); // Paper ++ } ++ // Paper end - Folia schedulers ++ + try { + this.server.getServicesManager().unregisterAll(plugin); + } catch (Throwable ex) { +diff --git a/src/main/java/io/papermc/paper/threadedregions/EntityScheduler.java b/src/main/java/io/papermc/paper/threadedregions/EntityScheduler.java +new file mode 100644 +index 0000000000000000000000000000000000000000..c03608fec96b51e1867f43d8f42e5aefb1520e46 +--- /dev/null ++++ b/src/main/java/io/papermc/paper/threadedregions/EntityScheduler.java +@@ -0,0 +1,181 @@ ++package io.papermc.paper.threadedregions; ++ ++import ca.spottedleaf.concurrentutil.util.Validate; ++import ca.spottedleaf.moonrise.common.util.TickThread; ++import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap; ++import net.minecraft.world.entity.Entity; ++import org.bukkit.craftbukkit.entity.CraftEntity; ++ ++import java.util.ArrayDeque; ++import java.util.ArrayList; ++import java.util.List; ++import java.util.function.Consumer; ++ ++/** ++ * An entity can move between worlds with an arbitrary tick delay, be temporarily removed ++ * for players (i.e end credits), be partially removed from world state (i.e inactive but not removed), ++ * teleport between ticking regions, teleport between worlds (which will change the underlying Entity object ++ * for non-players), and even be removed entirely from the server. The uncertainty of an entity's state can make ++ * it difficult to schedule tasks without worrying about undefined behaviors resulting from any of the states listed ++ * previously. ++ * ++ *

    ++ * This class is designed to eliminate those states by providing an interface to run tasks only when an entity ++ * is contained in a world, on the owning thread for the region, and by providing the current Entity object. ++ * The scheduler also allows a task to provide a callback, the "retired" callback, that will be invoked ++ * if the entity is removed before a task that was scheduled could be executed. The scheduler is also ++ * completely thread-safe, allowing tasks to be scheduled from any thread context. The scheduler also indicates ++ * properly whether a task was scheduled successfully (i.e scheduler not retired), thus the code scheduling any task ++ * knows whether the given callbacks will be invoked eventually or not - which may be critical for off-thread ++ * contexts. ++ *

    ++ */ ++public final class EntityScheduler { ++ ++ /** ++ * The Entity. Note that it is the CraftEntity, since only that class properly tracks world transfers. ++ */ ++ public final CraftEntity entity; ++ ++ private static final record ScheduledTask(Consumer run, Consumer retired) {} ++ ++ private long tickCount = 0L; ++ private static final long RETIRED_TICK_COUNT = -1L; ++ private final Object stateLock = new Object(); ++ private final Long2ObjectOpenHashMap> oneTimeDelayed = new Long2ObjectOpenHashMap<>(); ++ ++ private final ArrayDeque currentlyExecuting = new ArrayDeque<>(); ++ ++ public EntityScheduler(final CraftEntity entity) { ++ this.entity = Validate.notNull(entity); ++ } ++ ++ /** ++ * Retires the scheduler, preventing new tasks from being scheduled and invoking the retired callback ++ * on all currently scheduled tasks. ++ * ++ *

    ++ * Note: This should only be invoked after synchronously removing the entity from the world. ++ *

    ++ * ++ * @throws IllegalStateException If the scheduler is already retired. ++ */ ++ public void retire() { ++ synchronized (this.stateLock) { ++ if (this.tickCount == RETIRED_TICK_COUNT) { ++ throw new IllegalStateException("Already retired"); ++ } ++ this.tickCount = RETIRED_TICK_COUNT; ++ } ++ ++ final Entity thisEntity = this.entity.getHandleRaw(); ++ ++ // correctly handle and order retiring while running executeTick ++ for (int i = 0, len = this.currentlyExecuting.size(); i < len; ++i) { ++ final ScheduledTask task = this.currentlyExecuting.pollFirst(); ++ final Consumer retireTask = (Consumer)task.retired; ++ if (retireTask == null) { ++ continue; ++ } ++ ++ retireTask.accept(thisEntity); ++ } ++ ++ for (final List tasks : this.oneTimeDelayed.values()) { ++ for (int i = 0, len = tasks.size(); i < len; ++i) { ++ final ScheduledTask task = tasks.get(i); ++ final Consumer retireTask = (Consumer)task.retired; ++ if (retireTask == null) { ++ continue; ++ } ++ ++ retireTask.accept(thisEntity); ++ } ++ } ++ } ++ ++ /** ++ * Schedules a task with the given delay. If the task failed to schedule because the scheduler is retired (entity ++ * removed), then returns {@code false}. Otherwise, either the run callback will be invoked after the specified delay, ++ * or the retired callback will be invoked if the scheduler is retired. ++ * Note that the retired callback is invoked in critical code, so it should not attempt to remove the entity, remove ++ * other entities, load chunks, load worlds, modify ticket levels, etc. ++ * ++ *

    ++ * It is guaranteed that the run and retired callback are invoked on the region which owns the entity. ++ *

    ++ *

    ++ * The run and retired callback take an Entity parameter representing the current object entity that the scheduler ++ * is tied to. Since the scheduler is transferred when an entity changes dimensions, it is possible the entity parameter ++ * is not the same when the task was first scheduled. Thus, only the parameter provided should be used. ++ *

    ++ * @param run The callback to run after the specified delay, may not be null. ++ * @param retired Retire callback to run if the entity is retired before the run callback can be invoked, may be null. ++ * @param delay The delay in ticks before the run callback is invoked. Any value less-than 1 is treated as 1. ++ * @return {@code true} if the task was scheduled, which means that either the run function or the retired function ++ * will be invoked (but never both), or {@code false} indicating neither the run nor retired function will be invoked ++ * since the scheduler has been retired. ++ */ ++ public boolean schedule(final Consumer run, final Consumer retired, final long delay) { ++ Validate.notNull(run, "Run task may not be null"); ++ ++ final ScheduledTask task = new ScheduledTask(run, retired); ++ synchronized (this.stateLock) { ++ if (this.tickCount == RETIRED_TICK_COUNT) { ++ return false; ++ } ++ this.oneTimeDelayed.computeIfAbsent(this.tickCount + Math.max(1L, delay), (final long keyInMap) -> { ++ return new ArrayList<>(); ++ }).add(task); ++ } ++ ++ return true; ++ } ++ ++ /** ++ * Executes a tick for the scheduler. ++ * ++ * @throws IllegalStateException If the scheduler is retired. ++ */ ++ public void executeTick() { ++ final Entity thisEntity = this.entity.getHandleRaw(); ++ ++ TickThread.ensureTickThread(thisEntity, "May not tick entity scheduler asynchronously"); ++ final List toRun; ++ synchronized (this.stateLock) { ++ if (this.tickCount == RETIRED_TICK_COUNT) { ++ throw new IllegalStateException("Ticking retired scheduler"); ++ } ++ ++this.tickCount; ++ if (this.oneTimeDelayed.isEmpty()) { ++ toRun = null; ++ } else { ++ toRun = this.oneTimeDelayed.remove(this.tickCount); ++ } ++ } ++ ++ if (toRun != null) { ++ for (int i = 0, len = toRun.size(); i < len; ++i) { ++ this.currentlyExecuting.addLast(toRun.get(i)); ++ } ++ } ++ ++ // Note: It is allowed for the tasks executed to retire the entity in a given task. ++ for (int i = 0, len = this.currentlyExecuting.size(); i < len; ++i) { ++ if (!TickThread.isTickThreadFor(thisEntity)) { ++ // tp has been queued sync by one of the tasks ++ // in this case, we need to delay the tasks for next tick ++ break; ++ } ++ final ScheduledTask task = this.currentlyExecuting.pollFirst(); ++ ++ if (this.tickCount != RETIRED_TICK_COUNT) { ++ ((Consumer)task.run).accept(thisEntity); ++ } else { ++ // retired synchronously ++ // note: here task is null ++ break; ++ } ++ } ++ } ++} +diff --git a/src/main/java/io/papermc/paper/threadedregions/scheduler/FallbackRegionScheduler.java b/src/main/java/io/papermc/paper/threadedregions/scheduler/FallbackRegionScheduler.java +new file mode 100644 +index 0000000000000000000000000000000000000000..94056d61a304ee012ae1828a33412516095f996f +--- /dev/null ++++ b/src/main/java/io/papermc/paper/threadedregions/scheduler/FallbackRegionScheduler.java +@@ -0,0 +1,30 @@ ++package io.papermc.paper.threadedregions.scheduler; ++ ++import org.bukkit.World; ++import org.bukkit.plugin.Plugin; ++import org.jetbrains.annotations.NotNull; ++ ++import java.util.function.Consumer; ++ ++public final class FallbackRegionScheduler implements RegionScheduler { ++ ++ @Override ++ public void execute(@NotNull final Plugin plugin, @NotNull final World world, final int chunkX, final int chunkZ, @NotNull final Runnable run) { ++ plugin.getServer().getGlobalRegionScheduler().execute(plugin, run); ++ } ++ ++ @Override ++ public @NotNull ScheduledTask run(@NotNull final Plugin plugin, @NotNull final World world, final int chunkX, final int chunkZ, @NotNull final Consumer task) { ++ return plugin.getServer().getGlobalRegionScheduler().run(plugin, task); ++ } ++ ++ @Override ++ public @NotNull ScheduledTask runDelayed(@NotNull final Plugin plugin, @NotNull final World world, final int chunkX, final int chunkZ, @NotNull final Consumer task, final long delayTicks) { ++ return plugin.getServer().getGlobalRegionScheduler().runDelayed(plugin, task, delayTicks); ++ } ++ ++ @Override ++ public @NotNull ScheduledTask runAtFixedRate(@NotNull final Plugin plugin, @NotNull final World world, final int chunkX, final int chunkZ, @NotNull final Consumer task, final long initialDelayTicks, final long periodTicks) { ++ return plugin.getServer().getGlobalRegionScheduler().runAtFixedRate(plugin, task, initialDelayTicks, periodTicks); ++ } ++} +diff --git a/src/main/java/io/papermc/paper/threadedregions/scheduler/FoliaAsyncScheduler.java b/src/main/java/io/papermc/paper/threadedregions/scheduler/FoliaAsyncScheduler.java +new file mode 100644 +index 0000000000000000000000000000000000000000..374abffb9f1ce1a308822aed13038e77fe9ca08b +--- /dev/null ++++ b/src/main/java/io/papermc/paper/threadedregions/scheduler/FoliaAsyncScheduler.java +@@ -0,0 +1,328 @@ ++package io.papermc.paper.threadedregions.scheduler; ++ ++import ca.spottedleaf.concurrentutil.util.Validate; ++import com.mojang.logging.LogUtils; ++import org.bukkit.plugin.IllegalPluginAccessException; ++import org.bukkit.plugin.Plugin; ++import org.slf4j.Logger; ++ ++import java.util.Set; ++import java.util.concurrent.ConcurrentHashMap; ++import java.util.concurrent.Executor; ++import java.util.concurrent.Executors; ++import java.util.concurrent.ScheduledExecutorService; ++import java.util.concurrent.ScheduledFuture; ++import java.util.concurrent.SynchronousQueue; ++import java.util.concurrent.ThreadFactory; ++import java.util.concurrent.ThreadPoolExecutor; ++import java.util.concurrent.TimeUnit; ++import java.util.concurrent.atomic.AtomicInteger; ++import java.util.function.Consumer; ++import java.util.logging.Level; ++ ++public final class FoliaAsyncScheduler implements AsyncScheduler { ++ ++ private static final Logger LOGGER = LogUtils.getClassLogger(); ++ ++ private final Executor executors = new ThreadPoolExecutor(Math.max(4, Runtime.getRuntime().availableProcessors() / 2), Integer.MAX_VALUE, ++ 30L, TimeUnit.SECONDS, new SynchronousQueue<>(), ++ new ThreadFactory() { ++ private final AtomicInteger idGenerator = new AtomicInteger(); ++ ++ @Override ++ public Thread newThread(final Runnable run) { ++ final Thread ret = new Thread(run); ++ ++ ret.setName("Folia Async Scheduler Thread #" + this.idGenerator.getAndIncrement()); ++ ret.setPriority(Thread.NORM_PRIORITY - 1); ++ ret.setUncaughtExceptionHandler((final Thread thread, final Throwable thr) -> { ++ LOGGER.error("Uncaught exception in thread: " + thread.getName(), thr); ++ }); ++ ++ return ret; ++ } ++ } ++ ); ++ ++ private final ScheduledExecutorService timerThread = Executors.newSingleThreadScheduledExecutor(new ThreadFactory() { ++ @Override ++ public Thread newThread(final Runnable run) { ++ final Thread ret = new Thread(run); ++ ++ ret.setName("Folia Async Scheduler Thread Timer"); ++ ret.setPriority(Thread.NORM_PRIORITY + 1); ++ ret.setUncaughtExceptionHandler((final Thread thread, final Throwable thr) -> { ++ LOGGER.error("Uncaught exception in thread: " + thread.getName(), thr); ++ }); ++ ++ return ret; ++ } ++ }); ++ ++ private final Set tasks = ConcurrentHashMap.newKeySet(); ++ ++ @Override ++ public ScheduledTask runNow(final Plugin plugin, final Consumer task) { ++ Validate.notNull(plugin, "Plugin may not be null"); ++ Validate.notNull(task, "Task may not be null"); ++ ++ if (!plugin.isEnabled()) { ++ throw new IllegalPluginAccessException("Plugin attempted to register task while disabled"); ++ } ++ ++ final AsyncScheduledTask ret = new AsyncScheduledTask(plugin, -1L, task, null, -1L); ++ ++ this.tasks.add(ret); ++ this.executors.execute(ret); ++ ++ if (!plugin.isEnabled()) { ++ // handle race condition where plugin is disabled asynchronously ++ ret.cancel(); ++ } ++ ++ return ret; ++ } ++ ++ @Override ++ public ScheduledTask runDelayed(final Plugin plugin, final Consumer task, final long delay, ++ final TimeUnit unit) { ++ Validate.notNull(plugin, "Plugin may not be null"); ++ Validate.notNull(task, "Task may not be null"); ++ Validate.notNull(unit, "Time unit may not be null"); ++ if (delay < 0L) { ++ throw new IllegalArgumentException("Delay may not be < 0"); ++ } ++ ++ if (!plugin.isEnabled()) { ++ throw new IllegalPluginAccessException("Plugin attempted to register task while disabled"); ++ } ++ ++ return this.scheduleTimerTask(plugin, task, delay, -1L, unit); ++ } ++ ++ @Override ++ public ScheduledTask runAtFixedRate(final Plugin plugin, final Consumer task, final long initialDelay, ++ final long period, final TimeUnit unit) { ++ Validate.notNull(plugin, "Plugin may not be null"); ++ Validate.notNull(task, "Task may not be null"); ++ Validate.notNull(unit, "Time unit may not be null"); ++ if (initialDelay < 0L) { ++ throw new IllegalArgumentException("Initial delay may not be < 0"); ++ } ++ if (period <= 0L) { ++ throw new IllegalArgumentException("Period may not be <= 0"); ++ } ++ ++ if (!plugin.isEnabled()) { ++ throw new IllegalPluginAccessException("Plugin attempted to register task while disabled"); ++ } ++ ++ return this.scheduleTimerTask(plugin, task, initialDelay, period, unit); ++ } ++ ++ private AsyncScheduledTask scheduleTimerTask(final Plugin plugin, final Consumer task, final long initialDelay, ++ final long period, final TimeUnit unit) { ++ final AsyncScheduledTask ret = new AsyncScheduledTask( ++ plugin, period <= 0 ? period : unit.toNanos(period), task, null, ++ System.nanoTime() + unit.toNanos(initialDelay) ++ ); ++ ++ synchronized (ret) { ++ // even though ret is not published, we need to synchronise while scheduling to avoid a race condition ++ // for when a scheduled task immediately executes before we update the delay field and state field ++ ret.setDelay(this.timerThread.schedule(ret, initialDelay, unit)); ++ this.tasks.add(ret); ++ } ++ ++ if (!plugin.isEnabled()) { ++ // handle race condition where plugin is disabled asynchronously ++ ret.cancel(); ++ } ++ ++ return ret; ++ } ++ ++ @Override ++ public void cancelTasks(final Plugin plugin) { ++ Validate.notNull(plugin, "Plugin may not be null"); ++ ++ for (final AsyncScheduledTask task : this.tasks) { ++ if (task.plugin == plugin) { ++ task.cancel(); ++ } ++ } ++ } ++ ++ private final class AsyncScheduledTask implements ScheduledTask, Runnable { ++ ++ private static final int STATE_ON_TIMER = 0; ++ private static final int STATE_SCHEDULED_EXECUTOR = 1; ++ private static final int STATE_EXECUTING = 2; ++ private static final int STATE_EXECUTING_CANCELLED = 3; ++ private static final int STATE_FINISHED = 4; ++ private static final int STATE_CANCELLED = 5; ++ ++ private final Plugin plugin; ++ private final long repeatDelay; // in ns ++ private Consumer run; ++ private ScheduledFuture delay; ++ private int state; ++ private long scheduleTarget; ++ ++ public AsyncScheduledTask(final Plugin plugin, final long repeatDelay, final Consumer run, ++ final ScheduledFuture delay, final long firstTarget) { ++ this.plugin = plugin; ++ this.repeatDelay = repeatDelay; ++ this.run = run; ++ this.delay = delay; ++ this.state = delay == null ? STATE_SCHEDULED_EXECUTOR : STATE_ON_TIMER; ++ this.scheduleTarget = firstTarget; ++ } ++ ++ private void setDelay(final ScheduledFuture delay) { ++ this.delay = delay; ++ this.state = STATE_SCHEDULED_EXECUTOR; ++ } ++ ++ @Override ++ public void run() { ++ final boolean repeating = this.isRepeatingTask(); ++ // try to advance state ++ final boolean timer; ++ synchronized (this) { ++ if (this.state == STATE_ON_TIMER) { ++ timer = true; ++ this.delay = null; ++ this.state = STATE_SCHEDULED_EXECUTOR; ++ } else if (this.state != STATE_SCHEDULED_EXECUTOR) { ++ // cancelled ++ if (this.state != STATE_CANCELLED) { ++ throw new IllegalStateException("Wrong state: " + this.state); ++ } ++ return; ++ } else { ++ timer = false; ++ this.state = STATE_EXECUTING; ++ } ++ } ++ ++ if (timer) { ++ // the scheduled executor is single thread, and unfortunately not expandable with threads ++ // so we just schedule onto the executor ++ FoliaAsyncScheduler.this.executors.execute(this); ++ return; ++ } ++ ++ try { ++ this.run.accept(this); ++ } catch (final Throwable throwable) { ++ this.plugin.getLogger().log(Level.WARNING, "Async task for " + this.plugin.getDescription().getFullName() + " generated an exception", throwable); ++ } finally { ++ boolean removeFromTasks = false; ++ synchronized (this) { ++ if (!repeating) { ++ // only want to execute once, so we're done ++ removeFromTasks = true; ++ this.state = STATE_FINISHED; ++ } else if (this.state != STATE_EXECUTING_CANCELLED) { ++ this.state = STATE_ON_TIMER; ++ // account for any delays, whether it be by task exec. or scheduler issues so that we keep ++ // the fixed schedule ++ final long currTime = System.nanoTime(); ++ final long delay = Math.max(0L, this.scheduleTarget + this.repeatDelay - currTime); ++ this.scheduleTarget = currTime + delay; ++ this.delay = FoliaAsyncScheduler.this.timerThread.schedule(this, delay, TimeUnit.NANOSECONDS); ++ } else { ++ // cancelled repeating task ++ removeFromTasks = true; ++ } ++ } ++ ++ if (removeFromTasks) { ++ this.run = null; ++ FoliaAsyncScheduler.this.tasks.remove(this); ++ } ++ } ++ } ++ ++ @Override ++ public Plugin getOwningPlugin() { ++ return this.plugin; ++ } ++ ++ @Override ++ public boolean isRepeatingTask() { ++ return this.repeatDelay > 0L; ++ } ++ ++ @Override ++ public CancelledState cancel() { ++ ScheduledFuture delay = null; ++ CancelledState ret; ++ synchronized (this) { ++ switch (this.state) { ++ case STATE_ON_TIMER: { ++ delay = this.delay; ++ this.delay = null; ++ this.state = STATE_CANCELLED; ++ ret = CancelledState.CANCELLED_BY_CALLER; ++ break; ++ } ++ case STATE_SCHEDULED_EXECUTOR: { ++ this.state = STATE_CANCELLED; ++ ret = CancelledState.CANCELLED_BY_CALLER; ++ break; ++ } ++ case STATE_EXECUTING: { ++ if (!this.isRepeatingTask()) { ++ return CancelledState.RUNNING; ++ } ++ this.state = STATE_EXECUTING_CANCELLED; ++ return CancelledState.NEXT_RUNS_CANCELLED; ++ } ++ case STATE_EXECUTING_CANCELLED: { ++ return CancelledState.NEXT_RUNS_CANCELLED_ALREADY; ++ } ++ case STATE_FINISHED: { ++ return CancelledState.ALREADY_EXECUTED; ++ } ++ case STATE_CANCELLED: { ++ return CancelledState.CANCELLED_ALREADY; ++ } ++ default: { ++ throw new IllegalStateException("Unknown state: " + this.state); ++ } ++ } ++ } ++ ++ if (delay != null) { ++ delay.cancel(false); ++ } ++ this.run = null; ++ FoliaAsyncScheduler.this.tasks.remove(this); ++ return ret; ++ } ++ ++ @Override ++ public ExecutionState getExecutionState() { ++ synchronized (this) { ++ switch (this.state) { ++ case STATE_ON_TIMER: ++ case STATE_SCHEDULED_EXECUTOR: ++ return ExecutionState.IDLE; ++ case STATE_EXECUTING: ++ return ExecutionState.RUNNING; ++ case STATE_EXECUTING_CANCELLED: ++ return ExecutionState.CANCELLED_RUNNING; ++ case STATE_FINISHED: ++ return ExecutionState.FINISHED; ++ case STATE_CANCELLED: ++ return ExecutionState.CANCELLED; ++ default: { ++ throw new IllegalStateException("Unknown state: " + this.state); ++ } ++ } ++ } ++ } ++ } ++} +diff --git a/src/main/java/io/papermc/paper/threadedregions/scheduler/FoliaEntityScheduler.java b/src/main/java/io/papermc/paper/threadedregions/scheduler/FoliaEntityScheduler.java +new file mode 100644 +index 0000000000000000000000000000000000000000..011754962896e32f51ed4606dcbea18a430a2bc1 +--- /dev/null ++++ b/src/main/java/io/papermc/paper/threadedregions/scheduler/FoliaEntityScheduler.java +@@ -0,0 +1,268 @@ ++package io.papermc.paper.threadedregions.scheduler; ++ ++import ca.spottedleaf.concurrentutil.util.ConcurrentUtil; ++import ca.spottedleaf.concurrentutil.util.Validate; ++import net.minecraft.world.entity.Entity; ++import org.bukkit.craftbukkit.entity.CraftEntity; ++import org.bukkit.plugin.IllegalPluginAccessException; ++import org.bukkit.plugin.Plugin; ++import org.jetbrains.annotations.Nullable; ++ ++import java.lang.invoke.VarHandle; ++import java.util.function.Consumer; ++import java.util.logging.Level; ++ ++public final class FoliaEntityScheduler implements EntityScheduler { ++ ++ private final CraftEntity entity; ++ ++ public FoliaEntityScheduler(final CraftEntity entity) { ++ this.entity = entity; ++ } ++ ++ private static Consumer wrap(final Plugin plugin, final Runnable runnable) { ++ Validate.notNull(plugin, "Plugin may not be null"); ++ Validate.notNull(runnable, "Runnable may not be null"); ++ ++ return (final Entity nmsEntity) -> { ++ if (!plugin.isEnabled()) { ++ // don't execute if the plugin is disabled ++ return; ++ } ++ try { ++ runnable.run(); ++ } catch (final Throwable throwable) { ++ plugin.getLogger().log(Level.WARNING, "Entity task for " + plugin.getDescription().getFullName() + " generated an exception", throwable); ++ } ++ }; ++ } ++ ++ @Override ++ public boolean execute(final Plugin plugin, final Runnable run, final Runnable retired, ++ final long delay) { ++ final Consumer runNMS = wrap(plugin, run); ++ final Consumer runRetired = retired == null ? null : wrap(plugin, retired); ++ ++ return this.entity.taskScheduler.schedule(runNMS, runRetired, delay); ++ } ++ ++ @Override ++ public @Nullable ScheduledTask run(final Plugin plugin, final Consumer task, final Runnable retired) { ++ return this.runDelayed(plugin, task, retired, 1); ++ } ++ ++ @Override ++ public @Nullable ScheduledTask runDelayed(final Plugin plugin, final Consumer task, final Runnable retired, ++ final long delayTicks) { ++ Validate.notNull(plugin, "Plugin may not be null"); ++ Validate.notNull(task, "Task may not be null"); ++ if (delayTicks <= 0) { ++ throw new IllegalArgumentException("Delay ticks may not be <= 0"); ++ } ++ ++ if (!plugin.isEnabled()) { ++ throw new IllegalPluginAccessException("Plugin attempted to register task while disabled"); ++ } ++ ++ final EntityScheduledTask ret = new EntityScheduledTask(plugin, -1, task, retired); ++ ++ if (!this.scheduleInternal(ret, delayTicks)) { ++ return null; ++ } ++ ++ if (!plugin.isEnabled()) { ++ // handle race condition where plugin is disabled asynchronously ++ ret.cancel(); ++ } ++ ++ return ret; ++ } ++ ++ @Override ++ public @Nullable ScheduledTask runAtFixedRate(final Plugin plugin, final Consumer task, ++ final Runnable retired, final long initialDelayTicks, final long periodTicks) { ++ Validate.notNull(plugin, "Plugin may not be null"); ++ Validate.notNull(task, "Task may not be null"); ++ if (initialDelayTicks <= 0) { ++ throw new IllegalArgumentException("Initial delay ticks may not be <= 0"); ++ } ++ if (periodTicks <= 0) { ++ throw new IllegalArgumentException("Period ticks may not be <= 0"); ++ } ++ ++ if (!plugin.isEnabled()) { ++ throw new IllegalPluginAccessException("Plugin attempted to register task while disabled"); ++ } ++ ++ final EntityScheduledTask ret = new EntityScheduledTask(plugin, periodTicks, task, retired); ++ ++ if (!this.scheduleInternal(ret, initialDelayTicks)) { ++ return null; ++ } ++ ++ if (!plugin.isEnabled()) { ++ // handle race condition where plugin is disabled asynchronously ++ ret.cancel(); ++ } ++ ++ return ret; ++ } ++ ++ private boolean scheduleInternal(final EntityScheduledTask ret, final long delay) { ++ return this.entity.taskScheduler.schedule(ret, ret, delay); ++ } ++ ++ private final class EntityScheduledTask implements ScheduledTask, Consumer { ++ ++ private static final int STATE_IDLE = 0; ++ private static final int STATE_EXECUTING = 1; ++ private static final int STATE_EXECUTING_CANCELLED = 2; ++ private static final int STATE_FINISHED = 3; ++ private static final int STATE_CANCELLED = 4; ++ ++ private final Plugin plugin; ++ private final long repeatDelay; // in ticks ++ private Consumer run; ++ private Runnable retired; ++ private volatile int state; ++ ++ private static final VarHandle STATE_HANDLE = ConcurrentUtil.getVarHandle(EntityScheduledTask.class, "state", int.class); ++ ++ private EntityScheduledTask(final Plugin plugin, final long repeatDelay, final Consumer run, final Runnable retired) { ++ this.plugin = plugin; ++ this.repeatDelay = repeatDelay; ++ this.run = run; ++ this.retired = retired; ++ } ++ ++ private final int getStateVolatile() { ++ return (int)STATE_HANDLE.get(this); ++ } ++ ++ private final int compareAndExchangeStateVolatile(final int expect, final int update) { ++ return (int)STATE_HANDLE.compareAndExchange(this, expect, update); ++ } ++ ++ private final void setStateVolatile(final int value) { ++ STATE_HANDLE.setVolatile(this, value); ++ } ++ ++ @Override ++ public void accept(final Entity entity) { ++ if (!this.plugin.isEnabled()) { ++ // don't execute if the plugin is disabled ++ this.setStateVolatile(STATE_CANCELLED); ++ return; ++ } ++ ++ final boolean repeating = this.isRepeatingTask(); ++ if (STATE_IDLE != this.compareAndExchangeStateVolatile(STATE_IDLE, STATE_EXECUTING)) { ++ // cancelled ++ return; ++ } ++ ++ final boolean retired = entity.isRemoved(); ++ ++ try { ++ if (!retired) { ++ this.run.accept(this); ++ } else { ++ if (this.retired != null) { ++ this.retired.run(); ++ } ++ } ++ } catch (final Throwable throwable) { ++ this.plugin.getLogger().log(Level.WARNING, "Entity task for " + this.plugin.getDescription().getFullName() + " generated an exception", throwable); ++ } finally { ++ boolean reschedule = false; ++ if (!repeating && !retired) { ++ this.setStateVolatile(STATE_FINISHED); ++ } else if (retired || !this.plugin.isEnabled()) { ++ this.setStateVolatile(STATE_CANCELLED); ++ } else if (STATE_EXECUTING == this.compareAndExchangeStateVolatile(STATE_EXECUTING, STATE_IDLE)) { ++ reschedule = true; ++ } // else: cancelled repeating task ++ ++ if (!reschedule) { ++ this.run = null; ++ this.retired = null; ++ } else { ++ if (!FoliaEntityScheduler.this.scheduleInternal(this, this.repeatDelay)) { ++ // the task itself must have removed the entity, so in this case we need to mark as cancelled ++ this.setStateVolatile(STATE_CANCELLED); ++ } ++ } ++ } ++ } ++ ++ @Override ++ public Plugin getOwningPlugin() { ++ return this.plugin; ++ } ++ ++ @Override ++ public boolean isRepeatingTask() { ++ return this.repeatDelay > 0; ++ } ++ ++ @Override ++ public CancelledState cancel() { ++ for (int curr = this.getStateVolatile();;) { ++ switch (curr) { ++ case STATE_IDLE: { ++ if (STATE_IDLE == (curr = this.compareAndExchangeStateVolatile(STATE_IDLE, STATE_CANCELLED))) { ++ this.state = STATE_CANCELLED; ++ this.run = null; ++ this.retired = null; ++ return CancelledState.CANCELLED_BY_CALLER; ++ } ++ // try again ++ continue; ++ } ++ case STATE_EXECUTING: { ++ if (!this.isRepeatingTask()) { ++ return CancelledState.RUNNING; ++ } ++ if (STATE_EXECUTING == (curr = this.compareAndExchangeStateVolatile(STATE_EXECUTING, STATE_EXECUTING_CANCELLED))) { ++ return CancelledState.NEXT_RUNS_CANCELLED; ++ } ++ // try again ++ continue; ++ } ++ case STATE_EXECUTING_CANCELLED: { ++ return CancelledState.NEXT_RUNS_CANCELLED_ALREADY; ++ } ++ case STATE_FINISHED: { ++ return CancelledState.ALREADY_EXECUTED; ++ } ++ case STATE_CANCELLED: { ++ return CancelledState.CANCELLED_ALREADY; ++ } ++ default: { ++ throw new IllegalStateException("Unknown state: " + curr); ++ } ++ } ++ } ++ } ++ ++ @Override ++ public ExecutionState getExecutionState() { ++ final int state = this.getStateVolatile(); ++ switch (state) { ++ case STATE_IDLE: ++ return ExecutionState.IDLE; ++ case STATE_EXECUTING: ++ return ExecutionState.RUNNING; ++ case STATE_EXECUTING_CANCELLED: ++ return ExecutionState.CANCELLED_RUNNING; ++ case STATE_FINISHED: ++ return ExecutionState.FINISHED; ++ case STATE_CANCELLED: ++ return ExecutionState.CANCELLED; ++ default: { ++ throw new IllegalStateException("Unknown state: " + state); ++ } ++ } ++ } ++ } ++} +diff --git a/src/main/java/io/papermc/paper/threadedregions/scheduler/FoliaGlobalRegionScheduler.java b/src/main/java/io/papermc/paper/threadedregions/scheduler/FoliaGlobalRegionScheduler.java +new file mode 100644 +index 0000000000000000000000000000000000000000..d306f911757a4d556c82c0070d4837db87afc497 +--- /dev/null ++++ b/src/main/java/io/papermc/paper/threadedregions/scheduler/FoliaGlobalRegionScheduler.java +@@ -0,0 +1,267 @@ ++package io.papermc.paper.threadedregions.scheduler; ++ ++import ca.spottedleaf.concurrentutil.util.ConcurrentUtil; ++import ca.spottedleaf.concurrentutil.util.Validate; ++import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap; ++import org.bukkit.plugin.IllegalPluginAccessException; ++import org.bukkit.plugin.Plugin; ++ ++import java.lang.invoke.VarHandle; ++import java.util.ArrayList; ++import java.util.List; ++import java.util.function.Consumer; ++import java.util.logging.Level; ++ ++public class FoliaGlobalRegionScheduler implements GlobalRegionScheduler { ++ ++ private long tickCount = 0L; ++ private final Object stateLock = new Object(); ++ private final Long2ObjectOpenHashMap> tasksByDeadline = new Long2ObjectOpenHashMap<>(); ++ ++ public void tick() { ++ final List run; ++ synchronized (this.stateLock) { ++ ++this.tickCount; ++ if (this.tasksByDeadline.isEmpty()) { ++ run = null; ++ } else { ++ run = this.tasksByDeadline.remove(this.tickCount); ++ } ++ } ++ ++ if (run == null) { ++ return; ++ } ++ ++ for (int i = 0, len = run.size(); i < len; ++i) { ++ run.get(i).run(); ++ } ++ } ++ ++ @Override ++ public void execute(final Plugin plugin, final Runnable run) { ++ Validate.notNull(plugin, "Plugin may not be null"); ++ Validate.notNull(run, "Runnable may not be null"); ++ ++ this.run(plugin, (final ScheduledTask task) -> { ++ run.run(); ++ }); ++ } ++ ++ @Override ++ public ScheduledTask run(final Plugin plugin, final Consumer task) { ++ return this.runDelayed(plugin, task, 1); ++ } ++ ++ @Override ++ public ScheduledTask runDelayed(final Plugin plugin, final Consumer task, final long delayTicks) { ++ Validate.notNull(plugin, "Plugin may not be null"); ++ Validate.notNull(task, "Task may not be null"); ++ if (delayTicks <= 0) { ++ throw new IllegalArgumentException("Delay ticks may not be <= 0"); ++ } ++ ++ if (!plugin.isEnabled()) { ++ throw new IllegalPluginAccessException("Plugin attempted to register task while disabled"); ++ } ++ ++ final GlobalScheduledTask ret = new GlobalScheduledTask(plugin, -1, task); ++ ++ this.scheduleInternal(ret, delayTicks); ++ ++ if (!plugin.isEnabled()) { ++ // handle race condition where plugin is disabled asynchronously ++ ret.cancel(); ++ } ++ ++ return ret; ++ } ++ ++ @Override ++ public ScheduledTask runAtFixedRate(final Plugin plugin, final Consumer task, final long initialDelayTicks, final long periodTicks) { ++ Validate.notNull(plugin, "Plugin may not be null"); ++ Validate.notNull(task, "Task may not be null"); ++ if (initialDelayTicks <= 0) { ++ throw new IllegalArgumentException("Initial delay ticks may not be <= 0"); ++ } ++ if (periodTicks <= 0) { ++ throw new IllegalArgumentException("Period ticks may not be <= 0"); ++ } ++ ++ if (!plugin.isEnabled()) { ++ throw new IllegalPluginAccessException("Plugin attempted to register task while disabled"); ++ } ++ ++ final GlobalScheduledTask ret = new GlobalScheduledTask(plugin, periodTicks, task); ++ ++ this.scheduleInternal(ret, initialDelayTicks); ++ ++ if (!plugin.isEnabled()) { ++ // handle race condition where plugin is disabled asynchronously ++ ret.cancel(); ++ } ++ ++ return ret; ++ } ++ ++ @Override ++ public void cancelTasks(final Plugin plugin) { ++ Validate.notNull(plugin, "Plugin may not be null"); ++ ++ final List toCancel = new ArrayList<>(); ++ synchronized (this.stateLock) { ++ for (final List tasks : this.tasksByDeadline.values()) { ++ for (int i = 0, len = tasks.size(); i < len; ++i) { ++ final GlobalScheduledTask task = tasks.get(i); ++ if (task.plugin == plugin) { ++ toCancel.add(task); ++ } ++ } ++ } ++ } ++ ++ for (int i = 0, len = toCancel.size(); i < len; ++i) { ++ toCancel.get(i).cancel(); ++ } ++ } ++ ++ private void scheduleInternal(final GlobalScheduledTask task, final long delay) { ++ // note: delay > 0 ++ synchronized (this.stateLock) { ++ this.tasksByDeadline.computeIfAbsent(this.tickCount + delay, (final long keyInMap) -> { ++ return new ArrayList<>(); ++ }).add(task); ++ } ++ } ++ ++ private final class GlobalScheduledTask implements ScheduledTask, Runnable { ++ ++ private static final int STATE_IDLE = 0; ++ private static final int STATE_EXECUTING = 1; ++ private static final int STATE_EXECUTING_CANCELLED = 2; ++ private static final int STATE_FINISHED = 3; ++ private static final int STATE_CANCELLED = 4; ++ ++ private final Plugin plugin; ++ private final long repeatDelay; // in ticks ++ private Consumer run; ++ private volatile int state; ++ ++ private static final VarHandle STATE_HANDLE = ConcurrentUtil.getVarHandle(GlobalScheduledTask.class, "state", int.class); ++ ++ private GlobalScheduledTask(final Plugin plugin, final long repeatDelay, final Consumer run) { ++ this.plugin = plugin; ++ this.repeatDelay = repeatDelay; ++ this.run = run; ++ } ++ ++ private final int getStateVolatile() { ++ return (int)STATE_HANDLE.get(this); ++ } ++ ++ private final int compareAndExchangeStateVolatile(final int expect, final int update) { ++ return (int)STATE_HANDLE.compareAndExchange(this, expect, update); ++ } ++ ++ private final void setStateVolatile(final int value) { ++ STATE_HANDLE.setVolatile(this, value); ++ } ++ ++ @Override ++ public void run() { ++ final boolean repeating = this.isRepeatingTask(); ++ if (STATE_IDLE != this.compareAndExchangeStateVolatile(STATE_IDLE, STATE_EXECUTING)) { ++ // cancelled ++ return; ++ } ++ ++ try { ++ this.run.accept(this); ++ } catch (final Throwable throwable) { ++ this.plugin.getLogger().log(Level.WARNING, "Global task for " + this.plugin.getDescription().getFullName() + " generated an exception", throwable); ++ } finally { ++ boolean reschedule = false; ++ if (!repeating) { ++ this.setStateVolatile(STATE_FINISHED); ++ } else if (STATE_EXECUTING == this.compareAndExchangeStateVolatile(STATE_EXECUTING, STATE_IDLE)) { ++ reschedule = true; ++ } // else: cancelled repeating task ++ ++ if (!reschedule) { ++ this.run = null; ++ } else { ++ FoliaGlobalRegionScheduler.this.scheduleInternal(this, this.repeatDelay); ++ } ++ } ++ } ++ ++ @Override ++ public Plugin getOwningPlugin() { ++ return this.plugin; ++ } ++ ++ @Override ++ public boolean isRepeatingTask() { ++ return this.repeatDelay > 0; ++ } ++ ++ @Override ++ public CancelledState cancel() { ++ for (int curr = this.getStateVolatile();;) { ++ switch (curr) { ++ case STATE_IDLE: { ++ if (STATE_IDLE == (curr = this.compareAndExchangeStateVolatile(STATE_IDLE, STATE_CANCELLED))) { ++ this.state = STATE_CANCELLED; ++ this.run = null; ++ return CancelledState.CANCELLED_BY_CALLER; ++ } ++ // try again ++ continue; ++ } ++ case STATE_EXECUTING: { ++ if (!this.isRepeatingTask()) { ++ return CancelledState.RUNNING; ++ } ++ if (STATE_EXECUTING == (curr = this.compareAndExchangeStateVolatile(STATE_EXECUTING, STATE_EXECUTING_CANCELLED))) { ++ return CancelledState.NEXT_RUNS_CANCELLED; ++ } ++ // try again ++ continue; ++ } ++ case STATE_EXECUTING_CANCELLED: { ++ return CancelledState.NEXT_RUNS_CANCELLED_ALREADY; ++ } ++ case STATE_FINISHED: { ++ return CancelledState.ALREADY_EXECUTED; ++ } ++ case STATE_CANCELLED: { ++ return CancelledState.CANCELLED_ALREADY; ++ } ++ default: { ++ throw new IllegalStateException("Unknown state: " + curr); ++ } ++ } ++ } ++ } ++ ++ @Override ++ public ExecutionState getExecutionState() { ++ final int state = this.getStateVolatile(); ++ switch (state) { ++ case STATE_IDLE: ++ return ExecutionState.IDLE; ++ case STATE_EXECUTING: ++ return ExecutionState.RUNNING; ++ case STATE_EXECUTING_CANCELLED: ++ return ExecutionState.CANCELLED_RUNNING; ++ case STATE_FINISHED: ++ return ExecutionState.FINISHED; ++ case STATE_CANCELLED: ++ return ExecutionState.CANCELLED; ++ default: { ++ throw new IllegalStateException("Unknown state: " + state); ++ } ++ } ++ } ++ } ++} +diff --git a/src/main/java/net/minecraft/server/MinecraftServer.java b/src/main/java/net/minecraft/server/MinecraftServer.java +index 8f7a808b0c89953a7f2932e68e2c85f9330469f2..be188079f12b3f7b394ae91db62cc17b1d0f4e79 100644 +--- a/src/main/java/net/minecraft/server/MinecraftServer.java ++++ b/src/main/java/net/minecraft/server/MinecraftServer.java +@@ -1625,6 +1625,20 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop { ++ for (final Entity entity : level.getEntities().getAll()) { ++ if (entity.isRemoved()) { ++ continue; ++ } ++ final org.bukkit.craftbukkit.entity.CraftEntity bukkit = entity.getBukkitEntityRaw(); ++ if (bukkit != null) { ++ bukkit.taskScheduler.executeTick(); ++ } ++ } ++ }); ++ // Paper end - Folia scheduler API + io.papermc.paper.adventure.providers.ClickCallbackProviderImpl.CALLBACK_MANAGER.handleQueue(this.tickCount); // Paper + gameprofilerfiller.push("commandFunctions"); + this.getFunctions().tick(); +diff --git a/src/main/java/net/minecraft/server/players/PlayerList.java b/src/main/java/net/minecraft/server/players/PlayerList.java +index cbba549176b3acfc25ec42c2935b31ab2176e0fa..4ac0bb0ec3222ebdd3fa386696dcb0723dc162a4 100644 +--- a/src/main/java/net/minecraft/server/players/PlayerList.java ++++ b/src/main/java/net/minecraft/server/players/PlayerList.java +@@ -597,6 +597,7 @@ public abstract class PlayerList { + } + + worldserver.removePlayerImmediately(entityplayer, Entity.RemovalReason.UNLOADED_WITH_PLAYER); ++ entityplayer.retireScheduler(); // Paper - Folia schedulers + entityplayer.getAdvancements().stopListening(); + this.players.remove(entityplayer); + this.playersByName.remove(entityplayer.getScoreboardName().toLowerCase(java.util.Locale.ROOT)); // Spigot +diff --git a/src/main/java/net/minecraft/world/entity/Entity.java b/src/main/java/net/minecraft/world/entity/Entity.java +index a8bb9caaf0db01d8b7cfb18e5f162c6eeada5d6a..cc4906ed08e84beaca0ba93200693a0a3730fb4c 100644 +--- a/src/main/java/net/minecraft/world/entity/Entity.java ++++ b/src/main/java/net/minecraft/world/entity/Entity.java +@@ -262,10 +262,21 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess + + public CraftEntity getBukkitEntity() { + if (this.bukkitEntity == null) { +- this.bukkitEntity = CraftEntity.getEntity(this.level.getCraftServer(), this); ++ // Paper start - Folia schedulers ++ synchronized (this) { ++ if (this.bukkitEntity == null) { ++ return this.bukkitEntity = CraftEntity.getEntity(this.level.getCraftServer(), this); ++ } ++ } ++ // Paper end - Folia schedulers + } + return this.bukkitEntity; + } ++ // Paper start ++ public CraftEntity getBukkitEntityRaw() { ++ return this.bukkitEntity; ++ } ++ // Paper end + + // CraftBukkit - SPIGOT-6907: re-implement LivingEntity#setMaximumAir() + public int getDefaultMaxAirSupply() { +@@ -4676,6 +4687,7 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess + public final void setRemoved(Entity.RemovalReason entity_removalreason, EntityRemoveEvent.Cause cause) { + CraftEventFactory.callEntityRemoveEvent(this, cause); + // CraftBukkit end ++ final boolean alreadyRemoved = this.removalReason != null; // Paper - Folia schedulers + if (this.removalReason == null) { + this.removalReason = entity_removalreason; + } +@@ -4687,12 +4699,28 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess + this.getPassengers().forEach(Entity::stopRiding); + this.levelCallback.onRemove(entity_removalreason); + this.onRemoval(entity_removalreason); ++ // Paper start - Folia schedulers ++ if (!(this instanceof ServerPlayer) && entity_removalreason != RemovalReason.CHANGED_DIMENSION && !alreadyRemoved) { ++ // Players need to be special cased, because they are regularly removed from the world ++ this.retireScheduler(); ++ } ++ // Paper end - Folia schedulers + } + + public void unsetRemoved() { + this.removalReason = null; + } + ++ // Paper start - Folia schedulers ++ /** ++ * Invoked only when the entity is truly removed from the server, never to be added to any world. ++ */ ++ public final void retireScheduler() { ++ // we need to force create the bukkit entity so that the scheduler can be retired... ++ this.getBukkitEntity().taskScheduler.retire(); ++ } ++ // Paper end - Folia schedulers ++ + @Override + public void setLevelCallback(EntityInLevelCallback changeListener) { + this.levelCallback = changeListener; +diff --git a/src/main/java/org/bukkit/craftbukkit/CraftServer.java b/src/main/java/org/bukkit/craftbukkit/CraftServer.java +index a7b79c9ec51f037287fad609c29b240e2071f2f8..46807d5d94a0f90b230dfaf4d37378a3bfa21414 100644 +--- a/src/main/java/org/bukkit/craftbukkit/CraftServer.java ++++ b/src/main/java/org/bukkit/craftbukkit/CraftServer.java +@@ -313,6 +313,76 @@ public final class CraftServer implements Server { + private final io.papermc.paper.logging.SysoutCatcher sysoutCatcher = new io.papermc.paper.logging.SysoutCatcher(); // Paper + private final io.papermc.paper.potion.PaperPotionBrewer potionBrewer; // Paper - Custom Potion Mixes + ++ // Paper start - Folia region threading API ++ private final io.papermc.paper.threadedregions.scheduler.FallbackRegionScheduler regionizedScheduler = new io.papermc.paper.threadedregions.scheduler.FallbackRegionScheduler(); ++ private final io.papermc.paper.threadedregions.scheduler.FoliaAsyncScheduler asyncScheduler = new io.papermc.paper.threadedregions.scheduler.FoliaAsyncScheduler(); ++ private final io.papermc.paper.threadedregions.scheduler.FoliaGlobalRegionScheduler globalRegionScheduler = new io.papermc.paper.threadedregions.scheduler.FoliaGlobalRegionScheduler(); ++ ++ @Override ++ public final io.papermc.paper.threadedregions.scheduler.RegionScheduler getRegionScheduler() { ++ return this.regionizedScheduler; ++ } ++ ++ @Override ++ public final io.papermc.paper.threadedregions.scheduler.AsyncScheduler getAsyncScheduler() { ++ return this.asyncScheduler; ++ } ++ ++ @Override ++ public final io.papermc.paper.threadedregions.scheduler.FoliaGlobalRegionScheduler getGlobalRegionScheduler() { ++ return this.globalRegionScheduler; ++ } ++ ++ @Override ++ public final boolean isOwnedByCurrentRegion(World world, io.papermc.paper.math.Position position) { ++ return ca.spottedleaf.moonrise.common.util.TickThread.isTickThreadFor( ++ ((CraftWorld) world).getHandle(), position.blockX() >> 4, position.blockZ() >> 4 ++ ); ++ } ++ ++ @Override ++ public final boolean isOwnedByCurrentRegion(World world, io.papermc.paper.math.Position position, int squareRadiusChunks) { ++ return ca.spottedleaf.moonrise.common.util.TickThread.isTickThreadFor( ++ ((CraftWorld) world).getHandle(), position.blockX() >> 4, position.blockZ() >> 4, squareRadiusChunks ++ ); ++ } ++ ++ @Override ++ public final boolean isOwnedByCurrentRegion(Location location) { ++ World world = location.getWorld(); ++ return ca.spottedleaf.moonrise.common.util.TickThread.isTickThreadFor( ++ ((CraftWorld) world).getHandle(), location.getBlockX() >> 4, location.getBlockZ() >> 4 ++ ); ++ } ++ ++ @Override ++ public final boolean isOwnedByCurrentRegion(Location location, int squareRadiusChunks) { ++ World world = location.getWorld(); ++ return ca.spottedleaf.moonrise.common.util.TickThread.isTickThreadFor( ++ ((CraftWorld) world).getHandle(), location.getBlockX() >> 4, location.getBlockZ() >> 4, squareRadiusChunks ++ ); ++ } ++ ++ @Override ++ public final boolean isOwnedByCurrentRegion(World world, int chunkX, int chunkZ) { ++ return ca.spottedleaf.moonrise.common.util.TickThread.isTickThreadFor( ++ ((CraftWorld) world).getHandle(), chunkX, chunkZ ++ ); ++ } ++ ++ @Override ++ public final boolean isOwnedByCurrentRegion(World world, int chunkX, int chunkZ, int squareRadiusChunks) { ++ return ca.spottedleaf.moonrise.common.util.TickThread.isTickThreadFor( ++ ((CraftWorld) world).getHandle(), chunkX, chunkZ, squareRadiusChunks ++ ); ++ } ++ ++ @Override ++ public final boolean isOwnedByCurrentRegion(Entity entity) { ++ return ca.spottedleaf.moonrise.common.util.TickThread.isTickThreadFor(((org.bukkit.craftbukkit.entity.CraftEntity) entity).getHandleRaw()); ++ } ++ // Paper end - Folia reagion threading API ++ + static { + ConfigurationSerialization.registerClass(CraftOfflinePlayer.class); + ConfigurationSerialization.registerClass(CraftPlayerProfile.class); +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java +index 4209434ff066670000dadb0c59fea297f03300f4..df199c1cffa7795126ab83af67e184238ddb211d 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java +@@ -71,6 +71,15 @@ public abstract class CraftEntity implements org.bukkit.entity.Entity { + 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); ++ ++ @Override ++ public final io.papermc.paper.threadedregions.scheduler.EntityScheduler getScheduler() { ++ return this.apiScheduler; ++ }; ++ // Paper end - Folia schedulers + + public CraftEntity(final CraftServer server, final Entity entity) { + this.server = server; +@@ -487,6 +496,12 @@ public abstract class CraftEntity implements org.bukkit.entity.Entity { + return this.entity; + } + ++ // Paper start ++ public Entity getHandleRaw() { ++ return this.entity; ++ } ++ // Paper end ++ + @Override + public final EntityType getType() { + return this.entityType; diff --git a/patches/server/0836-Determine-lava-and-water-fluid-explosion-resistance-.patch b/patches/server/0836-Determine-lava-and-water-fluid-explosion-resistance-.patch deleted file mode 100644 index 5f51eb317c2c..000000000000 --- a/patches/server/0836-Determine-lava-and-water-fluid-explosion-resistance-.patch +++ /dev/null @@ -1,36 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: galacticwarrior9 -Date: Thu, 13 Jul 2023 21:32:13 +0100 -Subject: [PATCH] Determine lava and water fluid explosion resistance by their - block explosion resistance - -When selecting which explosion resistance to use for lava and water, vanilla selects the highest value between their block explosion resistance and fluid explosion resistance. - -Problems emerge when we want to reduce the explosion resistance of water or lava, since the fluid explosion resistance is hardcoded to return 100.0F and can't be changed by a plugin. This simply makes the fluid explosion resistance the same as the block explosion resistance, which allows plugin to change the value. Since both are the same in vanilla, this has no side effects on servers that do not need to do this. - -diff --git a/src/main/java/net/minecraft/world/level/material/LavaFluid.java b/src/main/java/net/minecraft/world/level/material/LavaFluid.java -index 72f8b72c6436ca3b8eaeb39c7d3efe2c1462ae1d..3bb4a9a1a6249e8ba2de237f801210e7f4fd5825 100644 ---- a/src/main/java/net/minecraft/world/level/material/LavaFluid.java -+++ b/src/main/java/net/minecraft/world/level/material/LavaFluid.java -@@ -232,7 +232,7 @@ public abstract class LavaFluid extends FlowingFluid { - - @Override - protected float getExplosionResistance() { -- return 100.0F; -+ return Blocks.LAVA.getExplosionResistance(); // Paper - Get explosion resistance from actual block - } - - @Override -diff --git a/src/main/java/net/minecraft/world/level/material/WaterFluid.java b/src/main/java/net/minecraft/world/level/material/WaterFluid.java -index 21b4afd053e01073eb514264d4960f0f3b1ee3d8..109f71401c65f476ccf6813137386fc9fef10254 100644 ---- a/src/main/java/net/minecraft/world/level/material/WaterFluid.java -+++ b/src/main/java/net/minecraft/world/level/material/WaterFluid.java -@@ -125,7 +125,7 @@ public abstract class WaterFluid extends FlowingFluid { - - @Override - protected float getExplosionResistance() { -- return 100.0F; -+ return Blocks.WATER.getExplosionResistance(); // Paper - Get explosion resistance from actual block - } - - @Override diff --git a/patches/server/0836-Only-erase-allay-memory-on-non-item-targets.patch b/patches/server/0836-Only-erase-allay-memory-on-non-item-targets.patch new file mode 100644 index 000000000000..b26cfd040b5a --- /dev/null +++ b/patches/server/0836-Only-erase-allay-memory-on-non-item-targets.patch @@ -0,0 +1,29 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Bjarne Koll +Date: Fri, 4 Aug 2023 15:53:36 +0200 +Subject: [PATCH] Only erase allay memory on non-item targets + +Spigot incorrectly instanceOf checks the EntityTargetEvent#getTarget +against the internal ItemEntity type and removes the nearest wanted item +memory if said instanceOf check fails, (which is always the case) +causing allays to behave differently as they constantly loose their +target item. + +This commit fixes the faulty behaviour by instance performing a check +against the CraftItem type. + +diff --git a/src/main/java/net/minecraft/world/entity/ai/behavior/GoToWantedItem.java b/src/main/java/net/minecraft/world/entity/ai/behavior/GoToWantedItem.java +index 07db92870f9efd515751453569f0dd7b6fada8fc..7d814e5b4b634674995d55ab97155f949f14ef3b 100644 +--- a/src/main/java/net/minecraft/world/entity/ai/behavior/GoToWantedItem.java ++++ b/src/main/java/net/minecraft/world/entity/ai/behavior/GoToWantedItem.java +@@ -35,8 +35,9 @@ public class GoToWantedItem { + if (event.isCancelled()) { + return false; + } +- if (!(event.getTarget() instanceof ItemEntity)) { ++ if (!(event.getTarget() instanceof org.bukkit.craftbukkit.entity.CraftItem)) { // Paper - only erase allay memory on non-item targets + memoryaccessor2.erase(); ++ return false; // Paper - only erase allay memory on non-item targets + } + + entityitem = (ItemEntity) ((org.bukkit.craftbukkit.entity.CraftEntity) event.getTarget()).getHandle(); diff --git a/patches/server/0837-Fix-possible-NPE-on-painting-creation.patch b/patches/server/0837-Fix-possible-NPE-on-painting-creation.patch deleted file mode 100644 index dc3838be9485..000000000000 --- a/patches/server/0837-Fix-possible-NPE-on-painting-creation.patch +++ /dev/null @@ -1,34 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Jake Potrebic -Date: Sat, 24 Jun 2023 09:42:53 -0700 -Subject: [PATCH] Fix possible NPE on painting creation - - -diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftEntityTypes.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftEntityTypes.java -index 46a4f31e2b6eee6f8dc5f8fccd7de4c48a698b61..577ed3656480271a491bcd3d346c63854fd840e4 100644 ---- a/src/main/java/org/bukkit/craftbukkit/entity/CraftEntityTypes.java -+++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftEntityTypes.java -@@ -326,8 +326,13 @@ public final class CraftEntityTypes { - // Hanging - register(new EntityTypeData<>(EntityType.PAINTING, Painting.class, CraftPainting::new, createHanging(Painting.class, (spawnData, hangingData) -> { - if (spawnData.normalWorld && hangingData.randomize()) { -- return net.minecraft.world.entity.decoration.Painting.create(spawnData.minecraftWorld(), hangingData.position(), hangingData.direction()).orElse(null); -- } else { -+ // Paper start - if randomizeData fails, force it -+ final net.minecraft.world.entity.decoration.Painting entity = net.minecraft.world.entity.decoration.Painting.create(spawnData.minecraftWorld(), hangingData.position(), hangingData.direction()).orElse(null); -+ if (entity != null) { -+ return entity; -+ } -+ } /*else*/ { -+ // Paper end - if randomizeData fails, force it - net.minecraft.world.entity.decoration.Painting entity = new net.minecraft.world.entity.decoration.Painting(net.minecraft.world.entity.EntityType.PAINTING, spawnData.minecraftWorld()); - entity.absMoveTo(spawnData.x(), spawnData.y(), spawnData.z(), spawnData.yaw(), spawnData.pitch()); - entity.setDirection(hangingData.direction()); -@@ -466,6 +471,7 @@ public final class CraftEntityTypes { - AABB bb = (ItemFrame.class.isAssignableFrom(clazz)) - ? net.minecraft.world.entity.decoration.ItemFrame.calculateBoundingBoxStatic(pos, CraftBlock.blockFaceToNotch(dir).getOpposite()) - : net.minecraft.world.entity.decoration.Painting.calculateBoundingBoxStatic(pos, CraftBlock.blockFaceToNotch(dir).getOpposite(), width, height); -+ if (!spawnData.world.noCollision(bb)) continue; // Paper - add collision check - List list = spawnData.world().getEntities(null, bb); - for (Iterator it = list.iterator(); !taken && it.hasNext(); ) { - net.minecraft.world.entity.Entity e = it.next(); diff --git a/patches/server/0837-Fix-rotation-when-spawning-display-entities.patch b/patches/server/0837-Fix-rotation-when-spawning-display-entities.patch new file mode 100644 index 000000000000..240a0931182f --- /dev/null +++ b/patches/server/0837-Fix-rotation-when-spawning-display-entities.patch @@ -0,0 +1,32 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Gameoholic +Date: Sun, 30 Jul 2023 13:30:34 +0300 +Subject: [PATCH] Fix rotation when spawning display entities + + +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftEntityTypes.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftEntityTypes.java +index 8715d8e790b6735610e2f880f8c1f88a89967f21..6cf57640b3bd2bb0417129256ed77d9d67fcf04a 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftEntityTypes.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftEntityTypes.java +@@ -254,6 +254,7 @@ public final class CraftEntityTypes { + Vector direction = spawnData.location().getDirection(); + entity.assignDirectionalMovement(new Vec3(direction.getX(), direction.getY(), direction.getZ()), 1.0); + }; ++ private static final BiConsumer ROT = (spawnData, entity) -> entity.setRot(spawnData.yaw(), spawnData.pitch()); // Paper + private static final Map, EntityTypeData> CLASS_TYPE_DATA = new HashMap<>(); + private static final Map> ENTITY_TYPE_DATA = new HashMap<>(); + +@@ -415,10 +416,10 @@ public final class CraftEntityTypes { + + // Set pos + register(new EntityTypeData<>(EntityType.MARKER, Marker.class, CraftMarker::new, createAndSetPos(net.minecraft.world.entity.EntityType.MARKER))); +- register(new EntityTypeData<>(EntityType.BLOCK_DISPLAY, BlockDisplay.class, CraftBlockDisplay::new, createAndSetPos(net.minecraft.world.entity.EntityType.BLOCK_DISPLAY))); ++ register(new EntityTypeData<>(EntityType.BLOCK_DISPLAY, BlockDisplay.class, CraftBlockDisplay::new, combine(createAndSetPos(net.minecraft.world.entity.EntityType.BLOCK_DISPLAY), ROT))); // Paper + register(new EntityTypeData<>(EntityType.INTERACTION, Interaction.class, CraftInteraction::new, createAndSetPos(net.minecraft.world.entity.EntityType.INTERACTION))); +- register(new EntityTypeData<>(EntityType.ITEM_DISPLAY, ItemDisplay.class, CraftItemDisplay::new, createAndSetPos(net.minecraft.world.entity.EntityType.ITEM_DISPLAY))); +- register(new EntityTypeData<>(EntityType.TEXT_DISPLAY, TextDisplay.class, CraftTextDisplay::new, createAndSetPos(net.minecraft.world.entity.EntityType.TEXT_DISPLAY))); ++ register(new EntityTypeData<>(EntityType.ITEM_DISPLAY, ItemDisplay.class, CraftItemDisplay::new, combine(createAndSetPos(net.minecraft.world.entity.EntityType.ITEM_DISPLAY), ROT))); // Paper ++ register(new EntityTypeData<>(EntityType.TEXT_DISPLAY, TextDisplay.class, CraftTextDisplay::new, combine(createAndSetPos(net.minecraft.world.entity.EntityType.TEXT_DISPLAY), ROT))); // Paper + + // MISC + register(new EntityTypeData<>(EntityType.ITEM, Item.class, CraftItem::new, spawnData -> { diff --git a/patches/server/0838-Only-capture-actual-tree-growth.patch b/patches/server/0838-Only-capture-actual-tree-growth.patch new file mode 100644 index 000000000000..f1dee3ad482e --- /dev/null +++ b/patches/server/0838-Only-capture-actual-tree-growth.patch @@ -0,0 +1,75 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Jake Potrebic +Date: Sat, 21 Aug 2021 18:53:03 -0700 +Subject: [PATCH] Only capture actual tree growth + + +diff --git a/src/main/java/net/minecraft/core/dispenser/DispenseItemBehavior.java b/src/main/java/net/minecraft/core/dispenser/DispenseItemBehavior.java +index f8f570a97789ab16e57774f233506a289277d5d9..18304349c9ab24657c4152aff800dba969174665 100644 +--- a/src/main/java/net/minecraft/core/dispenser/DispenseItemBehavior.java ++++ b/src/main/java/net/minecraft/core/dispenser/DispenseItemBehavior.java +@@ -550,6 +550,7 @@ public interface DispenseItemBehavior { + if (!fertilizeEvent.isCancelled()) { + for (org.bukkit.block.BlockState blockstate : blocks) { + blockstate.update(true); ++ worldserver.checkCapturedTreeStateForObserverNotify(blockposition, (org.bukkit.craftbukkit.block.CraftBlockState) blockstate); // Paper - notify observers even if grow failed + } + } + } +diff --git a/src/main/java/net/minecraft/server/level/ServerLevel.java b/src/main/java/net/minecraft/server/level/ServerLevel.java +index e9096a138175448279a03a55043982b6d83fbe12..0db41d36d5daf015c750fb0246ab3e5da1cd81a2 100644 +--- a/src/main/java/net/minecraft/server/level/ServerLevel.java ++++ b/src/main/java/net/minecraft/server/level/ServerLevel.java +@@ -2198,6 +2198,16 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe + return range <= 0 ? 64.0 * 64.0 : range * range; // 64 is taken from default in ServerLevel#levelEvent + } + // Paper end - respect global sound events gamerule ++ // Paper start - notify observers even if grow failed ++ public void checkCapturedTreeStateForObserverNotify(final BlockPos pos, final org.bukkit.craftbukkit.block.CraftBlockState craftBlockState) { ++ // notify observers if the block state is the same and the Y level equals the original y level (for mega trees) ++ // blocks at the same Y level with the same state can be assumed to be saplings which trigger observers regardless of if the ++ // tree grew or not ++ if (craftBlockState.getPosition().getY() == pos.getY() && this.getBlockState(craftBlockState.getPosition()) == craftBlockState.getHandle()) { ++ this.notifyAndUpdatePhysics(craftBlockState.getPosition(), null, craftBlockState.getHandle(), craftBlockState.getHandle(), craftBlockState.getHandle(), craftBlockState.getFlag(), 512); ++ } ++ } ++ // Paper end - notify observers even if grow failed + + @Override + public CrashReportCategory fillReportDetails(CrashReport report) { +diff --git a/src/main/java/net/minecraft/world/item/ItemStack.java b/src/main/java/net/minecraft/world/item/ItemStack.java +index f81b3e050f21ce10ea86892d9a6bee9a42561514..cb6bcf8b61793882252827309ffa99526244e445 100644 +--- a/src/main/java/net/minecraft/world/item/ItemStack.java ++++ b/src/main/java/net/minecraft/world/item/ItemStack.java +@@ -469,6 +469,7 @@ public final class ItemStack implements DataComponentHolder { + for (CraftBlockState blockstate : blocks) { + // SPIGOT-7572 - Move fix for SPIGOT-7248 to CapturedBlockState, to allow bees in bee nest + CapturedBlockState.setBlockState(blockstate); ++ world.checkCapturedTreeStateForObserverNotify(blockposition, blockstate); // Paper - notify observers even if grow failed + } + entityhuman.awardStat(Stats.ITEM_USED.get(item)); // SPIGOT-7236 - award stat + } +diff --git a/src/main/java/net/minecraft/world/level/block/SaplingBlock.java b/src/main/java/net/minecraft/world/level/block/SaplingBlock.java +index 2d7290ace5fc8890325a8ec623075ad32f9b1d44..d262a5a6da57ef9ba9a6fe0dfbc88f577105e74f 100644 +--- a/src/main/java/net/minecraft/world/level/block/SaplingBlock.java ++++ b/src/main/java/net/minecraft/world/level/block/SaplingBlock.java +@@ -86,6 +86,7 @@ public class SaplingBlock extends BushBlock implements BonemealableBlock { + if (event == null || !event.isCancelled()) { + for (BlockState blockstate : blocks) { + CapturedBlockState.setBlockState(blockstate); ++ world.checkCapturedTreeStateForObserverNotify(pos, (org.bukkit.craftbukkit.block.CraftBlockState) blockstate); // Paper - notify observers even if grow failed + } + } + } +diff --git a/src/main/java/org/bukkit/craftbukkit/block/CraftBlock.java b/src/main/java/org/bukkit/craftbukkit/block/CraftBlock.java +index 7afb933782cac7e1ad621e80211c13066c680ad7..6dceb6082538df6f6147254e861d1cec8af7ec50 100644 +--- a/src/main/java/org/bukkit/craftbukkit/block/CraftBlock.java ++++ b/src/main/java/org/bukkit/craftbukkit/block/CraftBlock.java +@@ -572,6 +572,7 @@ public class CraftBlock implements Block { + if (!event.isCancelled()) { + for (BlockState blockstate : blocks) { + blockstate.update(true); ++ world.checkCapturedTreeStateForObserverNotify(this.position, (org.bukkit.craftbukkit.block.CraftBlockState) blockstate); // Paper - notify observers even if grow failed + } + } + } diff --git a/patches/server/0838-Only-set-despawnTimer-for-Wandering-Traders-spawned-.patch b/patches/server/0838-Only-set-despawnTimer-for-Wandering-Traders-spawned-.patch deleted file mode 100644 index 6785f755abf5..000000000000 --- a/patches/server/0838-Only-set-despawnTimer-for-Wandering-Traders-spawned-.patch +++ /dev/null @@ -1,33 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Jason Penilla <11360596+jpenilla@users.noreply.github.com> -Date: Fri, 19 Mar 2021 16:07:21 -0700 -Subject: [PATCH] Only set despawnTimer for Wandering Traders spawned by - WanderingTraderSpawner - - -diff --git a/src/main/java/net/minecraft/world/entity/npc/WanderingTrader.java b/src/main/java/net/minecraft/world/entity/npc/WanderingTrader.java -index 856a93324f5ac411713851ccfb38dba52fb0af5e..0af34e0f9c9696fbcb11b12fb27472ef17ad532a 100644 ---- a/src/main/java/net/minecraft/world/entity/npc/WanderingTrader.java -+++ b/src/main/java/net/minecraft/world/entity/npc/WanderingTrader.java -@@ -68,7 +68,7 @@ public class WanderingTrader extends net.minecraft.world.entity.npc.AbstractVill - - public WanderingTrader(EntityType type, Level world) { - super(type, world); -- this.setDespawnDelay(48000); // CraftBukkit - set default from MobSpawnerTrader -+ //this.setDespawnDelay(48000); // CraftBukkit - set default from MobSpawnerTrader // Paper - move back to MobSpawnerTrader - Vanilla behavior is that only traders spawned by it have this value set. - } - - @Override -diff --git a/src/main/java/net/minecraft/world/entity/npc/WanderingTraderSpawner.java b/src/main/java/net/minecraft/world/entity/npc/WanderingTraderSpawner.java -index d5d27b0d352ca3fd57a26605cb35c499e88b57fc..c72b6ea5530e54fc373c701028e1c147cea34b59 100644 ---- a/src/main/java/net/minecraft/world/entity/npc/WanderingTraderSpawner.java -+++ b/src/main/java/net/minecraft/world/entity/npc/WanderingTraderSpawner.java -@@ -120,7 +120,7 @@ public class WanderingTraderSpawner implements CustomSpawner { - return false; - } - -- WanderingTrader entityvillagertrader = (WanderingTrader) EntityType.WANDERING_TRADER.spawn(world, blockposition2, MobSpawnType.EVENT, org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason.NATURAL); // CraftBukkit -+ WanderingTrader entityvillagertrader = (WanderingTrader) EntityType.WANDERING_TRADER.spawn(world, trader -> trader.setDespawnDelay(48000), blockposition2, MobSpawnType.EVENT, false, false, org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason.NATURAL); // CraftBukkit // Paper - set despawnTimer before spawn events called - - if (entityvillagertrader != null) { - for (int i = 0; i < 2; ++i) { diff --git a/patches/server/0839-ExperienceOrb-should-call-EntitySpawnEvent.patch b/patches/server/0839-ExperienceOrb-should-call-EntitySpawnEvent.patch deleted file mode 100644 index b70d980d3161..000000000000 --- a/patches/server/0839-ExperienceOrb-should-call-EntitySpawnEvent.patch +++ /dev/null @@ -1,20 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Golfing8 -Date: Mon, 8 May 2023 09:18:17 -0400 -Subject: [PATCH] ExperienceOrb should call EntitySpawnEvent - - -diff --git a/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java b/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java -index 9b3023fdda9d08c0f5489542f644e2ea311af191..907ea60b647d529f133986632d00416269bc311e 100644 ---- a/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java -+++ b/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java -@@ -746,7 +746,8 @@ public class CraftEventFactory { - // Spigot start - SPIGOT-7523: Merge after spawn event and only merge if the event was not cancelled (gets checked above) - if (entity instanceof net.minecraft.world.entity.ExperienceOrb xp) { - double radius = world.spigotConfig.expMerge; -- if (radius > 0) { -+ event = CraftEventFactory.callEntitySpawnEvent(entity); // Call spawn event for ExperienceOrb entities -+ if (radius > 0 && !event.isCancelled() && !entity.isRemoved()) { - // Paper start - Maximum exp value when merging; Whole section has been tweaked, see comments for specifics - final long maxValue = world.paperConfig().entities.behavior.experienceMergeMaxValue; - final boolean mergeUnconditionally = maxValue <= 0; diff --git a/patches/server/0839-Use-correct-source-for-mushroom-block-spread-event.patch b/patches/server/0839-Use-correct-source-for-mushroom-block-spread-event.patch new file mode 100644 index 000000000000..658d9ec3761b --- /dev/null +++ b/patches/server/0839-Use-correct-source-for-mushroom-block-spread-event.patch @@ -0,0 +1,27 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Warrior <50800980+Warriorrrr@users.noreply.github.com> +Date: Tue, 8 Aug 2023 11:49:32 +0200 +Subject: [PATCH] Use correct source for mushroom block spread event + + +diff --git a/src/main/java/net/minecraft/world/level/block/MushroomBlock.java b/src/main/java/net/minecraft/world/level/block/MushroomBlock.java +index 61fb134686c7cd538ae2f8fefe78fd9618a9990b..7900856c9e86afe907c00c9ac31bec93ece50abe 100644 +--- a/src/main/java/net/minecraft/world/level/block/MushroomBlock.java ++++ b/src/main/java/net/minecraft/world/level/block/MushroomBlock.java +@@ -68,6 +68,7 @@ public class MushroomBlock extends BushBlock implements BonemealableBlock { + } + + BlockPos blockposition2 = pos.offset(random.nextInt(3) - 1, random.nextInt(2) - random.nextInt(2), random.nextInt(3) - 1); ++ final BlockPos sourcePos = pos; // Paper - Use correct source for mushroom block spread event + + for (int j = 0; j < 4; ++j) { + if (world.isEmptyBlock(blockposition2) && state.canSurvive(world, blockposition2)) { +@@ -78,7 +79,7 @@ public class MushroomBlock extends BushBlock implements BonemealableBlock { + } + + if (world.isEmptyBlock(blockposition2) && state.canSurvive(world, blockposition2)) { +- org.bukkit.craftbukkit.event.CraftEventFactory.handleBlockSpreadEvent(world, pos, blockposition2, state, 2); // CraftBukkit ++ org.bukkit.craftbukkit.event.CraftEventFactory.handleBlockSpreadEvent(world, sourcePos, blockposition2, state, 2); // CraftBukkit // Paper - Use correct source for mushroom block spread event + } + } + diff --git a/patches/server/0840-Respect-randomizeData-on-more-entities-when-spawning.patch b/patches/server/0840-Respect-randomizeData-on-more-entities-when-spawning.patch new file mode 100644 index 000000000000..67a5238077df --- /dev/null +++ b/patches/server/0840-Respect-randomizeData-on-more-entities-when-spawning.patch @@ -0,0 +1,68 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Jake Potrebic +Date: Thu, 13 Jul 2023 16:10:07 -0700 +Subject: [PATCH] Respect randomizeData on more entities when spawning + +* ItemEntity +* PrimedTNT +* FireworkRocketEntity +* ExperienceOrb + +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftEntityTypes.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftEntityTypes.java +index 6cf57640b3bd2bb0417129256ed77d9d67fcf04a..3b5277f90b5bba30d40d79ba67903a8d04d2c0df 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftEntityTypes.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftEntityTypes.java +@@ -255,6 +255,13 @@ public final class CraftEntityTypes { + entity.assignDirectionalMovement(new Vec3(direction.getX(), direction.getY(), direction.getZ()), 1.0); + }; + private static final BiConsumer ROT = (spawnData, entity) -> entity.setRot(spawnData.yaw(), spawnData.pitch()); // Paper ++ // Paper start - respect randomizeData ++ private static final BiConsumer CLEAR_MOVE_IF_NOT_RANDOMIZED = (spawnData, entity) -> { ++ if (!spawnData.randomizeData()) { ++ entity.setDeltaMovement(net.minecraft.world.phys.Vec3.ZERO); ++ } ++ }; ++ // Paper end - respect randomizeData + private static final Map, EntityTypeData> CLASS_TYPE_DATA = new HashMap<>(); + private static final Map> ENTITY_TYPE_DATA = new HashMap<>(); + +@@ -428,11 +435,12 @@ public final class CraftEntityTypes { + net.minecraft.world.item.ItemStack itemStack = new net.minecraft.world.item.ItemStack(Items.STONE); + ItemEntity item = new ItemEntity(spawnData.minecraftWorld(), spawnData.x(), spawnData.y(), spawnData.z(), itemStack); + item.setPickUpDelay(10); ++ CLEAR_MOVE_IF_NOT_RANDOMIZED.accept(spawnData, item); // Paper - respect randomizeData + + return item; + })); + register(new EntityTypeData<>(EntityType.EXPERIENCE_ORB, ExperienceOrb.class, CraftExperienceOrb::new, +- spawnData -> new net.minecraft.world.entity.ExperienceOrb(spawnData.minecraftWorld(), spawnData.x(), spawnData.y(), spawnData.z(), 0, org.bukkit.entity.ExperienceOrb.SpawnReason.CUSTOM, null, null) // Paper ++ combine(combine(spawnData -> new net.minecraft.world.entity.ExperienceOrb(spawnData.minecraftWorld(), spawnData.x(), spawnData.y(), spawnData.z(), 0, org.bukkit.entity.ExperienceOrb.SpawnReason.CUSTOM, null, null), CLEAR_MOVE_IF_NOT_RANDOMIZED), (spawnData, experienceOrb) -> { if (!spawnData.randomizeData()) { experienceOrb.setYRot(0); } }) // Paper - respect randomizeData + )); + register(new EntityTypeData<>(EntityType.AREA_EFFECT_CLOUD, AreaEffectCloud.class, CraftAreaEffectCloud::new, createAndMove(net.minecraft.world.entity.EntityType.AREA_EFFECT_CLOUD))); // Paper - set area effect cloud rotation + register(new EntityTypeData<>(EntityType.EGG, Egg.class, CraftEgg::new, spawnData -> new ThrownEgg(spawnData.minecraftWorld(), spawnData.x(), spawnData.y(), spawnData.z(), new net.minecraft.world.item.ItemStack(Items.EGG)))); +@@ -443,12 +451,23 @@ public final class CraftEntityTypes { + net.minecraft.world.entity.projectile.ThrownPotion entity = new net.minecraft.world.entity.projectile.ThrownPotion(spawnData.minecraftWorld(), spawnData.x(), spawnData.y(), spawnData.z(), new net.minecraft.world.item.ItemStack(Items.SPLASH_POTION)); + return entity; + })); +- register(new EntityTypeData<>(EntityType.TNT, TNTPrimed.class, CraftTNTPrimed::new, spawnData -> new PrimedTnt(spawnData.minecraftWorld(), spawnData.x(), spawnData.y(), spawnData.z(), null))); ++ register(new EntityTypeData<>(EntityType.TNT, TNTPrimed.class, CraftTNTPrimed::new, combine(spawnData -> new PrimedTnt(spawnData.minecraftWorld(), spawnData.x(), spawnData.y(), spawnData.z(), null), CLEAR_MOVE_IF_NOT_RANDOMIZED))); // Paper - respect randomizeData + register(new EntityTypeData<>(EntityType.FALLING_BLOCK, FallingBlock.class, CraftFallingBlock::new, spawnData -> { + BlockPos pos = BlockPos.containing(spawnData.x(), spawnData.y(), spawnData.z()); + return new FallingBlockEntity(spawnData.minecraftWorld(), spawnData.x(), spawnData.y(), spawnData.z(), spawnData.world().getBlockState(pos)); // Paper - create falling block entities correctly + })); +- register(new EntityTypeData<>(EntityType.FIREWORK_ROCKET, Firework.class, CraftFirework::new, spawnData -> new FireworkRocketEntity(spawnData.minecraftWorld(), spawnData.x(), spawnData.y(), spawnData.z(), FireworkRocketEntity.getDefaultItem()))); // Paper - pass correct default to rocket for data storage ++ // Paper start - respect randomizeData ++ register(new EntityTypeData<>(EntityType.FIREWORK_ROCKET, Firework.class, CraftFirework::new, spawnData -> { ++ FireworkRocketEntity entity = new FireworkRocketEntity(spawnData.minecraftWorld(), spawnData.x(), spawnData.y(), spawnData.z(), FireworkRocketEntity.getDefaultItem()); // Paper - pass correct default to rocket for data storage ++ if (!spawnData.randomizeData()) { ++ // logic below was taken from FireworkRocketEntity constructor ++ entity.setDeltaMovement(0, 0.05, 0); ++ //noinspection PointlessArithmeticExpression ++ entity.lifetime = 10 * 1 + 6; ++ } ++ return entity; ++ })); ++ // Paper end - respect randomizeData + register(new EntityTypeData<>(EntityType.EVOKER_FANGS, EvokerFangs.class, CraftEvokerFangs::new, spawnData -> new net.minecraft.world.entity.projectile.EvokerFangs(spawnData.minecraftWorld(), spawnData.x(), spawnData.y(), spawnData.z(), (float) Math.toRadians(spawnData.yaw()), 0, null))); + register(new EntityTypeData<>(EntityType.COMMAND_BLOCK_MINECART, CommandMinecart.class, CraftMinecartCommand::new, createMinecart(net.minecraft.world.entity.EntityType.COMMAND_BLOCK_MINECART))); + register(new EntityTypeData<>(EntityType.MINECART, RideableMinecart.class, CraftMinecartRideable::new, createMinecart(net.minecraft.world.entity.EntityType.MINECART))); diff --git a/patches/server/0841-Use-correct-seed-on-api-world-load.patch b/patches/server/0841-Use-correct-seed-on-api-world-load.patch new file mode 100644 index 000000000000..997631e23b84 --- /dev/null +++ b/patches/server/0841-Use-correct-seed-on-api-world-load.patch @@ -0,0 +1,19 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Florian Schmidt +Date: Fri, 28 Jul 2023 14:14:35 +0200 +Subject: [PATCH] Use correct seed on api world load + + +diff --git a/src/main/java/org/bukkit/craftbukkit/CraftServer.java b/src/main/java/org/bukkit/craftbukkit/CraftServer.java +index 46807d5d94a0f90b230dfaf4d37378a3bfa21414..da3819f49b105bd98ce94b5abdf1c7652ff625a4 100644 +--- a/src/main/java/org/bukkit/craftbukkit/CraftServer.java ++++ b/src/main/java/org/bukkit/craftbukkit/CraftServer.java +@@ -1388,7 +1388,7 @@ public final class CraftServer implements Server { + net.minecraft.server.Main.forceUpgrade(worldSession, DataFixers.getDataFixer(), this.console.options.has("eraseCache"), () -> true, iregistrycustom_dimension, this.console.options.has("recreateRegionFiles")); + } + +- long j = BiomeManager.obfuscateSeed(creator.seed()); ++ long j = BiomeManager.obfuscateSeed(worlddata.worldGenOptions().seed()); // Paper - use world seed + List list = ImmutableList.of(new PhantomSpawner(), new PatrolSpawner(), new CatSpawner(), new VillageSiege(), new WanderingTraderSpawner(worlddata)); + LevelStem worlddimension = iregistry.getValue(actualDimension); + diff --git a/patches/server/0842-Implement-PlayerFailMoveEvent.patch b/patches/server/0842-Implement-PlayerFailMoveEvent.patch deleted file mode 100644 index 6a55a3af38f5..000000000000 --- a/patches/server/0842-Implement-PlayerFailMoveEvent.patch +++ /dev/null @@ -1,111 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Moulberry -Date: Wed, 26 Jul 2023 20:13:31 +0800 -Subject: [PATCH] Implement PlayerFailMoveEvent - - -diff --git a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java -index db9c04d74475e00983cc9df74d615e4b68f83688..adcfa6a783222482ce7ec5c7805fe44dd24bf7d0 100644 ---- a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java -+++ b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java -@@ -1260,8 +1260,8 @@ public class ServerGamePacketListenerImpl extends ServerCommonPacketListenerImpl - double d0 = ServerGamePacketListenerImpl.clampHorizontal(packet.getX(this.player.getX())); final double toX = d0; // Paper - OBFHELPER - double d1 = ServerGamePacketListenerImpl.clampVertical(packet.getY(this.player.getY())); final double toY = d1; // Paper - OBFHELPER - double d2 = ServerGamePacketListenerImpl.clampHorizontal(packet.getZ(this.player.getZ())); final double toZ = d2; // Paper - OBFHELPER -- float f = Mth.wrapDegrees(packet.getYRot(this.player.getYRot())); -- float f1 = Mth.wrapDegrees(packet.getXRot(this.player.getXRot())); -+ float f = Mth.wrapDegrees(packet.getYRot(this.player.getYRot())); final float toYaw = f; // Paper - OBFHELPER -+ float f1 = Mth.wrapDegrees(packet.getXRot(this.player.getXRot())); final float toPitch = f1; // Paper - OBFHELPER - - if (this.player.isPassenger()) { - this.player.absMoveTo(this.player.getX(), this.player.getY(), this.player.getZ(), f, f1); -@@ -1328,8 +1328,14 @@ public class ServerGamePacketListenerImpl extends ServerCommonPacketListenerImpl - } - // Paper start - Prevent moving into unloaded chunks - if (this.player.level().paperConfig().chunks.preventMovingIntoUnloadedChunks && (this.player.getX() != toX || this.player.getZ() != toZ) && !worldserver.areChunksLoadedForMove(this.player.getBoundingBox().expandTowards(new Vec3(toX, toY, toZ).subtract(this.player.position())))) { -- this.internalTeleport(this.player.getX(), this.player.getY(), this.player.getZ(), this.player.getYRot(), this.player.getXRot(), Collections.emptySet()); -- return; -+ // Paper start - Add fail move event -+ io.papermc.paper.event.player.PlayerFailMoveEvent event = fireFailMove(io.papermc.paper.event.player.PlayerFailMoveEvent.FailReason.MOVED_INTO_UNLOADED_CHUNK, -+ toX, toY, toZ, toYaw, toPitch, false); -+ if (!event.isAllowed()) { -+ this.internalTeleport(this.player.getX(), this.player.getY(), this.player.getZ(), this.player.getYRot(), this.player.getXRot(), Collections.emptySet()); -+ return; -+ } -+ // Paper end - Add fail move event - } - // Paper end - Prevent moving into unloaded chunks - -@@ -1338,9 +1344,16 @@ public class ServerGamePacketListenerImpl extends ServerCommonPacketListenerImpl - - if (d10 - d9 > Math.max(f2, Math.pow((double) (org.spigotmc.SpigotConfig.movedTooQuicklyMultiplier * (float) i * speed), 2)) && !this.isSingleplayerOwner()) { - // CraftBukkit end -- ServerGamePacketListenerImpl.LOGGER.warn("{} moved too quickly! {},{},{}", new Object[]{this.player.getName().getString(), d6, d7, d8}); -- this.teleport(this.player.getX(), this.player.getY(), this.player.getZ(), this.player.getYRot(), this.player.getXRot()); -- return; -+ // Paper start - Add fail move event -+ io.papermc.paper.event.player.PlayerFailMoveEvent event = fireFailMove(io.papermc.paper.event.player.PlayerFailMoveEvent.FailReason.MOVED_TOO_QUICKLY, -+ toX, toY, toZ, toYaw, toPitch, true); -+ if (!event.isAllowed()) { -+ if (event.getLogWarning()) -+ ServerGamePacketListenerImpl.LOGGER.warn("{} moved too quickly! {},{},{}", new Object[]{this.player.getName().getString(), d6, d7, d8}); -+ this.teleport(this.player.getX(), this.player.getY(), this.player.getZ(), this.player.getYRot(), this.player.getXRot()); -+ return; -+ } -+ // Paper end - Add fail move event - } - } - } -@@ -1402,14 +1415,31 @@ public class ServerGamePacketListenerImpl extends ServerCommonPacketListenerImpl - - d8 = d2 - this.player.getZ(); - d10 = d6 * d6 + d7 * d7 + d8 * d8; -- boolean flag3 = false; -+ boolean movedWrongly = false; // Paper - Add fail move event; rename - - if (!this.player.isChangingDimension() && d10 > org.spigotmc.SpigotConfig.movedWronglyThreshold && !this.player.isSleeping() && !this.player.gameMode.isCreative() && this.player.gameMode.getGameModeForPlayer() != GameType.SPECTATOR) { // Spigot -- flag3 = true; -+ // Paper start - Add fail move event -+ io.papermc.paper.event.player.PlayerFailMoveEvent event = fireFailMove(io.papermc.paper.event.player.PlayerFailMoveEvent.FailReason.MOVED_WRONGLY, -+ toX, toY, toZ, toYaw, toPitch, true); -+ if (!event.isAllowed()) { -+ movedWrongly = true; -+ if (event.getLogWarning()) -+ // Paper end - ServerGamePacketListenerImpl.LOGGER.warn("{} moved wrongly!", this.player.getName().getString()); -+ } // Paper - } - -- if (!this.player.noPhysics && !this.player.isSleeping() && (flag3 && worldserver.noCollision(this.player, axisalignedbb) || this.isPlayerCollidingWithAnythingNew(worldserver, axisalignedbb, d0, d1, d2))) { -+ // Paper start - Add fail move event -+ boolean teleportBack = !this.player.noPhysics && !this.player.isSleeping() && (movedWrongly && worldserver.noCollision(this.player, axisalignedbb) || this.isPlayerCollidingWithAnythingNew(worldserver, axisalignedbb, d0, d1, d2)); -+ if (teleportBack) { -+ io.papermc.paper.event.player.PlayerFailMoveEvent event = fireFailMove(io.papermc.paper.event.player.PlayerFailMoveEvent.FailReason.CLIPPED_INTO_BLOCK, -+ toX, toY, toZ, toYaw, toPitch, false); -+ if (event.isAllowed()) { -+ teleportBack = false; -+ } -+ } -+ if (teleportBack) { -+ // Paper end - Add fail move event - this.internalTeleport(d3, d4, d5, f, f1, Collections.emptySet()); // CraftBukkit - SPIGOT-1807: Don't call teleport event, when the client thinks the player is falling, because the chunks are not loaded on the client yet. - this.player.doCheckFallDamage(this.player.getX() - d3, this.player.getY() - d4, this.player.getZ() - d5, packet.isOnGround()); - } else { -@@ -3465,4 +3495,17 @@ public class ServerGamePacketListenerImpl extends ServerCommonPacketListenerImpl - - InteractionResult run(ServerPlayer player, Entity entity, InteractionHand hand); - } -+ -+ // Paper start - Add fail move event -+ private io.papermc.paper.event.player.PlayerFailMoveEvent fireFailMove(io.papermc.paper.event.player.PlayerFailMoveEvent.FailReason failReason, -+ double toX, double toY, double toZ, float toYaw, float toPitch, boolean logWarning) { -+ Player player = this.getCraftPlayer(); -+ Location from = new Location(player.getWorld(), this.lastPosX, this.lastPosY, this.lastPosZ, this.lastYaw, this.lastPitch); -+ Location to = new Location(player.getWorld(), toX, toY, toZ, toYaw, toPitch); -+ io.papermc.paper.event.player.PlayerFailMoveEvent event = new io.papermc.paper.event.player.PlayerFailMoveEvent(player, failReason, -+ false, logWarning, from, to); -+ event.callEvent(); -+ return event; -+ } -+ // Paper end - Add fail move event - } diff --git a/patches/server/0842-Remove-UpgradeData-neighbour-ticks-outside-of-range.patch b/patches/server/0842-Remove-UpgradeData-neighbour-ticks-outside-of-range.patch new file mode 100644 index 000000000000..d87f310ded9b --- /dev/null +++ b/patches/server/0842-Remove-UpgradeData-neighbour-ticks-outside-of-range.patch @@ -0,0 +1,50 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Spottedleaf +Date: Wed, 9 Aug 2023 14:00:40 -0700 +Subject: [PATCH] Remove UpgradeData neighbour ticks outside of range + +The lists are only supposed to contain ticks for the 1 radius +neighbours of the chunk. + +diff --git a/src/main/java/net/minecraft/world/level/chunk/UpgradeData.java b/src/main/java/net/minecraft/world/level/chunk/UpgradeData.java +index 5c7c8fb3c67047ddc1b92f03e889839b5a303ff4..ce3d79ebc7f933d0b34b3f8f71bbec872076847a 100644 +--- a/src/main/java/net/minecraft/world/level/chunk/UpgradeData.java ++++ b/src/main/java/net/minecraft/world/level/chunk/UpgradeData.java +@@ -113,6 +113,25 @@ public class UpgradeData { + } + } + ++ // Paper start - filter out relocated neighbour ticks ++ // The lists are only supposed to contain ticks for the 1 radius neighbours of the chunk ++ private static void filterTickList(int chunkX, int chunkZ, List> ticks) { ++ for (java.util.Iterator> iterator = ticks.iterator(); iterator.hasNext();) { ++ SavedTick tick = iterator.next(); ++ BlockPos tickPos = tick.pos(); ++ int tickCX = tickPos.getX() >> 4; ++ int tickCZ = tickPos.getZ() >> 4; ++ ++ int dist = Math.max(Math.abs(chunkX - tickCX), Math.abs(chunkZ - tickCZ)); ++ ++ if (dist != 1) { ++ LOGGER.warn("Neighbour tick '" + tick + "' serialized in chunk (" + chunkX + "," + chunkZ + ") is too far (" + tickCX + "," + tickCZ + ")"); ++ iterator.remove(); ++ } ++ } ++ } ++ // Paper end - filter out relocated neighbour ticks ++ + public void upgrade(LevelChunk chunk) { + this.upgradeInside(chunk); + +@@ -120,6 +139,11 @@ public class UpgradeData { + upgradeSides(chunk, direction8); + } + ++ // Paper start - filter out relocated neighbour ticks ++ filterTickList(chunk.locX, chunk.locZ, this.neighborBlockTicks); ++ filterTickList(chunk.locX, chunk.locZ, this.neighborFluidTicks); ++ // Paper end - filter out relocated neighbour ticks ++ + Level level = chunk.getLevel(); + this.neighborBlockTicks.forEach(tick -> { + Block block = tick.type() == Blocks.AIR ? level.getBlockState(tick.pos()).getBlock() : tick.type(); diff --git a/patches/server/0843-Cache-map-ids-on-item-frames.patch b/patches/server/0843-Cache-map-ids-on-item-frames.patch new file mode 100644 index 000000000000..01fb83165d8b --- /dev/null +++ b/patches/server/0843-Cache-map-ids-on-item-frames.patch @@ -0,0 +1,39 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Warrior <50800980+Warriorrrr@users.noreply.github.com> +Date: Mon, 7 Aug 2023 12:58:28 +0200 +Subject: [PATCH] Cache map ids on item frames + + +diff --git a/src/main/java/net/minecraft/server/level/ServerEntity.java b/src/main/java/net/minecraft/server/level/ServerEntity.java +index 3cdcc4f44608d24550f2a8c6f3f5ce675d7777c5..7118e1f806af98159ec292f9340d7e4004e2b486 100644 +--- a/src/main/java/net/minecraft/server/level/ServerEntity.java ++++ b/src/main/java/net/minecraft/server/level/ServerEntity.java +@@ -121,7 +121,7 @@ public class ServerEntity { + ItemStack itemstack = entityitemframe.getItem(); + + if (this.level.paperConfig().maps.itemFrameCursorUpdateInterval > 0 && this.tickCount % this.level.paperConfig().maps.itemFrameCursorUpdateInterval == 0 && itemstack.getItem() instanceof MapItem) { // CraftBukkit - Moved this.tickCounter % 10 logic here so item frames do not enter the other blocks // Paper - Make item frame map cursor update interval configurable +- MapId mapid = (MapId) itemstack.get(DataComponents.MAP_ID); ++ MapId mapid = entityitemframe.cachedMapId; // Paper - Perf: Cache map ids on item frames + MapItemSavedData worldmap = MapItem.getSavedData(mapid, this.level); + + if (worldmap != null) { +diff --git a/src/main/java/net/minecraft/world/entity/decoration/ItemFrame.java b/src/main/java/net/minecraft/world/entity/decoration/ItemFrame.java +index d431ee93cd7e87a24ff4079288facd089053d725..bbdaaa1cc0b4aed28bc39385508d221055b99d4d 100644 +--- a/src/main/java/net/minecraft/world/entity/decoration/ItemFrame.java ++++ b/src/main/java/net/minecraft/world/entity/decoration/ItemFrame.java +@@ -51,6 +51,7 @@ public class ItemFrame extends HangingEntity { + private static final float HEIGHT = 0.75F; + public float dropChance; + public boolean fixed; ++ public @Nullable MapId cachedMapId; // Paper - Perf: Cache map ids on item frames + + public ItemFrame(EntityType type, Level world) { + super(type, world); +@@ -334,6 +335,7 @@ public class ItemFrame extends HangingEntity { + } + + private void onItemChanged(ItemStack stack) { ++ this.cachedMapId = stack.getComponents().get(net.minecraft.core.component.DataComponents.MAP_ID); // Paper - Perf: Cache map ids on item frames + if (!stack.isEmpty() && stack.getFrame() != this) { + stack.setEntityRepresentation(this); + } diff --git a/patches/server/0843-Folia-scheduler-and-owned-region-API.patch b/patches/server/0843-Folia-scheduler-and-owned-region-API.patch deleted file mode 100644 index 811bde5d9a1f..000000000000 --- a/patches/server/0843-Folia-scheduler-and-owned-region-API.patch +++ /dev/null @@ -1,1366 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Spottedleaf -Date: Sat, 17 Jun 2023 11:52:52 +0200 -Subject: [PATCH] Folia scheduler and owned region API - -Pulling Folia API to Paper is primarily intended for plugins -that want to target both Paper and Folia without unnecessary -compatibility layers. - -Add both a location based scheduler, an entity based scheduler, -and a global region scheduler. - -Owned region API may be useful for plugins which want to perform -operations over large areas outside of the buffer zone provided -by the regionaliser, as it is not guaranteed that anything -outside of the buffer zone is owned. Then, the plugins may use -the schedulers depending on the result of the ownership check. - -diff --git a/src/main/java/io/papermc/paper/plugin/manager/PaperPluginInstanceManager.java b/src/main/java/io/papermc/paper/plugin/manager/PaperPluginInstanceManager.java -index d2dee700f2c5cc7d6a272e751a933901fe7a55b6..834b85f24df023642f8abf7213fe578ac8c17a3e 100644 ---- a/src/main/java/io/papermc/paper/plugin/manager/PaperPluginInstanceManager.java -+++ b/src/main/java/io/papermc/paper/plugin/manager/PaperPluginInstanceManager.java -@@ -263,6 +263,22 @@ class PaperPluginInstanceManager { - + pluginName + " (Is it up to date?)", ex, plugin); // Paper - } - -+ // Paper start - Folia schedulers -+ try { -+ this.server.getGlobalRegionScheduler().cancelTasks(plugin); -+ } catch (Throwable ex) { -+ this.handlePluginException("Error occurred (in the plugin loader) while cancelling global tasks for " -+ + pluginName + " (Is it up to date?)", ex, plugin); // Paper -+ } -+ -+ try { -+ this.server.getAsyncScheduler().cancelTasks(plugin); -+ } catch (Throwable ex) { -+ this.handlePluginException("Error occurred (in the plugin loader) while cancelling async tasks for " -+ + pluginName + " (Is it up to date?)", ex, plugin); // Paper -+ } -+ // Paper end - Folia schedulers -+ - try { - this.server.getServicesManager().unregisterAll(plugin); - } catch (Throwable ex) { -diff --git a/src/main/java/io/papermc/paper/threadedregions/EntityScheduler.java b/src/main/java/io/papermc/paper/threadedregions/EntityScheduler.java -new file mode 100644 -index 0000000000000000000000000000000000000000..c03608fec96b51e1867f43d8f42e5aefb1520e46 ---- /dev/null -+++ b/src/main/java/io/papermc/paper/threadedregions/EntityScheduler.java -@@ -0,0 +1,181 @@ -+package io.papermc.paper.threadedregions; -+ -+import ca.spottedleaf.concurrentutil.util.Validate; -+import ca.spottedleaf.moonrise.common.util.TickThread; -+import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap; -+import net.minecraft.world.entity.Entity; -+import org.bukkit.craftbukkit.entity.CraftEntity; -+ -+import java.util.ArrayDeque; -+import java.util.ArrayList; -+import java.util.List; -+import java.util.function.Consumer; -+ -+/** -+ * An entity can move between worlds with an arbitrary tick delay, be temporarily removed -+ * for players (i.e end credits), be partially removed from world state (i.e inactive but not removed), -+ * teleport between ticking regions, teleport between worlds (which will change the underlying Entity object -+ * for non-players), and even be removed entirely from the server. The uncertainty of an entity's state can make -+ * it difficult to schedule tasks without worrying about undefined behaviors resulting from any of the states listed -+ * previously. -+ * -+ *

    -+ * This class is designed to eliminate those states by providing an interface to run tasks only when an entity -+ * is contained in a world, on the owning thread for the region, and by providing the current Entity object. -+ * The scheduler also allows a task to provide a callback, the "retired" callback, that will be invoked -+ * if the entity is removed before a task that was scheduled could be executed. The scheduler is also -+ * completely thread-safe, allowing tasks to be scheduled from any thread context. The scheduler also indicates -+ * properly whether a task was scheduled successfully (i.e scheduler not retired), thus the code scheduling any task -+ * knows whether the given callbacks will be invoked eventually or not - which may be critical for off-thread -+ * contexts. -+ *

    -+ */ -+public final class EntityScheduler { -+ -+ /** -+ * The Entity. Note that it is the CraftEntity, since only that class properly tracks world transfers. -+ */ -+ public final CraftEntity entity; -+ -+ private static final record ScheduledTask(Consumer run, Consumer retired) {} -+ -+ private long tickCount = 0L; -+ private static final long RETIRED_TICK_COUNT = -1L; -+ private final Object stateLock = new Object(); -+ private final Long2ObjectOpenHashMap> oneTimeDelayed = new Long2ObjectOpenHashMap<>(); -+ -+ private final ArrayDeque currentlyExecuting = new ArrayDeque<>(); -+ -+ public EntityScheduler(final CraftEntity entity) { -+ this.entity = Validate.notNull(entity); -+ } -+ -+ /** -+ * Retires the scheduler, preventing new tasks from being scheduled and invoking the retired callback -+ * on all currently scheduled tasks. -+ * -+ *

    -+ * Note: This should only be invoked after synchronously removing the entity from the world. -+ *

    -+ * -+ * @throws IllegalStateException If the scheduler is already retired. -+ */ -+ public void retire() { -+ synchronized (this.stateLock) { -+ if (this.tickCount == RETIRED_TICK_COUNT) { -+ throw new IllegalStateException("Already retired"); -+ } -+ this.tickCount = RETIRED_TICK_COUNT; -+ } -+ -+ final Entity thisEntity = this.entity.getHandleRaw(); -+ -+ // correctly handle and order retiring while running executeTick -+ for (int i = 0, len = this.currentlyExecuting.size(); i < len; ++i) { -+ final ScheduledTask task = this.currentlyExecuting.pollFirst(); -+ final Consumer retireTask = (Consumer)task.retired; -+ if (retireTask == null) { -+ continue; -+ } -+ -+ retireTask.accept(thisEntity); -+ } -+ -+ for (final List tasks : this.oneTimeDelayed.values()) { -+ for (int i = 0, len = tasks.size(); i < len; ++i) { -+ final ScheduledTask task = tasks.get(i); -+ final Consumer retireTask = (Consumer)task.retired; -+ if (retireTask == null) { -+ continue; -+ } -+ -+ retireTask.accept(thisEntity); -+ } -+ } -+ } -+ -+ /** -+ * Schedules a task with the given delay. If the task failed to schedule because the scheduler is retired (entity -+ * removed), then returns {@code false}. Otherwise, either the run callback will be invoked after the specified delay, -+ * or the retired callback will be invoked if the scheduler is retired. -+ * Note that the retired callback is invoked in critical code, so it should not attempt to remove the entity, remove -+ * other entities, load chunks, load worlds, modify ticket levels, etc. -+ * -+ *

    -+ * It is guaranteed that the run and retired callback are invoked on the region which owns the entity. -+ *

    -+ *

    -+ * The run and retired callback take an Entity parameter representing the current object entity that the scheduler -+ * is tied to. Since the scheduler is transferred when an entity changes dimensions, it is possible the entity parameter -+ * is not the same when the task was first scheduled. Thus, only the parameter provided should be used. -+ *

    -+ * @param run The callback to run after the specified delay, may not be null. -+ * @param retired Retire callback to run if the entity is retired before the run callback can be invoked, may be null. -+ * @param delay The delay in ticks before the run callback is invoked. Any value less-than 1 is treated as 1. -+ * @return {@code true} if the task was scheduled, which means that either the run function or the retired function -+ * will be invoked (but never both), or {@code false} indicating neither the run nor retired function will be invoked -+ * since the scheduler has been retired. -+ */ -+ public boolean schedule(final Consumer run, final Consumer retired, final long delay) { -+ Validate.notNull(run, "Run task may not be null"); -+ -+ final ScheduledTask task = new ScheduledTask(run, retired); -+ synchronized (this.stateLock) { -+ if (this.tickCount == RETIRED_TICK_COUNT) { -+ return false; -+ } -+ this.oneTimeDelayed.computeIfAbsent(this.tickCount + Math.max(1L, delay), (final long keyInMap) -> { -+ return new ArrayList<>(); -+ }).add(task); -+ } -+ -+ return true; -+ } -+ -+ /** -+ * Executes a tick for the scheduler. -+ * -+ * @throws IllegalStateException If the scheduler is retired. -+ */ -+ public void executeTick() { -+ final Entity thisEntity = this.entity.getHandleRaw(); -+ -+ TickThread.ensureTickThread(thisEntity, "May not tick entity scheduler asynchronously"); -+ final List toRun; -+ synchronized (this.stateLock) { -+ if (this.tickCount == RETIRED_TICK_COUNT) { -+ throw new IllegalStateException("Ticking retired scheduler"); -+ } -+ ++this.tickCount; -+ if (this.oneTimeDelayed.isEmpty()) { -+ toRun = null; -+ } else { -+ toRun = this.oneTimeDelayed.remove(this.tickCount); -+ } -+ } -+ -+ if (toRun != null) { -+ for (int i = 0, len = toRun.size(); i < len; ++i) { -+ this.currentlyExecuting.addLast(toRun.get(i)); -+ } -+ } -+ -+ // Note: It is allowed for the tasks executed to retire the entity in a given task. -+ for (int i = 0, len = this.currentlyExecuting.size(); i < len; ++i) { -+ if (!TickThread.isTickThreadFor(thisEntity)) { -+ // tp has been queued sync by one of the tasks -+ // in this case, we need to delay the tasks for next tick -+ break; -+ } -+ final ScheduledTask task = this.currentlyExecuting.pollFirst(); -+ -+ if (this.tickCount != RETIRED_TICK_COUNT) { -+ ((Consumer)task.run).accept(thisEntity); -+ } else { -+ // retired synchronously -+ // note: here task is null -+ break; -+ } -+ } -+ } -+} -diff --git a/src/main/java/io/papermc/paper/threadedregions/scheduler/FallbackRegionScheduler.java b/src/main/java/io/papermc/paper/threadedregions/scheduler/FallbackRegionScheduler.java -new file mode 100644 -index 0000000000000000000000000000000000000000..94056d61a304ee012ae1828a33412516095f996f ---- /dev/null -+++ b/src/main/java/io/papermc/paper/threadedregions/scheduler/FallbackRegionScheduler.java -@@ -0,0 +1,30 @@ -+package io.papermc.paper.threadedregions.scheduler; -+ -+import org.bukkit.World; -+import org.bukkit.plugin.Plugin; -+import org.jetbrains.annotations.NotNull; -+ -+import java.util.function.Consumer; -+ -+public final class FallbackRegionScheduler implements RegionScheduler { -+ -+ @Override -+ public void execute(@NotNull final Plugin plugin, @NotNull final World world, final int chunkX, final int chunkZ, @NotNull final Runnable run) { -+ plugin.getServer().getGlobalRegionScheduler().execute(plugin, run); -+ } -+ -+ @Override -+ public @NotNull ScheduledTask run(@NotNull final Plugin plugin, @NotNull final World world, final int chunkX, final int chunkZ, @NotNull final Consumer task) { -+ return plugin.getServer().getGlobalRegionScheduler().run(plugin, task); -+ } -+ -+ @Override -+ public @NotNull ScheduledTask runDelayed(@NotNull final Plugin plugin, @NotNull final World world, final int chunkX, final int chunkZ, @NotNull final Consumer task, final long delayTicks) { -+ return plugin.getServer().getGlobalRegionScheduler().runDelayed(plugin, task, delayTicks); -+ } -+ -+ @Override -+ public @NotNull ScheduledTask runAtFixedRate(@NotNull final Plugin plugin, @NotNull final World world, final int chunkX, final int chunkZ, @NotNull final Consumer task, final long initialDelayTicks, final long periodTicks) { -+ return plugin.getServer().getGlobalRegionScheduler().runAtFixedRate(plugin, task, initialDelayTicks, periodTicks); -+ } -+} -diff --git a/src/main/java/io/papermc/paper/threadedregions/scheduler/FoliaAsyncScheduler.java b/src/main/java/io/papermc/paper/threadedregions/scheduler/FoliaAsyncScheduler.java -new file mode 100644 -index 0000000000000000000000000000000000000000..374abffb9f1ce1a308822aed13038e77fe9ca08b ---- /dev/null -+++ b/src/main/java/io/papermc/paper/threadedregions/scheduler/FoliaAsyncScheduler.java -@@ -0,0 +1,328 @@ -+package io.papermc.paper.threadedregions.scheduler; -+ -+import ca.spottedleaf.concurrentutil.util.Validate; -+import com.mojang.logging.LogUtils; -+import org.bukkit.plugin.IllegalPluginAccessException; -+import org.bukkit.plugin.Plugin; -+import org.slf4j.Logger; -+ -+import java.util.Set; -+import java.util.concurrent.ConcurrentHashMap; -+import java.util.concurrent.Executor; -+import java.util.concurrent.Executors; -+import java.util.concurrent.ScheduledExecutorService; -+import java.util.concurrent.ScheduledFuture; -+import java.util.concurrent.SynchronousQueue; -+import java.util.concurrent.ThreadFactory; -+import java.util.concurrent.ThreadPoolExecutor; -+import java.util.concurrent.TimeUnit; -+import java.util.concurrent.atomic.AtomicInteger; -+import java.util.function.Consumer; -+import java.util.logging.Level; -+ -+public final class FoliaAsyncScheduler implements AsyncScheduler { -+ -+ private static final Logger LOGGER = LogUtils.getClassLogger(); -+ -+ private final Executor executors = new ThreadPoolExecutor(Math.max(4, Runtime.getRuntime().availableProcessors() / 2), Integer.MAX_VALUE, -+ 30L, TimeUnit.SECONDS, new SynchronousQueue<>(), -+ new ThreadFactory() { -+ private final AtomicInteger idGenerator = new AtomicInteger(); -+ -+ @Override -+ public Thread newThread(final Runnable run) { -+ final Thread ret = new Thread(run); -+ -+ ret.setName("Folia Async Scheduler Thread #" + this.idGenerator.getAndIncrement()); -+ ret.setPriority(Thread.NORM_PRIORITY - 1); -+ ret.setUncaughtExceptionHandler((final Thread thread, final Throwable thr) -> { -+ LOGGER.error("Uncaught exception in thread: " + thread.getName(), thr); -+ }); -+ -+ return ret; -+ } -+ } -+ ); -+ -+ private final ScheduledExecutorService timerThread = Executors.newSingleThreadScheduledExecutor(new ThreadFactory() { -+ @Override -+ public Thread newThread(final Runnable run) { -+ final Thread ret = new Thread(run); -+ -+ ret.setName("Folia Async Scheduler Thread Timer"); -+ ret.setPriority(Thread.NORM_PRIORITY + 1); -+ ret.setUncaughtExceptionHandler((final Thread thread, final Throwable thr) -> { -+ LOGGER.error("Uncaught exception in thread: " + thread.getName(), thr); -+ }); -+ -+ return ret; -+ } -+ }); -+ -+ private final Set tasks = ConcurrentHashMap.newKeySet(); -+ -+ @Override -+ public ScheduledTask runNow(final Plugin plugin, final Consumer task) { -+ Validate.notNull(plugin, "Plugin may not be null"); -+ Validate.notNull(task, "Task may not be null"); -+ -+ if (!plugin.isEnabled()) { -+ throw new IllegalPluginAccessException("Plugin attempted to register task while disabled"); -+ } -+ -+ final AsyncScheduledTask ret = new AsyncScheduledTask(plugin, -1L, task, null, -1L); -+ -+ this.tasks.add(ret); -+ this.executors.execute(ret); -+ -+ if (!plugin.isEnabled()) { -+ // handle race condition where plugin is disabled asynchronously -+ ret.cancel(); -+ } -+ -+ return ret; -+ } -+ -+ @Override -+ public ScheduledTask runDelayed(final Plugin plugin, final Consumer task, final long delay, -+ final TimeUnit unit) { -+ Validate.notNull(plugin, "Plugin may not be null"); -+ Validate.notNull(task, "Task may not be null"); -+ Validate.notNull(unit, "Time unit may not be null"); -+ if (delay < 0L) { -+ throw new IllegalArgumentException("Delay may not be < 0"); -+ } -+ -+ if (!plugin.isEnabled()) { -+ throw new IllegalPluginAccessException("Plugin attempted to register task while disabled"); -+ } -+ -+ return this.scheduleTimerTask(plugin, task, delay, -1L, unit); -+ } -+ -+ @Override -+ public ScheduledTask runAtFixedRate(final Plugin plugin, final Consumer task, final long initialDelay, -+ final long period, final TimeUnit unit) { -+ Validate.notNull(plugin, "Plugin may not be null"); -+ Validate.notNull(task, "Task may not be null"); -+ Validate.notNull(unit, "Time unit may not be null"); -+ if (initialDelay < 0L) { -+ throw new IllegalArgumentException("Initial delay may not be < 0"); -+ } -+ if (period <= 0L) { -+ throw new IllegalArgumentException("Period may not be <= 0"); -+ } -+ -+ if (!plugin.isEnabled()) { -+ throw new IllegalPluginAccessException("Plugin attempted to register task while disabled"); -+ } -+ -+ return this.scheduleTimerTask(plugin, task, initialDelay, period, unit); -+ } -+ -+ private AsyncScheduledTask scheduleTimerTask(final Plugin plugin, final Consumer task, final long initialDelay, -+ final long period, final TimeUnit unit) { -+ final AsyncScheduledTask ret = new AsyncScheduledTask( -+ plugin, period <= 0 ? period : unit.toNanos(period), task, null, -+ System.nanoTime() + unit.toNanos(initialDelay) -+ ); -+ -+ synchronized (ret) { -+ // even though ret is not published, we need to synchronise while scheduling to avoid a race condition -+ // for when a scheduled task immediately executes before we update the delay field and state field -+ ret.setDelay(this.timerThread.schedule(ret, initialDelay, unit)); -+ this.tasks.add(ret); -+ } -+ -+ if (!plugin.isEnabled()) { -+ // handle race condition where plugin is disabled asynchronously -+ ret.cancel(); -+ } -+ -+ return ret; -+ } -+ -+ @Override -+ public void cancelTasks(final Plugin plugin) { -+ Validate.notNull(plugin, "Plugin may not be null"); -+ -+ for (final AsyncScheduledTask task : this.tasks) { -+ if (task.plugin == plugin) { -+ task.cancel(); -+ } -+ } -+ } -+ -+ private final class AsyncScheduledTask implements ScheduledTask, Runnable { -+ -+ private static final int STATE_ON_TIMER = 0; -+ private static final int STATE_SCHEDULED_EXECUTOR = 1; -+ private static final int STATE_EXECUTING = 2; -+ private static final int STATE_EXECUTING_CANCELLED = 3; -+ private static final int STATE_FINISHED = 4; -+ private static final int STATE_CANCELLED = 5; -+ -+ private final Plugin plugin; -+ private final long repeatDelay; // in ns -+ private Consumer run; -+ private ScheduledFuture delay; -+ private int state; -+ private long scheduleTarget; -+ -+ public AsyncScheduledTask(final Plugin plugin, final long repeatDelay, final Consumer run, -+ final ScheduledFuture delay, final long firstTarget) { -+ this.plugin = plugin; -+ this.repeatDelay = repeatDelay; -+ this.run = run; -+ this.delay = delay; -+ this.state = delay == null ? STATE_SCHEDULED_EXECUTOR : STATE_ON_TIMER; -+ this.scheduleTarget = firstTarget; -+ } -+ -+ private void setDelay(final ScheduledFuture delay) { -+ this.delay = delay; -+ this.state = STATE_SCHEDULED_EXECUTOR; -+ } -+ -+ @Override -+ public void run() { -+ final boolean repeating = this.isRepeatingTask(); -+ // try to advance state -+ final boolean timer; -+ synchronized (this) { -+ if (this.state == STATE_ON_TIMER) { -+ timer = true; -+ this.delay = null; -+ this.state = STATE_SCHEDULED_EXECUTOR; -+ } else if (this.state != STATE_SCHEDULED_EXECUTOR) { -+ // cancelled -+ if (this.state != STATE_CANCELLED) { -+ throw new IllegalStateException("Wrong state: " + this.state); -+ } -+ return; -+ } else { -+ timer = false; -+ this.state = STATE_EXECUTING; -+ } -+ } -+ -+ if (timer) { -+ // the scheduled executor is single thread, and unfortunately not expandable with threads -+ // so we just schedule onto the executor -+ FoliaAsyncScheduler.this.executors.execute(this); -+ return; -+ } -+ -+ try { -+ this.run.accept(this); -+ } catch (final Throwable throwable) { -+ this.plugin.getLogger().log(Level.WARNING, "Async task for " + this.plugin.getDescription().getFullName() + " generated an exception", throwable); -+ } finally { -+ boolean removeFromTasks = false; -+ synchronized (this) { -+ if (!repeating) { -+ // only want to execute once, so we're done -+ removeFromTasks = true; -+ this.state = STATE_FINISHED; -+ } else if (this.state != STATE_EXECUTING_CANCELLED) { -+ this.state = STATE_ON_TIMER; -+ // account for any delays, whether it be by task exec. or scheduler issues so that we keep -+ // the fixed schedule -+ final long currTime = System.nanoTime(); -+ final long delay = Math.max(0L, this.scheduleTarget + this.repeatDelay - currTime); -+ this.scheduleTarget = currTime + delay; -+ this.delay = FoliaAsyncScheduler.this.timerThread.schedule(this, delay, TimeUnit.NANOSECONDS); -+ } else { -+ // cancelled repeating task -+ removeFromTasks = true; -+ } -+ } -+ -+ if (removeFromTasks) { -+ this.run = null; -+ FoliaAsyncScheduler.this.tasks.remove(this); -+ } -+ } -+ } -+ -+ @Override -+ public Plugin getOwningPlugin() { -+ return this.plugin; -+ } -+ -+ @Override -+ public boolean isRepeatingTask() { -+ return this.repeatDelay > 0L; -+ } -+ -+ @Override -+ public CancelledState cancel() { -+ ScheduledFuture delay = null; -+ CancelledState ret; -+ synchronized (this) { -+ switch (this.state) { -+ case STATE_ON_TIMER: { -+ delay = this.delay; -+ this.delay = null; -+ this.state = STATE_CANCELLED; -+ ret = CancelledState.CANCELLED_BY_CALLER; -+ break; -+ } -+ case STATE_SCHEDULED_EXECUTOR: { -+ this.state = STATE_CANCELLED; -+ ret = CancelledState.CANCELLED_BY_CALLER; -+ break; -+ } -+ case STATE_EXECUTING: { -+ if (!this.isRepeatingTask()) { -+ return CancelledState.RUNNING; -+ } -+ this.state = STATE_EXECUTING_CANCELLED; -+ return CancelledState.NEXT_RUNS_CANCELLED; -+ } -+ case STATE_EXECUTING_CANCELLED: { -+ return CancelledState.NEXT_RUNS_CANCELLED_ALREADY; -+ } -+ case STATE_FINISHED: { -+ return CancelledState.ALREADY_EXECUTED; -+ } -+ case STATE_CANCELLED: { -+ return CancelledState.CANCELLED_ALREADY; -+ } -+ default: { -+ throw new IllegalStateException("Unknown state: " + this.state); -+ } -+ } -+ } -+ -+ if (delay != null) { -+ delay.cancel(false); -+ } -+ this.run = null; -+ FoliaAsyncScheduler.this.tasks.remove(this); -+ return ret; -+ } -+ -+ @Override -+ public ExecutionState getExecutionState() { -+ synchronized (this) { -+ switch (this.state) { -+ case STATE_ON_TIMER: -+ case STATE_SCHEDULED_EXECUTOR: -+ return ExecutionState.IDLE; -+ case STATE_EXECUTING: -+ return ExecutionState.RUNNING; -+ case STATE_EXECUTING_CANCELLED: -+ return ExecutionState.CANCELLED_RUNNING; -+ case STATE_FINISHED: -+ return ExecutionState.FINISHED; -+ case STATE_CANCELLED: -+ return ExecutionState.CANCELLED; -+ default: { -+ throw new IllegalStateException("Unknown state: " + this.state); -+ } -+ } -+ } -+ } -+ } -+} -diff --git a/src/main/java/io/papermc/paper/threadedregions/scheduler/FoliaEntityScheduler.java b/src/main/java/io/papermc/paper/threadedregions/scheduler/FoliaEntityScheduler.java -new file mode 100644 -index 0000000000000000000000000000000000000000..011754962896e32f51ed4606dcbea18a430a2bc1 ---- /dev/null -+++ b/src/main/java/io/papermc/paper/threadedregions/scheduler/FoliaEntityScheduler.java -@@ -0,0 +1,268 @@ -+package io.papermc.paper.threadedregions.scheduler; -+ -+import ca.spottedleaf.concurrentutil.util.ConcurrentUtil; -+import ca.spottedleaf.concurrentutil.util.Validate; -+import net.minecraft.world.entity.Entity; -+import org.bukkit.craftbukkit.entity.CraftEntity; -+import org.bukkit.plugin.IllegalPluginAccessException; -+import org.bukkit.plugin.Plugin; -+import org.jetbrains.annotations.Nullable; -+ -+import java.lang.invoke.VarHandle; -+import java.util.function.Consumer; -+import java.util.logging.Level; -+ -+public final class FoliaEntityScheduler implements EntityScheduler { -+ -+ private final CraftEntity entity; -+ -+ public FoliaEntityScheduler(final CraftEntity entity) { -+ this.entity = entity; -+ } -+ -+ private static Consumer wrap(final Plugin plugin, final Runnable runnable) { -+ Validate.notNull(plugin, "Plugin may not be null"); -+ Validate.notNull(runnable, "Runnable may not be null"); -+ -+ return (final Entity nmsEntity) -> { -+ if (!plugin.isEnabled()) { -+ // don't execute if the plugin is disabled -+ return; -+ } -+ try { -+ runnable.run(); -+ } catch (final Throwable throwable) { -+ plugin.getLogger().log(Level.WARNING, "Entity task for " + plugin.getDescription().getFullName() + " generated an exception", throwable); -+ } -+ }; -+ } -+ -+ @Override -+ public boolean execute(final Plugin plugin, final Runnable run, final Runnable retired, -+ final long delay) { -+ final Consumer runNMS = wrap(plugin, run); -+ final Consumer runRetired = retired == null ? null : wrap(plugin, retired); -+ -+ return this.entity.taskScheduler.schedule(runNMS, runRetired, delay); -+ } -+ -+ @Override -+ public @Nullable ScheduledTask run(final Plugin plugin, final Consumer task, final Runnable retired) { -+ return this.runDelayed(plugin, task, retired, 1); -+ } -+ -+ @Override -+ public @Nullable ScheduledTask runDelayed(final Plugin plugin, final Consumer task, final Runnable retired, -+ final long delayTicks) { -+ Validate.notNull(plugin, "Plugin may not be null"); -+ Validate.notNull(task, "Task may not be null"); -+ if (delayTicks <= 0) { -+ throw new IllegalArgumentException("Delay ticks may not be <= 0"); -+ } -+ -+ if (!plugin.isEnabled()) { -+ throw new IllegalPluginAccessException("Plugin attempted to register task while disabled"); -+ } -+ -+ final EntityScheduledTask ret = new EntityScheduledTask(plugin, -1, task, retired); -+ -+ if (!this.scheduleInternal(ret, delayTicks)) { -+ return null; -+ } -+ -+ if (!plugin.isEnabled()) { -+ // handle race condition where plugin is disabled asynchronously -+ ret.cancel(); -+ } -+ -+ return ret; -+ } -+ -+ @Override -+ public @Nullable ScheduledTask runAtFixedRate(final Plugin plugin, final Consumer task, -+ final Runnable retired, final long initialDelayTicks, final long periodTicks) { -+ Validate.notNull(plugin, "Plugin may not be null"); -+ Validate.notNull(task, "Task may not be null"); -+ if (initialDelayTicks <= 0) { -+ throw new IllegalArgumentException("Initial delay ticks may not be <= 0"); -+ } -+ if (periodTicks <= 0) { -+ throw new IllegalArgumentException("Period ticks may not be <= 0"); -+ } -+ -+ if (!plugin.isEnabled()) { -+ throw new IllegalPluginAccessException("Plugin attempted to register task while disabled"); -+ } -+ -+ final EntityScheduledTask ret = new EntityScheduledTask(plugin, periodTicks, task, retired); -+ -+ if (!this.scheduleInternal(ret, initialDelayTicks)) { -+ return null; -+ } -+ -+ if (!plugin.isEnabled()) { -+ // handle race condition where plugin is disabled asynchronously -+ ret.cancel(); -+ } -+ -+ return ret; -+ } -+ -+ private boolean scheduleInternal(final EntityScheduledTask ret, final long delay) { -+ return this.entity.taskScheduler.schedule(ret, ret, delay); -+ } -+ -+ private final class EntityScheduledTask implements ScheduledTask, Consumer { -+ -+ private static final int STATE_IDLE = 0; -+ private static final int STATE_EXECUTING = 1; -+ private static final int STATE_EXECUTING_CANCELLED = 2; -+ private static final int STATE_FINISHED = 3; -+ private static final int STATE_CANCELLED = 4; -+ -+ private final Plugin plugin; -+ private final long repeatDelay; // in ticks -+ private Consumer run; -+ private Runnable retired; -+ private volatile int state; -+ -+ private static final VarHandle STATE_HANDLE = ConcurrentUtil.getVarHandle(EntityScheduledTask.class, "state", int.class); -+ -+ private EntityScheduledTask(final Plugin plugin, final long repeatDelay, final Consumer run, final Runnable retired) { -+ this.plugin = plugin; -+ this.repeatDelay = repeatDelay; -+ this.run = run; -+ this.retired = retired; -+ } -+ -+ private final int getStateVolatile() { -+ return (int)STATE_HANDLE.get(this); -+ } -+ -+ private final int compareAndExchangeStateVolatile(final int expect, final int update) { -+ return (int)STATE_HANDLE.compareAndExchange(this, expect, update); -+ } -+ -+ private final void setStateVolatile(final int value) { -+ STATE_HANDLE.setVolatile(this, value); -+ } -+ -+ @Override -+ public void accept(final Entity entity) { -+ if (!this.plugin.isEnabled()) { -+ // don't execute if the plugin is disabled -+ this.setStateVolatile(STATE_CANCELLED); -+ return; -+ } -+ -+ final boolean repeating = this.isRepeatingTask(); -+ if (STATE_IDLE != this.compareAndExchangeStateVolatile(STATE_IDLE, STATE_EXECUTING)) { -+ // cancelled -+ return; -+ } -+ -+ final boolean retired = entity.isRemoved(); -+ -+ try { -+ if (!retired) { -+ this.run.accept(this); -+ } else { -+ if (this.retired != null) { -+ this.retired.run(); -+ } -+ } -+ } catch (final Throwable throwable) { -+ this.plugin.getLogger().log(Level.WARNING, "Entity task for " + this.plugin.getDescription().getFullName() + " generated an exception", throwable); -+ } finally { -+ boolean reschedule = false; -+ if (!repeating && !retired) { -+ this.setStateVolatile(STATE_FINISHED); -+ } else if (retired || !this.plugin.isEnabled()) { -+ this.setStateVolatile(STATE_CANCELLED); -+ } else if (STATE_EXECUTING == this.compareAndExchangeStateVolatile(STATE_EXECUTING, STATE_IDLE)) { -+ reschedule = true; -+ } // else: cancelled repeating task -+ -+ if (!reschedule) { -+ this.run = null; -+ this.retired = null; -+ } else { -+ if (!FoliaEntityScheduler.this.scheduleInternal(this, this.repeatDelay)) { -+ // the task itself must have removed the entity, so in this case we need to mark as cancelled -+ this.setStateVolatile(STATE_CANCELLED); -+ } -+ } -+ } -+ } -+ -+ @Override -+ public Plugin getOwningPlugin() { -+ return this.plugin; -+ } -+ -+ @Override -+ public boolean isRepeatingTask() { -+ return this.repeatDelay > 0; -+ } -+ -+ @Override -+ public CancelledState cancel() { -+ for (int curr = this.getStateVolatile();;) { -+ switch (curr) { -+ case STATE_IDLE: { -+ if (STATE_IDLE == (curr = this.compareAndExchangeStateVolatile(STATE_IDLE, STATE_CANCELLED))) { -+ this.state = STATE_CANCELLED; -+ this.run = null; -+ this.retired = null; -+ return CancelledState.CANCELLED_BY_CALLER; -+ } -+ // try again -+ continue; -+ } -+ case STATE_EXECUTING: { -+ if (!this.isRepeatingTask()) { -+ return CancelledState.RUNNING; -+ } -+ if (STATE_EXECUTING == (curr = this.compareAndExchangeStateVolatile(STATE_EXECUTING, STATE_EXECUTING_CANCELLED))) { -+ return CancelledState.NEXT_RUNS_CANCELLED; -+ } -+ // try again -+ continue; -+ } -+ case STATE_EXECUTING_CANCELLED: { -+ return CancelledState.NEXT_RUNS_CANCELLED_ALREADY; -+ } -+ case STATE_FINISHED: { -+ return CancelledState.ALREADY_EXECUTED; -+ } -+ case STATE_CANCELLED: { -+ return CancelledState.CANCELLED_ALREADY; -+ } -+ default: { -+ throw new IllegalStateException("Unknown state: " + curr); -+ } -+ } -+ } -+ } -+ -+ @Override -+ public ExecutionState getExecutionState() { -+ final int state = this.getStateVolatile(); -+ switch (state) { -+ case STATE_IDLE: -+ return ExecutionState.IDLE; -+ case STATE_EXECUTING: -+ return ExecutionState.RUNNING; -+ case STATE_EXECUTING_CANCELLED: -+ return ExecutionState.CANCELLED_RUNNING; -+ case STATE_FINISHED: -+ return ExecutionState.FINISHED; -+ case STATE_CANCELLED: -+ return ExecutionState.CANCELLED; -+ default: { -+ throw new IllegalStateException("Unknown state: " + state); -+ } -+ } -+ } -+ } -+} -diff --git a/src/main/java/io/papermc/paper/threadedregions/scheduler/FoliaGlobalRegionScheduler.java b/src/main/java/io/papermc/paper/threadedregions/scheduler/FoliaGlobalRegionScheduler.java -new file mode 100644 -index 0000000000000000000000000000000000000000..d306f911757a4d556c82c0070d4837db87afc497 ---- /dev/null -+++ b/src/main/java/io/papermc/paper/threadedregions/scheduler/FoliaGlobalRegionScheduler.java -@@ -0,0 +1,267 @@ -+package io.papermc.paper.threadedregions.scheduler; -+ -+import ca.spottedleaf.concurrentutil.util.ConcurrentUtil; -+import ca.spottedleaf.concurrentutil.util.Validate; -+import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap; -+import org.bukkit.plugin.IllegalPluginAccessException; -+import org.bukkit.plugin.Plugin; -+ -+import java.lang.invoke.VarHandle; -+import java.util.ArrayList; -+import java.util.List; -+import java.util.function.Consumer; -+import java.util.logging.Level; -+ -+public class FoliaGlobalRegionScheduler implements GlobalRegionScheduler { -+ -+ private long tickCount = 0L; -+ private final Object stateLock = new Object(); -+ private final Long2ObjectOpenHashMap> tasksByDeadline = new Long2ObjectOpenHashMap<>(); -+ -+ public void tick() { -+ final List run; -+ synchronized (this.stateLock) { -+ ++this.tickCount; -+ if (this.tasksByDeadline.isEmpty()) { -+ run = null; -+ } else { -+ run = this.tasksByDeadline.remove(this.tickCount); -+ } -+ } -+ -+ if (run == null) { -+ return; -+ } -+ -+ for (int i = 0, len = run.size(); i < len; ++i) { -+ run.get(i).run(); -+ } -+ } -+ -+ @Override -+ public void execute(final Plugin plugin, final Runnable run) { -+ Validate.notNull(plugin, "Plugin may not be null"); -+ Validate.notNull(run, "Runnable may not be null"); -+ -+ this.run(plugin, (final ScheduledTask task) -> { -+ run.run(); -+ }); -+ } -+ -+ @Override -+ public ScheduledTask run(final Plugin plugin, final Consumer task) { -+ return this.runDelayed(plugin, task, 1); -+ } -+ -+ @Override -+ public ScheduledTask runDelayed(final Plugin plugin, final Consumer task, final long delayTicks) { -+ Validate.notNull(plugin, "Plugin may not be null"); -+ Validate.notNull(task, "Task may not be null"); -+ if (delayTicks <= 0) { -+ throw new IllegalArgumentException("Delay ticks may not be <= 0"); -+ } -+ -+ if (!plugin.isEnabled()) { -+ throw new IllegalPluginAccessException("Plugin attempted to register task while disabled"); -+ } -+ -+ final GlobalScheduledTask ret = new GlobalScheduledTask(plugin, -1, task); -+ -+ this.scheduleInternal(ret, delayTicks); -+ -+ if (!plugin.isEnabled()) { -+ // handle race condition where plugin is disabled asynchronously -+ ret.cancel(); -+ } -+ -+ return ret; -+ } -+ -+ @Override -+ public ScheduledTask runAtFixedRate(final Plugin plugin, final Consumer task, final long initialDelayTicks, final long periodTicks) { -+ Validate.notNull(plugin, "Plugin may not be null"); -+ Validate.notNull(task, "Task may not be null"); -+ if (initialDelayTicks <= 0) { -+ throw new IllegalArgumentException("Initial delay ticks may not be <= 0"); -+ } -+ if (periodTicks <= 0) { -+ throw new IllegalArgumentException("Period ticks may not be <= 0"); -+ } -+ -+ if (!plugin.isEnabled()) { -+ throw new IllegalPluginAccessException("Plugin attempted to register task while disabled"); -+ } -+ -+ final GlobalScheduledTask ret = new GlobalScheduledTask(plugin, periodTicks, task); -+ -+ this.scheduleInternal(ret, initialDelayTicks); -+ -+ if (!plugin.isEnabled()) { -+ // handle race condition where plugin is disabled asynchronously -+ ret.cancel(); -+ } -+ -+ return ret; -+ } -+ -+ @Override -+ public void cancelTasks(final Plugin plugin) { -+ Validate.notNull(plugin, "Plugin may not be null"); -+ -+ final List toCancel = new ArrayList<>(); -+ synchronized (this.stateLock) { -+ for (final List tasks : this.tasksByDeadline.values()) { -+ for (int i = 0, len = tasks.size(); i < len; ++i) { -+ final GlobalScheduledTask task = tasks.get(i); -+ if (task.plugin == plugin) { -+ toCancel.add(task); -+ } -+ } -+ } -+ } -+ -+ for (int i = 0, len = toCancel.size(); i < len; ++i) { -+ toCancel.get(i).cancel(); -+ } -+ } -+ -+ private void scheduleInternal(final GlobalScheduledTask task, final long delay) { -+ // note: delay > 0 -+ synchronized (this.stateLock) { -+ this.tasksByDeadline.computeIfAbsent(this.tickCount + delay, (final long keyInMap) -> { -+ return new ArrayList<>(); -+ }).add(task); -+ } -+ } -+ -+ private final class GlobalScheduledTask implements ScheduledTask, Runnable { -+ -+ private static final int STATE_IDLE = 0; -+ private static final int STATE_EXECUTING = 1; -+ private static final int STATE_EXECUTING_CANCELLED = 2; -+ private static final int STATE_FINISHED = 3; -+ private static final int STATE_CANCELLED = 4; -+ -+ private final Plugin plugin; -+ private final long repeatDelay; // in ticks -+ private Consumer run; -+ private volatile int state; -+ -+ private static final VarHandle STATE_HANDLE = ConcurrentUtil.getVarHandle(GlobalScheduledTask.class, "state", int.class); -+ -+ private GlobalScheduledTask(final Plugin plugin, final long repeatDelay, final Consumer run) { -+ this.plugin = plugin; -+ this.repeatDelay = repeatDelay; -+ this.run = run; -+ } -+ -+ private final int getStateVolatile() { -+ return (int)STATE_HANDLE.get(this); -+ } -+ -+ private final int compareAndExchangeStateVolatile(final int expect, final int update) { -+ return (int)STATE_HANDLE.compareAndExchange(this, expect, update); -+ } -+ -+ private final void setStateVolatile(final int value) { -+ STATE_HANDLE.setVolatile(this, value); -+ } -+ -+ @Override -+ public void run() { -+ final boolean repeating = this.isRepeatingTask(); -+ if (STATE_IDLE != this.compareAndExchangeStateVolatile(STATE_IDLE, STATE_EXECUTING)) { -+ // cancelled -+ return; -+ } -+ -+ try { -+ this.run.accept(this); -+ } catch (final Throwable throwable) { -+ this.plugin.getLogger().log(Level.WARNING, "Global task for " + this.plugin.getDescription().getFullName() + " generated an exception", throwable); -+ } finally { -+ boolean reschedule = false; -+ if (!repeating) { -+ this.setStateVolatile(STATE_FINISHED); -+ } else if (STATE_EXECUTING == this.compareAndExchangeStateVolatile(STATE_EXECUTING, STATE_IDLE)) { -+ reschedule = true; -+ } // else: cancelled repeating task -+ -+ if (!reschedule) { -+ this.run = null; -+ } else { -+ FoliaGlobalRegionScheduler.this.scheduleInternal(this, this.repeatDelay); -+ } -+ } -+ } -+ -+ @Override -+ public Plugin getOwningPlugin() { -+ return this.plugin; -+ } -+ -+ @Override -+ public boolean isRepeatingTask() { -+ return this.repeatDelay > 0; -+ } -+ -+ @Override -+ public CancelledState cancel() { -+ for (int curr = this.getStateVolatile();;) { -+ switch (curr) { -+ case STATE_IDLE: { -+ if (STATE_IDLE == (curr = this.compareAndExchangeStateVolatile(STATE_IDLE, STATE_CANCELLED))) { -+ this.state = STATE_CANCELLED; -+ this.run = null; -+ return CancelledState.CANCELLED_BY_CALLER; -+ } -+ // try again -+ continue; -+ } -+ case STATE_EXECUTING: { -+ if (!this.isRepeatingTask()) { -+ return CancelledState.RUNNING; -+ } -+ if (STATE_EXECUTING == (curr = this.compareAndExchangeStateVolatile(STATE_EXECUTING, STATE_EXECUTING_CANCELLED))) { -+ return CancelledState.NEXT_RUNS_CANCELLED; -+ } -+ // try again -+ continue; -+ } -+ case STATE_EXECUTING_CANCELLED: { -+ return CancelledState.NEXT_RUNS_CANCELLED_ALREADY; -+ } -+ case STATE_FINISHED: { -+ return CancelledState.ALREADY_EXECUTED; -+ } -+ case STATE_CANCELLED: { -+ return CancelledState.CANCELLED_ALREADY; -+ } -+ default: { -+ throw new IllegalStateException("Unknown state: " + curr); -+ } -+ } -+ } -+ } -+ -+ @Override -+ public ExecutionState getExecutionState() { -+ final int state = this.getStateVolatile(); -+ switch (state) { -+ case STATE_IDLE: -+ return ExecutionState.IDLE; -+ case STATE_EXECUTING: -+ return ExecutionState.RUNNING; -+ case STATE_EXECUTING_CANCELLED: -+ return ExecutionState.CANCELLED_RUNNING; -+ case STATE_FINISHED: -+ return ExecutionState.FINISHED; -+ case STATE_CANCELLED: -+ return ExecutionState.CANCELLED; -+ default: { -+ throw new IllegalStateException("Unknown state: " + state); -+ } -+ } -+ } -+ } -+} -diff --git a/src/main/java/net/minecraft/server/MinecraftServer.java b/src/main/java/net/minecraft/server/MinecraftServer.java -index 2cc22a9a5483c62cdf64870f5ce62b33018bef06..3ee0e2adf576e312c18da90adc46160ad85179fb 100644 ---- a/src/main/java/net/minecraft/server/MinecraftServer.java -+++ b/src/main/java/net/minecraft/server/MinecraftServer.java -@@ -1576,6 +1576,20 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop { -+ for (final Entity entity : level.getEntities().getAll()) { -+ if (entity.isRemoved()) { -+ continue; -+ } -+ final org.bukkit.craftbukkit.entity.CraftEntity bukkit = entity.getBukkitEntityRaw(); -+ if (bukkit != null) { -+ bukkit.taskScheduler.executeTick(); -+ } -+ } -+ }); -+ // Paper end - Folia scheduler API - io.papermc.paper.adventure.providers.ClickCallbackProviderImpl.CALLBACK_MANAGER.handleQueue(this.tickCount); // Paper - this.profiler.push("commandFunctions"); - MinecraftTimings.commandFunctionsTimer.startTiming(); // Spigot // Paper -diff --git a/src/main/java/net/minecraft/server/players/PlayerList.java b/src/main/java/net/minecraft/server/players/PlayerList.java -index bd67245393f512264db774e0b855db0ce925a3f4..e85b91036c2470b2f164a4641d1c07d27553a078 100644 ---- a/src/main/java/net/minecraft/server/players/PlayerList.java -+++ b/src/main/java/net/minecraft/server/players/PlayerList.java -@@ -640,6 +640,7 @@ public abstract class PlayerList { - - entityplayer.unRide(); - worldserver.removePlayerImmediately(entityplayer, Entity.RemovalReason.UNLOADED_WITH_PLAYER); -+ entityplayer.retireScheduler(); // Paper - Folia schedulers - entityplayer.getAdvancements().stopListening(); - this.players.remove(entityplayer); - this.playersByName.remove(entityplayer.getScoreboardName().toLowerCase(java.util.Locale.ROOT)); // Spigot -diff --git a/src/main/java/net/minecraft/world/entity/Entity.java b/src/main/java/net/minecraft/world/entity/Entity.java -index 37f37df8b4185c92c13e43eb89d19514e360059e..33ac5c816c98c2dfa99898279ed74d42a92646cf 100644 ---- a/src/main/java/net/minecraft/world/entity/Entity.java -+++ b/src/main/java/net/minecraft/world/entity/Entity.java -@@ -254,11 +254,23 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess - - public CraftEntity getBukkitEntity() { - if (this.bukkitEntity == null) { -- this.bukkitEntity = CraftEntity.getEntity(this.level.getCraftServer(), this); -+ // Paper start - Folia schedulers -+ synchronized (this) { -+ if (this.bukkitEntity == null) { -+ return this.bukkitEntity = CraftEntity.getEntity(this.level.getCraftServer(), this); -+ } -+ } -+ // Paper end - Folia schedulers - } - return this.bukkitEntity; - } - -+ // Paper start -+ public CraftEntity getBukkitEntityRaw() { -+ return this.bukkitEntity; -+ } -+ // Paper end -+ - @Override - public CommandSender getBukkitSender(CommandSourceStack wrapper) { - return this.getBukkitEntity(); -@@ -4486,6 +4498,7 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess - public final void setRemoved(Entity.RemovalReason entity_removalreason, EntityRemoveEvent.Cause cause) { - CraftEventFactory.callEntityRemoveEvent(this, cause); - // CraftBukkit end -+ final boolean alreadyRemoved = this.removalReason != null; // Paper - Folia schedulers - if (this.removalReason == null) { - this.removalReason = entity_removalreason; - } -@@ -4496,12 +4509,28 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess - - this.getPassengers().forEach(Entity::stopRiding); - this.levelCallback.onRemove(entity_removalreason); -+ // Paper start - Folia schedulers -+ if (!(this instanceof ServerPlayer) && entity_removalreason != RemovalReason.CHANGED_DIMENSION && !alreadyRemoved) { -+ // Players need to be special cased, because they are regularly removed from the world -+ this.retireScheduler(); -+ } -+ // Paper end - Folia schedulers - } - - public void unsetRemoved() { - this.removalReason = null; - } - -+ // Paper start - Folia schedulers -+ /** -+ * Invoked only when the entity is truly removed from the server, never to be added to any world. -+ */ -+ public final void retireScheduler() { -+ // we need to force create the bukkit entity so that the scheduler can be retired... -+ this.getBukkitEntity().taskScheduler.retire(); -+ } -+ // Paper end - Folia schedulers -+ - @Override - public void setLevelCallback(EntityInLevelCallback changeListener) { - this.levelCallback = changeListener; -diff --git a/src/main/java/org/bukkit/craftbukkit/CraftServer.java b/src/main/java/org/bukkit/craftbukkit/CraftServer.java -index 622fe949819ec80737da21305e1a1e0c46480a63..2dd20823769f1a3a2d028cd64d3af5989429d1ac 100644 ---- a/src/main/java/org/bukkit/craftbukkit/CraftServer.java -+++ b/src/main/java/org/bukkit/craftbukkit/CraftServer.java -@@ -310,6 +310,76 @@ public final class CraftServer implements Server { - private final io.papermc.paper.logging.SysoutCatcher sysoutCatcher = new io.papermc.paper.logging.SysoutCatcher(); // Paper - private final io.papermc.paper.potion.PaperPotionBrewer potionBrewer; // Paper - Custom Potion Mixes - -+ // Paper start - Folia region threading API -+ private final io.papermc.paper.threadedregions.scheduler.FallbackRegionScheduler regionizedScheduler = new io.papermc.paper.threadedregions.scheduler.FallbackRegionScheduler(); -+ private final io.papermc.paper.threadedregions.scheduler.FoliaAsyncScheduler asyncScheduler = new io.papermc.paper.threadedregions.scheduler.FoliaAsyncScheduler(); -+ private final io.papermc.paper.threadedregions.scheduler.FoliaGlobalRegionScheduler globalRegionScheduler = new io.papermc.paper.threadedregions.scheduler.FoliaGlobalRegionScheduler(); -+ -+ @Override -+ public final io.papermc.paper.threadedregions.scheduler.RegionScheduler getRegionScheduler() { -+ return this.regionizedScheduler; -+ } -+ -+ @Override -+ public final io.papermc.paper.threadedregions.scheduler.AsyncScheduler getAsyncScheduler() { -+ return this.asyncScheduler; -+ } -+ -+ @Override -+ public final io.papermc.paper.threadedregions.scheduler.FoliaGlobalRegionScheduler getGlobalRegionScheduler() { -+ return this.globalRegionScheduler; -+ } -+ -+ @Override -+ public final boolean isOwnedByCurrentRegion(World world, io.papermc.paper.math.Position position) { -+ return ca.spottedleaf.moonrise.common.util.TickThread.isTickThreadFor( -+ ((CraftWorld) world).getHandle(), position.blockX() >> 4, position.blockZ() >> 4 -+ ); -+ } -+ -+ @Override -+ public final boolean isOwnedByCurrentRegion(World world, io.papermc.paper.math.Position position, int squareRadiusChunks) { -+ return ca.spottedleaf.moonrise.common.util.TickThread.isTickThreadFor( -+ ((CraftWorld) world).getHandle(), position.blockX() >> 4, position.blockZ() >> 4, squareRadiusChunks -+ ); -+ } -+ -+ @Override -+ public final boolean isOwnedByCurrentRegion(Location location) { -+ World world = location.getWorld(); -+ return ca.spottedleaf.moonrise.common.util.TickThread.isTickThreadFor( -+ ((CraftWorld) world).getHandle(), location.getBlockX() >> 4, location.getBlockZ() >> 4 -+ ); -+ } -+ -+ @Override -+ public final boolean isOwnedByCurrentRegion(Location location, int squareRadiusChunks) { -+ World world = location.getWorld(); -+ return ca.spottedleaf.moonrise.common.util.TickThread.isTickThreadFor( -+ ((CraftWorld) world).getHandle(), location.getBlockX() >> 4, location.getBlockZ() >> 4, squareRadiusChunks -+ ); -+ } -+ -+ @Override -+ public final boolean isOwnedByCurrentRegion(World world, int chunkX, int chunkZ) { -+ return ca.spottedleaf.moonrise.common.util.TickThread.isTickThreadFor( -+ ((CraftWorld) world).getHandle(), chunkX, chunkZ -+ ); -+ } -+ -+ @Override -+ public final boolean isOwnedByCurrentRegion(World world, int chunkX, int chunkZ, int squareRadiusChunks) { -+ return ca.spottedleaf.moonrise.common.util.TickThread.isTickThreadFor( -+ ((CraftWorld) world).getHandle(), chunkX, chunkZ, squareRadiusChunks -+ ); -+ } -+ -+ @Override -+ public final boolean isOwnedByCurrentRegion(Entity entity) { -+ return ca.spottedleaf.moonrise.common.util.TickThread.isTickThreadFor(((org.bukkit.craftbukkit.entity.CraftEntity) entity).getHandleRaw()); -+ } -+ // Paper end - Folia reagion threading API -+ - static { - ConfigurationSerialization.registerClass(CraftOfflinePlayer.class); - ConfigurationSerialization.registerClass(CraftPlayerProfile.class); -diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java -index 7c7501b4b21530d0641774f64e87d7d1ca71a33c..d3ff8015b2f713451b0aeb50e1b4cf81f2bcb7bc 100644 ---- a/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java -+++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java -@@ -70,6 +70,15 @@ public abstract class CraftEntity implements org.bukkit.entity.Entity { - 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); -+ -+ @Override -+ public final io.papermc.paper.threadedregions.scheduler.EntityScheduler getScheduler() { -+ return this.apiScheduler; -+ }; -+ // Paper end - Folia schedulers - - public CraftEntity(final CraftServer server, final Entity entity) { - this.server = server; -@@ -486,6 +495,12 @@ public abstract class CraftEntity implements org.bukkit.entity.Entity { - return this.entity; - } - -+ // Paper start -+ public Entity getHandleRaw() { -+ return this.entity; -+ } -+ // Paper end -+ - @Override - public final EntityType getType() { - return this.entityType; diff --git a/patches/server/0844-API-for-updating-recipes-on-clients.patch b/patches/server/0844-API-for-updating-recipes-on-clients.patch new file mode 100644 index 000000000000..bda5ca5713ff --- /dev/null +++ b/patches/server/0844-API-for-updating-recipes-on-clients.patch @@ -0,0 +1,112 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Jake Potrebic +Date: Sat, 21 Aug 2021 17:25:38 -0700 +Subject: [PATCH] API for updating recipes on clients + + +diff --git a/src/main/java/net/minecraft/server/players/PlayerList.java b/src/main/java/net/minecraft/server/players/PlayerList.java +index 4ac0bb0ec3222ebdd3fa386696dcb0723dc162a4..d92fb522d88a790d9cea2e6c4edad30bb73298fc 100644 +--- a/src/main/java/net/minecraft/server/players/PlayerList.java ++++ b/src/main/java/net/minecraft/server/players/PlayerList.java +@@ -1448,6 +1448,13 @@ public abstract class PlayerList { + } + + public void reloadResources() { ++ // Paper start - API for updating recipes on clients ++ this.reloadAdvancementData(); ++ this.reloadTagData(); ++ this.reloadRecipeData(); ++ } ++ public void reloadAdvancementData() { ++ // Paper end - API for updating recipes on clients + // CraftBukkit start + /*Iterator iterator = this.advancements.values().iterator(); + +@@ -1463,7 +1470,13 @@ public abstract class PlayerList { + } + // CraftBukkit end + ++ // Paper start - API for updating recipes on clients ++ } ++ public void reloadTagData() { + this.broadcastAll(new ClientboundUpdateTagsPacket(TagNetworkSerialization.serializeTagsToNetwork(this.registries))); ++ } ++ public void reloadRecipeData() { ++ // Paper end - API for updating recipes on clients + RecipeManager craftingmanager = this.server.getRecipeManager(); + ClientboundUpdateRecipesPacket packetplayoutrecipeupdate = new ClientboundUpdateRecipesPacket(craftingmanager.getSynchronizedItemProperties(), craftingmanager.getSynchronizedStonecutterRecipes()); + Iterator iterator1 = this.players.iterator(); +diff --git a/src/main/java/org/bukkit/craftbukkit/CraftServer.java b/src/main/java/org/bukkit/craftbukkit/CraftServer.java +index da3819f49b105bd98ce94b5abdf1c7652ff625a4..fa505c0714bb95b2ab08b4bbb9ea79ce98898f4b 100644 +--- a/src/main/java/org/bukkit/craftbukkit/CraftServer.java ++++ b/src/main/java/org/bukkit/craftbukkit/CraftServer.java +@@ -1178,6 +1178,18 @@ public final class CraftServer implements Server { + ReloadCommand.reload(this.console); + } + ++ // Paper start - API for updating recipes on clients ++ @Override ++ public void updateResources() { ++ this.playerList.reloadResources(); ++ } ++ ++ @Override ++ public void updateRecipes() { ++ this.playerList.reloadRecipeData(); ++ } ++ // Paper end - API for updating recipes on clients ++ + private void loadIcon() { + this.icon = new CraftIconCache(null); + try { +@@ -1557,6 +1569,13 @@ public final class CraftServer implements Server { + + @Override + public boolean addRecipe(Recipe recipe) { ++ // Paper start - API for updating recipes on clients ++ return this.addRecipe(recipe, false); ++ } ++ ++ @Override ++ public boolean addRecipe(Recipe recipe, boolean resendRecipes) { ++ // Paper end - API for updating recipes on clients + CraftRecipe toAdd; + if (recipe instanceof CraftRecipe) { + toAdd = (CraftRecipe) recipe; +@@ -1588,6 +1607,11 @@ public final class CraftServer implements Server { + } + } + toAdd.addToCraftingManager(); ++ // Paper start - API for updating recipes on clients ++ if (true || resendRecipes) { // Always needs to be resent now... TODO ++ this.playerList.reloadRecipeData(); ++ } ++ // Paper end - API for updating recipes on clients + return true; + } + +@@ -1768,9 +1792,23 @@ public final class CraftServer implements Server { + + @Override + public boolean removeRecipe(NamespacedKey recipeKey) { ++ // Paper start - API for updating recipes on clients ++ return this.removeRecipe(recipeKey, false); ++ } ++ ++ @Override ++ public boolean removeRecipe(NamespacedKey recipeKey, boolean resendRecipes) { ++ // Paper end - API for updating recipes on clients + Preconditions.checkArgument(recipeKey != null, "recipeKey == null"); + +- return this.getServer().getRecipeManager().removeRecipe(CraftRecipe.toMinecraft(recipeKey)); ++ // Paper start - resend recipes on successful removal ++ final ResourceKey> minecraftKey = CraftRecipe.toMinecraft(recipeKey); ++ final boolean removed = this.getServer().getRecipeManager().removeRecipe(minecraftKey); ++ if (removed/* && resendRecipes*/) { // TODO Always need to resend them rn - deprecate this method? ++ this.playerList.reloadRecipeData(); ++ } ++ return removed; ++ // Paper end - resend recipes on successful removal + } + + @Override diff --git a/patches/server/0844-Only-erase-allay-memory-on-non-item-targets.patch b/patches/server/0844-Only-erase-allay-memory-on-non-item-targets.patch deleted file mode 100644 index 4afac36fc329..000000000000 --- a/patches/server/0844-Only-erase-allay-memory-on-non-item-targets.patch +++ /dev/null @@ -1,29 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Bjarne Koll -Date: Fri, 4 Aug 2023 15:53:36 +0200 -Subject: [PATCH] Only erase allay memory on non-item targets - -Spigot incorrectly instanceOf checks the EntityTargetEvent#getTarget -against the internal ItemEntity type and removes the nearest wanted item -memory if said instanceOf check fails, (which is always the case) -causing allays to behave differently as they constantly loose their -target item. - -This commit fixes the faulty behaviour by instance performing a check -against the CraftItem type. - -diff --git a/src/main/java/net/minecraft/world/entity/ai/behavior/GoToWantedItem.java b/src/main/java/net/minecraft/world/entity/ai/behavior/GoToWantedItem.java -index bd9aa4a5443da862be3403c1941113373741a87c..2f92eb39cde7b30a894db347ca85a506d880411e 100644 ---- a/src/main/java/net/minecraft/world/entity/ai/behavior/GoToWantedItem.java -+++ b/src/main/java/net/minecraft/world/entity/ai/behavior/GoToWantedItem.java -@@ -35,8 +35,9 @@ public class GoToWantedItem { - if (event.isCancelled()) { - return false; - } -- if (!(event.getTarget() instanceof ItemEntity)) { -+ if (!(event.getTarget() instanceof org.bukkit.craftbukkit.entity.CraftItem)) { // Paper - only erase allay memory on non-item targets - memoryaccessor2.erase(); -+ return false; // Paper - only erase allay memory on non-item targets - } - - entityitem = (ItemEntity) ((org.bukkit.craftbukkit.entity.CraftEntity) event.getTarget()).getHandle(); diff --git a/patches/server/0845-API-for-updating-recipes-on-clients.patch b/patches/server/0845-API-for-updating-recipes-on-clients.patch deleted file mode 100644 index 7cae85735941..000000000000 --- a/patches/server/0845-API-for-updating-recipes-on-clients.patch +++ /dev/null @@ -1,114 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Jake Potrebic -Date: Sat, 21 Aug 2021 17:25:38 -0700 -Subject: [PATCH] API for updating recipes on clients - - -diff --git a/src/main/java/net/minecraft/server/players/PlayerList.java b/src/main/java/net/minecraft/server/players/PlayerList.java -index 2912b15ccda373cf52cec020b0e06ac2c5cf2950..a6caf3a0df22f124a4ee1cfb3981bbeb23a8630e 100644 ---- a/src/main/java/net/minecraft/server/players/PlayerList.java -+++ b/src/main/java/net/minecraft/server/players/PlayerList.java -@@ -1490,6 +1490,13 @@ public abstract class PlayerList { - } - - public void reloadResources() { -+ // Paper start - API for updating recipes on clients -+ this.reloadAdvancementData(); -+ this.reloadTagData(); -+ this.reloadRecipeData(); -+ } -+ public void reloadAdvancementData() { -+ // Paper end - API for updating recipes on clients - // CraftBukkit start - /*Iterator iterator = this.advancements.values().iterator(); - -@@ -1505,7 +1512,15 @@ public abstract class PlayerList { - } - // CraftBukkit end - -+ // Paper start - API for updating recipes on clients -+ } -+ public void reloadTagData() { -+ // Paper end - API for updating recipes on clients - this.broadcastAll(new ClientboundUpdateTagsPacket(TagNetworkSerialization.serializeTagsToNetwork(this.registries))); -+ // Paper start - API for updating recipes on clients -+ } -+ public void reloadRecipeData() { -+ // Paper end - API for updating recipes on clients - ClientboundUpdateRecipesPacket packetplayoutrecipeupdate = new ClientboundUpdateRecipesPacket(this.server.getRecipeManager().getOrderedRecipes()); - Iterator iterator1 = this.players.iterator(); - -diff --git a/src/main/java/org/bukkit/craftbukkit/CraftServer.java b/src/main/java/org/bukkit/craftbukkit/CraftServer.java -index bb89247a87067a74d793a1acc1eb95b98ace3d9e..75c222e592d676e98b293767d00de54a61411ae7 100644 ---- a/src/main/java/org/bukkit/craftbukkit/CraftServer.java -+++ b/src/main/java/org/bukkit/craftbukkit/CraftServer.java -@@ -1176,6 +1176,18 @@ public final class CraftServer implements Server { - ReloadCommand.reload(this.console); - } - -+ // Paper start - API for updating recipes on clients -+ @Override -+ public void updateResources() { -+ this.playerList.reloadResources(); -+ } -+ -+ @Override -+ public void updateRecipes() { -+ this.playerList.reloadRecipeData(); -+ } -+ // Paper end - API for updating recipes on clients -+ - private void loadIcon() { - this.icon = new CraftIconCache(null); - try { -@@ -1555,6 +1567,13 @@ public final class CraftServer implements Server { - - @Override - public boolean addRecipe(Recipe recipe) { -+ // Paper start - API for updating recipes on clients -+ return this.addRecipe(recipe, false); -+ } -+ -+ @Override -+ public boolean addRecipe(Recipe recipe, boolean resendRecipes) { -+ // Paper end - API for updating recipes on clients - CraftRecipe toAdd; - if (recipe instanceof CraftRecipe) { - toAdd = (CraftRecipe) recipe; -@@ -1584,6 +1603,11 @@ public final class CraftServer implements Server { - } - } - toAdd.addToCraftingManager(); -+ // Paper start - API for updating recipes on clients -+ if (resendRecipes) { -+ this.playerList.reloadRecipeData(); -+ } -+ // Paper end - API for updating recipes on clients - return true; - } - -@@ -1764,10 +1788,23 @@ public final class CraftServer implements Server { - - @Override - public boolean removeRecipe(NamespacedKey recipeKey) { -+ // Paper start - API for updating recipes on clients -+ return this.removeRecipe(recipeKey, false); -+ } -+ -+ @Override -+ public boolean removeRecipe(NamespacedKey recipeKey, boolean resendRecipes) { -+ // Paper end - API for updating recipes on clients - Preconditions.checkArgument(recipeKey != null, "recipeKey == null"); - - ResourceLocation mcKey = CraftNamespacedKey.toMinecraft(recipeKey); -- return this.getServer().getRecipeManager().removeRecipe(mcKey); -+ // Paper start - resend recipes on successful removal -+ boolean removed = this.getServer().getRecipeManager().removeRecipe(mcKey); -+ if (removed && resendRecipes) { -+ this.playerList.reloadRecipeData(); -+ } -+ return removed; -+ // Paper end - } - - @Override diff --git a/patches/server/0845-Fix-custom-statistic-criteria-creation.patch b/patches/server/0845-Fix-custom-statistic-criteria-creation.patch new file mode 100644 index 000000000000..c57ac9625590 --- /dev/null +++ b/patches/server/0845-Fix-custom-statistic-criteria-creation.patch @@ -0,0 +1,23 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Noah van der Aa +Date: Sat, 12 Aug 2023 15:33:49 +0200 +Subject: [PATCH] Fix custom statistic criteria creation + + +diff --git a/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java b/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java +index 9f4124485dac3d029ec8247b64098042aa1a48d2..a74784ddf63d316f253381ed803822a149e92bc7 100644 +--- a/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java ++++ b/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java +@@ -600,6 +600,12 @@ public final class CraftMagicNumbers implements UnsafeValues { + net.minecraft.core.Holder biomeBase = cra.getHandle().registryAccess().lookupOrThrow(net.minecraft.core.registries.Registries.BIOME).getOrThrow(net.minecraft.resources.ResourceKey.create(net.minecraft.core.registries.Registries.BIOME, org.bukkit.craftbukkit.util.CraftNamespacedKey.toMinecraft(biomeKey))); + cra.setBiome(x, y, z, biomeBase); + } ++ ++ @Override ++ public String getStatisticCriteriaKey(org.bukkit.Statistic statistic) { ++ if (statistic.getType() != org.bukkit.Statistic.Type.UNTYPED) return "minecraft.custom:minecraft." + statistic.getKey().getKey(); ++ return org.bukkit.craftbukkit.CraftStatistic.getNMSStatistic(statistic).getName(); ++ } + // Paper end + + /** diff --git a/patches/server/0846-Bandaid-fix-for-Effect.patch b/patches/server/0846-Bandaid-fix-for-Effect.patch new file mode 100644 index 000000000000..9d00be010b3d --- /dev/null +++ b/patches/server/0846-Bandaid-fix-for-Effect.patch @@ -0,0 +1,179 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Jake Potrebic +Date: Fri, 28 Jul 2023 15:02:44 -0700 +Subject: [PATCH] Bandaid fix for Effect + +Effect or LevelEvent needs to be replaced +but ideally after the enum PR has been merged +upstream. Until then, this test and these fixes +should address all the known issues with them + +diff --git a/src/main/java/org/bukkit/craftbukkit/CraftEffect.java b/src/main/java/org/bukkit/craftbukkit/CraftEffect.java +index 71733f918ed84b9879ac1b142ef6205c5e768a9c..c856384019eff2f2d0bb831ebe1ccb0fb9210782 100644 +--- a/src/main/java/org/bukkit/craftbukkit/CraftEffect.java ++++ b/src/main/java/org/bukkit/craftbukkit/CraftEffect.java +@@ -15,6 +15,14 @@ public class CraftEffect { + public static int getDataValue(Effect effect, T data) { + int datavalue; + switch (effect) { ++ // Paper start - add missing effects ++ case PARTICLES_SCULK_CHARGE: ++ case TRIAL_SPAWNER_DETECT_PLAYER: ++ case BEE_GROWTH: ++ case TURTLE_EGG_PLACEMENT: ++ case SMASH_ATTACK: ++ case TRIAL_SPAWNER_DETECT_PLAYER_OMINOUS: ++ // Paper end - add missing effects + case VILLAGER_PLANT_GROW: + datavalue = (Integer) data; + break; +@@ -26,6 +34,13 @@ public class CraftEffect { + Preconditions.checkArgument(data == Material.AIR || ((Material) data).isRecord(), "Invalid record type for Material %s!", data); + datavalue = Item.getId(CraftItemType.bukkitToMinecraft((Material) data)); + break; ++ // Paper start - handle shoot white smoke event ++ case SHOOT_WHITE_SMOKE: ++ final BlockFace face = (BlockFace) data; ++ Preconditions.checkArgument(face.isCartesian(), face + " isn't cartesian"); ++ datavalue = org.bukkit.craftbukkit.block.CraftBlock.blockFaceToNotch(face).get3DDataValue(); ++ break; ++ // Paper end - handle shoot white smoke event + case SMOKE: + switch ((BlockFace) data) { + case DOWN: +@@ -57,10 +72,25 @@ public class CraftEffect { + } + break; + case STEP_SOUND: ++ if (data instanceof Material) { // Paper - support BlockData + Preconditions.checkArgument(((Material) data).isBlock(), "Material %s is not a block!", data); + datavalue = Block.getId(CraftBlockType.bukkitToMinecraft((Material) data).defaultBlockState()); ++ // Paper start - support BlockData ++ break; ++ } ++ case PARTICLES_AND_SOUND_BRUSH_BLOCK_COMPLETE: ++ datavalue = Block.getId(((org.bukkit.craftbukkit.block.data.CraftBlockData) data).getState()); ++ // Paper end + break; + case COMPOSTER_FILL_ATTEMPT: ++ // Paper start - add missing effects ++ case TRIAL_SPAWNER_SPAWN: ++ case TRIAL_SPAWNER_SPAWN_MOB_AT: ++ case VAULT_ACTIVATE: ++ case VAULT_DEACTIVATE: ++ case TRIAL_SPAWNER_BECOME_OMINOUS: ++ case TRIAL_SPAWNER_SPAWN_ITEM: ++ // Paper end - add missing effects + datavalue = ((Boolean) data) ? 1 : 0; + break; + case BONE_MEAL_USE: +diff --git a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java +index c0f5e4497e1ffc93f56fc2b5748bcf76569e8c3a..188bd33f46b6baaa3fc21c9da6fa9a9d004e899e 100644 +--- a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java ++++ b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java +@@ -1382,7 +1382,7 @@ public class CraftWorld extends CraftRegionAccessor implements World { + public void playEffect(Location loc, Effect effect, T data, int radius) { + if (data != null) { + Preconditions.checkArgument(effect.getData() != null, "Effect.%s does not have a valid Data", effect); +- Preconditions.checkArgument(effect.getData().isAssignableFrom(data.getClass()), "%s data cannot be used for the %s effect", data.getClass().getName(), effect); ++ Preconditions.checkArgument(effect.isApplicable(data), "%s data cannot be used for the %s effect", data.getClass().getName(), effect); // Paper + } else { + // Special case: the axis is optional for ELECTRIC_SPARK + Preconditions.checkArgument(effect.getData() == null || effect == Effect.ELECTRIC_SPARK, "Wrong kind of data for the %s effect", effect); +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java +index 3ddf3b893605f5763b1f685ad951b1ccdd4a44ce..abb0370243ad95260c5c6034e20097a09415fe00 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java +@@ -934,7 +934,7 @@ public class CraftPlayer extends CraftHumanEntity implements Player { + Preconditions.checkArgument(effect != null, "Effect cannot be null"); + if (data != null) { + Preconditions.checkArgument(effect.getData() != null, "Effect.%s does not have a valid Data", effect); +- Preconditions.checkArgument(effect.getData().isAssignableFrom(data.getClass()), "%s data cannot be used for the %s effect", data.getClass().getName(), effect); ++ Preconditions.checkArgument(effect.isApplicable(data), "%s data cannot be used for the %s effect", data.getClass().getName(), effect); // Paper + } else { + // Special case: the axis is optional for ELECTRIC_SPARK + Preconditions.checkArgument(effect.getData() == null || effect == Effect.ELECTRIC_SPARK, "Wrong kind of data for the %s effect", effect); +diff --git a/src/test/java/org/bukkit/EffectTest.java b/src/test/java/org/bukkit/EffectTest.java +new file mode 100644 +index 0000000000000000000000000000000000000000..3129212415c151aa028995b6e698f81b638e4c35 +--- /dev/null ++++ b/src/test/java/org/bukkit/EffectTest.java +@@ -0,0 +1,78 @@ ++package org.bukkit; ++ ++import com.google.common.base.Joiner; ++import java.lang.reflect.Field; ++import java.lang.reflect.Modifier; ++import java.util.ArrayList; ++import java.util.HashMap; ++import java.util.HashSet; ++import java.util.List; ++import java.util.Map; ++import java.util.Set; ++import net.minecraft.world.level.block.LevelEvent; ++import org.junit.jupiter.api.Test; ++ ++import static org.junit.jupiter.api.Assertions.assertNotNull; ++import static org.junit.jupiter.api.Assertions.assertNull; ++import static org.junit.jupiter.api.Assertions.assertTrue; ++import static org.junit.jupiter.api.Assertions.fail; ++ ++public class EffectTest { ++ ++ private static List collectNmsLevelEvents() throws ReflectiveOperationException { ++ final List events = new ArrayList<>(); ++ for (final Field field : LevelEvent.class.getFields()) { ++ if (Modifier.isStatic(field.getModifiers()) && Modifier.isFinal(field.getModifiers()) && field.getType() == int.class) { ++ events.add((int) field.get(null)); ++ } ++ } ++ return events; ++ } ++ ++ private static boolean isNotDeprecated(Effect effect) throws ReflectiveOperationException { ++ return !Effect.class.getDeclaredField(effect.name()).isAnnotationPresent(Deprecated.class); ++ } ++ ++ @Test ++ public void checkAllApiExists() throws ReflectiveOperationException { ++ Map toId = new HashMap<>(); ++ for (final Effect effect : Effect.values()) { ++ if (isNotDeprecated(effect)) { ++ final Effect put = toId.put(effect.getId(), effect); ++ assertNull(put, "duplicate API effect: " + put); ++ } ++ } ++ ++ final Set missingEvents = new HashSet<>(); ++ for (final Integer event : collectNmsLevelEvents()) { ++ if (toId.get(event) == null) { ++ missingEvents.add(event); ++ } ++ } ++ if (!missingEvents.isEmpty()) { ++ fail("Missing API Effects:\n" + Joiner.on("\n").join(missingEvents)); ++ } ++ } ++ ++ @Test ++ public void checkNoExtraApi() throws ReflectiveOperationException { ++ Map toId = new HashMap<>(); ++ for (final Effect effect : Effect.values()) { ++ if (isNotDeprecated(effect)) { ++ final Effect put = toId.put(effect.getId(), effect); ++ assertNull(put, "duplicate API effect: " + put); ++ } ++ } ++ ++ final List nmsEvents = collectNmsLevelEvents(); ++ final Set extraApiEffects = new HashSet<>(); ++ for (final Map.Entry entry : toId.entrySet()) { ++ if (!nmsEvents.contains(entry.getKey())) { ++ extraApiEffects.add(entry.getValue()); ++ } ++ } ++ if (!extraApiEffects.isEmpty()) { ++ fail("Extra API Effects:\n" + Joiner.on("\n").join(extraApiEffects)); ++ } ++ } ++} diff --git a/patches/server/0846-Fix-rotation-when-spawning-display-entities.patch b/patches/server/0846-Fix-rotation-when-spawning-display-entities.patch deleted file mode 100644 index 7491ff60fed3..000000000000 --- a/patches/server/0846-Fix-rotation-when-spawning-display-entities.patch +++ /dev/null @@ -1,32 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Gameoholic -Date: Sun, 30 Jul 2023 13:30:34 +0300 -Subject: [PATCH] Fix rotation when spawning display entities - - -diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftEntityTypes.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftEntityTypes.java -index 577ed3656480271a491bcd3d346c63854fd840e4..39aa116193f313f53540b0135d2ab26acb7f1469 100644 ---- a/src/main/java/org/bukkit/craftbukkit/entity/CraftEntityTypes.java -+++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftEntityTypes.java -@@ -221,6 +221,7 @@ public final class CraftEntityTypes { - Vector direction = spawnData.location().getDirection(); - entity.assignDirectionalMovement(new Vec3(direction.getX(), direction.getY(), direction.getZ()), 1.0); - }; -+ private static final BiConsumer ROT = (spawnData, entity) -> entity.setRot(spawnData.yaw(), spawnData.pitch()); // Paper - private static final Map, EntityTypeData> CLASS_TYPE_DATA = new HashMap<>(); - private static final Map> ENTITY_TYPE_DATA = new HashMap<>(); - -@@ -361,10 +362,10 @@ public final class CraftEntityTypes { - - // Set pos - register(new EntityTypeData<>(EntityType.MARKER, Marker.class, CraftMarker::new, createAndSetPos(net.minecraft.world.entity.EntityType.MARKER))); -- register(new EntityTypeData<>(EntityType.BLOCK_DISPLAY, BlockDisplay.class, CraftBlockDisplay::new, createAndSetPos(net.minecraft.world.entity.EntityType.BLOCK_DISPLAY))); -+ register(new EntityTypeData<>(EntityType.BLOCK_DISPLAY, BlockDisplay.class, CraftBlockDisplay::new, combine(createAndSetPos(net.minecraft.world.entity.EntityType.BLOCK_DISPLAY), ROT))); // Paper - register(new EntityTypeData<>(EntityType.INTERACTION, Interaction.class, CraftInteraction::new, createAndSetPos(net.minecraft.world.entity.EntityType.INTERACTION))); -- register(new EntityTypeData<>(EntityType.ITEM_DISPLAY, ItemDisplay.class, CraftItemDisplay::new, createAndSetPos(net.minecraft.world.entity.EntityType.ITEM_DISPLAY))); -- register(new EntityTypeData<>(EntityType.TEXT_DISPLAY, TextDisplay.class, CraftTextDisplay::new, createAndSetPos(net.minecraft.world.entity.EntityType.TEXT_DISPLAY))); -+ register(new EntityTypeData<>(EntityType.ITEM_DISPLAY, ItemDisplay.class, CraftItemDisplay::new, combine(createAndSetPos(net.minecraft.world.entity.EntityType.ITEM_DISPLAY), ROT))); // Paper -+ register(new EntityTypeData<>(EntityType.TEXT_DISPLAY, TextDisplay.class, CraftTextDisplay::new, combine(createAndSetPos(net.minecraft.world.entity.EntityType.TEXT_DISPLAY), ROT))); // Paper - - // MISC - register(new EntityTypeData<>(EntityType.ITEM, Item.class, CraftItem::new, spawnData -> { diff --git a/patches/server/0847-Only-capture-actual-tree-growth.patch b/patches/server/0847-Only-capture-actual-tree-growth.patch deleted file mode 100644 index de9896609a0e..000000000000 --- a/patches/server/0847-Only-capture-actual-tree-growth.patch +++ /dev/null @@ -1,73 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Jake Potrebic -Date: Sat, 21 Aug 2021 18:53:03 -0700 -Subject: [PATCH] Only capture actual tree growth - - -diff --git a/src/main/java/net/minecraft/core/dispenser/DispenseItemBehavior.java b/src/main/java/net/minecraft/core/dispenser/DispenseItemBehavior.java -index 78951f50188528718cdb3dbbaabe3f9f2760ffe3..7826e2a52da47914aca39fef958b8f398a2ff937 100644 ---- a/src/main/java/net/minecraft/core/dispenser/DispenseItemBehavior.java -+++ b/src/main/java/net/minecraft/core/dispenser/DispenseItemBehavior.java -@@ -634,6 +634,7 @@ public interface DispenseItemBehavior { - if (!fertilizeEvent.isCancelled()) { - for (org.bukkit.block.BlockState blockstate : blocks) { - blockstate.update(true); -+ worldserver.checkCapturedTreeStateForObserverNotify(blockposition, (org.bukkit.craftbukkit.block.CraftBlockState) blockstate); // Paper - notify observers even if grow failed - } - } - } -diff --git a/src/main/java/net/minecraft/world/item/ItemStack.java b/src/main/java/net/minecraft/world/item/ItemStack.java -index 10b3e41d589d480046e15a957902c2751b731cec..87cae96faf33fa0491b13add79e02fc225bd2f70 100644 ---- a/src/main/java/net/minecraft/world/item/ItemStack.java -+++ b/src/main/java/net/minecraft/world/item/ItemStack.java -@@ -466,6 +466,7 @@ public final class ItemStack implements DataComponentHolder { - for (CraftBlockState blockstate : blocks) { - // SPIGOT-7572 - Move fix for SPIGOT-7248 to CapturedBlockState, to allow bees in bee nest - CapturedBlockState.setBlockState(blockstate); -+ world.checkCapturedTreeStateForObserverNotify(blockposition, blockstate); // Paper - notify observers even if grow failed - } - entityhuman.awardStat(Stats.ITEM_USED.get(item)); // SPIGOT-7236 - award stat - } -diff --git a/src/main/java/net/minecraft/world/level/Level.java b/src/main/java/net/minecraft/world/level/Level.java -index bd909b08b5e604778d35ba6514cf8c353d489228..c8a3e49f08fd0d110ffc3be763cfda4e62f55916 100644 ---- a/src/main/java/net/minecraft/world/level/Level.java -+++ b/src/main/java/net/minecraft/world/level/Level.java -@@ -1395,4 +1395,14 @@ public abstract class Level implements LevelAccessor, AutoCloseable { - return range <= 0 ? 64.0 * 64.0 : range * range; // 64 is taken from default in ServerLevel#levelEvent - } - // Paper end - respect global sound events gamerule -+ // Paper start - notify observers even if grow failed -+ public void checkCapturedTreeStateForObserverNotify(final BlockPos pos, final CraftBlockState craftBlockState) { -+ // notify observers if the block state is the same and the Y level equals the original y level (for mega trees) -+ // blocks at the same Y level with the same state can be assumed to be saplings which trigger observers regardless of if the -+ // tree grew or not -+ if (craftBlockState.getPosition().getY() == pos.getY() && this.getBlockState(craftBlockState.getPosition()) == craftBlockState.getHandle()) { -+ this.notifyAndUpdatePhysics(craftBlockState.getPosition(), null, craftBlockState.getHandle(), craftBlockState.getHandle(), craftBlockState.getHandle(), craftBlockState.getFlag(), 512); -+ } -+ } -+ // Paper end - notify observers even if grow failed - } -diff --git a/src/main/java/net/minecraft/world/level/block/SaplingBlock.java b/src/main/java/net/minecraft/world/level/block/SaplingBlock.java -index 2d7290ace5fc8890325a8ec623075ad32f9b1d44..d262a5a6da57ef9ba9a6fe0dfbc88f577105e74f 100644 ---- a/src/main/java/net/minecraft/world/level/block/SaplingBlock.java -+++ b/src/main/java/net/minecraft/world/level/block/SaplingBlock.java -@@ -86,6 +86,7 @@ public class SaplingBlock extends BushBlock implements BonemealableBlock { - if (event == null || !event.isCancelled()) { - for (BlockState blockstate : blocks) { - CapturedBlockState.setBlockState(blockstate); -+ world.checkCapturedTreeStateForObserverNotify(pos, (org.bukkit.craftbukkit.block.CraftBlockState) blockstate); // Paper - notify observers even if grow failed - } - } - } -diff --git a/src/main/java/org/bukkit/craftbukkit/block/CraftBlock.java b/src/main/java/org/bukkit/craftbukkit/block/CraftBlock.java -index af219df5267589300f0ad1d30fa5c81a1f50234f..461a66c323a74db5a70981fafc5fa20f54f0f40d 100644 ---- a/src/main/java/org/bukkit/craftbukkit/block/CraftBlock.java -+++ b/src/main/java/org/bukkit/craftbukkit/block/CraftBlock.java -@@ -572,6 +572,7 @@ public class CraftBlock implements Block { - if (!event.isCancelled()) { - for (BlockState blockstate : blocks) { - blockstate.update(true); -+ world.checkCapturedTreeStateForObserverNotify(this.position, (org.bukkit.craftbukkit.block.CraftBlockState) blockstate); // Paper - notify observers even if grow failed - } - } - } diff --git a/patches/server/0855-SculkCatalyst-bloom-API.patch b/patches/server/0847-SculkCatalyst-bloom-API.patch similarity index 100% rename from patches/server/0855-SculkCatalyst-bloom-API.patch rename to patches/server/0847-SculkCatalyst-bloom-API.patch diff --git a/patches/server/0848-API-for-an-entity-s-scoreboard-name.patch b/patches/server/0848-API-for-an-entity-s-scoreboard-name.patch new file mode 100644 index 000000000000..68f3c7789a89 --- /dev/null +++ b/patches/server/0848-API-for-an-entity-s-scoreboard-name.patch @@ -0,0 +1,24 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Jake Potrebic +Date: Sun, 9 Jul 2023 11:55:02 -0700 +Subject: [PATCH] API for an entity's scoreboard name + +Was obtainable through different methods, but you had to use different +methods depending on the implementation of Entity you were working with. + +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java +index df199c1cffa7795126ab83af67e184238ddb211d..dd84f7d6874a799aa09349fe0a1bd6ebb4cea2dd 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java +@@ -1264,4 +1264,11 @@ public abstract class CraftEntity implements org.bukkit.entity.Entity { + return !this.getHandle().level().noCollision(this.getHandle(), aabb); + } + // Paper end - Collision API ++ ++ // Paper start - entity scoreboard name ++ @Override ++ public String getScoreboardEntryName() { ++ return this.getHandle().getScoreboardName(); ++ } ++ // Paper end - entity scoreboard name + } diff --git a/patches/server/0848-Use-correct-source-for-mushroom-block-spread-event.patch b/patches/server/0848-Use-correct-source-for-mushroom-block-spread-event.patch deleted file mode 100644 index a295e29bcbb6..000000000000 --- a/patches/server/0848-Use-correct-source-for-mushroom-block-spread-event.patch +++ /dev/null @@ -1,27 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Warrior <50800980+Warriorrrr@users.noreply.github.com> -Date: Tue, 8 Aug 2023 11:49:32 +0200 -Subject: [PATCH] Use correct source for mushroom block spread event - - -diff --git a/src/main/java/net/minecraft/world/level/block/MushroomBlock.java b/src/main/java/net/minecraft/world/level/block/MushroomBlock.java -index f549cac99a665f20f1cd1d8c807ed3649fed7531..1172d85c5c26ab2142343d91149766e5993cb36a 100644 ---- a/src/main/java/net/minecraft/world/level/block/MushroomBlock.java -+++ b/src/main/java/net/minecraft/world/level/block/MushroomBlock.java -@@ -68,6 +68,7 @@ public class MushroomBlock extends BushBlock implements BonemealableBlock { - } - - BlockPos blockposition2 = pos.offset(random.nextInt(3) - 1, random.nextInt(2) - random.nextInt(2), random.nextInt(3) - 1); -+ final BlockPos sourcePos = pos; // Paper - Use correct source for mushroom block spread event - - for (int j = 0; j < 4; ++j) { - if (world.isEmptyBlock(blockposition2) && state.canSurvive(world, blockposition2)) { -@@ -78,7 +79,7 @@ public class MushroomBlock extends BushBlock implements BonemealableBlock { - } - - if (world.isEmptyBlock(blockposition2) && state.canSurvive(world, blockposition2)) { -- org.bukkit.craftbukkit.event.CraftEventFactory.handleBlockSpreadEvent(world, pos, blockposition2, state, 2); // CraftBukkit -+ org.bukkit.craftbukkit.event.CraftEventFactory.handleBlockSpreadEvent(world, sourcePos, blockposition2, state, 2); // CraftBukkit // Paper - Use correct source for mushroom block spread event - } - } - diff --git a/patches/server/0849-Deprecate-and-replace-methods-with-old-StructureType.patch b/patches/server/0849-Deprecate-and-replace-methods-with-old-StructureType.patch new file mode 100644 index 000000000000..80817c11759d --- /dev/null +++ b/patches/server/0849-Deprecate-and-replace-methods-with-old-StructureType.patch @@ -0,0 +1,54 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Jake Potrebic +Date: Sat, 10 Dec 2022 17:52:38 -0800 +Subject: [PATCH] Deprecate and replace methods with old StructureType + + +diff --git a/src/main/java/org/bukkit/craftbukkit/CraftServer.java b/src/main/java/org/bukkit/craftbukkit/CraftServer.java +index fa505c0714bb95b2ab08b4bbb9ea79ce98898f4b..a1f2c8fd0348a6a5dad521430f473867bdadd1a5 100644 +--- a/src/main/java/org/bukkit/craftbukkit/CraftServer.java ++++ b/src/main/java/org/bukkit/craftbukkit/CraftServer.java +@@ -2007,6 +2007,11 @@ public final class CraftServer implements Server { + + ServerLevel worldServer = ((CraftWorld) world).getHandle(); + Location structureLocation = world.locateNearestStructure(location, structureType, radius, findUnexplored); ++ // Paper start - don't throw NPE ++ if (structureLocation == null) { ++ throw new IllegalStateException("Could not find a structure for " + structureType); ++ } ++ // Paper end + BlockPos structurePosition = CraftLocation.toBlockPosition(structureLocation); + + // Create map with trackPlayer = true, unlimitedTracking = true +@@ -2017,6 +2022,31 @@ public final class CraftServer implements Server { + + return CraftItemStack.asBukkitCopy(stack); + } ++ // Paper start - copied from above (uses un-deprecated StructureType type) ++ @Override ++ public ItemStack createExplorerMap(World world, Location location, org.bukkit.generator.structure.StructureType structureType, org.bukkit.map.MapCursor.Type mapIcon, int radius, boolean findUnexplored) { ++ Preconditions.checkArgument(world != null, "World cannot be null"); ++ Preconditions.checkArgument(location != null, "Location cannot be null"); ++ Preconditions.checkArgument(structureType != null, "StructureType cannot be null"); ++ Preconditions.checkArgument(mapIcon != null, "mapIcon cannot be null"); ++ ++ ServerLevel worldServer = ((CraftWorld) world).getHandle(); ++ final org.bukkit.util.StructureSearchResult structureSearchResult = world.locateNearestStructure(location, structureType, radius, findUnexplored); ++ if (structureSearchResult == null) { ++ return null; ++ } ++ Location structureLocation = structureSearchResult.getLocation(); ++ BlockPos structurePosition = new BlockPos(structureLocation.getBlockX(), structureLocation.getBlockY(), structureLocation.getBlockZ()); ++ ++ // Create map with showIcons = true, unlimitedTracking = true ++ net.minecraft.world.item.ItemStack stack = MapItem.create(worldServer, structurePosition.getX(), structurePosition.getZ(), MapView.Scale.NORMAL.getValue(), true, true); ++ MapItem.renderBiomePreviewMap(worldServer, stack); ++ // "+" map ID taken from VillagerTrades$TreasureMapForEmeralds ++ MapItem.getSavedData(stack, worldServer).addTargetDecoration(stack, structurePosition, "+", CraftMapCursor.CraftType.bukkitToMinecraftHolder(mapIcon)); ++ ++ return CraftItemStack.asBukkitCopy(stack); ++ } ++ // Paper end + + @Override + public void shutdown() { diff --git a/patches/server/0849-Respect-randomizeData-on-more-entities-when-spawning.patch b/patches/server/0849-Respect-randomizeData-on-more-entities-when-spawning.patch deleted file mode 100644 index d04e8bf4f636..000000000000 --- a/patches/server/0849-Respect-randomizeData-on-more-entities-when-spawning.patch +++ /dev/null @@ -1,68 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Jake Potrebic -Date: Thu, 13 Jul 2023 16:10:07 -0700 -Subject: [PATCH] Respect randomizeData on more entities when spawning - -* ItemEntity -* PrimedTNT -* FireworkRocketEntity -* ExperienceOrb - -diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftEntityTypes.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftEntityTypes.java -index 39aa116193f313f53540b0135d2ab26acb7f1469..afc75ef3fb0fac40673fdfb684bd1f0d0edeb6ce 100644 ---- a/src/main/java/org/bukkit/craftbukkit/entity/CraftEntityTypes.java -+++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftEntityTypes.java -@@ -222,6 +222,13 @@ public final class CraftEntityTypes { - entity.assignDirectionalMovement(new Vec3(direction.getX(), direction.getY(), direction.getZ()), 1.0); - }; - private static final BiConsumer ROT = (spawnData, entity) -> entity.setRot(spawnData.yaw(), spawnData.pitch()); // Paper -+ // Paper start - respect randomizeData -+ private static final BiConsumer CLEAR_MOVE_IF_NOT_RANDOMIZED = (spawnData, entity) -> { -+ if (!spawnData.randomizeData()) { -+ entity.setDeltaMovement(net.minecraft.world.phys.Vec3.ZERO); -+ } -+ }; -+ // Paper end - respect randomizeData - private static final Map, EntityTypeData> CLASS_TYPE_DATA = new HashMap<>(); - private static final Map> ENTITY_TYPE_DATA = new HashMap<>(); - -@@ -374,11 +381,12 @@ public final class CraftEntityTypes { - net.minecraft.world.item.ItemStack itemStack = new net.minecraft.world.item.ItemStack(Items.STONE); - ItemEntity item = new ItemEntity(spawnData.minecraftWorld(), spawnData.x(), spawnData.y(), spawnData.z(), itemStack); - item.setPickUpDelay(10); -+ CLEAR_MOVE_IF_NOT_RANDOMIZED.accept(spawnData, item); // Paper - respect randomizeData - - return item; - })); - register(new EntityTypeData<>(EntityType.EXPERIENCE_ORB, ExperienceOrb.class, CraftExperienceOrb::new, -- spawnData -> new net.minecraft.world.entity.ExperienceOrb(spawnData.minecraftWorld(), spawnData.x(), spawnData.y(), spawnData.z(), 0, org.bukkit.entity.ExperienceOrb.SpawnReason.CUSTOM, null, null) // Paper -+ combine(combine(spawnData -> new net.minecraft.world.entity.ExperienceOrb(spawnData.minecraftWorld(), spawnData.x(), spawnData.y(), spawnData.z(), 0, org.bukkit.entity.ExperienceOrb.SpawnReason.CUSTOM, null, null), CLEAR_MOVE_IF_NOT_RANDOMIZED), (spawnData, experienceOrb) -> { if (!spawnData.randomizeData()) { experienceOrb.setYRot(0); } }) // Paper - respect randomizeData - )); - register(new EntityTypeData<>(EntityType.AREA_EFFECT_CLOUD, AreaEffectCloud.class, CraftAreaEffectCloud::new, createAndMove(net.minecraft.world.entity.EntityType.AREA_EFFECT_CLOUD))); // Paper - set area effect cloud rotation - register(new EntityTypeData<>(EntityType.EGG, Egg.class, CraftEgg::new, spawnData -> new ThrownEgg(spawnData.minecraftWorld(), spawnData.x(), spawnData.y(), spawnData.z()))); -@@ -390,12 +398,23 @@ public final class CraftEntityTypes { - entity.setItem(CraftItemStack.asNMSCopy(new ItemStack(Material.SPLASH_POTION, 1))); - return entity; - })); -- register(new EntityTypeData<>(EntityType.TNT, TNTPrimed.class, CraftTNTPrimed::new, spawnData -> new PrimedTnt(spawnData.minecraftWorld(), spawnData.x(), spawnData.y(), spawnData.z(), null))); -+ register(new EntityTypeData<>(EntityType.TNT, TNTPrimed.class, CraftTNTPrimed::new, combine(spawnData -> new PrimedTnt(spawnData.minecraftWorld(), spawnData.x(), spawnData.y(), spawnData.z(), null), CLEAR_MOVE_IF_NOT_RANDOMIZED))); // Paper - respect randomizeData - register(new EntityTypeData<>(EntityType.FALLING_BLOCK, FallingBlock.class, CraftFallingBlock::new, spawnData -> { - BlockPos pos = BlockPos.containing(spawnData.x(), spawnData.y(), spawnData.z()); - return new FallingBlockEntity(spawnData.minecraftWorld(), spawnData.x(), spawnData.y(), spawnData.z(), spawnData.world().getBlockState(pos)); // Paper - create falling block entities correctly - })); -- register(new EntityTypeData<>(EntityType.FIREWORK_ROCKET, Firework.class, CraftFirework::new, spawnData -> new FireworkRocketEntity(spawnData.minecraftWorld(), spawnData.x(), spawnData.y(), spawnData.z(), FireworkRocketEntity.getDefaultItem()))); // Paper - pass correct default to rocket for data storage -+ // Paper start - respect randomizeData -+ register(new EntityTypeData<>(EntityType.FIREWORK_ROCKET, Firework.class, CraftFirework::new, spawnData -> { -+ FireworkRocketEntity entity = new FireworkRocketEntity(spawnData.minecraftWorld(), spawnData.x(), spawnData.y(), spawnData.z(), FireworkRocketEntity.getDefaultItem()); // Paper - pass correct default to rocket for data storage -+ if (!spawnData.randomizeData()) { -+ // logic below was taken from FireworkRocketEntity constructor -+ entity.setDeltaMovement(0, 0.05, 0); -+ //noinspection PointlessArithmeticExpression -+ entity.lifetime = 10 * 1 + 6; -+ } -+ return entity; -+ })); -+ // Paper end - respect randomizeData - register(new EntityTypeData<>(EntityType.EVOKER_FANGS, EvokerFangs.class, CraftEvokerFangs::new, spawnData -> new net.minecraft.world.entity.projectile.EvokerFangs(spawnData.minecraftWorld(), spawnData.x(), spawnData.y(), spawnData.z(), (float) Math.toRadians(spawnData.yaw()), 0, null))); - register(new EntityTypeData<>(EntityType.COMMAND_BLOCK_MINECART, CommandMinecart.class, CraftMinecartCommand::new, spawnData -> new MinecartCommandBlock(spawnData.minecraftWorld(), spawnData.x(), spawnData.y(), spawnData.z()))); - register(new EntityTypeData<>(EntityType.MINECART, RideableMinecart.class, CraftMinecartRideable::new, spawnData -> new Minecart(spawnData.minecraftWorld(), spawnData.x(), spawnData.y(), spawnData.z()))); diff --git a/patches/server/0850-Don-t-tab-complete-namespaced-commands-if-send-names.patch b/patches/server/0850-Don-t-tab-complete-namespaced-commands-if-send-names.patch new file mode 100644 index 000000000000..8834a03dcbe1 --- /dev/null +++ b/patches/server/0850-Don-t-tab-complete-namespaced-commands-if-send-names.patch @@ -0,0 +1,28 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: EpicPlayerA10 +Date: Sun, 18 Jun 2023 12:38:24 +0200 +Subject: [PATCH] Don't tab-complete namespaced commands if send-namespaced is + false + +Tab-complete packet is supposed to tab-complete args for commands, but +it also can suggest commands like in version 1.12.2 or lower. + +This patch prevents server from sending namespaced commands when player +requests tab-complete only commands. + +diff --git a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java +index 134d9fbdb757094a4624325c0ad8f32efadd61b6..688d6e51bc8b844ba7648cf1aed1bdd9629c1675 100644 +--- a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java ++++ b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java +@@ -832,6 +832,11 @@ public class ServerGamePacketListenerImpl extends ServerCommonPacketListenerImpl + ParseResults parseresults = this.server.getCommands().getDispatcher().parse(stringreader, this.player.createCommandSourceStack()); + + this.server.getCommands().getDispatcher().getCompletionSuggestions(parseresults).thenAccept((suggestions) -> { ++ // Paper start - Don't tab-complete namespaced commands if send-namespaced is false ++ if (!org.spigotmc.SpigotConfig.sendNamespaced && suggestions.getRange().getStart() <= 1) { ++ suggestions.getList().removeIf(suggestion -> suggestion.getText().contains(":")); ++ } ++ // Paper end - Don't tab-complete namespaced commands if send-namespaced is false + // Paper start - Brigadier API + com.destroystokyo.paper.event.brigadier.AsyncPlayerSendSuggestionsEvent suggestEvent = new com.destroystokyo.paper.event.brigadier.AsyncPlayerSendSuggestionsEvent(this.getCraftPlayer(), suggestions, packet.getCommand()); + suggestEvent.setCancelled(suggestions.isEmpty()); diff --git a/patches/server/0850-Use-correct-seed-on-api-world-load.patch b/patches/server/0850-Use-correct-seed-on-api-world-load.patch deleted file mode 100644 index 58cf8e10f3f7..000000000000 --- a/patches/server/0850-Use-correct-seed-on-api-world-load.patch +++ /dev/null @@ -1,19 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Florian Schmidt -Date: Fri, 28 Jul 2023 14:14:35 +0200 -Subject: [PATCH] Use correct seed on api world load - - -diff --git a/src/main/java/org/bukkit/craftbukkit/CraftServer.java b/src/main/java/org/bukkit/craftbukkit/CraftServer.java -index 75c222e592d676e98b293767d00de54a61411ae7..69df0cf285a5af1011cc769a433683489ff3dded 100644 ---- a/src/main/java/org/bukkit/craftbukkit/CraftServer.java -+++ b/src/main/java/org/bukkit/craftbukkit/CraftServer.java -@@ -1398,7 +1398,7 @@ public final class CraftServer implements Server { - net.minecraft.server.Main.forceUpgrade(worldSession, DataFixers.getDataFixer(), this.console.options.has("eraseCache"), () -> true, iregistrycustom_dimension, this.console.options.has("recreateRegionFiles")); - } - -- long j = BiomeManager.obfuscateSeed(creator.seed()); -+ long j = BiomeManager.obfuscateSeed(worlddata.worldGenOptions().seed()); // Paper - use world seed - List list = ImmutableList.of(new PhantomSpawner(), new PatrolSpawner(), new CatSpawner(), new VillageSiege(), new WanderingTraderSpawner(worlddata)); - LevelStem worlddimension = iregistry.get(actualDimension); - diff --git a/patches/server/0851-Properly-handle-BlockBreakEvent-isDropItems.patch b/patches/server/0851-Properly-handle-BlockBreakEvent-isDropItems.patch new file mode 100644 index 000000000000..7bf30bb1122d --- /dev/null +++ b/patches/server/0851-Properly-handle-BlockBreakEvent-isDropItems.patch @@ -0,0 +1,162 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Jake Potrebic +Date: Sat, 4 Mar 2023 10:52:52 -0800 +Subject: [PATCH] Properly handle BlockBreakEvent#isDropItems + +Setting whether a block break dropped items controlled +far more than just whether blocks dropped, like stat increases +food consumption, turtle egg count decreases, ice to water +conversions and beehive releases + +diff --git a/src/main/java/net/minecraft/server/level/ServerPlayerGameMode.java b/src/main/java/net/minecraft/server/level/ServerPlayerGameMode.java +index 2aee9c2fbe38076317a3de7c3fdbd6988b64b389..3bd4ab8161c29bb8df2ba496a4430393b6593476 100644 +--- a/src/main/java/net/minecraft/server/level/ServerPlayerGameMode.java ++++ b/src/main/java/net/minecraft/server/level/ServerPlayerGameMode.java +@@ -437,8 +437,8 @@ public class ServerPlayerGameMode { + isCorrectTool = flag1; // Paper - Trigger bee_nest_destroyed trigger in the correct place + + itemstack.mineBlock(this.level, iblockdata1, pos, this.player); +- if (flag && flag1 && event.isDropItems()) { // CraftBukkit - Check if block should drop items +- block.playerDestroy(this.level, this.player, pos, iblockdata1, tileentity, itemstack1); ++ if (flag && flag1/* && event.isDropItems() */) { // CraftBukkit - Check if block should drop items // Paper - fix drops not preventing stats/food exhaustion ++ block.playerDestroy(this.level, this.player, pos, iblockdata1, tileentity, itemstack1, event.isDropItems(), false); // Paper - fix drops not preventing stats/food exhaustion + } + + // return true; // CraftBukkit +diff --git a/src/main/java/net/minecraft/world/level/block/BeehiveBlock.java b/src/main/java/net/minecraft/world/level/block/BeehiveBlock.java +index d5ed53c37bba79f84b60d616887cd5176e124f10..6c0ea0bde1c36edda92807e317ed37f8b1bdac6a 100644 +--- a/src/main/java/net/minecraft/world/level/block/BeehiveBlock.java ++++ b/src/main/java/net/minecraft/world/level/block/BeehiveBlock.java +@@ -94,8 +94,8 @@ public class BeehiveBlock extends BaseEntityBlock { + } + + @Override +- public void playerDestroy(Level world, Player player, BlockPos pos, BlockState state, @Nullable BlockEntity blockEntity, ItemStack tool) { +- super.playerDestroy(world, player, pos, state, blockEntity, tool); ++ public void playerDestroy(Level world, Player player, BlockPos pos, BlockState state, @Nullable BlockEntity blockEntity, ItemStack tool, boolean includeDrops, boolean dropExp) { // Paper - fix drops not preventing stats/food exhaustion ++ super.playerDestroy(world, player, pos, state, blockEntity, tool, includeDrops, dropExp); // Paper - fix drops not preventing stats/food exhaustion + if (!world.isClientSide && blockEntity instanceof BeehiveBlockEntity tileentitybeehive) { + if (!EnchantmentHelper.hasTag(tool, EnchantmentTags.PREVENTS_BEE_SPAWNS_WHEN_MINING)) { + tileentitybeehive.emptyAllLivingFromHive(player, state, BeehiveBlockEntity.BeeReleaseStatus.EMERGENCY); +diff --git a/src/main/java/net/minecraft/world/level/block/Block.java b/src/main/java/net/minecraft/world/level/block/Block.java +index 9d8c4ecd89b05a0e5d4ebb5e686eba5d899765f2..4ff32e3fb1a1979827ef063cda196a43995440fe 100644 +--- a/src/main/java/net/minecraft/world/level/block/Block.java ++++ b/src/main/java/net/minecraft/world/level/block/Block.java +@@ -396,10 +396,18 @@ public class Block extends BlockBehaviour implements ItemLike { + return this.defaultBlockState(); + } + ++ @io.papermc.paper.annotation.DoNotUse @Deprecated // Paper - fix drops not preventing stats/food exhaustion + public void playerDestroy(Level world, Player player, BlockPos pos, BlockState state, @Nullable BlockEntity blockEntity, ItemStack tool) { ++ // Paper start - fix drops not preventing stats/food exhaustion ++ this.playerDestroy(world, player, pos, state, blockEntity, tool, true, true); ++ } ++ public void playerDestroy(Level world, Player player, BlockPos pos, BlockState state, @Nullable BlockEntity blockEntity, ItemStack tool, boolean includeDrops, boolean dropExp) { ++ // Paper end - fix drops not preventing stats/food exhaustion + player.awardStat(Stats.BLOCK_MINED.get(this)); + player.causeFoodExhaustion(0.005F, org.bukkit.event.entity.EntityExhaustionEvent.ExhaustionReason.BLOCK_MINED); // CraftBukkit - EntityExhaustionEvent ++ if (includeDrops) { // Paper - fix drops not preventing stats/food exhaustion + Block.dropResources(state, world, pos, blockEntity, player, tool); ++ } // Paper - fix drops not preventing stats/food exhaustion + } + + public void setPlacedBy(Level world, BlockPos pos, BlockState state, @Nullable LivingEntity placer, ItemStack itemStack) {} +diff --git a/src/main/java/net/minecraft/world/level/block/DoublePlantBlock.java b/src/main/java/net/minecraft/world/level/block/DoublePlantBlock.java +index edb3b6cdb617c48140539728af1373993e78648f..4fe83bd0f355549847b66afb7e61f6f2a6d97016 100644 +--- a/src/main/java/net/minecraft/world/level/block/DoublePlantBlock.java ++++ b/src/main/java/net/minecraft/world/level/block/DoublePlantBlock.java +@@ -98,8 +98,8 @@ public class DoublePlantBlock extends BushBlock { + } + + @Override +- public void playerDestroy(Level world, Player player, BlockPos pos, BlockState state, @Nullable BlockEntity blockEntity, ItemStack tool) { +- super.playerDestroy(world, player, pos, Blocks.AIR.defaultBlockState(), blockEntity, tool); ++ public void playerDestroy(Level world, Player player, BlockPos pos, BlockState state, @Nullable BlockEntity blockEntity, ItemStack tool, boolean includeDrops, boolean dropExp) { // Paper - fix drops not preventing stats/food exhaustion ++ super.playerDestroy(world, player, pos, Blocks.AIR.defaultBlockState(), blockEntity, tool, includeDrops, dropExp); // Paper - fix drops not preventing stats/food exhaustion + } + + protected static void preventDropFromBottomPart(Level world, BlockPos pos, BlockState state, Player player) { +diff --git a/src/main/java/net/minecraft/world/level/block/IceBlock.java b/src/main/java/net/minecraft/world/level/block/IceBlock.java +index 067a2d969ca0979ae67b600e303deec93eda0251..a94762e65853ccad38cf90b0049ca256106c0c9f 100644 +--- a/src/main/java/net/minecraft/world/level/block/IceBlock.java ++++ b/src/main/java/net/minecraft/world/level/block/IceBlock.java +@@ -34,8 +34,8 @@ public class IceBlock extends HalfTransparentBlock { + } + + @Override +- public void playerDestroy(Level world, Player player, BlockPos pos, BlockState state, @Nullable BlockEntity blockEntity, ItemStack tool) { +- super.playerDestroy(world, player, pos, state, blockEntity, tool); ++ public void playerDestroy(Level world, Player player, BlockPos pos, BlockState state, @Nullable BlockEntity blockEntity, ItemStack tool, boolean includeDrops, boolean dropExp) { // Paper - fix drops not preventing stats/food exhaustion ++ super.playerDestroy(world, player, pos, state, blockEntity, tool, includeDrops, dropExp); // Paper - fix drops not preventing stats/food exhaustion + // Paper start - Improve Block#breakNaturally API + this.afterDestroy(world, pos, tool); + } +diff --git a/src/main/java/net/minecraft/world/level/block/TurtleEggBlock.java b/src/main/java/net/minecraft/world/level/block/TurtleEggBlock.java +index 85d9829e979ef5f22aee9c346800612f479786b7..953ddb2ea6fd48e57712e30a6addf23e188e5312 100644 +--- a/src/main/java/net/minecraft/world/level/block/TurtleEggBlock.java ++++ b/src/main/java/net/minecraft/world/level/block/TurtleEggBlock.java +@@ -175,8 +175,8 @@ public class TurtleEggBlock extends Block { + } + + @Override +- public void playerDestroy(Level world, Player player, BlockPos pos, BlockState state, @Nullable BlockEntity blockEntity, ItemStack tool) { +- super.playerDestroy(world, player, pos, state, blockEntity, tool); ++ public void playerDestroy(Level world, Player player, BlockPos pos, BlockState state, @Nullable BlockEntity blockEntity, ItemStack tool, boolean includeDrops, boolean dropExp) { // Paper - fix drops not preventing stats/food exhaustion ++ super.playerDestroy(world, player, pos, state, blockEntity, tool, includeDrops, dropExp); // Paper - fix drops not preventing stats/food exhaustion + this.decreaseEggs(world, pos, state); + } + +diff --git a/src/test/java/io/papermc/paper/world/block/BlockPlayerDestroyOverrideTest.java b/src/test/java/io/papermc/paper/world/block/BlockPlayerDestroyOverrideTest.java +new file mode 100644 +index 0000000000000000000000000000000000000000..22145bf698b3d1ff0a07a3aaa8d55a19905f99ad +--- /dev/null ++++ b/src/test/java/io/papermc/paper/world/block/BlockPlayerDestroyOverrideTest.java +@@ -0,0 +1,48 @@ ++package io.papermc.paper.world.block; ++ ++import io.github.classgraph.ClassGraph; ++import io.github.classgraph.ClassInfo; ++import io.github.classgraph.MethodInfo; ++import io.github.classgraph.MethodInfoList; ++import io.github.classgraph.MethodParameterInfo; ++import io.github.classgraph.ScanResult; ++import java.util.ArrayList; ++import java.util.List; ++import java.util.stream.Stream; ++import org.bukkit.support.environment.Normal; ++import org.junit.jupiter.params.ParameterizedTest; ++import org.junit.jupiter.params.provider.MethodSource; ++ ++import static org.junit.jupiter.api.Assertions.assertEquals; ++ ++@Normal ++public class BlockPlayerDestroyOverrideTest { ++ ++ public static Stream parameters() { ++ final List classInfo = new ArrayList<>(); ++ try (ScanResult scanResult = new ClassGraph() ++ .enableClassInfo() ++ .enableMethodInfo() ++ .whitelistPackages("net.minecraft") ++ .scan() ++ ) { ++ for (final ClassInfo subclass : scanResult.getSubclasses("net.minecraft.world.level.block.Block")) { ++ final MethodInfoList playerDestroy = subclass.getDeclaredMethodInfo("playerDestroy"); ++ if (!playerDestroy.isEmpty()) { ++ classInfo.add(subclass); ++ } ++ } ++ } ++ return classInfo.stream(); ++ } ++ ++ @ParameterizedTest ++ @MethodSource("parameters") ++ public void checkPlayerDestroyOverrides(ClassInfo overridesPlayerDestroy) { ++ final MethodInfoList playerDestroy = overridesPlayerDestroy.getDeclaredMethodInfo("playerDestroy"); ++ assertEquals(1, playerDestroy.size(), overridesPlayerDestroy.getName() + " has multiple playerDestroy methods"); ++ final MethodInfo next = playerDestroy.iterator().next(); ++ final MethodParameterInfo[] parameterInfo = next.getParameterInfo(); ++ assertEquals("boolean", parameterInfo[parameterInfo.length - 1].getTypeDescriptor().toStringWithSimpleNames(), overridesPlayerDestroy.getName() + " needs to change its override of playerDestroy"); ++ } ++} diff --git a/patches/server/0851-Remove-UpgradeData-neighbour-ticks-outside-of-range.patch b/patches/server/0851-Remove-UpgradeData-neighbour-ticks-outside-of-range.patch deleted file mode 100644 index 2f19c9d94a1e..000000000000 --- a/patches/server/0851-Remove-UpgradeData-neighbour-ticks-outside-of-range.patch +++ /dev/null @@ -1,50 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Spottedleaf -Date: Wed, 9 Aug 2023 14:00:40 -0700 -Subject: [PATCH] Remove UpgradeData neighbour ticks outside of range - -The lists are only supposed to contain ticks for the 1 radius -neighbours of the chunk. - -diff --git a/src/main/java/net/minecraft/world/level/chunk/UpgradeData.java b/src/main/java/net/minecraft/world/level/chunk/UpgradeData.java -index bfb73e016f107fb8da12903bf233a824d062ed73..22b6d0851a51da180cd8fbbe6554c5370f5ac5bd 100644 ---- a/src/main/java/net/minecraft/world/level/chunk/UpgradeData.java -+++ b/src/main/java/net/minecraft/world/level/chunk/UpgradeData.java -@@ -100,6 +100,25 @@ public class UpgradeData { - } - } - -+ // Paper start - filter out relocated neighbour ticks -+ // The lists are only supposed to contain ticks for the 1 radius neighbours of the chunk -+ private static void filterTickList(int chunkX, int chunkZ, List> ticks) { -+ for (java.util.Iterator> iterator = ticks.iterator(); iterator.hasNext();) { -+ SavedTick tick = iterator.next(); -+ BlockPos tickPos = tick.pos(); -+ int tickCX = tickPos.getX() >> 4; -+ int tickCZ = tickPos.getZ() >> 4; -+ -+ int dist = Math.max(Math.abs(chunkX - tickCX), Math.abs(chunkZ - tickCZ)); -+ -+ if (dist != 1) { -+ LOGGER.warn("Neighbour tick '" + tick + "' serialized in chunk (" + chunkX + "," + chunkZ + ") is too far (" + tickCX + "," + tickCZ + ")"); -+ iterator.remove(); -+ } -+ } -+ } -+ // Paper end - filter out relocated neighbour ticks -+ - public void upgrade(LevelChunk chunk) { - this.upgradeInside(chunk); - -@@ -107,6 +126,11 @@ public class UpgradeData { - upgradeSides(chunk, direction8); - } - -+ // Paper start - filter out relocated neighbour ticks -+ filterTickList(chunk.locX, chunk.locZ, this.neighborBlockTicks); -+ filterTickList(chunk.locX, chunk.locZ, this.neighborFluidTicks); -+ // Paper end - filter out relocated neighbour ticks -+ - Level level = chunk.getLevel(); - this.neighborBlockTicks.forEach(tick -> { - Block block = tick.type() == Blocks.AIR ? level.getBlockState(tick.pos()).getBlock() : tick.type(); diff --git a/patches/server/0852-Cache-map-ids-on-item-frames.patch b/patches/server/0852-Cache-map-ids-on-item-frames.patch deleted file mode 100644 index 65e3e3499f44..000000000000 --- a/patches/server/0852-Cache-map-ids-on-item-frames.patch +++ /dev/null @@ -1,39 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Warrior <50800980+Warriorrrr@users.noreply.github.com> -Date: Mon, 7 Aug 2023 12:58:28 +0200 -Subject: [PATCH] Cache map ids on item frames - - -diff --git a/src/main/java/net/minecraft/server/level/ServerEntity.java b/src/main/java/net/minecraft/server/level/ServerEntity.java -index f010be9605d7458add7e5693ff473fabf679c938..a2fbbbd7a66d4e7b1063638f8467e8887a417282 100644 ---- a/src/main/java/net/minecraft/server/level/ServerEntity.java -+++ b/src/main/java/net/minecraft/server/level/ServerEntity.java -@@ -117,7 +117,7 @@ public class ServerEntity { - ItemStack itemstack = entityitemframe.getItem(); - - if (this.level.paperConfig().maps.itemFrameCursorUpdateInterval > 0 && this.tickCount % this.level.paperConfig().maps.itemFrameCursorUpdateInterval == 0 && itemstack.getItem() instanceof MapItem) { // CraftBukkit - Moved this.tickCounter % 10 logic here so item frames do not enter the other blocks // Paper - Make item frame map cursor update interval configurable -- MapId mapid = (MapId) itemstack.get(DataComponents.MAP_ID); -+ MapId mapid = entityitemframe.cachedMapId; // Paper - Perf: Cache map ids on item frames - MapItemSavedData worldmap = MapItem.getSavedData(mapid, this.level); - - if (worldmap != null) { -diff --git a/src/main/java/net/minecraft/world/entity/decoration/ItemFrame.java b/src/main/java/net/minecraft/world/entity/decoration/ItemFrame.java -index ba4e0ad7c7c2731d61ec7f60f19d7e9ec31a28ba..5b7245cd99593ee90e17c97e0104f3aba9ae05ea 100644 ---- a/src/main/java/net/minecraft/world/entity/decoration/ItemFrame.java -+++ b/src/main/java/net/minecraft/world/entity/decoration/ItemFrame.java -@@ -50,6 +50,7 @@ public class ItemFrame extends HangingEntity { - private static final float HEIGHT = 0.75F; - public float dropChance; - public boolean fixed; -+ public @Nullable MapId cachedMapId; // Paper - Perf: Cache map ids on item frames - - public ItemFrame(EntityType type, Level world) { - super(type, world); -@@ -322,6 +323,7 @@ public class ItemFrame extends HangingEntity { - } - - private void onItemChanged(ItemStack stack) { -+ this.cachedMapId = stack.getComponents().get(net.minecraft.core.component.DataComponents.MAP_ID); // Paper - Perf: Cache map ids on item frames - if (!stack.isEmpty() && stack.getFrame() != this) { - stack.setEntityRepresentation(this); - } diff --git a/patches/server/0852-Fire-entity-death-event-for-ender-dragon.patch b/patches/server/0852-Fire-entity-death-event-for-ender-dragon.patch new file mode 100644 index 000000000000..0efcc781a41b --- /dev/null +++ b/patches/server/0852-Fire-entity-death-event-for-ender-dragon.patch @@ -0,0 +1,25 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Trevor Bedson <78997566+Prorickey@users.noreply.github.com> +Date: Fri, 14 Jul 2023 20:47:02 -0400 +Subject: [PATCH] Fire entity death event for ender dragon + + +diff --git a/src/main/java/net/minecraft/world/entity/boss/enderdragon/EnderDragon.java b/src/main/java/net/minecraft/world/entity/boss/enderdragon/EnderDragon.java +index 38456d3901e495e4c401cff0de7ae38544c1b2a7..2df8bf818345246cc1f88b93e4a3b62e61772efb 100644 +--- a/src/main/java/net/minecraft/world/entity/boss/enderdragon/EnderDragon.java ++++ b/src/main/java/net/minecraft/world/entity/boss/enderdragon/EnderDragon.java +@@ -627,6 +627,14 @@ public class EnderDragon extends Mob implements Enemy { + + @Override + public void kill(ServerLevel world) { ++ // Paper start - Fire entity death event ++ this.silentDeath = true; ++ org.bukkit.event.entity.EntityDeathEvent deathEvent = org.bukkit.craftbukkit.event.CraftEventFactory.callEntityDeathEvent(this, this.damageSources().genericKill()); ++ if (deathEvent.isCancelled()) { ++ this.silentDeath = false; // Reset to default if event was cancelled ++ return; ++ } ++ // Paper end - Fire entity death event + this.remove(Entity.RemovalReason.KILLED, EntityRemoveEvent.Cause.DEATH); // CraftBukkit - add Bukkit remove cause + this.gameEvent(GameEvent.ENTITY_DIE); + if (this.dragonFight != null) { diff --git a/patches/server/0853-Configurable-entity-tracking-range-by-Y-coordinate.patch b/patches/server/0853-Configurable-entity-tracking-range-by-Y-coordinate.patch new file mode 100644 index 000000000000..feb7011cca5a --- /dev/null +++ b/patches/server/0853-Configurable-entity-tracking-range-by-Y-coordinate.patch @@ -0,0 +1,30 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: ruViolence <78062896+ruViolence@users.noreply.github.com> +Date: Tue, 27 Jun 2023 15:38:18 +0800 +Subject: [PATCH] Configurable entity tracking range by Y coordinate + +Options to configure entity tracking by Y coordinate, also for each entity category. + +diff --git a/src/main/java/net/minecraft/server/level/ChunkMap.java b/src/main/java/net/minecraft/server/level/ChunkMap.java +index 8b5d11aceb77135c917c3581f4db792ef4b647ec..5b993cb8a99c6a0257b9d3d93162f9b2fff552b0 100644 +--- a/src/main/java/net/minecraft/server/level/ChunkMap.java ++++ b/src/main/java/net/minecraft/server/level/ChunkMap.java +@@ -1577,7 +1577,17 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider + double d0 = (double) Math.min(this.getEffectiveRange(), i * 16); + double d1 = vec3d.x * vec3d.x + vec3d.z * vec3d.z; + double d2 = d0 * d0; +- boolean flag = d1 <= d2 && this.entity.broadcastToPlayer(player) && ChunkMap.this.isChunkTracked(player, this.entity.chunkPosition().x, this.entity.chunkPosition().z); ++ // Paper start - Configurable entity tracking range by Y ++ boolean flag = d1 <= d2; ++ if (flag && level.paperConfig().entities.trackingRangeY.enabled) { ++ double rangeY = level.paperConfig().entities.trackingRangeY.get(this.entity, -1); ++ if (rangeY != -1) { ++ double vec3d_dy = player.getY() - this.entity.getY(); ++ flag = vec3d_dy * vec3d_dy <= rangeY * rangeY; ++ } ++ } ++ flag = flag && this.entity.broadcastToPlayer(player) && ChunkMap.this.isChunkTracked(player, this.entity.chunkPosition().x, this.entity.chunkPosition().z); ++ // Paper end - Configurable entity tracking range by Y + + // CraftBukkit start - respect vanish API + if (!player.getBukkitEntity().canSee(this.entity.getBukkitEntity())) { diff --git a/patches/server/0853-Fix-custom-statistic-criteria-creation.patch b/patches/server/0853-Fix-custom-statistic-criteria-creation.patch deleted file mode 100644 index 682cc84ee5aa..000000000000 --- a/patches/server/0853-Fix-custom-statistic-criteria-creation.patch +++ /dev/null @@ -1,25 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Noah van der Aa -Date: Sat, 12 Aug 2023 15:33:49 +0200 -Subject: [PATCH] Fix custom statistic criteria creation - - -diff --git a/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java b/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java -index 72c2f70d22c5ac920f13b11badac404dbb15c055..23ed0a130525a0b3a1b41330685476463c81f183 100644 ---- a/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java -+++ b/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java -@@ -599,6 +599,14 @@ public final class CraftMagicNumbers implements UnsafeValues { - } - // Paper end - namespaced key biome methods - -+ // Paper start - fix custom stats criteria creation -+ @Override -+ public String getStatisticCriteriaKey(org.bukkit.Statistic statistic) { -+ if (statistic.getType() != org.bukkit.Statistic.Type.UNTYPED) return "minecraft.custom:minecraft." + statistic.getKey().getKey(); -+ return org.bukkit.craftbukkit.CraftStatistic.getNMSStatistic(statistic).getName(); -+ } -+ // Paper end - fix custom stats criteria creation -+ - @Override - public String get(Class aClass, String s) { - if (aClass == Enchantment.class) { diff --git a/patches/server/0854-Add-Listing-API-for-Player.patch b/patches/server/0854-Add-Listing-API-for-Player.patch new file mode 100644 index 000000000000..f391783edbbd --- /dev/null +++ b/patches/server/0854-Add-Listing-API-for-Player.patch @@ -0,0 +1,186 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Corey Shupe +Date: Wed, 11 Jan 2023 16:40:39 -0500 +Subject: [PATCH] Add Listing API for Player + + +diff --git a/src/main/java/net/minecraft/network/protocol/game/ClientboundPlayerInfoUpdatePacket.java b/src/main/java/net/minecraft/network/protocol/game/ClientboundPlayerInfoUpdatePacket.java +index 29b465fc1dc50e0e84ddb889c5303e80fe662874..4d67d98257b2cb9045d03c999cfd4ba2caf8453c 100644 +--- a/src/main/java/net/minecraft/network/protocol/game/ClientboundPlayerInfoUpdatePacket.java ++++ b/src/main/java/net/minecraft/network/protocol/game/ClientboundPlayerInfoUpdatePacket.java +@@ -37,6 +37,17 @@ public class ClientboundPlayerInfoUpdatePacket implements Packet actions, List entries) { ++ this.actions = actions; ++ this.entries = entries; ++ } ++ ++ public ClientboundPlayerInfoUpdatePacket(EnumSet actions, ClientboundPlayerInfoUpdatePacket.Entry entry) { ++ this.actions = actions; ++ this.entries = List.of(entry); ++ } ++ // Paper end - Add Listing API for Player + + public static ClientboundPlayerInfoUpdatePacket createPlayerInitializing(Collection players) { + EnumSet enumSet = EnumSet.of( +@@ -51,6 +62,28 @@ public class ClientboundPlayerInfoUpdatePacket implements Packet players, ServerPlayer forPlayer) { ++ final EnumSet enumSet = EnumSet.of(ClientboundPlayerInfoUpdatePacket.Action.ADD_PLAYER, ClientboundPlayerInfoUpdatePacket.Action.INITIALIZE_CHAT, ClientboundPlayerInfoUpdatePacket.Action.UPDATE_GAME_MODE, ClientboundPlayerInfoUpdatePacket.Action.UPDATE_LISTED, ClientboundPlayerInfoUpdatePacket.Action.UPDATE_LATENCY, ClientboundPlayerInfoUpdatePacket.Action.UPDATE_DISPLAY_NAME); ++ final List entries = new java.util.ArrayList<>(players.size()); ++ final org.bukkit.craftbukkit.entity.CraftPlayer bukkitEntity = forPlayer.getBukkitEntity(); ++ for (final ServerPlayer player : players) { ++ entries.add(new ClientboundPlayerInfoUpdatePacket.Entry(player, bukkitEntity.isListed(player.getBukkitEntity()))); ++ } ++ return new ClientboundPlayerInfoUpdatePacket(enumSet, entries); ++ } ++ ++ public static ClientboundPlayerInfoUpdatePacket createSinglePlayerInitializing(ServerPlayer player, boolean listed) { ++ final EnumSet enumSet = EnumSet.of(ClientboundPlayerInfoUpdatePacket.Action.ADD_PLAYER, ClientboundPlayerInfoUpdatePacket.Action.INITIALIZE_CHAT, ClientboundPlayerInfoUpdatePacket.Action.UPDATE_GAME_MODE, ClientboundPlayerInfoUpdatePacket.Action.UPDATE_LISTED, ClientboundPlayerInfoUpdatePacket.Action.UPDATE_LATENCY, ClientboundPlayerInfoUpdatePacket.Action.UPDATE_DISPLAY_NAME); ++ final List entries = List.of(new Entry(player, listed)); ++ return new ClientboundPlayerInfoUpdatePacket(enumSet, entries); ++ } ++ ++ public static ClientboundPlayerInfoUpdatePacket updateListed(UUID playerInfoId, boolean listed) { ++ EnumSet enumSet = EnumSet.of(ClientboundPlayerInfoUpdatePacket.Action.UPDATE_LISTED); ++ return new ClientboundPlayerInfoUpdatePacket(enumSet, new ClientboundPlayerInfoUpdatePacket.Entry(playerInfoId, listed)); ++ } ++ // Paper end - Add Listing API for Player + private ClientboundPlayerInfoUpdatePacket(RegistryFriendlyByteBuf buf) { + this.actions = buf.readEnumSet(ClientboundPlayerInfoUpdatePacket.Action.class); + this.entries = buf.readList(buf2 -> { +@@ -161,10 +194,15 @@ public class ClientboundPlayerInfoUpdatePacket implements Packet onlinePlayers = Lists.newArrayListWithExpectedSize(this.players.size() - 1); // Paper - Use single player info update packet on join + for (int i = 0; i < this.players.size(); ++i) { + ServerPlayer entityplayer1 = (ServerPlayer) this.players.get(i); + + if (entityplayer1.getBukkitEntity().canSee(bukkitPlayer)) { ++ // Paper start - Add Listing API for Player ++ if (entityplayer1.getBukkitEntity().isListed(bukkitPlayer)) { ++ // Paper end - Add Listing API for Player + entityplayer1.connection.send(packet); ++ // Paper start - Add Listing API for Player ++ } else { ++ entityplayer1.connection.send(ClientboundPlayerInfoUpdatePacket.createSinglePlayerInitializing(player, false)); ++ } ++ // Paper end - Add Listing API for Player + } + + if (entityplayer1 == player || !bukkitPlayer.canSee(entityplayer1.getBukkitEntity())) { // Paper - Use single player info update packet on join; Don't include joining player +@@ -380,7 +388,7 @@ public abstract class PlayerList { + } + // Paper start - Use single player info update packet on join + if (!onlinePlayers.isEmpty()) { +- player.connection.send(ClientboundPlayerInfoUpdatePacket.createPlayerInitializing(onlinePlayers)); ++ player.connection.send(ClientboundPlayerInfoUpdatePacket.createPlayerInitializing(onlinePlayers, player)); // Paper - Add Listing API for Player + } + // Paper end - Use single player info update packet on join + player.sentListPacket = true; +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java +index abb0370243ad95260c5c6034e20097a09415fe00..47180d44b7e168352b17bc295ffb6d3ea6e0a937 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java +@@ -206,6 +206,7 @@ public class CraftPlayer extends CraftHumanEntity implements Player { + private final ConversationTracker conversationTracker = new ConversationTracker(); + private final Set channels = new HashSet(); + private final Map>> invertedVisibilityEntities = new HashMap<>(); ++ private final Set unlistedEntities = new HashSet<>(); // Paper - Add Listing API for Player + private static final WeakHashMap> pluginWeakReferences = new WeakHashMap<>(); + private int hash = 0; + private double health = 20; +@@ -2116,7 +2117,7 @@ public class CraftPlayer extends CraftHumanEntity implements Player { + otherPlayer.setUUID(uuidOverride); + } + // Paper end +- this.getHandle().connection.send(ClientboundPlayerInfoUpdatePacket.createPlayerInitializing(List.of(otherPlayer))); ++ this.getHandle().connection.send(ClientboundPlayerInfoUpdatePacket.createPlayerInitializing(List.of(otherPlayer), this.getHandle())); // Paper - Add Listing API for Player + if (original != null) otherPlayer.setUUID(original); // Paper - uuid override + } + +@@ -2220,6 +2221,41 @@ public class CraftPlayer extends CraftHumanEntity implements Player { + return (entity != null) ? this.canSee(entity) : false; // If we can't find it, we can't see it + } + ++ // Paper start - Add Listing API for Player ++ @Override ++ public boolean isListed(Player other) { ++ return !this.unlistedEntities.contains(other.getUniqueId()); ++ } ++ ++ @Override ++ public boolean unlistPlayer(@NotNull Player other) { ++ Preconditions.checkNotNull(other, "hidden entity cannot be null"); ++ if (this.getHandle().connection == null) return false; ++ if (!this.canSee(other)) return false; ++ ++ if (unlistedEntities.add(other.getUniqueId())) { ++ this.getHandle().connection.send(ClientboundPlayerInfoUpdatePacket.updateListed(other.getUniqueId(), false)); ++ return true; ++ } else { ++ return false; ++ } ++ } ++ ++ @Override ++ public boolean listPlayer(@NotNull Player other) { ++ Preconditions.checkNotNull(other, "hidden entity cannot be null"); ++ if (this.getHandle().connection == null) return false; ++ if (!this.canSee(other)) throw new IllegalStateException("Player cannot see other player"); ++ ++ if (this.unlistedEntities.remove(other.getUniqueId())) { ++ this.getHandle().connection.send(ClientboundPlayerInfoUpdatePacket.updateListed(other.getUniqueId(), true)); ++ return true; ++ } else { ++ return false; ++ } ++ } ++ // Paper end - Add Listing API for Player ++ + @Override + public Map serialize() { + Map result = new LinkedHashMap(); diff --git a/patches/server/0854-Bandaid-fix-for-Effect.patch b/patches/server/0854-Bandaid-fix-for-Effect.patch deleted file mode 100644 index 867e3cef97e7..000000000000 --- a/patches/server/0854-Bandaid-fix-for-Effect.patch +++ /dev/null @@ -1,179 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Jake Potrebic -Date: Fri, 28 Jul 2023 15:02:44 -0700 -Subject: [PATCH] Bandaid fix for Effect - -Effect or LevelEvent needs to be replaced -but ideally after the enum PR has been merged -upstream. Until then, this test and these fixes -should address all the known issues with them - -diff --git a/src/main/java/org/bukkit/craftbukkit/CraftEffect.java b/src/main/java/org/bukkit/craftbukkit/CraftEffect.java -index 71733f918ed84b9879ac1b142ef6205c5e768a9c..c856384019eff2f2d0bb831ebe1ccb0fb9210782 100644 ---- a/src/main/java/org/bukkit/craftbukkit/CraftEffect.java -+++ b/src/main/java/org/bukkit/craftbukkit/CraftEffect.java -@@ -15,6 +15,14 @@ public class CraftEffect { - public static int getDataValue(Effect effect, T data) { - int datavalue; - switch (effect) { -+ // Paper start - add missing effects -+ case PARTICLES_SCULK_CHARGE: -+ case TRIAL_SPAWNER_DETECT_PLAYER: -+ case BEE_GROWTH: -+ case TURTLE_EGG_PLACEMENT: -+ case SMASH_ATTACK: -+ case TRIAL_SPAWNER_DETECT_PLAYER_OMINOUS: -+ // Paper end - add missing effects - case VILLAGER_PLANT_GROW: - datavalue = (Integer) data; - break; -@@ -26,6 +34,13 @@ public class CraftEffect { - Preconditions.checkArgument(data == Material.AIR || ((Material) data).isRecord(), "Invalid record type for Material %s!", data); - datavalue = Item.getId(CraftItemType.bukkitToMinecraft((Material) data)); - break; -+ // Paper start - handle shoot white smoke event -+ case SHOOT_WHITE_SMOKE: -+ final BlockFace face = (BlockFace) data; -+ Preconditions.checkArgument(face.isCartesian(), face + " isn't cartesian"); -+ datavalue = org.bukkit.craftbukkit.block.CraftBlock.blockFaceToNotch(face).get3DDataValue(); -+ break; -+ // Paper end - handle shoot white smoke event - case SMOKE: - switch ((BlockFace) data) { - case DOWN: -@@ -57,10 +72,25 @@ public class CraftEffect { - } - break; - case STEP_SOUND: -+ if (data instanceof Material) { // Paper - support BlockData - Preconditions.checkArgument(((Material) data).isBlock(), "Material %s is not a block!", data); - datavalue = Block.getId(CraftBlockType.bukkitToMinecraft((Material) data).defaultBlockState()); -+ // Paper start - support BlockData -+ break; -+ } -+ case PARTICLES_AND_SOUND_BRUSH_BLOCK_COMPLETE: -+ datavalue = Block.getId(((org.bukkit.craftbukkit.block.data.CraftBlockData) data).getState()); -+ // Paper end - break; - case COMPOSTER_FILL_ATTEMPT: -+ // Paper start - add missing effects -+ case TRIAL_SPAWNER_SPAWN: -+ case TRIAL_SPAWNER_SPAWN_MOB_AT: -+ case VAULT_ACTIVATE: -+ case VAULT_DEACTIVATE: -+ case TRIAL_SPAWNER_BECOME_OMINOUS: -+ case TRIAL_SPAWNER_SPAWN_ITEM: -+ // Paper end - add missing effects - datavalue = ((Boolean) data) ? 1 : 0; - break; - case BONE_MEAL_USE: -diff --git a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java -index 58cd4ecd5582c01b836814f4151df2538b93143c..cb50fca4867688d2572e8ab5b7fef7243c5b8ba9 100644 ---- a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java -+++ b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java -@@ -1372,7 +1372,7 @@ public class CraftWorld extends CraftRegionAccessor implements World { - public void playEffect(Location loc, Effect effect, T data, int radius) { - if (data != null) { - Preconditions.checkArgument(effect.getData() != null, "Effect.%s does not have a valid Data", effect); -- Preconditions.checkArgument(effect.getData().isAssignableFrom(data.getClass()), "%s data cannot be used for the %s effect", data.getClass().getName(), effect); -+ Preconditions.checkArgument(effect.isApplicable(data), "%s data cannot be used for the %s effect", data.getClass().getName(), effect); // Paper - } else { - // Special case: the axis is optional for ELECTRIC_SPARK - Preconditions.checkArgument(effect.getData() == null || effect == Effect.ELECTRIC_SPARK, "Wrong kind of data for the %s effect", effect); -diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java -index 62914bde6f6b045e6b3682581899d756d1b272f1..ccd81bc7279a92f78bb882c9443cf9ca90fd9acf 100644 ---- a/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java -+++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java -@@ -917,7 +917,7 @@ public class CraftPlayer extends CraftHumanEntity implements Player { - Preconditions.checkArgument(effect != null, "Effect cannot be null"); - if (data != null) { - Preconditions.checkArgument(effect.getData() != null, "Effect.%s does not have a valid Data", effect); -- Preconditions.checkArgument(effect.getData().isAssignableFrom(data.getClass()), "%s data cannot be used for the %s effect", data.getClass().getName(), effect); -+ Preconditions.checkArgument(effect.isApplicable(data), "%s data cannot be used for the %s effect", data.getClass().getName(), effect); // Paper - } else { - // Special case: the axis is optional for ELECTRIC_SPARK - Preconditions.checkArgument(effect.getData() == null || effect == Effect.ELECTRIC_SPARK, "Wrong kind of data for the %s effect", effect); -diff --git a/src/test/java/org/bukkit/EffectTest.java b/src/test/java/org/bukkit/EffectTest.java -new file mode 100644 -index 0000000000000000000000000000000000000000..3129212415c151aa028995b6e698f81b638e4c35 ---- /dev/null -+++ b/src/test/java/org/bukkit/EffectTest.java -@@ -0,0 +1,78 @@ -+package org.bukkit; -+ -+import com.google.common.base.Joiner; -+import java.lang.reflect.Field; -+import java.lang.reflect.Modifier; -+import java.util.ArrayList; -+import java.util.HashMap; -+import java.util.HashSet; -+import java.util.List; -+import java.util.Map; -+import java.util.Set; -+import net.minecraft.world.level.block.LevelEvent; -+import org.junit.jupiter.api.Test; -+ -+import static org.junit.jupiter.api.Assertions.assertNotNull; -+import static org.junit.jupiter.api.Assertions.assertNull; -+import static org.junit.jupiter.api.Assertions.assertTrue; -+import static org.junit.jupiter.api.Assertions.fail; -+ -+public class EffectTest { -+ -+ private static List collectNmsLevelEvents() throws ReflectiveOperationException { -+ final List events = new ArrayList<>(); -+ for (final Field field : LevelEvent.class.getFields()) { -+ if (Modifier.isStatic(field.getModifiers()) && Modifier.isFinal(field.getModifiers()) && field.getType() == int.class) { -+ events.add((int) field.get(null)); -+ } -+ } -+ return events; -+ } -+ -+ private static boolean isNotDeprecated(Effect effect) throws ReflectiveOperationException { -+ return !Effect.class.getDeclaredField(effect.name()).isAnnotationPresent(Deprecated.class); -+ } -+ -+ @Test -+ public void checkAllApiExists() throws ReflectiveOperationException { -+ Map toId = new HashMap<>(); -+ for (final Effect effect : Effect.values()) { -+ if (isNotDeprecated(effect)) { -+ final Effect put = toId.put(effect.getId(), effect); -+ assertNull(put, "duplicate API effect: " + put); -+ } -+ } -+ -+ final Set missingEvents = new HashSet<>(); -+ for (final Integer event : collectNmsLevelEvents()) { -+ if (toId.get(event) == null) { -+ missingEvents.add(event); -+ } -+ } -+ if (!missingEvents.isEmpty()) { -+ fail("Missing API Effects:\n" + Joiner.on("\n").join(missingEvents)); -+ } -+ } -+ -+ @Test -+ public void checkNoExtraApi() throws ReflectiveOperationException { -+ Map toId = new HashMap<>(); -+ for (final Effect effect : Effect.values()) { -+ if (isNotDeprecated(effect)) { -+ final Effect put = toId.put(effect.getId(), effect); -+ assertNull(put, "duplicate API effect: " + put); -+ } -+ } -+ -+ final List nmsEvents = collectNmsLevelEvents(); -+ final Set extraApiEffects = new HashSet<>(); -+ for (final Map.Entry entry : toId.entrySet()) { -+ if (!nmsEvents.contains(entry.getKey())) { -+ extraApiEffects.add(entry.getValue()); -+ } -+ } -+ if (!extraApiEffects.isEmpty()) { -+ fail("Extra API Effects:\n" + Joiner.on("\n").join(extraApiEffects)); -+ } -+ } -+} diff --git a/patches/server/0863-Configurable-Region-Compression-Format.patch b/patches/server/0855-Configurable-Region-Compression-Format.patch similarity index 100% rename from patches/server/0863-Configurable-Region-Compression-Format.patch rename to patches/server/0855-Configurable-Region-Compression-Format.patch diff --git a/patches/server/0856-API-for-an-entity-s-scoreboard-name.patch b/patches/server/0856-API-for-an-entity-s-scoreboard-name.patch deleted file mode 100644 index ac804a3ed5ae..000000000000 --- a/patches/server/0856-API-for-an-entity-s-scoreboard-name.patch +++ /dev/null @@ -1,24 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Jake Potrebic -Date: Sun, 9 Jul 2023 11:55:02 -0700 -Subject: [PATCH] API for an entity's scoreboard name - -Was obtainable through different methods, but you had to use different -methods depending on the implementation of Entity you were working with. - -diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java -index d3ff8015b2f713451b0aeb50e1b4cf81f2bcb7bc..3626fea17527da69e6fbee26f018f52ef036cf90 100644 ---- a/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java -+++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java -@@ -1263,4 +1263,11 @@ public abstract class CraftEntity implements org.bukkit.entity.Entity { - return !this.getHandle().level().noCollision(this.getHandle(), aabb); - } - // Paper end - Collision API -+ -+ // Paper start - entity scoreboard name -+ @Override -+ public String getScoreboardEntryName() { -+ return this.getHandle().getScoreboardName(); -+ } -+ // Paper end - entity scoreboard name - } diff --git a/patches/server/0856-Add-BlockFace-to-BlockDamageEvent.patch b/patches/server/0856-Add-BlockFace-to-BlockDamageEvent.patch new file mode 100644 index 000000000000..2dcf331517fc --- /dev/null +++ b/patches/server/0856-Add-BlockFace-to-BlockDamageEvent.patch @@ -0,0 +1,39 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: aerulion +Date: Mon, 21 Aug 2023 04:36:07 +0200 +Subject: [PATCH] Add BlockFace to BlockDamageEvent + + +diff --git a/src/main/java/net/minecraft/server/level/ServerPlayerGameMode.java b/src/main/java/net/minecraft/server/level/ServerPlayerGameMode.java +index 3bd4ab8161c29bb8df2ba496a4430393b6593476..0da6496c18341c01fc4551ead7e740a6037dcf31 100644 +--- a/src/main/java/net/minecraft/server/level/ServerPlayerGameMode.java ++++ b/src/main/java/net/minecraft/server/level/ServerPlayerGameMode.java +@@ -259,7 +259,7 @@ public class ServerPlayerGameMode { + } + return; + } +- org.bukkit.event.block.BlockDamageEvent blockEvent = CraftEventFactory.callBlockDamageEvent(this.player, pos, this.player.getInventory().getSelected(), f >= 1.0f); ++ org.bukkit.event.block.BlockDamageEvent blockEvent = CraftEventFactory.callBlockDamageEvent(this.player, pos, direction, this.player.getInventory().getSelected(), f >= 1.0f); // Paper - Add BlockFace to BlockDamageEvent + + if (blockEvent.isCancelled()) { + // Let the client know the block still exists +diff --git a/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java b/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java +index 449380c97dc332ecd43e308fcf110c9548930600..c5f9d9aeeb1fc61edb0e57cef51f5f6371861000 100644 +--- a/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java ++++ b/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java +@@ -660,13 +660,13 @@ public class CraftEventFactory { + /** + * BlockDamageEvent + */ +- public static BlockDamageEvent callBlockDamageEvent(ServerPlayer who, BlockPos pos, ItemStack itemstack, boolean instaBreak) { ++ public static BlockDamageEvent callBlockDamageEvent(ServerPlayer who, BlockPos pos, Direction direction, ItemStack itemstack, boolean instaBreak) { // Paper - Add BlockFace to BlockDamageEvent + Player player = who.getBukkitEntity(); + CraftItemStack itemInHand = CraftItemStack.asCraftMirror(itemstack); + + Block blockClicked = CraftBlock.at(who.level(), pos); + +- BlockDamageEvent event = new BlockDamageEvent(player, blockClicked, itemInHand, instaBreak); ++ BlockDamageEvent event = new BlockDamageEvent(player, blockClicked, CraftBlock.notchToBlockFace(direction), itemInHand, instaBreak); // Paper - Add BlockFace to BlockDamageEvent + player.getServer().getPluginManager().callEvent(event); + + return event; diff --git a/patches/server/0857-Deprecate-and-replace-methods-with-old-StructureType.patch b/patches/server/0857-Deprecate-and-replace-methods-with-old-StructureType.patch deleted file mode 100644 index b9b8b8b88ebd..000000000000 --- a/patches/server/0857-Deprecate-and-replace-methods-with-old-StructureType.patch +++ /dev/null @@ -1,54 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Jake Potrebic -Date: Sat, 10 Dec 2022 17:52:38 -0800 -Subject: [PATCH] Deprecate and replace methods with old StructureType - - -diff --git a/src/main/java/org/bukkit/craftbukkit/CraftServer.java b/src/main/java/org/bukkit/craftbukkit/CraftServer.java -index 69df0cf285a5af1011cc769a433683489ff3dded..8b871d29d883119cd8ad9ca134f4c1fce9362705 100644 ---- a/src/main/java/org/bukkit/craftbukkit/CraftServer.java -+++ b/src/main/java/org/bukkit/craftbukkit/CraftServer.java -@@ -2003,6 +2003,11 @@ public final class CraftServer implements Server { - - ServerLevel worldServer = ((CraftWorld) world).getHandle(); - Location structureLocation = world.locateNearestStructure(location, structureType, radius, findUnexplored); -+ // Paper start - don't throw NPE -+ if (structureLocation == null) { -+ throw new IllegalStateException("Could not find a structure for " + structureType); -+ } -+ // Paper end - BlockPos structurePosition = CraftLocation.toBlockPosition(structureLocation); - - // Create map with trackPlayer = true, unlimitedTracking = true -@@ -2013,6 +2018,31 @@ public final class CraftServer implements Server { - - return CraftItemStack.asBukkitCopy(stack); - } -+ // Paper start - copied from above (uses un-deprecated StructureType type) -+ @Override -+ public ItemStack createExplorerMap(World world, Location location, org.bukkit.generator.structure.StructureType structureType, org.bukkit.map.MapCursor.Type mapIcon, int radius, boolean findUnexplored) { -+ Preconditions.checkArgument(world != null, "World cannot be null"); -+ Preconditions.checkArgument(location != null, "Location cannot be null"); -+ Preconditions.checkArgument(structureType != null, "StructureType cannot be null"); -+ Preconditions.checkArgument(mapIcon != null, "mapIcon cannot be null"); -+ -+ ServerLevel worldServer = ((CraftWorld) world).getHandle(); -+ final org.bukkit.util.StructureSearchResult structureSearchResult = world.locateNearestStructure(location, structureType, radius, findUnexplored); -+ if (structureSearchResult == null) { -+ return null; -+ } -+ Location structureLocation = structureSearchResult.getLocation(); -+ BlockPos structurePosition = new BlockPos(structureLocation.getBlockX(), structureLocation.getBlockY(), structureLocation.getBlockZ()); -+ -+ // Create map with showIcons = true, unlimitedTracking = true -+ net.minecraft.world.item.ItemStack stack = MapItem.create(worldServer, structurePosition.getX(), structurePosition.getZ(), MapView.Scale.NORMAL.getValue(), true, true); -+ MapItem.renderBiomePreviewMap(worldServer, stack); -+ // "+" map ID taken from VillagerTrades$TreasureMapForEmeralds -+ MapItem.getSavedData(stack, worldServer).addTargetDecoration(stack, structurePosition, "+", CraftMapCursor.CraftType.bukkitToMinecraftHolder(mapIcon)); -+ -+ return CraftItemStack.asBukkitCopy(stack); -+ } -+ // Paper end - - @Override - public void shutdown() { diff --git a/patches/server/0857-Fix-NPE-on-Boat-getStatus.patch b/patches/server/0857-Fix-NPE-on-Boat-getStatus.patch new file mode 100644 index 000000000000..19f832054de0 --- /dev/null +++ b/patches/server/0857-Fix-NPE-on-Boat-getStatus.patch @@ -0,0 +1,32 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: LemonCaramel +Date: Mon, 10 Apr 2023 20:48:26 +0900 +Subject: [PATCH] Fix NPE on Boat getStatus + +Boat status is null until the entity is added to the world and the tick() method is called. + +== AT == +public net.minecraft.world.entity.vehicle.AbstractBoat getStatus()Lnet/minecraft/world/entity/vehicle/AbstractBoat$Status; + +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftBoat.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftBoat.java +index 5d51a49228eaee94f91cd04843e27c7918ca8796..ff82dc98478a8ac564bdbf4ec58da612e5f6c2ce 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftBoat.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftBoat.java +@@ -87,6 +87,17 @@ public abstract class CraftBoat extends CraftVehicle implements Boat { + + @Override + public Status getStatus() { ++ // Paper start - Fix NPE on Boat getStatus ++ final net.minecraft.world.entity.vehicle.AbstractBoat handle = this.getHandle(); ++ if (handle.status == null) { ++ if (handle.valid) { ++ // Don't actually set the status because it would skew the old status check in the next tick ++ return CraftBoat.boatStatusFromNms(handle.getStatus()); ++ } else { ++ return Status.NOT_IN_WORLD; ++ } ++ } ++ // Paper end - Fix NPE on Boat getStatus + return CraftBoat.boatStatusFromNms(this.getHandle().status); + } + diff --git a/patches/server/0858-Don-t-tab-complete-namespaced-commands-if-send-names.patch b/patches/server/0858-Don-t-tab-complete-namespaced-commands-if-send-names.patch deleted file mode 100644 index 5725b805f02a..000000000000 --- a/patches/server/0858-Don-t-tab-complete-namespaced-commands-if-send-names.patch +++ /dev/null @@ -1,28 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: EpicPlayerA10 -Date: Sun, 18 Jun 2023 12:38:24 +0200 -Subject: [PATCH] Don't tab-complete namespaced commands if send-namespaced is - false - -Tab-complete packet is supposed to tab-complete args for commands, but -it also can suggest commands like in version 1.12.2 or lower. - -This patch prevents server from sending namespaced commands when player -requests tab-complete only commands. - -diff --git a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java -index adcfa6a783222482ce7ec5c7805fe44dd24bf7d0..7750660defe490bcdc24a3b4ad4f495c8a703fed 100644 ---- a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java -+++ b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java -@@ -820,6 +820,11 @@ public class ServerGamePacketListenerImpl extends ServerCommonPacketListenerImpl - ParseResults parseresults = this.server.getCommands().getDispatcher().parse(stringreader, this.player.createCommandSourceStack()); - - this.server.getCommands().getDispatcher().getCompletionSuggestions(parseresults).thenAccept((suggestions) -> { -+ // Paper start - Don't tab-complete namespaced commands if send-namespaced is false -+ if (!org.spigotmc.SpigotConfig.sendNamespaced && suggestions.getRange().getStart() <= 1) { -+ suggestions.getList().removeIf(suggestion -> suggestion.getText().contains(":")); -+ } -+ // Paper end - Don't tab-complete namespaced commands if send-namespaced is false - // Paper start - Brigadier API - com.destroystokyo.paper.event.brigadier.AsyncPlayerSendSuggestionsEvent suggestEvent = new com.destroystokyo.paper.event.brigadier.AsyncPlayerSendSuggestionsEvent(this.getCraftPlayer(), suggestions, packet.getCommand()); - suggestEvent.setCancelled(suggestions.isEmpty()); diff --git a/patches/server/0858-Expand-Pose-API.patch b/patches/server/0858-Expand-Pose-API.patch new file mode 100644 index 000000000000..118d44664465 --- /dev/null +++ b/patches/server/0858-Expand-Pose-API.patch @@ -0,0 +1,51 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: SoSeDiK +Date: Wed, 11 Jan 2023 20:59:01 +0200 +Subject: [PATCH] Expand Pose API + + +diff --git a/src/main/java/net/minecraft/world/entity/Entity.java b/src/main/java/net/minecraft/world/entity/Entity.java +index cc4906ed08e84beaca0ba93200693a0a3730fb4c..67dda7d37cc4af2f09734da5653e8fa33098c7db 100644 +--- a/src/main/java/net/minecraft/world/entity/Entity.java ++++ b/src/main/java/net/minecraft/world/entity/Entity.java +@@ -427,6 +427,7 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess + @javax.annotation.Nullable + private UUID originWorld; + public boolean freezeLocked = false; // Paper - Freeze Tick Lock API ++ public boolean fixedPose = false; // Paper - Expand Pose API + + public void setOrigin(@javax.annotation.Nonnull Location location) { + this.origin = location.toVector(); +@@ -625,6 +626,7 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess + public void onRemoval(Entity.RemovalReason reason) {} + + public void setPose(net.minecraft.world.entity.Pose pose) { ++ if (this.fixedPose) return; // Paper - Expand Pose API + // CraftBukkit start + if (pose == this.getPose()) { + return; +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java +index dd84f7d6874a799aa09349fe0a1bd6ebb4cea2dd..11a08ff211a9a32a825519ddb1736e02cf7f685e 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java +@@ -900,6 +900,20 @@ public abstract class CraftEntity implements org.bukkit.entity.Entity { + public boolean isSneaking() { + return this.getHandle().isShiftKeyDown(); + } ++ ++ @Override ++ public void setPose(Pose pose, boolean fixed) { ++ Preconditions.checkNotNull(pose, "Pose cannot be null"); ++ final Entity handle = this.getHandle(); ++ handle.fixedPose = false; ++ handle.setPose(net.minecraft.world.entity.Pose.values()[pose.ordinal()]); ++ handle.fixedPose = fixed; ++ } ++ ++ @Override ++ public boolean hasFixedPose() { ++ return this.getHandle().fixedPose; ++ } + // Paper end + + @Override diff --git a/patches/server/0859-More-DragonBattle-API.patch b/patches/server/0859-More-DragonBattle-API.patch new file mode 100644 index 000000000000..a752a4de8170 --- /dev/null +++ b/patches/server/0859-More-DragonBattle-API.patch @@ -0,0 +1,91 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Jake Potrebic +Date: Sun, 18 Dec 2022 13:40:05 -0800 +Subject: [PATCH] More DragonBattle API + +== AT == +public net.minecraft.world.level.dimension.end.EndDragonFight GATEWAY_COUNT +public net.minecraft.world.level.dimension.end.EndDragonFight gateways +public net.minecraft.world.level.dimension.end.EndDragonFight respawnCrystals +public net.minecraft.world.level.dimension.end.EndDragonFight spawnNewGateway(Lnet/minecraft/core/BlockPos;)V + +diff --git a/src/main/java/net/minecraft/world/level/dimension/end/EndDragonFight.java b/src/main/java/net/minecraft/world/level/dimension/end/EndDragonFight.java +index 39b1d6ee10d4a10ab4fb339621ca80f27d383324..8ec6b33126096fb9e1de5f41290097e004d7a455 100644 +--- a/src/main/java/net/minecraft/world/level/dimension/end/EndDragonFight.java ++++ b/src/main/java/net/minecraft/world/level/dimension/end/EndDragonFight.java +@@ -442,6 +442,24 @@ public class EndDragonFight { + this.gateways.clear(); + } + ++ // Paper start - More DragonBattle API ++ public boolean spawnNewGatewayIfPossible() { ++ if (!this.gateways.isEmpty()) { ++ this.spawnNewGateway(); ++ return true; ++ } ++ return false; ++ } ++ ++ public List getSpikeCrystals() { ++ final List endCrystals = new java.util.ArrayList<>(); ++ for (final SpikeFeature.EndSpike spike : SpikeFeature.getSpikesForLevel(this.level)) { ++ endCrystals.addAll(this.level.getEntitiesOfClass(EndCrystal.class, spike.getTopBoundingBox())); ++ } ++ return endCrystals; ++ } ++ // Paper end - More DragonBattle API ++ + private void spawnNewGateway() { + if (!this.gateways.isEmpty()) { + int i = (Integer) this.gateways.remove(this.gateways.size() - 1); +diff --git a/src/main/java/org/bukkit/craftbukkit/boss/CraftDragonBattle.java b/src/main/java/org/bukkit/craftbukkit/boss/CraftDragonBattle.java +index dc7fdc120f4317ee1b7935ef226eddb0406aee6e..6bfabb38b51115beb2a65a165f235347838b6006 100644 +--- a/src/main/java/org/bukkit/craftbukkit/boss/CraftDragonBattle.java ++++ b/src/main/java/org/bukkit/craftbukkit/boss/CraftDragonBattle.java +@@ -137,4 +137,46 @@ public class CraftDragonBattle implements DragonBattle { + private DragonRespawnAnimation toNMSRespawnPhase(RespawnPhase phase) { + return (phase != RespawnPhase.NONE) ? DragonRespawnAnimation.values()[phase.ordinal()] : null; + } ++ // Paper start - More DragonBattle API ++ @Override ++ public int getGatewayCount() { ++ return EndDragonFight.GATEWAY_COUNT - this.handle.gateways.size(); ++ } ++ ++ @Override ++ public boolean spawnNewGateway() { ++ return this.handle.spawnNewGatewayIfPossible(); ++ } ++ ++ @Override ++ public void spawnNewGateway(final io.papermc.paper.math.Position position) { ++ this.handle.spawnNewGateway(io.papermc.paper.util.MCUtil.toBlockPos(position)); ++ } ++ ++ @Override ++ public java.util.List getRespawnCrystals() { ++ if (this.handle.respawnCrystals == null) { ++ return java.util.Collections.emptyList(); ++ } ++ ++ final java.util.List enderCrystals = new java.util.ArrayList<>(); ++ for (final net.minecraft.world.entity.boss.enderdragon.EndCrystal endCrystal : this.handle.respawnCrystals) { ++ if (!endCrystal.isRemoved() && endCrystal.isAlive() && endCrystal.valid) { ++ enderCrystals.add(((org.bukkit.entity.EnderCrystal) endCrystal.getBukkitEntity())); ++ } ++ } ++ return java.util.Collections.unmodifiableList(enderCrystals); ++ } ++ ++ @Override ++ public java.util.List getHealingCrystals() { ++ final java.util.List enderCrystals = new java.util.ArrayList<>(); ++ for (final net.minecraft.world.entity.boss.enderdragon.EndCrystal endCrystal : this.handle.getSpikeCrystals()) { ++ if (!endCrystal.isRemoved() && endCrystal.isAlive() && endCrystal.valid) { ++ enderCrystals.add(((org.bukkit.entity.EnderCrystal) endCrystal.getBukkitEntity())); ++ } ++ } ++ return java.util.Collections.unmodifiableList(enderCrystals); ++ } ++ // Paper end - More DragonBattle API + } diff --git a/patches/server/0859-Properly-handle-BlockBreakEvent-isDropItems.patch b/patches/server/0859-Properly-handle-BlockBreakEvent-isDropItems.patch deleted file mode 100644 index 4cd189674296..000000000000 --- a/patches/server/0859-Properly-handle-BlockBreakEvent-isDropItems.patch +++ /dev/null @@ -1,162 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Jake Potrebic -Date: Sat, 4 Mar 2023 10:52:52 -0800 -Subject: [PATCH] Properly handle BlockBreakEvent#isDropItems - -Setting whether a block break dropped items controlled -far more than just whether blocks dropped, like stat increases -food consumption, turtle egg count decreases, ice to water -conversions and beehive releases - -diff --git a/src/main/java/net/minecraft/server/level/ServerPlayerGameMode.java b/src/main/java/net/minecraft/server/level/ServerPlayerGameMode.java -index 717d015dd4637dd9d568b751be1dc1046b0a0560..c680f081ba548f84f07a968a46811090c53e57e3 100644 ---- a/src/main/java/net/minecraft/server/level/ServerPlayerGameMode.java -+++ b/src/main/java/net/minecraft/server/level/ServerPlayerGameMode.java -@@ -439,8 +439,8 @@ public class ServerPlayerGameMode { - isCorrectTool = flag1; // Paper - Trigger bee_nest_destroyed trigger in the correct place - - itemstack.mineBlock(this.level, iblockdata1, pos, this.player); -- if (flag && flag1 && event.isDropItems()) { // CraftBukkit - Check if block should drop items -- block.playerDestroy(this.level, this.player, pos, iblockdata1, tileentity, itemstack1); -+ if (flag && flag1/* && event.isDropItems() */) { // CraftBukkit - Check if block should drop items // Paper - fix drops not preventing stats/food exhaustion -+ block.playerDestroy(this.level, this.player, pos, iblockdata1, tileentity, itemstack1, event.isDropItems(), false); // Paper - fix drops not preventing stats/food exhaustion - } - - // return true; // CraftBukkit -diff --git a/src/main/java/net/minecraft/world/level/block/BeehiveBlock.java b/src/main/java/net/minecraft/world/level/block/BeehiveBlock.java -index c75227b2ea165dcd65c203e60157ac7cdebd4bc6..4669b1877be5eecf6738eefd81a35bde531759d6 100644 ---- a/src/main/java/net/minecraft/world/level/block/BeehiveBlock.java -+++ b/src/main/java/net/minecraft/world/level/block/BeehiveBlock.java -@@ -86,8 +86,8 @@ public class BeehiveBlock extends BaseEntityBlock { - } - - @Override -- public void playerDestroy(Level world, Player player, BlockPos pos, BlockState state, @Nullable BlockEntity blockEntity, ItemStack tool) { -- super.playerDestroy(world, player, pos, state, blockEntity, tool); -+ public void playerDestroy(Level world, Player player, BlockPos pos, BlockState state, @Nullable BlockEntity blockEntity, ItemStack tool, boolean includeDrops, boolean dropExp) { // Paper - fix drops not preventing stats/food exhaustion -+ super.playerDestroy(world, player, pos, state, blockEntity, tool, includeDrops, dropExp); // Paper - fix drops not preventing stats/food exhaustion - if (!world.isClientSide && blockEntity instanceof BeehiveBlockEntity tileentitybeehive) { - if (!EnchantmentHelper.hasTag(tool, EnchantmentTags.PREVENTS_BEE_SPAWNS_WHEN_MINING)) { - tileentitybeehive.emptyAllLivingFromHive(player, state, BeehiveBlockEntity.BeeReleaseStatus.EMERGENCY); -diff --git a/src/main/java/net/minecraft/world/level/block/Block.java b/src/main/java/net/minecraft/world/level/block/Block.java -index 232e6216dc36aa698047fc0badf78c347414b3a5..c083dc8b2a69c3747b250d13f1a28ad22b5e6119 100644 ---- a/src/main/java/net/minecraft/world/level/block/Block.java -+++ b/src/main/java/net/minecraft/world/level/block/Block.java -@@ -402,10 +402,18 @@ public class Block extends BlockBehaviour implements ItemLike { - return this.defaultBlockState(); - } - -+ @io.papermc.paper.annotation.DoNotUse @Deprecated // Paper - fix drops not preventing stats/food exhaustion - public void playerDestroy(Level world, Player player, BlockPos pos, BlockState state, @Nullable BlockEntity blockEntity, ItemStack tool) { -+ // Paper start - fix drops not preventing stats/food exhaustion -+ this.playerDestroy(world, player, pos, state, blockEntity, tool, true, true); -+ } -+ public void playerDestroy(Level world, Player player, BlockPos pos, BlockState state, @Nullable BlockEntity blockEntity, ItemStack tool, boolean includeDrops, boolean dropExp) { -+ // Paper end - fix drops not preventing stats/food exhaustion - player.awardStat(Stats.BLOCK_MINED.get(this)); - player.causeFoodExhaustion(0.005F, org.bukkit.event.entity.EntityExhaustionEvent.ExhaustionReason.BLOCK_MINED); // CraftBukkit - EntityExhaustionEvent -+ if (includeDrops) { // Paper - fix drops not preventing stats/food exhaustion - Block.dropResources(state, world, pos, blockEntity, player, tool); -+ } // Paper - fix drops not preventing stats/food exhaustion - } - - public void setPlacedBy(Level world, BlockPos pos, BlockState state, @Nullable LivingEntity placer, ItemStack itemStack) {} -diff --git a/src/main/java/net/minecraft/world/level/block/DoublePlantBlock.java b/src/main/java/net/minecraft/world/level/block/DoublePlantBlock.java -index 7fdf744a2be55313cc75c1322f6534f55cf463f5..f446c40c4d90307c568faa2866800f5326634df6 100644 ---- a/src/main/java/net/minecraft/world/level/block/DoublePlantBlock.java -+++ b/src/main/java/net/minecraft/world/level/block/DoublePlantBlock.java -@@ -96,8 +96,8 @@ public class DoublePlantBlock extends BushBlock { - } - - @Override -- public void playerDestroy(Level world, Player player, BlockPos pos, BlockState state, @Nullable BlockEntity blockEntity, ItemStack tool) { -- super.playerDestroy(world, player, pos, Blocks.AIR.defaultBlockState(), blockEntity, tool); -+ public void playerDestroy(Level world, Player player, BlockPos pos, BlockState state, @Nullable BlockEntity blockEntity, ItemStack tool, boolean includeDrops, boolean dropExp) { // Paper - fix drops not preventing stats/food exhaustion -+ super.playerDestroy(world, player, pos, Blocks.AIR.defaultBlockState(), blockEntity, tool, includeDrops, dropExp); // Paper - fix drops not preventing stats/food exhaustion - } - - protected static void preventDropFromBottomPart(Level world, BlockPos pos, BlockState state, Player player) { -diff --git a/src/main/java/net/minecraft/world/level/block/IceBlock.java b/src/main/java/net/minecraft/world/level/block/IceBlock.java -index e862814c1e54776f11050623b52476accc2f1f79..ac775afb265430ac202cfa3900a036d11a308b1e 100644 ---- a/src/main/java/net/minecraft/world/level/block/IceBlock.java -+++ b/src/main/java/net/minecraft/world/level/block/IceBlock.java -@@ -33,8 +33,8 @@ public class IceBlock extends HalfTransparentBlock { - } - - @Override -- public void playerDestroy(Level world, Player player, BlockPos pos, BlockState state, @Nullable BlockEntity blockEntity, ItemStack tool) { -- super.playerDestroy(world, player, pos, state, blockEntity, tool); -+ public void playerDestroy(Level world, Player player, BlockPos pos, BlockState state, @Nullable BlockEntity blockEntity, ItemStack tool, boolean includeDrops, boolean dropExp) { // Paper - fix drops not preventing stats/food exhaustion -+ super.playerDestroy(world, player, pos, state, blockEntity, tool, includeDrops, dropExp); // Paper - fix drops not preventing stats/food exhaustion - // Paper start - Improve Block#breakNaturally API - this.afterDestroy(world, pos, tool); - } -diff --git a/src/main/java/net/minecraft/world/level/block/TurtleEggBlock.java b/src/main/java/net/minecraft/world/level/block/TurtleEggBlock.java -index bdcb732a31fff0cfc2119132079ce197c7a77c9a..a6f408e56fa6c9de82fd93555fe21e1b11ce1022 100644 ---- a/src/main/java/net/minecraft/world/level/block/TurtleEggBlock.java -+++ b/src/main/java/net/minecraft/world/level/block/TurtleEggBlock.java -@@ -174,8 +174,8 @@ public class TurtleEggBlock extends Block { - } - - @Override -- public void playerDestroy(Level world, Player player, BlockPos pos, BlockState state, @Nullable BlockEntity blockEntity, ItemStack tool) { -- super.playerDestroy(world, player, pos, state, blockEntity, tool); -+ public void playerDestroy(Level world, Player player, BlockPos pos, BlockState state, @Nullable BlockEntity blockEntity, ItemStack tool, boolean includeDrops, boolean dropExp) { // Paper - fix drops not preventing stats/food exhaustion -+ super.playerDestroy(world, player, pos, state, blockEntity, tool, includeDrops, dropExp); // Paper - fix drops not preventing stats/food exhaustion - this.decreaseEggs(world, pos, state); - } - -diff --git a/src/test/java/io/papermc/paper/world/block/BlockPlayerDestroyOverrideTest.java b/src/test/java/io/papermc/paper/world/block/BlockPlayerDestroyOverrideTest.java -new file mode 100644 -index 0000000000000000000000000000000000000000..22145bf698b3d1ff0a07a3aaa8d55a19905f99ad ---- /dev/null -+++ b/src/test/java/io/papermc/paper/world/block/BlockPlayerDestroyOverrideTest.java -@@ -0,0 +1,48 @@ -+package io.papermc.paper.world.block; -+ -+import io.github.classgraph.ClassGraph; -+import io.github.classgraph.ClassInfo; -+import io.github.classgraph.MethodInfo; -+import io.github.classgraph.MethodInfoList; -+import io.github.classgraph.MethodParameterInfo; -+import io.github.classgraph.ScanResult; -+import java.util.ArrayList; -+import java.util.List; -+import java.util.stream.Stream; -+import org.bukkit.support.environment.Normal; -+import org.junit.jupiter.params.ParameterizedTest; -+import org.junit.jupiter.params.provider.MethodSource; -+ -+import static org.junit.jupiter.api.Assertions.assertEquals; -+ -+@Normal -+public class BlockPlayerDestroyOverrideTest { -+ -+ public static Stream parameters() { -+ final List classInfo = new ArrayList<>(); -+ try (ScanResult scanResult = new ClassGraph() -+ .enableClassInfo() -+ .enableMethodInfo() -+ .whitelistPackages("net.minecraft") -+ .scan() -+ ) { -+ for (final ClassInfo subclass : scanResult.getSubclasses("net.minecraft.world.level.block.Block")) { -+ final MethodInfoList playerDestroy = subclass.getDeclaredMethodInfo("playerDestroy"); -+ if (!playerDestroy.isEmpty()) { -+ classInfo.add(subclass); -+ } -+ } -+ } -+ return classInfo.stream(); -+ } -+ -+ @ParameterizedTest -+ @MethodSource("parameters") -+ public void checkPlayerDestroyOverrides(ClassInfo overridesPlayerDestroy) { -+ final MethodInfoList playerDestroy = overridesPlayerDestroy.getDeclaredMethodInfo("playerDestroy"); -+ assertEquals(1, playerDestroy.size(), overridesPlayerDestroy.getName() + " has multiple playerDestroy methods"); -+ final MethodInfo next = playerDestroy.iterator().next(); -+ final MethodParameterInfo[] parameterInfo = next.getParameterInfo(); -+ assertEquals("boolean", parameterInfo[parameterInfo.length - 1].getTypeDescriptor().toStringWithSimpleNames(), overridesPlayerDestroy.getName() + " needs to change its override of playerDestroy"); -+ } -+} diff --git a/patches/server/0860-Add-PlayerPickItemEvent.patch b/patches/server/0860-Add-PlayerPickItemEvent.patch new file mode 100644 index 000000000000..0290c1d95099 --- /dev/null +++ b/patches/server/0860-Add-PlayerPickItemEvent.patch @@ -0,0 +1,48 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: RodneyMKay <36546810+RodneyMKay@users.noreply.github.com> +Date: Wed, 8 Sep 2021 21:34:01 +0200 +Subject: [PATCH] Add PlayerPickItemEvent + + +diff --git a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java +index 688d6e51bc8b844ba7648cf1aed1bdd9629c1675..b840f7aac9c830b8aa0aa133bf43f87dfc598b2c 100644 +--- a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java ++++ b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java +@@ -947,7 +947,17 @@ public class ServerGamePacketListenerImpl extends ServerCommonPacketListenerImpl + this.disconnect(Component.literal("Invalid hotbar selection (Hacking?)"), org.bukkit.event.player.PlayerKickEvent.Cause.ILLEGAL_ACTION); // Paper - kick event cause + return; + } +- this.player.getInventory().pickSlot(packet.getSlot()); // Paper - Diff above if changed ++ // Paper start - Add PlayerPickItemEvent ++ // this.player.getInventory().pickSlot(packet.getSlot()); // Paper - Diff above if changed - moved down ++ Player bukkitPlayer = this.player.getBukkitEntity(); ++ int targetSlot = this.player.getInventory().getSuitableHotbarSlot(); ++ int sourceSlot = packet.getSlot(); ++ ++ io.papermc.paper.event.player.PlayerPickItemEvent event = new io.papermc.paper.event.player.PlayerPickItemEvent(bukkitPlayer, targetSlot, sourceSlot); ++ if (!event.callEvent()) return; ++ // Paper end - Add PlayerPickItemEvent ++ ++ this.player.getInventory().pickSlot(event.getSourceSlot(), event.getTargetSlot()); // Paper - Add PlayerPickItemEvent + // Paper end - validate pick item position + int i = this.player.getInventory().selected; + +diff --git a/src/main/java/net/minecraft/world/entity/player/Inventory.java b/src/main/java/net/minecraft/world/entity/player/Inventory.java +index 8bdc2dd7e6b6824f4df5dce8bff5e87fa73d3d3a..aaff1592876ac4a967e4fd47e4b6619a17d57867 100644 +--- a/src/main/java/net/minecraft/world/entity/player/Inventory.java ++++ b/src/main/java/net/minecraft/world/entity/player/Inventory.java +@@ -170,7 +170,13 @@ public class Inventory implements Container, Nameable { + } + + public void pickSlot(int slot) { +- this.selected = this.getSuitableHotbarSlot(); ++ // Paper start - Add PlayerPickItemEvent ++ pickSlot(slot, this.getSuitableHotbarSlot()); ++ } ++ ++ public void pickSlot(int slot, int targetSlot) { ++ this.selected = targetSlot; ++ // Paper end - Add PlayerPickItemEvent + ItemStack itemstack = (ItemStack) this.items.get(this.selected); + + this.items.set(this.selected, (ItemStack) this.items.get(slot)); diff --git a/patches/server/0860-Fire-entity-death-event-for-ender-dragon.patch b/patches/server/0860-Fire-entity-death-event-for-ender-dragon.patch deleted file mode 100644 index 66624e354c18..000000000000 --- a/patches/server/0860-Fire-entity-death-event-for-ender-dragon.patch +++ /dev/null @@ -1,26 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Trevor Bedson <78997566+Prorickey@users.noreply.github.com> -Date: Fri, 14 Jul 2023 20:47:02 -0400 -Subject: [PATCH] Fire entity death event for ender dragon - - -diff --git a/src/main/java/net/minecraft/world/entity/boss/enderdragon/EnderDragon.java b/src/main/java/net/minecraft/world/entity/boss/enderdragon/EnderDragon.java -index 6306f7925ac4852d8eed7508a11764c636dd7d36..5868d2e9e05a698c77117cf87c79b636b50fe289 100644 ---- a/src/main/java/net/minecraft/world/entity/boss/enderdragon/EnderDragon.java -+++ b/src/main/java/net/minecraft/world/entity/boss/enderdragon/EnderDragon.java -@@ -657,6 +657,15 @@ public class EnderDragon extends Mob implements Enemy { - - @Override - public void kill() { -+ // Paper start - Fire entity death event -+ this.silentDeath = true; -+ org.bukkit.event.entity.EntityDeathEvent deathEvent = org.bukkit.craftbukkit.event.CraftEventFactory.callEntityDeathEvent(this, this.damageSources().genericKill()); -+ if (deathEvent.isCancelled()) { -+ this.silentDeath = false; // Reset to default if event was cancelled -+ return; -+ } -+ // Paper end - Fire entity death event -+ - this.remove(Entity.RemovalReason.KILLED, EntityRemoveEvent.Cause.DEATH); // CraftBukkit - add Bukkit remove cause - this.gameEvent(GameEvent.ENTITY_DIE); - if (this.dragonFight != null) { diff --git a/patches/server/0861-Allow-trident-custom-damage.patch b/patches/server/0861-Allow-trident-custom-damage.patch new file mode 100644 index 000000000000..ee7ac1a043fa --- /dev/null +++ b/patches/server/0861-Allow-trident-custom-damage.patch @@ -0,0 +1,39 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Lulu13022002 <41980282+Lulu13022002@users.noreply.github.com> +Date: Tue, 12 Jul 2022 18:01:14 +0200 +Subject: [PATCH] Allow trident custom damage + + +diff --git a/src/main/java/net/minecraft/world/entity/projectile/ThrownTrident.java b/src/main/java/net/minecraft/world/entity/projectile/ThrownTrident.java +index 248410547de380e3195bbdc8b7b39cff908a0c32..322733266fdca8ce43434a8ffea304c51794bcbb 100644 +--- a/src/main/java/net/minecraft/world/entity/projectile/ThrownTrident.java ++++ b/src/main/java/net/minecraft/world/entity/projectile/ThrownTrident.java +@@ -36,16 +36,19 @@ public class ThrownTrident extends AbstractArrow { + + public ThrownTrident(EntityType type, Level world) { + super(type, world); ++ this.setBaseDamage(net.minecraft.world.item.TridentItem.BASE_DAMAGE); // Paper - Allow trident custom damage + } + + public ThrownTrident(Level world, LivingEntity owner, ItemStack stack) { + super(EntityType.TRIDENT, owner, world, stack, (ItemStack) null); ++ this.setBaseDamage(net.minecraft.world.item.TridentItem.BASE_DAMAGE); // Paper - Allow trident custom damage + this.entityData.set(ThrownTrident.ID_LOYALTY, this.getLoyaltyFromItem(stack)); + this.entityData.set(ThrownTrident.ID_FOIL, stack.hasFoil()); + } + + public ThrownTrident(Level world, double x, double y, double z, ItemStack stack) { + super(EntityType.TRIDENT, x, y, z, world, stack, stack); ++ this.setBaseDamage(net.minecraft.world.item.TridentItem.BASE_DAMAGE); // Paper - Allow trident custom damage + this.entityData.set(ThrownTrident.ID_LOYALTY, this.getLoyaltyFromItem(stack)); + this.entityData.set(ThrownTrident.ID_FOIL, stack.hasFoil()); + } +@@ -136,7 +139,7 @@ public class ThrownTrident extends AbstractArrow { + @Override + protected void onHitEntity(EntityHitResult entityHitResult) { + Entity entity = entityHitResult.getEntity(); +- float f = 8.0F; ++ float f = (float) this.getBaseDamage(); // Paper - Allow trident custom damage + Entity entity1 = this.getOwner(); + DamageSource damagesource = this.damageSources().trident(this, (Entity) (entity1 == null ? this : entity1)); + Level world = this.level(); diff --git a/patches/server/0861-Configurable-entity-tracking-range-by-Y-coordinate.patch b/patches/server/0861-Configurable-entity-tracking-range-by-Y-coordinate.patch deleted file mode 100644 index 4b7085d8317b..000000000000 --- a/patches/server/0861-Configurable-entity-tracking-range-by-Y-coordinate.patch +++ /dev/null @@ -1,30 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: ruViolence <78062896+ruViolence@users.noreply.github.com> -Date: Tue, 27 Jun 2023 15:38:18 +0800 -Subject: [PATCH] Configurable entity tracking range by Y coordinate - -Options to configure entity tracking by Y coordinate, also for each entity category. - -diff --git a/src/main/java/net/minecraft/server/level/ChunkMap.java b/src/main/java/net/minecraft/server/level/ChunkMap.java -index dca6087dc4e1c177c3dfdae01f140cf80c179803..57af5d1986021c9a135599c6f55b091aebd3bab7 100644 ---- a/src/main/java/net/minecraft/server/level/ChunkMap.java -+++ b/src/main/java/net/minecraft/server/level/ChunkMap.java -@@ -1524,7 +1524,17 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider - double d0 = (double) Math.min(this.getEffectiveRange(), i * 16); - double d1 = vec3d.x * vec3d.x + vec3d.z * vec3d.z; - double d2 = d0 * d0; -- boolean flag = d1 <= d2 && this.entity.broadcastToPlayer(player) && ChunkMap.this.isChunkTracked(player, this.entity.chunkPosition().x, this.entity.chunkPosition().z); -+ // Paper start - Configurable entity tracking range by Y -+ boolean flag = d1 <= d2; -+ if (flag && level.paperConfig().entities.trackingRangeY.enabled) { -+ double rangeY = level.paperConfig().entities.trackingRangeY.get(this.entity, -1); -+ if (rangeY != -1) { -+ double vec3d_dy = player.getY() - this.entity.getY(); -+ flag = vec3d_dy * vec3d_dy <= rangeY * rangeY; -+ } -+ } -+ flag = flag && this.entity.broadcastToPlayer(player) && ChunkMap.this.isChunkTracked(player, this.entity.chunkPosition().x, this.entity.chunkPosition().z); -+ // Paper end - Configurable entity tracking range by Y - - // CraftBukkit start - respect vanish API - if (!player.getBukkitEntity().canSee(this.entity.getBukkitEntity())) { diff --git a/patches/server/0862-Add-Listing-API-for-Player.patch b/patches/server/0862-Add-Listing-API-for-Player.patch deleted file mode 100644 index bd96c74d581e..000000000000 --- a/patches/server/0862-Add-Listing-API-for-Player.patch +++ /dev/null @@ -1,183 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Corey Shupe -Date: Wed, 11 Jan 2023 16:40:39 -0500 -Subject: [PATCH] Add Listing API for Player - - -diff --git a/src/main/java/net/minecraft/network/protocol/game/ClientboundPlayerInfoUpdatePacket.java b/src/main/java/net/minecraft/network/protocol/game/ClientboundPlayerInfoUpdatePacket.java -index 6247a21c9c391abf1f6db3482c659593e4f29355..9ccca41bf23efadba5329cc584bbcdcacbe09a92 100644 ---- a/src/main/java/net/minecraft/network/protocol/game/ClientboundPlayerInfoUpdatePacket.java -+++ b/src/main/java/net/minecraft/network/protocol/game/ClientboundPlayerInfoUpdatePacket.java -@@ -37,6 +37,17 @@ public class ClientboundPlayerInfoUpdatePacket implements Packet actions, List entries) { -+ this.actions = actions; -+ this.entries = entries; -+ } -+ -+ public ClientboundPlayerInfoUpdatePacket(EnumSet actions, ClientboundPlayerInfoUpdatePacket.Entry entry) { -+ this.actions = actions; -+ this.entries = List.of(entry); -+ } -+ // Paper end - Add Listing API for Player - - public static ClientboundPlayerInfoUpdatePacket createPlayerInitializing(Collection players) { - EnumSet enumSet = EnumSet.of( -@@ -50,6 +61,28 @@ public class ClientboundPlayerInfoUpdatePacket implements Packet players, ServerPlayer forPlayer) { -+ final EnumSet enumSet = EnumSet.of(ClientboundPlayerInfoUpdatePacket.Action.ADD_PLAYER, ClientboundPlayerInfoUpdatePacket.Action.INITIALIZE_CHAT, ClientboundPlayerInfoUpdatePacket.Action.UPDATE_GAME_MODE, ClientboundPlayerInfoUpdatePacket.Action.UPDATE_LISTED, ClientboundPlayerInfoUpdatePacket.Action.UPDATE_LATENCY, ClientboundPlayerInfoUpdatePacket.Action.UPDATE_DISPLAY_NAME); -+ final List entries = new java.util.ArrayList<>(players.size()); -+ final org.bukkit.craftbukkit.entity.CraftPlayer bukkitEntity = forPlayer.getBukkitEntity(); -+ for (final ServerPlayer player : players) { -+ entries.add(new ClientboundPlayerInfoUpdatePacket.Entry(player, bukkitEntity.isListed(player.getBukkitEntity()))); -+ } -+ return new ClientboundPlayerInfoUpdatePacket(enumSet, entries); -+ } -+ -+ public static ClientboundPlayerInfoUpdatePacket createSinglePlayerInitializing(ServerPlayer player, boolean listed) { -+ final EnumSet enumSet = EnumSet.of(ClientboundPlayerInfoUpdatePacket.Action.ADD_PLAYER, ClientboundPlayerInfoUpdatePacket.Action.INITIALIZE_CHAT, ClientboundPlayerInfoUpdatePacket.Action.UPDATE_GAME_MODE, ClientboundPlayerInfoUpdatePacket.Action.UPDATE_LISTED, ClientboundPlayerInfoUpdatePacket.Action.UPDATE_LATENCY, ClientboundPlayerInfoUpdatePacket.Action.UPDATE_DISPLAY_NAME); -+ final List entries = List.of(new Entry(player, listed)); -+ return new ClientboundPlayerInfoUpdatePacket(enumSet, entries); -+ } -+ -+ public static ClientboundPlayerInfoUpdatePacket updateListed(UUID playerInfoId, boolean listed) { -+ EnumSet enumSet = EnumSet.of(ClientboundPlayerInfoUpdatePacket.Action.UPDATE_LISTED); -+ return new ClientboundPlayerInfoUpdatePacket(enumSet, new ClientboundPlayerInfoUpdatePacket.Entry(playerInfoId, listed)); -+ } -+ // Paper end - Add Listing API for Player - private ClientboundPlayerInfoUpdatePacket(RegistryFriendlyByteBuf buf) { - this.actions = buf.readEnumSet(ClientboundPlayerInfoUpdatePacket.Action.class); - this.entries = buf.readList(buf2 -> { -@@ -158,16 +191,24 @@ public class ClientboundPlayerInfoUpdatePacket implements Packet onlinePlayers = Lists.newArrayListWithExpectedSize(this.players.size() - 1); // Paper - Use single player info update packet on join - for (int i = 0; i < this.players.size(); ++i) { - ServerPlayer entityplayer1 = (ServerPlayer) this.players.get(i); - - if (entityplayer1.getBukkitEntity().canSee(bukkitPlayer)) { -+ // Paper start - Add Listing API for Player -+ if (entityplayer1.getBukkitEntity().isListed(bukkitPlayer)) { -+ // Paper end - Add Listing API for Player - entityplayer1.connection.send(packet); -+ // Paper start - Add Listing API for Player -+ } else { -+ entityplayer1.connection.send(ClientboundPlayerInfoUpdatePacket.createSinglePlayerInitializing(player, false)); -+ } -+ // Paper end - Add Listing API for Player - } - - if (entityplayer1 == player || !bukkitPlayer.canSee(entityplayer1.getBukkitEntity())) { // Paper - Use single player info update packet on join; Don't include joining player -@@ -377,7 +385,7 @@ public abstract class PlayerList { - } - // Paper start - Use single player info update packet on join - if (!onlinePlayers.isEmpty()) { -- player.connection.send(ClientboundPlayerInfoUpdatePacket.createPlayerInitializing(onlinePlayers)); -+ player.connection.send(ClientboundPlayerInfoUpdatePacket.createPlayerInitializing(onlinePlayers, player)); // Paper - Add Listing API for Player - } - // Paper end - Use single player info update packet on join - player.sentListPacket = true; -diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java -index ccd81bc7279a92f78bb882c9443cf9ca90fd9acf..a3912d4de96a6bb2b9b3977705fa3facb891f73d 100644 ---- a/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java -+++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java -@@ -201,6 +201,7 @@ public class CraftPlayer extends CraftHumanEntity implements Player { - private final ConversationTracker conversationTracker = new ConversationTracker(); - private final Set channels = new HashSet(); - private final Map>> invertedVisibilityEntities = new HashMap<>(); -+ private final Set unlistedEntities = new HashSet<>(); // Paper - Add Listing API for Player - private static final WeakHashMap> pluginWeakReferences = new WeakHashMap<>(); - private int hash = 0; - private double health = 20; -@@ -2092,7 +2093,7 @@ public class CraftPlayer extends CraftHumanEntity implements Player { - otherPlayer.setUUID(uuidOverride); - } - // Paper end -- this.getHandle().connection.send(ClientboundPlayerInfoUpdatePacket.createPlayerInitializing(List.of(otherPlayer))); -+ this.getHandle().connection.send(ClientboundPlayerInfoUpdatePacket.createPlayerInitializing(List.of(otherPlayer), this.getHandle())); // Paper - Add Listing API for Player - if (original != null) otherPlayer.setUUID(original); // Paper - uuid override - } - -@@ -2196,6 +2197,41 @@ public class CraftPlayer extends CraftHumanEntity implements Player { - return (entity != null) ? this.canSee(entity) : false; // If we can't find it, we can't see it - } - -+ // Paper start - Add Listing API for Player -+ @Override -+ public boolean isListed(Player other) { -+ return !this.unlistedEntities.contains(other.getUniqueId()); -+ } -+ -+ @Override -+ public boolean unlistPlayer(@NotNull Player other) { -+ Preconditions.checkNotNull(other, "hidden entity cannot be null"); -+ if (this.getHandle().connection == null) return false; -+ if (!this.canSee(other)) return false; -+ -+ if (unlistedEntities.add(other.getUniqueId())) { -+ this.getHandle().connection.send(ClientboundPlayerInfoUpdatePacket.updateListed(other.getUniqueId(), false)); -+ return true; -+ } else { -+ return false; -+ } -+ } -+ -+ @Override -+ public boolean listPlayer(@NotNull Player other) { -+ Preconditions.checkNotNull(other, "hidden entity cannot be null"); -+ if (this.getHandle().connection == null) return false; -+ if (!this.canSee(other)) throw new IllegalStateException("Player cannot see other player"); -+ -+ if (this.unlistedEntities.remove(other.getUniqueId())) { -+ this.getHandle().connection.send(ClientboundPlayerInfoUpdatePacket.updateListed(other.getUniqueId(), true)); -+ return true; -+ } else { -+ return false; -+ } -+ } -+ // Paper end - Add Listing API for Player -+ - @Override - public Map serialize() { - Map result = new LinkedHashMap(); diff --git a/patches/server/0862-Expose-hand-in-BlockCanBuildEvent.patch b/patches/server/0862-Expose-hand-in-BlockCanBuildEvent.patch new file mode 100644 index 000000000000..f3ed54f3f988 --- /dev/null +++ b/patches/server/0862-Expose-hand-in-BlockCanBuildEvent.patch @@ -0,0 +1,32 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: The456gamer +Date: Mon, 21 Aug 2023 14:13:42 +0100 +Subject: [PATCH] Expose hand in BlockCanBuildEvent + + +diff --git a/src/main/java/net/minecraft/world/item/BlockItem.java b/src/main/java/net/minecraft/world/item/BlockItem.java +index d59120f0304823361cc4112f5583323945df4229..986b14a7b7c98641201ece649df09e4b8279a36c 100644 +--- a/src/main/java/net/minecraft/world/item/BlockItem.java ++++ b/src/main/java/net/minecraft/world/item/BlockItem.java +@@ -185,7 +185,7 @@ public class BlockItem extends Item { + boolean defaultReturn = (!this.mustSurvive() || state.canSurvive(context.getLevel(), context.getClickedPos())) && world.checkEntityCollision(state, entityhuman, voxelshapecollision, context.getClickedPos(), true); // Paper - Cancel hit for vanished players + org.bukkit.entity.Player player = (context.getPlayer() instanceof ServerPlayer) ? (org.bukkit.entity.Player) context.getPlayer().getBukkitEntity() : null; + +- BlockCanBuildEvent event = new BlockCanBuildEvent(CraftBlock.at(context.getLevel(), context.getClickedPos()), player, CraftBlockData.fromData(state), defaultReturn); ++ BlockCanBuildEvent event = new BlockCanBuildEvent(CraftBlock.at(context.getLevel(), context.getClickedPos()), player, CraftBlockData.fromData(state), defaultReturn, org.bukkit.craftbukkit.CraftEquipmentSlot.getHand(context.getHand())); // Paper - Expose hand in BlockCanBuildEvent + context.getLevel().getCraftServer().getPluginManager().callEvent(event); + + return event.isBuildable(); +diff --git a/src/main/java/net/minecraft/world/item/StandingAndWallBlockItem.java b/src/main/java/net/minecraft/world/item/StandingAndWallBlockItem.java +index 81915cec3ec372d1ff59160b96399b8c6e693eba..1451b25cedb7a8f01c046c8e1f8c6853aca42283 100644 +--- a/src/main/java/net/minecraft/world/item/StandingAndWallBlockItem.java ++++ b/src/main/java/net/minecraft/world/item/StandingAndWallBlockItem.java +@@ -59,7 +59,7 @@ public class StandingAndWallBlockItem extends BlockItem { + boolean defaultReturn = world.isUnobstructed(iblockdata1, blockposition, CollisionContext.empty()); + org.bukkit.entity.Player player = (context.getPlayer() instanceof ServerPlayer) ? (org.bukkit.entity.Player) context.getPlayer().getBukkitEntity() : null; + +- BlockCanBuildEvent event = new BlockCanBuildEvent(CraftBlock.at(world, blockposition), player, CraftBlockData.fromData(iblockdata1), defaultReturn); ++ BlockCanBuildEvent event = new BlockCanBuildEvent(CraftBlock.at(world, blockposition), player, CraftBlockData.fromData(iblockdata1), defaultReturn, org.bukkit.craftbukkit.CraftEquipmentSlot.getHand(context.getHand())); // Paper - Expose hand in BlockCanBuildEvent + context.getLevel().getCraftServer().getPluginManager().callEvent(event); + + return (event.isBuildable()) ? iblockdata1 : null; diff --git a/patches/server/0863-Optimize-nearest-structure-border-iteration.patch b/patches/server/0863-Optimize-nearest-structure-border-iteration.patch new file mode 100644 index 000000000000..edd50774ccac --- /dev/null +++ b/patches/server/0863-Optimize-nearest-structure-border-iteration.patch @@ -0,0 +1,39 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Martijn Muijsers +Date: Mon, 21 Aug 2023 21:05:09 +0200 +Subject: [PATCH] Optimize nearest structure border iteration + +Getting the nearest generated structure contains a nested set of loops that +iterates over all chunks at a specific chessboard distance. It does this by +iterating over the entire square of chunks within that distance, and checking +if the coordinates are at exactly the right distance to be on the border. + +This patch optimizes the iteration by only iterating over the border chunks. +This evaluated chunks are the same, and in the same order, as before, to +ensure that the returned found structure (which may for example be a buried +treasure that will be marked on a treasure map) is the same as in vanilla. + +diff --git a/src/main/java/net/minecraft/world/level/chunk/ChunkGenerator.java b/src/main/java/net/minecraft/world/level/chunk/ChunkGenerator.java +index 31c5f54c90d6e35875f762747f8618e58e2eed91..582065b2d4e818c0edec36b2e9847f8ed3266b10 100644 +--- a/src/main/java/net/minecraft/world/level/chunk/ChunkGenerator.java ++++ b/src/main/java/net/minecraft/world/level/chunk/ChunkGenerator.java +@@ -265,12 +265,15 @@ public abstract class ChunkGenerator { + int i1 = placement.spacing(); + + for (int j1 = -radius; j1 <= radius; ++j1) { +- boolean flag1 = j1 == -radius || j1 == radius; ++ // Paper start - Perf: iterate over border chunks instead of entire square chunk area ++ boolean flag1 = j1 == -radius || j1 == radius; final boolean onBorderAlongZAxis = flag1; // Paper - OBFHELPER + +- for (int k1 = -radius; k1 <= radius; ++k1) { +- boolean flag2 = k1 == -radius || k1 == radius; ++ for (int k1 = -radius; k1 <= radius; k1 += onBorderAlongZAxis ? 1 : radius * 2) { ++ // boolean flag2 = k1 == -radius || k1 == radius; + +- if (flag1 || flag2) { ++ // if (flag1 || flag2) { ++ if (true) { ++ // Paper end - Perf: iterate over border chunks instead of entire square chunk area + int l1 = centerChunkX + i1 * j1; + int i2 = centerChunkZ + i1 * k1; + ChunkPos chunkcoordintpair = placement.getPotentialStructureChunk(seed, l1, i2); diff --git a/patches/server/0864-Add-BlockFace-to-BlockDamageEvent.patch b/patches/server/0864-Add-BlockFace-to-BlockDamageEvent.patch deleted file mode 100644 index 749d93224ec1..000000000000 --- a/patches/server/0864-Add-BlockFace-to-BlockDamageEvent.patch +++ /dev/null @@ -1,39 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: aerulion -Date: Mon, 21 Aug 2023 04:36:07 +0200 -Subject: [PATCH] Add BlockFace to BlockDamageEvent - - -diff --git a/src/main/java/net/minecraft/server/level/ServerPlayerGameMode.java b/src/main/java/net/minecraft/server/level/ServerPlayerGameMode.java -index c680f081ba548f84f07a968a46811090c53e57e3..d839f8df658c894f144ba4637d290ffbed77e132 100644 ---- a/src/main/java/net/minecraft/server/level/ServerPlayerGameMode.java -+++ b/src/main/java/net/minecraft/server/level/ServerPlayerGameMode.java -@@ -261,7 +261,7 @@ public class ServerPlayerGameMode { - } - return; - } -- org.bukkit.event.block.BlockDamageEvent blockEvent = CraftEventFactory.callBlockDamageEvent(this.player, pos, this.player.getInventory().getSelected(), f >= 1.0f); -+ org.bukkit.event.block.BlockDamageEvent blockEvent = CraftEventFactory.callBlockDamageEvent(this.player, pos, direction, this.player.getInventory().getSelected(), f >= 1.0f); // Paper - Add BlockFace to BlockDamageEvent - - if (blockEvent.isCancelled()) { - // Let the client know the block still exists -diff --git a/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java b/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java -index 907ea60b647d529f133986632d00416269bc311e..6b241ec85f360ec79f18b911175d7092861024a4 100644 ---- a/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java -+++ b/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java -@@ -661,13 +661,13 @@ public class CraftEventFactory { - /** - * BlockDamageEvent - */ -- public static BlockDamageEvent callBlockDamageEvent(ServerPlayer who, BlockPos pos, ItemStack itemstack, boolean instaBreak) { -+ public static BlockDamageEvent callBlockDamageEvent(ServerPlayer who, BlockPos pos, Direction direction, ItemStack itemstack, boolean instaBreak) { // Paper - Add BlockFace to BlockDamageEvent - Player player = who.getBukkitEntity(); - CraftItemStack itemInHand = CraftItemStack.asCraftMirror(itemstack); - - Block blockClicked = CraftBlock.at(who.level(), pos); - -- BlockDamageEvent event = new BlockDamageEvent(player, blockClicked, itemInHand, instaBreak); -+ BlockDamageEvent event = new BlockDamageEvent(player, blockClicked, CraftBlock.notchToBlockFace(direction), itemInHand, instaBreak); // Paper - Add BlockFace to BlockDamageEvent - player.getServer().getPluginManager().callEvent(event); - - return event; diff --git a/patches/server/0864-Implement-OfflinePlayer-isConnected.patch b/patches/server/0864-Implement-OfflinePlayer-isConnected.patch new file mode 100644 index 000000000000..213901ccea20 --- /dev/null +++ b/patches/server/0864-Implement-OfflinePlayer-isConnected.patch @@ -0,0 +1,42 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Aeltumn +Date: Thu, 24 Aug 2023 13:05:30 +0200 +Subject: [PATCH] Implement OfflinePlayer#isConnected + + +diff --git a/src/main/java/org/bukkit/craftbukkit/CraftOfflinePlayer.java b/src/main/java/org/bukkit/craftbukkit/CraftOfflinePlayer.java +index 2c2c4db31a746b4eb853dc04c6b3e5631bbfa034..4f4e3ee18d586f61706504218cddc06a38ca5580 100644 +--- a/src/main/java/org/bukkit/craftbukkit/CraftOfflinePlayer.java ++++ b/src/main/java/org/bukkit/craftbukkit/CraftOfflinePlayer.java +@@ -54,6 +54,13 @@ public class CraftOfflinePlayer implements OfflinePlayer, ConfigurationSerializa + return this.getPlayer() != null; + } + ++ // Paper start ++ @Override ++ public boolean isConnected() { ++ return false; ++ } ++ // Paper end ++ + @Override + public String getName() { + Player player = this.getPlayer(); +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java +index 47180d44b7e168352b17bc295ffb6d3ea6e0a937..96d8bd55ecfb6aca8a6f65bc2a25e5756eecc4d8 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java +@@ -261,6 +261,13 @@ public class CraftPlayer extends CraftHumanEntity implements Player { + return this.server.getPlayer(this.getUniqueId()) != null; + } + ++ // Paper start ++ @Override ++ public boolean isConnected() { ++ return !this.getHandle().hasDisconnected(); ++ } ++ // Paper end ++ + @Override + public InetSocketAddress getAddress() { + if (this.getHandle().connection.protocol() == null) return null; diff --git a/patches/server/0865-Fix-NPE-on-Boat-getStatus.patch b/patches/server/0865-Fix-NPE-on-Boat-getStatus.patch deleted file mode 100644 index 70323a435274..000000000000 --- a/patches/server/0865-Fix-NPE-on-Boat-getStatus.patch +++ /dev/null @@ -1,32 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: LemonCaramel -Date: Mon, 10 Apr 2023 20:48:26 +0900 -Subject: [PATCH] Fix NPE on Boat getStatus - -Boat status is null until the entity is added to the world and the tick() method is called. - -== AT == -public net.minecraft.world.entity.vehicle.Boat getStatus()Lnet/minecraft/world/entity/vehicle/Boat$Status; - -diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftBoat.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftBoat.java -index d161cbf9c83cd78593864850b98f688da2c85aa5..e33b1b6fd50a4eea57500cc00dba20d6edcab75d 100644 ---- a/src/main/java/org/bukkit/craftbukkit/entity/CraftBoat.java -+++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftBoat.java -@@ -88,6 +88,17 @@ public class CraftBoat extends CraftVehicle implements Boat { - - @Override - public Status getStatus() { -+ // Paper start - Fix NPE on Boat getStatus -+ final net.minecraft.world.entity.vehicle.Boat handle = this.getHandle(); -+ if (handle.status == null) { -+ if (handle.valid) { -+ // Don't actually set the status because it would skew the old status check in the next tick -+ return CraftBoat.boatStatusFromNms(handle.getStatus()); -+ } else { -+ return Status.NOT_IN_WORLD; -+ } -+ } -+ // Paper end - Fix NPE on Boat getStatus - return CraftBoat.boatStatusFromNms(this.getHandle().status); - } - diff --git a/patches/server/0865-Fix-slot-desync.patch b/patches/server/0865-Fix-slot-desync.patch new file mode 100644 index 000000000000..eb0e27694525 --- /dev/null +++ b/patches/server/0865-Fix-slot-desync.patch @@ -0,0 +1,129 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Jake Potrebic +Date: Wed, 23 Aug 2023 13:22:09 -0700 +Subject: [PATCH] Fix slot desync + +General patch fixing slot desyncs between the server and client that +result from cancelled events/paper introduced logic. + +Co-authored-by: Minecrell +Co-authored-by: Newwind + +diff --git a/src/main/java/net/minecraft/server/level/ServerPlayer.java b/src/main/java/net/minecraft/server/level/ServerPlayer.java +index f05a9fd321a4af28e9771bbf39d73f80dd4160c9..90aa8e401e1d092a31ff21699409b8366629cdcc 100644 +--- a/src/main/java/net/minecraft/server/level/ServerPlayer.java ++++ b/src/main/java/net/minecraft/server/level/ServerPlayer.java +@@ -460,6 +460,7 @@ public class ServerPlayer extends net.minecraft.world.entity.player.Player { + + // Use method to resend items in hands in case of client desync, because the item use got cancelled. + // For example, when cancelling the leash event ++ @Deprecated // Paper - this shouldn't be used, use the regular sendAllDataToRemote call to resync all + public void resendItemInHands() { + this.containerMenu.findSlot(this.getInventory(), this.getInventory().selected).ifPresent(s -> { + this.containerSynchronizer.sendSlotChange(this.containerMenu, s, this.getMainHandItem()); +diff --git a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java +index b840f7aac9c830b8aa0aa133bf43f87dfc598b2c..0cb0d2f863efb86bb589b30bae61ac57bda40fab 100644 +--- a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java ++++ b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java +@@ -2746,10 +2746,11 @@ public class ServerGamePacketListenerImpl extends ServerCommonPacketListenerImpl + // Refresh the current entity metadata + entity.refreshEntityData(ServerGamePacketListenerImpl.this.player); + // SPIGOT-7136 - Allays +- if (entity instanceof Allay) { ++ if (entity instanceof Allay || entity instanceof net.minecraft.world.entity.animal.horse.AbstractHorse) { // Paper - Fix horse armor desync + ServerGamePacketListenerImpl.this.send(new ClientboundSetEquipmentPacket(entity.getId(), Arrays.stream(net.minecraft.world.entity.EquipmentSlot.values()).map((slot) -> Pair.of(slot, ((LivingEntity) entity).getItemBySlot(slot).copy())).collect(Collectors.toList()))); +- ServerGamePacketListenerImpl.this.player.containerMenu.sendAllDataToRemote(); + } ++ ++ ServerGamePacketListenerImpl.this.player.containerMenu.sendAllDataToRemote(); // Paper - fix slot desync - always refresh player inventory + } + + if (event.isCancelled()) { +diff --git a/src/main/java/net/minecraft/world/entity/Entity.java b/src/main/java/net/minecraft/world/entity/Entity.java +index 67dda7d37cc4af2f09734da5653e8fa33098c7db..56f2ac87f42572556646eb62f16274726e7ae455 100644 +--- a/src/main/java/net/minecraft/world/entity/Entity.java ++++ b/src/main/java/net/minecraft/world/entity/Entity.java +@@ -2752,8 +2752,9 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess + if (!this.level().isClientSide()) { + // CraftBukkit start - fire PlayerLeashEntityEvent + if (CraftEventFactory.callPlayerLeashEntityEvent(this, player, player, hand).isCancelled()) { +- ((ServerPlayer) player).resendItemInHands(); // SPIGOT-7615: Resend to fix client desync with used item ++ // ((ServerPlayer) player).resendItemInHands(); // SPIGOT-7615: Resend to fix client desync with used item // Paper - Fix inventory desync + ((ServerPlayer) player).connection.send(new ClientboundSetEntityLinkPacket(this, leashable.getLeashHolder())); ++ player.containerMenu.sendAllDataToRemote(); // Paper - Fix inventory desync + return InteractionResult.PASS; + } + // CraftBukkit end +diff --git a/src/main/java/net/minecraft/world/entity/animal/Cow.java b/src/main/java/net/minecraft/world/entity/animal/Cow.java +index d99760f943846a1cfe5d0ec97313f453004feb98..3e00bbff266fc71b07014e7e047d77b7f809239f 100644 +--- a/src/main/java/net/minecraft/world/entity/animal/Cow.java ++++ b/src/main/java/net/minecraft/world/entity/animal/Cow.java +@@ -101,6 +101,7 @@ public class Cow extends Animal { + PlayerBucketFillEvent event = CraftEventFactory.callPlayerBucketFillEvent((ServerLevel) player.level(), player, this.blockPosition(), this.blockPosition(), null, itemstack, Items.MILK_BUCKET, hand); + + if (event.isCancelled()) { ++ player.containerMenu.sendAllDataToRemote(); // Paper - Fix inventory desync + return InteractionResult.PASS; + } + // CraftBukkit end +diff --git a/src/main/java/net/minecraft/world/entity/animal/goat/Goat.java b/src/main/java/net/minecraft/world/entity/animal/goat/Goat.java +index 4c6dc427b90012b0945e073dd905dc7e8d1bec82..76aca47d8638d5c37c57d3a59fa7f8ceaa5a53b4 100644 +--- a/src/main/java/net/minecraft/world/entity/animal/goat/Goat.java ++++ b/src/main/java/net/minecraft/world/entity/animal/goat/Goat.java +@@ -238,6 +238,7 @@ public class Goat extends Animal { + PlayerBucketFillEvent event = CraftEventFactory.callPlayerBucketFillEvent((ServerLevel) player.level(), player, this.blockPosition(), this.blockPosition(), null, itemstack, Items.MILK_BUCKET, hand); + + if (event.isCancelled()) { ++ player.containerMenu.sendAllDataToRemote(); // Paper - Fix inventory desync + return InteractionResult.PASS; + } + // CraftBukkit end +diff --git a/src/main/java/net/minecraft/world/item/ArmorStandItem.java b/src/main/java/net/minecraft/world/item/ArmorStandItem.java +index 15c7c2417db1661c77c35455b59a77b1d1055858..cb4baebe22eeab17aed67a5ecc506b932fe2230b 100644 +--- a/src/main/java/net/minecraft/world/item/ArmorStandItem.java ++++ b/src/main/java/net/minecraft/world/item/ArmorStandItem.java +@@ -55,6 +55,7 @@ public class ArmorStandItem extends Item { + entityarmorstand.moveTo(entityarmorstand.getX(), entityarmorstand.getY(), entityarmorstand.getZ(), f, 0.0F); + // CraftBukkit start + if (org.bukkit.craftbukkit.event.CraftEventFactory.callEntityPlaceEvent(context, entityarmorstand).isCancelled()) { ++ if (context.getPlayer() != null) context.getPlayer().containerMenu.sendAllDataToRemote(); // Paper - Fix inventory desync + return InteractionResult.FAIL; + } + // CraftBukkit end +diff --git a/src/main/java/net/minecraft/world/item/BlockItem.java b/src/main/java/net/minecraft/world/item/BlockItem.java +index 986b14a7b7c98641201ece649df09e4b8279a36c..c816c935ecc74a811ffdffbe6ded73c06e92324a 100644 +--- a/src/main/java/net/minecraft/world/item/BlockItem.java ++++ b/src/main/java/net/minecraft/world/item/BlockItem.java +@@ -108,7 +108,7 @@ public class BlockItem extends Item { + if (placeEvent != null && (placeEvent.isCancelled() || !placeEvent.canBuild())) { + blockstate.update(true, false); + +- if (this instanceof SolidBucketItem) { ++ if (true) { // Paper - if the event is called here, the inventory should be updated + ((ServerPlayer) entityhuman).getBukkitEntity().updateInventory(); // SPIGOT-4541 + } + return InteractionResult.FAIL; +diff --git a/src/main/java/net/minecraft/world/item/EndCrystalItem.java b/src/main/java/net/minecraft/world/item/EndCrystalItem.java +index 5311e653dceb085fcebb8ac5090d84e97e573e62..48317c7436445a7acff9103bac1de558c42f31cc 100644 +--- a/src/main/java/net/minecraft/world/item/EndCrystalItem.java ++++ b/src/main/java/net/minecraft/world/item/EndCrystalItem.java +@@ -49,6 +49,7 @@ public class EndCrystalItem extends Item { + entityendercrystal.setShowBottom(false); + // CraftBukkit start + if (org.bukkit.craftbukkit.event.CraftEventFactory.callEntityPlaceEvent(context, entityendercrystal).isCancelled()) { ++ if (context.getPlayer() != null) context.getPlayer().containerMenu.sendAllDataToRemote(); // Paper - Fix inventory desync + return InteractionResult.FAIL; + } + // CraftBukkit end +diff --git a/src/main/java/net/minecraft/world/item/MinecartItem.java b/src/main/java/net/minecraft/world/item/MinecartItem.java +index 04eb25b05681a72968917b0b59c030d6629776fa..7153d9ed12276a0f2d8b8a17c79734aa25ed1fa5 100644 +--- a/src/main/java/net/minecraft/world/item/MinecartItem.java ++++ b/src/main/java/net/minecraft/world/item/MinecartItem.java +@@ -69,6 +69,7 @@ public class MinecartItem extends Item { + + // CraftBukkit start + if (org.bukkit.craftbukkit.event.CraftEventFactory.callEntityPlaceEvent(context, entityminecartabstract).isCancelled()) { ++ if (context.getPlayer() != null) context.getPlayer().containerMenu.sendAllDataToRemote(); // Paper - Fix inventory desync + return InteractionResult.FAIL; + } + // CraftBukkit end diff --git a/patches/server/0866-Add-titleOverride-to-InventoryOpenEvent.patch b/patches/server/0866-Add-titleOverride-to-InventoryOpenEvent.patch new file mode 100644 index 000000000000..1b98c1b4a0e5 --- /dev/null +++ b/patches/server/0866-Add-titleOverride-to-InventoryOpenEvent.patch @@ -0,0 +1,120 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Jake Potrebic +Date: Fri, 4 Mar 2022 12:45:03 -0800 +Subject: [PATCH] Add titleOverride to InventoryOpenEvent + + +diff --git a/src/main/java/net/minecraft/server/level/ServerPlayer.java b/src/main/java/net/minecraft/server/level/ServerPlayer.java +index 90aa8e401e1d092a31ff21699409b8366629cdcc..419fcb4cd97cf10a2601e02024b999a51a0ff952 100644 +--- a/src/main/java/net/minecraft/server/level/ServerPlayer.java ++++ b/src/main/java/net/minecraft/server/level/ServerPlayer.java +@@ -1924,12 +1924,17 @@ public class ServerPlayer extends net.minecraft.world.entity.player.Player { + this.nextContainerCounter(); + AbstractContainerMenu container = factory.createMenu(this.containerCounter, this.getInventory(), this); + ++ Component title = null; // Paper - Add titleOverride to InventoryOpenEvent + // CraftBukkit start - Inventory open hook + if (container != null) { + container.setTitle(factory.getDisplayName()); + + boolean cancelled = false; +- container = CraftEventFactory.callInventoryOpenEvent(this, container, cancelled); ++ // Paper start - Add titleOverride to InventoryOpenEvent ++ final com.mojang.datafixers.util.Pair result = CraftEventFactory.callInventoryOpenEventWithTitle(this, container, cancelled); ++ container = result.getSecond(); ++ title = PaperAdventure.asVanilla(result.getFirst()); ++ // Paper end - Add titleOverride to InventoryOpenEvent + if (container == null && !cancelled) { // Let pre-cancelled events fall through + // SPIGOT-5263 - close chest if cancelled + if (factory instanceof Container) { +@@ -1951,7 +1956,7 @@ public class ServerPlayer extends net.minecraft.world.entity.player.Player { + } else { + // CraftBukkit start + this.containerMenu = container; +- if (!this.isImmobile()) this.connection.send(new ClientboundOpenScreenPacket(container.containerId, container.getType(), container.getTitle())); // Paper - Prevent opening inventories when frozen ++ if (!this.isImmobile()) this.connection.send(new ClientboundOpenScreenPacket(container.containerId, container.getType(), Objects.requireNonNullElseGet(title, container::getTitle))); // Paper - Add titleOverride to InventoryOpenEvent + // CraftBukkit end + this.initMenu(container); + return OptionalInt.of(this.containerCounter); +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftHumanEntity.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftHumanEntity.java +index 7dddf4dd090fcd9e86b147d7e4ddeaa99800713e..4312290ad970f71e1dc25b707ab312c597a481a9 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftHumanEntity.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftHumanEntity.java +@@ -366,12 +366,16 @@ public class CraftHumanEntity extends CraftLivingEntity implements HumanEntity { + Preconditions.checkArgument(windowType != null, "Unknown windowType"); + AbstractContainerMenu container = new CraftContainer(inventory, player, player.nextContainerCounter()); + +- container = CraftEventFactory.callInventoryOpenEvent(player, container); ++ // Paper start - Add titleOverride to InventoryOpenEvent ++ final com.mojang.datafixers.util.Pair result = CraftEventFactory.callInventoryOpenEventWithTitle(player, container); ++ container = result.getSecond(); ++ // Paper end - Add titleOverride to InventoryOpenEvent + if (container == null) return; + + //String title = container.getBukkitView().getTitle(); // Paper - comment + net.kyori.adventure.text.Component adventure$title = container.getBukkitView().title(); // Paper + if (adventure$title == null) adventure$title = net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer.legacySection().deserialize(container.getBukkitView().getTitle()); // Paper ++ if (result.getFirst() != null) adventure$title = result.getFirst(); // Paper - Add titleOverride to InventoryOpenEvent + + //player.connection.send(new ClientboundOpenScreenPacket(container.containerId, windowType, CraftChatMessage.fromString(title)[0])); // Paper - comment + if (!player.isImmobile()) player.connection.send(new ClientboundOpenScreenPacket(container.containerId, windowType, io.papermc.paper.adventure.PaperAdventure.asVanilla(adventure$title))); // Paper - Prevent opening inventories when frozen +@@ -448,7 +452,10 @@ public class CraftHumanEntity extends CraftLivingEntity implements HumanEntity { + } + + // Trigger an INVENTORY_OPEN event +- container = CraftEventFactory.callInventoryOpenEvent(player, container); ++ // Paper start - Add titleOverride to InventoryOpenEvent ++ final com.mojang.datafixers.util.Pair result = CraftEventFactory.callInventoryOpenEventWithTitle(player, container); ++ container = result.getSecond(); ++ // Paper end - Add titleOverride to InventoryOpenEvent + if (container == null) { + return; + } +@@ -459,6 +466,7 @@ public class CraftHumanEntity extends CraftLivingEntity implements HumanEntity { + //String title = inventory.getTitle(); // Paper - comment + net.kyori.adventure.text.Component adventure$title = inventory.title(); // Paper + if (adventure$title == null) adventure$title = net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer.legacySection().deserialize(inventory.getTitle()); // Paper ++ if (result.getFirst() != null) adventure$title = result.getFirst(); // Paper - Add titleOverride to InventoryOpenEvent + //player.connection.send(new ClientboundOpenScreenPacket(container.containerId, windowType, CraftChatMessage.fromString(title)[0])); // Paper - comment + if (!player.isImmobile()) player.connection.send(new ClientboundOpenScreenPacket(container.containerId, windowType, io.papermc.paper.adventure.PaperAdventure.asVanilla(adventure$title))); // Paper - Prevent opening inventories when frozen + player.containerMenu = container; +diff --git a/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java b/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java +index c5f9d9aeeb1fc61edb0e57cef51f5f6371861000..dbef230ae88ee1bfbc20ba53b534434c3ccac985 100644 +--- a/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java ++++ b/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java +@@ -1401,10 +1401,21 @@ public class CraftEventFactory { + } + + public static AbstractContainerMenu callInventoryOpenEvent(ServerPlayer player, AbstractContainerMenu container) { +- return CraftEventFactory.callInventoryOpenEvent(player, container, false); ++ // Paper start - Add titleOverride to InventoryOpenEvent ++ return callInventoryOpenEventWithTitle(player, container).getSecond(); ++ } ++ public static com.mojang.datafixers.util.Pair callInventoryOpenEventWithTitle(ServerPlayer player, AbstractContainerMenu container) { ++ return CraftEventFactory.callInventoryOpenEventWithTitle(player, container, false); ++ // Paper end - Add titleOverride to InventoryOpenEvent + } + ++ @Deprecated @io.papermc.paper.annotation.DoNotUse // Paper - use method that acknowledges title overrides + public static AbstractContainerMenu callInventoryOpenEvent(ServerPlayer player, AbstractContainerMenu container, boolean cancelled) { ++ // Paper start - Add titleOverride to InventoryOpenEvent ++ return callInventoryOpenEventWithTitle(player, container, cancelled).getSecond(); ++ } ++ public static com.mojang.datafixers.util.Pair callInventoryOpenEventWithTitle(ServerPlayer player, AbstractContainerMenu container, boolean cancelled) { ++ // Paper end - Add titleOverride to InventoryOpenEvent + if (player.containerMenu != player.inventoryMenu) { // fire INVENTORY_CLOSE if one already open + player.connection.handleContainerClose(new ServerboundContainerClosePacket(player.containerMenu.containerId), InventoryCloseEvent.Reason.OPEN_NEW); // Paper - Inventory close reason + } +@@ -1419,10 +1430,10 @@ public class CraftEventFactory { + + if (event.isCancelled()) { + container.transferTo(player.containerMenu, craftPlayer); +- return null; ++ return com.mojang.datafixers.util.Pair.of(null, null); // Paper - Add titleOverride to InventoryOpenEvent + } + +- return container; ++ return com.mojang.datafixers.util.Pair.of(event.titleOverride(), container); // Paper - Add titleOverride to InventoryOpenEvent + } + + public static ItemStack callPreCraftEvent(CraftingContainer matrix, Container resultInventory, ItemStack result, InventoryView lastCraftView, boolean isRepair) { diff --git a/patches/server/0866-Expand-Pose-API.patch b/patches/server/0866-Expand-Pose-API.patch deleted file mode 100644 index 1a9699d342a3..000000000000 --- a/patches/server/0866-Expand-Pose-API.patch +++ /dev/null @@ -1,51 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: SoSeDiK -Date: Wed, 11 Jan 2023 20:59:01 +0200 -Subject: [PATCH] Expand Pose API - - -diff --git a/src/main/java/net/minecraft/world/entity/Entity.java b/src/main/java/net/minecraft/world/entity/Entity.java -index f81a576084ccceb2b02e8d8cd57442efc7ff1e9f..84d283cd637b5c84a8682acec503b0e3831d1684 100644 ---- a/src/main/java/net/minecraft/world/entity/Entity.java -+++ b/src/main/java/net/minecraft/world/entity/Entity.java -@@ -425,6 +425,7 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess - @javax.annotation.Nullable - private UUID originWorld; - public boolean freezeLocked = false; // Paper - Freeze Tick Lock API -+ public boolean fixedPose = false; // Paper - Expand Pose API - - public void setOrigin(@javax.annotation.Nonnull Location location) { - this.origin = location.toVector(); -@@ -618,6 +619,7 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess - public void onClientRemoval() {} - - public void setPose(net.minecraft.world.entity.Pose pose) { -+ if (this.fixedPose) return; // Paper - Expand Pose API - // CraftBukkit start - if (pose == this.getPose()) { - return; -diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java -index 3626fea17527da69e6fbee26f018f52ef036cf90..580427bf1521ac9fef37f7464e12a7bfe4fbfb10 100644 ---- a/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java -+++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java -@@ -899,6 +899,20 @@ public abstract class CraftEntity implements org.bukkit.entity.Entity { - public boolean isSneaking() { - return this.getHandle().isShiftKeyDown(); - } -+ -+ @Override -+ public void setPose(Pose pose, boolean fixed) { -+ Preconditions.checkNotNull(pose, "Pose cannot be null"); -+ final Entity handle = this.getHandle(); -+ handle.fixedPose = false; -+ handle.setPose(net.minecraft.world.entity.Pose.values()[pose.ordinal()]); -+ handle.fixedPose = fixed; -+ } -+ -+ @Override -+ public boolean hasFixedPose() { -+ return this.getHandle().fixedPose; -+ } - // Paper end - - @Override diff --git a/patches/server/0867-Configure-sniffer-egg-hatch-time.patch b/patches/server/0867-Configure-sniffer-egg-hatch-time.patch new file mode 100644 index 000000000000..e39971b614a0 --- /dev/null +++ b/patches/server/0867-Configure-sniffer-egg-hatch-time.patch @@ -0,0 +1,28 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Lulu13022002 <41980282+Lulu13022002@users.noreply.github.com> +Date: Tue, 27 Jun 2023 13:26:06 +0200 +Subject: [PATCH] Configure sniffer egg hatch time + + +diff --git a/src/main/java/net/minecraft/world/level/block/SnifferEggBlock.java b/src/main/java/net/minecraft/world/level/block/SnifferEggBlock.java +index c6b7cfd78bc0c4cb64eada507876c293541890f4..ec04fcc23c86d34a6dc1eaadda7f9d876f3d8ffe 100644 +--- a/src/main/java/net/minecraft/world/level/block/SnifferEggBlock.java ++++ b/src/main/java/net/minecraft/world/level/block/SnifferEggBlock.java +@@ -63,7 +63,7 @@ public class SnifferEggBlock extends Block { + + // Paper start - Call BlockFadeEvent + private void rescheduleTick(ServerLevel world, BlockPos pos) { +- int baseDelay = hatchBoost(world, pos) ? BOOSTED_HATCH_TIME_TICKS : REGULAR_HATCH_TIME_TICKS; ++ int baseDelay = hatchBoost(world, pos) ? world.paperConfig().entities.sniffer.boostedHatchTime.or(BOOSTED_HATCH_TIME_TICKS) : world.paperConfig().entities.sniffer.hatchTime.or(REGULAR_HATCH_TIME_TICKS); // Paper - Configure sniffer egg hatch time + world.scheduleTick(pos, this, (baseDelay / 3) + world.random.nextInt(RANDOM_HATCH_OFFSET_TICKS)); + // reschedule to avoid being stuck here and behave like the other calls (see #onPlace) + } +@@ -105,7 +105,7 @@ public class SnifferEggBlock extends Block { + world.levelEvent(3009, pos, 0); + } + +- int i = bl ? 12000 : 24000; ++ int i = bl ? world.paperConfig().entities.sniffer.boostedHatchTime.or(BOOSTED_HATCH_TIME_TICKS) : world.paperConfig().entities.sniffer.hatchTime.or(REGULAR_HATCH_TIME_TICKS); // Paper + int j = i / 3; + world.gameEvent(GameEvent.BLOCK_PLACE, pos, GameEvent.Context.of(state)); + world.scheduleTick(pos, this, j + world.random.nextInt(300)); diff --git a/patches/server/0867-More-DragonBattle-API.patch b/patches/server/0867-More-DragonBattle-API.patch deleted file mode 100644 index b7b58f6c9920..000000000000 --- a/patches/server/0867-More-DragonBattle-API.patch +++ /dev/null @@ -1,91 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Jake Potrebic -Date: Sun, 18 Dec 2022 13:40:05 -0800 -Subject: [PATCH] More DragonBattle API - -== AT == -public net.minecraft.world.level.dimension.end.EndDragonFight GATEWAY_COUNT -public net.minecraft.world.level.dimension.end.EndDragonFight gateways -public net.minecraft.world.level.dimension.end.EndDragonFight respawnCrystals -public net.minecraft.world.level.dimension.end.EndDragonFight spawnNewGateway(Lnet/minecraft/core/BlockPos;)V - -diff --git a/src/main/java/net/minecraft/world/level/dimension/end/EndDragonFight.java b/src/main/java/net/minecraft/world/level/dimension/end/EndDragonFight.java -index bf39f006efef529db697ed4dccbd1657a2673b79..4a0479fcff94bf6a1f239c1f694202345cdea1d4 100644 ---- a/src/main/java/net/minecraft/world/level/dimension/end/EndDragonFight.java -+++ b/src/main/java/net/minecraft/world/level/dimension/end/EndDragonFight.java -@@ -441,6 +441,24 @@ public class EndDragonFight { - this.gateways.clear(); - } - -+ // Paper start - More DragonBattle API -+ public boolean spawnNewGatewayIfPossible() { -+ if (!this.gateways.isEmpty()) { -+ this.spawnNewGateway(); -+ return true; -+ } -+ return false; -+ } -+ -+ public List getSpikeCrystals() { -+ final List endCrystals = new java.util.ArrayList<>(); -+ for (final SpikeFeature.EndSpike spike : SpikeFeature.getSpikesForLevel(this.level)) { -+ endCrystals.addAll(this.level.getEntitiesOfClass(EndCrystal.class, spike.getTopBoundingBox())); -+ } -+ return endCrystals; -+ } -+ // Paper end - More DragonBattle API -+ - private void spawnNewGateway() { - if (!this.gateways.isEmpty()) { - int i = (Integer) this.gateways.remove(this.gateways.size() - 1); -diff --git a/src/main/java/org/bukkit/craftbukkit/boss/CraftDragonBattle.java b/src/main/java/org/bukkit/craftbukkit/boss/CraftDragonBattle.java -index dc7fdc120f4317ee1b7935ef226eddb0406aee6e..6bfabb38b51115beb2a65a165f235347838b6006 100644 ---- a/src/main/java/org/bukkit/craftbukkit/boss/CraftDragonBattle.java -+++ b/src/main/java/org/bukkit/craftbukkit/boss/CraftDragonBattle.java -@@ -137,4 +137,46 @@ public class CraftDragonBattle implements DragonBattle { - private DragonRespawnAnimation toNMSRespawnPhase(RespawnPhase phase) { - return (phase != RespawnPhase.NONE) ? DragonRespawnAnimation.values()[phase.ordinal()] : null; - } -+ // Paper start - More DragonBattle API -+ @Override -+ public int getGatewayCount() { -+ return EndDragonFight.GATEWAY_COUNT - this.handle.gateways.size(); -+ } -+ -+ @Override -+ public boolean spawnNewGateway() { -+ return this.handle.spawnNewGatewayIfPossible(); -+ } -+ -+ @Override -+ public void spawnNewGateway(final io.papermc.paper.math.Position position) { -+ this.handle.spawnNewGateway(io.papermc.paper.util.MCUtil.toBlockPos(position)); -+ } -+ -+ @Override -+ public java.util.List getRespawnCrystals() { -+ if (this.handle.respawnCrystals == null) { -+ return java.util.Collections.emptyList(); -+ } -+ -+ final java.util.List enderCrystals = new java.util.ArrayList<>(); -+ for (final net.minecraft.world.entity.boss.enderdragon.EndCrystal endCrystal : this.handle.respawnCrystals) { -+ if (!endCrystal.isRemoved() && endCrystal.isAlive() && endCrystal.valid) { -+ enderCrystals.add(((org.bukkit.entity.EnderCrystal) endCrystal.getBukkitEntity())); -+ } -+ } -+ return java.util.Collections.unmodifiableList(enderCrystals); -+ } -+ -+ @Override -+ public java.util.List getHealingCrystals() { -+ final java.util.List enderCrystals = new java.util.ArrayList<>(); -+ for (final net.minecraft.world.entity.boss.enderdragon.EndCrystal endCrystal : this.handle.getSpikeCrystals()) { -+ if (!endCrystal.isRemoved() && endCrystal.isAlive() && endCrystal.valid) { -+ enderCrystals.add(((org.bukkit.entity.EnderCrystal) endCrystal.getBukkitEntity())); -+ } -+ } -+ return java.util.Collections.unmodifiableList(enderCrystals); -+ } -+ // Paper end - More DragonBattle API - } diff --git a/patches/server/0868-Add-PlayerPickItemEvent.patch b/patches/server/0868-Add-PlayerPickItemEvent.patch deleted file mode 100644 index b2bfa9ea75fc..000000000000 --- a/patches/server/0868-Add-PlayerPickItemEvent.patch +++ /dev/null @@ -1,48 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: RodneyMKay <36546810+RodneyMKay@users.noreply.github.com> -Date: Wed, 8 Sep 2021 21:34:01 +0200 -Subject: [PATCH] Add PlayerPickItemEvent - - -diff --git a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java -index 7750660defe490bcdc24a3b4ad4f495c8a703fed..be0ce72bb493593a3d2eb7d7c37e3a650b7cc34b 100644 ---- a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java -+++ b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java -@@ -935,8 +935,17 @@ public class ServerGamePacketListenerImpl extends ServerCommonPacketListenerImpl - this.disconnect(Component.literal("Invalid hotbar selection (Hacking?)"), org.bukkit.event.player.PlayerKickEvent.Cause.ILLEGAL_ACTION); // Paper - kick event cause - return; - } -- this.player.getInventory().pickSlot(packet.getSlot()); // Paper - Diff above if changed - // Paper end - validate pick item position -+ // Paper start - Add PlayerPickItemEvent -+ Player bukkitPlayer = this.player.getBukkitEntity(); -+ int targetSlot = this.player.getInventory().getSuitableHotbarSlot(); -+ int sourceSlot = packet.getSlot(); -+ -+ io.papermc.paper.event.player.PlayerPickItemEvent event = new io.papermc.paper.event.player.PlayerPickItemEvent(bukkitPlayer, targetSlot, sourceSlot); -+ if (!event.callEvent()) return; -+ -+ this.player.getInventory().pickSlot(event.getSourceSlot(), event.getTargetSlot()); -+ // Paper end - Add PlayerPickItemEvent - this.player.connection.send(new ClientboundContainerSetSlotPacket(-2, 0, this.player.getInventory().selected, this.player.getInventory().getItem(this.player.getInventory().selected))); - this.player.connection.send(new ClientboundContainerSetSlotPacket(-2, 0, packet.getSlot(), this.player.getInventory().getItem(packet.getSlot()))); - this.player.connection.send(new ClientboundSetCarriedItemPacket(this.player.getInventory().selected)); -diff --git a/src/main/java/net/minecraft/world/entity/player/Inventory.java b/src/main/java/net/minecraft/world/entity/player/Inventory.java -index 688ed0c32b05b0135b94ec05738cdc6ff8dcb677..a62e7354bf67a66bdf9cd7c8f5d2e8f6bcacc74f 100644 ---- a/src/main/java/net/minecraft/world/entity/player/Inventory.java -+++ b/src/main/java/net/minecraft/world/entity/player/Inventory.java -@@ -171,7 +171,13 @@ public class Inventory implements Container, Nameable { - } - - public void pickSlot(int slot) { -- this.selected = this.getSuitableHotbarSlot(); -+ // Paper start - Add PlayerPickItemEvent -+ pickSlot(slot, this.getSuitableHotbarSlot()); -+ } -+ -+ public void pickSlot(int slot, int targetSlot) { -+ this.selected = targetSlot; -+ // Paper end - Add PlayerPickItemEvent - ItemStack itemstack = (ItemStack) this.items.get(this.selected); - - this.items.set(this.selected, (ItemStack) this.items.get(slot)); diff --git a/patches/server/0868-Do-crystal-portal-proximity-check-before-entity-look.patch b/patches/server/0868-Do-crystal-portal-proximity-check-before-entity-look.patch new file mode 100644 index 000000000000..0ba2ce3d55e1 --- /dev/null +++ b/patches/server/0868-Do-crystal-portal-proximity-check-before-entity-look.patch @@ -0,0 +1,75 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Martijn Muijsers +Date: Tue, 15 Aug 2023 21:04:55 +0200 +Subject: [PATCH] Do crystal-portal proximity check before entity lookup + +This adds a very cheap distance check when an end crystal is placed. + +Attempting to respawn the dragon, which involves looking up the end crystal +entities near the portal, every time an end crystal is placed, can be slow on +some servers that have players placing end crystals as a style of combat. + +The very cheap distance check prevents running the entity lookup every time. + +diff --git a/src/main/java/net/minecraft/world/item/EndCrystalItem.java b/src/main/java/net/minecraft/world/item/EndCrystalItem.java +index 48317c7436445a7acff9103bac1de558c42f31cc..b62db8c7c8c57e43869ee239ebf4b02f112355d9 100644 +--- a/src/main/java/net/minecraft/world/item/EndCrystalItem.java ++++ b/src/main/java/net/minecraft/world/item/EndCrystalItem.java +@@ -30,7 +30,7 @@ public class EndCrystalItem extends Item { + if (!iblockdata.is(Blocks.OBSIDIAN) && !iblockdata.is(Blocks.BEDROCK)) { + return InteractionResult.FAIL; + } else { +- BlockPos blockposition1 = blockposition.above(); ++ BlockPos blockposition1 = blockposition.above(); final BlockPos aboveBlockPosition = blockposition1; // Paper - OBFHELPER + + if (!world.isEmptyBlock(blockposition1)) { + return InteractionResult.FAIL; +@@ -58,7 +58,7 @@ public class EndCrystalItem extends Item { + EndDragonFight enderdragonbattle = ((ServerLevel) world).getDragonFight(); + + if (enderdragonbattle != null) { +- enderdragonbattle.tryRespawn(); ++ enderdragonbattle.tryRespawn(aboveBlockPosition); // Paper - Perf: Do crystal-portal proximity check before entity lookup + } + } + +diff --git a/src/main/java/net/minecraft/world/level/dimension/end/EndDragonFight.java b/src/main/java/net/minecraft/world/level/dimension/end/EndDragonFight.java +index 8ec6b33126096fb9e1de5f41290097e004d7a455..b331c93c82c27f9456fec208a0c008c5bedfa8c4 100644 +--- a/src/main/java/net/minecraft/world/level/dimension/end/EndDragonFight.java ++++ b/src/main/java/net/minecraft/world/level/dimension/end/EndDragonFight.java +@@ -563,6 +563,12 @@ public class EndDragonFight { + } + + public boolean tryRespawn() { // CraftBukkit - return boolean ++ // Paper start - Perf: Do crystal-portal proximity check before entity lookup ++ return this.tryRespawn(null); ++ } ++ ++ public boolean tryRespawn(@Nullable BlockPos placedEndCrystalPos) { // placedEndCrystalPos is null if the tryRespawn() call was not caused by a placed end crystal ++ // Paper end - Perf: Do crystal-portal proximity check before entity lookup + if (this.dragonKilled && this.respawnStage == null) { + BlockPos blockposition = this.portalLocation; + +@@ -580,6 +586,22 @@ public class EndDragonFight { + blockposition = this.portalLocation; + } + ++ // Paper start - Perf: Do crystal-portal proximity check before entity lookup ++ if (placedEndCrystalPos != null) { ++ // The end crystal must be 0 or 1 higher than the portal origin ++ int dy = placedEndCrystalPos.getY() - blockposition.getY(); ++ if (dy != 0 && dy != 1) { ++ return false; ++ } ++ // The end crystal must be within a distance of 1 in one planar direction, and 3 in the other ++ int dx = placedEndCrystalPos.getX() - blockposition.getX(); ++ int dz = placedEndCrystalPos.getZ() - blockposition.getZ(); ++ if (!((dx >= -1 && dx <= 1 && dz >= -3 && dz <= 3) || (dx >= -3 && dx <= 3 && dz >= -1 && dz <= 1))) { ++ return false; ++ } ++ } ++ // Paper end - Perf: Do crystal-portal proximity check before entity lookup ++ + List list = Lists.newArrayList(); + BlockPos blockposition1 = blockposition.above(1); + Iterator iterator = Direction.Plane.HORIZONTAL.iterator(); diff --git a/patches/server/0869-Allow-trident-custom-damage.patch b/patches/server/0869-Allow-trident-custom-damage.patch deleted file mode 100644 index 95137bd7d5fe..000000000000 --- a/patches/server/0869-Allow-trident-custom-damage.patch +++ /dev/null @@ -1,39 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Lulu13022002 <41980282+Lulu13022002@users.noreply.github.com> -Date: Tue, 12 Jul 2022 18:01:14 +0200 -Subject: [PATCH] Allow trident custom damage - - -diff --git a/src/main/java/net/minecraft/world/entity/projectile/ThrownTrident.java b/src/main/java/net/minecraft/world/entity/projectile/ThrownTrident.java -index e45c3a9805d9fac1fabe6d891c817743acd9969e..44c733c5b2c3e9942f28e882ad72306a24459c2c 100644 ---- a/src/main/java/net/minecraft/world/entity/projectile/ThrownTrident.java -+++ b/src/main/java/net/minecraft/world/entity/projectile/ThrownTrident.java -@@ -36,16 +36,19 @@ public class ThrownTrident extends AbstractArrow { - - public ThrownTrident(EntityType type, Level world) { - super(type, world); -+ this.setBaseDamage(net.minecraft.world.item.TridentItem.BASE_DAMAGE); // Paper - Allow trident custom damage - } - - public ThrownTrident(Level world, LivingEntity owner, ItemStack stack) { - super(EntityType.TRIDENT, owner, world, stack, (ItemStack) null); -+ this.setBaseDamage(net.minecraft.world.item.TridentItem.BASE_DAMAGE); // Paper - Allow trident custom damage - this.entityData.set(ThrownTrident.ID_LOYALTY, this.getLoyaltyFromItem(stack)); - this.entityData.set(ThrownTrident.ID_FOIL, stack.hasFoil()); - } - - public ThrownTrident(Level world, double x, double y, double z, ItemStack stack) { - super(EntityType.TRIDENT, x, y, z, world, stack, stack); -+ this.setBaseDamage(net.minecraft.world.item.TridentItem.BASE_DAMAGE); // Paper - Allow trident custom damage - this.entityData.set(ThrownTrident.ID_LOYALTY, this.getLoyaltyFromItem(stack)); - this.entityData.set(ThrownTrident.ID_FOIL, stack.hasFoil()); - } -@@ -129,7 +132,7 @@ public class ThrownTrident extends AbstractArrow { - @Override - protected void onHitEntity(EntityHitResult entityHitResult) { - Entity entity = entityHitResult.getEntity(); -- float f = 8.0F; -+ float f = (float) this.getBaseDamage(); // Paper - Allow trident custom damage - Entity entity1 = this.getOwner(); - DamageSource damagesource = this.damageSources().trident(this, (Entity) (entity1 == null ? this : entity1)); - Level world = this.level(); diff --git a/patches/server/0877-Skip-POI-finding-if-stuck-in-vehicle.patch b/patches/server/0869-Skip-POI-finding-if-stuck-in-vehicle.patch similarity index 100% rename from patches/server/0877-Skip-POI-finding-if-stuck-in-vehicle.patch rename to patches/server/0869-Skip-POI-finding-if-stuck-in-vehicle.patch diff --git a/patches/server/0870-Add-slot-sanity-checks-in-container-clicks.patch b/patches/server/0870-Add-slot-sanity-checks-in-container-clicks.patch new file mode 100644 index 000000000000..0925155fcfc9 --- /dev/null +++ b/patches/server/0870-Add-slot-sanity-checks-in-container-clicks.patch @@ -0,0 +1,43 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Nassim Jahnke +Date: Mon, 11 Sep 2023 12:01:57 +1000 +Subject: [PATCH] Add slot sanity checks in container clicks + + +diff --git a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java +index 0cb0d2f863efb86bb589b30bae61ac57bda40fab..f92624ccd43f448abdee92c975d613cbcb3457c6 100644 +--- a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java ++++ b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java +@@ -3008,6 +3008,12 @@ public class ServerGamePacketListenerImpl extends ServerCommonPacketListenerImpl + break; + case SWAP: + if ((packet.getButtonNum() >= 0 && packet.getButtonNum() < 9) || packet.getButtonNum() == 40) { ++ // Paper start - Add slot sanity checks to container clicks ++ if (packet.getSlotNum() < 0) { ++ action = InventoryAction.NOTHING; ++ break; ++ } ++ // Paper end - Add slot sanity checks to container clicks + click = (packet.getButtonNum() == 40) ? ClickType.SWAP_OFFHAND : ClickType.NUMBER_KEY; + Slot clickedSlot = this.player.containerMenu.getSlot(packet.getSlotNum()); + if (clickedSlot.mayPickup(this.player)) { +diff --git a/src/main/java/net/minecraft/world/inventory/AbstractContainerMenu.java b/src/main/java/net/minecraft/world/inventory/AbstractContainerMenu.java +index fc23e2dc42e907d5f8dc134a06102cc3e7fde515..22dc1569aabe69f139d9682ceb81311993706cb8 100644 +--- a/src/main/java/net/minecraft/world/inventory/AbstractContainerMenu.java ++++ b/src/main/java/net/minecraft/world/inventory/AbstractContainerMenu.java +@@ -460,6 +460,7 @@ public abstract class AbstractContainerMenu { + this.resetQuickCraft(); + } + } else if (this.quickcraftStatus == 1) { ++ if (slotIndex < 0) return; // Paper - Add slot sanity checks to container clicks + slot = (Slot) this.slots.get(slotIndex); + itemstack = this.getCarried(); + if (AbstractContainerMenu.canItemQuickReplace(slot, itemstack, true) && slot.mayPlace(itemstack) && (this.quickcraftType == 2 || itemstack.getCount() > this.quickcraftSlots.size()) && this.canDragTo(slot)) { +@@ -634,6 +635,7 @@ public abstract class AbstractContainerMenu { + int j2; + + if (actionType == ClickType.SWAP && (button >= 0 && button < 9 || button == 40)) { ++ if (slotIndex < 0) return; // Paper - Add slot sanity checks to container clicks + ItemStack itemstack4 = playerinventory.getItem(button); + + slot = (Slot) this.slots.get(slotIndex); diff --git a/patches/server/0870-Expose-hand-in-BlockCanBuildEvent.patch b/patches/server/0870-Expose-hand-in-BlockCanBuildEvent.patch deleted file mode 100644 index 4abc7e9fc5fc..000000000000 --- a/patches/server/0870-Expose-hand-in-BlockCanBuildEvent.patch +++ /dev/null @@ -1,32 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: The456gamer -Date: Mon, 21 Aug 2023 14:13:42 +0100 -Subject: [PATCH] Expose hand in BlockCanBuildEvent - - -diff --git a/src/main/java/net/minecraft/world/item/BlockItem.java b/src/main/java/net/minecraft/world/item/BlockItem.java -index 7180996027f70aef7afe32fb2adfce6431429401..fc7d978f9e57814a933b9cb725c3af1e7d403795 100644 ---- a/src/main/java/net/minecraft/world/item/BlockItem.java -+++ b/src/main/java/net/minecraft/world/item/BlockItem.java -@@ -191,7 +191,7 @@ public class BlockItem extends Item { - boolean defaultReturn = (!this.mustSurvive() || state.canSurvive(context.getLevel(), context.getClickedPos())) && world.checkEntityCollision(state, entityhuman, voxelshapecollision, context.getClickedPos(), true); // Paper - Cancel hit for vanished players - org.bukkit.entity.Player player = (context.getPlayer() instanceof ServerPlayer) ? (org.bukkit.entity.Player) context.getPlayer().getBukkitEntity() : null; - -- BlockCanBuildEvent event = new BlockCanBuildEvent(CraftBlock.at(context.getLevel(), context.getClickedPos()), player, CraftBlockData.fromData(state), defaultReturn); -+ BlockCanBuildEvent event = new BlockCanBuildEvent(CraftBlock.at(context.getLevel(), context.getClickedPos()), player, CraftBlockData.fromData(state), defaultReturn, org.bukkit.craftbukkit.CraftEquipmentSlot.getHand(context.getHand())); // Paper - Expose hand in BlockCanBuildEvent - context.getLevel().getCraftServer().getPluginManager().callEvent(event); - - return event.isBuildable(); -diff --git a/src/main/java/net/minecraft/world/item/StandingAndWallBlockItem.java b/src/main/java/net/minecraft/world/item/StandingAndWallBlockItem.java -index 39b8b3675ac58409e05fac07e07c8016c5280d81..f8f909ebdad5e96379e8bd8c610164ef0697368e 100644 ---- a/src/main/java/net/minecraft/world/item/StandingAndWallBlockItem.java -+++ b/src/main/java/net/minecraft/world/item/StandingAndWallBlockItem.java -@@ -59,7 +59,7 @@ public class StandingAndWallBlockItem extends BlockItem { - boolean defaultReturn = world.isUnobstructed(iblockdata1, blockposition, CollisionContext.empty()); - org.bukkit.entity.Player player = (context.getPlayer() instanceof ServerPlayer) ? (org.bukkit.entity.Player) context.getPlayer().getBukkitEntity() : null; - -- BlockCanBuildEvent event = new BlockCanBuildEvent(CraftBlock.at(world, blockposition), player, CraftBlockData.fromData(iblockdata1), defaultReturn); -+ BlockCanBuildEvent event = new BlockCanBuildEvent(CraftBlock.at(world, blockposition), player, CraftBlockData.fromData(iblockdata1), defaultReturn, org.bukkit.craftbukkit.CraftEquipmentSlot.getHand(context.getHand())); // Paper - Expose hand in BlockCanBuildEvent - context.getLevel().getCraftServer().getPluginManager().callEvent(event); - - return (event.isBuildable()) ? iblockdata1 : null; diff --git a/patches/server/0871-Call-BlockRedstoneEvents-for-lecterns.patch b/patches/server/0871-Call-BlockRedstoneEvents-for-lecterns.patch new file mode 100644 index 000000000000..7726626d78f7 --- /dev/null +++ b/patches/server/0871-Call-BlockRedstoneEvents-for-lecterns.patch @@ -0,0 +1,27 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Warrior <50800980+Warriorrrr@users.noreply.github.com> +Date: Wed, 13 Sep 2023 05:46:10 +0200 +Subject: [PATCH] Call BlockRedstoneEvents for lecterns + + +diff --git a/src/main/java/net/minecraft/world/level/block/LecternBlock.java b/src/main/java/net/minecraft/world/level/block/LecternBlock.java +index 44d322d26187bd7528799069d0e08dbf571a57f3..3537795720be76483579fc50715914974c97c9c4 100644 +--- a/src/main/java/net/minecraft/world/level/block/LecternBlock.java ++++ b/src/main/java/net/minecraft/world/level/block/LecternBlock.java +@@ -180,6 +180,16 @@ public class LecternBlock extends BaseEntityBlock { + } + + private static void changePowered(Level world, BlockPos pos, BlockState state, boolean powered) { ++ // Paper start - call BlockRedstoneEvents for lecterns ++ final int currentRedstoneLevel = state.getValue(LecternBlock.POWERED) ? 15 : 0, targetRedstoneLevel = powered ? 15 : 0; ++ if (currentRedstoneLevel != targetRedstoneLevel) { ++ final org.bukkit.event.block.BlockRedstoneEvent event = org.bukkit.craftbukkit.event.CraftEventFactory.callRedstoneChange(world, pos, currentRedstoneLevel, targetRedstoneLevel); ++ ++ if (event.getNewCurrent() != targetRedstoneLevel) { ++ return; ++ } ++ } ++ // Paper end - call BlockRedstoneEvents for lecterns + world.setBlock(pos, (BlockState) state.setValue(LecternBlock.POWERED, powered), 3); + LecternBlock.updateBelow(world, pos, state); + } diff --git a/patches/server/0871-Optimize-nearest-structure-border-iteration.patch b/patches/server/0871-Optimize-nearest-structure-border-iteration.patch deleted file mode 100644 index df5f60c52cbd..000000000000 --- a/patches/server/0871-Optimize-nearest-structure-border-iteration.patch +++ /dev/null @@ -1,39 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Martijn Muijsers -Date: Mon, 21 Aug 2023 21:05:09 +0200 -Subject: [PATCH] Optimize nearest structure border iteration - -Getting the nearest generated structure contains a nested set of loops that -iterates over all chunks at a specific chessboard distance. It does this by -iterating over the entire square of chunks within that distance, and checking -if the coordinates are at exactly the right distance to be on the border. - -This patch optimizes the iteration by only iterating over the border chunks. -This evaluated chunks are the same, and in the same order, as before, to -ensure that the returned found structure (which may for example be a buried -treasure that will be marked on a treasure map) is the same as in vanilla. - -diff --git a/src/main/java/net/minecraft/world/level/chunk/ChunkGenerator.java b/src/main/java/net/minecraft/world/level/chunk/ChunkGenerator.java -index 102de569415ef011dacdca9a6ea8134d0ef62454..29697fad32dad3377eebc82d280ba48d3c1ad516 100644 ---- a/src/main/java/net/minecraft/world/level/chunk/ChunkGenerator.java -+++ b/src/main/java/net/minecraft/world/level/chunk/ChunkGenerator.java -@@ -265,12 +265,15 @@ public abstract class ChunkGenerator { - int i1 = placement.spacing(); - - for (int j1 = -radius; j1 <= radius; ++j1) { -- boolean flag1 = j1 == -radius || j1 == radius; -+ // Paper start - Perf: iterate over border chunks instead of entire square chunk area -+ boolean flag1 = j1 == -radius || j1 == radius; final boolean onBorderAlongZAxis = flag1; // Paper - OBFHELPER - -- for (int k1 = -radius; k1 <= radius; ++k1) { -- boolean flag2 = k1 == -radius || k1 == radius; -+ for (int k1 = -radius; k1 <= radius; k1 += onBorderAlongZAxis ? 1 : radius * 2) { -+ // boolean flag2 = k1 == -radius || k1 == radius; - -- if (flag1 || flag2) { -+ // if (flag1 || flag2) { -+ if (true) { -+ // Paper end - Perf: iterate over border chunks instead of entire square chunk area - int l1 = centerChunkX + i1 * j1; - int i2 = centerChunkZ + i1 * k1; - ChunkPos chunkcoordintpair = placement.getPotentialStructureChunk(seed, l1, i2); diff --git a/patches/server/0872-Allow-proper-checking-of-empty-item-stacks.patch b/patches/server/0872-Allow-proper-checking-of-empty-item-stacks.patch new file mode 100644 index 000000000000..85819379c183 --- /dev/null +++ b/patches/server/0872-Allow-proper-checking-of-empty-item-stacks.patch @@ -0,0 +1,31 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Aeltumn +Date: Mon, 28 Aug 2023 13:44:09 +0200 +Subject: [PATCH] Allow proper checking of empty item stacks + + +diff --git a/src/main/java/org/bukkit/craftbukkit/inventory/CraftItemStack.java b/src/main/java/org/bukkit/craftbukkit/inventory/CraftItemStack.java +index 134db8c2dd72d0651fc889cc8931e7c971f62deb..30eba68435387daa3917fa2b3071892c350d9ddc 100644 +--- a/src/main/java/org/bukkit/craftbukkit/inventory/CraftItemStack.java ++++ b/src/main/java/org/bukkit/craftbukkit/inventory/CraftItemStack.java +@@ -45,12 +45,19 @@ public final class CraftItemStack extends ItemStack { + } + // Paper end - MC Utils + ++ // Paper start - override isEmpty to use vanilla's impl ++ @Override ++ public boolean isEmpty() { ++ return handle == null || handle.isEmpty(); ++ } ++ // Paper end - override isEmpty to use vanilla's impl ++ + public static net.minecraft.world.item.ItemStack asNMSCopy(ItemStack original) { + if (original instanceof CraftItemStack) { + CraftItemStack stack = (CraftItemStack) original; + return stack.handle == null ? net.minecraft.world.item.ItemStack.EMPTY : stack.handle.copy(); + } +- if (original == null || original.getType() == Material.AIR) { ++ if (original == null || original.isEmpty()) { // Paper - override isEmpty to use vanilla's impl; use isEmpty + return net.minecraft.world.item.ItemStack.EMPTY; + } + diff --git a/patches/server/0872-Implement-OfflinePlayer-isConnected.patch b/patches/server/0872-Implement-OfflinePlayer-isConnected.patch deleted file mode 100644 index 199101506f46..000000000000 --- a/patches/server/0872-Implement-OfflinePlayer-isConnected.patch +++ /dev/null @@ -1,42 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Aeltumn -Date: Thu, 24 Aug 2023 13:05:30 +0200 -Subject: [PATCH] Implement OfflinePlayer#isConnected - - -diff --git a/src/main/java/org/bukkit/craftbukkit/CraftOfflinePlayer.java b/src/main/java/org/bukkit/craftbukkit/CraftOfflinePlayer.java -index 2c2c4db31a746b4eb853dc04c6b3e5631bbfa034..4f4e3ee18d586f61706504218cddc06a38ca5580 100644 ---- a/src/main/java/org/bukkit/craftbukkit/CraftOfflinePlayer.java -+++ b/src/main/java/org/bukkit/craftbukkit/CraftOfflinePlayer.java -@@ -54,6 +54,13 @@ public class CraftOfflinePlayer implements OfflinePlayer, ConfigurationSerializa - return this.getPlayer() != null; - } - -+ // Paper start -+ @Override -+ public boolean isConnected() { -+ return false; -+ } -+ // Paper end -+ - @Override - public String getName() { - Player player = this.getPlayer(); -diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java -index a3912d4de96a6bb2b9b3977705fa3facb891f73d..a1b870fb3d1fd5d209474c47bf523d0d5d58ce39 100644 ---- a/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java -+++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java -@@ -256,6 +256,13 @@ public class CraftPlayer extends CraftHumanEntity implements Player { - return this.server.getPlayer(this.getUniqueId()) != null; - } - -+ // Paper start -+ @Override -+ public boolean isConnected() { -+ return !this.getHandle().hasDisconnected(); -+ } -+ // Paper end -+ - @Override - public InetSocketAddress getAddress() { - if (this.getHandle().connection.protocol() == null) return null; diff --git a/patches/server/0873-Fix-silent-equipment-change-for-mobs.patch b/patches/server/0873-Fix-silent-equipment-change-for-mobs.patch new file mode 100644 index 000000000000..14b0a2dab0f4 --- /dev/null +++ b/patches/server/0873-Fix-silent-equipment-change-for-mobs.patch @@ -0,0 +1,113 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Lulu13022002 <41980282+Lulu13022002@users.noreply.github.com> +Date: Thu, 31 Aug 2023 17:32:48 +0200 +Subject: [PATCH] Fix silent equipment change for mobs + + +diff --git a/src/main/java/net/minecraft/world/entity/Mob.java b/src/main/java/net/minecraft/world/entity/Mob.java +index ac7a52e77fd2fcbe9f95709b95ba54f8c2a6514b..8a0e65ac8318a467996f48b423db1ac621359fbe 100644 +--- a/src/main/java/net/minecraft/world/entity/Mob.java ++++ b/src/main/java/net/minecraft/world/entity/Mob.java +@@ -1100,19 +1100,26 @@ public abstract class Mob extends LivingEntity implements EquipmentUser, Leashab + + @Override + public void setItemSlot(EquipmentSlot slot, ItemStack stack) { ++ // Paper start - Fix silent equipment change ++ setItemSlot(slot, stack, false); ++ } ++ ++ @Override ++ public void setItemSlot(EquipmentSlot slot, ItemStack stack, boolean silent) { ++ // Paper end - Fix silent equipment change + this.verifyEquippedItem(stack); + switch (slot.getType()) { + case HAND: +- this.onEquipItem(slot, (ItemStack) this.handItems.set(slot.getIndex(), stack), stack); ++ this.onEquipItem(slot, (ItemStack) this.handItems.set(slot.getIndex(), stack), stack, silent); // Paper - Fix silent equipment change + break; + case HUMANOID_ARMOR: +- this.onEquipItem(slot, (ItemStack) this.armorItems.set(slot.getIndex(), stack), stack); ++ this.onEquipItem(slot, (ItemStack) this.armorItems.set(slot.getIndex(), stack), stack, silent); // Paper - Fix silent equipment change + break; + case ANIMAL_ARMOR: + ItemStack itemstack1 = this.bodyArmorItem; + + this.bodyArmorItem = stack; +- this.onEquipItem(slot, itemstack1, stack); ++ this.onEquipItem(slot, itemstack1, stack, silent); // Paper - Fix silent equipment change + } + + } +diff --git a/src/main/java/net/minecraft/world/entity/monster/AbstractSkeleton.java b/src/main/java/net/minecraft/world/entity/monster/AbstractSkeleton.java +index 0cb179bc28cc863b09a079b37b957744f26f3e1d..32670a3cb4b54b66d655197e3fde834d2b2b6d34 100644 +--- a/src/main/java/net/minecraft/world/entity/monster/AbstractSkeleton.java ++++ b/src/main/java/net/minecraft/world/entity/monster/AbstractSkeleton.java +@@ -258,8 +258,8 @@ public abstract class AbstractSkeleton extends Monster implements RangedAttackMo + // Paper end - shouldBurnInDay API + + @Override +- public void setItemSlot(EquipmentSlot slot, ItemStack stack) { +- super.setItemSlot(slot, stack); ++ public void setItemSlot(EquipmentSlot slot, ItemStack stack, boolean silent) { // Paper - Fix silent equipment change ++ super.setItemSlot(slot, stack, silent); // Paper - Fix silent equipment change + if (!this.level().isClientSide) { + this.reassessWeaponGoal(); + } +diff --git a/src/test/java/io/papermc/paper/entity/EntitySetItemSlotSilentOverrideTest.java b/src/test/java/io/papermc/paper/entity/EntitySetItemSlotSilentOverrideTest.java +new file mode 100644 +index 0000000000000000000000000000000000000000..18e0ae815528f3b2f944febc01df48f346b3a4f6 +--- /dev/null ++++ b/src/test/java/io/papermc/paper/entity/EntitySetItemSlotSilentOverrideTest.java +@@ -0,0 +1,52 @@ ++package io.papermc.paper.entity; ++ ++import io.github.classgraph.ClassGraph; ++import io.github.classgraph.ClassInfo; ++import io.github.classgraph.MethodInfo; ++import io.github.classgraph.MethodInfoList; ++import io.github.classgraph.MethodParameterInfo; ++import io.github.classgraph.ScanResult; ++import java.util.ArrayList; ++import java.util.List; ++import java.util.stream.Stream; ++import org.bukkit.support.environment.Normal; ++import org.junit.jupiter.params.ParameterizedTest; ++import org.junit.jupiter.params.provider.MethodSource; ++ ++import static org.junit.jupiter.api.Assertions.fail; ++ ++@Normal ++public class EntitySetItemSlotSilentOverrideTest { ++ ++ public static Stream parameters() { ++ final List classInfo = new ArrayList<>(); ++ try (ScanResult scanResult = new ClassGraph() ++ .enableClassInfo() ++ .enableMethodInfo() ++ .whitelistPackages("net.minecraft") ++ .scan() ++ ) { ++ for (final ClassInfo subclass : scanResult.getSubclasses("net.minecraft.world.entity.LivingEntity")) { ++ final MethodInfoList setItemSlot = subclass.getDeclaredMethodInfo("setItemSlot"); ++ if (!setItemSlot.isEmpty()) { ++ classInfo.add(subclass); ++ } ++ } ++ } ++ return classInfo.stream(); ++ } ++ ++ @ParameterizedTest ++ @MethodSource("parameters") ++ public void checkSetItemSlotSilentOverrides(ClassInfo overridesSetItemSlot) { ++ final MethodInfoList setItemSlot = overridesSetItemSlot.getDeclaredMethodInfo("setItemSlot"); ++ for (final MethodInfo methodInfo : setItemSlot) { ++ for (final MethodParameterInfo methodParameterInfo : methodInfo.getParameterInfo()) { ++ if ("boolean".equals(methodParameterInfo.getTypeDescriptor().toStringWithSimpleNames())) { ++ return; ++ } ++ } ++ } ++ fail(overridesSetItemSlot.getName() + " needs to override setItemSlot with the boolean silent parameter as well"); ++ } ++} diff --git a/patches/server/0873-Fix-slot-desync.patch b/patches/server/0873-Fix-slot-desync.patch deleted file mode 100644 index aa5a0249855f..000000000000 --- a/patches/server/0873-Fix-slot-desync.patch +++ /dev/null @@ -1,129 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Jake Potrebic -Date: Wed, 23 Aug 2023 13:22:09 -0700 -Subject: [PATCH] Fix slot desync - -General patch fixing slot desyncs between the server and client that -result from cancelled events/paper introduced logic. - -Co-authored-by: Minecrell -Co-authored-by: Newwind - -diff --git a/src/main/java/net/minecraft/server/level/ServerPlayer.java b/src/main/java/net/minecraft/server/level/ServerPlayer.java -index 7270b6fa96bae937663c0fea77887e21fbd0eb57..530728cd4b258444f6efe064903b521f4b23cd04 100644 ---- a/src/main/java/net/minecraft/server/level/ServerPlayer.java -+++ b/src/main/java/net/minecraft/server/level/ServerPlayer.java -@@ -400,6 +400,7 @@ public class ServerPlayer extends net.minecraft.world.entity.player.Player { - - // Use method to resend items in hands in case of client desync, because the item use got cancelled. - // For example, when cancelling the leash event -+ @Deprecated // Paper - this shouldn't be used, use the regular sendAllDataToRemote call to resync all - public void resendItemInHands() { - this.containerMenu.findSlot(this.getInventory(), this.getInventory().selected).ifPresent(s -> { - this.containerSynchronizer.sendSlotChange(this.containerMenu, s, this.getMainHandItem()); -diff --git a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java -index be0ce72bb493593a3d2eb7d7c37e3a650b7cc34b..0ffa95542a0ed49a3f83700841f7c76c0717ae22 100644 ---- a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java -+++ b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java -@@ -2729,10 +2729,11 @@ public class ServerGamePacketListenerImpl extends ServerCommonPacketListenerImpl - // Refresh the current entity metadata - entity.refreshEntityData(ServerGamePacketListenerImpl.this.player); - // SPIGOT-7136 - Allays -- if (entity instanceof Allay) { -+ if (entity instanceof Allay || entity instanceof net.minecraft.world.entity.animal.horse.AbstractHorse) { // Paper - Fix horse armor desync - ServerGamePacketListenerImpl.this.send(new ClientboundSetEquipmentPacket(entity.getId(), Arrays.stream(net.minecraft.world.entity.EquipmentSlot.values()).map((slot) -> Pair.of(slot, ((LivingEntity) entity).getItemBySlot(slot).copy())).collect(Collectors.toList()))); -- ServerGamePacketListenerImpl.this.player.containerMenu.sendAllDataToRemote(); - } -+ -+ ServerGamePacketListenerImpl.this.player.containerMenu.sendAllDataToRemote(); // Paper - fix slot desync - always refresh player inventory - } - - if (event.isCancelled()) { -diff --git a/src/main/java/net/minecraft/world/entity/Entity.java b/src/main/java/net/minecraft/world/entity/Entity.java -index 84d283cd637b5c84a8682acec503b0e3831d1684..bd0e205c760414784ae483452fd02292939dcb5c 100644 ---- a/src/main/java/net/minecraft/world/entity/Entity.java -+++ b/src/main/java/net/minecraft/world/entity/Entity.java -@@ -2638,8 +2638,9 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess - if (!this.level().isClientSide()) { - // CraftBukkit start - fire PlayerLeashEntityEvent - if (CraftEventFactory.callPlayerLeashEntityEvent(this, player, player, hand).isCancelled()) { -- ((ServerPlayer) player).resendItemInHands(); // SPIGOT-7615: Resend to fix client desync with used item -+ // ((ServerPlayer) player).resendItemInHands(); // SPIGOT-7615: Resend to fix client desync with used item // Paper - Fix inventory desync - ((ServerPlayer) player).connection.send(new ClientboundSetEntityLinkPacket(this, leashable.getLeashHolder())); -+ player.containerMenu.sendAllDataToRemote(); // Paper - Fix inventory desync - return InteractionResult.PASS; - } - // CraftBukkit end -diff --git a/src/main/java/net/minecraft/world/entity/animal/Cow.java b/src/main/java/net/minecraft/world/entity/animal/Cow.java -index 5a7b1be351834a6b8889b1380cede1be025cb302..e336934f37075a827843e4b1bb2b6b660d2c60c9 100644 ---- a/src/main/java/net/minecraft/world/entity/animal/Cow.java -+++ b/src/main/java/net/minecraft/world/entity/animal/Cow.java -@@ -101,6 +101,7 @@ public class Cow extends Animal { - PlayerBucketFillEvent event = CraftEventFactory.callPlayerBucketFillEvent((ServerLevel) player.level(), player, this.blockPosition(), this.blockPosition(), null, itemstack, Items.MILK_BUCKET, hand); - - if (event.isCancelled()) { -+ player.containerMenu.sendAllDataToRemote(); // Paper - Fix inventory desync - return InteractionResult.PASS; - } - // CraftBukkit end -diff --git a/src/main/java/net/minecraft/world/entity/animal/goat/Goat.java b/src/main/java/net/minecraft/world/entity/animal/goat/Goat.java -index 02e49c7ae5e120302b6479cf3e3934b9217eebf0..376bcbc189008464f4d518c1e07643431ba96306 100644 ---- a/src/main/java/net/minecraft/world/entity/animal/goat/Goat.java -+++ b/src/main/java/net/minecraft/world/entity/animal/goat/Goat.java -@@ -234,6 +234,7 @@ public class Goat extends Animal { - PlayerBucketFillEvent event = CraftEventFactory.callPlayerBucketFillEvent((ServerLevel) player.level(), player, this.blockPosition(), this.blockPosition(), null, itemstack, Items.MILK_BUCKET, hand); - - if (event.isCancelled()) { -+ player.containerMenu.sendAllDataToRemote(); // Paper - Fix inventory desync - return InteractionResult.PASS; - } - // CraftBukkit end -diff --git a/src/main/java/net/minecraft/world/item/ArmorStandItem.java b/src/main/java/net/minecraft/world/item/ArmorStandItem.java -index 1634a7d5ff06583408cf2f02f2b5f90931b1e02a..066a6e5ed2632a55324ec0d10f2f8a6bf3f30a0f 100644 ---- a/src/main/java/net/minecraft/world/item/ArmorStandItem.java -+++ b/src/main/java/net/minecraft/world/item/ArmorStandItem.java -@@ -55,6 +55,7 @@ public class ArmorStandItem extends Item { - entityarmorstand.moveTo(entityarmorstand.getX(), entityarmorstand.getY(), entityarmorstand.getZ(), f, 0.0F); - // CraftBukkit start - if (org.bukkit.craftbukkit.event.CraftEventFactory.callEntityPlaceEvent(context, entityarmorstand).isCancelled()) { -+ if (context.getPlayer() != null) context.getPlayer().containerMenu.sendAllDataToRemote(); // Paper - Fix inventory desync - return InteractionResult.FAIL; - } - // CraftBukkit end -diff --git a/src/main/java/net/minecraft/world/item/BlockItem.java b/src/main/java/net/minecraft/world/item/BlockItem.java -index fc7d978f9e57814a933b9cb725c3af1e7d403795..96fb69ec6db2e7c8c728435f0c537b076259b2fb 100644 ---- a/src/main/java/net/minecraft/world/item/BlockItem.java -+++ b/src/main/java/net/minecraft/world/item/BlockItem.java -@@ -114,7 +114,7 @@ public class BlockItem extends Item { - if (placeEvent != null && (placeEvent.isCancelled() || !placeEvent.canBuild())) { - blockstate.update(true, false); - -- if (this instanceof SolidBucketItem) { -+ if (true) { // Paper - if the event is called here, the inventory should be updated - ((ServerPlayer) entityhuman).getBukkitEntity().updateInventory(); // SPIGOT-4541 - } - return InteractionResult.FAIL; -diff --git a/src/main/java/net/minecraft/world/item/EndCrystalItem.java b/src/main/java/net/minecraft/world/item/EndCrystalItem.java -index f9a940cdff3983d9d4de46bd5ddc1905f9254dcf..273bb38f14b8af08d123e02742d365fb5d91cdf5 100644 ---- a/src/main/java/net/minecraft/world/item/EndCrystalItem.java -+++ b/src/main/java/net/minecraft/world/item/EndCrystalItem.java -@@ -49,6 +49,7 @@ public class EndCrystalItem extends Item { - entityendercrystal.setShowBottom(false); - // CraftBukkit start - if (org.bukkit.craftbukkit.event.CraftEventFactory.callEntityPlaceEvent(context, entityendercrystal).isCancelled()) { -+ if (context.getPlayer() != null) context.getPlayer().containerMenu.sendAllDataToRemote(); // Paper - Fix inventory desync - return InteractionResult.FAIL; - } - // CraftBukkit end -diff --git a/src/main/java/net/minecraft/world/item/MinecartItem.java b/src/main/java/net/minecraft/world/item/MinecartItem.java -index 66074445d3908b9bb1c8d70e1e27d057720ec8e5..d524fcc191cb95d6ec7f12ae7fceeb8077bb08fc 100644 ---- a/src/main/java/net/minecraft/world/item/MinecartItem.java -+++ b/src/main/java/net/minecraft/world/item/MinecartItem.java -@@ -137,6 +137,7 @@ public class MinecartItem extends Item { - - // CraftBukkit start - if (org.bukkit.craftbukkit.event.CraftEventFactory.callEntityPlaceEvent(context, entityminecartabstract).isCancelled()) { -+ if (context.getPlayer() != null) context.getPlayer().containerMenu.sendAllDataToRemote(); // Paper - Fix inventory desync - return InteractionResult.FAIL; - } - // CraftBukkit end diff --git a/patches/server/0874-Add-titleOverride-to-InventoryOpenEvent.patch b/patches/server/0874-Add-titleOverride-to-InventoryOpenEvent.patch deleted file mode 100644 index ee1767993470..000000000000 --- a/patches/server/0874-Add-titleOverride-to-InventoryOpenEvent.patch +++ /dev/null @@ -1,120 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Jake Potrebic -Date: Fri, 4 Mar 2022 12:45:03 -0800 -Subject: [PATCH] Add titleOverride to InventoryOpenEvent - - -diff --git a/src/main/java/net/minecraft/server/level/ServerPlayer.java b/src/main/java/net/minecraft/server/level/ServerPlayer.java -index 530728cd4b258444f6efe064903b521f4b23cd04..b65d816bb9feb18ecf74e10e9728c302e5657587 100644 ---- a/src/main/java/net/minecraft/server/level/ServerPlayer.java -+++ b/src/main/java/net/minecraft/server/level/ServerPlayer.java -@@ -1656,12 +1656,17 @@ public class ServerPlayer extends net.minecraft.world.entity.player.Player { - this.nextContainerCounter(); - AbstractContainerMenu container = factory.createMenu(this.containerCounter, this.getInventory(), this); - -+ Component title = null; // Paper - Add titleOverride to InventoryOpenEvent - // CraftBukkit start - Inventory open hook - if (container != null) { - container.setTitle(factory.getDisplayName()); - - boolean cancelled = false; -- container = CraftEventFactory.callInventoryOpenEvent(this, container, cancelled); -+ // Paper start - Add titleOverride to InventoryOpenEvent -+ final com.mojang.datafixers.util.Pair result = CraftEventFactory.callInventoryOpenEventWithTitle(this, container, cancelled); -+ container = result.getSecond(); -+ title = PaperAdventure.asVanilla(result.getFirst()); -+ // Paper end - Add titleOverride to InventoryOpenEvent - if (container == null && !cancelled) { // Let pre-cancelled events fall through - // SPIGOT-5263 - close chest if cancelled - if (factory instanceof Container) { -@@ -1683,7 +1688,7 @@ public class ServerPlayer extends net.minecraft.world.entity.player.Player { - } else { - // CraftBukkit start - this.containerMenu = container; -- if (!this.isImmobile()) this.connection.send(new ClientboundOpenScreenPacket(container.containerId, container.getType(), container.getTitle())); // Paper - Prevent opening inventories when frozen -+ if (!this.isImmobile()) this.connection.send(new ClientboundOpenScreenPacket(container.containerId, container.getType(), Objects.requireNonNullElseGet(title, container::getTitle))); // Paper - Add titleOverride to InventoryOpenEvent - // CraftBukkit end - this.initMenu(container); - return OptionalInt.of(this.containerCounter); -diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftHumanEntity.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftHumanEntity.java -index b782cc64426a058881947ed62316c1cb8d332037..7dcfb45c24d7743956be514c7d554e06aac77b3e 100644 ---- a/src/main/java/org/bukkit/craftbukkit/entity/CraftHumanEntity.java -+++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftHumanEntity.java -@@ -366,12 +366,16 @@ public class CraftHumanEntity extends CraftLivingEntity implements HumanEntity { - Preconditions.checkArgument(windowType != null, "Unknown windowType"); - AbstractContainerMenu container = new CraftContainer(inventory, player, player.nextContainerCounter()); - -- container = CraftEventFactory.callInventoryOpenEvent(player, container); -+ // Paper start - Add titleOverride to InventoryOpenEvent -+ final com.mojang.datafixers.util.Pair result = CraftEventFactory.callInventoryOpenEventWithTitle(player, container); -+ container = result.getSecond(); -+ // Paper end - Add titleOverride to InventoryOpenEvent - if (container == null) return; - - //String title = container.getBukkitView().getTitle(); // Paper - comment - net.kyori.adventure.text.Component adventure$title = container.getBukkitView().title(); // Paper - if (adventure$title == null) adventure$title = net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer.legacySection().deserialize(container.getBukkitView().getTitle()); // Paper -+ if (result.getFirst() != null) adventure$title = result.getFirst(); // Paper - Add titleOverride to InventoryOpenEvent - - //player.connection.send(new ClientboundOpenScreenPacket(container.containerId, windowType, CraftChatMessage.fromString(title)[0])); // Paper - comment - if (!player.isImmobile()) player.connection.send(new ClientboundOpenScreenPacket(container.containerId, windowType, io.papermc.paper.adventure.PaperAdventure.asVanilla(adventure$title))); // Paper - Prevent opening inventories when frozen -@@ -448,7 +452,10 @@ public class CraftHumanEntity extends CraftLivingEntity implements HumanEntity { - } - - // Trigger an INVENTORY_OPEN event -- container = CraftEventFactory.callInventoryOpenEvent(player, container); -+ // Paper start - Add titleOverride to InventoryOpenEvent -+ final com.mojang.datafixers.util.Pair result = CraftEventFactory.callInventoryOpenEventWithTitle(player, container); -+ container = result.getSecond(); -+ // Paper end - Add titleOverride to InventoryOpenEvent - if (container == null) { - return; - } -@@ -459,6 +466,7 @@ public class CraftHumanEntity extends CraftLivingEntity implements HumanEntity { - //String title = inventory.getTitle(); // Paper - comment - net.kyori.adventure.text.Component adventure$title = inventory.title(); // Paper - if (adventure$title == null) adventure$title = net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer.legacySection().deserialize(inventory.getTitle()); // Paper -+ if (result.getFirst() != null) adventure$title = result.getFirst(); // Paper - Add titleOverride to InventoryOpenEvent - //player.connection.send(new ClientboundOpenScreenPacket(container.containerId, windowType, CraftChatMessage.fromString(title)[0])); // Paper - comment - if (!player.isImmobile()) player.connection.send(new ClientboundOpenScreenPacket(container.containerId, windowType, io.papermc.paper.adventure.PaperAdventure.asVanilla(adventure$title))); // Paper - Prevent opening inventories when frozen - player.containerMenu = container; -diff --git a/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java b/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java -index 6b241ec85f360ec79f18b911175d7092861024a4..2690c471b819f8308f6db44150025dfa323d4e0c 100644 ---- a/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java -+++ b/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java -@@ -1403,10 +1403,21 @@ public class CraftEventFactory { - } - - public static AbstractContainerMenu callInventoryOpenEvent(ServerPlayer player, AbstractContainerMenu container) { -- return CraftEventFactory.callInventoryOpenEvent(player, container, false); -+ // Paper start - Add titleOverride to InventoryOpenEvent -+ return callInventoryOpenEventWithTitle(player, container).getSecond(); -+ } -+ public static com.mojang.datafixers.util.Pair callInventoryOpenEventWithTitle(ServerPlayer player, AbstractContainerMenu container) { -+ return CraftEventFactory.callInventoryOpenEventWithTitle(player, container, false); -+ // Paper end - Add titleOverride to InventoryOpenEvent - } - -+ @Deprecated @io.papermc.paper.annotation.DoNotUse // Paper - use method that acknowledges title overrides - public static AbstractContainerMenu callInventoryOpenEvent(ServerPlayer player, AbstractContainerMenu container, boolean cancelled) { -+ // Paper start - Add titleOverride to InventoryOpenEvent -+ return callInventoryOpenEventWithTitle(player, container, cancelled).getSecond(); -+ } -+ public static com.mojang.datafixers.util.Pair callInventoryOpenEventWithTitle(ServerPlayer player, AbstractContainerMenu container, boolean cancelled) { -+ // Paper end - Add titleOverride to InventoryOpenEvent - if (player.containerMenu != player.inventoryMenu) { // fire INVENTORY_CLOSE if one already open - player.connection.handleContainerClose(new ServerboundContainerClosePacket(player.containerMenu.containerId), InventoryCloseEvent.Reason.OPEN_NEW); // Paper - Inventory close reason - } -@@ -1421,10 +1432,10 @@ public class CraftEventFactory { - - if (event.isCancelled()) { - container.transferTo(player.containerMenu, craftPlayer); -- return null; -+ return com.mojang.datafixers.util.Pair.of(null, null); // Paper - Add titleOverride to InventoryOpenEvent - } - -- return container; -+ return com.mojang.datafixers.util.Pair.of(event.titleOverride(), container); // Paper - Add titleOverride to InventoryOpenEvent - } - - public static ItemStack callPreCraftEvent(CraftingContainer matrix, Container resultInventory, ItemStack result, InventoryView lastCraftView, boolean isRepair) { diff --git a/patches/server/0874-Fix-spigot-s-Forced-Stats.patch b/patches/server/0874-Fix-spigot-s-Forced-Stats.patch new file mode 100644 index 000000000000..8e920d0d8974 --- /dev/null +++ b/patches/server/0874-Fix-spigot-s-Forced-Stats.patch @@ -0,0 +1,54 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: The456gamer +Date: Mon, 28 Aug 2023 01:32:39 +0100 +Subject: [PATCH] Fix spigot's Forced-Stats + +moves the loading after vanilla loading, so it overrides the values. +disables saving any forced stats, so it stays at the same value (without enabling disableStatSaving) +fixes stat initialization to not cause a NullPointerException + +diff --git a/src/main/java/net/minecraft/stats/ServerStatsCounter.java b/src/main/java/net/minecraft/stats/ServerStatsCounter.java +index ec437644adff6a6ec1e3fe2dd3a6354edafde1db..da7e1a69ecb4e6b3be2d8544ac406aa519bd196e 100644 +--- a/src/main/java/net/minecraft/stats/ServerStatsCounter.java ++++ b/src/main/java/net/minecraft/stats/ServerStatsCounter.java +@@ -48,13 +48,6 @@ public class ServerStatsCounter extends StatsCounter { + public ServerStatsCounter(MinecraftServer server, File file) { + this.server = server; + this.file = file; +- // Spigot start +- for ( Map.Entry entry : org.spigotmc.SpigotConfig.forcedStats.entrySet() ) +- { +- Stat wrapper = Stats.CUSTOM.get( entry.getKey() ); +- this.stats.put( wrapper, entry.getValue().intValue() ); +- } +- // Spigot end + if (file.isFile()) { + try { + this.parseLocal(server.getFixerUpper(), FileUtils.readFileToString(file)); +@@ -65,6 +58,18 @@ public class ServerStatsCounter extends StatsCounter { + } + } + ++ // Paper start - Moved after stat fetching for player state file ++ // Moves the loading after vanilla loading, so it overrides the values. ++ // Disables saving any forced stats, so it stays at the same value (without enabling disableStatSaving) ++ // Fixes stat initialization to not cause a NullPointerException ++ // Spigot start ++ for ( Map.Entry entry : org.spigotmc.SpigotConfig.forcedStats.entrySet() ) ++ { ++ Stat wrapper = Stats.CUSTOM.get(java.util.Objects.requireNonNull(BuiltInRegistries.CUSTOM_STAT.getValue(entry.getKey()))); // Paper - ensured by SpigotConfig#stats ++ this.stats.put( wrapper, entry.getValue().intValue() ); ++ } ++ // Spigot end ++ // Paper end - Moved after stat fetching for player state file + } + + public void save() { +@@ -80,6 +85,7 @@ public class ServerStatsCounter extends StatsCounter { + @Override + public void setValue(Player player, Stat stat, int value) { + if ( org.spigotmc.SpigotConfig.disableStatSaving ) return; // Spigot ++ if (stat.getType() == Stats.CUSTOM && stat.getValue() instanceof final ResourceLocation resourceLocation && org.spigotmc.SpigotConfig.forcedStats.get(resourceLocation) != null) return; // Paper - disable saving forced stats + super.setValue(player, stat, value); + this.dirty.add(stat); + } diff --git a/patches/server/0875-Add-missing-InventoryHolders-to-inventories.patch b/patches/server/0875-Add-missing-InventoryHolders-to-inventories.patch new file mode 100644 index 000000000000..d6bf23a4f847 --- /dev/null +++ b/patches/server/0875-Add-missing-InventoryHolders-to-inventories.patch @@ -0,0 +1,314 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Jake Potrebic +Date: Mon, 24 Jan 2022 00:09:02 -0800 +Subject: [PATCH] Add missing InventoryHolders to inventories + + +diff --git a/src/main/java/net/minecraft/world/Container.java b/src/main/java/net/minecraft/world/Container.java +index db41ffbd24adccd78edf3368ba1f7a3ab9f6072c..5db5ba026462ca642dcee718af732f80fadabef5 100644 +--- a/src/main/java/net/minecraft/world/Container.java ++++ b/src/main/java/net/minecraft/world/Container.java +@@ -103,7 +103,7 @@ public interface Container extends Clearable { + + java.util.List getViewers(); + +- org.bukkit.inventory.InventoryHolder getOwner(); ++ org.bukkit.inventory.@org.jetbrains.annotations.Nullable InventoryHolder getOwner(); // Paper - annotation + + void setMaxStackSize(int size); + +diff --git a/src/main/java/net/minecraft/world/SimpleContainer.java b/src/main/java/net/minecraft/world/SimpleContainer.java +index b0963c534afe5be164701cb283f1849e32ae5a86..7ed52b887c4d766c23220a8809914d5d80f12ea4 100644 +--- a/src/main/java/net/minecraft/world/SimpleContainer.java ++++ b/src/main/java/net/minecraft/world/SimpleContainer.java +@@ -30,7 +30,7 @@ public class SimpleContainer implements Container, StackedContentsCompatible { + // CraftBukkit start - add fields and methods + public List transaction = new java.util.ArrayList(); + private int maxStack = MAX_STACK; +- protected org.bukkit.inventory.InventoryHolder bukkitOwner; ++ protected @Nullable org.bukkit.inventory.InventoryHolder bukkitOwner; // Paper - annotation + + public List getContents() { + return this.items; +@@ -58,6 +58,11 @@ public class SimpleContainer implements Container, StackedContentsCompatible { + } + + public org.bukkit.inventory.InventoryHolder getOwner() { ++ // Paper start - Add missing InventoryHolders ++ if (this.bukkitOwner == null && this.bukkitOwnerCreator != null) { ++ this.bukkitOwner = this.bukkitOwnerCreator.get(); ++ } ++ // Paper end - Add missing InventoryHolders + return this.bukkitOwner; + } + +@@ -86,6 +91,13 @@ public class SimpleContainer implements Container, StackedContentsCompatible { + public SimpleContainer(int size) { + this(size, null); + } ++ // Paper start - Add missing InventoryHolders ++ private @Nullable java.util.function.Supplier bukkitOwnerCreator; ++ public SimpleContainer(java.util.function.Supplier bukkitOwnerCreator, int size) { ++ this(size); ++ this.bukkitOwnerCreator = bukkitOwnerCreator; ++ } ++ // Paper end - Add missing InventoryHolders + + public SimpleContainer(int i, org.bukkit.inventory.InventoryHolder owner) { + this.bukkitOwner = owner; +diff --git a/src/main/java/net/minecraft/world/inventory/AbstractContainerMenu.java b/src/main/java/net/minecraft/world/inventory/AbstractContainerMenu.java +index 22dc1569aabe69f139d9682ceb81311993706cb8..85caec29a705f216eff8a5ae11f250697891a05c 100644 +--- a/src/main/java/net/minecraft/world/inventory/AbstractContainerMenu.java ++++ b/src/main/java/net/minecraft/world/inventory/AbstractContainerMenu.java +@@ -1054,4 +1054,15 @@ public abstract class AbstractContainerMenu { + this.stateId = this.stateId + 1 & 32767; + return this.stateId; + } ++ ++ // Paper start - Add missing InventoryHolders ++ // The reason this is a supplier, is that the createHolder method uses the bukkit InventoryView#getTopInventory to get the inventory in question ++ // and that can't be obtained safely until the AbstractContainerMenu has been fully constructed. Using a supplier lazily ++ // initializes the InventoryHolder safely. ++ protected final Supplier createBlockHolder(final ContainerLevelAccess context) { ++ //noinspection ConstantValue ++ Preconditions.checkArgument(context != null, "context was null"); ++ return () -> context.createBlockHolder(this); ++ } ++ // Paper end - Add missing InventoryHolders + } +diff --git a/src/main/java/net/minecraft/world/inventory/BeaconMenu.java b/src/main/java/net/minecraft/world/inventory/BeaconMenu.java +index c0dfa04511110be366ee5b0bd75efc51afcce2e4..e0ee1c10960d24d2369d16f22431a8e02a5570aa 100644 +--- a/src/main/java/net/minecraft/world/inventory/BeaconMenu.java ++++ b/src/main/java/net/minecraft/world/inventory/BeaconMenu.java +@@ -42,7 +42,7 @@ public class BeaconMenu extends AbstractContainerMenu { + public BeaconMenu(int syncId, Container inventory, ContainerData propertyDelegate, ContainerLevelAccess context) { + super(MenuType.BEACON, syncId); + this.player = (Inventory) inventory; // CraftBukkit - TODO: check this +- this.beacon = new SimpleContainer(1) { // CraftBukkit - decompile error ++ this.beacon = new SimpleContainer(this.createBlockHolder(context), 1) { // CraftBukkit - decompile error // Paper - Add missing InventoryHolders + @Override + public boolean canPlaceItem(int slot, ItemStack stack) { + return stack.is(ItemTags.BEACON_PAYMENT_ITEMS); +diff --git a/src/main/java/net/minecraft/world/inventory/CartographyTableMenu.java b/src/main/java/net/minecraft/world/inventory/CartographyTableMenu.java +index 8b72d1fd551dcb113f4137aee441a9531c00288a..b3a16b024e46ee8203b225ee429a5c973eab12c6 100644 +--- a/src/main/java/net/minecraft/world/inventory/CartographyTableMenu.java ++++ b/src/main/java/net/minecraft/world/inventory/CartographyTableMenu.java +@@ -54,7 +54,7 @@ public class CartographyTableMenu extends AbstractContainerMenu { + + public CartographyTableMenu(int syncId, Inventory inventory, final ContainerLevelAccess context) { + super(MenuType.CARTOGRAPHY_TABLE, syncId); +- this.container = new SimpleContainer(2) { ++ this.container = new SimpleContainer(this.createBlockHolder(context), 2) { // Paper - Add missing InventoryHolders + @Override + public void setChanged() { + CartographyTableMenu.this.slotsChanged(this); +@@ -68,7 +68,7 @@ public class CartographyTableMenu extends AbstractContainerMenu { + } + // CraftBukkit end + }; +- this.resultContainer = new ResultContainer() { ++ this.resultContainer = new ResultContainer(this.createBlockHolder(context)) { // Paper - Add missing InventoryHolders + @Override + public void setChanged() { + CartographyTableMenu.this.slotsChanged(this); +diff --git a/src/main/java/net/minecraft/world/inventory/ContainerLevelAccess.java b/src/main/java/net/minecraft/world/inventory/ContainerLevelAccess.java +index 85e336637db8643fc5aca1dba724c9b341cbf46f..12b466ccb7c36021cf807c4f3fd2bcb037943abc 100644 +--- a/src/main/java/net/minecraft/world/inventory/ContainerLevelAccess.java ++++ b/src/main/java/net/minecraft/world/inventory/ContainerLevelAccess.java +@@ -21,6 +21,18 @@ public interface ContainerLevelAccess { + return new org.bukkit.Location(this.getWorld().getWorld(), this.getPosition().getX(), this.getPosition().getY(), this.getPosition().getZ()); + } + // CraftBukkit end ++ // Paper start - Add missing InventoryHolders ++ default boolean isBlock() { ++ return false; ++ } ++ ++ default org.bukkit.inventory.@org.jetbrains.annotations.Nullable BlockInventoryHolder createBlockHolder(AbstractContainerMenu menu) { ++ if (!this.isBlock()) { ++ return null; ++ } ++ return new org.bukkit.craftbukkit.inventory.CraftBlockInventoryHolder(this, menu.getBukkitView().getTopInventory()); ++ } ++ // Paper end - Add missing InventoryHolders + + ContainerLevelAccess NULL = new ContainerLevelAccess() { + @Override +@@ -48,6 +60,12 @@ public interface ContainerLevelAccess { + return pos; + } + // CraftBukkit end ++ // Paper start - Add missing InventoryHolders ++ @Override ++ public boolean isBlock() { ++ return true; ++ } ++ // Paper end - Add missing InventoryHolders + + @Override + public Optional evaluate(BiFunction getter) { +diff --git a/src/main/java/net/minecraft/world/inventory/EnchantmentMenu.java b/src/main/java/net/minecraft/world/inventory/EnchantmentMenu.java +index 2642d8ddc79ad97675f958c89ef03da39903f359..36496144dd6fa87163b692034570eba70c83678c 100644 +--- a/src/main/java/net/minecraft/world/inventory/EnchantmentMenu.java ++++ b/src/main/java/net/minecraft/world/inventory/EnchantmentMenu.java +@@ -63,7 +63,7 @@ public class EnchantmentMenu extends AbstractContainerMenu { + + public EnchantmentMenu(int syncId, Inventory playerInventory, ContainerLevelAccess context) { + super(MenuType.ENCHANTMENT, syncId); +- this.enchantSlots = new SimpleContainer(2) { ++ this.enchantSlots = new SimpleContainer(this.createBlockHolder(context), 2) { // Paper - Add missing InventoryHolders + @Override + public void setChanged() { + super.setChanged(); +diff --git a/src/main/java/net/minecraft/world/inventory/GrindstoneMenu.java b/src/main/java/net/minecraft/world/inventory/GrindstoneMenu.java +index 8e58dbb4b110338dfe8add26697d0b1c9ec5fa9c..3b303d41b9facfb2892ff8402ee0de4608db7318 100644 +--- a/src/main/java/net/minecraft/world/inventory/GrindstoneMenu.java ++++ b/src/main/java/net/minecraft/world/inventory/GrindstoneMenu.java +@@ -60,8 +60,8 @@ public class GrindstoneMenu extends AbstractContainerMenu { + + public GrindstoneMenu(int syncId, Inventory playerInventory, final ContainerLevelAccess context) { + super(MenuType.GRINDSTONE, syncId); +- this.resultSlots = new ResultContainer(); +- this.repairSlots = new SimpleContainer(2) { ++ this.resultSlots = new ResultContainer(this.createBlockHolder(context)); // Paper - Add missing InventoryHolders ++ this.repairSlots = new SimpleContainer(this.createBlockHolder(context), 2) { // Paper - Add missing InventoryHolders + @Override + public void setChanged() { + super.setChanged(); +diff --git a/src/main/java/net/minecraft/world/inventory/ItemCombinerMenu.java b/src/main/java/net/minecraft/world/inventory/ItemCombinerMenu.java +index 4734dd90f2a66e1ac7a64b35ecd62a630108cf07..a5d53a656513ae81cc3f9fc506caf6adaba62a8e 100644 +--- a/src/main/java/net/minecraft/world/inventory/ItemCombinerMenu.java ++++ b/src/main/java/net/minecraft/world/inventory/ItemCombinerMenu.java +@@ -17,12 +17,7 @@ public abstract class ItemCombinerMenu extends AbstractContainerMenu { + protected final ContainerLevelAccess access; + protected final Player player; + protected final Container inputSlots; +- protected final ResultContainer resultSlots = new ResultContainer() { +- @Override +- public void setChanged() { +- ItemCombinerMenu.this.slotsChanged(this); +- } +- }; ++ protected final ResultContainer resultSlots; // Paper - Add missing InventoryHolders; delay field init + private final int resultSlotIndex; + + protected boolean mayPickup(Player player, boolean present) { +@@ -36,6 +31,14 @@ public abstract class ItemCombinerMenu extends AbstractContainerMenu { + public ItemCombinerMenu(@Nullable MenuType type, int syncId, Inventory playerInventory, ContainerLevelAccess context, ItemCombinerMenuSlotDefinition forgingSlotsManager) { + super(type, syncId); + this.access = context; ++ // Paper start - Add missing InventoryHolders; delay field init ++ this.resultSlots = new ResultContainer(this.createBlockHolder(this.access)) { ++ @Override ++ public void setChanged() { ++ ItemCombinerMenu.this.slotsChanged(this); ++ } ++ }; ++ // Paper end - Add missing InventoryHolders; delay field init + this.player = playerInventory.player; + this.inputSlots = this.createContainer(forgingSlotsManager.getNumOfInputSlots()); + this.resultSlotIndex = forgingSlotsManager.getResultSlotIndex(); +@@ -82,7 +85,7 @@ public abstract class ItemCombinerMenu extends AbstractContainerMenu { + public abstract void createResult(); + + private SimpleContainer createContainer(int size) { +- return new SimpleContainer(size) { ++ return new SimpleContainer(this.createBlockHolder(this.access), size) { + @Override + public void setChanged() { + super.setChanged(); +diff --git a/src/main/java/net/minecraft/world/inventory/LoomMenu.java b/src/main/java/net/minecraft/world/inventory/LoomMenu.java +index 1b7cf165ab0818792870f43719a6324b282bb57a..98519dd3bccca516acdedc69a59189b1106fb75b 100644 +--- a/src/main/java/net/minecraft/world/inventory/LoomMenu.java ++++ b/src/main/java/net/minecraft/world/inventory/LoomMenu.java +@@ -73,7 +73,7 @@ public class LoomMenu extends AbstractContainerMenu { + this.selectablePatterns = List.of(); + this.slotUpdateListener = () -> { + }; +- this.inputContainer = new SimpleContainer(3) { ++ this.inputContainer = new SimpleContainer(this.createBlockHolder(context), 3) { // Paper - Add missing InventoryHolders + @Override + public void setChanged() { + super.setChanged(); +@@ -88,7 +88,7 @@ public class LoomMenu extends AbstractContainerMenu { + } + // CraftBukkit end + }; +- this.outputContainer = new SimpleContainer(1) { ++ this.outputContainer = new SimpleContainer(this.createBlockHolder(context), 1) { // Paper - Add missing InventoryHolders + @Override + public void setChanged() { + super.setChanged(); +diff --git a/src/main/java/net/minecraft/world/inventory/ResultContainer.java b/src/main/java/net/minecraft/world/inventory/ResultContainer.java +index d4592218d761eb38402e3d95c642e80a708cb333..4c4266a85c38e41e6c7e6144a68624f4daa50c54 100644 +--- a/src/main/java/net/minecraft/world/inventory/ResultContainer.java ++++ b/src/main/java/net/minecraft/world/inventory/ResultContainer.java +@@ -29,7 +29,12 @@ public class ResultContainer implements Container, RecipeCraftingHolder { + } + + public org.bukkit.inventory.InventoryHolder getOwner() { +- return null; // Result slots don't get an owner ++ // Paper start - Add missing InventoryHolders ++ if (this.holder == null && this.holderCreator != null) { ++ this.holder = this.holderCreator.get(); ++ } ++ return this.holder; // Result slots don't get an owner ++ // Paper end - Add missing InventoryHolders + } + + // Don't need a transaction; the InventoryCrafting keeps track of it for us +@@ -53,6 +58,14 @@ public class ResultContainer implements Container, RecipeCraftingHolder { + return null; + } + // CraftBukkit end ++ // Paper start - Add missing InventoryHolders ++ private @Nullable java.util.function.Supplier holderCreator; ++ private @Nullable org.bukkit.inventory.InventoryHolder holder; ++ public ResultContainer(java.util.function.Supplier holderCreator) { ++ this(); ++ this.holderCreator = holderCreator; ++ } ++ // Paper end - Add missing InventoryHolders + + public ResultContainer() { + this.itemStacks = NonNullList.withSize(1, ItemStack.EMPTY); +diff --git a/src/main/java/net/minecraft/world/inventory/StonecutterMenu.java b/src/main/java/net/minecraft/world/inventory/StonecutterMenu.java +index a93870952e2ef674028b8a20aa52a685c743e7ea..ca65965757e6f12abc972250a04817c7547bb0bd 100644 +--- a/src/main/java/net/minecraft/world/inventory/StonecutterMenu.java ++++ b/src/main/java/net/minecraft/world/inventory/StonecutterMenu.java +@@ -69,7 +69,7 @@ public class StonecutterMenu extends AbstractContainerMenu { + this.input = ItemStack.EMPTY; + this.slotUpdateListener = () -> { + }; +- this.container = new SimpleContainer(1) { ++ this.container = new SimpleContainer(this.createBlockHolder(context), 1) { // Paper - Add missing InventoryHolders + @Override + public void setChanged() { + super.setChanged(); +@@ -84,7 +84,7 @@ public class StonecutterMenu extends AbstractContainerMenu { + } + // CraftBukkit end + }; +- this.resultContainer = new ResultContainer(); ++ this.resultContainer = new ResultContainer(this.createBlockHolder(context)); // Paper - Add missing InventoryHolders + this.access = context; + this.level = playerInventory.player.level(); + this.inputSlot = this.addSlot(new Slot(this.container, 0, 20, 33)); +diff --git a/src/main/java/org/bukkit/craftbukkit/inventory/CraftBlockInventoryHolder.java b/src/main/java/org/bukkit/craftbukkit/inventory/CraftBlockInventoryHolder.java +index 7ae484b0fa5bf5494c6ead15f7f1c0fa840ae270..7129eb5f5cea39992b4c690cb421004004a952ea 100644 +--- a/src/main/java/org/bukkit/craftbukkit/inventory/CraftBlockInventoryHolder.java ++++ b/src/main/java/org/bukkit/craftbukkit/inventory/CraftBlockInventoryHolder.java +@@ -17,6 +17,13 @@ public class CraftBlockInventoryHolder implements BlockInventoryHolder { + this.block = CraftBlock.at(world, pos); + this.inventory = new CraftInventory(inv); + } ++ // Paper start - Add missing InventoryHolders ++ public CraftBlockInventoryHolder(net.minecraft.world.inventory.ContainerLevelAccess levelAccess, Inventory inventory) { ++ com.google.common.base.Preconditions.checkArgument(levelAccess.isBlock()); ++ this.block = CraftBlock.at(levelAccess.getWorld(), levelAccess.getPosition()); ++ this.inventory = inventory; ++ } ++ // Paper end - Add missing InventoryHolders + + @Override + public Block getBlock() { diff --git a/patches/server/0875-Configure-sniffer-egg-hatch-time.patch b/patches/server/0875-Configure-sniffer-egg-hatch-time.patch deleted file mode 100644 index 7def55b1f7f4..000000000000 --- a/patches/server/0875-Configure-sniffer-egg-hatch-time.patch +++ /dev/null @@ -1,28 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Lulu13022002 <41980282+Lulu13022002@users.noreply.github.com> -Date: Tue, 27 Jun 2023 13:26:06 +0200 -Subject: [PATCH] Configure sniffer egg hatch time - - -diff --git a/src/main/java/net/minecraft/world/level/block/SnifferEggBlock.java b/src/main/java/net/minecraft/world/level/block/SnifferEggBlock.java -index b943384eb6a4612993556036f0d3beec6939a559..ffe5e14cf8190774c8935513ebeb041eda81f912 100644 ---- a/src/main/java/net/minecraft/world/level/block/SnifferEggBlock.java -+++ b/src/main/java/net/minecraft/world/level/block/SnifferEggBlock.java -@@ -62,7 +62,7 @@ public class SnifferEggBlock extends Block { - - // Paper start - Call BlockFadeEvent - private void rescheduleTick(ServerLevel world, BlockPos pos) { -- int baseDelay = hatchBoost(world, pos) ? BOOSTED_HATCH_TIME_TICKS : REGULAR_HATCH_TIME_TICKS; -+ int baseDelay = hatchBoost(world, pos) ? world.paperConfig().entities.sniffer.boostedHatchTime.or(BOOSTED_HATCH_TIME_TICKS) : world.paperConfig().entities.sniffer.hatchTime.or(REGULAR_HATCH_TIME_TICKS); // Paper - Configure sniffer egg hatch time - world.scheduleTick(pos, this, (baseDelay / 3) + world.random.nextInt(RANDOM_HATCH_OFFSET_TICKS)); - // reschedule to avoid being stuck here and behave like the other calls (see #onPlace) - } -@@ -104,7 +104,7 @@ public class SnifferEggBlock extends Block { - world.levelEvent(3009, pos, 0); - } - -- int i = bl ? 12000 : 24000; -+ int i = bl ? world.paperConfig().entities.sniffer.boostedHatchTime.or(BOOSTED_HATCH_TIME_TICKS) : world.paperConfig().entities.sniffer.hatchTime.or(REGULAR_HATCH_TIME_TICKS); // Paper - int j = i / 3; - world.gameEvent(GameEvent.BLOCK_PLACE, pos, GameEvent.Context.of(state)); - world.scheduleTick(pos, this, j + world.random.nextInt(300)); diff --git a/patches/server/0876-Do-crystal-portal-proximity-check-before-entity-look.patch b/patches/server/0876-Do-crystal-portal-proximity-check-before-entity-look.patch deleted file mode 100644 index ee3855f2b8b4..000000000000 --- a/patches/server/0876-Do-crystal-portal-proximity-check-before-entity-look.patch +++ /dev/null @@ -1,75 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Martijn Muijsers -Date: Tue, 15 Aug 2023 21:04:55 +0200 -Subject: [PATCH] Do crystal-portal proximity check before entity lookup - -This adds a very cheap distance check when an end crystal is placed. - -Attempting to respawn the dragon, which involves looking up the end crystal -entities near the portal, every time an end crystal is placed, can be slow on -some servers that have players placing end crystals as a style of combat. - -The very cheap distance check prevents running the entity lookup every time. - -diff --git a/src/main/java/net/minecraft/world/item/EndCrystalItem.java b/src/main/java/net/minecraft/world/item/EndCrystalItem.java -index 273bb38f14b8af08d123e02742d365fb5d91cdf5..5f51e64cb0611a4ba6bdcdcacbcba1063a7f3a5c 100644 ---- a/src/main/java/net/minecraft/world/item/EndCrystalItem.java -+++ b/src/main/java/net/minecraft/world/item/EndCrystalItem.java -@@ -30,7 +30,7 @@ public class EndCrystalItem extends Item { - if (!iblockdata.is(Blocks.OBSIDIAN) && !iblockdata.is(Blocks.BEDROCK)) { - return InteractionResult.FAIL; - } else { -- BlockPos blockposition1 = blockposition.above(); -+ BlockPos blockposition1 = blockposition.above(); final BlockPos aboveBlockPosition = blockposition1; // Paper - OBFHELPER - - if (!world.isEmptyBlock(blockposition1)) { - return InteractionResult.FAIL; -@@ -58,7 +58,7 @@ public class EndCrystalItem extends Item { - EndDragonFight enderdragonbattle = ((ServerLevel) world).getDragonFight(); - - if (enderdragonbattle != null) { -- enderdragonbattle.tryRespawn(); -+ enderdragonbattle.tryRespawn(aboveBlockPosition); // Paper - Perf: Do crystal-portal proximity check before entity lookup - } - } - -diff --git a/src/main/java/net/minecraft/world/level/dimension/end/EndDragonFight.java b/src/main/java/net/minecraft/world/level/dimension/end/EndDragonFight.java -index 4a0479fcff94bf6a1f239c1f694202345cdea1d4..84300f2f7b7be4f5281edd8e263646dbcbb3ba07 100644 ---- a/src/main/java/net/minecraft/world/level/dimension/end/EndDragonFight.java -+++ b/src/main/java/net/minecraft/world/level/dimension/end/EndDragonFight.java -@@ -560,6 +560,12 @@ public class EndDragonFight { - } - - public boolean tryRespawn() { // CraftBukkit - return boolean -+ // Paper start - Perf: Do crystal-portal proximity check before entity lookup -+ return this.tryRespawn(null); -+ } -+ -+ public boolean tryRespawn(@Nullable BlockPos placedEndCrystalPos) { // placedEndCrystalPos is null if the tryRespawn() call was not caused by a placed end crystal -+ // Paper end - Perf: Do crystal-portal proximity check before entity lookup - if (this.dragonKilled && this.respawnStage == null) { - BlockPos blockposition = this.portalLocation; - -@@ -577,6 +583,22 @@ public class EndDragonFight { - blockposition = this.portalLocation; - } - -+ // Paper start - Perf: Do crystal-portal proximity check before entity lookup -+ if (placedEndCrystalPos != null) { -+ // The end crystal must be 0 or 1 higher than the portal origin -+ int dy = placedEndCrystalPos.getY() - blockposition.getY(); -+ if (dy != 0 && dy != 1) { -+ return false; -+ } -+ // The end crystal must be within a distance of 1 in one planar direction, and 3 in the other -+ int dx = placedEndCrystalPos.getX() - blockposition.getX(); -+ int dz = placedEndCrystalPos.getZ() - blockposition.getZ(); -+ if (!((dx >= -1 && dx <= 1 && dz >= -3 && dz <= 3) || (dx >= -3 && dx <= 3 && dz >= -1 && dz <= 1))) { -+ return false; -+ } -+ } -+ // Paper end - Perf: Do crystal-portal proximity check before entity lookup -+ - List list = Lists.newArrayList(); - BlockPos blockposition1 = blockposition.above(1); - Iterator iterator = Direction.Plane.HORIZONTAL.iterator(); diff --git a/patches/server/0876-Do-not-read-tile-entities-in-chunks-that-are-positio.patch b/patches/server/0876-Do-not-read-tile-entities-in-chunks-that-are-positio.patch new file mode 100644 index 000000000000..c55e56d15738 --- /dev/null +++ b/patches/server/0876-Do-not-read-tile-entities-in-chunks-that-are-positio.patch @@ -0,0 +1,43 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Spottedleaf +Date: Sun, 18 Jun 2023 23:04:46 -0700 +Subject: [PATCH] Do not read tile entities in chunks that are positioned + outside of the chunk + +The tile entities are not accessible and so should not be loaded. +This can happen as a result of users moving regionfiles around, +which would cause a crash on Folia but would appear to function +fine on Paper. + +diff --git a/src/main/java/net/minecraft/world/level/chunk/storage/SerializableChunkData.java b/src/main/java/net/minecraft/world/level/chunk/storage/SerializableChunkData.java +index d7a204216332ccbd6bece23bd507be0366ea4d61..b86b3bf713668999a21c4120b1d16c295531b2ad 100644 +--- a/src/main/java/net/minecraft/world/level/chunk/storage/SerializableChunkData.java ++++ b/src/main/java/net/minecraft/world/level/chunk/storage/SerializableChunkData.java +@@ -363,6 +363,13 @@ public record SerializableChunkData(Registry biomeRegistry, ChunkPos chun + + while (iterator2.hasNext()) { + nbttagcompound = (CompoundTag) iterator2.next(); ++ // Paper start - do not read tile entities positioned outside the chunk ++ final BlockPos blockposition = BlockEntity.getPosFromTag(nbttagcompound); ++ if ((blockposition.getX() >> 4) != chunkPos.x || (blockposition.getZ() >> 4) != chunkPos.z) { ++ LOGGER.warn("Tile entity serialized in chunk {} in world '{}' positioned at {} is located outside of the chunk", chunkPos, world.getWorld().getName(), blockposition); ++ continue; ++ } ++ // Paper end - do not read tile entities positioned outside the chunk + protochunk1.setBlockEntityNbt(nbttagcompound); + } + +@@ -616,6 +623,13 @@ public record SerializableChunkData(Registry biomeRegistry, ChunkPos chun + chunk.setBlockEntityNbt(nbttagcompound); + } else { + BlockPos blockposition = BlockEntity.getPosFromTag(nbttagcompound); ++ // Paper start - do not read tile entities positioned outside the chunk ++ ChunkPos chunkPos = chunk.getPos(); ++ if ((blockposition.getX() >> 4) != chunkPos.x || (blockposition.getZ() >> 4) != chunkPos.z) { ++ LOGGER.warn("Tile entity serialized in chunk " + chunkPos + " in world '" + world.getWorld().getName() + "' positioned at " + blockposition + " is located outside of the chunk"); ++ continue; ++ } ++ // Paper end - do not read tile entities positioned outside the chunk + BlockEntity tileentity = BlockEntity.loadStatic(blockposition, chunk.getBlockState(blockposition), nbttagcompound, world.registryAccess()); + + if (tileentity != null) { diff --git a/patches/server/0885-Add-missing-logs-for-log-ips-config-option.patch b/patches/server/0877-Add-missing-logs-for-log-ips-config-option.patch similarity index 100% rename from patches/server/0885-Add-missing-logs-for-log-ips-config-option.patch rename to patches/server/0877-Add-missing-logs-for-log-ips-config-option.patch diff --git a/patches/server/0878-Add-slot-sanity-checks-in-container-clicks.patch b/patches/server/0878-Add-slot-sanity-checks-in-container-clicks.patch deleted file mode 100644 index 3be07bcfa2b4..000000000000 --- a/patches/server/0878-Add-slot-sanity-checks-in-container-clicks.patch +++ /dev/null @@ -1,43 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Nassim Jahnke -Date: Mon, 11 Sep 2023 12:01:57 +1000 -Subject: [PATCH] Add slot sanity checks in container clicks - - -diff --git a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java -index 0ffa95542a0ed49a3f83700841f7c76c0717ae22..d377804026f3f5cf80a623228d24e09c56ef0dae 100644 ---- a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java -+++ b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java -@@ -2986,6 +2986,12 @@ public class ServerGamePacketListenerImpl extends ServerCommonPacketListenerImpl - break; - case SWAP: - if ((packet.getButtonNum() >= 0 && packet.getButtonNum() < 9) || packet.getButtonNum() == 40) { -+ // Paper start - Add slot sanity checks to container clicks -+ if (packet.getSlotNum() < 0) { -+ action = InventoryAction.NOTHING; -+ break; -+ } -+ // Paper end - Add slot sanity checks to container clicks - click = (packet.getButtonNum() == 40) ? ClickType.SWAP_OFFHAND : ClickType.NUMBER_KEY; - Slot clickedSlot = this.player.containerMenu.getSlot(packet.getSlotNum()); - if (clickedSlot.mayPickup(this.player)) { -diff --git a/src/main/java/net/minecraft/world/inventory/AbstractContainerMenu.java b/src/main/java/net/minecraft/world/inventory/AbstractContainerMenu.java -index 0e380151b038e2133013eb7d73621cf247b5b954..afd8f48bd41d2cac413c292f7988c903da1dc700 100644 ---- a/src/main/java/net/minecraft/world/inventory/AbstractContainerMenu.java -+++ b/src/main/java/net/minecraft/world/inventory/AbstractContainerMenu.java -@@ -423,6 +423,7 @@ public abstract class AbstractContainerMenu { - this.resetQuickCraft(); - } - } else if (this.quickcraftStatus == 1) { -+ if (slotIndex < 0) return; // Paper - Add slot sanity checks to container clicks - slot = (Slot) this.slots.get(slotIndex); - itemstack = this.getCarried(); - if (AbstractContainerMenu.canItemQuickReplace(slot, itemstack, true) && slot.mayPlace(itemstack) && (this.quickcraftType == 2 || itemstack.getCount() > this.quickcraftSlots.size()) && this.canDragTo(slot)) { -@@ -597,6 +598,7 @@ public abstract class AbstractContainerMenu { - int j2; - - if (actionType == ClickType.SWAP && (button >= 0 && button < 9 || button == 40)) { -+ if (slotIndex < 0) return; // Paper - Add slot sanity checks to container clicks - ItemStack itemstack4 = playerinventory.getItem(button); - - slot = (Slot) this.slots.get(slotIndex); diff --git a/patches/server/0878-Fix-race-condition-on-UpgradeData.BlockFixers-class-.patch b/patches/server/0878-Fix-race-condition-on-UpgradeData.BlockFixers-class-.patch new file mode 100644 index 000000000000..b6ec32df6171 --- /dev/null +++ b/patches/server/0878-Fix-race-condition-on-UpgradeData.BlockFixers-class-.patch @@ -0,0 +1,27 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Spottedleaf +Date: Tue, 8 Aug 2023 17:29:33 -0700 +Subject: [PATCH] Fix race condition on UpgradeData.BlockFixers class init + +The CHUNKY_FIXERS field is modified during the constructors +of the BlockFixers, but the code that uses CHUNKY_FIXERS does +not properly ensure that BlockFixers has been initialised before +using it, leading to a possible race condition where instances of +BlockFixers are accessed before they have initialised correctly. + +We can force the class to initialise fully before accessing the +field by calling any method on the class, and for convenience +we use values(). + +diff --git a/src/main/java/net/minecraft/world/level/chunk/UpgradeData.java b/src/main/java/net/minecraft/world/level/chunk/UpgradeData.java +index ce3d79ebc7f933d0b34b3f8f71bbec872076847a..ce84851c0c2bf9a066fd5e07be8635d3dcaea0b9 100644 +--- a/src/main/java/net/minecraft/world/level/chunk/UpgradeData.java ++++ b/src/main/java/net/minecraft/world/level/chunk/UpgradeData.java +@@ -153,6 +153,7 @@ public class UpgradeData { + Fluid fluid = tick.type() == Fluids.EMPTY ? level.getFluidState(tick.pos()).getType() : tick.type(); + level.scheduleTick(tick.pos(), fluid, tick.delay(), tick.priority()); + }); ++ UpgradeData.BlockFixers.values(); // Paper - force the class init so that we don't access CHUNKY_FIXERS before all BlockFixers are initialised + CHUNKY_FIXERS.forEach(logic -> logic.processChunk(level)); + } + diff --git a/patches/server/0879-Call-BlockRedstoneEvents-for-lecterns.patch b/patches/server/0879-Call-BlockRedstoneEvents-for-lecterns.patch deleted file mode 100644 index 2c76754071e4..000000000000 --- a/patches/server/0879-Call-BlockRedstoneEvents-for-lecterns.patch +++ /dev/null @@ -1,27 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Warrior <50800980+Warriorrrr@users.noreply.github.com> -Date: Wed, 13 Sep 2023 05:46:10 +0200 -Subject: [PATCH] Call BlockRedstoneEvents for lecterns - - -diff --git a/src/main/java/net/minecraft/world/level/block/LecternBlock.java b/src/main/java/net/minecraft/world/level/block/LecternBlock.java -index 751ff3d51f8ae57f847c3acf7a7cd663a6ae1c68..0c52e1f8bc233bb66e53f4c69e1d8757382bbe81 100644 ---- a/src/main/java/net/minecraft/world/level/block/LecternBlock.java -+++ b/src/main/java/net/minecraft/world/level/block/LecternBlock.java -@@ -179,6 +179,16 @@ public class LecternBlock extends BaseEntityBlock { - } - - private static void changePowered(Level world, BlockPos pos, BlockState state, boolean powered) { -+ // Paper start - call BlockRedstoneEvents for lecterns -+ final int currentRedstoneLevel = state.getValue(LecternBlock.POWERED) ? 15 : 0, targetRedstoneLevel = powered ? 15 : 0; -+ if (currentRedstoneLevel != targetRedstoneLevel) { -+ final org.bukkit.event.block.BlockRedstoneEvent event = org.bukkit.craftbukkit.event.CraftEventFactory.callRedstoneChange(world, pos, currentRedstoneLevel, targetRedstoneLevel); -+ -+ if (event.getNewCurrent() != targetRedstoneLevel) { -+ return; -+ } -+ } -+ // Paper end - call BlockRedstoneEvents for lecterns - world.setBlock(pos, (BlockState) state.setValue(LecternBlock.POWERED, powered), 3); - LecternBlock.updateBelow(world, pos, state); - } diff --git a/patches/server/0887-Fix-NPE-in-AdvancementProgress-getDateAwarded.patch b/patches/server/0879-Fix-NPE-in-AdvancementProgress-getDateAwarded.patch similarity index 100% rename from patches/server/0887-Fix-NPE-in-AdvancementProgress-getDateAwarded.patch rename to patches/server/0879-Fix-NPE-in-AdvancementProgress-getDateAwarded.patch diff --git a/patches/server/0880-Allow-proper-checking-of-empty-item-stacks.patch b/patches/server/0880-Allow-proper-checking-of-empty-item-stacks.patch deleted file mode 100644 index c578a2c3c2b5..000000000000 --- a/patches/server/0880-Allow-proper-checking-of-empty-item-stacks.patch +++ /dev/null @@ -1,31 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Aeltumn -Date: Mon, 28 Aug 2023 13:44:09 +0200 -Subject: [PATCH] Allow proper checking of empty item stacks - - -diff --git a/src/main/java/org/bukkit/craftbukkit/inventory/CraftItemStack.java b/src/main/java/org/bukkit/craftbukkit/inventory/CraftItemStack.java -index 4d29c34e221b749b6972c7ed79ac1f86da999ed7..c5a09f086d35f84c0a30266f78e06e2dfb5603e6 100644 ---- a/src/main/java/org/bukkit/craftbukkit/inventory/CraftItemStack.java -+++ b/src/main/java/org/bukkit/craftbukkit/inventory/CraftItemStack.java -@@ -40,12 +40,19 @@ public final class CraftItemStack extends ItemStack { - } - // Paper end - MC Utils - -+ // Paper start - override isEmpty to use vanilla's impl -+ @Override -+ public boolean isEmpty() { -+ return handle == null || handle.isEmpty(); -+ } -+ // Paper end - override isEmpty to use vanilla's impl -+ - public static net.minecraft.world.item.ItemStack asNMSCopy(ItemStack original) { - if (original instanceof CraftItemStack) { - CraftItemStack stack = (CraftItemStack) original; - return stack.handle == null ? net.minecraft.world.item.ItemStack.EMPTY : stack.handle.copy(); - } -- if (original == null || original.getType() == Material.AIR) { -+ if (original == null || original.isEmpty()) { // Paper - override isEmpty to use vanilla's impl; use isEmpty - return net.minecraft.world.item.ItemStack.EMPTY; - } - diff --git a/patches/server/0880-Fix-team-sidebar-objectives-not-being-cleared.patch b/patches/server/0880-Fix-team-sidebar-objectives-not-being-cleared.patch new file mode 100644 index 000000000000..cebe7aefc27f --- /dev/null +++ b/patches/server/0880-Fix-team-sidebar-objectives-not-being-cleared.patch @@ -0,0 +1,25 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Jake Potrebic +Date: Sat, 23 Sep 2023 22:31:54 -0700 +Subject: [PATCH] Fix team sidebar objectives not being cleared + +Objectives displayed in team sidebars were not cleared when switching +scoreboards. If a player's scoreboard has a displayed objective for the +'gold' sidebar, and their scoreboard was switched to one where they +still had a 'gold' team, it would still be displayed + +diff --git a/src/main/java/org/bukkit/craftbukkit/scoreboard/CraftScoreboardManager.java b/src/main/java/org/bukkit/craftbukkit/scoreboard/CraftScoreboardManager.java +index c7ca6210d6ae37fe95068c9baa5fb654f95307e0..f3184be3853dfc4df4ae4b8af764dfef07628ef4 100644 +--- a/src/main/java/org/bukkit/craftbukkit/scoreboard/CraftScoreboardManager.java ++++ b/src/main/java/org/bukkit/craftbukkit/scoreboard/CraftScoreboardManager.java +@@ -87,8 +87,8 @@ public final class CraftScoreboardManager implements ScoreboardManager { + + // Old objective tracking + HashSet removed = new HashSet<>(); +- for (int i = 0; i < 3; ++i) { +- Objective scoreboardobjective = oldboard.getDisplayObjective(net.minecraft.world.scores.DisplaySlot.BY_ID.apply(i)); ++ for (net.minecraft.world.scores.DisplaySlot slot : net.minecraft.world.scores.DisplaySlot.values()) { // Paper - clear all display slots ++ Objective scoreboardobjective = oldboard.getDisplayObjective(slot); // Paper - clear all display slots + if (scoreboardobjective != null && !removed.contains(scoreboardobjective)) { + entityplayer.connection.send(new ClientboundSetObjectivePacket(scoreboardobjective, 1)); + removed.add(scoreboardobjective); diff --git a/patches/server/0881-Fix-missing-map-initialize-event-call.patch b/patches/server/0881-Fix-missing-map-initialize-event-call.patch new file mode 100644 index 000000000000..0eb662ab8269 --- /dev/null +++ b/patches/server/0881-Fix-missing-map-initialize-event-call.patch @@ -0,0 +1,48 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Warrior <50800980+Warriorrrr@users.noreply.github.com> +Date: Sun, 24 Sep 2023 18:35:28 +0200 +Subject: [PATCH] Fix missing map initialize event call + +== AT == +public net.minecraft.world.level.storage.DimensionDataStorage readSavedData(Ljava/util/function/Function;Lnet/minecraft/util/datafix/DataFixTypes;Ljava/lang/String;)Lnet/minecraft/world/level/saveddata/SavedData; + +diff --git a/src/main/java/net/minecraft/server/level/ServerLevel.java b/src/main/java/net/minecraft/server/level/ServerLevel.java +index 0db41d36d5daf015c750fb0246ab3e5da1cd81a2..7cecbac43f1cd2d9516034ea9d2633c0c76e61f4 100644 +--- a/src/main/java/net/minecraft/server/level/ServerLevel.java ++++ b/src/main/java/net/minecraft/server/level/ServerLevel.java +@@ -1696,13 +1696,29 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe + @Nullable + @Override + public MapItemSavedData getMapData(MapId id) { +- // CraftBukkit start +- MapItemSavedData worldmap = (MapItemSavedData) this.getServer().overworld().getDataStorage().get(MapItemSavedData.factory(), id.key()); +- if (worldmap != null) { +- worldmap.id = id; ++ // Paper start - Call missing map initialize event and set id ++ final DimensionDataStorage storage = this.getServer().overworld().getDataStorage(); ++ ++ final Optional cacheEntry = storage.cache.get(id.key()); ++ if (cacheEntry == null) { // Cache did not contain, try to load and may init ++ final MapItemSavedData worldmap = storage.get(MapItemSavedData.factory(), id.key()); // get populates the cache ++ if (worldmap != null) { // map was read, init it and return ++ worldmap.id = id; ++ new MapInitializeEvent(worldmap.mapView).callEvent(); ++ return worldmap; ++ } ++ ++ return null; // Map does not exist, reading failed. + } +- return worldmap; +- // CraftBukkit end ++ ++ // Cache entry exists, update it with the id ref and return. ++ if (cacheEntry.orElse(null) instanceof final MapItemSavedData mapItemSavedData) { ++ mapItemSavedData.id = id; ++ return mapItemSavedData; ++ } ++ ++ return null; ++ // Paper end - Call missing map initialize event and set id + } + + @Override diff --git a/patches/server/0881-Fix-silent-equipment-change-for-mobs.patch b/patches/server/0881-Fix-silent-equipment-change-for-mobs.patch deleted file mode 100644 index 882525c86d9e..000000000000 --- a/patches/server/0881-Fix-silent-equipment-change-for-mobs.patch +++ /dev/null @@ -1,113 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Lulu13022002 <41980282+Lulu13022002@users.noreply.github.com> -Date: Thu, 31 Aug 2023 17:32:48 +0200 -Subject: [PATCH] Fix silent equipment change for mobs - - -diff --git a/src/main/java/net/minecraft/world/entity/Mob.java b/src/main/java/net/minecraft/world/entity/Mob.java -index e87360e21e6eb7b0161c34a3ac6cb83d18bcd1e8..cf1f6a0081f0dbcf43842ec23accf6c3ae9b79d8 100644 ---- a/src/main/java/net/minecraft/world/entity/Mob.java -+++ b/src/main/java/net/minecraft/world/entity/Mob.java -@@ -1106,19 +1106,26 @@ public abstract class Mob extends LivingEntity implements EquipmentUser, Leashab - - @Override - public void setItemSlot(EquipmentSlot slot, ItemStack stack) { -+ // Paper start - Fix silent equipment change -+ setItemSlot(slot, stack, false); -+ } -+ -+ @Override -+ public void setItemSlot(EquipmentSlot slot, ItemStack stack, boolean silent) { -+ // Paper end - Fix silent equipment change - this.verifyEquippedItem(stack); - switch (slot.getType()) { - case HAND: -- this.onEquipItem(slot, (ItemStack) this.handItems.set(slot.getIndex(), stack), stack); -+ this.onEquipItem(slot, (ItemStack) this.handItems.set(slot.getIndex(), stack), stack, silent); // Paper - Fix silent equipment change - break; - case HUMANOID_ARMOR: -- this.onEquipItem(slot, (ItemStack) this.armorItems.set(slot.getIndex(), stack), stack); -+ this.onEquipItem(slot, (ItemStack) this.armorItems.set(slot.getIndex(), stack), stack, silent); // Paper - Fix silent equipment change - break; - case ANIMAL_ARMOR: - ItemStack itemstack1 = this.bodyArmorItem; - - this.bodyArmorItem = stack; -- this.onEquipItem(slot, itemstack1, stack); -+ this.onEquipItem(slot, itemstack1, stack, silent); // Paper - Fix silent equipment change - } - - } -diff --git a/src/main/java/net/minecraft/world/entity/monster/AbstractSkeleton.java b/src/main/java/net/minecraft/world/entity/monster/AbstractSkeleton.java -index 6627126ab02dbd5e9d1de6b186d75d850ef11280..3b5cf6ffb74d11bea5eb21bd66d679734ff5000c 100644 ---- a/src/main/java/net/minecraft/world/entity/monster/AbstractSkeleton.java -+++ b/src/main/java/net/minecraft/world/entity/monster/AbstractSkeleton.java -@@ -254,8 +254,8 @@ public abstract class AbstractSkeleton extends Monster implements RangedAttackMo - // Paper end - shouldBurnInDay API - - @Override -- public void setItemSlot(EquipmentSlot slot, ItemStack stack) { -- super.setItemSlot(slot, stack); -+ public void setItemSlot(EquipmentSlot slot, ItemStack stack, boolean silent) { // Paper - Fix silent equipment change -+ super.setItemSlot(slot, stack, silent); // Paper - Fix silent equipment change - if (!this.level().isClientSide) { - this.reassessWeaponGoal(); - } -diff --git a/src/test/java/io/papermc/paper/entity/EntitySetItemSlotSilentOverrideTest.java b/src/test/java/io/papermc/paper/entity/EntitySetItemSlotSilentOverrideTest.java -new file mode 100644 -index 0000000000000000000000000000000000000000..18e0ae815528f3b2f944febc01df48f346b3a4f6 ---- /dev/null -+++ b/src/test/java/io/papermc/paper/entity/EntitySetItemSlotSilentOverrideTest.java -@@ -0,0 +1,52 @@ -+package io.papermc.paper.entity; -+ -+import io.github.classgraph.ClassGraph; -+import io.github.classgraph.ClassInfo; -+import io.github.classgraph.MethodInfo; -+import io.github.classgraph.MethodInfoList; -+import io.github.classgraph.MethodParameterInfo; -+import io.github.classgraph.ScanResult; -+import java.util.ArrayList; -+import java.util.List; -+import java.util.stream.Stream; -+import org.bukkit.support.environment.Normal; -+import org.junit.jupiter.params.ParameterizedTest; -+import org.junit.jupiter.params.provider.MethodSource; -+ -+import static org.junit.jupiter.api.Assertions.fail; -+ -+@Normal -+public class EntitySetItemSlotSilentOverrideTest { -+ -+ public static Stream parameters() { -+ final List classInfo = new ArrayList<>(); -+ try (ScanResult scanResult = new ClassGraph() -+ .enableClassInfo() -+ .enableMethodInfo() -+ .whitelistPackages("net.minecraft") -+ .scan() -+ ) { -+ for (final ClassInfo subclass : scanResult.getSubclasses("net.minecraft.world.entity.LivingEntity")) { -+ final MethodInfoList setItemSlot = subclass.getDeclaredMethodInfo("setItemSlot"); -+ if (!setItemSlot.isEmpty()) { -+ classInfo.add(subclass); -+ } -+ } -+ } -+ return classInfo.stream(); -+ } -+ -+ @ParameterizedTest -+ @MethodSource("parameters") -+ public void checkSetItemSlotSilentOverrides(ClassInfo overridesSetItemSlot) { -+ final MethodInfoList setItemSlot = overridesSetItemSlot.getDeclaredMethodInfo("setItemSlot"); -+ for (final MethodInfo methodInfo : setItemSlot) { -+ for (final MethodParameterInfo methodParameterInfo : methodInfo.getParameterInfo()) { -+ if ("boolean".equals(methodParameterInfo.getTypeDescriptor().toStringWithSimpleNames())) { -+ return; -+ } -+ } -+ } -+ fail(overridesSetItemSlot.getName() + " needs to override setItemSlot with the boolean silent parameter as well"); -+ } -+} diff --git a/patches/server/0882-Fix-spigot-s-Forced-Stats.patch b/patches/server/0882-Fix-spigot-s-Forced-Stats.patch deleted file mode 100644 index 554f1f884493..000000000000 --- a/patches/server/0882-Fix-spigot-s-Forced-Stats.patch +++ /dev/null @@ -1,54 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: The456gamer -Date: Mon, 28 Aug 2023 01:32:39 +0100 -Subject: [PATCH] Fix spigot's Forced-Stats - -moves the loading after vanilla loading, so it overrides the values. -disables saving any forced stats, so it stays at the same value (without enabling disableStatSaving) -fixes stat initialization to not cause a NullPointerException - -diff --git a/src/main/java/net/minecraft/stats/ServerStatsCounter.java b/src/main/java/net/minecraft/stats/ServerStatsCounter.java -index ec437644adff6a6ec1e3fe2dd3a6354edafde1db..fb7342f7a5008a283c3400c6313c637de8210dfa 100644 ---- a/src/main/java/net/minecraft/stats/ServerStatsCounter.java -+++ b/src/main/java/net/minecraft/stats/ServerStatsCounter.java -@@ -48,13 +48,6 @@ public class ServerStatsCounter extends StatsCounter { - public ServerStatsCounter(MinecraftServer server, File file) { - this.server = server; - this.file = file; -- // Spigot start -- for ( Map.Entry entry : org.spigotmc.SpigotConfig.forcedStats.entrySet() ) -- { -- Stat wrapper = Stats.CUSTOM.get( entry.getKey() ); -- this.stats.put( wrapper, entry.getValue().intValue() ); -- } -- // Spigot end - if (file.isFile()) { - try { - this.parseLocal(server.getFixerUpper(), FileUtils.readFileToString(file)); -@@ -65,6 +58,18 @@ public class ServerStatsCounter extends StatsCounter { - } - } - -+ // Paper start - Moved after stat fetching for player state file -+ // Moves the loading after vanilla loading, so it overrides the values. -+ // Disables saving any forced stats, so it stays at the same value (without enabling disableStatSaving) -+ // Fixes stat initialization to not cause a NullPointerException -+ // Spigot start -+ for ( Map.Entry entry : org.spigotmc.SpigotConfig.forcedStats.entrySet() ) -+ { -+ Stat wrapper = Stats.CUSTOM.get(java.util.Objects.requireNonNull(BuiltInRegistries.CUSTOM_STAT.get(entry.getKey()))); // Paper - ensured by SpigotConfig#stats -+ this.stats.put( wrapper, entry.getValue().intValue() ); -+ } -+ // Spigot end -+ // Paper end - Moved after stat fetching for player state file - } - - public void save() { -@@ -80,6 +85,7 @@ public class ServerStatsCounter extends StatsCounter { - @Override - public void setValue(Player player, Stat stat, int value) { - if ( org.spigotmc.SpigotConfig.disableStatSaving ) return; // Spigot -+ if (stat.getType() == Stats.CUSTOM && stat.getValue() instanceof final ResourceLocation resourceLocation && org.spigotmc.SpigotConfig.forcedStats.get(resourceLocation) != null) return; // Paper - disable saving forced stats - super.setValue(player, stat, value); - this.dirty.add(stat); - } diff --git a/patches/server/0890-Update-entity-data-when-attaching-firework-to-entity.patch b/patches/server/0882-Update-entity-data-when-attaching-firework-to-entity.patch similarity index 100% rename from patches/server/0890-Update-entity-data-when-attaching-firework-to-entity.patch rename to patches/server/0882-Update-entity-data-when-attaching-firework-to-entity.patch diff --git a/patches/server/0883-Add-missing-InventoryHolders-to-inventories.patch b/patches/server/0883-Add-missing-InventoryHolders-to-inventories.patch deleted file mode 100644 index d05856417401..000000000000 --- a/patches/server/0883-Add-missing-InventoryHolders-to-inventories.patch +++ /dev/null @@ -1,302 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Jake Potrebic -Date: Mon, 24 Jan 2022 00:09:02 -0800 -Subject: [PATCH] Add missing InventoryHolders to inventories - - -diff --git a/src/main/java/net/minecraft/world/Container.java b/src/main/java/net/minecraft/world/Container.java -index db41ffbd24adccd78edf3368ba1f7a3ab9f6072c..5db5ba026462ca642dcee718af732f80fadabef5 100644 ---- a/src/main/java/net/minecraft/world/Container.java -+++ b/src/main/java/net/minecraft/world/Container.java -@@ -103,7 +103,7 @@ public interface Container extends Clearable { - - java.util.List getViewers(); - -- org.bukkit.inventory.InventoryHolder getOwner(); -+ org.bukkit.inventory.@org.jetbrains.annotations.Nullable InventoryHolder getOwner(); // Paper - annotation - - void setMaxStackSize(int size); - -diff --git a/src/main/java/net/minecraft/world/SimpleContainer.java b/src/main/java/net/minecraft/world/SimpleContainer.java -index 6632cf24ebe6d147950a1fdb876660937da86b73..d04bf7d06855022c973073fb84c5d3d65f2553e1 100644 ---- a/src/main/java/net/minecraft/world/SimpleContainer.java -+++ b/src/main/java/net/minecraft/world/SimpleContainer.java -@@ -30,7 +30,7 @@ public class SimpleContainer implements Container, StackedContentsCompatible { - // CraftBukkit start - add fields and methods - public List transaction = new java.util.ArrayList(); - private int maxStack = MAX_STACK; -- protected org.bukkit.inventory.InventoryHolder bukkitOwner; -+ protected @Nullable org.bukkit.inventory.InventoryHolder bukkitOwner; // Paper - annotation - - public List getContents() { - return this.items; -@@ -58,6 +58,11 @@ public class SimpleContainer implements Container, StackedContentsCompatible { - } - - public org.bukkit.inventory.InventoryHolder getOwner() { -+ // Paper start - Add missing InventoryHolders -+ if (this.bukkitOwner == null && this.bukkitOwnerCreator != null) { -+ this.bukkitOwner = this.bukkitOwnerCreator.get(); -+ } -+ // Paper end - Add missing InventoryHolders - return this.bukkitOwner; - } - -@@ -86,6 +91,13 @@ public class SimpleContainer implements Container, StackedContentsCompatible { - public SimpleContainer(int size) { - this(size, null); - } -+ // Paper start - Add missing InventoryHolders -+ private @Nullable java.util.function.Supplier bukkitOwnerCreator; -+ public SimpleContainer(java.util.function.Supplier bukkitOwnerCreator, int size) { -+ this(size); -+ this.bukkitOwnerCreator = bukkitOwnerCreator; -+ } -+ // Paper end - Add missing InventoryHolders - - public SimpleContainer(int i, org.bukkit.inventory.InventoryHolder owner) { - this.bukkitOwner = owner; -diff --git a/src/main/java/net/minecraft/world/inventory/AbstractContainerMenu.java b/src/main/java/net/minecraft/world/inventory/AbstractContainerMenu.java -index afd8f48bd41d2cac413c292f7988c903da1dc700..8a1035c0aa859f67a6806c183d96a88ddf760baa 100644 ---- a/src/main/java/net/minecraft/world/inventory/AbstractContainerMenu.java -+++ b/src/main/java/net/minecraft/world/inventory/AbstractContainerMenu.java -@@ -991,4 +991,15 @@ public abstract class AbstractContainerMenu { - this.stateId = this.stateId + 1 & 32767; - return this.stateId; - } -+ -+ // Paper start - Add missing InventoryHolders -+ // The reason this is a supplier, is that the createHolder method uses the bukkit InventoryView#getTopInventory to get the inventory in question -+ // and that can't be obtained safely until the AbstractContainerMenu has been fully constructed. Using a supplier lazily -+ // initializes the InventoryHolder safely. -+ protected final Supplier createBlockHolder(final ContainerLevelAccess context) { -+ //noinspection ConstantValue -+ Preconditions.checkArgument(context != null, "context was null"); -+ return () -> context.createBlockHolder(this); -+ } -+ // Paper end - Add missing InventoryHolders - } -diff --git a/src/main/java/net/minecraft/world/inventory/BeaconMenu.java b/src/main/java/net/minecraft/world/inventory/BeaconMenu.java -index a735aeeb59f79154ce797c6e2f5600305f46d217..b93c118d957f0a2f40e2f31fd6400bd69438cf72 100644 ---- a/src/main/java/net/minecraft/world/inventory/BeaconMenu.java -+++ b/src/main/java/net/minecraft/world/inventory/BeaconMenu.java -@@ -42,7 +42,7 @@ public class BeaconMenu extends AbstractContainerMenu { - public BeaconMenu(int syncId, Container inventory, ContainerData propertyDelegate, ContainerLevelAccess context) { - super(MenuType.BEACON, syncId); - this.player = (Inventory) inventory; // CraftBukkit - TODO: check this -- this.beacon = new SimpleContainer(1) { // CraftBukkit - decompile error -+ this.beacon = new SimpleContainer(this.createBlockHolder(context), 1) { // CraftBukkit - decompile error // Paper - Add missing InventoryHolders - @Override - public boolean canPlaceItem(int slot, ItemStack stack) { - return stack.is(ItemTags.BEACON_PAYMENT_ITEMS); -diff --git a/src/main/java/net/minecraft/world/inventory/CartographyTableMenu.java b/src/main/java/net/minecraft/world/inventory/CartographyTableMenu.java -index c52c4c4210bc6ae082443318d9795c48c816aba6..ab98637bf967ac19f0bc06e8cb7f18a8b13ec809 100644 ---- a/src/main/java/net/minecraft/world/inventory/CartographyTableMenu.java -+++ b/src/main/java/net/minecraft/world/inventory/CartographyTableMenu.java -@@ -54,7 +54,7 @@ public class CartographyTableMenu extends AbstractContainerMenu { - - public CartographyTableMenu(int syncId, Inventory inventory, final ContainerLevelAccess context) { - super(MenuType.CARTOGRAPHY_TABLE, syncId); -- this.container = new SimpleContainer(2) { -+ this.container = new SimpleContainer(this.createBlockHolder(context), 2) { // Paper - Add missing InventoryHolders - @Override - public void setChanged() { - CartographyTableMenu.this.slotsChanged(this); -@@ -68,7 +68,7 @@ public class CartographyTableMenu extends AbstractContainerMenu { - } - // CraftBukkit end - }; -- this.resultContainer = new ResultContainer() { -+ this.resultContainer = new ResultContainer(this.createBlockHolder(context)) { // Paper - Add missing InventoryHolders - @Override - public void setChanged() { - CartographyTableMenu.this.slotsChanged(this); -diff --git a/src/main/java/net/minecraft/world/inventory/ContainerLevelAccess.java b/src/main/java/net/minecraft/world/inventory/ContainerLevelAccess.java -index 85e336637db8643fc5aca1dba724c9b341cbf46f..12b466ccb7c36021cf807c4f3fd2bcb037943abc 100644 ---- a/src/main/java/net/minecraft/world/inventory/ContainerLevelAccess.java -+++ b/src/main/java/net/minecraft/world/inventory/ContainerLevelAccess.java -@@ -21,6 +21,18 @@ public interface ContainerLevelAccess { - return new org.bukkit.Location(this.getWorld().getWorld(), this.getPosition().getX(), this.getPosition().getY(), this.getPosition().getZ()); - } - // CraftBukkit end -+ // Paper start - Add missing InventoryHolders -+ default boolean isBlock() { -+ return false; -+ } -+ -+ default org.bukkit.inventory.@org.jetbrains.annotations.Nullable BlockInventoryHolder createBlockHolder(AbstractContainerMenu menu) { -+ if (!this.isBlock()) { -+ return null; -+ } -+ return new org.bukkit.craftbukkit.inventory.CraftBlockInventoryHolder(this, menu.getBukkitView().getTopInventory()); -+ } -+ // Paper end - Add missing InventoryHolders - - ContainerLevelAccess NULL = new ContainerLevelAccess() { - @Override -@@ -48,6 +60,12 @@ public interface ContainerLevelAccess { - return pos; - } - // CraftBukkit end -+ // Paper start - Add missing InventoryHolders -+ @Override -+ public boolean isBlock() { -+ return true; -+ } -+ // Paper end - Add missing InventoryHolders - - @Override - public Optional evaluate(BiFunction getter) { -diff --git a/src/main/java/net/minecraft/world/inventory/EnchantmentMenu.java b/src/main/java/net/minecraft/world/inventory/EnchantmentMenu.java -index 2fa009b33bccd6aeee30f23f9207ab039740d95d..fff1c39920e7d7051dfe3dd39c77865d3bdf113e 100644 ---- a/src/main/java/net/minecraft/world/inventory/EnchantmentMenu.java -+++ b/src/main/java/net/minecraft/world/inventory/EnchantmentMenu.java -@@ -63,7 +63,7 @@ public class EnchantmentMenu extends AbstractContainerMenu { - - public EnchantmentMenu(int syncId, Inventory playerInventory, ContainerLevelAccess context) { - super(MenuType.ENCHANTMENT, syncId); -- this.enchantSlots = new SimpleContainer(2) { -+ this.enchantSlots = new SimpleContainer(this.createBlockHolder(context), 2) { // Paper - Add missing InventoryHolders - @Override - public void setChanged() { - super.setChanged(); -diff --git a/src/main/java/net/minecraft/world/inventory/GrindstoneMenu.java b/src/main/java/net/minecraft/world/inventory/GrindstoneMenu.java -index 15ec798e149d80aace186f84b9236ddeaba690c3..1678f6c8b2c7db761783e53043169bf12bc2cb29 100644 ---- a/src/main/java/net/minecraft/world/inventory/GrindstoneMenu.java -+++ b/src/main/java/net/minecraft/world/inventory/GrindstoneMenu.java -@@ -60,8 +60,8 @@ public class GrindstoneMenu extends AbstractContainerMenu { - - public GrindstoneMenu(int syncId, Inventory playerInventory, final ContainerLevelAccess context) { - super(MenuType.GRINDSTONE, syncId); -- this.resultSlots = new ResultContainer(); -- this.repairSlots = new SimpleContainer(2) { -+ this.resultSlots = new ResultContainer(this.createBlockHolder(context)); // Paper - Add missing InventoryHolders -+ this.repairSlots = new SimpleContainer(this.createBlockHolder(context), 2) { // Paper - Add missing InventoryHolders - @Override - public void setChanged() { - super.setChanged(); -diff --git a/src/main/java/net/minecraft/world/inventory/ItemCombinerMenu.java b/src/main/java/net/minecraft/world/inventory/ItemCombinerMenu.java -index be840717e180b6b5abd14db6cc9263349737f9a3..7de5e47f9a54263734eeef855a2dc07ef64d30ea 100644 ---- a/src/main/java/net/minecraft/world/inventory/ItemCombinerMenu.java -+++ b/src/main/java/net/minecraft/world/inventory/ItemCombinerMenu.java -@@ -18,7 +18,7 @@ public abstract class ItemCombinerMenu extends AbstractContainerMenu { - protected final Player player; - protected final Container inputSlots; - private final List inputSlotIndexes; -- protected final ResultContainer resultSlots = new ResultContainer(); -+ protected final ResultContainer resultSlots; // Paper - Add missing InventoryHolders; delay field init - private final int resultSlotIndex; - - protected abstract boolean mayPickup(Player player, boolean present); -@@ -30,6 +30,7 @@ public abstract class ItemCombinerMenu extends AbstractContainerMenu { - public ItemCombinerMenu(@Nullable MenuType type, int syncId, Inventory playerInventory, ContainerLevelAccess context) { - super(type, syncId); - this.access = context; -+ this.resultSlots = new ResultContainer(this.createBlockHolder(this.access)); // Paper - Add missing InventoryHolders; delay field init - this.player = playerInventory.player; - ItemCombinerMenuSlotDefinition itemcombinermenuslotdefinition = this.createInputSlotDefinitions(); - -@@ -96,7 +97,7 @@ public abstract class ItemCombinerMenu extends AbstractContainerMenu { - protected abstract ItemCombinerMenuSlotDefinition createInputSlotDefinitions(); - - private SimpleContainer createContainer(int size) { -- return new SimpleContainer(size) { -+ return new SimpleContainer(this.createBlockHolder(this.access), size) { // Paper - Add missing InventoryHolders - @Override - public void setChanged() { - super.setChanged(); -diff --git a/src/main/java/net/minecraft/world/inventory/LoomMenu.java b/src/main/java/net/minecraft/world/inventory/LoomMenu.java -index dc23b646e55bb66e0aa584d82b75b4b3d233276a..2c0e182f7b03ed0f87c259af4df9c4e52106220d 100644 ---- a/src/main/java/net/minecraft/world/inventory/LoomMenu.java -+++ b/src/main/java/net/minecraft/world/inventory/LoomMenu.java -@@ -73,7 +73,7 @@ public class LoomMenu extends AbstractContainerMenu { - this.selectablePatterns = List.of(); - this.slotUpdateListener = () -> { - }; -- this.inputContainer = new SimpleContainer(3) { -+ this.inputContainer = new SimpleContainer(this.createBlockHolder(context), 3) { // Paper - Add missing InventoryHolders - @Override - public void setChanged() { - super.setChanged(); -@@ -88,7 +88,7 @@ public class LoomMenu extends AbstractContainerMenu { - } - // CraftBukkit end - }; -- this.outputContainer = new SimpleContainer(1) { -+ this.outputContainer = new SimpleContainer(this.createBlockHolder(context), 1) { // Paper - Add missing InventoryHolders - @Override - public void setChanged() { - super.setChanged(); -diff --git a/src/main/java/net/minecraft/world/inventory/ResultContainer.java b/src/main/java/net/minecraft/world/inventory/ResultContainer.java -index d4592218d761eb38402e3d95c642e80a708cb333..4c4266a85c38e41e6c7e6144a68624f4daa50c54 100644 ---- a/src/main/java/net/minecraft/world/inventory/ResultContainer.java -+++ b/src/main/java/net/minecraft/world/inventory/ResultContainer.java -@@ -29,7 +29,12 @@ public class ResultContainer implements Container, RecipeCraftingHolder { - } - - public org.bukkit.inventory.InventoryHolder getOwner() { -- return null; // Result slots don't get an owner -+ // Paper start - Add missing InventoryHolders -+ if (this.holder == null && this.holderCreator != null) { -+ this.holder = this.holderCreator.get(); -+ } -+ return this.holder; // Result slots don't get an owner -+ // Paper end - Add missing InventoryHolders - } - - // Don't need a transaction; the InventoryCrafting keeps track of it for us -@@ -53,6 +58,14 @@ public class ResultContainer implements Container, RecipeCraftingHolder { - return null; - } - // CraftBukkit end -+ // Paper start - Add missing InventoryHolders -+ private @Nullable java.util.function.Supplier holderCreator; -+ private @Nullable org.bukkit.inventory.InventoryHolder holder; -+ public ResultContainer(java.util.function.Supplier holderCreator) { -+ this(); -+ this.holderCreator = holderCreator; -+ } -+ // Paper end - Add missing InventoryHolders - - public ResultContainer() { - this.itemStacks = NonNullList.withSize(1, ItemStack.EMPTY); -diff --git a/src/main/java/net/minecraft/world/inventory/StonecutterMenu.java b/src/main/java/net/minecraft/world/inventory/StonecutterMenu.java -index 37e75c02c374314372630f4bda0b92519809f2a4..5a0015f761f6a25d7bb7b9cfe7a9b4771a6a37ec 100644 ---- a/src/main/java/net/minecraft/world/inventory/StonecutterMenu.java -+++ b/src/main/java/net/minecraft/world/inventory/StonecutterMenu.java -@@ -69,7 +69,7 @@ public class StonecutterMenu extends AbstractContainerMenu { - this.input = ItemStack.EMPTY; - this.slotUpdateListener = () -> { - }; -- this.container = new SimpleContainer(1) { -+ this.container = new SimpleContainer(this.createBlockHolder(context), 1) { // Paper - Add missing InventoryHolders - @Override - public void setChanged() { - super.setChanged(); -@@ -84,7 +84,7 @@ public class StonecutterMenu extends AbstractContainerMenu { - } - // CraftBukkit end - }; -- this.resultContainer = new ResultContainer(); -+ this.resultContainer = new ResultContainer(this.createBlockHolder(context)); // Paper - Add missing InventoryHolders - this.access = context; - this.level = playerInventory.player.level(); - this.inputSlot = this.addSlot(new Slot(this.container, 0, 20, 33)); -diff --git a/src/main/java/org/bukkit/craftbukkit/inventory/CraftBlockInventoryHolder.java b/src/main/java/org/bukkit/craftbukkit/inventory/CraftBlockInventoryHolder.java -index 7ae484b0fa5bf5494c6ead15f7f1c0fa840ae270..7129eb5f5cea39992b4c690cb421004004a952ea 100644 ---- a/src/main/java/org/bukkit/craftbukkit/inventory/CraftBlockInventoryHolder.java -+++ b/src/main/java/org/bukkit/craftbukkit/inventory/CraftBlockInventoryHolder.java -@@ -17,6 +17,13 @@ public class CraftBlockInventoryHolder implements BlockInventoryHolder { - this.block = CraftBlock.at(world, pos); - this.inventory = new CraftInventory(inv); - } -+ // Paper start - Add missing InventoryHolders -+ public CraftBlockInventoryHolder(net.minecraft.world.inventory.ContainerLevelAccess levelAccess, Inventory inventory) { -+ com.google.common.base.Preconditions.checkArgument(levelAccess.isBlock()); -+ this.block = CraftBlock.at(levelAccess.getWorld(), levelAccess.getPosition()); -+ this.inventory = inventory; -+ } -+ // Paper end - Add missing InventoryHolders - - @Override - public Block getBlock() { diff --git a/patches/server/0883-Fix-UnsafeValues-loadAdvancement.patch b/patches/server/0883-Fix-UnsafeValues-loadAdvancement.patch new file mode 100644 index 000000000000..dc4c28926e54 --- /dev/null +++ b/patches/server/0883-Fix-UnsafeValues-loadAdvancement.patch @@ -0,0 +1,43 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: LemonCaramel +Date: Sun, 24 Sep 2023 20:19:44 +0900 +Subject: [PATCH] Fix UnsafeValues#loadAdvancement + + +diff --git a/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java b/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java +index a74784ddf63d316f253381ed803822a149e92bc7..1835f8cfda0222fadd9db31abfb7e85899051853 100644 +--- a/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java ++++ b/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java +@@ -302,9 +302,30 @@ public final class CraftMagicNumbers implements UnsafeValues { + ResourceLocation minecraftkey = CraftNamespacedKey.toMinecraft(key); + + JsonElement jsonelement = JsonParser.parseString(advancement); +- net.minecraft.advancements.Advancement nms = net.minecraft.advancements.Advancement.CODEC.parse(JsonOps.INSTANCE, jsonelement).getOrThrow(JsonParseException::new); ++ final net.minecraft.resources.RegistryOps ops = CraftRegistry.getMinecraftRegistry().createSerializationContext(JsonOps.INSTANCE); // Paper - use RegistryOps ++ final net.minecraft.advancements.Advancement nms = net.minecraft.advancements.Advancement.CODEC.parse(ops, jsonelement).getOrThrow(JsonParseException::new); // Paper - use RegistryOps + if (nms != null) { +- MinecraftServer.getServer().getAdvancements().advancements.put(minecraftkey, new AdvancementHolder(minecraftkey, nms)); ++ // Paper start - Fix throw UnsupportedOperationException ++ //MinecraftServer.getServer().getAdvancements().advancements.put(minecraftkey, new AdvancementHolder(minecraftkey, nms)); ++ final com.google.common.collect.ImmutableMap.Builder mapBuilder = com.google.common.collect.ImmutableMap.builder(); ++ mapBuilder.putAll(MinecraftServer.getServer().getAdvancements().advancements); ++ ++ final AdvancementHolder holder = new AdvancementHolder(minecraftkey, nms); ++ mapBuilder.put(minecraftkey, holder); ++ ++ MinecraftServer.getServer().getAdvancements().advancements = mapBuilder.build(); ++ final net.minecraft.advancements.AdvancementTree tree = MinecraftServer.getServer().getAdvancements().tree(); ++ tree.addAll(java.util.List.of(holder)); ++ ++ // recalculate advancement position ++ final net.minecraft.advancements.AdvancementNode node = tree.get(minecraftkey); ++ if (node != null) { ++ final net.minecraft.advancements.AdvancementNode root = node.root(); ++ if (root.holder().value().display().isPresent()) { ++ net.minecraft.advancements.TreeNodePosition.run(root); ++ } ++ } ++ // Paper end - Fix throw UnsupportedOperationException + Advancement bukkit = Bukkit.getAdvancement(key); + + if (bukkit != null) { diff --git a/patches/server/0884-Add-player-idle-duration-API.patch b/patches/server/0884-Add-player-idle-duration-API.patch new file mode 100644 index 000000000000..ebbd9c2f34e9 --- /dev/null +++ b/patches/server/0884-Add-player-idle-duration-API.patch @@ -0,0 +1,30 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: booky10 +Date: Sat, 14 Oct 2023 03:11:11 +0200 +Subject: [PATCH] Add player idle duration API + +Implements API for getting and resetting a player's idle duration. + +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java +index 96d8bd55ecfb6aca8a6f65bc2a25e5756eecc4d8..fee7f60d457f34dd46756efc51bf21bd5498d854 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java +@@ -3443,6 +3443,18 @@ public class CraftPlayer extends CraftHumanEntity implements Player { + } + // Paper end + ++ // Paper start ++ @Override ++ public Duration getIdleDuration() { ++ return Duration.ofMillis(net.minecraft.Util.getMillis() - this.getHandle().getLastActionTime()); ++ } ++ ++ @Override ++ public void resetIdleDuration() { ++ this.getHandle().resetLastActionTime(); ++ } ++ // Paper end ++ + public Player.Spigot spigot() + { + return this.spigot; diff --git a/patches/server/0884-Do-not-read-tile-entities-in-chunks-that-are-positio.patch b/patches/server/0884-Do-not-read-tile-entities-in-chunks-that-are-positio.patch deleted file mode 100644 index ab4d61b97a7c..000000000000 --- a/patches/server/0884-Do-not-read-tile-entities-in-chunks-that-are-positio.patch +++ /dev/null @@ -1,50 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Spottedleaf -Date: Sun, 18 Jun 2023 23:04:46 -0700 -Subject: [PATCH] Do not read tile entities in chunks that are positioned - outside of the chunk - -The tile entities are not accessible and so should not be loaded. -This can happen as a result of users moving regionfiles around, -which would cause a crash on Folia but would appear to function -fine on Paper. - -diff --git a/src/main/java/net/minecraft/world/level/chunk/storage/ChunkSerializer.java b/src/main/java/net/minecraft/world/level/chunk/storage/ChunkSerializer.java -index 5e1a68e3a920aab10a459b9b54f6abd59e7855e0..d42585bccb03f8ee1be5e37cfbe8520af4cc5454 100644 ---- a/src/main/java/net/minecraft/world/level/chunk/storage/ChunkSerializer.java -+++ b/src/main/java/net/minecraft/world/level/chunk/storage/ChunkSerializer.java -@@ -303,6 +303,13 @@ public class ChunkSerializer { - for (int k1 = 0; k1 < nbttaglist3.size(); ++k1) { - CompoundTag nbttagcompound4 = nbttaglist3.getCompound(k1); - -+ // Paper start - do not read tile entities positioned outside the chunk -+ BlockPos blockposition = BlockEntity.getPosFromTag(nbttagcompound4); -+ if ((blockposition.getX() >> 4) != chunkPos.x || (blockposition.getZ() >> 4) != chunkPos.z) { -+ LOGGER.warn("Tile entity serialized in chunk " + chunkPos + " in world '" + world.getWorld().getName() + "' positioned at " + blockposition + " is located outside of the chunk"); -+ continue; -+ } -+ // Paper end - do not read tile entities positioned outside the chunk - ((ChunkAccess) object1).setBlockEntityNbt(nbttagcompound4); - } - -@@ -507,10 +514,19 @@ public class ChunkSerializer { - CompoundTag nbttagcompound1 = nbttaglist1.getCompound(i); - boolean flag = nbttagcompound1.getBoolean("keepPacked"); - -+ // Paper start - do not read tile entities positioned outside the chunk -+ BlockPos blockposition = BlockEntity.getPosFromTag(nbttagcompound1); // moved up -+ ChunkPos chunkPos = chunk.getPos(); -+ if ((blockposition.getX() >> 4) != chunkPos.x || (blockposition.getZ() >> 4) != chunkPos.z) { -+ LOGGER.warn("Tile entity serialized in chunk " + chunkPos + " in world '" + world.getWorld().getName() + "' positioned at " + blockposition + " is located outside of the chunk"); -+ continue; -+ } -+ // Paper end - do not read tile entities positioned outside the chunk -+ - if (flag) { - chunk.setBlockEntityNbt(nbttagcompound1); - } else { -- BlockPos blockposition = BlockEntity.getPosFromTag(nbttagcompound1); -+ // Paper - do not read tile entities positioned outside the chunk; move up - BlockEntity tileentity = BlockEntity.loadStatic(blockposition, chunk.getBlockState(blockposition), nbttagcompound1, world.registryAccess()); - - if (tileentity != null) { diff --git a/patches/server/0885-Don-t-check-if-we-can-see-non-visible-entities.patch b/patches/server/0885-Don-t-check-if-we-can-see-non-visible-entities.patch new file mode 100644 index 000000000000..5a591c2450e2 --- /dev/null +++ b/patches/server/0885-Don-t-check-if-we-can-see-non-visible-entities.patch @@ -0,0 +1,19 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Brokkonaut +Date: Sat, 21 Oct 2023 20:52:57 +0100 +Subject: [PATCH] Don't check if we can see non-visible entities + + +diff --git a/src/main/java/net/minecraft/server/level/ChunkMap.java b/src/main/java/net/minecraft/server/level/ChunkMap.java +index 5b993cb8a99c6a0257b9d3d93162f9b2fff552b0..7483f9f2639c58a4f43e264211791f4377e1db64 100644 +--- a/src/main/java/net/minecraft/server/level/ChunkMap.java ++++ b/src/main/java/net/minecraft/server/level/ChunkMap.java +@@ -1590,7 +1590,7 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider + // Paper end - Configurable entity tracking range by Y + + // CraftBukkit start - respect vanish API +- if (!player.getBukkitEntity().canSee(this.entity.getBukkitEntity())) { ++ if (flag && !player.getBukkitEntity().canSee(this.entity.getBukkitEntity())) { // Paper - only consider hits + flag = false; + } + // CraftBukkit end diff --git a/patches/server/0886-Fix-NPE-in-SculkBloomEvent-world-access.patch b/patches/server/0886-Fix-NPE-in-SculkBloomEvent-world-access.patch new file mode 100644 index 000000000000..3f44bad4f135 --- /dev/null +++ b/patches/server/0886-Fix-NPE-in-SculkBloomEvent-world-access.patch @@ -0,0 +1,43 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Lulu13022002 <41980282+Lulu13022002@users.noreply.github.com> +Date: Fri, 20 Oct 2023 19:50:22 +0200 +Subject: [PATCH] Fix NPE in SculkBloomEvent world access + + +diff --git a/src/main/java/net/minecraft/world/level/block/entity/SculkCatalystBlockEntity.java b/src/main/java/net/minecraft/world/level/block/entity/SculkCatalystBlockEntity.java +index da81f76a2ee779ffedf9a02d91b5971004167725..4729befa12732a9fd65cce243b33b3b479026c41 100644 +--- a/src/main/java/net/minecraft/world/level/block/entity/SculkCatalystBlockEntity.java ++++ b/src/main/java/net/minecraft/world/level/block/entity/SculkCatalystBlockEntity.java +@@ -35,9 +35,16 @@ public class SculkCatalystBlockEntity extends BlockEntity implements GameEventLi + public SculkCatalystBlockEntity(BlockPos pos, BlockState state) { + super(BlockEntityType.SCULK_CATALYST, pos, state); + this.catalystListener = new SculkCatalystBlockEntity.CatalystListener(state, new BlockPositionSource(pos)); +- this.catalystListener.level = this.level; // CraftBukkit + } + ++ // Paper start - Fix NPE in SculkBloomEvent world access ++ @Override ++ public void setLevel(Level level) { ++ super.setLevel(level); ++ this.catalystListener.sculkSpreader.level = level; ++ } ++ // Paper end - Fix NPE in SculkBloomEvent world access ++ + public static void serverTick(Level world, BlockPos pos, BlockState state, SculkCatalystBlockEntity blockEntity) { + org.bukkit.craftbukkit.event.CraftEventFactory.sourceBlockOverride = blockEntity.getBlockPos(); // CraftBukkit - SPIGOT-7068: Add source block override, not the most elegant way but better than passing down a BlockPosition up to five methods deep. + blockEntity.catalystListener.getSculkSpreader().updateCursors(world, pos, world.getRandom(), true); +@@ -67,13 +74,12 @@ public class SculkCatalystBlockEntity extends BlockEntity implements GameEventLi + final SculkSpreader sculkSpreader; + private final BlockState blockState; + private final PositionSource positionSource; +- private Level level; // CraftBukkit + + public CatalystListener(BlockState state, PositionSource positionSource) { + this.blockState = state; + this.positionSource = positionSource; + this.sculkSpreader = SculkSpreader.createLevelSpreader(); +- this.sculkSpreader.level = this.level; // CraftBukkit ++ // this.sculkSpreader.level = this.level; // CraftBukkit // Paper - Fix NPE in SculkBloomEvent world access + } + + @Override diff --git a/patches/server/0886-Fix-race-condition-on-UpgradeData.BlockFixers-class-.patch b/patches/server/0886-Fix-race-condition-on-UpgradeData.BlockFixers-class-.patch deleted file mode 100644 index e1f2934daaa4..000000000000 --- a/patches/server/0886-Fix-race-condition-on-UpgradeData.BlockFixers-class-.patch +++ /dev/null @@ -1,27 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Spottedleaf -Date: Tue, 8 Aug 2023 17:29:33 -0700 -Subject: [PATCH] Fix race condition on UpgradeData.BlockFixers class init - -The CHUNKY_FIXERS field is modified during the constructors -of the BlockFixers, but the code that uses CHUNKY_FIXERS does -not properly ensure that BlockFixers has been initialised before -using it, leading to a possible race condition where instances of -BlockFixers are accessed before they have initialised correctly. - -We can force the class to initialise fully before accessing the -field by calling any method on the class, and for convenience -we use values(). - -diff --git a/src/main/java/net/minecraft/world/level/chunk/UpgradeData.java b/src/main/java/net/minecraft/world/level/chunk/UpgradeData.java -index 22b6d0851a51da180cd8fbbe6554c5370f5ac5bd..cd9b65f278a750a0177a3252271015d43172b2e9 100644 ---- a/src/main/java/net/minecraft/world/level/chunk/UpgradeData.java -+++ b/src/main/java/net/minecraft/world/level/chunk/UpgradeData.java -@@ -140,6 +140,7 @@ public class UpgradeData { - Fluid fluid = tick.type() == Fluids.EMPTY ? level.getFluidState(tick.pos()).getType() : tick.type(); - level.scheduleTick(tick.pos(), fluid, tick.delay(), tick.priority()); - }); -+ UpgradeData.BlockFixers.values(); // Paper - force the class init so that we don't access CHUNKY_FIXERS before all BlockFixers are initialised - CHUNKY_FIXERS.forEach(logic -> logic.processChunk(level)); - } - diff --git a/patches/server/0887-Allow-null-itemstack-for-Player-sendEquipmentChange.patch b/patches/server/0887-Allow-null-itemstack-for-Player-sendEquipmentChange.patch new file mode 100644 index 000000000000..1569ec7b59b4 --- /dev/null +++ b/patches/server/0887-Allow-null-itemstack-for-Player-sendEquipmentChange.patch @@ -0,0 +1,19 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: David Scandurra +Date: Wed, 25 Oct 2023 20:36:25 +0200 +Subject: [PATCH] Allow null itemstack for Player#sendEquipmentChange + + +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java +index fee7f60d457f34dd46756efc51bf21bd5498d854..90df4f4a70b416b2d750f3d2063a68848bd38085 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java +@@ -1144,7 +1144,7 @@ public class CraftPlayer extends CraftHumanEntity implements Player { + + @Override + public void sendEquipmentChange(LivingEntity entity, EquipmentSlot slot, ItemStack item) { +- this.sendEquipmentChange(entity, Map.of(slot, item)); ++ this.sendEquipmentChange(entity, java.util.Collections.singletonMap(slot, item)); // Paper - replace Map.of to allow null values + } + + @Override diff --git a/patches/server/0888-Fix-team-sidebar-objectives-not-being-cleared.patch b/patches/server/0888-Fix-team-sidebar-objectives-not-being-cleared.patch deleted file mode 100644 index 88a39d0b2021..000000000000 --- a/patches/server/0888-Fix-team-sidebar-objectives-not-being-cleared.patch +++ /dev/null @@ -1,25 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Jake Potrebic -Date: Sat, 23 Sep 2023 22:31:54 -0700 -Subject: [PATCH] Fix team sidebar objectives not being cleared - -Objectives displayed in team sidebars were not cleared when switching -scoreboards. If a player's scoreboard has a displayed objective for the -'gold' sidebar, and their scoreboard was switched to one where they -still had a 'gold' team, it would still be displayed - -diff --git a/src/main/java/org/bukkit/craftbukkit/scoreboard/CraftScoreboardManager.java b/src/main/java/org/bukkit/craftbukkit/scoreboard/CraftScoreboardManager.java -index cad42a0f3c016bf65181e50d139ae4e2fb9158a5..b3e1adeb932da9b3bed16acd94e2f16da48a7c72 100644 ---- a/src/main/java/org/bukkit/craftbukkit/scoreboard/CraftScoreboardManager.java -+++ b/src/main/java/org/bukkit/craftbukkit/scoreboard/CraftScoreboardManager.java -@@ -87,8 +87,8 @@ public final class CraftScoreboardManager implements ScoreboardManager { - - // Old objective tracking - HashSet removed = new HashSet<>(); -- for (int i = 0; i < 3; ++i) { -- Objective scoreboardobjective = oldboard.getDisplayObjective(net.minecraft.world.scores.DisplaySlot.BY_ID.apply(i)); -+ for (net.minecraft.world.scores.DisplaySlot slot : net.minecraft.world.scores.DisplaySlot.values()) { // Paper - clear all display slots -+ Objective scoreboardobjective = oldboard.getDisplayObjective(slot); // Paper - clear all display slots - if (scoreboardobjective != null && !removed.contains(scoreboardobjective)) { - entityplayer.connection.send(new ClientboundSetObjectivePacket(scoreboardobjective, 1)); - removed.add(scoreboardobjective); diff --git a/patches/server/0896-Optimize-VarInts.patch b/patches/server/0888-Optimize-VarInts.patch similarity index 100% rename from patches/server/0896-Optimize-VarInts.patch rename to patches/server/0888-Optimize-VarInts.patch diff --git a/patches/server/0889-Add-API-to-get-the-collision-shape-of-a-block-before.patch b/patches/server/0889-Add-API-to-get-the-collision-shape-of-a-block-before.patch new file mode 100644 index 000000000000..50464e50cbf0 --- /dev/null +++ b/patches/server/0889-Add-API-to-get-the-collision-shape-of-a-block-before.patch @@ -0,0 +1,32 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: TrollyLoki +Date: Wed, 11 Oct 2023 00:45:53 -0400 +Subject: [PATCH] Add API to get the collision shape of a block before it's + placed + + +diff --git a/src/main/java/org/bukkit/craftbukkit/block/data/CraftBlockData.java b/src/main/java/org/bukkit/craftbukkit/block/data/CraftBlockData.java +index 3ec64c995dcb59a758741e32b886925983a8be56..50fb7edd25c1b38f5c463b78d21d4583bdc89229 100644 +--- a/src/main/java/org/bukkit/craftbukkit/block/data/CraftBlockData.java ++++ b/src/main/java/org/bukkit/craftbukkit/block/data/CraftBlockData.java +@@ -683,6 +683,20 @@ public class CraftBlockData implements BlockData { + return this.state.isFaceSturdy(EmptyBlockGetter.INSTANCE, BlockPos.ZERO, CraftBlock.blockFaceToNotch(face), CraftBlockSupport.toNMS(support)); + } + ++ // Paper start ++ @Override ++ public org.bukkit.util.VoxelShape getCollisionShape(Location location) { ++ Preconditions.checkArgument(location != null, "location must not be null"); ++ ++ CraftWorld world = (CraftWorld) location.getWorld(); ++ Preconditions.checkArgument(world != null, "location must not have a null world"); ++ ++ BlockPos position = CraftLocation.toBlockPosition(location); ++ net.minecraft.world.phys.shapes.VoxelShape shape = this.state.getCollisionShape(world.getHandle(), position); ++ return new org.bukkit.craftbukkit.util.CraftVoxelShape(shape); ++ } ++ // Paper end ++ + @Override + public Color getMapColor() { + return Color.fromRGB(this.state.getMapColor(null, null).col); diff --git a/patches/server/0889-Fix-missing-map-initialize-event-call.patch b/patches/server/0889-Fix-missing-map-initialize-event-call.patch deleted file mode 100644 index 3cb380d89c9f..000000000000 --- a/patches/server/0889-Fix-missing-map-initialize-event-call.patch +++ /dev/null @@ -1,43 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Warrior <50800980+Warriorrrr@users.noreply.github.com> -Date: Sun, 24 Sep 2023 18:35:28 +0200 -Subject: [PATCH] Fix missing map initialize event call - -== AT == -public net.minecraft.world.level.storage.DimensionDataStorage readSavedData(Ljava/util/function/Function;Lnet/minecraft/util/datafix/DataFixTypes;Ljava/lang/String;)Lnet/minecraft/world/level/saveddata/SavedData; - -diff --git a/src/main/java/net/minecraft/server/level/ServerLevel.java b/src/main/java/net/minecraft/server/level/ServerLevel.java -index 1ed6002501b501e72bfff81ae9abc506133a10d0..c13652fccf08ff9235f22e89941a37b283f92f03 100644 ---- a/src/main/java/net/minecraft/server/level/ServerLevel.java -+++ b/src/main/java/net/minecraft/server/level/ServerLevel.java -@@ -1664,13 +1664,24 @@ public class ServerLevel extends Level implements WorldGenLevel { - @Nullable - @Override - public MapItemSavedData getMapData(MapId id) { -- // CraftBukkit start -- MapItemSavedData worldmap = (MapItemSavedData) this.getServer().overworld().getDataStorage().get(MapItemSavedData.factory(), id.key()); -- if (worldmap != null) { -- worldmap.id = id; -+ // Paper start - Call missing map initialize event and set id -+ final DimensionDataStorage storage = this.getServer().overworld().getDataStorage(); -+ -+ final net.minecraft.world.level.saveddata.SavedData existing = storage.cache.get(id.key()); -+ if (existing == null && !storage.cache.containsKey(id.key())) { -+ final MapItemSavedData worldmap = (MapItemSavedData) this.getServer().overworld().getDataStorage().get(MapItemSavedData.factory(), id.key()); -+ storage.cache.put(id.key(), worldmap); -+ if (worldmap != null) { -+ worldmap.id = id; -+ new MapInitializeEvent(worldmap.mapView).callEvent(); -+ return worldmap; -+ } -+ } else if (existing instanceof MapItemSavedData mapItemSavedData) { -+ mapItemSavedData.id = id; - } -- return worldmap; -- // CraftBukkit end -+ -+ return existing instanceof MapItemSavedData data ? data : null; -+ // Paper end - Call missing map initialize event and set id - } - - @Override diff --git a/patches/server/0890-Add-predicate-for-blocks-when-raytracing.patch b/patches/server/0890-Add-predicate-for-blocks-when-raytracing.patch new file mode 100644 index 000000000000..fdeae8627c1f --- /dev/null +++ b/patches/server/0890-Add-predicate-for-blocks-when-raytracing.patch @@ -0,0 +1,116 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: TonytheMacaroni +Date: Wed, 6 Sep 2023 19:24:16 -0400 +Subject: [PATCH] Add predicate for blocks when raytracing + + +diff --git a/src/main/java/net/minecraft/world/level/BlockGetter.java b/src/main/java/net/minecraft/world/level/BlockGetter.java +index 7e1a332168357b9af14dbe3299549c2c93903fa6..93738c7dea1ea3d19013a47380391274612a719b 100644 +--- a/src/main/java/net/minecraft/world/level/BlockGetter.java ++++ b/src/main/java/net/minecraft/world/level/BlockGetter.java +@@ -71,6 +71,12 @@ public interface BlockGetter extends LevelHeightAccessor { + + // CraftBukkit start - moved block handling into separate method for use by Block#rayTrace + default BlockHitResult clip(ClipContext raytrace1, BlockPos blockposition) { ++ // Paper start - Add predicate for blocks when raytracing ++ return clip(raytrace1, blockposition, null); ++ } ++ ++ default BlockHitResult clip(ClipContext raytrace1, BlockPos blockposition, java.util.function.Predicate canCollide) { ++ // Paper end - Add predicate for blocks when raytracing + // Paper start - Prevent raytrace from loading chunks + BlockState iblockdata = this.getBlockStateIfLoaded(blockposition); + if (iblockdata == null) { +@@ -80,7 +86,7 @@ public interface BlockGetter extends LevelHeightAccessor { + return BlockHitResult.miss(raytrace1.getTo(), Direction.getApproximateNearest(vec3d.x, vec3d.y, vec3d.z), BlockPos.containing(raytrace1.getTo())); + } + // Paper end - Prevent raytrace from loading chunks +- if (iblockdata.isAir()) return null; // Paper - Perf: optimise air cases ++ if (iblockdata.isAir() || (canCollide != null && this instanceof LevelAccessor levelAccessor && !canCollide.test(org.bukkit.craftbukkit.block.CraftBlock.at(levelAccessor, blockposition)))) return null; // Paper - Perf: optimise air cases & check canCollide predicate + FluidState fluid = iblockdata.getFluidState(); // Paper - Perf: don't need to go to world state again + Vec3 vec3d = raytrace1.getFrom(); + Vec3 vec3d1 = raytrace1.getTo(); +@@ -96,8 +102,14 @@ public interface BlockGetter extends LevelHeightAccessor { + // CraftBukkit end + + default BlockHitResult clip(ClipContext context) { ++ // Paper start - Add predicate for blocks when raytracing ++ return clip(context, (java.util.function.Predicate) null); ++ } ++ ++ default BlockHitResult clip(ClipContext context, java.util.function.Predicate canCollide) { ++ // Paper end - Add predicate for blocks when raytracing + return (BlockHitResult) BlockGetter.traverseBlocks(context.getFrom(), context.getTo(), context, (raytrace1, blockposition) -> { +- return this.clip(raytrace1, blockposition); // CraftBukkit - moved into separate method ++ return this.clip(raytrace1, blockposition, canCollide); // CraftBukkit - moved into separate method // Paper - Add predicate for blocks when raytracing + }, (raytrace1) -> { + Vec3 vec3d = raytrace1.getFrom().subtract(raytrace1.getTo()); + +diff --git a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java +index 188bd33f46b6baaa3fc21c9da6fa9a9d004e899e..041a6042a91f2a8933d7f9bcb44bb78894ffd405 100644 +--- a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java ++++ b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java +@@ -1116,9 +1116,15 @@ public class CraftWorld extends CraftRegionAccessor implements World { + + @Override + public RayTraceResult rayTraceEntities(Location start, Vector direction, double maxDistance, double raySize, Predicate filter) { ++ // Paper start - Add predicate for blocks when raytracing ++ return rayTraceEntities((io.papermc.paper.math.Position) start, direction, maxDistance, raySize, filter); ++ } ++ ++ public RayTraceResult rayTraceEntities(io.papermc.paper.math.Position start, Vector direction, double maxDistance, double raySize, Predicate filter) { + Preconditions.checkArgument(start != null, "Location start cannot be null"); +- Preconditions.checkArgument(this.equals(start.getWorld()), "Location start cannot be in a different world"); +- start.checkFinite(); ++ Preconditions.checkArgument(!(start instanceof Location location) || this.equals(location.getWorld()), "Location start cannot be in a different world"); ++ Preconditions.checkArgument(start.isFinite(), "Location start is not finite"); ++ // Paper end - Add predicate for blocks when raytracing + + Preconditions.checkArgument(direction != null, "Vector direction cannot be null"); + direction.checkFinite(); +@@ -1168,9 +1174,16 @@ public class CraftWorld extends CraftRegionAccessor implements World { + + @Override + public RayTraceResult rayTraceBlocks(Location start, Vector direction, double maxDistance, FluidCollisionMode fluidCollisionMode, boolean ignorePassableBlocks) { ++ // Paper start - Add predicate for blocks when raytracing ++ return this.rayTraceBlocks(start, direction, maxDistance, fluidCollisionMode, ignorePassableBlocks, null); ++ } ++ ++ @Override ++ public RayTraceResult rayTraceBlocks(io.papermc.paper.math.Position start, Vector direction, double maxDistance, FluidCollisionMode fluidCollisionMode, boolean ignorePassableBlocks, Predicate canCollide) { + Preconditions.checkArgument(start != null, "Location start cannot be null"); +- Preconditions.checkArgument(this.equals(start.getWorld()), "Location start cannot be in a different world"); +- start.checkFinite(); ++ Preconditions.checkArgument(!(start instanceof Location location) || this.equals(location.getWorld()), "Location start cannot be in a different world"); ++ Preconditions.checkArgument(start.isFinite(), "Location start is not finite"); ++ // Paper end - Add predicate for blocks when raytracing + + Preconditions.checkArgument(direction != null, "Vector direction cannot be null"); + direction.checkFinite(); +@@ -1183,16 +1196,23 @@ public class CraftWorld extends CraftRegionAccessor implements World { + } + + Vector dir = direction.clone().normalize().multiply(maxDistance); +- Vec3 startPos = CraftLocation.toVec3D(start); ++ Vec3 startPos = io.papermc.paper.util.MCUtil.toVec3(start); // Paper - Add predicate for blocks when raytracing + Vec3 endPos = startPos.add(dir.getX(), dir.getY(), dir.getZ()); +- HitResult nmsHitResult = this.getHandle().clip(new ClipContext(startPos, endPos, ignorePassableBlocks ? ClipContext.Block.COLLIDER : ClipContext.Block.OUTLINE, CraftFluidCollisionMode.toNMS(fluidCollisionMode), CollisionContext.empty())); ++ HitResult nmsHitResult = this.getHandle().clip(new ClipContext(startPos, endPos, ignorePassableBlocks ? ClipContext.Block.COLLIDER : ClipContext.Block.OUTLINE, CraftFluidCollisionMode.toNMS(fluidCollisionMode), CollisionContext.empty()), canCollide); // Paper - Add predicate for blocks when raytracing + + return CraftRayTraceResult.fromNMS(this, nmsHitResult); + } + + @Override + public RayTraceResult rayTrace(Location start, Vector direction, double maxDistance, FluidCollisionMode fluidCollisionMode, boolean ignorePassableBlocks, double raySize, Predicate filter) { +- RayTraceResult blockHit = this.rayTraceBlocks(start, direction, maxDistance, fluidCollisionMode, ignorePassableBlocks); ++ // Paper start - Add predicate for blocks when raytracing ++ return this.rayTrace(start, direction, maxDistance, fluidCollisionMode, ignorePassableBlocks, raySize, filter, null); ++ } ++ ++ @Override ++ public RayTraceResult rayTrace(io.papermc.paper.math.Position start, Vector direction, double maxDistance, FluidCollisionMode fluidCollisionMode, boolean ignorePassableBlocks, double raySize, Predicate filter, Predicate canCollide) { ++ RayTraceResult blockHit = this.rayTraceBlocks(start, direction, maxDistance, fluidCollisionMode, ignorePassableBlocks, canCollide); ++ // Paper end - Add predicate for blocks when raytracing + Vector startVec = null; + double blockHitDistance = maxDistance; + diff --git a/patches/server/0891-Broadcast-take-item-packets-with-collector-as-source.patch b/patches/server/0891-Broadcast-take-item-packets-with-collector-as-source.patch new file mode 100644 index 000000000000..1fb19203b6a8 --- /dev/null +++ b/patches/server/0891-Broadcast-take-item-packets-with-collector-as-source.patch @@ -0,0 +1,20 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: booky10 +Date: Sun, 29 Oct 2023 02:36:10 +0100 +Subject: [PATCH] Broadcast take item packets with collector as source + +This fixes players (which can't view the collector) seeing item pickups with themselves as the target. + +diff --git a/src/main/java/net/minecraft/world/entity/LivingEntity.java b/src/main/java/net/minecraft/world/entity/LivingEntity.java +index 24245cfb160dc990b3661388c2f95b9383f98428..09bd6ba5907d42bed08872f18d40d8c743d392ff 100644 +--- a/src/main/java/net/minecraft/world/entity/LivingEntity.java ++++ b/src/main/java/net/minecraft/world/entity/LivingEntity.java +@@ -3891,7 +3891,7 @@ public abstract class LivingEntity extends Entity implements Attackable { + + public void take(Entity item, int count) { + if (!item.isRemoved() && !this.level().isClientSide && (item instanceof ItemEntity || item instanceof AbstractArrow || item instanceof ExperienceOrb)) { +- ((ServerLevel) this.level()).getChunkSource().broadcast(item, new ClientboundTakeItemEntityPacket(item.getId(), this.getId(), count)); ++ ((ServerLevel) this.level()).getChunkSource().broadcastAndSend(this, new ClientboundTakeItemEntityPacket(item.getId(), this.getId(), count)); // Paper - broadcast with collector as source + } + + } diff --git a/patches/server/0891-Fix-UnsafeValues-loadAdvancement.patch b/patches/server/0891-Fix-UnsafeValues-loadAdvancement.patch deleted file mode 100644 index 8f824385f852..000000000000 --- a/patches/server/0891-Fix-UnsafeValues-loadAdvancement.patch +++ /dev/null @@ -1,43 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: LemonCaramel -Date: Sun, 24 Sep 2023 20:19:44 +0900 -Subject: [PATCH] Fix UnsafeValues#loadAdvancement - - -diff --git a/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java b/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java -index 23ed0a130525a0b3a1b41330685476463c81f183..b07c8111daa010dee2bb8be52162aafa4c267f1f 100644 ---- a/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java -+++ b/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java -@@ -308,9 +308,30 @@ public final class CraftMagicNumbers implements UnsafeValues { - ResourceLocation minecraftkey = CraftNamespacedKey.toMinecraft(key); - - JsonElement jsonelement = ServerAdvancementManager.GSON.fromJson(advancement, JsonElement.class); -- net.minecraft.advancements.Advancement nms = net.minecraft.advancements.Advancement.CODEC.parse(JsonOps.INSTANCE, jsonelement).getOrThrow(JsonParseException::new); -+ final net.minecraft.resources.RegistryOps ops = CraftRegistry.getMinecraftRegistry().createSerializationContext(JsonOps.INSTANCE); // Paper - use RegistryOps -+ final net.minecraft.advancements.Advancement nms = net.minecraft.advancements.Advancement.CODEC.parse(ops, jsonelement).getOrThrow(JsonParseException::new); // Paper - use RegistryOps - if (nms != null) { -- MinecraftServer.getServer().getAdvancements().advancements.put(minecraftkey, new AdvancementHolder(minecraftkey, nms)); -+ // Paper start - Fix throw UnsupportedOperationException -+ //MinecraftServer.getServer().getAdvancements().advancements.put(minecraftkey, new AdvancementHolder(minecraftkey, nms)); -+ final com.google.common.collect.ImmutableMap.Builder mapBuilder = com.google.common.collect.ImmutableMap.builder(); -+ mapBuilder.putAll(MinecraftServer.getServer().getAdvancements().advancements); -+ -+ final AdvancementHolder holder = new AdvancementHolder(minecraftkey, nms); -+ mapBuilder.put(minecraftkey, holder); -+ -+ MinecraftServer.getServer().getAdvancements().advancements = mapBuilder.build(); -+ final net.minecraft.advancements.AdvancementTree tree = MinecraftServer.getServer().getAdvancements().tree(); -+ tree.addAll(java.util.List.of(holder)); -+ -+ // recalculate advancement position -+ final net.minecraft.advancements.AdvancementNode node = tree.get(minecraftkey); -+ if (node != null) { -+ final net.minecraft.advancements.AdvancementNode root = node.root(); -+ if (root.holder().value().display().isPresent()) { -+ net.minecraft.advancements.TreeNodePosition.run(root); -+ } -+ } -+ // Paper end - Fix throw UnsupportedOperationException - Advancement bukkit = Bukkit.getAdvancement(key); - - if (bukkit != null) { diff --git a/patches/server/0892-Add-player-idle-duration-API.patch b/patches/server/0892-Add-player-idle-duration-API.patch deleted file mode 100644 index 0713bbba0b0f..000000000000 --- a/patches/server/0892-Add-player-idle-duration-API.patch +++ /dev/null @@ -1,30 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: booky10 -Date: Sat, 14 Oct 2023 03:11:11 +0200 -Subject: [PATCH] Add player idle duration API - -Implements API for getting and resetting a player's idle duration. - -diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java -index a1b870fb3d1fd5d209474c47bf523d0d5d58ce39..1d79bacbfb24442573245550263141ad56332971 100644 ---- a/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java -+++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java -@@ -3419,6 +3419,18 @@ public class CraftPlayer extends CraftHumanEntity implements Player { - } - // Paper end - -+ // Paper start -+ @Override -+ public Duration getIdleDuration() { -+ return Duration.ofMillis(net.minecraft.Util.getMillis() - this.getHandle().getLastActionTime()); -+ } -+ -+ @Override -+ public void resetIdleDuration() { -+ this.getHandle().resetLastActionTime(); -+ } -+ // Paper end -+ - public Player.Spigot spigot() - { - return this.spigot; diff --git a/patches/server/0892-Expand-LingeringPotion-API.patch b/patches/server/0892-Expand-LingeringPotion-API.patch new file mode 100644 index 000000000000..3a58f0528a7c --- /dev/null +++ b/patches/server/0892-Expand-LingeringPotion-API.patch @@ -0,0 +1,19 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Tamion <70228790+notTamion@users.noreply.github.com> +Date: Sat, 4 Nov 2023 23:57:05 +0100 +Subject: [PATCH] Expand LingeringPotion API + + +diff --git a/src/main/java/net/minecraft/world/entity/projectile/ThrownPotion.java b/src/main/java/net/minecraft/world/entity/projectile/ThrownPotion.java +index e78eef3b6fbcd657f9dd180df4cb2eeb55d0814f..9d79b193fe2a737a20d1709548b2cd6c454ff27b 100644 +--- a/src/main/java/net/minecraft/world/entity/projectile/ThrownPotion.java ++++ b/src/main/java/net/minecraft/world/entity/projectile/ThrownPotion.java +@@ -276,7 +276,7 @@ public class ThrownPotion extends ThrowableItemProjectile { + boolean noEffects = potioncontents.hasEffects(); // Paper - Fix potions splash events + // CraftBukkit start + org.bukkit.event.entity.LingeringPotionSplashEvent event = org.bukkit.craftbukkit.event.CraftEventFactory.callLingeringPotionSplashEvent(this, position, entityareaeffectcloud); +- if (!(event.isCancelled() || entityareaeffectcloud.isRemoved() || (noEffects && !entityareaeffectcloud.potionContents.hasEffects()))) { // Paper - don't spawn area effect cloud if the effects were empty and not changed during the event handling ++ if (!(event.isCancelled() || entityareaeffectcloud.isRemoved() || (!event.allowsEmptyCreation() && (noEffects && !entityareaeffectcloud.potionContents.hasEffects())))) { // Paper - don't spawn area effect cloud if the effects were empty and not changed during the event handling + this.level().addFreshEntity(entityareaeffectcloud); + } else { + entityareaeffectcloud.discard(null); // CraftBukkit - add Bukkit remove cause diff --git a/patches/server/0893-Don-t-check-if-we-can-see-non-visible-entities.patch b/patches/server/0893-Don-t-check-if-we-can-see-non-visible-entities.patch deleted file mode 100644 index 5d9fbe505fe4..000000000000 --- a/patches/server/0893-Don-t-check-if-we-can-see-non-visible-entities.patch +++ /dev/null @@ -1,19 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Brokkonaut -Date: Sat, 21 Oct 2023 20:52:57 +0100 -Subject: [PATCH] Don't check if we can see non-visible entities - - -diff --git a/src/main/java/net/minecraft/server/level/ChunkMap.java b/src/main/java/net/minecraft/server/level/ChunkMap.java -index 57af5d1986021c9a135599c6f55b091aebd3bab7..6c667823c2ef15766f3afc1a9c819cb6c24b0912 100644 ---- a/src/main/java/net/minecraft/server/level/ChunkMap.java -+++ b/src/main/java/net/minecraft/server/level/ChunkMap.java -@@ -1537,7 +1537,7 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider - // Paper end - Configurable entity tracking range by Y - - // CraftBukkit start - respect vanish API -- if (!player.getBukkitEntity().canSee(this.entity.getBukkitEntity())) { -+ if (flag && !player.getBukkitEntity().canSee(this.entity.getBukkitEntity())) { // Paper - only consider hits - flag = false; - } - // CraftBukkit end diff --git a/patches/server/0893-Fix-strikeLightningEffect-powers-lightning-rods-and-.patch b/patches/server/0893-Fix-strikeLightningEffect-powers-lightning-rods-and-.patch new file mode 100644 index 000000000000..ac9852081f26 --- /dev/null +++ b/patches/server/0893-Fix-strikeLightningEffect-powers-lightning-rods-and-.patch @@ -0,0 +1,72 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Tamion <70228790+notTamion@users.noreply.github.com> +Date: Sat, 30 Sep 2023 12:36:14 +0200 +Subject: [PATCH] Fix strikeLightningEffect powers lightning rods and clears + copper + + +diff --git a/src/main/java/net/minecraft/world/entity/LightningBolt.java b/src/main/java/net/minecraft/world/entity/LightningBolt.java +index 152ecd38814089333b8d61538297ce720756d2c3..12127b14babf646711d3a118416453c4f1ac1460 100644 +--- a/src/main/java/net/minecraft/world/entity/LightningBolt.java ++++ b/src/main/java/net/minecraft/world/entity/LightningBolt.java +@@ -48,6 +48,7 @@ public class LightningBolt extends Entity { + private ServerPlayer cause; + private final Set hitEntities = Sets.newHashSet(); + private int blocksSetOnFire; ++ public boolean isEffect; // Paper - Properly handle lightning effects api + + public LightningBolt(EntityType type, Level world) { + super(type, world); +@@ -86,7 +87,7 @@ public class LightningBolt extends Entity { + @Override + public void tick() { + super.tick(); +- if (this.life == 2) { ++ if (!this.isEffect && this.life == 2) { // Paper - Properly handle lightning effects api + if (this.level().isClientSide()) { + this.level().playLocalSound(this.getX(), this.getY(), this.getZ(), SoundEvents.LIGHTNING_BOLT_THUNDER, SoundSource.WEATHER, 10000.0F, 0.8F + this.random.nextFloat() * 0.2F, false); + this.level().playLocalSound(this.getX(), this.getY(), this.getZ(), SoundEvents.LIGHTNING_BOLT_IMPACT, SoundSource.WEATHER, 2.0F, 0.5F + this.random.nextFloat() * 0.2F, false); +@@ -133,7 +134,7 @@ public class LightningBolt extends Entity { + } + } + +- if (this.life >= 0 && !this.visualOnly) { // CraftBukkit - add !this.visualOnly ++ if (this.life >= 0 && !this.isEffect) { // CraftBukkit - add !this.visualOnly // Paper - Properly handle lightning effects api + if (!(this.level() instanceof ServerLevel)) { + this.level().setSkyFlashTime(2); + } else if (!this.visualOnly) { +@@ -162,7 +163,7 @@ public class LightningBolt extends Entity { + } + + private void spawnFire(int spreadAttempts) { +- if (!this.visualOnly) { ++ if (!this.visualOnly && !this.isEffect) { // Paper - Properly handle lightning effects api + Level world = this.level(); + + if (world instanceof ServerLevel) { +diff --git a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java +index 041a6042a91f2a8933d7f9bcb44bb78894ffd405..b1ad6c47d3d42c93411753d4505ac9142b1697d3 100644 +--- a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java ++++ b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java +@@ -742,7 +742,7 @@ public class CraftWorld extends CraftRegionAccessor implements World { + + LightningBolt lightning = EntityType.LIGHTNING_BOLT.create(this.world, EntitySpawnReason.COMMAND); + lightning.moveTo(loc.getX(), loc.getY(), loc.getZ()); +- lightning.setVisualOnly(isVisual); ++ lightning.isEffect = isVisual; // Paper - Properly handle lightning effects api + this.world.strikeLightning(lightning, LightningStrikeEvent.Cause.CUSTOM); + return (LightningStrike) lightning.getBukkitEntity(); + } +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftLightningStrike.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftLightningStrike.java +index 6fed8075aa75e3852dc826a45ca44603c0446a56..e9f471e60af0725ec34e2985d63ae9ea9f88590a 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftLightningStrike.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftLightningStrike.java +@@ -13,7 +13,7 @@ public class CraftLightningStrike extends CraftEntity implements LightningStrike + + @Override + public boolean isEffect() { +- return this.getHandle().visualOnly; ++ return this.getHandle().isEffect; // Paper - Properly handle lightning effects api + } + + public int getFlashes() { diff --git a/patches/server/0894-Add-hand-to-fish-event-for-all-player-interactions.patch b/patches/server/0894-Add-hand-to-fish-event-for-all-player-interactions.patch new file mode 100644 index 000000000000..cb3197a1b2e2 --- /dev/null +++ b/patches/server/0894-Add-hand-to-fish-event-for-all-player-interactions.patch @@ -0,0 +1,75 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: booky10 +Date: Mon, 3 Jul 2023 01:55:32 +0200 +Subject: [PATCH] Add hand to fish event for all player interactions + + +diff --git a/src/main/java/net/minecraft/world/entity/projectile/FishingHook.java b/src/main/java/net/minecraft/world/entity/projectile/FishingHook.java +index 1f95234c0a1457050574aa0f6c4b2a8c91b1f272..5b49b11d2d88b33731df582b119ef7a680d862e9 100644 +--- a/src/main/java/net/minecraft/world/entity/projectile/FishingHook.java ++++ b/src/main/java/net/minecraft/world/entity/projectile/FishingHook.java +@@ -487,7 +487,15 @@ public class FishingHook extends Projectile { + @Override + public void readAdditionalSaveData(CompoundTag nbt) {} + ++ // Paper start - Add hand parameter to PlayerFishEvent ++ @Deprecated ++ @io.papermc.paper.annotation.DoNotUse + public int retrieve(ItemStack usedItem) { ++ return this.retrieve(net.minecraft.world.InteractionHand.MAIN_HAND, usedItem); ++ } ++ ++ public int retrieve(net.minecraft.world.InteractionHand hand, ItemStack usedItem) { ++ // Paper end - Add hand parameter to PlayerFishEvent + net.minecraft.world.entity.player.Player entityhuman = this.getPlayerOwner(); + + if (!this.level().isClientSide && entityhuman != null && !this.shouldStopFishing(entityhuman)) { +@@ -495,7 +503,7 @@ public class FishingHook extends Projectile { + + if (this.hookedIn != null) { + // CraftBukkit start +- PlayerFishEvent playerFishEvent = new PlayerFishEvent((Player) entityhuman.getBukkitEntity(), this.hookedIn.getBukkitEntity(), (FishHook) this.getBukkitEntity(), PlayerFishEvent.State.CAUGHT_ENTITY); ++ PlayerFishEvent playerFishEvent = new PlayerFishEvent((Player) entityhuman.getBukkitEntity(), this.hookedIn.getBukkitEntity(), (FishHook) this.getBukkitEntity(), org.bukkit.craftbukkit.CraftEquipmentSlot.getHand(hand), PlayerFishEvent.State.CAUGHT_ENTITY); // Paper - Add hand parameter to PlayerFishEvent + this.level().getCraftServer().getPluginManager().callEvent(playerFishEvent); + + if (playerFishEvent.isCancelled()) { +@@ -524,7 +532,7 @@ public class FishingHook extends Projectile { + } + // Paper end + // CraftBukkit start +- PlayerFishEvent playerFishEvent = new PlayerFishEvent((Player) entityhuman.getBukkitEntity(), entityitem != null ? entityitem.getBukkitEntity() : null, (FishHook) this.getBukkitEntity(), PlayerFishEvent.State.CAUGHT_FISH); // Paper - entityitem may be null ++ PlayerFishEvent playerFishEvent = new PlayerFishEvent((Player) entityhuman.getBukkitEntity(), entityitem != null ? entityitem.getBukkitEntity() : null, (FishHook) this.getBukkitEntity(), org.bukkit.craftbukkit.CraftEquipmentSlot.getHand(hand), PlayerFishEvent.State.CAUGHT_FISH); // Paper - entityitem may be null // Paper - Add hand parameter to PlayerFishEvent + playerFishEvent.setExpToDrop(this.random.nextInt(6) + 1); + this.level().getCraftServer().getPluginManager().callEvent(playerFishEvent); + +@@ -558,7 +566,7 @@ public class FishingHook extends Projectile { + + if (this.onGround()) { + // CraftBukkit start +- PlayerFishEvent playerFishEvent = new PlayerFishEvent((Player) entityhuman.getBukkitEntity(), null, (FishHook) this.getBukkitEntity(), PlayerFishEvent.State.IN_GROUND); ++ PlayerFishEvent playerFishEvent = new PlayerFishEvent((Player) entityhuman.getBukkitEntity(), null, (FishHook) this.getBukkitEntity(), org.bukkit.craftbukkit.CraftEquipmentSlot.getHand(hand), PlayerFishEvent.State.IN_GROUND); // Paper - Add hand parameter to PlayerFishEvent + this.level().getCraftServer().getPluginManager().callEvent(playerFishEvent); + + if (playerFishEvent.isCancelled()) { +@@ -569,7 +577,7 @@ public class FishingHook extends Projectile { + } + // CraftBukkit start + if (i == 0) { +- PlayerFishEvent playerFishEvent = new PlayerFishEvent((Player) entityhuman.getBukkitEntity(), null, (FishHook) this.getBukkitEntity(), PlayerFishEvent.State.REEL_IN); ++ PlayerFishEvent playerFishEvent = new PlayerFishEvent((Player) entityhuman.getBukkitEntity(), null, (FishHook) this.getBukkitEntity(), org.bukkit.craftbukkit.CraftEquipmentSlot.getHand(hand), PlayerFishEvent.State.REEL_IN); // Paper - Add hand parameter to PlayerFishEvent + this.level().getCraftServer().getPluginManager().callEvent(playerFishEvent); + if (playerFishEvent.isCancelled()) { + return 0; +diff --git a/src/main/java/net/minecraft/world/item/FishingRodItem.java b/src/main/java/net/minecraft/world/item/FishingRodItem.java +index 5a50f3391d73fe881b8219d9da05c1dc45533251..801a513d67637136a15307a98fc6bbec9d202b00 100644 +--- a/src/main/java/net/minecraft/world/item/FishingRodItem.java ++++ b/src/main/java/net/minecraft/world/item/FishingRodItem.java +@@ -31,7 +31,7 @@ public class FishingRodItem extends Item { + + if (user.fishing != null) { + if (!world.isClientSide) { +- int i = user.fishing.retrieve(itemstack); ++ int i = user.fishing.retrieve(hand, itemstack); // Paper - Add hand parameter to PlayerFishEvent + + itemstack.hurtAndBreak(i, user, LivingEntity.getSlotForHand(hand)); + } diff --git a/patches/server/0894-Fix-NPE-in-SculkBloomEvent-world-access.patch b/patches/server/0894-Fix-NPE-in-SculkBloomEvent-world-access.patch deleted file mode 100644 index 5abf7cda2cf6..000000000000 --- a/patches/server/0894-Fix-NPE-in-SculkBloomEvent-world-access.patch +++ /dev/null @@ -1,43 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Lulu13022002 <41980282+Lulu13022002@users.noreply.github.com> -Date: Fri, 20 Oct 2023 19:50:22 +0200 -Subject: [PATCH] Fix NPE in SculkBloomEvent world access - - -diff --git a/src/main/java/net/minecraft/world/level/block/entity/SculkCatalystBlockEntity.java b/src/main/java/net/minecraft/world/level/block/entity/SculkCatalystBlockEntity.java -index 8d95d129cae03af9ded699047742324807186994..a74732902c0494c67e6acf2fc04581ff9c46b832 100644 ---- a/src/main/java/net/minecraft/world/level/block/entity/SculkCatalystBlockEntity.java -+++ b/src/main/java/net/minecraft/world/level/block/entity/SculkCatalystBlockEntity.java -@@ -35,9 +35,16 @@ public class SculkCatalystBlockEntity extends BlockEntity implements GameEventLi - public SculkCatalystBlockEntity(BlockPos pos, BlockState state) { - super(BlockEntityType.SCULK_CATALYST, pos, state); - this.catalystListener = new SculkCatalystBlockEntity.CatalystListener(state, new BlockPositionSource(pos)); -- this.catalystListener.level = this.level; // CraftBukkit - } - -+ // Paper start - Fix NPE in SculkBloomEvent world access -+ @Override -+ public void setLevel(Level level) { -+ super.setLevel(level); -+ this.catalystListener.sculkSpreader.level = level; -+ } -+ // Paper end - Fix NPE in SculkBloomEvent world access -+ - public static void serverTick(Level world, BlockPos pos, BlockState state, SculkCatalystBlockEntity blockEntity) { - org.bukkit.craftbukkit.event.CraftEventFactory.sourceBlockOverride = blockEntity.getBlockPos(); // CraftBukkit - SPIGOT-7068: Add source block override, not the most elegant way but better than passing down a BlockPosition up to five methods deep. - blockEntity.catalystListener.getSculkSpreader().updateCursors(world, pos, world.getRandom(), true); -@@ -67,13 +74,12 @@ public class SculkCatalystBlockEntity extends BlockEntity implements GameEventLi - final SculkSpreader sculkSpreader; - private final BlockState blockState; - private final PositionSource positionSource; -- private Level level; // CraftBukkit - - public CatalystListener(BlockState state, PositionSource positionSource) { - this.blockState = state; - this.positionSource = positionSource; - this.sculkSpreader = SculkSpreader.createLevelSpreader(); -- this.sculkSpreader.level = this.level; // CraftBukkit -+ // this.sculkSpreader.level = this.level; // CraftBukkit // Paper - Fix NPE in SculkBloomEvent world access - } - - @Override diff --git a/patches/server/0895-Allow-null-itemstack-for-Player-sendEquipmentChange.patch b/patches/server/0895-Allow-null-itemstack-for-Player-sendEquipmentChange.patch deleted file mode 100644 index 6839ae5814ea..000000000000 --- a/patches/server/0895-Allow-null-itemstack-for-Player-sendEquipmentChange.patch +++ /dev/null @@ -1,19 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: David Scandurra -Date: Wed, 25 Oct 2023 20:36:25 +0200 -Subject: [PATCH] Allow null itemstack for Player#sendEquipmentChange - - -diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java -index 1d79bacbfb24442573245550263141ad56332971..b2abaf6b535da13cdeeb99c568b91c585ac2ed20 100644 ---- a/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java -+++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java -@@ -1127,7 +1127,7 @@ public class CraftPlayer extends CraftHumanEntity implements Player { - - @Override - public void sendEquipmentChange(LivingEntity entity, EquipmentSlot slot, ItemStack item) { -- this.sendEquipmentChange(entity, Map.of(slot, item)); -+ this.sendEquipmentChange(entity, java.util.Collections.singletonMap(slot, item)); // Paper - replace Map.of to allow null values - } - - @Override diff --git a/patches/server/0895-Fix-several-issues-with-EntityBreedEvent.patch b/patches/server/0895-Fix-several-issues-with-EntityBreedEvent.patch new file mode 100644 index 000000000000..7d7a988fc0c7 --- /dev/null +++ b/patches/server/0895-Fix-several-issues-with-EntityBreedEvent.patch @@ -0,0 +1,118 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Jake Potrebic +Date: Thu, 15 Dec 2022 00:14:44 -0800 +Subject: [PATCH] Fix several issues with EntityBreedEvent + +Upstream did not account for different hands when storing +the breed item for later use in the event. Also they only +stored a reference to the stack, not a copy so if the stack +changed after love mode was started, the breed item in the event +also changed. Also in several places, the breed item was stored after +it was decreased by one to consume the item. + +diff --git a/src/main/java/net/minecraft/world/entity/animal/Animal.java b/src/main/java/net/minecraft/world/entity/animal/Animal.java +index 775bfac26aaa6db998c2647ec81247b67d0bf784..5677dc97ed83652f261100cf391883cfac7d16fe 100644 +--- a/src/main/java/net/minecraft/world/entity/animal/Animal.java ++++ b/src/main/java/net/minecraft/world/entity/animal/Animal.java +@@ -158,8 +158,9 @@ public abstract class Animal extends AgeableMob { + int i = this.getAge(); + + if (!this.level().isClientSide && i == 0 && this.canFallInLove()) { ++ final ItemStack breedCopy = itemstack.copy(); // Paper - Fix EntityBreedEvent copying + this.usePlayerItem(player, hand, itemstack); +- this.setInLove(player); ++ this.setInLove(player, breedCopy); // Paper - Fix EntityBreedEvent copying + this.playEatingSound(); + return InteractionResult.SUCCESS_SERVER; + } +@@ -201,10 +202,18 @@ public abstract class Animal extends AgeableMob { + return this.inLove <= 0; + } + ++ @Deprecated @io.papermc.paper.annotation.DoNotUse // Paper - Fix EntityBreedEvent copying + public void setInLove(@Nullable Player player) { ++ // Paper start - Fix EntityBreedEvent copying ++ this.setInLove(player, null); ++ } ++ public void setInLove(@Nullable Player player, @Nullable ItemStack breedItemCopy) { ++ if (breedItemCopy != null) this.breedItem = breedItemCopy; ++ // Paper end - Fix EntityBreedEvent copying + // CraftBukkit start + EntityEnterLoveModeEvent entityEnterLoveModeEvent = CraftEventFactory.callEntityEnterLoveModeEvent(player, this, 600); + if (entityEnterLoveModeEvent.isCancelled()) { ++ this.breedItem = null; // Paper - Fix EntityBreedEvent copying; clear if cancelled + return; + } + this.inLove = entityEnterLoveModeEvent.getTicksInLove(); +@@ -212,7 +221,7 @@ public abstract class Animal extends AgeableMob { + if (player != null) { + this.loveCause = player.getUUID(); + } +- this.breedItem = player.getInventory().getSelected(); // CraftBukkit ++ // Paper - Fix EntityBreedEvent copying; set breed item in better place + + this.level().broadcastEntityEvent(this, (byte) 18); + } +diff --git a/src/main/java/net/minecraft/world/entity/animal/Panda.java b/src/main/java/net/minecraft/world/entity/animal/Panda.java +index b654bec0fbe903fac24f3bb99399455bf367c68a..be753557d7ebd6f1e82b1bdb6d60ecc450f72eec 100644 +--- a/src/main/java/net/minecraft/world/entity/animal/Panda.java ++++ b/src/main/java/net/minecraft/world/entity/animal/Panda.java +@@ -653,8 +653,9 @@ public class Panda extends Animal { + this.usePlayerItem(player, hand, itemstack); + this.ageUp((int) ((float) (-this.getAge() / 20) * 0.1F), true); + } else if (!this.level().isClientSide && this.getAge() == 0 && this.canFallInLove()) { ++ final ItemStack breedCopy = itemstack.copy(); // Paper - Fix EntityBreedEvent copying + this.usePlayerItem(player, hand, itemstack); +- this.setInLove(player); ++ this.setInLove(player, breedCopy); // Paper - Fix EntityBreedEvent copying + } else { + Level world = this.level(); + +diff --git a/src/main/java/net/minecraft/world/entity/animal/camel/Camel.java b/src/main/java/net/minecraft/world/entity/animal/camel/Camel.java +index c99d37a40c63726c11980adccc67d09fd5132885..f3c884ab9c09f04dd01cabf2ee9de3b5b620563d 100644 +--- a/src/main/java/net/minecraft/world/entity/animal/camel/Camel.java ++++ b/src/main/java/net/minecraft/world/entity/animal/camel/Camel.java +@@ -395,7 +395,7 @@ public class Camel extends AbstractHorse { + boolean flag1 = this.isTamed() && this.getAge() == 0 && this.canFallInLove(); + + if (flag1) { +- this.setInLove(player); ++ this.setInLove(player, item.copy()); // Paper - Fix EntityBreedEvent copying + } + + boolean flag2 = this.isBaby(); +diff --git a/src/main/java/net/minecraft/world/entity/animal/horse/AbstractHorse.java b/src/main/java/net/minecraft/world/entity/animal/horse/AbstractHorse.java +index 74151d69380e4adede40c7d7fc20834553706730..8aed30cdbbfdd42c20dcd4c8773c8a0ee21a980d 100644 +--- a/src/main/java/net/minecraft/world/entity/animal/horse/AbstractHorse.java ++++ b/src/main/java/net/minecraft/world/entity/animal/horse/AbstractHorse.java +@@ -585,7 +585,7 @@ public abstract class AbstractHorse extends Animal implements ContainerListener, + b0 = 5; + if (!this.level().isClientSide && this.isTamed() && this.getAge() == 0 && !this.isInLove()) { + flag = true; +- this.setInLove(player); ++ this.setInLove(player, item.copy()); // Paper - Fix EntityBreedEvent copying + } + } else if (item.is(Items.GOLDEN_APPLE) || item.is(Items.ENCHANTED_GOLDEN_APPLE)) { + f = 10.0F; +@@ -593,7 +593,7 @@ public abstract class AbstractHorse extends Animal implements ContainerListener, + b0 = 10; + if (!this.level().isClientSide && this.isTamed() && this.getAge() == 0 && !this.isInLove()) { + flag = true; +- this.setInLove(player); ++ this.setInLove(player, item.copy()); // Paper - Fix EntityBreedEvent copying + } + } + +diff --git a/src/main/java/net/minecraft/world/entity/animal/horse/Llama.java b/src/main/java/net/minecraft/world/entity/animal/horse/Llama.java +index 4da66867109394c8966e27551d20b4bcdf4bc9be..18bd483fe46de3d9dc129bffbccfba9d4cab9550 100644 +--- a/src/main/java/net/minecraft/world/entity/animal/horse/Llama.java ++++ b/src/main/java/net/minecraft/world/entity/animal/horse/Llama.java +@@ -177,7 +177,7 @@ public class Llama extends AbstractChestedHorse implements VariantHolder -Date: Wed, 11 Oct 2023 00:45:53 -0400 -Subject: [PATCH] Add API to get the collision shape of a block before it's - placed - - -diff --git a/src/main/java/org/bukkit/craftbukkit/block/data/CraftBlockData.java b/src/main/java/org/bukkit/craftbukkit/block/data/CraftBlockData.java -index 64ddd6d1c40dc91b6e7fc3118403415bb4533d97..0daa0bf7e56aa7228d89867500cb780b37f06541 100644 ---- a/src/main/java/org/bukkit/craftbukkit/block/data/CraftBlockData.java -+++ b/src/main/java/org/bukkit/craftbukkit/block/data/CraftBlockData.java -@@ -679,6 +679,20 @@ public class CraftBlockData implements BlockData { - return this.state.isFaceSturdy(EmptyBlockGetter.INSTANCE, BlockPos.ZERO, CraftBlock.blockFaceToNotch(face), CraftBlockSupport.toNMS(support)); - } - -+ // Paper start -+ @Override -+ public org.bukkit.util.VoxelShape getCollisionShape(Location location) { -+ Preconditions.checkArgument(location != null, "location must not be null"); -+ -+ CraftWorld world = (CraftWorld) location.getWorld(); -+ Preconditions.checkArgument(world != null, "location must not have a null world"); -+ -+ BlockPos position = CraftLocation.toBlockPosition(location); -+ net.minecraft.world.phys.shapes.VoxelShape shape = this.state.getCollisionShape(world.getHandle(), position); -+ return new org.bukkit.craftbukkit.util.CraftVoxelShape(shape); -+ } -+ // Paper end -+ - @Override - public Color getMapColor() { - return Color.fromRGB(this.state.getMapColor(null, null).col); diff --git a/patches/server/0897-Fix-missing-event-call-for-entity-teleport-API.patch b/patches/server/0897-Fix-missing-event-call-for-entity-teleport-API.patch new file mode 100644 index 000000000000..c00c1e49bd4e --- /dev/null +++ b/patches/server/0897-Fix-missing-event-call-for-entity-teleport-API.patch @@ -0,0 +1,28 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: booky10 +Date: Sun, 12 Nov 2023 05:09:47 +0100 +Subject: [PATCH] Fix missing event call for entity teleport API + + +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java +index 11a08ff211a9a32a825519ddb1736e02cf7f685e..7536ab5c22d97a074c08a95fff6bc756d61e387d 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java +@@ -259,6 +259,17 @@ public abstract class CraftEntity implements org.bukkit.entity.Entity { + return false; + } + ++ // Paper start - fix teleport event not being called ++ org.bukkit.event.entity.EntityTeleportEvent event = new org.bukkit.event.entity.EntityTeleportEvent( ++ this, this.getLocation(), location); ++ // cancelling the event is handled differently for players and entities, ++ // entities just stop teleporting, players will still teleport to the "from" location of the event ++ if (!event.callEvent() || event.getTo() == null) { ++ return false; ++ } ++ location = event.getTo(); ++ // Paper end ++ + // If this entity is riding another entity, we must dismount before teleporting. + if (dismount) this.entity.stopRiding(); // Paper - Teleport passenger API + diff --git a/patches/server/0898-Add-predicate-for-blocks-when-raytracing.patch b/patches/server/0898-Add-predicate-for-blocks-when-raytracing.patch deleted file mode 100644 index d609b7862013..000000000000 --- a/patches/server/0898-Add-predicate-for-blocks-when-raytracing.patch +++ /dev/null @@ -1,116 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: TonytheMacaroni -Date: Wed, 6 Sep 2023 19:24:16 -0400 -Subject: [PATCH] Add predicate for blocks when raytracing - - -diff --git a/src/main/java/net/minecraft/world/level/BlockGetter.java b/src/main/java/net/minecraft/world/level/BlockGetter.java -index c978f3b2d42f512e982f289e76c2422e41b7eec6..bb8e962e63c7a2d931f9bd7f7c002aa35cfa5fd3 100644 ---- a/src/main/java/net/minecraft/world/level/BlockGetter.java -+++ b/src/main/java/net/minecraft/world/level/BlockGetter.java -@@ -70,6 +70,12 @@ public interface BlockGetter extends LevelHeightAccessor { - - // CraftBukkit start - moved block handling into separate method for use by Block#rayTrace - default BlockHitResult clip(ClipContext raytrace1, BlockPos blockposition) { -+ // Paper start - Add predicate for blocks when raytracing -+ return clip(raytrace1, blockposition, null); -+ } -+ -+ default BlockHitResult clip(ClipContext raytrace1, BlockPos blockposition, java.util.function.Predicate canCollide) { -+ // Paper end - Add predicate for blocks when raytracing - // Paper start - Prevent raytrace from loading chunks - BlockState iblockdata = this.getBlockStateIfLoaded(blockposition); - if (iblockdata == null) { -@@ -79,7 +85,7 @@ public interface BlockGetter extends LevelHeightAccessor { - return BlockHitResult.miss(raytrace1.getTo(), Direction.getNearest(vec3d.x, vec3d.y, vec3d.z), BlockPos.containing(raytrace1.getTo())); - } - // Paper end - Prevent raytrace from loading chunks -- if (iblockdata.isAir()) return null; // Paper - Perf: optimise air cases -+ if (iblockdata.isAir() || (canCollide != null && this instanceof LevelAccessor levelAccessor && !canCollide.test(org.bukkit.craftbukkit.block.CraftBlock.at(levelAccessor, blockposition)))) return null; // Paper - Perf: optimise air cases & check canCollide predicate - FluidState fluid = iblockdata.getFluidState(); // Paper - Perf: don't need to go to world state again - Vec3 vec3d = raytrace1.getFrom(); - Vec3 vec3d1 = raytrace1.getTo(); -@@ -95,8 +101,14 @@ public interface BlockGetter extends LevelHeightAccessor { - // CraftBukkit end - - default BlockHitResult clip(ClipContext context) { -+ // Paper start - Add predicate for blocks when raytracing -+ return clip(context, (java.util.function.Predicate) null); -+ } -+ -+ default BlockHitResult clip(ClipContext context, java.util.function.Predicate canCollide) { -+ // Paper end - Add predicate for blocks when raytracing - return (BlockHitResult) BlockGetter.traverseBlocks(context.getFrom(), context.getTo(), context, (raytrace1, blockposition) -> { -- return this.clip(raytrace1, blockposition); // CraftBukkit - moved into separate method -+ return this.clip(raytrace1, blockposition, canCollide); // CraftBukkit - moved into separate method // Paper - Add predicate for blocks when raytracing - }, (raytrace1) -> { - Vec3 vec3d = raytrace1.getFrom().subtract(raytrace1.getTo()); - -diff --git a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java -index cb50fca4867688d2572e8ab5b7fef7243c5b8ba9..7ada46aa24e1bc15207a34c15b056e4e76238757 100644 ---- a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java -+++ b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java -@@ -1106,9 +1106,15 @@ public class CraftWorld extends CraftRegionAccessor implements World { - - @Override - public RayTraceResult rayTraceEntities(Location start, Vector direction, double maxDistance, double raySize, Predicate filter) { -+ // Paper start - Add predicate for blocks when raytracing -+ return rayTraceEntities((io.papermc.paper.math.Position) start, direction, maxDistance, raySize, filter); -+ } -+ -+ public RayTraceResult rayTraceEntities(io.papermc.paper.math.Position start, Vector direction, double maxDistance, double raySize, Predicate filter) { - Preconditions.checkArgument(start != null, "Location start cannot be null"); -- Preconditions.checkArgument(this.equals(start.getWorld()), "Location start cannot be in a different world"); -- start.checkFinite(); -+ Preconditions.checkArgument(!(start instanceof Location location) || this.equals(location.getWorld()), "Location start cannot be in a different world"); -+ Preconditions.checkArgument(start.isFinite(), "Location start is not finite"); -+ // Paper end - Add predicate for blocks when raytracing - - Preconditions.checkArgument(direction != null, "Vector direction cannot be null"); - direction.checkFinite(); -@@ -1158,9 +1164,16 @@ public class CraftWorld extends CraftRegionAccessor implements World { - - @Override - public RayTraceResult rayTraceBlocks(Location start, Vector direction, double maxDistance, FluidCollisionMode fluidCollisionMode, boolean ignorePassableBlocks) { -+ // Paper start - Add predicate for blocks when raytracing -+ return this.rayTraceBlocks(start, direction, maxDistance, fluidCollisionMode, ignorePassableBlocks, null); -+ } -+ -+ @Override -+ public RayTraceResult rayTraceBlocks(io.papermc.paper.math.Position start, Vector direction, double maxDistance, FluidCollisionMode fluidCollisionMode, boolean ignorePassableBlocks, Predicate canCollide) { - Preconditions.checkArgument(start != null, "Location start cannot be null"); -- Preconditions.checkArgument(this.equals(start.getWorld()), "Location start cannot be in a different world"); -- start.checkFinite(); -+ Preconditions.checkArgument(!(start instanceof Location location) || this.equals(location.getWorld()), "Location start cannot be in a different world"); -+ Preconditions.checkArgument(start.isFinite(), "Location start is not finite"); -+ // Paper end - Add predicate for blocks when raytracing - - Preconditions.checkArgument(direction != null, "Vector direction cannot be null"); - direction.checkFinite(); -@@ -1173,16 +1186,23 @@ public class CraftWorld extends CraftRegionAccessor implements World { - } - - Vector dir = direction.clone().normalize().multiply(maxDistance); -- Vec3 startPos = CraftLocation.toVec3D(start); -+ Vec3 startPos = io.papermc.paper.util.MCUtil.toVec3(start); // Paper - Add predicate for blocks when raytracing - Vec3 endPos = startPos.add(dir.getX(), dir.getY(), dir.getZ()); -- HitResult nmsHitResult = this.getHandle().clip(new ClipContext(startPos, endPos, ignorePassableBlocks ? ClipContext.Block.COLLIDER : ClipContext.Block.OUTLINE, CraftFluidCollisionMode.toNMS(fluidCollisionMode), CollisionContext.empty())); -+ HitResult nmsHitResult = this.getHandle().clip(new ClipContext(startPos, endPos, ignorePassableBlocks ? ClipContext.Block.COLLIDER : ClipContext.Block.OUTLINE, CraftFluidCollisionMode.toNMS(fluidCollisionMode), CollisionContext.empty()), canCollide); // Paper - Add predicate for blocks when raytracing - - return CraftRayTraceResult.fromNMS(this, nmsHitResult); - } - - @Override - public RayTraceResult rayTrace(Location start, Vector direction, double maxDistance, FluidCollisionMode fluidCollisionMode, boolean ignorePassableBlocks, double raySize, Predicate filter) { -- RayTraceResult blockHit = this.rayTraceBlocks(start, direction, maxDistance, fluidCollisionMode, ignorePassableBlocks); -+ // Paper start - Add predicate for blocks when raytracing -+ return this.rayTrace(start, direction, maxDistance, fluidCollisionMode, ignorePassableBlocks, raySize, filter, null); -+ } -+ -+ @Override -+ public RayTraceResult rayTrace(io.papermc.paper.math.Position start, Vector direction, double maxDistance, FluidCollisionMode fluidCollisionMode, boolean ignorePassableBlocks, double raySize, Predicate filter, Predicate canCollide) { -+ RayTraceResult blockHit = this.rayTraceBlocks(start, direction, maxDistance, fluidCollisionMode, ignorePassableBlocks, canCollide); -+ // Paper end - Add predicate for blocks when raytracing - Vector startVec = null; - double blockHitDistance = maxDistance; - diff --git a/patches/server/0906-Lazily-create-LootContext-for-criterions.patch b/patches/server/0898-Lazily-create-LootContext-for-criterions.patch similarity index 100% rename from patches/server/0906-Lazily-create-LootContext-for-criterions.patch rename to patches/server/0898-Lazily-create-LootContext-for-criterions.patch diff --git a/patches/server/0899-Broadcast-take-item-packets-with-collector-as-source.patch b/patches/server/0899-Broadcast-take-item-packets-with-collector-as-source.patch deleted file mode 100644 index 740ea621a79b..000000000000 --- a/patches/server/0899-Broadcast-take-item-packets-with-collector-as-source.patch +++ /dev/null @@ -1,20 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: booky10 -Date: Sun, 29 Oct 2023 02:36:10 +0100 -Subject: [PATCH] Broadcast take item packets with collector as source - -This fixes players (which can't view the collector) seeing item pickups with themselves as the target. - -diff --git a/src/main/java/net/minecraft/world/entity/LivingEntity.java b/src/main/java/net/minecraft/world/entity/LivingEntity.java -index 4e96a65396b687d2823f2229744f5d448ba87512..04232f281ebd46954ef2259962c7bfc43b8a8aeb 100644 ---- a/src/main/java/net/minecraft/world/entity/LivingEntity.java -+++ b/src/main/java/net/minecraft/world/entity/LivingEntity.java -@@ -3749,7 +3749,7 @@ public abstract class LivingEntity extends Entity implements Attackable { - - public void take(Entity item, int count) { - if (!item.isRemoved() && !this.level().isClientSide && (item instanceof ItemEntity || item instanceof AbstractArrow || item instanceof ExperienceOrb)) { -- ((ServerLevel) this.level()).getChunkSource().broadcast(item, new ClientboundTakeItemEntityPacket(item.getId(), this.getId(), count)); -+ ((ServerLevel) this.level()).getChunkSource().broadcastAndSend(this, new ClientboundTakeItemEntityPacket(item.getId(), this.getId(), count)); // Paper - broadcast with collector as source - } - - } diff --git a/patches/server/0899-Don-t-fire-sync-events-during-worldgen.patch b/patches/server/0899-Don-t-fire-sync-events-during-worldgen.patch new file mode 100644 index 000000000000..24339297b71a --- /dev/null +++ b/patches/server/0899-Don-t-fire-sync-events-during-worldgen.patch @@ -0,0 +1,208 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Jake Potrebic +Date: Thu, 23 Nov 2023 10:33:25 -0800 +Subject: [PATCH] Don't fire sync events during worldgen + +Fixes EntityPotionEffectEvent +Fixes EntityPoseChangeEvent + +Asynchronous chunk generation provides an opportunity for things +to happen async that previously fired synchronous-only events. This +patch is for mitigating those issues by various methods. + +Also fixes correctly marking/clearing the entity generation flag. +This patch sets the generation flag anytime an entity is created +via StructureTemplate before loading from NBT to catch uses of +the flag during the loading logic. This patch clears the generation +flag from an entity when added to a ServerLevel for the situation +where generation happened directly to a ServerLevel and the +entity still has the flag set. + +diff --git a/src/main/java/net/minecraft/server/level/ServerLevel.java b/src/main/java/net/minecraft/server/level/ServerLevel.java +index 7cecbac43f1cd2d9516034ea9d2633c0c76e61f4..7a985c30a973efacf3e8b70e7163c550d86b0870 100644 +--- a/src/main/java/net/minecraft/server/level/ServerLevel.java ++++ b/src/main/java/net/minecraft/server/level/ServerLevel.java +@@ -1189,6 +1189,7 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe + // CraftBukkit start + private boolean addEntity(Entity entity, CreatureSpawnEvent.SpawnReason spawnReason) { + org.spigotmc.AsyncCatcher.catchOp("entity add"); // Spigot ++ entity.generation = false; // Paper - Don't fire sync event during generation; Reset flag if it was added during a ServerLevel generation process + // Paper start - extra debug info + if (entity.valid) { + MinecraftServer.LOGGER.error("Attempted Double World add on {}", entity, new Throwable()); +diff --git a/src/main/java/net/minecraft/world/entity/Entity.java b/src/main/java/net/minecraft/world/entity/Entity.java +index 56f2ac87f42572556646eb62f16274726e7ae455..5c7df0efee4a0aa078e36d3e262cd0b48a27cf47 100644 +--- a/src/main/java/net/minecraft/world/entity/Entity.java ++++ b/src/main/java/net/minecraft/world/entity/Entity.java +@@ -631,7 +631,11 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess + if (pose == this.getPose()) { + return; + } +- this.level.getCraftServer().getPluginManager().callEvent(new EntityPoseChangeEvent(this.getBukkitEntity(), Pose.values()[pose.ordinal()])); ++ // Paper start - Don't fire sync event during generation ++ if (!this.generation) { ++ this.level.getCraftServer().getPluginManager().callEvent(new EntityPoseChangeEvent(this.getBukkitEntity(), Pose.values()[pose.ordinal()])); ++ } ++ // Paper end - Don't fire sync event during generation + // CraftBukkit end + this.entityData.set(Entity.DATA_POSE, pose); + } +diff --git a/src/main/java/net/minecraft/world/entity/EntityType.java b/src/main/java/net/minecraft/world/entity/EntityType.java +index 64dc0bd1900575e40ac72a98c6df371223bd244c..c2693d530be00af16b2aa4ca4afd1d136db68183 100644 +--- a/src/main/java/net/minecraft/world/entity/EntityType.java ++++ b/src/main/java/net/minecraft/world/entity/EntityType.java +@@ -649,9 +649,15 @@ public class EntityType implements FeatureElement, EntityTypeT + } + + public static Optional create(CompoundTag nbt, Level world, EntitySpawnReason reason) { ++ // Paper start - Don't fire sync event during generation ++ return create(nbt, world, reason, false); ++ } ++ public static Optional create(CompoundTag nbt, Level world, EntitySpawnReason reason, boolean generation) { ++ // Paper end - Don't fire sync event during generation + return Util.ifElse(EntityType.by(nbt).map((entitytypes) -> { + return entitytypes.create(world, reason); + }), (entity) -> { ++ if (generation) entity.generation = true; // Paper - Don't fire sync event during generation + entity.load(nbt); + }, () -> { + EntityType.LOGGER.warn("Skipping Entity with id {}", nbt.getString("id")); +diff --git a/src/main/java/net/minecraft/world/entity/LivingEntity.java b/src/main/java/net/minecraft/world/entity/LivingEntity.java +index 09bd6ba5907d42bed08872f18d40d8c743d392ff..251abe382f951c3ddac8112a0ffe1dc906b88e4c 100644 +--- a/src/main/java/net/minecraft/world/entity/LivingEntity.java ++++ b/src/main/java/net/minecraft/world/entity/LivingEntity.java +@@ -1159,6 +1159,11 @@ public abstract class LivingEntity extends Entity implements Attackable { + } + + public boolean addEffect(MobEffectInstance mobeffect, @Nullable Entity entity, EntityPotionEffectEvent.Cause cause) { ++ // Paper start - Don't fire sync event during generation ++ return this.addEffect(mobeffect, entity, cause, true); ++ } ++ public boolean addEffect(MobEffectInstance mobeffect, @Nullable Entity entity, EntityPotionEffectEvent.Cause cause, boolean fireEvent) { ++ // Paper end - Don't fire sync event during generation + // org.spigotmc.AsyncCatcher.catchOp("effect add"); // Spigot // Paper - move to API + if (this.isTickingEffects) { + this.effectsToProcess.add(new ProcessableEffect(mobeffect, cause)); +@@ -1178,10 +1183,13 @@ public abstract class LivingEntity extends Entity implements Attackable { + override = new MobEffectInstance(mobeffect1).update(mobeffect); + } + ++ if (fireEvent) { // Paper - Don't fire sync event during generation + EntityPotionEffectEvent event = CraftEventFactory.callEntityPotionEffectChangeEvent(this, mobeffect1, mobeffect, cause, override); ++ override = event.isOverride(); // Paper - Don't fire sync event during generation + if (event.isCancelled()) { + return false; + } ++ } // Paper - Don't fire sync event during generation + // CraftBukkit end + + if (mobeffect1 == null) { +@@ -1190,7 +1198,7 @@ public abstract class LivingEntity extends Entity implements Attackable { + flag = true; + mobeffect.onEffectAdded(this); + // CraftBukkit start +- } else if (event.isOverride()) { ++ } else if (override) { // Paper - Don't fire sync event during generation + mobeffect1.update(mobeffect); + this.onEffectUpdated(mobeffect1, true, entity); + // CraftBukkit end +diff --git a/src/main/java/net/minecraft/world/entity/monster/Spider.java b/src/main/java/net/minecraft/world/entity/monster/Spider.java +index 6c2d4c2163cf299c0943af21d4dc367b5677c089..72e42605c278028480c368762da18f61806d766a 100644 +--- a/src/main/java/net/minecraft/world/entity/monster/Spider.java ++++ b/src/main/java/net/minecraft/world/entity/monster/Spider.java +@@ -172,7 +172,7 @@ public class Spider extends Monster { + Holder holder = entityspider_groupdataspider.effect; + + if (holder != null) { +- this.addEffect(new MobEffectInstance(holder, -1), org.bukkit.event.entity.EntityPotionEffectEvent.Cause.SPIDER_SPAWN); // CraftBukkit ++ this.addEffect(new MobEffectInstance(holder, -1), null, org.bukkit.event.entity.EntityPotionEffectEvent.Cause.SPIDER_SPAWN, world instanceof net.minecraft.server.level.ServerLevel); // CraftBukkit + } + } + +diff --git a/src/main/java/net/minecraft/world/level/levelgen/structure/templatesystem/StructureTemplate.java b/src/main/java/net/minecraft/world/level/levelgen/structure/templatesystem/StructureTemplate.java +index 734f511d197bc6bf2b02588069eb02c0224781f5..d35b731751e851bee531aa5e7996557658ba6fae 100644 +--- a/src/main/java/net/minecraft/world/level/levelgen/structure/templatesystem/StructureTemplate.java ++++ b/src/main/java/net/minecraft/world/level/levelgen/structure/templatesystem/StructureTemplate.java +@@ -549,7 +549,7 @@ public class StructureTemplate { + private static Optional createEntityIgnoreException(ServerLevelAccessor world, CompoundTag nbt) { + // CraftBukkit start + // try { +- return EntityType.create(nbt, world.getLevel(), EntitySpawnReason.STRUCTURE); ++ return EntityType.create(nbt, world.getLevel(), EntitySpawnReason.STRUCTURE, true); // Paper - Don't fire sync event during generation + // } catch (Exception exception) { + // return Optional.empty(); + // } +diff --git a/src/main/java/org/bukkit/craftbukkit/util/DelegatedGeneratorAccess.java b/src/main/java/org/bukkit/craftbukkit/util/DelegatedGeneratorAccess.java +index e444662ee4d9405eeea7caa41b9cd6b36586d840..54c4434662d057a08800918641b95708cda61207 100644 +--- a/src/main/java/org/bukkit/craftbukkit/util/DelegatedGeneratorAccess.java ++++ b/src/main/java/org/bukkit/craftbukkit/util/DelegatedGeneratorAccess.java +@@ -90,15 +90,17 @@ public abstract class DelegatedGeneratorAccess implements WorldGenLevel { + return this.handle.getLevel(); + } + +- @Override +- public void addFreshEntityWithPassengers(Entity entity) { +- this.handle.addFreshEntityWithPassengers(entity); +- } +- +- @Override +- public void addFreshEntityWithPassengers(Entity entity, CreatureSpawnEvent.SpawnReason reason) { +- this.handle.addFreshEntityWithPassengers(entity, reason); +- } ++ // Paper start - Don't fire sync event during generation; don't override these methods so all entities are run through addFreshEntity ++ // @Override ++ // public void addFreshEntityWithPassengers(Entity entity) { ++ // this.handle.addFreshEntityWithPassengers(entity); ++ // } ++ // ++ // @Override ++ // public void addFreshEntityWithPassengers(Entity entity, CreatureSpawnEvent.SpawnReason reason) { ++ // this.handle.addFreshEntityWithPassengers(entity, reason); ++ // } ++ // Paper end - Don't fire sync event during generation; don't override these methods so all entities are run through addFreshEntity + + @Override + public ServerLevel getMinecraftWorld() { +diff --git a/src/main/java/org/bukkit/craftbukkit/util/TransformerGeneratorAccess.java b/src/main/java/org/bukkit/craftbukkit/util/TransformerGeneratorAccess.java +index 35ecf6f824aca56a20280dd683123df1d0c7d66e..1d1fdcf10498c421f106158254e052da6d68d8a5 100644 +--- a/src/main/java/org/bukkit/craftbukkit/util/TransformerGeneratorAccess.java ++++ b/src/main/java/org/bukkit/craftbukkit/util/TransformerGeneratorAccess.java +@@ -39,21 +39,23 @@ public class TransformerGeneratorAccess extends DelegatedGeneratorAccess { + return super.addFreshEntity(arg0, arg1); + } + +- @Override +- public void addFreshEntityWithPassengers(Entity entity) { +- if (this.structureTransformer != null && !this.structureTransformer.transformEntity(entity)) { +- return; +- } +- super.addFreshEntityWithPassengers(entity); +- } +- +- @Override +- public void addFreshEntityWithPassengers(Entity arg0, SpawnReason arg1) { +- if (this.structureTransformer != null && !this.structureTransformer.transformEntity(arg0)) { +- return; +- } +- super.addFreshEntityWithPassengers(arg0, arg1); +- } ++ // Paper start - Don't fire sync event during generation; don't override these methods so all entities are run through addFreshEntity ++ // @Override ++ // public void addFreshEntityWithPassengers(Entity entity) { ++ // if (this.structureTransformer != null && !this.structureTransformer.transformEntity(entity)) { ++ // return; ++ // } ++ // super.addFreshEntityWithPassengers(entity); ++ // } ++ // ++ // @Override ++ // public void addFreshEntityWithPassengers(Entity arg0, SpawnReason arg1) { ++ // if (this.structureTransformer != null && !this.structureTransformer.transformEntity(arg0)) { ++ // return; ++ // } ++ // super.addFreshEntityWithPassengers(arg0, arg1); ++ // } ++ // Paper end - Don't fire sync event during generation; don't override these methods + + public boolean setCraftBlock(BlockPos position, CraftBlockState craftBlockState, int i, int j) { + if (this.structureTransformer != null) { diff --git a/patches/server/0900-Add-Structure-check-API.patch b/patches/server/0900-Add-Structure-check-API.patch new file mode 100644 index 000000000000..d138a01f44b9 --- /dev/null +++ b/patches/server/0900-Add-Structure-check-API.patch @@ -0,0 +1,26 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Jake Potrebic +Date: Mon, 27 Mar 2023 10:20:00 -0700 +Subject: [PATCH] Add Structure check API + + +diff --git a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java +index b1ad6c47d3d42c93411753d4505ac9142b1697d3..34e8afae13085f0a9ce0c916d911c88c395418e0 100644 +--- a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java ++++ b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java +@@ -250,6 +250,15 @@ public class CraftWorld extends CraftRegionAccessor implements World { + }; + } + // Paper end ++ // Paper start - structure check API ++ @Override ++ public boolean hasStructureAt(final io.papermc.paper.math.Position position, final Structure structure) { ++ return this.world.structureManager().getStructureWithPieceAt( ++ io.papermc.paper.util.MCUtil.toBlockPos(position), ++ CraftStructure.bukkitToMinecraft(structure) ++ ).isValid(); ++ } ++ // Paper end + + private static final Random rand = new Random(); + diff --git a/patches/server/0900-Expand-LingeringPotion-API.patch b/patches/server/0900-Expand-LingeringPotion-API.patch deleted file mode 100644 index fdd0abf66829..000000000000 --- a/patches/server/0900-Expand-LingeringPotion-API.patch +++ /dev/null @@ -1,19 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Tamion <70228790+notTamion@users.noreply.github.com> -Date: Sat, 4 Nov 2023 23:57:05 +0100 -Subject: [PATCH] Expand LingeringPotion API - - -diff --git a/src/main/java/net/minecraft/world/entity/projectile/ThrownPotion.java b/src/main/java/net/minecraft/world/entity/projectile/ThrownPotion.java -index 5c6cb752f61c3f3c2960a337173fb7dfe86cc1d3..86c4b593a97431efd062b8c9d86bf92269c00536 100644 ---- a/src/main/java/net/minecraft/world/entity/projectile/ThrownPotion.java -+++ b/src/main/java/net/minecraft/world/entity/projectile/ThrownPotion.java -@@ -274,7 +274,7 @@ public class ThrownPotion extends ThrowableItemProjectile implements ItemSupplie - boolean noEffects = potioncontents.hasEffects(); // Paper - Fix potions splash events - // CraftBukkit start - org.bukkit.event.entity.LingeringPotionSplashEvent event = org.bukkit.craftbukkit.event.CraftEventFactory.callLingeringPotionSplashEvent(this, position, entityareaeffectcloud); -- if (!(event.isCancelled() || entityareaeffectcloud.isRemoved() || (noEffects && !entityareaeffectcloud.potionContents.hasEffects()))) { // Paper - don't spawn area effect cloud if the effects were empty and not changed during the event handling -+ if (!(event.isCancelled() || entityareaeffectcloud.isRemoved() || (!event.allowsEmptyCreation() && (noEffects && !entityareaeffectcloud.potionContents.hasEffects())))) { // Paper - don't spawn area effect cloud if the effects were empty and not changed during the event handling - this.level().addFreshEntity(entityareaeffectcloud); - } else { - entityareaeffectcloud.discard(null); // CraftBukkit - add Bukkit remove cause diff --git a/patches/server/0901-Fix-CraftMetaItem-getAttributeModifier-duplication-c.patch b/patches/server/0901-Fix-CraftMetaItem-getAttributeModifier-duplication-c.patch new file mode 100644 index 000000000000..db45e464ac2b --- /dev/null +++ b/patches/server/0901-Fix-CraftMetaItem-getAttributeModifier-duplication-c.patch @@ -0,0 +1,19 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: hyper1423 +Date: Sun, 3 Dec 2023 07:38:09 +0900 +Subject: [PATCH] Fix CraftMetaItem#getAttributeModifier duplication check + + +diff --git a/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaItem.java b/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaItem.java +index af78e73755743fb2db7a99b834affc963b44bc10..3bcc807005a677884255f1ee36cbf1653797ba55 100644 +--- a/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaItem.java ++++ b/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaItem.java +@@ -1666,7 +1666,7 @@ class CraftMetaItem implements ItemMeta, Damageable, Repairable, BlockDataMeta { + Preconditions.checkNotNull(modifier, "AttributeModifier cannot be null"); + this.checkAttributeList(); + for (Map.Entry entry : this.attributeModifiers.entries()) { +- Preconditions.checkArgument(!entry.getValue().getKey().equals(modifier.getKey()), "Cannot register AttributeModifier. Modifier is already applied! %s", modifier); ++ Preconditions.checkArgument(!(entry.getValue().getKey().equals(modifier.getKey()) && entry.getKey() == attribute), "Cannot register AttributeModifier. Modifier is already applied! %s", modifier); // Paper - attribute modifiers with same namespaced key but on different attributes are fine + } + return this.attributeModifiers.put(attribute, modifier); + } diff --git a/patches/server/0901-Fix-strikeLightningEffect-powers-lightning-rods-and-.patch b/patches/server/0901-Fix-strikeLightningEffect-powers-lightning-rods-and-.patch deleted file mode 100644 index da48750747c4..000000000000 --- a/patches/server/0901-Fix-strikeLightningEffect-powers-lightning-rods-and-.patch +++ /dev/null @@ -1,72 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Tamion <70228790+notTamion@users.noreply.github.com> -Date: Sat, 30 Sep 2023 12:36:14 +0200 -Subject: [PATCH] Fix strikeLightningEffect powers lightning rods and clears - copper - - -diff --git a/src/main/java/net/minecraft/world/entity/LightningBolt.java b/src/main/java/net/minecraft/world/entity/LightningBolt.java -index 0471d9c85af02133f99cca4e181b83b58a3f1abc..4f701788bd21b61cad251a3a88f9bc416fb99051 100644 ---- a/src/main/java/net/minecraft/world/entity/LightningBolt.java -+++ b/src/main/java/net/minecraft/world/entity/LightningBolt.java -@@ -47,6 +47,7 @@ public class LightningBolt extends Entity { - private ServerPlayer cause; - private final Set hitEntities = Sets.newHashSet(); - private int blocksSetOnFire; -+ public boolean isEffect; // Paper - Properly handle lightning effects api - - public LightningBolt(EntityType type, Level world) { - super(type, world); -@@ -87,7 +88,7 @@ public class LightningBolt extends Entity { - @Override - public void tick() { - super.tick(); -- if (this.life == 2) { -+ if (!this.isEffect && this.life == 2) { // Paper - Properly handle lightning effects api - if (this.level().isClientSide()) { - this.level().playLocalSound(this.getX(), this.getY(), this.getZ(), SoundEvents.LIGHTNING_BOLT_THUNDER, SoundSource.WEATHER, 10000.0F, 0.8F + this.random.nextFloat() * 0.2F, false); - this.level().playLocalSound(this.getX(), this.getY(), this.getZ(), SoundEvents.LIGHTNING_BOLT_IMPACT, SoundSource.WEATHER, 2.0F, 0.5F + this.random.nextFloat() * 0.2F, false); -@@ -134,7 +135,7 @@ public class LightningBolt extends Entity { - } - } - -- if (this.life >= 0 && !this.visualOnly) { // CraftBukkit - add !this.visualOnly -+ if (this.life >= 0 && !this.isEffect) { // CraftBukkit - add !this.visualOnly // Paper - Properly handle lightning effects api - if (!(this.level() instanceof ServerLevel)) { - this.level().setSkyFlashTime(2); - } else if (!this.visualOnly) { -@@ -163,7 +164,7 @@ public class LightningBolt extends Entity { - } - - private void spawnFire(int spreadAttempts) { -- if (!this.visualOnly && !this.level().isClientSide && this.level().getGameRules().getBoolean(GameRules.RULE_DOFIRETICK)) { -+ if (!this.visualOnly && !this.isEffect && !this.level().isClientSide && this.level().getGameRules().getBoolean(GameRules.RULE_DOFIRETICK)) { // Paper - Properly handle lightning effects api - BlockPos blockposition = this.blockPosition(); - BlockState iblockdata = BaseFireBlock.getState(this.level(), blockposition); - -diff --git a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java -index 166458785b507208caa7ecf8ee8b60650ca3523a..7200c336de27ffd4d37231768ff5192956ede972 100644 ---- a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java -+++ b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java -@@ -736,7 +736,7 @@ public class CraftWorld extends CraftRegionAccessor implements World { - - LightningBolt lightning = EntityType.LIGHTNING_BOLT.create(this.world); - lightning.moveTo(loc.getX(), loc.getY(), loc.getZ()); -- lightning.setVisualOnly(isVisual); -+ lightning.isEffect = isVisual; // Paper - Properly handle lightning effects api - this.world.strikeLightning(lightning, LightningStrikeEvent.Cause.CUSTOM); - return (LightningStrike) lightning.getBukkitEntity(); - } -diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftLightningStrike.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftLightningStrike.java -index 6fed8075aa75e3852dc826a45ca44603c0446a56..e9f471e60af0725ec34e2985d63ae9ea9f88590a 100644 ---- a/src/main/java/org/bukkit/craftbukkit/entity/CraftLightningStrike.java -+++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftLightningStrike.java -@@ -13,7 +13,7 @@ public class CraftLightningStrike extends CraftEntity implements LightningStrike - - @Override - public boolean isEffect() { -- return this.getHandle().visualOnly; -+ return this.getHandle().isEffect; // Paper - Properly handle lightning effects api - } - - public int getFlashes() { diff --git a/patches/server/0902-Add-hand-to-fish-event-for-all-player-interactions.patch b/patches/server/0902-Add-hand-to-fish-event-for-all-player-interactions.patch deleted file mode 100644 index dd3e5bba4413..000000000000 --- a/patches/server/0902-Add-hand-to-fish-event-for-all-player-interactions.patch +++ /dev/null @@ -1,75 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: booky10 -Date: Mon, 3 Jul 2023 01:55:32 +0200 -Subject: [PATCH] Add hand to fish event for all player interactions - - -diff --git a/src/main/java/net/minecraft/world/entity/projectile/FishingHook.java b/src/main/java/net/minecraft/world/entity/projectile/FishingHook.java -index e70ca1b2e6fbbc1f20e65429298d01b4ebd2dd29..270f4c94912b16c7d4a2d62670847cbb5e011819 100644 ---- a/src/main/java/net/minecraft/world/entity/projectile/FishingHook.java -+++ b/src/main/java/net/minecraft/world/entity/projectile/FishingHook.java -@@ -482,7 +482,15 @@ public class FishingHook extends Projectile { - @Override - public void readAdditionalSaveData(CompoundTag nbt) {} - -+ // Paper start - Add hand parameter to PlayerFishEvent -+ @Deprecated -+ @io.papermc.paper.annotation.DoNotUse - public int retrieve(ItemStack usedItem) { -+ return this.retrieve(net.minecraft.world.InteractionHand.MAIN_HAND, usedItem); -+ } -+ -+ public int retrieve(net.minecraft.world.InteractionHand hand, ItemStack usedItem) { -+ // Paper end - Add hand parameter to PlayerFishEvent - net.minecraft.world.entity.player.Player entityhuman = this.getPlayerOwner(); - - if (!this.level().isClientSide && entityhuman != null && !this.shouldStopFishing(entityhuman)) { -@@ -490,7 +498,7 @@ public class FishingHook extends Projectile { - - if (this.hookedIn != null) { - // CraftBukkit start -- PlayerFishEvent playerFishEvent = new PlayerFishEvent((Player) entityhuman.getBukkitEntity(), this.hookedIn.getBukkitEntity(), (FishHook) this.getBukkitEntity(), PlayerFishEvent.State.CAUGHT_ENTITY); -+ PlayerFishEvent playerFishEvent = new PlayerFishEvent((Player) entityhuman.getBukkitEntity(), this.hookedIn.getBukkitEntity(), (FishHook) this.getBukkitEntity(), org.bukkit.craftbukkit.CraftEquipmentSlot.getHand(hand), PlayerFishEvent.State.CAUGHT_ENTITY); // Paper - Add hand parameter to PlayerFishEvent - this.level().getCraftServer().getPluginManager().callEvent(playerFishEvent); - - if (playerFishEvent.isCancelled()) { -@@ -519,7 +527,7 @@ public class FishingHook extends Projectile { - } - // Paper end - // CraftBukkit start -- PlayerFishEvent playerFishEvent = new PlayerFishEvent((Player) entityhuman.getBukkitEntity(), entityitem != null ? entityitem.getBukkitEntity() : null, (FishHook) this.getBukkitEntity(), PlayerFishEvent.State.CAUGHT_FISH); // Paper - entityitem may be null -+ PlayerFishEvent playerFishEvent = new PlayerFishEvent((Player) entityhuman.getBukkitEntity(), entityitem != null ? entityitem.getBukkitEntity() : null, (FishHook) this.getBukkitEntity(), org.bukkit.craftbukkit.CraftEquipmentSlot.getHand(hand), PlayerFishEvent.State.CAUGHT_FISH); // Paper - entityitem may be null // Paper - Add hand parameter to PlayerFishEvent - playerFishEvent.setExpToDrop(this.random.nextInt(6) + 1); - this.level().getCraftServer().getPluginManager().callEvent(playerFishEvent); - -@@ -553,7 +561,7 @@ public class FishingHook extends Projectile { - - if (this.onGround()) { - // CraftBukkit start -- PlayerFishEvent playerFishEvent = new PlayerFishEvent((Player) entityhuman.getBukkitEntity(), null, (FishHook) this.getBukkitEntity(), PlayerFishEvent.State.IN_GROUND); -+ PlayerFishEvent playerFishEvent = new PlayerFishEvent((Player) entityhuman.getBukkitEntity(), null, (FishHook) this.getBukkitEntity(), org.bukkit.craftbukkit.CraftEquipmentSlot.getHand(hand), PlayerFishEvent.State.IN_GROUND); // Paper - Add hand parameter to PlayerFishEvent - this.level().getCraftServer().getPluginManager().callEvent(playerFishEvent); - - if (playerFishEvent.isCancelled()) { -@@ -564,7 +572,7 @@ public class FishingHook extends Projectile { - } - // CraftBukkit start - if (i == 0) { -- PlayerFishEvent playerFishEvent = new PlayerFishEvent((Player) entityhuman.getBukkitEntity(), null, (FishHook) this.getBukkitEntity(), PlayerFishEvent.State.REEL_IN); -+ PlayerFishEvent playerFishEvent = new PlayerFishEvent((Player) entityhuman.getBukkitEntity(), null, (FishHook) this.getBukkitEntity(), org.bukkit.craftbukkit.CraftEquipmentSlot.getHand(hand), PlayerFishEvent.State.REEL_IN); // Paper - Add hand parameter to PlayerFishEvent - this.level().getCraftServer().getPluginManager().callEvent(playerFishEvent); - if (playerFishEvent.isCancelled()) { - return 0; -diff --git a/src/main/java/net/minecraft/world/item/FishingRodItem.java b/src/main/java/net/minecraft/world/item/FishingRodItem.java -index 364c3090057405f83130089d275baf1a61d8e0ca..efb21967fdd2c3b4cb35db6faf213d55da5fc30e 100644 ---- a/src/main/java/net/minecraft/world/item/FishingRodItem.java -+++ b/src/main/java/net/minecraft/world/item/FishingRodItem.java -@@ -30,7 +30,7 @@ public class FishingRodItem extends Item { - - if (user.fishing != null) { - if (!world.isClientSide) { -- int i = user.fishing.retrieve(itemstack); -+ int i = user.fishing.retrieve(hand, itemstack); // Paper - Add hand parameter to PlayerFishEvent - - itemstack.hurtAndBreak(i, user, LivingEntity.getSlotForHand(hand)); - } diff --git a/patches/server/0902-Restore-vanilla-entity-drops-behavior.patch b/patches/server/0902-Restore-vanilla-entity-drops-behavior.patch new file mode 100644 index 000000000000..d6f9795558e7 --- /dev/null +++ b/patches/server/0902-Restore-vanilla-entity-drops-behavior.patch @@ -0,0 +1,269 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Jake Potrebic +Date: Tue, 22 Mar 2022 09:34:41 -0700 +Subject: [PATCH] Restore vanilla entity drops behavior + +Instead of just tracking the itemstacks, this tracks with it, the +action to take with that itemstack to apply the correct logic +on dropping the item instead of generalizing it for all dropped +items like CB does. + +diff --git a/src/main/java/net/minecraft/server/level/ServerPlayer.java b/src/main/java/net/minecraft/server/level/ServerPlayer.java +index 419fcb4cd97cf10a2601e02024b999a51a0ff952..df21cd1bd2a3dda7169edbea18bbfdf043db76f8 100644 +--- a/src/main/java/net/minecraft/server/level/ServerPlayer.java ++++ b/src/main/java/net/minecraft/server/level/ServerPlayer.java +@@ -1235,20 +1235,20 @@ public class ServerPlayer extends net.minecraft.world.entity.player.Player { + if (this.isRemoved()) { + return; + } +- java.util.List loot = new java.util.ArrayList<>(this.getInventory().getContainerSize()); ++ List loot = new java.util.ArrayList<>(this.getInventory().getContainerSize()); // Paper - Restore vanilla drops behavior + boolean keepInventory = this.serverLevel().getGameRules().getBoolean(GameRules.RULE_KEEPINVENTORY) || this.isSpectator(); + + if (!keepInventory) { + for (ItemStack item : this.getInventory().getContents()) { + if (!item.isEmpty() && !EnchantmentHelper.has(item, EnchantmentEffectComponents.PREVENT_EQUIPMENT_DROP)) { +- loot.add(CraftItemStack.asCraftMirror(item).markForInventoryDrop()); ++ loot.add(new DefaultDrop(item, stack -> this.drop(stack, true, false, false))); // Paper - Restore vanilla drops behavior; drop function taken from Inventory#dropAll (don't fire drop event) + } + } + } + if (this.shouldDropLoot() && this.serverLevel().getGameRules().getBoolean(GameRules.RULE_DOMOBLOOT)) { // Paper - fix player loottables running when mob loot gamerule is false + // SPIGOT-5071: manually add player loot tables (SPIGOT-5195 - ignores keepInventory rule) + this.dropFromLootTable(this.serverLevel(), damageSource, this.lastHurtByPlayerTime > 0); +- this.dropCustomDeathLoot(this.serverLevel(), damageSource, flag); ++ // Paper - Restore vanilla drops behaviour; custom death loot is a noop on server player, remove. + + loot.addAll(this.drops); + this.drops.clear(); // SPIGOT-5188: make sure to clear +diff --git a/src/main/java/net/minecraft/world/entity/Entity.java b/src/main/java/net/minecraft/world/entity/Entity.java +index 5c7df0efee4a0aa078e36d3e262cd0b48a27cf47..b78395ab8f393a0f6551951731ddce7a5228b44c 100644 +--- a/src/main/java/net/minecraft/world/entity/Entity.java ++++ b/src/main/java/net/minecraft/world/entity/Entity.java +@@ -2678,19 +2678,45 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess + + @Nullable + public ItemEntity spawnAtLocation(ServerLevel world, ItemStack stack, float yOffset) { ++ // Paper start - Restore vanilla drops behavior ++ return this.spawnAtLocation(world, stack, yOffset, null); ++ } ++ public record DefaultDrop(Item item, org.bukkit.inventory.ItemStack stack, @Nullable java.util.function.Consumer dropConsumer) { ++ public DefaultDrop(final ItemStack stack, final java.util.function.Consumer dropConsumer) { ++ this(stack.getItem(), org.bukkit.craftbukkit.inventory.CraftItemStack.asCraftMirror(stack), dropConsumer); ++ } ++ ++ public void runConsumer(final java.util.function.Consumer fallback) { ++ if (this.dropConsumer == null || org.bukkit.craftbukkit.inventory.CraftItemType.bukkitToMinecraft(this.stack.getType()) != this.item) { ++ fallback.accept(this.stack); ++ } else { ++ this.dropConsumer.accept(org.bukkit.craftbukkit.inventory.CraftItemStack.asNMSCopy(this.stack)); ++ } ++ } ++ } ++ @Nullable ++ public ItemEntity spawnAtLocation(ServerLevel world, ItemStack stack, float yOffset, @Nullable java.util.function.Consumer delayedAddConsumer) { ++ // Paper end - Restore vanilla drops behavior + if (stack.isEmpty()) { + return null; + } else { + // CraftBukkit start - Capture drops for death event + if (this instanceof net.minecraft.world.entity.LivingEntity && !((net.minecraft.world.entity.LivingEntity) this).forceDrops) { +- ((net.minecraft.world.entity.LivingEntity) this).drops.add(org.bukkit.craftbukkit.inventory.CraftItemStack.asCraftMirror(stack)); // Paper - mirror so we can destroy it later ++ // Paper start - Restore vanilla drops behavior ++ ((net.minecraft.world.entity.LivingEntity) this).drops.add(new net.minecraft.world.entity.Entity.DefaultDrop(stack, itemStack -> { ++ ItemEntity itemEntity = new ItemEntity(this.level, this.getX(), this.getY() + (double) yOffset, this.getZ(), itemStack); // stack is copied before consumer ++ itemEntity.setDefaultPickUpDelay(); ++ this.level.addFreshEntity(itemEntity); ++ if (delayedAddConsumer != null) delayedAddConsumer.accept(itemEntity); ++ })); ++ // Paper end - Restore vanilla drops behavior + return null; + } + // CraftBukkit end + ItemEntity entityitem = new ItemEntity(world, this.getX(), this.getY() + (double) yOffset, this.getZ(), stack.copy()); // Paper - copy so we can destroy original + stack.setCount(0); // Paper - destroy this item - if this ever leaks due to game bugs, ensure it doesn't dupe + +- entityitem.setDefaultPickUpDelay(); ++ entityitem.setDefaultPickUpDelay(); // Paper - diff on change (in dropConsumer) + // Paper start - Call EntityDropItemEvent + return this.spawnAtLocation(world, entityitem); + } +diff --git a/src/main/java/net/minecraft/world/entity/LivingEntity.java b/src/main/java/net/minecraft/world/entity/LivingEntity.java +index 251abe382f951c3ddac8112a0ffe1dc906b88e4c..adde7352cdbcb8684f43d6bf5978b6943e9f165b 100644 +--- a/src/main/java/net/minecraft/world/entity/LivingEntity.java ++++ b/src/main/java/net/minecraft/world/entity/LivingEntity.java +@@ -289,7 +289,7 @@ public abstract class LivingEntity extends Entity implements Attackable { + protected float appliedScale; + // CraftBukkit start + public int expToDrop; +- public ArrayList drops = new ArrayList(); ++ public ArrayList drops = new ArrayList<>(); // Paper - Restore vanilla drops behavior + public final org.bukkit.craftbukkit.attribute.CraftAttributeMap craftAttributes; + public boolean collides = true; + public Set collidableExemptions = new HashSet<>(); +diff --git a/src/main/java/net/minecraft/world/entity/boss/wither/WitherBoss.java b/src/main/java/net/minecraft/world/entity/boss/wither/WitherBoss.java +index e6b18d7f8922cb42acb9e40bef2f71a56aea8646..e1be143959fbaa1d54af2a1a2c27187d70e6a9e9 100644 +--- a/src/main/java/net/minecraft/world/entity/boss/wither/WitherBoss.java ++++ b/src/main/java/net/minecraft/world/entity/boss/wither/WitherBoss.java +@@ -536,10 +536,10 @@ public class WitherBoss extends Monster implements RangedAttackMob { + @Override + protected void dropCustomDeathLoot(ServerLevel world, DamageSource source, boolean causedByPlayer) { + super.dropCustomDeathLoot(world, source, causedByPlayer); +- ItemEntity entityitem = this.spawnAtLocation(world, (ItemLike) Items.NETHER_STAR); ++ ItemEntity entityitem = this.spawnAtLocation(world, new net.minecraft.world.item.ItemStack(Items.NETHER_STAR), 0, ItemEntity::setExtendedLifetime); // Paper - Restore vanilla drops behavior; spawnAtLocation returns null so modify the item entity with a consumer + + if (entityitem != null) { +- entityitem.setExtendedLifetime(); ++ entityitem.setExtendedLifetime(); // Paper - diff on change + } + + } +diff --git a/src/main/java/net/minecraft/world/entity/decoration/ArmorStand.java b/src/main/java/net/minecraft/world/entity/decoration/ArmorStand.java +index 2bb2b36f793d25b6e49d1a72bb665cfa9f212730..63f02cdc67d9e88cc6998d0ae9d139c83e85b447 100644 +--- a/src/main/java/net/minecraft/world/entity/decoration/ArmorStand.java ++++ b/src/main/java/net/minecraft/world/entity/decoration/ArmorStand.java +@@ -619,7 +619,7 @@ public class ArmorStand extends LivingEntity { + ItemStack itemstack = new ItemStack(Items.ARMOR_STAND); + + itemstack.set(DataComponents.CUSTOM_NAME, this.getCustomName()); +- this.drops.add(org.bukkit.craftbukkit.inventory.CraftItemStack.asBukkitCopy(itemstack)); // CraftBukkit - add to drops ++ this.drops.add(new DefaultDrop(itemstack, stack -> Block.popResource(this.level(), this.blockPosition(), stack))); // CraftBukkit - add to drops // Paper - Restore vanilla drops behavior + return this.brokenByAnything(world, damageSource); // Paper + } + +@@ -633,7 +633,7 @@ public class ArmorStand extends LivingEntity { + for (i = 0; i < this.handItems.size(); ++i) { + itemstack = (ItemStack) this.handItems.get(i); + if (!itemstack.isEmpty()) { +- this.drops.add(org.bukkit.craftbukkit.inventory.CraftItemStack.asCraftMirror(itemstack)); // CraftBukkit - add to drops // Paper - mirror so we can destroy it later - though this call site was safe ++ this.drops.add(new DefaultDrop(itemstack, stack -> Block.popResource(this.level(), this.blockPosition().above(), stack))); // CraftBukkit - add to drops // Paper - Restore vanilla drops behavior; mirror so we can destroy it later - though this call site was safe & spawn drops correctly + this.handItems.set(i, ItemStack.EMPTY); + } + } +@@ -641,7 +641,7 @@ public class ArmorStand extends LivingEntity { + for (i = 0; i < this.armorItems.size(); ++i) { + itemstack = (ItemStack) this.armorItems.get(i); + if (!itemstack.isEmpty()) { +- this.drops.add(org.bukkit.craftbukkit.inventory.CraftItemStack.asCraftMirror(itemstack)); // CraftBukkit - add to drops // Paper - mirror so we can destroy it later - though this call site was safe ++ this.drops.add(new DefaultDrop(itemstack, stack -> Block.popResource(this.level(), this.blockPosition().above(), stack))); // CraftBukkit - add to drops // Paper - Restore vanilla drops behavior; mirror so we can destroy it later - though this call site was safe & spawn drops correctly + this.armorItems.set(i, ItemStack.EMPTY); + } + } +diff --git a/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java b/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java +index dbef230ae88ee1bfbc20ba53b534434c3ccac985..a0455f590d549343d6d8fd7991ba1b87a87acdb8 100644 +--- a/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java ++++ b/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java +@@ -973,19 +973,25 @@ public class CraftEventFactory { + } + + public static EntityDeathEvent callEntityDeathEvent(net.minecraft.world.entity.LivingEntity victim, DamageSource damageSource) { +- return CraftEventFactory.callEntityDeathEvent(victim, damageSource, new ArrayList(0)); ++ return CraftEventFactory.callEntityDeathEvent(victim, damageSource, new ArrayList<>(0)); // Paper - Restore vanilla drops behavior + } + +- public static EntityDeathEvent callEntityDeathEvent(net.minecraft.world.entity.LivingEntity victim, DamageSource damageSource, List drops) { ++ public static EntityDeathEvent callEntityDeathEvent(net.minecraft.world.entity.LivingEntity victim, DamageSource damageSource, List drops) { // Paper - Restore vanilla drops behavior + // Paper start + return CraftEventFactory.callEntityDeathEvent(victim, damageSource, drops, com.google.common.util.concurrent.Runnables.doNothing()); + } +- public static EntityDeathEvent callEntityDeathEvent(net.minecraft.world.entity.LivingEntity victim, DamageSource damageSource, List drops, Runnable lootCheck) { ++ ++ private static final java.util.function.Function FROM_FUNCTION = stack -> { ++ if (stack == null) return null; ++ return new Entity.DefaultDrop(CraftItemType.bukkitToMinecraft(stack.getType()), stack, null); ++ }; ++ ++ public static EntityDeathEvent callEntityDeathEvent(net.minecraft.world.entity.LivingEntity victim, DamageSource damageSource, List drops, Runnable lootCheck) { // Paper - Restore vanilla drops behavior + // Paper end + CraftLivingEntity entity = (CraftLivingEntity) victim.getBukkitEntity(); + CraftDamageSource bukkitDamageSource = new CraftDamageSource(damageSource); + CraftWorld world = (CraftWorld) entity.getWorld(); +- EntityDeathEvent event = new EntityDeathEvent(entity, bukkitDamageSource, drops, victim.getExpReward(world.getHandle(), damageSource.getEntity())); ++ EntityDeathEvent event = new EntityDeathEvent(entity, bukkitDamageSource, new io.papermc.paper.util.TransformingRandomAccessList<>(drops, Entity.DefaultDrop::stack, FROM_FUNCTION), victim.getExpReward(world.getHandle(), damageSource.getEntity())); // Paper - Restore vanilla drops behavior + populateFields(victim, event); // Paper - make cancellable + Bukkit.getServer().getPluginManager().callEvent(event); + +@@ -998,20 +1004,24 @@ public class CraftEventFactory { + victim.expToDrop = event.getDroppedExp(); + lootCheck.run(); // Paper - advancement triggers before destroying items + +- for (org.bukkit.inventory.ItemStack stack : event.getDrops()) { ++ // Paper start - Restore vanilla drops behavior ++ for (Entity.DefaultDrop drop : drops) { ++ if (drop == null) continue; ++ final org.bukkit.inventory.ItemStack stack = drop.stack(); ++ // Paper end - Restore vanilla drops behavior + if (stack == null || stack.getType() == Material.AIR || stack.getAmount() == 0) continue; + +- world.dropItem(entity.getLocation(), stack); // Paper - note: dropItem already clones due to this being bukkit -> NMS ++ drop.runConsumer(s -> world.dropItem(entity.getLocation(), s)); // Paper - Restore vanilla drops behavior + if (stack instanceof CraftItemStack) stack.setAmount(0); // Paper - destroy this item - if this ever leaks due to game bugs, ensure it doesn't dupe, but don't nuke bukkit stacks of manually added items + } + + return event; + } + +- public static PlayerDeathEvent callPlayerDeathEvent(ServerPlayer victim, DamageSource damageSource, List drops, net.kyori.adventure.text.Component deathMessage, boolean keepInventory) { // Paper - Adventure ++ public static PlayerDeathEvent callPlayerDeathEvent(ServerPlayer victim, DamageSource damageSource, List drops, net.kyori.adventure.text.Component deathMessage, boolean keepInventory) { // Paper - Adventure & Restore vanilla drops behavior + CraftPlayer entity = victim.getBukkitEntity(); + CraftDamageSource bukkitDamageSource = new CraftDamageSource(damageSource); +- PlayerDeathEvent event = new PlayerDeathEvent(entity, bukkitDamageSource, drops, victim.getExpReward(victim.serverLevel(), damageSource.getEntity()), 0, deathMessage); ++ PlayerDeathEvent event = new PlayerDeathEvent(entity, bukkitDamageSource, new io.papermc.paper.util.TransformingRandomAccessList<>(drops, Entity.DefaultDrop::stack, FROM_FUNCTION), victim.getExpReward(victim.serverLevel(), damageSource.getEntity()), 0, deathMessage); // Paper - Restore vanilla drops behavior + event.setKeepInventory(keepInventory); + event.setKeepLevel(victim.keepLevel); // SPIGOT-2222: pre-set keepLevel + populateFields(victim, event); // Paper - make cancellable +@@ -1029,16 +1039,14 @@ public class CraftEventFactory { + victim.expToDrop = event.getDroppedExp(); + victim.newExp = event.getNewExp(); + +- for (org.bukkit.inventory.ItemStack stack : event.getDrops()) { ++ // Paper start - Restore vanilla drops behavior ++ for (Entity.DefaultDrop drop : drops) { ++ if (drop == null) continue; ++ final org.bukkit.inventory.ItemStack stack = drop.stack(); ++ // Paper end - Restore vanilla drops behavior + if (stack == null || stack.getType() == Material.AIR) continue; + +- if (stack instanceof CraftItemStack craftItemStack && craftItemStack.isForInventoryDrop()) { +- victim.drop(CraftItemStack.asNMSCopy(stack), true, false, false); // SPIGOT-7800, SPIGOT-7801: Vanilla Behaviour for Player Inventory dropped items +- } else { +- victim.forceDrops = true; +- victim.spawnAtLocation(victim.serverLevel(), CraftItemStack.asNMSCopy(stack)); // SPIGOT-7806: Vanilla Behaviour for items not related to Player Inventory dropped items +- victim.forceDrops = false; +- } ++ drop.runConsumer(s -> victim.drop(CraftItemStack.unwrap(s), true, false, false)); // Paper - Restore vanilla drops behavior + } + + return event; +diff --git a/src/main/java/org/bukkit/craftbukkit/inventory/CraftItemStack.java b/src/main/java/org/bukkit/craftbukkit/inventory/CraftItemStack.java +index 30eba68435387daa3917fa2b3071892c350d9ddc..0b7bc5e83634a26ac6521694377b554c74c6bff0 100644 +--- a/src/main/java/org/bukkit/craftbukkit/inventory/CraftItemStack.java ++++ b/src/main/java/org/bukkit/craftbukkit/inventory/CraftItemStack.java +@@ -142,27 +142,6 @@ public final class CraftItemStack extends ItemStack { + this.setItemMeta(itemMeta); + } + +- /** +- * Gets if the item is marked as an inventory drop in death events. +- * +- * @return true if the item is marked as an inventory drop +- */ +- @ApiStatus.Internal +- public boolean isForInventoryDrop() { +- return this.isForInventoryDrop; +- } +- +- /** +- * Marks this item as an inventory drop in death events. +- * +- * @return the ItemStack marked as an inventory drop +- */ +- @ApiStatus.Internal +- public ItemStack markForInventoryDrop() { +- this.isForInventoryDrop = true; +- return this; +- } +- + @Override + public MaterialData getData() { + return this.handle != null ? CraftMagicNumbers.getMaterialData(this.handle.getItem()) : super.getData(); diff --git a/patches/server/0903-Dont-resend-blocks-on-interactions.patch b/patches/server/0903-Dont-resend-blocks-on-interactions.patch new file mode 100644 index 000000000000..04f0061970ea --- /dev/null +++ b/patches/server/0903-Dont-resend-blocks-on-interactions.patch @@ -0,0 +1,171 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Owen1212055 <23108066+Owen1212055@users.noreply.github.com> +Date: Tue, 27 Jun 2023 21:09:11 -0400 +Subject: [PATCH] Dont resend blocks on interactions + +In general, the client now has an acknowledgment system which will prevent block changes made by the client to be reverted correctly. + +It should be noted that this system does not yet support block entities, so those still need to resynced when needed. + +diff --git a/src/main/java/net/minecraft/server/level/ServerPlayerGameMode.java b/src/main/java/net/minecraft/server/level/ServerPlayerGameMode.java +index 0da6496c18341c01fc4551ead7e740a6037dcf31..c4bc1819cba3287c4a67ae5d00f8c4d6ab899732 100644 +--- a/src/main/java/net/minecraft/server/level/ServerPlayerGameMode.java ++++ b/src/main/java/net/minecraft/server/level/ServerPlayerGameMode.java +@@ -202,7 +202,7 @@ public class ServerPlayerGameMode { + PlayerInteractEvent event = CraftEventFactory.callPlayerInteractEvent(this.player, Action.LEFT_CLICK_BLOCK, pos, direction, this.player.getInventory().getSelected(), InteractionHand.MAIN_HAND); + if (event.isCancelled()) { + // Let the client know the block still exists +- this.player.connection.send(new ClientboundBlockUpdatePacket(this.level, pos)); ++ // this.player.connection.send(new ClientboundBlockUpdatePacket(this.level, pos)); // Paper - Don't resync blocks + // Update any tile entity data for this block + capturedBlockEntity = true; // Paper - Send block entities after destroy prediction + return; +@@ -217,7 +217,7 @@ public class ServerPlayerGameMode { + // Spigot start - handle debug stick left click for non-creative + if (this.player.getMainHandItem().is(net.minecraft.world.item.Items.DEBUG_STICK) + && ((net.minecraft.world.item.DebugStickItem) net.minecraft.world.item.Items.DEBUG_STICK).handleInteraction(this.player, this.level.getBlockState(pos), this.level, pos, false, this.player.getMainHandItem())) { +- this.player.connection.send(new ClientboundBlockUpdatePacket(this.level, pos)); ++ // this.player.connection.send(new ClientboundBlockUpdatePacket(this.level, pos)); // Paper - Don't resync block + return; + } + // Spigot end +@@ -235,15 +235,17 @@ public class ServerPlayerGameMode { + // CraftBukkit start - Swings at air do *NOT* exist. + if (event.useInteractedBlock() == Event.Result.DENY) { + // If we denied a door from opening, we need to send a correcting update to the client, as it already opened the door. +- BlockState data = this.level.getBlockState(pos); +- if (data.getBlock() instanceof DoorBlock) { +- // For some reason *BOTH* the bottom/top part have to be marked updated. +- boolean bottom = data.getValue(DoorBlock.HALF) == DoubleBlockHalf.LOWER; +- this.player.connection.send(new ClientboundBlockUpdatePacket(this.level, pos)); +- this.player.connection.send(new ClientboundBlockUpdatePacket(this.level, bottom ? pos.above() : pos.below())); +- } else if (data.getBlock() instanceof TrapDoorBlock) { +- this.player.connection.send(new ClientboundBlockUpdatePacket(this.level, pos)); +- } ++ // Paper start - Don't resync blocks ++ //BlockState data = this.level.getBlockState(pos); ++ //if (data.getBlock() instanceof DoorBlock) { ++ // // For some reason *BOTH* the bottom/top part have to be marked updated. ++ // boolean bottom = data.getValue(DoorBlock.HALF) == DoubleBlockHalf.LOWER; ++ // this.player.connection.send(new ClientboundBlockUpdatePacket(this.level, pos)); ++ // this.player.connection.send(new ClientboundBlockUpdatePacket(this.level, bottom ? pos.above() : pos.below())); ++ //} else if (data.getBlock() instanceof TrapDoorBlock) { ++ // this.player.connection.send(new ClientboundBlockUpdatePacket(this.level, pos)); ++ //} ++ // Paper end - Don't resync blocks + } else if (!iblockdata.isAir()) { + EnchantmentHelper.onHitBlock(this.level, this.player.getMainHandItem(), this.player, this.player, EquipmentSlot.MAINHAND, Vec3.atCenterOf(pos), iblockdata, (item) -> { + this.player.onEquippedItemBroken(item, EquipmentSlot.MAINHAND); +@@ -255,7 +257,7 @@ public class ServerPlayerGameMode { + if (event.useItemInHand() == Event.Result.DENY) { + // If we 'insta destroyed' then the client needs to be informed. + if (f > 1.0f) { +- this.player.connection.send(new ClientboundBlockUpdatePacket(this.level, pos)); ++ // this.player.connection.send(new ClientboundBlockUpdatePacket(this.level, pos)); // Paper - Don't resync blocks + } + return; + } +@@ -263,7 +265,7 @@ public class ServerPlayerGameMode { + + if (blockEvent.isCancelled()) { + // Let the client know the block still exists +- this.player.connection.send(new ClientboundBlockUpdatePacket(this.level, pos)); ++ // this.player.connection.send(new ClientboundBlockUpdatePacket(this.level, pos)); // Paper - Don't resync block + return; + } + +@@ -354,7 +356,7 @@ public class ServerPlayerGameMode { + + // Tell client the block is gone immediately then process events + // Don't tell the client if its a creative sword break because its not broken! +- if (this.level.getBlockEntity(pos) == null && !isSwordNoBreak) { ++ if (false && this.level.getBlockEntity(pos) == null && !isSwordNoBreak) { // Paper - Don't resync block + ClientboundBlockUpdatePacket packet = new ClientboundBlockUpdatePacket(pos, Blocks.AIR.defaultBlockState()); + this.player.connection.send(packet); + } +@@ -380,13 +382,15 @@ public class ServerPlayerGameMode { + if (isSwordNoBreak) { + return false; + } ++ // Paper start - Don't resync blocks + // Let the client know the block still exists +- this.player.connection.send(new ClientboundBlockUpdatePacket(this.level, pos)); ++ //this.player.connection.send(new ClientboundBlockUpdatePacket(this.level, pos)); + + // Brute force all possible updates +- for (Direction dir : Direction.values()) { +- this.player.connection.send(new ClientboundBlockUpdatePacket(this.level, pos.relative(dir))); +- } ++ //for (Direction dir : Direction.values()) { ++ // this.player.connection.send(new ClientboundBlockUpdatePacket(this.level, pos.relative(dir))); ++ //} ++ // Paper end - Don't resync blocks + + // Update any tile entity data for this block + if (!captureSentBlockEntities) { // Paper - Send block entities after destroy prediction +@@ -543,16 +547,18 @@ public class ServerPlayerGameMode { + if (event.useInteractedBlock() == Event.Result.DENY) { + // If we denied a door from opening, we need to send a correcting update to the client, as it already opened the door. + if (iblockdata.getBlock() instanceof DoorBlock) { +- boolean bottom = iblockdata.getValue(DoorBlock.HALF) == DoubleBlockHalf.LOWER; +- player.connection.send(new ClientboundBlockUpdatePacket(world, bottom ? blockposition.above() : blockposition.below())); ++ // Paper start - Don't resync blocks ++ // boolean bottom = iblockdata.getValue(DoorBlock.HALF) == DoubleBlockHalf.LOWER; ++ // player.connection.send(new ClientboundBlockUpdatePacket(world, bottom ? blockposition.above() : blockposition.below())); ++ // Paper end - Don't resync blocks + } else if (iblockdata.getBlock() instanceof CakeBlock) { + player.getBukkitEntity().sendHealthUpdate(); // SPIGOT-1341 - reset health for cake + } else if (this.interactItemStack.getItem() instanceof DoubleHighBlockItem) { + // send a correcting update to the client, as it already placed the upper half of the bisected item +- player.connection.send(new ClientboundBlockUpdatePacket(world, blockposition.relative(hitResult.getDirection()).above())); ++ //player.connection.send(new ClientboundBlockUpdatePacket(world, blockposition.relative(hitResult.getDirection()).above())); // Paper - Don't resync blocks + + // send a correcting update to the client for the block above as well, this because of replaceable blocks (such as grass, sea grass etc) +- player.connection.send(new ClientboundBlockUpdatePacket(world, blockposition.above())); ++ //player.connection.send(new ClientboundBlockUpdatePacket(world, blockposition.above())); // Paper - Don't resync blocks + // Paper start - extend Player Interact cancellation // TODO: consider merging this into the extracted method + } else if (iblockdata.is(Blocks.JIGSAW) || iblockdata.is(Blocks.STRUCTURE_BLOCK) || iblockdata.getBlock() instanceof net.minecraft.world.level.block.CommandBlock) { + player.connection.send(new net.minecraft.network.protocol.game.ClientboundContainerClosePacket(this.player.containerMenu.containerId)); +diff --git a/src/main/java/net/minecraft/world/item/BucketItem.java b/src/main/java/net/minecraft/world/item/BucketItem.java +index 002e2f8e956b2631529e2189be225385dfb501df..3bddfb6f7412ab86e0c090d0cbc6cf254b3f891c 100644 +--- a/src/main/java/net/minecraft/world/item/BucketItem.java ++++ b/src/main/java/net/minecraft/world/item/BucketItem.java +@@ -79,7 +79,7 @@ public class BucketItem extends Item implements DispensibleContainerItem { + PlayerBucketFillEvent event = CraftEventFactory.callPlayerBucketFillEvent((ServerLevel) world, user, blockposition, blockposition, movingobjectpositionblock.getDirection(), itemstack, dummyFluid.getItem(), hand); + + if (event.isCancelled()) { +- ((ServerPlayer) user).connection.send(new ClientboundBlockUpdatePacket(world, blockposition)); // SPIGOT-5163 (see PlayerInteractManager) ++ // ((ServerPlayer) user).connection.send(new ClientboundBlockUpdatePacket(world, blockposition)); // SPIGOT-5163 (see PlayerInteractManager) // Paper - Don't resend blocks + ((ServerPlayer) user).getBukkitEntity().updateInventory(); // SPIGOT-4541 + return InteractionResult.FAIL; + } +@@ -187,7 +187,7 @@ public class BucketItem extends Item implements DispensibleContainerItem { + if (flag2 && entityhuman != null) { + PlayerBucketEmptyEvent event = CraftEventFactory.callPlayerBucketEmptyEvent((ServerLevel) world, entityhuman, blockposition, clicked, enumdirection, itemstack, enumhand); + if (event.isCancelled()) { +- ((ServerPlayer) entityhuman).connection.send(new ClientboundBlockUpdatePacket(world, blockposition)); // SPIGOT-4238: needed when looking through entity ++ // ((ServerPlayer) entityhuman).connection.send(new ClientboundBlockUpdatePacket(world, blockposition)); // SPIGOT-4238: needed when looking through entity // Paper - Don't resend blocks + ((ServerPlayer) entityhuman).getBukkitEntity().updateInventory(); // SPIGOT-4541 + return false; + } +diff --git a/src/main/java/net/minecraft/world/item/ItemStack.java b/src/main/java/net/minecraft/world/item/ItemStack.java +index cb6bcf8b61793882252827309ffa99526244e445..98b5208baeaa12a5ff2788e457c542000d6ea48b 100644 +--- a/src/main/java/net/minecraft/world/item/ItemStack.java ++++ b/src/main/java/net/minecraft/world/item/ItemStack.java +@@ -506,10 +506,12 @@ public final class ItemStack implements DataComponentHolder { + world.preventPoiUpdated = false; + + // Brute force all possible updates +- BlockPos placedPos = ((CraftBlock) placeEvent.getBlock()).getPosition(); +- for (Direction dir : Direction.values()) { +- ((ServerPlayer) entityhuman).connection.send(new ClientboundBlockUpdatePacket(world, placedPos.relative(dir))); +- } ++ // Paper start - Don't resync blocks ++ // BlockPos placedPos = ((CraftBlock) placeEvent.getBlock()).getPosition(); ++ // for (Direction dir : Direction.values()) { ++ // ((ServerPlayer) entityhuman).connection.send(new ClientboundBlockUpdatePacket(world, placedPos.relative(dir))); ++ // } ++ // Paper end - Don't resync blocks + SignItem.openSign = null; // SPIGOT-6758 - Reset on early return + } else { + // Change the stack to its new contents if it hasn't been tampered with. diff --git a/patches/server/0903-Fix-several-issues-with-EntityBreedEvent.patch b/patches/server/0903-Fix-several-issues-with-EntityBreedEvent.patch deleted file mode 100644 index 6a97d9fb9e5f..000000000000 --- a/patches/server/0903-Fix-several-issues-with-EntityBreedEvent.patch +++ /dev/null @@ -1,133 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Jake Potrebic -Date: Thu, 15 Dec 2022 00:14:44 -0800 -Subject: [PATCH] Fix several issues with EntityBreedEvent - -Upstream did not account for different hands when storing -the breed item for later use in the event. Also they only -stored a reference to the stack, not a copy so if the stack -changed after love mode was started, the breed item in the event -also changed. Also in several places, the breed item was stored after -it was decreased by one to consume the item. - -diff --git a/src/main/java/net/minecraft/world/entity/animal/Animal.java b/src/main/java/net/minecraft/world/entity/animal/Animal.java -index b46352b328178df2a48d1c9e895bed3fabd2c292..1808e1b01afa3041a54c9c9a7586d4d61960527a 100644 ---- a/src/main/java/net/minecraft/world/entity/animal/Animal.java -+++ b/src/main/java/net/minecraft/world/entity/animal/Animal.java -@@ -148,8 +148,9 @@ public abstract class Animal extends AgeableMob { - int i = this.getAge(); - - if (!this.level().isClientSide && i == 0 && this.canFallInLove()) { -+ final ItemStack breedCopy = itemstack.copy(); // Paper - Fix EntityBreedEvent copying - this.usePlayerItem(player, hand, itemstack); -- this.setInLove(player); -+ this.setInLove(player, breedCopy); // Paper - Fix EntityBreedEvent copying - return InteractionResult.SUCCESS; - } - -@@ -175,10 +176,18 @@ public abstract class Animal extends AgeableMob { - return this.inLove <= 0; - } - -+ @Deprecated @io.papermc.paper.annotation.DoNotUse // Paper - Fix EntityBreedEvent copying - public void setInLove(@Nullable Player player) { -+ // Paper start - Fix EntityBreedEvent copying -+ this.setInLove(player, null); -+ } -+ public void setInLove(@Nullable Player player, @Nullable ItemStack breedItemCopy) { -+ if (breedItemCopy != null) this.breedItem = breedItemCopy; -+ // Paper end - Fix EntityBreedEvent copying - // CraftBukkit start - EntityEnterLoveModeEvent entityEnterLoveModeEvent = CraftEventFactory.callEntityEnterLoveModeEvent(player, this, 600); - if (entityEnterLoveModeEvent.isCancelled()) { -+ this.breedItem = null; // Paper - Fix EntityBreedEvent copying; clear if cancelled - return; - } - this.inLove = entityEnterLoveModeEvent.getTicksInLove(); -@@ -186,7 +195,7 @@ public abstract class Animal extends AgeableMob { - if (player != null) { - this.loveCause = player.getUUID(); - } -- this.breedItem = player.getInventory().getSelected(); // CraftBukkit -+ // Paper - Fix EntityBreedEvent copying; set breed item in better place - - this.level().broadcastEntityEvent(this, (byte) 18); - } -diff --git a/src/main/java/net/minecraft/world/entity/animal/Panda.java b/src/main/java/net/minecraft/world/entity/animal/Panda.java -index 293d6891948e99ac9bd741008f7dcbc5fc1a2e3d..e108f876d3f129c6287f13d68427aed2a6f0c5b1 100644 ---- a/src/main/java/net/minecraft/world/entity/animal/Panda.java -+++ b/src/main/java/net/minecraft/world/entity/animal/Panda.java -@@ -673,8 +673,9 @@ public class Panda extends Animal { - this.usePlayerItem(player, hand, itemstack); - this.ageUp((int) ((float) (-this.getAge() / 20) * 0.1F), true); - } else if (!this.level().isClientSide && this.getAge() == 0 && this.canFallInLove()) { -+ final ItemStack breedCopy = itemstack.copy(); // Paper - Fix EntityBreedEvent copying - this.usePlayerItem(player, hand, itemstack); -- this.setInLove(player); -+ this.setInLove(player, breedCopy); // Paper - Fix EntityBreedEvent copying - } else { - if (this.level().isClientSide || this.isSitting() || this.isInWater()) { - return InteractionResult.PASS; -diff --git a/src/main/java/net/minecraft/world/entity/animal/armadillo/Armadillo.java b/src/main/java/net/minecraft/world/entity/animal/armadillo/Armadillo.java -index 4eb305bc77767531efb6f9d299216248d4ee39d2..729fd2d52dd48e25ee7a077a3ffafc80ecef7c9f 100644 ---- a/src/main/java/net/minecraft/world/entity/animal/armadillo/Armadillo.java -+++ b/src/main/java/net/minecraft/world/entity/animal/armadillo/Armadillo.java -@@ -352,8 +352,8 @@ public class Armadillo extends Animal { - } - - @Override -- public void setInLove(@Nullable Player player) { -- super.setInLove(player); -+ public void setInLove(@Nullable Player player, @Nullable ItemStack breedItemCopy) { // Paper -+ super.setInLove(player, breedItemCopy); // Paper - this.makeSound(SoundEvents.ARMADILLO_EAT); - } - -diff --git a/src/main/java/net/minecraft/world/entity/animal/camel/Camel.java b/src/main/java/net/minecraft/world/entity/animal/camel/Camel.java -index 0388b09e1c4f03958384680ed487792a54007463..8941752e4600ccd11b3fa1147b2e414785589eed 100644 ---- a/src/main/java/net/minecraft/world/entity/animal/camel/Camel.java -+++ b/src/main/java/net/minecraft/world/entity/animal/camel/Camel.java -@@ -393,7 +393,7 @@ public class Camel extends AbstractHorse implements PlayerRideableJumping, Saddl - boolean flag1 = this.isTamed() && this.getAge() == 0 && this.canFallInLove(); - - if (flag1) { -- this.setInLove(player); -+ this.setInLove(player, item.copy()); // Paper - Fix EntityBreedEvent copying - } - - boolean flag2 = this.isBaby(); -diff --git a/src/main/java/net/minecraft/world/entity/animal/horse/AbstractHorse.java b/src/main/java/net/minecraft/world/entity/animal/horse/AbstractHorse.java -index f1e43254936feedfe0ffbf77071505f3a65e5053..1f5ed236fb7c0c1b0181675747d25d233f534284 100644 ---- a/src/main/java/net/minecraft/world/entity/animal/horse/AbstractHorse.java -+++ b/src/main/java/net/minecraft/world/entity/animal/horse/AbstractHorse.java -@@ -572,7 +572,7 @@ public abstract class AbstractHorse extends Animal implements ContainerListener, - b0 = 5; - if (!this.level().isClientSide && this.isTamed() && this.getAge() == 0 && !this.isInLove()) { - flag = true; -- this.setInLove(player); -+ this.setInLove(player, item.copy()); // Paper - Fix EntityBreedEvent copying - } - } else if (item.is(Items.GOLDEN_APPLE) || item.is(Items.ENCHANTED_GOLDEN_APPLE)) { - f = 10.0F; -@@ -580,7 +580,7 @@ public abstract class AbstractHorse extends Animal implements ContainerListener, - b0 = 10; - if (!this.level().isClientSide && this.isTamed() && this.getAge() == 0 && !this.isInLove()) { - flag = true; -- this.setInLove(player); -+ this.setInLove(player, item.copy()); // Paper - Fix EntityBreedEvent copying - } - } - -diff --git a/src/main/java/net/minecraft/world/entity/animal/horse/Llama.java b/src/main/java/net/minecraft/world/entity/animal/horse/Llama.java -index 82c57124433cc033c99e609e8ad71e03d340bc5e..cf6a3a63b6f2b96943c0f399e8c82d293fee31ba 100644 ---- a/src/main/java/net/minecraft/world/entity/animal/horse/Llama.java -+++ b/src/main/java/net/minecraft/world/entity/animal/horse/Llama.java -@@ -179,7 +179,7 @@ public class Llama extends AbstractChestedHorse implements VariantHolder -Date: Sun, 12 Nov 2023 05:09:47 +0100 -Subject: [PATCH] Fix missing event call for entity teleport API - - -diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java -index 580427bf1521ac9fef37f7464e12a7bfe4fbfb10..9ca1fee03bfa557f1df7388c6043c9ec6d02a79a 100644 ---- a/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java -+++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java -@@ -258,6 +258,17 @@ public abstract class CraftEntity implements org.bukkit.entity.Entity { - return false; - } - -+ // Paper start - fix teleport event not being called -+ org.bukkit.event.entity.EntityTeleportEvent event = new org.bukkit.event.entity.EntityTeleportEvent( -+ this, this.getLocation(), location); -+ // cancelling the event is handled differently for players and entities, -+ // entities just stop teleporting, players will still teleport to the "from" location of the event -+ if (!event.callEvent() || event.getTo() == null) { -+ return false; -+ } -+ location = event.getTo(); -+ // Paper end -+ - // If this entity is riding another entity, we must dismount before teleporting. - if (dismount) this.entity.stopRiding(); // Paper - Teleport passenger API - diff --git a/patches/server/0905-Improve-Registry.patch b/patches/server/0905-Improve-Registry.patch new file mode 100644 index 000000000000..d5769f63c038 --- /dev/null +++ b/patches/server/0905-Improve-Registry.patch @@ -0,0 +1,102 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Jake Potrebic +Date: Wed, 20 Dec 2023 02:03:05 -0800 +Subject: [PATCH] Improve Registry + + +diff --git a/src/main/java/org/bukkit/craftbukkit/CraftRegistry.java b/src/main/java/org/bukkit/craftbukkit/CraftRegistry.java +index 09929f580164abcd1c04061d04c6aa992767e256..aa66fd8dca886c1f064d8cb4a3d15c2086c1719a 100644 +--- a/src/main/java/org/bukkit/craftbukkit/CraftRegistry.java ++++ b/src/main/java/org/bukkit/craftbukkit/CraftRegistry.java +@@ -155,6 +155,7 @@ public class CraftRegistry implements Registry { + + private final Class bukkitClass; // Paper - relax preload class + private final Map cache = new HashMap<>(); ++ private final Map byValue = new java.util.IdentityHashMap<>(); // Paper - improve Registry + private final net.minecraft.core.Registry minecraftRegistry; + private final BiFunction minecraftToBukkit; + private final BiFunction serializationUpdater; // Paper - rename to make it *clear* what it is *only* for +@@ -203,6 +204,7 @@ public class CraftRegistry implements Registry { + } + + this.cache.put(namespacedKey, bukkit); ++ this.byValue.put(bukkit, namespacedKey); // Paper - improve Registry + + return bukkit; + } +@@ -235,4 +237,11 @@ public class CraftRegistry implements Registry { + + return this.minecraftToBukkit.apply(namespacedKey, minecraft); + } ++ ++ // Paper start - improve Registry ++ @Override ++ public NamespacedKey getKey(final B value) { ++ return this.byValue.get(value); ++ } ++ // Paper end - improve Registry + } +diff --git a/src/main/java/org/bukkit/craftbukkit/inventory/trim/CraftTrimMaterial.java b/src/main/java/org/bukkit/craftbukkit/inventory/trim/CraftTrimMaterial.java +index caf7e4312e95e90dd0822355c8832006e69a2700..38578ef887227ecc8e8bba281eae17a849b4fbe6 100644 +--- a/src/main/java/org/bukkit/craftbukkit/inventory/trim/CraftTrimMaterial.java ++++ b/src/main/java/org/bukkit/craftbukkit/inventory/trim/CraftTrimMaterial.java +@@ -54,6 +54,7 @@ public class CraftTrimMaterial implements TrimMaterial, Handleable this + " doesn't have a key"); // Paper + return this.key; + } + +diff --git a/src/main/java/org/bukkit/craftbukkit/inventory/trim/CraftTrimPattern.java b/src/main/java/org/bukkit/craftbukkit/inventory/trim/CraftTrimPattern.java +index f91577c9239c8d5ed4b72b23dde9c053b4beae0b..36df80a8be8a2485823f699e45c99674dbe71507 100644 +--- a/src/main/java/org/bukkit/craftbukkit/inventory/trim/CraftTrimPattern.java ++++ b/src/main/java/org/bukkit/craftbukkit/inventory/trim/CraftTrimPattern.java +@@ -54,6 +54,7 @@ public class CraftTrimPattern implements TrimPattern, Handleable this + " doesn't have a key"); // Paper + return this.key; + } + +diff --git a/src/test/java/org/bukkit/registry/PerRegistryTest.java b/src/test/java/org/bukkit/registry/PerRegistryTest.java +index 18859eea522ff26cbefb5bbc5065b5369ed6c189..319e000519fd719cea0e6daf2ba9cfa67e6958a3 100644 +--- a/src/test/java/org/bukkit/registry/PerRegistryTest.java ++++ b/src/test/java/org/bukkit/registry/PerRegistryTest.java +@@ -49,19 +49,22 @@ public class PerRegistryTest { + + @ParameterizedTest + @MethodSource("data") +- public void testGet(Registry registry) { ++ public void testGet(Registry registry) { // Paper - improve Registry + registry.forEach(element -> { ++ NamespacedKey key = registry.getKey(element); // Paper - improve Registry ++ assertNotNull(key); // Paper - improve Registry + // Values in the registry should be referentially equal to what is returned with #get() + // This ensures that new instances are not created each time #get() is invoked +- assertSame(element, registry.get(element.getKey())); ++ assertSame(element, registry.get(key)); // Paper - improve Registry + }); + } + + @ParameterizedTest + @MethodSource("data") +- public void testMatch(Registry registry) { ++ public void testMatch(Registry registry) { // Paper - improve Registry + registry.forEach(element -> { +- NamespacedKey key = element.getKey(); ++ NamespacedKey key = registry.getKey(element); // Paper - improve Registry ++ assertNotNull(key); // Paper - improve Registry + + this.assertSameMatchWithKeyMessage(registry, element, key.toString()); // namespace:key + this.assertSameMatchWithKeyMessage(registry, element, key.getKey()); // key +@@ -72,7 +75,7 @@ public class PerRegistryTest { + }); + } + +- private void assertSameMatchWithKeyMessage(Registry registry, Keyed element, String key) { ++ private void assertSameMatchWithKeyMessage(Registry registry, T element, String key) { // Paper - improve Registry + assertSame(element, registry.match(key), key); + } + diff --git a/patches/server/0906-Fix-NPE-on-null-loc-for-EntityTeleportEvent.patch b/patches/server/0906-Fix-NPE-on-null-loc-for-EntityTeleportEvent.patch new file mode 100644 index 000000000000..dc1f06ed5606 --- /dev/null +++ b/patches/server/0906-Fix-NPE-on-null-loc-for-EntityTeleportEvent.patch @@ -0,0 +1,66 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Jake Potrebic +Date: Sat, 9 Dec 2023 19:15:59 -0800 +Subject: [PATCH] Fix NPE on null loc for EntityTeleportEvent + +EntityTeleportEvent#setTo is marked as nullable and so is the +getTo method. This fixes the handling of a null "to" location +by treating it the same as the event being cancelled. This is +already existing behavior for the EntityPortalEvent (which +extends EntityTeleportEvent). + +diff --git a/src/main/java/net/minecraft/server/commands/TeleportCommand.java b/src/main/java/net/minecraft/server/commands/TeleportCommand.java +index c6dcc37ac5fcf50bcb246f533b99983dfc5c19c2..c13b6f14c3061710c2b27034db240cc94ec0fcb5 100644 +--- a/src/main/java/net/minecraft/server/commands/TeleportCommand.java ++++ b/src/main/java/net/minecraft/server/commands/TeleportCommand.java +@@ -181,9 +181,10 @@ public class TeleportCommand { + Location to = new Location(world.getWorld(), d3, d4, d5, f4, f5); + EntityTeleportEvent event = new EntityTeleportEvent(target.getBukkitEntity(), target.getBukkitEntity().getLocation(), to); + world.getCraftServer().getPluginManager().callEvent(event); +- if (event.isCancelled()) { ++ if (event.isCancelled() || event.getTo() == null) { // Paper + return; + } ++ to = event.getTo(); // Paper - actually track new location + + d3 = to.getX(); + d4 = to.getY(); +diff --git a/src/main/java/net/minecraft/world/entity/LivingEntity.java b/src/main/java/net/minecraft/world/entity/LivingEntity.java +index adde7352cdbcb8684f43d6bf5978b6943e9f165b..9d3d6f012cfca60884019ed9710804aa37b11fbf 100644 +--- a/src/main/java/net/minecraft/world/entity/LivingEntity.java ++++ b/src/main/java/net/minecraft/world/entity/LivingEntity.java +@@ -4363,7 +4363,7 @@ public abstract class LivingEntity extends Entity implements Attackable { + if (!(this instanceof ServerPlayer)) { + EntityTeleportEvent teleport = new EntityTeleportEvent(this.getBukkitEntity(), new Location(this.level().getWorld(), d3, d4, d5), new Location(this.level().getWorld(), d0, d6, d2)); + this.level().getCraftServer().getPluginManager().callEvent(teleport); +- if (!teleport.isCancelled()) { ++ if (!teleport.isCancelled() && teleport.getTo() != null) { // Paper + Location to = teleport.getTo(); + this.teleportTo(to.getX(), to.getY(), to.getZ()); + } else { +diff --git a/src/main/java/net/minecraft/world/entity/TamableAnimal.java b/src/main/java/net/minecraft/world/entity/TamableAnimal.java +index f6a253e063f4a2cf78a036e44431806a0ba270d9..332ae836826270507110f1e0438aaa36d6e9deb5 100644 +--- a/src/main/java/net/minecraft/world/entity/TamableAnimal.java ++++ b/src/main/java/net/minecraft/world/entity/TamableAnimal.java +@@ -314,7 +314,7 @@ public abstract class TamableAnimal extends Animal implements OwnableEntity { + } else { + // CraftBukkit start + EntityTeleportEvent event = CraftEventFactory.callEntityTeleportEvent(this, (double) x + 0.5D, (double) y, (double) z + 0.5D); +- if (event.isCancelled()) { ++ if (event.isCancelled() || event.getTo() == null) { // Paper - prevent NP on null event to location + return false; + } + Location to = event.getTo(); +diff --git a/src/main/java/net/minecraft/world/entity/monster/Shulker.java b/src/main/java/net/minecraft/world/entity/monster/Shulker.java +index dc1870baf172982ebb34eccd4ee79497f48f8050..0fffa9dbcbbb15a2138f9a4e4d8e812c8047a7bb 100644 +--- a/src/main/java/net/minecraft/world/entity/monster/Shulker.java ++++ b/src/main/java/net/minecraft/world/entity/monster/Shulker.java +@@ -416,7 +416,7 @@ public class Shulker extends AbstractGolem implements VariantHolder +Date: Tue, 5 Sep 2023 20:34:20 +0200 +Subject: [PATCH] Add experience points API + + +diff --git a/src/main/java/net/minecraft/world/entity/player/Player.java b/src/main/java/net/minecraft/world/entity/player/Player.java +index aca888c2f02b09ac6739bdc81b194c4527dd69f5..a19a795deaa7f46c92b97912e2ade006bc90c2d5 100644 +--- a/src/main/java/net/minecraft/world/entity/player/Player.java ++++ b/src/main/java/net/minecraft/world/entity/player/Player.java +@@ -1823,7 +1823,7 @@ public abstract class Player extends LivingEntity { + } + + public int getXpNeededForNextLevel() { +- return this.experienceLevel >= 30 ? 112 + (this.experienceLevel - 30) * 9 : (this.experienceLevel >= 15 ? 37 + (this.experienceLevel - 15) * 5 : 7 + this.experienceLevel * 2); ++ return this.experienceLevel >= 30 ? 112 + (this.experienceLevel - 30) * 9 : (this.experienceLevel >= 15 ? 37 + (this.experienceLevel - 15) * 5 : 7 + this.experienceLevel * 2); // Paper - diff on change; calculateTotalExperiencePoints + } + // Paper start - send while respecting visibility + private static void sendSoundEffect(Player fromEntity, double x, double y, double z, SoundEvent soundEffect, SoundSource soundCategory, float volume, float pitch) { +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java +index 90df4f4a70b416b2d750f3d2063a68848bd38085..933390c4244200dc1be5311a174e5df95f6ac633 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java +@@ -1945,6 +1945,49 @@ public class CraftPlayer extends CraftHumanEntity implements Player { + Preconditions.checkArgument(exp >= 0, "Total experience points must not be negative (%s)", exp); + this.getHandle().totalExperience = exp; + } ++ // Paper start ++ @Override ++ public int calculateTotalExperiencePoints() { ++ return calculateTotalExperiencePoints(this.getLevel()) + Math.round(this.getExperiencePointsNeededForNextLevel() * getExp()); ++ } ++ ++ @Override ++ public void setExperienceLevelAndProgress(final int totalExperience) { ++ Preconditions.checkArgument(totalExperience >= 0, "Total experience points must not be negative (%s)", totalExperience); ++ int level = calculateLevelsForExperiencePoints(totalExperience); ++ int remainingPoints = totalExperience - calculateTotalExperiencePoints(level); ++ ++ this.getHandle().experienceLevel = level; ++ this.getHandle().experienceProgress = (float) remainingPoints / this.getExperiencePointsNeededForNextLevel(); ++ this.getHandle().lastSentExp = -1; ++ } ++ ++ @Override ++ public int getExperiencePointsNeededForNextLevel() { ++ return this.getHandle().getXpNeededForNextLevel(); ++ } ++ ++ // See https://minecraft.wiki/w/Experience#Leveling_up for reference ++ private int calculateTotalExperiencePoints(int level) { ++ if (level <= 16) { ++ return (int) (Math.pow(level, 2) + 6 * level); ++ } else if (level <= 31) { ++ return (int) (2.5 * Math.pow(level, 2) - 40.5 * level + 360.0); ++ } else { ++ return (int) (4.5 * Math.pow(level, 2) - 162.5 * level + 2220.0); ++ } ++ } ++ ++ private int calculateLevelsForExperiencePoints(int points) { ++ if (points <= 352) { // Level 0-16 ++ return (int) Math.floor(Math.sqrt(points + 9) - 3); ++ } else if (points <= 1507) { // Level 17-31 ++ return (int) Math.floor(8.1 + Math.sqrt(0.4 * (points - (7839.0 / 40.0)))); ++ } else { // 32+ ++ return (int) Math.floor((325.0 / 18.0) + Math.sqrt((2.0 / 9.0) * (points - (54215.0 / 72.0)))); ++ } ++ } ++ // Paper end + + @Override + public void sendExperienceChange(float progress) { diff --git a/patches/server/0907-Don-t-fire-sync-events-during-worldgen.patch b/patches/server/0907-Don-t-fire-sync-events-during-worldgen.patch deleted file mode 100644 index 6d438fe4c585..000000000000 --- a/patches/server/0907-Don-t-fire-sync-events-during-worldgen.patch +++ /dev/null @@ -1,208 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Jake Potrebic -Date: Thu, 23 Nov 2023 10:33:25 -0800 -Subject: [PATCH] Don't fire sync events during worldgen - -Fixes EntityPotionEffectEvent -Fixes EntityPoseChangeEvent - -Asynchronous chunk generation provides an opportunity for things -to happen async that previously fired synchronous-only events. This -patch is for mitigating those issues by various methods. - -Also fixes correctly marking/clearing the entity generation flag. -This patch sets the generation flag anytime an entity is created -via StructureTemplate before loading from NBT to catch uses of -the flag during the loading logic. This patch clears the generation -flag from an entity when added to a ServerLevel for the situation -where generation happened directly to a ServerLevel and the -entity still has the flag set. - -diff --git a/src/main/java/net/minecraft/server/level/ServerLevel.java b/src/main/java/net/minecraft/server/level/ServerLevel.java -index 60c75f7c4258efb26c2595ca7724b08d3c046c9d..34ece37aee68e4d8eeaa919e8bd489a672756049 100644 ---- a/src/main/java/net/minecraft/server/level/ServerLevel.java -+++ b/src/main/java/net/minecraft/server/level/ServerLevel.java -@@ -1224,6 +1224,7 @@ public class ServerLevel extends Level implements WorldGenLevel { - // CraftBukkit start - private boolean addEntity(Entity entity, CreatureSpawnEvent.SpawnReason spawnReason) { - org.spigotmc.AsyncCatcher.catchOp("entity add"); // Spigot -+ entity.generation = false; // Paper - Don't fire sync event during generation; Reset flag if it was added during a ServerLevel generation process - // Paper start - extra debug info - if (entity.valid) { - MinecraftServer.LOGGER.error("Attempted Double World add on {}", entity, new Throwable()); -diff --git a/src/main/java/net/minecraft/world/entity/Entity.java b/src/main/java/net/minecraft/world/entity/Entity.java -index bd0e205c760414784ae483452fd02292939dcb5c..f163cb3c75e90b96b3794a7e0c5b803268814c8c 100644 ---- a/src/main/java/net/minecraft/world/entity/Entity.java -+++ b/src/main/java/net/minecraft/world/entity/Entity.java -@@ -624,7 +624,11 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess - if (pose == this.getPose()) { - return; - } -- this.level.getCraftServer().getPluginManager().callEvent(new EntityPoseChangeEvent(this.getBukkitEntity(), Pose.values()[pose.ordinal()])); -+ // Paper start - Don't fire sync event during generation -+ if (!this.generation) { -+ this.level.getCraftServer().getPluginManager().callEvent(new EntityPoseChangeEvent(this.getBukkitEntity(), Pose.values()[pose.ordinal()])); -+ } -+ // Paper end - Don't fire sync event during generation - // CraftBukkit end - this.entityData.set(Entity.DATA_POSE, pose); - } -diff --git a/src/main/java/net/minecraft/world/entity/EntityType.java b/src/main/java/net/minecraft/world/entity/EntityType.java -index 5fb3279342506611882b5780cfbee0371919c93c..b98f9246b60daf31460f41ce214dfa7c011f5684 100644 ---- a/src/main/java/net/minecraft/world/entity/EntityType.java -+++ b/src/main/java/net/minecraft/world/entity/EntityType.java -@@ -592,9 +592,15 @@ public class EntityType implements FeatureElement, EntityTypeT - } - - public static Optional create(CompoundTag nbt, Level world) { -+ // Paper start - Don't fire sync event during generation -+ return create(nbt, world, false); -+ } -+ public static Optional create(CompoundTag nbt, Level world, boolean generation) { -+ // Paper end - Don't fire sync event during generation - return Util.ifElse(EntityType.by(nbt).map((entitytypes) -> { - return entitytypes.create(world); - }), (entity) -> { -+ if (generation) entity.generation = true; // Paper - Don't fire sync event during generation - entity.load(nbt); - }, () -> { - EntityType.LOGGER.warn("Skipping Entity with id {}", nbt.getString("id")); -diff --git a/src/main/java/net/minecraft/world/entity/LivingEntity.java b/src/main/java/net/minecraft/world/entity/LivingEntity.java -index 04232f281ebd46954ef2259962c7bfc43b8a8aeb..0c67f04ae7eeaa10a90419031447ad5397c6b9e2 100644 ---- a/src/main/java/net/minecraft/world/entity/LivingEntity.java -+++ b/src/main/java/net/minecraft/world/entity/LivingEntity.java -@@ -1138,6 +1138,11 @@ public abstract class LivingEntity extends Entity implements Attackable { - } - - public boolean addEffect(MobEffectInstance mobeffect, @Nullable Entity entity, EntityPotionEffectEvent.Cause cause) { -+ // Paper start - Don't fire sync event during generation -+ return this.addEffect(mobeffect, entity, cause, true); -+ } -+ public boolean addEffect(MobEffectInstance mobeffect, @Nullable Entity entity, EntityPotionEffectEvent.Cause cause, boolean fireEvent) { -+ // Paper end - Don't fire sync event during generation - // org.spigotmc.AsyncCatcher.catchOp("effect add"); // Spigot // Paper - move to API - if (this.isTickingEffects) { - this.effectsToProcess.add(new ProcessableEffect(mobeffect, cause)); -@@ -1157,10 +1162,13 @@ public abstract class LivingEntity extends Entity implements Attackable { - override = new MobEffectInstance(mobeffect1).update(mobeffect); - } - -+ if (fireEvent) { // Paper - Don't fire sync event during generation - EntityPotionEffectEvent event = CraftEventFactory.callEntityPotionEffectChangeEvent(this, mobeffect1, mobeffect, cause, override); -+ override = event.isOverride(); // Paper - Don't fire sync event during generation - if (event.isCancelled()) { - return false; - } -+ } // Paper - Don't fire sync event during generation - // CraftBukkit end - - if (mobeffect1 == null) { -@@ -1169,7 +1177,7 @@ public abstract class LivingEntity extends Entity implements Attackable { - flag = true; - mobeffect.onEffectAdded(this); - // CraftBukkit start -- } else if (event.isOverride()) { -+ } else if (override) { // Paper - Don't fire sync event during generation - mobeffect1.update(mobeffect); - this.onEffectUpdated(mobeffect1, true, entity); - // CraftBukkit end -diff --git a/src/main/java/net/minecraft/world/entity/monster/Spider.java b/src/main/java/net/minecraft/world/entity/monster/Spider.java -index f91ea9ac5a0d0d3bae5d1eb0c409f4f9c4e5a62b..9045f18f49a5c6685597d0a77126d7cb35bc5e88 100644 ---- a/src/main/java/net/minecraft/world/entity/monster/Spider.java -+++ b/src/main/java/net/minecraft/world/entity/monster/Spider.java -@@ -172,7 +172,7 @@ public class Spider extends Monster { - Holder holder = entityspider_groupdataspider.effect; - - if (holder != null) { -- this.addEffect(new MobEffectInstance(holder, -1), org.bukkit.event.entity.EntityPotionEffectEvent.Cause.SPIDER_SPAWN); // CraftBukkit -+ this.addEffect(new MobEffectInstance(holder, -1), null, org.bukkit.event.entity.EntityPotionEffectEvent.Cause.SPIDER_SPAWN, world instanceof net.minecraft.server.level.ServerLevel); // CraftBukkit - } - } - -diff --git a/src/main/java/net/minecraft/world/level/levelgen/structure/templatesystem/StructureTemplate.java b/src/main/java/net/minecraft/world/level/levelgen/structure/templatesystem/StructureTemplate.java -index cf8258e8d46ca7286a66c38fa24af369bd9a279f..0555abfda468c343af8244a122ebe769e70a0292 100644 ---- a/src/main/java/net/minecraft/world/level/levelgen/structure/templatesystem/StructureTemplate.java -+++ b/src/main/java/net/minecraft/world/level/levelgen/structure/templatesystem/StructureTemplate.java -@@ -525,7 +525,7 @@ public class StructureTemplate { - private static Optional createEntityIgnoreException(ServerLevelAccessor world, CompoundTag nbt) { - // CraftBukkit start - // try { -- return EntityType.create(nbt, world.getLevel()); -+ return EntityType.create(nbt, world.getLevel(), true); // Paper - Don't fire sync event during generation - // } catch (Exception exception) { - // return Optional.empty(); - // } -diff --git a/src/main/java/org/bukkit/craftbukkit/util/DelegatedGeneratorAccess.java b/src/main/java/org/bukkit/craftbukkit/util/DelegatedGeneratorAccess.java -index 524b51a0ab808a0629c871ad813115abd4b49dbd..fceed3d08ee6f4c171685986bb19d2be592eedc6 100644 ---- a/src/main/java/org/bukkit/craftbukkit/util/DelegatedGeneratorAccess.java -+++ b/src/main/java/org/bukkit/craftbukkit/util/DelegatedGeneratorAccess.java -@@ -92,15 +92,17 @@ public abstract class DelegatedGeneratorAccess implements WorldGenLevel { - return this.handle.getLevel(); - } - -- @Override -- public void addFreshEntityWithPassengers(Entity arg0, CreatureSpawnEvent.SpawnReason arg1) { -- this.handle.addFreshEntityWithPassengers(arg0, arg1); -- } -- -- @Override -- public void addFreshEntityWithPassengers(Entity entity) { -- this.handle.addFreshEntityWithPassengers(entity); -- } -+ // Paper start - Don't fire sync event during generation; don't override these methods so all entities are run through addFreshEntity -+ // @Override -+ // public void addFreshEntityWithPassengers(Entity arg0, CreatureSpawnEvent.SpawnReason arg1) { -+ // this.handle.addFreshEntityWithPassengers(arg0, arg1); -+ // } -+ // -+ // @Override -+ // public void addFreshEntityWithPassengers(Entity entity) { -+ // this.handle.addFreshEntityWithPassengers(entity); -+ // } -+ // Paper end - Don't fire sync event during generation; don't override these methods - - @Override - public ServerLevel getMinecraftWorld() { -diff --git a/src/main/java/org/bukkit/craftbukkit/util/TransformerGeneratorAccess.java b/src/main/java/org/bukkit/craftbukkit/util/TransformerGeneratorAccess.java -index 35ecf6f824aca56a20280dd683123df1d0c7d66e..1d1fdcf10498c421f106158254e052da6d68d8a5 100644 ---- a/src/main/java/org/bukkit/craftbukkit/util/TransformerGeneratorAccess.java -+++ b/src/main/java/org/bukkit/craftbukkit/util/TransformerGeneratorAccess.java -@@ -39,21 +39,23 @@ public class TransformerGeneratorAccess extends DelegatedGeneratorAccess { - return super.addFreshEntity(arg0, arg1); - } - -- @Override -- public void addFreshEntityWithPassengers(Entity entity) { -- if (this.structureTransformer != null && !this.structureTransformer.transformEntity(entity)) { -- return; -- } -- super.addFreshEntityWithPassengers(entity); -- } -- -- @Override -- public void addFreshEntityWithPassengers(Entity arg0, SpawnReason arg1) { -- if (this.structureTransformer != null && !this.structureTransformer.transformEntity(arg0)) { -- return; -- } -- super.addFreshEntityWithPassengers(arg0, arg1); -- } -+ // Paper start - Don't fire sync event during generation; don't override these methods so all entities are run through addFreshEntity -+ // @Override -+ // public void addFreshEntityWithPassengers(Entity entity) { -+ // if (this.structureTransformer != null && !this.structureTransformer.transformEntity(entity)) { -+ // return; -+ // } -+ // super.addFreshEntityWithPassengers(entity); -+ // } -+ // -+ // @Override -+ // public void addFreshEntityWithPassengers(Entity arg0, SpawnReason arg1) { -+ // if (this.structureTransformer != null && !this.structureTransformer.transformEntity(arg0)) { -+ // return; -+ // } -+ // super.addFreshEntityWithPassengers(arg0, arg1); -+ // } -+ // Paper end - Don't fire sync event during generation; don't override these methods - - public boolean setCraftBlock(BlockPos position, CraftBlockState craftBlockState, int i, int j) { - if (this.structureTransformer != null) { diff --git a/patches/server/0908-Add-Structure-check-API.patch b/patches/server/0908-Add-Structure-check-API.patch deleted file mode 100644 index 6f901b1c7f39..000000000000 --- a/patches/server/0908-Add-Structure-check-API.patch +++ /dev/null @@ -1,26 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Jake Potrebic -Date: Mon, 27 Mar 2023 10:20:00 -0700 -Subject: [PATCH] Add Structure check API - - -diff --git a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java -index 7200c336de27ffd4d37231768ff5192956ede972..cb85180daaa2b40cba2474784548d650bc4d639b 100644 ---- a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java -+++ b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java -@@ -246,6 +246,15 @@ public class CraftWorld extends CraftRegionAccessor implements World { - }; - } - // Paper end -+ // Paper start - structure check API -+ @Override -+ public boolean hasStructureAt(final io.papermc.paper.math.Position position, final Structure structure) { -+ return this.world.structureManager().getStructureWithPieceAt( -+ io.papermc.paper.util.MCUtil.toBlockPos(position), -+ CraftStructure.bukkitToMinecraft(structure) -+ ).isValid(); -+ } -+ // Paper end - - private static final Random rand = new Random(); - diff --git a/patches/server/0908-Add-drops-to-shear-events.patch b/patches/server/0908-Add-drops-to-shear-events.patch new file mode 100644 index 000000000000..0ae1794dca13 --- /dev/null +++ b/patches/server/0908-Add-drops-to-shear-events.patch @@ -0,0 +1,400 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Jake Potrebic +Date: Tue, 18 May 2021 12:32:02 -0700 +Subject: [PATCH] Add drops to shear events + + +diff --git a/src/main/java/net/minecraft/core/dispenser/ShearsDispenseItemBehavior.java b/src/main/java/net/minecraft/core/dispenser/ShearsDispenseItemBehavior.java +index 9b5a1dc958232e4c2c9631f3504edc6383afd92a..f5206e4176f58cff4cfe70c94f014afebc98c589 100644 +--- a/src/main/java/net/minecraft/core/dispenser/ShearsDispenseItemBehavior.java ++++ b/src/main/java/net/minecraft/core/dispenser/ShearsDispenseItemBehavior.java +@@ -103,11 +103,14 @@ public class ShearsDispenseItemBehavior extends OptionalDispenseItemBehavior { + if (entityliving instanceof Shearable ishearable) { + if (ishearable.readyForShearing()) { + // CraftBukkit start +- if (CraftEventFactory.callBlockShearEntityEvent(entityliving, bukkitBlock, craftItem).isCancelled()) { ++ // Paper start - Add drops to shear events ++ org.bukkit.event.block.BlockShearEntityEvent event = CraftEventFactory.callBlockShearEntityEvent(entityliving, bukkitBlock, craftItem, ishearable.generateDefaultDrops(worldserver, itemstack)); ++ if (event.isCancelled()) { ++ // Paper end - Add drops to shear events + continue; + } + // CraftBukkit end +- ishearable.shear(worldserver, SoundSource.BLOCKS, itemstack); ++ ishearable.shear(worldserver, SoundSource.BLOCKS, itemstack, CraftItemStack.asNMSCopy(event.getDrops())); // Paper - Add drops to shear events + worldserver.gameEvent((Entity) null, (Holder) GameEvent.SHEAR, blockposition); + return true; + } +diff --git a/src/main/java/net/minecraft/world/entity/Shearable.java b/src/main/java/net/minecraft/world/entity/Shearable.java +index 35076593f3ccd651295ae1fc9bcf8256c19672dd..8fda407c9fbfdde623564a7d9607275c4894b744 100644 +--- a/src/main/java/net/minecraft/world/entity/Shearable.java ++++ b/src/main/java/net/minecraft/world/entity/Shearable.java +@@ -5,8 +5,15 @@ import net.minecraft.sounds.SoundSource; + import net.minecraft.world.item.ItemStack; + + public interface Shearable { ++ default void shear(ServerLevel world, SoundSource soundCategory, ItemStack shears, java.util.List drops) { this.shear(world, soundCategory, shears); } // Paper - Add drops to shear events + void shear(ServerLevel world, SoundSource shearedSoundCategory, ItemStack shears); + + boolean readyForShearing(); + net.minecraft.world.level.Level level(); // Shearable API - expose default level needed for shearing. ++ ++ // Paper start - custom shear drops; ensure all implementing entities override this ++ default java.util.List generateDefaultDrops(final ServerLevel serverLevel, final ItemStack shears) { ++ return java.util.Collections.emptyList(); ++ } ++ // Paper end - custom shear drops + } +diff --git a/src/main/java/net/minecraft/world/entity/animal/MushroomCow.java b/src/main/java/net/minecraft/world/entity/animal/MushroomCow.java +index d4d343e2d75a3e3ea787c3c68c64970f5b239f81..feeb7bc34ae02e44d7f13f0bae5d175ef924c53a 100644 +--- a/src/main/java/net/minecraft/world/entity/animal/MushroomCow.java ++++ b/src/main/java/net/minecraft/world/entity/animal/MushroomCow.java +@@ -46,6 +46,7 @@ import net.minecraft.world.level.storage.loot.BuiltInLootTables; + // CraftBukkit start + import org.bukkit.Bukkit; + import org.bukkit.craftbukkit.event.CraftEventFactory; ++import org.bukkit.craftbukkit.inventory.CraftItemStack; + import org.bukkit.event.entity.EntityDropItemEvent; + import org.bukkit.event.entity.EntityTransformEvent; + // CraftBukkit end +@@ -128,11 +129,18 @@ public class MushroomCow extends Cow implements Shearable, VariantHolder drops = this.generateDefaultDrops(worldserver, itemstack); ++ org.bukkit.event.player.PlayerShearEntityEvent event = CraftEventFactory.handlePlayerShearEntityEvent(player, this, itemstack, hand, drops); ++ if (event != null) { ++ if (event.isCancelled()) { ++ return InteractionResult.PASS; ++ } ++ drops = org.bukkit.craftbukkit.inventory.CraftItemStack.asNMSCopy(event.getDrops()); ++ // Paper end - custom shear drops + } + // CraftBukkit end +- this.shear(worldserver, SoundSource.PLAYERS, itemstack); ++ this.shear(worldserver, SoundSource.PLAYERS, itemstack, drops); // Paper - custom shear drops + this.gameEvent(GameEvent.SHEAR, player); + itemstack.hurtAndBreak(1, player, getSlotForHand(hand)); + } +@@ -168,22 +176,32 @@ public class MushroomCow extends Cow implements Shearable, VariantHolder generateDefaultDrops(final ServerLevel serverLevel, final ItemStack shears) { ++ final java.util.List drops = new it.unimi.dsi.fastutil.objects.ObjectArrayList<>(); ++ this.dropFromShearingLootTable(serverLevel, BuiltInLootTables.SHEAR_MOOSHROOM, shears, (ignored, stack) -> { ++ for (int i = 0; i < stack.getCount(); ++i) drops.add(stack.copyWithCount(1)); ++ }); ++ return drops; ++ } ++ ++ @Override ++ public void shear(ServerLevel world, SoundSource shearedSoundCategory, ItemStack shears, java.util.List drops) { ++ // Paper end - custom shear drops + world.playSound((Player) null, (Entity) this, SoundEvents.MOOSHROOM_SHEAR, shearedSoundCategory, 1.0F, 1.0F); + this.convertTo(EntityType.COW, ConversionParams.single(this, false, false), (entitycow) -> { + world.sendParticles(ParticleTypes.EXPLOSION, this.getX(), this.getY(0.5D), this.getZ(), 1, 0.0D, 0.0D, 0.0D, 0.0D); +- this.dropFromShearingLootTable(world, BuiltInLootTables.SHEAR_MOOSHROOM, shears, (worldserver1, itemstack1) -> { +- for (int i = 0; i < itemstack1.getCount(); ++i) { +- // CraftBukkit start +- ItemEntity entityitem = new ItemEntity(this.level(), this.getX(), this.getY(1.0D), this.getZ(), itemstack1.copyWithCount(1)); +- EntityDropItemEvent event = new EntityDropItemEvent(this.getBukkitEntity(), (org.bukkit.entity.Item) entityitem.getBukkitEntity()); +- Bukkit.getPluginManager().callEvent(event); +- if (event.isCancelled()) { +- continue; +- } +- worldserver1.addFreshEntity(entityitem); +- // CraftBukkit end ++ // Paper start - custom shear drops; moved drop generation to separate method ++ drops.forEach(itemstack1 -> { ++ for (final ItemStack drop : drops) { ++ ItemEntity entityitem = new ItemEntity(this.level(), this.getX(), this.getY(1.0D), this.getZ(), drop); ++ this.spawnAtLocation(world, entityitem); + } +- ++ // Paper end - custom shear drops; moved drop generation to separate method + }); + }, EntityTransformEvent.TransformReason.SHEARED, org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason.SHEARED); // CraftBukkit + } +diff --git a/src/main/java/net/minecraft/world/entity/animal/Sheep.java b/src/main/java/net/minecraft/world/entity/animal/Sheep.java +index 1bc638ab505850bffcbd08025de9664dd27e47c6..432ad1c785e133ef18390108fd342be50ec4dddc 100644 +--- a/src/main/java/net/minecraft/world/entity/animal/Sheep.java ++++ b/src/main/java/net/minecraft/world/entity/animal/Sheep.java +@@ -173,11 +173,18 @@ public class Sheep extends Animal implements Shearable { + + if (this.readyForShearing()) { + // CraftBukkit start +- if (!CraftEventFactory.handlePlayerShearEntityEvent(player, this, itemstack, hand)) { +- return InteractionResult.PASS; ++ // Paper start - custom shear drops ++ java.util.List drops = this.generateDefaultDrops(worldserver, itemstack); ++ org.bukkit.event.player.PlayerShearEntityEvent event = CraftEventFactory.handlePlayerShearEntityEvent(player, this, itemstack, hand, drops); ++ if (event != null) { ++ if (event.isCancelled()) { ++ return InteractionResult.PASS; ++ } ++ drops = org.bukkit.craftbukkit.inventory.CraftItemStack.asNMSCopy(event.getDrops()); ++ // Paper end - custom shear drops + } + // CraftBukkit end +- this.shear(worldserver, SoundSource.PLAYERS, itemstack); ++ this.shear(worldserver, SoundSource.PLAYERS, itemstack, drops); // Paper - custom shear drops + this.gameEvent(GameEvent.SHEAR, player); + itemstack.hurtAndBreak(1, player, getSlotForHand(hand)); + return InteractionResult.SUCCESS_SERVER; +@@ -192,9 +199,26 @@ public class Sheep extends Animal implements Shearable { + + @Override + public void shear(ServerLevel world, SoundSource shearedSoundCategory, ItemStack shears) { ++ // Paper start - custom shear drops ++ this.shear(world, shearedSoundCategory, shears, this.generateDefaultDrops(world, shears)); ++ } ++ ++ @Override ++ public java.util.List generateDefaultDrops(final ServerLevel serverLevel, final ItemStack shears) { ++ final java.util.List drops = new it.unimi.dsi.fastutil.objects.ObjectArrayList<>(); ++ this.dropFromShearingLootTable(serverLevel, BuiltInLootTables.SHEAR_SHEEP, shears, (ignored, stack) -> { ++ for (int i = 0; i < stack.getCount(); ++i) drops.add(stack.copyWithCount(1)); ++ }); ++ return drops; ++ } ++ ++ @Override ++ public void shear(ServerLevel world, SoundSource shearedSoundCategory, ItemStack shears, java.util.List drops) { ++ final ServerLevel worldserver1 = world; // Named for lambda consumption ++ // Paper end - custom shear drops + world.playSound((Player) null, (Entity) this, SoundEvents.SHEEP_SHEAR, shearedSoundCategory, 1.0F, 1.0F); +- this.dropFromShearingLootTable(world, BuiltInLootTables.SHEAR_SHEEP, shears, (worldserver1, itemstack1) -> { +- for (int i = 0; i < itemstack1.getCount(); ++i) { ++ drops.forEach(itemstack1 -> { // Paper - custom drops - loop in generated default drops ++ if (true) { // Paper - custom drops - loop in generated default drops + this.forceDrops = true; // CraftBukkit + ItemEntity entityitem = this.spawnAtLocation(worldserver1, itemstack1.copyWithCount(1), 1.0F); + this.forceDrops = false; // CraftBukkit +diff --git a/src/main/java/net/minecraft/world/entity/animal/SnowGolem.java b/src/main/java/net/minecraft/world/entity/animal/SnowGolem.java +index 975a9e35303bec29aedfbd554c38e4331cdfb174..fd9f6c17448a4d87f940eb8f544ecb9669068582 100644 +--- a/src/main/java/net/minecraft/world/entity/animal/SnowGolem.java ++++ b/src/main/java/net/minecraft/world/entity/animal/SnowGolem.java +@@ -161,11 +161,18 @@ public class SnowGolem extends AbstractGolem implements Shearable, RangedAttackM + ServerLevel worldserver = (ServerLevel) world; + + // CraftBukkit start +- if (!CraftEventFactory.handlePlayerShearEntityEvent(player, this, itemstack, hand)) { +- return InteractionResult.PASS; ++ // Paper start - custom shear drops ++ java.util.List drops = this.generateDefaultDrops(worldserver, itemstack); ++ org.bukkit.event.player.PlayerShearEntityEvent event = CraftEventFactory.handlePlayerShearEntityEvent(player, this, itemstack, hand, drops); ++ if (event != null) { ++ if (event.isCancelled()) { ++ return InteractionResult.PASS; ++ } ++ drops = org.bukkit.craftbukkit.inventory.CraftItemStack.asNMSCopy(event.getDrops()); ++ // Paper end - custom shear drops + } + // CraftBukkit end +- this.shear(worldserver, SoundSource.PLAYERS, itemstack); ++ this.shear(worldserver, SoundSource.PLAYERS, itemstack, drops); // Paper - custom shear drops + this.gameEvent(GameEvent.SHEAR, player); + itemstack.hurtAndBreak(1, player, getSlotForHand(hand)); + } +@@ -178,9 +185,26 @@ public class SnowGolem extends AbstractGolem implements Shearable, RangedAttackM + + @Override + public void shear(ServerLevel world, SoundSource shearedSoundCategory, ItemStack shears) { ++ // Paper start - custom shear drops ++ this.shear(world, shearedSoundCategory, shears, this.generateDefaultDrops(world, shears)); ++ } ++ ++ @Override ++ public java.util.List generateDefaultDrops(final ServerLevel serverLevel, final ItemStack shears) { ++ final java.util.List drops = new it.unimi.dsi.fastutil.objects.ObjectArrayList<>(); ++ this.dropFromShearingLootTable(serverLevel, BuiltInLootTables.SHEAR_SNOW_GOLEM, shears, (ignored, stack) -> { ++ drops.add(stack); ++ }); ++ return drops; ++ } ++ ++ @Override ++ public void shear(ServerLevel world, SoundSource shearedSoundCategory, ItemStack shears, java.util.List drops) { ++ final ServerLevel worldserver1 = world; // Named for lambda consumption ++ // Paper end - custom shear drops + world.playSound((Player) null, (Entity) this, SoundEvents.SNOW_GOLEM_SHEAR, shearedSoundCategory, 1.0F, 1.0F); + this.setPumpkin(false); +- this.dropFromShearingLootTable(world, BuiltInLootTables.SHEAR_SNOW_GOLEM, shears, (worldserver1, itemstack1) -> { ++ drops.forEach(itemstack1 -> { // Paper - custom shear drops + this.forceDrops = true; // CraftBukkit + this.spawnAtLocation(worldserver1, itemstack1, this.getEyeHeight()); + this.forceDrops = false; // CraftBukkit +diff --git a/src/main/java/net/minecraft/world/entity/monster/Bogged.java b/src/main/java/net/minecraft/world/entity/monster/Bogged.java +index 9d416f775fa19ad1978c7c9c9e0d5bc16728879d..18dae37d65552077aa3825c76f433bbd31152db9 100644 +--- a/src/main/java/net/minecraft/world/entity/monster/Bogged.java ++++ b/src/main/java/net/minecraft/world/entity/monster/Bogged.java +@@ -27,6 +27,7 @@ import net.minecraft.world.item.Items; + import net.minecraft.world.level.Level; + import net.minecraft.world.level.gameevent.GameEvent; + import net.minecraft.world.level.storage.loot.BuiltInLootTables; ++import org.bukkit.craftbukkit.event.CraftEventFactory; + + public class Bogged extends AbstractSkeleton implements Shearable { + +@@ -80,12 +81,19 @@ public class Bogged extends AbstractSkeleton implements Shearable { + ServerLevel worldserver = (ServerLevel) world; + + // CraftBukkit start +- if (!org.bukkit.craftbukkit.event.CraftEventFactory.handlePlayerShearEntityEvent(player, this, itemstack, hand)) { +- this.getEntityData().markDirty(Bogged.DATA_SHEARED); // CraftBukkit - mark dirty to restore sheared state to clients +- return InteractionResult.PASS; ++ // Paper start - custom shear drops ++ java.util.List drops = this.generateDefaultDrops(worldserver, itemstack); ++ org.bukkit.event.player.PlayerShearEntityEvent event = CraftEventFactory.handlePlayerShearEntityEvent(player, this, itemstack, hand, drops); ++ if (event != null) { ++ if (event.isCancelled()) { ++ this.getEntityData().markDirty(Bogged.DATA_SHEARED); // CraftBukkit - mark dirty to restore sheared state to clients ++ return InteractionResult.PASS; ++ } ++ drops = org.bukkit.craftbukkit.inventory.CraftItemStack.asNMSCopy(event.getDrops()); ++ // Paper end - custom shear drops + } + // CraftBukkit end +- this.shear(worldserver, SoundSource.PLAYERS, itemstack); ++ this.shear(worldserver, SoundSource.PLAYERS, itemstack, drops); // Paper - custom shear drops + this.gameEvent(GameEvent.SHEAR, player); + itemstack.hurtAndBreak(1, player, getSlotForHand(hand)); + } +@@ -139,13 +147,32 @@ public class Bogged extends AbstractSkeleton implements Shearable { + + @Override + public void shear(ServerLevel world, SoundSource shearedSoundCategory, ItemStack shears) { ++ // Paper start - custom shear drops ++ this.shear(world, shearedSoundCategory, shears, this.generateDefaultDrops(world, shears)); ++ } ++ ++ @Override ++ public java.util.List generateDefaultDrops(final ServerLevel serverLevel, final ItemStack shears) { ++ final java.util.List drops = new it.unimi.dsi.fastutil.objects.ObjectArrayList<>(); ++ this.dropFromShearingLootTable(serverLevel, BuiltInLootTables.BOGGED_SHEAR, shears, (ignored, stack) -> { ++ drops.add(stack); ++ }); ++ return drops; ++ } ++ ++ @Override ++ public void shear(ServerLevel world, SoundSource shearedSoundCategory, ItemStack shears, java.util.List drops) { ++ // Paper end - custom shear drops + world.playSound((Player) null, (Entity) this, SoundEvents.BOGGED_SHEAR, shearedSoundCategory, 1.0F, 1.0F); +- this.spawnShearedMushrooms(world, shears); ++ this.spawnShearedMushrooms(world, shears, drops); // Paper - custom shear drops + this.setSheared(true); + } + +- private void spawnShearedMushrooms(ServerLevel world, ItemStack shears) { +- this.dropFromShearingLootTable(world, BuiltInLootTables.BOGGED_SHEAR, shears, (worldserver1, itemstack1) -> { ++ // Paper start - custom shear drops ++ private void spawnShearedMushrooms(ServerLevel world, ItemStack shears, java.util.List drops) { ++ final ServerLevel worldserver1 = world; // Named for lambda consumption ++ drops.forEach(itemstack1 -> { ++ // Paper end - custom shear drops + this.spawnAtLocation(worldserver1, itemstack1, this.getBbHeight()); + }); + } +diff --git a/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java b/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java +index a0455f590d549343d6d8fd7991ba1b87a87acdb8..e7749a3ef6289d73379649f2f76f4e4fdfac7a8b 100644 +--- a/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java ++++ b/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java +@@ -1689,20 +1689,20 @@ public class CraftEventFactory { + player.level().getCraftServer().getPluginManager().callEvent(event); + } + +- public static BlockShearEntityEvent callBlockShearEntityEvent(Entity animal, org.bukkit.block.Block dispenser, CraftItemStack is) { +- BlockShearEntityEvent bse = new BlockShearEntityEvent(dispenser, animal.getBukkitEntity(), is); ++ public static BlockShearEntityEvent callBlockShearEntityEvent(Entity animal, org.bukkit.block.Block dispenser, CraftItemStack is, List drops) { // Paper - custom shear drops ++ BlockShearEntityEvent bse = new BlockShearEntityEvent(dispenser, animal.getBukkitEntity(), is, Lists.transform(drops, CraftItemStack::asCraftMirror)); // Paper - custom shear drops + Bukkit.getPluginManager().callEvent(bse); + return bse; + } + +- public static boolean handlePlayerShearEntityEvent(net.minecraft.world.entity.player.Player player, Entity sheared, ItemStack shears, InteractionHand hand) { ++ public static PlayerShearEntityEvent handlePlayerShearEntityEvent(net.minecraft.world.entity.player.Player player, Entity sheared, ItemStack shears, InteractionHand hand, List drops) { // Paper - custom shear drops + if (!(player instanceof ServerPlayer)) { +- return true; ++ return null; // Paper - custom shear drops + } + +- PlayerShearEntityEvent event = new PlayerShearEntityEvent((Player) player.getBukkitEntity(), sheared.getBukkitEntity(), CraftItemStack.asCraftMirror(shears), (hand == InteractionHand.OFF_HAND ? EquipmentSlot.OFF_HAND : EquipmentSlot.HAND)); ++ PlayerShearEntityEvent event = new PlayerShearEntityEvent((Player) player.getBukkitEntity(), sheared.getBukkitEntity(), CraftItemStack.asCraftMirror(shears), (hand == InteractionHand.OFF_HAND ? EquipmentSlot.OFF_HAND : EquipmentSlot.HAND), Lists.transform(drops, CraftItemStack::asCraftMirror)); // Paper - custom shear drops + Bukkit.getPluginManager().callEvent(event); +- return !event.isCancelled(); ++ return event; // Paper - custom shear drops + } + + public static Cancellable handleStatisticsIncrease(net.minecraft.world.entity.player.Player entityHuman, net.minecraft.stats.Stat statistic, int current, int newValue) { +diff --git a/src/main/java/org/bukkit/craftbukkit/inventory/CraftItemStack.java b/src/main/java/org/bukkit/craftbukkit/inventory/CraftItemStack.java +index 0b7bc5e83634a26ac6521694377b554c74c6bff0..ffd7ba14be38a117f5a7d7035a8d71a20fb1c4fc 100644 +--- a/src/main/java/org/bukkit/craftbukkit/inventory/CraftItemStack.java ++++ b/src/main/java/org/bukkit/craftbukkit/inventory/CraftItemStack.java +@@ -74,6 +74,16 @@ public final class CraftItemStack extends ItemStack { + return stack; + } + ++ // Paper start ++ public static java.util.List asNMSCopy(java.util.List originals) { ++ final java.util.List items = new java.util.ArrayList<>(originals.size()); ++ for (final ItemStack original : originals) { ++ items.add(asNMSCopy(original)); ++ } ++ return items; ++ } ++ // Paper end ++ + public static net.minecraft.world.item.ItemStack copyNMSStack(net.minecraft.world.item.ItemStack original, int amount) { + net.minecraft.world.item.ItemStack stack = original.copy(); + stack.setCount(amount); +diff --git a/src/test/java/io/papermc/paper/entity/ShearableDropsTest.java b/src/test/java/io/papermc/paper/entity/ShearableDropsTest.java +new file mode 100644 +index 0000000000000000000000000000000000000000..5e6dfc93c86ec369b686f15ca066478e1635dbc3 +--- /dev/null ++++ b/src/test/java/io/papermc/paper/entity/ShearableDropsTest.java +@@ -0,0 +1,35 @@ ++package io.papermc.paper.entity; ++ ++import io.github.classgraph.ClassGraph; ++import io.github.classgraph.ClassInfo; ++import io.github.classgraph.MethodInfoList; ++import io.github.classgraph.ScanResult; ++import java.util.ArrayList; ++import net.minecraft.world.entity.Shearable; ++import org.bukkit.support.environment.Normal; ++import org.junit.jupiter.params.ParameterizedTest; ++import org.junit.jupiter.params.provider.MethodSource; ++ ++import static org.junit.jupiter.api.Assertions.assertEquals; ++ ++@Normal ++class ShearableDropsTest { ++ ++ static Iterable parameters() { ++ try (ScanResult scanResult = new ClassGraph() ++ .enableClassInfo() ++ .enableMethodInfo() ++ .whitelistPackages("net.minecraft") ++ .scan() ++ ) { ++ return new ArrayList<>(scanResult.getClassesImplementing(Shearable.class.getName())); ++ } ++ } ++ ++ @ParameterizedTest ++ @MethodSource("parameters") ++ void checkShearableDropOverrides(final ClassInfo classInfo) { ++ final MethodInfoList generateDefaultDrops = classInfo.getDeclaredMethodInfo("generateDefaultDrops"); ++ assertEquals(1, generateDefaultDrops.size(), classInfo.getName() + " doesn't implement Shearable#generateDefaultDrops"); ++ } ++} diff --git a/patches/server/0909-Add-PlayerShieldDisableEvent.patch b/patches/server/0909-Add-PlayerShieldDisableEvent.patch new file mode 100644 index 000000000000..d2d860a0284e --- /dev/null +++ b/patches/server/0909-Add-PlayerShieldDisableEvent.patch @@ -0,0 +1,53 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Cryptite +Date: Mon, 1 May 2023 16:22:43 -0500 +Subject: [PATCH] Add PlayerShieldDisableEvent + +Called whenever a players shield is disabled. This is mainly caused by +attacking players or monsters that carry axes. + +The event, while similar to the PlayerItemCooldownEvent, offers other +behaviour and can hence not be implemented as a childtype of said event. +Specifically, cancelling the event prevents the game events from being +sent to the player. + +Plugins listening to just the PlayerItemCooldownEvent may not want said +sideeffects, meaning the disable event cannot share a handlerlist with +the cooldown event + +diff --git a/src/main/java/net/minecraft/world/entity/player/Player.java b/src/main/java/net/minecraft/world/entity/player/Player.java +index a19a795deaa7f46c92b97912e2ade006bc90c2d5..61d412c4f1ebd55661cc3f0260468e3ac0efe0bb 100644 +--- a/src/main/java/net/minecraft/world/entity/player/Player.java ++++ b/src/main/java/net/minecraft/world/entity/player/Player.java +@@ -970,7 +970,7 @@ public abstract class Player extends LivingEntity { + ItemStack itemstack = this.getItemBlockingWith(); + + if (attacker.canDisableShield() && itemstack != null) { +- this.disableShield(itemstack); ++ this.disableShield(itemstack, attacker); // Paper - Add PlayerShieldDisableEvent + } + + } +@@ -1463,8 +1463,21 @@ public abstract class Player extends LivingEntity { + this.attack(target); + } + ++ @io.papermc.paper.annotation.DoNotUse @Deprecated // Paper - Add PlayerShieldDisableEvent + public void disableShield(ItemStack shield) { +- this.getCooldowns().addCooldown(shield, 100); ++ // Paper start - Add PlayerShieldDisableEvent ++ this.disableShield(shield, null); ++ } ++ public void disableShield(ItemStack shield, @Nullable LivingEntity attacker) { ++ final org.bukkit.entity.Entity finalAttacker = attacker != null ? attacker.getBukkitEntity() : null; ++ if (finalAttacker != null) { ++ final io.papermc.paper.event.player.PlayerShieldDisableEvent shieldDisableEvent = new io.papermc.paper.event.player.PlayerShieldDisableEvent((org.bukkit.entity.Player) getBukkitEntity(), finalAttacker, 100); ++ if (!shieldDisableEvent.callEvent()) return; ++ this.getCooldowns().addCooldown(shield, shieldDisableEvent.getCooldown()); ++ } else { ++ this.getCooldowns().addCooldown(shield, 100); ++ } ++ // Paper end - Add PlayerShieldDisableEvent + this.stopUsingItem(); + this.level().broadcastEntityEvent(this, (byte) 30); + } diff --git a/patches/server/0909-Fix-CraftMetaItem-getAttributeModifier-duplication-c.patch b/patches/server/0909-Fix-CraftMetaItem-getAttributeModifier-duplication-c.patch deleted file mode 100644 index 86ed135ebc33..000000000000 --- a/patches/server/0909-Fix-CraftMetaItem-getAttributeModifier-duplication-c.patch +++ /dev/null @@ -1,19 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: hyper1423 -Date: Sun, 3 Dec 2023 07:38:09 +0900 -Subject: [PATCH] Fix CraftMetaItem#getAttributeModifier duplication check - - -diff --git a/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaItem.java b/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaItem.java -index df34eba383c0d3035c8baed7fdd054ecdd681fa3..f15456b02cabbbe33d701450ef53a0561d91cb8c 100644 ---- a/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaItem.java -+++ b/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaItem.java -@@ -1414,7 +1414,7 @@ class CraftMetaItem implements ItemMeta, Damageable, Repairable, BlockDataMeta { - Preconditions.checkNotNull(modifier, "AttributeModifier cannot be null"); - this.checkAttributeList(); - for (Map.Entry entry : this.attributeModifiers.entries()) { -- Preconditions.checkArgument(!entry.getValue().getKey().equals(modifier.getKey()), "Cannot register AttributeModifier. Modifier is already applied! %s", modifier); -+ Preconditions.checkArgument(!(entry.getValue().getKey().equals(modifier.getKey()) && entry.getKey() == attribute), "Cannot register AttributeModifier. Modifier is already applied! %s", modifier); // Paper - attribute modifiers with same namespaced key but on different attributes are fine - } - return this.attributeModifiers.put(attribute, modifier); - } diff --git a/patches/server/0910-Restore-vanilla-entity-drops-behavior.patch b/patches/server/0910-Restore-vanilla-entity-drops-behavior.patch deleted file mode 100644 index 76363209030c..000000000000 --- a/patches/server/0910-Restore-vanilla-entity-drops-behavior.patch +++ /dev/null @@ -1,270 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Jake Potrebic -Date: Tue, 22 Mar 2022 09:34:41 -0700 -Subject: [PATCH] Restore vanilla entity drops behavior - -Instead of just tracking the itemstacks, this tracks with it, the -action to take with that itemstack to apply the correct logic -on dropping the item instead of generalizing it for all dropped -items like CB does. - -diff --git a/src/main/java/net/minecraft/server/level/ServerPlayer.java b/src/main/java/net/minecraft/server/level/ServerPlayer.java -index b65d816bb9feb18ecf74e10e9728c302e5657587..62ec627e80b87a92a2a51ba9fc3626a67636855f 100644 ---- a/src/main/java/net/minecraft/server/level/ServerPlayer.java -+++ b/src/main/java/net/minecraft/server/level/ServerPlayer.java -@@ -976,20 +976,20 @@ public class ServerPlayer extends net.minecraft.world.entity.player.Player { - if (this.isRemoved()) { - return; - } -- java.util.List loot = new java.util.ArrayList<>(this.getInventory().getContainerSize()); -+ List loot = new java.util.ArrayList<>(this.getInventory().getContainerSize()); // Paper - Restore vanilla drops behavior - boolean keepInventory = this.level().getGameRules().getBoolean(GameRules.RULE_KEEPINVENTORY) || this.isSpectator(); - - if (!keepInventory) { - for (ItemStack item : this.getInventory().getContents()) { - if (!item.isEmpty() && !EnchantmentHelper.has(item, EnchantmentEffectComponents.PREVENT_EQUIPMENT_DROP)) { -- loot.add(CraftItemStack.asCraftMirror(item).markForInventoryDrop()); -+ loot.add(new DefaultDrop(item, stack -> this.drop(stack, true, false, false))); // Paper - Restore vanilla drops behavior; drop function taken from Inventory#dropAll (don't fire drop event) - } - } - } - if (this.shouldDropLoot() && this.level().getGameRules().getBoolean(GameRules.RULE_DOMOBLOOT)) { // Paper - fix player loottables running when mob loot gamerule is false - // SPIGOT-5071: manually add player loot tables (SPIGOT-5195 - ignores keepInventory rule) - this.dropFromLootTable(damageSource, this.lastHurtByPlayerTime > 0); -- this.dropCustomDeathLoot(this.serverLevel(), damageSource, flag); -+ // Paper - Restore vanilla drops behaviour; custom death loot is a noop on server player, remove. - - loot.addAll(this.drops); - this.drops.clear(); // SPIGOT-5188: make sure to clear -diff --git a/src/main/java/net/minecraft/world/entity/Entity.java b/src/main/java/net/minecraft/world/entity/Entity.java -index f163cb3c75e90b96b3794a7e0c5b803268814c8c..e88c88b6f87dddda8e8e3ed215a8f2c166afa3ef 100644 ---- a/src/main/java/net/minecraft/world/entity/Entity.java -+++ b/src/main/java/net/minecraft/world/entity/Entity.java -@@ -2562,6 +2562,25 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess - - @Nullable - public ItemEntity spawnAtLocation(ItemStack stack, float yOffset) { -+ // Paper start - Restore vanilla drops behavior -+ return this.spawnAtLocation(stack, yOffset, null); -+ } -+ public record DefaultDrop(Item item, org.bukkit.inventory.ItemStack stack, @Nullable java.util.function.Consumer dropConsumer) { -+ public DefaultDrop(final ItemStack stack, final java.util.function.Consumer dropConsumer) { -+ this(stack.getItem(), org.bukkit.craftbukkit.inventory.CraftItemStack.asCraftMirror(stack), dropConsumer); -+ } -+ -+ public void runConsumer(final java.util.function.Consumer fallback) { -+ if (this.dropConsumer == null || org.bukkit.craftbukkit.inventory.CraftItemType.bukkitToMinecraft(this.stack.getType()) != this.item) { -+ fallback.accept(this.stack); -+ } else { -+ this.dropConsumer.accept(org.bukkit.craftbukkit.inventory.CraftItemStack.asNMSCopy(this.stack)); -+ } -+ } -+ } -+ @Nullable -+ public ItemEntity spawnAtLocation(ItemStack stack, float yOffset, @Nullable java.util.function.Consumer delayedAddConsumer) { -+ // Paper end - Restore vanilla drops behavior - if (stack.isEmpty()) { - return null; - } else if (this.level().isClientSide) { -@@ -2569,14 +2588,21 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess - } else { - // CraftBukkit start - Capture drops for death event - if (this instanceof net.minecraft.world.entity.LivingEntity && !((net.minecraft.world.entity.LivingEntity) this).forceDrops) { -- ((net.minecraft.world.entity.LivingEntity) this).drops.add(org.bukkit.craftbukkit.inventory.CraftItemStack.asCraftMirror(stack)); // Paper - mirror so we can destroy it later -+ // Paper start - Restore vanilla drops behavior -+ ((net.minecraft.world.entity.LivingEntity) this).drops.add(new net.minecraft.world.entity.Entity.DefaultDrop(stack, itemStack -> { -+ ItemEntity itemEntity = new ItemEntity(this.level, this.getX(), this.getY() + (double) yOffset, this.getZ(), itemStack); // stack is copied before consumer -+ itemEntity.setDefaultPickUpDelay(); -+ this.level.addFreshEntity(itemEntity); -+ if (delayedAddConsumer != null) delayedAddConsumer.accept(itemEntity); -+ })); -+ // Paper end - Restore vanilla drops behavior - return null; - } - // CraftBukkit end - ItemEntity entityitem = new ItemEntity(this.level(), this.getX(), this.getY() + (double) yOffset, this.getZ(), stack.copy()); // Paper - copy so we can destroy original - stack.setCount(0); // Paper - destroy this item - if this ever leaks due to game bugs, ensure it doesn't dupe - -- entityitem.setDefaultPickUpDelay(); -+ entityitem.setDefaultPickUpDelay(); // Paper - diff on change (in dropConsumer) - // Paper start - Call EntityDropItemEvent - return this.spawnAtLocation(entityitem); - } -diff --git a/src/main/java/net/minecraft/world/entity/LivingEntity.java b/src/main/java/net/minecraft/world/entity/LivingEntity.java -index 0c67f04ae7eeaa10a90419031447ad5397c6b9e2..6b2dc430126713e11658f94762035ff10bbe018c 100644 ---- a/src/main/java/net/minecraft/world/entity/LivingEntity.java -+++ b/src/main/java/net/minecraft/world/entity/LivingEntity.java -@@ -278,7 +278,7 @@ public abstract class LivingEntity extends Entity implements Attackable { - protected float appliedScale; - // CraftBukkit start - public int expToDrop; -- public ArrayList drops = new ArrayList(); -+ public ArrayList drops = new ArrayList<>(); // Paper - Restore vanilla drops behavior - public final org.bukkit.craftbukkit.attribute.CraftAttributeMap craftAttributes; - public boolean collides = true; - public Set collidableExemptions = new HashSet<>(); -diff --git a/src/main/java/net/minecraft/world/entity/boss/wither/WitherBoss.java b/src/main/java/net/minecraft/world/entity/boss/wither/WitherBoss.java -index 1b49090a466bc74d9e5f2815314955b6dfbb83dc..62271e74399a827a488159da234465ef18e15e6e 100644 ---- a/src/main/java/net/minecraft/world/entity/boss/wither/WitherBoss.java -+++ b/src/main/java/net/minecraft/world/entity/boss/wither/WitherBoss.java -@@ -538,10 +538,9 @@ public class WitherBoss extends Monster implements PowerableMob, RangedAttackMob - @Override - protected void dropCustomDeathLoot(ServerLevel world, DamageSource source, boolean causedByPlayer) { - super.dropCustomDeathLoot(world, source, causedByPlayer); -- ItemEntity entityitem = this.spawnAtLocation((ItemLike) Items.NETHER_STAR); -- -+ ItemEntity entityitem = this.spawnAtLocation(new net.minecraft.world.item.ItemStack(Items.NETHER_STAR), 0, ItemEntity::setExtendedLifetime); // Paper - Restore vanilla drops behavior; spawnAtLocation returns null so modify the item entity with a consumer - if (entityitem != null) { -- entityitem.setExtendedLifetime(); -+ entityitem.setExtendedLifetime(); // Paper - diff on change - } - - } -diff --git a/src/main/java/net/minecraft/world/entity/decoration/ArmorStand.java b/src/main/java/net/minecraft/world/entity/decoration/ArmorStand.java -index 5bcb9a53ebebeef4bd6ec2458df4b63002ebd804..2f398750bfee5758ad8b1367b6fc14364e4de776 100644 ---- a/src/main/java/net/minecraft/world/entity/decoration/ArmorStand.java -+++ b/src/main/java/net/minecraft/world/entity/decoration/ArmorStand.java -@@ -621,7 +621,7 @@ public class ArmorStand extends LivingEntity { - ItemStack itemstack = new ItemStack(Items.ARMOR_STAND); - - itemstack.set(DataComponents.CUSTOM_NAME, this.getCustomName()); -- this.drops.add(org.bukkit.craftbukkit.inventory.CraftItemStack.asBukkitCopy(itemstack)); // CraftBukkit - add to drops -+ this.drops.add(new DefaultDrop(itemstack, stack -> Block.popResource(this.level(), this.blockPosition(), stack))); // CraftBukkit - add to drops // Paper - Restore vanilla drops behavior - return this.brokenByAnything(world, damageSource); // Paper - } - -@@ -635,7 +635,7 @@ public class ArmorStand extends LivingEntity { - for (i = 0; i < this.handItems.size(); ++i) { - itemstack = (ItemStack) this.handItems.get(i); - if (!itemstack.isEmpty()) { -- this.drops.add(org.bukkit.craftbukkit.inventory.CraftItemStack.asCraftMirror(itemstack)); // CraftBukkit - add to drops // Paper - mirror so we can destroy it later - though this call site was safe -+ this.drops.add(new DefaultDrop(itemstack, stack -> Block.popResource(this.level(), this.blockPosition().above(), stack))); // CraftBukkit - add to drops // Paper - Restore vanilla drops behavior; mirror so we can destroy it later - though this call site was safe & spawn drops correctly - this.handItems.set(i, ItemStack.EMPTY); - } - } -@@ -643,7 +643,7 @@ public class ArmorStand extends LivingEntity { - for (i = 0; i < this.armorItems.size(); ++i) { - itemstack = (ItemStack) this.armorItems.get(i); - if (!itemstack.isEmpty()) { -- this.drops.add(org.bukkit.craftbukkit.inventory.CraftItemStack.asCraftMirror(itemstack)); // CraftBukkit - add to drops // Paper - mirror so we can destroy it later - though this call site was safe -+ this.drops.add(new DefaultDrop(itemstack, stack -> Block.popResource(this.level(), this.blockPosition().above(), stack))); // CraftBukkit - add to drops // Paper - Restore vanilla drops behavior; mirror so we can destroy it later - though this call site was safe & spawn drops correctly - this.armorItems.set(i, ItemStack.EMPTY); - } - } -diff --git a/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java b/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java -index 2690c471b819f8308f6db44150025dfa323d4e0c..f529243852df8e193a604bb6d895333848b14a74 100644 ---- a/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java -+++ b/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java -@@ -975,18 +975,24 @@ public class CraftEventFactory { - } - - public static EntityDeathEvent callEntityDeathEvent(net.minecraft.world.entity.LivingEntity victim, DamageSource damageSource) { -- return CraftEventFactory.callEntityDeathEvent(victim, damageSource, new ArrayList(0)); -+ return CraftEventFactory.callEntityDeathEvent(victim, damageSource, new ArrayList<>(0)); // Paper - Restore vanilla drops behavior - } - -- public static EntityDeathEvent callEntityDeathEvent(net.minecraft.world.entity.LivingEntity victim, DamageSource damageSource, List drops) { -+ public static EntityDeathEvent callEntityDeathEvent(net.minecraft.world.entity.LivingEntity victim, DamageSource damageSource, List drops) { // Paper - Restore vanilla drops behavior - // Paper start - return CraftEventFactory.callEntityDeathEvent(victim, damageSource, drops, com.google.common.util.concurrent.Runnables.doNothing()); - } -- public static EntityDeathEvent callEntityDeathEvent(net.minecraft.world.entity.LivingEntity victim, DamageSource damageSource, List drops, Runnable lootCheck) { -+ -+ private static final java.util.function.Function FROM_FUNCTION = stack -> { -+ if (stack == null) return null; -+ return new Entity.DefaultDrop(CraftItemType.bukkitToMinecraft(stack.getType()), stack, null); -+ }; -+ -+ public static EntityDeathEvent callEntityDeathEvent(net.minecraft.world.entity.LivingEntity victim, DamageSource damageSource, List drops, Runnable lootCheck) { // Paper - // Paper end - CraftLivingEntity entity = (CraftLivingEntity) victim.getBukkitEntity(); - CraftDamageSource bukkitDamageSource = new CraftDamageSource(damageSource); -- EntityDeathEvent event = new EntityDeathEvent(entity, bukkitDamageSource, drops, victim.getExpReward(damageSource.getEntity())); -+ EntityDeathEvent event = new EntityDeathEvent(entity, bukkitDamageSource, new io.papermc.paper.util.TransformingRandomAccessList<>(drops, Entity.DefaultDrop::stack, FROM_FUNCTION), victim.getExpReward(damageSource.getEntity())); // Paper - Restore vanilla drops behavior - populateFields(victim, event); // Paper - make cancellable - CraftWorld world = (CraftWorld) entity.getWorld(); - Bukkit.getServer().getPluginManager().callEvent(event); -@@ -1000,20 +1006,24 @@ public class CraftEventFactory { - victim.expToDrop = event.getDroppedExp(); - lootCheck.run(); // Paper - advancement triggers before destroying items - -- for (org.bukkit.inventory.ItemStack stack : event.getDrops()) { -+ // Paper start - Restore vanilla drops behavior -+ for (Entity.DefaultDrop drop : drops) { -+ if (drop == null) continue; -+ final org.bukkit.inventory.ItemStack stack = drop.stack(); -+ // Paper end - Restore vanilla drops behavior - if (stack == null || stack.getType() == Material.AIR || stack.getAmount() == 0) continue; - -- world.dropItem(entity.getLocation(), stack); // Paper - note: dropItem already clones due to this being bukkit -> NMS -+ drop.runConsumer(s -> world.dropItem(entity.getLocation(), s)); // Paper - Restore vanilla drops behavior - if (stack instanceof CraftItemStack) stack.setAmount(0); // Paper - destroy this item - if this ever leaks due to game bugs, ensure it doesn't dupe, but don't nuke bukkit stacks of manually added items - } - - return event; - } - -- public static PlayerDeathEvent callPlayerDeathEvent(ServerPlayer victim, DamageSource damageSource, List drops, net.kyori.adventure.text.Component deathMessage, boolean keepInventory) { // Paper - Adventure -+ public static PlayerDeathEvent callPlayerDeathEvent(ServerPlayer victim, DamageSource damageSource, List drops, net.kyori.adventure.text.Component deathMessage, boolean keepInventory) { // Paper - Adventure & Restore vanilla drops behavior - CraftPlayer entity = victim.getBukkitEntity(); - CraftDamageSource bukkitDamageSource = new CraftDamageSource(damageSource); -- PlayerDeathEvent event = new PlayerDeathEvent(entity, bukkitDamageSource, drops, victim.getExpReward(damageSource.getEntity()), 0, deathMessage); -+ PlayerDeathEvent event = new PlayerDeathEvent(entity, bukkitDamageSource, new io.papermc.paper.util.TransformingRandomAccessList<>(drops, Entity.DefaultDrop::stack, FROM_FUNCTION), victim.getExpReward(damageSource.getEntity()), 0, deathMessage); // Paper - Restore vanilla drops behavior - event.setKeepInventory(keepInventory); - event.setKeepLevel(victim.keepLevel); // SPIGOT-2222: pre-set keepLevel - populateFields(victim, event); // Paper - make cancellable -@@ -1031,16 +1041,14 @@ public class CraftEventFactory { - victim.expToDrop = event.getDroppedExp(); - victim.newExp = event.getNewExp(); - -- for (org.bukkit.inventory.ItemStack stack : event.getDrops()) { -+ // Paper start - Restore vanilla drops behavior -+ for (Entity.DefaultDrop drop : drops) { -+ if (drop == null) continue; -+ final org.bukkit.inventory.ItemStack stack = drop.stack(); -+ // Paper end - Restore vanilla drops behavior - if (stack == null || stack.getType() == Material.AIR) continue; - -- if (stack instanceof CraftItemStack craftItemStack && craftItemStack.isForInventoryDrop()) { -- victim.drop(CraftItemStack.asNMSCopy(stack), true, false, false); // SPIGOT-7800, SPIGOT-7801: Vanilla Behaviour for Player Inventory dropped items -- } else { -- victim.forceDrops = true; -- victim.spawnAtLocation(CraftItemStack.asNMSCopy(stack)); // SPIGOT-7806: Vanilla Behaviour for items not related to Player Inventory dropped items -- victim.forceDrops = false; -- } -+ drop.runConsumer(s -> victim.drop(CraftItemStack.unwrap(s), true, false, false)); // Paper - Restore vanilla drops behavior - } - - return event; -diff --git a/src/main/java/org/bukkit/craftbukkit/inventory/CraftItemStack.java b/src/main/java/org/bukkit/craftbukkit/inventory/CraftItemStack.java -index c5a09f086d35f84c0a30266f78e06e2dfb5603e6..f33742ee06e8443a5f5adaaa987d7523dc193b5a 100644 ---- a/src/main/java/org/bukkit/craftbukkit/inventory/CraftItemStack.java -+++ b/src/main/java/org/bukkit/craftbukkit/inventory/CraftItemStack.java -@@ -130,27 +130,6 @@ public final class CraftItemStack extends ItemStack { - this.setItemMeta(itemMeta); - } - -- /** -- * Gets if the item is marked as an inventory drop in death events. -- * -- * @return true if the item is marked as an inventory drop -- */ -- @ApiStatus.Internal -- public boolean isForInventoryDrop() { -- return this.isForInventoryDrop; -- } -- -- /** -- * Marks this item as an inventory drop in death events. -- * -- * @return the ItemStack marked as an inventory drop -- */ -- @ApiStatus.Internal -- public ItemStack markForInventoryDrop() { -- this.isForInventoryDrop = true; -- return this; -- } -- - @Override - public MaterialData getData() { - return this.handle != null ? CraftMagicNumbers.getMaterialData(this.handle.getItem()) : super.getData(); diff --git a/patches/server/0910-Validate-ResourceLocation-in-NBT-reading.patch b/patches/server/0910-Validate-ResourceLocation-in-NBT-reading.patch new file mode 100644 index 000000000000..a7e42195f17f --- /dev/null +++ b/patches/server/0910-Validate-ResourceLocation-in-NBT-reading.patch @@ -0,0 +1,173 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Nassim Jahnke +Date: Thu, 4 Jan 2024 13:49:14 +0100 +Subject: [PATCH] Validate ResourceLocation in NBT reading + + +diff --git a/src/main/java/net/minecraft/nbt/NbtUtils.java b/src/main/java/net/minecraft/nbt/NbtUtils.java +index 4929bac8e476664086470f078efce6c0a6164413..f88dd37783b3c155c23b547c360b8d3c16e030c0 100644 +--- a/src/main/java/net/minecraft/nbt/NbtUtils.java ++++ b/src/main/java/net/minecraft/nbt/NbtUtils.java +@@ -149,8 +149,10 @@ public final class NbtUtils { + if (!nbt.contains("Name", 8)) { + return Blocks.AIR.defaultBlockState(); + } else { +- ResourceLocation resourceLocation = ResourceLocation.parse(nbt.getString("Name")); +- Optional> optional = blockLookup.get(ResourceKey.create(Registries.BLOCK, resourceLocation)); ++ // Paper start - Validate resource location ++ ResourceLocation resourceLocation = ResourceLocation.tryParse(nbt.getString("Name")); ++ Optional> optional = resourceLocation != null ? blockLookup.get(ResourceKey.create(Registries.BLOCK, resourceLocation)) : Optional.empty(); ++ // Paper end - Validate resource location + if (optional.isEmpty()) { + return Blocks.AIR.defaultBlockState(); + } else { +diff --git a/src/main/java/net/minecraft/resources/ResourceLocation.java b/src/main/java/net/minecraft/resources/ResourceLocation.java +index 87afe84791af2d5e9f869cd4c09eed4bb5fee75b..1967c43ee3a12e63365cc40ee6565307e2fd73cf 100644 +--- a/src/main/java/net/minecraft/resources/ResourceLocation.java ++++ b/src/main/java/net/minecraft/resources/ResourceLocation.java +@@ -41,6 +41,13 @@ public final class ResourceLocation implements Comparable { + + assert isValidPath(path); + ++ // Paper start - Validate ResourceLocation ++ // Check for the max network string length (capped at Short.MAX_VALUE) as well as the max bytes of a StringTag (length written as an unsigned short) ++ final String resourceLocation = namespace + ":" + path; ++ if (resourceLocation.length() > Short.MAX_VALUE || io.netty.buffer.ByteBufUtil.utf8MaxBytes(resourceLocation) > 2 * Short.MAX_VALUE + 1) { ++ throw new ResourceLocationException("Resource location too long: " + resourceLocation); ++ } ++ // Paper end - Validate ResourceLocation + this.namespace = namespace; + this.path = path; + } +diff --git a/src/main/java/net/minecraft/world/RandomizableContainer.java b/src/main/java/net/minecraft/world/RandomizableContainer.java +index 084935138b1484f3d96e99f4e5655a6c04931907..9e357abe13f55bd9ce3a1d5348bcf19a15ea5433 100644 +--- a/src/main/java/net/minecraft/world/RandomizableContainer.java ++++ b/src/main/java/net/minecraft/world/RandomizableContainer.java +@@ -50,7 +50,7 @@ public interface RandomizableContainer extends Container { + + default boolean tryLoadLootTable(CompoundTag nbt) { + if (nbt.contains("LootTable", 8)) { +- this.setLootTable(ResourceKey.create(Registries.LOOT_TABLE, ResourceLocation.parse(nbt.getString("LootTable")))); ++ this.setLootTable(net.minecraft.Optionull.map(ResourceLocation.tryParse(nbt.getString("LootTable")), rl -> ResourceKey.create(Registries.LOOT_TABLE, rl))); // Paper - Validate ResourceLocation + if (this.lootableData() != null && this.getLootTable() != null) this.lootableData().loadNbt(nbt); // Paper - LootTable API + if (nbt.contains("LootTableSeed", 4)) { + this.setLootTableSeed(nbt.getLong("LootTableSeed")); +diff --git a/src/main/java/net/minecraft/world/entity/EntityType.java b/src/main/java/net/minecraft/world/entity/EntityType.java +index c2693d530be00af16b2aa4ca4afd1d136db68183..629c1316920ad4c111fff489f8c3ea0ed39d0099 100644 +--- a/src/main/java/net/minecraft/world/entity/EntityType.java ++++ b/src/main/java/net/minecraft/world/entity/EntityType.java +@@ -680,7 +680,7 @@ public class EntityType implements FeatureElement, EntityTypeT + } + + public static Optional> by(CompoundTag nbt) { +- return BuiltInRegistries.ENTITY_TYPE.getOptional(ResourceLocation.parse(nbt.getString("id"))); ++ return BuiltInRegistries.ENTITY_TYPE.getOptional(ResourceLocation.tryParse(nbt.getString("id"))); // Paper - Validate ResourceLocation + } + + @Nullable +diff --git a/src/main/java/net/minecraft/world/entity/Leashable.java b/src/main/java/net/minecraft/world/entity/Leashable.java +index b7721ed97305d1cd6725935f965c2effc1bef5a1..5f880a8809f9c20bc8e8c0b2d48590bab02cf077 100644 +--- a/src/main/java/net/minecraft/world/entity/Leashable.java ++++ b/src/main/java/net/minecraft/world/entity/Leashable.java +@@ -55,7 +55,13 @@ public interface Leashable { + @Nullable + default Leashable.LeashData readLeashData(CompoundTag nbt) { + if (nbt.contains("leash", 10)) { +- return new Leashable.LeashData(Either.left(nbt.getCompound("leash").getUUID("UUID"))); ++ // Paper start ++ final CompoundTag leashTag = nbt.getCompound("leash"); ++ if (!leashTag.hasUUID("UUID")) { ++ return null; ++ } ++ return new Leashable.LeashData(Either.left(leashTag.getUUID("UUID"))); ++ // Paper end + } else { + if (nbt.contains("leash", 11)) { + Either either = (Either) NbtUtils.readBlockPos(nbt, "leash").map(Either::right).orElse(null); // CraftBukkit - decompile error +diff --git a/src/main/java/net/minecraft/world/entity/LivingEntity.java b/src/main/java/net/minecraft/world/entity/LivingEntity.java +index 9d3d6f012cfca60884019ed9710804aa37b11fbf..49b3d8d2bc34c0785f143bbc8976308f5bf8c9de 100644 +--- a/src/main/java/net/minecraft/world/entity/LivingEntity.java ++++ b/src/main/java/net/minecraft/world/entity/LivingEntity.java +@@ -909,11 +909,13 @@ public abstract class LivingEntity extends Entity implements Attackable { + if (nbt.contains("SleepingX", 99) && nbt.contains("SleepingY", 99) && nbt.contains("SleepingZ", 99)) { + BlockPos blockposition = new BlockPos(nbt.getInt("SleepingX"), nbt.getInt("SleepingY"), nbt.getInt("SleepingZ")); + ++ if (this.position().distanceToSqr(blockposition.getX(), blockposition.getY(), blockposition.getZ()) < 16 * 16) { // Paper - The sleeping pos will always also set the actual pos, so a desync suggests something is wrong + this.setSleepingPos(blockposition); + this.entityData.set(LivingEntity.DATA_POSE, Pose.SLEEPING); + if (!this.firstTick) { + this.setPosToBed(blockposition); + } ++ } // Paper - The sleeping pos will always also set the actual pos, so a desync suggests something is wrong + } + + if (nbt.contains("Brain", 10)) { +diff --git a/src/main/java/net/minecraft/world/entity/Mob.java b/src/main/java/net/minecraft/world/entity/Mob.java +index 8a0e65ac8318a467996f48b423db1ac621359fbe..aad63549d7c4f501b683b8dead4938eac27895eb 100644 +--- a/src/main/java/net/minecraft/world/entity/Mob.java ++++ b/src/main/java/net/minecraft/world/entity/Mob.java +@@ -601,7 +601,7 @@ public abstract class Mob extends LivingEntity implements EquipmentUser, Leashab + this.leashData = this.readLeashData(nbt); + this.setLeftHanded(nbt.getBoolean("LeftHanded")); + if (nbt.contains("DeathLootTable", 8)) { +- this.lootTable = Optional.of(ResourceKey.create(Registries.LOOT_TABLE, ResourceLocation.parse(nbt.getString("DeathLootTable")))); ++ this.lootTable = Optional.ofNullable(ResourceLocation.tryParse(nbt.getString("DeathLootTable"))).map((rs) -> ResourceKey.create(Registries.LOOT_TABLE, rs)); // Paper - Validate ResourceLocation + this.lootTableSeed = nbt.getLong("DeathLootTableSeed"); + } + +diff --git a/src/main/java/net/minecraft/world/entity/projectile/AbstractArrow.java b/src/main/java/net/minecraft/world/entity/projectile/AbstractArrow.java +index 75cc3db39c974abab8510af4a633fc6812efc647..14e31ae88e90d8ea1a98800cc6c1c3527bb2ed6b 100644 +--- a/src/main/java/net/minecraft/world/entity/projectile/AbstractArrow.java ++++ b/src/main/java/net/minecraft/world/entity/projectile/AbstractArrow.java +@@ -695,7 +695,7 @@ public abstract class AbstractArrow extends Projectile { + this.setCritArrow(nbt.getBoolean("crit")); + this.setPierceLevel(nbt.getByte("PierceLevel")); + if (nbt.contains("SoundEvent", 8)) { +- this.soundEvent = (SoundEvent) BuiltInRegistries.SOUND_EVENT.getOptional(ResourceLocation.parse(nbt.getString("SoundEvent"))).orElse(this.getDefaultHitGroundSoundEvent()); ++ this.soundEvent = (SoundEvent) BuiltInRegistries.SOUND_EVENT.getOptional(ResourceLocation.tryParse(nbt.getString("SoundEvent"))).orElse(this.getDefaultHitGroundSoundEvent()); // Paper - Validate resource location + } + + if (nbt.contains("item", 10)) { +diff --git a/src/main/java/net/minecraft/world/entity/vehicle/ContainerEntity.java b/src/main/java/net/minecraft/world/entity/vehicle/ContainerEntity.java +index 874a44ab77248665c2db243764e8542bfc0d6514..cc7826a10f22e3307231d887db2fee98063b1f46 100644 +--- a/src/main/java/net/minecraft/world/entity/vehicle/ContainerEntity.java ++++ b/src/main/java/net/minecraft/world/entity/vehicle/ContainerEntity.java +@@ -73,7 +73,7 @@ public interface ContainerEntity extends Container, MenuProvider { + default void readChestVehicleSaveData(CompoundTag nbt, HolderLookup.Provider registries) { + this.clearItemStacks(); + if (nbt.contains("LootTable", 8)) { +- this.setContainerLootTable(ResourceKey.create(Registries.LOOT_TABLE, ResourceLocation.parse(nbt.getString("LootTable")))); ++ this.setContainerLootTable(net.minecraft.Optionull.map(ResourceLocation.tryParse(nbt.getString("LootTable")), rl -> ResourceKey.create(Registries.LOOT_TABLE, rl))); // Paper - Validate ResourceLocation + // Paper start - LootTable API + if (this.getContainerLootTable() != null) { + this.lootableData().loadNbt(nbt); +diff --git a/src/main/java/net/minecraft/world/level/block/entity/AbstractFurnaceBlockEntity.java b/src/main/java/net/minecraft/world/level/block/entity/AbstractFurnaceBlockEntity.java +index b1067a3add5dc0cfa853b02b5b556d6d67e2932a..15e0861486a2bda3e2f4049b1b5a299c870acd31 100644 +--- a/src/main/java/net/minecraft/world/level/block/entity/AbstractFurnaceBlockEntity.java ++++ b/src/main/java/net/minecraft/world/level/block/entity/AbstractFurnaceBlockEntity.java +@@ -180,7 +180,11 @@ public abstract class AbstractFurnaceBlockEntity extends BaseContainerBlockEntit + while (iterator.hasNext()) { + String s = (String) iterator.next(); + +- this.recipesUsed.put(ResourceKey.create(Registries.RECIPE, ResourceLocation.parse(s)), nbttagcompound1.getInt(s)); ++ // Paper start - Validate ResourceLocation ++ final ResourceLocation resourceLocation = ResourceLocation.tryParse(s); ++ if (resourceLocation != null) { ++ this.recipesUsed.put(ResourceKey.create(Registries.RECIPE, resourceLocation), nbttagcompound1.getInt(s)); ++ } + } + + // Paper start - cook speed multiplier API +diff --git a/src/main/java/net/minecraft/world/level/block/entity/BrushableBlockEntity.java b/src/main/java/net/minecraft/world/level/block/entity/BrushableBlockEntity.java +index 1bfffbf54b1b440c6e19a908ea2bd70387d06b5c..b08867878e56f88569d547765f29cab018a9e791 100644 +--- a/src/main/java/net/minecraft/world/level/block/entity/BrushableBlockEntity.java ++++ b/src/main/java/net/minecraft/world/level/block/entity/BrushableBlockEntity.java +@@ -194,7 +194,7 @@ public class BrushableBlockEntity extends BlockEntity { + + private boolean tryLoadLootTable(CompoundTag nbt) { + if (nbt.contains("LootTable", 8)) { +- this.lootTable = ResourceKey.create(Registries.LOOT_TABLE, ResourceLocation.parse(nbt.getString("LootTable"))); ++ this.lootTable = net.minecraft.Optionull.map(ResourceLocation.tryParse(nbt.getString("LootTable")), rl -> ResourceKey.create(Registries.LOOT_TABLE, rl)); // Paper - Validate ResourceLocation + this.lootTableSeed = nbt.getLong("LootTableSeed"); + return true; + } else { diff --git a/patches/server/0911-Dont-resend-blocks-on-interactions.patch b/patches/server/0911-Dont-resend-blocks-on-interactions.patch deleted file mode 100644 index 74650cebfd9d..000000000000 --- a/patches/server/0911-Dont-resend-blocks-on-interactions.patch +++ /dev/null @@ -1,171 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Owen1212055 <23108066+Owen1212055@users.noreply.github.com> -Date: Tue, 27 Jun 2023 21:09:11 -0400 -Subject: [PATCH] Dont resend blocks on interactions - -In general, the client now has an acknowledgment system which will prevent block changes made by the client to be reverted correctly. - -It should be noted that this system does not yet support block entities, so those still need to resynced when needed. - -diff --git a/src/main/java/net/minecraft/server/level/ServerPlayerGameMode.java b/src/main/java/net/minecraft/server/level/ServerPlayerGameMode.java -index d839f8df658c894f144ba4637d290ffbed77e132..415d9802ae4dd75b44055b8faf19672fa50c585f 100644 ---- a/src/main/java/net/minecraft/server/level/ServerPlayerGameMode.java -+++ b/src/main/java/net/minecraft/server/level/ServerPlayerGameMode.java -@@ -204,7 +204,7 @@ public class ServerPlayerGameMode { - PlayerInteractEvent event = CraftEventFactory.callPlayerInteractEvent(this.player, Action.LEFT_CLICK_BLOCK, pos, direction, this.player.getInventory().getSelected(), InteractionHand.MAIN_HAND); - if (event.isCancelled()) { - // Let the client know the block still exists -- this.player.connection.send(new ClientboundBlockUpdatePacket(this.level, pos)); -+ // this.player.connection.send(new ClientboundBlockUpdatePacket(this.level, pos)); // Paper - Don't resync blocks - // Update any tile entity data for this block - capturedBlockEntity = true; // Paper - Send block entities after destroy prediction - return; -@@ -219,7 +219,7 @@ public class ServerPlayerGameMode { - // Spigot start - handle debug stick left click for non-creative - if (this.player.getMainHandItem().is(net.minecraft.world.item.Items.DEBUG_STICK) - && ((net.minecraft.world.item.DebugStickItem) net.minecraft.world.item.Items.DEBUG_STICK).handleInteraction(this.player, this.level.getBlockState(pos), this.level, pos, false, this.player.getMainHandItem())) { -- this.player.connection.send(new ClientboundBlockUpdatePacket(this.level, pos)); -+ // this.player.connection.send(new ClientboundBlockUpdatePacket(this.level, pos)); // Paper - Don't resync block - return; - } - // Spigot end -@@ -237,15 +237,17 @@ public class ServerPlayerGameMode { - // CraftBukkit start - Swings at air do *NOT* exist. - if (event.useInteractedBlock() == Event.Result.DENY) { - // If we denied a door from opening, we need to send a correcting update to the client, as it already opened the door. -- BlockState data = this.level.getBlockState(pos); -- if (data.getBlock() instanceof DoorBlock) { -- // For some reason *BOTH* the bottom/top part have to be marked updated. -- boolean bottom = data.getValue(DoorBlock.HALF) == DoubleBlockHalf.LOWER; -- this.player.connection.send(new ClientboundBlockUpdatePacket(this.level, pos)); -- this.player.connection.send(new ClientboundBlockUpdatePacket(this.level, bottom ? pos.above() : pos.below())); -- } else if (data.getBlock() instanceof TrapDoorBlock) { -- this.player.connection.send(new ClientboundBlockUpdatePacket(this.level, pos)); -- } -+ // Paper start - Don't resync blocks -+ //BlockState data = this.level.getBlockState(pos); -+ //if (data.getBlock() instanceof DoorBlock) { -+ // // For some reason *BOTH* the bottom/top part have to be marked updated. -+ // boolean bottom = data.getValue(DoorBlock.HALF) == DoubleBlockHalf.LOWER; -+ // this.player.connection.send(new ClientboundBlockUpdatePacket(this.level, pos)); -+ // this.player.connection.send(new ClientboundBlockUpdatePacket(this.level, bottom ? pos.above() : pos.below())); -+ //} else if (data.getBlock() instanceof TrapDoorBlock) { -+ // this.player.connection.send(new ClientboundBlockUpdatePacket(this.level, pos)); -+ //} -+ // Paper end - Don't resync blocks - } else if (!iblockdata.isAir()) { - EnchantmentHelper.onHitBlock(this.level, this.player.getMainHandItem(), this.player, this.player, EquipmentSlot.MAINHAND, Vec3.atCenterOf(pos), iblockdata, (item) -> { - this.player.onEquippedItemBroken(item, EquipmentSlot.MAINHAND); -@@ -257,7 +259,7 @@ public class ServerPlayerGameMode { - if (event.useItemInHand() == Event.Result.DENY) { - // If we 'insta destroyed' then the client needs to be informed. - if (f > 1.0f) { -- this.player.connection.send(new ClientboundBlockUpdatePacket(this.level, pos)); -+ // this.player.connection.send(new ClientboundBlockUpdatePacket(this.level, pos)); // Paper - Don't resync blocks - } - return; - } -@@ -265,7 +267,7 @@ public class ServerPlayerGameMode { - - if (blockEvent.isCancelled()) { - // Let the client know the block still exists -- this.player.connection.send(new ClientboundBlockUpdatePacket(this.level, pos)); -+ // this.player.connection.send(new ClientboundBlockUpdatePacket(this.level, pos)); // Paper - Don't resync block - return; - } - -@@ -356,7 +358,7 @@ public class ServerPlayerGameMode { - - // Tell client the block is gone immediately then process events - // Don't tell the client if its a creative sword break because its not broken! -- if (this.level.getBlockEntity(pos) == null && !isSwordNoBreak) { -+ if (false && this.level.getBlockEntity(pos) == null && !isSwordNoBreak) { // Paper - Don't resync block - ClientboundBlockUpdatePacket packet = new ClientboundBlockUpdatePacket(pos, Blocks.AIR.defaultBlockState()); - this.player.connection.send(packet); - } -@@ -382,13 +384,15 @@ public class ServerPlayerGameMode { - if (isSwordNoBreak) { - return false; - } -+ // Paper start - Don't resync blocks - // Let the client know the block still exists -- this.player.connection.send(new ClientboundBlockUpdatePacket(this.level, pos)); -+ //this.player.connection.send(new ClientboundBlockUpdatePacket(this.level, pos)); - - // Brute force all possible updates -- for (Direction dir : Direction.values()) { -- this.player.connection.send(new ClientboundBlockUpdatePacket(this.level, pos.relative(dir))); -- } -+ //for (Direction dir : Direction.values()) { -+ // this.player.connection.send(new ClientboundBlockUpdatePacket(this.level, pos.relative(dir))); -+ //} -+ // Paper end - Don't resync blocks - - // Update any tile entity data for this block - if (!captureSentBlockEntities) { // Paper - Send block entities after destroy prediction -@@ -537,16 +541,18 @@ public class ServerPlayerGameMode { - if (event.useInteractedBlock() == Event.Result.DENY) { - // If we denied a door from opening, we need to send a correcting update to the client, as it already opened the door. - if (iblockdata.getBlock() instanceof DoorBlock) { -- boolean bottom = iblockdata.getValue(DoorBlock.HALF) == DoubleBlockHalf.LOWER; -- player.connection.send(new ClientboundBlockUpdatePacket(world, bottom ? blockposition.above() : blockposition.below())); -+ // Paper start - Don't resync blocks -+ // boolean bottom = iblockdata.getValue(DoorBlock.HALF) == DoubleBlockHalf.LOWER; -+ // player.connection.send(new ClientboundBlockUpdatePacket(world, bottom ? blockposition.above() : blockposition.below())); -+ // Paper end - Don't resync blocks - } else if (iblockdata.getBlock() instanceof CakeBlock) { - player.getBukkitEntity().sendHealthUpdate(); // SPIGOT-1341 - reset health for cake - } else if (this.interactItemStack.getItem() instanceof DoubleHighBlockItem) { - // send a correcting update to the client, as it already placed the upper half of the bisected item -- player.connection.send(new ClientboundBlockUpdatePacket(world, blockposition.relative(hitResult.getDirection()).above())); -+ //player.connection.send(new ClientboundBlockUpdatePacket(world, blockposition.relative(hitResult.getDirection()).above())); // Paper - Don't resync blocks - - // send a correcting update to the client for the block above as well, this because of replaceable blocks (such as grass, sea grass etc) -- player.connection.send(new ClientboundBlockUpdatePacket(world, blockposition.above())); -+ //player.connection.send(new ClientboundBlockUpdatePacket(world, blockposition.above())); // Paper - Don't resync blocks - // Paper start - extend Player Interact cancellation // TODO: consider merging this into the extracted method - } else if (iblockdata.is(Blocks.JIGSAW) || iblockdata.is(Blocks.STRUCTURE_BLOCK) || iblockdata.getBlock() instanceof net.minecraft.world.level.block.CommandBlock) { - player.connection.send(new net.minecraft.network.protocol.game.ClientboundContainerClosePacket(this.player.containerMenu.containerId)); -diff --git a/src/main/java/net/minecraft/world/item/BucketItem.java b/src/main/java/net/minecraft/world/item/BucketItem.java -index 6caed156ed0cfe0017d578f58cb963ee68272d78..321188173918d0d60858a258400dfd682ccdb21c 100644 ---- a/src/main/java/net/minecraft/world/item/BucketItem.java -+++ b/src/main/java/net/minecraft/world/item/BucketItem.java -@@ -79,7 +79,7 @@ public class BucketItem extends Item implements DispensibleContainerItem { - PlayerBucketFillEvent event = CraftEventFactory.callPlayerBucketFillEvent((ServerLevel) world, user, blockposition, blockposition, movingobjectpositionblock.getDirection(), itemstack, dummyFluid.getItem(), hand); - - if (event.isCancelled()) { -- ((ServerPlayer) user).connection.send(new ClientboundBlockUpdatePacket(world, blockposition)); // SPIGOT-5163 (see PlayerInteractManager) -+ // ((ServerPlayer) user).connection.send(new ClientboundBlockUpdatePacket(world, blockposition)); // SPIGOT-5163 (see PlayerInteractManager) // Paper - Don't resend blocks - ((ServerPlayer) user).getBukkitEntity().updateInventory(); // SPIGOT-4541 - return InteractionResultHolder.fail(itemstack); - } -@@ -187,7 +187,7 @@ public class BucketItem extends Item implements DispensibleContainerItem { - if (flag2 && entityhuman != null) { - PlayerBucketEmptyEvent event = CraftEventFactory.callPlayerBucketEmptyEvent((ServerLevel) world, entityhuman, blockposition, clicked, enumdirection, itemstack, enumhand); - if (event.isCancelled()) { -- ((ServerPlayer) entityhuman).connection.send(new ClientboundBlockUpdatePacket(world, blockposition)); // SPIGOT-4238: needed when looking through entity -+ // ((ServerPlayer) entityhuman).connection.send(new ClientboundBlockUpdatePacket(world, blockposition)); // SPIGOT-4238: needed when looking through entity // Paper - Don't resend blocks - ((ServerPlayer) entityhuman).getBukkitEntity().updateInventory(); // SPIGOT-4541 - return false; - } -diff --git a/src/main/java/net/minecraft/world/item/ItemStack.java b/src/main/java/net/minecraft/world/item/ItemStack.java -index 87cae96faf33fa0491b13add79e02fc225bd2f70..e9a9c89bd6a4ce7cb30c2fcf79a537fc18204aeb 100644 ---- a/src/main/java/net/minecraft/world/item/ItemStack.java -+++ b/src/main/java/net/minecraft/world/item/ItemStack.java -@@ -500,10 +500,12 @@ public final class ItemStack implements DataComponentHolder { - world.preventPoiUpdated = false; - - // Brute force all possible updates -- BlockPos placedPos = ((CraftBlock) placeEvent.getBlock()).getPosition(); -- for (Direction dir : Direction.values()) { -- ((ServerPlayer) entityhuman).connection.send(new ClientboundBlockUpdatePacket(world, placedPos.relative(dir))); -- } -+ // Paper start - Don't resync blocks -+ // BlockPos placedPos = ((CraftBlock) placeEvent.getBlock()).getPosition(); -+ // for (Direction dir : Direction.values()) { -+ // ((ServerPlayer) entityhuman).connection.send(new ClientboundBlockUpdatePacket(world, placedPos.relative(dir))); -+ // } -+ // Paper end - Don't resync blocks - SignItem.openSign = null; // SPIGOT-6758 - Reset on early return - } else { - // Change the stack to its new contents if it hasn't been tampered with. diff --git a/patches/server/0911-Properly-handle-experience-dropping-on-block-break.patch b/patches/server/0911-Properly-handle-experience-dropping-on-block-break.patch new file mode 100644 index 000000000000..5b45a7fb9204 --- /dev/null +++ b/patches/server/0911-Properly-handle-experience-dropping-on-block-break.patch @@ -0,0 +1,94 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Owen1212055 <23108066+Owen1212055@users.noreply.github.com> +Date: Sat, 30 Dec 2023 15:00:06 -0500 +Subject: [PATCH] Properly handle experience dropping on block break + +This causes spawnAfterBreak to spawn xp by default, removing the need to manually add xp wherever this method is used. +For classes that use custom xp amounts, they can drop the resources with disabling + +diff --git a/src/main/java/net/minecraft/world/level/Level.java b/src/main/java/net/minecraft/world/level/Level.java +index 18c011c1943867dbc4abee338b03b9be499876dd..01fbefdbed48ab85481c811cca532c91860626f7 100644 +--- a/src/main/java/net/minecraft/world/level/Level.java ++++ b/src/main/java/net/minecraft/world/level/Level.java +@@ -615,7 +615,8 @@ public abstract class Level implements LevelAccessor, AutoCloseable { + if (drop) { + BlockEntity tileentity = iblockdata.hasBlockEntity() ? this.getBlockEntity(pos) : null; + +- Block.dropResources(iblockdata, this, pos, tileentity, breakingEntity, ItemStack.EMPTY); ++ Block.dropResources(iblockdata, this, pos, tileentity, breakingEntity, ItemStack.EMPTY, false); // Paper - Properly handle xp dropping ++ iblockdata.getBlock().popExperience((ServerLevel) this, pos, xp, breakingEntity); // Paper - Properly handle xp dropping; custom amount + } + + boolean flag1 = this.setBlock(pos, fluid.createLegacyBlock(), 3, maxUpdateDepth); +diff --git a/src/main/java/net/minecraft/world/level/block/Block.java b/src/main/java/net/minecraft/world/level/block/Block.java +index 4ff32e3fb1a1979827ef063cda196a43995440fe..dc242451f397ae6a30b830ef1f211f1a066423a4 100644 +--- a/src/main/java/net/minecraft/world/level/block/Block.java ++++ b/src/main/java/net/minecraft/world/level/block/Block.java +@@ -302,23 +302,31 @@ public class Block extends BlockBehaviour implements ItemLike { + for (ItemStack drop : Block.getDrops(state, serverLevel, pos, blockEntity)) { + items.add(org.bukkit.craftbukkit.inventory.CraftItemStack.asBukkitCopy(drop)); + } ++ Block block = state.getBlock(); // Paper - Properly handle xp dropping + io.papermc.paper.event.block.BlockBreakBlockEvent event = new io.papermc.paper.event.block.BlockBreakBlockEvent(org.bukkit.craftbukkit.block.CraftBlock.at(levelAccessor, pos), org.bukkit.craftbukkit.block.CraftBlock.at(levelAccessor, source), items); ++ event.setExpToDrop(block.getExpDrop(state, serverLevel, pos, net.minecraft.world.item.ItemStack.EMPTY, true)); // Paper - Properly handle xp dropping + event.callEvent(); + for (org.bukkit.inventory.ItemStack drop : event.getDrops()) { + popResource(serverLevel, pos, org.bukkit.craftbukkit.inventory.CraftItemStack.asNMSCopy(drop)); + } +- state.spawnAfterBreak(serverLevel, pos, ItemStack.EMPTY, true); ++ state.spawnAfterBreak(serverLevel, pos, ItemStack.EMPTY, false); // Paper - Properly handle xp dropping ++ block.popExperience(serverLevel, pos, event.getExpToDrop()); // Paper - Properly handle xp dropping + } + return true; + } + // Paper end - Add BlockBreakBlockEvent + + public static void dropResources(BlockState state, Level world, BlockPos pos, @Nullable BlockEntity blockEntity, @Nullable Entity entity, ItemStack tool) { ++ // Paper start - Properly handle xp dropping ++ dropResources(state, world, pos, blockEntity, entity, tool, true); ++ } ++ public static void dropResources(BlockState state, Level world, BlockPos pos, @Nullable BlockEntity blockEntity, @Nullable Entity entity, ItemStack tool, boolean dropExperience) { ++ // Paper end - Properly handle xp dropping + if (world instanceof ServerLevel) { + Block.getDrops(state, (ServerLevel) world, pos, blockEntity, entity, tool).forEach((itemstack1) -> { + Block.popResource(world, pos, itemstack1); + }); +- state.spawnAfterBreak((ServerLevel) world, pos, tool, true); ++ state.spawnAfterBreak((ServerLevel) world, pos, tool, dropExperience); // Paper - Properly handle xp dropping + } + + } +@@ -406,7 +414,7 @@ public class Block extends BlockBehaviour implements ItemLike { + player.awardStat(Stats.BLOCK_MINED.get(this)); + player.causeFoodExhaustion(0.005F, org.bukkit.event.entity.EntityExhaustionEvent.ExhaustionReason.BLOCK_MINED); // CraftBukkit - EntityExhaustionEvent + if (includeDrops) { // Paper - fix drops not preventing stats/food exhaustion +- Block.dropResources(state, world, pos, blockEntity, player, tool); ++ Block.dropResources(state, world, pos, blockEntity, player, tool, dropExp); // Paper - Properly handle xp dropping + } // Paper - fix drops not preventing stats/food exhaustion + } + +diff --git a/src/main/java/net/minecraft/world/level/block/state/BlockBehaviour.java b/src/main/java/net/minecraft/world/level/block/state/BlockBehaviour.java +index 0665ca48fe2f8ab1ce1c0306b11be19b06445f74..1b988b92e80faa1ac224caf9f9e955ac43a4c45a 100644 +--- a/src/main/java/net/minecraft/world/level/block/state/BlockBehaviour.java ++++ b/src/main/java/net/minecraft/world/level/block/state/BlockBehaviour.java +@@ -1180,6 +1180,7 @@ public abstract class BlockBehaviour implements FeatureElement { + + public void spawnAfterBreak(ServerLevel world, BlockPos pos, ItemStack tool, boolean dropExperience) { + this.getBlock().spawnAfterBreak(this.asState(), world, pos, tool, dropExperience); ++ if (dropExperience) {getBlock().popExperience(world, pos, this.getBlock().getExpDrop(asState(), world, pos, tool, true));} // Paper - Properly handle xp dropping + } + + public List getDrops(LootParams.Builder builder) { +diff --git a/src/main/java/org/bukkit/craftbukkit/block/CraftBlock.java b/src/main/java/org/bukkit/craftbukkit/block/CraftBlock.java +index 6dceb6082538df6f6147254e861d1cec8af7ec50..5cb69d0b822e11a99a96aef4f59986d083b079f4 100644 +--- a/src/main/java/org/bukkit/craftbukkit/block/CraftBlock.java ++++ b/src/main/java/org/bukkit/craftbukkit/block/CraftBlock.java +@@ -509,7 +509,7 @@ public class CraftBlock implements Block { + + // Modelled off EntityHuman#hasBlock + if (block != Blocks.AIR && (item == null || !iblockdata.requiresCorrectToolForDrops() || nmsItem.isCorrectToolForDrops(iblockdata))) { +- net.minecraft.world.level.block.Block.dropResources(iblockdata, this.world.getMinecraftWorld(), this.position, this.world.getBlockEntity(this.position), null, nmsItem); ++ net.minecraft.world.level.block.Block.dropResources(iblockdata, this.world.getMinecraftWorld(), this.position, this.world.getBlockEntity(this.position), null, nmsItem, false); // Paper - Properly handle xp dropping + // Paper start - improve Block#breanNaturally + if (triggerEffect) { + if (iblockdata.getBlock() instanceof net.minecraft.world.level.block.BaseFireBlock) { diff --git a/patches/server/0912-Fixup-NamespacedKey-handling.patch b/patches/server/0912-Fixup-NamespacedKey-handling.patch new file mode 100644 index 000000000000..932cc370d881 --- /dev/null +++ b/patches/server/0912-Fixup-NamespacedKey-handling.patch @@ -0,0 +1,170 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Nassim Jahnke +Date: Sat, 6 Jan 2024 14:31:00 +0100 +Subject: [PATCH] Fixup NamespacedKey handling + + +diff --git a/src/main/java/org/bukkit/craftbukkit/CraftLootTable.java b/src/main/java/org/bukkit/craftbukkit/CraftLootTable.java +index 90b82ad996b2b85628c9a5ddeef9410150b7f70c..5fd22a80e9d05afbea273471cee991732a9485fd 100644 +--- a/src/main/java/org/bukkit/craftbukkit/CraftLootTable.java ++++ b/src/main/java/org/bukkit/craftbukkit/CraftLootTable.java +@@ -38,7 +38,7 @@ public class CraftLootTable implements org.bukkit.loot.LootTable { + } + + public static org.bukkit.loot.LootTable minecraftToBukkit(ResourceKey minecraft) { +- return (minecraft == null) ? null : Bukkit.getLootTable(CraftLootTable.minecraftToBukkitKey(minecraft)); ++ return (minecraft == null || minecraft.location().getPath().isEmpty()) ? null : Bukkit.getLootTable(CraftLootTable.minecraftToBukkitKey(minecraft)); // Paper - fix some NamespacedKey parsing + } + + public static NamespacedKey minecraftToBukkitKey(ResourceKey minecraft) { +diff --git a/src/main/java/org/bukkit/craftbukkit/CraftRegistry.java b/src/main/java/org/bukkit/craftbukkit/CraftRegistry.java +index aa66fd8dca886c1f064d8cb4a3d15c2086c1719a..f8450a2abd1e96fac7827d252cc00038b9dee839 100644 +--- a/src/main/java/org/bukkit/craftbukkit/CraftRegistry.java ++++ b/src/main/java/org/bukkit/craftbukkit/CraftRegistry.java +@@ -122,6 +122,16 @@ public class CraftRegistry implements Registry { + + ", this can happen if a plugin creates its own registry entry with out properly registering it."); + } + ++ // Paper start - fixup upstream being dum ++ public static java.util.Optional unwrapAndConvertHolder(final io.papermc.paper.registry.RegistryKey registryKey, final Holder value) { ++ return unwrapAndConvertHolder(io.papermc.paper.registry.RegistryAccess.registryAccess().getRegistry(registryKey), value); ++ } ++ ++ public static java.util.Optional unwrapAndConvertHolder(final Registry registry, final Holder value) { ++ return value.unwrapKey().map(key -> registry.get(CraftNamespacedKey.fromMinecraft(key.location()))); ++ } ++ // Paper end - fixup upstream being dum ++ + // Paper - move to PaperRegistries + + // Paper - NOTE: As long as all uses of the method below relate to *serialization* via ConfigurationSerializable, it's fine +diff --git a/src/main/java/org/bukkit/craftbukkit/attribute/CraftAttribute.java b/src/main/java/org/bukkit/craftbukkit/attribute/CraftAttribute.java +index cc97638e038ea64ad180ebfded2528aa07d1809e..10e4318782107644f67818109784fff60d017e0a 100644 +--- a/src/main/java/org/bukkit/craftbukkit/attribute/CraftAttribute.java ++++ b/src/main/java/org/bukkit/craftbukkit/attribute/CraftAttribute.java +@@ -37,6 +37,7 @@ public class CraftAttribute { + string = FieldRename.convertAttributeName(ApiVersion.CURRENT, string); + string = string.toLowerCase(Locale.ROOT); + NamespacedKey key = NamespacedKey.fromString(string); ++ if (key == null) return null; // Paper - Fixup NamespacedKey handling + + // Now also convert from when keys where saved + return CraftRegistry.get(Registry.ATTRIBUTE, key, ApiVersion.CURRENT); +diff --git a/src/main/java/org/bukkit/craftbukkit/block/CraftBanner.java b/src/main/java/org/bukkit/craftbukkit/block/CraftBanner.java +index afed8bdb9bd6a135e9b5f7bd9bfc61964cb240f7..bb2d1dddca6bfe719b28df136e80a7c5a339a5ce 100644 +--- a/src/main/java/org/bukkit/craftbukkit/block/CraftBanner.java ++++ b/src/main/java/org/bukkit/craftbukkit/block/CraftBanner.java +@@ -38,7 +38,11 @@ public class CraftBanner extends CraftBlockEntityState implem + if (banner.getPatterns() != null) { + for (int i = 0; i < banner.getPatterns().layers().size(); i++) { + BannerPatternLayers.Layer p = banner.getPatterns().layers().get(i); +- this.patterns.add(new Pattern(DyeColor.getByWoolData((byte) p.color().getId()), CraftPatternType.minecraftHolderToBukkit(p.pattern()))); ++ // Paper start - fix upstream not handling inlined banner pattern ++ java.util.Optional type = org.bukkit.craftbukkit.CraftRegistry.unwrapAndConvertHolder(org.bukkit.Registry.BANNER_PATTERN, p.pattern()); ++ if (type.isEmpty()) continue; ++ this.patterns.add(new Pattern(DyeColor.getByWoolData((byte) p.color().getId()), type.get())); ++ // Paper end - fix upstream not handling inlined banner pattern + } + } + } +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftPainting.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftPainting.java +index bcac1359c667ef1ee46384f9c7a5adf4010d2b08..98a4463c9f194f33f4f85d95a0b9fa061cf6faaf 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftPainting.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftPainting.java +@@ -16,7 +16,7 @@ public class CraftPainting extends CraftHanging implements Painting { + + @Override + public Art getArt() { +- return CraftArt.minecraftHolderToBukkit(this.getHandle().getVariant()); ++ return org.bukkit.craftbukkit.CraftRegistry.unwrapAndConvertHolder(org.bukkit.Registry.ART, this.getHandle().getVariant()).orElseThrow(() -> new IllegalStateException("Inlined/custom painting variants are not supported yet in the API!")); // Paper + } + + @Override +diff --git a/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaArmor.java b/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaArmor.java +index 0fdd9dd47594a7e7e785c34c09d9b4a79aad2439..0d3b1692af010bfd7ea83e22e9571dc954906f71 100644 +--- a/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaArmor.java ++++ b/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaArmor.java +@@ -38,8 +38,9 @@ public class CraftMetaArmor extends CraftMetaItem implements ArmorMeta { + super(tag); + + getOrEmpty(tag, CraftMetaArmor.TRIM).ifPresent((trimCompound) -> { +- TrimMaterial trimMaterial = CraftTrimMaterial.minecraftHolderToBukkit(trimCompound.material()); +- TrimPattern trimPattern = CraftTrimPattern.minecraftHolderToBukkit(trimCompound.pattern()); ++ TrimMaterial trimMaterial = org.bukkit.craftbukkit.CraftRegistry.unwrapAndConvertHolder(io.papermc.paper.registry.RegistryKey.TRIM_MATERIAL, trimCompound.material()).orElse(null); // Paper - fix upstream not being correct ++ TrimPattern trimPattern = org.bukkit.craftbukkit.CraftRegistry.unwrapAndConvertHolder(io.papermc.paper.registry.RegistryKey.TRIM_PATTERN, trimCompound.pattern()).orElse(null); // Paper - fix upstream not being correct ++ if (trimMaterial == null || trimPattern == null) return; // Paper - just delete the trim because upstream is not doing this right + + this.trim = new ArmorTrim(trimMaterial, trimPattern); + +diff --git a/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaBanner.java b/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaBanner.java +index 1c1a2d66d1ebcbe2ded732e759d0f9d471d43b56..eb44c19f6af624df458981e46c73a64358d6e1ce 100644 +--- a/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaBanner.java ++++ b/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaBanner.java +@@ -42,7 +42,7 @@ public class CraftMetaBanner extends CraftMetaItem implements BannerMeta { + for (int i = 0; i < Math.min(patterns.size(), 20); i++) { + BannerPatternLayers.Layer p = patterns.get(i); + DyeColor color = DyeColor.getByWoolData((byte) p.color().getId()); +- PatternType pattern = CraftPatternType.minecraftHolderToBukkit(p.pattern()); ++ PatternType pattern = org.bukkit.craftbukkit.CraftRegistry.unwrapAndConvertHolder(org.bukkit.Registry.BANNER_PATTERN, p.pattern()).orElse(null); // Paper - fix upstream not handling inlined banner pattern + + if (color != null && pattern != null) { + this.patterns.add(new Pattern(color, pattern)); +diff --git a/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaMusicInstrument.java b/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaMusicInstrument.java +index 478059eb3ad76b41e6a20e9b489a2a4fb19e7c7c..76a3e4893cbdba903a712d6db1d30b9c644795be 100644 +--- a/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaMusicInstrument.java ++++ b/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaMusicInstrument.java +@@ -30,7 +30,7 @@ public class CraftMetaMusicInstrument extends CraftMetaItem implements MusicInst + super(tag); + + getOrEmpty(tag, CraftMetaMusicInstrument.GOAT_HORN_INSTRUMENT).ifPresent((instrument) -> { +- this.instrument = CraftMusicInstrument.minecraftHolderToBukkit(instrument); ++ this.instrument = org.bukkit.craftbukkit.CraftRegistry.unwrapAndConvertHolder(org.bukkit.Registry.INSTRUMENT, instrument).orElse(null); // Paper - fix upstream not handling inlined instrument + }); + } + +diff --git a/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaShield.java b/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaShield.java +index c8eec04685456d89cb41466cddcc3975d0ceeb29..bcd6cc29e4e621805cbd923d747f652ced240c6d 100644 +--- a/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaShield.java ++++ b/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaShield.java +@@ -17,6 +17,7 @@ import org.bukkit.block.BlockState; + import org.bukkit.block.banner.Pattern; + import org.bukkit.block.banner.PatternType; + import org.bukkit.configuration.serialization.DelegateDeserialization; ++import org.bukkit.craftbukkit.CraftRegistry; + import org.bukkit.craftbukkit.block.CraftBlockStates; + import org.bukkit.craftbukkit.block.banner.CraftPatternType; + import org.bukkit.inventory.meta.BlockStateMeta; +@@ -53,7 +54,7 @@ public class CraftMetaShield extends CraftMetaItem implements ShieldMeta, BlockS + for (int i = 0; i < Math.min(patterns.size(), 20); i++) { + BannerPatternLayers.Layer p = patterns.get(i); + DyeColor color = DyeColor.getByWoolData((byte) p.color().getId()); +- PatternType pattern = CraftPatternType.minecraftHolderToBukkit(p.pattern()); ++ PatternType pattern = CraftRegistry.unwrapAndConvertHolder(io.papermc.paper.registry.RegistryKey.BANNER_PATTERN, p.pattern()).orElse(null); // Paper - fix upstream not being correct + + if (color != null && pattern != null) { + this.addPattern(new Pattern(color, pattern)); +diff --git a/src/main/java/org/bukkit/craftbukkit/potion/CraftPotionType.java b/src/main/java/org/bukkit/craftbukkit/potion/CraftPotionType.java +index 82a50b06c08b632f77d73745e1fa9bd22dfd950a..f1d8ed4a2b8959873b02d57f6a40323a841f3d7f 100644 +--- a/src/main/java/org/bukkit/craftbukkit/potion/CraftPotionType.java ++++ b/src/main/java/org/bukkit/craftbukkit/potion/CraftPotionType.java +@@ -69,6 +69,7 @@ public class CraftPotionType implements PotionType.InternalPotionData { + string = FieldRename.convertPotionTypeName(ApiVersion.CURRENT, string); + string = string.toLowerCase(Locale.ROOT); + NamespacedKey key = NamespacedKey.fromString(string); ++ if (key == null) return null; // Paper - Fixup NamespacedKey handling + + // Now also convert from when keys where saved + return CraftRegistry.get(Registry.POTION, key, ApiVersion.CURRENT); +diff --git a/src/main/java/org/bukkit/craftbukkit/util/CraftNamespacedKey.java b/src/main/java/org/bukkit/craftbukkit/util/CraftNamespacedKey.java +index dc88ba24ed3b0024c39a30c2d90628fc708d63cf..944bed9b6c803df1a312383fed9de7d61e7d2c70 100644 +--- a/src/main/java/org/bukkit/craftbukkit/util/CraftNamespacedKey.java ++++ b/src/main/java/org/bukkit/craftbukkit/util/CraftNamespacedKey.java +@@ -13,7 +13,7 @@ public final class CraftNamespacedKey { + return null; + } + ResourceLocation minecraft = ResourceLocation.tryParse(string); +- return (minecraft == null) ? null : CraftNamespacedKey.fromMinecraft(minecraft); ++ return (minecraft == null || minecraft.getPath().isEmpty()) ? null : CraftNamespacedKey.fromMinecraft(minecraft); // Paper - Bukkit's parser does not match Vanilla for empty paths + } + + public static NamespacedKey fromString(String string) { diff --git a/patches/server/0921-Expose-LootTable-of-DecoratedPot.patch b/patches/server/0913-Expose-LootTable-of-DecoratedPot.patch similarity index 100% rename from patches/server/0921-Expose-LootTable-of-DecoratedPot.patch rename to patches/server/0913-Expose-LootTable-of-DecoratedPot.patch diff --git a/patches/server/0913-Improve-Registry.patch b/patches/server/0913-Improve-Registry.patch deleted file mode 100644 index 2f04f6238338..000000000000 --- a/patches/server/0913-Improve-Registry.patch +++ /dev/null @@ -1,102 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Jake Potrebic -Date: Wed, 20 Dec 2023 02:03:05 -0800 -Subject: [PATCH] Improve Registry - - -diff --git a/src/main/java/org/bukkit/craftbukkit/CraftRegistry.java b/src/main/java/org/bukkit/craftbukkit/CraftRegistry.java -index 3dbdfc2fb973c3c9aecc6582451071e8a939f5f0..c410e2d94562afc6bdd5bb3c9c01995eac0bc3fc 100644 ---- a/src/main/java/org/bukkit/craftbukkit/CraftRegistry.java -+++ b/src/main/java/org/bukkit/craftbukkit/CraftRegistry.java -@@ -155,6 +155,7 @@ public class CraftRegistry implements Registry { - - private final Class bukkitClass; // Paper - relax preload class - private final Map cache = new HashMap<>(); -+ private final Map byValue = new java.util.IdentityHashMap<>(); // Paper - improve Registry - private final net.minecraft.core.Registry minecraftRegistry; - private final BiFunction minecraftToBukkit; - private final BiFunction serializationUpdater; // Paper - rename to make it *clear* what it is *only* for -@@ -203,6 +204,7 @@ public class CraftRegistry implements Registry { - } - - this.cache.put(namespacedKey, bukkit); -+ this.byValue.put(bukkit, namespacedKey); // Paper - improve Registry - - return bukkit; - } -@@ -235,4 +237,11 @@ public class CraftRegistry implements Registry { - - return this.minecraftToBukkit.apply(namespacedKey, minecraft); - } -+ -+ // Paper start - improve Registry -+ @Override -+ public NamespacedKey getKey(final B value) { -+ return this.byValue.get(value); -+ } -+ // Paper end - improve Registry - } -diff --git a/src/main/java/org/bukkit/craftbukkit/inventory/trim/CraftTrimMaterial.java b/src/main/java/org/bukkit/craftbukkit/inventory/trim/CraftTrimMaterial.java -index cd3e35867075e65f46051fb88d8a2460a8bb4b53..76627683f256a034a147765db693a9fd2ab9613f 100644 ---- a/src/main/java/org/bukkit/craftbukkit/inventory/trim/CraftTrimMaterial.java -+++ b/src/main/java/org/bukkit/craftbukkit/inventory/trim/CraftTrimMaterial.java -@@ -54,6 +54,7 @@ public class CraftTrimMaterial implements TrimMaterial, Handleable this + " doesn't have a key"); // Paper - return this.key; - } - -diff --git a/src/main/java/org/bukkit/craftbukkit/inventory/trim/CraftTrimPattern.java b/src/main/java/org/bukkit/craftbukkit/inventory/trim/CraftTrimPattern.java -index 364f8d7a7106259401154d91b1b79869d014a469..f336bf98574e4fdeabc3b210629834393ec11a74 100644 ---- a/src/main/java/org/bukkit/craftbukkit/inventory/trim/CraftTrimPattern.java -+++ b/src/main/java/org/bukkit/craftbukkit/inventory/trim/CraftTrimPattern.java -@@ -54,6 +54,7 @@ public class CraftTrimPattern implements TrimPattern, Handleable this + " doesn't have a key"); // Paper - return this.key; - } - -diff --git a/src/test/java/org/bukkit/registry/PerRegistryTest.java b/src/test/java/org/bukkit/registry/PerRegistryTest.java -index 18859eea522ff26cbefb5bbc5065b5369ed6c189..319e000519fd719cea0e6daf2ba9cfa67e6958a3 100644 ---- a/src/test/java/org/bukkit/registry/PerRegistryTest.java -+++ b/src/test/java/org/bukkit/registry/PerRegistryTest.java -@@ -49,19 +49,22 @@ public class PerRegistryTest { - - @ParameterizedTest - @MethodSource("data") -- public void testGet(Registry registry) { -+ public void testGet(Registry registry) { // Paper - improve Registry - registry.forEach(element -> { -+ NamespacedKey key = registry.getKey(element); // Paper - improve Registry -+ assertNotNull(key); // Paper - improve Registry - // Values in the registry should be referentially equal to what is returned with #get() - // This ensures that new instances are not created each time #get() is invoked -- assertSame(element, registry.get(element.getKey())); -+ assertSame(element, registry.get(key)); // Paper - improve Registry - }); - } - - @ParameterizedTest - @MethodSource("data") -- public void testMatch(Registry registry) { -+ public void testMatch(Registry registry) { // Paper - improve Registry - registry.forEach(element -> { -- NamespacedKey key = element.getKey(); -+ NamespacedKey key = registry.getKey(element); // Paper - improve Registry -+ assertNotNull(key); // Paper - improve Registry - - this.assertSameMatchWithKeyMessage(registry, element, key.toString()); // namespace:key - this.assertSameMatchWithKeyMessage(registry, element, key.getKey()); // key -@@ -72,7 +75,7 @@ public class PerRegistryTest { - }); - } - -- private void assertSameMatchWithKeyMessage(Registry registry, Keyed element, String key) { -+ private void assertSameMatchWithKeyMessage(Registry registry, T element, String key) { // Paper - improve Registry - assertSame(element, registry.match(key), key); - } - diff --git a/patches/server/0914-Fix-NPE-on-null-loc-for-EntityTeleportEvent.patch b/patches/server/0914-Fix-NPE-on-null-loc-for-EntityTeleportEvent.patch deleted file mode 100644 index d0dc04a7eb7d..000000000000 --- a/patches/server/0914-Fix-NPE-on-null-loc-for-EntityTeleportEvent.patch +++ /dev/null @@ -1,66 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Jake Potrebic -Date: Sat, 9 Dec 2023 19:15:59 -0800 -Subject: [PATCH] Fix NPE on null loc for EntityTeleportEvent - -EntityTeleportEvent#setTo is marked as nullable and so is the -getTo method. This fixes the handling of a null "to" location -by treating it the same as the event being cancelled. This is -already existing behavior for the EntityPortalEvent (which -extends EntityTeleportEvent). - -diff --git a/src/main/java/net/minecraft/server/commands/TeleportCommand.java b/src/main/java/net/minecraft/server/commands/TeleportCommand.java -index a306b30af19277386a2f3e560b4902a8b5796f2a..54851f6cc0d5fddb32a9a1e84a4f5ae41af18758 100644 ---- a/src/main/java/net/minecraft/server/commands/TeleportCommand.java -+++ b/src/main/java/net/minecraft/server/commands/TeleportCommand.java -@@ -169,9 +169,10 @@ public class TeleportCommand { - Location to = new Location(world.getWorld(), x, y, z, f2, f3); - EntityTeleportEvent event = new EntityTeleportEvent(target.getBukkitEntity(), target.getBukkitEntity().getLocation(), to); - world.getCraftServer().getPluginManager().callEvent(event); -- if (event.isCancelled()) { -+ if (event.isCancelled() || event.getTo() == null) { // Paper - return; - } -+ to = event.getTo(); // Paper - actually track new location - - x = to.getX(); - y = to.getY(); -diff --git a/src/main/java/net/minecraft/world/entity/LivingEntity.java b/src/main/java/net/minecraft/world/entity/LivingEntity.java -index 6b2dc430126713e11658f94762035ff10bbe018c..13ef1ad250b56dbadba0244186e369d7ba9b5c0e 100644 ---- a/src/main/java/net/minecraft/world/entity/LivingEntity.java -+++ b/src/main/java/net/minecraft/world/entity/LivingEntity.java -@@ -4232,7 +4232,7 @@ public abstract class LivingEntity extends Entity implements Attackable { - if (!(this instanceof ServerPlayer)) { - EntityTeleportEvent teleport = new EntityTeleportEvent(this.getBukkitEntity(), new Location(this.level().getWorld(), d3, d4, d5), new Location(this.level().getWorld(), d0, d6, d2)); - this.level().getCraftServer().getPluginManager().callEvent(teleport); -- if (!teleport.isCancelled()) { -+ if (!teleport.isCancelled() && teleport.getTo() != null) { // Paper - Location to = teleport.getTo(); - this.teleportTo(to.getX(), to.getY(), to.getZ()); - } else { -diff --git a/src/main/java/net/minecraft/world/entity/TamableAnimal.java b/src/main/java/net/minecraft/world/entity/TamableAnimal.java -index 87c669517baa923ffa7392e400e7344e81fc9406..9aee0569447d351729c26eedbe24d5defe620162 100644 ---- a/src/main/java/net/minecraft/world/entity/TamableAnimal.java -+++ b/src/main/java/net/minecraft/world/entity/TamableAnimal.java -@@ -303,7 +303,7 @@ public abstract class TamableAnimal extends Animal implements OwnableEntity { - } else { - // CraftBukkit start - EntityTeleportEvent event = CraftEventFactory.callEntityTeleportEvent(this, (double) x + 0.5D, (double) y, (double) z + 0.5D); -- if (event.isCancelled()) { -+ if (event.isCancelled() || event.getTo() == null) { // Paper - prevent NP on null event to location - return false; - } - Location to = event.getTo(); -diff --git a/src/main/java/net/minecraft/world/entity/monster/Shulker.java b/src/main/java/net/minecraft/world/entity/monster/Shulker.java -index 632b74e84d6ee58da8806e30b75e16fb864afa64..bf58956379d0a5dbfdc34e8626847638b4111433 100644 ---- a/src/main/java/net/minecraft/world/entity/monster/Shulker.java -+++ b/src/main/java/net/minecraft/world/entity/monster/Shulker.java -@@ -415,7 +415,7 @@ public class Shulker extends AbstractGolem implements VariantHolder +Date: Mon, 27 Apr 2020 00:04:16 -0700 +Subject: [PATCH] Reduce allocation of Vec3D by entity tracker + + +diff --git a/src/main/java/net/minecraft/network/protocol/game/VecDeltaCodec.java b/src/main/java/net/minecraft/network/protocol/game/VecDeltaCodec.java +index a043ac10834562d357ef0b5aded2e916e2a0d056..74276c368016fcc4dbf9579b2ecbadc9614baf15 100644 +--- a/src/main/java/net/minecraft/network/protocol/game/VecDeltaCodec.java ++++ b/src/main/java/net/minecraft/network/protocol/game/VecDeltaCodec.java +@@ -5,7 +5,7 @@ import org.jetbrains.annotations.VisibleForTesting; + + public class VecDeltaCodec { + private static final double TRUNCATION_STEPS = 4096.0; +- private Vec3 base = Vec3.ZERO; ++ public Vec3 base = Vec3.ZERO; // Paper + + @VisibleForTesting + static long encode(double value) { +diff --git a/src/main/java/net/minecraft/server/level/ChunkMap.java b/src/main/java/net/minecraft/server/level/ChunkMap.java +index 7483f9f2639c58a4f43e264211791f4377e1db64..7317c353edab8b11d9d94e257f968ac49284f47a 100644 +--- a/src/main/java/net/minecraft/server/level/ChunkMap.java ++++ b/src/main/java/net/minecraft/server/level/ChunkMap.java +@@ -1572,10 +1572,14 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider + public void updatePlayer(ServerPlayer player) { + org.spigotmc.AsyncCatcher.catchOp("player tracker update"); // Spigot + if (player != this.entity) { +- Vec3 vec3d = player.position().subtract(this.entity.position()); ++ // Paper start - remove allocation of Vec3D here ++ // Vec3 vec3d = player.position().subtract(this.entity.position()); ++ double vec3d_dx = player.getX() - this.entity.getX(); ++ double vec3d_dz = player.getZ() - this.entity.getZ(); ++ // Paper end - remove allocation of Vec3D here + int i = ChunkMap.this.getPlayerViewDistance(player); + double d0 = (double) Math.min(this.getEffectiveRange(), i * 16); +- double d1 = vec3d.x * vec3d.x + vec3d.z * vec3d.z; ++ double d1 = vec3d_dx * vec3d_dx + vec3d_dz * vec3d_dz; // Paper + double d2 = d0 * d0; + // Paper start - Configurable entity tracking range by Y + boolean flag = d1 <= d2; +diff --git a/src/main/java/net/minecraft/server/level/ServerEntity.java b/src/main/java/net/minecraft/server/level/ServerEntity.java +index 7118e1f806af98159ec292f9340d7e4004e2b486..f3456aeeab7eee5b6d0383a4bf1338dd8cc95bb3 100644 +--- a/src/main/java/net/minecraft/server/level/ServerEntity.java ++++ b/src/main/java/net/minecraft/server/level/ServerEntity.java +@@ -178,7 +178,13 @@ public class ServerEntity { + + ++this.teleportDelay; + Vec3 vec3d = this.entity.trackingPosition(); +- boolean flag1 = this.positionCodec.delta(vec3d).lengthSqr() >= 7.62939453125E-6D; ++ // Paper start - reduce allocation of Vec3D here ++ Vec3 base = this.positionCodec.base; ++ double vec3d_dx = vec3d.x - base.x; ++ double vec3d_dy = vec3d.y - base.y; ++ double vec3d_dz = vec3d.z - base.z; ++ boolean flag1 = (vec3d_dx * vec3d_dx + vec3d_dy * vec3d_dy + vec3d_dz * vec3d_dz) >= 7.62939453125E-6D; ++ // Paper end - reduce allocation of Vec3D here + Packet packet1 = null; + boolean flag2 = flag1 || this.tickCount % 60 == 0; + boolean flag3 = false; diff --git a/patches/server/0915-Add-PlayerTradeEvent-and-PlayerPurchaseEvent.patch b/patches/server/0915-Add-PlayerTradeEvent-and-PlayerPurchaseEvent.patch new file mode 100644 index 000000000000..4ed1121ea18c --- /dev/null +++ b/patches/server/0915-Add-PlayerTradeEvent-and-PlayerPurchaseEvent.patch @@ -0,0 +1,240 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Jake Potrebic +Date: Thu, 2 Jul 2020 16:12:10 -0700 +Subject: [PATCH] Add PlayerTradeEvent and PlayerPurchaseEvent + +Co-authored-by: Alexander + +diff --git a/src/main/java/net/minecraft/world/entity/npc/AbstractVillager.java b/src/main/java/net/minecraft/world/entity/npc/AbstractVillager.java +index 39cb40b077e9c07471437d5bec16ba9a7e6bce52..5f656fc726a1dc5f42657095a2f2b7cf85b92d7c 100644 +--- a/src/main/java/net/minecraft/world/entity/npc/AbstractVillager.java ++++ b/src/main/java/net/minecraft/world/entity/npc/AbstractVillager.java +@@ -141,11 +141,24 @@ public abstract class AbstractVillager extends AgeableMob implements InventoryCa + @Override + public void overrideXp(int experience) {} + ++ // Paper start - Add PlayerTradeEvent and PlayerPurchaseEvent ++ @Override ++ public void processTrade(MerchantOffer recipe, @Nullable io.papermc.paper.event.player.PlayerPurchaseEvent event) { // The MerchantRecipe passed in here is the one set by the PlayerPurchaseEvent ++ if (event == null || event.willIncreaseTradeUses()) { ++ recipe.increaseUses(); ++ } ++ if (event == null || event.isRewardingExp()) { ++ this.rewardTradeXp(recipe); ++ } ++ this.notifyTrade(recipe); ++ } ++ // Paper end - Add PlayerTradeEvent and PlayerPurchaseEvent ++ + @Override + public void notifyTrade(MerchantOffer offer) { +- offer.increaseUses(); ++ // offer.increaseUses(); // Paper - Add PlayerTradeEvent and PlayerPurchaseEvent + this.ambientSoundTime = -this.getAmbientSoundInterval(); +- this.rewardTradeXp(offer); ++ // this.rewardTradeXp(offer); // Paper - Add PlayerTradeEvent and PlayerPurchaseEvent + if (this.tradingPlayer instanceof ServerPlayer) { + CriteriaTriggers.TRADE.trigger((ServerPlayer) this.tradingPlayer, this, offer.getResult()); + } +diff --git a/src/main/java/net/minecraft/world/inventory/AbstractContainerMenu.java b/src/main/java/net/minecraft/world/inventory/AbstractContainerMenu.java +index 85caec29a705f216eff8a5ae11f250697891a05c..78d0ff45c016e900d87010e8b26b0bb10e63f445 100644 +--- a/src/main/java/net/minecraft/world/inventory/AbstractContainerMenu.java ++++ b/src/main/java/net/minecraft/world/inventory/AbstractContainerMenu.java +@@ -831,6 +831,14 @@ public abstract class AbstractContainerMenu { + public abstract boolean stillValid(Player player); + + protected boolean moveItemStackTo(ItemStack stack, int startIndex, int endIndex, boolean fromLast) { ++ // Paper start - Add PlayerTradeEvent and PlayerPurchaseEvent ++ return this.moveItemStackTo(stack, startIndex, endIndex, fromLast, false); ++ } ++ protected boolean moveItemStackTo(ItemStack stack, int startIndex, int endIndex, boolean fromLast, boolean isCheck) { ++ if (isCheck) { ++ stack = stack.copy(); ++ } ++ // Paper end - Add PlayerTradeEvent and PlayerPurchaseEvent + boolean flag1 = false; + int k = startIndex; + +@@ -854,6 +862,11 @@ public abstract class AbstractContainerMenu { + + slot = (Slot) this.slots.get(k); + itemstack1 = slot.getItem(); ++ // Paper start - Add PlayerTradeEvent and PlayerPurchaseEvent; clone if only a check ++ if (isCheck) { ++ itemstack1 = itemstack1.copy(); ++ } ++ // Paper end - Add PlayerTradeEvent and PlayerPurchaseEvent + if (!itemstack1.isEmpty() && ItemStack.isSameItemSameComponents(stack, itemstack1)) { + l = itemstack1.getCount() + stack.getCount(); + int i1 = slot.getMaxStackSize(itemstack1); +@@ -861,12 +874,16 @@ public abstract class AbstractContainerMenu { + if (l <= i1) { + stack.setCount(0); + itemstack1.setCount(l); ++ if (!isCheck) { // Paper - Add PlayerTradeEvent and PlayerPurchaseEvent + slot.setChanged(); ++ } // Paper - Add PlayerTradeEvent and PlayerPurchaseEvent + flag1 = true; + } else if (itemstack1.getCount() < i1) { + stack.shrink(i1 - itemstack1.getCount()); + itemstack1.setCount(i1); ++ if (!isCheck) { // Paper - Add PlayerTradeEvent and PlayerPurchaseEvent + slot.setChanged(); ++ } // Paper - Add PlayerTradeEvent and PlayerPurchaseEvent + flag1 = true; + } + } +@@ -897,10 +914,21 @@ public abstract class AbstractContainerMenu { + + slot = (Slot) this.slots.get(k); + itemstack1 = slot.getItem(); ++ // Paper start - Add PlayerTradeEvent and PlayerPurchaseEvent ++ if (isCheck) { ++ itemstack1 = itemstack1.copy(); ++ } ++ // Paper end - Add PlayerTradeEvent and PlayerPurchaseEvent + if (itemstack1.isEmpty() && slot.mayPlace(stack)) { + l = slot.getMaxStackSize(stack); ++ // Paper start - Add PlayerTradeEvent and PlayerPurchaseEvent ++ if (isCheck) { ++ stack.shrink(Math.min(stack.getCount(), l)); ++ } else { ++ // Paper end - Add PlayerTradeEvent and PlayerPurchaseEvent + slot.setByPlayer(stack.split(Math.min(stack.getCount(), l))); + slot.setChanged(); ++ } // Paper - Add PlayerTradeEvent and PlayerPurchaseEvent + flag1 = true; + break; + } +diff --git a/src/main/java/net/minecraft/world/inventory/MerchantMenu.java b/src/main/java/net/minecraft/world/inventory/MerchantMenu.java +index 90560dfad263d42432dc034a29930e6bab953b9a..6a529b5e289c416c0ebdc0260086ec039777aa40 100644 +--- a/src/main/java/net/minecraft/world/inventory/MerchantMenu.java ++++ b/src/main/java/net/minecraft/world/inventory/MerchantMenu.java +@@ -123,12 +123,12 @@ public class MerchantMenu extends AbstractContainerMenu { + + itemstack = itemstack1.copy(); + if (slot == 2) { +- if (!this.moveItemStackTo(itemstack1, 3, 39, true)) { ++ if (!this.moveItemStackTo(itemstack1, 3, 39, true, true)) { // Paper - Add PlayerTradeEvent and PlayerPurchaseEvent + return ItemStack.EMPTY; + } + +- slot1.onQuickCraft(itemstack1, itemstack); +- this.playTradeSound(); ++ // slot1.onQuickCraft(itemstack1, itemstack); // Paper - Add PlayerTradeEvent and PlayerPurchaseEvent; moved to after the non-check moveItemStackTo call ++ // this.playTradeSound(); + } else if (slot != 0 && slot != 1) { + if (slot >= 3 && slot < 30) { + if (!this.moveItemStackTo(itemstack1, 30, 39, false)) { +@@ -141,6 +141,7 @@ public class MerchantMenu extends AbstractContainerMenu { + return ItemStack.EMPTY; + } + ++ if (slot != 2) { // Paper - Add PlayerTradeEvent and PlayerPurchaseEvent; moved down for slot 2 + if (itemstack1.isEmpty()) { + slot1.setByPlayer(ItemStack.EMPTY); + } else { +@@ -152,6 +153,21 @@ public class MerchantMenu extends AbstractContainerMenu { + } + + slot1.onTake(player, itemstack1); ++ } // Paper start - Add PlayerTradeEvent and PlayerPurchaseEvent; handle slot 2 ++ if (slot == 2) { // is merchant result slot ++ slot1.onTake(player, itemstack1); ++ if (itemstack1.isEmpty()) { ++ slot1.set(ItemStack.EMPTY); ++ return ItemStack.EMPTY; ++ } ++ ++ this.moveItemStackTo(itemstack1, 3, 39, true, false); // This should always succeed because it's checked above ++ ++ slot1.onQuickCraft(itemstack1, itemstack); ++ this.playTradeSound(); ++ slot1.set(ItemStack.EMPTY); // itemstack1 should ALWAYS be empty ++ } ++ // Paper end - Add PlayerTradeEvent and PlayerPurchaseEvent + } + + return itemstack; +diff --git a/src/main/java/net/minecraft/world/inventory/MerchantResultSlot.java b/src/main/java/net/minecraft/world/inventory/MerchantResultSlot.java +index 26227033613a641a9d5a6f29356b19e54753b3f1..c2376538215604210d08acd33e8e5fdc8a35c7d3 100644 +--- a/src/main/java/net/minecraft/world/inventory/MerchantResultSlot.java ++++ b/src/main/java/net/minecraft/world/inventory/MerchantResultSlot.java +@@ -47,13 +47,32 @@ public class MerchantResultSlot extends Slot { + + @Override + public void onTake(Player player, ItemStack stack) { +- this.checkTakeAchievements(stack); ++ // this.checkTakeAchievements(stack); // Paper - Add PlayerTradeEvent and PlayerPurchaseEvent; move to after event is called and not cancelled + MerchantOffer merchantOffer = this.slots.getActiveOffer(); ++ // Paper start - Add PlayerTradeEvent and PlayerPurchaseEvent ++ io.papermc.paper.event.player.PlayerPurchaseEvent event = null; ++ if (merchantOffer != null && player instanceof net.minecraft.server.level.ServerPlayer serverPlayer) { ++ if (this.merchant instanceof net.minecraft.world.entity.npc.AbstractVillager abstractVillager) { ++ event = new io.papermc.paper.event.player.PlayerTradeEvent(serverPlayer.getBukkitEntity(), (org.bukkit.entity.AbstractVillager) abstractVillager.getBukkitEntity(), merchantOffer.asBukkit(), true, true); ++ } else if (this.merchant instanceof org.bukkit.craftbukkit.inventory.CraftMerchantCustom.MinecraftMerchant) { ++ event = new io.papermc.paper.event.player.PlayerPurchaseEvent(serverPlayer.getBukkitEntity(), merchantOffer.asBukkit(), false, true); ++ } ++ if (event != null) { ++ if (!event.callEvent()) { ++ stack.setCount(0); ++ event.getPlayer().updateInventory(); ++ return; ++ } ++ merchantOffer = org.bukkit.craftbukkit.inventory.CraftMerchantRecipe.fromBukkit(event.getTrade()).toMinecraft(); ++ } ++ } ++ this.checkTakeAchievements(stack); ++ // Paper end - Add PlayerTradeEvent and PlayerPurchaseEvent + if (merchantOffer != null) { + ItemStack itemStack = this.slots.getItem(0); + ItemStack itemStack2 = this.slots.getItem(1); + if (merchantOffer.take(itemStack, itemStack2) || merchantOffer.take(itemStack2, itemStack)) { +- this.merchant.notifyTrade(merchantOffer); ++ this.merchant.processTrade(merchantOffer, event); // Paper - Add PlayerTradeEvent and PlayerPurchaseEvent + player.awardStat(Stats.TRADED_WITH_VILLAGER); + this.slots.setItem(0, itemStack); + this.slots.setItem(1, itemStack2); +diff --git a/src/main/java/net/minecraft/world/item/trading/Merchant.java b/src/main/java/net/minecraft/world/item/trading/Merchant.java +index 5a350948a4735902f5c612592bc9d100445a0c8a..716b30dcd7e63c66736c448dd136c9f74dc7fe43 100644 +--- a/src/main/java/net/minecraft/world/item/trading/Merchant.java ++++ b/src/main/java/net/minecraft/world/item/trading/Merchant.java +@@ -20,6 +20,7 @@ public interface Merchant { + + void overrideOffers(MerchantOffers offers); + ++ default void processTrade(MerchantOffer merchantRecipe, @Nullable io.papermc.paper.event.player.PlayerPurchaseEvent event) { this.notifyTrade(merchantRecipe); } // Paper + void notifyTrade(MerchantOffer offer); + + void notifyTradeUpdated(ItemStack stack); +diff --git a/src/main/java/org/bukkit/craftbukkit/inventory/CraftMerchantCustom.java b/src/main/java/org/bukkit/craftbukkit/inventory/CraftMerchantCustom.java +index ef47eb9a321a7b082413d3986d3d394afb899610..54debe9da0a26aea02c964fdc7efb372e07974c0 100644 +--- a/src/main/java/org/bukkit/craftbukkit/inventory/CraftMerchantCustom.java ++++ b/src/main/java/org/bukkit/craftbukkit/inventory/CraftMerchantCustom.java +@@ -76,10 +76,25 @@ public class CraftMerchantCustom implements CraftMerchant { + return this.trades; + } + ++ // Paper start - Add PlayerTradeEvent and PlayerPurchaseEvent ++ @Override ++ public void processTrade(MerchantOffer merchantRecipe, @javax.annotation.Nullable io.papermc.paper.event.player.PlayerPurchaseEvent event) { // The MerchantRecipe passed in here is the one set by the PlayerPurchaseEvent ++ /** Based on {@link net.minecraft.world.entity.npc.AbstractVillager#processTrade(MerchantOffer, io.papermc.paper.event.player.PlayerPurchaseEvent)} */ ++ if (getTradingPlayer() instanceof net.minecraft.server.level.ServerPlayer) { ++ if (event == null || event.willIncreaseTradeUses()) { ++ merchantRecipe.increaseUses(); ++ } ++ if (event == null || event.isRewardingExp()) { ++ this.tradingPlayer.level().addFreshEntity(new net.minecraft.world.entity.ExperienceOrb(this.tradingPlayer.level(), this.tradingPlayer.getX(), this.tradingPlayer.getY(), this.tradingPlayer.getZ(), merchantRecipe.getXp(), org.bukkit.entity.ExperienceOrb.SpawnReason.VILLAGER_TRADE, this.tradingPlayer, null)); ++ } ++ } ++ this.notifyTrade(merchantRecipe); ++ } ++ // Paper end - Add PlayerTradeEvent and PlayerPurchaseEvent + @Override + public void notifyTrade(MerchantOffer offer) { + // increase recipe's uses +- offer.increaseUses(); ++ // offer.increaseUses(); // Paper - Add PlayerTradeEvent and PlayerPurchaseEvent; handled above in processTrade + } + + @Override diff --git a/patches/server/0915-Add-experience-points-API.patch b/patches/server/0915-Add-experience-points-API.patch deleted file mode 100644 index fc3044601643..000000000000 --- a/patches/server/0915-Add-experience-points-API.patch +++ /dev/null @@ -1,73 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Lukas Planz -Date: Tue, 5 Sep 2023 20:34:20 +0200 -Subject: [PATCH] Add experience points API - - -diff --git a/src/main/java/net/minecraft/world/entity/player/Player.java b/src/main/java/net/minecraft/world/entity/player/Player.java -index 950ce40d268d89ff3c503116081db6c9ccd65329..454b29d8c9e42e328933aa578f49d28f1e77898a 100644 ---- a/src/main/java/net/minecraft/world/entity/player/Player.java -+++ b/src/main/java/net/minecraft/world/entity/player/Player.java -@@ -1869,7 +1869,7 @@ public abstract class Player extends LivingEntity { - } - - public int getXpNeededForNextLevel() { -- return this.experienceLevel >= 30 ? 112 + (this.experienceLevel - 30) * 9 : (this.experienceLevel >= 15 ? 37 + (this.experienceLevel - 15) * 5 : 7 + this.experienceLevel * 2); -+ return this.experienceLevel >= 30 ? 112 + (this.experienceLevel - 30) * 9 : (this.experienceLevel >= 15 ? 37 + (this.experienceLevel - 15) * 5 : 7 + this.experienceLevel * 2); // Paper - diff on change; calculateTotalExperiencePoints - } - // Paper start - send while respecting visibility - private static void sendSoundEffect(Player fromEntity, double x, double y, double z, SoundEvent soundEffect, SoundSource soundCategory, float volume, float pitch) { -diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java -index b2abaf6b535da13cdeeb99c568b91c585ac2ed20..6db4d21ee4dc97820943d3fd2aa55054cac95f50 100644 ---- a/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java -+++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java -@@ -1921,6 +1921,49 @@ public class CraftPlayer extends CraftHumanEntity implements Player { - Preconditions.checkArgument(exp >= 0, "Total experience points must not be negative (%s)", exp); - this.getHandle().totalExperience = exp; - } -+ // Paper start -+ @Override -+ public int calculateTotalExperiencePoints() { -+ return calculateTotalExperiencePoints(this.getLevel()) + Math.round(this.getExperiencePointsNeededForNextLevel() * getExp()); -+ } -+ -+ @Override -+ public void setExperienceLevelAndProgress(final int totalExperience) { -+ Preconditions.checkArgument(totalExperience >= 0, "Total experience points must not be negative (%s)", totalExperience); -+ int level = calculateLevelsForExperiencePoints(totalExperience); -+ int remainingPoints = totalExperience - calculateTotalExperiencePoints(level); -+ -+ this.getHandle().experienceLevel = level; -+ this.getHandle().experienceProgress = (float) remainingPoints / this.getExperiencePointsNeededForNextLevel(); -+ this.getHandle().lastSentExp = -1; -+ } -+ -+ @Override -+ public int getExperiencePointsNeededForNextLevel() { -+ return this.getHandle().getXpNeededForNextLevel(); -+ } -+ -+ // See https://minecraft.wiki/w/Experience#Leveling_up for reference -+ private int calculateTotalExperiencePoints(int level) { -+ if (level <= 16) { -+ return (int) (Math.pow(level, 2) + 6 * level); -+ } else if (level <= 31) { -+ return (int) (2.5 * Math.pow(level, 2) - 40.5 * level + 360.0); -+ } else { -+ return (int) (4.5 * Math.pow(level, 2) - 162.5 * level + 2220.0); -+ } -+ } -+ -+ private int calculateLevelsForExperiencePoints(int points) { -+ if (points <= 352) { // Level 0-16 -+ return (int) Math.floor(Math.sqrt(points + 9) - 3); -+ } else if (points <= 1507) { // Level 17-31 -+ return (int) Math.floor(8.1 + Math.sqrt(0.4 * (points - (7839.0 / 40.0)))); -+ } else { // 32+ -+ return (int) Math.floor((325.0 / 18.0) + Math.sqrt((2.0 / 9.0) * (points - (54215.0 / 72.0)))); -+ } -+ } -+ // Paper end - - @Override - public void sendExperienceChange(float progress) { diff --git a/patches/server/0916-Add-ShulkerDuplicateEvent.patch b/patches/server/0916-Add-ShulkerDuplicateEvent.patch new file mode 100644 index 000000000000..f06c91ae4814 --- /dev/null +++ b/patches/server/0916-Add-ShulkerDuplicateEvent.patch @@ -0,0 +1,22 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Chase Henderson +Date: Fri, 5 Jan 2024 03:50:10 -0500 +Subject: [PATCH] Add ShulkerDuplicateEvent + + +diff --git a/src/main/java/net/minecraft/world/entity/monster/Shulker.java b/src/main/java/net/minecraft/world/entity/monster/Shulker.java +index 0fffa9dbcbbb15a2138f9a4e4d8e812c8047a7bb..6e0f2f6573ed6be9b91de960d55c269417ad8907 100644 +--- a/src/main/java/net/minecraft/world/entity/monster/Shulker.java ++++ b/src/main/java/net/minecraft/world/entity/monster/Shulker.java +@@ -492,6 +492,11 @@ public class Shulker extends AbstractGolem implements VariantHolder -Date: Tue, 18 May 2021 12:32:02 -0700 -Subject: [PATCH] Add drops to shear events - - -diff --git a/src/main/java/net/minecraft/core/dispenser/ShearsDispenseItemBehavior.java b/src/main/java/net/minecraft/core/dispenser/ShearsDispenseItemBehavior.java -index f32f8d5cb22feb885a53d3b56c04ad4219d2bafa..44b79a7c2f8b95a484d1999fa2167ce588f7985b 100644 ---- a/src/main/java/net/minecraft/core/dispenser/ShearsDispenseItemBehavior.java -+++ b/src/main/java/net/minecraft/core/dispenser/ShearsDispenseItemBehavior.java -@@ -103,11 +103,14 @@ public class ShearsDispenseItemBehavior extends OptionalDispenseItemBehavior { - if (entityliving instanceof Shearable ishearable) { - if (ishearable.readyForShearing()) { - // CraftBukkit start -- if (CraftEventFactory.callBlockShearEntityEvent(entityliving, bukkitBlock, craftItem).isCancelled()) { -+ // Paper start - Add drops to shear events -+ org.bukkit.event.block.BlockShearEntityEvent event = CraftEventFactory.callBlockShearEntityEvent(entityliving, bukkitBlock, craftItem, ishearable.generateDefaultDrops()); -+ if (event.isCancelled()) { -+ // Paper end - Add drops to shear events - continue; - } - // CraftBukkit end -- ishearable.shear(SoundSource.BLOCKS); -+ ishearable.shear(SoundSource.BLOCKS, CraftItemStack.asNMSCopy(event.getDrops())); // Paper - Add drops to shear events - worldserver.gameEvent((Entity) null, (Holder) GameEvent.SHEAR, blockposition); - return true; - } -diff --git a/src/main/java/net/minecraft/world/entity/Shearable.java b/src/main/java/net/minecraft/world/entity/Shearable.java -index 5e8cc5cfac8888628c6d513148f41be09ca65a2c..2ee48ac3b665db2b02bcb1a30ec972d43a3725b0 100644 ---- a/src/main/java/net/minecraft/world/entity/Shearable.java -+++ b/src/main/java/net/minecraft/world/entity/Shearable.java -@@ -3,7 +3,13 @@ package net.minecraft.world.entity; - import net.minecraft.sounds.SoundSource; - - public interface Shearable { -+ default void shear(SoundSource soundCategory, java.util.List drops) { this.shear(soundCategory); } // Paper - Add drops to shear events - void shear(SoundSource shearedSoundCategory); - - boolean readyForShearing(); -+ // Paper start - custom shear drops; ensure all implementing entities override this -+ default java.util.List generateDefaultDrops() { -+ return java.util.Collections.emptyList(); -+ } -+ // Paper end - custom shear drops - } -diff --git a/src/main/java/net/minecraft/world/entity/animal/MushroomCow.java b/src/main/java/net/minecraft/world/entity/animal/MushroomCow.java -index aa125e3043b120935aaa015803f065f99eb8d050..0c21959f57ae88fcd0a4d6dc911c1ce347c96528 100644 ---- a/src/main/java/net/minecraft/world/entity/animal/MushroomCow.java -+++ b/src/main/java/net/minecraft/world/entity/animal/MushroomCow.java -@@ -123,11 +123,18 @@ public class MushroomCow extends Cow implements Shearable, VariantHolder drops = this.generateDefaultDrops(); -+ org.bukkit.event.player.PlayerShearEntityEvent event = CraftEventFactory.handlePlayerShearEntityEvent(player, this, itemstack, hand, drops); -+ if (event != null) { -+ if (event.isCancelled()) { -+ return InteractionResult.PASS; -+ } -+ drops = org.bukkit.craftbukkit.inventory.CraftItemStack.asNMSCopy(event.getDrops()); - } -+ // Paper end - custom shear drops - // CraftBukkit end -- this.shear(SoundSource.PLAYERS); -+ this.shear(SoundSource.PLAYERS, drops); // Paper - custom shear drops - this.gameEvent(GameEvent.SHEAR, player); - if (!this.level().isClientSide) { - itemstack.hurtAndBreak(1, player, getSlotForHand(hand)); -@@ -164,6 +171,22 @@ public class MushroomCow extends Cow implements Shearable, VariantHolder generateDefaultDrops() { -+ java.util.List dropEntities = new java.util.ArrayList<>(5); -+ for (int i = 0; i < 5; ++i) { -+ dropEntities.add(new ItemStack(this.getVariant().getBlockState().getBlock())); -+ } -+ return dropEntities; -+ } -+ -+ @Override -+ public void shear(SoundSource shearedSoundCategory, java.util.List drops) { // If drops is null, need to generate drops -+ // Paper end - custom shear drops - this.level().playSound((Player) null, (Entity) this, SoundEvents.MOOSHROOM_SHEAR, shearedSoundCategory, 1.0F, 1.0F); - if (!this.level().isClientSide()) { - Cow entitycow = (Cow) EntityType.COW.create(this.level()); -@@ -193,17 +216,12 @@ public class MushroomCow extends Cow implements Shearable, VariantHolder drops = this.generateDefaultDrops(); -+ org.bukkit.event.player.PlayerShearEntityEvent event = CraftEventFactory.handlePlayerShearEntityEvent(player, this, itemstack, hand, drops); -+ if (event != null) { -+ if (event.isCancelled()) { -+ return InteractionResult.PASS; -+ } -+ drops = org.bukkit.craftbukkit.inventory.CraftItemStack.asNMSCopy(event.getDrops()); - } -+ // Paper end - custom shear drops - // CraftBukkit end -- this.shear(SoundSource.PLAYERS); -+ this.shear(SoundSource.PLAYERS, drops); // Paper - this.gameEvent(GameEvent.SHEAR, player); - itemstack.hurtAndBreak(1, player, getSlotForHand(hand)); - return InteractionResult.SUCCESS; -@@ -274,13 +281,30 @@ public class Sheep extends Animal implements Shearable { - - @Override - public void shear(SoundSource shearedSoundCategory) { -+ // Paper start - custom shear drops -+ this.shear(shearedSoundCategory, this.generateDefaultDrops()); -+ } -+ -+ @Override -+ public java.util.List generateDefaultDrops() { -+ int count = 1 + this.random.nextInt(3); -+ java.util.List dropEntities = new java.util.ArrayList<>(count); -+ for (int j = 0; j < count; ++j) { -+ dropEntities.add(new ItemStack(Sheep.ITEM_BY_DYE.get(this.getColor()))); -+ } -+ return dropEntities; -+ } -+ -+ @Override -+ public void shear(SoundSource shearedSoundCategory, java.util.List drops) { -+ // Paper end - custom shear drops - this.level().playSound((Player) null, (Entity) this, SoundEvents.SHEEP_SHEAR, shearedSoundCategory, 1.0F, 1.0F); - this.setSheared(true); - int i = 1 + this.random.nextInt(3); - -- for (int j = 0; j < i; ++j) { -+ for (final ItemStack drop : drops) { // Paper - custom shear drops (moved drop generation to separate method) - this.forceDrops = true; // CraftBukkit -- ItemEntity entityitem = this.spawnAtLocation((ItemLike) Sheep.ITEM_BY_DYE.get(this.getColor()), 1); -+ ItemEntity entityitem = this.spawnAtLocation(drop, 1); // Paper - custom shear drops - this.forceDrops = false; // CraftBukkit - - if (entityitem != null) { -diff --git a/src/main/java/net/minecraft/world/entity/animal/SnowGolem.java b/src/main/java/net/minecraft/world/entity/animal/SnowGolem.java -index 2de1a2f666da9db1832907e1651dbff948e37252..5c2ed3c39c8eb850f3be1e2ea5b5a7ea266e16d1 100644 ---- a/src/main/java/net/minecraft/world/entity/animal/SnowGolem.java -+++ b/src/main/java/net/minecraft/world/entity/animal/SnowGolem.java -@@ -146,11 +146,18 @@ public class SnowGolem extends AbstractGolem implements Shearable, RangedAttackM - - if (itemstack.is(Items.SHEARS) && this.readyForShearing()) { - // CraftBukkit start -- if (!CraftEventFactory.handlePlayerShearEntityEvent(player, this, itemstack, hand)) { -- return InteractionResult.PASS; -+ // Paper start - custom shear drops -+ java.util.List drops = this.generateDefaultDrops(); -+ org.bukkit.event.player.PlayerShearEntityEvent event = CraftEventFactory.handlePlayerShearEntityEvent(player, this, itemstack, hand, drops); -+ if (event != null) { -+ if (event.isCancelled()) { -+ return InteractionResult.PASS; -+ } -+ drops = org.bukkit.craftbukkit.inventory.CraftItemStack.asNMSCopy(event.getDrops()); - } -+ // Paper end - custom shear drops - // CraftBukkit end -- this.shear(SoundSource.PLAYERS); -+ this.shear(SoundSource.PLAYERS, drops); // Paper - this.gameEvent(GameEvent.SHEAR, player); - if (!this.level().isClientSide) { - itemstack.hurtAndBreak(1, player, getSlotForHand(hand)); -@@ -164,12 +171,28 @@ public class SnowGolem extends AbstractGolem implements Shearable, RangedAttackM - - @Override - public void shear(SoundSource shearedSoundCategory) { -+ // Paper start - custom shear drops -+ this.shear(shearedSoundCategory, this.generateDefaultDrops()); -+ } -+ -+ @Override -+ public java.util.List generateDefaultDrops() { -+ return java.util.Collections.singletonList(new ItemStack(Items.CARVED_PUMPKIN)); -+ } -+ -+ @Override -+ public void shear(SoundSource shearedSoundCategory, java.util.List drops) { -+ // Paper end - custom shear drops - this.level().playSound((Player) null, (Entity) this, SoundEvents.SNOW_GOLEM_SHEAR, shearedSoundCategory, 1.0F, 1.0F); - if (!this.level().isClientSide()) { - this.setPumpkin(false); -- this.forceDrops = true; // CraftBukkit -- this.spawnAtLocation(new ItemStack(Items.CARVED_PUMPKIN), this.getEyeHeight()); -- this.forceDrops = false; // CraftBukkit -+ // Paper start - custom shear drops (moved drop generation to separate method) -+ for (final ItemStack drop : drops) { -+ this.forceDrops = true; -+ this.spawnAtLocation(drop, this.getEyeHeight()); -+ this.forceDrops = false; -+ } -+ // Paper end - custom shear drops - } - - } -diff --git a/src/main/java/net/minecraft/world/entity/monster/Bogged.java b/src/main/java/net/minecraft/world/entity/monster/Bogged.java -index dc6230458e09f7555eee7f6a567ff60ad454666b..9d50b9ac8084f3db1844cc7ad1ce9153614ff9d9 100644 ---- a/src/main/java/net/minecraft/world/entity/monster/Bogged.java -+++ b/src/main/java/net/minecraft/world/entity/monster/Bogged.java -@@ -80,12 +80,19 @@ public class Bogged extends AbstractSkeleton implements Shearable { - - if (itemstack.is(Items.SHEARS) && this.readyForShearing()) { - // CraftBukkit start -- if (!org.bukkit.craftbukkit.event.CraftEventFactory.handlePlayerShearEntityEvent(player, this, itemstack, hand)) { -- this.getEntityData().markDirty(Bogged.DATA_SHEARED); // CraftBukkit - mark dirty to restore sheared state to clients -- return InteractionResult.PASS; -+ // Paper start - expose drops in event -+ java.util.List drops = generateDefaultDrops(); -+ final org.bukkit.event.player.PlayerShearEntityEvent event = org.bukkit.craftbukkit.event.CraftEventFactory.handlePlayerShearEntityEvent(player, this, itemstack, hand, drops); -+ if (event != null) { -+ if (event.isCancelled()) { -+ if (player instanceof final net.minecraft.server.level.ServerPlayer serverPlayer) this.resendPossiblyDesyncedDataValues(java.util.List.of(Bogged.DATA_SHEARED), serverPlayer); -+ return InteractionResult.PASS; -+ } -+ drops = org.bukkit.craftbukkit.inventory.CraftItemStack.asNMSCopy(event.getDrops()); -+ // Paper end - expose drops in event - } - // CraftBukkit end -- this.shear(SoundSource.PLAYERS); -+ this.shear(SoundSource.PLAYERS, drops); // Paper - expose drops in event - this.gameEvent(GameEvent.SHEAR, player); - if (!this.level().isClientSide) { - itemstack.hurtAndBreak(1, player, getSlotForHand(hand)); -@@ -140,12 +147,31 @@ public class Bogged extends AbstractSkeleton implements Shearable { - - @Override - public void shear(SoundSource shearedSoundCategory) { -+ // Paper start - shear drop API -+ this.shear(shearedSoundCategory, generateDefaultDrops()); -+ } -+ -+ @Override -+ public void shear(SoundSource shearedSoundCategory, java.util.List drops) { -+ // Paper end - shear drop API - this.level().playSound((Player) null, (Entity) this, SoundEvents.BOGGED_SHEAR, shearedSoundCategory, 1.0F, 1.0F); -- this.spawnShearedMushrooms(); -+ this.spawnDrops(drops); // Paper - shear drop API - this.setSheared(true); - } - - private void spawnShearedMushrooms() { -+ // Paper start - shear drops API -+ this.spawnDrops(generateDefaultDrops()); // Only here for people calling spawnSheardMushrooms. Not used otherwise. -+ } -+ private void spawnDrops(java.util.List drops) { -+ drops.forEach(stack -> { -+ this.forceDrops = true; -+ this.spawnAtLocation(stack, this.getBbHeight()); -+ this.forceDrops = false; -+ }); -+ } -+ private void generateShearedMushrooms(java.util.function.Consumer stackConsumer) { -+ // Paper end - shear drops API - Level world = this.level(); - - if (world instanceof ServerLevel worldserver) { -@@ -156,12 +182,21 @@ public class Bogged extends AbstractSkeleton implements Shearable { - while (objectlistiterator.hasNext()) { - ItemStack itemstack = (ItemStack) objectlistiterator.next(); - -- this.spawnAtLocation(itemstack, this.getBbHeight()); -+ stackConsumer.accept(itemstack); // Paper - } - } - - } - -+ // Paper start - shear drops API -+ @Override -+ public java.util.List generateDefaultDrops() { -+ final java.util.List drops = new java.util.ArrayList<>(); -+ this.generateShearedMushrooms(drops::add); -+ return drops; -+ } -+ // Paper end - shear drops API -+ - @Override - public boolean readyForShearing() { - return !this.isSheared() && this.isAlive(); -diff --git a/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java b/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java -index f529243852df8e193a604bb6d895333848b14a74..d407e9e8f1b530f00e632e0249514fb68d48316b 100644 ---- a/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java -+++ b/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java -@@ -1691,20 +1691,20 @@ public class CraftEventFactory { - player.level().getCraftServer().getPluginManager().callEvent(event); - } - -- public static BlockShearEntityEvent callBlockShearEntityEvent(Entity animal, org.bukkit.block.Block dispenser, CraftItemStack is) { -- BlockShearEntityEvent bse = new BlockShearEntityEvent(dispenser, animal.getBukkitEntity(), is); -+ public static BlockShearEntityEvent callBlockShearEntityEvent(Entity animal, org.bukkit.block.Block dispenser, CraftItemStack is, List drops) { // Paper - custom shear drops -+ BlockShearEntityEvent bse = new BlockShearEntityEvent(dispenser, animal.getBukkitEntity(), is, Lists.transform(drops, CraftItemStack::asCraftMirror)); // Paper - custom shear drops - Bukkit.getPluginManager().callEvent(bse); - return bse; - } - -- public static boolean handlePlayerShearEntityEvent(net.minecraft.world.entity.player.Player player, Entity sheared, ItemStack shears, InteractionHand hand) { -+ public static PlayerShearEntityEvent handlePlayerShearEntityEvent(net.minecraft.world.entity.player.Player player, Entity sheared, ItemStack shears, InteractionHand hand, List drops) { // Paper - custom shear drops - if (!(player instanceof ServerPlayer)) { -- return true; -+ return null; // Paper - custom shear drops - } - -- PlayerShearEntityEvent event = new PlayerShearEntityEvent((Player) player.getBukkitEntity(), sheared.getBukkitEntity(), CraftItemStack.asCraftMirror(shears), (hand == InteractionHand.OFF_HAND ? EquipmentSlot.OFF_HAND : EquipmentSlot.HAND)); -+ PlayerShearEntityEvent event = new PlayerShearEntityEvent((Player) player.getBukkitEntity(), sheared.getBukkitEntity(), CraftItemStack.asCraftMirror(shears), (hand == InteractionHand.OFF_HAND ? EquipmentSlot.OFF_HAND : EquipmentSlot.HAND), Lists.transform(drops, CraftItemStack::asCraftMirror)); // Paper - custom shear drops - Bukkit.getPluginManager().callEvent(event); -- return !event.isCancelled(); -+ return event; // Paper - custom shear drops - } - - public static Cancellable handleStatisticsIncrease(net.minecraft.world.entity.player.Player entityHuman, net.minecraft.stats.Stat statistic, int current, int newValue) { -diff --git a/src/main/java/org/bukkit/craftbukkit/inventory/CraftItemStack.java b/src/main/java/org/bukkit/craftbukkit/inventory/CraftItemStack.java -index f33742ee06e8443a5f5adaaa987d7523dc193b5a..a1a32a77bda0560a7b7f30a5d1c1837ee96997d3 100644 ---- a/src/main/java/org/bukkit/craftbukkit/inventory/CraftItemStack.java -+++ b/src/main/java/org/bukkit/craftbukkit/inventory/CraftItemStack.java -@@ -69,6 +69,16 @@ public final class CraftItemStack extends ItemStack { - return stack; - } - -+ // Paper start -+ public static java.util.List asNMSCopy(java.util.List originals) { -+ final java.util.List items = new java.util.ArrayList<>(originals.size()); -+ for (final ItemStack original : originals) { -+ items.add(asNMSCopy(original)); -+ } -+ return items; -+ } -+ // Paper end -+ - public static net.minecraft.world.item.ItemStack copyNMSStack(net.minecraft.world.item.ItemStack original, int amount) { - net.minecraft.world.item.ItemStack stack = original.copy(); - stack.setCount(amount); -diff --git a/src/test/java/io/papermc/paper/entity/ShearableDropsTest.java b/src/test/java/io/papermc/paper/entity/ShearableDropsTest.java -new file mode 100644 -index 0000000000000000000000000000000000000000..5e6dfc93c86ec369b686f15ca066478e1635dbc3 ---- /dev/null -+++ b/src/test/java/io/papermc/paper/entity/ShearableDropsTest.java -@@ -0,0 +1,35 @@ -+package io.papermc.paper.entity; -+ -+import io.github.classgraph.ClassGraph; -+import io.github.classgraph.ClassInfo; -+import io.github.classgraph.MethodInfoList; -+import io.github.classgraph.ScanResult; -+import java.util.ArrayList; -+import net.minecraft.world.entity.Shearable; -+import org.bukkit.support.environment.Normal; -+import org.junit.jupiter.params.ParameterizedTest; -+import org.junit.jupiter.params.provider.MethodSource; -+ -+import static org.junit.jupiter.api.Assertions.assertEquals; -+ -+@Normal -+class ShearableDropsTest { -+ -+ static Iterable parameters() { -+ try (ScanResult scanResult = new ClassGraph() -+ .enableClassInfo() -+ .enableMethodInfo() -+ .whitelistPackages("net.minecraft") -+ .scan() -+ ) { -+ return new ArrayList<>(scanResult.getClassesImplementing(Shearable.class.getName())); -+ } -+ } -+ -+ @ParameterizedTest -+ @MethodSource("parameters") -+ void checkShearableDropOverrides(final ClassInfo classInfo) { -+ final MethodInfoList generateDefaultDrops = classInfo.getDeclaredMethodInfo("generateDefaultDrops"); -+ assertEquals(1, generateDefaultDrops.size(), classInfo.getName() + " doesn't implement Shearable#generateDefaultDrops"); -+ } -+} diff --git a/patches/server/0917-Add-PlayerShieldDisableEvent.patch b/patches/server/0917-Add-PlayerShieldDisableEvent.patch deleted file mode 100644 index 5bbd9b6ff69a..000000000000 --- a/patches/server/0917-Add-PlayerShieldDisableEvent.patch +++ /dev/null @@ -1,53 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Cryptite -Date: Mon, 1 May 2023 16:22:43 -0500 -Subject: [PATCH] Add PlayerShieldDisableEvent - -Called whenever a players shield is disabled. This is mainly caused by -attacking players or monsters that carry axes. - -The event, while similar to the PlayerItemCooldownEvent, offers other -behaviour and can hence not be implemented as a childtype of said event. -Specifically, cancelling the event prevents the game events from being -sent to the player. - -Plugins listening to just the PlayerItemCooldownEvent may not want said -sideeffects, meaning the disable event cannot share a handlerlist with -the cooldown event - -diff --git a/src/main/java/net/minecraft/world/entity/player/Player.java b/src/main/java/net/minecraft/world/entity/player/Player.java -index 454b29d8c9e42e328933aa578f49d28f1e77898a..d0b51d96d6795b5fa03bc195b90324680545b752 100644 ---- a/src/main/java/net/minecraft/world/entity/player/Player.java -+++ b/src/main/java/net/minecraft/world/entity/player/Player.java -@@ -1005,7 +1005,7 @@ public abstract class Player extends LivingEntity { - protected void blockUsingShield(LivingEntity attacker) { - super.blockUsingShield(attacker); - if (attacker.canDisableShield()) { -- this.disableShield(); -+ this.disableShield(attacker); // Paper - Add PlayerShieldDisableEvent - } - - } -@@ -1498,8 +1498,21 @@ public abstract class Player extends LivingEntity { - this.attack(target); - } - -+ @io.papermc.paper.annotation.DoNotUse @Deprecated // Paper - Add PlayerShieldDisableEvent - public void disableShield() { -- this.getCooldowns().addCooldown(Items.SHIELD, 100); -+ // Paper start - Add PlayerShieldDisableEvent -+ this.disableShield(null); -+ } -+ public void disableShield(@Nullable LivingEntity attacker) { -+ final org.bukkit.entity.Entity finalAttacker = attacker != null ? attacker.getBukkitEntity() : null; -+ if (finalAttacker != null) { -+ final io.papermc.paper.event.player.PlayerShieldDisableEvent shieldDisableEvent = new io.papermc.paper.event.player.PlayerShieldDisableEvent((org.bukkit.entity.Player) getBukkitEntity(), finalAttacker, 100); -+ if (!shieldDisableEvent.callEvent()) return; -+ this.getCooldowns().addCooldown(Items.SHIELD, shieldDisableEvent.getCooldown()); -+ } else { -+ this.getCooldowns().addCooldown(Items.SHIELD, 100); -+ } -+ // Paper end - Add PlayerShieldDisableEvent - this.stopUsingItem(); - this.level().broadcastEntityEvent(this, (byte) 30); - } diff --git a/patches/server/0917-Add-api-for-spawn-egg-texture-colors.patch b/patches/server/0917-Add-api-for-spawn-egg-texture-colors.patch new file mode 100644 index 000000000000..af708be49f2e --- /dev/null +++ b/patches/server/0917-Add-api-for-spawn-egg-texture-colors.patch @@ -0,0 +1,26 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Luis +Date: Thu, 11 Jan 2024 19:58:23 +0100 +Subject: [PATCH] Add api for spawn egg texture colors + + +diff --git a/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java b/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java +index 1835f8cfda0222fadd9db31abfb7e85899051853..30106a999db1bae217333b5e94913b9ec55e4615 100644 +--- a/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java ++++ b/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java +@@ -629,6 +629,15 @@ public final class CraftMagicNumbers implements UnsafeValues { + } + // Paper end + ++ // Paper start - spawn egg color visibility ++ @Override ++ public org.bukkit.Color getSpawnEggLayerColor(final EntityType entityType, final int layer) { ++ final net.minecraft.world.entity.EntityType nmsType = org.bukkit.craftbukkit.entity.CraftEntityType.bukkitToMinecraft(entityType); ++ final net.minecraft.world.item.SpawnEggItem eggItem = net.minecraft.world.item.SpawnEggItem.byId(nmsType); ++ return eggItem == null ? null : org.bukkit.Color.fromRGB(eggItem.getColor(layer)); ++ } ++ // Paper end - spawn egg color visibility ++ + /** + * This helper class represents the different NBT Tags. + *

    diff --git a/patches/server/0918-Add-Lifecycle-Event-system.patch b/patches/server/0918-Add-Lifecycle-Event-system.patch new file mode 100644 index 000000000000..0eaff54d3402 --- /dev/null +++ b/patches/server/0918-Add-Lifecycle-Event-system.patch @@ -0,0 +1,785 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Jake Potrebic +Date: Tue, 18 Jul 2023 17:49:38 -0700 +Subject: [PATCH] Add Lifecycle Event system + +This event system is separate from Bukkit's event system and is +meant for managing resources across reloads and from points in the +PluginBootstrap. + +diff --git a/src/main/java/io/papermc/paper/plugin/bootstrap/PluginBootstrapContextImpl.java b/src/main/java/io/papermc/paper/plugin/bootstrap/PluginBootstrapContextImpl.java +index 30b50e6294c6eaade5e17cfaf34600d122e6251c..0bb7694188d5fb75bb756ce75d0060ea980027ee 100644 +--- a/src/main/java/io/papermc/paper/plugin/bootstrap/PluginBootstrapContextImpl.java ++++ b/src/main/java/io/papermc/paper/plugin/bootstrap/PluginBootstrapContextImpl.java +@@ -1,6 +1,8 @@ + package io.papermc.paper.plugin.bootstrap; + + import io.papermc.paper.plugin.configuration.PluginMeta; ++import io.papermc.paper.plugin.lifecycle.event.LifecycleEventManager; ++import io.papermc.paper.plugin.lifecycle.event.PaperLifecycleEventManager; + import io.papermc.paper.plugin.provider.PluginProvider; + import java.nio.file.Path; + import net.kyori.adventure.text.logger.slf4j.ComponentLogger; +@@ -12,6 +14,10 @@ public final class PluginBootstrapContextImpl implements BootstrapContext { + private final Path dataFolder; + private final ComponentLogger logger; + private final Path pluginSource; ++ // Paper start - lifecycle events ++ private boolean allowsLifecycleRegistration = true; ++ private final PaperLifecycleEventManager lifecycleEventManager = new PaperLifecycleEventManager<>(this, () -> this.allowsLifecycleRegistration); // Paper - lifecycle events ++ // Paper end - lifecycle events + + public PluginBootstrapContextImpl(PluginMeta config, Path dataFolder, ComponentLogger logger, Path pluginSource) { + this.config = config; +@@ -45,4 +51,20 @@ public final class PluginBootstrapContextImpl implements BootstrapContext { + public @NotNull Path getPluginSource() { + return this.pluginSource; + } ++ ++ // Paper start - lifecycle event system ++ @Override ++ public @NotNull PluginMeta getPluginMeta() { ++ return this.config; ++ } ++ ++ @Override ++ public LifecycleEventManager getLifecycleManager() { ++ return this.lifecycleEventManager; ++ } ++ ++ public void lockLifecycleEventRegistration() { ++ this.allowsLifecycleRegistration = false; ++ } ++ // Paper end + } +diff --git a/src/main/java/io/papermc/paper/plugin/lifecycle/event/LifecycleEventRunner.java b/src/main/java/io/papermc/paper/plugin/lifecycle/event/LifecycleEventRunner.java +new file mode 100644 +index 0000000000000000000000000000000000000000..ce808520d639581696689a2ab85de00d85aa0ee3 +--- /dev/null ++++ b/src/main/java/io/papermc/paper/plugin/lifecycle/event/LifecycleEventRunner.java +@@ -0,0 +1,100 @@ ++package io.papermc.paper.plugin.lifecycle.event; ++ ++import io.papermc.paper.plugin.lifecycle.event.registrar.PaperRegistrar; ++import io.papermc.paper.plugin.lifecycle.event.registrar.RegistrarEvent; ++import io.papermc.paper.plugin.lifecycle.event.registrar.RegistrarEventImpl; ++import io.papermc.paper.plugin.lifecycle.event.registrar.ReloadableRegistrarEvent; ++import io.papermc.paper.plugin.lifecycle.event.types.AbstractLifecycleEventType; ++import io.papermc.paper.plugin.lifecycle.event.types.LifecycleEventType; ++import io.papermc.paper.plugin.lifecycle.event.types.OwnerAwareLifecycleEvent; ++import java.util.ArrayList; ++import java.util.List; ++import java.util.function.Predicate; ++import org.bukkit.plugin.Plugin; ++import org.checkerframework.checker.nullness.qual.NonNull; ++import org.checkerframework.checker.nullness.qual.Nullable; ++import org.checkerframework.framework.qual.DefaultQualifier; ++ ++@DefaultQualifier(NonNull.class) ++public class LifecycleEventRunner { ++ ++ public static final LifecycleEventRunner INSTANCE = new LifecycleEventRunner(); ++ ++ private final List> lifecycleEventTypes = new ArrayList<>(); ++ private boolean blockPluginReloading = false; ++ ++ public void checkRegisteredHandler(final O owner, final AbstractLifecycleEventType eventType) { ++ /* ++ Lifecycle event handlers for reloadable events that are registered from the BootstrapContext prevent ++ the server from reloading plugins. This is because reloading plugins requires disabling all the plugins, ++ running the reload logic (which would include places where these events should fire) and then re-enabling plugins. ++ */ ++ if (eventType.blocksReloading(owner)) { ++ this.blockPluginReloading = true; ++ } ++ } ++ ++ public boolean blocksPluginReloading() { ++ return this.blockPluginReloading; ++ } ++ ++ public > void addEventType(final ET eventType) { ++ this.lifecycleEventTypes.add(eventType); ++ } ++ ++ public void callEvent(final LifecycleEventType eventType, final E event) { ++ this.callEvent(eventType, event, $ -> true); ++ } ++ ++ public void callEvent(final LifecycleEventType eventType, final E event, final Predicate ownerPredicate) { ++ final AbstractLifecycleEventType lifecycleEventType = (AbstractLifecycleEventType) eventType; ++ lifecycleEventType.forEachHandler(event, registeredHandler -> { ++ try { ++ if (event instanceof final OwnerAwareLifecycleEvent ownerAwareEvent) { ++ ownerAwareGenericHelper(ownerAwareEvent, registeredHandler.owner()); ++ } ++ registeredHandler.lifecycleEventHandler().run(event); ++ } catch (final Throwable ex) { ++ throw new RuntimeException("Could not run '%s' lifecycle event handler from %s".formatted(lifecycleEventType.name(), registeredHandler.owner().getPluginMeta().getDisplayName()), ex); ++ } finally { ++ if (event instanceof final OwnerAwareLifecycleEvent ownerAwareEvent) { ++ ownerAwareEvent.setOwner(null); ++ } ++ } ++ }, handler -> ownerPredicate.test(handler.owner())); ++ event.invalidate(); ++ } ++ ++ private static void ownerAwareGenericHelper(final OwnerAwareLifecycleEvent event, final LifecycleEventOwner possibleOwner) { ++ final @Nullable O owner = event.castOwner(possibleOwner); ++ if (owner != null) { ++ event.setOwner(owner); ++ } else { ++ throw new IllegalStateException("Found invalid owner " + possibleOwner + " for event " + event); ++ } ++ } ++ ++ public void unregisterAllEventHandlersFor(final Plugin plugin) { ++ for (final LifecycleEventType lifecycleEventType : this.lifecycleEventTypes) { ++ this.removeEventHandlersOwnedBy(lifecycleEventType, plugin); ++ } ++ } ++ ++ private void removeEventHandlersOwnedBy(final LifecycleEventType eventType, final Plugin possibleOwner) { ++ final AbstractLifecycleEventType lifecycleEventType = (AbstractLifecycleEventType) eventType; ++ lifecycleEventType.removeMatching(registeredHandler -> registeredHandler.owner().getPluginMeta().getName().equals(possibleOwner.getPluginMeta().getName())); ++ } ++ ++ @SuppressWarnings("unchecked") ++ public > void callStaticRegistrarEvent(final LifecycleEventType, ?> lifecycleEventType, final R registrar, final Class ownerClass) { ++ this.callEvent((LifecycleEventType, ?>) lifecycleEventType, new RegistrarEventImpl<>(registrar, ownerClass), ownerClass::isInstance); ++ } ++ ++ @SuppressWarnings("unchecked") ++ public > void callReloadableRegistrarEvent(final LifecycleEventType, ?> lifecycleEventType, final R registrar, final Class ownerClass, final ReloadableRegistrarEvent.Cause cause) { ++ this.callEvent((LifecycleEventType, ?>) lifecycleEventType, new RegistrarEventImpl.ReloadableImpl<>(registrar, ownerClass, cause), ownerClass::isInstance); ++ } ++ ++ private LifecycleEventRunner() { ++ } ++} +diff --git a/src/main/java/io/papermc/paper/plugin/lifecycle/event/PaperLifecycleEvent.java b/src/main/java/io/papermc/paper/plugin/lifecycle/event/PaperLifecycleEvent.java +new file mode 100644 +index 0000000000000000000000000000000000000000..e941405269a773e8a77e26ffd1afd84f53fadff5 +--- /dev/null ++++ b/src/main/java/io/papermc/paper/plugin/lifecycle/event/PaperLifecycleEvent.java +@@ -0,0 +1,10 @@ ++package io.papermc.paper.plugin.lifecycle.event; ++ ++public interface PaperLifecycleEvent extends LifecycleEvent { ++ ++ // called after all handlers have been run. Can be ++ // used to invalid various contexts to plugins can't ++ // try to re-use them by storing them from the event ++ default void invalidate() { ++ } ++} +diff --git a/src/main/java/io/papermc/paper/plugin/lifecycle/event/PaperLifecycleEventManager.java b/src/main/java/io/papermc/paper/plugin/lifecycle/event/PaperLifecycleEventManager.java +new file mode 100644 +index 0000000000000000000000000000000000000000..d05334016bd01201c755dea04c0cea56b6dfcb50 +--- /dev/null ++++ b/src/main/java/io/papermc/paper/plugin/lifecycle/event/PaperLifecycleEventManager.java +@@ -0,0 +1,26 @@ ++package io.papermc.paper.plugin.lifecycle.event; ++ ++import com.google.common.base.Preconditions; ++import io.papermc.paper.plugin.lifecycle.event.handler.configuration.AbstractLifecycleEventHandlerConfiguration; ++import io.papermc.paper.plugin.lifecycle.event.handler.configuration.LifecycleEventHandlerConfiguration; ++import java.util.function.BooleanSupplier; ++import org.checkerframework.checker.nullness.qual.NonNull; ++import org.checkerframework.framework.qual.DefaultQualifier; ++ ++@DefaultQualifier(NonNull.class) ++public final class PaperLifecycleEventManager implements LifecycleEventManager { ++ ++ private final O owner; ++ public final BooleanSupplier registrationCheck; ++ ++ public PaperLifecycleEventManager(final O owner, final BooleanSupplier registrationCheck) { ++ this.owner = owner; ++ this.registrationCheck = registrationCheck; ++ } ++ ++ @Override ++ public void registerEventHandler(final LifecycleEventHandlerConfiguration handlerConfiguration) { ++ Preconditions.checkState(this.registrationCheck.getAsBoolean(), "Cannot register lifecycle event handlers"); ++ ((AbstractLifecycleEventHandlerConfiguration) handlerConfiguration).registerFrom(this.owner); ++ } ++} +diff --git a/src/main/java/io/papermc/paper/plugin/lifecycle/event/handler/configuration/AbstractLifecycleEventHandlerConfiguration.java b/src/main/java/io/papermc/paper/plugin/lifecycle/event/handler/configuration/AbstractLifecycleEventHandlerConfiguration.java +new file mode 100644 +index 0000000000000000000000000000000000000000..fa216e6fd804859293385ed43c53dfca057f317f +--- /dev/null ++++ b/src/main/java/io/papermc/paper/plugin/lifecycle/event/handler/configuration/AbstractLifecycleEventHandlerConfiguration.java +@@ -0,0 +1,28 @@ ++package io.papermc.paper.plugin.lifecycle.event.handler.configuration; ++ ++import io.papermc.paper.plugin.lifecycle.event.LifecycleEvent; ++import io.papermc.paper.plugin.lifecycle.event.LifecycleEventOwner; ++import io.papermc.paper.plugin.lifecycle.event.handler.LifecycleEventHandler; ++import io.papermc.paper.plugin.lifecycle.event.types.AbstractLifecycleEventType; ++import org.checkerframework.checker.nullness.qual.NonNull; ++import org.checkerframework.framework.qual.DefaultQualifier; ++ ++@DefaultQualifier(NonNull.class) ++public abstract class AbstractLifecycleEventHandlerConfiguration implements LifecycleEventHandlerConfiguration { ++ ++ private final LifecycleEventHandler handler; ++ private final AbstractLifecycleEventType type; ++ ++ protected AbstractLifecycleEventHandlerConfiguration(final LifecycleEventHandler handler, final AbstractLifecycleEventType type) { ++ this.handler = handler; ++ this.type = type; ++ } ++ ++ public final void registerFrom(final O owner) { ++ this.type.tryRegister(owner, this); ++ } ++ ++ public LifecycleEventHandler handler() { ++ return this.handler; ++ } ++} +diff --git a/src/main/java/io/papermc/paper/plugin/lifecycle/event/handler/configuration/MonitorLifecycleEventHandlerConfigurationImpl.java b/src/main/java/io/papermc/paper/plugin/lifecycle/event/handler/configuration/MonitorLifecycleEventHandlerConfigurationImpl.java +new file mode 100644 +index 0000000000000000000000000000000000000000..ab444d60d72bd692843052df5d7b24fbb5621cf7 +--- /dev/null ++++ b/src/main/java/io/papermc/paper/plugin/lifecycle/event/handler/configuration/MonitorLifecycleEventHandlerConfigurationImpl.java +@@ -0,0 +1,28 @@ ++package io.papermc.paper.plugin.lifecycle.event.handler.configuration; ++ ++import io.papermc.paper.plugin.lifecycle.event.LifecycleEvent; ++import io.papermc.paper.plugin.lifecycle.event.LifecycleEventOwner; ++import io.papermc.paper.plugin.lifecycle.event.handler.LifecycleEventHandler; ++import io.papermc.paper.plugin.lifecycle.event.types.AbstractLifecycleEventType; ++import org.checkerframework.checker.nullness.qual.NonNull; ++import org.checkerframework.framework.qual.DefaultQualifier; ++ ++@DefaultQualifier(NonNull.class) ++public class MonitorLifecycleEventHandlerConfigurationImpl extends AbstractLifecycleEventHandlerConfiguration implements MonitorLifecycleEventHandlerConfiguration { ++ ++ private boolean monitor = false; ++ ++ public MonitorLifecycleEventHandlerConfigurationImpl(final LifecycleEventHandler handler, final AbstractLifecycleEventType eventType) { ++ super(handler, eventType); ++ } ++ ++ public boolean isMonitor() { ++ return this.monitor; ++ } ++ ++ @Override ++ public MonitorLifecycleEventHandlerConfiguration monitor() { ++ this.monitor = true; ++ return this; ++ } ++} +diff --git a/src/main/java/io/papermc/paper/plugin/lifecycle/event/handler/configuration/PrioritizedLifecycleEventHandlerConfigurationImpl.java b/src/main/java/io/papermc/paper/plugin/lifecycle/event/handler/configuration/PrioritizedLifecycleEventHandlerConfigurationImpl.java +new file mode 100644 +index 0000000000000000000000000000000000000000..ccdad31717bf12b844cbeaf11a49247485ec77f1 +--- /dev/null ++++ b/src/main/java/io/papermc/paper/plugin/lifecycle/event/handler/configuration/PrioritizedLifecycleEventHandlerConfigurationImpl.java +@@ -0,0 +1,40 @@ ++package io.papermc.paper.plugin.lifecycle.event.handler.configuration; ++ ++import io.papermc.paper.plugin.lifecycle.event.LifecycleEvent; ++import io.papermc.paper.plugin.lifecycle.event.LifecycleEventOwner; ++import io.papermc.paper.plugin.lifecycle.event.handler.LifecycleEventHandler; ++import io.papermc.paper.plugin.lifecycle.event.types.AbstractLifecycleEventType; ++import java.util.OptionalInt; ++import org.checkerframework.checker.nullness.qual.NonNull; ++import org.checkerframework.framework.qual.DefaultQualifier; ++ ++@DefaultQualifier(NonNull.class) ++public class PrioritizedLifecycleEventHandlerConfigurationImpl ++ extends AbstractLifecycleEventHandlerConfiguration ++ implements PrioritizedLifecycleEventHandlerConfiguration { ++ ++ private static final OptionalInt DEFAULT_PRIORITY = OptionalInt.of(0); ++ private static final OptionalInt MONITOR_PRIORITY = OptionalInt.empty(); ++ ++ private OptionalInt priority = DEFAULT_PRIORITY; ++ ++ public PrioritizedLifecycleEventHandlerConfigurationImpl(final LifecycleEventHandler handler, final AbstractLifecycleEventType eventType) { ++ super(handler, eventType); ++ } ++ ++ public OptionalInt priority() { ++ return this.priority; ++ } ++ ++ @Override ++ public PrioritizedLifecycleEventHandlerConfiguration priority(final int priority) { ++ this.priority = OptionalInt.of(priority); ++ return this; ++ } ++ ++ @Override ++ public PrioritizedLifecycleEventHandlerConfiguration monitor() { ++ this.priority = MONITOR_PRIORITY; ++ return this; ++ } ++} +diff --git a/src/main/java/io/papermc/paper/plugin/lifecycle/event/registrar/PaperRegistrar.java b/src/main/java/io/papermc/paper/plugin/lifecycle/event/registrar/PaperRegistrar.java +new file mode 100644 +index 0000000000000000000000000000000000000000..b2586c881988fbabe07eef1b43eb1b55f2d3fa52 +--- /dev/null ++++ b/src/main/java/io/papermc/paper/plugin/lifecycle/event/registrar/PaperRegistrar.java +@@ -0,0 +1,15 @@ ++package io.papermc.paper.plugin.lifecycle.event.registrar; ++ ++import io.papermc.paper.plugin.lifecycle.event.LifecycleEventOwner; ++import org.checkerframework.checker.nullness.qual.NonNull; ++import org.checkerframework.checker.nullness.qual.Nullable; ++import org.checkerframework.framework.qual.DefaultQualifier; ++ ++@DefaultQualifier(NonNull.class) ++public interface PaperRegistrar extends Registrar { ++ ++ void setCurrentContext(@Nullable O owner); ++ ++ default void invalidate() { ++ } ++} +diff --git a/src/main/java/io/papermc/paper/plugin/lifecycle/event/registrar/RegistrarEventImpl.java b/src/main/java/io/papermc/paper/plugin/lifecycle/event/registrar/RegistrarEventImpl.java +new file mode 100644 +index 0000000000000000000000000000000000000000..6d530c52aaf0dc2cdfe3bd56af557274a7f44256 +--- /dev/null ++++ b/src/main/java/io/papermc/paper/plugin/lifecycle/event/registrar/RegistrarEventImpl.java +@@ -0,0 +1,70 @@ ++package io.papermc.paper.plugin.lifecycle.event.registrar; ++ ++import io.papermc.paper.plugin.lifecycle.event.LifecycleEventOwner; ++import io.papermc.paper.plugin.lifecycle.event.PaperLifecycleEvent; ++import io.papermc.paper.plugin.lifecycle.event.types.OwnerAwareLifecycleEvent; ++import org.checkerframework.checker.nullness.qual.NonNull; ++import org.checkerframework.checker.nullness.qual.Nullable; ++import org.checkerframework.framework.qual.DefaultQualifier; ++ ++@DefaultQualifier(NonNull.class) ++public class RegistrarEventImpl, O extends LifecycleEventOwner> implements PaperLifecycleEvent, OwnerAwareLifecycleEvent, RegistrarEvent { ++ ++ private final R registrar; ++ private final Class ownerClass; ++ ++ public RegistrarEventImpl(final R registrar, final Class ownerClass) { ++ this.registrar = registrar; ++ this.ownerClass = ownerClass; ++ } ++ ++ @Override ++ public R registrar() { ++ return this.registrar; ++ } ++ ++ @Override ++ public final void setOwner(final @Nullable O owner) { ++ this.registrar.setCurrentContext(owner); ++ } ++ ++ @Override ++ public final @Nullable O castOwner(final LifecycleEventOwner owner) { ++ return this.ownerClass.isInstance(owner) ? this.ownerClass.cast(owner) : null; ++ } ++ ++ @Override ++ public void invalidate() { ++ this.registrar.invalidate(); ++ } ++ ++ @Override ++ public String toString() { ++ return "RegistrarEventImpl{" + ++ "registrar=" + this.registrar + ++ ", ownerClass=" + this.ownerClass + ++ '}'; ++ } ++ ++ public static class ReloadableImpl, O extends LifecycleEventOwner> extends RegistrarEventImpl implements ReloadableRegistrarEvent { ++ ++ private final ReloadableRegistrarEvent.Cause cause; ++ ++ public ReloadableImpl(final R registrar, final Class ownerClass, final Cause cause) { ++ super(registrar, ownerClass); ++ this.cause = cause; ++ } ++ ++ @Override ++ public Cause cause() { ++ return this.cause; ++ } ++ ++ @Override ++ public String toString() { ++ return "ReloadableImpl{" + ++ "cause=" + this.cause + ++ "} " + super.toString(); ++ } ++ } ++} +diff --git a/src/main/java/io/papermc/paper/plugin/lifecycle/event/types/AbstractLifecycleEventType.java b/src/main/java/io/papermc/paper/plugin/lifecycle/event/types/AbstractLifecycleEventType.java +new file mode 100644 +index 0000000000000000000000000000000000000000..01a4e9a36a9970f30ed9f9236fc5a4a1cf71844e +--- /dev/null ++++ b/src/main/java/io/papermc/paper/plugin/lifecycle/event/types/AbstractLifecycleEventType.java +@@ -0,0 +1,62 @@ ++package io.papermc.paper.plugin.lifecycle.event.types; ++ ++import io.papermc.paper.plugin.bootstrap.BootstrapContext; ++import io.papermc.paper.plugin.lifecycle.event.LifecycleEvent; ++import io.papermc.paper.plugin.lifecycle.event.LifecycleEventOwner; ++import io.papermc.paper.plugin.lifecycle.event.LifecycleEventRunner; ++import io.papermc.paper.plugin.lifecycle.event.handler.LifecycleEventHandler; ++import io.papermc.paper.plugin.lifecycle.event.handler.configuration.AbstractLifecycleEventHandlerConfiguration; ++import io.papermc.paper.plugin.lifecycle.event.handler.configuration.LifecycleEventHandlerConfiguration; ++import java.util.function.Consumer; ++import java.util.function.Predicate; ++import org.checkerframework.checker.nullness.qual.NonNull; ++import org.checkerframework.framework.qual.DefaultQualifier; ++ ++@DefaultQualifier(NonNull.class) ++public abstract class AbstractLifecycleEventType> implements LifecycleEventType { ++ ++ private final String name; ++ private final Class ownerType; ++ ++ protected AbstractLifecycleEventType(final String name, final Class ownerType) { ++ this.name = name; ++ this.ownerType = ownerType; ++ LifecycleEventRunner.INSTANCE.addEventType(this); ++ } ++ ++ @Override ++ public String name() { ++ return this.name; ++ } ++ ++ private void verifyOwner(final O owner) { ++ if (!this.ownerType.isInstance(owner)) { ++ throw new IllegalArgumentException("You cannot register the lifecycle event '" + this.name + "' on " + owner); ++ } ++ } ++ ++ public boolean blocksReloading(final O eventOwner) { ++ return eventOwner instanceof BootstrapContext; ++ } ++ ++ public abstract boolean hasHandlers(); ++ ++ public abstract void forEachHandler(E event, Consumer> consumer, Predicate> predicate); ++ ++ public abstract void removeMatching(Predicate> predicate); ++ ++ protected abstract void register(O owner, AbstractLifecycleEventHandlerConfiguration config); ++ ++ public final void tryRegister(final O owner, final AbstractLifecycleEventHandlerConfiguration config) { ++ this.verifyOwner(owner); ++ LifecycleEventRunner.INSTANCE.checkRegisteredHandler(owner, this); ++ this.register(owner, config); ++ } ++ ++ public record RegisteredHandler(O owner, AbstractLifecycleEventHandlerConfiguration config) { ++ ++ public LifecycleEventHandler lifecycleEventHandler() { ++ return this.config().handler(); ++ } ++ } ++} +diff --git a/src/main/java/io/papermc/paper/plugin/lifecycle/event/types/LifecycleEventTypeProviderImpl.java b/src/main/java/io/papermc/paper/plugin/lifecycle/event/types/LifecycleEventTypeProviderImpl.java +new file mode 100644 +index 0000000000000000000000000000000000000000..b11346e04bb16c3238f32deb87dbd680e261d4d2 +--- /dev/null ++++ b/src/main/java/io/papermc/paper/plugin/lifecycle/event/types/LifecycleEventTypeProviderImpl.java +@@ -0,0 +1,25 @@ ++package io.papermc.paper.plugin.lifecycle.event.types; ++ ++import io.papermc.paper.plugin.lifecycle.event.LifecycleEvent; ++import io.papermc.paper.plugin.lifecycle.event.LifecycleEventOwner; ++import io.papermc.paper.plugin.lifecycle.event.LifecycleEventRunner; ++import org.checkerframework.checker.nullness.qual.NonNull; ++import org.checkerframework.framework.qual.DefaultQualifier; ++ ++@DefaultQualifier(NonNull.class) ++public final class LifecycleEventTypeProviderImpl implements LifecycleEventTypeProvider { ++ ++ public static LifecycleEventTypeProviderImpl instance() { ++ return (LifecycleEventTypeProviderImpl) LifecycleEventTypeProvider.provider(); ++ } ++ ++ @Override ++ public LifecycleEventType.Monitorable monitor(final String name, final Class ownerType) { ++ return new MonitorableLifecycleEventType<>(name, ownerType); ++ } ++ ++ @Override ++ public LifecycleEventType.Prioritizable prioritized(final String name, final Class ownerType) { ++ return new PrioritizableLifecycleEventType.Simple<>(name, ownerType); ++ } ++} +diff --git a/src/main/java/io/papermc/paper/plugin/lifecycle/event/types/MonitorableLifecycleEventType.java b/src/main/java/io/papermc/paper/plugin/lifecycle/event/types/MonitorableLifecycleEventType.java +new file mode 100644 +index 0000000000000000000000000000000000000000..abb969cf6ed967fe7720c56d3b3157bd1b74700d +--- /dev/null ++++ b/src/main/java/io/papermc/paper/plugin/lifecycle/event/types/MonitorableLifecycleEventType.java +@@ -0,0 +1,63 @@ ++package io.papermc.paper.plugin.lifecycle.event.types; ++ ++import io.papermc.paper.plugin.lifecycle.event.LifecycleEvent; ++import io.papermc.paper.plugin.lifecycle.event.LifecycleEventOwner; ++import io.papermc.paper.plugin.lifecycle.event.handler.LifecycleEventHandler; ++import io.papermc.paper.plugin.lifecycle.event.handler.configuration.AbstractLifecycleEventHandlerConfiguration; ++import io.papermc.paper.plugin.lifecycle.event.handler.configuration.MonitorLifecycleEventHandlerConfiguration; ++import io.papermc.paper.plugin.lifecycle.event.handler.configuration.MonitorLifecycleEventHandlerConfigurationImpl; ++import java.util.ArrayList; ++import java.util.List; ++import java.util.function.Consumer; ++import java.util.function.Predicate; ++import org.checkerframework.checker.nullness.qual.NonNull; ++import org.checkerframework.framework.qual.DefaultQualifier; ++ ++@DefaultQualifier(NonNull.class) ++public class MonitorableLifecycleEventType extends AbstractLifecycleEventType> implements LifecycleEventType.Monitorable { ++ ++ final List> handlers = new ArrayList<>(); ++ int nonMonitorIdx = 0; ++ ++ public MonitorableLifecycleEventType(final String name, final Class ownerType) { ++ super(name, ownerType); ++ } ++ ++ @Override ++ public boolean hasHandlers() { ++ return !this.handlers.isEmpty(); ++ } ++ ++ @Override ++ public MonitorLifecycleEventHandlerConfigurationImpl newHandler(final LifecycleEventHandler handler) { ++ return new MonitorLifecycleEventHandlerConfigurationImpl<>(handler, this); ++ } ++ ++ @Override ++ protected void register(final O owner, final AbstractLifecycleEventHandlerConfiguration config) { ++ if (!(config instanceof final MonitorLifecycleEventHandlerConfigurationImpl monitor)) { ++ throw new IllegalArgumentException("Configuration must be a MonitorLifecycleEventHandlerConfiguration"); ++ } ++ final RegisteredHandler registeredHandler = new RegisteredHandler<>(owner, config); ++ if (!monitor.isMonitor()) { ++ this.handlers.add(this.nonMonitorIdx, registeredHandler); ++ this.nonMonitorIdx++; ++ } else { ++ this.handlers.add(registeredHandler); ++ } ++ } ++ ++ @Override ++ public void forEachHandler(final E event, final Consumer> consumer, final Predicate> predicate) { ++ for (final RegisteredHandler handler : this.handlers) { ++ if (predicate.test(handler)) { ++ consumer.accept(handler); ++ } ++ } ++ } ++ ++ @Override ++ public void removeMatching(final Predicate> predicate) { ++ this.handlers.removeIf(predicate); ++ } ++} +diff --git a/src/main/java/io/papermc/paper/plugin/lifecycle/event/types/OwnerAwareLifecycleEvent.java b/src/main/java/io/papermc/paper/plugin/lifecycle/event/types/OwnerAwareLifecycleEvent.java +new file mode 100644 +index 0000000000000000000000000000000000000000..3e7e7474f301c0725fa2bcd6e19e476fc35f2d5a +--- /dev/null ++++ b/src/main/java/io/papermc/paper/plugin/lifecycle/event/types/OwnerAwareLifecycleEvent.java +@@ -0,0 +1,15 @@ ++package io.papermc.paper.plugin.lifecycle.event.types; ++ ++import io.papermc.paper.plugin.lifecycle.event.LifecycleEvent; ++import io.papermc.paper.plugin.lifecycle.event.LifecycleEventOwner; ++import org.checkerframework.checker.nullness.qual.NonNull; ++import org.checkerframework.checker.nullness.qual.Nullable; ++import org.checkerframework.framework.qual.DefaultQualifier; ++ ++@DefaultQualifier(NonNull.class) ++public interface OwnerAwareLifecycleEvent extends LifecycleEvent { ++ ++ void setOwner(@Nullable O owner); ++ ++ @Nullable O castOwner(LifecycleEventOwner owner); ++} +diff --git a/src/main/java/io/papermc/paper/plugin/lifecycle/event/types/PrioritizableLifecycleEventType.java b/src/main/java/io/papermc/paper/plugin/lifecycle/event/types/PrioritizableLifecycleEventType.java +new file mode 100644 +index 0000000000000000000000000000000000000000..2ed622a61ddc37b11888867770b513909b9a2ecc +--- /dev/null ++++ b/src/main/java/io/papermc/paper/plugin/lifecycle/event/types/PrioritizableLifecycleEventType.java +@@ -0,0 +1,79 @@ ++package io.papermc.paper.plugin.lifecycle.event.types; ++ ++import com.google.common.base.Preconditions; ++import io.papermc.paper.plugin.lifecycle.event.LifecycleEvent; ++import io.papermc.paper.plugin.lifecycle.event.LifecycleEventOwner; ++import io.papermc.paper.plugin.lifecycle.event.handler.LifecycleEventHandler; ++import io.papermc.paper.plugin.lifecycle.event.handler.configuration.AbstractLifecycleEventHandlerConfiguration; ++import io.papermc.paper.plugin.lifecycle.event.handler.configuration.PrioritizedLifecycleEventHandlerConfiguration; ++import io.papermc.paper.plugin.lifecycle.event.handler.configuration.PrioritizedLifecycleEventHandlerConfigurationImpl; ++import java.util.ArrayList; ++import java.util.Comparator; ++import java.util.List; ++import java.util.function.Consumer; ++import java.util.function.Predicate; ++import org.checkerframework.checker.nullness.qual.NonNull; ++import org.checkerframework.framework.qual.DefaultQualifier; ++ ++@DefaultQualifier(NonNull.class) ++public abstract class PrioritizableLifecycleEventType< ++ O extends LifecycleEventOwner, ++ E extends LifecycleEvent, ++ C extends PrioritizedLifecycleEventHandlerConfiguration ++> extends AbstractLifecycleEventType { ++ ++ private static final Comparator> COMPARATOR = Comparator.comparing(handler -> ((PrioritizedLifecycleEventHandlerConfigurationImpl) handler.config()).priority(), (o1, o2) -> { ++ if (o1.equals(o2)) { ++ return 0; ++ } else if (o1.isEmpty()) { ++ return 1; ++ } else if (o2.isEmpty()) { ++ return -1; ++ } else { ++ return Integer.compare(o1.getAsInt(), o2.getAsInt()); ++ } ++ }); ++ ++ private final List> handlers = new ArrayList<>(); ++ ++ public PrioritizableLifecycleEventType(final String name, final Class ownerType) { ++ super(name, ownerType); ++ } ++ ++ @Override ++ public boolean hasHandlers() { ++ return !this.handlers.isEmpty(); ++ } ++ ++ @Override ++ protected void register(final O owner, final AbstractLifecycleEventHandlerConfiguration config) { ++ Preconditions.checkArgument(config instanceof PrioritizedLifecycleEventHandlerConfigurationImpl, "Configuration must be a PrioritizedLifecycleEventHandlerConfiguration"); ++ this.handlers.add(new RegisteredHandler<>(owner, config)); ++ this.handlers.sort(COMPARATOR); ++ } ++ ++ @Override ++ public void forEachHandler(final E event, final Consumer> consumer, final Predicate> predicate) { ++ for (final RegisteredHandler handler : this.handlers) { ++ if (predicate.test(handler)) { ++ consumer.accept(handler); ++ } ++ } ++ } ++ ++ @Override ++ public void removeMatching(final Predicate> predicate) { ++ this.handlers.removeIf(predicate); ++ } ++ ++ public static class Simple extends PrioritizableLifecycleEventType> implements LifecycleEventType.Prioritizable { ++ public Simple(final String name, final Class ownerType) { ++ super(name, ownerType); ++ } ++ ++ @Override ++ public PrioritizedLifecycleEventHandlerConfiguration newHandler(final LifecycleEventHandler handler) { ++ return new PrioritizedLifecycleEventHandlerConfigurationImpl<>(handler, this); ++ } ++ } ++} +diff --git a/src/main/java/io/papermc/paper/plugin/manager/PaperPluginInstanceManager.java b/src/main/java/io/papermc/paper/plugin/manager/PaperPluginInstanceManager.java +index 834b85f24df023642f8abf7213fe578ac8c17a3e..3e82ea07ca4194844c5528446e2c4a46ff4acee5 100644 +--- a/src/main/java/io/papermc/paper/plugin/manager/PaperPluginInstanceManager.java ++++ b/src/main/java/io/papermc/paper/plugin/manager/PaperPluginInstanceManager.java +@@ -293,6 +293,15 @@ class PaperPluginInstanceManager { + + pluginName + " (Is it up to date?)", ex, plugin); // Paper + } + ++ // Paper start - lifecycle event system ++ try { ++ io.papermc.paper.plugin.lifecycle.event.LifecycleEventRunner.INSTANCE.unregisterAllEventHandlersFor(plugin); ++ } catch (Throwable ex) { ++ this.handlePluginException("Error occurred (in the plugin loader) while unregistering lifecycle event handlers for " ++ + pluginName + " (Is it up to date?)", ex, plugin); ++ } ++ // Paper end ++ + try { + this.server.getMessenger().unregisterIncomingPluginChannel(plugin); + this.server.getMessenger().unregisterOutgoingPluginChannel(plugin); +diff --git a/src/main/java/io/papermc/paper/plugin/storage/BootstrapProviderStorage.java b/src/main/java/io/papermc/paper/plugin/storage/BootstrapProviderStorage.java +index 2e96308696e131f3f013469a395e5ddda2c5d529..65a66e484c1c39c5f41d97db52f31c67b4479d20 100644 +--- a/src/main/java/io/papermc/paper/plugin/storage/BootstrapProviderStorage.java ++++ b/src/main/java/io/papermc/paper/plugin/storage/BootstrapProviderStorage.java +@@ -32,8 +32,9 @@ public class BootstrapProviderStorage extends SimpleProviderStorage provider, PluginBootstrap provided) { + try { +- BootstrapContext context = PluginBootstrapContextImpl.create(provider, PluginInitializerManager.instance().pluginDirectoryPath()); ++ PluginBootstrapContextImpl context = PluginBootstrapContextImpl.create(provider, PluginInitializerManager.instance().pluginDirectoryPath()); // Paper - lifecycle events + provided.bootstrap(context); ++ context.lockLifecycleEventRegistration(); // Paper - lifecycle events + return true; + } catch (Throwable e) { + LOGGER.error("Failed to run bootstrapper for %s. This plugin will not be loaded.".formatted(provider.getSource()), e); +diff --git a/src/main/java/org/bukkit/craftbukkit/CraftServer.java b/src/main/java/org/bukkit/craftbukkit/CraftServer.java +index a1f2c8fd0348a6a5dad521430f473867bdadd1a5..a2c749b2997557fec5c978f3bed8c35d7614e740 100644 +--- a/src/main/java/org/bukkit/craftbukkit/CraftServer.java ++++ b/src/main/java/org/bukkit/craftbukkit/CraftServer.java +@@ -1050,6 +1050,11 @@ public final class CraftServer implements Server { + + @Override + public void reload() { ++ // Paper start - lifecycle events ++ if (io.papermc.paper.plugin.lifecycle.event.LifecycleEventRunner.INSTANCE.blocksPluginReloading()) { ++ throw new IllegalStateException("A lifecycle event handler has been registered which makes reloading plugins not possible"); ++ } ++ // Paper end - lifecycle events + org.spigotmc.WatchdogThread.hasStarted = false; // Paper - Disable watchdog early timeout on reload + this.reloadCount++; + this.configuration = YamlConfiguration.loadConfiguration(this.getConfigFile()); +diff --git a/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java b/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java +index 30106a999db1bae217333b5e94913b9ec55e4615..06d66c8043daec3c736d82d972ceb98d55eae9d1 100644 +--- a/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java ++++ b/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java +@@ -638,6 +638,13 @@ public final class CraftMagicNumbers implements UnsafeValues { + } + // Paper end - spawn egg color visibility + ++ // Paper start - lifecycle event API ++ @Override ++ public io.papermc.paper.plugin.lifecycle.event.LifecycleEventManager createPluginLifecycleEventManager(final org.bukkit.plugin.java.JavaPlugin plugin, final java.util.function.BooleanSupplier registrationCheck) { ++ return new io.papermc.paper.plugin.lifecycle.event.PaperLifecycleEventManager<>(plugin, registrationCheck); ++ } ++ // Paper end - lifecycle event API ++ + /** + * This helper class represents the different NBT Tags. + *

    +diff --git a/src/main/resources/META-INF/services/io.papermc.paper.plugin.lifecycle.event.types.LifecycleEventTypeProvider b/src/main/resources/META-INF/services/io.papermc.paper.plugin.lifecycle.event.types.LifecycleEventTypeProvider +new file mode 100644 +index 0000000000000000000000000000000000000000..808b1192b60348ad05f0bfbdeda6f94df4876743 +--- /dev/null ++++ b/src/main/resources/META-INF/services/io.papermc.paper.plugin.lifecycle.event.types.LifecycleEventTypeProvider +@@ -0,0 +1 @@ ++io.papermc.paper.plugin.lifecycle.event.types.LifecycleEventTypeProviderImpl +diff --git a/src/test/java/io/papermc/paper/plugin/PaperTestPlugin.java b/src/test/java/io/papermc/paper/plugin/PaperTestPlugin.java +index 1d14f530ef888102e47eeeaf0d1a6076e51871c4..90cf0c702ca2ff9de64d9718ecba5f2d128953a6 100644 +--- a/src/test/java/io/papermc/paper/plugin/PaperTestPlugin.java ++++ b/src/test/java/io/papermc/paper/plugin/PaperTestPlugin.java +@@ -143,4 +143,11 @@ public class PaperTestPlugin extends PluginBase { + public List onTabComplete(CommandSender sender, Command command, String alias, String[] args) { + throw new UnsupportedOperationException("Not supported."); + } ++ ++ // Paper start - lifecycle events ++ @Override ++ public io.papermc.paper.plugin.lifecycle.event.LifecycleEventManager getLifecycleManager() { ++ throw new UnsupportedOperationException("Not supported."); ++ } ++ // Paper end - lifecycle events + } diff --git a/patches/server/0918-Validate-ResourceLocation-in-NBT-reading.patch b/patches/server/0918-Validate-ResourceLocation-in-NBT-reading.patch deleted file mode 100644 index 923bdbe37db3..000000000000 --- a/patches/server/0918-Validate-ResourceLocation-in-NBT-reading.patch +++ /dev/null @@ -1,174 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Nassim Jahnke -Date: Thu, 4 Jan 2024 13:49:14 +0100 -Subject: [PATCH] Validate ResourceLocation in NBT reading - - -diff --git a/src/main/java/net/minecraft/nbt/NbtUtils.java b/src/main/java/net/minecraft/nbt/NbtUtils.java -index 4929bac8e476664086470f078efce6c0a6164413..f88dd37783b3c155c23b547c360b8d3c16e030c0 100644 ---- a/src/main/java/net/minecraft/nbt/NbtUtils.java -+++ b/src/main/java/net/minecraft/nbt/NbtUtils.java -@@ -149,8 +149,10 @@ public final class NbtUtils { - if (!nbt.contains("Name", 8)) { - return Blocks.AIR.defaultBlockState(); - } else { -- ResourceLocation resourceLocation = ResourceLocation.parse(nbt.getString("Name")); -- Optional> optional = blockLookup.get(ResourceKey.create(Registries.BLOCK, resourceLocation)); -+ // Paper start - Validate resource location -+ ResourceLocation resourceLocation = ResourceLocation.tryParse(nbt.getString("Name")); -+ Optional> optional = resourceLocation != null ? blockLookup.get(ResourceKey.create(Registries.BLOCK, resourceLocation)) : Optional.empty(); -+ // Paper end - Validate resource location - if (optional.isEmpty()) { - return Blocks.AIR.defaultBlockState(); - } else { -diff --git a/src/main/java/net/minecraft/resources/ResourceLocation.java b/src/main/java/net/minecraft/resources/ResourceLocation.java -index 87afe84791af2d5e9f869cd4c09eed4bb5fee75b..1967c43ee3a12e63365cc40ee6565307e2fd73cf 100644 ---- a/src/main/java/net/minecraft/resources/ResourceLocation.java -+++ b/src/main/java/net/minecraft/resources/ResourceLocation.java -@@ -41,6 +41,13 @@ public final class ResourceLocation implements Comparable { - - assert isValidPath(path); - -+ // Paper start - Validate ResourceLocation -+ // Check for the max network string length (capped at Short.MAX_VALUE) as well as the max bytes of a StringTag (length written as an unsigned short) -+ final String resourceLocation = namespace + ":" + path; -+ if (resourceLocation.length() > Short.MAX_VALUE || io.netty.buffer.ByteBufUtil.utf8MaxBytes(resourceLocation) > 2 * Short.MAX_VALUE + 1) { -+ throw new ResourceLocationException("Resource location too long: " + resourceLocation); -+ } -+ // Paper end - Validate ResourceLocation - this.namespace = namespace; - this.path = path; - } -diff --git a/src/main/java/net/minecraft/world/RandomizableContainer.java b/src/main/java/net/minecraft/world/RandomizableContainer.java -index 084935138b1484f3d96e99f4e5655a6c04931907..9e357abe13f55bd9ce3a1d5348bcf19a15ea5433 100644 ---- a/src/main/java/net/minecraft/world/RandomizableContainer.java -+++ b/src/main/java/net/minecraft/world/RandomizableContainer.java -@@ -50,7 +50,7 @@ public interface RandomizableContainer extends Container { - - default boolean tryLoadLootTable(CompoundTag nbt) { - if (nbt.contains("LootTable", 8)) { -- this.setLootTable(ResourceKey.create(Registries.LOOT_TABLE, ResourceLocation.parse(nbt.getString("LootTable")))); -+ this.setLootTable(net.minecraft.Optionull.map(ResourceLocation.tryParse(nbt.getString("LootTable")), rl -> ResourceKey.create(Registries.LOOT_TABLE, rl))); // Paper - Validate ResourceLocation - if (this.lootableData() != null && this.getLootTable() != null) this.lootableData().loadNbt(nbt); // Paper - LootTable API - if (nbt.contains("LootTableSeed", 4)) { - this.setLootTableSeed(nbt.getLong("LootTableSeed")); -diff --git a/src/main/java/net/minecraft/world/entity/EntityType.java b/src/main/java/net/minecraft/world/entity/EntityType.java -index b98f9246b60daf31460f41ce214dfa7c011f5684..842b0cec0397d7ae5166617627340ffac0e35db1 100644 ---- a/src/main/java/net/minecraft/world/entity/EntityType.java -+++ b/src/main/java/net/minecraft/world/entity/EntityType.java -@@ -623,7 +623,7 @@ public class EntityType implements FeatureElement, EntityTypeT - } - - public static Optional> by(CompoundTag nbt) { -- return BuiltInRegistries.ENTITY_TYPE.getOptional(ResourceLocation.parse(nbt.getString("id"))); -+ return BuiltInRegistries.ENTITY_TYPE.getOptional(ResourceLocation.tryParse(nbt.getString("id"))); // Paper - Validate ResourceLocation - } - - @Nullable -diff --git a/src/main/java/net/minecraft/world/entity/Leashable.java b/src/main/java/net/minecraft/world/entity/Leashable.java -index e7535f15be3cc1537aafee53779ccfb4f21d1f38..bd6d587cedfe0e345536d7ebb6b7ca204f073efe 100644 ---- a/src/main/java/net/minecraft/world/entity/Leashable.java -+++ b/src/main/java/net/minecraft/world/entity/Leashable.java -@@ -54,7 +54,13 @@ public interface Leashable { - @Nullable - default Leashable.LeashData readLeashData(CompoundTag nbt) { - if (nbt.contains("leash", 10)) { -- return new Leashable.LeashData(Either.left(nbt.getCompound("leash").getUUID("UUID"))); -+ // Paper start -+ final CompoundTag leashTag = nbt.getCompound("leash"); -+ if (!leashTag.hasUUID("UUID")) { -+ return null; -+ } -+ return new Leashable.LeashData(Either.left(leashTag.getUUID("UUID"))); -+ // Paper end - } else { - if (nbt.contains("leash", 11)) { - Either either = (Either) NbtUtils.readBlockPos(nbt, "leash").map(Either::right).orElse(null); // CraftBukkit - decompile error -diff --git a/src/main/java/net/minecraft/world/entity/LivingEntity.java b/src/main/java/net/minecraft/world/entity/LivingEntity.java -index 13ef1ad250b56dbadba0244186e369d7ba9b5c0e..94169703c5a8111df1ed550d57f59f4a3bb97ae1 100644 ---- a/src/main/java/net/minecraft/world/entity/LivingEntity.java -+++ b/src/main/java/net/minecraft/world/entity/LivingEntity.java -@@ -887,11 +887,13 @@ public abstract class LivingEntity extends Entity implements Attackable { - if (nbt.contains("SleepingX", 99) && nbt.contains("SleepingY", 99) && nbt.contains("SleepingZ", 99)) { - BlockPos blockposition = new BlockPos(nbt.getInt("SleepingX"), nbt.getInt("SleepingY"), nbt.getInt("SleepingZ")); - -+ if (this.position().distanceToSqr(blockposition.getX(), blockposition.getY(), blockposition.getZ()) < 16 * 16) { // Paper - The sleeping pos will always also set the actual pos, so a desync suggests something is wrong - this.setSleepingPos(blockposition); - this.entityData.set(LivingEntity.DATA_POSE, Pose.SLEEPING); - if (!this.firstTick) { - this.setPosToBed(blockposition); - } -+ } // Paper - The sleeping pos will always also set the actual pos, so a desync suggests something is wrong - } - - if (nbt.contains("Brain", 10)) { -diff --git a/src/main/java/net/minecraft/world/entity/Mob.java b/src/main/java/net/minecraft/world/entity/Mob.java -index cf1f6a0081f0dbcf43842ec23accf6c3ae9b79d8..58ff5b4df2124901df757315e42b2490a7da7415 100644 ---- a/src/main/java/net/minecraft/world/entity/Mob.java -+++ b/src/main/java/net/minecraft/world/entity/Mob.java -@@ -603,7 +603,7 @@ public abstract class Mob extends LivingEntity implements EquipmentUser, Leashab - this.leashData = this.readLeashData(nbt); - this.setLeftHanded(nbt.getBoolean("LeftHanded")); - if (nbt.contains("DeathLootTable", 8)) { -- this.lootTable = ResourceKey.create(Registries.LOOT_TABLE, ResourceLocation.parse(nbt.getString("DeathLootTable"))); -+ this.lootTable = net.minecraft.Optionull.map(ResourceLocation.tryParse(nbt.getString("DeathLootTable")), rl -> ResourceKey.create(Registries.LOOT_TABLE, rl)); // Paper - Validate ResourceLocation - this.lootTableSeed = nbt.getLong("DeathLootTableSeed"); - } - -diff --git a/src/main/java/net/minecraft/world/entity/projectile/AbstractArrow.java b/src/main/java/net/minecraft/world/entity/projectile/AbstractArrow.java -index 10d30304c8c89b1f2a55be8529035311d1424e44..ddf47dab1ab92c45e3eea09239d418a9798ed59e 100644 ---- a/src/main/java/net/minecraft/world/entity/projectile/AbstractArrow.java -+++ b/src/main/java/net/minecraft/world/entity/projectile/AbstractArrow.java -@@ -649,7 +649,7 @@ public abstract class AbstractArrow extends Projectile { - this.setCritArrow(nbt.getBoolean("crit")); - this.setPierceLevel(nbt.getByte("PierceLevel")); - if (nbt.contains("SoundEvent", 8)) { -- this.soundEvent = (SoundEvent) BuiltInRegistries.SOUND_EVENT.getOptional(ResourceLocation.parse(nbt.getString("SoundEvent"))).orElse(this.getDefaultHitGroundSoundEvent()); -+ this.soundEvent = (SoundEvent) BuiltInRegistries.SOUND_EVENT.getOptional(ResourceLocation.tryParse(nbt.getString("SoundEvent"))).orElse(this.getDefaultHitGroundSoundEvent()); // Paper - Validate resource location - } - - if (nbt.contains("item", 10)) { -diff --git a/src/main/java/net/minecraft/world/entity/vehicle/ContainerEntity.java b/src/main/java/net/minecraft/world/entity/vehicle/ContainerEntity.java -index ccc7367ab2740bea0f2b907223a0920b11665092..845eff7401b811c179dc9dee70eca0d724be5c80 100644 ---- a/src/main/java/net/minecraft/world/entity/vehicle/ContainerEntity.java -+++ b/src/main/java/net/minecraft/world/entity/vehicle/ContainerEntity.java -@@ -73,7 +73,7 @@ public interface ContainerEntity extends Container, MenuProvider { - default void readChestVehicleSaveData(CompoundTag nbt, HolderLookup.Provider registriesLookup) { - this.clearItemStacks(); - if (nbt.contains("LootTable", 8)) { -- this.setLootTable(ResourceKey.create(Registries.LOOT_TABLE, ResourceLocation.parse(nbt.getString("LootTable")))); -+ this.setLootTable(net.minecraft.Optionull.map(ResourceLocation.tryParse(nbt.getString("LootTable")), rl -> ResourceKey.create(Registries.LOOT_TABLE, rl))); // Paper - Validate ResourceLocation - // Paper start - LootTable API - if (this.getLootTable() != null) { - this.lootableData().loadNbt(nbt); -diff --git a/src/main/java/net/minecraft/world/level/block/entity/AbstractFurnaceBlockEntity.java b/src/main/java/net/minecraft/world/level/block/entity/AbstractFurnaceBlockEntity.java -index 65ab9b22f724877b68f4f25aad2831e2cb080b19..730aca233f6e7564d4cb85b5b628d23c4f01d2f4 100644 ---- a/src/main/java/net/minecraft/world/level/block/entity/AbstractFurnaceBlockEntity.java -+++ b/src/main/java/net/minecraft/world/level/block/entity/AbstractFurnaceBlockEntity.java -@@ -295,7 +295,12 @@ public abstract class AbstractFurnaceBlockEntity extends BaseContainerBlockEntit - while (iterator.hasNext()) { - String s = (String) iterator.next(); - -- this.recipesUsed.put(ResourceLocation.parse(s), nbttagcompound1.getInt(s)); -+ // Paper start - Validate ResourceLocation -+ final ResourceLocation resourceLocation = ResourceLocation.tryParse(s); -+ if (resourceLocation != null) { -+ this.recipesUsed.put(resourceLocation, nbttagcompound1.getInt(s)); -+ } -+ // Paper end - Validate ResourceLocation - } - - // Paper start - cook speed multiplier API -diff --git a/src/main/java/net/minecraft/world/level/block/entity/BrushableBlockEntity.java b/src/main/java/net/minecraft/world/level/block/entity/BrushableBlockEntity.java -index 32de29c385c784ab87e29b2e072f3992386cd775..dc02a3d84b397f634f77f4df9c06e245cc4dcb75 100644 ---- a/src/main/java/net/minecraft/world/level/block/entity/BrushableBlockEntity.java -+++ b/src/main/java/net/minecraft/world/level/block/entity/BrushableBlockEntity.java -@@ -202,7 +202,7 @@ public class BrushableBlockEntity extends BlockEntity { - - private boolean tryLoadLootTable(CompoundTag nbt) { - if (nbt.contains("LootTable", 8)) { -- this.lootTable = ResourceKey.create(Registries.LOOT_TABLE, ResourceLocation.parse(nbt.getString("LootTable"))); -+ this.lootTable = net.minecraft.Optionull.map(ResourceLocation.tryParse(nbt.getString("LootTable")), rl -> ResourceKey.create(Registries.LOOT_TABLE, rl)); // Paper - Validate ResourceLocation - this.lootTableSeed = nbt.getLong("LootTableSeed"); - return true; - } else { diff --git a/patches/server/0919-ItemStack-Tooltip-API.patch b/patches/server/0919-ItemStack-Tooltip-API.patch new file mode 100644 index 000000000000..f06c4356cdba --- /dev/null +++ b/patches/server/0919-ItemStack-Tooltip-API.patch @@ -0,0 +1,30 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Yannick Lamprecht +Date: Mon, 22 Jan 2024 13:27:30 +0100 +Subject: [PATCH] ItemStack Tooltip API + + +diff --git a/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java b/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java +index 06d66c8043daec3c736d82d972ceb98d55eae9d1..51d5629b00ec4929c12ed9e6ba5a37f5903cf13e 100644 +--- a/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java ++++ b/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java +@@ -627,6 +627,19 @@ public final class CraftMagicNumbers implements UnsafeValues { + if (statistic.getType() != org.bukkit.Statistic.Type.UNTYPED) return "minecraft.custom:minecraft." + statistic.getKey().getKey(); + return org.bukkit.craftbukkit.CraftStatistic.getNMSStatistic(statistic).getName(); + } ++ ++ @Override ++ public java.util.List computeTooltipLines(final ItemStack itemStack, final io.papermc.paper.inventory.tooltip.TooltipContext tooltipContext, final org.bukkit.entity.Player player) { ++ Preconditions.checkArgument(tooltipContext != null, "tooltipContext cannot be null"); ++ net.minecraft.world.item.TooltipFlag.Default flag = tooltipContext.isAdvanced() ? net.minecraft.world.item.TooltipFlag.ADVANCED : net.minecraft.world.item.TooltipFlag.NORMAL; ++ if (tooltipContext.isCreative()) { ++ flag = flag.asCreative(); ++ } ++ final java.util.List lines = CraftItemStack.asNMSCopy(itemStack).getTooltipLines( ++ net.minecraft.world.item.Item.TooltipContext.of(player == null ? net.minecraft.server.MinecraftServer.getServer().registryAccess() : ((org.bukkit.craftbukkit.entity.CraftPlayer) player).getHandle().level().registryAccess()), ++ player == null ? null : ((org.bukkit.craftbukkit.entity.CraftPlayer) player).getHandle(), flag); ++ return lines.stream().map(io.papermc.paper.adventure.PaperAdventure::asAdventure).toList(); ++ } + // Paper end + + // Paper start - spawn egg color visibility diff --git a/patches/server/0919-Properly-handle-experience-dropping-on-block-break.patch b/patches/server/0919-Properly-handle-experience-dropping-on-block-break.patch deleted file mode 100644 index c5ffca5d89b5..000000000000 --- a/patches/server/0919-Properly-handle-experience-dropping-on-block-break.patch +++ /dev/null @@ -1,94 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Owen1212055 <23108066+Owen1212055@users.noreply.github.com> -Date: Sat, 30 Dec 2023 15:00:06 -0500 -Subject: [PATCH] Properly handle experience dropping on block break - -This causes spawnAfterBreak to spawn xp by default, removing the need to manually add xp wherever this method is used. -For classes that use custom xp amounts, they can drop the resources with disabling - -diff --git a/src/main/java/net/minecraft/world/level/Level.java b/src/main/java/net/minecraft/world/level/Level.java -index 3f87e60c0d43703a8450b5920dac59a970809397..471fd54edf6aa962d997878ee638974f7f594fa8 100644 ---- a/src/main/java/net/minecraft/world/level/Level.java -+++ b/src/main/java/net/minecraft/world/level/Level.java -@@ -617,7 +617,8 @@ public abstract class Level implements LevelAccessor, AutoCloseable { - if (drop) { - BlockEntity tileentity = iblockdata.hasBlockEntity() ? this.getBlockEntity(pos) : null; - -- Block.dropResources(iblockdata, this, pos, tileentity, breakingEntity, ItemStack.EMPTY); -+ Block.dropResources(iblockdata, this, pos, tileentity, breakingEntity, ItemStack.EMPTY, false); // Paper - Properly handle xp dropping -+ iblockdata.getBlock().popExperience((ServerLevel) this, pos, xp, breakingEntity); // Paper - Properly handle xp dropping; custom amount - } - - boolean flag1 = this.setBlock(pos, fluid.createLegacyBlock(), 3, maxUpdateDepth); -diff --git a/src/main/java/net/minecraft/world/level/block/Block.java b/src/main/java/net/minecraft/world/level/block/Block.java -index c083dc8b2a69c3747b250d13f1a28ad22b5e6119..bf52c36f31992a01a7403d8c85151327c9e944c4 100644 ---- a/src/main/java/net/minecraft/world/level/block/Block.java -+++ b/src/main/java/net/minecraft/world/level/block/Block.java -@@ -312,23 +312,31 @@ public class Block extends BlockBehaviour implements ItemLike { - for (ItemStack drop : Block.getDrops(state, serverLevel, pos, blockEntity)) { - items.add(org.bukkit.craftbukkit.inventory.CraftItemStack.asBukkitCopy(drop)); - } -+ Block block = state.getBlock(); // Paper - Properly handle xp dropping - io.papermc.paper.event.block.BlockBreakBlockEvent event = new io.papermc.paper.event.block.BlockBreakBlockEvent(org.bukkit.craftbukkit.block.CraftBlock.at(levelAccessor, pos), org.bukkit.craftbukkit.block.CraftBlock.at(levelAccessor, source), items); -+ event.setExpToDrop(block.getExpDrop(state, serverLevel, pos, net.minecraft.world.item.ItemStack.EMPTY, true)); // Paper - Properly handle xp dropping - event.callEvent(); - for (org.bukkit.inventory.ItemStack drop : event.getDrops()) { - popResource(serverLevel, pos, org.bukkit.craftbukkit.inventory.CraftItemStack.asNMSCopy(drop)); - } -- state.spawnAfterBreak(serverLevel, pos, ItemStack.EMPTY, true); -+ state.spawnAfterBreak(serverLevel, pos, ItemStack.EMPTY, false); // Paper - Properly handle xp dropping -+ block.popExperience(serverLevel, pos, event.getExpToDrop()); // Paper - Properly handle xp dropping - } - return true; - } - // Paper end - Add BlockBreakBlockEvent - - public static void dropResources(BlockState state, Level world, BlockPos pos, @Nullable BlockEntity blockEntity, @Nullable Entity entity, ItemStack tool) { -+ // Paper start - Properly handle xp dropping -+ dropResources(state, world, pos, blockEntity, entity, tool, true); -+ } -+ public static void dropResources(BlockState state, Level world, BlockPos pos, @Nullable BlockEntity blockEntity, @Nullable Entity entity, ItemStack tool, boolean dropExperience) { -+ // Paper end - Properly handle xp dropping - if (world instanceof ServerLevel) { - Block.getDrops(state, (ServerLevel) world, pos, blockEntity, entity, tool).forEach((itemstack1) -> { - Block.popResource(world, pos, itemstack1); - }); -- state.spawnAfterBreak((ServerLevel) world, pos, tool, true); -+ state.spawnAfterBreak((ServerLevel) world, pos, tool, dropExperience); // Paper - Properly handle xp dropping - } - - } -@@ -412,7 +420,7 @@ public class Block extends BlockBehaviour implements ItemLike { - player.awardStat(Stats.BLOCK_MINED.get(this)); - player.causeFoodExhaustion(0.005F, org.bukkit.event.entity.EntityExhaustionEvent.ExhaustionReason.BLOCK_MINED); // CraftBukkit - EntityExhaustionEvent - if (includeDrops) { // Paper - fix drops not preventing stats/food exhaustion -- Block.dropResources(state, world, pos, blockEntity, player, tool); -+ Block.dropResources(state, world, pos, blockEntity, player, tool, dropExp); // Paper - Properly handle xp dropping - } // Paper - fix drops not preventing stats/food exhaustion - } - -diff --git a/src/main/java/net/minecraft/world/level/block/state/BlockBehaviour.java b/src/main/java/net/minecraft/world/level/block/state/BlockBehaviour.java -index a8bec3c405732e5863cf717b1fe948d00837bed2..240c250a93289776686d09d7eae17c07d7278da5 100644 ---- a/src/main/java/net/minecraft/world/level/block/state/BlockBehaviour.java -+++ b/src/main/java/net/minecraft/world/level/block/state/BlockBehaviour.java -@@ -1119,6 +1119,7 @@ public abstract class BlockBehaviour implements FeatureElement { - - public void spawnAfterBreak(ServerLevel world, BlockPos pos, ItemStack tool, boolean dropExperience) { - this.getBlock().spawnAfterBreak(this.asState(), world, pos, tool, dropExperience); -+ if (dropExperience) {getBlock().popExperience(world, pos, this.getBlock().getExpDrop(asState(), world, pos, tool, true));} // Paper - Properly handle xp dropping - } - - public List getDrops(LootParams.Builder builder) { -diff --git a/src/main/java/org/bukkit/craftbukkit/block/CraftBlock.java b/src/main/java/org/bukkit/craftbukkit/block/CraftBlock.java -index 461a66c323a74db5a70981fafc5fa20f54f0f40d..ac11f18690434922179b61ffcc3036dea025b0cb 100644 ---- a/src/main/java/org/bukkit/craftbukkit/block/CraftBlock.java -+++ b/src/main/java/org/bukkit/craftbukkit/block/CraftBlock.java -@@ -509,7 +509,7 @@ public class CraftBlock implements Block { - - // Modelled off EntityHuman#hasBlock - if (block != Blocks.AIR && (item == null || !iblockdata.requiresCorrectToolForDrops() || nmsItem.isCorrectToolForDrops(iblockdata))) { -- net.minecraft.world.level.block.Block.dropResources(iblockdata, this.world.getMinecraftWorld(), this.position, this.world.getBlockEntity(this.position), null, nmsItem); -+ net.minecraft.world.level.block.Block.dropResources(iblockdata, this.world.getMinecraftWorld(), this.position, this.world.getBlockEntity(this.position), null, nmsItem, false); // Paper - Properly handle xp dropping - // Paper start - improve Block#breanNaturally - if (triggerEffect) { - if (iblockdata.getBlock() instanceof net.minecraft.world.level.block.BaseFireBlock) { diff --git a/patches/server/0920-Add-getChunkSnapshot-includeLightData-parameter.patch b/patches/server/0920-Add-getChunkSnapshot-includeLightData-parameter.patch new file mode 100644 index 000000000000..ba33560babab --- /dev/null +++ b/patches/server/0920-Add-getChunkSnapshot-includeLightData-parameter.patch @@ -0,0 +1,70 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Warrior <50800980+Warriorrrr@users.noreply.github.com> +Date: Sat, 10 Feb 2024 10:03:48 +0100 +Subject: [PATCH] Add getChunkSnapshot includeLightData parameter + + +diff --git a/src/main/java/org/bukkit/craftbukkit/CraftChunk.java b/src/main/java/org/bukkit/craftbukkit/CraftChunk.java +index 887a17a0833064eb5701222e5fb6f5ccf9511588..5fc9e8e969debb3e15ed474b36a1c48b086d0449 100644 +--- a/src/main/java/org/bukkit/craftbukkit/CraftChunk.java ++++ b/src/main/java/org/bukkit/craftbukkit/CraftChunk.java +@@ -328,12 +328,21 @@ public class CraftChunk implements Chunk { + + @Override + public ChunkSnapshot getChunkSnapshot(boolean includeMaxBlockY, boolean includeBiome, boolean includeBiomeTempRain) { ++ // Paper start - Add getChunkSnapshot includeLightData parameter ++ return getChunkSnapshot(includeMaxBlockY, includeBiome, includeBiomeTempRain, true); ++ } ++ ++ @Override ++ public ChunkSnapshot getChunkSnapshot(boolean includeMaxBlockY, boolean includeBiome, boolean includeBiomeTempRain, boolean includeLightData) { ++ // Paper end - Add getChunkSnapshot includeLightData parameter + ChunkAccess chunk = this.getHandle(ChunkStatus.FULL); + + LevelChunkSection[] cs = chunk.getSections(); + PalettedContainer[] sectionBlockIDs = new PalettedContainer[cs.length]; +- byte[][] sectionSkyLights = new byte[cs.length][]; +- byte[][] sectionEmitLights = new byte[cs.length][]; ++ // Paper start - Add getChunkSnapshot includeLightData parameter ++ byte[][] sectionSkyLights = includeLightData ? new byte[cs.length][] : null; ++ byte[][] sectionEmitLights = includeLightData ? new byte[cs.length][] : null; ++ // Paper end - Add getChunkSnapshot includeLightData parameter + boolean[] sectionEmpty = new boolean[cs.length]; + PalettedContainerRO>[] biome = (includeBiome || includeBiomeTempRain) ? new PalettedContainer[cs.length] : null; + +@@ -350,6 +359,7 @@ public class CraftChunk implements Chunk { + } + // Paper end - Fix ChunkSnapshot#isSectionEmpty(int) + ++ if (includeLightData) { // Paper - Add getChunkSnapshot includeLightData parameter + LevelLightEngine lightengine = this.worldServer.getLightEngine(); + DataLayer skyLightArray = lightengine.getLayerListener(LightLayer.SKY).getDataLayerData(SectionPos.of(this.x, chunk.getSectionYFromSectionIndex(i), this.z)); // SPIGOT-7498: Convert section index + if (skyLightArray == null) { +@@ -365,6 +375,7 @@ public class CraftChunk implements Chunk { + sectionEmitLights[i] = new byte[2048]; + System.arraycopy(emitLightArray.getData(), 0, sectionEmitLights[i], 0, 2048); + } ++ } // Paper - Add getChunkSnapshot includeLightData parameter + + if (biome != null) { + biome[i] = ((PalettedContainer>) cs[i].getBiomes()).copy(); // Paper - Perf: use copy instead of round tripping with codecs +diff --git a/src/main/java/org/bukkit/craftbukkit/CraftChunkSnapshot.java b/src/main/java/org/bukkit/craftbukkit/CraftChunkSnapshot.java +index c88e4ba701b2a2325b76478b7f47278157afd2ef..ddcbdbcbfeb6efe0a587b1181505423cc9d2c951 100644 +--- a/src/main/java/org/bukkit/craftbukkit/CraftChunkSnapshot.java ++++ b/src/main/java/org/bukkit/craftbukkit/CraftChunkSnapshot.java +@@ -119,6 +119,7 @@ public class CraftChunkSnapshot implements ChunkSnapshot { + + @Override + public final int getBlockSkyLight(int x, int y, int z) { ++ Preconditions.checkState(this.skylight != null, "ChunkSnapshot created without light data. Please call getSnapshot with includeLightData=true"); // Paper - Add getChunkSnapshot includeLightData parameter + this.validateChunkCoordinates(x, y, z); + + int off = ((y & 0xF) << 7) | (z << 3) | (x >> 1); +@@ -127,6 +128,7 @@ public class CraftChunkSnapshot implements ChunkSnapshot { + + @Override + public final int getBlockEmittedLight(int x, int y, int z) { ++ Preconditions.checkState(this.emitlight != null, "ChunkSnapshot created without light data. Please call getSnapshot with includeLightData=true"); // Paper - Add getChunkSnapshot includeLightData parameter + this.validateChunkCoordinates(x, y, z); + + int off = ((y & 0xF) << 7) | (z << 3) | (x >> 1); diff --git a/patches/server/0920-Fixup-NamespacedKey-handling.patch b/patches/server/0920-Fixup-NamespacedKey-handling.patch deleted file mode 100644 index f5d990944ba5..000000000000 --- a/patches/server/0920-Fixup-NamespacedKey-handling.patch +++ /dev/null @@ -1,170 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Nassim Jahnke -Date: Sat, 6 Jan 2024 14:31:00 +0100 -Subject: [PATCH] Fixup NamespacedKey handling - - -diff --git a/src/main/java/org/bukkit/craftbukkit/CraftLootTable.java b/src/main/java/org/bukkit/craftbukkit/CraftLootTable.java -index e34deaf398dc6722c3128bdd6b9bc16da2d33bf7..f028daa4f23a1f1868c9922991259739cadc5da2 100644 ---- a/src/main/java/org/bukkit/craftbukkit/CraftLootTable.java -+++ b/src/main/java/org/bukkit/craftbukkit/CraftLootTable.java -@@ -38,7 +38,7 @@ public class CraftLootTable implements org.bukkit.loot.LootTable { - } - - public static org.bukkit.loot.LootTable minecraftToBukkit(ResourceKey minecraft) { -- return (minecraft == null) ? null : Bukkit.getLootTable(CraftLootTable.minecraftToBukkitKey(minecraft)); -+ return (minecraft == null || minecraft.location().getPath().isEmpty()) ? null : Bukkit.getLootTable(CraftLootTable.minecraftToBukkitKey(minecraft)); // Paper - fix some NamespacedKey parsing - } - - public static NamespacedKey minecraftToBukkitKey(ResourceKey minecraft) { -diff --git a/src/main/java/org/bukkit/craftbukkit/CraftRegistry.java b/src/main/java/org/bukkit/craftbukkit/CraftRegistry.java -index 9b78bfd187a1bd5f99af5690f194441904956c7d..bd16933a5341908b21e549f66080c33466ad1079 100644 ---- a/src/main/java/org/bukkit/craftbukkit/CraftRegistry.java -+++ b/src/main/java/org/bukkit/craftbukkit/CraftRegistry.java -@@ -122,6 +122,16 @@ public class CraftRegistry implements Registry { - + ", this can happen if a plugin creates its own registry entry with out properly registering it."); - } - -+ // Paper start - fixup upstream being dum -+ public static java.util.Optional unwrapAndConvertHolder(final io.papermc.paper.registry.RegistryKey registryKey, final Holder value) { -+ return unwrapAndConvertHolder(io.papermc.paper.registry.RegistryAccess.registryAccess().getRegistry(registryKey), value); -+ } -+ -+ public static java.util.Optional unwrapAndConvertHolder(final Registry registry, final Holder value) { -+ return value.unwrapKey().map(key -> registry.get(CraftNamespacedKey.fromMinecraft(key.location()))); -+ } -+ // Paper end - fixup upstream being dum -+ - // Paper - move to PaperRegistries - - // Paper - NOTE: As long as all uses of the method below relate to *serialization* via ConfigurationSerializable, it's fine -diff --git a/src/main/java/org/bukkit/craftbukkit/attribute/CraftAttribute.java b/src/main/java/org/bukkit/craftbukkit/attribute/CraftAttribute.java -index cc97638e038ea64ad180ebfded2528aa07d1809e..10e4318782107644f67818109784fff60d017e0a 100644 ---- a/src/main/java/org/bukkit/craftbukkit/attribute/CraftAttribute.java -+++ b/src/main/java/org/bukkit/craftbukkit/attribute/CraftAttribute.java -@@ -37,6 +37,7 @@ public class CraftAttribute { - string = FieldRename.convertAttributeName(ApiVersion.CURRENT, string); - string = string.toLowerCase(Locale.ROOT); - NamespacedKey key = NamespacedKey.fromString(string); -+ if (key == null) return null; // Paper - Fixup NamespacedKey handling - - // Now also convert from when keys where saved - return CraftRegistry.get(Registry.ATTRIBUTE, key, ApiVersion.CURRENT); -diff --git a/src/main/java/org/bukkit/craftbukkit/block/CraftBanner.java b/src/main/java/org/bukkit/craftbukkit/block/CraftBanner.java -index afed8bdb9bd6a135e9b5f7bd9bfc61964cb240f7..bb2d1dddca6bfe719b28df136e80a7c5a339a5ce 100644 ---- a/src/main/java/org/bukkit/craftbukkit/block/CraftBanner.java -+++ b/src/main/java/org/bukkit/craftbukkit/block/CraftBanner.java -@@ -38,7 +38,11 @@ public class CraftBanner extends CraftBlockEntityState implem - if (banner.getPatterns() != null) { - for (int i = 0; i < banner.getPatterns().layers().size(); i++) { - BannerPatternLayers.Layer p = banner.getPatterns().layers().get(i); -- this.patterns.add(new Pattern(DyeColor.getByWoolData((byte) p.color().getId()), CraftPatternType.minecraftHolderToBukkit(p.pattern()))); -+ // Paper start - fix upstream not handling inlined banner pattern -+ java.util.Optional type = org.bukkit.craftbukkit.CraftRegistry.unwrapAndConvertHolder(org.bukkit.Registry.BANNER_PATTERN, p.pattern()); -+ if (type.isEmpty()) continue; -+ this.patterns.add(new Pattern(DyeColor.getByWoolData((byte) p.color().getId()), type.get())); -+ // Paper end - fix upstream not handling inlined banner pattern - } - } - } -diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftPainting.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftPainting.java -index bcac1359c667ef1ee46384f9c7a5adf4010d2b08..98a4463c9f194f33f4f85d95a0b9fa061cf6faaf 100644 ---- a/src/main/java/org/bukkit/craftbukkit/entity/CraftPainting.java -+++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftPainting.java -@@ -16,7 +16,7 @@ public class CraftPainting extends CraftHanging implements Painting { - - @Override - public Art getArt() { -- return CraftArt.minecraftHolderToBukkit(this.getHandle().getVariant()); -+ return org.bukkit.craftbukkit.CraftRegistry.unwrapAndConvertHolder(org.bukkit.Registry.ART, this.getHandle().getVariant()).orElseThrow(() -> new IllegalStateException("Inlined/custom painting variants are not supported yet in the API!")); // Paper - } - - @Override -diff --git a/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaArmor.java b/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaArmor.java -index 2c57fd269484ed79814d974877585f9f7e6393d3..865977ce17fbb8793a1eefd71079729e83f5cfaf 100644 ---- a/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaArmor.java -+++ b/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaArmor.java -@@ -38,8 +38,9 @@ public class CraftMetaArmor extends CraftMetaItem implements ArmorMeta { - super(tag); - - getOrEmpty(tag, CraftMetaArmor.TRIM).ifPresent((trimCompound) -> { -- TrimMaterial trimMaterial = CraftTrimMaterial.minecraftHolderToBukkit(trimCompound.material()); -- TrimPattern trimPattern = CraftTrimPattern.minecraftHolderToBukkit(trimCompound.pattern()); -+ TrimMaterial trimMaterial = org.bukkit.craftbukkit.CraftRegistry.unwrapAndConvertHolder(io.papermc.paper.registry.RegistryKey.TRIM_MATERIAL, trimCompound.material()).orElse(null); // Paper - fix upstream not being correct -+ TrimPattern trimPattern = org.bukkit.craftbukkit.CraftRegistry.unwrapAndConvertHolder(io.papermc.paper.registry.RegistryKey.TRIM_PATTERN, trimCompound.pattern()).orElse(null); // Paper - fix upstream not being correct -+ if (trimMaterial == null || trimPattern == null) return; // Paper - just delete the trim because upstream is not doing this right - - this.trim = new ArmorTrim(trimMaterial, trimPattern); - -diff --git a/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaBanner.java b/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaBanner.java -index 1c1a2d66d1ebcbe2ded732e759d0f9d471d43b56..eb44c19f6af624df458981e46c73a64358d6e1ce 100644 ---- a/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaBanner.java -+++ b/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaBanner.java -@@ -42,7 +42,7 @@ public class CraftMetaBanner extends CraftMetaItem implements BannerMeta { - for (int i = 0; i < Math.min(patterns.size(), 20); i++) { - BannerPatternLayers.Layer p = patterns.get(i); - DyeColor color = DyeColor.getByWoolData((byte) p.color().getId()); -- PatternType pattern = CraftPatternType.minecraftHolderToBukkit(p.pattern()); -+ PatternType pattern = org.bukkit.craftbukkit.CraftRegistry.unwrapAndConvertHolder(org.bukkit.Registry.BANNER_PATTERN, p.pattern()).orElse(null); // Paper - fix upstream not handling inlined banner pattern - - if (color != null && pattern != null) { - this.patterns.add(new Pattern(color, pattern)); -diff --git a/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaMusicInstrument.java b/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaMusicInstrument.java -index 478059eb3ad76b41e6a20e9b489a2a4fb19e7c7c..76a3e4893cbdba903a712d6db1d30b9c644795be 100644 ---- a/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaMusicInstrument.java -+++ b/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaMusicInstrument.java -@@ -30,7 +30,7 @@ public class CraftMetaMusicInstrument extends CraftMetaItem implements MusicInst - super(tag); - - getOrEmpty(tag, CraftMetaMusicInstrument.GOAT_HORN_INSTRUMENT).ifPresent((instrument) -> { -- this.instrument = CraftMusicInstrument.minecraftHolderToBukkit(instrument); -+ this.instrument = org.bukkit.craftbukkit.CraftRegistry.unwrapAndConvertHolder(org.bukkit.Registry.INSTRUMENT, instrument).orElse(null); // Paper - fix upstream not handling inlined instrument - }); - } - -diff --git a/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaShield.java b/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaShield.java -index c8eec04685456d89cb41466cddcc3975d0ceeb29..bcd6cc29e4e621805cbd923d747f652ced240c6d 100644 ---- a/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaShield.java -+++ b/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaShield.java -@@ -17,6 +17,7 @@ import org.bukkit.block.BlockState; - import org.bukkit.block.banner.Pattern; - import org.bukkit.block.banner.PatternType; - import org.bukkit.configuration.serialization.DelegateDeserialization; -+import org.bukkit.craftbukkit.CraftRegistry; - import org.bukkit.craftbukkit.block.CraftBlockStates; - import org.bukkit.craftbukkit.block.banner.CraftPatternType; - import org.bukkit.inventory.meta.BlockStateMeta; -@@ -53,7 +54,7 @@ public class CraftMetaShield extends CraftMetaItem implements ShieldMeta, BlockS - for (int i = 0; i < Math.min(patterns.size(), 20); i++) { - BannerPatternLayers.Layer p = patterns.get(i); - DyeColor color = DyeColor.getByWoolData((byte) p.color().getId()); -- PatternType pattern = CraftPatternType.minecraftHolderToBukkit(p.pattern()); -+ PatternType pattern = CraftRegistry.unwrapAndConvertHolder(io.papermc.paper.registry.RegistryKey.BANNER_PATTERN, p.pattern()).orElse(null); // Paper - fix upstream not being correct - - if (color != null && pattern != null) { - this.addPattern(new Pattern(color, pattern)); -diff --git a/src/main/java/org/bukkit/craftbukkit/potion/CraftPotionType.java b/src/main/java/org/bukkit/craftbukkit/potion/CraftPotionType.java -index 82a50b06c08b632f77d73745e1fa9bd22dfd950a..f1d8ed4a2b8959873b02d57f6a40323a841f3d7f 100644 ---- a/src/main/java/org/bukkit/craftbukkit/potion/CraftPotionType.java -+++ b/src/main/java/org/bukkit/craftbukkit/potion/CraftPotionType.java -@@ -69,6 +69,7 @@ public class CraftPotionType implements PotionType.InternalPotionData { - string = FieldRename.convertPotionTypeName(ApiVersion.CURRENT, string); - string = string.toLowerCase(Locale.ROOT); - NamespacedKey key = NamespacedKey.fromString(string); -+ if (key == null) return null; // Paper - Fixup NamespacedKey handling - - // Now also convert from when keys where saved - return CraftRegistry.get(Registry.POTION, key, ApiVersion.CURRENT); -diff --git a/src/main/java/org/bukkit/craftbukkit/util/CraftNamespacedKey.java b/src/main/java/org/bukkit/craftbukkit/util/CraftNamespacedKey.java -index dc88ba24ed3b0024c39a30c2d90628fc708d63cf..944bed9b6c803df1a312383fed9de7d61e7d2c70 100644 ---- a/src/main/java/org/bukkit/craftbukkit/util/CraftNamespacedKey.java -+++ b/src/main/java/org/bukkit/craftbukkit/util/CraftNamespacedKey.java -@@ -13,7 +13,7 @@ public final class CraftNamespacedKey { - return null; - } - ResourceLocation minecraft = ResourceLocation.tryParse(string); -- return (minecraft == null) ? null : CraftNamespacedKey.fromMinecraft(minecraft); -+ return (minecraft == null || minecraft.getPath().isEmpty()) ? null : CraftNamespacedKey.fromMinecraft(minecraft); // Paper - Bukkit's parser does not match Vanilla for empty paths - } - - public static NamespacedKey fromString(String string) { diff --git a/patches/server/0921-Add-FluidState-API.patch b/patches/server/0921-Add-FluidState-API.patch new file mode 100644 index 000000000000..e1e2fe004fe2 --- /dev/null +++ b/patches/server/0921-Add-FluidState-API.patch @@ -0,0 +1,208 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: vicisacat +Date: Fri, 17 Nov 2023 20:22:43 +0100 +Subject: [PATCH] Add FluidState API + + +diff --git a/src/main/java/io/papermc/paper/block/fluid/PaperFluidData.java b/src/main/java/io/papermc/paper/block/fluid/PaperFluidData.java +new file mode 100644 +index 0000000000000000000000000000000000000000..479bc32241ebadf8bbc1080b601f61391ad37fa4 +--- /dev/null ++++ b/src/main/java/io/papermc/paper/block/fluid/PaperFluidData.java +@@ -0,0 +1,110 @@ ++package io.papermc.paper.block.fluid; ++ ++import com.google.common.base.Preconditions; ++import io.papermc.paper.block.fluid.type.PaperFallingFluidData; ++import io.papermc.paper.block.fluid.type.PaperFlowingFluidData; ++import io.papermc.paper.util.MCUtil; ++import java.util.HashMap; ++import java.util.Map; ++import java.util.function.Function; ++import net.minecraft.world.level.material.FluidState; ++import net.minecraft.world.level.material.LavaFluid; ++import net.minecraft.world.level.material.WaterFluid; ++import org.bukkit.Fluid; ++import org.bukkit.Location; ++import org.bukkit.craftbukkit.CraftFluid; ++import org.bukkit.craftbukkit.CraftWorld; ++import org.bukkit.craftbukkit.util.CraftVector; ++import org.bukkit.util.Vector; ++import org.jetbrains.annotations.NotNull; ++ ++public class PaperFluidData implements FluidData { ++ ++ private final FluidState state; ++ ++ protected PaperFluidData(final FluidState state) { ++ this.state = state; ++ } ++ ++ /** ++ * Provides the internal server representation of this fluid data. ++ * @return the fluid state. ++ */ ++ public FluidState getState() { ++ return this.state; ++ } ++ ++ @Override ++ public final @NotNull Fluid getFluidType() { ++ return CraftFluid.minecraftToBukkit(this.state.getType()); ++ } ++ ++ @Override ++ public @NotNull PaperFluidData clone() { ++ try { ++ return (PaperFluidData) super.clone(); ++ } catch (final CloneNotSupportedException ex) { ++ throw new AssertionError("Clone not supported", ex); ++ } ++ } ++ ++ @Override ++ public @NotNull Vector computeFlowDirection(final Location location) { ++ Preconditions.checkArgument(location.getWorld() != null, "Cannot compute flow direction on world-less location"); ++ return CraftVector.toBukkit(this.state.getFlow( ++ ((CraftWorld) location.getWorld()).getHandle(), ++ MCUtil.toBlockPosition(location) ++ )); ++ } ++ ++ @Override ++ public int getLevel() { ++ return this.state.getAmount(); ++ } ++ ++ @Override ++ public float computeHeight(@NotNull final Location location) { ++ Preconditions.checkArgument(location.getWorld() != null, "Cannot compute height on world-less location"); ++ return this.state.getHeight(((CraftWorld) location.getWorld()).getHandle(), MCUtil.toBlockPos(location)); ++ } ++ ++ @Override ++ public boolean isSource() { ++ return this.state.isSource(); ++ } ++ ++ @Override ++ public int hashCode() { ++ return this.state.hashCode(); ++ } ++ ++ @Override ++ public boolean equals(final Object obj) { ++ return obj instanceof final PaperFluidData paperFluidData && this.state.equals(paperFluidData.state); ++ } ++ ++ @Override ++ public String toString() { ++ return "PaperFluidData{" + this.state + "}"; ++ } ++ ++ /* Registry */ ++ private static final Map, Function> MAP = new HashMap<>(); ++ static { ++ // ++ register(LavaFluid.Source.class, PaperFallingFluidData::new); ++ register(WaterFluid.Source.class, PaperFallingFluidData::new); ++ register(LavaFluid.Flowing.class, PaperFlowingFluidData::new); ++ register(WaterFluid.Flowing.class, PaperFlowingFluidData::new); ++ // ++ } ++ ++ static void register(final Class fluid, final Function creator) { ++ Preconditions.checkState(MAP.put(fluid, creator) == null, "Duplicate mapping %s->%s", fluid, creator); ++ MAP.put(fluid, creator); ++ } ++ ++ public static PaperFluidData createData(final FluidState state) { ++ return MAP.getOrDefault(state.getType().getClass(), PaperFluidData::new).apply(state); ++ } ++} +diff --git a/src/main/java/io/papermc/paper/block/fluid/package-info.java b/src/main/java/io/papermc/paper/block/fluid/package-info.java +new file mode 100644 +index 0000000000000000000000000000000000000000..cfabb814ebd281aab299c6c655266ff357e08806 +--- /dev/null ++++ b/src/main/java/io/papermc/paper/block/fluid/package-info.java +@@ -0,0 +1,5 @@ ++@DefaultQualifier(NonNull.class) ++package io.papermc.paper.block.fluid; ++ ++import org.checkerframework.checker.nullness.qual.NonNull; ++import org.checkerframework.framework.qual.DefaultQualifier; +diff --git a/src/main/java/io/papermc/paper/block/fluid/type/PaperFallingFluidData.java b/src/main/java/io/papermc/paper/block/fluid/type/PaperFallingFluidData.java +new file mode 100644 +index 0000000000000000000000000000000000000000..655dbd83ff4e632f1168b75e9b402b05aa9d8edf +--- /dev/null ++++ b/src/main/java/io/papermc/paper/block/fluid/type/PaperFallingFluidData.java +@@ -0,0 +1,18 @@ ++ ++package io.papermc.paper.block.fluid.type; ++ ++import io.papermc.paper.block.fluid.PaperFluidData; ++import net.minecraft.world.level.material.FlowingFluid; ++import net.minecraft.world.level.material.FluidState; ++ ++public class PaperFallingFluidData extends PaperFluidData implements FallingFluidData { ++ ++ public PaperFallingFluidData(final FluidState state) { ++ super(state); ++ } ++ ++ @Override ++ public boolean isFalling() { ++ return this.getState().getValue(FlowingFluid.FALLING); ++ } ++} +diff --git a/src/main/java/io/papermc/paper/block/fluid/type/PaperFlowingFluidData.java b/src/main/java/io/papermc/paper/block/fluid/type/PaperFlowingFluidData.java +new file mode 100644 +index 0000000000000000000000000000000000000000..c0c2805cb045cdd835b402776a6923fe2ecc2a99 +--- /dev/null ++++ b/src/main/java/io/papermc/paper/block/fluid/type/PaperFlowingFluidData.java +@@ -0,0 +1,11 @@ ++package io.papermc.paper.block.fluid.type; ++ ++import net.minecraft.world.level.material.FluidState; ++ ++public class PaperFlowingFluidData extends PaperFallingFluidData implements FlowingFluidData { ++ ++ public PaperFlowingFluidData(final FluidState state) { ++ super(state); ++ } ++ ++} +diff --git a/src/main/java/org/bukkit/craftbukkit/CraftRegionAccessor.java b/src/main/java/org/bukkit/craftbukkit/CraftRegionAccessor.java +index 4c234e887c42b27754ed8f05f2000d9309274427..f0bd7d01f56bb792886354ca4f199e46c2cf7503 100644 +--- a/src/main/java/org/bukkit/craftbukkit/CraftRegionAccessor.java ++++ b/src/main/java/org/bukkit/craftbukkit/CraftRegionAccessor.java +@@ -108,6 +108,13 @@ public abstract class CraftRegionAccessor implements RegionAccessor { + return CraftBlock.at(this.getHandle(), new BlockPos(x, y, z)).getState(); + } + ++ // Paper start - FluidState API ++ @Override ++ public io.papermc.paper.block.fluid.FluidData getFluidData(final int x, final int y, final int z) { ++ return io.papermc.paper.block.fluid.PaperFluidData.createData(getHandle().getFluidState(new BlockPos(x, y, z))); ++ } ++ // Paper end ++ + @Override + public BlockData getBlockData(Location location) { + return this.getBlockData(location.getBlockX(), location.getBlockY(), location.getBlockZ()); +diff --git a/src/main/java/org/bukkit/craftbukkit/generator/CraftLimitedRegion.java b/src/main/java/org/bukkit/craftbukkit/generator/CraftLimitedRegion.java +index a23269e3bdb83f85a1d08d5f7b54742025223ada..a57ac9dc8d08b12ec00ad41d9a1779e5a81e4e8b 100644 +--- a/src/main/java/org/bukkit/craftbukkit/generator/CraftLimitedRegion.java ++++ b/src/main/java/org/bukkit/craftbukkit/generator/CraftLimitedRegion.java +@@ -304,4 +304,11 @@ public class CraftLimitedRegion extends CraftRegionAccessor implements LimitedRe + return centerChunkZ; + } + // Paper end - Add more LimitedRegion API ++ // Paper start - Fluid API ++ @Override ++ public io.papermc.paper.block.fluid.FluidData getFluidData(int x, int y, int z) { ++ Preconditions.checkArgument(this.isInRegion(x, y, z), "Coordinates %s, %s, %s are not in the region", x, y, z); ++ return super.getFluidData(x, y, z); ++ } ++ // Paper end + } diff --git a/patches/server/0922-Reduce-allocation-of-Vec3D-by-entity-tracker.patch b/patches/server/0922-Reduce-allocation-of-Vec3D-by-entity-tracker.patch deleted file mode 100644 index 37c0ef0eefce..000000000000 --- a/patches/server/0922-Reduce-allocation-of-Vec3D-by-entity-tracker.patch +++ /dev/null @@ -1,59 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Spottedleaf -Date: Mon, 27 Apr 2020 00:04:16 -0700 -Subject: [PATCH] Reduce allocation of Vec3D by entity tracker - - -diff --git a/src/main/java/net/minecraft/network/protocol/game/VecDeltaCodec.java b/src/main/java/net/minecraft/network/protocol/game/VecDeltaCodec.java -index a043ac10834562d357ef0b5aded2e916e2a0d056..74276c368016fcc4dbf9579b2ecbadc9614baf15 100644 ---- a/src/main/java/net/minecraft/network/protocol/game/VecDeltaCodec.java -+++ b/src/main/java/net/minecraft/network/protocol/game/VecDeltaCodec.java -@@ -5,7 +5,7 @@ import org.jetbrains.annotations.VisibleForTesting; - - public class VecDeltaCodec { - private static final double TRUNCATION_STEPS = 4096.0; -- private Vec3 base = Vec3.ZERO; -+ public Vec3 base = Vec3.ZERO; // Paper - - @VisibleForTesting - static long encode(double value) { -diff --git a/src/main/java/net/minecraft/server/level/ChunkMap.java b/src/main/java/net/minecraft/server/level/ChunkMap.java -index 6c667823c2ef15766f3afc1a9c819cb6c24b0912..4c1cf5798209297e1e8a634b63770e917a84a63c 100644 ---- a/src/main/java/net/minecraft/server/level/ChunkMap.java -+++ b/src/main/java/net/minecraft/server/level/ChunkMap.java -@@ -1519,10 +1519,14 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider - public void updatePlayer(ServerPlayer player) { - org.spigotmc.AsyncCatcher.catchOp("player tracker update"); // Spigot - if (player != this.entity) { -- Vec3 vec3d = player.position().subtract(this.entity.position()); -+ // Paper start - remove allocation of Vec3D here -+ // Vec3 vec3d = player.position().subtract(this.entity.position()); -+ double vec3d_dx = player.getX() - this.entity.getX(); -+ double vec3d_dz = player.getZ() - this.entity.getZ(); -+ // Paper end - remove allocation of Vec3D here - int i = ChunkMap.this.getPlayerViewDistance(player); - double d0 = (double) Math.min(this.getEffectiveRange(), i * 16); -- double d1 = vec3d.x * vec3d.x + vec3d.z * vec3d.z; -+ double d1 = vec3d_dx * vec3d_dx + vec3d_dz * vec3d_dz; // Paper - double d2 = d0 * d0; - // Paper start - Configurable entity tracking range by Y - boolean flag = d1 <= d2; -diff --git a/src/main/java/net/minecraft/server/level/ServerEntity.java b/src/main/java/net/minecraft/server/level/ServerEntity.java -index a2fbbbd7a66d4e7b1063638f8467e8887a417282..0e7ace92522fbd4cef7b2c2b8a0f8b86c2cce192 100644 ---- a/src/main/java/net/minecraft/server/level/ServerEntity.java -+++ b/src/main/java/net/minecraft/server/level/ServerEntity.java -@@ -163,7 +163,13 @@ public class ServerEntity { - i = Mth.floor(this.entity.getYRot() * 256.0F / 360.0F); - j = Mth.floor(this.entity.getXRot() * 256.0F / 360.0F); - Vec3 vec3d = this.entity.trackingPosition(); -- boolean flag1 = this.positionCodec.delta(vec3d).lengthSqr() >= 7.62939453125E-6D; -+ // Paper start - reduce allocation of Vec3D here -+ Vec3 base = this.positionCodec.base; -+ double vec3d_dx = vec3d.x - base.x; -+ double vec3d_dy = vec3d.y - base.y; -+ double vec3d_dz = vec3d.z - base.z; -+ boolean flag1 = (vec3d_dx * vec3d_dx + vec3d_dy * vec3d_dy + vec3d_dz * vec3d_dz) >= 7.62939453125E-6D; -+ // Paper end - reduce allocation of Vec3D here - Packet packet1 = null; - boolean flag2 = flag1 || this.tickCount % 60 == 0; - boolean flag3 = Math.abs(i - this.lastSentYRot) >= 1 || Math.abs(j - this.lastSentXRot) >= 1; diff --git a/patches/server/0930-add-number-format-api.patch b/patches/server/0922-add-number-format-api.patch similarity index 100% rename from patches/server/0930-add-number-format-api.patch rename to patches/server/0922-add-number-format-api.patch diff --git a/patches/server/0923-Add-PlayerTradeEvent-and-PlayerPurchaseEvent.patch b/patches/server/0923-Add-PlayerTradeEvent-and-PlayerPurchaseEvent.patch deleted file mode 100644 index 95e1104a250d..000000000000 --- a/patches/server/0923-Add-PlayerTradeEvent-and-PlayerPurchaseEvent.patch +++ /dev/null @@ -1,240 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Jake Potrebic -Date: Thu, 2 Jul 2020 16:12:10 -0700 -Subject: [PATCH] Add PlayerTradeEvent and PlayerPurchaseEvent - -Co-authored-by: Alexander - -diff --git a/src/main/java/net/minecraft/world/entity/npc/AbstractVillager.java b/src/main/java/net/minecraft/world/entity/npc/AbstractVillager.java -index 2a155d3611ca2370830ca763d40074df6641958f..d28ebcae036168dd65a5f3236d12ee416308c23f 100644 ---- a/src/main/java/net/minecraft/world/entity/npc/AbstractVillager.java -+++ b/src/main/java/net/minecraft/world/entity/npc/AbstractVillager.java -@@ -141,11 +141,24 @@ public abstract class AbstractVillager extends AgeableMob implements InventoryCa - @Override - public void overrideXp(int experience) {} - -+ // Paper start - Add PlayerTradeEvent and PlayerPurchaseEvent -+ @Override -+ public void processTrade(MerchantOffer recipe, @Nullable io.papermc.paper.event.player.PlayerPurchaseEvent event) { // The MerchantRecipe passed in here is the one set by the PlayerPurchaseEvent -+ if (event == null || event.willIncreaseTradeUses()) { -+ recipe.increaseUses(); -+ } -+ if (event == null || event.isRewardingExp()) { -+ this.rewardTradeXp(recipe); -+ } -+ this.notifyTrade(recipe); -+ } -+ // Paper end - Add PlayerTradeEvent and PlayerPurchaseEvent -+ - @Override - public void notifyTrade(MerchantOffer offer) { -- offer.increaseUses(); -+ // offer.increaseUses(); // Paper - Add PlayerTradeEvent and PlayerPurchaseEvent - this.ambientSoundTime = -this.getAmbientSoundInterval(); -- this.rewardTradeXp(offer); -+ // this.rewardTradeXp(offer); // Paper - Add PlayerTradeEvent and PlayerPurchaseEvent - if (this.tradingPlayer instanceof ServerPlayer) { - CriteriaTriggers.TRADE.trigger((ServerPlayer) this.tradingPlayer, this, offer.getResult()); - } -diff --git a/src/main/java/net/minecraft/world/inventory/AbstractContainerMenu.java b/src/main/java/net/minecraft/world/inventory/AbstractContainerMenu.java -index 8a1035c0aa859f67a6806c183d96a88ddf760baa..46159a127d910028c62ada90ff2d2dccc3b62fc3 100644 ---- a/src/main/java/net/minecraft/world/inventory/AbstractContainerMenu.java -+++ b/src/main/java/net/minecraft/world/inventory/AbstractContainerMenu.java -@@ -768,6 +768,14 @@ public abstract class AbstractContainerMenu { - public abstract boolean stillValid(Player player); - - protected boolean moveItemStackTo(ItemStack stack, int startIndex, int endIndex, boolean fromLast) { -+ // Paper start - Add PlayerTradeEvent and PlayerPurchaseEvent -+ return this.moveItemStackTo(stack, startIndex, endIndex, fromLast, false); -+ } -+ protected boolean moveItemStackTo(ItemStack stack, int startIndex, int endIndex, boolean fromLast, boolean isCheck) { -+ if (isCheck) { -+ stack = stack.copy(); -+ } -+ // Paper end - Add PlayerTradeEvent and PlayerPurchaseEvent - boolean flag1 = false; - int k = startIndex; - -@@ -791,6 +799,11 @@ public abstract class AbstractContainerMenu { - - slot = (Slot) this.slots.get(k); - itemstack1 = slot.getItem(); -+ // Paper start - Add PlayerTradeEvent and PlayerPurchaseEvent; clone if only a check -+ if (isCheck) { -+ itemstack1 = itemstack1.copy(); -+ } -+ // Paper end - Add PlayerTradeEvent and PlayerPurchaseEvent - if (!itemstack1.isEmpty() && ItemStack.isSameItemSameComponents(stack, itemstack1)) { - l = itemstack1.getCount() + stack.getCount(); - int i1 = slot.getMaxStackSize(itemstack1); -@@ -798,12 +811,16 @@ public abstract class AbstractContainerMenu { - if (l <= i1) { - stack.setCount(0); - itemstack1.setCount(l); -+ if (!isCheck) { // Paper - Add PlayerTradeEvent and PlayerPurchaseEvent - slot.setChanged(); -+ } // Paper - Add PlayerTradeEvent and PlayerPurchaseEvent - flag1 = true; - } else if (itemstack1.getCount() < i1) { - stack.shrink(i1 - itemstack1.getCount()); - itemstack1.setCount(i1); -+ if (!isCheck) { // Paper - Add PlayerTradeEvent and PlayerPurchaseEvent - slot.setChanged(); -+ } // Paper - Add PlayerTradeEvent and PlayerPurchaseEvent - flag1 = true; - } - } -@@ -834,10 +851,21 @@ public abstract class AbstractContainerMenu { - - slot = (Slot) this.slots.get(k); - itemstack1 = slot.getItem(); -+ // Paper start - Add PlayerTradeEvent and PlayerPurchaseEvent -+ if (isCheck) { -+ itemstack1 = itemstack1.copy(); -+ } -+ // Paper end - Add PlayerTradeEvent and PlayerPurchaseEvent - if (itemstack1.isEmpty() && slot.mayPlace(stack)) { - l = slot.getMaxStackSize(stack); -+ // Paper start - Add PlayerTradeEvent and PlayerPurchaseEvent -+ if (isCheck) { -+ stack.shrink(Math.min(stack.getCount(), l)); -+ } else { -+ // Paper end - Add PlayerTradeEvent and PlayerPurchaseEvent - slot.setByPlayer(stack.split(Math.min(stack.getCount(), l))); - slot.setChanged(); -+ } // Paper - Add PlayerTradeEvent and PlayerPurchaseEvent - flag1 = true; - break; - } -diff --git a/src/main/java/net/minecraft/world/inventory/MerchantMenu.java b/src/main/java/net/minecraft/world/inventory/MerchantMenu.java -index e45ab844afdf1a65f23eeff4c4d6cd9e3a8a28e2..5de2030452b96a4df7ce0be82f07e002db595dee 100644 ---- a/src/main/java/net/minecraft/world/inventory/MerchantMenu.java -+++ b/src/main/java/net/minecraft/world/inventory/MerchantMenu.java -@@ -135,12 +135,12 @@ public class MerchantMenu extends AbstractContainerMenu { - - itemstack = itemstack1.copy(); - if (slot == 2) { -- if (!this.moveItemStackTo(itemstack1, 3, 39, true)) { -+ if (!this.moveItemStackTo(itemstack1, 3, 39, true, true)) { // Paper - Add PlayerTradeEvent and PlayerPurchaseEvent - return ItemStack.EMPTY; - } - -- slot1.onQuickCraft(itemstack1, itemstack); -- this.playTradeSound(); -+ // slot1.onQuickCraft(itemstack1, itemstack); // Paper - Add PlayerTradeEvent and PlayerPurchaseEvent; moved to after the non-check moveItemStackTo call -+ // this.playTradeSound(); - } else if (slot != 0 && slot != 1) { - if (slot >= 3 && slot < 30) { - if (!this.moveItemStackTo(itemstack1, 30, 39, false)) { -@@ -153,6 +153,7 @@ public class MerchantMenu extends AbstractContainerMenu { - return ItemStack.EMPTY; - } - -+ if (slot != 2) { // Paper - Add PlayerTradeEvent and PlayerPurchaseEvent; moved down for slot 2 - if (itemstack1.isEmpty()) { - slot1.setByPlayer(ItemStack.EMPTY); - } else { -@@ -164,6 +165,21 @@ public class MerchantMenu extends AbstractContainerMenu { - } - - slot1.onTake(player, itemstack1); -+ } // Paper start - Add PlayerTradeEvent and PlayerPurchaseEvent; handle slot 2 -+ if (slot == 2) { // is merchant result slot -+ slot1.onTake(player, itemstack1); -+ if (itemstack1.isEmpty()) { -+ slot1.set(ItemStack.EMPTY); -+ return ItemStack.EMPTY; -+ } -+ -+ this.moveItemStackTo(itemstack1, 3, 39, true, false); // This should always succeed because it's checked above -+ -+ slot1.onQuickCraft(itemstack1, itemstack); -+ this.playTradeSound(); -+ slot1.set(ItemStack.EMPTY); // itemstack1 should ALWAYS be empty -+ } -+ // Paper end - Add PlayerTradeEvent and PlayerPurchaseEvent - } - - return itemstack; -diff --git a/src/main/java/net/minecraft/world/inventory/MerchantResultSlot.java b/src/main/java/net/minecraft/world/inventory/MerchantResultSlot.java -index 26227033613a641a9d5a6f29356b19e54753b3f1..c2376538215604210d08acd33e8e5fdc8a35c7d3 100644 ---- a/src/main/java/net/minecraft/world/inventory/MerchantResultSlot.java -+++ b/src/main/java/net/minecraft/world/inventory/MerchantResultSlot.java -@@ -47,13 +47,32 @@ public class MerchantResultSlot extends Slot { - - @Override - public void onTake(Player player, ItemStack stack) { -- this.checkTakeAchievements(stack); -+ // this.checkTakeAchievements(stack); // Paper - Add PlayerTradeEvent and PlayerPurchaseEvent; move to after event is called and not cancelled - MerchantOffer merchantOffer = this.slots.getActiveOffer(); -+ // Paper start - Add PlayerTradeEvent and PlayerPurchaseEvent -+ io.papermc.paper.event.player.PlayerPurchaseEvent event = null; -+ if (merchantOffer != null && player instanceof net.minecraft.server.level.ServerPlayer serverPlayer) { -+ if (this.merchant instanceof net.minecraft.world.entity.npc.AbstractVillager abstractVillager) { -+ event = new io.papermc.paper.event.player.PlayerTradeEvent(serverPlayer.getBukkitEntity(), (org.bukkit.entity.AbstractVillager) abstractVillager.getBukkitEntity(), merchantOffer.asBukkit(), true, true); -+ } else if (this.merchant instanceof org.bukkit.craftbukkit.inventory.CraftMerchantCustom.MinecraftMerchant) { -+ event = new io.papermc.paper.event.player.PlayerPurchaseEvent(serverPlayer.getBukkitEntity(), merchantOffer.asBukkit(), false, true); -+ } -+ if (event != null) { -+ if (!event.callEvent()) { -+ stack.setCount(0); -+ event.getPlayer().updateInventory(); -+ return; -+ } -+ merchantOffer = org.bukkit.craftbukkit.inventory.CraftMerchantRecipe.fromBukkit(event.getTrade()).toMinecraft(); -+ } -+ } -+ this.checkTakeAchievements(stack); -+ // Paper end - Add PlayerTradeEvent and PlayerPurchaseEvent - if (merchantOffer != null) { - ItemStack itemStack = this.slots.getItem(0); - ItemStack itemStack2 = this.slots.getItem(1); - if (merchantOffer.take(itemStack, itemStack2) || merchantOffer.take(itemStack2, itemStack)) { -- this.merchant.notifyTrade(merchantOffer); -+ this.merchant.processTrade(merchantOffer, event); // Paper - Add PlayerTradeEvent and PlayerPurchaseEvent - player.awardStat(Stats.TRADED_WITH_VILLAGER); - this.slots.setItem(0, itemStack); - this.slots.setItem(1, itemStack2); -diff --git a/src/main/java/net/minecraft/world/item/trading/Merchant.java b/src/main/java/net/minecraft/world/item/trading/Merchant.java -index 5a350948a4735902f5c612592bc9d100445a0c8a..716b30dcd7e63c66736c448dd136c9f74dc7fe43 100644 ---- a/src/main/java/net/minecraft/world/item/trading/Merchant.java -+++ b/src/main/java/net/minecraft/world/item/trading/Merchant.java -@@ -20,6 +20,7 @@ public interface Merchant { - - void overrideOffers(MerchantOffers offers); - -+ default void processTrade(MerchantOffer merchantRecipe, @Nullable io.papermc.paper.event.player.PlayerPurchaseEvent event) { this.notifyTrade(merchantRecipe); } // Paper - void notifyTrade(MerchantOffer offer); - - void notifyTradeUpdated(ItemStack stack); -diff --git a/src/main/java/org/bukkit/craftbukkit/inventory/CraftMerchantCustom.java b/src/main/java/org/bukkit/craftbukkit/inventory/CraftMerchantCustom.java -index ef47eb9a321a7b082413d3986d3d394afb899610..54debe9da0a26aea02c964fdc7efb372e07974c0 100644 ---- a/src/main/java/org/bukkit/craftbukkit/inventory/CraftMerchantCustom.java -+++ b/src/main/java/org/bukkit/craftbukkit/inventory/CraftMerchantCustom.java -@@ -76,10 +76,25 @@ public class CraftMerchantCustom implements CraftMerchant { - return this.trades; - } - -+ // Paper start - Add PlayerTradeEvent and PlayerPurchaseEvent -+ @Override -+ public void processTrade(MerchantOffer merchantRecipe, @javax.annotation.Nullable io.papermc.paper.event.player.PlayerPurchaseEvent event) { // The MerchantRecipe passed in here is the one set by the PlayerPurchaseEvent -+ /** Based on {@link net.minecraft.world.entity.npc.AbstractVillager#processTrade(MerchantOffer, io.papermc.paper.event.player.PlayerPurchaseEvent)} */ -+ if (getTradingPlayer() instanceof net.minecraft.server.level.ServerPlayer) { -+ if (event == null || event.willIncreaseTradeUses()) { -+ merchantRecipe.increaseUses(); -+ } -+ if (event == null || event.isRewardingExp()) { -+ this.tradingPlayer.level().addFreshEntity(new net.minecraft.world.entity.ExperienceOrb(this.tradingPlayer.level(), this.tradingPlayer.getX(), this.tradingPlayer.getY(), this.tradingPlayer.getZ(), merchantRecipe.getXp(), org.bukkit.entity.ExperienceOrb.SpawnReason.VILLAGER_TRADE, this.tradingPlayer, null)); -+ } -+ } -+ this.notifyTrade(merchantRecipe); -+ } -+ // Paper end - Add PlayerTradeEvent and PlayerPurchaseEvent - @Override - public void notifyTrade(MerchantOffer offer) { - // increase recipe's uses -- offer.increaseUses(); -+ // offer.increaseUses(); // Paper - Add PlayerTradeEvent and PlayerPurchaseEvent; handled above in processTrade - } - - @Override diff --git a/patches/server/0923-improve-BanList-types.patch b/patches/server/0923-improve-BanList-types.patch new file mode 100644 index 000000000000..c89914131fbe --- /dev/null +++ b/patches/server/0923-improve-BanList-types.patch @@ -0,0 +1,32 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Yannick Lamprecht +Date: Sat, 10 Feb 2024 20:50:01 +0100 +Subject: [PATCH] improve BanList types + + +diff --git a/src/main/java/org/bukkit/craftbukkit/CraftServer.java b/src/main/java/org/bukkit/craftbukkit/CraftServer.java +index a2c749b2997557fec5c978f3bed8c35d7614e740..6136037d3d096300d93b9710dd854224b30e0738 100644 +--- a/src/main/java/org/bukkit/craftbukkit/CraftServer.java ++++ b/src/main/java/org/bukkit/craftbukkit/CraftServer.java +@@ -2260,6 +2260,21 @@ public final class CraftServer implements Server { + }; + } + ++ // Paper start - add BanListType (which has a generic) ++ @SuppressWarnings("unchecked") ++ @Override ++ public , E> B getBanList(final io.papermc.paper.ban.BanListType type) { ++ Preconditions.checkArgument(type != null, "BanList.BanType cannot be null"); ++ if (type == io.papermc.paper.ban.BanListType.IP) { ++ return (B) new CraftIpBanList(this.playerList.getIpBans()); ++ } else if (type == io.papermc.paper.ban.BanListType.PROFILE) { ++ return (B) new CraftProfileBanList(this.playerList.getBans()); ++ } else { ++ throw new IllegalArgumentException("Unknown BanListType: " + type); ++ } ++ } ++ // Paper end - add BanListType (which has a generic) ++ + @Override + public void setWhitelist(boolean value) { + this.playerList.setUsingWhiteList(value); diff --git a/patches/server/0924-Add-ShulkerDuplicateEvent.patch b/patches/server/0924-Add-ShulkerDuplicateEvent.patch deleted file mode 100644 index de864137e235..000000000000 --- a/patches/server/0924-Add-ShulkerDuplicateEvent.patch +++ /dev/null @@ -1,22 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Chase Henderson -Date: Fri, 5 Jan 2024 03:50:10 -0500 -Subject: [PATCH] Add ShulkerDuplicateEvent - - -diff --git a/src/main/java/net/minecraft/world/entity/monster/Shulker.java b/src/main/java/net/minecraft/world/entity/monster/Shulker.java -index bf58956379d0a5dbfdc34e8626847638b4111433..920c7a92643e83598f39bf984cca430d9deed2cd 100644 ---- a/src/main/java/net/minecraft/world/entity/monster/Shulker.java -+++ b/src/main/java/net/minecraft/world/entity/monster/Shulker.java -@@ -491,6 +491,11 @@ public class Shulker extends AbstractGolem implements VariantHolder +Date: Mon, 4 Mar 2024 22:18:28 -0500 +Subject: [PATCH] Add BlockBreakProgressUpdateEvent + + +diff --git a/src/main/java/net/minecraft/server/level/ServerLevel.java b/src/main/java/net/minecraft/server/level/ServerLevel.java +index 7a985c30a973efacf3e8b70e7163c550d86b0870..ea1281c9a3b83b17de64d583e029db9bacabcd88 100644 +--- a/src/main/java/net/minecraft/server/level/ServerLevel.java ++++ b/src/main/java/net/minecraft/server/level/ServerLevel.java +@@ -1285,6 +1285,17 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe + if (entity instanceof Player) entityhuman = (Player) entity; + // CraftBukkit end + ++ // Paper start - Add BlockBreakProgressUpdateEvent ++ // If a plugin is using this method to send destroy packets for a client-side only entity id, no block progress occurred on the server. ++ // Hence, do not call the event. ++ if (entity != null) { ++ float progressFloat = Mth.clamp(progress, 0, 10) / 10.0f; ++ org.bukkit.craftbukkit.block.CraftBlock bukkitBlock = org.bukkit.craftbukkit.block.CraftBlock.at(this, pos); ++ new io.papermc.paper.event.block.BlockBreakProgressUpdateEvent(bukkitBlock, progressFloat, entity.getBukkitEntity()) ++ .callEvent(); ++ } ++ // Paper end - Add BlockBreakProgressUpdateEvent ++ + while (iterator.hasNext()) { + ServerPlayer entityplayer = (ServerPlayer) iterator.next(); + diff --git a/patches/server/0925-Add-api-for-spawn-egg-texture-colors.patch b/patches/server/0925-Add-api-for-spawn-egg-texture-colors.patch deleted file mode 100644 index 8eac793c2ed6..000000000000 --- a/patches/server/0925-Add-api-for-spawn-egg-texture-colors.patch +++ /dev/null @@ -1,26 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Luis -Date: Thu, 11 Jan 2024 19:58:23 +0100 -Subject: [PATCH] Add api for spawn egg texture colors - - -diff --git a/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java b/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java -index b07c8111daa010dee2bb8be52162aafa4c267f1f..73a3e708f21c1b9c5de1fc180f728d6da7eea0fb 100644 ---- a/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java -+++ b/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java -@@ -643,6 +643,15 @@ public final class CraftMagicNumbers implements UnsafeValues { - return CraftRegistry.get(registry, namespacedKey, ApiVersion.CURRENT); - } - -+ // Paper start - spawn egg color visibility -+ @Override -+ public org.bukkit.Color getSpawnEggLayerColor(final EntityType entityType, final int layer) { -+ final net.minecraft.world.entity.EntityType nmsType = org.bukkit.craftbukkit.entity.CraftEntityType.bukkitToMinecraft(entityType); -+ final net.minecraft.world.item.SpawnEggItem eggItem = net.minecraft.world.item.SpawnEggItem.byId(nmsType); -+ return eggItem == null ? null : org.bukkit.Color.fromRGB(eggItem.getColor(layer)); -+ } -+ // Paper end - spawn egg color visibility -+ - /** - * This helper class represents the different NBT Tags. - *

    diff --git a/patches/server/0926-Add-Lifecycle-Event-system.patch b/patches/server/0926-Add-Lifecycle-Event-system.patch deleted file mode 100644 index f6600c437067..000000000000 --- a/patches/server/0926-Add-Lifecycle-Event-system.patch +++ /dev/null @@ -1,785 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Jake Potrebic -Date: Tue, 18 Jul 2023 17:49:38 -0700 -Subject: [PATCH] Add Lifecycle Event system - -This event system is separate from Bukkit's event system and is -meant for managing resources across reloads and from points in the -PluginBootstrap. - -diff --git a/src/main/java/io/papermc/paper/plugin/bootstrap/PluginBootstrapContextImpl.java b/src/main/java/io/papermc/paper/plugin/bootstrap/PluginBootstrapContextImpl.java -index 30b50e6294c6eaade5e17cfaf34600d122e6251c..0bb7694188d5fb75bb756ce75d0060ea980027ee 100644 ---- a/src/main/java/io/papermc/paper/plugin/bootstrap/PluginBootstrapContextImpl.java -+++ b/src/main/java/io/papermc/paper/plugin/bootstrap/PluginBootstrapContextImpl.java -@@ -1,6 +1,8 @@ - package io.papermc.paper.plugin.bootstrap; - - import io.papermc.paper.plugin.configuration.PluginMeta; -+import io.papermc.paper.plugin.lifecycle.event.LifecycleEventManager; -+import io.papermc.paper.plugin.lifecycle.event.PaperLifecycleEventManager; - import io.papermc.paper.plugin.provider.PluginProvider; - import java.nio.file.Path; - import net.kyori.adventure.text.logger.slf4j.ComponentLogger; -@@ -12,6 +14,10 @@ public final class PluginBootstrapContextImpl implements BootstrapContext { - private final Path dataFolder; - private final ComponentLogger logger; - private final Path pluginSource; -+ // Paper start - lifecycle events -+ private boolean allowsLifecycleRegistration = true; -+ private final PaperLifecycleEventManager lifecycleEventManager = new PaperLifecycleEventManager<>(this, () -> this.allowsLifecycleRegistration); // Paper - lifecycle events -+ // Paper end - lifecycle events - - public PluginBootstrapContextImpl(PluginMeta config, Path dataFolder, ComponentLogger logger, Path pluginSource) { - this.config = config; -@@ -45,4 +51,20 @@ public final class PluginBootstrapContextImpl implements BootstrapContext { - public @NotNull Path getPluginSource() { - return this.pluginSource; - } -+ -+ // Paper start - lifecycle event system -+ @Override -+ public @NotNull PluginMeta getPluginMeta() { -+ return this.config; -+ } -+ -+ @Override -+ public LifecycleEventManager getLifecycleManager() { -+ return this.lifecycleEventManager; -+ } -+ -+ public void lockLifecycleEventRegistration() { -+ this.allowsLifecycleRegistration = false; -+ } -+ // Paper end - } -diff --git a/src/main/java/io/papermc/paper/plugin/lifecycle/event/LifecycleEventRunner.java b/src/main/java/io/papermc/paper/plugin/lifecycle/event/LifecycleEventRunner.java -new file mode 100644 -index 0000000000000000000000000000000000000000..ce808520d639581696689a2ab85de00d85aa0ee3 ---- /dev/null -+++ b/src/main/java/io/papermc/paper/plugin/lifecycle/event/LifecycleEventRunner.java -@@ -0,0 +1,100 @@ -+package io.papermc.paper.plugin.lifecycle.event; -+ -+import io.papermc.paper.plugin.lifecycle.event.registrar.PaperRegistrar; -+import io.papermc.paper.plugin.lifecycle.event.registrar.RegistrarEvent; -+import io.papermc.paper.plugin.lifecycle.event.registrar.RegistrarEventImpl; -+import io.papermc.paper.plugin.lifecycle.event.registrar.ReloadableRegistrarEvent; -+import io.papermc.paper.plugin.lifecycle.event.types.AbstractLifecycleEventType; -+import io.papermc.paper.plugin.lifecycle.event.types.LifecycleEventType; -+import io.papermc.paper.plugin.lifecycle.event.types.OwnerAwareLifecycleEvent; -+import java.util.ArrayList; -+import java.util.List; -+import java.util.function.Predicate; -+import org.bukkit.plugin.Plugin; -+import org.checkerframework.checker.nullness.qual.NonNull; -+import org.checkerframework.checker.nullness.qual.Nullable; -+import org.checkerframework.framework.qual.DefaultQualifier; -+ -+@DefaultQualifier(NonNull.class) -+public class LifecycleEventRunner { -+ -+ public static final LifecycleEventRunner INSTANCE = new LifecycleEventRunner(); -+ -+ private final List> lifecycleEventTypes = new ArrayList<>(); -+ private boolean blockPluginReloading = false; -+ -+ public void checkRegisteredHandler(final O owner, final AbstractLifecycleEventType eventType) { -+ /* -+ Lifecycle event handlers for reloadable events that are registered from the BootstrapContext prevent -+ the server from reloading plugins. This is because reloading plugins requires disabling all the plugins, -+ running the reload logic (which would include places where these events should fire) and then re-enabling plugins. -+ */ -+ if (eventType.blocksReloading(owner)) { -+ this.blockPluginReloading = true; -+ } -+ } -+ -+ public boolean blocksPluginReloading() { -+ return this.blockPluginReloading; -+ } -+ -+ public > void addEventType(final ET eventType) { -+ this.lifecycleEventTypes.add(eventType); -+ } -+ -+ public void callEvent(final LifecycleEventType eventType, final E event) { -+ this.callEvent(eventType, event, $ -> true); -+ } -+ -+ public void callEvent(final LifecycleEventType eventType, final E event, final Predicate ownerPredicate) { -+ final AbstractLifecycleEventType lifecycleEventType = (AbstractLifecycleEventType) eventType; -+ lifecycleEventType.forEachHandler(event, registeredHandler -> { -+ try { -+ if (event instanceof final OwnerAwareLifecycleEvent ownerAwareEvent) { -+ ownerAwareGenericHelper(ownerAwareEvent, registeredHandler.owner()); -+ } -+ registeredHandler.lifecycleEventHandler().run(event); -+ } catch (final Throwable ex) { -+ throw new RuntimeException("Could not run '%s' lifecycle event handler from %s".formatted(lifecycleEventType.name(), registeredHandler.owner().getPluginMeta().getDisplayName()), ex); -+ } finally { -+ if (event instanceof final OwnerAwareLifecycleEvent ownerAwareEvent) { -+ ownerAwareEvent.setOwner(null); -+ } -+ } -+ }, handler -> ownerPredicate.test(handler.owner())); -+ event.invalidate(); -+ } -+ -+ private static void ownerAwareGenericHelper(final OwnerAwareLifecycleEvent event, final LifecycleEventOwner possibleOwner) { -+ final @Nullable O owner = event.castOwner(possibleOwner); -+ if (owner != null) { -+ event.setOwner(owner); -+ } else { -+ throw new IllegalStateException("Found invalid owner " + possibleOwner + " for event " + event); -+ } -+ } -+ -+ public void unregisterAllEventHandlersFor(final Plugin plugin) { -+ for (final LifecycleEventType lifecycleEventType : this.lifecycleEventTypes) { -+ this.removeEventHandlersOwnedBy(lifecycleEventType, plugin); -+ } -+ } -+ -+ private void removeEventHandlersOwnedBy(final LifecycleEventType eventType, final Plugin possibleOwner) { -+ final AbstractLifecycleEventType lifecycleEventType = (AbstractLifecycleEventType) eventType; -+ lifecycleEventType.removeMatching(registeredHandler -> registeredHandler.owner().getPluginMeta().getName().equals(possibleOwner.getPluginMeta().getName())); -+ } -+ -+ @SuppressWarnings("unchecked") -+ public > void callStaticRegistrarEvent(final LifecycleEventType, ?> lifecycleEventType, final R registrar, final Class ownerClass) { -+ this.callEvent((LifecycleEventType, ?>) lifecycleEventType, new RegistrarEventImpl<>(registrar, ownerClass), ownerClass::isInstance); -+ } -+ -+ @SuppressWarnings("unchecked") -+ public > void callReloadableRegistrarEvent(final LifecycleEventType, ?> lifecycleEventType, final R registrar, final Class ownerClass, final ReloadableRegistrarEvent.Cause cause) { -+ this.callEvent((LifecycleEventType, ?>) lifecycleEventType, new RegistrarEventImpl.ReloadableImpl<>(registrar, ownerClass, cause), ownerClass::isInstance); -+ } -+ -+ private LifecycleEventRunner() { -+ } -+} -diff --git a/src/main/java/io/papermc/paper/plugin/lifecycle/event/PaperLifecycleEvent.java b/src/main/java/io/papermc/paper/plugin/lifecycle/event/PaperLifecycleEvent.java -new file mode 100644 -index 0000000000000000000000000000000000000000..e941405269a773e8a77e26ffd1afd84f53fadff5 ---- /dev/null -+++ b/src/main/java/io/papermc/paper/plugin/lifecycle/event/PaperLifecycleEvent.java -@@ -0,0 +1,10 @@ -+package io.papermc.paper.plugin.lifecycle.event; -+ -+public interface PaperLifecycleEvent extends LifecycleEvent { -+ -+ // called after all handlers have been run. Can be -+ // used to invalid various contexts to plugins can't -+ // try to re-use them by storing them from the event -+ default void invalidate() { -+ } -+} -diff --git a/src/main/java/io/papermc/paper/plugin/lifecycle/event/PaperLifecycleEventManager.java b/src/main/java/io/papermc/paper/plugin/lifecycle/event/PaperLifecycleEventManager.java -new file mode 100644 -index 0000000000000000000000000000000000000000..d05334016bd01201c755dea04c0cea56b6dfcb50 ---- /dev/null -+++ b/src/main/java/io/papermc/paper/plugin/lifecycle/event/PaperLifecycleEventManager.java -@@ -0,0 +1,26 @@ -+package io.papermc.paper.plugin.lifecycle.event; -+ -+import com.google.common.base.Preconditions; -+import io.papermc.paper.plugin.lifecycle.event.handler.configuration.AbstractLifecycleEventHandlerConfiguration; -+import io.papermc.paper.plugin.lifecycle.event.handler.configuration.LifecycleEventHandlerConfiguration; -+import java.util.function.BooleanSupplier; -+import org.checkerframework.checker.nullness.qual.NonNull; -+import org.checkerframework.framework.qual.DefaultQualifier; -+ -+@DefaultQualifier(NonNull.class) -+public final class PaperLifecycleEventManager implements LifecycleEventManager { -+ -+ private final O owner; -+ public final BooleanSupplier registrationCheck; -+ -+ public PaperLifecycleEventManager(final O owner, final BooleanSupplier registrationCheck) { -+ this.owner = owner; -+ this.registrationCheck = registrationCheck; -+ } -+ -+ @Override -+ public void registerEventHandler(final LifecycleEventHandlerConfiguration handlerConfiguration) { -+ Preconditions.checkState(this.registrationCheck.getAsBoolean(), "Cannot register lifecycle event handlers"); -+ ((AbstractLifecycleEventHandlerConfiguration) handlerConfiguration).registerFrom(this.owner); -+ } -+} -diff --git a/src/main/java/io/papermc/paper/plugin/lifecycle/event/handler/configuration/AbstractLifecycleEventHandlerConfiguration.java b/src/main/java/io/papermc/paper/plugin/lifecycle/event/handler/configuration/AbstractLifecycleEventHandlerConfiguration.java -new file mode 100644 -index 0000000000000000000000000000000000000000..fa216e6fd804859293385ed43c53dfca057f317f ---- /dev/null -+++ b/src/main/java/io/papermc/paper/plugin/lifecycle/event/handler/configuration/AbstractLifecycleEventHandlerConfiguration.java -@@ -0,0 +1,28 @@ -+package io.papermc.paper.plugin.lifecycle.event.handler.configuration; -+ -+import io.papermc.paper.plugin.lifecycle.event.LifecycleEvent; -+import io.papermc.paper.plugin.lifecycle.event.LifecycleEventOwner; -+import io.papermc.paper.plugin.lifecycle.event.handler.LifecycleEventHandler; -+import io.papermc.paper.plugin.lifecycle.event.types.AbstractLifecycleEventType; -+import org.checkerframework.checker.nullness.qual.NonNull; -+import org.checkerframework.framework.qual.DefaultQualifier; -+ -+@DefaultQualifier(NonNull.class) -+public abstract class AbstractLifecycleEventHandlerConfiguration implements LifecycleEventHandlerConfiguration { -+ -+ private final LifecycleEventHandler handler; -+ private final AbstractLifecycleEventType type; -+ -+ protected AbstractLifecycleEventHandlerConfiguration(final LifecycleEventHandler handler, final AbstractLifecycleEventType type) { -+ this.handler = handler; -+ this.type = type; -+ } -+ -+ public final void registerFrom(final O owner) { -+ this.type.tryRegister(owner, this); -+ } -+ -+ public LifecycleEventHandler handler() { -+ return this.handler; -+ } -+} -diff --git a/src/main/java/io/papermc/paper/plugin/lifecycle/event/handler/configuration/MonitorLifecycleEventHandlerConfigurationImpl.java b/src/main/java/io/papermc/paper/plugin/lifecycle/event/handler/configuration/MonitorLifecycleEventHandlerConfigurationImpl.java -new file mode 100644 -index 0000000000000000000000000000000000000000..ab444d60d72bd692843052df5d7b24fbb5621cf7 ---- /dev/null -+++ b/src/main/java/io/papermc/paper/plugin/lifecycle/event/handler/configuration/MonitorLifecycleEventHandlerConfigurationImpl.java -@@ -0,0 +1,28 @@ -+package io.papermc.paper.plugin.lifecycle.event.handler.configuration; -+ -+import io.papermc.paper.plugin.lifecycle.event.LifecycleEvent; -+import io.papermc.paper.plugin.lifecycle.event.LifecycleEventOwner; -+import io.papermc.paper.plugin.lifecycle.event.handler.LifecycleEventHandler; -+import io.papermc.paper.plugin.lifecycle.event.types.AbstractLifecycleEventType; -+import org.checkerframework.checker.nullness.qual.NonNull; -+import org.checkerframework.framework.qual.DefaultQualifier; -+ -+@DefaultQualifier(NonNull.class) -+public class MonitorLifecycleEventHandlerConfigurationImpl extends AbstractLifecycleEventHandlerConfiguration implements MonitorLifecycleEventHandlerConfiguration { -+ -+ private boolean monitor = false; -+ -+ public MonitorLifecycleEventHandlerConfigurationImpl(final LifecycleEventHandler handler, final AbstractLifecycleEventType eventType) { -+ super(handler, eventType); -+ } -+ -+ public boolean isMonitor() { -+ return this.monitor; -+ } -+ -+ @Override -+ public MonitorLifecycleEventHandlerConfiguration monitor() { -+ this.monitor = true; -+ return this; -+ } -+} -diff --git a/src/main/java/io/papermc/paper/plugin/lifecycle/event/handler/configuration/PrioritizedLifecycleEventHandlerConfigurationImpl.java b/src/main/java/io/papermc/paper/plugin/lifecycle/event/handler/configuration/PrioritizedLifecycleEventHandlerConfigurationImpl.java -new file mode 100644 -index 0000000000000000000000000000000000000000..ccdad31717bf12b844cbeaf11a49247485ec77f1 ---- /dev/null -+++ b/src/main/java/io/papermc/paper/plugin/lifecycle/event/handler/configuration/PrioritizedLifecycleEventHandlerConfigurationImpl.java -@@ -0,0 +1,40 @@ -+package io.papermc.paper.plugin.lifecycle.event.handler.configuration; -+ -+import io.papermc.paper.plugin.lifecycle.event.LifecycleEvent; -+import io.papermc.paper.plugin.lifecycle.event.LifecycleEventOwner; -+import io.papermc.paper.plugin.lifecycle.event.handler.LifecycleEventHandler; -+import io.papermc.paper.plugin.lifecycle.event.types.AbstractLifecycleEventType; -+import java.util.OptionalInt; -+import org.checkerframework.checker.nullness.qual.NonNull; -+import org.checkerframework.framework.qual.DefaultQualifier; -+ -+@DefaultQualifier(NonNull.class) -+public class PrioritizedLifecycleEventHandlerConfigurationImpl -+ extends AbstractLifecycleEventHandlerConfiguration -+ implements PrioritizedLifecycleEventHandlerConfiguration { -+ -+ private static final OptionalInt DEFAULT_PRIORITY = OptionalInt.of(0); -+ private static final OptionalInt MONITOR_PRIORITY = OptionalInt.empty(); -+ -+ private OptionalInt priority = DEFAULT_PRIORITY; -+ -+ public PrioritizedLifecycleEventHandlerConfigurationImpl(final LifecycleEventHandler handler, final AbstractLifecycleEventType eventType) { -+ super(handler, eventType); -+ } -+ -+ public OptionalInt priority() { -+ return this.priority; -+ } -+ -+ @Override -+ public PrioritizedLifecycleEventHandlerConfiguration priority(final int priority) { -+ this.priority = OptionalInt.of(priority); -+ return this; -+ } -+ -+ @Override -+ public PrioritizedLifecycleEventHandlerConfiguration monitor() { -+ this.priority = MONITOR_PRIORITY; -+ return this; -+ } -+} -diff --git a/src/main/java/io/papermc/paper/plugin/lifecycle/event/registrar/PaperRegistrar.java b/src/main/java/io/papermc/paper/plugin/lifecycle/event/registrar/PaperRegistrar.java -new file mode 100644 -index 0000000000000000000000000000000000000000..b2586c881988fbabe07eef1b43eb1b55f2d3fa52 ---- /dev/null -+++ b/src/main/java/io/papermc/paper/plugin/lifecycle/event/registrar/PaperRegistrar.java -@@ -0,0 +1,15 @@ -+package io.papermc.paper.plugin.lifecycle.event.registrar; -+ -+import io.papermc.paper.plugin.lifecycle.event.LifecycleEventOwner; -+import org.checkerframework.checker.nullness.qual.NonNull; -+import org.checkerframework.checker.nullness.qual.Nullable; -+import org.checkerframework.framework.qual.DefaultQualifier; -+ -+@DefaultQualifier(NonNull.class) -+public interface PaperRegistrar extends Registrar { -+ -+ void setCurrentContext(@Nullable O owner); -+ -+ default void invalidate() { -+ } -+} -diff --git a/src/main/java/io/papermc/paper/plugin/lifecycle/event/registrar/RegistrarEventImpl.java b/src/main/java/io/papermc/paper/plugin/lifecycle/event/registrar/RegistrarEventImpl.java -new file mode 100644 -index 0000000000000000000000000000000000000000..6d530c52aaf0dc2cdfe3bd56af557274a7f44256 ---- /dev/null -+++ b/src/main/java/io/papermc/paper/plugin/lifecycle/event/registrar/RegistrarEventImpl.java -@@ -0,0 +1,70 @@ -+package io.papermc.paper.plugin.lifecycle.event.registrar; -+ -+import io.papermc.paper.plugin.lifecycle.event.LifecycleEventOwner; -+import io.papermc.paper.plugin.lifecycle.event.PaperLifecycleEvent; -+import io.papermc.paper.plugin.lifecycle.event.types.OwnerAwareLifecycleEvent; -+import org.checkerframework.checker.nullness.qual.NonNull; -+import org.checkerframework.checker.nullness.qual.Nullable; -+import org.checkerframework.framework.qual.DefaultQualifier; -+ -+@DefaultQualifier(NonNull.class) -+public class RegistrarEventImpl, O extends LifecycleEventOwner> implements PaperLifecycleEvent, OwnerAwareLifecycleEvent, RegistrarEvent { -+ -+ private final R registrar; -+ private final Class ownerClass; -+ -+ public RegistrarEventImpl(final R registrar, final Class ownerClass) { -+ this.registrar = registrar; -+ this.ownerClass = ownerClass; -+ } -+ -+ @Override -+ public R registrar() { -+ return this.registrar; -+ } -+ -+ @Override -+ public final void setOwner(final @Nullable O owner) { -+ this.registrar.setCurrentContext(owner); -+ } -+ -+ @Override -+ public final @Nullable O castOwner(final LifecycleEventOwner owner) { -+ return this.ownerClass.isInstance(owner) ? this.ownerClass.cast(owner) : null; -+ } -+ -+ @Override -+ public void invalidate() { -+ this.registrar.invalidate(); -+ } -+ -+ @Override -+ public String toString() { -+ return "RegistrarEventImpl{" + -+ "registrar=" + this.registrar + -+ ", ownerClass=" + this.ownerClass + -+ '}'; -+ } -+ -+ public static class ReloadableImpl, O extends LifecycleEventOwner> extends RegistrarEventImpl implements ReloadableRegistrarEvent { -+ -+ private final ReloadableRegistrarEvent.Cause cause; -+ -+ public ReloadableImpl(final R registrar, final Class ownerClass, final Cause cause) { -+ super(registrar, ownerClass); -+ this.cause = cause; -+ } -+ -+ @Override -+ public Cause cause() { -+ return this.cause; -+ } -+ -+ @Override -+ public String toString() { -+ return "ReloadableImpl{" + -+ "cause=" + this.cause + -+ "} " + super.toString(); -+ } -+ } -+} -diff --git a/src/main/java/io/papermc/paper/plugin/lifecycle/event/types/AbstractLifecycleEventType.java b/src/main/java/io/papermc/paper/plugin/lifecycle/event/types/AbstractLifecycleEventType.java -new file mode 100644 -index 0000000000000000000000000000000000000000..01a4e9a36a9970f30ed9f9236fc5a4a1cf71844e ---- /dev/null -+++ b/src/main/java/io/papermc/paper/plugin/lifecycle/event/types/AbstractLifecycleEventType.java -@@ -0,0 +1,62 @@ -+package io.papermc.paper.plugin.lifecycle.event.types; -+ -+import io.papermc.paper.plugin.bootstrap.BootstrapContext; -+import io.papermc.paper.plugin.lifecycle.event.LifecycleEvent; -+import io.papermc.paper.plugin.lifecycle.event.LifecycleEventOwner; -+import io.papermc.paper.plugin.lifecycle.event.LifecycleEventRunner; -+import io.papermc.paper.plugin.lifecycle.event.handler.LifecycleEventHandler; -+import io.papermc.paper.plugin.lifecycle.event.handler.configuration.AbstractLifecycleEventHandlerConfiguration; -+import io.papermc.paper.plugin.lifecycle.event.handler.configuration.LifecycleEventHandlerConfiguration; -+import java.util.function.Consumer; -+import java.util.function.Predicate; -+import org.checkerframework.checker.nullness.qual.NonNull; -+import org.checkerframework.framework.qual.DefaultQualifier; -+ -+@DefaultQualifier(NonNull.class) -+public abstract class AbstractLifecycleEventType> implements LifecycleEventType { -+ -+ private final String name; -+ private final Class ownerType; -+ -+ protected AbstractLifecycleEventType(final String name, final Class ownerType) { -+ this.name = name; -+ this.ownerType = ownerType; -+ LifecycleEventRunner.INSTANCE.addEventType(this); -+ } -+ -+ @Override -+ public String name() { -+ return this.name; -+ } -+ -+ private void verifyOwner(final O owner) { -+ if (!this.ownerType.isInstance(owner)) { -+ throw new IllegalArgumentException("You cannot register the lifecycle event '" + this.name + "' on " + owner); -+ } -+ } -+ -+ public boolean blocksReloading(final O eventOwner) { -+ return eventOwner instanceof BootstrapContext; -+ } -+ -+ public abstract boolean hasHandlers(); -+ -+ public abstract void forEachHandler(E event, Consumer> consumer, Predicate> predicate); -+ -+ public abstract void removeMatching(Predicate> predicate); -+ -+ protected abstract void register(O owner, AbstractLifecycleEventHandlerConfiguration config); -+ -+ public final void tryRegister(final O owner, final AbstractLifecycleEventHandlerConfiguration config) { -+ this.verifyOwner(owner); -+ LifecycleEventRunner.INSTANCE.checkRegisteredHandler(owner, this); -+ this.register(owner, config); -+ } -+ -+ public record RegisteredHandler(O owner, AbstractLifecycleEventHandlerConfiguration config) { -+ -+ public LifecycleEventHandler lifecycleEventHandler() { -+ return this.config().handler(); -+ } -+ } -+} -diff --git a/src/main/java/io/papermc/paper/plugin/lifecycle/event/types/LifecycleEventTypeProviderImpl.java b/src/main/java/io/papermc/paper/plugin/lifecycle/event/types/LifecycleEventTypeProviderImpl.java -new file mode 100644 -index 0000000000000000000000000000000000000000..b11346e04bb16c3238f32deb87dbd680e261d4d2 ---- /dev/null -+++ b/src/main/java/io/papermc/paper/plugin/lifecycle/event/types/LifecycleEventTypeProviderImpl.java -@@ -0,0 +1,25 @@ -+package io.papermc.paper.plugin.lifecycle.event.types; -+ -+import io.papermc.paper.plugin.lifecycle.event.LifecycleEvent; -+import io.papermc.paper.plugin.lifecycle.event.LifecycleEventOwner; -+import io.papermc.paper.plugin.lifecycle.event.LifecycleEventRunner; -+import org.checkerframework.checker.nullness.qual.NonNull; -+import org.checkerframework.framework.qual.DefaultQualifier; -+ -+@DefaultQualifier(NonNull.class) -+public final class LifecycleEventTypeProviderImpl implements LifecycleEventTypeProvider { -+ -+ public static LifecycleEventTypeProviderImpl instance() { -+ return (LifecycleEventTypeProviderImpl) LifecycleEventTypeProvider.provider(); -+ } -+ -+ @Override -+ public LifecycleEventType.Monitorable monitor(final String name, final Class ownerType) { -+ return new MonitorableLifecycleEventType<>(name, ownerType); -+ } -+ -+ @Override -+ public LifecycleEventType.Prioritizable prioritized(final String name, final Class ownerType) { -+ return new PrioritizableLifecycleEventType.Simple<>(name, ownerType); -+ } -+} -diff --git a/src/main/java/io/papermc/paper/plugin/lifecycle/event/types/MonitorableLifecycleEventType.java b/src/main/java/io/papermc/paper/plugin/lifecycle/event/types/MonitorableLifecycleEventType.java -new file mode 100644 -index 0000000000000000000000000000000000000000..abb969cf6ed967fe7720c56d3b3157bd1b74700d ---- /dev/null -+++ b/src/main/java/io/papermc/paper/plugin/lifecycle/event/types/MonitorableLifecycleEventType.java -@@ -0,0 +1,63 @@ -+package io.papermc.paper.plugin.lifecycle.event.types; -+ -+import io.papermc.paper.plugin.lifecycle.event.LifecycleEvent; -+import io.papermc.paper.plugin.lifecycle.event.LifecycleEventOwner; -+import io.papermc.paper.plugin.lifecycle.event.handler.LifecycleEventHandler; -+import io.papermc.paper.plugin.lifecycle.event.handler.configuration.AbstractLifecycleEventHandlerConfiguration; -+import io.papermc.paper.plugin.lifecycle.event.handler.configuration.MonitorLifecycleEventHandlerConfiguration; -+import io.papermc.paper.plugin.lifecycle.event.handler.configuration.MonitorLifecycleEventHandlerConfigurationImpl; -+import java.util.ArrayList; -+import java.util.List; -+import java.util.function.Consumer; -+import java.util.function.Predicate; -+import org.checkerframework.checker.nullness.qual.NonNull; -+import org.checkerframework.framework.qual.DefaultQualifier; -+ -+@DefaultQualifier(NonNull.class) -+public class MonitorableLifecycleEventType extends AbstractLifecycleEventType> implements LifecycleEventType.Monitorable { -+ -+ final List> handlers = new ArrayList<>(); -+ int nonMonitorIdx = 0; -+ -+ public MonitorableLifecycleEventType(final String name, final Class ownerType) { -+ super(name, ownerType); -+ } -+ -+ @Override -+ public boolean hasHandlers() { -+ return !this.handlers.isEmpty(); -+ } -+ -+ @Override -+ public MonitorLifecycleEventHandlerConfigurationImpl newHandler(final LifecycleEventHandler handler) { -+ return new MonitorLifecycleEventHandlerConfigurationImpl<>(handler, this); -+ } -+ -+ @Override -+ protected void register(final O owner, final AbstractLifecycleEventHandlerConfiguration config) { -+ if (!(config instanceof final MonitorLifecycleEventHandlerConfigurationImpl monitor)) { -+ throw new IllegalArgumentException("Configuration must be a MonitorLifecycleEventHandlerConfiguration"); -+ } -+ final RegisteredHandler registeredHandler = new RegisteredHandler<>(owner, config); -+ if (!monitor.isMonitor()) { -+ this.handlers.add(this.nonMonitorIdx, registeredHandler); -+ this.nonMonitorIdx++; -+ } else { -+ this.handlers.add(registeredHandler); -+ } -+ } -+ -+ @Override -+ public void forEachHandler(final E event, final Consumer> consumer, final Predicate> predicate) { -+ for (final RegisteredHandler handler : this.handlers) { -+ if (predicate.test(handler)) { -+ consumer.accept(handler); -+ } -+ } -+ } -+ -+ @Override -+ public void removeMatching(final Predicate> predicate) { -+ this.handlers.removeIf(predicate); -+ } -+} -diff --git a/src/main/java/io/papermc/paper/plugin/lifecycle/event/types/OwnerAwareLifecycleEvent.java b/src/main/java/io/papermc/paper/plugin/lifecycle/event/types/OwnerAwareLifecycleEvent.java -new file mode 100644 -index 0000000000000000000000000000000000000000..3e7e7474f301c0725fa2bcd6e19e476fc35f2d5a ---- /dev/null -+++ b/src/main/java/io/papermc/paper/plugin/lifecycle/event/types/OwnerAwareLifecycleEvent.java -@@ -0,0 +1,15 @@ -+package io.papermc.paper.plugin.lifecycle.event.types; -+ -+import io.papermc.paper.plugin.lifecycle.event.LifecycleEvent; -+import io.papermc.paper.plugin.lifecycle.event.LifecycleEventOwner; -+import org.checkerframework.checker.nullness.qual.NonNull; -+import org.checkerframework.checker.nullness.qual.Nullable; -+import org.checkerframework.framework.qual.DefaultQualifier; -+ -+@DefaultQualifier(NonNull.class) -+public interface OwnerAwareLifecycleEvent extends LifecycleEvent { -+ -+ void setOwner(@Nullable O owner); -+ -+ @Nullable O castOwner(LifecycleEventOwner owner); -+} -diff --git a/src/main/java/io/papermc/paper/plugin/lifecycle/event/types/PrioritizableLifecycleEventType.java b/src/main/java/io/papermc/paper/plugin/lifecycle/event/types/PrioritizableLifecycleEventType.java -new file mode 100644 -index 0000000000000000000000000000000000000000..2ed622a61ddc37b11888867770b513909b9a2ecc ---- /dev/null -+++ b/src/main/java/io/papermc/paper/plugin/lifecycle/event/types/PrioritizableLifecycleEventType.java -@@ -0,0 +1,79 @@ -+package io.papermc.paper.plugin.lifecycle.event.types; -+ -+import com.google.common.base.Preconditions; -+import io.papermc.paper.plugin.lifecycle.event.LifecycleEvent; -+import io.papermc.paper.plugin.lifecycle.event.LifecycleEventOwner; -+import io.papermc.paper.plugin.lifecycle.event.handler.LifecycleEventHandler; -+import io.papermc.paper.plugin.lifecycle.event.handler.configuration.AbstractLifecycleEventHandlerConfiguration; -+import io.papermc.paper.plugin.lifecycle.event.handler.configuration.PrioritizedLifecycleEventHandlerConfiguration; -+import io.papermc.paper.plugin.lifecycle.event.handler.configuration.PrioritizedLifecycleEventHandlerConfigurationImpl; -+import java.util.ArrayList; -+import java.util.Comparator; -+import java.util.List; -+import java.util.function.Consumer; -+import java.util.function.Predicate; -+import org.checkerframework.checker.nullness.qual.NonNull; -+import org.checkerframework.framework.qual.DefaultQualifier; -+ -+@DefaultQualifier(NonNull.class) -+public abstract class PrioritizableLifecycleEventType< -+ O extends LifecycleEventOwner, -+ E extends LifecycleEvent, -+ C extends PrioritizedLifecycleEventHandlerConfiguration -+> extends AbstractLifecycleEventType { -+ -+ private static final Comparator> COMPARATOR = Comparator.comparing(handler -> ((PrioritizedLifecycleEventHandlerConfigurationImpl) handler.config()).priority(), (o1, o2) -> { -+ if (o1.equals(o2)) { -+ return 0; -+ } else if (o1.isEmpty()) { -+ return 1; -+ } else if (o2.isEmpty()) { -+ return -1; -+ } else { -+ return Integer.compare(o1.getAsInt(), o2.getAsInt()); -+ } -+ }); -+ -+ private final List> handlers = new ArrayList<>(); -+ -+ public PrioritizableLifecycleEventType(final String name, final Class ownerType) { -+ super(name, ownerType); -+ } -+ -+ @Override -+ public boolean hasHandlers() { -+ return !this.handlers.isEmpty(); -+ } -+ -+ @Override -+ protected void register(final O owner, final AbstractLifecycleEventHandlerConfiguration config) { -+ Preconditions.checkArgument(config instanceof PrioritizedLifecycleEventHandlerConfigurationImpl, "Configuration must be a PrioritizedLifecycleEventHandlerConfiguration"); -+ this.handlers.add(new RegisteredHandler<>(owner, config)); -+ this.handlers.sort(COMPARATOR); -+ } -+ -+ @Override -+ public void forEachHandler(final E event, final Consumer> consumer, final Predicate> predicate) { -+ for (final RegisteredHandler handler : this.handlers) { -+ if (predicate.test(handler)) { -+ consumer.accept(handler); -+ } -+ } -+ } -+ -+ @Override -+ public void removeMatching(final Predicate> predicate) { -+ this.handlers.removeIf(predicate); -+ } -+ -+ public static class Simple extends PrioritizableLifecycleEventType> implements LifecycleEventType.Prioritizable { -+ public Simple(final String name, final Class ownerType) { -+ super(name, ownerType); -+ } -+ -+ @Override -+ public PrioritizedLifecycleEventHandlerConfiguration newHandler(final LifecycleEventHandler handler) { -+ return new PrioritizedLifecycleEventHandlerConfigurationImpl<>(handler, this); -+ } -+ } -+} -diff --git a/src/main/java/io/papermc/paper/plugin/manager/PaperPluginInstanceManager.java b/src/main/java/io/papermc/paper/plugin/manager/PaperPluginInstanceManager.java -index 834b85f24df023642f8abf7213fe578ac8c17a3e..3e82ea07ca4194844c5528446e2c4a46ff4acee5 100644 ---- a/src/main/java/io/papermc/paper/plugin/manager/PaperPluginInstanceManager.java -+++ b/src/main/java/io/papermc/paper/plugin/manager/PaperPluginInstanceManager.java -@@ -293,6 +293,15 @@ class PaperPluginInstanceManager { - + pluginName + " (Is it up to date?)", ex, plugin); // Paper - } - -+ // Paper start - lifecycle event system -+ try { -+ io.papermc.paper.plugin.lifecycle.event.LifecycleEventRunner.INSTANCE.unregisterAllEventHandlersFor(plugin); -+ } catch (Throwable ex) { -+ this.handlePluginException("Error occurred (in the plugin loader) while unregistering lifecycle event handlers for " -+ + pluginName + " (Is it up to date?)", ex, plugin); -+ } -+ // Paper end -+ - try { - this.server.getMessenger().unregisterIncomingPluginChannel(plugin); - this.server.getMessenger().unregisterOutgoingPluginChannel(plugin); -diff --git a/src/main/java/io/papermc/paper/plugin/storage/BootstrapProviderStorage.java b/src/main/java/io/papermc/paper/plugin/storage/BootstrapProviderStorage.java -index 2e96308696e131f3f013469a395e5ddda2c5d529..65a66e484c1c39c5f41d97db52f31c67b4479d20 100644 ---- a/src/main/java/io/papermc/paper/plugin/storage/BootstrapProviderStorage.java -+++ b/src/main/java/io/papermc/paper/plugin/storage/BootstrapProviderStorage.java -@@ -32,8 +32,9 @@ public class BootstrapProviderStorage extends SimpleProviderStorage provider, PluginBootstrap provided) { - try { -- BootstrapContext context = PluginBootstrapContextImpl.create(provider, PluginInitializerManager.instance().pluginDirectoryPath()); -+ PluginBootstrapContextImpl context = PluginBootstrapContextImpl.create(provider, PluginInitializerManager.instance().pluginDirectoryPath()); // Paper - lifecycle events - provided.bootstrap(context); -+ context.lockLifecycleEventRegistration(); // Paper - lifecycle events - return true; - } catch (Throwable e) { - LOGGER.error("Failed to run bootstrapper for %s. This plugin will not be loaded.".formatted(provider.getSource()), e); -diff --git a/src/main/java/org/bukkit/craftbukkit/CraftServer.java b/src/main/java/org/bukkit/craftbukkit/CraftServer.java -index 8b871d29d883119cd8ad9ca134f4c1fce9362705..f6b87a5249e1a21b3221f253f891d8978249c50a 100644 ---- a/src/main/java/org/bukkit/craftbukkit/CraftServer.java -+++ b/src/main/java/org/bukkit/craftbukkit/CraftServer.java -@@ -1048,6 +1048,11 @@ public final class CraftServer implements Server { - - @Override - public void reload() { -+ // Paper start - lifecycle events -+ if (io.papermc.paper.plugin.lifecycle.event.LifecycleEventRunner.INSTANCE.blocksPluginReloading()) { -+ throw new IllegalStateException("A lifecycle event handler has been registered which makes reloading plugins not possible"); -+ } -+ // Paper end - lifecycle events - org.spigotmc.WatchdogThread.hasStarted = false; // Paper - Disable watchdog early timeout on reload - this.reloadCount++; - this.configuration = YamlConfiguration.loadConfiguration(this.getConfigFile()); -diff --git a/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java b/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java -index 73a3e708f21c1b9c5de1fc180f728d6da7eea0fb..b89db5335aa3984b76513165456ebe43ad9a0cc7 100644 ---- a/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java -+++ b/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java -@@ -652,6 +652,13 @@ public final class CraftMagicNumbers implements UnsafeValues { - } - // Paper end - spawn egg color visibility - -+ // Paper start - lifecycle event API -+ @Override -+ public io.papermc.paper.plugin.lifecycle.event.LifecycleEventManager createPluginLifecycleEventManager(final org.bukkit.plugin.java.JavaPlugin plugin, final java.util.function.BooleanSupplier registrationCheck) { -+ return new io.papermc.paper.plugin.lifecycle.event.PaperLifecycleEventManager<>(plugin, registrationCheck); -+ } -+ // Paper end - lifecycle event API -+ - /** - * This helper class represents the different NBT Tags. - *

    -diff --git a/src/main/resources/META-INF/services/io.papermc.paper.plugin.lifecycle.event.types.LifecycleEventTypeProvider b/src/main/resources/META-INF/services/io.papermc.paper.plugin.lifecycle.event.types.LifecycleEventTypeProvider -new file mode 100644 -index 0000000000000000000000000000000000000000..808b1192b60348ad05f0bfbdeda6f94df4876743 ---- /dev/null -+++ b/src/main/resources/META-INF/services/io.papermc.paper.plugin.lifecycle.event.types.LifecycleEventTypeProvider -@@ -0,0 +1 @@ -+io.papermc.paper.plugin.lifecycle.event.types.LifecycleEventTypeProviderImpl -diff --git a/src/test/java/io/papermc/paper/plugin/PaperTestPlugin.java b/src/test/java/io/papermc/paper/plugin/PaperTestPlugin.java -index 1d14f530ef888102e47eeeaf0d1a6076e51871c4..90cf0c702ca2ff9de64d9718ecba5f2d128953a6 100644 ---- a/src/test/java/io/papermc/paper/plugin/PaperTestPlugin.java -+++ b/src/test/java/io/papermc/paper/plugin/PaperTestPlugin.java -@@ -143,4 +143,11 @@ public class PaperTestPlugin extends PluginBase { - public List onTabComplete(CommandSender sender, Command command, String alias, String[] args) { - throw new UnsupportedOperationException("Not supported."); - } -+ -+ // Paper start - lifecycle events -+ @Override -+ public io.papermc.paper.plugin.lifecycle.event.LifecycleEventManager getLifecycleManager() { -+ throw new UnsupportedOperationException("Not supported."); -+ } -+ // Paper end - lifecycle events - } diff --git a/patches/server/0926-Deprecate-ItemStack-setType.patch b/patches/server/0926-Deprecate-ItemStack-setType.patch new file mode 100644 index 000000000000..e58b22013ff7 --- /dev/null +++ b/patches/server/0926-Deprecate-ItemStack-setType.patch @@ -0,0 +1,35 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Owen1212055 <23108066+Owen1212055@users.noreply.github.com> +Date: Tue, 26 Mar 2024 21:42:23 -0400 +Subject: [PATCH] Deprecate ItemStack#setType + + +diff --git a/src/main/java/org/bukkit/craftbukkit/inventory/CraftItemStack.java b/src/main/java/org/bukkit/craftbukkit/inventory/CraftItemStack.java +index ffd7ba14be38a117f5a7d7035a8d71a20fb1c4fc..7228d43d331de16cbaa0e97c7e3fa45c0bc89558 100644 +--- a/src/main/java/org/bukkit/craftbukkit/inventory/CraftItemStack.java ++++ b/src/main/java/org/bukkit/craftbukkit/inventory/CraftItemStack.java +@@ -429,4 +429,24 @@ public final class CraftItemStack extends ItemStack { + static boolean hasItemMeta(net.minecraft.world.item.ItemStack item) { + return !(item == null || item.getComponentsPatch().isEmpty()); + } ++ // Paper start - with type ++ @Override ++ public ItemStack withType(final Material type) { ++ if (type == Material.AIR) { ++ return CraftItemStack.asCraftMirror(null); ++ } ++ ++ final net.minecraft.world.item.ItemStack copy = new net.minecraft.world.item.ItemStack( ++ CraftItemType.bukkitToMinecraft(type), this.getAmount() ++ ); ++ ++ if (this.handle != null) { ++ copy.applyComponents(this.handle.getComponentsPatch()); ++ } ++ ++ final CraftItemStack mirrored = CraftItemStack.asCraftMirror(copy); ++ mirrored.setItemMeta(mirrored.getItemMeta()); ++ return mirrored; ++ } ++ // Paper end + } diff --git a/patches/server/0927-Add-CartographyItemEvent.patch b/patches/server/0927-Add-CartographyItemEvent.patch new file mode 100644 index 000000000000..544bc15eda79 --- /dev/null +++ b/patches/server/0927-Add-CartographyItemEvent.patch @@ -0,0 +1,43 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Janet Blackquill +Date: Sun, 7 Apr 2024 16:52:42 -0400 +Subject: [PATCH] Add CartographyItemEvent + + +diff --git a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java +index f92624ccd43f448abdee92c975d613cbcb3457c6..90bed0a36b2d518b56164a414350ec02822ad42a 100644 +--- a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java ++++ b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java +@@ -3130,6 +3130,19 @@ public class ServerGamePacketListenerImpl extends ServerCommonPacketListenerImpl + } + } + ++ // Paper start - cartography item event ++ if (packet.getSlotNum() == net.minecraft.world.inventory.CartographyTableMenu.RESULT_SLOT && top instanceof org.bukkit.inventory.CartographyInventory cartographyInventory) { ++ org.bukkit.inventory.ItemStack result = cartographyInventory.getResult(); ++ if (result != null && !result.isEmpty()) { ++ if (click == ClickType.NUMBER_KEY) { ++ event = new io.papermc.paper.event.player.CartographyItemEvent(inventory, type, packet.getSlotNum(), click, action, packet.getButtonNum()); ++ } else { ++ event = new io.papermc.paper.event.player.CartographyItemEvent(inventory, type, packet.getSlotNum(), click, action); ++ } ++ } ++ } ++ // Paper end - cartography item event ++ + event.setCancelled(cancelled); + AbstractContainerMenu oldContainer = this.player.containerMenu; // SPIGOT-1224 + this.cserver.getPluginManager().callEvent(event); +diff --git a/src/main/java/net/minecraft/world/inventory/CartographyTableMenu.java b/src/main/java/net/minecraft/world/inventory/CartographyTableMenu.java +index b3a16b024e46ee8203b225ee429a5c973eab12c6..dc42d42ef91bc3e49f967c0a30a0d1b56e09cf21 100644 +--- a/src/main/java/net/minecraft/world/inventory/CartographyTableMenu.java ++++ b/src/main/java/net/minecraft/world/inventory/CartographyTableMenu.java +@@ -71,7 +71,7 @@ public class CartographyTableMenu extends AbstractContainerMenu { + this.resultContainer = new ResultContainer(this.createBlockHolder(context)) { // Paper - Add missing InventoryHolders + @Override + public void setChanged() { +- CartographyTableMenu.this.slotsChanged(this); ++ // CartographyTableMenu.this.slotsChanged(this); // Paper - Add CatographyItemEvent - do not recompute results if the result slot changes - allows to set the result slot via api + super.setChanged(); + } + diff --git a/patches/server/0927-ItemStack-Tooltip-API.patch b/patches/server/0927-ItemStack-Tooltip-API.patch deleted file mode 100644 index c487ffd71f25..000000000000 --- a/patches/server/0927-ItemStack-Tooltip-API.patch +++ /dev/null @@ -1,32 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Yannick Lamprecht -Date: Mon, 22 Jan 2024 13:27:30 +0100 -Subject: [PATCH] ItemStack Tooltip API - - -diff --git a/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java b/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java -index b89db5335aa3984b76513165456ebe43ad9a0cc7..6e8838245b0792b15fd9788f2ce11f6503d0e070 100644 ---- a/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java -+++ b/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java -@@ -628,6 +628,21 @@ public final class CraftMagicNumbers implements UnsafeValues { - } - // Paper end - fix custom stats criteria creation - -+ // Paper start - expose itemstack tooltip lines -+ @Override -+ public java.util.List computeTooltipLines(final ItemStack itemStack, final io.papermc.paper.inventory.tooltip.TooltipContext tooltipContext, final org.bukkit.entity.Player player) { -+ Preconditions.checkArgument(tooltipContext != null, "tooltipContext cannot be null"); -+ net.minecraft.world.item.TooltipFlag.Default flag = tooltipContext.isAdvanced() ? net.minecraft.world.item.TooltipFlag.ADVANCED : net.minecraft.world.item.TooltipFlag.NORMAL; -+ if (tooltipContext.isCreative()) { -+ flag = flag.asCreative(); -+ } -+ final java.util.List lines = CraftItemStack.asNMSCopy(itemStack).getTooltipLines( -+ net.minecraft.world.item.Item.TooltipContext.of(player == null ? net.minecraft.server.MinecraftServer.getServer().registryAccess() : ((org.bukkit.craftbukkit.entity.CraftPlayer) player).getHandle().level().registryAccess()), -+ player == null ? null : ((org.bukkit.craftbukkit.entity.CraftPlayer) player).getHandle(), flag); -+ return lines.stream().map(io.papermc.paper.adventure.PaperAdventure::asAdventure).toList(); -+ } -+ // Paper end - expose itemstack tooltip lines -+ - @Override - public String get(Class aClass, String s) { - if (aClass == Enchantment.class) { diff --git a/patches/server/0928-Add-getChunkSnapshot-includeLightData-parameter.patch b/patches/server/0928-Add-getChunkSnapshot-includeLightData-parameter.patch deleted file mode 100644 index 54aa35539f07..000000000000 --- a/patches/server/0928-Add-getChunkSnapshot-includeLightData-parameter.patch +++ /dev/null @@ -1,70 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Warrior <50800980+Warriorrrr@users.noreply.github.com> -Date: Sat, 10 Feb 2024 10:03:48 +0100 -Subject: [PATCH] Add getChunkSnapshot includeLightData parameter - - -diff --git a/src/main/java/org/bukkit/craftbukkit/CraftChunk.java b/src/main/java/org/bukkit/craftbukkit/CraftChunk.java -index d6eab2a0fdbafc35efa7ed5b404357391565f4f3..69c7fe5bf5b914276a9f7a0e57ce668e569d91f9 100644 ---- a/src/main/java/org/bukkit/craftbukkit/CraftChunk.java -+++ b/src/main/java/org/bukkit/craftbukkit/CraftChunk.java -@@ -328,12 +328,21 @@ public class CraftChunk implements Chunk { - - @Override - public ChunkSnapshot getChunkSnapshot(boolean includeMaxBlockY, boolean includeBiome, boolean includeBiomeTempRain) { -+ // Paper start - Add getChunkSnapshot includeLightData parameter -+ return getChunkSnapshot(includeMaxBlockY, includeBiome, includeBiomeTempRain, true); -+ } -+ -+ @Override -+ public ChunkSnapshot getChunkSnapshot(boolean includeMaxBlockY, boolean includeBiome, boolean includeBiomeTempRain, boolean includeLightData) { -+ // Paper end - Add getChunkSnapshot includeLightData parameter - ChunkAccess chunk = this.getHandle(ChunkStatus.FULL); - - LevelChunkSection[] cs = chunk.getSections(); - PalettedContainer[] sectionBlockIDs = new PalettedContainer[cs.length]; -- byte[][] sectionSkyLights = new byte[cs.length][]; -- byte[][] sectionEmitLights = new byte[cs.length][]; -+ // Paper start - Add getChunkSnapshot includeLightData parameter -+ byte[][] sectionSkyLights = includeLightData ? new byte[cs.length][] : null; -+ byte[][] sectionEmitLights = includeLightData ? new byte[cs.length][] : null; -+ // Paper end - Add getChunkSnapshot includeLightData parameter - boolean[] sectionEmpty = new boolean[cs.length]; - PalettedContainerRO>[] biome = (includeBiome || includeBiomeTempRain) ? new PalettedContainer[cs.length] : null; - -@@ -350,6 +359,7 @@ public class CraftChunk implements Chunk { - } - // Paper end - Fix ChunkSnapshot#isSectionEmpty(int) - -+ if (includeLightData) { // Paper - Add getChunkSnapshot includeLightData parameter - LevelLightEngine lightengine = this.worldServer.getLightEngine(); - DataLayer skyLightArray = lightengine.getLayerListener(LightLayer.SKY).getDataLayerData(SectionPos.of(this.x, chunk.getSectionYFromSectionIndex(i), this.z)); // SPIGOT-7498: Convert section index - if (skyLightArray == null) { -@@ -365,6 +375,7 @@ public class CraftChunk implements Chunk { - sectionEmitLights[i] = new byte[2048]; - System.arraycopy(emitLightArray.getData(), 0, sectionEmitLights[i], 0, 2048); - } -+ } // Paper - Add getChunkSnapshot includeLightData parameter - - if (biome != null) { - biome[i] = ((PalettedContainer>) cs[i].getBiomes()).copy(); // Paper - Perf: use copy instead of round tripping with codecs -diff --git a/src/main/java/org/bukkit/craftbukkit/CraftChunkSnapshot.java b/src/main/java/org/bukkit/craftbukkit/CraftChunkSnapshot.java -index 85029f1acfdbb411d9ebdf95838d6db3898f4e58..0756b5adb3039997feadeb94afb10b596abd9424 100644 ---- a/src/main/java/org/bukkit/craftbukkit/CraftChunkSnapshot.java -+++ b/src/main/java/org/bukkit/craftbukkit/CraftChunkSnapshot.java -@@ -118,6 +118,7 @@ public class CraftChunkSnapshot implements ChunkSnapshot { - - @Override - public final int getBlockSkyLight(int x, int y, int z) { -+ Preconditions.checkState(this.skylight != null, "ChunkSnapshot created without light data. Please call getSnapshot with includeLightData=true"); // Paper - Add getChunkSnapshot includeLightData parameter - this.validateChunkCoordinates(x, y, z); - - int off = ((y & 0xF) << 7) | (z << 3) | (x >> 1); -@@ -126,6 +127,7 @@ public class CraftChunkSnapshot implements ChunkSnapshot { - - @Override - public final int getBlockEmittedLight(int x, int y, int z) { -+ Preconditions.checkState(this.emitlight != null, "ChunkSnapshot created without light data. Please call getSnapshot with includeLightData=true"); // Paper - Add getChunkSnapshot includeLightData parameter - this.validateChunkCoordinates(x, y, z); - - int off = ((y & 0xF) << 7) | (z << 3) | (x >> 1); diff --git a/patches/server/0928-More-Raid-API.patch b/patches/server/0928-More-Raid-API.patch new file mode 100644 index 000000000000..2039ffa00df2 --- /dev/null +++ b/patches/server/0928-More-Raid-API.patch @@ -0,0 +1,106 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Jake Potrebic +Date: Fri, 4 Mar 2022 09:46:33 -0800 +Subject: [PATCH] More Raid API + +== AT == +public net.minecraft.world.entity.raid.Raid raidEvent + +diff --git a/src/main/java/net/minecraft/world/entity/raid/Raid.java b/src/main/java/net/minecraft/world/entity/raid/Raid.java +index 28922f2a1cf4762d5d7d09635b9268c6091300c3..11cf2d9def087b0898c828eaa21eb5f7b8811d5f 100644 +--- a/src/main/java/net/minecraft/world/entity/raid/Raid.java ++++ b/src/main/java/net/minecraft/world/entity/raid/Raid.java +@@ -107,6 +107,11 @@ public class Raid { + private Raid.RaidStatus status; + private int celebrationTicks; + private Optional waveSpawnPos; ++ // Paper start ++ private static final String PDC_NBT_KEY = "BukkitValues"; ++ private static final org.bukkit.craftbukkit.persistence.CraftPersistentDataTypeRegistry PDC_TYPE_REGISTRY = new org.bukkit.craftbukkit.persistence.CraftPersistentDataTypeRegistry(); ++ public final org.bukkit.craftbukkit.persistence.CraftPersistentDataContainer persistentDataContainer = new org.bukkit.craftbukkit.persistence.CraftPersistentDataContainer(PDC_TYPE_REGISTRY); ++ // Paper end + + public Raid(int id, ServerLevel world, BlockPos pos) { + this.raidEvent = new ServerBossEvent(Raid.RAID_NAME_COMPONENT, BossEvent.BossBarColor.RED, BossEvent.BossBarOverlay.NOTCHED_10); +@@ -150,6 +155,11 @@ public class Raid { + this.heroesOfTheVillage.add(NbtUtils.loadUUID(nbtbase)); + } + } ++ // Paper start ++ if (nbt.contains(PDC_NBT_KEY, net.minecraft.nbt.Tag.TAG_COMPOUND)) { ++ this.persistentDataContainer.putAll(nbt.getCompound(PDC_NBT_KEY)); ++ } ++ // Paper end + + } + +@@ -862,6 +872,11 @@ public class Raid { + } + + nbt.put("HeroesOfTheVillage", nbttaglist); ++ // Paper start ++ if (!this.persistentDataContainer.isEmpty()) { ++ nbt.put(PDC_NBT_KEY, this.persistentDataContainer.toTagCompound()); ++ } ++ // Paper end + return nbt; + } + +diff --git a/src/main/java/org/bukkit/craftbukkit/CraftRaid.java b/src/main/java/org/bukkit/craftbukkit/CraftRaid.java +index b8ce1c1c2447f9cff1717bfcfd6eb911ade0d4b3..51f21af9d75769abdcba713b9aa33392e994d9b0 100644 +--- a/src/main/java/org/bukkit/craftbukkit/CraftRaid.java ++++ b/src/main/java/org/bukkit/craftbukkit/CraftRaid.java +@@ -103,4 +103,34 @@ public final class CraftRaid implements Raid { + public net.minecraft.world.entity.raid.Raid getHandle() { + return this.handle; + } ++ ++ // Paper start - more Raid API ++ @Override ++ public int getId() { ++ return this.handle.getId(); ++ } ++ ++ @Override ++ public org.bukkit.boss.BossBar getBossBar() { ++ return new org.bukkit.craftbukkit.boss.CraftBossBar(this.handle.raidEvent); ++ } ++ ++ @Override ++ public org.bukkit.persistence.PersistentDataContainer getPersistentDataContainer() { ++ return this.handle.persistentDataContainer; ++ } ++ ++ @Override ++ public boolean equals(final Object o) { ++ if (this == o) return true; ++ if (o == null || this.getClass() != o.getClass()) return false; ++ final org.bukkit.craftbukkit.CraftRaid craftRaid = (org.bukkit.craftbukkit.CraftRaid) o; ++ return this.handle.equals(craftRaid.handle); ++ } ++ ++ @Override ++ public int hashCode() { ++ return this.handle.hashCode(); ++ } ++ // Paper end - more Raid API + } +diff --git a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java +index 34e8afae13085f0a9ce0c916d911c88c395418e0..406a3b4a6e213879a9b262d2ffa9ba404cf31cc1 100644 +--- a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java ++++ b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java +@@ -2326,6 +2326,14 @@ public class CraftWorld extends CraftRegionAccessor implements World { + return (raid == null) ? null : new CraftRaid(raid); + } + ++ // Paper start - more Raid API ++ @Override ++ public @Nullable Raid getRaid(final int id) { ++ final net.minecraft.world.entity.raid.@Nullable Raid nmsRaid = this.world.getRaids().raidMap.get(id); ++ return nmsRaid != null ? new CraftRaid(nmsRaid) : null; ++ } ++ // Paper end - more Raid API ++ + @Override + public List getRaids() { + Raids persistentRaid = this.world.getRaids(); diff --git a/patches/server/0929-Add-FluidState-API.patch b/patches/server/0929-Add-FluidState-API.patch deleted file mode 100644 index aac0912819f6..000000000000 --- a/patches/server/0929-Add-FluidState-API.patch +++ /dev/null @@ -1,208 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: vicisacat -Date: Fri, 17 Nov 2023 20:22:43 +0100 -Subject: [PATCH] Add FluidState API - - -diff --git a/src/main/java/io/papermc/paper/block/fluid/PaperFluidData.java b/src/main/java/io/papermc/paper/block/fluid/PaperFluidData.java -new file mode 100644 -index 0000000000000000000000000000000000000000..479bc32241ebadf8bbc1080b601f61391ad37fa4 ---- /dev/null -+++ b/src/main/java/io/papermc/paper/block/fluid/PaperFluidData.java -@@ -0,0 +1,110 @@ -+package io.papermc.paper.block.fluid; -+ -+import com.google.common.base.Preconditions; -+import io.papermc.paper.block.fluid.type.PaperFallingFluidData; -+import io.papermc.paper.block.fluid.type.PaperFlowingFluidData; -+import io.papermc.paper.util.MCUtil; -+import java.util.HashMap; -+import java.util.Map; -+import java.util.function.Function; -+import net.minecraft.world.level.material.FluidState; -+import net.minecraft.world.level.material.LavaFluid; -+import net.minecraft.world.level.material.WaterFluid; -+import org.bukkit.Fluid; -+import org.bukkit.Location; -+import org.bukkit.craftbukkit.CraftFluid; -+import org.bukkit.craftbukkit.CraftWorld; -+import org.bukkit.craftbukkit.util.CraftVector; -+import org.bukkit.util.Vector; -+import org.jetbrains.annotations.NotNull; -+ -+public class PaperFluidData implements FluidData { -+ -+ private final FluidState state; -+ -+ protected PaperFluidData(final FluidState state) { -+ this.state = state; -+ } -+ -+ /** -+ * Provides the internal server representation of this fluid data. -+ * @return the fluid state. -+ */ -+ public FluidState getState() { -+ return this.state; -+ } -+ -+ @Override -+ public final @NotNull Fluid getFluidType() { -+ return CraftFluid.minecraftToBukkit(this.state.getType()); -+ } -+ -+ @Override -+ public @NotNull PaperFluidData clone() { -+ try { -+ return (PaperFluidData) super.clone(); -+ } catch (final CloneNotSupportedException ex) { -+ throw new AssertionError("Clone not supported", ex); -+ } -+ } -+ -+ @Override -+ public @NotNull Vector computeFlowDirection(final Location location) { -+ Preconditions.checkArgument(location.getWorld() != null, "Cannot compute flow direction on world-less location"); -+ return CraftVector.toBukkit(this.state.getFlow( -+ ((CraftWorld) location.getWorld()).getHandle(), -+ MCUtil.toBlockPosition(location) -+ )); -+ } -+ -+ @Override -+ public int getLevel() { -+ return this.state.getAmount(); -+ } -+ -+ @Override -+ public float computeHeight(@NotNull final Location location) { -+ Preconditions.checkArgument(location.getWorld() != null, "Cannot compute height on world-less location"); -+ return this.state.getHeight(((CraftWorld) location.getWorld()).getHandle(), MCUtil.toBlockPos(location)); -+ } -+ -+ @Override -+ public boolean isSource() { -+ return this.state.isSource(); -+ } -+ -+ @Override -+ public int hashCode() { -+ return this.state.hashCode(); -+ } -+ -+ @Override -+ public boolean equals(final Object obj) { -+ return obj instanceof final PaperFluidData paperFluidData && this.state.equals(paperFluidData.state); -+ } -+ -+ @Override -+ public String toString() { -+ return "PaperFluidData{" + this.state + "}"; -+ } -+ -+ /* Registry */ -+ private static final Map, Function> MAP = new HashMap<>(); -+ static { -+ // -+ register(LavaFluid.Source.class, PaperFallingFluidData::new); -+ register(WaterFluid.Source.class, PaperFallingFluidData::new); -+ register(LavaFluid.Flowing.class, PaperFlowingFluidData::new); -+ register(WaterFluid.Flowing.class, PaperFlowingFluidData::new); -+ // -+ } -+ -+ static void register(final Class fluid, final Function creator) { -+ Preconditions.checkState(MAP.put(fluid, creator) == null, "Duplicate mapping %s->%s", fluid, creator); -+ MAP.put(fluid, creator); -+ } -+ -+ public static PaperFluidData createData(final FluidState state) { -+ return MAP.getOrDefault(state.getType().getClass(), PaperFluidData::new).apply(state); -+ } -+} -diff --git a/src/main/java/io/papermc/paper/block/fluid/package-info.java b/src/main/java/io/papermc/paper/block/fluid/package-info.java -new file mode 100644 -index 0000000000000000000000000000000000000000..cfabb814ebd281aab299c6c655266ff357e08806 ---- /dev/null -+++ b/src/main/java/io/papermc/paper/block/fluid/package-info.java -@@ -0,0 +1,5 @@ -+@DefaultQualifier(NonNull.class) -+package io.papermc.paper.block.fluid; -+ -+import org.checkerframework.checker.nullness.qual.NonNull; -+import org.checkerframework.framework.qual.DefaultQualifier; -diff --git a/src/main/java/io/papermc/paper/block/fluid/type/PaperFallingFluidData.java b/src/main/java/io/papermc/paper/block/fluid/type/PaperFallingFluidData.java -new file mode 100644 -index 0000000000000000000000000000000000000000..655dbd83ff4e632f1168b75e9b402b05aa9d8edf ---- /dev/null -+++ b/src/main/java/io/papermc/paper/block/fluid/type/PaperFallingFluidData.java -@@ -0,0 +1,18 @@ -+ -+package io.papermc.paper.block.fluid.type; -+ -+import io.papermc.paper.block.fluid.PaperFluidData; -+import net.minecraft.world.level.material.FlowingFluid; -+import net.minecraft.world.level.material.FluidState; -+ -+public class PaperFallingFluidData extends PaperFluidData implements FallingFluidData { -+ -+ public PaperFallingFluidData(final FluidState state) { -+ super(state); -+ } -+ -+ @Override -+ public boolean isFalling() { -+ return this.getState().getValue(FlowingFluid.FALLING); -+ } -+} -diff --git a/src/main/java/io/papermc/paper/block/fluid/type/PaperFlowingFluidData.java b/src/main/java/io/papermc/paper/block/fluid/type/PaperFlowingFluidData.java -new file mode 100644 -index 0000000000000000000000000000000000000000..c0c2805cb045cdd835b402776a6923fe2ecc2a99 ---- /dev/null -+++ b/src/main/java/io/papermc/paper/block/fluid/type/PaperFlowingFluidData.java -@@ -0,0 +1,11 @@ -+package io.papermc.paper.block.fluid.type; -+ -+import net.minecraft.world.level.material.FluidState; -+ -+public class PaperFlowingFluidData extends PaperFallingFluidData implements FlowingFluidData { -+ -+ public PaperFlowingFluidData(final FluidState state) { -+ super(state); -+ } -+ -+} -diff --git a/src/main/java/org/bukkit/craftbukkit/CraftRegionAccessor.java b/src/main/java/org/bukkit/craftbukkit/CraftRegionAccessor.java -index a14d3e6c43b94c543790571b13808713444a239f..284234fcdd15c4c7a4567c7c887d47bf0b7967f4 100644 ---- a/src/main/java/org/bukkit/craftbukkit/CraftRegionAccessor.java -+++ b/src/main/java/org/bukkit/craftbukkit/CraftRegionAccessor.java -@@ -108,6 +108,13 @@ public abstract class CraftRegionAccessor implements RegionAccessor { - return CraftBlock.at(this.getHandle(), new BlockPos(x, y, z)).getState(); - } - -+ // Paper start - FluidState API -+ @Override -+ public io.papermc.paper.block.fluid.FluidData getFluidData(final int x, final int y, final int z) { -+ return io.papermc.paper.block.fluid.PaperFluidData.createData(getHandle().getFluidState(new BlockPos(x, y, z))); -+ } -+ // Paper end -+ - @Override - public BlockData getBlockData(Location location) { - return this.getBlockData(location.getBlockX(), location.getBlockY(), location.getBlockZ()); -diff --git a/src/main/java/org/bukkit/craftbukkit/generator/CraftLimitedRegion.java b/src/main/java/org/bukkit/craftbukkit/generator/CraftLimitedRegion.java -index eaa9ba70b0b80d86eb376a0641420093a7c9dfdb..25598df0bb0d4347b2c17b6ec0afbfe4ecf808b9 100644 ---- a/src/main/java/org/bukkit/craftbukkit/generator/CraftLimitedRegion.java -+++ b/src/main/java/org/bukkit/craftbukkit/generator/CraftLimitedRegion.java -@@ -303,4 +303,11 @@ public class CraftLimitedRegion extends CraftRegionAccessor implements LimitedRe - return centerChunkZ; - } - // Paper end - Add more LimitedRegion API -+ // Paper start - Fluid API -+ @Override -+ public io.papermc.paper.block.fluid.FluidData getFluidData(int x, int y, int z) { -+ Preconditions.checkArgument(this.isInRegion(x, y, z), "Coordinates %s, %s, %s are not in the region", x, y, z); -+ return super.getFluidData(x, y, z); -+ } -+ // Paper end - } diff --git a/patches/server/0929-Add-onboarding-message-for-initial-server-start.patch b/patches/server/0929-Add-onboarding-message-for-initial-server-start.patch new file mode 100644 index 000000000000..73d18bd703a4 --- /dev/null +++ b/patches/server/0929-Add-onboarding-message-for-initial-server-start.patch @@ -0,0 +1,103 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: leguan +Date: Sun, 10 Mar 2024 20:10:41 +0100 +Subject: [PATCH] Add onboarding message for initial server start + + +diff --git a/src/main/java/io/papermc/paper/configuration/Configurations.java b/src/main/java/io/papermc/paper/configuration/Configurations.java +index d9502ba028a96f9cc846f9ed428bd8066b857ca3..87e5f614ba988547a827486740db217e28585773 100644 +--- a/src/main/java/io/papermc/paper/configuration/Configurations.java ++++ b/src/main/java/io/papermc/paper/configuration/Configurations.java +@@ -129,6 +129,7 @@ public abstract class Configurations { + if (Files.notExists(configFile)) { + node = CommentedConfigurationNode.root(loader.defaultOptions()); + node.node(Configuration.VERSION_FIELD).raw(this.globalConfigVersion()); ++ GlobalConfiguration.isFirstStart = true; + } else { + node = loader.load(); + this.verifyGlobalConfigVersion(node); +diff --git a/src/main/java/io/papermc/paper/configuration/GlobalConfiguration.java b/src/main/java/io/papermc/paper/configuration/GlobalConfiguration.java +index 56798215644d8bca1695856b3a941e8089f49e48..46c37c8db8ecf3cc808fcf59f6bee5fe6ca49b75 100644 +--- a/src/main/java/io/papermc/paper/configuration/GlobalConfiguration.java ++++ b/src/main/java/io/papermc/paper/configuration/GlobalConfiguration.java +@@ -25,6 +25,7 @@ public class GlobalConfiguration extends ConfigurationPart { + private static final Logger LOGGER = LogUtils.getLogger(); + static final int CURRENT_VERSION = 29; // (when you change the version, change the comment, so it conflicts on rebases): + private static GlobalConfiguration instance; ++ public static boolean isFirstStart = false; + public static GlobalConfiguration get() { + return instance; + } +diff --git a/src/main/java/net/minecraft/server/MinecraftServer.java b/src/main/java/net/minecraft/server/MinecraftServer.java +index be188079f12b3f7b394ae91db62cc17b1d0f4e79..2de0ae09de41f3ed254318a78d65045fc76e5016 100644 +--- a/src/main/java/net/minecraft/server/MinecraftServer.java ++++ b/src/main/java/net/minecraft/server/MinecraftServer.java +@@ -1149,6 +1149,16 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop " + onboardingLink + ""); ++ link.setFont(MinecraftServerGui.MONOSPACED); ++ link.setCursor(new java.awt.Cursor(java.awt.Cursor.HAND_CURSOR)); ++ link.addMouseListener(new java.awt.event.MouseAdapter() { ++ @Override ++ public void mouseClicked(final java.awt.event.MouseEvent e) { ++ try { ++ java.awt.Desktop.getDesktop().browse(java.net.URI.create(onboardingLink)); ++ } catch (java.io.IOException exception) { ++ LOGGER.error("Unable to find a default browser. Please manually visit the website: " + onboardingLink, exception); ++ } catch (UnsupportedOperationException exception) { ++ LOGGER.error("This platform does not support the BROWSE action. Please manually visit the website: " + onboardingLink, exception); ++ } catch (SecurityException exception) { ++ LOGGER.error("This action has been denied by the security manager. Please manually visit the website: " + onboardingLink, exception); ++ } ++ } ++ }); ++ ++ jPanel.add(jLabel); ++ jPanel.add(link); ++ ++ return jPanel; ++ } ++ // Paper end - Add onboarding message for initial server start ++ + private JComponent buildPlayerPanel() { + JList jlist = new PlayerListComponent(this.server); + JScrollPane jscrollpane = new JScrollPane(jlist, 22, 30); diff --git a/patches/server/0930-Configurable-max-block-fluid-ticks.patch b/patches/server/0930-Configurable-max-block-fluid-ticks.patch new file mode 100644 index 000000000000..c93e0006cc21 --- /dev/null +++ b/patches/server/0930-Configurable-max-block-fluid-ticks.patch @@ -0,0 +1,22 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Gero +Date: Mon, 19 Feb 2024 17:39:59 +0100 +Subject: [PATCH] Configurable max block/fluid ticks + + +diff --git a/src/main/java/net/minecraft/server/level/ServerLevel.java b/src/main/java/net/minecraft/server/level/ServerLevel.java +index ea1281c9a3b83b17de64d583e029db9bacabcd88..711d5136124c0fa21015f0154057ab5742071e59 100644 +--- a/src/main/java/net/minecraft/server/level/ServerLevel.java ++++ b/src/main/java/net/minecraft/server/level/ServerLevel.java +@@ -489,9 +489,9 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe + if (!this.isDebug() && flag) { + j = this.getGameTime(); + gameprofilerfiller.push("blockTicks"); +- this.blockTicks.tick(j, 65536, this::tickBlock); ++ this.blockTicks.tick(j, paperConfig().environment.maxBlockTicks, this::tickBlock); // Paper - configurable max block ticks + gameprofilerfiller.popPush("fluidTicks"); +- this.fluidTicks.tick(j, 65536, this::tickFluid); ++ this.fluidTicks.tick(j, paperConfig().environment.maxFluidTicks, this::tickFluid); // Paper - configurable max fluid ticks + gameprofilerfiller.pop(); + } + diff --git a/patches/server/0931-Fix-bees-aging-inside-hives.patch b/patches/server/0931-Fix-bees-aging-inside-hives.patch new file mode 100644 index 000000000000..8d4dcd0b64c7 --- /dev/null +++ b/patches/server/0931-Fix-bees-aging-inside-hives.patch @@ -0,0 +1,41 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Jake Potrebic +Date: Sat, 21 Aug 2021 21:54:16 -0700 +Subject: [PATCH] Fix bees aging inside hives + +Fixes bees incorrectly being aged up due to upstream's +resetting the ticks inside hive on a failed release + +diff --git a/src/main/java/net/minecraft/world/level/block/entity/BeehiveBlockEntity.java b/src/main/java/net/minecraft/world/level/block/entity/BeehiveBlockEntity.java +index f8828dc8334af19d7b2118e8cf34d94be5e09ee8..83ad45aed0894e90825d22e078632352c3a06816 100644 +--- a/src/main/java/net/minecraft/world/level/block/entity/BeehiveBlockEntity.java ++++ b/src/main/java/net/minecraft/world/level/block/entity/BeehiveBlockEntity.java +@@ -315,7 +315,7 @@ public class BeehiveBlockEntity extends BlockEntity { + iterator.remove(); + // CraftBukkit start + } else { +- tileentitybeehive_hivebee.ticksInHive = tileentitybeehive_hivebee.occupant.minTicksInHive / 2; // Not strictly Vanilla behaviour in cases where bees cannot spawn but still reasonable ++ tileentitybeehive_hivebee.exitTickCounter = tileentitybeehive_hivebee.occupant.minTicksInHive / 2; // Not strictly Vanilla behaviour in cases where bees cannot spawn but still reasonable // Paper - Fix bees aging inside hives; use exitTickCounter to keep actual bee life + // CraftBukkit end + } + } +@@ -475,15 +475,18 @@ public class BeehiveBlockEntity extends BlockEntity { + private static class BeeData { + + private final BeehiveBlockEntity.Occupant occupant; ++ private int exitTickCounter; // Paper - Fix bees aging inside hives; separate counter for checking if bee should exit to reduce exit attempts + private int ticksInHive; + + BeeData(BeehiveBlockEntity.Occupant data) { + this.occupant = data; + this.ticksInHive = data.ticksInHive(); ++ this.exitTickCounter = this.ticksInHive; // Paper - Fix bees aging inside hives + } + + public boolean tick() { +- return this.ticksInHive++ > this.occupant.minTicksInHive; ++ this.ticksInHive++; // Paper - Fix bees aging inside hives ++ return this.exitTickCounter++ > this.occupant.minTicksInHive; // Paper - Fix bees aging inside hives + } + + public BeehiveBlockEntity.Occupant toOccupant() { diff --git a/patches/server/0931-improve-BanList-types.patch b/patches/server/0931-improve-BanList-types.patch deleted file mode 100644 index a3e327bc830f..000000000000 --- a/patches/server/0931-improve-BanList-types.patch +++ /dev/null @@ -1,32 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Yannick Lamprecht -Date: Sat, 10 Feb 2024 20:50:01 +0100 -Subject: [PATCH] improve BanList types - - -diff --git a/src/main/java/org/bukkit/craftbukkit/CraftServer.java b/src/main/java/org/bukkit/craftbukkit/CraftServer.java -index f6b87a5249e1a21b3221f253f891d8978249c50a..0fc6e659915a4547c2db9205fed205a1d28f21d4 100644 ---- a/src/main/java/org/bukkit/craftbukkit/CraftServer.java -+++ b/src/main/java/org/bukkit/craftbukkit/CraftServer.java -@@ -2256,6 +2256,21 @@ public final class CraftServer implements Server { - }; - } - -+ // Paper start - add BanListType (which has a generic) -+ @SuppressWarnings("unchecked") -+ @Override -+ public , E> B getBanList(final io.papermc.paper.ban.BanListType type) { -+ Preconditions.checkArgument(type != null, "BanList.BanType cannot be null"); -+ if (type == io.papermc.paper.ban.BanListType.IP) { -+ return (B) new CraftIpBanList(this.playerList.getIpBans()); -+ } else if (type == io.papermc.paper.ban.BanListType.PROFILE) { -+ return (B) new CraftProfileBanList(this.playerList.getBans()); -+ } else { -+ throw new IllegalArgumentException("Unknown BanListType: " + type); -+ } -+ } -+ // Paper end - add BanListType (which has a generic) -+ - @Override - public void setWhitelist(boolean value) { - this.playerList.setUsingWhiteList(value); diff --git a/patches/server/0940-Disable-memory-reserve-allocating.patch b/patches/server/0932-Disable-memory-reserve-allocating.patch similarity index 100% rename from patches/server/0940-Disable-memory-reserve-allocating.patch rename to patches/server/0932-Disable-memory-reserve-allocating.patch diff --git a/patches/server/0933-Add-BlockBreakProgressUpdateEvent.patch b/patches/server/0933-Add-BlockBreakProgressUpdateEvent.patch deleted file mode 100644 index 3481a288eed1..000000000000 --- a/patches/server/0933-Add-BlockBreakProgressUpdateEvent.patch +++ /dev/null @@ -1,28 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Badbird5907 <50347938+Badbird5907@users.noreply.github.com> -Date: Mon, 4 Mar 2024 22:18:28 -0500 -Subject: [PATCH] Add BlockBreakProgressUpdateEvent - - -diff --git a/src/main/java/net/minecraft/server/level/ServerLevel.java b/src/main/java/net/minecraft/server/level/ServerLevel.java -index 34ece37aee68e4d8eeaa919e8bd489a672756049..dcd024afef50c90974723632c879258fb1dda073 100644 ---- a/src/main/java/net/minecraft/server/level/ServerLevel.java -+++ b/src/main/java/net/minecraft/server/level/ServerLevel.java -@@ -1320,6 +1320,17 @@ public class ServerLevel extends Level implements WorldGenLevel { - if (entity instanceof Player) entityhuman = (Player) entity; - // CraftBukkit end - -+ // Paper start - Add BlockBreakProgressUpdateEvent -+ // If a plugin is using this method to send destroy packets for a client-side only entity id, no block progress occurred on the server. -+ // Hence, do not call the event. -+ if (entity != null) { -+ float progressFloat = Mth.clamp(progress, 0, 10) / 10.0f; -+ org.bukkit.craftbukkit.block.CraftBlock bukkitBlock = org.bukkit.craftbukkit.block.CraftBlock.at(this, pos); -+ new io.papermc.paper.event.block.BlockBreakProgressUpdateEvent(bukkitBlock, progressFloat, entity.getBukkitEntity()) -+ .callEvent(); -+ } -+ // Paper end - Add BlockBreakProgressUpdateEvent -+ - while (iterator.hasNext()) { - ServerPlayer entityplayer = (ServerPlayer) iterator.next(); - diff --git a/patches/server/0933-Fire-EntityDamageByEntityEvent-for-unowned-wither-sk.patch b/patches/server/0933-Fire-EntityDamageByEntityEvent-for-unowned-wither-sk.patch new file mode 100644 index 000000000000..3cf04503d8e0 --- /dev/null +++ b/patches/server/0933-Fire-EntityDamageByEntityEvent-for-unowned-wither-sk.patch @@ -0,0 +1,19 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: RodneyMKay <36546810+RodneyMKay@users.noreply.github.com> +Date: Sun, 11 Feb 2024 20:05:11 +0100 +Subject: [PATCH] Fire EntityDamageByEntityEvent for unowned wither skulls + + +diff --git a/src/main/java/net/minecraft/world/entity/projectile/WitherSkull.java b/src/main/java/net/minecraft/world/entity/projectile/WitherSkull.java +index 8c17bed56c4659a8e226e88e113884160c3fa959..8831af1000d17d5a60302e3b0c522b2d2f082484 100644 +--- a/src/main/java/net/minecraft/world/entity/projectile/WitherSkull.java ++++ b/src/main/java/net/minecraft/world/entity/projectile/WitherSkull.java +@@ -77,7 +77,7 @@ public class WitherSkull extends AbstractHurtingProjectile { + } + } + } else { +- flag = entity.hurtServer(worldserver, this.damageSources().magic(), 5.0F); ++ flag = entity.hurtServer(worldserver, this.damageSources().magic().customCausingEntity(this), 5.0F); // Paper - Fire EntityDamageByEntityEvent for unowned wither skulls + } + + if (flag && entity instanceof LivingEntity entityliving) { diff --git a/patches/server/0934-Deprecate-ItemStack-setType.patch b/patches/server/0934-Deprecate-ItemStack-setType.patch deleted file mode 100644 index e0a6acc7477f..000000000000 --- a/patches/server/0934-Deprecate-ItemStack-setType.patch +++ /dev/null @@ -1,35 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Owen1212055 <23108066+Owen1212055@users.noreply.github.com> -Date: Tue, 26 Mar 2024 21:42:23 -0400 -Subject: [PATCH] Deprecate ItemStack#setType - - -diff --git a/src/main/java/org/bukkit/craftbukkit/inventory/CraftItemStack.java b/src/main/java/org/bukkit/craftbukkit/inventory/CraftItemStack.java -index a1a32a77bda0560a7b7f30a5d1c1837ee96997d3..9be7859ccb5283b2040ba68d72d6dbdafb4d6835 100644 ---- a/src/main/java/org/bukkit/craftbukkit/inventory/CraftItemStack.java -+++ b/src/main/java/org/bukkit/craftbukkit/inventory/CraftItemStack.java -@@ -417,4 +417,24 @@ public final class CraftItemStack extends ItemStack { - static boolean hasItemMeta(net.minecraft.world.item.ItemStack item) { - return !(item == null || item.getComponentsPatch().isEmpty()); - } -+ // Paper start - with type -+ @Override -+ public ItemStack withType(final Material type) { -+ if (type == Material.AIR) { -+ return CraftItemStack.asCraftMirror(null); -+ } -+ -+ final net.minecraft.world.item.ItemStack copy = new net.minecraft.world.item.ItemStack( -+ CraftItemType.bukkitToMinecraft(type), this.getAmount() -+ ); -+ -+ if (this.handle != null) { -+ copy.applyComponents(this.handle.getComponentsPatch()); -+ } -+ -+ final CraftItemStack mirrored = CraftItemStack.asCraftMirror(copy); -+ mirrored.setItemMeta(mirrored.getItemMeta()); -+ return mirrored; -+ } -+ // Paper end - } diff --git a/patches/server/0934-Fix-DamageSource-API.patch b/patches/server/0934-Fix-DamageSource-API.patch new file mode 100644 index 000000000000..bdeb62149f99 --- /dev/null +++ b/patches/server/0934-Fix-DamageSource-API.patch @@ -0,0 +1,234 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Jake Potrebic +Date: Sat, 9 Mar 2024 14:13:04 -0800 +Subject: [PATCH] Fix DamageSource API + +Uses the correct entity in the EntityDamageByEntity event +Returns the correct entity for API's DamageSource#getCausingEntity + +diff --git a/src/main/java/net/minecraft/world/damagesource/DamageSource.java b/src/main/java/net/minecraft/world/damagesource/DamageSource.java +index aee26dd78953ff43306aaa64161f5b9edcdd4b83..bb1a60180e58c1333e7bb33e8acf1b0225eda8a8 100644 +--- a/src/main/java/net/minecraft/world/damagesource/DamageSource.java ++++ b/src/main/java/net/minecraft/world/damagesource/DamageSource.java +@@ -29,8 +29,8 @@ public class DamageSource { + private boolean sweep = false; + private boolean melting = false; + private boolean poison = false; +- private Entity customEntityDamager = null; // This field is a helper for when direct entity damage is not set by vanilla +- private Entity customCausingEntityDamager = null; // This field is a helper for when causing entity damage is not set by vanilla ++ @Nullable ++ private Entity customEventDamager = null; // This field is a helper for when causing entity damage is not set by vanilla // Paper - fix DamageSource API + + public DamageSource sweep() { + this.sweep = true; +@@ -59,33 +59,19 @@ public class DamageSource { + return this.poison; + } + +- public Entity getDamager() { +- return (this.customEntityDamager != null) ? this.customEntityDamager : this.directEntity; +- } +- +- public Entity getCausingDamager() { +- return (this.customCausingEntityDamager != null) ? this.customCausingEntityDamager : this.causingEntity; +- } +- +- public DamageSource customEntityDamager(Entity entity) { +- // This method is not intended for change the causing entity if is already set +- // also is only necessary if the entity passed is not the direct entity or different from the current causingEntity +- if (this.customEntityDamager != null || this.directEntity == entity || this.causingEntity == entity) { +- return this; +- } +- DamageSource damageSource = this.cloneInstance(); +- damageSource.customEntityDamager = entity; +- return damageSource; ++ // Paper start - fix DamageSource API ++ @Nullable ++ public Entity getCustomEventDamager() { ++ return (this.customEventDamager != null) ? this.customEventDamager : this.directEntity; + } + +- public DamageSource customCausingEntityDamager(Entity entity) { +- // This method is not intended for change the causing entity if is already set +- // also is only necessary if the entity passed is not the direct entity or different from the current causingEntity +- if (this.customCausingEntityDamager != null || this.directEntity == entity || this.causingEntity == entity) { +- return this; ++ public DamageSource customEventDamager(Entity entity) { ++ if (this.directEntity != null) { ++ throw new IllegalStateException("Cannot set custom event damager when direct entity is already set (report a bug to Paper)"); + } + DamageSource damageSource = this.cloneInstance(); +- damageSource.customCausingEntityDamager = entity; ++ damageSource.customEventDamager = entity; ++ // Paper end - fix DamageSource API + return damageSource; + } + +diff --git a/src/main/java/net/minecraft/world/damagesource/DamageSources.java b/src/main/java/net/minecraft/world/damagesource/DamageSources.java +index fddbdb7322a2063996a28c5c3d93c265188b1256..be87cb3cfa15a7d889118cdc4b87232e30749023 100644 +--- a/src/main/java/net/minecraft/world/damagesource/DamageSources.java ++++ b/src/main/java/net/minecraft/world/damagesource/DamageSources.java +@@ -270,13 +270,7 @@ public class DamageSources { + } + + public DamageSource explosion(@Nullable Entity source, @Nullable Entity attacker) { +- // CraftBukkit start +- return this.explosion(source, attacker, attacker != null && source != null ? DamageTypes.PLAYER_EXPLOSION : DamageTypes.EXPLOSION); +- } +- +- public DamageSource explosion(@Nullable Entity entity, @Nullable Entity entity1, ResourceKey resourceKey) { +- return this.source(resourceKey, entity, entity1); +- // CraftBukkit end ++ return this.source(attacker != null && source != null ? DamageTypes.PLAYER_EXPLOSION : DamageTypes.EXPLOSION, source, attacker); // Paper - revert to vanilla + } + + public DamageSource sonicBoom(Entity attacker) { +diff --git a/src/main/java/net/minecraft/world/entity/Entity.java b/src/main/java/net/minecraft/world/entity/Entity.java +index b78395ab8f393a0f6551951731ddce7a5228b44c..e2322f361271712aca31c95b8b79cdf1c04945f2 100644 +--- a/src/main/java/net/minecraft/world/entity/Entity.java ++++ b/src/main/java/net/minecraft/world/entity/Entity.java +@@ -3388,7 +3388,7 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess + return; + } + +- if (!this.hurtServer(world, this.damageSources().lightningBolt().customEntityDamager(lightning), 5.0F)) { ++ if (!this.hurtServer(world, this.damageSources().lightningBolt().customEventDamager(lightning), 5.0F)) { // Paper - fix DamageSource API + return; + } + // CraftBukkit end +diff --git a/src/main/java/net/minecraft/world/entity/animal/Turtle.java b/src/main/java/net/minecraft/world/entity/animal/Turtle.java +index 9a9ecc3e2c176c6d9700c4c585250b9780b7629b..d6605c15111dbdb6ee61a24822bc0a9aed7198d6 100644 +--- a/src/main/java/net/minecraft/world/entity/animal/Turtle.java ++++ b/src/main/java/net/minecraft/world/entity/animal/Turtle.java +@@ -341,7 +341,7 @@ public class Turtle extends Animal { + + @Override + public void thunderHit(ServerLevel world, LightningBolt lightning) { +- this.hurtServer(world, this.damageSources().lightningBolt().customEntityDamager(lightning), Float.MAX_VALUE); // CraftBukkit ++ this.hurtServer(world, this.damageSources().lightningBolt().customEventDamager(lightning), Float.MAX_VALUE); // CraftBukkit // Paper - fix DamageSource API + } + + @Override +diff --git a/src/main/java/net/minecraft/world/entity/decoration/BlockAttachedEntity.java b/src/main/java/net/minecraft/world/entity/decoration/BlockAttachedEntity.java +index 608acb4f3cb8f9a0dc10b819ed4bf577c229dd2c..7ed127a0a0c0d6e7b726216c48ef37af4db37628 100644 +--- a/src/main/java/net/minecraft/world/entity/decoration/BlockAttachedEntity.java ++++ b/src/main/java/net/minecraft/world/entity/decoration/BlockAttachedEntity.java +@@ -108,7 +108,7 @@ public abstract class BlockAttachedEntity extends Entity { + } else { + if (!this.isRemoved()) { + // CraftBukkit start - fire break events +- Entity damager = (source.isDirect()) ? source.getDirectEntity() : source.getEntity(); ++ Entity damager = (!source.isDirect() && source.getEntity() != null) ? source.getEntity() : source.getDirectEntity(); // Paper - fix DamageSource API + HangingBreakEvent event; + if (damager != null) { + event = new HangingBreakByEntityEvent((Hanging) this.getBukkitEntity(), damager.getBukkitEntity(), source.is(DamageTypeTags.IS_EXPLOSION) ? HangingBreakEvent.RemoveCause.EXPLOSION : HangingBreakEvent.RemoveCause.ENTITY); +diff --git a/src/main/java/net/minecraft/world/entity/monster/Creeper.java b/src/main/java/net/minecraft/world/entity/monster/Creeper.java +index 7a18dc59aed5294cd442994aa2d34ea00b877f46..1906dfc22af208d6e801ad4a8f2f9e9702432691 100644 +--- a/src/main/java/net/minecraft/world/entity/monster/Creeper.java ++++ b/src/main/java/net/minecraft/world/entity/monster/Creeper.java +@@ -271,7 +271,7 @@ public class Creeper extends Monster { + if (!event.isCancelled()) { + // CraftBukkit end + this.dead = true; +- worldserver.explode(this, net.minecraft.world.level.Explosion.getDefaultDamageSource(this.level(), this).customCausingEntityDamager(this.entityIgniter), null, this.getX(), this.getY(), this.getZ(), event.getRadius(), event.getFire(), Level.ExplosionInteraction.MOB); // CraftBukkit ++ worldserver.explode(this, this.getX(), this.getY(), this.getZ(), event.getRadius(), event.getFire(), Level.ExplosionInteraction.MOB); // CraftBukkit // Paper - fix DamageSource API (revert to vanilla, no, just no, don't change this) + this.spawnLingeringCloud(); + this.triggerOnDeathMobEffects(worldserver, Entity.RemovalReason.KILLED); + this.discard(EntityRemoveEvent.Cause.EXPLODE); // CraftBukkit - add Bukkit remove cause +diff --git a/src/main/java/net/minecraft/world/entity/projectile/EvokerFangs.java b/src/main/java/net/minecraft/world/entity/projectile/EvokerFangs.java +index c08074d43d105b30ed5093614062a3bcafadfb74..ccc72a2cf02a633655b95f961be310879d8f904f 100644 +--- a/src/main/java/net/minecraft/world/entity/projectile/EvokerFangs.java ++++ b/src/main/java/net/minecraft/world/entity/projectile/EvokerFangs.java +@@ -135,7 +135,7 @@ public class EvokerFangs extends Entity implements TraceableEntity { + + if (target.isAlive() && !target.isInvulnerable() && target != entityliving1) { + if (entityliving1 == null) { +- target.hurt(this.damageSources().magic().customEntityDamager(this), 6.0F); // CraftBukkit ++ target.hurt(this.damageSources().magic().customEventDamager(this), 6.0F); // CraftBukkit // Paper - fix DamageSource API + } else { + if (entityliving1.isAlliedTo((Entity) target)) { + return; +diff --git a/src/main/java/net/minecraft/world/entity/projectile/ThrownEnderpearl.java b/src/main/java/net/minecraft/world/entity/projectile/ThrownEnderpearl.java +index 080d886008e1f596733ea5331b32db35adef2e9b..5f790dd24f2bdae827c6dc597064b9b265089751 100644 +--- a/src/main/java/net/minecraft/world/entity/projectile/ThrownEnderpearl.java ++++ b/src/main/java/net/minecraft/world/entity/projectile/ThrownEnderpearl.java +@@ -170,7 +170,7 @@ public class ThrownEnderpearl extends ThrowableItemProjectile { + if (entityplayer1 != null) { + entityplayer1.resetFallDistance(); + entityplayer1.resetCurrentImpulseContext(); +- entityplayer1.hurtServer(entityplayer.serverLevel(), this.damageSources().enderPearl().customEntityDamager(this), 5.0F); // CraftBukkit ++ entityplayer1.hurtServer(entityplayer.serverLevel(), this.damageSources().enderPearl().customEventDamager(this), 5.0F); // CraftBukkit // Paper - fix DamageSource API + } + + this.playSound(worldserver, vec3d); +diff --git a/src/main/java/net/minecraft/world/entity/projectile/WitherSkull.java b/src/main/java/net/minecraft/world/entity/projectile/WitherSkull.java +index 8831af1000d17d5a60302e3b0c522b2d2f082484..4c47b30867e30d84908abf93dbefc252bc8c3453 100644 +--- a/src/main/java/net/minecraft/world/entity/projectile/WitherSkull.java ++++ b/src/main/java/net/minecraft/world/entity/projectile/WitherSkull.java +@@ -77,7 +77,7 @@ public class WitherSkull extends AbstractHurtingProjectile { + } + } + } else { +- flag = entity.hurtServer(worldserver, this.damageSources().magic().customCausingEntity(this), 5.0F); // Paper - Fire EntityDamageByEntityEvent for unowned wither skulls ++ flag = entity.hurtServer(worldserver, this.damageSources().magic().customEventDamager(this), 5.0F); // Paper - Fire EntityDamageByEntityEvent for unowned wither skulls // Paper - fix DamageSource API + } + + if (flag && entity instanceof LivingEntity entityliving) { +diff --git a/src/main/java/org/bukkit/craftbukkit/damage/CraftDamageSource.java b/src/main/java/org/bukkit/craftbukkit/damage/CraftDamageSource.java +index 8532324060eed772a10e2a3429890438cf32f9ba..7df86e7124a9ed359f05324b8fc4c8862f7e4b79 100644 +--- a/src/main/java/org/bukkit/craftbukkit/damage/CraftDamageSource.java ++++ b/src/main/java/org/bukkit/craftbukkit/damage/CraftDamageSource.java +@@ -41,13 +41,13 @@ public class CraftDamageSource implements DamageSource { + + @Override + public org.bukkit.entity.Entity getCausingEntity() { +- net.minecraft.world.entity.Entity entity = this.getHandle().getCausingDamager(); ++ net.minecraft.world.entity.Entity entity = this.getHandle().getEntity(); // Paper - fix DamageSource API - revert to vanilla + return (entity != null) ? entity.getBukkitEntity() : null; + } + + @Override + public org.bukkit.entity.Entity getDirectEntity() { +- net.minecraft.world.entity.Entity entity = this.getHandle().getDamager(); ++ net.minecraft.world.entity.Entity entity = this.getHandle().getDirectEntity(); // Paper - fix DamageSource API + return (entity != null) ? entity.getBukkitEntity() : null; + } + +@@ -65,7 +65,7 @@ public class CraftDamageSource implements DamageSource { + + @Override + public boolean isIndirect() { +- return this.getHandle().getCausingDamager() != this.getHandle().getDamager(); ++ return !this.getHandle().isDirect(); // Paper - fix DamageSource API + } + + @Override +diff --git a/src/main/java/org/bukkit/craftbukkit/damage/CraftDamageSourceBuilder.java b/src/main/java/org/bukkit/craftbukkit/damage/CraftDamageSourceBuilder.java +index 4c6e15535fa40aad8cf1920f392589404f9ba79c..35eb95ef6fb6a0f7ea63351e90741c489fdd15f9 100644 +--- a/src/main/java/org/bukkit/craftbukkit/damage/CraftDamageSourceBuilder.java ++++ b/src/main/java/org/bukkit/craftbukkit/damage/CraftDamageSourceBuilder.java +@@ -41,6 +41,11 @@ public class CraftDamageSourceBuilder implements DamageSource.Builder { + + @Override + public DamageSource build() { ++ // Paper start - fix DamageCause API ++ if (this.causingEntity != null && this.directEntity == null) { ++ throw new IllegalArgumentException("Direct entity must be set if causing entity is set"); ++ } ++ // Paper end - fix DamageCause API + return CraftDamageSource.buildFromBukkit(this.damageType, this.causingEntity, this.directEntity, this.damageLocation); + } + } +diff --git a/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java b/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java +index e7749a3ef6289d73379649f2f76f4e4fdfac7a8b..96b901d07718d8926a2175925e867b4417c3947c 100644 +--- a/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java ++++ b/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java +@@ -1092,7 +1092,7 @@ public class CraftEventFactory { + + private static EntityDamageEvent handleEntityDamageEvent(Entity entity, DamageSource source, Map modifiers, Map> modifierFunctions, boolean cancelled) { + CraftDamageSource bukkitDamageSource = new CraftDamageSource(source); +- Entity damager = (source.getDamager() != null) ? source.getDamager() : source.getEntity(); ++ final Entity damager = source.getCustomEventDamager(); // Paper - fix DamageSource API + if (source.is(DamageTypeTags.IS_EXPLOSION)) { + if (damager == null) { + return CraftEventFactory.callEntityDamageEvent(source.getDirectBlock(), source.getDirectBlockState(), entity, DamageCause.BLOCK_EXPLOSION, bukkitDamageSource, modifiers, modifierFunctions, cancelled); diff --git a/patches/server/0935-Add-CartographyItemEvent.patch b/patches/server/0935-Add-CartographyItemEvent.patch deleted file mode 100644 index 9e487673815f..000000000000 --- a/patches/server/0935-Add-CartographyItemEvent.patch +++ /dev/null @@ -1,43 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Janet Blackquill -Date: Sun, 7 Apr 2024 16:52:42 -0400 -Subject: [PATCH] Add CartographyItemEvent - - -diff --git a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java -index d377804026f3f5cf80a623228d24e09c56ef0dae..fb935b505ec048522f9ab5d689b2c64c64f401a7 100644 ---- a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java -+++ b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java -@@ -3108,6 +3108,19 @@ public class ServerGamePacketListenerImpl extends ServerCommonPacketListenerImpl - } - } - -+ // Paper start - cartography item event -+ if (packet.getSlotNum() == net.minecraft.world.inventory.CartographyTableMenu.RESULT_SLOT && top instanceof org.bukkit.inventory.CartographyInventory cartographyInventory) { -+ org.bukkit.inventory.ItemStack result = cartographyInventory.getResult(); -+ if (result != null && !result.isEmpty()) { -+ if (click == ClickType.NUMBER_KEY) { -+ event = new io.papermc.paper.event.player.CartographyItemEvent(inventory, type, packet.getSlotNum(), click, action, packet.getButtonNum()); -+ } else { -+ event = new io.papermc.paper.event.player.CartographyItemEvent(inventory, type, packet.getSlotNum(), click, action); -+ } -+ } -+ } -+ // Paper end - cartography item event -+ - event.setCancelled(cancelled); - AbstractContainerMenu oldContainer = this.player.containerMenu; // SPIGOT-1224 - this.cserver.getPluginManager().callEvent(event); -diff --git a/src/main/java/net/minecraft/world/inventory/CartographyTableMenu.java b/src/main/java/net/minecraft/world/inventory/CartographyTableMenu.java -index ab98637bf967ac19f0bc06e8cb7f18a8b13ec809..a90e2c56c54797b2fec40eb3865835c5f640a544 100644 ---- a/src/main/java/net/minecraft/world/inventory/CartographyTableMenu.java -+++ b/src/main/java/net/minecraft/world/inventory/CartographyTableMenu.java -@@ -71,7 +71,7 @@ public class CartographyTableMenu extends AbstractContainerMenu { - this.resultContainer = new ResultContainer(this.createBlockHolder(context)) { // Paper - Add missing InventoryHolders - @Override - public void setChanged() { -- CartographyTableMenu.this.slotsChanged(this); -+ // CartographyTableMenu.this.slotsChanged(this); // Paper - Add CatographyItemEvent - do not recompute results if the result slot changes - allows to set the result slot via api - super.setChanged(); - } - diff --git a/patches/server/0935-Fix-creation-of-invalid-block-entity-during-world-ge.patch b/patches/server/0935-Fix-creation-of-invalid-block-entity-during-world-ge.patch new file mode 100644 index 000000000000..d12e5cd665f3 --- /dev/null +++ b/patches/server/0935-Fix-creation-of-invalid-block-entity-during-world-ge.patch @@ -0,0 +1,59 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Pierpaolo Coletta +Date: Sat, 30 Mar 2024 21:06:10 +0100 +Subject: [PATCH] Fix creation of invalid block entity during world generation + + +diff --git a/src/main/java/net/minecraft/server/level/WorldGenRegion.java b/src/main/java/net/minecraft/server/level/WorldGenRegion.java +index 2e72e92762877b28dd908711671e1dfb933de9b0..b7d29389a357f142237cecd75f8ca91cf1eb6b5b 100644 +--- a/src/main/java/net/minecraft/server/level/WorldGenRegion.java ++++ b/src/main/java/net/minecraft/server/level/WorldGenRegion.java +@@ -324,7 +324,7 @@ public class WorldGenRegion implements WorldGenLevel { + return false; + } else { + ChunkAccess ichunkaccess = this.getChunk(pos); +- BlockState iblockdata1 = ichunkaccess.setBlockState(pos, state, false); ++ BlockState iblockdata1 = ichunkaccess.setBlockState(pos, state, false); final BlockState previousBlockState = iblockdata1; // Paper - Clear block entity before setting up a DUMMY block entity - obfhelper + + if (iblockdata1 != null) { + this.level.onBlockStateChange(pos, iblockdata1, state); +@@ -340,6 +340,17 @@ public class WorldGenRegion implements WorldGenLevel { + ichunkaccess.removeBlockEntity(pos); + } + } else { ++ // Paper start - Clear block entity before setting up a DUMMY block entity ++ // The concept of removing a block entity when the block itself changes is generally lifted ++ // from LevelChunk#setBlockState. ++ // It is however to note that this may only run if the block actually changes. ++ // Otherwise a chest block entity generated by a structure template that is later "updated" to ++ // be waterlogged would remove its existing block entity (see PaperMC/Paper#10750) ++ // This logic is *also* found in LevelChunk#setBlockState. ++ if (previousBlockState != null && !java.util.Objects.equals(previousBlockState.getBlock(), state.getBlock())) { ++ ichunkaccess.removeBlockEntity(pos); ++ } ++ // Paper end - Clear block entity before setting up a DUMMY block entity + CompoundTag nbttagcompound = new CompoundTag(); + + nbttagcompound.putInt("x", pos.getX()); +diff --git a/src/main/java/net/minecraft/world/level/chunk/LevelChunk.java b/src/main/java/net/minecraft/world/level/chunk/LevelChunk.java +index d4bd4cbc5c4773659662a6d7a09b21a99463c1fb..7a794bb0587ce55b067c67dd17ab5be6a4773030 100644 +--- a/src/main/java/net/minecraft/world/level/chunk/LevelChunk.java ++++ b/src/main/java/net/minecraft/world/level/chunk/LevelChunk.java +@@ -991,9 +991,14 @@ public class LevelChunk extends ChunkAccess { + if (this.blockEntity.getType().isValid(iblockdata)) { + this.ticker.tick(LevelChunk.this.level, this.blockEntity.getBlockPos(), iblockdata, this.blockEntity); + this.loggedInvalidBlockState = false; +- } else if (!this.loggedInvalidBlockState) { +- this.loggedInvalidBlockState = true; +- LevelChunk.LOGGER.warn("Block entity {} @ {} state {} invalid for ticking:", new Object[]{LogUtils.defer(this::getType), LogUtils.defer(this::getPos), iblockdata}); ++ // Paper start - Remove the Block Entity if it's invalid ++ } else { ++ LevelChunk.this.removeBlockEntity(this.getPos()); ++ if (!this.loggedInvalidBlockState) { ++ this.loggedInvalidBlockState = true; ++ LevelChunk.LOGGER.warn("Block entity {} @ {} state {} invalid for ticking:", new Object[]{LogUtils.defer(this::getType), LogUtils.defer(this::getPos), iblockdata}); ++ } ++ // Paper end - Remove the Block Entity if it's invalid + } + + gameprofilerfiller.pop(); diff --git a/patches/server/0936-Fix-possible-StackOverflowError-and-NPE-for-some-dis.patch b/patches/server/0936-Fix-possible-StackOverflowError-and-NPE-for-some-dis.patch new file mode 100644 index 000000000000..25ace4aa4113 --- /dev/null +++ b/patches/server/0936-Fix-possible-StackOverflowError-and-NPE-for-some-dis.patch @@ -0,0 +1,306 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Jake Potrebic +Date: Sat, 29 Oct 2022 17:02:42 -0700 +Subject: [PATCH] Fix possible StackOverflowError and NPE for some dispenses + +For saddles, carpets, horse armor, and chests for horse-likes +a BlockDispenseEvent handler that always mutated the item without +changing the type would result in a SO error because when it went +to find the replacement dispense behavior (since the item "changed") +it didn't properly handle if the replacement was the same instance +of dispense behavior. + +Additionally equippable mob heads, wither skulls, and carved pumpkins +are subject to the same possible error. + +Furthermore since 1.21.2, the DISPENSER_REGISTRY map doesn't have a default +return value anymore and some dispense behaviors like equippable and +regular items will not have a defined behavior in that map and might throw +a NPE in that case. + +diff --git a/src/main/java/net/minecraft/core/dispenser/BoatDispenseItemBehavior.java b/src/main/java/net/minecraft/core/dispenser/BoatDispenseItemBehavior.java +index 36180e80dbd681e68c60e097015dad890a48b574..dff30954e4c588ee4cc79d3f6dab6fb456934d65 100644 +--- a/src/main/java/net/minecraft/core/dispenser/BoatDispenseItemBehavior.java ++++ b/src/main/java/net/minecraft/core/dispenser/BoatDispenseItemBehavior.java +@@ -67,7 +67,7 @@ public class BoatDispenseItemBehavior extends DefaultDispenseItemBehavior { + stack.grow(1); + // Chain to handler for new item + ItemStack eventStack = CraftItemStack.asNMSCopy(event.getItem()); +- DispenseItemBehavior idispensebehavior = (DispenseItemBehavior) DispenserBlock.DISPENSER_REGISTRY.get(eventStack.getItem()); ++ DispenseItemBehavior idispensebehavior = DispenserBlock.getDispenseBehavior(pointer, eventStack); // Paper - Fix NPE with equippable and items without behavior + if (idispensebehavior != DispenseItemBehavior.NOOP && idispensebehavior != this) { + idispensebehavior.dispense(pointer, eventStack); + return stack; +diff --git a/src/main/java/net/minecraft/core/dispenser/DefaultDispenseItemBehavior.java b/src/main/java/net/minecraft/core/dispenser/DefaultDispenseItemBehavior.java +index 39c96f5db6e90a470404c6387fa0c1d5531822e5..8aae1d113e84dfad9f2b6f0bcd203ca6c68bc5ce 100644 +--- a/src/main/java/net/minecraft/core/dispenser/DefaultDispenseItemBehavior.java ++++ b/src/main/java/net/minecraft/core/dispenser/DefaultDispenseItemBehavior.java +@@ -101,7 +101,7 @@ public class DefaultDispenseItemBehavior implements DispenseItemBehavior { + if (!dropper && !event.getItem().getType().equals(craftItem.getType())) { + // Chain to handler for new item + ItemStack eventStack = CraftItemStack.asNMSCopy(event.getItem()); +- DispenseItemBehavior idispensebehavior = (DispenseItemBehavior) DispenserBlock.DISPENSER_REGISTRY.get(eventStack.getItem()); ++ DispenseItemBehavior idispensebehavior = DispenserBlock.getDispenseBehavior(sourceblock, eventStack); // Paper - Fix NPE with equippable and items without behavior + if (idispensebehavior != DispenseItemBehavior.NOOP && idispensebehavior.getClass() != DefaultDispenseItemBehavior.class) { + idispensebehavior.dispense(sourceblock, eventStack); + } else { +diff --git a/src/main/java/net/minecraft/core/dispenser/DispenseItemBehavior.java b/src/main/java/net/minecraft/core/dispenser/DispenseItemBehavior.java +index 18304349c9ab24657c4152aff800dba969174665..681c38f4457fc10806c10518b16159580c0f2619 100644 +--- a/src/main/java/net/minecraft/core/dispenser/DispenseItemBehavior.java ++++ b/src/main/java/net/minecraft/core/dispenser/DispenseItemBehavior.java +@@ -124,7 +124,7 @@ public interface DispenseItemBehavior { + stack.grow(1); + // Chain to handler for new item + ItemStack eventStack = CraftItemStack.asNMSCopy(event.getItem()); +- DispenseItemBehavior idispensebehavior = (DispenseItemBehavior) DispenserBlock.DISPENSER_REGISTRY.get(eventStack.getItem()); ++ DispenseItemBehavior idispensebehavior = DispenserBlock.getDispenseBehavior(pointer, eventStack); // Paper - Fix NPE with equippable and items without behavior + if (idispensebehavior != DispenseItemBehavior.NOOP && idispensebehavior != this) { + idispensebehavior.dispense(pointer, eventStack); + return stack; +@@ -178,7 +178,7 @@ public interface DispenseItemBehavior { + stack.grow(1); + // Chain to handler for new item + ItemStack eventStack = CraftItemStack.asNMSCopy(event.getItem()); +- DispenseItemBehavior idispensebehavior = (DispenseItemBehavior) DispenserBlock.DISPENSER_REGISTRY.get(eventStack.getItem()); ++ DispenseItemBehavior idispensebehavior = DispenserBlock.getDispenseBehavior(pointer, eventStack); // Paper - Fix NPE with equippable and items without behavior + if (idispensebehavior != DispenseItemBehavior.NOOP && idispensebehavior != this) { + idispensebehavior.dispense(pointer, eventStack); + return stack; +@@ -231,8 +231,8 @@ public interface DispenseItemBehavior { + stack.grow(1); + // Chain to handler for new item + ItemStack eventStack = CraftItemStack.asNMSCopy(event.getItem()); +- DispenseItemBehavior idispensebehavior = (DispenseItemBehavior) DispenserBlock.DISPENSER_REGISTRY.get(eventStack.getItem()); +- if (idispensebehavior != DispenseItemBehavior.NOOP && idispensebehavior != EquipmentDispenseItemBehavior.INSTANCE) { ++ DispenseItemBehavior idispensebehavior = DispenserBlock.getDispenseBehavior(pointer, eventStack); // Paper - Fix NPE with equippable and items without behavior ++ if (idispensebehavior != DispenseItemBehavior.NOOP && idispensebehavior != this) { // Paper - fix possible StackOverflowError + idispensebehavior.dispense(pointer, eventStack); + return stack; + } +@@ -282,8 +282,8 @@ public interface DispenseItemBehavior { + if (!event.getItem().equals(craftItem)) { + // Chain to handler for new item + ItemStack eventStack = CraftItemStack.asNMSCopy(event.getItem()); +- DispenseItemBehavior idispensebehavior = (DispenseItemBehavior) DispenserBlock.DISPENSER_REGISTRY.get(eventStack.getItem()); +- if (idispensebehavior != DispenseItemBehavior.NOOP && idispensebehavior != EquipmentDispenseItemBehavior.INSTANCE) { ++ DispenseItemBehavior idispensebehavior = DispenserBlock.getDispenseBehavior(pointer, eventStack); // Paper - Fix NPE with equippable and items without behavior ++ if (idispensebehavior != DispenseItemBehavior.NOOP && idispensebehavior != this) { // Paper - fix possible StackOverflowError + idispensebehavior.dispense(pointer, eventStack); + return stack; + } +@@ -352,7 +352,7 @@ public interface DispenseItemBehavior { + if (!event.getItem().equals(craftItem)) { + // Chain to handler for new item + ItemStack eventStack = CraftItemStack.asNMSCopy(event.getItem()); +- DispenseItemBehavior idispensebehavior = (DispenseItemBehavior) DispenserBlock.DISPENSER_REGISTRY.get(eventStack.getItem()); ++ DispenseItemBehavior idispensebehavior = DispenserBlock.getDispenseBehavior(pointer, eventStack); // Paper - Fix NPE with equippable and items without behavior + if (idispensebehavior != DispenseItemBehavior.NOOP && idispensebehavior != this) { + idispensebehavior.dispense(pointer, eventStack); + return stack; +@@ -414,7 +414,7 @@ public interface DispenseItemBehavior { + if (!event.getItem().equals(craftItem)) { + // Chain to handler for new item + ItemStack eventStack = CraftItemStack.asNMSCopy(event.getItem()); +- DispenseItemBehavior idispensebehavior = (DispenseItemBehavior) DispenserBlock.DISPENSER_REGISTRY.get(eventStack.getItem()); ++ DispenseItemBehavior idispensebehavior = DispenserBlock.getDispenseBehavior(pointer, eventStack); // Paper - Fix NPE with equippable and items without behavior + if (idispensebehavior != DispenseItemBehavior.NOOP && idispensebehavior != this) { + idispensebehavior.dispense(pointer, eventStack); + return stack; +@@ -452,7 +452,7 @@ public interface DispenseItemBehavior { + if (!event.getItem().equals(craftItem)) { + // Chain to handler for new item + ItemStack eventStack = CraftItemStack.asNMSCopy(event.getItem()); +- DispenseItemBehavior idispensebehavior = (DispenseItemBehavior) DispenserBlock.DISPENSER_REGISTRY.get(eventStack.getItem()); ++ DispenseItemBehavior idispensebehavior = DispenserBlock.getDispenseBehavior(pointer, eventStack); // Paper - Fix NPE with equippable and items without behavior + if (idispensebehavior != DispenseItemBehavior.NOOP && idispensebehavior != this) { + idispensebehavior.dispense(pointer, eventStack); + return stack; +@@ -514,7 +514,7 @@ public interface DispenseItemBehavior { + if (!event.getItem().equals(craftItem)) { + // Chain to handler for new item + ItemStack eventStack = CraftItemStack.asNMSCopy(event.getItem()); +- DispenseItemBehavior idispensebehavior = (DispenseItemBehavior) DispenserBlock.DISPENSER_REGISTRY.get(eventStack.getItem()); ++ DispenseItemBehavior idispensebehavior = DispenserBlock.getDispenseBehavior(pointer, eventStack); // Paper - Fix NPE with equippable and items without behavior + if (idispensebehavior != DispenseItemBehavior.NOOP && idispensebehavior != this) { + idispensebehavior.dispense(pointer, eventStack); + return stack; +@@ -585,7 +585,7 @@ public interface DispenseItemBehavior { + stack.grow(1); + // Chain to handler for new item + ItemStack eventStack = CraftItemStack.asNMSCopy(event.getItem()); +- DispenseItemBehavior idispensebehavior = (DispenseItemBehavior) DispenserBlock.DISPENSER_REGISTRY.get(eventStack.getItem()); ++ DispenseItemBehavior idispensebehavior = DispenserBlock.getDispenseBehavior(pointer, eventStack); // Paper - Fix NPE with equippable and items without behavior + if (idispensebehavior != DispenseItemBehavior.NOOP && idispensebehavior != this) { + idispensebehavior.dispense(pointer, eventStack); + return stack; +@@ -625,7 +625,7 @@ public interface DispenseItemBehavior { + if (!event.getItem().equals(craftItem)) { + // Chain to handler for new item + ItemStack eventStack = CraftItemStack.asNMSCopy(event.getItem()); +- DispenseItemBehavior idispensebehavior = (DispenseItemBehavior) DispenserBlock.DISPENSER_REGISTRY.get(eventStack.getItem()); ++ DispenseItemBehavior idispensebehavior = DispenserBlock.getDispenseBehavior(pointer, eventStack); // Paper - Fix NPE with equippable and items without behavior + if (idispensebehavior != DispenseItemBehavior.NOOP && idispensebehavior != this) { + idispensebehavior.dispense(pointer, eventStack); + return stack; +@@ -645,7 +645,7 @@ public interface DispenseItemBehavior { + stack.shrink(1); + this.setSuccess(true); + } else { +- this.setSuccess(EquipmentDispenseItemBehavior.dispenseEquipment(pointer, stack)); ++ this.setSuccess(EquipmentDispenseItemBehavior.dispenseEquipment(pointer, stack, this)); // Paper - fix possible StackOverflowError + } + + return stack; +@@ -674,7 +674,7 @@ public interface DispenseItemBehavior { + if (!event.getItem().equals(craftItem)) { + // Chain to handler for new item + ItemStack eventStack = CraftItemStack.asNMSCopy(event.getItem()); +- DispenseItemBehavior idispensebehavior = (DispenseItemBehavior) DispenserBlock.DISPENSER_REGISTRY.get(eventStack.getItem()); ++ DispenseItemBehavior idispensebehavior = DispenserBlock.getDispenseBehavior(pointer, eventStack); // Paper - Fix NPE with equippable and items without behavior + if (idispensebehavior != DispenseItemBehavior.NOOP && idispensebehavior != this) { + idispensebehavior.dispense(pointer, eventStack); + return stack; +@@ -691,7 +691,7 @@ public interface DispenseItemBehavior { + stack.shrink(1); + this.setSuccess(true); + } else { +- this.setSuccess(EquipmentDispenseItemBehavior.dispenseEquipment(pointer, stack)); ++ this.setSuccess(EquipmentDispenseItemBehavior.dispenseEquipment(pointer, stack, this)); // Paper - fix possible StackOverflowError + } + + return stack; +@@ -736,7 +736,7 @@ public interface DispenseItemBehavior { + if (!event.getItem().equals(craftItem)) { + // Chain to handler for new item + ItemStack eventStack = CraftItemStack.asNMSCopy(event.getItem()); +- DispenseItemBehavior idispensebehavior = (DispenseItemBehavior) DispenserBlock.DISPENSER_REGISTRY.get(eventStack.getItem()); ++ DispenseItemBehavior idispensebehavior = DispenserBlock.getDispenseBehavior(pointer, eventStack); // Paper - Fix NPE with equippable and items without behavior + if (idispensebehavior != DispenseItemBehavior.NOOP && idispensebehavior != this) { + idispensebehavior.dispense(pointer, eventStack); + return stack; +@@ -818,8 +818,8 @@ public interface DispenseItemBehavior { + if (!event.getItem().equals(craftItem)) { + // Chain to handler for new item + ItemStack eventStack = CraftItemStack.asNMSCopy(event.getItem()); +- DispenseItemBehavior idispensebehavior = (DispenseItemBehavior) DispenserBlock.DISPENSER_REGISTRY.get(eventStack.getItem()); +- if (idispensebehavior != DispenseItemBehavior.NOOP && idispensebehavior != EquipmentDispenseItemBehavior.INSTANCE) { ++ DispenseItemBehavior idispensebehavior = DispenserBlock.getDispenseBehavior(pointer, eventStack); // Paper - Fix NPE with equippable and items without behavior ++ if (idispensebehavior != DispenseItemBehavior.NOOP && idispensebehavior != this) { // Paper - fix possible StackOverflowError + idispensebehavior.dispense(pointer, eventStack); + return stack; + } +diff --git a/src/main/java/net/minecraft/core/dispenser/EquipmentDispenseItemBehavior.java b/src/main/java/net/minecraft/core/dispenser/EquipmentDispenseItemBehavior.java +index a03cc350973fda213251cad273a2db86f438904b..a43ea83dbbd5946096cdde31af766674bda6c3be 100644 +--- a/src/main/java/net/minecraft/core/dispenser/EquipmentDispenseItemBehavior.java ++++ b/src/main/java/net/minecraft/core/dispenser/EquipmentDispenseItemBehavior.java +@@ -23,10 +23,15 @@ public class EquipmentDispenseItemBehavior extends DefaultDispenseItemBehavior { + + @Override + protected ItemStack execute(BlockSource pointer, ItemStack stack) { +- return EquipmentDispenseItemBehavior.dispenseEquipment(pointer, stack) ? stack : super.execute(pointer, stack); ++ return EquipmentDispenseItemBehavior.dispenseEquipment(pointer, stack, this) ? stack : super.execute(pointer, stack); // Paper - fix possible StackOverflowError + } + +- public static boolean dispenseEquipment(BlockSource pointer, ItemStack stack) { ++ @Deprecated @io.papermc.paper.annotation.DoNotUse // Paper ++ public static boolean dispenseEquipment(BlockSource pointer, ItemStack armor) { ++ // Paper start ++ return dispenseEquipment(pointer, armor, null); ++ } ++ public static boolean dispenseEquipment(BlockSource pointer, ItemStack stack, @javax.annotation.Nullable DispenseItemBehavior currentBehavior) { + BlockPos blockposition = pointer.pos().relative((Direction) pointer.state().getValue(DispenserBlock.FACING)); + List list = pointer.level().getEntitiesOfClass(LivingEntity.class, new AABB(blockposition), (entityliving) -> { + return entityliving.canEquipWithDispenser(stack); +@@ -58,8 +63,8 @@ public class EquipmentDispenseItemBehavior extends DefaultDispenseItemBehavior { + stack.grow(1); + // Chain to handler for new item + ItemStack eventStack = CraftItemStack.asNMSCopy(event.getItem()); +- DispenseItemBehavior idispensebehavior = (DispenseItemBehavior) DispenserBlock.DISPENSER_REGISTRY.get(eventStack.getItem()); +- if (idispensebehavior != DispenseItemBehavior.NOOP && idispensebehavior != EquipmentDispenseItemBehavior.INSTANCE) { ++ DispenseItemBehavior idispensebehavior = DispenserBlock.getDispenseBehavior(pointer, eventStack); // Paper - Fix NPE with equippable and items without behavior ++ if (idispensebehavior != DispenseItemBehavior.NOOP && (currentBehavior == null || idispensebehavior != currentBehavior)) { // Paper - fix possible StackOverflowError + idispensebehavior.dispense(pointer, eventStack); + return true; + } +diff --git a/src/main/java/net/minecraft/core/dispenser/MinecartDispenseItemBehavior.java b/src/main/java/net/minecraft/core/dispenser/MinecartDispenseItemBehavior.java +index f2e19218cf0d3b44a617c7d74f782c3e15e3f07f..aae9ec8f3bd39685b37251bef3f9ac846d65c192 100644 +--- a/src/main/java/net/minecraft/core/dispenser/MinecartDispenseItemBehavior.java ++++ b/src/main/java/net/minecraft/core/dispenser/MinecartDispenseItemBehavior.java +@@ -87,7 +87,7 @@ public class MinecartDispenseItemBehavior extends DefaultDispenseItemBehavior { + stack.grow(1); + // Chain to handler for new item + ItemStack eventStack = CraftItemStack.asNMSCopy(event.getItem()); +- DispenseItemBehavior idispensebehavior = (DispenseItemBehavior) DispenserBlock.DISPENSER_REGISTRY.get(eventStack.getItem()); ++ DispenseItemBehavior idispensebehavior = DispenserBlock.getDispenseBehavior(pointer, eventStack); // Paper - Fix NPE with equippable and items without behavior + if (idispensebehavior != DispenseItemBehavior.NOOP && idispensebehavior != this) { + idispensebehavior.dispense(pointer, eventStack); + return stack; +diff --git a/src/main/java/net/minecraft/core/dispenser/ProjectileDispenseBehavior.java b/src/main/java/net/minecraft/core/dispenser/ProjectileDispenseBehavior.java +index a9d230d6ff22d5e3a11b2f31e7d751f44888a12c..aba0ddfc5009a11b6c5cba95a90479083643bdad 100644 +--- a/src/main/java/net/minecraft/core/dispenser/ProjectileDispenseBehavior.java ++++ b/src/main/java/net/minecraft/core/dispenser/ProjectileDispenseBehavior.java +@@ -57,7 +57,7 @@ public class ProjectileDispenseBehavior extends DefaultDispenseItemBehavior { + stack.grow(1); + // Chain to handler for new item + ItemStack eventStack = CraftItemStack.asNMSCopy(event.getItem()); +- DispenseItemBehavior idispensebehavior = (DispenseItemBehavior) DispenserBlock.DISPENSER_REGISTRY.get(eventStack.getItem()); ++ DispenseItemBehavior idispensebehavior = DispenserBlock.getDispenseBehavior(pointer, eventStack); // Paper - Fix NPE with equippable and items without behavior + if (idispensebehavior != DispenseItemBehavior.NOOP && idispensebehavior != this) { + idispensebehavior.dispense(pointer, eventStack); + return stack; +diff --git a/src/main/java/net/minecraft/core/dispenser/ShearsDispenseItemBehavior.java b/src/main/java/net/minecraft/core/dispenser/ShearsDispenseItemBehavior.java +index f5206e4176f58cff4cfe70c94f014afebc98c589..afad4fa3ca1a3186c4569ea073f776dac16817e1 100644 +--- a/src/main/java/net/minecraft/core/dispenser/ShearsDispenseItemBehavior.java ++++ b/src/main/java/net/minecraft/core/dispenser/ShearsDispenseItemBehavior.java +@@ -52,7 +52,7 @@ public class ShearsDispenseItemBehavior extends OptionalDispenseItemBehavior { + if (!event.getItem().equals(craftItem)) { + // Chain to handler for new item + ItemStack eventStack = CraftItemStack.asNMSCopy(event.getItem()); +- DispenseItemBehavior idispensebehavior = (DispenseItemBehavior) DispenserBlock.DISPENSER_REGISTRY.get(eventStack.getItem()); ++ DispenseItemBehavior idispensebehavior = DispenserBlock.getDispenseBehavior(pointer, eventStack); // Paper - Fix NPE with equippable and items without behavior + if (idispensebehavior != DispenseItemBehavior.NOOP && idispensebehavior != this) { + idispensebehavior.dispense(pointer, eventStack); + return stack; +diff --git a/src/main/java/net/minecraft/core/dispenser/ShulkerBoxDispenseBehavior.java b/src/main/java/net/minecraft/core/dispenser/ShulkerBoxDispenseBehavior.java +index f84987c36a16df19286d6f1badfb1ffb9cc7e770..cc85e96035f7cb2e6493b1cc4748031171d7dbee 100644 +--- a/src/main/java/net/minecraft/core/dispenser/ShulkerBoxDispenseBehavior.java ++++ b/src/main/java/net/minecraft/core/dispenser/ShulkerBoxDispenseBehavior.java +@@ -48,7 +48,7 @@ public class ShulkerBoxDispenseBehavior extends OptionalDispenseItemBehavior { + if (!event.getItem().equals(craftItem)) { + // Chain to handler for new item + ItemStack eventStack = CraftItemStack.asNMSCopy(event.getItem()); +- DispenseItemBehavior idispensebehavior = (DispenseItemBehavior) DispenserBlock.DISPENSER_REGISTRY.get(eventStack.getItem()); ++ DispenseItemBehavior idispensebehavior = DispenserBlock.getDispenseBehavior(pointer, eventStack); // Paper - Fix NPE with equippable and items without behavior + if (idispensebehavior != DispenseItemBehavior.NOOP && idispensebehavior != this) { + idispensebehavior.dispense(pointer, eventStack); + return stack; +diff --git a/src/main/java/net/minecraft/world/level/block/DispenserBlock.java b/src/main/java/net/minecraft/world/level/block/DispenserBlock.java +index a02f24448b002824b068278fa427003008c0d0f1..500c56c4ef0878434582a50d6dba2ccca9773275 100644 +--- a/src/main/java/net/minecraft/world/level/block/DispenserBlock.java ++++ b/src/main/java/net/minecraft/world/level/block/DispenserBlock.java +@@ -116,6 +116,12 @@ public class DispenserBlock extends BaseEntityBlock { + } + } + ++ // Paper start - Fix NPE with equippable and items without behavior ++ public static DispenseItemBehavior getDispenseBehavior(BlockSource pointer, ItemStack stack) { ++ return ((DispenserBlock) pointer.state().getBlock()).getDispenseMethod(pointer.level(), stack); ++ } ++ // Paper end - Fix NPE with equippable and items without behavior ++ + protected DispenseItemBehavior getDispenseMethod(Level world, ItemStack stack) { + if (!stack.isItemEnabled(world.enabledFeatures())) { + return DispenserBlock.DEFAULT_BEHAVIOR; +diff --git a/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java b/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java +index 96b901d07718d8926a2175925e867b4417c3947c..418e67ef5896325fe143501f5a4f1604b065ba0f 100644 +--- a/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java ++++ b/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java +@@ -2232,7 +2232,7 @@ public class CraftEventFactory { + if (!event.getItem().equals(craftItem)) { + // Chain to handler for new item + ItemStack eventStack = CraftItemStack.asNMSCopy(event.getItem()); +- net.minecraft.core.dispenser.DispenseItemBehavior itemBehavior = net.minecraft.world.level.block.DispenserBlock.DISPENSER_REGISTRY.get(eventStack.getItem()); ++ net.minecraft.core.dispenser.DispenseItemBehavior itemBehavior = net.minecraft.world.level.block.DispenserBlock.getDispenseBehavior(pointer, eventStack); // Paper - Fix NPE with equippable and items without behavior + if (itemBehavior != net.minecraft.core.dispenser.DispenseItemBehavior.NOOP && itemBehavior != instance) { + itemBehavior.dispense(pointer, eventStack); + return itemStack; diff --git a/patches/server/0936-More-Raid-API.patch b/patches/server/0936-More-Raid-API.patch deleted file mode 100644 index 8824526cadf1..000000000000 --- a/patches/server/0936-More-Raid-API.patch +++ /dev/null @@ -1,106 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Jake Potrebic -Date: Fri, 4 Mar 2022 09:46:33 -0800 -Subject: [PATCH] More Raid API - -== AT == -public net.minecraft.world.entity.raid.Raid raidEvent - -diff --git a/src/main/java/net/minecraft/world/entity/raid/Raid.java b/src/main/java/net/minecraft/world/entity/raid/Raid.java -index fe2a01ae8207c97203d331bbab51699502b977e2..dcbef04bbaab988096bf416163264833e84d1967 100644 ---- a/src/main/java/net/minecraft/world/entity/raid/Raid.java -+++ b/src/main/java/net/minecraft/world/entity/raid/Raid.java -@@ -107,6 +107,11 @@ public class Raid { - private Raid.RaidStatus status; - private int celebrationTicks; - private Optional waveSpawnPos; -+ // Paper start -+ private static final String PDC_NBT_KEY = "BukkitValues"; -+ private static final org.bukkit.craftbukkit.persistence.CraftPersistentDataTypeRegistry PDC_TYPE_REGISTRY = new org.bukkit.craftbukkit.persistence.CraftPersistentDataTypeRegistry(); -+ public final org.bukkit.craftbukkit.persistence.CraftPersistentDataContainer persistentDataContainer = new org.bukkit.craftbukkit.persistence.CraftPersistentDataContainer(PDC_TYPE_REGISTRY); -+ // Paper end - - public Raid(int id, ServerLevel world, BlockPos pos) { - this.raidEvent = new ServerBossEvent(Raid.RAID_NAME_COMPONENT, BossEvent.BossBarColor.RED, BossEvent.BossBarOverlay.NOTCHED_10); -@@ -150,6 +155,11 @@ public class Raid { - this.heroesOfTheVillage.add(NbtUtils.loadUUID(nbtbase)); - } - } -+ // Paper start -+ if (nbt.contains(PDC_NBT_KEY, net.minecraft.nbt.Tag.TAG_COMPOUND)) { -+ this.persistentDataContainer.putAll(nbt.getCompound(PDC_NBT_KEY)); -+ } -+ // Paper end - - } - -@@ -867,6 +877,11 @@ public class Raid { - } - - nbt.put("HeroesOfTheVillage", nbttaglist); -+ // Paper start -+ if (!this.persistentDataContainer.isEmpty()) { -+ nbt.put(PDC_NBT_KEY, this.persistentDataContainer.toTagCompound()); -+ } -+ // Paper end - return nbt; - } - -diff --git a/src/main/java/org/bukkit/craftbukkit/CraftRaid.java b/src/main/java/org/bukkit/craftbukkit/CraftRaid.java -index b8ce1c1c2447f9cff1717bfcfd6eb911ade0d4b3..51f21af9d75769abdcba713b9aa33392e994d9b0 100644 ---- a/src/main/java/org/bukkit/craftbukkit/CraftRaid.java -+++ b/src/main/java/org/bukkit/craftbukkit/CraftRaid.java -@@ -103,4 +103,34 @@ public final class CraftRaid implements Raid { - public net.minecraft.world.entity.raid.Raid getHandle() { - return this.handle; - } -+ -+ // Paper start - more Raid API -+ @Override -+ public int getId() { -+ return this.handle.getId(); -+ } -+ -+ @Override -+ public org.bukkit.boss.BossBar getBossBar() { -+ return new org.bukkit.craftbukkit.boss.CraftBossBar(this.handle.raidEvent); -+ } -+ -+ @Override -+ public org.bukkit.persistence.PersistentDataContainer getPersistentDataContainer() { -+ return this.handle.persistentDataContainer; -+ } -+ -+ @Override -+ public boolean equals(final Object o) { -+ if (this == o) return true; -+ if (o == null || this.getClass() != o.getClass()) return false; -+ final org.bukkit.craftbukkit.CraftRaid craftRaid = (org.bukkit.craftbukkit.CraftRaid) o; -+ return this.handle.equals(craftRaid.handle); -+ } -+ -+ @Override -+ public int hashCode() { -+ return this.handle.hashCode(); -+ } -+ // Paper end - more Raid API - } -diff --git a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java -index f27568d26e02b5244a84d0fb6d8c1d9f14b7a519..e8404d5bad60b8fa290f334d3c64ee5503e01e5c 100644 ---- a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java -+++ b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java -@@ -2313,6 +2313,14 @@ public class CraftWorld extends CraftRegionAccessor implements World { - return (raid == null) ? null : new CraftRaid(raid); - } - -+ // Paper start - more Raid API -+ @Override -+ public @Nullable Raid getRaid(final int id) { -+ final net.minecraft.world.entity.raid.@Nullable Raid nmsRaid = this.world.getRaids().raidMap.get(id); -+ return nmsRaid != null ? new CraftRaid(nmsRaid) : null; -+ } -+ // Paper end - more Raid API -+ - @Override - public List getRaids() { - Raids persistentRaid = this.world.getRaids(); diff --git a/patches/server/0937-Add-onboarding-message-for-initial-server-start.patch b/patches/server/0937-Add-onboarding-message-for-initial-server-start.patch deleted file mode 100644 index 5bb4e9d2fcb9..000000000000 --- a/patches/server/0937-Add-onboarding-message-for-initial-server-start.patch +++ /dev/null @@ -1,103 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: leguan -Date: Sun, 10 Mar 2024 20:10:41 +0100 -Subject: [PATCH] Add onboarding message for initial server start - - -diff --git a/src/main/java/io/papermc/paper/configuration/Configurations.java b/src/main/java/io/papermc/paper/configuration/Configurations.java -index d9502ba028a96f9cc846f9ed428bd8066b857ca3..87e5f614ba988547a827486740db217e28585773 100644 ---- a/src/main/java/io/papermc/paper/configuration/Configurations.java -+++ b/src/main/java/io/papermc/paper/configuration/Configurations.java -@@ -129,6 +129,7 @@ public abstract class Configurations { - if (Files.notExists(configFile)) { - node = CommentedConfigurationNode.root(loader.defaultOptions()); - node.node(Configuration.VERSION_FIELD).raw(this.globalConfigVersion()); -+ GlobalConfiguration.isFirstStart = true; - } else { - node = loader.load(); - this.verifyGlobalConfigVersion(node); -diff --git a/src/main/java/io/papermc/paper/configuration/GlobalConfiguration.java b/src/main/java/io/papermc/paper/configuration/GlobalConfiguration.java -index 917bd0c1dd8f356edc3741ee59d24e4d090af182..f19b4a17a7e22969f7b51d3bea4056b41ab25484 100644 ---- a/src/main/java/io/papermc/paper/configuration/GlobalConfiguration.java -+++ b/src/main/java/io/papermc/paper/configuration/GlobalConfiguration.java -@@ -27,6 +27,7 @@ public class GlobalConfiguration extends ConfigurationPart { - private static final Logger LOGGER = LogUtils.getLogger(); - static final int CURRENT_VERSION = 29; // (when you change the version, change the comment, so it conflicts on rebases): - private static GlobalConfiguration instance; -+ public static boolean isFirstStart = false; - public static GlobalConfiguration get() { - return instance; - } -diff --git a/src/main/java/net/minecraft/server/MinecraftServer.java b/src/main/java/net/minecraft/server/MinecraftServer.java -index 3ee0e2adf576e312c18da90adc46160ad85179fb..42a74f5471da882d63c194b1e212f78a94b103ec 100644 ---- a/src/main/java/net/minecraft/server/MinecraftServer.java -+++ b/src/main/java/net/minecraft/server/MinecraftServer.java -@@ -1135,6 +1135,16 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop " + onboardingLink + ""); -+ link.setFont(MinecraftServerGui.MONOSPACED); -+ link.setCursor(new java.awt.Cursor(java.awt.Cursor.HAND_CURSOR)); -+ link.addMouseListener(new java.awt.event.MouseAdapter() { -+ @Override -+ public void mouseClicked(final java.awt.event.MouseEvent e) { -+ try { -+ java.awt.Desktop.getDesktop().browse(java.net.URI.create(onboardingLink)); -+ } catch (java.io.IOException exception) { -+ LOGGER.error("Unable to find a default browser. Please manually visit the website: " + onboardingLink, exception); -+ } catch (UnsupportedOperationException exception) { -+ LOGGER.error("This platform does not support the BROWSE action. Please manually visit the website: " + onboardingLink, exception); -+ } catch (SecurityException exception) { -+ LOGGER.error("This action has been denied by the security manager. Please manually visit the website: " + onboardingLink, exception); -+ } -+ } -+ }); -+ -+ jPanel.add(jLabel); -+ jPanel.add(link); -+ -+ return jPanel; -+ } -+ // Paper end - Add onboarding message for initial server start -+ - private JComponent buildPlayerPanel() { - JList jlist = new PlayerListComponent(this.server); - JScrollPane jscrollpane = new JScrollPane(jlist, 22, 30); diff --git a/patches/server/0937-Improve-tag-parser-handling.patch b/patches/server/0937-Improve-tag-parser-handling.patch new file mode 100644 index 000000000000..36bc92d7a0ee --- /dev/null +++ b/patches/server/0937-Improve-tag-parser-handling.patch @@ -0,0 +1,285 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Nassim Jahnke +Date: Mon, 5 Feb 2024 11:54:04 +0100 +Subject: [PATCH] Improve tag parser handling + + +diff --git a/src/main/java/com/mojang/brigadier/CommandDispatcher.java b/src/main/java/com/mojang/brigadier/CommandDispatcher.java +index 92848b64a78fce7a92e1657c2da6fc5ee53eea44..4b4f812eb13d5f03bcf3f8724d8aa8dbbc724e8b 100644 +--- a/src/main/java/com/mojang/brigadier/CommandDispatcher.java ++++ b/src/main/java/com/mojang/brigadier/CommandDispatcher.java +@@ -304,9 +304,15 @@ public class CommandDispatcher { + } + final CommandContextBuilder context = contextSoFar.copy(); + final StringReader reader = new StringReader(originalReader); ++ boolean stop = false; // Paper - Handle non-recoverable exceptions + try { + try { + child.parse(reader, context); ++ // Paper start - Handle non-recoverable exceptions ++ } catch (final io.papermc.paper.brigadier.TagParseCommandSyntaxException e) { ++ stop = true; ++ throw e; ++ // Paper end - Handle non-recoverable exceptions + } catch (final RuntimeException ex) { + throw CommandSyntaxException.BUILT_IN_EXCEPTIONS.dispatcherParseException().createWithContext(reader, ex.getMessage()); + } +@@ -321,6 +327,7 @@ public class CommandDispatcher { + } + errors.put(child, ex); + reader.setCursor(cursor); ++ if (stop) return new ParseResults<>(contextSoFar, originalReader, errors); // Paper - Handle non-recoverable exceptions + continue; + } + +diff --git a/src/main/java/io/papermc/paper/brigadier/TagParseCommandSyntaxException.java b/src/main/java/io/papermc/paper/brigadier/TagParseCommandSyntaxException.java +new file mode 100644 +index 0000000000000000000000000000000000000000..a375ad4ba9db990b24a2b9ff366fcba66b753815 +--- /dev/null ++++ b/src/main/java/io/papermc/paper/brigadier/TagParseCommandSyntaxException.java +@@ -0,0 +1,15 @@ ++package io.papermc.paper.brigadier; ++ ++import com.mojang.brigadier.LiteralMessage; ++import com.mojang.brigadier.exceptions.CommandSyntaxException; ++import com.mojang.brigadier.exceptions.SimpleCommandExceptionType; ++import net.minecraft.network.chat.Component; ++ ++public final class TagParseCommandSyntaxException extends CommandSyntaxException { ++ ++ private static final SimpleCommandExceptionType EXCEPTION_TYPE = new SimpleCommandExceptionType(new LiteralMessage("Error parsing NBT")); ++ ++ public TagParseCommandSyntaxException(final String message) { ++ super(EXCEPTION_TYPE, Component.literal(message)); ++ } ++} +diff --git a/src/main/java/net/minecraft/commands/arguments/selector/SelectorPattern.java b/src/main/java/net/minecraft/commands/arguments/selector/SelectorPattern.java +index 82ecd9c034a2ee5040b6a66233070ce1cb4e48d2..50e66b10ec3943b9068e546763087161395f673a 100644 +--- a/src/main/java/net/minecraft/commands/arguments/selector/SelectorPattern.java ++++ b/src/main/java/net/minecraft/commands/arguments/selector/SelectorPattern.java +@@ -13,7 +13,7 @@ public record SelectorPattern(String pattern, EntitySelector resolved) { + EntitySelectorParser entitySelectorParser = new EntitySelectorParser(new StringReader(selector), true); + return DataResult.success(new SelectorPattern(selector, entitySelectorParser.parse())); + } catch (CommandSyntaxException var2) { +- return DataResult.error(() -> "Invalid selector component: " + selector + ": " + var2.getMessage()); ++ return DataResult.error(() -> "Invalid selector component"); // Paper - limit selector error message + } + } + +diff --git a/src/main/java/net/minecraft/nbt/TagParser.java b/src/main/java/net/minecraft/nbt/TagParser.java +index da101bca71f4710812621b98f0a0d8cab180346a..3cd112584accb8e8f050ac99738eed11c902e60e 100644 +--- a/src/main/java/net/minecraft/nbt/TagParser.java ++++ b/src/main/java/net/minecraft/nbt/TagParser.java +@@ -49,6 +49,7 @@ public class TagParser { + }, CompoundTag::toString); + public static final Codec LENIENT_CODEC = Codec.withAlternative(AS_CODEC, CompoundTag.CODEC); + private final StringReader reader; ++ private int depth; // Paper + + public static CompoundTag parseTag(String string) throws CommandSyntaxException { + return new TagParser(new StringReader(string)).readSingleStruct(); +@@ -159,6 +160,7 @@ public class TagParser { + + public CompoundTag readStruct() throws CommandSyntaxException { + this.expect('{'); ++ this.increaseDepth(); // Paper + CompoundTag compoundTag = new CompoundTag(); + this.reader.skipWhitespace(); + +@@ -182,6 +184,7 @@ public class TagParser { + } + + this.expect('}'); ++ this.depth--; // Paper + return compoundTag; + } + +@@ -191,6 +194,7 @@ public class TagParser { + if (!this.reader.canRead()) { + throw ERROR_EXPECTED_VALUE.createWithContext(this.reader); + } else { ++ this.increaseDepth(); // Paper + ListTag listTag = new ListTag(); + TagType tagType = null; + +@@ -216,6 +220,7 @@ public class TagParser { + } + + this.expect(']'); ++ this.depth--; // Paper + return listTag; + } + } +@@ -288,4 +293,11 @@ public class TagParser { + this.reader.skipWhitespace(); + this.reader.expect(c); + } ++ ++ private void increaseDepth() throws CommandSyntaxException { ++ this.depth++; ++ if (this.depth > 512) { ++ throw new io.papermc.paper.brigadier.TagParseCommandSyntaxException("NBT tag is too complex, depth > 512"); ++ } ++ } + } +diff --git a/src/main/java/net/minecraft/network/chat/ComponentUtils.java b/src/main/java/net/minecraft/network/chat/ComponentUtils.java +index 3365aed2b67ae0e4dd0410f5190ba474f146139b..8b776434a733b91129b089d702b2583b727bf3d2 100644 +--- a/src/main/java/net/minecraft/network/chat/ComponentUtils.java ++++ b/src/main/java/net/minecraft/network/chat/ComponentUtils.java +@@ -33,10 +33,30 @@ public class ComponentUtils { + } + } + ++ @io.papermc.paper.annotation.DoNotUse // Paper - validate separators - right now this method is only used for separator evaluation. Error on build if this changes to re-evaluate. + public static Optional updateForEntity(@Nullable CommandSourceStack source, Optional text, @Nullable Entity sender, int depth) throws CommandSyntaxException { + return text.isPresent() ? Optional.of(updateForEntity(source, text.get(), sender, depth)) : Optional.empty(); + } + ++ // Paper start - validate separator ++ public static Optional updateSeparatorForEntity(@Nullable CommandSourceStack source, Optional text, @Nullable Entity sender, int depth) throws CommandSyntaxException { ++ if (text.isEmpty() || !isValidSelector(text.get())) return Optional.empty(); ++ return Optional.of(updateForEntity(source, text.get(), sender, depth)); ++ } ++ public static boolean isValidSelector(final Component component) { ++ final ComponentContents contents = component.getContents(); ++ ++ if (contents instanceof net.minecraft.network.chat.contents.NbtContents || contents instanceof net.minecraft.network.chat.contents.SelectorContents) return false; ++ if (contents instanceof final net.minecraft.network.chat.contents.TranslatableContents translatableContents) { ++ for (final Object arg : translatableContents.getArgs()) { ++ if (arg instanceof final Component argumentAsComponent && !isValidSelector(argumentAsComponent)) return false; ++ } ++ } ++ ++ return true; ++ } ++ // Paper end - validate separator ++ + public static MutableComponent updateForEntity(@Nullable CommandSourceStack source, Component text, @Nullable Entity sender, int depth) throws CommandSyntaxException { + if (depth > 100) { + return text.copy(); +diff --git a/src/main/java/net/minecraft/network/chat/contents/NbtContents.java b/src/main/java/net/minecraft/network/chat/contents/NbtContents.java +index df26c39a2bb20e2021b50211dce905483a77f4e6..5634122dac8afeecab0cde623e9868d8e865e02a 100644 +--- a/src/main/java/net/minecraft/network/chat/contents/NbtContents.java ++++ b/src/main/java/net/minecraft/network/chat/contents/NbtContents.java +@@ -120,7 +120,7 @@ public class NbtContents implements ComponentContents { + }).map(Tag::getAsString); + if (this.interpreting) { + Component component = DataFixUtils.orElse( +- ComponentUtils.updateForEntity(source, this.separator, sender, depth), ComponentUtils.DEFAULT_NO_STYLE_SEPARATOR ++ ComponentUtils.updateSeparatorForEntity(source, this.separator, sender, depth), ComponentUtils.DEFAULT_NO_STYLE_SEPARATOR // Paper - validate separator + ); + return stream.flatMap(text -> { + try { +@@ -132,7 +132,7 @@ public class NbtContents implements ComponentContents { + } + }).reduce((accumulator, current) -> accumulator.append(component).append(current)).orElseGet(Component::empty); + } else { +- return ComponentUtils.updateForEntity(source, this.separator, sender, depth) ++ return ComponentUtils.updateSeparatorForEntity(source, this.separator, sender, depth) // Paper - validate separator + .map( + text -> stream.map(Component::literal) + .reduce((accumulator, current) -> accumulator.append(text).append(current)) +diff --git a/src/main/java/net/minecraft/network/chat/contents/SelectorContents.java b/src/main/java/net/minecraft/network/chat/contents/SelectorContents.java +index 5e87f5a2bbffe2eba3fc4fb0da51acee99e72fc2..4131067620f723be28a6cdb7508e14d7e2aed48b 100644 +--- a/src/main/java/net/minecraft/network/chat/contents/SelectorContents.java ++++ b/src/main/java/net/minecraft/network/chat/contents/SelectorContents.java +@@ -36,7 +36,7 @@ public record SelectorContents(SelectorPattern selector, Optional sep + if (source == null) { + return Component.empty(); + } else { +- Optional optional = ComponentUtils.updateForEntity(source, this.separator, sender, depth); ++ Optional optional = ComponentUtils.updateSeparatorForEntity(source, this.separator, sender, depth); // Paper - validate separator + return ComponentUtils.formatList(this.selector.resolved().findEntities(source), optional, Entity::getDisplayName); + } + } +diff --git a/src/main/java/net/minecraft/network/chat/contents/TranslatableContents.java b/src/main/java/net/minecraft/network/chat/contents/TranslatableContents.java +index 56e641bc5f6edc657647993ea2efbb7bb9c2f732..4aa6232bf0f72fcde32d257100bd15b1c5192aaa 100644 +--- a/src/main/java/net/minecraft/network/chat/contents/TranslatableContents.java ++++ b/src/main/java/net/minecraft/network/chat/contents/TranslatableContents.java +@@ -181,6 +181,15 @@ public class TranslatableContents implements ComponentContents { + + @Override + public Optional visit(FormattedText.ContentConsumer visitor) { ++ // Paper start - Count visited parts ++ try { ++ return this.visit(new TranslatableContentConsumer<>(visitor)); ++ } catch (IllegalArgumentException ignored) { ++ return visitor.accept("..."); ++ } ++ } ++ private Optional visit(TranslatableContentConsumer visitor) { ++ // Paper end - Count visited parts + this.decompose(); + + for (FormattedText formattedText : this.decomposedParts) { +@@ -192,6 +201,25 @@ public class TranslatableContents implements ComponentContents { + + return Optional.empty(); + } ++ // Paper start - Count visited parts ++ private static final class TranslatableContentConsumer implements FormattedText.ContentConsumer { ++ private static final IllegalArgumentException EX = new IllegalArgumentException("Too long"); ++ private final FormattedText.ContentConsumer visitor; ++ private int visited; ++ ++ private TranslatableContentConsumer(FormattedText.ContentConsumer visitor) { ++ this.visitor = visitor; ++ } ++ ++ @Override ++ public Optional accept(final String asString) { ++ if (visited++ > 32) { ++ throw EX; ++ } ++ return this.visitor.accept(asString); ++ } ++ } ++ // Paper end - Count visited parts + + @Override + public MutableComponent resolve(@Nullable CommandSourceStack source, @Nullable Entity sender, int depth) throws CommandSyntaxException { +diff --git a/src/main/java/net/minecraft/network/protocol/game/ServerboundCommandSuggestionPacket.java b/src/main/java/net/minecraft/network/protocol/game/ServerboundCommandSuggestionPacket.java +index 898b19887ed34c87003fc63cb5905df2ba6234a5..b47eeb23055b135d5567552ba983bfbc3e1fab67 100644 +--- a/src/main/java/net/minecraft/network/protocol/game/ServerboundCommandSuggestionPacket.java ++++ b/src/main/java/net/minecraft/network/protocol/game/ServerboundCommandSuggestionPacket.java +@@ -19,7 +19,7 @@ public class ServerboundCommandSuggestionPacket implements Packet 64 && ((index = packet.getCommand().indexOf(' ')) == -1 || index >= 64)) { ++ this.disconnect(Component.translatable("disconnect.spam"), org.bukkit.event.player.PlayerKickEvent.Cause.SPAM); ++ return; ++ } ++ // Paper end + // Paper start - AsyncTabCompleteEvent + TAB_COMPLETE_EXECUTOR.execute(() -> this.handleCustomCommandSuggestions0(packet)); + } +@@ -830,6 +837,13 @@ public class ServerGamePacketListenerImpl extends ServerCommonPacketListenerImpl + private void sendServerSuggestions(final ServerboundCommandSuggestionPacket packet, final StringReader stringreader) { + // Paper end - AsyncTabCompleteEvent + ParseResults parseresults = this.server.getCommands().getDispatcher().parse(stringreader, this.player.createCommandSourceStack()); ++ // Paper start - Handle non-recoverable exceptions ++ if (!parseresults.getExceptions().isEmpty() ++ && parseresults.getExceptions().values().stream().anyMatch(e -> e instanceof io.papermc.paper.brigadier.TagParseCommandSyntaxException)) { ++ this.disconnect(Component.translatable("disconnect.spam"), org.bukkit.event.player.PlayerKickEvent.Cause.SPAM); ++ return; ++ } ++ // Paper end - Handle non-recoverable exceptions + + this.server.getCommands().getDispatcher().getCompletionSuggestions(parseresults).thenAccept((suggestions) -> { + // Paper start - Don't tab-complete namespaced commands if send-namespaced is false diff --git a/patches/server/0938-Configurable-max-block-fluid-ticks.patch b/patches/server/0938-Configurable-max-block-fluid-ticks.patch deleted file mode 100644 index 2d7a50b998a5..000000000000 --- a/patches/server/0938-Configurable-max-block-fluid-ticks.patch +++ /dev/null @@ -1,22 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Gero -Date: Mon, 19 Feb 2024 17:39:59 +0100 -Subject: [PATCH] Configurable max block/fluid ticks - - -diff --git a/src/main/java/net/minecraft/server/level/ServerLevel.java b/src/main/java/net/minecraft/server/level/ServerLevel.java -index dcd024afef50c90974723632c879258fb1dda073..81f0a0dfefd9e1698548a68679dab0974921c37a 100644 ---- a/src/main/java/net/minecraft/server/level/ServerLevel.java -+++ b/src/main/java/net/minecraft/server/level/ServerLevel.java -@@ -501,9 +501,9 @@ public class ServerLevel extends Level implements WorldGenLevel { - if (!this.isDebug() && flag) { - j = this.getGameTime(); - gameprofilerfiller.push("blockTicks"); -- this.blockTicks.tick(j, 65536, this::tickBlock); -+ this.blockTicks.tick(j, paperConfig().environment.maxBlockTicks, this::tickBlock); // Paper - configurable max block ticks - gameprofilerfiller.popPush("fluidTicks"); -- this.fluidTicks.tick(j, 65536, this::tickFluid); -+ this.fluidTicks.tick(j, paperConfig().environment.maxFluidTicks, this::tickFluid); // Paper - configurable max fluid ticks - gameprofilerfiller.pop(); - } - this.timings.scheduledBlocks.stopTiming(); // Paper diff --git a/patches/server/0938-Item-Mutation-Fixes.patch b/patches/server/0938-Item-Mutation-Fixes.patch new file mode 100644 index 000000000000..f7242e591f91 --- /dev/null +++ b/patches/server/0938-Item-Mutation-Fixes.patch @@ -0,0 +1,38 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Owen1212055 <23108066+Owen1212055@users.noreply.github.com> +Date: Wed, 20 Mar 2024 20:41:35 -0400 +Subject: [PATCH] Item Mutation Fixes + + +diff --git a/src/main/java/net/minecraft/world/inventory/EnchantmentMenu.java b/src/main/java/net/minecraft/world/inventory/EnchantmentMenu.java +index 36496144dd6fa87163b692034570eba70c83678c..b7300052f3c3d496ea41b681a2d5d5b554e67c63 100644 +--- a/src/main/java/net/minecraft/world/inventory/EnchantmentMenu.java ++++ b/src/main/java/net/minecraft/world/inventory/EnchantmentMenu.java +@@ -220,7 +220,7 @@ public class EnchantmentMenu extends AbstractContainerMenu { + return false; + } else if (this.costs[id] > 0 && !itemstack.isEmpty() && (player.experienceLevel >= j && player.experienceLevel >= this.costs[id] || player.getAbilities().instabuild)) { + this.access.execute((world, blockposition) -> { +- ItemStack itemstack2 = itemstack; ++ ItemStack itemstack2 = itemstack; // Paper - diff on change + List list = this.getEnchantmentList(world.registryAccess(), itemstack, id, this.costs[id]); + + // CraftBukkit start +@@ -243,10 +243,16 @@ public class EnchantmentMenu extends AbstractContainerMenu { + return; + } + // CraftBukkit end +- if (itemstack.is(Items.BOOK)) { +- itemstack2 = itemstack.transmuteCopy(Items.ENCHANTED_BOOK); ++ // Paper start ++ itemstack2 = org.bukkit.craftbukkit.inventory.CraftItemStack.getOrCloneOnMutation(item, event.getItem()); ++ if (itemstack2 != itemstack) { + this.enchantSlots.setItem(0, itemstack2); + } ++ if (itemstack2.is(Items.BOOK)) { ++ itemstack2 = itemstack2.transmuteCopy(Items.ENCHANTED_BOOK); ++ this.enchantSlots.setItem(0, itemstack2); ++ } ++ // Paper end + + // CraftBukkit start + for (Map.Entry entry : event.getEnchantsToAdd().entrySet()) { diff --git a/patches/server/0939-Fix-bees-aging-inside-hives.patch b/patches/server/0939-Fix-bees-aging-inside-hives.patch deleted file mode 100644 index 400ede116026..000000000000 --- a/patches/server/0939-Fix-bees-aging-inside-hives.patch +++ /dev/null @@ -1,41 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Jake Potrebic -Date: Sat, 21 Aug 2021 21:54:16 -0700 -Subject: [PATCH] Fix bees aging inside hives - -Fixes bees incorrectly being aged up due to upstream's -resetting the ticks inside hive on a failed release - -diff --git a/src/main/java/net/minecraft/world/level/block/entity/BeehiveBlockEntity.java b/src/main/java/net/minecraft/world/level/block/entity/BeehiveBlockEntity.java -index 1a3d8755c8f6a7cfe06069e2082d8147aaaff097..f933fa419a4b55b0096ff42caf1b071d027b8e7e 100644 ---- a/src/main/java/net/minecraft/world/level/block/entity/BeehiveBlockEntity.java -+++ b/src/main/java/net/minecraft/world/level/block/entity/BeehiveBlockEntity.java -@@ -314,7 +314,7 @@ public class BeehiveBlockEntity extends BlockEntity { - iterator.remove(); - // CraftBukkit start - } else { -- tileentitybeehive_hivebee.ticksInHive = tileentitybeehive_hivebee.occupant.minTicksInHive / 2; // Not strictly Vanilla behaviour in cases where bees cannot spawn but still reasonable -+ tileentitybeehive_hivebee.exitTickCounter = tileentitybeehive_hivebee.occupant.minTicksInHive / 2; // Not strictly Vanilla behaviour in cases where bees cannot spawn but still reasonable // Paper - Fix bees aging inside hives; use exitTickCounter to keep actual bee life - // CraftBukkit end - } - } -@@ -474,15 +474,18 @@ public class BeehiveBlockEntity extends BlockEntity { - private static class BeeData { - - private final BeehiveBlockEntity.Occupant occupant; -+ private int exitTickCounter; // Paper - Fix bees aging inside hives; separate counter for checking if bee should exit to reduce exit attempts - private int ticksInHive; - - BeeData(BeehiveBlockEntity.Occupant data) { - this.occupant = data; - this.ticksInHive = data.ticksInHive(); -+ this.exitTickCounter = this.ticksInHive; // Paper - Fix bees aging inside hives - } - - public boolean tick() { -- return this.ticksInHive++ > this.occupant.minTicksInHive; -+ this.ticksInHive++; // Paper - Fix bees aging inside hives -+ return this.exitTickCounter++ > this.occupant.minTicksInHive; // Paper - Fix bees aging inside hives - } - - public BeehiveBlockEntity.Occupant toOccupant() { diff --git a/patches/server/0939-Per-world-ticks-per-spawn-settings.patch b/patches/server/0939-Per-world-ticks-per-spawn-settings.patch new file mode 100644 index 000000000000..2746e3483b59 --- /dev/null +++ b/patches/server/0939-Per-world-ticks-per-spawn-settings.patch @@ -0,0 +1,35 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Jake Potrebic +Date: Sat, 13 Nov 2021 12:36:26 -0800 +Subject: [PATCH] Per world ticks per spawn settings + + +diff --git a/src/main/java/net/minecraft/world/level/Level.java b/src/main/java/net/minecraft/world/level/Level.java +index 01fbefdbed48ab85481c811cca532c91860626f7..083b72ebacfbba22af2230fb69b311aeee62cb6a 100644 +--- a/src/main/java/net/minecraft/world/level/Level.java ++++ b/src/main/java/net/minecraft/world/level/Level.java +@@ -185,6 +185,15 @@ public abstract class Level implements LevelAccessor, AutoCloseable { + return this.getChunkIfLoaded(chunkX, chunkZ) != null; + } + // Paper end - Use getChunkIfLoadedImmediately ++ // Paper start - per world ticks per spawn ++ private int getTicksPerSpawn(SpawnCategory spawnCategory) { ++ final int perWorld = this.paperConfig().entities.spawning.ticksPerSpawn.getInt(CraftSpawnCategory.toNMS(spawnCategory)); ++ if (perWorld >= 0) { ++ return perWorld; ++ } ++ return this.getCraftServer().getTicksPerSpawns(spawnCategory); ++ } ++ // Paper end + + + public abstract ResourceKey getTypeKey(); +@@ -198,7 +207,7 @@ public abstract class Level implements LevelAccessor, AutoCloseable { + // CraftBukkit Ticks things + for (SpawnCategory spawnCategory : SpawnCategory.values()) { + if (CraftSpawnCategory.isValidForLimits(spawnCategory)) { +- this.ticksPerSpawnCategory.put(spawnCategory, (long) this.getCraftServer().getTicksPerSpawns(spawnCategory)); ++ this.ticksPerSpawnCategory.put(spawnCategory, this.getTicksPerSpawn(spawnCategory)); // Paper + } + } + diff --git a/patches/server/0940-Properly-track-the-changed-item-from-dispense-events.patch b/patches/server/0940-Properly-track-the-changed-item-from-dispense-events.patch new file mode 100644 index 000000000000..872400fdee81 --- /dev/null +++ b/patches/server/0940-Properly-track-the-changed-item-from-dispense-events.patch @@ -0,0 +1,104 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Jake Potrebic +Date: Mon, 12 Dec 2022 12:14:03 -0800 +Subject: [PATCH] Properly track the changed item from dispense events + + +diff --git a/src/main/java/net/minecraft/core/dispenser/DispenseItemBehavior.java b/src/main/java/net/minecraft/core/dispenser/DispenseItemBehavior.java +index 681c38f4457fc10806c10518b16159580c0f2619..8f9b86e50717746e55232293d9e5ac05b8616aa0 100644 +--- a/src/main/java/net/minecraft/core/dispenser/DispenseItemBehavior.java ++++ b/src/main/java/net/minecraft/core/dispenser/DispenseItemBehavior.java +@@ -129,10 +129,14 @@ public interface DispenseItemBehavior { + idispensebehavior.dispense(pointer, eventStack); + return stack; + } ++ // Paper start - track changed items in the dispense event ++ itemstack1 = CraftItemStack.unwrap(event.getItem()); // unwrap is safe because the stack won't be modified ++ entitytypes = ((SpawnEggItem) itemstack1.getItem()).getType(itemstack1); ++ // Paper end - track changed item from dispense event + } + + try { +- entitytypes.spawn(pointer.level(), stack, (Player) null, pointer.pos().relative(enumdirection), EntitySpawnReason.DISPENSER, enumdirection != Direction.UP, false); ++ entitytypes.spawn(pointer.level(), itemstack1, (Player) null, pointer.pos().relative(enumdirection), EntitySpawnReason.DISPENSER, enumdirection != Direction.UP, false); // Paper - track changed item in dispense event + } catch (Exception exception) { + DispenseItemBehavior.LOGGER.error("Error while dispensing spawn egg from dispenser at {}", pointer.pos(), exception); // CraftBukkit - decompile error + return ItemStack.EMPTY; +@@ -186,9 +190,10 @@ public interface DispenseItemBehavior { + } + // CraftBukkit end + ++ final ItemStack newStack = CraftItemStack.unwrap(event.getItem()); // Paper - use event itemstack (unwrap is fine here because the stack won't be modified) + Consumer consumer = EntityType.appendDefaultStackConfig((entityarmorstand) -> { + entityarmorstand.setYRot(enumdirection.toYRot()); +- }, worldserver, stack, (Player) null); ++ }, worldserver, newStack, (Player) null); // Paper - track changed items in the dispense event + ArmorStand entityarmorstand = (ArmorStand) EntityType.ARMOR_STAND.spawn(worldserver, consumer, blockposition, EntitySpawnReason.DISPENSER, false, false); + + if (entityarmorstand != null) { +@@ -237,7 +242,7 @@ public interface DispenseItemBehavior { + return stack; + } + } +- ((Saddleable) list.get(0)).equipSaddle(itemstack1, SoundSource.BLOCKS); ++ ((Saddleable) list.get(0)).equipSaddle(CraftItemStack.asNMSCopy(event.getItem()), SoundSource.BLOCKS); // Paper - track changed items in dispense event + // CraftBukkit end + this.setSuccess(true); + return stack; +@@ -330,6 +335,7 @@ public interface DispenseItemBehavior { + int y = blockposition.getY(); + int z = blockposition.getZ(); + BlockState iblockdata = worldserver.getBlockState(blockposition); ++ ItemStack dispensedItem = stack; // Paper - track changed item from the dispense event + // Paper start - correctly check if the bucket place will succeed + /* Taken from SolidBucketItem#emptyContents */ + boolean willEmptyContentsSolidBucketItem = dispensiblecontaineritem instanceof net.minecraft.world.item.SolidBucketItem && worldserver.isInWorldBounds(blockposition) && iblockdata.isAir(); +@@ -359,12 +365,15 @@ public interface DispenseItemBehavior { + } + } + +- dispensiblecontaineritem = (DispensibleContainerItem) CraftItemStack.asNMSCopy(event.getItem()).getItem(); ++ // Paper start - track changed item from dispense event ++ dispensedItem = CraftItemStack.unwrap(event.getItem()); // unwrap is safe here as the stack isn't mutated ++ dispensiblecontaineritem = (DispensibleContainerItem) dispensedItem.getItem(); ++ // Paper end - track changed item from dispense event + } + // CraftBukkit end + + if (dispensiblecontaineritem.emptyContents((Player) null, worldserver, blockposition, (BlockHitResult) null)) { +- dispensiblecontaineritem.checkExtraContent((Player) null, worldserver, stack, blockposition); ++ dispensiblecontaineritem.checkExtraContent((Player) null, worldserver, dispensedItem, blockposition); // Paper - track changed item from dispense event + return this.consumeWithRemainder(pointer, stack, new ItemStack(Items.BUCKET)); + } else { + return this.defaultDispenseItemBehavior.dispense(pointer, stack); +diff --git a/src/main/java/net/minecraft/core/dispenser/ProjectileDispenseBehavior.java b/src/main/java/net/minecraft/core/dispenser/ProjectileDispenseBehavior.java +index aba0ddfc5009a11b6c5cba95a90479083643bdad..b341792b694e4d6d7ca98061976b8857d83c4233 100644 +--- a/src/main/java/net/minecraft/core/dispenser/ProjectileDispenseBehavior.java ++++ b/src/main/java/net/minecraft/core/dispenser/ProjectileDispenseBehavior.java +@@ -64,7 +64,7 @@ public class ProjectileDispenseBehavior extends DefaultDispenseItemBehavior { + } + } + +- Projectile iprojectile = Projectile.spawnProjectileUsingShoot(this.projectileItem.asProjectile(worldserver, iposition, stack, enumdirection), worldserver, stack, event.getVelocity().getX(), event.getVelocity().getY(), event.getVelocity().getZ(), this.dispenseConfig.power(), this.dispenseConfig.uncertainty()); ++ Projectile iprojectile = Projectile.spawnProjectileUsingShoot(this.projectileItem.asProjectile(worldserver, iposition, CraftItemStack.unwrap(event.getItem()), enumdirection), worldserver, stack, event.getVelocity().getX(), event.getVelocity().getY(), event.getVelocity().getZ(), this.dispenseConfig.power(), this.dispenseConfig.uncertainty()); // Paper - track changed items in the dispense event; unwrap is safe here because all uses of the stack make their own copies + ((Entity) iprojectile).projectileSource = new org.bukkit.craftbukkit.projectiles.CraftBlockProjectileSource(pointer.blockEntity()); + // itemstack.shrink(1); // CraftBukkit - Handled during event processing + // CraftBukkit end +diff --git a/src/main/java/net/minecraft/core/dispenser/ShulkerBoxDispenseBehavior.java b/src/main/java/net/minecraft/core/dispenser/ShulkerBoxDispenseBehavior.java +index cc85e96035f7cb2e6493b1cc4748031171d7dbee..16b435216dc7c6a3f8c1c0f9e2323e6afb3a6cb9 100644 +--- a/src/main/java/net/minecraft/core/dispenser/ShulkerBoxDispenseBehavior.java ++++ b/src/main/java/net/minecraft/core/dispenser/ShulkerBoxDispenseBehavior.java +@@ -57,7 +57,12 @@ public class ShulkerBoxDispenseBehavior extends OptionalDispenseItemBehavior { + // CraftBukkit end + + try { +- this.setSuccess(((BlockItem) item).place(new DirectionalPlaceContext(pointer.level(), blockposition, enumdirection, stack, enumdirection1)).consumesAction()); ++ // Paper start - track changed items in the dispense event ++ this.setSuccess(((BlockItem) item).place(new DirectionalPlaceContext(pointer.level(), blockposition, enumdirection, CraftItemStack.asNMSCopy(event.getItem()), enumdirection1)).consumesAction()); ++ if (this.isSuccess()) { ++ stack.shrink(1); // vanilla shrink is in the place function above, manually handle it here ++ } ++ // Paper end - track changed items in the dispense event + } catch (Exception exception) { + ShulkerBoxDispenseBehavior.LOGGER.error("Error trying to place shulker box at {}", blockposition, exception); + } diff --git a/patches/server/0941-Fire-EntityDamageByEntityEvent-for-unowned-wither-sk.patch b/patches/server/0941-Fire-EntityDamageByEntityEvent-for-unowned-wither-sk.patch deleted file mode 100644 index d98b3aeccc56..000000000000 --- a/patches/server/0941-Fire-EntityDamageByEntityEvent-for-unowned-wither-sk.patch +++ /dev/null @@ -1,19 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: RodneyMKay <36546810+RodneyMKay@users.noreply.github.com> -Date: Sun, 11 Feb 2024 20:05:11 +0100 -Subject: [PATCH] Fire EntityDamageByEntityEvent for unowned wither skulls - - -diff --git a/src/main/java/net/minecraft/world/entity/projectile/WitherSkull.java b/src/main/java/net/minecraft/world/entity/projectile/WitherSkull.java -index 315610e95c91a0db096bf572789a40e746e72ebe..60eac9df10a9a395a1568925515d010eb51a64e5 100644 ---- a/src/main/java/net/minecraft/world/entity/projectile/WitherSkull.java -+++ b/src/main/java/net/minecraft/world/entity/projectile/WitherSkull.java -@@ -77,7 +77,7 @@ public class WitherSkull extends AbstractHurtingProjectile { - } - } - } else { -- flag = entity.hurt(this.damageSources().magic(), 5.0F); -+ flag = entity.hurt(this.damageSources().magic().customCausingEntity(this), 5.0F); // Paper - Fire EntityDamageByEntityEvent for unowned wither skulls - } - - if (flag && entity instanceof LivingEntity entityliving) { diff --git a/patches/server/0941-Protect-Bedrock-and-End-Portal-Frames-from-being-des.patch b/patches/server/0941-Protect-Bedrock-and-End-Portal-Frames-from-being-des.patch new file mode 100644 index 000000000000..5c06384279e0 --- /dev/null +++ b/patches/server/0941-Protect-Bedrock-and-End-Portal-Frames-from-being-des.patch @@ -0,0 +1,171 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Aikar +Date: Wed, 13 May 2020 23:01:26 -0400 +Subject: [PATCH] Protect Bedrock and End Portal/Frames from being destroyed + +This fixes exploits that let players destroy bedrock by Pistons, explosions +and Mushrooom/Tree generation. + +These blocks are designed to not be broken except by creative players/commands. +So protect them from a multitude of methods of destroying them. + +A config is provided if you rather let players use these exploits, and let +them destroy the worlds End Portals and get on top of the nether easy. + +diff --git a/src/main/java/net/minecraft/world/level/Level.java b/src/main/java/net/minecraft/world/level/Level.java +index 083b72ebacfbba22af2230fb69b311aeee62cb6a..ba4006bc7dc31d10f37023cba7995a9621796f73 100644 +--- a/src/main/java/net/minecraft/world/level/Level.java ++++ b/src/main/java/net/minecraft/world/level/Level.java +@@ -445,6 +445,10 @@ public abstract class Level implements LevelAccessor, AutoCloseable { + public boolean setBlock(BlockPos pos, BlockState state, int flags, int maxUpdateDepth) { + // CraftBukkit start - tree generation + if (this.captureTreeGeneration) { ++ // Paper start - Protect Bedrock and End Portal/Frames from being destroyed ++ BlockState type = getBlockState(pos); ++ if (!type.isDestroyable()) return false; ++ // Paper end - Protect Bedrock and End Portal/Frames from being destroyed + CraftBlockState blockstate = this.capturedBlockStates.get(pos); + if (blockstate == null) { + blockstate = CapturedBlockState.getTreeBlockState(this, pos, flags); +diff --git a/src/main/java/net/minecraft/world/level/ServerExplosion.java b/src/main/java/net/minecraft/world/level/ServerExplosion.java +index 86656de31b1e33381eddd3ef210122118b31e620..fd1ecedfab037e377e4dded61539689bacc90f80 100644 +--- a/src/main/java/net/minecraft/world/level/ServerExplosion.java ++++ b/src/main/java/net/minecraft/world/level/ServerExplosion.java +@@ -150,6 +150,7 @@ public class ServerExplosion implements Explosion { + for (float f1 = 0.3F; f > 0.0F; f -= 0.22500001F) { + BlockPos blockposition = BlockPos.containing(d4, d5, d6); + BlockState iblockdata = this.level.getBlockState(blockposition); ++ if (!iblockdata.isDestroyable()) continue; // Paper - Protect Bedrock and End Portal/Frames from being destroyed + FluidState fluid = iblockdata.getFluidState(); // Paper - Perf: Optimize call to getFluid for explosions + + if (!this.level.isInWorldBounds(blockposition)) { +diff --git a/src/main/java/net/minecraft/world/level/block/Block.java b/src/main/java/net/minecraft/world/level/block/Block.java +index dc242451f397ae6a30b830ef1f211f1a066423a4..0f7b73634930df02d7b0a7f44890597cc2e6deca 100644 +--- a/src/main/java/net/minecraft/world/level/block/Block.java ++++ b/src/main/java/net/minecraft/world/level/block/Block.java +@@ -88,6 +88,21 @@ public class Block extends BlockBehaviour implements ItemLike { + public static final int UPDATE_LIMIT = 512; + protected final StateDefinition stateDefinition; + private BlockState defaultBlockState; ++ // Paper start - Protect Bedrock and End Portal/Frames from being destroyed ++ public final boolean isDestroyable() { ++ return io.papermc.paper.configuration.GlobalConfiguration.get().unsupportedSettings.allowPermanentBlockBreakExploits || ++ this != Blocks.BARRIER && ++ this != Blocks.BEDROCK && ++ this != Blocks.END_PORTAL_FRAME && ++ this != Blocks.END_PORTAL && ++ this != Blocks.END_GATEWAY && ++ this != Blocks.COMMAND_BLOCK && ++ this != Blocks.REPEATING_COMMAND_BLOCK && ++ this != Blocks.CHAIN_COMMAND_BLOCK && ++ this != Blocks.STRUCTURE_BLOCK && ++ this != Blocks.JIGSAW; ++ } ++ // Paper end - Protect Bedrock and End Portal/Frames from being destroyed + @Nullable + private Item item; + private static final int CACHE_SIZE = 256; +diff --git a/src/main/java/net/minecraft/world/level/block/piston/PistonBaseBlock.java b/src/main/java/net/minecraft/world/level/block/piston/PistonBaseBlock.java +index e841fccb8f298ef692677583b468869f56dc722c..4b51472502d08ea357da437afeb4b581979e9cff 100644 +--- a/src/main/java/net/minecraft/world/level/block/piston/PistonBaseBlock.java ++++ b/src/main/java/net/minecraft/world/level/block/piston/PistonBaseBlock.java +@@ -216,6 +216,12 @@ public class PistonBaseBlock extends DirectionalBlock { + @Override + protected boolean triggerEvent(BlockState state, Level world, BlockPos pos, int type, int data) { + Direction enumdirection = (Direction) state.getValue(PistonBaseBlock.FACING); ++ // Paper start - Protect Bedrock and End Portal/Frames from being destroyed; prevent retracting when we're facing the wrong way (we were replaced before retraction could occur) ++ Direction directionQueuedAs = Direction.from3DDataValue(data & 7); // Paper - copied from below ++ if (!io.papermc.paper.configuration.GlobalConfiguration.get().unsupportedSettings.allowPermanentBlockBreakExploits && enumdirection != directionQueuedAs) { ++ return false; ++ } ++ // Paper end - Protect Bedrock and End Portal/Frames from being destroyed + BlockState iblockdata1 = (BlockState) state.setValue(PistonBaseBlock.EXTENDED, true); + + if (!world.isClientSide) { +@@ -256,7 +262,7 @@ public class PistonBaseBlock extends DirectionalBlock { + } + // Paper end - Fix sticky pistons and BlockPistonRetractEvent + world.setBlock(pos, iblockdata2, 20); +- world.setBlockEntity(MovingPistonBlock.newMovingBlockEntity(pos, iblockdata2, (BlockState) this.defaultBlockState().setValue(PistonBaseBlock.FACING, Direction.from3DDataValue(data & 7)), enumdirection, false, true)); ++ world.setBlockEntity(MovingPistonBlock.newMovingBlockEntity(pos, iblockdata2, (BlockState) this.defaultBlockState().setValue(PistonBaseBlock.FACING, Direction.from3DDataValue(data & 7)), enumdirection, false, true)); // Paper - Protect Bedrock and End Portal/Frames from being destroyed; diff on change + world.blockUpdated(pos, iblockdata2.getBlock()); + iblockdata2.updateNeighbourShapes(world, pos, 2); + if (this.isSticky) { +@@ -292,7 +298,14 @@ public class PistonBaseBlock extends DirectionalBlock { + } + } + } else { +- world.removeBlock(pos.relative(enumdirection), false); ++ // Paper start - Protect Bedrock and End Portal/Frames from being destroyed; fix headless pistons breaking blocks ++ BlockPos headPos = pos.relative(enumdirection); ++ if (io.papermc.paper.configuration.GlobalConfiguration.get().unsupportedSettings.allowPermanentBlockBreakExploits || world.getBlockState(headPos) == Blocks.PISTON_HEAD.defaultBlockState().setValue(FACING, enumdirection)) { // double check to make sure we're not a headless piston. ++ world.removeBlock(headPos, false); ++ } else { ++ ((ServerLevel) world).getChunkSource().blockChanged(headPos); // ... fix client desync ++ } ++ // Paper end - Protect Bedrock and End Portal/Frames from being destroyed + } + + world.playSound((Player) null, pos, SoundEvents.PISTON_CONTRACT, SoundSource.BLOCKS, 0.5F, world.random.nextFloat() * 0.15F + 0.6F); +diff --git a/src/main/java/net/minecraft/world/level/block/state/BlockBehaviour.java b/src/main/java/net/minecraft/world/level/block/state/BlockBehaviour.java +index 1b988b92e80faa1ac224caf9f9e955ac43a4c45a..95d30c2db7e291d65c24feb114b0f3598d280912 100644 +--- a/src/main/java/net/minecraft/world/level/block/state/BlockBehaviour.java ++++ b/src/main/java/net/minecraft/world/level/block/state/BlockBehaviour.java +@@ -178,7 +178,7 @@ public abstract class BlockBehaviour implements FeatureElement { + } + + protected void onExplosionHit(BlockState state, ServerLevel world, BlockPos pos, Explosion explosion, BiConsumer stackMerger) { +- if (!state.isAir() && explosion.getBlockInteraction() != Explosion.BlockInteraction.TRIGGER_BLOCK) { ++ if (!state.isAir() && explosion.getBlockInteraction() != Explosion.BlockInteraction.TRIGGER_BLOCK && state.isDestroyable()) { // Paper - Protect Bedrock and End Portal/Frames from being destroyed + Block block = state.getBlock(); + boolean flag = explosion.getIndirectSourceEntity() instanceof Player; + +@@ -257,7 +257,7 @@ public abstract class BlockBehaviour implements FeatureElement { + } + + protected boolean canBeReplaced(BlockState state, BlockPlaceContext context) { +- return state.canBeReplaced() && (context.getItemInHand().isEmpty() || !context.getItemInHand().is(this.asItem())); ++ return state.canBeReplaced() && (context.getItemInHand().isEmpty() || !context.getItemInHand().is(this.asItem())) && (state.isDestroyable() || (context.getPlayer() != null && context.getPlayer().getAbilities().instabuild)); // Paper - Protect Bedrock and End Portal/Frames from being destroyed + } + + protected boolean canBeReplaced(BlockState state, Fluid fluid) { +@@ -945,6 +945,12 @@ public abstract class BlockBehaviour implements FeatureElement { + return this.legacySolid; + } + ++ // Paper start - Protect Bedrock and End Portal/Frames from being destroyed ++ public final boolean isDestroyable() { ++ return getBlock().isDestroyable(); ++ } ++ // Paper end - Protect Bedrock and End Portal/Frames from being destroyed ++ + public boolean isValidSpawn(BlockGetter world, BlockPos pos, EntityType type) { + return this.getBlock().properties.isValidSpawn.test(this.asState(), world, pos, type); + } +@@ -1048,7 +1054,7 @@ public abstract class BlockBehaviour implements FeatureElement { + } + + public PushReaction getPistonPushReaction() { +- return this.pushReaction; ++ return !this.isDestroyable() ? PushReaction.BLOCK : this.pushReaction; // Paper - Protect Bedrock and End Portal/Frames from being destroyed + } + + public boolean isSolidRender() { +diff --git a/src/main/java/net/minecraft/world/level/portal/PortalForcer.java b/src/main/java/net/minecraft/world/level/portal/PortalForcer.java +index eb409fb5e673d2a343813946cc59cb5da2328eec..83d294f6f48b867d09ea0d339c779011bf4138a5 100644 +--- a/src/main/java/net/minecraft/world/level/portal/PortalForcer.java ++++ b/src/main/java/net/minecraft/world/level/portal/PortalForcer.java +@@ -207,6 +207,13 @@ public class PortalForcer { + for (int j = -1; j < 3; ++j) { + for (int k = -1; k < 4; ++k) { + temp.setWithOffset(pos, portalDirection.getStepX() * j + enumdirection1.getStepX() * distanceOrthogonalToPortal, k, portalDirection.getStepZ() * j + enumdirection1.getStepZ() * distanceOrthogonalToPortal); ++ // Paper start - Protect Bedrock and End Portal/Frames from being destroyed ++ if (!io.papermc.paper.configuration.GlobalConfiguration.get().unsupportedSettings.allowPermanentBlockBreakExploits) { ++ if (!this.level.getBlockState(temp).isDestroyable()) { ++ return false; ++ } ++ } ++ // Paper end - Protect Bedrock and End Portal/Frames from being destroyed + if (k < 0 && !this.level.getBlockState(temp).isSolid()) { + return false; + } diff --git a/patches/server/0942-Add-config-for-mobs-immune-to-default-effects.patch b/patches/server/0942-Add-config-for-mobs-immune-to-default-effects.patch new file mode 100644 index 000000000000..c3b01a16eda7 --- /dev/null +++ b/patches/server/0942-Add-config-for-mobs-immune-to-default-effects.patch @@ -0,0 +1,44 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Jake Potrebic +Date: Wed, 2 Dec 2020 21:03:02 -0800 +Subject: [PATCH] Add config for mobs immune to default effects + + +diff --git a/src/main/java/net/minecraft/world/entity/boss/wither/WitherBoss.java b/src/main/java/net/minecraft/world/entity/boss/wither/WitherBoss.java +index e1be143959fbaa1d54af2a1a2c27187d70e6a9e9..244e38db508efa3eebebb6392c4ebb0805367baf 100644 +--- a/src/main/java/net/minecraft/world/entity/boss/wither/WitherBoss.java ++++ b/src/main/java/net/minecraft/world/entity/boss/wither/WitherBoss.java +@@ -602,7 +602,7 @@ public class WitherBoss extends Monster implements RangedAttackMob { + + @Override + public boolean canBeAffected(MobEffectInstance effect) { +- return effect.is(MobEffects.WITHER) ? false : super.canBeAffected(effect); ++ return effect.is(MobEffects.WITHER) && this.level().paperConfig().entities.mobEffects.immuneToWitherEffect.wither ? false : super.canBeAffected(effect); // Paper - Add config for mobs immune to default effects + } + + private class WitherDoNothingGoal extends Goal { +diff --git a/src/main/java/net/minecraft/world/entity/monster/Spider.java b/src/main/java/net/minecraft/world/entity/monster/Spider.java +index 72e42605c278028480c368762da18f61806d766a..91e521414c3ea5722aac7506b7589fbb399e9636 100644 +--- a/src/main/java/net/minecraft/world/entity/monster/Spider.java ++++ b/src/main/java/net/minecraft/world/entity/monster/Spider.java +@@ -126,7 +126,7 @@ public class Spider extends Monster { + + @Override + public boolean canBeAffected(MobEffectInstance effect) { +- return effect.is(MobEffects.POISON) ? false : super.canBeAffected(effect); ++ return effect.is(MobEffects.POISON) && this.level().paperConfig().entities.mobEffects.spidersImmuneToPoisonEffect ? false : super.canBeAffected(effect); // Paper - Add config for mobs immune to default effects + } + + public boolean isClimbing() { +diff --git a/src/main/java/net/minecraft/world/entity/monster/WitherSkeleton.java b/src/main/java/net/minecraft/world/entity/monster/WitherSkeleton.java +index c295b604f438c62d589ab05ea44c85dbefcb258b..37d3acda84a984bf4f1c44b3d27e2102839d3e8e 100644 +--- a/src/main/java/net/minecraft/world/entity/monster/WitherSkeleton.java ++++ b/src/main/java/net/minecraft/world/entity/monster/WitherSkeleton.java +@@ -114,6 +114,6 @@ public class WitherSkeleton extends AbstractSkeleton { + + @Override + public boolean canBeAffected(MobEffectInstance effect) { +- return effect.is(MobEffects.WITHER) ? false : super.canBeAffected(effect); ++ return effect.is(MobEffects.WITHER) && this.level().paperConfig().entities.mobEffects.immuneToWitherEffect.witherSkeleton ? false : super.canBeAffected(effect); // Paper - Add config for mobs immune to default effects + } + } diff --git a/patches/server/0942-Fix-DamageSource-API.patch b/patches/server/0942-Fix-DamageSource-API.patch deleted file mode 100644 index f92badc241fe..000000000000 --- a/patches/server/0942-Fix-DamageSource-API.patch +++ /dev/null @@ -1,234 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Jake Potrebic -Date: Sat, 9 Mar 2024 14:13:04 -0800 -Subject: [PATCH] Fix DamageSource API - -Uses the correct entity in the EntityDamageByEntity event -Returns the correct entity for API's DamageSource#getCausingEntity - -diff --git a/src/main/java/net/minecraft/world/damagesource/DamageSource.java b/src/main/java/net/minecraft/world/damagesource/DamageSource.java -index aee26dd78953ff43306aaa64161f5b9edcdd4b83..bb1a60180e58c1333e7bb33e8acf1b0225eda8a8 100644 ---- a/src/main/java/net/minecraft/world/damagesource/DamageSource.java -+++ b/src/main/java/net/minecraft/world/damagesource/DamageSource.java -@@ -29,8 +29,8 @@ public class DamageSource { - private boolean sweep = false; - private boolean melting = false; - private boolean poison = false; -- private Entity customEntityDamager = null; // This field is a helper for when direct entity damage is not set by vanilla -- private Entity customCausingEntityDamager = null; // This field is a helper for when causing entity damage is not set by vanilla -+ @Nullable -+ private Entity customEventDamager = null; // This field is a helper for when causing entity damage is not set by vanilla // Paper - fix DamageSource API - - public DamageSource sweep() { - this.sweep = true; -@@ -59,33 +59,19 @@ public class DamageSource { - return this.poison; - } - -- public Entity getDamager() { -- return (this.customEntityDamager != null) ? this.customEntityDamager : this.directEntity; -- } -- -- public Entity getCausingDamager() { -- return (this.customCausingEntityDamager != null) ? this.customCausingEntityDamager : this.causingEntity; -- } -- -- public DamageSource customEntityDamager(Entity entity) { -- // This method is not intended for change the causing entity if is already set -- // also is only necessary if the entity passed is not the direct entity or different from the current causingEntity -- if (this.customEntityDamager != null || this.directEntity == entity || this.causingEntity == entity) { -- return this; -- } -- DamageSource damageSource = this.cloneInstance(); -- damageSource.customEntityDamager = entity; -- return damageSource; -+ // Paper start - fix DamageSource API -+ @Nullable -+ public Entity getCustomEventDamager() { -+ return (this.customEventDamager != null) ? this.customEventDamager : this.directEntity; - } - -- public DamageSource customCausingEntityDamager(Entity entity) { -- // This method is not intended for change the causing entity if is already set -- // also is only necessary if the entity passed is not the direct entity or different from the current causingEntity -- if (this.customCausingEntityDamager != null || this.directEntity == entity || this.causingEntity == entity) { -- return this; -+ public DamageSource customEventDamager(Entity entity) { -+ if (this.directEntity != null) { -+ throw new IllegalStateException("Cannot set custom event damager when direct entity is already set (report a bug to Paper)"); - } - DamageSource damageSource = this.cloneInstance(); -- damageSource.customCausingEntityDamager = entity; -+ damageSource.customEventDamager = entity; -+ // Paper end - fix DamageSource API - return damageSource; - } - -diff --git a/src/main/java/net/minecraft/world/damagesource/DamageSources.java b/src/main/java/net/minecraft/world/damagesource/DamageSources.java -index caf1d79e2bbdd257a5439e2973653747e678805f..e34584e4780f343d6c946af5377088d53818e88e 100644 ---- a/src/main/java/net/minecraft/world/damagesource/DamageSources.java -+++ b/src/main/java/net/minecraft/world/damagesource/DamageSources.java -@@ -264,13 +264,7 @@ public class DamageSources { - } - - public DamageSource explosion(@Nullable Entity source, @Nullable Entity attacker) { -- // CraftBukkit start -- return this.explosion(source, attacker, attacker != null && source != null ? DamageTypes.PLAYER_EXPLOSION : DamageTypes.EXPLOSION); -- } -- -- public DamageSource explosion(@Nullable Entity entity, @Nullable Entity entity1, ResourceKey resourceKey) { -- return this.source(resourceKey, entity, entity1); -- // CraftBukkit end -+ return this.source(attacker != null && source != null ? DamageTypes.PLAYER_EXPLOSION : DamageTypes.EXPLOSION, source, attacker); // Paper - revert to vanilla - } - - public DamageSource sonicBoom(Entity attacker) { -diff --git a/src/main/java/net/minecraft/world/entity/Entity.java b/src/main/java/net/minecraft/world/entity/Entity.java -index e88c88b6f87dddda8e8e3ed215a8f2c166afa3ef..62ef618aff33c98f96d0271bb2ab42235c941052 100644 ---- a/src/main/java/net/minecraft/world/entity/Entity.java -+++ b/src/main/java/net/minecraft/world/entity/Entity.java -@@ -3262,7 +3262,7 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess - return; - } - -- if (!this.hurt(this.damageSources().lightningBolt().customEntityDamager(lightning), 5.0F)) { -+ if (!this.hurt(this.damageSources().lightningBolt().customEventDamager(lightning), 5.0F)) { // Paper - fix DamageSource API - return; - } - // CraftBukkit end -diff --git a/src/main/java/net/minecraft/world/entity/animal/Turtle.java b/src/main/java/net/minecraft/world/entity/animal/Turtle.java -index 2e5ef2a680e294b49f29e8d7ba8bd0ed023c393c..4bfa947531c4a67989e18032754dabf4c69e989c 100644 ---- a/src/main/java/net/minecraft/world/entity/animal/Turtle.java -+++ b/src/main/java/net/minecraft/world/entity/animal/Turtle.java -@@ -334,7 +334,7 @@ public class Turtle extends Animal { - - @Override - public void thunderHit(ServerLevel world, LightningBolt lightning) { -- this.hurt(this.damageSources().lightningBolt().customEntityDamager(lightning), Float.MAX_VALUE); // CraftBukkit -+ this.hurt(this.damageSources().lightningBolt().customEventDamager(lightning), Float.MAX_VALUE); // CraftBukkit // Paper - fix DamageSource API - } - - @Override -diff --git a/src/main/java/net/minecraft/world/entity/decoration/BlockAttachedEntity.java b/src/main/java/net/minecraft/world/entity/decoration/BlockAttachedEntity.java -index 7bc612890f941177da11da0ce047d5a74d8ebb33..270acce7411e5ada71eaa04c05efc888b295d9e3 100644 ---- a/src/main/java/net/minecraft/world/entity/decoration/BlockAttachedEntity.java -+++ b/src/main/java/net/minecraft/world/entity/decoration/BlockAttachedEntity.java -@@ -96,7 +96,7 @@ public abstract class BlockAttachedEntity extends Entity { - } else { - if (!this.isRemoved() && !this.level().isClientSide) { - // CraftBukkit start - fire break events -- Entity damager = (source.isDirect()) ? source.getDirectEntity() : source.getEntity(); -+ Entity damager = (!source.isDirect() && source.getEntity() != null) ? source.getEntity() : source.getDirectEntity(); // Paper - fix DamageSource API - HangingBreakEvent event; - if (damager != null) { - event = new HangingBreakByEntityEvent((Hanging) this.getBukkitEntity(), damager.getBukkitEntity(), source.is(DamageTypeTags.IS_EXPLOSION) ? HangingBreakEvent.RemoveCause.EXPLOSION : HangingBreakEvent.RemoveCause.ENTITY); -diff --git a/src/main/java/net/minecraft/world/entity/monster/Creeper.java b/src/main/java/net/minecraft/world/entity/monster/Creeper.java -index 9bf11a8b44e696b6587bc775904a836d390e437b..d1041f2a4f1b3ea29ad532006e18cdc30c5019fa 100644 ---- a/src/main/java/net/minecraft/world/entity/monster/Creeper.java -+++ b/src/main/java/net/minecraft/world/entity/monster/Creeper.java -@@ -271,7 +271,7 @@ public class Creeper extends Monster implements PowerableMob { - if (!event.isCancelled()) { - // CraftBukkit end - this.dead = true; -- this.level().explode(this, net.minecraft.world.level.Explosion.getDefaultDamageSource(this.level(), this).customCausingEntityDamager(this.entityIgniter), null, this.getX(), this.getY(), this.getZ(), event.getRadius(), event.getFire(), Level.ExplosionInteraction.MOB); // CraftBukkit -+ this.level().explode(this, this.getX(), this.getY(), this.getZ(), event.getRadius(), event.getFire(), Level.ExplosionInteraction.MOB); // CraftBukkit // Paper - fix DamageSource API (revert to vanilla, no, just no, don't change this) - this.spawnLingeringCloud(); - this.triggerOnDeathMobEffects(Entity.RemovalReason.KILLED); - this.discard(EntityRemoveEvent.Cause.EXPLODE); // CraftBukkit - add Bukkit remove cause -diff --git a/src/main/java/net/minecraft/world/entity/projectile/EvokerFangs.java b/src/main/java/net/minecraft/world/entity/projectile/EvokerFangs.java -index c75433bb0fcd4264148950467bf6b700296aca7b..820965950c8b6c868ee261cf9613665e583f092e 100644 ---- a/src/main/java/net/minecraft/world/entity/projectile/EvokerFangs.java -+++ b/src/main/java/net/minecraft/world/entity/projectile/EvokerFangs.java -@@ -135,7 +135,7 @@ public class EvokerFangs extends Entity implements TraceableEntity { - - if (target.isAlive() && !target.isInvulnerable() && target != entityliving1) { - if (entityliving1 == null) { -- target.hurt(this.damageSources().magic().customEntityDamager(this), 6.0F); // CraftBukkit -+ target.hurt(this.damageSources().magic().customEventDamager(this), 6.0F); // CraftBukkit // Paper - fix DamageSource API - } else { - if (entityliving1.isAlliedTo((Entity) target)) { - return; -diff --git a/src/main/java/net/minecraft/world/entity/projectile/ThrownEnderpearl.java b/src/main/java/net/minecraft/world/entity/projectile/ThrownEnderpearl.java -index f43dd56182ced23cf1cf65c149c532a611cc933a..1aa5e57a4e6a4be60514d8808a2e6c973d93e798 100644 ---- a/src/main/java/net/minecraft/world/entity/projectile/ThrownEnderpearl.java -+++ b/src/main/java/net/minecraft/world/entity/projectile/ThrownEnderpearl.java -@@ -89,7 +89,7 @@ public class ThrownEnderpearl extends ThrowableItemProjectile { - // entity.changeDimension(new DimensionTransition(worldserver, this.position(), entity.getDeltaMovement(), entity.getYRot(), entity.getXRot(), DimensionTransition.DO_NOTHING)); // CraftBukkit - moved up - entity.resetFallDistance(); - entityplayer.resetCurrentImpulseContext(); -- entity.hurt(this.damageSources().fall().customEntityDamager(this), 5.0F); // CraftBukkit -+ entity.hurt(this.damageSources().fall().customEventDamager(this), 5.0F); // CraftBukkit // Paper - fix DamageSource API - this.playSound(worldserver, this.position()); - } - } else { -diff --git a/src/main/java/net/minecraft/world/entity/projectile/WitherSkull.java b/src/main/java/net/minecraft/world/entity/projectile/WitherSkull.java -index 60eac9df10a9a395a1568925515d010eb51a64e5..55fd997a4e894eeab24de269d59e486196ffbe8d 100644 ---- a/src/main/java/net/minecraft/world/entity/projectile/WitherSkull.java -+++ b/src/main/java/net/minecraft/world/entity/projectile/WitherSkull.java -@@ -77,7 +77,7 @@ public class WitherSkull extends AbstractHurtingProjectile { - } - } - } else { -- flag = entity.hurt(this.damageSources().magic().customCausingEntity(this), 5.0F); // Paper - Fire EntityDamageByEntityEvent for unowned wither skulls -+ flag = entity.hurt(this.damageSources().magic().customEventDamager(this), 5.0F); // Paper - Fire EntityDamageByEntityEvent for unowned wither skulls // Paper - fix DamageSource API - } - - if (flag && entity instanceof LivingEntity entityliving) { -diff --git a/src/main/java/org/bukkit/craftbukkit/damage/CraftDamageSource.java b/src/main/java/org/bukkit/craftbukkit/damage/CraftDamageSource.java -index 8532324060eed772a10e2a3429890438cf32f9ba..7df86e7124a9ed359f05324b8fc4c8862f7e4b79 100644 ---- a/src/main/java/org/bukkit/craftbukkit/damage/CraftDamageSource.java -+++ b/src/main/java/org/bukkit/craftbukkit/damage/CraftDamageSource.java -@@ -41,13 +41,13 @@ public class CraftDamageSource implements DamageSource { - - @Override - public org.bukkit.entity.Entity getCausingEntity() { -- net.minecraft.world.entity.Entity entity = this.getHandle().getCausingDamager(); -+ net.minecraft.world.entity.Entity entity = this.getHandle().getEntity(); // Paper - fix DamageSource API - revert to vanilla - return (entity != null) ? entity.getBukkitEntity() : null; - } - - @Override - public org.bukkit.entity.Entity getDirectEntity() { -- net.minecraft.world.entity.Entity entity = this.getHandle().getDamager(); -+ net.minecraft.world.entity.Entity entity = this.getHandle().getDirectEntity(); // Paper - fix DamageSource API - return (entity != null) ? entity.getBukkitEntity() : null; - } - -@@ -65,7 +65,7 @@ public class CraftDamageSource implements DamageSource { - - @Override - public boolean isIndirect() { -- return this.getHandle().getCausingDamager() != this.getHandle().getDamager(); -+ return !this.getHandle().isDirect(); // Paper - fix DamageSource API - } - - @Override -diff --git a/src/main/java/org/bukkit/craftbukkit/damage/CraftDamageSourceBuilder.java b/src/main/java/org/bukkit/craftbukkit/damage/CraftDamageSourceBuilder.java -index 4c6e15535fa40aad8cf1920f392589404f9ba79c..35eb95ef6fb6a0f7ea63351e90741c489fdd15f9 100644 ---- a/src/main/java/org/bukkit/craftbukkit/damage/CraftDamageSourceBuilder.java -+++ b/src/main/java/org/bukkit/craftbukkit/damage/CraftDamageSourceBuilder.java -@@ -41,6 +41,11 @@ public class CraftDamageSourceBuilder implements DamageSource.Builder { - - @Override - public DamageSource build() { -+ // Paper start - fix DamageCause API -+ if (this.causingEntity != null && this.directEntity == null) { -+ throw new IllegalArgumentException("Direct entity must be set if causing entity is set"); -+ } -+ // Paper end - fix DamageCause API - return CraftDamageSource.buildFromBukkit(this.damageType, this.causingEntity, this.directEntity, this.damageLocation); - } - } -diff --git a/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java b/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java -index d407e9e8f1b530f00e632e0249514fb68d48316b..1b21fdab4269df375d2f445df3bd56f7b1fd2b15 100644 ---- a/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java -+++ b/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java -@@ -1094,7 +1094,7 @@ public class CraftEventFactory { - - private static EntityDamageEvent handleEntityDamageEvent(Entity entity, DamageSource source, Map modifiers, Map> modifierFunctions, boolean cancelled) { - CraftDamageSource bukkitDamageSource = new CraftDamageSource(source); -- Entity damager = (source.getDamager() != null) ? source.getDamager() : source.getEntity(); -+ final Entity damager = source.getCustomEventDamager(); // Paper - fix DamageSource API - if (source.is(DamageTypeTags.IS_EXPLOSION)) { - if (damager == null) { - return CraftEventFactory.callEntityDamageEvent(source.getDirectBlock(), source.getDirectBlockState(), entity, DamageCause.BLOCK_EXPLOSION, bukkitDamageSource, modifiers, modifierFunctions, cancelled); diff --git a/patches/server/0943-Deep-clone-nbt-tags-in-PDC.patch b/patches/server/0943-Deep-clone-nbt-tags-in-PDC.patch new file mode 100644 index 000000000000..559c6cddea9b --- /dev/null +++ b/patches/server/0943-Deep-clone-nbt-tags-in-PDC.patch @@ -0,0 +1,45 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: SoSeDiK +Date: Thu, 26 May 2022 03:30:05 +0300 +Subject: [PATCH] Deep clone nbt tags in PDC + + +diff --git a/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaItem.java b/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaItem.java +index 3bcc807005a677884255f1ee36cbf1653797ba55..8d13505c36d732f17293c6a6d65cac20919b8b7a 100644 +--- a/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaItem.java ++++ b/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaItem.java +@@ -372,7 +372,7 @@ class CraftMetaItem implements ItemMeta, Damageable, Repairable, BlockDataMeta { + this.maxDamage = meta.maxDamage; + this.unhandledTags.copy(meta.unhandledTags.build()); + this.removedTags.addAll(meta.removedTags); +- this.persistentDataContainer.putAll(meta.persistentDataContainer.getRaw()); ++ this.persistentDataContainer.putAll(meta.persistentDataContainer.getTagsCloned()); // Paper - deep clone NBT tags + + this.customTag = meta.customTag; + +@@ -1966,7 +1966,7 @@ class CraftMetaItem implements ItemMeta, Damageable, Repairable, BlockDataMeta { + clone.customTag = this.customTag.copy(); + } + clone.removedTags = Sets.newHashSet(this.removedTags); +- clone.persistentDataContainer = new CraftPersistentDataContainer(this.persistentDataContainer.getRaw(), CraftMetaItem.DATA_TYPE_REGISTRY); ++ clone.persistentDataContainer = new CraftPersistentDataContainer(this.persistentDataContainer.getTagsCloned(), CraftMetaItem.DATA_TYPE_REGISTRY); // Paper - deep clone NBT tags + clone.hideFlag = this.hideFlag; + clone.hideTooltip = this.hideTooltip; + clone.tooltipStyle = this.tooltipStyle; +diff --git a/src/main/java/org/bukkit/craftbukkit/persistence/CraftPersistentDataContainer.java b/src/main/java/org/bukkit/craftbukkit/persistence/CraftPersistentDataContainer.java +index 5a4e7e7150b7c137b077e0b393f17ed35b5aec34..f55fdd57ced259ad5a95878840e98ffaa3db2e05 100644 +--- a/src/main/java/org/bukkit/craftbukkit/persistence/CraftPersistentDataContainer.java ++++ b/src/main/java/org/bukkit/craftbukkit/persistence/CraftPersistentDataContainer.java +@@ -207,4 +207,12 @@ public class CraftPersistentDataContainer implements PersistentDataContainer { + } + } + // Paper end - byte array serialization ++ ++ // Paper start - deep clone tags ++ public Map getTagsCloned() { ++ final Map tags = new HashMap<>(); ++ this.customDataTags.forEach((key, tag) -> tags.put(key, tag.copy())); ++ return tags; ++ } ++ // Paper end - deep clone tags + } diff --git a/patches/server/0943-Fix-creation-of-invalid-block-entity-during-world-ge.patch b/patches/server/0943-Fix-creation-of-invalid-block-entity-during-world-ge.patch deleted file mode 100644 index 8e09c4fb05bf..000000000000 --- a/patches/server/0943-Fix-creation-of-invalid-block-entity-during-world-ge.patch +++ /dev/null @@ -1,59 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Pierpaolo Coletta -Date: Sat, 30 Mar 2024 21:06:10 +0100 -Subject: [PATCH] Fix creation of invalid block entity during world generation - - -diff --git a/src/main/java/net/minecraft/server/level/WorldGenRegion.java b/src/main/java/net/minecraft/server/level/WorldGenRegion.java -index 682c8cfbd917c086072f1756861a340800ea40da..b26a4a38144ec1b171db911bbf949b53ed35708f 100644 ---- a/src/main/java/net/minecraft/server/level/WorldGenRegion.java -+++ b/src/main/java/net/minecraft/server/level/WorldGenRegion.java -@@ -324,7 +324,7 @@ public class WorldGenRegion implements WorldGenLevel { - return false; - } else { - ChunkAccess ichunkaccess = this.getChunk(pos); -- BlockState iblockdata1 = ichunkaccess.setBlockState(pos, state, false); -+ BlockState iblockdata1 = ichunkaccess.setBlockState(pos, state, false); final BlockState previousBlockState = iblockdata1; // Paper - Clear block entity before setting up a DUMMY block entity - obfhelper - - if (iblockdata1 != null) { - this.level.onBlockStateChange(pos, iblockdata1, state); -@@ -340,6 +340,17 @@ public class WorldGenRegion implements WorldGenLevel { - ichunkaccess.removeBlockEntity(pos); - } - } else { -+ // Paper start - Clear block entity before setting up a DUMMY block entity -+ // The concept of removing a block entity when the block itself changes is generally lifted -+ // from LevelChunk#setBlockState. -+ // It is however to note that this may only run if the block actually changes. -+ // Otherwise a chest block entity generated by a structure template that is later "updated" to -+ // be waterlogged would remove its existing block entity (see PaperMC/Paper#10750) -+ // This logic is *also* found in LevelChunk#setBlockState. -+ if (previousBlockState != null && !java.util.Objects.equals(previousBlockState.getBlock(), state.getBlock())) { -+ ichunkaccess.removeBlockEntity(pos); -+ } -+ // Paper end - Clear block entity before setting up a DUMMY block entity - CompoundTag nbttagcompound = new CompoundTag(); - - nbttagcompound.putInt("x", pos.getX()); -diff --git a/src/main/java/net/minecraft/world/level/chunk/LevelChunk.java b/src/main/java/net/minecraft/world/level/chunk/LevelChunk.java -index 59f9ff720e92c69e11afe7f6ccecd81b0e54a74d..86eb9029969f4de3ada7be9e135e9764172b85f5 100644 ---- a/src/main/java/net/minecraft/world/level/chunk/LevelChunk.java -+++ b/src/main/java/net/minecraft/world/level/chunk/LevelChunk.java -@@ -961,9 +961,14 @@ public class LevelChunk extends ChunkAccess { - if (this.blockEntity.getType().isValid(iblockdata)) { - this.ticker.tick(LevelChunk.this.level, this.blockEntity.getBlockPos(), iblockdata, this.blockEntity); - this.loggedInvalidBlockState = false; -- } else if (!this.loggedInvalidBlockState) { -- this.loggedInvalidBlockState = true; -- LevelChunk.LOGGER.warn("Block entity {} @ {} state {} invalid for ticking:", new Object[]{LogUtils.defer(this::getType), LogUtils.defer(this::getPos), iblockdata}); -+ // Paper start - Remove the Block Entity if it's invalid -+ } else { -+ LevelChunk.this.removeBlockEntity(this.getPos()); -+ if (!this.loggedInvalidBlockState) { -+ this.loggedInvalidBlockState = true; -+ LevelChunk.LOGGER.warn("Block entity {} @ {} state {} invalid for ticking:", new Object[]{LogUtils.defer(this::getType), LogUtils.defer(this::getPos), iblockdata}); -+ } -+ // Paper end - Remove the Block Entity if it's invalid - } - - gameprofilerfiller.pop(); diff --git a/patches/server/0944-Fix-possible-StackOverflowError-for-some-dispenses.patch b/patches/server/0944-Fix-possible-StackOverflowError-for-some-dispenses.patch deleted file mode 100644 index 5351dde83c76..000000000000 --- a/patches/server/0944-Fix-possible-StackOverflowError-for-some-dispenses.patch +++ /dev/null @@ -1,117 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Jake Potrebic -Date: Sat, 29 Oct 2022 17:02:42 -0700 -Subject: [PATCH] Fix possible StackOverflowError for some dispenses - -For saddles, carpets, horse armor, and chests for horse-likes -a BlockDispenseEvent handler that always mutated the item without -changing the type would result in a SO error because when it went -to find the replacement dispense behavior (since the item "changed") -it didn't properly handle if the replacement was the same instance -of dispense behavior. - -Additionally equippable mob heads, wither skulls, and carved pumpkins -are subject to the same possible error. - -diff --git a/src/main/java/net/minecraft/core/dispenser/DispenseItemBehavior.java b/src/main/java/net/minecraft/core/dispenser/DispenseItemBehavior.java -index 7826e2a52da47914aca39fef958b8f398a2ff937..0c0b8a49b3342cd015381c6a93fab23c32cd32e6 100644 ---- a/src/main/java/net/minecraft/core/dispenser/DispenseItemBehavior.java -+++ b/src/main/java/net/minecraft/core/dispenser/DispenseItemBehavior.java -@@ -239,7 +239,7 @@ public interface DispenseItemBehavior { - // Chain to handler for new item - ItemStack eventStack = CraftItemStack.asNMSCopy(event.getItem()); - DispenseItemBehavior idispensebehavior = (DispenseItemBehavior) DispenserBlock.DISPENSER_REGISTRY.get(eventStack.getItem()); -- if (idispensebehavior != DispenseItemBehavior.NOOP && idispensebehavior != ArmorItem.DISPENSE_ITEM_BEHAVIOR) { -+ if (idispensebehavior != DispenseItemBehavior.NOOP && idispensebehavior != this) { // Paper - fix possible StackOverflowError - idispensebehavior.dispense(pointer, eventStack); - return stack; - } -@@ -295,7 +295,7 @@ public interface DispenseItemBehavior { - // Chain to handler for new item - ItemStack eventStack = CraftItemStack.asNMSCopy(event.getItem()); - DispenseItemBehavior idispensebehavior = (DispenseItemBehavior) DispenserBlock.DISPENSER_REGISTRY.get(eventStack.getItem()); -- if (idispensebehavior != DispenseItemBehavior.NOOP && idispensebehavior != ArmorItem.DISPENSE_ITEM_BEHAVIOR) { -+ if (idispensebehavior != DispenseItemBehavior.NOOP && idispensebehavior != this) { // Paper - fix possible StackOverflowError - idispensebehavior.dispense(pointer, eventStack); - return stack; - } -@@ -369,7 +369,7 @@ public interface DispenseItemBehavior { - // Chain to handler for new item - ItemStack eventStack = CraftItemStack.asNMSCopy(event.getItem()); - DispenseItemBehavior idispensebehavior = (DispenseItemBehavior) DispenserBlock.DISPENSER_REGISTRY.get(eventStack.getItem()); -- if (idispensebehavior != DispenseItemBehavior.NOOP && idispensebehavior != ArmorItem.DISPENSE_ITEM_BEHAVIOR) { -+ if (idispensebehavior != DispenseItemBehavior.NOOP && idispensebehavior != this) { // Paper - fix possible StackOverflowError - idispensebehavior.dispense(pointer, eventStack); - return stack; - } -@@ -690,7 +690,7 @@ public interface DispenseItemBehavior { - OptionalDispenseItemBehavior dispensebehaviormaybe1 = new OptionalDispenseItemBehavior() { - @Override - protected ItemStack execute(BlockSource pointer, ItemStack stack) { -- this.setSuccess(ArmorItem.dispenseArmor(pointer, stack)); -+ this.setSuccess(ArmorItem.dispenseArmor(pointer, stack, this)); // Paper - fix possible StackOverflowError - return stack; - } - }; -@@ -744,7 +744,7 @@ public interface DispenseItemBehavior { - stack.shrink(1); - this.setSuccess(true); - } else { -- this.setSuccess(ArmorItem.dispenseArmor(pointer, stack)); -+ this.setSuccess(ArmorItem.dispenseArmor(pointer, stack, this)); // Paper - fix possible StackOverflowError - } - - return stack; -@@ -790,7 +790,7 @@ public interface DispenseItemBehavior { - stack.shrink(1); - this.setSuccess(true); - } else { -- this.setSuccess(ArmorItem.dispenseArmor(pointer, stack)); -+ this.setSuccess(ArmorItem.dispenseArmor(pointer, stack, this)); // Paper - fix possible StackOverflowError - } - - return stack; -@@ -918,7 +918,7 @@ public interface DispenseItemBehavior { - // Chain to handler for new item - ItemStack eventStack = CraftItemStack.asNMSCopy(event.getItem()); - DispenseItemBehavior idispensebehavior = (DispenseItemBehavior) DispenserBlock.DISPENSER_REGISTRY.get(eventStack.getItem()); -- if (idispensebehavior != DispenseItemBehavior.NOOP && idispensebehavior != ArmorItem.DISPENSE_ITEM_BEHAVIOR) { -+ if (idispensebehavior != DispenseItemBehavior.NOOP && idispensebehavior != this) { // Paper - fix possible StackOverflowError - idispensebehavior.dispense(pointer, eventStack); - return stack; - } -diff --git a/src/main/java/net/minecraft/world/item/ArmorItem.java b/src/main/java/net/minecraft/world/item/ArmorItem.java -index fb518f87cc4ccd810fb32cade2fdd7e09ab0abfc..647a4601deace52f8d855f512a73671f82b4762a 100644 ---- a/src/main/java/net/minecraft/world/item/ArmorItem.java -+++ b/src/main/java/net/minecraft/world/item/ArmorItem.java -@@ -39,14 +39,20 @@ public class ArmorItem extends Item implements Equipable { - public static final DispenseItemBehavior DISPENSE_ITEM_BEHAVIOR = new DefaultDispenseItemBehavior() { - @Override - protected ItemStack execute(BlockSource pointer, ItemStack stack) { -- return ArmorItem.dispenseArmor(pointer, stack) ? stack : super.execute(pointer, stack); -+ return ArmorItem.dispenseArmor(pointer, stack, this) ? stack : super.execute(pointer, stack); // Paper - fix possible StackOverflowError - } - }; - protected final ArmorItem.Type type; - protected final Holder material; - private final Supplier defaultModifiers; - -+ @Deprecated @io.papermc.paper.annotation.DoNotUse // Paper - public static boolean dispenseArmor(BlockSource pointer, ItemStack armor) { -+ // Paper start -+ return dispenseArmor(pointer, armor, null); -+ } -+ public static boolean dispenseArmor(BlockSource pointer, ItemStack armor, @javax.annotation.Nullable DispenseItemBehavior currentBehavior) { -+ // Paper end - BlockPos blockposition = pointer.pos().relative((Direction) pointer.state().getValue(DispenserBlock.FACING)); - List list = pointer.level().getEntitiesOfClass(LivingEntity.class, new AABB(blockposition), EntitySelector.NO_SPECTATORS.and(new EntitySelector.MobCanWearArmorEntitySelector(armor))); - -@@ -77,7 +83,7 @@ public class ArmorItem extends Item implements Equipable { - // Chain to handler for new item - ItemStack eventStack = CraftItemStack.asNMSCopy(event.getItem()); - DispenseItemBehavior idispensebehavior = (DispenseItemBehavior) DispenserBlock.DISPENSER_REGISTRY.get(eventStack.getItem()); -- if (idispensebehavior != DispenseItemBehavior.NOOP && idispensebehavior != ArmorItem.DISPENSE_ITEM_BEHAVIOR) { -+ if (idispensebehavior != DispenseItemBehavior.NOOP && (currentBehavior == null || idispensebehavior != currentBehavior)) { // Paper - fix possible StackOverflowError - idispensebehavior.dispense(pointer, eventStack); - return true; - } diff --git a/patches/server/0953-Support-old-UUID-format-for-NBT.patch b/patches/server/0944-Support-old-UUID-format-for-NBT.patch similarity index 100% rename from patches/server/0953-Support-old-UUID-format-for-NBT.patch rename to patches/server/0944-Support-old-UUID-format-for-NBT.patch diff --git a/patches/server/0945-Fix-shield-disable-inconsistency.patch b/patches/server/0945-Fix-shield-disable-inconsistency.patch new file mode 100644 index 000000000000..0f02785268c7 --- /dev/null +++ b/patches/server/0945-Fix-shield-disable-inconsistency.patch @@ -0,0 +1,22 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Jake Potrebic +Date: Fri, 26 Apr 2024 19:08:37 -0700 +Subject: [PATCH] Fix shield disable inconsistency + +In vanilla, if the damage source is tagged as a projectile, +it will not disable the shield if the attacker is holding +an axe item. + +diff --git a/src/main/java/net/minecraft/world/entity/LivingEntity.java b/src/main/java/net/minecraft/world/entity/LivingEntity.java +index 49b3d8d2bc34c0785f143bbc8976308f5bf8c9de..f6d55ff3027bb7f0dcef186c52d48d9c5358ffd0 100644 +--- a/src/main/java/net/minecraft/world/entity/LivingEntity.java ++++ b/src/main/java/net/minecraft/world/entity/LivingEntity.java +@@ -2424,7 +2424,7 @@ public abstract class LivingEntity extends Entity implements Attackable { + this.hurtCurrentlyUsedShield((float) -event.getDamage(DamageModifier.BLOCKING)); + Entity entity = damagesource.getDirectEntity(); + +- if (entity instanceof LivingEntity) { ++ if (!damagesource.is(DamageTypeTags.IS_PROJECTILE) && entity instanceof LivingEntity) { // Paper - Fix shield disable inconsistency + this.blockUsingShield((LivingEntity) entity); + } + } diff --git a/patches/server/0945-Improve-tag-parser-handling.patch b/patches/server/0945-Improve-tag-parser-handling.patch deleted file mode 100644 index 9ffa70e38885..000000000000 --- a/patches/server/0945-Improve-tag-parser-handling.patch +++ /dev/null @@ -1,281 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Nassim Jahnke -Date: Mon, 5 Feb 2024 11:54:04 +0100 -Subject: [PATCH] Improve tag parser handling - - -diff --git a/src/main/java/com/mojang/brigadier/CommandDispatcher.java b/src/main/java/com/mojang/brigadier/CommandDispatcher.java -index 92848b64a78fce7a92e1657c2da6fc5ee53eea44..4b4f812eb13d5f03bcf3f8724d8aa8dbbc724e8b 100644 ---- a/src/main/java/com/mojang/brigadier/CommandDispatcher.java -+++ b/src/main/java/com/mojang/brigadier/CommandDispatcher.java -@@ -304,9 +304,15 @@ public class CommandDispatcher { - } - final CommandContextBuilder context = contextSoFar.copy(); - final StringReader reader = new StringReader(originalReader); -+ boolean stop = false; // Paper - Handle non-recoverable exceptions - try { - try { - child.parse(reader, context); -+ // Paper start - Handle non-recoverable exceptions -+ } catch (final io.papermc.paper.brigadier.TagParseCommandSyntaxException e) { -+ stop = true; -+ throw e; -+ // Paper end - Handle non-recoverable exceptions - } catch (final RuntimeException ex) { - throw CommandSyntaxException.BUILT_IN_EXCEPTIONS.dispatcherParseException().createWithContext(reader, ex.getMessage()); - } -@@ -321,6 +327,7 @@ public class CommandDispatcher { - } - errors.put(child, ex); - reader.setCursor(cursor); -+ if (stop) return new ParseResults<>(contextSoFar, originalReader, errors); // Paper - Handle non-recoverable exceptions - continue; - } - -diff --git a/src/main/java/io/papermc/paper/brigadier/TagParseCommandSyntaxException.java b/src/main/java/io/papermc/paper/brigadier/TagParseCommandSyntaxException.java -new file mode 100644 -index 0000000000000000000000000000000000000000..a375ad4ba9db990b24a2b9ff366fcba66b753815 ---- /dev/null -+++ b/src/main/java/io/papermc/paper/brigadier/TagParseCommandSyntaxException.java -@@ -0,0 +1,15 @@ -+package io.papermc.paper.brigadier; -+ -+import com.mojang.brigadier.LiteralMessage; -+import com.mojang.brigadier.exceptions.CommandSyntaxException; -+import com.mojang.brigadier.exceptions.SimpleCommandExceptionType; -+import net.minecraft.network.chat.Component; -+ -+public final class TagParseCommandSyntaxException extends CommandSyntaxException { -+ -+ private static final SimpleCommandExceptionType EXCEPTION_TYPE = new SimpleCommandExceptionType(new LiteralMessage("Error parsing NBT")); -+ -+ public TagParseCommandSyntaxException(final String message) { -+ super(EXCEPTION_TYPE, Component.literal(message)); -+ } -+} -diff --git a/src/main/java/net/minecraft/nbt/TagParser.java b/src/main/java/net/minecraft/nbt/TagParser.java -index da101bca71f4710812621b98f0a0d8cab180346a..3cd112584accb8e8f050ac99738eed11c902e60e 100644 ---- a/src/main/java/net/minecraft/nbt/TagParser.java -+++ b/src/main/java/net/minecraft/nbt/TagParser.java -@@ -49,6 +49,7 @@ public class TagParser { - }, CompoundTag::toString); - public static final Codec LENIENT_CODEC = Codec.withAlternative(AS_CODEC, CompoundTag.CODEC); - private final StringReader reader; -+ private int depth; // Paper - - public static CompoundTag parseTag(String string) throws CommandSyntaxException { - return new TagParser(new StringReader(string)).readSingleStruct(); -@@ -159,6 +160,7 @@ public class TagParser { - - public CompoundTag readStruct() throws CommandSyntaxException { - this.expect('{'); -+ this.increaseDepth(); // Paper - CompoundTag compoundTag = new CompoundTag(); - this.reader.skipWhitespace(); - -@@ -182,6 +184,7 @@ public class TagParser { - } - - this.expect('}'); -+ this.depth--; // Paper - return compoundTag; - } - -@@ -191,6 +194,7 @@ public class TagParser { - if (!this.reader.canRead()) { - throw ERROR_EXPECTED_VALUE.createWithContext(this.reader); - } else { -+ this.increaseDepth(); // Paper - ListTag listTag = new ListTag(); - TagType tagType = null; - -@@ -216,6 +220,7 @@ public class TagParser { - } - - this.expect(']'); -+ this.depth--; // Paper - return listTag; - } - } -@@ -288,4 +293,11 @@ public class TagParser { - this.reader.skipWhitespace(); - this.reader.expect(c); - } -+ -+ private void increaseDepth() throws CommandSyntaxException { -+ this.depth++; -+ if (this.depth > 512) { -+ throw new io.papermc.paper.brigadier.TagParseCommandSyntaxException("NBT tag is too complex, depth > 512"); -+ } -+ } - } -diff --git a/src/main/java/net/minecraft/network/chat/ComponentUtils.java b/src/main/java/net/minecraft/network/chat/ComponentUtils.java -index 3365aed2b67ae0e4dd0410f5190ba474f146139b..8b776434a733b91129b089d702b2583b727bf3d2 100644 ---- a/src/main/java/net/minecraft/network/chat/ComponentUtils.java -+++ b/src/main/java/net/minecraft/network/chat/ComponentUtils.java -@@ -33,10 +33,30 @@ public class ComponentUtils { - } - } - -+ @io.papermc.paper.annotation.DoNotUse // Paper - validate separators - right now this method is only used for separator evaluation. Error on build if this changes to re-evaluate. - public static Optional updateForEntity(@Nullable CommandSourceStack source, Optional text, @Nullable Entity sender, int depth) throws CommandSyntaxException { - return text.isPresent() ? Optional.of(updateForEntity(source, text.get(), sender, depth)) : Optional.empty(); - } - -+ // Paper start - validate separator -+ public static Optional updateSeparatorForEntity(@Nullable CommandSourceStack source, Optional text, @Nullable Entity sender, int depth) throws CommandSyntaxException { -+ if (text.isEmpty() || !isValidSelector(text.get())) return Optional.empty(); -+ return Optional.of(updateForEntity(source, text.get(), sender, depth)); -+ } -+ public static boolean isValidSelector(final Component component) { -+ final ComponentContents contents = component.getContents(); -+ -+ if (contents instanceof net.minecraft.network.chat.contents.NbtContents || contents instanceof net.minecraft.network.chat.contents.SelectorContents) return false; -+ if (contents instanceof final net.minecraft.network.chat.contents.TranslatableContents translatableContents) { -+ for (final Object arg : translatableContents.getArgs()) { -+ if (arg instanceof final Component argumentAsComponent && !isValidSelector(argumentAsComponent)) return false; -+ } -+ } -+ -+ return true; -+ } -+ // Paper end - validate separator -+ - public static MutableComponent updateForEntity(@Nullable CommandSourceStack source, Component text, @Nullable Entity sender, int depth) throws CommandSyntaxException { - if (depth > 100) { - return text.copy(); -diff --git a/src/main/java/net/minecraft/network/chat/contents/NbtContents.java b/src/main/java/net/minecraft/network/chat/contents/NbtContents.java -index df26c39a2bb20e2021b50211dce905483a77f4e6..5634122dac8afeecab0cde623e9868d8e865e02a 100644 ---- a/src/main/java/net/minecraft/network/chat/contents/NbtContents.java -+++ b/src/main/java/net/minecraft/network/chat/contents/NbtContents.java -@@ -120,7 +120,7 @@ public class NbtContents implements ComponentContents { - }).map(Tag::getAsString); - if (this.interpreting) { - Component component = DataFixUtils.orElse( -- ComponentUtils.updateForEntity(source, this.separator, sender, depth), ComponentUtils.DEFAULT_NO_STYLE_SEPARATOR -+ ComponentUtils.updateSeparatorForEntity(source, this.separator, sender, depth), ComponentUtils.DEFAULT_NO_STYLE_SEPARATOR // Paper - validate separator - ); - return stream.flatMap(text -> { - try { -@@ -132,7 +132,7 @@ public class NbtContents implements ComponentContents { - } - }).reduce((accumulator, current) -> accumulator.append(component).append(current)).orElseGet(Component::empty); - } else { -- return ComponentUtils.updateForEntity(source, this.separator, sender, depth) -+ return ComponentUtils.updateSeparatorForEntity(source, this.separator, sender, depth) // Paper - validate separator - .map( - text -> stream.map(Component::literal) - .reduce((accumulator, current) -> accumulator.append(text).append(current)) -diff --git a/src/main/java/net/minecraft/network/chat/contents/SelectorContents.java b/src/main/java/net/minecraft/network/chat/contents/SelectorContents.java -index 1337853badf8e124aa8439ce33a255bc4164125b..933388a60eda2af259d4ef761e31c5abb69c31fd 100644 ---- a/src/main/java/net/minecraft/network/chat/contents/SelectorContents.java -+++ b/src/main/java/net/minecraft/network/chat/contents/SelectorContents.java -@@ -50,7 +50,7 @@ public class SelectorContents implements ComponentContents { - EntitySelectorParser entitySelectorParser = new EntitySelectorParser(new StringReader(pattern), true); - entitySelector = entitySelectorParser.parse(); - } catch (CommandSyntaxException var3) { -- LOGGER.warn("Invalid selector component: {}: {}", pattern, var3.getMessage()); -+ return null; // Paper - } - - return entitySelector; -@@ -77,7 +77,7 @@ public class SelectorContents implements ComponentContents { - @Override - public MutableComponent resolve(@Nullable CommandSourceStack source, @Nullable Entity sender, int depth) throws CommandSyntaxException { - if (source != null && this.selector != null) { -- Optional optional = ComponentUtils.updateForEntity(source, this.separator, sender, depth); -+ Optional optional = ComponentUtils.updateSeparatorForEntity(source, this.separator, sender, depth); // Paper - validate separator - return ComponentUtils.formatList(this.selector.findEntities(source), optional, Entity::getDisplayName); - } else { - return Component.empty(); -diff --git a/src/main/java/net/minecraft/network/chat/contents/TranslatableContents.java b/src/main/java/net/minecraft/network/chat/contents/TranslatableContents.java -index 56e641bc5f6edc657647993ea2efbb7bb9c2f732..4aa6232bf0f72fcde32d257100bd15b1c5192aaa 100644 ---- a/src/main/java/net/minecraft/network/chat/contents/TranslatableContents.java -+++ b/src/main/java/net/minecraft/network/chat/contents/TranslatableContents.java -@@ -181,6 +181,15 @@ public class TranslatableContents implements ComponentContents { - - @Override - public Optional visit(FormattedText.ContentConsumer visitor) { -+ // Paper start - Count visited parts -+ try { -+ return this.visit(new TranslatableContentConsumer<>(visitor)); -+ } catch (IllegalArgumentException ignored) { -+ return visitor.accept("..."); -+ } -+ } -+ private Optional visit(TranslatableContentConsumer visitor) { -+ // Paper end - Count visited parts - this.decompose(); - - for (FormattedText formattedText : this.decomposedParts) { -@@ -192,6 +201,25 @@ public class TranslatableContents implements ComponentContents { - - return Optional.empty(); - } -+ // Paper start - Count visited parts -+ private static final class TranslatableContentConsumer implements FormattedText.ContentConsumer { -+ private static final IllegalArgumentException EX = new IllegalArgumentException("Too long"); -+ private final FormattedText.ContentConsumer visitor; -+ private int visited; -+ -+ private TranslatableContentConsumer(FormattedText.ContentConsumer visitor) { -+ this.visitor = visitor; -+ } -+ -+ @Override -+ public Optional accept(final String asString) { -+ if (visited++ > 32) { -+ throw EX; -+ } -+ return this.visitor.accept(asString); -+ } -+ } -+ // Paper end - Count visited parts - - @Override - public MutableComponent resolve(@Nullable CommandSourceStack source, @Nullable Entity sender, int depth) throws CommandSyntaxException { -diff --git a/src/main/java/net/minecraft/network/protocol/game/ServerboundCommandSuggestionPacket.java b/src/main/java/net/minecraft/network/protocol/game/ServerboundCommandSuggestionPacket.java -index 898b19887ed34c87003fc63cb5905df2ba6234a5..b47eeb23055b135d5567552ba983bfbc3e1fab67 100644 ---- a/src/main/java/net/minecraft/network/protocol/game/ServerboundCommandSuggestionPacket.java -+++ b/src/main/java/net/minecraft/network/protocol/game/ServerboundCommandSuggestionPacket.java -@@ -19,7 +19,7 @@ public class ServerboundCommandSuggestionPacket implements Packet 64 && ((index = packet.getCommand().indexOf(' ')) == -1 || index >= 64)) { -+ this.disconnect(Component.translatable("disconnect.spam"), org.bukkit.event.player.PlayerKickEvent.Cause.SPAM); -+ return; -+ } -+ // Paper end - // Paper start - AsyncTabCompleteEvent - TAB_COMPLETE_EXECUTOR.execute(() -> this.handleCustomCommandSuggestions0(packet)); - } -@@ -818,6 +825,13 @@ public class ServerGamePacketListenerImpl extends ServerCommonPacketListenerImpl - private void sendServerSuggestions(final ServerboundCommandSuggestionPacket packet, final StringReader stringreader) { - // Paper end - AsyncTabCompleteEvent - ParseResults parseresults = this.server.getCommands().getDispatcher().parse(stringreader, this.player.createCommandSourceStack()); -+ // Paper start - Handle non-recoverable exceptions -+ if (!parseresults.getExceptions().isEmpty() -+ && parseresults.getExceptions().values().stream().anyMatch(e -> e instanceof io.papermc.paper.brigadier.TagParseCommandSyntaxException)) { -+ this.disconnect(Component.translatable("disconnect.spam"), org.bukkit.event.player.PlayerKickEvent.Cause.SPAM); -+ return; -+ } -+ // Paper end - Handle non-recoverable exceptions - - this.server.getCommands().getDispatcher().getCompletionSuggestions(parseresults).thenAccept((suggestions) -> { - // Paper start - Don't tab-complete namespaced commands if send-namespaced is false diff --git a/patches/server/0946-Handle-Large-Packets-disconnecting-client.patch b/patches/server/0946-Handle-Large-Packets-disconnecting-client.patch new file mode 100644 index 000000000000..32e463a24238 --- /dev/null +++ b/patches/server/0946-Handle-Large-Packets-disconnecting-client.patch @@ -0,0 +1,135 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Aikar +Date: Tue, 27 Nov 2018 21:18:06 -0500 +Subject: [PATCH] Handle Large Packets disconnecting client + +If a players inventory is too big to send in a single packet, +split the inventory set into multiple packets instead. + +diff --git a/src/main/java/net/minecraft/network/Connection.java b/src/main/java/net/minecraft/network/Connection.java +index a8dfe7a4b3d01bf75587be078f471d1ef1d7a667..ea16dfa718b526d6520d7fcfc21d28f972f1f2bf 100644 +--- a/src/main/java/net/minecraft/network/Connection.java ++++ b/src/main/java/net/minecraft/network/Connection.java +@@ -176,6 +176,21 @@ public class Connection extends SimpleChannelInboundHandler> { + } + + public void exceptionCaught(ChannelHandlerContext channelhandlercontext, Throwable throwable) { ++ // Paper start - Handle large packets disconnecting client ++ if (throwable instanceof io.netty.handler.codec.EncoderException && throwable.getCause() instanceof PacketEncoder.PacketTooLargeException packetTooLargeException) { ++ final Packet packet = packetTooLargeException.getPacket(); ++ if (packet.packetTooLarge(this)) { ++ ProtocolSwapHandler.handleOutboundTerminalPacket(channelhandlercontext, packet); ++ return; ++ } else if (packet.isSkippable()) { ++ Connection.LOGGER.debug("Skipping packet due to errors", throwable.getCause()); ++ ProtocolSwapHandler.handleOutboundTerminalPacket(channelhandlercontext, packet); ++ return; ++ } else { ++ throwable = throwable.getCause(); ++ } ++ } ++ // Paper end - Handle large packets disconnecting client + if (throwable instanceof SkipPacketException) { + Connection.LOGGER.debug("Skipping packet due to errors", throwable.getCause()); + } else { +diff --git a/src/main/java/net/minecraft/network/PacketEncoder.java b/src/main/java/net/minecraft/network/PacketEncoder.java +index 046bfc212b640de174b300e7a05cc30bb3cac93e..af3ec112e142a2c91c46882dad6180b18f39eec2 100644 +--- a/src/main/java/net/minecraft/network/PacketEncoder.java ++++ b/src/main/java/net/minecraft/network/PacketEncoder.java +@@ -40,7 +40,33 @@ public class PacketEncoder extends MessageToByteEncode + + throw var9; + } finally { ++ // Paper start - Handle large packets disconnecting client ++ int packetLength = byteBuf.readableBytes(); ++ if (packetLength > MAX_PACKET_SIZE || (packetLength > MAX_FINAL_PACKET_SIZE && packet.hasLargePacketFallback())) { ++ throw new PacketTooLargeException(packet, packetLength); ++ } ++ // Paper end - Handle large packets disconnecting client + ProtocolSwapHandler.handleOutboundTerminalPacket(channelHandlerContext, packet); + } + } ++ ++ // Paper start ++ // packet size is encoded into 3-byte varint ++ private static final int MAX_FINAL_PACKET_SIZE = (1 << 21) - 1; ++ // Vanilla Max size for the encoder (before compression) ++ private static final int MAX_PACKET_SIZE = 8388608; ++ ++ public static class PacketTooLargeException extends RuntimeException { ++ private final Packet packet; ++ ++ PacketTooLargeException(Packet packet, int packetLength) { ++ super("PacketTooLarge - " + packet.getClass().getSimpleName() + " is " + packetLength + ". Max is " + MAX_PACKET_SIZE); ++ this.packet = packet; ++ } ++ ++ public Packet getPacket() { ++ return this.packet; ++ } ++ } ++ // Paper end + } +diff --git a/src/main/java/net/minecraft/network/protocol/Packet.java b/src/main/java/net/minecraft/network/protocol/Packet.java +index 4c776c591dd0a7b36945a6487fdfe86d1187b4af..82fc12ffbd1585b4a8d09a025914830af77b0f8d 100644 +--- a/src/main/java/net/minecraft/network/protocol/Packet.java ++++ b/src/main/java/net/minecraft/network/protocol/Packet.java +@@ -11,6 +11,19 @@ public interface Packet { + + void handle(T listener); + ++ // Paper start ++ default boolean hasLargePacketFallback() { ++ return false; ++ } ++ ++ /** ++ * override {@link #hasLargePacketFallback()} to return true when overriding in subclasses ++ */ ++ default boolean packetTooLarge(net.minecraft.network.Connection manager) { ++ return false; ++ } ++ // Paper end ++ + default boolean isSkippable() { + return false; + } +diff --git a/src/main/java/net/minecraft/network/protocol/game/ClientboundContainerSetContentPacket.java b/src/main/java/net/minecraft/network/protocol/game/ClientboundContainerSetContentPacket.java +index f53c74fb1863a2d1268a4ddf8cf69d1fda32d73f..8d5939e03a065197af125d95a10134abbccd07ec 100644 +--- a/src/main/java/net/minecraft/network/protocol/game/ClientboundContainerSetContentPacket.java ++++ b/src/main/java/net/minecraft/network/protocol/game/ClientboundContainerSetContentPacket.java +@@ -36,6 +36,21 @@ public class ClientboundContainerSetContentPacket implements Packet 2097152) { ++ if (i > 2097152) { // Paper - diff on change - if this changes, update PacketEncoder + throw new RuntimeException("Chunk Packet trying to allocate too much memory on read."); + } else { + this.buffer = new byte[i]; diff --git a/patches/server/0946-Item-Mutation-Fixes.patch b/patches/server/0946-Item-Mutation-Fixes.patch deleted file mode 100644 index bad3be61f9d6..000000000000 --- a/patches/server/0946-Item-Mutation-Fixes.patch +++ /dev/null @@ -1,38 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Owen1212055 <23108066+Owen1212055@users.noreply.github.com> -Date: Wed, 20 Mar 2024 20:41:35 -0400 -Subject: [PATCH] Item Mutation Fixes - - -diff --git a/src/main/java/net/minecraft/world/inventory/EnchantmentMenu.java b/src/main/java/net/minecraft/world/inventory/EnchantmentMenu.java -index fff1c39920e7d7051dfe3dd39c77865d3bdf113e..1ef014b29645ed09ccffb898f1819428c3dc6259 100644 ---- a/src/main/java/net/minecraft/world/inventory/EnchantmentMenu.java -+++ b/src/main/java/net/minecraft/world/inventory/EnchantmentMenu.java -@@ -232,7 +232,7 @@ public class EnchantmentMenu extends AbstractContainerMenu { - return false; - } else if (this.costs[id] > 0 && !itemstack.isEmpty() && (player.experienceLevel >= j && player.experienceLevel >= this.costs[id] || player.getAbilities().instabuild)) { - this.access.execute((world, blockposition) -> { -- ItemStack itemstack2 = itemstack; -+ ItemStack itemstack2 = itemstack; // Paper - diff on change - List list = this.getEnchantmentList(world.registryAccess(), itemstack, id, this.costs[id]); - - // CraftBukkit start -@@ -255,10 +255,16 @@ public class EnchantmentMenu extends AbstractContainerMenu { - return; - } - // CraftBukkit end -- if (itemstack.is(Items.BOOK)) { -- itemstack2 = itemstack.transmuteCopy(Items.ENCHANTED_BOOK); -+ // Paper start -+ itemstack2 = org.bukkit.craftbukkit.inventory.CraftItemStack.getOrCloneOnMutation(item, event.getItem()); -+ if (itemstack2 != itemstack) { - this.enchantSlots.setItem(0, itemstack2); - } -+ if (itemstack2.is(Items.BOOK)) { -+ itemstack2 = itemstack2.transmuteCopy(Items.ENCHANTED_BOOK); -+ this.enchantSlots.setItem(0, itemstack2); -+ } -+ // Paper end - - // CraftBukkit start - for (Map.Entry entry : event.getEnchantsToAdd().entrySet()) { diff --git a/patches/server/0947-Fix-ItemFlags.patch b/patches/server/0947-Fix-ItemFlags.patch new file mode 100644 index 000000000000..e045394f3c64 --- /dev/null +++ b/patches/server/0947-Fix-ItemFlags.patch @@ -0,0 +1,199 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Jake Potrebic +Date: Sat, 27 Apr 2024 12:16:38 -0700 +Subject: [PATCH] Fix ItemFlags + +Re-adds missing functionality for HIDE_DESTROYS and +HIDE_PLACED_ON. Also adds new flag in HIDE_STORED_ENCHANTS +which was split from HIDE_ADDITIONAL_TOOLTIP. + +== AT == +public net.minecraft.world.item.AdventureModePredicate predicates + +diff --git a/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaEnchantedBook.java b/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaEnchantedBook.java +index 73fe41322e0349ad1d46a760f621b6c91112e90e..19af55ec2bf62b70bd3be44f499b32f5efe71ab1 100644 +--- a/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaEnchantedBook.java ++++ b/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaEnchantedBook.java +@@ -38,7 +38,7 @@ class CraftMetaEnchantedBook extends CraftMetaItem implements EnchantmentStorage + getOrEmpty(tag, CraftMetaEnchantedBook.STORED_ENCHANTMENTS).ifPresent((itemEnchantments) -> { + this.enchantments = buildEnchantments(itemEnchantments); + if (!itemEnchantments.showInTooltip) { +- this.addItemFlags(ItemFlag.HIDE_ADDITIONAL_TOOLTIP); ++ this.addItemFlags(ItemFlag.HIDE_STORED_ENCHANTS); // Paper - new ItemFlag + } + }); + } +@@ -53,7 +53,7 @@ class CraftMetaEnchantedBook extends CraftMetaItem implements EnchantmentStorage + void applyToItem(CraftMetaItem.Applicator itemTag) { + super.applyToItem(itemTag); + +- this.applyEnchantments(this.enchantments, itemTag, CraftMetaEnchantedBook.STORED_ENCHANTMENTS, ItemFlag.HIDE_ADDITIONAL_TOOLTIP); ++ this.applyEnchantments(this.enchantments, itemTag, CraftMetaEnchantedBook.STORED_ENCHANTMENTS, ItemFlag.HIDE_STORED_ENCHANTS); + } + + @Override +diff --git a/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaItem.java b/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaItem.java +index 8d13505c36d732f17293c6a6d65cac20919b8b7a..6b3eed94c26bc16177f9b9fadd140f9a89163af2 100644 +--- a/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaItem.java ++++ b/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaItem.java +@@ -275,6 +275,12 @@ class CraftMetaItem implements ItemMeta, Damageable, Repairable, BlockDataMeta { + static final ItemMetaKeyType HIDE_ADDITIONAL_TOOLTIP = new ItemMetaKeyType(DataComponents.HIDE_ADDITIONAL_TOOLTIP); + @Specific(Specific.To.NBT) + static final ItemMetaKeyType CUSTOM_DATA = new ItemMetaKeyType<>(DataComponents.CUSTOM_DATA); ++ // Paper start - fix ItemFlags ++ static final ItemMetaKeyType CAN_PLACE_ON = new ItemMetaKeyType<>(DataComponents.CAN_PLACE_ON); ++ static final ItemMetaKeyType CAN_BREAK = new ItemMetaKeyType<>(DataComponents.CAN_BREAK); ++ private List canPlaceOnPredicates; ++ private List canBreakPredicates; ++ // Paper end - fix ItemFlags + + // We store the raw original JSON representation of all text data. See SPIGOT-5063, SPIGOT-5656, SPIGOT-5304 + private Component displayName; +@@ -377,6 +383,10 @@ class CraftMetaItem implements ItemMeta, Damageable, Repairable, BlockDataMeta { + this.customTag = meta.customTag; + + this.version = meta.version; ++ // Paper start ++ this.canPlaceOnPredicates = meta.canPlaceOnPredicates; ++ this.canBreakPredicates = meta.canBreakPredicates; ++ // Paper end + } + + CraftMetaItem(DataComponentPatch tag) { +@@ -496,6 +506,20 @@ class CraftMetaItem implements ItemMeta, Damageable, Repairable, BlockDataMeta { + this.customTag = null; + } + }); ++ // Paper start - fix ItemFlags ++ CraftMetaItem.getOrEmpty(tag, CraftMetaItem.CAN_PLACE_ON).ifPresent(data -> { ++ this.canPlaceOnPredicates = List.copyOf(data.predicates); ++ if (!data.showInTooltip()) { ++ this.addItemFlags(ItemFlag.HIDE_PLACED_ON); ++ } ++ }); ++ CraftMetaItem.getOrEmpty(tag, CraftMetaItem.CAN_BREAK).ifPresent(data -> { ++ this.canBreakPredicates = List.copyOf(data.predicates); ++ if (!data.showInTooltip()) { ++ this.addItemFlags(ItemFlag.HIDE_DESTROYS); ++ } ++ }); ++ // Paper end - fix ItemFlags + + Set, Optional>> keys = tag.entrySet(); + for (Map.Entry, Optional> key : keys) { +@@ -735,7 +759,15 @@ class CraftMetaItem implements ItemMeta, Damageable, Repairable, BlockDataMeta { + try { + CompoundTag unhandledTag = NbtIo.readCompressed(buf, NbtAccounter.unlimitedHeap()); + DataComponentPatch unhandledPatch = DataComponentPatch.CODEC.parse(MinecraftServer.getDefaultRegistryAccess().createSerializationContext(NbtOps.INSTANCE), unhandledTag).result().get(); +- this.unhandledTags.copy(unhandledPatch); ++ // Paper start ++ CraftMetaItem.getOrEmpty(unhandledPatch, CraftMetaItem.CAN_PLACE_ON).ifPresent(data -> { ++ this.canPlaceOnPredicates = List.copyOf(data.predicates); ++ }); ++ CraftMetaItem.getOrEmpty(unhandledPatch, CraftMetaItem.CAN_BREAK).ifPresent(data -> { ++ this.canBreakPredicates = List.copyOf(data.predicates); ++ }); ++ this.unhandledTags.copy(unhandledPatch.forget(type -> type == CraftMetaItem.CAN_PLACE_ON.TYPE || type == CraftMetaItem.CAN_BREAK.TYPE)); ++ // Paper end + + for (Entry, Optional> entry : unhandledPatch.entrySet()) { + // Move removed unhandled tags to dedicated removedTags +@@ -1006,6 +1038,15 @@ class CraftMetaItem implements ItemMeta, Damageable, Repairable, BlockDataMeta { + itemTag.put(CraftMetaItem.MAX_DAMAGE, this.maxDamage); + } + ++ // Paper start ++ if (this.canPlaceOnPredicates != null && !this.canPlaceOnPredicates.isEmpty()) { ++ itemTag.put(CraftMetaItem.CAN_PLACE_ON, new net.minecraft.world.item.AdventureModePredicate(this.canPlaceOnPredicates, !this.hasItemFlag(ItemFlag.HIDE_PLACED_ON))); ++ } ++ if (this.canBreakPredicates != null && !this.canBreakPredicates.isEmpty()) { ++ itemTag.put(CraftMetaItem.CAN_BREAK, new net.minecraft.world.item.AdventureModePredicate(this.canBreakPredicates, !this.hasItemFlag(ItemFlag.HIDE_DESTROYS))); ++ } ++ // Paper end ++ + for (Map.Entry, Optional> e : this.unhandledTags.build().entrySet()) { + e.getValue().ifPresent((value) -> { + itemTag.builder.set((DataComponentType) e.getKey(), value); +@@ -1096,7 +1137,7 @@ class CraftMetaItem implements ItemMeta, Damageable, Repairable, BlockDataMeta { + + @Overridden + boolean isEmpty() { +- return !(this.hasDisplayName() || this.hasItemName() || this.hasLocalizedName() || this.hasEnchants() || (this.lore != null) || this.hasCustomModelData() || this.hasEnchantable() || this.hasBlockData() || this.hasRepairCost() || !this.unhandledTags.build().isEmpty() || !this.removedTags.isEmpty() || !this.persistentDataContainer.isEmpty() || this.hideFlag != 0 || this.isHideTooltip() || this.hasTooltipStyle() || this.hasItemModel() || this.isUnbreakable() || this.hasEnchantmentGlintOverride() || this.isGlider() || this.hasDamageResistant() || this.hasMaxStackSize() || this.hasRarity() || this.hasUseRemainder() || this.hasUseCooldown() || this.hasFood() || this.hasTool() || this.hasJukeboxPlayable() || this.hasEquippable() || this.hasDamage() || this.hasMaxDamage() || this.hasAttributeModifiers() || this.customTag != null); ++ return !(this.hasDisplayName() || this.hasItemName() || this.hasLocalizedName() || this.hasEnchants() || (this.lore != null) || this.hasCustomModelData() || this.hasEnchantable() || this.hasBlockData() || this.hasRepairCost() || !this.unhandledTags.build().isEmpty() || !this.removedTags.isEmpty() || !this.persistentDataContainer.isEmpty() || this.hideFlag != 0 || this.isHideTooltip() || this.hasTooltipStyle() || this.hasItemModel() || this.isUnbreakable() || this.hasEnchantmentGlintOverride() || this.isGlider() || this.hasDamageResistant() || this.hasMaxStackSize() || this.hasRarity() || this.hasUseRemainder() || this.hasUseCooldown() || this.hasFood() || this.hasTool() || this.hasJukeboxPlayable() || this.hasEquippable() || this.hasDamage() || this.hasMaxDamage() || this.hasAttributeModifiers() || this.customTag != null || this.canPlaceOnPredicates != null || this.canBreakPredicates != null); // Paper + } + + // Paper start +@@ -1889,6 +1930,8 @@ class CraftMetaItem implements ItemMeta, Damageable, Repairable, BlockDataMeta { + && (this.hasJukeboxPlayable() ? that.hasJukeboxPlayable() && this.jukebox.equals(that.jukebox) : !that.hasJukeboxPlayable()) + && (this.hasDamage() ? that.hasDamage() && this.damage == that.damage : !that.hasDamage()) + && (this.hasMaxDamage() ? that.hasMaxDamage() && this.maxDamage.equals(that.maxDamage) : !that.hasMaxDamage()) ++ && (this.canPlaceOnPredicates != null ? that.canPlaceOnPredicates != null && this.canPlaceOnPredicates.equals(that.canPlaceOnPredicates) : that.canPlaceOnPredicates == null) // Paper ++ && (this.canBreakPredicates != null ? that.canBreakPredicates != null && this.canBreakPredicates.equals(that.canBreakPredicates) : that.canBreakPredicates == null) // Paper + && (this.version == that.version); + } + +@@ -1941,6 +1984,8 @@ class CraftMetaItem implements ItemMeta, Damageable, Repairable, BlockDataMeta { + hash = 61 * hash + (this.hasDamage() ? this.damage : 0); + hash = 61 * hash + (this.hasMaxDamage() ? 1231 : 1237); + hash = 61 * hash + (this.hasAttributeModifiers() ? this.attributeModifiers.hashCode() : 0); ++ hash = 61 * hash + (this.canPlaceOnPredicates != null ? this.canPlaceOnPredicates.hashCode() : 0); // Paper ++ hash = 61 * hash + (this.canBreakPredicates != null ? this.canBreakPredicates.hashCode() : 0); // Paper + hash = 61 * hash + this.version; + return hash; + } +@@ -1998,6 +2043,14 @@ class CraftMetaItem implements ItemMeta, Damageable, Repairable, BlockDataMeta { + clone.damage = this.damage; + clone.maxDamage = this.maxDamage; + clone.version = this.version; ++ // Paper start ++ if (this.canPlaceOnPredicates != null) { ++ clone.canPlaceOnPredicates = List.copyOf(this.canPlaceOnPredicates); ++ } ++ if (this.canBreakPredicates != null) { ++ clone.canBreakPredicates = List.copyOf(this.canBreakPredicates); ++ } ++ // Paper end + return clone; + } catch (CloneNotSupportedException e) { + throw new Error(e); +@@ -2142,6 +2195,16 @@ class CraftMetaItem implements ItemMeta, Damageable, Repairable, BlockDataMeta { + } + } + ++ // Paper start ++ final boolean canBreakAddToUnhandled = this.canBreakPredicates != null && !this.canBreakPredicates.isEmpty(); ++ if (canBreakAddToUnhandled) { ++ this.unhandledTags.set(DataComponents.CAN_BREAK, new net.minecraft.world.item.AdventureModePredicate(this.canBreakPredicates, !this.hasItemFlag(ItemFlag.HIDE_DESTROYS))); ++ } ++ final boolean canPlaceOnAddToUnhandled = this.canPlaceOnPredicates != null && !this.canPlaceOnPredicates.isEmpty(); ++ if (canPlaceOnAddToUnhandled) { ++ this.unhandledTags.set(DataComponents.CAN_PLACE_ON, new net.minecraft.world.item.AdventureModePredicate(this.canPlaceOnPredicates, !this.hasItemFlag(ItemFlag.HIDE_PLACED_ON))); ++ } ++ // Paper end + if (!this.unhandledTags.isEmpty()) { + net.minecraft.nbt.Tag unhandled = DataComponentPatch.CODEC.encodeStart(MinecraftServer.getDefaultRegistryAccess().createSerializationContext(NbtOps.INSTANCE), this.unhandledTags.build()).getOrThrow(IllegalStateException::new); + try { +@@ -2152,6 +2215,14 @@ class CraftMetaItem implements ItemMeta, Damageable, Repairable, BlockDataMeta { + Logger.getLogger(CraftMetaItem.class.getName()).log(Level.SEVERE, null, ex); + } + } ++ // Paper start ++ if (canBreakAddToUnhandled) { ++ this.unhandledTags.clear(DataComponents.CAN_BREAK); ++ } ++ if (canPlaceOnAddToUnhandled) { ++ this.unhandledTags.clear(DataComponents.CAN_PLACE_ON); ++ } ++ // Paper end + + if (!this.removedTags.isEmpty()) { + RegistryAccess registryAccess = CraftRegistry.getMinecraftRegistry(); +@@ -2312,6 +2383,8 @@ class CraftMetaItem implements ItemMeta, Damageable, Repairable, BlockDataMeta { + CraftMetaItem.MAX_DAMAGE.TYPE, + CraftMetaItem.CUSTOM_DATA.TYPE, + CraftMetaItem.ATTRIBUTES.TYPE, ++ CraftMetaItem.CAN_PLACE_ON.TYPE, // Paper ++ CraftMetaItem.CAN_BREAK.TYPE, // Paper + CraftMetaArmor.TRIM.TYPE, + CraftMetaArmorStand.ENTITY_TAG.TYPE, + CraftMetaBanner.PATTERNS.TYPE, diff --git a/patches/server/0947-Per-world-ticks-per-spawn-settings.patch b/patches/server/0947-Per-world-ticks-per-spawn-settings.patch deleted file mode 100644 index 0d7c9ea5b4eb..000000000000 --- a/patches/server/0947-Per-world-ticks-per-spawn-settings.patch +++ /dev/null @@ -1,35 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Jake Potrebic -Date: Sat, 13 Nov 2021 12:36:26 -0800 -Subject: [PATCH] Per world ticks per spawn settings - - -diff --git a/src/main/java/net/minecraft/world/level/Level.java b/src/main/java/net/minecraft/world/level/Level.java -index 471fd54edf6aa962d997878ee638974f7f594fa8..b7bf7b3b91046c81467aeb483087e12b6d9191bf 100644 ---- a/src/main/java/net/minecraft/world/level/Level.java -+++ b/src/main/java/net/minecraft/world/level/Level.java -@@ -186,6 +186,15 @@ public abstract class Level implements LevelAccessor, AutoCloseable { - return this.getChunkIfLoaded(chunkX, chunkZ) != null; - } - // Paper end - Use getChunkIfLoadedImmediately -+ // Paper start - per world ticks per spawn -+ private int getTicksPerSpawn(SpawnCategory spawnCategory) { -+ final int perWorld = this.paperConfig().entities.spawning.ticksPerSpawn.getInt(CraftSpawnCategory.toNMS(spawnCategory)); -+ if (perWorld >= 0) { -+ return perWorld; -+ } -+ return this.getCraftServer().getTicksPerSpawns(spawnCategory); -+ } -+ // Paper end - - public abstract ResourceKey getTypeKey(); - -@@ -198,7 +207,7 @@ public abstract class Level implements LevelAccessor, AutoCloseable { - // CraftBukkit Ticks things - for (SpawnCategory spawnCategory : SpawnCategory.values()) { - if (CraftSpawnCategory.isValidForLimits(spawnCategory)) { -- this.ticksPerSpawnCategory.put(spawnCategory, (long) this.getCraftServer().getTicksPerSpawns(spawnCategory)); -+ this.ticksPerSpawnCategory.put(spawnCategory, this.getTicksPerSpawn(spawnCategory)); // Paper - } - } - diff --git a/patches/server/0948-Fix-damage-modifier-inconsistencies.patch b/patches/server/0948-Fix-damage-modifier-inconsistencies.patch new file mode 100644 index 000000000000..1494c35c0642 --- /dev/null +++ b/patches/server/0948-Fix-damage-modifier-inconsistencies.patch @@ -0,0 +1,27 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Lulu13022002 <41980282+Lulu13022002@users.noreply.github.com> +Date: Sat, 27 Apr 2024 21:51:58 +0200 +Subject: [PATCH] Fix damage modifier inconsistencies + +Affect the falling stalactite damage type where the +reduction is not applied like in Vanilla. +Additionally fix the "is_freezing" damage type tag. + +diff --git a/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java b/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java +index 418e67ef5896325fe143501f5a4f1604b065ba0f..864e4c660bf3d381880e5928b6945bea213ac57e 100644 +--- a/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java ++++ b/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java +@@ -1222,11 +1222,11 @@ public class CraftEventFactory { + Map> modifierFunctions = new EnumMap<>(DamageModifier.class); + modifiers.put(DamageModifier.BASE, rawDamage); + modifierFunctions.put(DamageModifier.BASE, CraftEventFactory.ZERO); +- if (source.is(DamageTypes.FREEZE)) { ++ if (source.is(DamageTypeTags.IS_FREEZING)) { // Paper + modifiers.put(DamageModifier.FREEZING, freezingModifier); + modifierFunctions.put(DamageModifier.FREEZING, freezing); + } +- if (source.is(DamageTypes.FALLING_BLOCK) || source.is(DamageTypes.FALLING_ANVIL)) { ++ if (source.is(DamageTypeTags.DAMAGES_HELMET)) { // Paper + modifiers.put(DamageModifier.HARD_HAT, hardHatModifier); + modifierFunctions.put(DamageModifier.HARD_HAT, hardHat); + } diff --git a/patches/server/0948-Properly-track-the-changed-item-from-dispense-events.patch b/patches/server/0948-Properly-track-the-changed-item-from-dispense-events.patch deleted file mode 100644 index 2dc797fea793..000000000000 --- a/patches/server/0948-Properly-track-the-changed-item-from-dispense-events.patch +++ /dev/null @@ -1,112 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Jake Potrebic -Date: Mon, 12 Dec 2022 12:14:03 -0800 -Subject: [PATCH] Properly track the changed item from dispense events - - -diff --git a/src/main/java/net/minecraft/core/dispenser/DispenseItemBehavior.java b/src/main/java/net/minecraft/core/dispenser/DispenseItemBehavior.java -index 0c0b8a49b3342cd015381c6a93fab23c32cd32e6..60d3319016beb4f60cbc26dde165f64cf7577602 100644 ---- a/src/main/java/net/minecraft/core/dispenser/DispenseItemBehavior.java -+++ b/src/main/java/net/minecraft/core/dispenser/DispenseItemBehavior.java -@@ -134,10 +134,14 @@ public interface DispenseItemBehavior { - idispensebehavior.dispense(pointer, eventStack); - return stack; - } -+ // Paper start - track changed items in the dispense event -+ itemstack1 = CraftItemStack.unwrap(event.getItem()); // unwrap is safe because the stack won't be modified -+ entitytypes = ((SpawnEggItem) itemstack1.getItem()).getType(itemstack1); -+ // Paper end - track changed item from dispense event - } - - try { -- entitytypes.spawn(pointer.level(), stack, (Player) null, pointer.pos().relative(enumdirection), MobSpawnType.DISPENSER, enumdirection != Direction.UP, false); -+ entitytypes.spawn(pointer.level(), itemstack1, (Player) null, pointer.pos().relative(enumdirection), MobSpawnType.DISPENSER, enumdirection != Direction.UP, false); // Paper - track changed item in dispense event - } catch (Exception exception) { - DispenseItemBehavior.LOGGER.error("Error while dispensing spawn egg from dispenser at {}", pointer.pos(), exception); // CraftBukkit - decompile error - return ItemStack.EMPTY; -@@ -192,9 +196,10 @@ public interface DispenseItemBehavior { - } - // CraftBukkit end - -+ final ItemStack newStack = CraftItemStack.unwrap(event.getItem()); // Paper - use event itemstack (unwrap is fine here because the stack won't be modified) - Consumer consumer = EntityType.appendDefaultStackConfig((entityarmorstand) -> { - entityarmorstand.setYRot(enumdirection.toYRot()); -- }, worldserver, stack, (Player) null); -+ }, worldserver, newStack, (Player) null); // Paper - track changed items in the dispense event - ArmorStand entityarmorstand = (ArmorStand) EntityType.ARMOR_STAND.spawn(worldserver, consumer, blockposition, MobSpawnType.DISPENSER, false, false); - - if (entityarmorstand != null) { -@@ -244,7 +249,7 @@ public interface DispenseItemBehavior { - return stack; - } - } -- ((Saddleable) list.get(0)).equipSaddle(itemstack1, SoundSource.BLOCKS); -+ ((Saddleable) list.get(0)).equipSaddle(CraftItemStack.asNMSCopy(event.getItem()), SoundSource.BLOCKS); // Paper - track changed items in dispense event - // CraftBukkit end - if (shrink) stack.shrink(1); // Paper - actually handle here - this.setSuccess(true); -@@ -414,6 +419,7 @@ public interface DispenseItemBehavior { - int y = blockposition.getY(); - int z = blockposition.getZ(); - BlockState iblockdata = worldserver.getBlockState(blockposition); -+ ItemStack dispensedItem = stack; // Paper - track changed item from the dispense event - // Paper start - correctly check if the bucket place will succeed - /* Taken from SolidBucketItem#emptyContents */ - boolean willEmptyContentsSolidBucketItem = dispensiblecontaineritem instanceof net.minecraft.world.item.SolidBucketItem && worldserver.isInWorldBounds(blockposition) && iblockdata.isAir(); -@@ -443,12 +449,15 @@ public interface DispenseItemBehavior { - } - } - -- dispensiblecontaineritem = (DispensibleContainerItem) CraftItemStack.asNMSCopy(event.getItem()).getItem(); -+ // Paper start - track changed item from dispense event -+ dispensedItem = CraftItemStack.unwrap(event.getItem()); // unwrap is safe here as the stack isn't mutated -+ dispensiblecontaineritem = (DispensibleContainerItem) dispensedItem.getItem(); -+ // Paper end - track changed item from dispense event - } - // CraftBukkit end - - if (dispensiblecontaineritem.emptyContents((Player) null, worldserver, blockposition, (BlockHitResult) null)) { -- dispensiblecontaineritem.checkExtraContent((Player) null, worldserver, stack, blockposition); -+ dispensiblecontaineritem.checkExtraContent((Player) null, worldserver, dispensedItem, blockposition); // Paper - track changed item from dispense event - return this.consumeWithRemainder(pointer, stack, new ItemStack(Items.BUCKET)); - } else { - return this.defaultDispenseItemBehavior.dispense(pointer, stack); -diff --git a/src/main/java/net/minecraft/core/dispenser/ProjectileDispenseBehavior.java b/src/main/java/net/minecraft/core/dispenser/ProjectileDispenseBehavior.java -index 985954030654d521291cccbfc3ca49b67ee4357d..e37d2d29f3ba67cfe28abe4847a3dca07121f0be 100644 ---- a/src/main/java/net/minecraft/core/dispenser/ProjectileDispenseBehavior.java -+++ b/src/main/java/net/minecraft/core/dispenser/ProjectileDispenseBehavior.java -@@ -36,7 +36,7 @@ public class ProjectileDispenseBehavior extends DefaultDispenseItemBehavior { - ServerLevel worldserver = pointer.level(); - Direction enumdirection = (Direction) pointer.state().getValue(DispenserBlock.FACING); - Position iposition = this.dispenseConfig.positionFunction().getDispensePosition(pointer, enumdirection); -- Projectile iprojectile = this.projectileItem.asProjectile(worldserver, iposition, stack, enumdirection); -+ // Paper - move down - - // CraftBukkit start - // this.projectileItem.shoot(iprojectile, (double) enumdirection.getStepX(), (double) enumdirection.getStepY(), (double) enumdirection.getStepZ(), this.dispenseConfig.power(), this.dispenseConfig.uncertainty()); -@@ -66,6 +66,7 @@ public class ProjectileDispenseBehavior extends DefaultDispenseItemBehavior { - } - } - -+ Projectile iprojectile = this.projectileItem.asProjectile(worldserver, iposition, CraftItemStack.unwrap(event.getItem()), enumdirection); // Paper - move from above and track changed items in the dispense event; unwrap is safe here because all uses of the stack make their own copies - this.projectileItem.shoot(iprojectile, event.getVelocity().getX(), event.getVelocity().getY(), event.getVelocity().getZ(), this.dispenseConfig.power(), this.dispenseConfig.uncertainty()); - ((Entity) iprojectile).projectileSource = new org.bukkit.craftbukkit.projectiles.CraftBlockProjectileSource(pointer.blockEntity()); - // CraftBukkit end -diff --git a/src/main/java/net/minecraft/core/dispenser/ShulkerBoxDispenseBehavior.java b/src/main/java/net/minecraft/core/dispenser/ShulkerBoxDispenseBehavior.java -index 6f2adf2334e35e8a617a4ced0c1af2abf32bbd8d..cb308808906a8cdb127df8284e106e00553473ca 100644 ---- a/src/main/java/net/minecraft/core/dispenser/ShulkerBoxDispenseBehavior.java -+++ b/src/main/java/net/minecraft/core/dispenser/ShulkerBoxDispenseBehavior.java -@@ -57,7 +57,12 @@ public class ShulkerBoxDispenseBehavior extends OptionalDispenseItemBehavior { - // CraftBukkit end - - try { -- this.setSuccess(((BlockItem) item).place(new DirectionalPlaceContext(pointer.level(), blockposition, enumdirection, stack, enumdirection1)).consumesAction()); -+ // Paper start - track changed items in the dispense event -+ this.setSuccess(((BlockItem) item).place(new DirectionalPlaceContext(pointer.level(), blockposition, enumdirection, CraftItemStack.asNMSCopy(event.getItem()), enumdirection1)).consumesAction()); -+ if (this.isSuccess()) { -+ stack.shrink(1); // vanilla shrink is in the place function above, manually handle it here -+ } -+ // Paper end - track changed items in the dispense event - } catch (Exception exception) { - ShulkerBoxDispenseBehavior.LOGGER.error("Error trying to place shulker box at {}", blockposition, exception); - } diff --git a/patches/server/0949-Protect-Bedrock-and-End-Portal-Frames-from-being-des.patch b/patches/server/0949-Protect-Bedrock-and-End-Portal-Frames-from-being-des.patch deleted file mode 100644 index 89974873d20c..000000000000 --- a/patches/server/0949-Protect-Bedrock-and-End-Portal-Frames-from-being-des.patch +++ /dev/null @@ -1,169 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Aikar -Date: Wed, 13 May 2020 23:01:26 -0400 -Subject: [PATCH] Protect Bedrock and End Portal/Frames from being destroyed - -This fixes exploits that let players destroy bedrock by Pistons, explosions -and Mushrooom/Tree generation. - -These blocks are designed to not be broken except by creative players/commands. -So protect them from a multitude of methods of destroying them. - -A config is provided if you rather let players use these exploits, and let -them destroy the worlds End Portals and get on top of the nether easy. - -diff --git a/src/main/java/net/minecraft/world/level/Explosion.java b/src/main/java/net/minecraft/world/level/Explosion.java -index edcc9d53ad81e2b2444335ac79abde5a4e2d9d47..2109257fde8606abda4f41427f690da3b96c0175 100644 ---- a/src/main/java/net/minecraft/world/level/Explosion.java -+++ b/src/main/java/net/minecraft/world/level/Explosion.java -@@ -194,6 +194,7 @@ public class Explosion { - for (float f1 = 0.3F; f > 0.0F; f -= 0.22500001F) { - BlockPos blockposition = BlockPos.containing(d4, d5, d6); - BlockState iblockdata = this.level.getBlockState(blockposition); -+ if (!iblockdata.isDestroyable()) continue; // Paper - Protect Bedrock and End Portal/Frames from being destroyed - FluidState fluid = iblockdata.getFluidState(); // Paper - Perf: Optimize call to getFluid for explosions - - if (!this.level.isInWorldBounds(blockposition)) { -diff --git a/src/main/java/net/minecraft/world/level/Level.java b/src/main/java/net/minecraft/world/level/Level.java -index 9ca71a060f916868e70222e0cdc0f96b96bac450..751da19e147049c4f196d9b200b7baf9bebaed81 100644 ---- a/src/main/java/net/minecraft/world/level/Level.java -+++ b/src/main/java/net/minecraft/world/level/Level.java -@@ -447,6 +447,10 @@ public abstract class Level implements LevelAccessor, AutoCloseable { - public boolean setBlock(BlockPos pos, BlockState state, int flags, int maxUpdateDepth) { - // CraftBukkit start - tree generation - if (this.captureTreeGeneration) { -+ // Paper start - Protect Bedrock and End Portal/Frames from being destroyed -+ BlockState type = getBlockState(pos); -+ if (!type.isDestroyable()) return false; -+ // Paper end - Protect Bedrock and End Portal/Frames from being destroyed - CraftBlockState blockstate = this.capturedBlockStates.get(pos); - if (blockstate == null) { - blockstate = CapturedBlockState.getTreeBlockState(this, pos, flags); -diff --git a/src/main/java/net/minecraft/world/level/block/Block.java b/src/main/java/net/minecraft/world/level/block/Block.java -index bf52c36f31992a01a7403d8c85151327c9e944c4..3b06c080afebde1d649f05eca0af938ba32931c1 100644 ---- a/src/main/java/net/minecraft/world/level/block/Block.java -+++ b/src/main/java/net/minecraft/world/level/block/Block.java -@@ -89,6 +89,19 @@ public class Block extends BlockBehaviour implements ItemLike { - protected final StateDefinition stateDefinition; - private BlockState defaultBlockState; - // Paper start -+ public final boolean isDestroyable() { -+ return io.papermc.paper.configuration.GlobalConfiguration.get().unsupportedSettings.allowPermanentBlockBreakExploits || -+ this != Blocks.BARRIER && -+ this != Blocks.BEDROCK && -+ this != Blocks.END_PORTAL_FRAME && -+ this != Blocks.END_PORTAL && -+ this != Blocks.END_GATEWAY && -+ this != Blocks.COMMAND_BLOCK && -+ this != Blocks.REPEATING_COMMAND_BLOCK && -+ this != Blocks.CHAIN_COMMAND_BLOCK && -+ this != Blocks.STRUCTURE_BLOCK && -+ this != Blocks.JIGSAW; -+ } - public co.aikar.timings.Timing timing; - public co.aikar.timings.Timing getTiming() { - if (timing == null) { -diff --git a/src/main/java/net/minecraft/world/level/block/piston/PistonBaseBlock.java b/src/main/java/net/minecraft/world/level/block/piston/PistonBaseBlock.java -index e6bfbe2588e0c2a1be14e38d654e889d392ad4db..e0c62227b279a5fe0f3868fbf9ce8c78c515a09c 100644 ---- a/src/main/java/net/minecraft/world/level/block/piston/PistonBaseBlock.java -+++ b/src/main/java/net/minecraft/world/level/block/piston/PistonBaseBlock.java -@@ -213,6 +213,12 @@ public class PistonBaseBlock extends DirectionalBlock { - @Override - protected boolean triggerEvent(BlockState state, Level world, BlockPos pos, int type, int data) { - Direction enumdirection = (Direction) state.getValue(PistonBaseBlock.FACING); -+ // Paper start - Protect Bedrock and End Portal/Frames from being destroyed; prevent retracting when we're facing the wrong way (we were replaced before retraction could occur) -+ Direction directionQueuedAs = Direction.from3DDataValue(data & 7); // Paper - copied from below -+ if (!io.papermc.paper.configuration.GlobalConfiguration.get().unsupportedSettings.allowPermanentBlockBreakExploits && enumdirection != directionQueuedAs) { -+ return false; -+ } -+ // Paper end - Protect Bedrock and End Portal/Frames from being destroyed - BlockState iblockdata1 = (BlockState) state.setValue(PistonBaseBlock.EXTENDED, true); - - if (!world.isClientSide) { -@@ -253,7 +259,7 @@ public class PistonBaseBlock extends DirectionalBlock { - } - // Paper end - Fix sticky pistons and BlockPistonRetractEvent - world.setBlock(pos, iblockdata2, 20); -- world.setBlockEntity(MovingPistonBlock.newMovingBlockEntity(pos, iblockdata2, (BlockState) this.defaultBlockState().setValue(PistonBaseBlock.FACING, Direction.from3DDataValue(data & 7)), enumdirection, false, true)); -+ world.setBlockEntity(MovingPistonBlock.newMovingBlockEntity(pos, iblockdata2, (BlockState) this.defaultBlockState().setValue(PistonBaseBlock.FACING, Direction.from3DDataValue(data & 7)), enumdirection, false, true)); // Paper - Protect Bedrock and End Portal/Frames from being destroyed; diff on change - world.blockUpdated(pos, iblockdata2.getBlock()); - iblockdata2.updateNeighbourShapes(world, pos, 2); - if (this.isSticky) { -@@ -289,7 +295,14 @@ public class PistonBaseBlock extends DirectionalBlock { - } - } - } else { -- world.removeBlock(pos.relative(enumdirection), false); -+ // Paper start - Protect Bedrock and End Portal/Frames from being destroyed; fix headless pistons breaking blocks -+ BlockPos headPos = pos.relative(enumdirection); -+ if (io.papermc.paper.configuration.GlobalConfiguration.get().unsupportedSettings.allowPermanentBlockBreakExploits || world.getBlockState(headPos) == Blocks.PISTON_HEAD.defaultBlockState().setValue(FACING, enumdirection)) { // double check to make sure we're not a headless piston. -+ world.removeBlock(headPos, false); -+ } else { -+ ((ServerLevel) world).getChunkSource().blockChanged(headPos); // ... fix client desync -+ } -+ // Paper end - Protect Bedrock and End Portal/Frames from being destroyed - } - - world.playSound((Player) null, pos, SoundEvents.PISTON_CONTRACT, SoundSource.BLOCKS, 0.5F, world.random.nextFloat() * 0.15F + 0.6F); -diff --git a/src/main/java/net/minecraft/world/level/block/state/BlockBehaviour.java b/src/main/java/net/minecraft/world/level/block/state/BlockBehaviour.java -index 240c250a93289776686d09d7eae17c07d7278da5..f2036917c5ba9f536087d7ee559704055469730e 100644 ---- a/src/main/java/net/minecraft/world/level/block/state/BlockBehaviour.java -+++ b/src/main/java/net/minecraft/world/level/block/state/BlockBehaviour.java -@@ -174,7 +174,7 @@ public abstract class BlockBehaviour implements FeatureElement { - } - - protected void onExplosionHit(BlockState state, Level world, BlockPos pos, Explosion explosion, BiConsumer stackMerger) { -- if (!state.isAir() && explosion.getBlockInteraction() != Explosion.BlockInteraction.TRIGGER_BLOCK) { -+ if (!state.isAir() && explosion.getBlockInteraction() != Explosion.BlockInteraction.TRIGGER_BLOCK && state.isDestroyable()) { // Paper - Protect Bedrock and End Portal/Frames from being destroyed - Block block = state.getBlock(); - boolean flag = explosion.getIndirectSourceEntity() instanceof Player; - -@@ -254,7 +254,7 @@ public abstract class BlockBehaviour implements FeatureElement { - } - - protected boolean canBeReplaced(BlockState state, BlockPlaceContext context) { -- return state.canBeReplaced() && (context.getItemInHand().isEmpty() || !context.getItemInHand().is(this.asItem())); -+ return state.canBeReplaced() && (context.getItemInHand().isEmpty() || !context.getItemInHand().is(this.asItem())) && (state.isDestroyable() || (context.getPlayer() != null && context.getPlayer().getAbilities().instabuild)); // Paper - Protect Bedrock and End Portal/Frames from being destroyed - } - - protected boolean canBeReplaced(BlockState state, Fluid fluid) { -@@ -883,6 +883,12 @@ public abstract class BlockBehaviour implements FeatureElement { - return this.legacySolid; - } - -+ // Paper start - Protect Bedrock and End Portal/Frames from being destroyed -+ public final boolean isDestroyable() { -+ return getBlock().isDestroyable(); -+ } -+ // Paper end - Protect Bedrock and End Portal/Frames from being destroyed -+ - public boolean isValidSpawn(BlockGetter world, BlockPos pos, EntityType type) { - return this.getBlock().properties.isValidSpawn.test(this.asState(), world, pos, type); - } -@@ -986,7 +992,7 @@ public abstract class BlockBehaviour implements FeatureElement { - } - - public PushReaction getPistonPushReaction() { -- return this.pushReaction; -+ return !this.isDestroyable() ? PushReaction.BLOCK : this.pushReaction; // Paper - Protect Bedrock and End Portal/Frames from being destroyed - } - - public boolean isSolidRender(BlockGetter world, BlockPos pos) { -diff --git a/src/main/java/net/minecraft/world/level/portal/PortalForcer.java b/src/main/java/net/minecraft/world/level/portal/PortalForcer.java -index 5c4b2a33d4007c36aef68604bca40a4eba510b4e..fd04a50183ccb1f21fc6efa70256e1bb4db2d6d4 100644 ---- a/src/main/java/net/minecraft/world/level/portal/PortalForcer.java -+++ b/src/main/java/net/minecraft/world/level/portal/PortalForcer.java -@@ -207,6 +207,13 @@ public class PortalForcer { - for (int j = -1; j < 3; ++j) { - for (int k = -1; k < 4; ++k) { - temp.setWithOffset(pos, portalDirection.getStepX() * j + enumdirection1.getStepX() * distanceOrthogonalToPortal, k, portalDirection.getStepZ() * j + enumdirection1.getStepZ() * distanceOrthogonalToPortal); -+ // Paper start - Protect Bedrock and End Portal/Frames from being destroyed -+ if (!io.papermc.paper.configuration.GlobalConfiguration.get().unsupportedSettings.allowPermanentBlockBreakExploits) { -+ if (!this.level.getBlockState(temp).isDestroyable()) { -+ return false; -+ } -+ } -+ // Paper end - Protect Bedrock and End Portal/Frames from being destroyed - if (k < 0 && !this.level.getBlockState(temp).isSolid()) { - return false; - } diff --git a/patches/server/0949-Revert-to-vanilla-handling-of-LivingEntity-actuallyH.patch b/patches/server/0949-Revert-to-vanilla-handling-of-LivingEntity-actuallyH.patch new file mode 100644 index 000000000000..035254da6706 --- /dev/null +++ b/patches/server/0949-Revert-to-vanilla-handling-of-LivingEntity-actuallyH.patch @@ -0,0 +1,50 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Jake Potrebic +Date: Sat, 27 Apr 2024 09:44:53 -0700 +Subject: [PATCH] Revert to vanilla handling of LivingEntity#actuallyHurt + + +diff --git a/src/main/java/net/minecraft/world/entity/LivingEntity.java b/src/main/java/net/minecraft/world/entity/LivingEntity.java +index f6d55ff3027bb7f0dcef186c52d48d9c5358ffd0..563e008740bf2017a1767470a2e34629dfa5cfa1 100644 +--- a/src/main/java/net/minecraft/world/entity/LivingEntity.java ++++ b/src/main/java/net/minecraft/world/entity/LivingEntity.java +@@ -1455,7 +1455,7 @@ public abstract class LivingEntity extends Entity implements Attackable { + amount = 0.0F; + } + +- float f1 = amount; ++ float f1 = amount; final float originalAmount = f1; // Paper - revert to vanilla #hurt - OBFHELPER + boolean flag = amount > 0.0F && this.isDamageSourceBlocked(source); // Copied from below + float f2 = 0.0F; + +@@ -1513,6 +1513,7 @@ public abstract class LivingEntity extends Entity implements Attackable { + if (!this.actuallyHurt(world, source, (float) event.getFinalDamage() - this.lastHurt, event)) { + return false; + } ++ if (this instanceof ServerPlayer && event.getDamage() == 0 && originalAmount == 0) return false; // Paper - revert to vanilla damage - players are not affected by damage that is 0 - skip damage if the vanilla damage is 0 and was not modified by plugins in the event. + // CraftBukkit end + this.lastHurt = amount; + flag1 = false; +@@ -1521,6 +1522,7 @@ public abstract class LivingEntity extends Entity implements Attackable { + if (!this.actuallyHurt(world, source, (float) event.getFinalDamage(), event)) { + return false; + } ++ if (this instanceof ServerPlayer && event.getDamage() == 0 && originalAmount == 0) return false; // Paper - revert to vanilla damage - players are not affected by damage that is 0 - skip damage if the vanilla damage is 0 and was not modified by plugins in the event. + this.lastHurt = amount; + this.invulnerableTime = this.invulnerableDuration; // CraftBukkit - restore use of maxNoDamageTicks + // this.actuallyHurt(worldserver, damagesource, f); +@@ -2486,12 +2488,12 @@ public abstract class LivingEntity extends Entity implements Attackable { + + return true; + } else { +- return originalDamage > 0; ++ return true; // Paper - return false ONLY if event was cancelled + } + // CraftBukkit end + } + } +- return false; // CraftBukkit ++ return true; // CraftBukkit // Paper - return false ONLY if event was cancelled + } + + public CombatTracker getCombatTracker() { diff --git a/patches/server/0950-Fix-tripwire-disarming-not-working-as-intended.patch b/patches/server/0950-Fix-tripwire-disarming-not-working-as-intended.patch deleted file mode 100644 index fa7f6bde0667..000000000000 --- a/patches/server/0950-Fix-tripwire-disarming-not-working-as-intended.patch +++ /dev/null @@ -1,22 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: DungeonDev -Date: Sun, 2 Jul 2023 02:34:54 +0100 -Subject: [PATCH] Fix tripwire disarming not working as intended - -Fixes MC-129055 - -diff --git a/src/main/java/net/minecraft/world/level/block/TripWireHookBlock.java b/src/main/java/net/minecraft/world/level/block/TripWireHookBlock.java -index 8614fad5b3df7a6030384b108b1689bf6b9f1209..76aca266d3f3222502ff4c196228f08fcd88c5f8 100644 ---- a/src/main/java/net/minecraft/world/level/block/TripWireHookBlock.java -+++ b/src/main/java/net/minecraft/world/level/block/TripWireHookBlock.java -@@ -202,9 +202,8 @@ public class TripWireHookBlock extends Block { - BlockState iblockdata4 = aiblockdata[l]; - - if (iblockdata4 != null) { -+ if (world.getBlockState(blockposition2).is(Blocks.TRIPWIRE) || io.papermc.paper.configuration.GlobalConfiguration.get().unsupportedSettings.allowTripwireDisarmingExploits) { // Paper - Fix tripwire disarming not working as intended - world.setBlock(blockposition2, (BlockState) iblockdata4.trySetValue(TripWireHookBlock.ATTACHED, flag4), 3); -- if (!world.getBlockState(blockposition2).isAir()) { -- ; - } - } - } diff --git a/patches/server/0950-improve-checking-handled-tags-in-itemmeta.patch b/patches/server/0950-improve-checking-handled-tags-in-itemmeta.patch new file mode 100644 index 000000000000..8d257c881d29 --- /dev/null +++ b/patches/server/0950-improve-checking-handled-tags-in-itemmeta.patch @@ -0,0 +1,887 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Jake Potrebic +Date: Mon, 10 Jul 2023 16:10:15 -0700 +Subject: [PATCH] improve checking handled tags in itemmeta + + +diff --git a/src/main/java/org/bukkit/craftbukkit/inventory/CraftItemMetas.java b/src/main/java/org/bukkit/craftbukkit/inventory/CraftItemMetas.java +index d29f4dd9808e2001c79535d663ca77d6840f6092..1fd6e5a1ada184f9b399b7c41718ffd7db086d91 100644 +--- a/src/main/java/org/bukkit/craftbukkit/inventory/CraftItemMetas.java ++++ b/src/main/java/org/bukkit/craftbukkit/inventory/CraftItemMetas.java +@@ -40,120 +40,120 @@ import org.bukkit.inventory.meta.TropicalFishBucketMeta; + + public final class CraftItemMetas { + +- public record ItemMetaData(Class metaClass, Function fromItemStack, ++ public record ItemMetaData(Class metaClass, BiFunction>, I> fromItemStack, + BiFunction, CraftMetaItem, I> fromItemMeta) { + } + + private static final ItemMetaData EMPTY_META_DATA = new ItemMetaData<>(ItemMeta.class, +- item -> null, ++ (item, extras) -> null, + (type, meta) -> null); + + private static final ItemMetaData ITEM_META_DATA = new ItemMetaData<>(ItemMeta.class, +- item -> new CraftMetaItem(item.getComponentsPatch()), ++ (item, extras) -> new CraftMetaItem(item.getComponentsPatch(), extras), + (type, meta) -> new CraftMetaItem(meta)); + + private static final ItemMetaData SIGNED_BOOK_META_DATA = new ItemMetaData<>(BookMeta.class, +- item -> new CraftMetaBookSigned(item.getComponentsPatch()), ++ (item, extras) -> new CraftMetaBookSigned(item.getComponentsPatch(), extras), + (type, meta) -> meta instanceof CraftMetaBookSigned signed ? signed : new CraftMetaBookSigned(meta)); + + private static final ItemMetaData WRITABLE_BOOK_META_DATA = new ItemMetaData<>(BookMeta.class, +- item -> new CraftMetaBook(item.getComponentsPatch()), ++ (item, extras) -> new CraftMetaBook(item.getComponentsPatch(), extras), + (type, meta) -> meta != null && meta.getClass().equals(CraftMetaBook.class) ? (BookMeta) meta : new CraftMetaBook(meta)); + + private static final ItemMetaData SKULL_META_DATA = new ItemMetaData<>(SkullMeta.class, +- item -> new CraftMetaSkull(item.getComponentsPatch()), ++ (item, extras) -> new CraftMetaSkull(item.getComponentsPatch(), extras), + (type, meta) -> meta instanceof CraftMetaSkull skull ? skull : new CraftMetaSkull(meta)); + + private static final ItemMetaData ARMOR_META_DATA = new ItemMetaData<>(ArmorMeta.class, +- item -> new CraftMetaArmor(item.getComponentsPatch()), ++ (item, extras) -> new CraftMetaArmor(item.getComponentsPatch(), extras), + (type, meta) -> meta != null && meta.getClass().equals(CraftMetaArmor.class) ? (ArmorMeta) meta : new CraftMetaArmor(meta)); + + private static final ItemMetaData COLORABLE_ARMOR_META_DATA = new ItemMetaData<>(ColorableArmorMeta.class, +- item -> new CraftMetaColorableArmor(item.getComponentsPatch()), ++ (item, extras) -> new CraftMetaColorableArmor(item.getComponentsPatch(), extras), + (type, meta) -> meta instanceof ColorableArmorMeta colorable ? colorable : new CraftMetaColorableArmor(meta)); + + private static final ItemMetaData LEATHER_ARMOR_META_DATA = new ItemMetaData<>(LeatherArmorMeta.class, +- item -> new CraftMetaLeatherArmor(item.getComponentsPatch()), ++ (item, extras) -> new CraftMetaLeatherArmor(item.getComponentsPatch(), extras), + (type, meta) -> meta instanceof CraftMetaLeatherArmor leather ? leather : new CraftMetaLeatherArmor(meta)); + + private static final ItemMetaData POTION_META_DATA = new ItemMetaData<>(PotionMeta.class, +- item -> new CraftMetaPotion(item.getComponentsPatch()), ++ (item, extras) -> new CraftMetaPotion(item.getComponentsPatch(), extras), + (type, meta) -> meta instanceof CraftMetaPotion potion ? potion : new CraftMetaPotion(meta)); + + private static final ItemMetaData MAP_META_DATA = new ItemMetaData<>(MapMeta.class, +- item -> new CraftMetaMap(item.getComponentsPatch()), ++ (item, extras) -> new CraftMetaMap(item.getComponentsPatch(), extras), + (type, meta) -> meta instanceof CraftMetaMap map ? map : new CraftMetaMap(meta)); + + private static final ItemMetaData FIREWORK_META_DATA = new ItemMetaData<>(FireworkMeta.class, +- item -> new CraftMetaFirework(item.getComponentsPatch()), ++ (item, extras) -> new CraftMetaFirework(item.getComponentsPatch(), extras), + (type, meta) -> meta instanceof CraftMetaFirework firework ? firework : new CraftMetaFirework(meta)); + + private static final ItemMetaData CHARGE_META_DATA = new ItemMetaData<>(FireworkEffectMeta.class, +- item -> new CraftMetaCharge(item.getComponentsPatch()), ++ (item, extras) -> new CraftMetaCharge(item.getComponentsPatch(), extras), + (type, meta) -> meta instanceof CraftMetaCharge charge ? charge : new CraftMetaCharge(meta)); + + private static final ItemMetaData ENCHANTED_BOOK_META_DATA = new ItemMetaData<>(EnchantmentStorageMeta.class, +- item -> new CraftMetaEnchantedBook(item.getComponentsPatch()), ++ (item, extras) -> new CraftMetaEnchantedBook(item.getComponentsPatch(), extras), + (type, meta) -> meta instanceof CraftMetaEnchantedBook enchantedBook ? enchantedBook : new CraftMetaEnchantedBook(meta)); + + private static final ItemMetaData BANNER_META_DATA = new ItemMetaData<>(BannerMeta.class, +- item -> new CraftMetaBanner(item.getComponentsPatch()), ++ (item, extras) -> new CraftMetaBanner(item.getComponentsPatch(), extras), + (type, meta) -> meta instanceof CraftMetaBanner banner ? banner : new CraftMetaBanner(meta)); + + private static final ItemMetaData SPAWN_EGG_META_DATA = new ItemMetaData<>(SpawnEggMeta.class, +- item -> new CraftMetaSpawnEgg(item.getComponentsPatch()), ++ (item, extras) -> new CraftMetaSpawnEgg(item.getComponentsPatch(), extras), + (type, meta) -> meta instanceof CraftMetaSpawnEgg spawnEgg ? spawnEgg : new CraftMetaSpawnEgg(meta)); + + private static final ItemMetaData ARMOR_STAND_META_DATA = new ItemMetaData<>(ArmorStandMeta.class, // paper +- item -> new CraftMetaArmorStand(item.getComponentsPatch()), ++ (item, extras) -> new CraftMetaArmorStand(item.getComponentsPatch(), extras), + (type, meta) -> meta instanceof CraftMetaArmorStand armorStand ? armorStand : new CraftMetaArmorStand(meta)); + + private static final ItemMetaData KNOWLEDGE_BOOK_META_DATA = new ItemMetaData<>(KnowledgeBookMeta.class, +- item -> new CraftMetaKnowledgeBook(item.getComponentsPatch()), ++ (item, extras) -> new CraftMetaKnowledgeBook(item.getComponentsPatch(), extras), + (type, meta) -> meta instanceof CraftMetaKnowledgeBook knowledgeBook ? knowledgeBook : new CraftMetaKnowledgeBook(meta)); + + private static final ItemMetaData BLOCK_STATE_META_DATA = new ItemMetaData<>(BlockStateMeta.class, +- item -> new CraftMetaBlockState(item.getComponentsPatch(), CraftItemType.minecraftToBukkit(item.getItem())), ++ (item, extras) -> new CraftMetaBlockState(item.getComponentsPatch(), CraftItemType.minecraftToBukkit(item.getItem()), extras), + (type, meta) -> new CraftMetaBlockState(meta, type.asMaterial())); + + private static final ItemMetaData SHIELD_META_DATA = new ItemMetaData<>(ShieldMeta.class, +- item -> new CraftMetaShield(item.getComponentsPatch()), ++ (item, extras) -> new CraftMetaShield(item.getComponentsPatch(), extras), + (type, meta) -> new CraftMetaShield(meta)); + + private static final ItemMetaData TROPICAL_FISH_BUCKET_META_DATA = new ItemMetaData<>(TropicalFishBucketMeta.class, +- item -> new CraftMetaTropicalFishBucket(item.getComponentsPatch()), ++ (item, extras) -> new CraftMetaTropicalFishBucket(item.getComponentsPatch(), extras), + (type, meta) -> meta instanceof CraftMetaTropicalFishBucket tropicalFishBucket ? tropicalFishBucket : new CraftMetaTropicalFishBucket(meta)); + + private static final ItemMetaData AXOLOTL_BUCKET_META_DATA = new ItemMetaData<>(AxolotlBucketMeta.class, +- item -> new CraftMetaAxolotlBucket(item.getComponentsPatch()), ++ (item, extras) -> new CraftMetaAxolotlBucket(item.getComponentsPatch(), extras), + (type, meta) -> meta instanceof CraftMetaAxolotlBucket axolotlBucket ? axolotlBucket : new CraftMetaAxolotlBucket(meta)); + + private static final ItemMetaData CROSSBOW_META_DATA = new ItemMetaData<>(CrossbowMeta.class, +- item -> new CraftMetaCrossbow(item.getComponentsPatch()), ++ (item, extras) -> new CraftMetaCrossbow(item.getComponentsPatch(), extras), + (type, meta) -> meta instanceof CraftMetaCrossbow crossbow ? crossbow : new CraftMetaCrossbow(meta)); + + private static final ItemMetaData SUSPICIOUS_STEW_META_DATA = new ItemMetaData<>(SuspiciousStewMeta.class, +- item -> new CraftMetaSuspiciousStew(item.getComponentsPatch()), ++ (item, extras) -> new CraftMetaSuspiciousStew(item.getComponentsPatch(), extras), + (type, meta) -> meta instanceof CraftMetaSuspiciousStew suspiciousStew ? suspiciousStew : new CraftMetaSuspiciousStew(meta)); + + private static final ItemMetaData ENTITY_TAG_META_DATA = new ItemMetaData<>(ItemMeta.class, +- item -> new CraftMetaEntityTag(item.getComponentsPatch()), ++ (item, extras) -> new CraftMetaEntityTag(item.getComponentsPatch(), extras), + (type, meta) -> meta instanceof CraftMetaEntityTag entityTag ? entityTag : new CraftMetaEntityTag(meta)); + + private static final ItemMetaData COMPASS_META_DATA = new ItemMetaData<>(CompassMeta.class, +- item -> new CraftMetaCompass(item.getComponentsPatch()), ++ (item, extras) -> new CraftMetaCompass(item.getComponentsPatch(), extras), + (type, meta) -> meta instanceof CraftMetaCompass compass ? compass : new CraftMetaCompass(meta)); + + private static final ItemMetaData BUNDLE_META_DATA = new ItemMetaData<>(BundleMeta.class, +- item -> new CraftMetaBundle(item.getComponentsPatch()), ++ (item, extras) -> new CraftMetaBundle(item.getComponentsPatch(), extras), + (type, meta) -> meta instanceof CraftMetaBundle bundle ? bundle : new CraftMetaBundle(meta)); + + private static final ItemMetaData MUSIC_INSTRUMENT_META_DATA = new ItemMetaData<>(MusicInstrumentMeta.class, +- item -> new CraftMetaMusicInstrument(item.getComponentsPatch()), ++ (item, extras) -> new CraftMetaMusicInstrument(item.getComponentsPatch(), extras), + (type, meta) -> meta instanceof CraftMetaMusicInstrument musicInstrument ? musicInstrument : new CraftMetaMusicInstrument(meta)); + + private static final ItemMetaData OMINOUS_BOTTLE_META_DATA = new ItemMetaData<>(OminousBottleMeta.class, +- item -> new CraftMetaOminousBottle(item.getComponentsPatch()), ++ (item, extras) -> new CraftMetaOminousBottle(item.getComponentsPatch(), extras), + (type, meta) -> meta instanceof CraftMetaOminousBottle musicInstrument ? musicInstrument : new CraftMetaOminousBottle(meta)); + + // We use if instead of a set, since the result gets cached in CraftItemType, +diff --git a/src/main/java/org/bukkit/craftbukkit/inventory/CraftItemStack.java b/src/main/java/org/bukkit/craftbukkit/inventory/CraftItemStack.java +index 7228d43d331de16cbaa0e97c7e3fa45c0bc89558..dfdabf86433d323966a748baef769cf0462d3f38 100644 +--- a/src/main/java/org/bukkit/craftbukkit/inventory/CraftItemStack.java ++++ b/src/main/java/org/bukkit/craftbukkit/inventory/CraftItemStack.java +@@ -173,10 +173,11 @@ public final class CraftItemStack extends ItemStack { + } else if (this.handle == null) { + this.handle = new net.minecraft.world.item.ItemStack(CraftItemType.bukkitToMinecraft(type), 1); + } else { ++ final Material oldType = CraftMagicNumbers.getMaterial(this.handle.getItem()); // Paper + this.handle.setItem(CraftItemType.bukkitToMinecraft(type)); + if (this.hasItemMeta()) { + // This will create the appropriate item meta, which will contain all the data we intend to keep +- CraftItemStack.setItemMeta(this.handle, CraftItemStack.getItemMeta(this.handle)); ++ this.adjustTagForItemMeta(oldType); // Paper + } + } + this.setData(null); +@@ -337,6 +338,19 @@ public final class CraftItemStack extends ItemStack { + public ItemMeta getItemMeta() { + return CraftItemStack.getItemMeta(this.handle); + } ++ // Paper start - improve handled tags on type change ++ public void adjustTagForItemMeta(final Material oldType) { ++ final CraftMetaItem oldMeta = (CraftMetaItem) CraftItemFactory.instance().getItemMeta(oldType); ++ final ItemMeta newMeta; ++ if (oldMeta == null) { ++ newMeta = getItemMeta(this.handle); ++ } else { ++ final java.util.Set> extraHandledDcts = new java.util.HashSet<>(CraftMetaItem.getTopLevelHandledDcts(oldMeta.getClass())); ++ newMeta = getItemMeta(this.handle, CraftItemType.minecraftToBukkitNew(this.handle.getItem()), extraHandledDcts); ++ } ++ this.setItemMeta(newMeta); ++ } ++ // Paper end - improve handled tags on type change + // Paper start + public static void applyMetaToItem(net.minecraft.world.item.ItemStack itemStack, ItemMeta itemMeta) { + final CraftMetaItem.Applicator tag = new CraftMetaItem.Applicator(); +@@ -349,12 +363,17 @@ public final class CraftItemStack extends ItemStack { + } + public static ItemMeta getItemMeta(net.minecraft.world.item.ItemStack item, org.bukkit.inventory.ItemType metaForType) { + // Paper end ++ // Paper start - handled tags on type change ++ return getItemMeta(item, metaForType, null); ++ } ++ public static ItemMeta getItemMeta(net.minecraft.world.item.ItemStack item, org.bukkit.inventory.ItemType metaForType, final java.util.Set> extraHandledDcts) { ++ // Paper end - handled tags on type change + if (!CraftItemStack.hasItemMeta(item)) { + return CraftItemFactory.instance().getItemMeta(CraftItemStack.getType(item)); + } + +- if (metaForType != null) { return ((CraftItemType) metaForType).getItemMeta(item); } // Paper +- return ((CraftItemType) CraftItemType.minecraftToBukkitNew(item.getItem())).getItemMeta(item); ++ if (metaForType != null) { return ((CraftItemType) metaForType).getItemMeta(item, extraHandledDcts); } // Paper ++ return ((CraftItemType) CraftItemType.minecraftToBukkitNew(item.getItem())).getItemMeta(item, extraHandledDcts); // Paper + } + + static Material getType(net.minecraft.world.item.ItemStack item) { +diff --git a/src/main/java/org/bukkit/craftbukkit/inventory/CraftItemType.java b/src/main/java/org/bukkit/craftbukkit/inventory/CraftItemType.java +index 6d76cc1db3ac3f1ae74c13511937fb86082a0b3d..f4a6ee6dfcb2d516a9a1a9c81494b50a629110e4 100644 +--- a/src/main/java/org/bukkit/craftbukkit/inventory/CraftItemType.java ++++ b/src/main/java/org/bukkit/craftbukkit/inventory/CraftItemType.java +@@ -114,8 +114,8 @@ public class CraftItemType implements ItemType.Typed, Han + return this.item; + } + +- public M getItemMeta(net.minecraft.world.item.ItemStack itemStack) { +- return this.itemMetaData.get().fromItemStack().apply(itemStack); ++ public M getItemMeta(net.minecraft.world.item.ItemStack itemStack, final java.util.Set> extraHandledDcts) { ++ return this.itemMetaData.get().fromItemStack().apply(itemStack, extraHandledDcts); + } + + public M getItemMeta(ItemMeta itemMeta) { +diff --git a/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaArmor.java b/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaArmor.java +index 0d3b1692af010bfd7ea83e22e9571dc954906f71..e83a662f82b144b11a003a682633cd0ee797fd19 100644 +--- a/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaArmor.java ++++ b/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaArmor.java +@@ -34,8 +34,8 @@ public class CraftMetaArmor extends CraftMetaItem implements ArmorMeta { + } + } + +- CraftMetaArmor(DataComponentPatch tag) { +- super(tag); ++ CraftMetaArmor(DataComponentPatch tag, java.util.Set> extraHandledDcts) { // Paper ++ super(tag, extraHandledDcts); // Paper + + getOrEmpty(tag, CraftMetaArmor.TRIM).ifPresent((trimCompound) -> { + TrimMaterial trimMaterial = org.bukkit.craftbukkit.CraftRegistry.unwrapAndConvertHolder(io.papermc.paper.registry.RegistryKey.TRIM_MATERIAL, trimCompound.material()).orElse(null); // Paper - fix upstream not being correct +diff --git a/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaArmorStand.java b/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaArmorStand.java +index ecce5d0da946ca279c5608068442cc53437dd2a5..00b5c4ab6111f980db1b9e99f901667741266440 100644 +--- a/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaArmorStand.java ++++ b/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaArmorStand.java +@@ -35,8 +35,8 @@ public class CraftMetaArmorStand extends CraftMetaItem implements com.destroysto + this.entityTag = armorStand.entityTag; + } + +- CraftMetaArmorStand(DataComponentPatch tag) { +- super(tag); ++ CraftMetaArmorStand(DataComponentPatch tag, final java.util.Set> extraHandledDcts) { // Paper ++ super(tag, extraHandledDcts); // Paper + + getOrEmpty(tag, CraftMetaArmorStand.ENTITY_TAG).ifPresent((nbt) -> { + this.entityTag = nbt.copyTag(); +diff --git a/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaAxolotlBucket.java b/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaAxolotlBucket.java +index c4beb94d8e5448e69f31f30299448f344b5d8f59..169fefb64e1af444f7c2efb1234cb6e7779fb717 100644 +--- a/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaAxolotlBucket.java ++++ b/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaAxolotlBucket.java +@@ -36,8 +36,8 @@ public class CraftMetaAxolotlBucket extends CraftMetaItem implements AxolotlBuck + this.bucketEntityTag = bucket.bucketEntityTag; + } + +- CraftMetaAxolotlBucket(DataComponentPatch tag) { +- super(tag); ++ CraftMetaAxolotlBucket(DataComponentPatch tag, final java.util.Set> extraHandledDcts) { // Paper ++ super(tag, extraHandledDcts); // Paper + + getOrEmpty(tag, CraftMetaAxolotlBucket.ENTITY_TAG).ifPresent((nbt) -> { + this.entityTag = nbt.copyTag(); +diff --git a/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaBanner.java b/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaBanner.java +index eb44c19f6af624df458981e46c73a64358d6e1ce..d0a8cd89da3b8d87248494056470c306f8fb5ae8 100644 +--- a/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaBanner.java ++++ b/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaBanner.java +@@ -34,8 +34,8 @@ public class CraftMetaBanner extends CraftMetaItem implements BannerMeta { + this.patterns = new ArrayList(banner.patterns); + } + +- CraftMetaBanner(DataComponentPatch tag) { +- super(tag); ++ CraftMetaBanner(DataComponentPatch tag, final java.util.Set> extraHandledDcts) { // Paper ++ super(tag, extraHandledDcts); // Paper + + getOrEmpty(tag, CraftMetaBanner.PATTERNS).ifPresent((entityTag) -> { + List patterns = entityTag.layers(); +diff --git a/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaBlockState.java b/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaBlockState.java +index 3985e5b4e2d65faa8eaea1d4a2acc6fb1e64f959..413e41f113226b8a2e9b30bb519076d78e451fa0 100644 +--- a/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaBlockState.java ++++ b/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaBlockState.java +@@ -73,8 +73,8 @@ public class CraftMetaBlockState extends CraftMetaItem implements BlockStateMeta + this.position = te.position; + } + +- CraftMetaBlockState(DataComponentPatch tag, Material material) { +- super(tag); ++ CraftMetaBlockState(DataComponentPatch tag, Material material, final Set> extraHandledDcts) { // Paper ++ super(tag, extraHandledDcts); // Paper + this.material = material; + + getOrEmpty(tag, CraftMetaBlockState.BLOCK_ENTITY_TAG).ifPresent((blockTag) -> { +diff --git a/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaBook.java b/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaBook.java +index 32e5188442551b3e72e1d4826d836d622d0e438a..257c835bc280eee9ee73ae75b5249bb568a687d0 100644 +--- a/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaBook.java ++++ b/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaBook.java +@@ -64,8 +64,8 @@ public class CraftMetaBook extends CraftMetaItem implements BookMeta, WritableBo + } + } + +- CraftMetaBook(DataComponentPatch tag) { +- super(tag); ++ CraftMetaBook(DataComponentPatch tag, java.util.Set> extraHandledDcts) { // Paper ++ super(tag, extraHandledDcts); // Paper + + getOrEmpty(tag, CraftMetaBook.BOOK_CONTENT).ifPresent((writable) -> { + List> pages = writable.pages(); +diff --git a/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaBookSigned.java b/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaBookSigned.java +index fd3b12477c30d1eabdbe57ea779027931e9dd957..cbb3d80cc7cd81b2505dff999a0baede737165f7 100644 +--- a/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaBookSigned.java ++++ b/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaBookSigned.java +@@ -78,8 +78,8 @@ public class CraftMetaBookSigned extends CraftMetaItem implements BookMeta { + } + } + +- CraftMetaBookSigned(DataComponentPatch tag) { +- super(tag); ++ CraftMetaBookSigned(DataComponentPatch tag, java.util.Set> extraHandledDcts) { // Paper ++ super(tag, extraHandledDcts); // Paper + + getOrEmpty(tag, CraftMetaBookSigned.BOOK_CONTENT).ifPresent((written) -> { + this.title = written.title().raw(); +diff --git a/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaBundle.java b/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaBundle.java +index 30533ce683e0471742b27d1d31df20def8ea169c..2736a87a6c481da0575e6e29ea08faa539c24378 100644 +--- a/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaBundle.java ++++ b/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaBundle.java +@@ -34,8 +34,8 @@ public class CraftMetaBundle extends CraftMetaItem implements BundleMeta { + } + } + +- CraftMetaBundle(DataComponentPatch tag) { +- super(tag); ++ CraftMetaBundle(DataComponentPatch tag, java.util.Set> extraHandledDcts) { // Paper ++ super(tag, extraHandledDcts); // Paper + + getOrEmpty(tag, CraftMetaBundle.ITEMS).ifPresent((bundle) -> { + bundle.items().forEach((item) -> { +diff --git a/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaCharge.java b/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaCharge.java +index 72340e7269a5464d72abe8370c8113f3de9573d2..56c6784e29cecf8655282235959de536d07c1e08 100644 +--- a/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaCharge.java ++++ b/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaCharge.java +@@ -29,8 +29,8 @@ class CraftMetaCharge extends CraftMetaItem implements FireworkEffectMeta { + this.setEffect(SerializableMeta.getObject(FireworkEffect.class, map, CraftMetaCharge.EXPLOSION.BUKKIT, true)); + } + +- CraftMetaCharge(DataComponentPatch tag) { +- super(tag); ++ CraftMetaCharge(DataComponentPatch tag, java.util.Set> extraHandledDcts) { // Paper ++ super(tag, extraHandledDcts); // Paper + + getOrEmpty(tag, CraftMetaCharge.EXPLOSION).ifPresent((f) -> { + try { +diff --git a/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaColorableArmor.java b/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaColorableArmor.java +index 366fec1aee66de4031727a1383acebd319eeef88..6517ec4933b0eae761fceb117ea1db175755d0b1 100644 +--- a/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaColorableArmor.java ++++ b/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaColorableArmor.java +@@ -18,8 +18,8 @@ public class CraftMetaColorableArmor extends CraftMetaArmor implements Colorable + CraftMetaLeatherArmor.readColor(this, meta); + } + +- CraftMetaColorableArmor(DataComponentPatch tag) { +- super(tag); ++ CraftMetaColorableArmor(DataComponentPatch tag, java.util.Set> extraHandledDcts) { // Paper ++ super(tag, extraHandledDcts); // Paper + CraftMetaLeatherArmor.readColor(this, tag); + } + +diff --git a/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaCompass.java b/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaCompass.java +index 607e23040383576b2805c25947a69f6efe6d2c88..69a112b3a9726966aecbe687d905fd1a11cfa1e3 100644 +--- a/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaCompass.java ++++ b/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaCompass.java +@@ -50,8 +50,8 @@ public class CraftMetaCompass extends CraftMetaItem implements CompassMeta { + this.tracked = compassMeta.tracked; + } + +- CraftMetaCompass(DataComponentPatch tag) { +- super(tag); ++ CraftMetaCompass(DataComponentPatch tag, java.util.Set> extraHandledDcts) { // Paper ++ super(tag, extraHandledDcts); // Paper + getOrEmpty(tag, CraftMetaCompass.LODESTONE_TARGET).ifPresent((lodestoneTarget) -> { + lodestoneTarget.target().ifPresent((target) -> { + this.lodestoneWorld = target.dimension(); +diff --git a/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaCrossbow.java b/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaCrossbow.java +index c278af519308c84ad76fc2312046980c01c528ba..0807c2172c5a4bee675cef265a45a9350e98b880 100644 +--- a/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaCrossbow.java ++++ b/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaCrossbow.java +@@ -36,8 +36,8 @@ public class CraftMetaCrossbow extends CraftMetaItem implements CrossbowMeta { + } + } + +- CraftMetaCrossbow(DataComponentPatch tag) { +- super(tag); ++ CraftMetaCrossbow(DataComponentPatch tag, java.util.Set> extraHandledDcts) { // Paper ++ super(tag, extraHandledDcts); // Paper + + getOrEmpty(tag, CraftMetaCrossbow.CHARGED_PROJECTILES).ifPresent((p) -> { + List list = p.getItems(); +diff --git a/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaEnchantedBook.java b/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaEnchantedBook.java +index 19af55ec2bf62b70bd3be44f499b32f5efe71ab1..c93f769ee6c55022653696da45de568fcf7589fe 100644 +--- a/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaEnchantedBook.java ++++ b/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaEnchantedBook.java +@@ -32,8 +32,8 @@ class CraftMetaEnchantedBook extends CraftMetaItem implements EnchantmentStorage + } + } + +- CraftMetaEnchantedBook(DataComponentPatch tag) { +- super(tag); ++ CraftMetaEnchantedBook(DataComponentPatch tag, java.util.Set> extraHandledDcts) { // Paper ++ super(tag, extraHandledDcts); // Paper + + getOrEmpty(tag, CraftMetaEnchantedBook.STORED_ENCHANTMENTS).ifPresent((itemEnchantments) -> { + this.enchantments = buildEnchantments(itemEnchantments); +diff --git a/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaEntityTag.java b/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaEntityTag.java +index 3ff0340c40e9dc9a6e690de15ccade7a0c4e8f02..3f6c5cbbf63631e4b72dc43558651ea94f31ca78 100644 +--- a/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaEntityTag.java ++++ b/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaEntityTag.java +@@ -39,8 +39,8 @@ public class CraftMetaEntityTag extends CraftMetaItem { + this.entityTag = entity.entityTag; + } + +- CraftMetaEntityTag(DataComponentPatch tag) { +- super(tag); ++ CraftMetaEntityTag(DataComponentPatch tag, final java.util.Set> extraHandledDcts) { // Paper ++ super(tag, extraHandledDcts); // Paper + + getOrEmpty(tag, CraftMetaEntityTag.ENTITY_TAG).ifPresent((nbt) -> { + this.entityTag = nbt.copyTag(); +diff --git a/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaFirework.java b/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaFirework.java +index 4941e0afff8df5f10f06c715b54bf58eb86051c5..566d893a413fd04b99e83dc2da8fe958a48492a8 100644 +--- a/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaFirework.java ++++ b/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaFirework.java +@@ -60,8 +60,8 @@ class CraftMetaFirework extends CraftMetaItem implements FireworkMeta { + } + } + +- CraftMetaFirework(DataComponentPatch tag) { +- super(tag); ++ CraftMetaFirework(DataComponentPatch tag, java.util.Set> extraHandledDcts) { // Paper ++ super(tag, extraHandledDcts); // Paper + + getOrEmpty(tag, CraftMetaFirework.FIREWORKS).ifPresent((fireworks) -> { + this.power = fireworks.flightDuration(); +diff --git a/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaItem.java b/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaItem.java +index 6b3eed94c26bc16177f9b9fadd140f9a89163af2..13b19adc21ece31476b2980c5bc01a50f15df634 100644 +--- a/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaItem.java ++++ b/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaItem.java +@@ -389,7 +389,7 @@ class CraftMetaItem implements ItemMeta, Damageable, Repairable, BlockDataMeta { + // Paper end + } + +- CraftMetaItem(DataComponentPatch tag) { ++ CraftMetaItem(DataComponentPatch tag, Set> extraHandledTags) { // Paper - improve handled tags on type changes + CraftMetaItem.getOrEmpty(tag, CraftMetaItem.NAME).ifPresent((component) -> { + this.displayName = component; + }); +@@ -521,9 +521,16 @@ class CraftMetaItem implements ItemMeta, Damageable, Repairable, BlockDataMeta { + }); + // Paper end - fix ItemFlags + ++ // Paper start - improve checking handled data component types ++ Set> handledTags = getTopLevelHandledDcts(this.getClass()); ++ if (extraHandledTags != null) { ++ extraHandledTags.addAll(handledTags); ++ handledTags = extraHandledTags; ++ } ++ // Paper end - improve checking handled data component types + Set, Optional>> keys = tag.entrySet(); + for (Map.Entry, Optional> key : keys) { +- if (!CraftMetaItem.getHandledTags().contains(key.getKey())) { ++ if (!handledTags.contains(key.getKey())) { // Paper - improve checking handled data component types + key.getValue().ifPresent((value) -> { + this.unhandledTags.set((DataComponentType) key.getKey(), value); + }); +@@ -2351,75 +2358,83 @@ class CraftMetaItem implements ItemMeta, Damageable, Repairable, BlockDataMeta { + this.version = version; + } + +- public static Set getHandledTags() { +- synchronized (CraftMetaItem.HANDLED_TAGS) { +- if (CraftMetaItem.HANDLED_TAGS.isEmpty()) { +- CraftMetaItem.HANDLED_TAGS.addAll(Arrays.asList( +- CraftMetaItem.NAME.TYPE, +- CraftMetaItem.ITEM_NAME.TYPE, +- CraftMetaItem.LORE.TYPE, +- CraftMetaItem.CUSTOM_MODEL_DATA.TYPE, +- CraftMetaItem.ENCHANTABLE.TYPE, +- CraftMetaItem.BLOCK_DATA.TYPE, +- CraftMetaItem.REPAIR.TYPE, +- CraftMetaItem.ENCHANTMENTS.TYPE, +- CraftMetaItem.HIDE_ADDITIONAL_TOOLTIP.TYPE, +- CraftMetaItem.HIDE_TOOLTIP.TYPE, +- CraftMetaItem.TOOLTIP_STYLE.TYPE, +- CraftMetaItem.ITEM_MODEL.TYPE, +- CraftMetaItem.UNBREAKABLE.TYPE, +- CraftMetaItem.ENCHANTMENT_GLINT_OVERRIDE.TYPE, +- CraftMetaItem.GLIDER.TYPE, +- CraftMetaItem.DAMAGE_RESISTANT.TYPE, +- CraftMetaItem.MAX_STACK_SIZE.TYPE, +- CraftMetaItem.RARITY.TYPE, +- CraftMetaItem.USE_REMAINDER.TYPE, +- CraftMetaItem.USE_COOLDOWN.TYPE, +- CraftMetaItem.FOOD.TYPE, +- CraftMetaItem.TOOL.TYPE, +- CraftMetaItem.EQUIPPABLE.TYPE, +- CraftMetaItem.JUKEBOX_PLAYABLE.TYPE, +- CraftMetaItem.DAMAGE.TYPE, +- CraftMetaItem.MAX_DAMAGE.TYPE, +- CraftMetaItem.CUSTOM_DATA.TYPE, +- CraftMetaItem.ATTRIBUTES.TYPE, +- CraftMetaItem.CAN_PLACE_ON.TYPE, // Paper +- CraftMetaItem.CAN_BREAK.TYPE, // Paper +- CraftMetaArmor.TRIM.TYPE, +- CraftMetaArmorStand.ENTITY_TAG.TYPE, +- CraftMetaBanner.PATTERNS.TYPE, +- CraftMetaEntityTag.ENTITY_TAG.TYPE, +- CraftMetaLeatherArmor.COLOR.TYPE, +- CraftMetaMap.MAP_POST_PROCESSING.TYPE, +- CraftMetaMap.MAP_COLOR.TYPE, +- CraftMetaMap.MAP_ID.TYPE, +- CraftMetaPotion.POTION_CONTENTS.TYPE, +- CraftMetaShield.BASE_COLOR.TYPE, +- CraftMetaSkull.SKULL_PROFILE.TYPE, +- CraftMetaSkull.NOTE_BLOCK_SOUND.TYPE, +- CraftMetaSpawnEgg.ENTITY_TAG.TYPE, +- CraftMetaBlockState.BLOCK_ENTITY_TAG.TYPE, +- CraftMetaBook.BOOK_CONTENT.TYPE, +- CraftMetaBookSigned.BOOK_CONTENT.TYPE, +- CraftMetaFirework.FIREWORKS.TYPE, +- CraftMetaEnchantedBook.STORED_ENCHANTMENTS.TYPE, +- CraftMetaCharge.EXPLOSION.TYPE, +- CraftMetaKnowledgeBook.BOOK_RECIPES.TYPE, +- CraftMetaTropicalFishBucket.ENTITY_TAG.TYPE, +- CraftMetaTropicalFishBucket.BUCKET_ENTITY_TAG.TYPE, +- CraftMetaAxolotlBucket.ENTITY_TAG.TYPE, +- CraftMetaAxolotlBucket.BUCKET_ENTITY_TAG.TYPE, +- CraftMetaCrossbow.CHARGED_PROJECTILES.TYPE, +- CraftMetaSuspiciousStew.EFFECTS.TYPE, +- CraftMetaCompass.LODESTONE_TARGET.TYPE, +- CraftMetaBundle.ITEMS.TYPE, +- CraftMetaMusicInstrument.GOAT_HORN_INSTRUMENT.TYPE, +- CraftMetaOminousBottle.OMINOUS_BOTTLE_AMPLIFIER.TYPE +- )); +- } +- return CraftMetaItem.HANDLED_TAGS; ++ // Paper start - improve checking handled tags ++ @org.jetbrains.annotations.VisibleForTesting ++ public static final Map, Set>> HANDLED_DCTS_PER_TYPE = new HashMap<>(); ++ private static final Set> DEFAULT_HANDLED_DCTS = Set.of( ++ CraftMetaItem.NAME.TYPE, ++ CraftMetaItem.ITEM_NAME.TYPE, ++ CraftMetaItem.LORE.TYPE, ++ CraftMetaItem.CUSTOM_MODEL_DATA.TYPE, ++ CraftMetaItem.ENCHANTABLE.TYPE, ++ CraftMetaItem.BLOCK_DATA.TYPE, ++ CraftMetaItem.REPAIR.TYPE, ++ CraftMetaItem.ENCHANTMENTS.TYPE, ++ CraftMetaItem.HIDE_ADDITIONAL_TOOLTIP.TYPE, ++ CraftMetaItem.HIDE_TOOLTIP.TYPE, ++ CraftMetaItem.TOOLTIP_STYLE.TYPE, ++ CraftMetaItem.ITEM_MODEL.TYPE, ++ CraftMetaItem.UNBREAKABLE.TYPE, ++ CraftMetaItem.ENCHANTMENT_GLINT_OVERRIDE.TYPE, ++ CraftMetaItem.GLIDER.TYPE, ++ CraftMetaItem.DAMAGE_RESISTANT.TYPE, ++ CraftMetaItem.MAX_STACK_SIZE.TYPE, ++ CraftMetaItem.RARITY.TYPE, ++ CraftMetaItem.USE_REMAINDER.TYPE, ++ CraftMetaItem.USE_COOLDOWN.TYPE, ++ CraftMetaItem.FOOD.TYPE, ++ CraftMetaItem.TOOL.TYPE, ++ CraftMetaItem.EQUIPPABLE.TYPE, ++ CraftMetaItem.JUKEBOX_PLAYABLE.TYPE, ++ CraftMetaItem.DAMAGE.TYPE, ++ CraftMetaItem.MAX_DAMAGE.TYPE, ++ CraftMetaItem.CUSTOM_DATA.TYPE, ++ CraftMetaItem.ATTRIBUTES.TYPE, ++ CraftMetaItem.CAN_PLACE_ON.TYPE, // Paper ++ CraftMetaItem.CAN_BREAK.TYPE // Paper ++ ); ++ public static Set> getTopLevelHandledDcts(final Class clazz) { ++ synchronized (HANDLED_DCTS_PER_TYPE) { ++ if (HANDLED_DCTS_PER_TYPE.isEmpty()) { ++ final Map, Set>> map = new HashMap<>(); ++ map.put(CraftMetaArmor.class, Set.of(CraftMetaArmor.TRIM.TYPE)); ++ map.put(CraftMetaArmorStand.class, Set.of(CraftMetaArmorStand.ENTITY_TAG.TYPE)); ++ map.put(CraftMetaAxolotlBucket.class, Set.of(CraftMetaAxolotlBucket.ENTITY_TAG.TYPE, CraftMetaAxolotlBucket.BUCKET_ENTITY_TAG.TYPE)); ++ map.put(CraftMetaBanner.class, Set.of(CraftMetaBanner.PATTERNS.TYPE)); // banner uses same tag as block state ++ map.put(CraftMetaShield.class, Set.of(CraftMetaShield.BASE_COLOR.TYPE, CraftMetaBanner.PATTERNS.TYPE)); ++ map.put(CraftMetaBlockState.class, Set.of(CraftMetaBlockState.BLOCK_ENTITY_TAG.TYPE)); ++ map.put(CraftMetaBook.class, Set.of(CraftMetaBook.BOOK_CONTENT.TYPE)); ++ map.put(CraftMetaBookSigned.class, Set.of(CraftMetaBookSigned.BOOK_CONTENT.TYPE)); ++ map.put(CraftMetaBundle.class, Set.of(CraftMetaBundle.ITEMS.TYPE)); ++ map.put(CraftMetaCharge.class, Set.of(CraftMetaCharge.EXPLOSION.TYPE)); ++ map.put(CraftMetaColorableArmor.class, Set.of(CraftMetaArmor.TRIM.TYPE, CraftMetaLeatherArmor.COLOR.TYPE)); ++ map.put(CraftMetaCompass.class, Set.of(CraftMetaCompass.LODESTONE_TARGET.TYPE)); ++ map.put(CraftMetaCrossbow.class, Set.of(CraftMetaCrossbow.CHARGED_PROJECTILES.TYPE)); ++ map.put(CraftMetaEnchantedBook.class, Set.of(CraftMetaEnchantedBook.STORED_ENCHANTMENTS.TYPE)); ++ map.put(CraftMetaEntityTag.class, Set.of(CraftMetaEntityTag.ENTITY_TAG.TYPE)); ++ map.put(CraftMetaFirework.class, Set.of(CraftMetaFirework.FIREWORKS.TYPE)); ++ map.put(CraftMetaKnowledgeBook.class, Set.of(CraftMetaKnowledgeBook.BOOK_RECIPES.TYPE)); ++ map.put(CraftMetaLeatherArmor.class, Set.of(CraftMetaLeatherArmor.COLOR.TYPE)); ++ map.put(CraftMetaMap.class, Set.of(CraftMetaMap.MAP_COLOR.TYPE, CraftMetaMap.MAP_POST_PROCESSING.TYPE, CraftMetaMap.MAP_ID.TYPE)); ++ map.put(CraftMetaMusicInstrument.class, Set.of(CraftMetaMusicInstrument.GOAT_HORN_INSTRUMENT.TYPE)); ++ map.put(CraftMetaOminousBottle.class, Set.of(CraftMetaOminousBottle.OMINOUS_BOTTLE_AMPLIFIER.TYPE)); ++ map.put(CraftMetaPotion.class, Set.of(CraftMetaPotion.POTION_CONTENTS.TYPE)); ++ map.put(CraftMetaSkull.class, Set.of(CraftMetaSkull.SKULL_PROFILE.TYPE, CraftMetaSkull.NOTE_BLOCK_SOUND.TYPE)); ++ map.put(CraftMetaSpawnEgg.class, Set.of(CraftMetaSpawnEgg.ENTITY_TAG.TYPE)); ++ map.put(CraftMetaSuspiciousStew.class, Set.of(CraftMetaSuspiciousStew.EFFECTS.TYPE)); ++ map.put(CraftMetaTropicalFishBucket.class, Set.of(CraftMetaTropicalFishBucket.ENTITY_TAG.TYPE, CraftMetaTropicalFishBucket.BUCKET_ENTITY_TAG.TYPE)); ++ ++ for (final Map.Entry, Set>> entry : map.entrySet()) { ++ final ArrayList> topLevelTags = new ArrayList<>(entry.getValue()); ++ // add tags common to CraftMetaItem to all ++ topLevelTags.addAll(DEFAULT_HANDLED_DCTS); ++ HANDLED_DCTS_PER_TYPE.put(entry.getKey(), Set.copyOf(topLevelTags)); ++ } ++ } ++ return HANDLED_DCTS_PER_TYPE.getOrDefault(clazz, DEFAULT_HANDLED_DCTS); + } + } ++ // Paper end - improve checking handled data component types + + protected static Optional getOrEmpty(DataComponentPatch tag, ItemMetaKeyType type) { + Optional result = tag.get(type.TYPE); +diff --git a/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaKnowledgeBook.java b/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaKnowledgeBook.java +index 68c0a6d5e06a44ddddddb9cbe093ed6814380444..4dc7adb626ccb74b7e3a60c5a7250d0dc9703997 100644 +--- a/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaKnowledgeBook.java ++++ b/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaKnowledgeBook.java +@@ -32,8 +32,8 @@ public class CraftMetaKnowledgeBook extends CraftMetaItem implements KnowledgeBo + } + } + +- CraftMetaKnowledgeBook(DataComponentPatch tag) { +- super(tag); ++ CraftMetaKnowledgeBook(DataComponentPatch tag, java.util.Set> extraHandledDcts) { // Paper ++ super(tag, extraHandledDcts); // Paper + + getOrEmpty(tag, CraftMetaKnowledgeBook.BOOK_RECIPES).ifPresent((pages) -> { + for (int i = 0; i < pages.size(); i++) { +diff --git a/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaLeatherArmor.java b/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaLeatherArmor.java +index 75a8ad69902103a2e33a457c3225a33860d075ed..e8c950aa74d31bf7a9128f4acc4bccee26bbcd7f 100644 +--- a/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaLeatherArmor.java ++++ b/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaLeatherArmor.java +@@ -25,8 +25,8 @@ class CraftMetaLeatherArmor extends CraftMetaItem implements LeatherArmorMeta { + CraftMetaLeatherArmor.readColor(this, meta); + } + +- CraftMetaLeatherArmor(DataComponentPatch tag) { +- super(tag); ++ CraftMetaLeatherArmor(DataComponentPatch tag, java.util.Set> extraHandledDcts) { // Paper ++ super(tag, extraHandledDcts); // Paper + CraftMetaLeatherArmor.readColor(this, tag); + } + +diff --git a/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaMap.java b/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaMap.java +index 08e18dcabbf52aae5c3843d49a72d1d52baa729b..149356981e586e4f67d4543d3df94a2ea99333fc 100644 +--- a/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaMap.java ++++ b/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaMap.java +@@ -44,8 +44,8 @@ class CraftMetaMap extends CraftMetaItem implements MapMeta { + this.color = map.color; + } + +- CraftMetaMap(DataComponentPatch tag) { +- super(tag); ++ CraftMetaMap(DataComponentPatch tag, java.util.Set> extraHandledDcts) { // Paper ++ super(tag, extraHandledDcts); // Paper + + getOrEmpty(tag, CraftMetaMap.MAP_ID).ifPresent((mapId) -> { + this.mapId = mapId.id(); +diff --git a/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaMusicInstrument.java b/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaMusicInstrument.java +index 76a3e4893cbdba903a712d6db1d30b9c644795be..a80b9b142ca99c7c0257b1bdeb059dce5f92ae93 100644 +--- a/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaMusicInstrument.java ++++ b/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaMusicInstrument.java +@@ -26,8 +26,8 @@ public class CraftMetaMusicInstrument extends CraftMetaItem implements MusicInst + } + } + +- CraftMetaMusicInstrument(DataComponentPatch tag) { +- super(tag); ++ CraftMetaMusicInstrument(DataComponentPatch tag, java.util.Set> extraHandledDcts) { // Paper ++ super(tag, extraHandledDcts); // Paper + + getOrEmpty(tag, CraftMetaMusicInstrument.GOAT_HORN_INSTRUMENT).ifPresent((instrument) -> { + this.instrument = org.bukkit.craftbukkit.CraftRegistry.unwrapAndConvertHolder(org.bukkit.Registry.INSTRUMENT, instrument).orElse(null); // Paper - fix upstream not handling inlined instrument +diff --git a/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaOminousBottle.java b/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaOminousBottle.java +index ed272c7eb435677de5f6b106a6012c472decbb62..b118d8ecb505d187c02bb158f14df333f487a87f 100644 +--- a/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaOminousBottle.java ++++ b/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaOminousBottle.java +@@ -24,8 +24,8 @@ public class CraftMetaOminousBottle extends CraftMetaItem implements OminousBott + this.ominousBottleAmplifier = bottleMeta.ominousBottleAmplifier; + } + +- CraftMetaOminousBottle(DataComponentPatch tag) { +- super(tag); ++ CraftMetaOminousBottle(DataComponentPatch tag, java.util.Set> extraHandledDcts) { // Paper ++ super(tag, extraHandledDcts); // Paper + getOrEmpty(tag, CraftMetaOminousBottle.OMINOUS_BOTTLE_AMPLIFIER).ifPresent((amplifier) -> { + this.ominousBottleAmplifier = amplifier.value(); + }); +diff --git a/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaPotion.java b/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaPotion.java +index 533c600d632eb733c121f5d8189142726b091713..6f0eebcaffa20337cf5a7f8485f891c690d948ae 100644 +--- a/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaPotion.java ++++ b/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaPotion.java +@@ -54,8 +54,8 @@ class CraftMetaPotion extends CraftMetaItem implements PotionMeta { + } + } + +- CraftMetaPotion(DataComponentPatch tag) { +- super(tag); ++ CraftMetaPotion(DataComponentPatch tag, java.util.Set> extraHandledDcts) { // Paper ++ super(tag, extraHandledDcts); // Paper + getOrEmpty(tag, CraftMetaPotion.POTION_CONTENTS).ifPresent((potionContents) -> { + potionContents.potion().ifPresent((potion) -> { + this.type = CraftPotionType.minecraftHolderToBukkit(potion); +diff --git a/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaShield.java b/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaShield.java +index bcd6cc29e4e621805cbd923d747f652ced240c6d..967d8940aec0065bce496d5d7a8c73de5733bd2c 100644 +--- a/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaShield.java ++++ b/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaShield.java +@@ -42,8 +42,8 @@ public class CraftMetaShield extends CraftMetaItem implements ShieldMeta, BlockS + } + } + +- CraftMetaShield(DataComponentPatch tag) { +- super(tag); ++ CraftMetaShield(DataComponentPatch tag, java.util.Set> extraHandledDcts) { // Paper - improve checking handled tags in item meta ++ super(tag, extraHandledDcts); // Paper - improve checking handled tags in item meta + + getOrEmpty(tag, CraftMetaShield.BASE_COLOR).ifPresent((color) -> { + this.banner = CraftMetaShield.getBlockState(DyeColor.getByWoolData((byte) color.getId())); +diff --git a/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaSkull.java b/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaSkull.java +index ca714e165e453d1072d083441d8b985290ada75a..302906467b12189e21633369c005736863f46dd5 100644 +--- a/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaSkull.java ++++ b/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaSkull.java +@@ -48,8 +48,8 @@ class CraftMetaSkull extends CraftMetaItem implements SkullMeta { + this.noteBlockSound = skullMeta.noteBlockSound; + } + +- CraftMetaSkull(DataComponentPatch tag) { +- super(tag); ++ CraftMetaSkull(DataComponentPatch tag, java.util.Set> extraHandledDcts) { // Paper ++ super(tag, extraHandledDcts); // Paper + + getOrEmpty(tag, CraftMetaSkull.SKULL_PROFILE).ifPresent(this::setProfile); + +diff --git a/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaSpawnEgg.java b/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaSpawnEgg.java +index 6c2c3b514be0dab47f3e44f65bdc6a3574e59b7c..8ddf091b3ff1262b6c97e8fe72e0a80db5e1037d 100644 +--- a/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaSpawnEgg.java ++++ b/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaSpawnEgg.java +@@ -33,8 +33,8 @@ public class CraftMetaSpawnEgg extends CraftMetaItem implements SpawnEggMeta { + this.entityTag = egg.entityTag; + } + +- CraftMetaSpawnEgg(DataComponentPatch tag) { +- super(tag); ++ CraftMetaSpawnEgg(DataComponentPatch tag, java.util.Set> extraHandledDcts) { // Paper ++ super(tag, extraHandledDcts); // Paper + + getOrEmpty(tag, CraftMetaSpawnEgg.ENTITY_TAG).ifPresent((nbt) -> { + this.entityTag = nbt.copyTag(); +diff --git a/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaSuspiciousStew.java b/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaSuspiciousStew.java +index 248efddbad2bfee4f9aa33ec738b5353054eda61..7a43e326e51300306b9c5c23a16ffae92030bd2b 100644 +--- a/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaSuspiciousStew.java ++++ b/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaSuspiciousStew.java +@@ -33,8 +33,8 @@ public class CraftMetaSuspiciousStew extends CraftMetaItem implements Suspicious + } + } + +- CraftMetaSuspiciousStew(DataComponentPatch tag) { +- super(tag); ++ CraftMetaSuspiciousStew(DataComponentPatch tag, java.util.Set> extraHandledDcts) { // Paper ++ super(tag, extraHandledDcts); // Paper + getOrEmpty(tag, CraftMetaSuspiciousStew.EFFECTS).ifPresent((suspiciousStewEffects) -> { + List list = suspiciousStewEffects.effects(); + int length = list.size(); +diff --git a/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaTropicalFishBucket.java b/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaTropicalFishBucket.java +index a514fe98d3d2b65d2cfd029079c69189bcb99c01..17705059b81942e4df43a4a5180092e09c985ade 100644 +--- a/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaTropicalFishBucket.java ++++ b/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaTropicalFishBucket.java +@@ -38,8 +38,8 @@ class CraftMetaTropicalFishBucket extends CraftMetaItem implements TropicalFishB + this.bucketEntityTag = bucket.bucketEntityTag; + } + +- CraftMetaTropicalFishBucket(DataComponentPatch tag) { +- super(tag); ++ CraftMetaTropicalFishBucket(DataComponentPatch tag, java.util.Set> extraHandledDcts) { // Paper ++ super(tag, extraHandledDcts); // Paper + + getOrEmpty(tag, CraftMetaTropicalFishBucket.ENTITY_TAG).ifPresent((nbt) -> { + this.entityTag = nbt.copyTag(); +diff --git a/src/test/java/org/bukkit/craftbukkit/inventory/DeprecatedItemMetaCustomValueTest.java b/src/test/java/org/bukkit/craftbukkit/inventory/DeprecatedItemMetaCustomValueTest.java +index 91cfa32272770cdfe56d97154ea9db4e2ed8a328..9cc1ef5c9221dd7d2069b280f0c91ce9439a995a 100644 +--- a/src/test/java/org/bukkit/craftbukkit/inventory/DeprecatedItemMetaCustomValueTest.java ++++ b/src/test/java/org/bukkit/craftbukkit/inventory/DeprecatedItemMetaCustomValueTest.java +@@ -97,7 +97,7 @@ public class DeprecatedItemMetaCustomValueTest { + CraftMetaItem.Applicator compound = new CraftMetaItem.Applicator(); + itemMeta.applyToItem(compound); + +- assertEquals(itemMeta, new CraftMetaItem(compound.build())); ++ assertEquals(itemMeta, new CraftMetaItem(compound.build(), null)); // Paper + } + + @Test +diff --git a/src/test/java/org/bukkit/craftbukkit/inventory/MetaHandledTagsTest.java b/src/test/java/org/bukkit/craftbukkit/inventory/MetaHandledTagsTest.java +new file mode 100644 +index 0000000000000000000000000000000000000000..df20446af3d43c624278d1a25f31f702677c8d96 +--- /dev/null ++++ b/src/test/java/org/bukkit/craftbukkit/inventory/MetaHandledTagsTest.java +@@ -0,0 +1,33 @@ ++package org.bukkit.craftbukkit.inventory; ++ ++import io.github.classgraph.ClassGraph; ++import io.github.classgraph.ClassInfo; ++import io.github.classgraph.ClassInfoList; ++import io.github.classgraph.ScanResult; ++import org.bukkit.support.environment.AllFeatures; ++import org.junit.jupiter.api.Test; ++ ++import static org.junit.jupiter.api.Assertions.assertFalse; ++import static org.junit.jupiter.api.Assertions.assertTrue; ++ ++// in cb package because of package-private stuff ++@AllFeatures ++class MetaHandledTagsTest { ++ ++ @Test ++ public void checkAllMetasHaveHandledTags() { ++ try (final ScanResult result = new ClassGraph() ++ .whitelistPackages("org.bukkit.craftbukkit.inventory") ++ .enableClassInfo().scan()) { ++ final ClassInfoList subclasses = result.getSubclasses(CraftMetaItem.class.getName()); ++ assertFalse(subclasses.isEmpty(), "found 0 sub types"); ++ for (final ClassInfo subclass : subclasses) { ++ final Class clazz = subclass.loadClass(CraftMetaItem.class); ++ CraftMetaItem.getTopLevelHandledDcts(clazz); // load into map ++ assertTrue(CraftMetaItem.HANDLED_DCTS_PER_TYPE.containsKey(clazz), subclass.getName() + " not found in handled tags map"); ++ } ++ } catch (Exception e) { ++ throw new RuntimeException(e); ++ } ++ } ++} +diff --git a/src/test/java/org/bukkit/craftbukkit/inventory/PersistentDataContainerTest.java b/src/test/java/org/bukkit/craftbukkit/inventory/PersistentDataContainerTest.java +index 5b16e6f5e5517eed218e4b60ecd75f8b80712e89..130c4500a5e854480962c8f720b1df4c67d43c33 100644 +--- a/src/test/java/org/bukkit/craftbukkit/inventory/PersistentDataContainerTest.java ++++ b/src/test/java/org/bukkit/craftbukkit/inventory/PersistentDataContainerTest.java +@@ -131,7 +131,7 @@ public class PersistentDataContainerTest { + CraftMetaItem.Applicator compound = new CraftMetaItem.Applicator(); + itemMeta.applyToItem(compound); + +- assertEquals(itemMeta, new CraftMetaItem(compound.build())); ++ assertEquals(itemMeta, new CraftMetaItem(compound.build(), null)); // Paper + } + + @Test +@@ -464,7 +464,7 @@ public class PersistentDataContainerTest { + + @Test + public void testEmptyListApplicationToAnyType() throws IOException { +- final CraftMetaItem craftItem = new CraftMetaItem(DataComponentPatch.EMPTY); ++ final CraftMetaItem craftItem = new CraftMetaItem(DataComponentPatch.EMPTY, null); // Paper + final PersistentDataContainer container = craftItem.getPersistentDataContainer(); + + container.set(PersistentDataContainerTest.requestKey("list"), PersistentDataType.LIST.strings(), List.of()); +@@ -477,7 +477,7 @@ public class PersistentDataContainerTest { + final CraftMetaItem.Applicator storage = new CraftMetaItem.Applicator(); + craftItem.applyToItem(storage); + +- final CraftMetaItem readItem = new CraftMetaItem(storage.build()); ++ final CraftMetaItem readItem = new CraftMetaItem(storage.build(), null); // Paper + final PersistentDataContainer readContainer = readItem.getPersistentDataContainer(); + + assertTrue(readContainer.has(PersistentDataContainerTest.requestKey("list"), PersistentDataType.LIST.strings())); diff --git a/patches/server/0951-Add-config-for-mobs-immune-to-default-effects.patch b/patches/server/0951-Add-config-for-mobs-immune-to-default-effects.patch deleted file mode 100644 index 74dd1a136012..000000000000 --- a/patches/server/0951-Add-config-for-mobs-immune-to-default-effects.patch +++ /dev/null @@ -1,44 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Jake Potrebic -Date: Wed, 2 Dec 2020 21:03:02 -0800 -Subject: [PATCH] Add config for mobs immune to default effects - - -diff --git a/src/main/java/net/minecraft/world/entity/boss/wither/WitherBoss.java b/src/main/java/net/minecraft/world/entity/boss/wither/WitherBoss.java -index 62271e74399a827a488159da234465ef18e15e6e..d3b4d492aee380dc17f4232d90eaae4f07bb9f86 100644 ---- a/src/main/java/net/minecraft/world/entity/boss/wither/WitherBoss.java -+++ b/src/main/java/net/minecraft/world/entity/boss/wither/WitherBoss.java -@@ -604,7 +604,7 @@ public class WitherBoss extends Monster implements PowerableMob, RangedAttackMob - - @Override - public boolean canBeAffected(MobEffectInstance effect) { -- return effect.is(MobEffects.WITHER) ? false : super.canBeAffected(effect); -+ return effect.is(MobEffects.WITHER) && this.level().paperConfig().entities.mobEffects.immuneToWitherEffect.wither ? false : super.canBeAffected(effect); // Paper - Add config for mobs immune to default effects - } - - private class WitherDoNothingGoal extends Goal { -diff --git a/src/main/java/net/minecraft/world/entity/monster/Spider.java b/src/main/java/net/minecraft/world/entity/monster/Spider.java -index 9045f18f49a5c6685597d0a77126d7cb35bc5e88..a30fb47559eb74b7fe634678e63a85e7e2cad9a4 100644 ---- a/src/main/java/net/minecraft/world/entity/monster/Spider.java -+++ b/src/main/java/net/minecraft/world/entity/monster/Spider.java -@@ -126,7 +126,7 @@ public class Spider extends Monster { - - @Override - public boolean canBeAffected(MobEffectInstance effect) { -- return effect.is(MobEffects.POISON) ? false : super.canBeAffected(effect); -+ return effect.is(MobEffects.POISON) && this.level().paperConfig().entities.mobEffects.spidersImmuneToPoisonEffect ? false : super.canBeAffected(effect); // Paper - Add config for mobs immune to default effects - } - - public boolean isClimbing() { -diff --git a/src/main/java/net/minecraft/world/entity/monster/WitherSkeleton.java b/src/main/java/net/minecraft/world/entity/monster/WitherSkeleton.java -index 56f23d7c7b5838ff8761de8691e685dd59d2eaa2..bb2e7cee612dc1fafa042674a0b0d07d7165b54c 100644 ---- a/src/main/java/net/minecraft/world/entity/monster/WitherSkeleton.java -+++ b/src/main/java/net/minecraft/world/entity/monster/WitherSkeleton.java -@@ -114,6 +114,6 @@ public class WitherSkeleton extends AbstractSkeleton { - - @Override - public boolean canBeAffected(MobEffectInstance effect) { -- return effect.is(MobEffects.WITHER) ? false : super.canBeAffected(effect); -+ return effect.is(MobEffects.WITHER) && this.level().paperConfig().entities.mobEffects.immuneToWitherEffect.witherSkeleton ? false : super.canBeAffected(effect); // Paper - Add config for mobs immune to default effects - } - } diff --git a/patches/server/0951-Expose-hasColor-to-leather-armor.patch b/patches/server/0951-Expose-hasColor-to-leather-armor.patch new file mode 100644 index 000000000000..9166ad6261cb --- /dev/null +++ b/patches/server/0951-Expose-hasColor-to-leather-armor.patch @@ -0,0 +1,38 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: SoSeDiK +Date: Wed, 1 May 2024 10:58:50 +0300 +Subject: [PATCH] Expose #hasColor to leather armor + + +diff --git a/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaColorableArmor.java b/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaColorableArmor.java +index 6517ec4933b0eae761fceb117ea1db175755d0b1..dc6398cfbd6e749733c3a681e1eb8ce15c3b2c5e 100644 +--- a/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaColorableArmor.java ++++ b/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaColorableArmor.java +@@ -100,4 +100,11 @@ public class CraftMetaColorableArmor extends CraftMetaArmor implements Colorable + } + return original != hash ? CraftMetaColorableArmor.class.hashCode() ^ hash : hash; + } ++ ++ // Paper start - Expose #hasColor to leather armor ++ @Override ++ public boolean isDyed() { ++ return hasColor(); ++ } ++ // Paper end - Expose #hasColor to leather armor + } +diff --git a/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaLeatherArmor.java b/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaLeatherArmor.java +index e8c950aa74d31bf7a9128f4acc4bccee26bbcd7f..e76749bffaf5a0bbd3a8d35f882edcc3598351b9 100644 +--- a/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaLeatherArmor.java ++++ b/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaLeatherArmor.java +@@ -156,4 +156,11 @@ class CraftMetaLeatherArmor extends CraftMetaItem implements LeatherArmorMeta { + builder.put(CraftMetaLeatherArmor.COLOR.BUKKIT, meta.getColor()); + } + } ++ ++ // Paper start - Expose #hasColor to leather armor ++ @Override ++ public boolean isDyed() { ++ return hasColor(); ++ } ++ // Paper end - Expose #hasColor to leather armor + } diff --git a/patches/server/0952-Added-API-to-get-player-ha-proxy-address.patch b/patches/server/0952-Added-API-to-get-player-ha-proxy-address.patch new file mode 100644 index 000000000000..02a74b9639a1 --- /dev/null +++ b/patches/server/0952-Added-API-to-get-player-ha-proxy-address.patch @@ -0,0 +1,65 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: nostalfinals +Date: Mon, 8 Apr 2024 23:24:38 +0800 +Subject: [PATCH] Added API to get player ha proxy address + + +diff --git a/src/main/java/net/minecraft/network/Connection.java b/src/main/java/net/minecraft/network/Connection.java +index ea16dfa718b526d6520d7fcfc21d28f972f1f2bf..4b9da6e2140b14f1e56056f5e9e94b2169d85501 100644 +--- a/src/main/java/net/minecraft/network/Connection.java ++++ b/src/main/java/net/minecraft/network/Connection.java +@@ -153,6 +153,7 @@ public class Connection extends SimpleChannelInboundHandler> { + this.stopReadingPackets = true; + } + // Paper end - packet limiter ++ @Nullable public SocketAddress haProxyAddress; // Paper - Add API to get player's proxy address + + public Connection(PacketFlow side) { + this.receiving = side; +diff --git a/src/main/java/net/minecraft/server/network/ServerConnectionListener.java b/src/main/java/net/minecraft/server/network/ServerConnectionListener.java +index c62df32af11636ad408b584fcc590590ce4fb0d0..baed0bb80d44973f9323bbe536551182979caff2 100644 +--- a/src/main/java/net/minecraft/server/network/ServerConnectionListener.java ++++ b/src/main/java/net/minecraft/server/network/ServerConnectionListener.java +@@ -144,6 +144,13 @@ public class ServerConnectionListener { + + Connection connection = (Connection) channel.pipeline().get("packet_handler"); + connection.address = socketaddr; ++ ++ // Paper start - Add API to get player's proxy address ++ final String proxyAddress = message.destinationAddress(); ++ final int proxyPort = message.destinationPort(); ++ ++ connection.haProxyAddress = new java.net.InetSocketAddress(proxyAddress, proxyPort); ++ // Paper end - Add API to get player's proxy address + } + } else { + super.channelRead(ctx, msg); +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java +index 933390c4244200dc1be5311a174e5df95f6ac633..e1e73e877dfef5bcf30a99b275ececbf42f59c95 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java +@@ -270,7 +270,7 @@ public class CraftPlayer extends CraftHumanEntity implements Player { + + @Override + public InetSocketAddress getAddress() { +- if (this.getHandle().connection.protocol() == null) return null; ++ if (this.getHandle().connection == null) return null; + + SocketAddress addr = this.getHandle().connection.getRemoteAddress(); + if (addr instanceof InetSocketAddress) { +@@ -280,6 +280,15 @@ public class CraftPlayer extends CraftHumanEntity implements Player { + } + } + ++ // Paper start - Add API to get player's proxy address ++ @Override ++ public @Nullable InetSocketAddress getHAProxyAddress() { ++ if (this.getHandle().connection == null) return null; ++ ++ return this.getHandle().connection.connection.haProxyAddress instanceof final InetSocketAddress inetSocketAddress ? inetSocketAddress : null; ++ } ++ // Paper end - Add API to get player's proxy address ++ + public interface TransferCookieConnection { + + boolean isTransferred(); diff --git a/patches/server/0952-Deep-clone-nbt-tags-in-PDC.patch b/patches/server/0952-Deep-clone-nbt-tags-in-PDC.patch deleted file mode 100644 index 944099f20910..000000000000 --- a/patches/server/0952-Deep-clone-nbt-tags-in-PDC.patch +++ /dev/null @@ -1,45 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: SoSeDiK -Date: Thu, 26 May 2022 03:30:05 +0300 -Subject: [PATCH] Deep clone nbt tags in PDC - - -diff --git a/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaItem.java b/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaItem.java -index f15456b02cabbbe33d701450ef53a0561d91cb8c..7ad1076de76c81c25b656e52237c2f60a2eca085 100644 ---- a/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaItem.java -+++ b/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaItem.java -@@ -322,7 +322,7 @@ class CraftMetaItem implements ItemMeta, Damageable, Repairable, BlockDataMeta { - this.maxDamage = meta.maxDamage; - this.unhandledTags.copy(meta.unhandledTags.build()); - this.removedTags.addAll(meta.removedTags); -- this.persistentDataContainer.putAll(meta.persistentDataContainer.getRaw()); -+ this.persistentDataContainer.putAll(meta.persistentDataContainer.getTagsCloned()); // Paper - deep clone NBT tags - - this.customTag = meta.customTag; - -@@ -1699,7 +1699,7 @@ class CraftMetaItem implements ItemMeta, Damageable, Repairable, BlockDataMeta { - clone.customTag = this.customTag.copy(); - } - clone.removedTags = Sets.newHashSet(this.removedTags); -- clone.persistentDataContainer = new CraftPersistentDataContainer(this.persistentDataContainer.getRaw(), CraftMetaItem.DATA_TYPE_REGISTRY); -+ clone.persistentDataContainer = new CraftPersistentDataContainer(this.persistentDataContainer.getTagsCloned(), CraftMetaItem.DATA_TYPE_REGISTRY); // Paper - deep clone NBT tags - clone.hideFlag = this.hideFlag; - clone.hideTooltip = this.hideTooltip; - clone.unbreakable = this.unbreakable; -diff --git a/src/main/java/org/bukkit/craftbukkit/persistence/CraftPersistentDataContainer.java b/src/main/java/org/bukkit/craftbukkit/persistence/CraftPersistentDataContainer.java -index 5a4e7e7150b7c137b077e0b393f17ed35b5aec34..f55fdd57ced259ad5a95878840e98ffaa3db2e05 100644 ---- a/src/main/java/org/bukkit/craftbukkit/persistence/CraftPersistentDataContainer.java -+++ b/src/main/java/org/bukkit/craftbukkit/persistence/CraftPersistentDataContainer.java -@@ -207,4 +207,12 @@ public class CraftPersistentDataContainer implements PersistentDataContainer { - } - } - // Paper end - byte array serialization -+ -+ // Paper start - deep clone tags -+ public Map getTagsCloned() { -+ final Map tags = new HashMap<>(); -+ this.customDataTags.forEach((key, tag) -> tags.put(key, tag.copy())); -+ return tags; -+ } -+ // Paper end - deep clone tags - } diff --git a/patches/server/0953-General-ItemMeta-fixes.patch b/patches/server/0953-General-ItemMeta-fixes.patch new file mode 100644 index 000000000000..9421f612dd97 --- /dev/null +++ b/patches/server/0953-General-ItemMeta-fixes.patch @@ -0,0 +1,2152 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Jake Potrebic +Date: Sat, 27 Apr 2024 20:56:17 -0700 +Subject: [PATCH] General ItemMeta fixes + +== AT == +private-f net/minecraft/world/item/ItemStack components +public net/minecraft/world/food/FoodProperties DEFAULT_EAT_SECONDS +public org/bukkit/craftbukkit/block/CraftBlockStates getBlockState(Lorg/bukkit/World;Lnet/minecraft/core/BlockPos;Lnet/minecraft/world/level/block/state/BlockState;Lnet/minecraft/world/level/block/entity/BlockEntity;)Lorg/bukkit/craftbukkit/block/CraftBlockState; +public net/minecraft/world/level/block/entity/BlockEntity saveId(Lnet/minecraft/nbt/CompoundTag;)V + +Co-authored-by: GhastCraftHD + +diff --git a/src/main/java/net/minecraft/world/item/ItemStack.java b/src/main/java/net/minecraft/world/item/ItemStack.java +index 98b5208baeaa12a5ff2788e457c542000d6ea48b..babd89f39c43b0c64709d99bf8aca6cdc6ca1b24 100644 +--- a/src/main/java/net/minecraft/world/item/ItemStack.java ++++ b/src/main/java/net/minecraft/world/item/ItemStack.java +@@ -1361,6 +1361,11 @@ public final class ItemStack implements DataComponentHolder { + public void setItem(Item item) { + this.bukkitStack = null; // Paper + this.item = item; ++ // Paper start - change base component prototype ++ final DataComponentPatch patch = this.getComponentsPatch(); ++ this.components = new PatchedDataComponentMap(this.item.components()); ++ this.applyComponents(patch); ++ // Paper end - change base component prototype + } + // CraftBukkit end + +diff --git a/src/main/java/net/minecraft/world/level/block/entity/BlockEntity.java b/src/main/java/net/minecraft/world/level/block/entity/BlockEntity.java +index 645a7ec0709cbd3c0cfbf75f7b8622a67515f74c..39fc5aa6ac8c66d8dd7437262124b61c4c138689 100644 +--- a/src/main/java/net/minecraft/world/level/block/entity/BlockEntity.java ++++ b/src/main/java/net/minecraft/world/level/block/entity/BlockEntity.java +@@ -147,6 +147,11 @@ public abstract class BlockEntity { + CompoundTag nbttagcompound = new CompoundTag(); + + this.saveAdditional(nbttagcompound, registries); ++ // Paper start - store PDC here as well ++ if (this.persistentDataContainer != null && !this.persistentDataContainer.isEmpty()) { ++ nbttagcompound.put("PublicBukkitValues", this.persistentDataContainer.toTagCompound()); ++ } ++ // Paper end + return nbttagcompound; + } + +diff --git a/src/main/java/org/bukkit/craftbukkit/block/CraftBlockEntityState.java b/src/main/java/org/bukkit/craftbukkit/block/CraftBlockEntityState.java +index fe7e3e0628783d8d1be9635b689da8a9cb46c5d7..04ae258a2f8e98421340d29d5cceedec045171b7 100644 +--- a/src/main/java/org/bukkit/craftbukkit/block/CraftBlockEntityState.java ++++ b/src/main/java/org/bukkit/craftbukkit/block/CraftBlockEntityState.java +@@ -151,6 +151,19 @@ public abstract class CraftBlockEntityState extends Craft + return this.snapshot.getUpdateTag(this.getRegistryAccess()); + } + ++ // Paper start - properly save blockentity itemstacks ++ public CompoundTag getSnapshotCustomNbtOnly() { ++ this.applyTo(this.snapshot); ++ final CompoundTag nbt = this.snapshot.saveCustomOnly(this.getRegistryAccess()); ++ this.snapshot.removeComponentsFromTag(nbt); ++ if (!nbt.isEmpty()) { ++ // have to include the "id" if it's going to have block entity data ++ this.snapshot.saveId(nbt); ++ } ++ return nbt; ++ } ++ // Paper end ++ + // copies the data of the given tile entity to this block state + protected void load(T tileEntity) { + if (tileEntity != null && tileEntity != this.snapshot) { +diff --git a/src/main/java/org/bukkit/craftbukkit/inventory/CraftItemStack.java b/src/main/java/org/bukkit/craftbukkit/inventory/CraftItemStack.java +index dfdabf86433d323966a748baef769cf0462d3f38..bb2b4528692aed8e3341428697a60c0abee13779 100644 +--- a/src/main/java/org/bukkit/craftbukkit/inventory/CraftItemStack.java ++++ b/src/main/java/org/bukkit/craftbukkit/inventory/CraftItemStack.java +@@ -291,7 +291,9 @@ public final class CraftItemStack extends ItemStack { + + @Override + public void removeEnchantments() { +- this.handle.remove(DataComponents.ENCHANTMENTS); ++ if (this.handle != null) { // Paper - fix NPE ++ this.handle.set(DataComponents.ENCHANTMENTS, ItemEnchantments.EMPTY); // Paper - set to default instead of removing the component ++ } // Paper + } + + @Override +@@ -353,7 +355,14 @@ public final class CraftItemStack extends ItemStack { + // Paper end - improve handled tags on type change + // Paper start + public static void applyMetaToItem(net.minecraft.world.item.ItemStack itemStack, ItemMeta itemMeta) { +- final CraftMetaItem.Applicator tag = new CraftMetaItem.Applicator(); ++ // Paper start - support updating profile after resolving it ++ final CraftMetaItem.Applicator tag = new CraftMetaItem.Applicator() { ++ @Override ++ void skullCallback(final net.minecraft.world.item.component.ResolvableProfile profile) { ++ itemStack.set(DataComponents.PROFILE, profile); ++ } ++ }; ++ // Paper end - support updating profile after resolving it + ((CraftMetaItem) itemMeta).applyToItem(tag); + itemStack.applyComponents(tag.build()); + } +@@ -401,15 +410,20 @@ public final class CraftItemStack extends ItemStack { + if (itemMeta == null) return true; + + if (!((CraftMetaItem) itemMeta).isEmpty()) { +- CraftMetaItem.Applicator tag = new CraftMetaItem.Applicator(); ++ // Paper start - support updating profile after resolving it ++ CraftMetaItem.Applicator tag = new CraftMetaItem.Applicator() { ++ @Override ++ void skullCallback(final net.minecraft.world.item.component.ResolvableProfile resolvableProfile) { ++ item.set(DataComponents.PROFILE, resolvableProfile); ++ } ++ }; ++ // Paper end - support updating profile after resolving it + + ((CraftMetaItem) itemMeta).applyToItem(tag); +- item.restorePatch(tag.build()); +- } +- // SpigotCraft#463 this is required now by the Vanilla client, so mimic ItemStack constructor in ensuring it +- if (item.getItem() != null && item.getMaxDamage() > 0) { +- item.setDamageValue(item.getDamageValue()); ++ item.restorePatch(DataComponentPatch.EMPTY); // Paper - properly apply the new patch from itemmeta ++ item.applyComponents(tag.build()); // Paper - properly apply the new patch from itemmeta + } ++ // Paper - this is no longer needed + + return true; + } +diff --git a/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaAxolotlBucket.java b/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaAxolotlBucket.java +index 169fefb64e1af444f7c2efb1234cb6e7779fb717..cb49ff5c94f33f00f626a31d958d2025819d1da8 100644 +--- a/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaAxolotlBucket.java ++++ b/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaAxolotlBucket.java +@@ -118,14 +118,13 @@ public class CraftMetaAxolotlBucket extends CraftMetaItem implements AxolotlBuck + + @Override + public Axolotl.Variant getVariant() { ++ com.google.common.base.Preconditions.checkState(this.hasVariant(), "Variant is absent, check hasVariant first!"); // Paper - fix NPE + return Axolotl.Variant.values()[this.variant]; + } + + @Override + public void setVariant(Axolotl.Variant variant) { +- if (variant == null) { +- variant = Axolotl.Variant.LUCY; +- } ++ com.google.common.base.Preconditions.checkArgument(variant != null, "Variant cannot be null!"); // Paper + this.variant = variant.ordinal(); + } + +diff --git a/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaBanner.java b/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaBanner.java +index d0a8cd89da3b8d87248494056470c306f8fb5ae8..fdc0c1d73bb523f003e4169589f1002375b9c88c 100644 +--- a/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaBanner.java ++++ b/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaBanner.java +@@ -69,6 +69,7 @@ public class CraftMetaBanner extends CraftMetaItem implements BannerMeta { + void applyToItem(CraftMetaItem.Applicator tag) { + super.applyToItem(tag); + ++ if (this.patterns.isEmpty()) return; // Paper - don't write empty patterns + List newPatterns = new ArrayList<>(); + + for (Pattern p : this.patterns) { +diff --git a/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaBlockState.java b/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaBlockState.java +index 413e41f113226b8a2e9b30bb519076d78e451fa0..d688339a57f0b4f12588ced0f7860a0d77eae728 100644 +--- a/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaBlockState.java ++++ b/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaBlockState.java +@@ -52,10 +52,24 @@ public class CraftMetaBlockState extends CraftMetaItem implements BlockStateMeta + + @ItemMetaKey.Specific(ItemMetaKey.Specific.To.NBT) + static final ItemMetaKeyType BLOCK_ENTITY_TAG = new ItemMetaKeyType<>(DataComponents.BLOCK_ENTITY_DATA, "BlockEntityTag"); ++ static final ItemMetaKey BLOCK_ENTITY_TAG_CUSTOM_DATA = new ItemMetaKey("block-entity-tag"); // Paper ++ static final ItemMetaKey BLOCK_ENTITY_COMPONENTS = new ItemMetaKey("block-entity-components"); // Paper + + final Material material; +- private CraftBlockEntityState blockEntityTag; +- private BlockVector position; ++ // Paper start - store data separately ++ DataComponentMap components; ++ CustomData blockEntityTag; ++ { ++ // this is because the fields are possibly assigned in the super constructor (via deserializeInternal) ++ // and a direct field initialization happens **after** the super constructor. So we only want to ++ // set them to empty if they weren't assigned by the super constructor (via deserializeInternal) ++ this.components = this.components != null ? this.components : DataComponentMap.EMPTY; ++ this.blockEntityTag = this.blockEntityTag != null ? this.blockEntityTag : CustomData.EMPTY; ++ } ++ private Material materialForBlockEntityType() { ++ return this.material; ++ } ++ // Paper end + private CompoundTag internalTag; + + CraftMetaBlockState(CraftMetaItem meta, Material material) { +@@ -64,47 +78,61 @@ public class CraftMetaBlockState extends CraftMetaItem implements BlockStateMeta + + if (!(meta instanceof CraftMetaBlockState) + || ((CraftMetaBlockState) meta).material != material) { +- this.blockEntityTag = null; ++ // Paper start ++ this.components = DataComponentMap.EMPTY; ++ this.blockEntityTag = CustomData.EMPTY; ++ // Paper end + return; + } + + CraftMetaBlockState te = (CraftMetaBlockState) meta; ++ // Paper start ++ this.components = te.components; + this.blockEntityTag = te.blockEntityTag; +- this.position = te.position; ++ // Paper end + } + + CraftMetaBlockState(DataComponentPatch tag, Material material, final Set> extraHandledDcts) { // Paper + super(tag, extraHandledDcts); // Paper + this.material = material; + +- getOrEmpty(tag, CraftMetaBlockState.BLOCK_ENTITY_TAG).ifPresent((blockTag) -> { +- CompoundTag nbt = blockTag.copyTag(); ++ // Paper start - move to separate method to be re-called ++ this.updateBlockState(tag); ++ } + +- this.blockEntityTag = CraftMetaBlockState.getBlockState(material, nbt); +- if (nbt.contains("x", CraftMagicNumbers.NBT.TAG_ANY_NUMBER) && nbt.contains("y", CraftMagicNumbers.NBT.TAG_ANY_NUMBER) && nbt.contains("z", CraftMagicNumbers.NBT.TAG_ANY_NUMBER)) { +- this.position = new BlockVector(nbt.getInt("x"), nbt.getInt("y"), nbt.getInt("z")); +- } ++ private void updateBlockState(final DataComponentPatch tag) { ++ // Paper end ++ getOrEmpty(tag, CraftMetaBlockState.BLOCK_ENTITY_TAG).ifPresent((nbt) -> { ++ this.blockEntityTag = nbt; // Paper + }); + + if (!tag.isEmpty()) { +- CraftBlockEntityState blockEntityTag = this.blockEntityTag; +- if (blockEntityTag == null) { +- blockEntityTag = CraftMetaBlockState.getBlockState(material, null); +- } +- +- // Convert to map +- PatchedDataComponentMap map = new PatchedDataComponentMap(DataComponentMap.EMPTY); +- map.applyPatch(tag); +- // Apply +- Set> applied = blockEntityTag.applyComponents(map, tag); ++ // Paper start - store data in a DataComponentMap to be used to construct CraftBlockEntityStates ++ final DataComponentMap.Builder map = DataComponentMap.builder(); ++ final net.minecraft.world.level.block.entity.BlockEntity dummyBlockEntity = java.util.Objects.requireNonNull( ++ org.bukkit.craftbukkit.block.CraftBlockStates.createNewTileEntity(this.materialForBlockEntityType()) ++ ); ++ ++ // we don't care about what's in here, all ++ // we want is to know which data component types are referenced ++ Set> applied = dummyBlockEntity.applyComponentsSet(DataComponentMap.EMPTY, DataComponentPatch.EMPTY); ++ // Paper end - store data in a DataComponentMap to be used to construct CraftBlockEntityStates + // Mark applied components as handled + for (DataComponentType seen : applied) { + this.unhandledTags.clear(seen); + } + // Only set blockEntityTag if something was applied + if (!applied.isEmpty()) { +- this.blockEntityTag = blockEntityTag; ++ // Paper start ++ for (final DataComponentType type : applied) { ++ if (CraftMetaItem.DEFAULT_HANDLED_DCTS.contains(type)) continue; ++ getOrEmpty(tag, type).ifPresent(value -> { ++ map.set(type, value); ++ }); ++ } ++ // Paper end + } ++ this.components = map.build(); // Paper + } + } + +@@ -118,42 +146,43 @@ public class CraftMetaBlockState extends CraftMetaItem implements BlockStateMeta + this.material = Material.AIR; + } + if (this.internalTag != null) { +- this.blockEntityTag = CraftMetaBlockState.getBlockState(this.material, this.internalTag); ++ this.setBlockState(CraftMetaBlockState.getBlockState(this.material, this.internalTag)); // Paper - general item meta fixes - pass through setter + this.internalTag = null; + } +- this.position = SerializableMeta.getObject(BlockVector.class, map, "blockPosition", true); ++ // Paper start - general item meta fixes - parse spigot legacy position and merge into block entity tag ++ final BlockVector legacyPosition = SerializableMeta.getObject(BlockVector.class, map, "blockPosition", true); ++ if (legacyPosition != null) { ++ this.blockEntityTag = this.blockEntityTag.update(t -> { ++ if (t.isEmpty()) { ++ BlockEntity.addEntityType(t, java.util.Objects.requireNonNull(CraftBlockStates.getBlockEntityType(this.materialForBlockEntityType()))); ++ } ++ t.putInt("x", legacyPosition.getBlockX()); ++ t.putInt("y", legacyPosition.getBlockY()); ++ t.putInt("z", legacyPosition.getBlockZ()); ++ }); ++ } ++ // Paper end - general item meta fixes - parse spigot legacy position and merge into block entity tag + } + + @Override + void applyToItem(CraftMetaItem.Applicator tag) { + super.applyToItem(tag); + +- CompoundTag nbt = null; +- if (this.blockEntityTag != null) { +- nbt = this.blockEntityTag.getItemNBT(); +- +- for (TypedDataComponent component : this.blockEntityTag.collectComponents()) { +- tag.putIfAbsent(component); +- } ++ // Paper start - accurately replicate logic for creating ItemStack from BlockEntity ++ // taken from BlockEntity#saveToItem and BlockItem#setBlockEntityData ++ final CompoundTag nbt = this.blockEntityTag.copyTag(); ++ if (nbt.contains("id", CraftMagicNumbers.NBT.TAG_STRING)) { ++ tag.put(CraftMetaBlockState.BLOCK_ENTITY_TAG, CustomData.of(nbt)); ++ } else if (!nbt.isEmpty()) { ++ BlockEntity.addEntityType(nbt, java.util.Objects.requireNonNull(CraftBlockStates.getBlockEntityType(this.materialForBlockEntityType()))); ++ tag.put(CraftMetaBlockState.BLOCK_ENTITY_TAG, CustomData.of(nbt)); + } + +- if (this.position != null) { +- if (nbt == null) { +- nbt = new CompoundTag(); +- } +- +- nbt.putInt("x", this.position.getBlockX()); +- nbt.putInt("y", this.position.getBlockY()); +- nbt.putInt("z", this.position.getBlockZ()); +- } +- +- if (nbt != null && !nbt.isEmpty()) { +- CraftBlockEntityState tile = (this.blockEntityTag != null) ? this.blockEntityTag : CraftMetaBlockState.getBlockState(this.material, null); +- // See ItemBlock#setBlockEntityData +- tile.addEntityType(nbt); +- +- tag.put(CraftMetaBlockState.BLOCK_ENTITY_TAG, CustomData.of(nbt)); ++ for (final TypedDataComponent component : this.components) { ++ if (CraftMetaItem.DEFAULT_HANDLED_DCTS.contains(component.type())) continue; // if the component type was already handled by CraftMetaItem, don't add it again ++ tag.builder.set(component); + } ++ // Paper end + } + + @Override +@@ -162,23 +191,35 @@ public class CraftMetaBlockState extends CraftMetaItem implements BlockStateMeta + + if (tag.contains(CraftMetaBlockState.BLOCK_ENTITY_TAG.NBT, CraftMagicNumbers.NBT.TAG_COMPOUND)) { + this.internalTag = tag.getCompound(CraftMetaBlockState.BLOCK_ENTITY_TAG.NBT); ++ return; // Paper - if legacy, don't check anything else ++ } ++ // Paper start - new serialization format ++ if (tag.contains(CraftMetaBlockState.BLOCK_ENTITY_TAG_CUSTOM_DATA.NBT, CraftMagicNumbers.NBT.TAG_COMPOUND)) { ++ this.blockEntityTag = CustomData.of(tag.getCompound(CraftMetaBlockState.BLOCK_ENTITY_TAG_CUSTOM_DATA.NBT)); ++ } ++ if (tag.contains(CraftMetaBlockState.BLOCK_ENTITY_COMPONENTS.NBT, CraftMagicNumbers.NBT.TAG_COMPOUND)) { ++ this.components = DataComponentMap.CODEC.parse(org.bukkit.craftbukkit.CraftRegistry.getMinecraftRegistry().createSerializationContext(net.minecraft.nbt.NbtOps.INSTANCE), tag.getCompound(CraftMetaBlockState.BLOCK_ENTITY_COMPONENTS.NBT)).getOrThrow(); + } ++ // Paper end - new serialization format + } + + @Override + void serializeInternal(final Map internalTags) { +- if (this.blockEntityTag != null) { +- internalTags.put(CraftMetaBlockState.BLOCK_ENTITY_TAG.NBT, this.blockEntityTag.getSnapshotNBT()); ++ // Paper start - new serialization format ++ if (!this.blockEntityTag.isEmpty()) { ++ internalTags.put(CraftMetaBlockState.BLOCK_ENTITY_TAG_CUSTOM_DATA.NBT, this.blockEntityTag.getUnsafe()); // unsafe because it's serialized right away + } ++ if (!this.components.isEmpty()) { ++ final Tag componentsTag = DataComponentMap.CODEC.encodeStart(org.bukkit.craftbukkit.CraftRegistry.getMinecraftRegistry().createSerializationContext(net.minecraft.nbt.NbtOps.INSTANCE), this.components).getOrThrow(); ++ internalTags.put(CraftMetaBlockState.BLOCK_ENTITY_COMPONENTS.NBT, componentsTag); ++ } ++ // Paper end - new serialization format + } + + @Override + ImmutableMap.Builder serialize(ImmutableMap.Builder builder) { + super.serialize(builder); + builder.put("blockMaterial", this.material.name()); +- if (this.position != null) { +- builder.put("blockPosition", this.position); +- } + return builder; + } + +@@ -186,12 +227,10 @@ public class CraftMetaBlockState extends CraftMetaItem implements BlockStateMeta + int applyHash() { + final int original; + int hash = original = super.applyHash(); +- if (this.blockEntityTag != null) { +- hash = 61 * hash + this.blockEntityTag.hashCode(); +- } +- if (this.position != null) { +- hash = 61 * hash + this.position.hashCode(); +- } ++ // Paper start ++ hash = 61 * hash + this.blockEntityTag.hashCode(); ++ hash = 61 * hash + this.components.hashCode(); ++ // Paper end + return original != hash ? CraftMetaBlockState.class.hashCode() ^ hash : hash; + } + +@@ -203,52 +242,75 @@ public class CraftMetaBlockState extends CraftMetaItem implements BlockStateMeta + if (meta instanceof CraftMetaBlockState) { + CraftMetaBlockState that = (CraftMetaBlockState) meta; + +- return Objects.equal(this.blockEntityTag, that.blockEntityTag) && Objects.equal(this.position, that.position); ++ return Objects.equal(this.blockEntityTag, that.blockEntityTag) && Objects.equal(this.components, that.components); // Paper + } + return true; + } + + boolean isBlockStateEmpty() { +- return !(this.blockEntityTag != null || this.position != null); ++ return !(this.blockEntityTag != null); + } + + @Override + boolean notUncommon(CraftMetaItem meta) { +- return super.notUncommon(meta) && (meta instanceof CraftMetaBlockState || this.isBlockStateEmpty()); ++ return super.notUncommon(meta) && (meta instanceof CraftMetaBlockState || (this.blockEntityTag.isEmpty() && this.components.isEmpty())); // Paper + } + + @Override + boolean isEmpty() { +- return super.isEmpty() && this.isBlockStateEmpty(); ++ return super.isEmpty() && this.blockEntityTag.isEmpty() && this.components.isEmpty(); // Paper + } + + @Override + public CraftMetaBlockState clone() { + CraftMetaBlockState meta = (CraftMetaBlockState) super.clone(); +- if (this.blockEntityTag != null) { +- meta.blockEntityTag = this.blockEntityTag.copy(); +- } +- if (this.position != null) { +- meta.position = this.position.clone(); +- } ++ // Paper start - no need for "clone" because they are essentially immutables ++ meta.blockEntityTag = this.blockEntityTag; ++ meta.components = this.components; ++ // Paper end + return meta; + } + + @Override + public boolean hasBlockState() { +- return this.blockEntityTag != null; ++ return !this.blockEntityTag.isEmpty() || !this.components.isEmpty(); // Paper + } + + // Paper start - add method to clear block state + @Override + public void clearBlockState() { +- this.blockEntityTag = null; ++ // Paper start ++ this.blockEntityTag = CustomData.EMPTY; ++ this.components = DataComponentMap.EMPTY; ++ // Paper end + } + // Paper end - add method to clear block state + + @Override +- public BlockState getBlockState() { +- return (this.blockEntityTag != null) ? this.blockEntityTag.copy() : CraftMetaBlockState.getBlockState(this.material, null); ++ // Paper start - create blockstate on-demand ++ public CraftBlockEntityState getBlockState() { ++ BlockPos pos = BlockPos.ZERO; ++ final Material stateMaterial = this.materialForBlockEntityType(); ++ if (!this.blockEntityTag.isEmpty()) { ++ // Paper "id" field is always present now ++ pos = BlockEntity.getPosFromTag(this.blockEntityTag.getUnsafe()); // unsafe is fine here, just querying ++ } ++ final net.minecraft.world.level.block.entity.BlockEntityType type = java.util.Objects.requireNonNull(CraftBlockStates.getBlockEntityType(stateMaterial)); ++ final net.minecraft.world.level.block.state.BlockState nmsBlockState = ((org.bukkit.craftbukkit.block.data.CraftBlockData) this.getBlockData(stateMaterial)).getState(); ++ final net.minecraft.world.level.block.entity.BlockEntity blockEntity = java.util.Objects.requireNonNull(type.create(pos, nmsBlockState)); ++ if (!this.blockEntityTag.isEmpty()) { ++ this.blockEntityTag.loadInto(blockEntity, org.bukkit.craftbukkit.CraftRegistry.getMinecraftRegistry()); ++ } ++ final PatchedDataComponentMap patchedMap = new PatchedDataComponentMap(nmsBlockState.getBlock().asItem().components()); ++ patchedMap.setAll(this.components); ++ final Applicator applicator = new Applicator() {}; ++ super.applyToItem(applicator); ++ patchedMap.applyPatch(applicator.build()); ++ blockEntity.applyComponents(nmsBlockState.getBlock().asItem().components(), patchedMap.asPatch()); ++ ++ // This is expected to always return a CraftBlockEntityState for the passed material: ++ return (CraftBlockEntityState) CraftBlockStates.getBlockState(null, pos, nmsBlockState, blockEntity); ++ // Paper end + } + + private static CraftBlockEntityState getBlockState(Material material, CompoundTag blockEntityTag) { +@@ -278,7 +340,23 @@ public class CraftMetaBlockState extends CraftMetaItem implements BlockStateMeta + Class blockStateType = CraftBlockStates.getBlockStateType(stateMaterial); + Preconditions.checkArgument(blockStateType == blockState.getClass() && blockState instanceof CraftBlockEntityState, "Invalid blockState for %s", this.material); + +- this.blockEntityTag = (CraftBlockEntityState) blockState; ++ // Paper start - when a new BlockState is set, the components from that block entity ++ // have to be used to update the fields on CraftMetaItem ++ final CraftBlockEntityState craftBlockState = (CraftBlockEntityState) blockState; ++ final CompoundTag data = craftBlockState.getSnapshotCustomNbtOnly(); ++ final PatchedDataComponentMap patchedMap = new net.minecraft.core.component.PatchedDataComponentMap(craftBlockState.getHandle().getBlock().asItem().components()); ++ final net.minecraft.core.component.DataComponentMap map = craftBlockState.collectComponents(); ++ patchedMap.setAll(map); ++ if (!data.isEmpty()) { ++ patchedMap.set(BLOCK_ENTITY_TAG.TYPE, CustomData.of(data)); ++ } ++ final DataComponentPatch patch = patchedMap.asPatch(); ++ this.updateFromPatch(patch, null); ++ // we have to reset the fields because this should be like a "new" block entity is being used ++ this.blockEntityTag = CustomData.EMPTY; ++ this.components = DataComponentMap.EMPTY; ++ this.updateBlockState(patch); ++ // Paper end + } + + private static Material shieldToBannerHack(CompoundTag tag) { +diff --git a/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaBook.java b/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaBook.java +index 257c835bc280eee9ee73ae75b5249bb568a687d0..70f20de37c1f8d57a8d9fe00dcd864fdd9948ec2 100644 +--- a/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaBook.java ++++ b/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaBook.java +@@ -34,7 +34,7 @@ public class CraftMetaBook extends CraftMetaItem implements BookMeta, WritableBo + @ItemMetaKey.Specific(ItemMetaKey.Specific.To.NBT) + static final ItemMetaKeyType BOOK_CONTENT = new ItemMetaKeyType<>(DataComponents.WRITABLE_BOOK_CONTENT); + static final ItemMetaKey BOOK_PAGES = new ItemMetaKey("pages"); +- static final int MAX_PAGES = Integer.MAX_VALUE; // SPIGOT-6911: Use Minecraft limits ++ static final int MAX_PAGES = WritableBookContent.MAX_PAGES; // SPIGOT-6911: Use Minecraft limits // Paper + static final int MAX_PAGE_LENGTH = WritableBookContent.PAGE_EDIT_LENGTH; // SPIGOT-6911: Use Minecraft limits + + // We store the pages in their raw original text representation. See SPIGOT-5063, SPIGOT-5350, SPIGOT-3206 +diff --git a/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaBookSigned.java b/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaBookSigned.java +index cbb3d80cc7cd81b2505dff999a0baede737165f7..040dac82e484cb44b3afd444b4bbd1fd994bfe7c 100644 +--- a/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaBookSigned.java ++++ b/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaBookSigned.java +@@ -124,13 +124,13 @@ public class CraftMetaBookSigned extends CraftMetaItem implements BookMeta { + void applyToItem(CraftMetaItem.Applicator itemData) { + super.applyToItem(itemData); + ++ List> list = new ArrayList<>(); // Paper - General ItemMeta Fixes + if (this.pages != null) { +- List> list = new ArrayList<>(); + for (Component page : this.pages) { + list.add(Filterable.passThrough(page)); + } +- itemData.put(CraftMetaBookSigned.BOOK_CONTENT, new WrittenBookContent(Filterable.from(FilteredText.passThrough(this.title)), this.author, this.generation, list, this.resolved)); + } ++ itemData.put(CraftMetaBookSigned.BOOK_CONTENT, new WrittenBookContent(Filterable.from(this.title == null ? FilteredText.EMPTY : FilteredText.passThrough(this.title)), this.author == null ? "" : this.author, this.generation, list, this.resolved)); // Paper - General ItemMeta Fixes + } + + @Override +diff --git a/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaBundle.java b/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaBundle.java +index 2736a87a6c481da0575e6e29ea08faa539c24378..51da0db4da3549efd69f367e28450408968fa8d0 100644 +--- a/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaBundle.java ++++ b/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaBundle.java +@@ -41,7 +41,7 @@ public class CraftMetaBundle extends CraftMetaItem implements BundleMeta { + bundle.items().forEach((item) -> { + ItemStack itemStack = CraftItemStack.asCraftMirror(item); + +- if (!itemStack.getType().isAir()) { // SPIGOT-7174 - Avoid adding air ++ if (!itemStack.isEmpty()) { // SPIGOT-7174 - Avoid adding air // Paper + this.addItem(itemStack); + } + }); +@@ -54,7 +54,7 @@ public class CraftMetaBundle extends CraftMetaItem implements BundleMeta { + Iterable items = SerializableMeta.getObject(Iterable.class, map, CraftMetaBundle.ITEMS.BUKKIT, true); + if (items != null) { + for (Object stack : items) { +- if (stack instanceof ItemStack itemStack && !itemStack.getType().isAir()) { // SPIGOT-7174 - Avoid adding air ++ if (stack instanceof ItemStack itemStack && !itemStack.isEmpty()) { // SPIGOT-7174 - Avoid adding air // Paper + this.addItem(itemStack); + } + } +@@ -110,7 +110,7 @@ public class CraftMetaBundle extends CraftMetaItem implements BundleMeta { + + @Override + public void addItem(ItemStack item) { +- Preconditions.checkArgument(item != null && !item.getType().isAir(), "item is null or air"); ++ Preconditions.checkArgument(item != null && !item.isEmpty(), "item is null or empty"); // Paper + + if (this.items == null) { + this.items = new ArrayList<>(); +diff --git a/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaColorableArmor.java b/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaColorableArmor.java +index dc6398cfbd6e749733c3a681e1eb8ce15c3b2c5e..51d7263cdd34359d9cdf72cc01ba654b519f838d 100644 +--- a/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaColorableArmor.java ++++ b/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaColorableArmor.java +@@ -11,16 +11,30 @@ import org.bukkit.inventory.meta.ColorableArmorMeta; + @DelegateDeserialization(SerializableMeta.class) + public class CraftMetaColorableArmor extends CraftMetaArmor implements ColorableArmorMeta { + +- private Color color = DEFAULT_LEATHER_COLOR; ++ private Integer color; // Paper - keep color component consistent with vanilla (top byte is ignored) + + CraftMetaColorableArmor(CraftMetaItem meta) { + super(meta); +- CraftMetaLeatherArmor.readColor(this, meta); ++ // Paper start ++ if (!(meta instanceof CraftMetaColorableArmor armorMeta)) { ++ return; ++ } ++ ++ this.color = armorMeta.color; ++ // Paper end + } + + CraftMetaColorableArmor(DataComponentPatch tag, java.util.Set> extraHandledDcts) { // Paper + super(tag, extraHandledDcts); // Paper +- CraftMetaLeatherArmor.readColor(this, tag); ++ // Paper start ++ getOrEmpty(tag, CraftMetaLeatherArmor.COLOR).ifPresent((dyedItemColor) -> { ++ if (!dyedItemColor.showInTooltip()) { ++ this.addItemFlags(org.bukkit.inventory.ItemFlag.HIDE_DYE); ++ } ++ ++ this.color = dyedItemColor.rgb(); ++ }); ++ // Paper end + } + + CraftMetaColorableArmor(Map map) { +@@ -31,7 +45,11 @@ public class CraftMetaColorableArmor extends CraftMetaArmor implements Colorable + @Override + void applyToItem(CraftMetaItem.Applicator itemTag) { + super.applyToItem(itemTag); +- CraftMetaLeatherArmor.applyColor(this, itemTag); ++ // Paper start ++ if (this.hasColor()) { ++ itemTag.put(CraftMetaLeatherArmor.COLOR, new net.minecraft.world.item.component.DyedItemColor(this.color, !this.hasItemFlag(org.bukkit.inventory.ItemFlag.HIDE_DYE))); ++ } ++ // Paper end + } + + @Override +@@ -52,16 +70,16 @@ public class CraftMetaColorableArmor extends CraftMetaArmor implements Colorable + + @Override + public Color getColor() { +- return this.color; ++ return this.color == null ? DEFAULT_LEATHER_COLOR : Color.fromRGB(this.color & 0xFFFFFF); // Paper - this should really be nullable + } + + @Override + public void setColor(Color color) { +- this.color = color == null ? DEFAULT_LEATHER_COLOR : color; ++ this.color = color == null ? null : color.asRGB(); // Paper + } + + boolean hasColor() { +- return CraftMetaLeatherArmor.hasColor(this); ++ return this.color != null; // Paper + } + + @Override +@@ -81,7 +99,7 @@ public class CraftMetaColorableArmor extends CraftMetaArmor implements Colorable + if (meta instanceof CraftMetaColorableArmor) { + CraftMetaColorableArmor that = (CraftMetaColorableArmor) meta; + +- return this.color.equals(that.color); ++ return this.hasColor() ? that.hasColor() && this.color.equals(that.color) : !that.hasColor(); // Paper - allow null + } + return true; + } +diff --git a/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaCompass.java b/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaCompass.java +index 69a112b3a9726966aecbe687d905fd1a11cfa1e3..ab424926c282fb03eabd1eebd2b7980899ef28e3 100644 +--- a/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaCompass.java ++++ b/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaCompass.java +@@ -31,11 +31,7 @@ public class CraftMetaCompass extends CraftMetaItem implements CompassMeta { + static final ItemMetaKey LODESTONE_POS_Z = new ItemMetaKey("LodestonePosZ"); + static final ItemMetaKey LODESTONE_TRACKED = new ItemMetaKey("LodestoneTracked"); + +- private ResourceKey lodestoneWorld; +- private int lodestoneX; +- private int lodestoneY; +- private int lodestoneZ; +- private boolean tracked = true; ++ private LodestoneTracker tracker; // Paper - use LodestoneTracker type + + CraftMetaCompass(CraftMetaItem meta) { + super(meta); +@@ -43,24 +39,13 @@ public class CraftMetaCompass extends CraftMetaItem implements CompassMeta { + return; + } + CraftMetaCompass compassMeta = (CraftMetaCompass) meta; +- this.lodestoneWorld = compassMeta.lodestoneWorld; +- this.lodestoneX = compassMeta.lodestoneX; +- this.lodestoneY = compassMeta.lodestoneY; +- this.lodestoneZ = compassMeta.lodestoneZ; +- this.tracked = compassMeta.tracked; ++ this.tracker = compassMeta.tracker; // Paper - use LodestoneTracker type + } + + CraftMetaCompass(DataComponentPatch tag, java.util.Set> extraHandledDcts) { // Paper + super(tag, extraHandledDcts); // Paper + getOrEmpty(tag, CraftMetaCompass.LODESTONE_TARGET).ifPresent((lodestoneTarget) -> { +- lodestoneTarget.target().ifPresent((target) -> { +- this.lodestoneWorld = target.dimension(); +- BlockPos pos = target.pos(); +- this.lodestoneX = pos.getX(); +- this.lodestoneY = pos.getY(); +- this.lodestoneZ = pos.getZ(); +- }); +- this.tracked = lodestoneTarget.tracked(); ++ this.tracker = lodestoneTarget; // Paper - use LodestoneTracker type + }); + } + +@@ -68,10 +53,13 @@ public class CraftMetaCompass extends CraftMetaItem implements CompassMeta { + super(map); + String lodestoneWorldString = SerializableMeta.getString(map, CraftMetaCompass.LODESTONE_POS_WORLD.BUKKIT, true); + if (lodestoneWorldString != null) { +- this.lodestoneWorld = ResourceKey.create(Registries.DIMENSION, ResourceLocation.tryParse(lodestoneWorldString)); +- this.lodestoneX = (Integer) map.get(CraftMetaCompass.LODESTONE_POS_X.BUKKIT); +- this.lodestoneY = (Integer) map.get(CraftMetaCompass.LODESTONE_POS_Y.BUKKIT); +- this.lodestoneZ = (Integer) map.get(CraftMetaCompass.LODESTONE_POS_Z.BUKKIT); ++ // Paper start - use LodestoneTracker type ++ ResourceKey lodestoneWorld = ResourceKey.create(Registries.DIMENSION, ResourceLocation.tryParse(lodestoneWorldString)); ++ int lodestoneX = (Integer) map.get(CraftMetaCompass.LODESTONE_POS_X.BUKKIT); ++ int lodestoneY = (Integer) map.get(CraftMetaCompass.LODESTONE_POS_Y.BUKKIT); ++ int lodestoneZ = (Integer) map.get(CraftMetaCompass.LODESTONE_POS_Z.BUKKIT); ++ this.tracker = new LodestoneTracker(Optional.of(new GlobalPos(lodestoneWorld, new BlockPos(lodestoneX, lodestoneY, lodestoneZ))), true); ++ // Paper end - use LodestoneTracker type + } else { + // legacy + Location lodestone = SerializableMeta.getObject(Location.class, map, CraftMetaCompass.LODESTONE_POS.BUKKIT, true); +@@ -79,21 +67,22 @@ public class CraftMetaCompass extends CraftMetaItem implements CompassMeta { + this.setLodestone(lodestone); + } + } +- this.tracked = SerializableMeta.getBoolean(map, CraftMetaCompass.LODESTONE_TRACKED.BUKKIT); ++ // Paper start - use LodestoneTracker type ++ final Optional tracked = SerializableMeta.getObjectOptionally(Boolean.class, map, CraftMetaCompass.LODESTONE_TRACKED.BUKKIT, true); ++ final Optional trackedPos = this.tracker != null ? this.tracker.target() : Optional.empty(); ++ tracked.ifPresent(isTracked -> this.tracker = new LodestoneTracker(trackedPos, isTracked)); ++ // Paper end - use LodestoneTracker type + } + + @Override + void applyToItem(CraftMetaItem.Applicator tag) { + super.applyToItem(tag); + +- Optional target = Optional.empty(); +- if (this.lodestoneWorld != null) { +- target = Optional.of(new GlobalPos(this.lodestoneWorld, new BlockPos(this.lodestoneX, this.lodestoneY, this.lodestoneZ))); +- } +- +- if (target.isPresent() || this.hasLodestoneTracked()) { +- tag.put(CraftMetaCompass.LODESTONE_TARGET, new LodestoneTracker(target, this.tracked)); ++ // Paper start - use LodestoneTracker type ++ if (this.tracker != null) { ++ tag.put(CraftMetaCompass.LODESTONE_TARGET, this.tracker); + } ++ // Paper end - use LodestoneTracker type + } + + @Override +@@ -102,7 +91,7 @@ public class CraftMetaCompass extends CraftMetaItem implements CompassMeta { + } + + boolean isCompassEmpty() { +- return !(this.hasLodestone() || this.hasLodestoneTracked()); ++ return this.tracker == null; // Paper - use LodestoneTracker type + } + + @Override +@@ -113,58 +102,69 @@ public class CraftMetaCompass extends CraftMetaItem implements CompassMeta { + + @Override + public boolean hasLodestone() { +- return this.lodestoneWorld != null; ++ return this.tracker != null && this.tracker.target().isPresent(); // Paper - use LodestoneTracker type + } + + @Override + public Location getLodestone() { +- if (this.lodestoneWorld == null) { ++ if (this.tracker == null || this.tracker.target().isEmpty()) { // Paper - use LodestoneTracker type + return null; + } +- ServerLevel worldServer = MinecraftServer.getServer().getLevel(this.lodestoneWorld); ++ ServerLevel worldServer = MinecraftServer.getServer().getLevel(this.tracker.target().get().dimension()); // Paper - use LodestoneTracker type + World world = worldServer != null ? worldServer.getWorld() : null; +- return new Location(world, this.lodestoneX, this.lodestoneY, this.lodestoneZ); // world may be null here, if the referenced world is not loaded ++ return org.bukkit.craftbukkit.util.CraftLocation.toBukkit(this.tracker.target().get().pos(), world); // world may be null here, if the referenced world is not loaded // Paper - use LodestoneTracker type + } + + @Override + public void setLodestone(Location lodestone) { + Preconditions.checkArgument(lodestone == null || lodestone.getWorld() != null, "world is null"); + if (lodestone == null) { +- this.lodestoneWorld = null; ++ // Paper start - use LodestoneTracker type ++ if (this.tracker != null) { ++ this.tracker = new LodestoneTracker(java.util.Optional.empty(), this.tracker.tracked()); // Paper - use LodestoneTracker type ++ } ++ // Paper end - use LodestoneTracker type + } else { +- this.lodestoneWorld = ((CraftWorld) lodestone.getWorld()).getHandle().dimension(); +- this.lodestoneX = lodestone.getBlockX(); +- this.lodestoneY = lodestone.getBlockY(); +- this.lodestoneZ = lodestone.getBlockZ(); ++ // Paper start - use LodestoneTracker type ++ GlobalPos pos = GlobalPos.of( ++ ((CraftWorld) lodestone.getWorld()).getHandle().dimension(), ++ io.papermc.paper.util.MCUtil.toBlockPosition(lodestone) ++ ); ++ boolean tracked = this.tracker == null || this.tracker.tracked(); ++ this.tracker = new LodestoneTracker(Optional.of(pos), tracked); ++ // Paper end - use LodestoneTracker type + } + } + +- boolean hasLodestoneTracked() { +- return !this.tracked; +- } +- + @Override + public boolean isLodestoneTracked() { +- return this.tracked; ++ return this.tracker != null && this.tracker.tracked(); // Paper - use LodestoneTracker type + } + + @Override + public void setLodestoneTracked(boolean tracked) { +- this.tracked = tracked; ++ final Optional trackedPos = this.tracker != null ? this.tracker.target() : Optional.empty(); // Paper - use LodestoneTracker type ++ this.tracker = new LodestoneTracker(trackedPos, tracked); // Paper - use LodestoneTracker type ++ } ++ ++ // Paper start - Add more lodestone compass methods ++ @Override ++ public boolean isLodestoneCompass() { ++ return this.tracker != null; ++ } ++ ++ @Override ++ public void clearLodestone() { ++ this.tracker = null; + } ++ // Paper end - Add more lodestone compass methods + + @Override + int applyHash() { + final int original; + int hash = original = super.applyHash(); +- if (this.hasLodestone()) { +- hash = 73 * hash + this.lodestoneWorld.hashCode(); +- hash = 73 * hash + this.lodestoneX; +- hash = 73 * hash + this.lodestoneY; +- hash = 73 * hash + this.lodestoneZ; +- } +- if (this.hasLodestoneTracked()) { +- hash = 73 * hash + (this.isLodestoneTracked() ? 1231 : 1237); ++ if (this.isLodestoneCompass()) { ++ hash = 73 * hash + this.tracker.hashCode(); // Paper - use LodestoneTracker type + } + + return original != hash ? CraftMetaCompass.class.hashCode() ^ hash : hash; +@@ -178,10 +178,7 @@ public class CraftMetaCompass extends CraftMetaItem implements CompassMeta { + if (meta instanceof CraftMetaCompass) { + CraftMetaCompass that = (CraftMetaCompass) meta; + +- return (this.hasLodestone() ? that.hasLodestone() && this.lodestoneWorld.equals(that.lodestoneWorld) +- && this.lodestoneX == that.lodestoneX && this.lodestoneY == that.lodestoneY +- && this.lodestoneZ == that.lodestoneZ : !that.hasLodestone()) +- && this.tracked == that.tracked; ++ return java.util.Objects.equals(this.tracker, that.tracker); // Paper - use LodestoneTracker type + } + return true; + } +@@ -195,14 +192,16 @@ public class CraftMetaCompass extends CraftMetaItem implements CompassMeta { + Builder serialize(Builder builder) { + super.serialize(builder); + +- if (this.hasLodestone()) { +- builder.put(CraftMetaCompass.LODESTONE_POS_WORLD.BUKKIT, this.lodestoneWorld.location().toString()); +- builder.put(CraftMetaCompass.LODESTONE_POS_X.BUKKIT, this.lodestoneX); +- builder.put(CraftMetaCompass.LODESTONE_POS_Y.BUKKIT, this.lodestoneY); +- builder.put(CraftMetaCompass.LODESTONE_POS_Z.BUKKIT, this.lodestoneZ); +- } +- if (this.hasLodestoneTracked()) { +- builder.put(CraftMetaCompass.LODESTONE_TRACKED.BUKKIT, this.tracked); ++ if (this.isLodestoneCompass()) { // Paper - use LodestoneTracker type ++ // Paper start - use LodestoneTracker type ++ if (this.tracker.target().isPresent()) { ++ builder.put(CraftMetaCompass.LODESTONE_POS_WORLD.BUKKIT, this.tracker.target().get().dimension().location().toString()); ++ builder.put(CraftMetaCompass.LODESTONE_POS_X.BUKKIT, this.tracker.target().get().pos().getX()); ++ builder.put(CraftMetaCompass.LODESTONE_POS_Y.BUKKIT, this.tracker.target().get().pos().getY()); ++ builder.put(CraftMetaCompass.LODESTONE_POS_Z.BUKKIT, this.tracker.target().get().pos().getZ()); ++ } ++ builder.put(CraftMetaCompass.LODESTONE_TRACKED.BUKKIT, this.tracker.tracked()); ++ // Paper end - use LodestoneTracker type + } + + return builder; +diff --git a/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaCrossbow.java b/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaCrossbow.java +index 0807c2172c5a4bee675cef265a45a9350e98b880..88ea260fb84a5f8eaab3a23a9a65d0411215a6a1 100644 +--- a/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaCrossbow.java ++++ b/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaCrossbow.java +@@ -117,7 +117,7 @@ public class CraftMetaCrossbow extends CraftMetaItem implements CrossbowMeta { + @Override + public void addChargedProjectile(ItemStack item) { + Preconditions.checkArgument(item != null, "item"); +- Preconditions.checkArgument(item.getType() == Material.FIREWORK_ROCKET || CraftItemType.bukkitToMinecraft(item.getType()) instanceof ArrowItem, "Item %s is not an arrow or firework rocket", item); ++ Preconditions.checkArgument(!item.isEmpty(), "Item cannot be empty"); // Paper + + if (this.chargedProjectiles == null) { + this.chargedProjectiles = new ArrayList<>(); +diff --git a/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaEntityTag.java b/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaEntityTag.java +index 3f6c5cbbf63631e4b72dc43558651ea94f31ca78..da474a5b963d8e6769d120e9091e60ed0a468a9f 100644 +--- a/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaEntityTag.java ++++ b/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaEntityTag.java +@@ -98,7 +98,7 @@ public class CraftMetaEntityTag extends CraftMetaItem { + if (meta instanceof CraftMetaEntityTag) { + CraftMetaEntityTag that = (CraftMetaEntityTag) meta; + +- return this.entityTag != null ? that.entityTag != null && this.entityTag.equals(that.entityTag) : this.entityTag == null; ++ return this.entityTag != null ? that.entityTag != null && this.entityTag.equals(that.entityTag) : that.entityTag == null; // Paper + } + return true; + } +diff --git a/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaFirework.java b/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaFirework.java +index 566d893a413fd04b99e83dc2da8fe958a48492a8..a944803771d514572f94b4e98a6d4435a009c078 100644 +--- a/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaFirework.java ++++ b/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaFirework.java +@@ -55,7 +55,7 @@ class CraftMetaFirework extends CraftMetaItem implements FireworkMeta { + + this.power = that.power; + +- if (that.hasEffects()) { ++ if (that.effects != null) { // Paper + this.effects = new ArrayList<>(that.effects); + } + } +@@ -88,7 +88,7 @@ class CraftMetaFirework extends CraftMetaItem implements FireworkMeta { + } + + Iterable effects = SerializableMeta.getObject(Iterable.class, map, CraftMetaFirework.EXPLOSIONS.BUKKIT, true); +- this.safelyAddEffects(effects); ++ this.safelyAddEffects(effects, false); // Paper - limit firework effects + } + + static FireworkEffect getEffect(FireworkExplosion explosion) { +@@ -98,19 +98,14 @@ class CraftMetaFirework extends CraftMetaItem implements FireworkMeta { + .with(CraftMetaFirework.getEffectType(explosion.shape())); + + IntList colors = explosion.colors(); +- // People using buggy command generators specify a list rather than an int here, so recover with dummy data. +- // Wrong: Colors: [1234] +- // Right: Colors: [I;1234] +- if (colors.isEmpty()) { +- effect.withColor(Color.WHITE); +- } ++ // Paper - this is no longer needed + + for (int color : colors) { +- effect.withColor(Color.fromRGB(color)); ++ effect.withColor(Color.fromRGB(color & 0xFFFFFF)); // Paper - try to keep color component consistent with vanilla (top byte is ignored), this will however change the color component for out of bound color + } + + for (int color : explosion.fadeColors()) { +- effect.withFade(Color.fromRGB(color)); ++ effect.withFade(Color.fromRGB(color & 0xFFFFFF)); // Paper + } + + return effect.build(); +@@ -162,7 +157,7 @@ class CraftMetaFirework extends CraftMetaItem implements FireworkMeta { + return !(this.effects == null || this.effects.isEmpty()); + } + +- void safelyAddEffects(Iterable collection) { ++ void safelyAddEffects(Iterable collection, final boolean throwOnOversize) { // Paper + if (collection == null || (collection instanceof Collection && ((Collection) collection).isEmpty())) { + return; + } +@@ -174,6 +169,15 @@ class CraftMetaFirework extends CraftMetaItem implements FireworkMeta { + + for (Object obj : collection) { + Preconditions.checkArgument(obj instanceof FireworkEffect, "%s in %s is not a FireworkEffect", obj, collection); ++ // Paper start - limit firework effects ++ if (effects.size() + 1 > Fireworks.MAX_EXPLOSIONS) { ++ if (throwOnOversize) { ++ throw new IllegalArgumentException("Cannot have more than " + Fireworks.MAX_EXPLOSIONS + " firework effects"); ++ } else { ++ continue; ++ } ++ } ++ // Paper end - limit firework effects + effects.add((FireworkEffect) obj); + } + } +@@ -215,7 +219,7 @@ class CraftMetaFirework extends CraftMetaItem implements FireworkMeta { + } + + boolean isFireworkEmpty() { +- return !(this.hasEffects() || this.hasPower()); ++ return !(this.effects != null || this.hasPower()); // Paper - empty effects list should stay on the item + } + + @Override +@@ -232,7 +236,7 @@ class CraftMetaFirework extends CraftMetaItem implements FireworkMeta { + if (meta instanceof CraftMetaFirework that) { + + return (Objects.equals(this.power, that.power)) +- && (this.hasEffects() ? that.hasEffects() && this.effects.equals(that.effects) : !that.hasEffects()); ++ && (this.effects != null ? that.effects != null && this.effects.equals(that.effects) : that.effects == null); // Paper + } + + return true; +@@ -250,7 +254,7 @@ class CraftMetaFirework extends CraftMetaItem implements FireworkMeta { + if (this.hasPower()) { + hash = 61 * hash + this.power; + } +- if (this.hasEffects()) { ++ if (this.effects != null) { // Paper + hash = 61 * hash + 13 * this.effects.hashCode(); + } + return hash != original ? CraftMetaFirework.class.hashCode() ^ hash : hash; +@@ -260,7 +264,7 @@ class CraftMetaFirework extends CraftMetaItem implements FireworkMeta { + Builder serialize(Builder builder) { + super.serialize(builder); + +- if (this.hasEffects()) { ++ if (this.effects != null) { // Paper + builder.put(CraftMetaFirework.EXPLOSIONS.BUKKIT, ImmutableList.copyOf(this.effects)); + } + +@@ -285,6 +289,7 @@ class CraftMetaFirework extends CraftMetaItem implements FireworkMeta { + @Override + public void addEffect(FireworkEffect effect) { + Preconditions.checkArgument(effect != null, "FireworkEffect cannot be null"); ++ Preconditions.checkArgument(this.effects == null || this.effects.size() + 1 <= Fireworks.MAX_EXPLOSIONS, "cannot have more than %s firework effects", Fireworks.MAX_EXPLOSIONS); // Paper - limit firework effects + if (this.effects == null) { + this.effects = new ArrayList(); + } +@@ -294,6 +299,10 @@ class CraftMetaFirework extends CraftMetaItem implements FireworkMeta { + @Override + public void addEffects(FireworkEffect... effects) { + Preconditions.checkArgument(effects != null, "effects cannot be null"); ++ // Paper start - limit firework effects ++ final int initialSize = this.effects == null ? 0 : this.effects.size(); ++ Preconditions.checkArgument(initialSize + effects.length <= Fireworks.MAX_EXPLOSIONS, "Cannot have more than %s firework effects", Fireworks.MAX_EXPLOSIONS); ++ // Paper end - limit firework effects + if (effects.length == 0) { + return; + } +@@ -312,7 +321,7 @@ class CraftMetaFirework extends CraftMetaItem implements FireworkMeta { + @Override + public void addEffects(Iterable effects) { + Preconditions.checkArgument(effects != null, "effects cannot be null"); +- this.safelyAddEffects(effects); ++ this.safelyAddEffects(effects, true); // Paper - limit firework effects + } + + @Override +diff --git a/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaItem.java b/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaItem.java +index 13b19adc21ece31476b2980c5bc01a50f15df634..a6e2281bfac94f1e19836d9c8415d8270387b16d 100644 +--- a/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaItem.java ++++ b/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaItem.java +@@ -199,9 +199,10 @@ class CraftMetaItem implements ItemMeta, Damageable, Repairable, BlockDataMeta { + } + } + +- static final class Applicator { ++ static abstract class Applicator { // Paper - support updating profile after resolving it + +- private final DataComponentPatch.Builder builder = DataComponentPatch.builder(); ++ final DataComponentPatch.Builder builder = DataComponentPatch.builder(); // Paper - private -> package-private ++ void skullCallback(net.minecraft.world.item.component.ResolvableProfile resolvableProfile) {} // Paper - support updating profile after resolving it + + Applicator put(ItemMetaKeyType key, T value) { + this.builder.set(key.TYPE, value); +@@ -308,7 +309,7 @@ class CraftMetaItem implements ItemMeta, Damageable, Repairable, BlockDataMeta { + private CraftToolComponent tool; + private CraftEquippableComponent equippable; + private CraftJukeboxComponent jukebox; +- private int damage; ++ private Integer damage; // Paper - may not be set + private Integer maxDamage; + + private static final Set HANDLED_TAGS = Sets.newHashSet(); +@@ -341,7 +342,7 @@ class CraftMetaItem implements ItemMeta, Damageable, Repairable, BlockDataMeta { + this.enchantments = new EnchantmentMap(meta.enchantments); // Paper + } + +- if (meta.hasAttributeModifiers()) { ++ if (meta.attributeModifiers != null) { // Paper + this.attributeModifiers = LinkedHashMultimap.create(meta.attributeModifiers); + } + +@@ -390,6 +391,11 @@ class CraftMetaItem implements ItemMeta, Damageable, Repairable, BlockDataMeta { + } + + CraftMetaItem(DataComponentPatch tag, Set> extraHandledTags) { // Paper - improve handled tags on type changes ++ // Paper start - properly support data components in BlockEntity ++ this.updateFromPatch(tag, extraHandledTags); ++ } ++ protected final void updateFromPatch(DataComponentPatch tag, Set> extraHandledTags) { ++ // Paper end - properly support data components in BlockEntity + CraftMetaItem.getOrEmpty(tag, CraftMetaItem.NAME).ifPresent((component) -> { + this.displayName = component; + }); +@@ -906,7 +912,7 @@ class CraftMetaItem implements ItemMeta, Damageable, Repairable, BlockDataMeta { + Map mods = SerializableMeta.getObject(Map.class, map, key.BUKKIT, true); + Multimap result = LinkedHashMultimap.create(); + if (mods == null) { +- return result; ++ return null; // Paper - null is different from an empty map + } + + for (Object obj : mods.keySet()) { +@@ -1037,7 +1043,7 @@ class CraftMetaItem implements ItemMeta, Damageable, Repairable, BlockDataMeta { + itemTag.put(CraftMetaItem.JUKEBOX_PLAYABLE, this.jukebox.getHandle()); + } + +- if (this.hasDamage()) { ++ if (this.hasDamageValue()) { // Paper - preserve empty/0 damage + itemTag.put(CraftMetaItem.DAMAGE, this.damage); + } + +@@ -1087,7 +1093,7 @@ class CraftMetaItem implements ItemMeta, Damageable, Repairable, BlockDataMeta { + } + + void applyEnchantments(Map enchantments, CraftMetaItem.Applicator tag, ItemMetaKeyType key, ItemFlag itemFlag) { +- if (enchantments == null && !this.hasItemFlag(itemFlag)) { ++ if (enchantments == null /*&& !this.hasItemFlag(itemFlag)*/) { // Paper - general item meta fixes - only emit enchantment component if enchantments are defined + return; + } + +@@ -1104,10 +1110,8 @@ class CraftMetaItem implements ItemMeta, Damageable, Repairable, BlockDataMeta { + } + + void applyModifiers(Multimap modifiers, CraftMetaItem.Applicator tag) { +- if (modifiers == null || modifiers.isEmpty()) { +- if (this.hasItemFlag(ItemFlag.HIDE_ATTRIBUTES)) { +- tag.put(CraftMetaItem.ATTRIBUTES, new ItemAttributeModifiers(Collections.emptyList(), false)); +- } ++ if (modifiers == null/* || modifiers.isEmpty()*/) { // Paper - empty modifiers has a specific meaning, they should still be saved ++ // Paper - don't save ItemFlag if the underlying data isn't present + return; + } + +@@ -1144,7 +1148,7 @@ class CraftMetaItem implements ItemMeta, Damageable, Repairable, BlockDataMeta { + + @Overridden + boolean isEmpty() { +- return !(this.hasDisplayName() || this.hasItemName() || this.hasLocalizedName() || this.hasEnchants() || (this.lore != null) || this.hasCustomModelData() || this.hasEnchantable() || this.hasBlockData() || this.hasRepairCost() || !this.unhandledTags.build().isEmpty() || !this.removedTags.isEmpty() || !this.persistentDataContainer.isEmpty() || this.hideFlag != 0 || this.isHideTooltip() || this.hasTooltipStyle() || this.hasItemModel() || this.isUnbreakable() || this.hasEnchantmentGlintOverride() || this.isGlider() || this.hasDamageResistant() || this.hasMaxStackSize() || this.hasRarity() || this.hasUseRemainder() || this.hasUseCooldown() || this.hasFood() || this.hasTool() || this.hasJukeboxPlayable() || this.hasEquippable() || this.hasDamage() || this.hasMaxDamage() || this.hasAttributeModifiers() || this.customTag != null || this.canPlaceOnPredicates != null || this.canBreakPredicates != null); // Paper ++ return !(this.hasDisplayName() || this.hasItemName() || this.hasLocalizedName() || this.hasEnchants() || (this.lore != null) || this.hasCustomModelData() || this.hasEnchantable() || this.hasBlockData() || this.hasRepairCost() || !this.unhandledTags.build().isEmpty() || !this.removedTags.isEmpty() || !this.persistentDataContainer.isEmpty() || this.hideFlag != 0 || this.isHideTooltip() || this.hasTooltipStyle() || this.hasItemModel() || this.isUnbreakable() || this.hasEnchantmentGlintOverride() || this.isGlider() || this.hasDamageResistant() || this.hasMaxStackSize() || this.hasRarity() || this.hasUseRemainder() || this.hasUseCooldown() || this.hasFood() || this.hasTool() || this.hasJukeboxPlayable() || this.hasEquippable() || this.hasDamageValue() || this.hasMaxDamage() || this.hasAttributeModifiers() || this.customTag != null || this.canPlaceOnPredicates != null || this.canBreakPredicates != null); // Paper + } + + // Paper start +@@ -1240,6 +1244,7 @@ class CraftMetaItem implements ItemMeta, Damageable, Repairable, BlockDataMeta { + + @Override + public void lore(final List lore) { ++ Preconditions.checkArgument(lore == null || lore.size() <= ItemLore.MAX_LINES, "lore cannot have more than %s lines", ItemLore.MAX_LINES); // Paper - limit lore lines + this.lore = lore != null ? io.papermc.paper.adventure.PaperAdventure.asVanilla(lore) : null; + } + // Paper end +@@ -1298,7 +1303,7 @@ class CraftMetaItem implements ItemMeta, Damageable, Repairable, BlockDataMeta { + @Override + public void removeEnchantments() { + if (this.hasEnchants()) { +- this.enchantments.clear(); ++ this.enchantments = null; // Paper - Correctly clear enchantments + } + } + +@@ -1364,6 +1369,7 @@ class CraftMetaItem implements ItemMeta, Damageable, Repairable, BlockDataMeta { + // Paper end + @Override + public void setLore(List lore) { ++ Preconditions.checkArgument(lore == null || lore.size() <= ItemLore.MAX_LINES, "lore cannot have more than %s lines", ItemLore.MAX_LINES); // Paper - limit lore lines + if (lore == null || lore.isEmpty()) { + this.lore = null; + } else { +@@ -1379,6 +1385,7 @@ class CraftMetaItem implements ItemMeta, Damageable, Repairable, BlockDataMeta { + // Paper start + @Override + public void setLoreComponents(List lore) { ++ Preconditions.checkArgument(lore == null || lore.size() <= ItemLore.MAX_LINES, "lore cannot have more than %s lines", ItemLore.MAX_LINES); // Paper - limit lore lines + if (lore == null) { + this.lore = null; + } else { +@@ -1596,6 +1603,7 @@ class CraftMetaItem implements ItemMeta, Damageable, Repairable, BlockDataMeta { + + @Override + public void setUseRemainder(ItemStack useRemainder) { ++ Preconditions.checkArgument(useRemainder == null || !useRemainder.isEmpty(), "Item cannot be empty"); // Paper + this.useRemainder = useRemainder; + } + +@@ -1606,7 +1614,7 @@ class CraftMetaItem implements ItemMeta, Damageable, Repairable, BlockDataMeta { + + @Override + public UseCooldownComponent getUseCooldown() { +- return (this.hasUseCooldown()) ? new CraftUseCooldownComponent(this.useCooldown) : new CraftUseCooldownComponent(new UseCooldown(0)); ++ return (this.hasUseCooldown()) ? new CraftUseCooldownComponent(this.useCooldown) : new CraftUseCooldownComponent(new UseCooldown(1.0F)); // Paper - Create a valid use_cooldown component + } + + @Override +@@ -1656,7 +1664,7 @@ class CraftMetaItem implements ItemMeta, Damageable, Repairable, BlockDataMeta { + + @Override + public void setEquippable(EquippableComponent equippable) { +- this.equippable = (equippable == null) ? null : new CraftEquippableComponent((CraftEquippableComponent) this.equippable); ++ this.equippable = (equippable == null) ? null : new CraftEquippableComponent((CraftEquippableComponent) equippable); // Paper + } + + @Override +@@ -1692,7 +1700,7 @@ class CraftMetaItem implements ItemMeta, Damageable, Repairable, BlockDataMeta { + + @Override + public Multimap getAttributeModifiers(@Nullable EquipmentSlot slot) { +- this.checkAttributeList(); ++ if (this.attributeModifiers == null) return LinkedHashMultimap.create(); // Paper - don't change the components + SetMultimap result = LinkedHashMultimap.create(); + for (Map.Entry entry : this.attributeModifiers.entries()) { + if (entry.getValue().getSlot() == null || entry.getValue().getSlot() == slot) { +@@ -1705,6 +1713,7 @@ class CraftMetaItem implements ItemMeta, Damageable, Repairable, BlockDataMeta { + @Override + public Collection getAttributeModifiers(@Nonnull Attribute attribute) { + Preconditions.checkNotNull(attribute, "Attribute cannot be null"); ++ if (this.attributeModifiers == null) return null; // Paper - fix NPE + return this.attributeModifiers.containsKey(attribute) ? ImmutableList.copyOf(this.attributeModifiers.get(attribute)) : null; + } + +@@ -1712,22 +1721,33 @@ class CraftMetaItem implements ItemMeta, Damageable, Repairable, BlockDataMeta { + public boolean addAttributeModifier(@Nonnull Attribute attribute, @Nonnull AttributeModifier modifier) { + Preconditions.checkNotNull(attribute, "Attribute cannot be null"); + Preconditions.checkNotNull(modifier, "AttributeModifier cannot be null"); +- this.checkAttributeList(); ++ if (this.attributeModifiers != null) { // Paper + for (Map.Entry entry : this.attributeModifiers.entries()) { + Preconditions.checkArgument(!(entry.getValue().getKey().equals(modifier.getKey()) && entry.getKey() == attribute), "Cannot register AttributeModifier. Modifier is already applied! %s", modifier); // Paper - attribute modifiers with same namespaced key but on different attributes are fine + } ++ } // Paper ++ this.checkAttributeList(); // Paper - moved down + return this.attributeModifiers.put(attribute, modifier); + } + + @Override + public void setAttributeModifiers(@Nullable Multimap attributeModifiers) { +- if (attributeModifiers == null || attributeModifiers.isEmpty()) { ++ // Paper start - distinguish between null and empty ++ if (attributeModifiers == null) { ++ this.attributeModifiers = null; ++ return; ++ } ++ if (attributeModifiers.isEmpty()) { ++ // Paper end - distinguish between null and empty + this.attributeModifiers = LinkedHashMultimap.create(); + return; + } + +- this.checkAttributeList(); +- this.attributeModifiers.clear(); ++ // Paper start - fix modifiers meta ++ if (this.attributeModifiers != null) { ++ this.attributeModifiers.clear(); ++ } ++ // Paper end + + Iterator> iterator = attributeModifiers.entries().iterator(); + while (iterator.hasNext()) { +@@ -1737,6 +1757,7 @@ class CraftMetaItem implements ItemMeta, Damageable, Repairable, BlockDataMeta { + iterator.remove(); + continue; + } ++ this.checkAttributeList(); // Paper - moved down + this.attributeModifiers.put(next.getKey(), next.getValue()); + } + } +@@ -1744,13 +1765,13 @@ class CraftMetaItem implements ItemMeta, Damageable, Repairable, BlockDataMeta { + @Override + public boolean removeAttributeModifier(@Nonnull Attribute attribute) { + Preconditions.checkNotNull(attribute, "Attribute cannot be null"); +- this.checkAttributeList(); ++ if (this.attributeModifiers == null) return false; // Paper + return !this.attributeModifiers.removeAll(attribute).isEmpty(); + } + + @Override + public boolean removeAttributeModifier(@Nullable EquipmentSlot slot) { +- this.checkAttributeList(); ++ if (this.attributeModifiers == null) return false; // Paper + int removed = 0; + Iterator> iter = this.attributeModifiers.entries().iterator(); + +@@ -1770,7 +1791,7 @@ class CraftMetaItem implements ItemMeta, Damageable, Repairable, BlockDataMeta { + public boolean removeAttributeModifier(@Nonnull Attribute attribute, @Nonnull AttributeModifier modifier) { + Preconditions.checkNotNull(attribute, "Attribute cannot be null"); + Preconditions.checkNotNull(modifier, "AttributeModifier cannot be null"); +- this.checkAttributeList(); ++ if (this.attributeModifiers == null) return false; // Paper + int removed = 0; + Iterator> iter = this.attributeModifiers.entries().iterator(); + +@@ -1792,7 +1813,7 @@ class CraftMetaItem implements ItemMeta, Damageable, Repairable, BlockDataMeta { + + @Override + public String getAsString() { +- CraftMetaItem.Applicator tag = new CraftMetaItem.Applicator(); ++ CraftMetaItem.Applicator tag = new CraftMetaItem.Applicator() {}; // Paper - support updating profile after resolving it + this.applyToItem(tag); + DataComponentPatch patch = tag.build(); + net.minecraft.nbt.Tag nbt = DataComponentPatch.CODEC.encodeStart(MinecraftServer.getDefaultRegistryAccess().createSerializationContext(NbtOps.INSTANCE), patch).getOrThrow(); +@@ -1801,7 +1822,7 @@ class CraftMetaItem implements ItemMeta, Damageable, Repairable, BlockDataMeta { + + @Override + public String getAsComponentString() { +- CraftMetaItem.Applicator tag = new CraftMetaItem.Applicator(); ++ CraftMetaItem.Applicator tag = new CraftMetaItem.Applicator() {}; // Paper + this.applyToItem(tag); + DataComponentPatch patch = tag.build(); + +@@ -1841,6 +1862,7 @@ class CraftMetaItem implements ItemMeta, Damageable, Repairable, BlockDataMeta { + if (first == null || second == null) { + return false; + } ++ if (first.isEmpty() && second.isEmpty()) return true; // Paper - empty modifiers are equivalent + for (Map.Entry entry : first.entries()) { + if (!second.containsEntry(entry.getKey(), entry.getValue())) { + return false; +@@ -1856,19 +1878,33 @@ class CraftMetaItem implements ItemMeta, Damageable, Repairable, BlockDataMeta { + + @Override + public boolean hasDamage() { +- return this.damage > 0; ++ return this.damage != null && this.damage > 0; // Paper - null check + } + + @Override + public int getDamage() { +- return this.damage; ++ return this.damage == null ? 0 : this.damage; // Paper - null check + } + + @Override + public void setDamage(int damage) { ++ Preconditions.checkArgument(damage >= 0, "Damage cannot be negative"); // Paper ++ Preconditions.checkArgument(!this.hasMaxDamage() || damage <= this.maxDamage, "Damage cannot exceed max damage"); // Paper + this.damage = damage; + } + ++ // Paper start - preserve empty/0 damage ++ @Override ++ public boolean hasDamageValue() { ++ return this.damage != null; ++ } ++ ++ @Override ++ public void resetDamage() { ++ this.damage = null; ++ } ++ // Paper end - preserve empty/0 damage ++ + @Override + public boolean hasMaxDamage() { + return this.maxDamage != null; +@@ -1882,6 +1918,7 @@ class CraftMetaItem implements ItemMeta, Damageable, Repairable, BlockDataMeta { + + @Override + public void setMaxDamage(Integer maxDamage) { ++ Preconditions.checkArgument(maxDamage == null || maxDamage > 0, "Max damage should be positive"); // Paper + this.maxDamage = maxDamage; + } + +@@ -1914,7 +1951,7 @@ class CraftMetaItem implements ItemMeta, Damageable, Repairable, BlockDataMeta { + && (this.hasEnchantable() ? that.hasEnchantable() && this.enchantableValue.equals(that.enchantableValue) : !that.hasEnchantable()) + && (this.hasBlockData() ? that.hasBlockData() && this.blockData.equals(that.blockData) : !that.hasBlockData()) + && (this.hasRepairCost() ? that.hasRepairCost() && this.repairCost == that.repairCost : !that.hasRepairCost()) +- && (this.hasAttributeModifiers() ? that.hasAttributeModifiers() && CraftMetaItem.compareModifiers(this.attributeModifiers, that.attributeModifiers) : !that.hasAttributeModifiers()) ++ && (this.attributeModifiers != null ? that.attributeModifiers != null && CraftMetaItem.compareModifiers(this.attributeModifiers, that.attributeModifiers) : that.attributeModifiers == null) // Paper - track only null modifiers + && (this.unhandledTags.equals(that.unhandledTags)) + && (this.removedTags.equals(that.removedTags)) + && (Objects.equals(this.customTag, that.customTag)) +@@ -1935,7 +1972,7 @@ class CraftMetaItem implements ItemMeta, Damageable, Repairable, BlockDataMeta { + && (this.hasTool() ? that.hasTool() && this.tool.equals(that.tool) : !that.hasTool()) + && (this.hasEquippable() ? that.hasEquippable() && this.equippable.equals(that.equippable) : !that.hasEquippable()) + && (this.hasJukeboxPlayable() ? that.hasJukeboxPlayable() && this.jukebox.equals(that.jukebox) : !that.hasJukeboxPlayable()) +- && (this.hasDamage() ? that.hasDamage() && this.damage == that.damage : !that.hasDamage()) ++ && (Objects.equals(this.damage, that.damage)) // Paper - preserve empty/0 damage + && (this.hasMaxDamage() ? that.hasMaxDamage() && this.maxDamage.equals(that.maxDamage) : !that.hasMaxDamage()) + && (this.canPlaceOnPredicates != null ? that.canPlaceOnPredicates != null && this.canPlaceOnPredicates.equals(that.canPlaceOnPredicates) : that.canPlaceOnPredicates == null) // Paper + && (this.canBreakPredicates != null ? that.canBreakPredicates != null && this.canBreakPredicates.equals(that.canBreakPredicates) : that.canBreakPredicates == null) // Paper +@@ -1988,9 +2025,9 @@ class CraftMetaItem implements ItemMeta, Damageable, Repairable, BlockDataMeta { + hash = 61 * hash + (this.hasTool() ? this.tool.hashCode() : 0); + hash = 61 * hash + (this.hasJukeboxPlayable() ? this.jukebox.hashCode() : 0); + hash = 61 * hash + (this.hasEquippable() ? this.equippable.hashCode() : 0); +- hash = 61 * hash + (this.hasDamage() ? this.damage : 0); +- hash = 61 * hash + (this.hasMaxDamage() ? 1231 : 1237); +- hash = 61 * hash + (this.hasAttributeModifiers() ? this.attributeModifiers.hashCode() : 0); ++ hash = 61 * hash + (this.hasDamageValue() ? this.damage : -1); // Paper - preserve empty/0 damage ++ hash = 61 * hash + (this.hasMaxDamage() ? this.maxDamage.hashCode() : 0); // Paper - max damage is not a boolean ++ hash = 61 * hash + (this.attributeModifiers != null ? this.attributeModifiers.hashCode() : 0); // Paper - track only null attributes + hash = 61 * hash + (this.canPlaceOnPredicates != null ? this.canPlaceOnPredicates.hashCode() : 0); // Paper + hash = 61 * hash + (this.canBreakPredicates != null ? this.canBreakPredicates.hashCode() : 0); // Paper + hash = 61 * hash + this.version; +@@ -2011,7 +2048,7 @@ class CraftMetaItem implements ItemMeta, Damageable, Repairable, BlockDataMeta { + if (this.enchantments != null) { + clone.enchantments = new EnchantmentMap(this.enchantments); // Paper + } +- if (this.hasAttributeModifiers()) { ++ if (this.attributeModifiers != null) { // Paper + clone.attributeModifiers = LinkedHashMultimap.create(this.attributeModifiers); + } + if (this.customTag != null) { +@@ -2178,7 +2215,7 @@ class CraftMetaItem implements ItemMeta, Damageable, Repairable, BlockDataMeta { + builder.put(CraftMetaItem.JUKEBOX_PLAYABLE.BUKKIT, this.jukebox); + } + +- if (this.hasDamage()) { ++ if (this.hasDamageValue()) { // Paper - preserve empty/0 damage + builder.put(CraftMetaItem.DAMAGE.BUKKIT, this.damage); + } + +@@ -2279,7 +2316,7 @@ class CraftMetaItem implements ItemMeta, Damageable, Repairable, BlockDataMeta { + } + + static void serializeModifiers(Multimap modifiers, ImmutableMap.Builder builder, ItemMetaKey key) { +- if (modifiers == null || modifiers.isEmpty()) { ++ if (modifiers == null/* || modifiers.isEmpty()*/) { // Paper - null and an empty map have different behaviors + return; + } + +@@ -2361,7 +2398,7 @@ class CraftMetaItem implements ItemMeta, Damageable, Repairable, BlockDataMeta { + // Paper start - improve checking handled tags + @org.jetbrains.annotations.VisibleForTesting + public static final Map, Set>> HANDLED_DCTS_PER_TYPE = new HashMap<>(); +- private static final Set> DEFAULT_HANDLED_DCTS = Set.of( ++ protected static final Set> DEFAULT_HANDLED_DCTS = Set.of( + CraftMetaItem.NAME.TYPE, + CraftMetaItem.ITEM_NAME.TYPE, + CraftMetaItem.LORE.TYPE, +@@ -2437,7 +2474,12 @@ class CraftMetaItem implements ItemMeta, Damageable, Repairable, BlockDataMeta { + // Paper end - improve checking handled data component types + + protected static Optional getOrEmpty(DataComponentPatch tag, ItemMetaKeyType type) { +- Optional result = tag.get(type.TYPE); ++ // Paper start ++ return getOrEmpty(tag, type.TYPE); ++ } ++ protected static Optional getOrEmpty(final DataComponentPatch tag, final DataComponentType type) { ++ Optional result = tag.get(type); ++ // Paper end + + return (result != null) ? result : Optional.empty(); + } +diff --git a/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaLeatherArmor.java b/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaLeatherArmor.java +index e76749bffaf5a0bbd3a8d35f882edcc3598351b9..49889026661fb2a558e14569324016d637de27a0 100644 +--- a/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaLeatherArmor.java ++++ b/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaLeatherArmor.java +@@ -18,16 +18,30 @@ class CraftMetaLeatherArmor extends CraftMetaItem implements LeatherArmorMeta { + + static final ItemMetaKeyType COLOR = new ItemMetaKeyType<>(DataComponents.DYED_COLOR, "color"); + +- private Color color = DEFAULT_LEATHER_COLOR; ++ private Integer color; // Paper - keep color component consistent with vanilla (top byte is ignored) + + CraftMetaLeatherArmor(CraftMetaItem meta) { + super(meta); +- CraftMetaLeatherArmor.readColor(this, meta); ++ // Paper start ++ if (!(meta instanceof CraftMetaLeatherArmor leatherMeta)) { ++ return; ++ } ++ ++ this.color = leatherMeta.color; ++ // Paper end + } + + CraftMetaLeatherArmor(DataComponentPatch tag, java.util.Set> extraHandledDcts) { // Paper + super(tag, extraHandledDcts); // Paper +- CraftMetaLeatherArmor.readColor(this, tag); ++ // Paper start ++ getOrEmpty(tag, CraftMetaLeatherArmor.COLOR).ifPresent((dyedItemColor) -> { ++ if (!dyedItemColor.showInTooltip()) { ++ this.addItemFlags(ItemFlag.HIDE_DYE); ++ } ++ ++ this.color = dyedItemColor.rgb(); ++ }); ++ // Paper end + } + + CraftMetaLeatherArmor(Map map) { +@@ -38,7 +52,11 @@ class CraftMetaLeatherArmor extends CraftMetaItem implements LeatherArmorMeta { + @Override + void applyToItem(CraftMetaItem.Applicator itemTag) { + super.applyToItem(itemTag); +- CraftMetaLeatherArmor.applyColor(this, itemTag); ++ // Paper start ++ if (this.hasColor()) { ++ itemTag.put(CraftMetaLeatherArmor.COLOR, new DyedItemColor(this.color, !this.hasItemFlag(ItemFlag.HIDE_DYE))); ++ } ++ // Paper end + } + + @Override +@@ -66,16 +84,16 @@ class CraftMetaLeatherArmor extends CraftMetaItem implements LeatherArmorMeta { + + @Override + public Color getColor() { +- return this.color; ++ return this.color == null ? DEFAULT_LEATHER_COLOR : Color.fromRGB(this.color & 0xFFFFFF); // Paper + } + + @Override + public void setColor(Color color) { +- this.color = color == null ? DEFAULT_LEATHER_COLOR : color; ++ this.color = color == null ? null : color.asRGB(); // Paper + } + + boolean hasColor() { +- return CraftMetaLeatherArmor.hasColor(this); ++ return this.color != null; // Paper + } + + @Override +@@ -95,7 +113,7 @@ class CraftMetaLeatherArmor extends CraftMetaItem implements LeatherArmorMeta { + if (meta instanceof CraftMetaLeatherArmor) { + CraftMetaLeatherArmor that = (CraftMetaLeatherArmor) meta; + +- return this.color.equals(that.color); ++ return this.hasColor() ? that.hasColor() && this.color.equals(that.color) : !that.hasColor(); // Paper - allow null + } + return true; + } +@@ -115,14 +133,16 @@ class CraftMetaLeatherArmor extends CraftMetaItem implements LeatherArmorMeta { + return original != hash ? CraftMetaLeatherArmor.class.hashCode() ^ hash : hash; + } + ++ @io.papermc.paper.annotation.DoNotUse // Paper + static void readColor(LeatherArmorMeta meta, CraftMetaItem other) { + if (!(other instanceof CraftMetaLeatherArmor armorMeta)) { + return; + } + +- meta.setColor(armorMeta.color); ++ // meta.setColor(armorMeta.color); // Paper - commented out, color is now an integer and cannot be passed to setColor + } + ++ @io.papermc.paper.annotation.DoNotUse // Paper + static void readColor(LeatherArmorMeta meta, DataComponentPatch tag) { + getOrEmpty(tag, CraftMetaLeatherArmor.COLOR).ifPresent((dyedItemColor) -> { + if (!dyedItemColor.showInTooltip()) { +@@ -145,6 +165,7 @@ class CraftMetaLeatherArmor extends CraftMetaItem implements LeatherArmorMeta { + return !DEFAULT_LEATHER_COLOR.equals(meta.getColor()); + } + ++ @io.papermc.paper.annotation.DoNotUse // Paper + static void applyColor(LeatherArmorMeta meta, CraftMetaItem.Applicator tag) { + if (CraftMetaLeatherArmor.hasColor(meta)) { + tag.put(CraftMetaLeatherArmor.COLOR, new DyedItemColor(meta.getColor().asRGB(), !meta.hasItemFlag(ItemFlag.HIDE_DYE))); +diff --git a/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaMap.java b/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaMap.java +index 149356981e586e4f67d4543d3df94a2ea99333fc..06c72ed83dab9492b70bfd13f7f9c1a4cbef9e2f 100644 +--- a/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaMap.java ++++ b/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaMap.java +@@ -29,7 +29,7 @@ class CraftMetaMap extends CraftMetaItem implements MapMeta { + + private Integer mapId; + private byte scaling = CraftMetaMap.SCALING_EMPTY; +- private Color color; ++ private Integer color; // Paper - keep color component consistent with vanilla (top byte is ignored) + + CraftMetaMap(CraftMetaItem meta) { + super(meta); +@@ -57,7 +57,7 @@ class CraftMetaMap extends CraftMetaItem implements MapMeta { + + getOrEmpty(tag, CraftMetaMap.MAP_COLOR).ifPresent((mapColor) -> { + try { +- this.color = Color.fromRGB(mapColor.rgb()); ++ this.color = mapColor.rgb(); // Paper + } catch (IllegalArgumentException ex) { + // Invalid colour + } +@@ -101,7 +101,7 @@ class CraftMetaMap extends CraftMetaItem implements MapMeta { + } + + if (this.hasColor()) { +- tag.put(CraftMetaMap.MAP_COLOR, new MapItemColor(this.color.asRGB())); ++ tag.put(CraftMetaMap.MAP_COLOR, new MapItemColor(this.color)); // Paper + } + } + +@@ -121,6 +121,7 @@ class CraftMetaMap extends CraftMetaItem implements MapMeta { + + @Override + public int getMapId() { ++ Preconditions.checkState(this.hasMapId(), "Item does not have map associated - check hasMapId() first!"); // Paper - fix NPE + return this.mapId; + } + +@@ -181,12 +182,12 @@ class CraftMetaMap extends CraftMetaItem implements MapMeta { + + @Override + public Color getColor() { +- return this.color; ++ return this.color == null ? null : Color.fromRGB(this.color & 0xFFFFFF); // Paper + } + + @Override + public void setColor(Color color) { +- this.color = color; ++ this.color = color == null ? null : color.asRGB(); // Paper + } + + @Override +diff --git a/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaOminousBottle.java b/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaOminousBottle.java +index b118d8ecb505d187c02bb158f14df333f487a87f..fa1d1a7f37aadf2750f03a0e215fb25fa948f9aa 100644 +--- a/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaOminousBottle.java ++++ b/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaOminousBottle.java +@@ -70,6 +70,7 @@ public class CraftMetaOminousBottle extends CraftMetaItem implements OminousBott + + @Override + public int getAmplifier() { ++ Preconditions.checkState(this.hasAmplifier(), "'ominous_bottle_amplifier' data component is absent. Check hasAmplifier first!"); // Paper - fix NPE + return this.ominousBottleAmplifier; + } + +diff --git a/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaPotion.java b/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaPotion.java +index 6f0eebcaffa20337cf5a7f8485f891c690d948ae..18dc2e83dab0821e5129bd68361de189363823b3 100644 +--- a/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaPotion.java ++++ b/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaPotion.java +@@ -38,7 +38,7 @@ class CraftMetaPotion extends CraftMetaItem implements PotionMeta { + + private PotionType type; + private List customEffects; +- private Color color; ++ private Integer color; // Paper - keep color component consistent with vanilla (top byte is ignored) + private String customName; + + CraftMetaPotion(CraftMetaItem meta) { +@@ -63,7 +63,7 @@ class CraftMetaPotion extends CraftMetaItem implements PotionMeta { + + potionContents.customColor().ifPresent((customColor) -> { + try { +- this.color = Color.fromRGB(customColor); ++ this.color = customColor; // Paper + } catch (IllegalArgumentException ex) { + // Invalid colour + } +@@ -132,7 +132,7 @@ class CraftMetaPotion extends CraftMetaItem implements PotionMeta { + } + + Optional> defaultPotion = (this.hasBasePotionType()) ? Optional.of(CraftPotionType.bukkitToMinecraftHolder(this.type)) : Optional.empty(); +- Optional potionColor = (this.hasColor()) ? Optional.of(this.color.asRGB()) : Optional.empty(); ++ Optional potionColor = (this.hasColor()) ? Optional.of(this.color) : Optional.empty(); // Paper + Optional customName = Optional.ofNullable(this.customName); + + List effectList = new ArrayList<>(); +@@ -297,12 +297,12 @@ class CraftMetaPotion extends CraftMetaItem implements PotionMeta { + + @Override + public Color getColor() { +- return this.color; ++ return this.color == null ? null : Color.fromRGB(this.color & 0xFFFFFF); // Paper + } + + @Override + public void setColor(Color color) { +- this.color = color; ++ this.color = color == null ? null : color.asRGB(); // Paper + } + + @Override +diff --git a/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaShield.java b/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaShield.java +index 967d8940aec0065bce496d5d7a8c73de5733bd2c..e229ca6acb6dbc3185f326f6653b3d66d835a9e5 100644 +--- a/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaShield.java ++++ b/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaShield.java +@@ -28,17 +28,29 @@ public class CraftMetaShield extends CraftMetaItem implements ShieldMeta, BlockS + + static final ItemMetaKeyType BASE_COLOR = new ItemMetaKeyType<>(DataComponents.BASE_COLOR, "Base", "base-color"); + +- private Banner banner; ++ // Paper start - general item meta fixes - decoupled base colour and patterns ++ private @org.jetbrains.annotations.Nullable List patterns; ++ private @org.jetbrains.annotations.Nullable DyeColor baseColor; ++ ++ // An empty pattern list is the same as the default on the Shield item, and will hence not be present in the data components of the stack. ++ private boolean hasPatterns() { ++ return this.patterns != null && !this.patterns.isEmpty(); ++ } ++ // Paper end - general item meta fixes - decoupled base colour and patterns + + CraftMetaShield(CraftMetaItem meta) { + super(meta); + + if (meta instanceof CraftMetaShield craftMetaShield) { +- if (craftMetaShield.banner != null) { +- this.banner = (Banner) craftMetaShield.banner.copy(); +- } ++ // Paper start - general item meta fixes - decoupled base colour and patterns ++ if (craftMetaShield.patterns != null) this.patterns = new ArrayList<>(craftMetaShield.getPatterns()); ++ if (craftMetaShield.baseColor != null) this.baseColor = craftMetaShield.baseColor; ++ // Paper end - general item meta fixes - decoupled base colour and patterns + } else if (meta instanceof CraftMetaBlockState state && state.hasBlockState() && state.getBlockState() instanceof Banner banner) { +- this.banner = (Banner) banner.copy(); ++ // Paper start - general item meta fixes - decoupled base colour and patterns ++ this.patterns = banner.getPatterns(); ++ this.baseColor = banner.getBaseColor(); ++ // Paper end - general item meta fixes - decoupled base colour and patterns + } + } + +@@ -46,7 +58,7 @@ public class CraftMetaShield extends CraftMetaItem implements ShieldMeta, BlockS + super(tag, extraHandledDcts); // Paper - improve checking handled tags in item meta + + getOrEmpty(tag, CraftMetaShield.BASE_COLOR).ifPresent((color) -> { +- this.banner = CraftMetaShield.getBlockState(DyeColor.getByWoolData((byte) color.getId())); ++ this.baseColor = DyeColor.getByWoolData((byte) color.getId()); // Paper - general item meta fixes - decoupled base colour and patterns + }); + + getOrEmpty(tag, CraftMetaBanner.PATTERNS).ifPresent((entityTag) -> { +@@ -68,7 +80,7 @@ public class CraftMetaShield extends CraftMetaItem implements ShieldMeta, BlockS + + String baseColor = SerializableMeta.getString(map, CraftMetaShield.BASE_COLOR.BUKKIT, true); + if (baseColor != null) { +- this.banner = CraftMetaShield.getBlockState(DyeColor.valueOf(baseColor)); ++ this.baseColor = DyeColor.valueOf(baseColor); // Paper - general item meta fixes - decoupled base colour and patterns + } + + Iterable rawPatternList = SerializableMeta.getObject(Iterable.class, map, CraftMetaBanner.PATTERNS.BUKKIT, true); +@@ -86,13 +98,14 @@ public class CraftMetaShield extends CraftMetaItem implements ShieldMeta, BlockS + void applyToItem(CraftMetaItem.Applicator tag) { + super.applyToItem(tag); + +- if (this.banner != null) { +- tag.put(CraftMetaShield.BASE_COLOR, net.minecraft.world.item.DyeColor.byId(this.banner.getBaseColor().getWoolData())); +- +- if (this.banner.numberOfPatterns() > 0) { ++ // Paper start - general item meta fixes - decoupled base colour and patterns ++ if (this.baseColor != null) tag.put(CraftMetaShield.BASE_COLOR, net.minecraft.world.item.DyeColor.byId(this.baseColor.getWoolData())); ++ if (this.patterns != null && !this.patterns.isEmpty()) { ++ { ++ // Paper end - general item meta fixes - decoupled base colour and patterns + List newPatterns = new ArrayList<>(); + +- for (Pattern p : this.banner.getPatterns()) { ++ for (Pattern p : this.patterns) { // Paper - general item meta fixes - decoupled base colour and patterns + newPatterns.add(new BannerPatternLayers.Layer(CraftPatternType.bukkitToMinecraftHolder(p.getPattern()), net.minecraft.world.item.DyeColor.byId(p.getColor().getWoolData()))); + } + +@@ -103,108 +116,84 @@ public class CraftMetaShield extends CraftMetaItem implements ShieldMeta, BlockS + + @Override + public List getPatterns() { +- if (this.banner == null) { ++ if (this.patterns == null) { // Paper - general item meta fixes - decoupled base colour and patterns + return new ArrayList<>(); + } + +- return this.banner.getPatterns(); ++ return new ArrayList<>(this.patterns); // Paper - general item meta fixes - decoupled base colour and patterns + } + + @Override + public void setPatterns(List patterns) { +- if (this.banner == null) { +- if (patterns.isEmpty()) { +- return; +- } +- +- this.banner = CraftMetaShield.getBlockState(null); +- } +- +- this.banner.setPatterns(patterns); ++ this.patterns = new ArrayList<>(patterns); // Paper - general item meta fixes - decoupled base colour and patterns + } + + @Override + public void addPattern(Pattern pattern) { +- if (this.banner == null) { +- this.banner = CraftMetaShield.getBlockState(null); +- } +- +- this.banner.addPattern(pattern); ++ // Paper start - general item meta fixes - decoupled base colour and patterns ++ if (this.patterns == null) this.patterns = new ArrayList<>(); ++ this.patterns.add(pattern); ++ // Paper end - general item meta fixes - decoupled base colour and patterns + } + + @Override + public Pattern getPattern(int i) { +- if (this.banner == null) { ++ if (this.patterns == null) { // Paper - general item meta fixes - decoupled base colour and patterns + throw new IndexOutOfBoundsException(i); + } + +- return this.banner.getPattern(i); ++ return this.patterns.get(i); // Paper - general item meta fixes - decoupled base colour and patterns + } + + @Override + public Pattern removePattern(int i) { +- if (this.banner == null) { ++ if (this.patterns == null) { // Paper - general item meta fixes - decoupled base colour and patterns + throw new IndexOutOfBoundsException(i); + } + +- return this.banner.removePattern(i); ++ return this.patterns.remove(i); // Paper - general item meta fixes - decoupled base colour and patterns + } + + @Override + public void setPattern(int i, Pattern pattern) { +- if (this.banner == null) { ++ if (this.patterns == null) { // Paper - general item meta fixes - decoupled base colour and patterns + throw new IndexOutOfBoundsException(i); + } + +- this.banner.setPattern(i, pattern); ++ this.patterns.set(i, pattern); // Paper - general item meta fixes - decoupled base colour and patterns + } + + @Override + public int numberOfPatterns() { +- if (this.banner == null) { ++ if (this.patterns == null) { // Paper - general item meta fixes - decoupled base colour and patterns + return 0; + } + +- return this.banner.numberOfPatterns(); ++ return this.patterns.size(); // Paper - general item meta fixes - decoupled base colour and patterns + } + + @Override + public DyeColor getBaseColor() { +- if (this.banner == null) { +- return null; +- } +- +- return this.banner.getBaseColor(); ++ return this.baseColor; // Paper - general item meta fixes - decoupled base colour and patterns + } + + @Override + public void setBaseColor(DyeColor baseColor) { +- if (baseColor == null) { +- if (this.banner.numberOfPatterns() > 0) { +- this.banner.setBaseColor(DyeColor.WHITE); +- } else { +- this.banner = null; +- } +- } else { +- if (this.banner == null) { +- this.banner = CraftMetaShield.getBlockState(baseColor); +- } +- +- this.banner.setBaseColor(baseColor); +- } ++ this.baseColor = baseColor; // Paper - general item meta fixes - decoupled base colour and patterns + } + + @Override + ImmutableMap.Builder serialize(ImmutableMap.Builder builder) { + super.serialize(builder); + +- if (this.banner != null) { +- builder.put(CraftMetaShield.BASE_COLOR.BUKKIT, this.banner.getBaseColor().toString()); +- +- if (this.banner.numberOfPatterns() > 0) { +- builder.put(CraftMetaBanner.PATTERNS.BUKKIT, this.banner.getPatterns()); +- } ++ // Paper start - general item meta fixes - decoupled base colour and patterns ++ if (this.baseColor != null) { ++ builder.put(CraftMetaShield.BASE_COLOR.BUKKIT, this.baseColor.toString()); ++ } ++ if (hasPatterns()) { ++ builder.put(CraftMetaBanner.PATTERNS.BUKKIT, this.patterns); + } ++ // Paper end - general item meta fixes - decoupled base colour and patterns + + return builder; + } +@@ -213,8 +202,13 @@ public class CraftMetaShield extends CraftMetaItem implements ShieldMeta, BlockS + int applyHash() { + final int original; + int hash = original = super.applyHash(); +- if (this.banner != null) { +- hash = 61 * hash + this.banner.hashCode(); ++ // Paper start - general item meta fixes - decoupled base colour and patterns ++ if (this.baseColor != null) { ++ hash = 61 * hash + this.baseColor.hashCode(); ++ } ++ if (hasPatterns()) { ++ hash = 61 * hash + this.patterns.hashCode(); ++ // Paper end - general item meta fixes - decoupled base colour and patterns + } + return original != hash ? CraftMetaShield.class.hashCode() ^ hash : hash; + } +@@ -225,29 +219,33 @@ public class CraftMetaShield extends CraftMetaItem implements ShieldMeta, BlockS + return false; + } + if (meta instanceof CraftMetaShield that) { +- return Objects.equal(this.banner, that.banner); ++ return Objects.equal(this.baseColor, that.baseColor) && Objects.equal(this.patterns, that.patterns); // Paper - general item meta fixes - decoupled base colour and patterns + } + return true; + } + + @Override + boolean notUncommon(CraftMetaItem meta) { +- return super.notUncommon(meta) && (meta instanceof CraftMetaShield || this.banner == null); ++ return super.notUncommon(meta) && (meta instanceof CraftMetaShield || (this.baseColor == null && !hasPatterns())); // Paper - general item meta fixes - decoupled base colour and patterns + } + + @Override + boolean isEmpty() { +- return super.isEmpty() && this.banner == null; ++ return super.isEmpty() && this.baseColor == null && !hasPatterns(); // Paper - general item meta fixes - decoupled base colour and patterns + } + + @Override + public boolean hasBlockState() { +- return this.banner != null; ++ return this.baseColor != null || hasPatterns(); // Paper - general item meta fixes - decoupled base colour and patterns + } + + @Override + public BlockState getBlockState() { +- return (this.banner != null) ? this.banner.copy() : CraftMetaShield.getBlockState(null); ++ // Paper start - general item meta fixes - decoupled base colour and patterns ++ final Banner banner = CraftMetaShield.getBlockState(this.baseColor); ++ if (this.patterns != null) banner.setPatterns(this.patterns); ++ return banner; ++ // Paper end - general item meta fixes - decoupled base colour and patterns + } + + @Override +@@ -255,13 +253,18 @@ public class CraftMetaShield extends CraftMetaItem implements ShieldMeta, BlockS + Preconditions.checkArgument(blockState != null, "blockState must not be null"); + Preconditions.checkArgument(blockState instanceof Banner, "Invalid blockState"); + +- this.banner = (Banner) blockState; ++ // Paper start - general item meta fixes - decoupled base colour and patterns ++ final Banner banner = (Banner) blockState; ++ this.baseColor = banner.getBaseColor(); ++ this.patterns = banner.getPatterns(); ++ // Paper end - general item meta fixes - decoupled base colour and patterns + } + + // Paper start - add method to clear block state + @Override + public void clearBlockState() { +- this.banner = null; ++ this.baseColor = null; ++ this.patterns = null; + } + // Paper end - add method to clear block state + +@@ -275,9 +278,10 @@ public class CraftMetaShield extends CraftMetaItem implements ShieldMeta, BlockS + @Override + public CraftMetaShield clone() { + CraftMetaShield meta = (CraftMetaShield) super.clone(); +- if (this.banner != null) { +- meta.banner = (Banner) this.banner.copy(); +- } ++ // Paper start - general item meta fixes - decoupled base colour and patterns ++ meta.baseColor = this.baseColor; ++ meta.patterns = this.patterns == null ? null : new ArrayList<>(this.patterns); ++ // Paper start - general item meta fixes - decoupled base colour and patterns + return meta; + } + +diff --git a/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaSkull.java b/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaSkull.java +index 302906467b12189e21633369c005736863f46dd5..5a4e5b8736ad2e2084e757e9d2034044e177d8f0 100644 +--- a/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaSkull.java ++++ b/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaSkull.java +@@ -112,10 +112,10 @@ class CraftMetaSkull extends CraftMetaItem implements SkullMeta { + // Fill in textures + PlayerProfile ownerProfile = new CraftPlayerProfile(this.profile); // getOwnerProfile may return null + if (ownerProfile.getTextures().isEmpty()) { +- ownerProfile.update().thenAccept((filledProfile) -> { ++ ownerProfile.update().thenAcceptAsync((filledProfile) -> { // Paper - run on main thread + this.setOwnerProfile(filledProfile); +- tag.put(CraftMetaSkull.SKULL_PROFILE, this.profile); +- }); ++ tag.skullCallback(this.profile); // Paper - actually set profile on itemstack ++ }, ((org.bukkit.craftbukkit.CraftServer) org.bukkit.Bukkit.getServer()).getServer()); // Paper - run on main thread + } + } + +diff --git a/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaSpawnEgg.java b/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaSpawnEgg.java +index 8ddf091b3ff1262b6c97e8fe72e0a80db5e1037d..be92ccda66f514459773916cc16b6c230e863444 100644 +--- a/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaSpawnEgg.java ++++ b/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaSpawnEgg.java +@@ -121,6 +121,7 @@ public class CraftMetaSpawnEgg extends CraftMetaItem implements SpawnEggMeta { + + @Override + public EntitySnapshot getSpawnedEntity() { ++ if (this.entityTag == null) return null; // Paper - fix NPE + return CraftEntitySnapshot.create(this.entityTag); + } + +@@ -138,7 +139,7 @@ public class CraftMetaSpawnEgg extends CraftMetaItem implements SpawnEggMeta { + if (meta instanceof CraftMetaSpawnEgg) { + CraftMetaSpawnEgg that = (CraftMetaSpawnEgg) meta; + +- return this.entityTag != null ? that.entityTag != null && this.entityTag.equals(that.entityTag) : this.entityTag == null; ++ return this.entityTag != null ? that.entityTag != null && this.entityTag.equals(that.entityTag) : that.entityTag == null; // Paper + } + return true; + } +diff --git a/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaTropicalFishBucket.java b/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaTropicalFishBucket.java +index 17705059b81942e4df43a4a5180092e09c985ade..80e6b85a107d5236edba99540cb5074e2dc1485a 100644 +--- a/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaTropicalFishBucket.java ++++ b/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaTropicalFishBucket.java +@@ -120,6 +120,7 @@ class CraftMetaTropicalFishBucket extends CraftMetaItem implements TropicalFishB + + @Override + public DyeColor getPatternColor() { ++ com.google.common.base.Preconditions.checkState(this.hasVariant(), "This bucket doesn't have variant, check hasVariant first!"); // Paper - fix NPE + return CraftTropicalFish.getPatternColor(this.variant); + } + +@@ -133,6 +134,7 @@ class CraftMetaTropicalFishBucket extends CraftMetaItem implements TropicalFishB + + @Override + public DyeColor getBodyColor() { ++ com.google.common.base.Preconditions.checkState(this.hasVariant(), "This bucket doesn't have variant, check hasVariant first!"); // Paper - fix NPE + return CraftTropicalFish.getBodyColor(this.variant); + } + +@@ -146,6 +148,7 @@ class CraftMetaTropicalFishBucket extends CraftMetaItem implements TropicalFishB + + @Override + public TropicalFish.Pattern getPattern() { ++ com.google.common.base.Preconditions.checkState(this.hasVariant(), "This bucket doesn't have variant, check hasVariant first!"); // Paper - fix NPE + return CraftTropicalFish.getPattern(this.variant); + } + +diff --git a/src/main/java/org/bukkit/craftbukkit/inventory/SerializableMeta.java b/src/main/java/org/bukkit/craftbukkit/inventory/SerializableMeta.java +index e00b757d6059715e8697428008fcb3e6e7abbe2e..dcf02bd0f7f4c67f5ab98003cc932b960704eef1 100644 +--- a/src/main/java/org/bukkit/craftbukkit/inventory/SerializableMeta.java ++++ b/src/main/java/org/bukkit/craftbukkit/inventory/SerializableMeta.java +@@ -136,4 +136,21 @@ public final class SerializableMeta implements ConfigurationSerializable { + } + throw new IllegalArgumentException(field + "(" + object + ") is not a valid " + clazz); + } ++ ++ // Paper start - General ItemMeta Fixes ++ public static java.util.Optional getObjectOptionally(Class clazz, Map map, Object field, boolean nullable) { ++ final Object object = map.get(field); ++ ++ if (clazz.isInstance(object)) { ++ return java.util.Optional.of(clazz.cast(object)); ++ } ++ if (object == null) { ++ if (!nullable) { ++ throw new NoSuchElementException(map + " does not contain " + field); ++ } ++ return java.util.Optional.empty(); ++ } ++ throw new IllegalArgumentException(field + "(" + object + ") is not a valid " + clazz); ++ } ++ // Paper end - General ItemMeta Fixes + } +diff --git a/src/main/java/org/bukkit/craftbukkit/inventory/components/CraftEquippableComponent.java b/src/main/java/org/bukkit/craftbukkit/inventory/components/CraftEquippableComponent.java +index 1f1589bcdb9e3abde7d25eac65326df1e7ed75ab..9b4c2502e45af4e7a58ff352f0f99c34d233f755 100644 +--- a/src/main/java/org/bukkit/craftbukkit/inventory/components/CraftEquippableComponent.java ++++ b/src/main/java/org/bukkit/craftbukkit/inventory/components/CraftEquippableComponent.java +@@ -170,7 +170,7 @@ public final class CraftEquippableComponent implements EquippableComponent { + + @Override + public void setAllowedEntities(Tag tag) { +- Preconditions.checkArgument(tag instanceof CraftEntityTag, "tag must be an entity tag"); ++ Preconditions.checkArgument(tag == null || tag instanceof CraftEntityTag, "tag must be an entity tag"); // Paper + + this.handle = new Equippable(this.handle.slot(), this.handle.equipSound(), this.handle.model(), this.handle.cameraOverlay(), + (tag != null) ? Optional.of(((CraftEntityTag) tag).getHandle()) : Optional.empty(), +diff --git a/src/main/java/org/bukkit/craftbukkit/inventory/components/CraftToolComponent.java b/src/main/java/org/bukkit/craftbukkit/inventory/components/CraftToolComponent.java +index 9a4a1d903c22e9d2a0cbda95ceb8b1dcfb29112e..4f7914f96207feda67cd910213bb624df4802a06 100644 +--- a/src/main/java/org/bukkit/craftbukkit/inventory/components/CraftToolComponent.java ++++ b/src/main/java/org/bukkit/craftbukkit/inventory/components/CraftToolComponent.java +@@ -106,6 +106,7 @@ public final class CraftToolComponent implements ToolComponent { + public ToolRule addRule(Material block, Float speed, Boolean correctForDrops) { + Preconditions.checkArgument(block != null, "block must not be null"); + Preconditions.checkArgument(block.isBlock(), "block must be a block type, given %s", block.getKey()); ++ Preconditions.checkArgument(speed == null || speed > 0, "speed must be positive"); // Paper - validate speed + + Holder.Reference nmsBlock = CraftBlockType.bukkitToMinecraft(block).builtInRegistryHolder(); + return this.addRule(HolderSet.direct(nmsBlock), speed, correctForDrops); +@@ -113,6 +114,7 @@ public final class CraftToolComponent implements ToolComponent { + + @Override + public ToolRule addRule(Collection blocks, Float speed, Boolean correctForDrops) { ++ Preconditions.checkArgument(speed == null || speed > 0, "speed must be positive"); // Paper - validate speed + List> nmsBlocks = new ArrayList<>(blocks.size()); + + for (Material material : blocks) { +@@ -126,6 +128,7 @@ public final class CraftToolComponent implements ToolComponent { + @Override + public ToolRule addRule(Tag tag, Float speed, Boolean correctForDrops) { + Preconditions.checkArgument(tag instanceof CraftBlockTag, "tag must be a block tag"); ++ Preconditions.checkArgument(speed == null || speed > 0, "speed must be positive"); // Paper - validate speed + return this.addRule(((CraftBlockTag) tag).getHandle(), speed, correctForDrops); + } + +@@ -258,6 +261,7 @@ public final class CraftToolComponent implements ToolComponent { + + @Override + public void setSpeed(Float speed) { ++ Preconditions.checkArgument(speed == null || speed > 0, "speed must be positive"); // Paper - validate speed + this.handle = new Tool.Rule(this.handle.blocks(), Optional.ofNullable(speed), this.handle.correctForDrops()); + } + +diff --git a/src/main/java/org/bukkit/craftbukkit/inventory/components/CraftUseCooldownComponent.java b/src/main/java/org/bukkit/craftbukkit/inventory/components/CraftUseCooldownComponent.java +index 952e7fa022161d1d21ba1a4f43abe5ee221dee4b..0f86ff23cc6a2f617fc5ef67e576e1e3cb29034c 100644 +--- a/src/main/java/org/bukkit/craftbukkit/inventory/components/CraftUseCooldownComponent.java ++++ b/src/main/java/org/bukkit/craftbukkit/inventory/components/CraftUseCooldownComponent.java +@@ -55,7 +55,7 @@ public final class CraftUseCooldownComponent implements UseCooldownComponent { + + @Override + public void setCooldownSeconds(float eatSeconds) { +- Preconditions.checkArgument(eatSeconds >= 0, "eatSeconds cannot be less than 0"); ++ Preconditions.checkArgument(eatSeconds > 0, "eatSeconds must be positive"); // Paper + + this.handle = new UseCooldown(eatSeconds, this.handle.cooldownGroup()); + } +diff --git a/src/test/java/org/bukkit/craftbukkit/inventory/DeprecatedItemMetaCustomValueTest.java b/src/test/java/org/bukkit/craftbukkit/inventory/DeprecatedItemMetaCustomValueTest.java +index 9cc1ef5c9221dd7d2069b280f0c91ce9439a995a..1c80fe7549d70ae16c7b755c22752549261f072a 100644 +--- a/src/test/java/org/bukkit/craftbukkit/inventory/DeprecatedItemMetaCustomValueTest.java ++++ b/src/test/java/org/bukkit/craftbukkit/inventory/DeprecatedItemMetaCustomValueTest.java +@@ -94,7 +94,7 @@ public class DeprecatedItemMetaCustomValueTest { + public void testNBTTagStoring() { + CraftMetaItem itemMeta = this.createComplexItemMeta(); + +- CraftMetaItem.Applicator compound = new CraftMetaItem.Applicator(); ++ CraftMetaItem.Applicator compound = new CraftMetaItem.Applicator() {}; // Paper + itemMeta.applyToItem(compound); + + assertEquals(itemMeta, new CraftMetaItem(compound.build(), null)); // Paper +diff --git a/src/test/java/org/bukkit/craftbukkit/inventory/PersistentDataContainerTest.java b/src/test/java/org/bukkit/craftbukkit/inventory/PersistentDataContainerTest.java +index 130c4500a5e854480962c8f720b1df4c67d43c33..f33b49915d1f1f0838c49ac943e8d4d619450f6b 100644 +--- a/src/test/java/org/bukkit/craftbukkit/inventory/PersistentDataContainerTest.java ++++ b/src/test/java/org/bukkit/craftbukkit/inventory/PersistentDataContainerTest.java +@@ -128,7 +128,7 @@ public class PersistentDataContainerTest { + public void testNBTTagStoring() { + CraftMetaItem itemMeta = this.createComplexItemMeta(); + +- CraftMetaItem.Applicator compound = new CraftMetaItem.Applicator(); ++ CraftMetaItem.Applicator compound = new CraftMetaItem.Applicator() {}; // Paper + itemMeta.applyToItem(compound); + + assertEquals(itemMeta, new CraftMetaItem(compound.build(), null)); // Paper +@@ -474,7 +474,7 @@ public class PersistentDataContainerTest { + assertEquals(List.of(), container.get(PersistentDataContainerTest.requestKey("list"), PersistentDataType.LIST.strings())); + + // Write and read the entire container to NBT +- final CraftMetaItem.Applicator storage = new CraftMetaItem.Applicator(); ++ final CraftMetaItem.Applicator storage = new CraftMetaItem.Applicator() {}; // Paper + craftItem.applyToItem(storage); + + final CraftMetaItem readItem = new CraftMetaItem(storage.build(), null); // Paper diff --git a/patches/server/0954-Fix-shield-disable-inconsistency.patch b/patches/server/0954-Fix-shield-disable-inconsistency.patch deleted file mode 100644 index 61614305d9a7..000000000000 --- a/patches/server/0954-Fix-shield-disable-inconsistency.patch +++ /dev/null @@ -1,22 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Jake Potrebic -Date: Fri, 26 Apr 2024 19:08:37 -0700 -Subject: [PATCH] Fix shield disable inconsistency - -In vanilla, if the damage source is tagged as a projectile, -it will not disable the shield if the attacker is holding -an axe item. - -diff --git a/src/main/java/net/minecraft/world/entity/LivingEntity.java b/src/main/java/net/minecraft/world/entity/LivingEntity.java -index 94169703c5a8111df1ed550d57f59f4a3bb97ae1..6ff33a6d46daffd15310f21ec00d4861478b360f 100644 ---- a/src/main/java/net/minecraft/world/entity/LivingEntity.java -+++ b/src/main/java/net/minecraft/world/entity/LivingEntity.java -@@ -2349,7 +2349,7 @@ public abstract class LivingEntity extends Entity implements Attackable { - this.hurtCurrentlyUsedShield((float) -event.getDamage(DamageModifier.BLOCKING)); - Entity entity = damagesource.getDirectEntity(); - -- if (entity instanceof LivingEntity) { -+ if (!damagesource.is(DamageTypeTags.IS_PROJECTILE) && entity instanceof LivingEntity) { // Paper - Fix shield disable inconsistency - this.blockUsingShield((LivingEntity) entity); - } - } diff --git a/patches/server/0954-More-Chest-Block-API.patch b/patches/server/0954-More-Chest-Block-API.patch new file mode 100644 index 000000000000..d7c00f61e18a --- /dev/null +++ b/patches/server/0954-More-Chest-Block-API.patch @@ -0,0 +1,73 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: SoSeDiK +Date: Wed, 1 May 2024 08:22:13 +0300 +Subject: [PATCH] More Chest Block API + +== AT == +public net.minecraft.world.level.block.ChestBlock isBlockedChestByBlock(Lnet/minecraft/world/level/BlockGetter;Lnet/minecraft/core/BlockPos;)Z + +diff --git a/src/main/java/net/minecraft/world/level/block/EnderChestBlock.java b/src/main/java/net/minecraft/world/level/block/EnderChestBlock.java +index b376f7d3b632d6aaea5c4d93382238074559d56a..ef0d469176ee74b6bb5f9e9cc508735145fda5b8 100644 +--- a/src/main/java/net/minecraft/world/level/block/EnderChestBlock.java ++++ b/src/main/java/net/minecraft/world/level/block/EnderChestBlock.java +@@ -83,7 +83,7 @@ public class EnderChestBlock extends AbstractChestBlock i + PlayerEnderChestContainer playerEnderChestContainer = player.getEnderChestInventory(); + if (playerEnderChestContainer != null && world.getBlockEntity(pos) instanceof EnderChestBlockEntity enderChestBlockEntity) { + BlockPos blockPos = pos.above(); +- if (world.getBlockState(blockPos).isRedstoneConductor(world, blockPos)) { ++ if (world.getBlockState(blockPos).isRedstoneConductor(world, blockPos)) { // Paper - diff on change; make sure that EnderChest#isBlocked uses the same logic + return InteractionResult.SUCCESS; + } else { + if (world instanceof ServerLevel serverLevel) { +diff --git a/src/main/java/org/bukkit/craftbukkit/block/CraftChest.java b/src/main/java/org/bukkit/craftbukkit/block/CraftChest.java +index 6e98a00d526b734992ce39b15768c5820dce4ca8..cc7bf4d39b834fba472bc163226a01a0cd4b6010 100644 +--- a/src/main/java/org/bukkit/craftbukkit/block/CraftChest.java ++++ b/src/main/java/org/bukkit/craftbukkit/block/CraftChest.java +@@ -99,4 +99,29 @@ public class CraftChest extends CraftLootable implements Chest + return getTileEntity().openersCounter.opened; + } + // Paper end - More Lidded Block API ++ ++ // Paper start - More Chest Block API ++ @Override ++ public boolean isBlocked() { ++ // Method mimics vanilla logic in ChestBlock and DoubleBlockCombiner when trying to open chest's container ++ if (!isPlaced()) { ++ return false; ++ } ++ net.minecraft.world.level.LevelAccessor world = getWorldHandle(); ++ if (ChestBlock.isChestBlockedAt(world, getPosition())) { ++ return true; ++ } ++ if (ChestBlock.getBlockType(this.data) == net.minecraft.world.level.block.DoubleBlockCombiner.BlockType.SINGLE) { ++ return false; ++ } ++ net.minecraft.core.Direction direction = ChestBlock.getConnectedDirection(this.data); ++ net.minecraft.core.BlockPos neighbourBlockPos = getPosition().relative(direction); ++ BlockState neighbourBlockState = world.getBlockStateIfLoaded(neighbourBlockPos); ++ return neighbourBlockState != null ++ && neighbourBlockState.is(this.data.getBlock()) ++ && ChestBlock.getBlockType(neighbourBlockState) != net.minecraft.world.level.block.DoubleBlockCombiner.BlockType.SINGLE ++ && ChestBlock.getConnectedDirection(neighbourBlockState) == direction.getOpposite() ++ && ChestBlock.isChestBlockedAt(world, neighbourBlockPos); ++ } ++ // Paper end - More Chest Block API + } +diff --git a/src/main/java/org/bukkit/craftbukkit/block/CraftEnderChest.java b/src/main/java/org/bukkit/craftbukkit/block/CraftEnderChest.java +index b64adbba3e52d32d439e64a243cb74f3fbca2ce3..f45ee675a10729845bf376fa95e648b23b9aac12 100644 +--- a/src/main/java/org/bukkit/craftbukkit/block/CraftEnderChest.java ++++ b/src/main/java/org/bukkit/craftbukkit/block/CraftEnderChest.java +@@ -58,4 +58,13 @@ public class CraftEnderChest extends CraftBlockEntityState -Date: Tue, 27 Nov 2018 21:18:06 -0500 -Subject: [PATCH] Handle Large Packets disconnecting client - -If a players inventory is too big to send in a single packet, -split the inventory set into multiple packets instead. - -diff --git a/src/main/java/net/minecraft/network/Connection.java b/src/main/java/net/minecraft/network/Connection.java -index a8dfe7a4b3d01bf75587be078f471d1ef1d7a667..ea16dfa718b526d6520d7fcfc21d28f972f1f2bf 100644 ---- a/src/main/java/net/minecraft/network/Connection.java -+++ b/src/main/java/net/minecraft/network/Connection.java -@@ -176,6 +176,21 @@ public class Connection extends SimpleChannelInboundHandler> { - } - - public void exceptionCaught(ChannelHandlerContext channelhandlercontext, Throwable throwable) { -+ // Paper start - Handle large packets disconnecting client -+ if (throwable instanceof io.netty.handler.codec.EncoderException && throwable.getCause() instanceof PacketEncoder.PacketTooLargeException packetTooLargeException) { -+ final Packet packet = packetTooLargeException.getPacket(); -+ if (packet.packetTooLarge(this)) { -+ ProtocolSwapHandler.handleOutboundTerminalPacket(channelhandlercontext, packet); -+ return; -+ } else if (packet.isSkippable()) { -+ Connection.LOGGER.debug("Skipping packet due to errors", throwable.getCause()); -+ ProtocolSwapHandler.handleOutboundTerminalPacket(channelhandlercontext, packet); -+ return; -+ } else { -+ throwable = throwable.getCause(); -+ } -+ } -+ // Paper end - Handle large packets disconnecting client - if (throwable instanceof SkipPacketException) { - Connection.LOGGER.debug("Skipping packet due to errors", throwable.getCause()); - } else { -diff --git a/src/main/java/net/minecraft/network/PacketEncoder.java b/src/main/java/net/minecraft/network/PacketEncoder.java -index 046bfc212b640de174b300e7a05cc30bb3cac93e..af3ec112e142a2c91c46882dad6180b18f39eec2 100644 ---- a/src/main/java/net/minecraft/network/PacketEncoder.java -+++ b/src/main/java/net/minecraft/network/PacketEncoder.java -@@ -40,7 +40,33 @@ public class PacketEncoder extends MessageToByteEncode - - throw var9; - } finally { -+ // Paper start - Handle large packets disconnecting client -+ int packetLength = byteBuf.readableBytes(); -+ if (packetLength > MAX_PACKET_SIZE || (packetLength > MAX_FINAL_PACKET_SIZE && packet.hasLargePacketFallback())) { -+ throw new PacketTooLargeException(packet, packetLength); -+ } -+ // Paper end - Handle large packets disconnecting client - ProtocolSwapHandler.handleOutboundTerminalPacket(channelHandlerContext, packet); - } - } -+ -+ // Paper start -+ // packet size is encoded into 3-byte varint -+ private static final int MAX_FINAL_PACKET_SIZE = (1 << 21) - 1; -+ // Vanilla Max size for the encoder (before compression) -+ private static final int MAX_PACKET_SIZE = 8388608; -+ -+ public static class PacketTooLargeException extends RuntimeException { -+ private final Packet packet; -+ -+ PacketTooLargeException(Packet packet, int packetLength) { -+ super("PacketTooLarge - " + packet.getClass().getSimpleName() + " is " + packetLength + ". Max is " + MAX_PACKET_SIZE); -+ this.packet = packet; -+ } -+ -+ public Packet getPacket() { -+ return this.packet; -+ } -+ } -+ // Paper end - } -diff --git a/src/main/java/net/minecraft/network/protocol/Packet.java b/src/main/java/net/minecraft/network/protocol/Packet.java -index 4c776c591dd0a7b36945a6487fdfe86d1187b4af..82fc12ffbd1585b4a8d09a025914830af77b0f8d 100644 ---- a/src/main/java/net/minecraft/network/protocol/Packet.java -+++ b/src/main/java/net/minecraft/network/protocol/Packet.java -@@ -11,6 +11,19 @@ public interface Packet { - - void handle(T listener); - -+ // Paper start -+ default boolean hasLargePacketFallback() { -+ return false; -+ } -+ -+ /** -+ * override {@link #hasLargePacketFallback()} to return true when overriding in subclasses -+ */ -+ default boolean packetTooLarge(net.minecraft.network.Connection manager) { -+ return false; -+ } -+ // Paper end -+ - default boolean isSkippable() { - return false; - } -diff --git a/src/main/java/net/minecraft/network/protocol/game/ClientboundContainerSetContentPacket.java b/src/main/java/net/minecraft/network/protocol/game/ClientboundContainerSetContentPacket.java -index 7e555ece0555b3d2a983ab2c39c5e7ec23fc7e88..8cca2ac616a2c80268c96b9f95e33f834a0fc8fd 100644 ---- a/src/main/java/net/minecraft/network/protocol/game/ClientboundContainerSetContentPacket.java -+++ b/src/main/java/net/minecraft/network/protocol/game/ClientboundContainerSetContentPacket.java -@@ -36,6 +36,21 @@ public class ClientboundContainerSetContentPacket implements Packet 2097152) { -+ if (i > 2097152) { // Paper - diff on change - if this changes, update PacketEncoder - throw new RuntimeException("Chunk Packet trying to allocate too much memory on read."); - } else { - this.buffer = new byte[i]; diff --git a/patches/server/0955-Print-data-component-type-on-encoding-error.patch b/patches/server/0955-Print-data-component-type-on-encoding-error.patch new file mode 100644 index 000000000000..f003f33b9254 --- /dev/null +++ b/patches/server/0955-Print-data-component-type-on-encoding-error.patch @@ -0,0 +1,24 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Nassim Jahnke +Date: Thu, 9 May 2024 15:11:34 +0200 +Subject: [PATCH] Print data component type on encoding error + + +diff --git a/src/main/java/net/minecraft/core/component/DataComponentPatch.java b/src/main/java/net/minecraft/core/component/DataComponentPatch.java +index 9de958d769a52a8df794988781f8601a4e6a73a4..9dec1c74d10f82636b7176e8f725b8acd1b52e4f 100644 +--- a/src/main/java/net/minecraft/core/component/DataComponentPatch.java ++++ b/src/main/java/net/minecraft/core/component/DataComponentPatch.java +@@ -144,7 +144,13 @@ public final class DataComponentPatch { + } + + private static void encodeComponent(RegistryFriendlyByteBuf buf, DataComponentType type, Object value) { ++ // Paper start - codec errors of random anonymous classes are useless ++ try { + type.streamCodec().encode(buf, (T) value); // CraftBukkit - decompile error ++ } catch (final Exception e) { ++ throw new RuntimeException("Error encoding component " + type, e); ++ } ++ // Paper end - codec errors of random anonymous classes are useless + } + }; + private static final String REMOVED_PREFIX = "!"; diff --git a/patches/server/0956-Brigadier-based-command-API.patch b/patches/server/0956-Brigadier-based-command-API.patch new file mode 100644 index 000000000000..fd34bd975c82 --- /dev/null +++ b/patches/server/0956-Brigadier-based-command-API.patch @@ -0,0 +1,2819 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Owen1212055 <23108066+Owen1212055@users.noreply.github.com> +Date: Mon, 1 Aug 2022 22:50:34 -0400 +Subject: [PATCH] Brigadier based command API + +== AT == +public net.minecraft.commands.arguments.blocks.BlockInput tag +public net.minecraft.commands.arguments.DimensionArgument ERROR_INVALID_VALUE +public net.minecraft.server.ReloadableServerResources registryLookup +public net.minecraft.server.ReloadableServerResources + +Co-authored-by: Jake Potrebic + +diff --git a/src/main/java/com/mojang/brigadier/CommandDispatcher.java b/src/main/java/com/mojang/brigadier/CommandDispatcher.java +index 4b4f812eb13d5f03bcf3f8724d8aa8dbbc724e8b..a4d5d7017e0be79844b996de85a63cad5f8488bc 100644 +--- a/src/main/java/com/mojang/brigadier/CommandDispatcher.java ++++ b/src/main/java/com/mojang/brigadier/CommandDispatcher.java +@@ -459,7 +459,7 @@ public class CommandDispatcher { + } + + private String getSmartUsage(final CommandNode node, final S source, final boolean optional, final boolean deep) { +- if (!node.canUse(source)) { ++ if (source != null && !node.canUse(source)) { // Paper + return null; + } + +@@ -473,7 +473,7 @@ public class CommandDispatcher { + final String redirect = node.getRedirect() == this.root ? "..." : "-> " + node.getRedirect().getUsageText(); + return self + CommandDispatcher.ARGUMENT_SEPARATOR + redirect; + } else { +- final Collection> children = node.getChildren().stream().filter(c -> c.canUse(source)).collect(Collectors.toList()); ++ final Collection> children = node.getChildren().stream().filter(c -> source == null || c.canUse(source)).collect(Collectors.toList()); // Paper + if (children.size() == 1) { + final String usage = this.getSmartUsage(children.iterator().next(), source, childOptional, childOptional); + if (usage != null) { +diff --git a/src/main/java/com/mojang/brigadier/tree/CommandNode.java b/src/main/java/com/mojang/brigadier/tree/CommandNode.java +index 1f4963bf4681a771130abc1da179819626ecfc1f..03ce8a2abb6dceaa922dcce7f3adbc228bbde4bc 100644 +--- a/src/main/java/com/mojang/brigadier/tree/CommandNode.java ++++ b/src/main/java/com/mojang/brigadier/tree/CommandNode.java +@@ -35,6 +35,8 @@ public abstract class CommandNode implements Comparable> { + private final boolean forks; + private Command command; + public CommandNode clientNode; // Paper - Brigadier API ++ public CommandNode unwrappedCached = null; // Paper - Brigadier Command API ++ public CommandNode wrappedCached = null; // Paper - Brigadier Command API + // CraftBukkit start + public void removeCommand(String name) { + this.children.remove(name); +@@ -203,4 +205,11 @@ public abstract class CommandNode implements Comparable> { + } + + public abstract Collection getExamples(); ++ // Paper start - Brigadier Command API ++ public void clearAll() { ++ this.children.clear(); ++ this.literals.clear(); ++ this.arguments.clear(); ++ } ++ // Paper end - Brigadier Command API + } +diff --git a/src/main/java/io/papermc/paper/brigadier/NullCommandSender.java b/src/main/java/io/papermc/paper/brigadier/NullCommandSender.java +new file mode 100644 +index 0000000000000000000000000000000000000000..367ef7e0769537e8c13c7fd818a1249e15a28a65 +--- /dev/null ++++ b/src/main/java/io/papermc/paper/brigadier/NullCommandSender.java +@@ -0,0 +1,151 @@ ++package io.papermc.paper.brigadier; ++ ++import java.util.Set; ++import java.util.UUID; ++import net.kyori.adventure.text.Component; ++import net.md_5.bungee.api.chat.BaseComponent; ++import org.bukkit.Bukkit; ++import org.bukkit.Server; ++import org.bukkit.command.CommandSender; ++import org.bukkit.permissions.PermissibleBase; ++import org.bukkit.permissions.Permission; ++import org.bukkit.permissions.PermissionAttachment; ++import org.bukkit.permissions.PermissionAttachmentInfo; ++import org.bukkit.plugin.Plugin; ++import org.checkerframework.checker.nullness.qual.NonNull; ++import org.checkerframework.framework.qual.DefaultQualifier; ++import org.jetbrains.annotations.NotNull; ++import org.jetbrains.annotations.Nullable; ++ ++@DefaultQualifier(NonNull.class) ++public final class NullCommandSender implements CommandSender { ++ ++ public static final CommandSender INSTANCE = new NullCommandSender(); ++ ++ private NullCommandSender() { ++ } ++ ++ @Override ++ public void sendMessage(final String message) { ++ } ++ ++ @Override ++ public void sendMessage(final String... messages) { ++ } ++ ++ @Override ++ public void sendMessage(@Nullable final UUID sender, final String message) { ++ } ++ ++ @Override ++ public void sendMessage(@Nullable final UUID sender, final String... messages) { ++ } ++ ++ @SuppressWarnings("ConstantValue") ++ @Override ++ public Server getServer() { ++ final @Nullable Server server = Bukkit.getServer(); ++ if (server == null) { ++ throw new UnsupportedOperationException("The server has not been created yet, you cannot access it at this time from the 'null' CommandSender"); ++ } ++ return server; ++ } ++ ++ @Override ++ public String getName() { ++ return ""; ++ } ++ ++ private final Spigot spigot = new Spigot(); ++ @Override ++ public Spigot spigot() { ++ return this.spigot; ++ } ++ ++ public final class Spigot extends CommandSender.Spigot { ++ ++ @Override ++ public void sendMessage(@NotNull final BaseComponent component) { ++ } ++ ++ @Override ++ public void sendMessage(@NonNull final @NotNull BaseComponent... components) { ++ } ++ ++ @Override ++ public void sendMessage(@Nullable final UUID sender, @NotNull final BaseComponent component) { ++ } ++ ++ @Override ++ public void sendMessage(@Nullable final UUID sender, @NonNull final @NotNull BaseComponent... components) { ++ } ++ } ++ ++ @Override ++ public Component name() { ++ return Component.empty(); ++ } ++ ++ @Override ++ public boolean isPermissionSet(final String name) { ++ return false; ++ } ++ ++ @Override ++ public boolean isPermissionSet(final Permission perm) { ++ return false; ++ } ++ ++ @Override ++ public boolean hasPermission(final String name) { ++ return true; ++ } ++ ++ @Override ++ public boolean hasPermission(final Permission perm) { ++ return true; ++ } ++ ++ @Override ++ public PermissionAttachment addAttachment(final Plugin plugin, final String name, final boolean value) { ++ throw new UnsupportedOperationException("Cannot add attachments to the 'null' CommandSender"); ++ } ++ ++ @Override ++ public PermissionAttachment addAttachment(final Plugin plugin) { ++ throw new UnsupportedOperationException("Cannot add attachments to the 'null' CommandSender"); ++ } ++ ++ @Override ++ public @Nullable PermissionAttachment addAttachment(final Plugin plugin, final String name, final boolean value, final int ticks) { ++ throw new UnsupportedOperationException("Cannot add attachments to the 'null' CommandSender"); ++ } ++ ++ @Override ++ public @Nullable PermissionAttachment addAttachment(final Plugin plugin, final int ticks) { ++ throw new UnsupportedOperationException("Cannot add attachments to the 'null' CommandSender"); ++ } ++ ++ @Override ++ public void removeAttachment(final PermissionAttachment attachment) { ++ throw new UnsupportedOperationException("Cannot add attachments to the 'null' CommandSender"); ++ } ++ ++ @Override ++ public void recalculatePermissions() { ++ } ++ ++ @Override ++ public Set getEffectivePermissions() { ++ throw new UnsupportedOperationException("Cannot remove attachments from the 'null' CommandSender"); ++ } ++ ++ @Override ++ public boolean isOp() { ++ return true; ++ } ++ ++ @Override ++ public void setOp(final boolean value) { ++ } ++} +diff --git a/src/main/java/io/papermc/paper/brigadier/PaperBrigadierProviderImpl.java b/src/main/java/io/papermc/paper/brigadier/PaperBrigadierProviderImpl.java +deleted file mode 100644 +index dd6012b6a097575b2d1471be5069eccee4537c0a..0000000000000000000000000000000000000000 +--- a/src/main/java/io/papermc/paper/brigadier/PaperBrigadierProviderImpl.java ++++ /dev/null +@@ -1,30 +0,0 @@ +-package io.papermc.paper.brigadier; +- +-import com.mojang.brigadier.Message; +-import io.papermc.paper.adventure.PaperAdventure; +-import net.kyori.adventure.text.Component; +-import net.kyori.adventure.text.ComponentLike; +-import net.minecraft.network.chat.ComponentUtils; +-import org.checkerframework.checker.nullness.qual.NonNull; +- +-import static java.util.Objects.requireNonNull; +- +-public enum PaperBrigadierProviderImpl implements PaperBrigadierProvider { +- INSTANCE; +- +- PaperBrigadierProviderImpl() { +- PaperBrigadierProvider.initialize(this); +- } +- +- @Override +- public @NonNull Message message(final @NonNull ComponentLike componentLike) { +- requireNonNull(componentLike, "componentLike"); +- return PaperAdventure.asVanilla(componentLike.asComponent()); +- } +- +- @Override +- public @NonNull Component componentFromMessage(final @NonNull Message message) { +- requireNonNull(message, "message"); +- return PaperAdventure.asAdventure(ComponentUtils.fromMessage(message)); +- } +-} +diff --git a/src/main/java/io/papermc/paper/command/brigadier/ApiMirrorRootNode.java b/src/main/java/io/papermc/paper/command/brigadier/ApiMirrorRootNode.java +new file mode 100644 +index 0000000000000000000000000000000000000000..23525592d880f340745a28c956fa38d3e4057231 +--- /dev/null ++++ b/src/main/java/io/papermc/paper/command/brigadier/ApiMirrorRootNode.java +@@ -0,0 +1,253 @@ ++package io.papermc.paper.command.brigadier; ++ ++import com.google.common.collect.Collections2; ++import com.mojang.brigadier.CommandDispatcher; ++import com.mojang.brigadier.arguments.ArgumentType; ++import com.mojang.brigadier.arguments.BoolArgumentType; ++import com.mojang.brigadier.arguments.DoubleArgumentType; ++import com.mojang.brigadier.arguments.FloatArgumentType; ++import com.mojang.brigadier.arguments.IntegerArgumentType; ++import com.mojang.brigadier.arguments.LongArgumentType; ++import com.mojang.brigadier.arguments.StringArgumentType; ++import com.mojang.brigadier.context.CommandContext; ++import com.mojang.brigadier.suggestion.SuggestionProvider; ++import com.mojang.brigadier.suggestion.SuggestionsBuilder; ++import com.mojang.brigadier.tree.ArgumentCommandNode; ++import com.mojang.brigadier.tree.CommandNode; ++import com.mojang.brigadier.tree.LiteralCommandNode; ++import com.mojang.brigadier.tree.RootCommandNode; ++import io.papermc.paper.command.brigadier.argument.CustomArgumentType; ++import io.papermc.paper.command.brigadier.argument.VanillaArgumentProviderImpl; ++import io.papermc.paper.command.brigadier.argument.WrappedArgumentCommandNode; ++import java.lang.reflect.Method; ++import java.util.Collection; ++import java.util.Set; ++import net.minecraft.commands.synchronization.ArgumentTypeInfos; ++import org.jetbrains.annotations.NotNull; ++import org.jetbrains.annotations.Nullable; ++ ++/** ++ * This root command node is responsible for wrapping around vanilla's dispatcher. ++ *

    ++ * The reason for this is conversion is we do NOT want there to be NMS types ++ * in the api. This allows us to reconstruct the nodes to be more api friendly, while ++ * we can then unwrap it when needed and convert them to NMS types. ++ *

    ++ * Command nodes such as vanilla (those without a proper "api node") ++ * will be assigned a {@link ShadowBrigNode}. ++ * This prevents certain parts of it (children) from being accessed by the api. ++ */ ++public abstract class ApiMirrorRootNode extends RootCommandNode { ++ ++ /** ++ * Represents argument types that are allowed to exist in the api. ++ * These typically represent primitives that don't need to be wrapped ++ * by NMS. ++ */ ++ private static final Set>> ARGUMENT_WHITELIST = Set.of( ++ BoolArgumentType.class, ++ DoubleArgumentType.class, ++ FloatArgumentType.class, ++ IntegerArgumentType.class, ++ LongArgumentType.class, ++ StringArgumentType.class ++ ); ++ ++ public static void validatePrimitiveType(ArgumentType type) { ++ if (ARGUMENT_WHITELIST.contains(type.getClass())) { ++ if (!ArgumentTypeInfos.isClassRecognized(type.getClass())) { ++ throw new IllegalArgumentException("This whitelisted primitive argument type is not recognized by the server!"); ++ } ++ } else if (!(type instanceof VanillaArgumentProviderImpl.NativeWrapperArgumentType nativeWrapperArgumentType) || !ArgumentTypeInfos.isClassRecognized(nativeWrapperArgumentType.nativeNmsArgumentType().getClass())) { ++ throw new IllegalArgumentException("Custom argument type was passed, this was not a recognized type to send to the client! You must only pass vanilla arguments or primitive brig args in the wrapper!"); ++ } ++ } ++ ++ public abstract CommandDispatcher getDispatcher(); ++ ++ /** ++ * This logic is responsible for unwrapping an API node to be supported by NMS. ++ * See the method implementation for detailed steps. ++ * ++ * @param maybeWrappedNode api provided node / node to be "wrapped" ++ * @return wrapped node ++ */ ++ @SuppressWarnings({"rawtypes", "unchecked"}) ++ private @NotNull CommandNode unwrapNode(final CommandNode maybeWrappedNode) { ++ /* ++ If the type is a shadow node we can assume that the type that it represents is an already supported NMS node. ++ This is because these are typically minecraft command nodes. ++ */ ++ if (maybeWrappedNode instanceof final ShadowBrigNode shadowBrigNode) { ++ return (CommandNode) shadowBrigNode.getHandle(); ++ } ++ ++ /* ++ This node already has had an unwrapped node created, so we can assume that it's safe to reuse that cached copy. ++ */ ++ if (maybeWrappedNode.unwrappedCached != null) { ++ return maybeWrappedNode.unwrappedCached; ++ } ++ ++ // convert the pure brig node into one compatible with the nms dispatcher ++ return this.convertFromPureBrigNode(maybeWrappedNode); ++ } ++ ++ private @NotNull CommandNode convertFromPureBrigNode(final CommandNode pureNode) { ++ /* ++ Logic for converting a node. ++ */ ++ final CommandNode converted; ++ if (pureNode instanceof final LiteralCommandNode node) { ++ /* ++ Remap the literal node, we only have to account ++ for the redirect in this case. ++ */ ++ converted = this.simpleUnwrap(node); ++ } else if (pureNode instanceof final ArgumentCommandNode pureArgumentNode) { ++ final ArgumentType pureArgumentType = pureArgumentNode.getType(); ++ /* ++ Check to see if this argument type is a wrapped type, if so we know that ++ we can unwrap the node to get an NMS type. ++ */ ++ if (pureArgumentType instanceof final CustomArgumentType customArgumentType) { ++ final SuggestionProvider suggestionProvider; ++ try { ++ final Method listSuggestions = customArgumentType.getClass().getMethod("listSuggestions", CommandContext.class, SuggestionsBuilder.class); ++ if (listSuggestions.getDeclaringClass() != CustomArgumentType.class) { ++ suggestionProvider = customArgumentType::listSuggestions; ++ } else { ++ suggestionProvider = null; ++ } ++ } catch (final NoSuchMethodException ex) { ++ throw new IllegalStateException("Could not determine if the custom argument type " + customArgumentType + " overrides listSuggestions", ex); ++ } ++ ++ converted = this.unwrapArgumentWrapper(pureArgumentNode, customArgumentType, customArgumentType.getNativeType(), suggestionProvider); ++ } else if (pureArgumentType instanceof final VanillaArgumentProviderImpl.NativeWrapperArgumentType nativeWrapperArgumentType) { ++ converted = this.unwrapArgumentWrapper(pureArgumentNode, nativeWrapperArgumentType, nativeWrapperArgumentType, null); // "null" for suggestion provider so it uses the argument type's suggestion provider ++ ++ /* ++ If it's not a wrapped type, it either has to be a primitive or an already ++ defined NMS type. ++ This method allows us to check if this is recognized by vanilla. ++ */ ++ } else if (ArgumentTypeInfos.isClassRecognized(pureArgumentType.getClass())) { ++ // Allow any type of argument, as long as it's recognized by the client (but in most cases, this should be API only types) ++ // Previously we only allowed whitelisted types. ++ converted = this.simpleUnwrap(pureArgumentNode); ++ } else { ++ // Unknown argument type was passed ++ throw new IllegalArgumentException("Custom unknown argument type was passed, should be wrapped inside an CustomArgumentType."); ++ } ++ } else { ++ throw new IllegalArgumentException("Unknown command node passed. Don't know how to unwrap this."); ++ } ++ ++ // Store unwrapped node before unwrapping children to avoid infinite recursion in cyclic redirects. ++ converted.wrappedCached = pureNode; ++ pureNode.unwrappedCached = converted; ++ ++ /* ++ Add the children to the node, unwrapping each child in the process. ++ */ ++ for (final CommandNode child : pureNode.getChildren()) { ++ converted.addChild(this.unwrapNode(child)); ++ } ++ ++ return converted; ++ } ++ ++ /** ++ * This logic is responsible for rewrapping a node. ++ * If a node was unwrapped in the past, it should have a wrapped type ++ * stored in its cache. ++ *

    ++ * However, if it doesn't seem to have a wrapped version we will return ++ * a {@link ShadowBrigNode} instead. This supports being unwrapped/wrapped while ++ * preventing the API from accessing it unsafely. ++ * ++ * @param unwrapped argument node ++ * @return wrapped node ++ */ ++ private @Nullable CommandNode wrapNode(@Nullable final CommandNode unwrapped) { ++ if (unwrapped == null) { ++ return null; ++ } ++ ++ /* ++ This was most likely created by API and has a wrapped variant, ++ so we can return this safely. ++ */ ++ if (unwrapped.wrappedCached != null) { ++ return unwrapped.wrappedCached; ++ } ++ ++ /* ++ We don't know the type of this, or where this came from. ++ Return a shadow, where we will allow the api to handle this but have ++ restrictive access. ++ */ ++ CommandNode shadow = new ShadowBrigNode(unwrapped); ++ unwrapped.wrappedCached = shadow; ++ return shadow; ++ } ++ ++ /** ++ * Nodes added to this dispatcher must be unwrapped ++ * in order to be added to the NMS dispatcher. ++ * ++ * @param node node to add ++ */ ++ @SuppressWarnings({"rawtypes", "unchecked"}) ++ @Override ++ public void addChild(CommandNode node) { ++ CommandNode convertedNode = this.unwrapNode(node); ++ this.getDispatcher().getRoot().addChild(convertedNode); ++ } ++ ++ /** ++ * Gets the children for the vanilla dispatcher, ++ * ensuring that all are wrapped. ++ * ++ * @return wrapped children ++ */ ++ @Override ++ public Collection> getChildren() { ++ return Collections2.transform(this.getDispatcher().getRoot().getChildren(), this::wrapNode); ++ } ++ ++ @Override ++ public CommandNode getChild(String name) { ++ return this.wrapNode(this.getDispatcher().getRoot().getChild(name)); ++ } ++ ++ // These are needed for bukkit... we should NOT allow this ++ @Override ++ public void removeCommand(String name) { ++ this.getDispatcher().getRoot().removeCommand(name); ++ } ++ ++ @Override ++ public void clearAll() { ++ this.getDispatcher().getRoot().clearAll(); ++ } ++ ++ @SuppressWarnings({"rawtypes", "unchecked"}) ++ private CommandNode unwrapArgumentWrapper(final ArgumentCommandNode pureNode, final ArgumentType pureArgumentType, final ArgumentType possiblyWrappedNativeArgumentType, @Nullable SuggestionProvider argumentTypeSuggestionProvider) { ++ validatePrimitiveType(possiblyWrappedNativeArgumentType); ++ final CommandNode redirectNode = pureNode.getRedirect() == null ? null : this.unwrapNode(pureNode.getRedirect()); ++ // If there is already a custom suggestion provider, ignore the suggestion provider from the argument type ++ final SuggestionProvider suggestionProvider = pureNode.getCustomSuggestions() != null ? pureNode.getCustomSuggestions() : argumentTypeSuggestionProvider; ++ ++ final ArgumentType nativeArgumentType = possiblyWrappedNativeArgumentType instanceof final VanillaArgumentProviderImpl.NativeWrapperArgumentType nativeWrapperArgumentType ? nativeWrapperArgumentType.nativeNmsArgumentType() : possiblyWrappedNativeArgumentType; ++ return new WrappedArgumentCommandNode<>(pureNode.getName(), pureArgumentType, nativeArgumentType, pureNode.getCommand(), pureNode.getRequirement(), redirectNode, pureNode.getRedirectModifier(), pureNode.isFork(), suggestionProvider); ++ } ++ ++ private CommandNode simpleUnwrap(final CommandNode node) { ++ return node.createBuilder() ++ .redirect(node.getRedirect() == null ? null : this.unwrapNode(node.getRedirect())) ++ .build(); ++ } ++ ++} +diff --git a/src/main/java/io/papermc/paper/command/brigadier/MessageComponentSerializerImpl.java b/src/main/java/io/papermc/paper/command/brigadier/MessageComponentSerializerImpl.java +new file mode 100644 +index 0000000000000000000000000000000000000000..0b33c6cf2366568641e6f2fd7f74fb74f6ea0145 +--- /dev/null ++++ b/src/main/java/io/papermc/paper/command/brigadier/MessageComponentSerializerImpl.java +@@ -0,0 +1,20 @@ ++package io.papermc.paper.command.brigadier; ++ ++import com.mojang.brigadier.Message; ++import io.papermc.paper.adventure.PaperAdventure; ++import net.kyori.adventure.text.Component; ++import net.minecraft.network.chat.ComponentUtils; ++import org.jetbrains.annotations.NotNull; ++ ++public final class MessageComponentSerializerImpl implements MessageComponentSerializer { ++ ++ @Override ++ public @NotNull Component deserialize(@NotNull Message input) { ++ return PaperAdventure.asAdventure(ComponentUtils.fromMessage(input)); ++ } ++ ++ @Override ++ public @NotNull Message serialize(@NotNull Component component) { ++ return PaperAdventure.asVanilla(component); ++ } ++} +diff --git a/src/main/java/io/papermc/paper/command/brigadier/PaperBrigadier.java b/src/main/java/io/papermc/paper/command/brigadier/PaperBrigadier.java +new file mode 100644 +index 0000000000000000000000000000000000000000..4acf7c3bcfbe61431bfbfa3c8addb33f671eb498 +--- /dev/null ++++ b/src/main/java/io/papermc/paper/command/brigadier/PaperBrigadier.java +@@ -0,0 +1,73 @@ ++package io.papermc.paper.command.brigadier; ++ ++import com.mojang.brigadier.CommandDispatcher; ++import com.mojang.brigadier.tree.CommandNode; ++import com.mojang.brigadier.tree.LiteralCommandNode; ++import io.papermc.paper.command.brigadier.bukkit.BukkitBrigForwardingMap; ++import io.papermc.paper.command.brigadier.bukkit.BukkitCommandNode; ++import net.minecraft.commands.CommandSource; ++import net.minecraft.commands.Commands; ++import net.minecraft.network.chat.CommonComponents; ++import net.minecraft.server.MinecraftServer; ++import net.minecraft.world.phys.Vec2; ++import net.minecraft.world.phys.Vec3; ++import org.bukkit.Bukkit; ++import org.bukkit.Server; ++import org.bukkit.command.Command; ++import org.bukkit.command.CommandMap; ++import org.bukkit.craftbukkit.command.VanillaCommandWrapper; ++ ++import java.util.Map; ++ ++public final class PaperBrigadier { ++ ++ @SuppressWarnings("DataFlowIssue") ++ static final net.minecraft.commands.CommandSourceStack DUMMY = new net.minecraft.commands.CommandSourceStack( ++ CommandSource.NULL, ++ Vec3.ZERO, ++ Vec2.ZERO, ++ null, ++ 4, ++ "", ++ CommonComponents.EMPTY, ++ null, ++ null ++ ); ++ ++ @SuppressWarnings({"unchecked", "rawtypes"}) ++ public static Command wrapNode(CommandNode node) { ++ if (!(node instanceof LiteralCommandNode)) { ++ throw new IllegalArgumentException("Unsure how to wrap a " + node); ++ } ++ ++ if (!(node instanceof PluginCommandNode pluginCommandNode)) { ++ return new VanillaCommandWrapper(null, node); ++ } ++ CommandNode argumentCommandNode = node; ++ if (argumentCommandNode.getRedirect() != null) { ++ argumentCommandNode = argumentCommandNode.getRedirect(); ++ } ++ ++ Map, String> map = PaperCommands.INSTANCE.getDispatcherInternal().getSmartUsage(argumentCommandNode, DUMMY); ++ String usage = map.isEmpty() ? pluginCommandNode.getUsageText() : pluginCommandNode.getUsageText() + " " + String.join("\n" + pluginCommandNode.getUsageText() + " ", map.values()); ++ return new PluginVanillaCommandWrapper(pluginCommandNode.getName(), pluginCommandNode.getDescription(), usage, pluginCommandNode.getAliases(), node, pluginCommandNode.getPlugin()); ++ } ++ ++ /* ++ Previously, Bukkit used one command dispatcher and ignored minecraft's reloading logic. ++ ++ In order to allow for legacy commands to be properly added, we will iterate through previous bukkit commands ++ in the old dispatcher and re-register them. ++ */ ++ @SuppressWarnings({"unchecked", "rawtypes"}) ++ public static void moveBukkitCommands(Commands before, Commands after) { ++ CommandDispatcher erasedDispatcher = before.getDispatcher(); ++ ++ for (Object node : erasedDispatcher.getRoot().getChildren()) { ++ if (node instanceof CommandNode commandNode && commandNode.getCommand() instanceof BukkitCommandNode.BukkitBrigCommand) { ++ after.getDispatcher().getRoot().removeCommand(((CommandNode) node).getName()); // Remove already existing commands ++ after.getDispatcher().getRoot().addChild((CommandNode) node); ++ } ++ } ++ } ++} +diff --git a/src/main/java/io/papermc/paper/command/brigadier/PaperCommandSourceStack.java b/src/main/java/io/papermc/paper/command/brigadier/PaperCommandSourceStack.java +new file mode 100644 +index 0000000000000000000000000000000000000000..1b1642f306771f029e6214a2e2ebebb6ae6abc3e +--- /dev/null ++++ b/src/main/java/io/papermc/paper/command/brigadier/PaperCommandSourceStack.java +@@ -0,0 +1,63 @@ ++package io.papermc.paper.command.brigadier; ++ ++import com.destroystokyo.paper.brigadier.BukkitBrigadierCommandSource; ++import net.minecraft.world.level.Level; ++import net.minecraft.world.phys.Vec2; ++import net.minecraft.world.phys.Vec3; ++import org.bukkit.Location; ++import org.bukkit.command.CommandSender; ++import org.bukkit.entity.Entity; ++import org.jetbrains.annotations.NotNull; ++import org.jetbrains.annotations.Nullable; ++ ++public interface PaperCommandSourceStack extends CommandSourceStack, BukkitBrigadierCommandSource { ++ ++ net.minecraft.commands.CommandSourceStack getHandle(); ++ ++ @Override ++ default @NotNull Location getLocation() { ++ Vec2 rot = this.getHandle().getRotation(); ++ Vec3 pos = this.getHandle().getPosition(); ++ Level level = this.getHandle().getLevel(); ++ ++ return new Location(level.getWorld(), pos.x, pos.y, pos.z, rot.y, rot.x); ++ } ++ ++ @Override ++ @NotNull ++ default CommandSender getSender() { ++ return this.getHandle().getBukkitSender(); ++ } ++ ++ @Override ++ @Nullable ++ default Entity getExecutor() { ++ net.minecraft.world.entity.Entity nmsEntity = this.getHandle().getEntity(); ++ if (nmsEntity == null) { ++ return null; ++ } ++ ++ return nmsEntity.getBukkitEntity(); ++ } ++ ++ // OLD METHODS ++ @Override ++ default org.bukkit.entity.Entity getBukkitEntity() { ++ return this.getExecutor(); ++ } ++ ++ @Override ++ default org.bukkit.World getBukkitWorld() { ++ return this.getLocation().getWorld(); ++ } ++ ++ @Override ++ default org.bukkit.Location getBukkitLocation() { ++ return this.getLocation(); ++ } ++ ++ @Override ++ default CommandSender getBukkitSender() { ++ return this.getSender(); ++ } ++} +diff --git a/src/main/java/io/papermc/paper/command/brigadier/PaperCommands.java b/src/main/java/io/papermc/paper/command/brigadier/PaperCommands.java +new file mode 100644 +index 0000000000000000000000000000000000000000..95d3b42cbe2184b0a04d941f27f7a6e643ef59be +--- /dev/null ++++ b/src/main/java/io/papermc/paper/command/brigadier/PaperCommands.java +@@ -0,0 +1,204 @@ ++package io.papermc.paper.command.brigadier; ++ ++import com.google.common.base.Preconditions; ++import com.mojang.brigadier.CommandDispatcher; ++import com.mojang.brigadier.arguments.StringArgumentType; ++import com.mojang.brigadier.builder.LiteralArgumentBuilder; ++import com.mojang.brigadier.suggestion.SuggestionsBuilder; ++import com.mojang.brigadier.tree.CommandNode; ++import com.mojang.brigadier.tree.LiteralCommandNode; ++import io.papermc.paper.command.brigadier.bukkit.BukkitCommandNode; ++import io.papermc.paper.plugin.configuration.PluginMeta; ++import io.papermc.paper.plugin.lifecycle.event.LifecycleEventOwner; ++import io.papermc.paper.plugin.lifecycle.event.registrar.PaperRegistrar; ++import java.util.ArrayList; ++import java.util.Collection; ++import java.util.Collections; ++import java.util.HashSet; ++import java.util.List; ++import java.util.Locale; ++import java.util.Set; ++import net.minecraft.commands.CommandBuildContext; ++import org.apache.commons.lang3.ArrayUtils; ++import org.apache.commons.lang3.StringUtils; ++import org.checkerframework.checker.nullness.qual.MonotonicNonNull; ++import org.checkerframework.checker.nullness.qual.NonNull; ++import org.checkerframework.checker.nullness.qual.Nullable; ++import org.checkerframework.framework.qual.DefaultQualifier; ++import org.jetbrains.annotations.NotNull; ++import org.jetbrains.annotations.Unmodifiable; ++ ++import static java.util.Objects.requireNonNull; ++ ++@DefaultQualifier(NonNull.class) ++public class PaperCommands implements Commands, PaperRegistrar { ++ ++ public static final PaperCommands INSTANCE = new PaperCommands(); ++ ++ private @Nullable LifecycleEventOwner currentContext; ++ private @MonotonicNonNull CommandDispatcher dispatcher; ++ private @MonotonicNonNull CommandBuildContext buildContext; ++ private boolean invalid = false; ++ ++ @Override ++ public void setCurrentContext(final @Nullable LifecycleEventOwner context) { ++ this.currentContext = context; ++ } ++ ++ public void setDispatcher(final net.minecraft.commands.Commands commands, final CommandBuildContext commandBuildContext) { ++ this.invalid = false; ++ this.dispatcher = new CommandDispatcher<>(new ApiMirrorRootNode() { ++ @Override ++ public CommandDispatcher getDispatcher() { ++ return commands.getDispatcher(); ++ } ++ }); ++ this.buildContext = commandBuildContext; ++ } ++ ++ public void setValid() { ++ this.invalid = false; ++ } ++ ++ @Override ++ public void invalidate() { ++ this.invalid = true; ++ } ++ ++ // use this method internally as it bypasses the valid check ++ public CommandDispatcher getDispatcherInternal() { ++ Preconditions.checkState(this.dispatcher != null, "the dispatcher hasn't been set yet"); ++ return this.dispatcher; ++ } ++ ++ public CommandBuildContext getBuildContext() { ++ Preconditions.checkState(this.buildContext != null, "the build context hasn't been set yet"); ++ return this.buildContext; ++ } ++ ++ @Override ++ public CommandDispatcher getDispatcher() { ++ Preconditions.checkState(!this.invalid && this.dispatcher != null, "cannot access the dispatcher in this context"); ++ return this.dispatcher; ++ } ++ ++ @Override ++ public @Unmodifiable Set register(final LiteralCommandNode node, final @Nullable String description, final Collection aliases) { ++ return this.register(requireNonNull(this.currentContext, "No lifecycle owner context is set").getPluginMeta(), node, description, aliases); ++ } ++ ++ @Override ++ public @Unmodifiable Set register(final PluginMeta pluginMeta, final LiteralCommandNode node, final @Nullable String description, final Collection aliases) { ++ return this.registerWithFlags(pluginMeta, node, description, aliases, Set.of()); ++ } ++ ++ @Override ++ public @Unmodifiable Set registerWithFlags(@NotNull final PluginMeta pluginMeta, @NotNull final LiteralCommandNode node, @org.jetbrains.annotations.Nullable final String description, @NotNull final Collection aliases, @NotNull final Set flags) { ++ final boolean hasFlattenRedirectFlag = flags.contains(CommandRegistrationFlag.FLATTEN_ALIASES); ++ final String identifier = pluginMeta.getName().toLowerCase(Locale.ROOT); ++ final String literal = node.getLiteral(); ++ final PluginCommandNode pluginLiteral = new PluginCommandNode(identifier + ":" + literal, pluginMeta, node, description); // Treat the keyed version of the command as the root ++ ++ final Set registeredLabels = new HashSet<>(aliases.size() * 2 + 2); ++ ++ if (this.registerIntoDispatcher(pluginLiteral, true)) { ++ registeredLabels.add(pluginLiteral.getLiteral()); ++ } ++ if (this.registerRedirect(literal, pluginMeta, pluginLiteral, description, true, hasFlattenRedirectFlag)) { // Plugin commands should override vanilla commands ++ registeredLabels.add(literal); ++ } ++ ++ // Add aliases ++ final List registeredAliases = new ArrayList<>(aliases.size() * 2); ++ for (final String alias : aliases) { ++ if (this.registerRedirect(alias, pluginMeta, pluginLiteral, description, false, hasFlattenRedirectFlag)) { ++ registeredAliases.add(alias); ++ } ++ if (this.registerRedirect(identifier + ":" + alias, pluginMeta, pluginLiteral, description, false, hasFlattenRedirectFlag)) { ++ registeredAliases.add(identifier + ":" + alias); ++ } ++ } ++ ++ if (!registeredAliases.isEmpty()) { ++ pluginLiteral.setAliases(registeredAliases); ++ } ++ ++ registeredLabels.addAll(registeredAliases); ++ return registeredLabels.isEmpty() ? Collections.emptySet() : Collections.unmodifiableSet(registeredLabels); ++ } ++ ++ private boolean registerRedirect(final String aliasLiteral, final PluginMeta plugin, final PluginCommandNode redirectTo, final @Nullable String description, final boolean override, boolean hasFlattenRedirectFlag) { ++ final LiteralCommandNode redirect; ++ if (redirectTo.getChildren().isEmpty() || hasFlattenRedirectFlag) { ++ redirect = Commands.literal(aliasLiteral) ++ .executes(redirectTo.getCommand()) ++ .requires(redirectTo.getRequirement()) ++ .build(); ++ ++ for (final CommandNode child : redirectTo.getChildren()) { ++ redirect.addChild(child); ++ } ++ } else { ++ redirect = Commands.literal(aliasLiteral) ++ .executes(redirectTo.getCommand()) ++ .redirect(redirectTo) ++ .requires(redirectTo.getRequirement()) ++ .build(); ++ } ++ ++ return this.registerIntoDispatcher(new PluginCommandNode(aliasLiteral, plugin, redirect, description), override); ++ } ++ ++ private boolean registerIntoDispatcher(final PluginCommandNode node, boolean override) { ++ final @Nullable CommandNode existingChild = this.getDispatcher().getRoot().getChild(node.getLiteral()); ++ if (existingChild != null && !(existingChild instanceof PluginCommandNode) && !(existingChild instanceof BukkitCommandNode)) { ++ override = true; // override vanilla commands ++ } ++ if (existingChild == null || override) { // Avoid merging behavior. Maybe something to look into in the future ++ if (override) { ++ this.getDispatcher().getRoot().removeCommand(node.getLiteral()); ++ } ++ this.getDispatcher().getRoot().addChild(node); ++ return true; ++ } ++ ++ return false; ++ } ++ ++ @Override ++ public @Unmodifiable Set register(final String label, final @Nullable String description, final Collection aliases, final BasicCommand basicCommand) { ++ return this.register(requireNonNull(this.currentContext, "No lifecycle owner context is set").getPluginMeta(), label, description, aliases, basicCommand); ++ } ++ ++ @Override ++ public @Unmodifiable Set register(final PluginMeta pluginMeta, final String label, final @Nullable String description, final Collection aliases, final BasicCommand basicCommand) { ++ final LiteralArgumentBuilder builder = Commands.literal(label) ++ .requires(stack -> basicCommand.canUse(stack.getSender())) ++ .then( ++ Commands.argument("args", StringArgumentType.greedyString()) ++ .suggests((context, suggestionsBuilder) -> { ++ String[] args = StringUtils.split(suggestionsBuilder.getRemaining()); ++ if (suggestionsBuilder.getRemaining().endsWith(" ")) { ++ // if there is trailing whitespace, we should add an empty argument to signify ++ // that there may be more, but no characters have been typed yet ++ args = ArrayUtils.add(args, ""); ++ } ++ final SuggestionsBuilder offsetSuggestionsBuilder = suggestionsBuilder.createOffset(suggestionsBuilder.getInput().lastIndexOf(' ') + 1); ++ ++ final Collection suggestions = basicCommand.suggest(context.getSource(), args); ++ suggestions.forEach(offsetSuggestionsBuilder::suggest); ++ return offsetSuggestionsBuilder.buildFuture(); ++ }) ++ .executes((stack) -> { ++ basicCommand.execute(stack.getSource(), StringUtils.split(stack.getArgument("args", String.class), ' ')); ++ return com.mojang.brigadier.Command.SINGLE_SUCCESS; ++ }) ++ ) ++ .executes((stack) -> { ++ basicCommand.execute(stack.getSource(), new String[0]); ++ return com.mojang.brigadier.Command.SINGLE_SUCCESS; ++ }); ++ ++ return this.register(pluginMeta, builder.build(), description, aliases); ++ } ++} +diff --git a/src/main/java/io/papermc/paper/command/brigadier/PluginCommandNode.java b/src/main/java/io/papermc/paper/command/brigadier/PluginCommandNode.java +new file mode 100644 +index 0000000000000000000000000000000000000000..3a9f58873b83f10ba354ae4968c4ab0632662439 +--- /dev/null ++++ b/src/main/java/io/papermc/paper/command/brigadier/PluginCommandNode.java +@@ -0,0 +1,50 @@ ++package io.papermc.paper.command.brigadier; ++ ++import com.mojang.brigadier.tree.CommandNode; ++import com.mojang.brigadier.tree.LiteralCommandNode; ++import java.util.Collections; ++import java.util.List; ++import java.util.Objects; ++import io.papermc.paper.plugin.configuration.PluginMeta; ++import org.bukkit.Bukkit; ++import org.bukkit.plugin.Plugin; ++import org.jetbrains.annotations.NotNull; ++import org.jetbrains.annotations.Nullable; ++ ++public class PluginCommandNode extends LiteralCommandNode { ++ ++ private final PluginMeta plugin; ++ private final String description; ++ private List aliases = Collections.emptyList(); ++ ++ public PluginCommandNode(final @NotNull String literal, final @NotNull PluginMeta plugin, final @NotNull LiteralCommandNode rootLiteral, final @Nullable String description) { ++ super( ++ literal, rootLiteral.getCommand(), rootLiteral.getRequirement(), ++ rootLiteral.getRedirect(), rootLiteral.getRedirectModifier(), rootLiteral.isFork() ++ ); ++ this.plugin = plugin; ++ this.description = description; ++ ++ for (CommandNode argument : rootLiteral.getChildren()) { ++ this.addChild(argument); ++ } ++ } ++ ++ @NotNull ++ public Plugin getPlugin() { ++ return Objects.requireNonNull(Bukkit.getPluginManager().getPlugin(this.plugin.getName())); ++ } ++ ++ @NotNull ++ public String getDescription() { ++ return this.description; ++ } ++ ++ public void setAliases(List aliases) { ++ this.aliases = aliases; ++ } ++ ++ public List getAliases() { ++ return this.aliases; ++ } ++} +diff --git a/src/main/java/io/papermc/paper/command/brigadier/PluginVanillaCommandWrapper.java b/src/main/java/io/papermc/paper/command/brigadier/PluginVanillaCommandWrapper.java +new file mode 100644 +index 0000000000000000000000000000000000000000..cf8359af60601d7917e77fd06a00b64992a85953 +--- /dev/null ++++ b/src/main/java/io/papermc/paper/command/brigadier/PluginVanillaCommandWrapper.java +@@ -0,0 +1,46 @@ ++package io.papermc.paper.command.brigadier; ++ ++import com.mojang.brigadier.tree.CommandNode; ++import net.minecraft.commands.CommandSourceStack; ++import net.minecraft.commands.Commands; ++import org.bukkit.command.Command; ++import org.bukkit.command.PluginIdentifiableCommand; ++import org.bukkit.craftbukkit.command.VanillaCommandWrapper; ++import org.bukkit.plugin.Plugin; ++import org.jetbrains.annotations.NotNull; ++ ++import java.util.List; ++ ++// Exists to that /help can show the plugin ++public class PluginVanillaCommandWrapper extends VanillaCommandWrapper implements PluginIdentifiableCommand { ++ ++ private final Plugin plugin; ++ private final List alises; ++ ++ public PluginVanillaCommandWrapper(String name, String description, String usageMessage, List aliases, CommandNode vanillaCommand, Plugin plugin) { ++ super(name, description, usageMessage, aliases, vanillaCommand); ++ this.plugin = plugin; ++ this.alises = aliases; ++ } ++ ++ @Override ++ public @NotNull List getAliases() { ++ return this.alises; ++ } ++ ++ @Override ++ public @NotNull Command setAliases(@NotNull List aliases) { ++ return this; ++ } ++ ++ @Override ++ public @NotNull Plugin getPlugin() { ++ return this.plugin; ++ } ++ ++ // Show in help menu! ++ @Override ++ public boolean isRegistered() { ++ return true; ++ } ++} +diff --git a/src/main/java/io/papermc/paper/command/brigadier/ShadowBrigNode.java b/src/main/java/io/papermc/paper/command/brigadier/ShadowBrigNode.java +new file mode 100644 +index 0000000000000000000000000000000000000000..895addef908e09d527e4bc9210599e8827c53807 +--- /dev/null ++++ b/src/main/java/io/papermc/paper/command/brigadier/ShadowBrigNode.java +@@ -0,0 +1,35 @@ ++package io.papermc.paper.command.brigadier; ++ ++import com.mojang.brigadier.tree.CommandNode; ++import com.mojang.brigadier.tree.LiteralCommandNode; ++ ++import java.util.Collection; ++ ++public class ShadowBrigNode extends LiteralCommandNode { ++ ++ private final CommandNode handle; ++ ++ public ShadowBrigNode(CommandNode node) { ++ super(node.getName(), context -> 0, (s) -> false, node.getRedirect() == null ? null : new ShadowBrigNode(node.getRedirect()), null, node.isFork()); ++ this.handle = node; ++ } ++ ++ @Override ++ public Collection> getChildren() { ++ throw new UnsupportedOperationException("Cannot retrieve children from this node."); ++ } ++ ++ @Override ++ public CommandNode getChild(String name) { ++ throw new UnsupportedOperationException("Cannot retrieve children from this node."); ++ } ++ ++ @Override ++ public void addChild(CommandNode node) { ++ throw new UnsupportedOperationException("Cannot modify children for this node."); ++ } ++ ++ public CommandNode getHandle() { ++ return this.handle; ++ } ++} +diff --git a/src/main/java/io/papermc/paper/command/brigadier/argument/SignedMessageResolverImpl.java b/src/main/java/io/papermc/paper/command/brigadier/argument/SignedMessageResolverImpl.java +new file mode 100644 +index 0000000000000000000000000000000000000000..07a23be2cd7b4592108dee0ae223e71b1d00cec9 +--- /dev/null ++++ b/src/main/java/io/papermc/paper/command/brigadier/argument/SignedMessageResolverImpl.java +@@ -0,0 +1,30 @@ ++package io.papermc.paper.command.brigadier.argument; ++ ++import com.mojang.brigadier.context.CommandContext; ++import com.mojang.brigadier.exceptions.CommandSyntaxException; ++import java.util.concurrent.CompletableFuture; ++import net.kyori.adventure.chat.SignedMessage; ++import net.minecraft.commands.CommandSourceStack; ++import net.minecraft.commands.arguments.MessageArgument; ++import org.jspecify.annotations.NullMarked; ++ ++@NullMarked ++public record SignedMessageResolverImpl(MessageArgument.Message message) implements SignedMessageResolver { ++ ++ @Override ++ public String content() { ++ return this.message.text(); ++ } ++ ++ @SuppressWarnings({"rawtypes", "unchecked"}) ++ @Override ++ public CompletableFuture resolveSignedMessage(final String argumentName, final CommandContext erased) throws CommandSyntaxException { ++ final CompletableFuture future = new CompletableFuture<>(); ++ ++ final MessageArgument.Message response = ((CommandContext) erased).getArgument(argumentName, SignedMessageResolverImpl.class).message; ++ MessageArgument.resolveChatMessage(response, erased, argumentName, (message) -> { ++ future.complete(message.adventureView()); ++ }); ++ return future; ++ } ++} +diff --git a/src/main/java/io/papermc/paper/command/brigadier/argument/VanillaArgumentProviderImpl.java b/src/main/java/io/papermc/paper/command/brigadier/argument/VanillaArgumentProviderImpl.java +new file mode 100644 +index 0000000000000000000000000000000000000000..38fb7d13abfcb55fe4a132b9b27e0c91f8c3d891 +--- /dev/null ++++ b/src/main/java/io/papermc/paper/command/brigadier/argument/VanillaArgumentProviderImpl.java +@@ -0,0 +1,366 @@ ++package io.papermc.paper.command.brigadier.argument; ++ ++import com.destroystokyo.paper.profile.CraftPlayerProfile; ++import com.google.common.collect.Collections2; ++import com.google.common.collect.Lists; ++import com.google.common.collect.Range; ++import com.mojang.brigadier.StringReader; ++import com.mojang.brigadier.arguments.ArgumentType; ++import com.mojang.brigadier.context.CommandContext; ++import com.mojang.brigadier.exceptions.CommandSyntaxException; ++import com.mojang.brigadier.suggestion.Suggestions; ++import com.mojang.brigadier.suggestion.SuggestionsBuilder; ++import io.papermc.paper.adventure.PaperAdventure; ++import io.papermc.paper.command.brigadier.PaperCommands; ++import io.papermc.paper.command.brigadier.argument.predicate.ItemStackPredicate; ++import io.papermc.paper.command.brigadier.argument.range.DoubleRangeProvider; ++import io.papermc.paper.command.brigadier.argument.range.IntegerRangeProvider; ++import io.papermc.paper.command.brigadier.argument.range.RangeProvider; ++import io.papermc.paper.command.brigadier.argument.resolvers.BlockPositionResolver; ++import io.papermc.paper.command.brigadier.argument.resolvers.FinePositionResolver; ++import io.papermc.paper.command.brigadier.argument.resolvers.PlayerProfileListResolver; ++import io.papermc.paper.command.brigadier.argument.resolvers.selector.EntitySelectorArgumentResolver; ++import io.papermc.paper.command.brigadier.argument.resolvers.selector.PlayerSelectorArgumentResolver; ++import io.papermc.paper.entity.LookAnchor; ++import io.papermc.paper.registry.PaperRegistries; ++import io.papermc.paper.registry.RegistryAccess; ++import io.papermc.paper.registry.RegistryKey; ++import io.papermc.paper.registry.TypedKey; ++import io.papermc.paper.util.MCUtil; ++import java.util.Collection; ++import java.util.Collections; ++import java.util.List; ++import java.util.UUID; ++import java.util.concurrent.CompletableFuture; ++import java.util.function.Function; ++import net.kyori.adventure.key.Key; ++import net.kyori.adventure.text.Component; ++import net.kyori.adventure.text.format.NamedTextColor; ++import net.kyori.adventure.text.format.Style; ++import net.minecraft.advancements.critereon.MinMaxBounds; ++import net.minecraft.commands.CommandSourceStack; ++import net.minecraft.commands.arguments.ColorArgument; ++import net.minecraft.commands.arguments.ComponentArgument; ++import net.minecraft.commands.arguments.DimensionArgument; ++import net.minecraft.commands.arguments.EntityAnchorArgument; ++import net.minecraft.commands.arguments.EntityArgument; ++import net.minecraft.commands.arguments.GameModeArgument; ++import net.minecraft.commands.arguments.GameProfileArgument; ++import net.minecraft.commands.arguments.HeightmapTypeArgument; ++import net.minecraft.commands.arguments.MessageArgument; ++import net.minecraft.commands.arguments.ObjectiveCriteriaArgument; ++import net.minecraft.commands.arguments.RangeArgument; ++import net.minecraft.commands.arguments.ResourceArgument; ++import net.minecraft.commands.arguments.ResourceKeyArgument; ++import net.minecraft.commands.arguments.ResourceLocationArgument; ++import net.minecraft.commands.arguments.ScoreboardSlotArgument; ++import net.minecraft.commands.arguments.StyleArgument; ++import net.minecraft.commands.arguments.TemplateMirrorArgument; ++import net.minecraft.commands.arguments.TemplateRotationArgument; ++import net.minecraft.commands.arguments.TimeArgument; ++import net.minecraft.commands.arguments.UuidArgument; ++import net.minecraft.commands.arguments.blocks.BlockStateArgument; ++import net.minecraft.commands.arguments.coordinates.BlockPosArgument; ++import net.minecraft.commands.arguments.coordinates.Vec3Argument; ++import net.minecraft.commands.arguments.item.ItemArgument; ++import net.minecraft.commands.arguments.item.ItemPredicateArgument; ++import net.minecraft.core.BlockPos; ++import net.minecraft.core.registries.Registries; ++import net.minecraft.resources.ResourceKey; ++import net.minecraft.server.MinecraftServer; ++import net.minecraft.server.level.ServerLevel; ++import net.minecraft.server.level.ServerPlayer; ++import net.minecraft.world.level.Level; ++import net.minecraft.world.phys.Vec3; ++import org.bukkit.GameMode; ++import org.bukkit.HeightMap; ++import org.bukkit.Keyed; ++import org.bukkit.NamespacedKey; ++import org.bukkit.World; ++import org.bukkit.block.BlockState; ++import org.bukkit.block.structure.Mirror; ++import org.bukkit.block.structure.StructureRotation; ++import org.bukkit.craftbukkit.CraftHeightMap; ++import org.bukkit.craftbukkit.CraftRegistry; ++import org.bukkit.craftbukkit.block.CraftBlockStates; ++import org.bukkit.craftbukkit.inventory.CraftItemStack; ++import org.bukkit.craftbukkit.scoreboard.CraftCriteria; ++import org.bukkit.craftbukkit.scoreboard.CraftScoreboardTranslations; ++import org.bukkit.craftbukkit.util.CraftNamespacedKey; ++import org.bukkit.inventory.ItemStack; ++import org.bukkit.scoreboard.Criteria; ++import org.bukkit.scoreboard.DisplaySlot; ++import org.checkerframework.checker.nullness.qual.NonNull; ++import org.checkerframework.checker.nullness.qual.Nullable; ++import org.checkerframework.framework.qual.DefaultQualifier; ++ ++import static java.util.Objects.requireNonNull; ++ ++@DefaultQualifier(NonNull.class) ++public class VanillaArgumentProviderImpl implements VanillaArgumentProvider { ++ ++ @Override ++ public ArgumentType entity() { ++ return this.wrap(EntityArgument.entity(), (result) -> sourceStack -> { ++ return List.of(result.findSingleEntity((CommandSourceStack) sourceStack).getBukkitEntity()); ++ }); ++ } ++ ++ @Override ++ public ArgumentType entities() { ++ return this.wrap(EntityArgument.entities(), (result) -> sourceStack -> { ++ return Lists.transform(result.findEntities((CommandSourceStack) sourceStack), net.minecraft.world.entity.Entity::getBukkitEntity); ++ }); ++ } ++ ++ @Override ++ public ArgumentType player() { ++ return this.wrap(EntityArgument.player(), (result) -> sourceStack -> { ++ return List.of(result.findSinglePlayer((CommandSourceStack) sourceStack).getBukkitEntity()); ++ }); ++ } ++ ++ @Override ++ public ArgumentType players() { ++ return this.wrap(EntityArgument.players(), (result) -> sourceStack -> { ++ return Lists.transform(result.findPlayers((CommandSourceStack) sourceStack), ServerPlayer::getBukkitEntity); ++ }); ++ } ++ ++ @Override ++ public ArgumentType playerProfiles() { ++ return this.wrap(GameProfileArgument.gameProfile(), result -> { ++ if (result instanceof GameProfileArgument.SelectorResult) { ++ return sourceStack -> Collections.unmodifiableCollection(Collections2.transform(result.getNames((CommandSourceStack) sourceStack), CraftPlayerProfile::new)); ++ } else { ++ return sourceStack -> Collections.unmodifiableCollection(Collections2.transform(result.getNames((CommandSourceStack) sourceStack), CraftPlayerProfile::new)); ++ } ++ }); ++ } ++ ++ @Override ++ public ArgumentType blockPosition() { ++ return this.wrap(BlockPosArgument.blockPos(), (result) -> sourceStack -> { ++ final BlockPos pos = result.getBlockPos((CommandSourceStack) sourceStack); ++ ++ return MCUtil.toPosition(pos); ++ }); ++ } ++ ++ @Override ++ public ArgumentType finePosition(final boolean centerIntegers) { ++ return this.wrap(Vec3Argument.vec3(centerIntegers), (result) -> sourceStack -> { ++ final Vec3 vec3 = result.getPosition((CommandSourceStack) sourceStack); ++ ++ return MCUtil.toPosition(vec3); ++ }); ++ } ++ ++ @Override ++ public ArgumentType blockState() { ++ return this.wrap(BlockStateArgument.block(PaperCommands.INSTANCE.getBuildContext()), (result) -> { ++ return CraftBlockStates.getBlockState(CraftRegistry.getMinecraftRegistry(), BlockPos.ZERO, result.getState(), result.tag); ++ }); ++ } ++ ++ @Override ++ public ArgumentType itemStack() { ++ return this.wrap(ItemArgument.item(PaperCommands.INSTANCE.getBuildContext()), (result) -> { ++ return CraftItemStack.asBukkitCopy(result.createItemStack(1, true)); ++ }); ++ } ++ ++ @Override ++ public ArgumentType itemStackPredicate() { ++ return this.wrap(ItemPredicateArgument.itemPredicate(PaperCommands.INSTANCE.getBuildContext()), type -> itemStack -> type.test(CraftItemStack.asNMSCopy(itemStack))); ++ } ++ ++ @Override ++ public ArgumentType namedColor() { ++ return this.wrap(ColorArgument.color(), result -> ++ requireNonNull( ++ NamedTextColor.namedColor( ++ requireNonNull(result.getColor(), () -> result + " didn't have a color") ++ ), ++ () -> result.getColor() + " didn't map to an adventure named color" ++ ) ++ ); ++ } ++ ++ @Override ++ public ArgumentType component() { ++ return this.wrap(ComponentArgument.textComponent(PaperCommands.INSTANCE.getBuildContext()), PaperAdventure::asAdventure); ++ } ++ ++ @Override ++ public ArgumentType