From 6d4e2357846217992d66014d50af17d826b10089 Mon Sep 17 00:00:00 2001 From: Bjarne Koll Date: Fri, 14 Jun 2024 11:59:44 +0200 Subject: 988 --- patches/server/0009-MC-Utils.patch | 2 +- patches/server/0023-Timings-v2.patch | 2 +- patches/server/0066-Chunk-Save-Reattempt.patch | 6 +- ...ise-RegionFileCache-and-make-configurable.patch | 4 +- .../0165-PlayerNaturallySpawnCreaturesEvent.patch | 2 +- ...Entities-option-to-debug-dupe-uuid-issues.patch | 2 +- .../server/0313-Tracking-Range-Improvements.patch | 2 +- ...ouble-PlayerChunkMap-adds-crashing-server.patch | 2 +- ...erJoinEvent-when-Player-is-actually-ready.patch | 2 +- ...613-Oprimise-map-impl-for-tracked-players.patch | 2 +- ...chunk-data-to-disk-if-it-serializes-witho.patch | 8 +- .../server/0755-Fix-a-bunch-of-vanilla-bugs.patch | 2 +- .../0784-Player-Entity-Tracking-Events.patch | 2 +- ...ble-entity-tracking-range-by-Y-coordinate.patch | 2 +- ...-check-if-we-can-see-non-visible-entities.patch | 2 +- ...uce-allocation-of-Vec3D-by-entity-tracker.patch | 2 +- ...rock-and-End-Portal-Frames-from-being-des.patch | 169 ++ ...ripwire-disarming-not-working-as-intended.patch | 22 + ...config-for-mobs-immune-to-default-effects.patch | 44 + .../server/0964-Deep-clone-nbt-tags-in-PDC.patch | 45 + .../0965-Support-old-UUID-format-for-NBT.patch | 63 + .../0966-Fix-shield-disable-inconsistency.patch | 22 + ...-lose-removed-data-components-in-ItemMeta.patch | 47 + ...Handle-Large-Packets-disconnecting-client.patch | 135 + patches/server/0969-Fix-ItemFlags.patch | 200 ++ ...x-helmet-damage-reduction-inconsistencies.patch | 21 + ...anilla-handling-of-LivingEntity-actuallyH.patch | 34 + ...improve-checking-handled-tags-in-itemmeta.patch | 806 ++++++ patches/server/0973-General-ItemMeta-fixes.patch | 1494 +++++++++++ .../0974-Expose-hasColor-to-leather-armor.patch | 38 + ...-Added-API-to-get-player-ha-proxy-address.patch | 56 + patches/server/0976-More-Chest-Block-API.patch | 73 + ...int-data-component-type-on-encoding-error.patch | 24 + .../server/0978-Brigadier-based-command-API.patch | 2831 ++++++++++++++++++++ .../server/0979-Fix-issues-with-Recipe-API.patch | 116 + .../0980-Fix-equipment-slot-and-group-API.patch | 114 + ...kkit-plugin-to-use-Paper-PluginLoader-API.patch | 130 + ...ding-oversized-item-data-in-equipment-and.patch | 228 ++ ...-Prevent-NPE-if-hooked-entity-was-cleared.patch | 24 + ...ncelling-BlockPlaceEvent-calling-onRemove.patch | 47 + .../0985-Add-missing-fishing-event-state.patch | 26 + ...Deprecate-InvAction-HOTBAR_MOVE_AND_READD.patch | 26 + ...-disconnect-packet-in-phases-where-it-doe.patch | 21 + patches/server/0988-Adopt-MaterialRerouting.patch | 134 + ...rock-and-End-Portal-Frames-from-being-des.patch | 169 -- ...ripwire-disarming-not-working-as-intended.patch | 22 - ...config-for-mobs-immune-to-default-effects.patch | 44 - .../server/1026-Deep-clone-nbt-tags-in-PDC.patch | 45 - .../1027-Support-old-UUID-format-for-NBT.patch | 63 - .../1028-Fix-shield-disable-inconsistency.patch | 22 - ...-lose-removed-data-components-in-ItemMeta.patch | 47 - ...31-Add-experimental-improved-give-command.patch | 255 -- ...Handle-Large-Packets-disconnecting-client.patch | 135 - patches/unapplied/server/1033-Fix-ItemFlags.patch | 200 -- ...x-helmet-damage-reduction-inconsistencies.patch | 21 - ...anilla-handling-of-LivingEntity-actuallyH.patch | 34 - ...improve-checking-handled-tags-in-itemmeta.patch | 804 ------ .../server/1037-General-ItemMeta-fixes.patch | 1494 ----------- .../1038-Expose-hasColor-to-leather-armor.patch | 38 - ...-Added-API-to-get-player-ha-proxy-address.patch | 56 - .../server/1040-More-Chest-Block-API.patch | 73 - ...int-data-component-type-on-encoding-error.patch | 24 - .../server/1043-Brigadier-based-command-API.patch | 2831 -------------------- .../server/1044-Fix-issues-with-Recipe-API.patch | 116 - .../1045-Fix-equipment-slot-and-group-API.patch | 114 - ...kkit-plugin-to-use-Paper-PluginLoader-API.patch | 130 - ...ding-oversized-item-data-in-equipment-and.patch | 228 -- ...-Prevent-NPE-if-hooked-entity-was-cleared.patch | 24 - ...ncelling-BlockPlaceEvent-calling-onRemove.patch | 47 - .../1050-Add-missing-fishing-event-state.patch | 26 - ...Deprecate-InvAction-HOTBAR_MOVE_AND_READD.patch | 26 - ...-disconnect-packet-in-phases-where-it-doe.patch | 21 - .../server/1053-Adopt-MaterialRerouting.patch | 134 - 73 files changed, 7012 insertions(+), 7265 deletions(-) create mode 100644 patches/server/0961-Protect-Bedrock-and-End-Portal-Frames-from-being-des.patch create mode 100644 patches/server/0962-Fix-tripwire-disarming-not-working-as-intended.patch create mode 100644 patches/server/0963-Add-config-for-mobs-immune-to-default-effects.patch create mode 100644 patches/server/0964-Deep-clone-nbt-tags-in-PDC.patch create mode 100644 patches/server/0965-Support-old-UUID-format-for-NBT.patch create mode 100644 patches/server/0966-Fix-shield-disable-inconsistency.patch create mode 100644 patches/server/0967-Don-t-lose-removed-data-components-in-ItemMeta.patch create mode 100644 patches/server/0968-Handle-Large-Packets-disconnecting-client.patch create mode 100644 patches/server/0969-Fix-ItemFlags.patch create mode 100644 patches/server/0970-Fix-helmet-damage-reduction-inconsistencies.patch create mode 100644 patches/server/0971-Revert-to-vanilla-handling-of-LivingEntity-actuallyH.patch create mode 100644 patches/server/0972-improve-checking-handled-tags-in-itemmeta.patch create mode 100644 patches/server/0973-General-ItemMeta-fixes.patch create mode 100644 patches/server/0974-Expose-hasColor-to-leather-armor.patch create mode 100644 patches/server/0975-Added-API-to-get-player-ha-proxy-address.patch create mode 100644 patches/server/0976-More-Chest-Block-API.patch create mode 100644 patches/server/0977-Print-data-component-type-on-encoding-error.patch create mode 100644 patches/server/0978-Brigadier-based-command-API.patch create mode 100644 patches/server/0979-Fix-issues-with-Recipe-API.patch create mode 100644 patches/server/0980-Fix-equipment-slot-and-group-API.patch create mode 100644 patches/server/0981-Allow-Bukkit-plugin-to-use-Paper-PluginLoader-API.patch create mode 100644 patches/server/0982-Prevent-sending-oversized-item-data-in-equipment-and.patch create mode 100644 patches/server/0983-Prevent-NPE-if-hooked-entity-was-cleared.patch create mode 100644 patches/server/0984-Fix-cancelling-BlockPlaceEvent-calling-onRemove.patch create mode 100644 patches/server/0985-Add-missing-fishing-event-state.patch create mode 100644 patches/server/0986-Deprecate-InvAction-HOTBAR_MOVE_AND_READD.patch create mode 100644 patches/server/0987-Fix-sending-disconnect-packet-in-phases-where-it-doe.patch create mode 100644 patches/server/0988-Adopt-MaterialRerouting.patch delete mode 100644 patches/unapplied/server/0994-Protect-Bedrock-and-End-Portal-Frames-from-being-des.patch delete mode 100644 patches/unapplied/server/1010-Fix-tripwire-disarming-not-working-as-intended.patch delete mode 100644 patches/unapplied/server/1025-Add-config-for-mobs-immune-to-default-effects.patch delete mode 100644 patches/unapplied/server/1026-Deep-clone-nbt-tags-in-PDC.patch delete mode 100644 patches/unapplied/server/1027-Support-old-UUID-format-for-NBT.patch delete mode 100644 patches/unapplied/server/1028-Fix-shield-disable-inconsistency.patch delete mode 100644 patches/unapplied/server/1030-Don-t-lose-removed-data-components-in-ItemMeta.patch delete mode 100644 patches/unapplied/server/1031-Add-experimental-improved-give-command.patch delete mode 100644 patches/unapplied/server/1032-Handle-Large-Packets-disconnecting-client.patch delete mode 100644 patches/unapplied/server/1033-Fix-ItemFlags.patch delete mode 100644 patches/unapplied/server/1034-Fix-helmet-damage-reduction-inconsistencies.patch delete mode 100644 patches/unapplied/server/1035-Revert-to-vanilla-handling-of-LivingEntity-actuallyH.patch delete mode 100644 patches/unapplied/server/1036-improve-checking-handled-tags-in-itemmeta.patch delete mode 100644 patches/unapplied/server/1037-General-ItemMeta-fixes.patch delete mode 100644 patches/unapplied/server/1038-Expose-hasColor-to-leather-armor.patch delete mode 100644 patches/unapplied/server/1039-Added-API-to-get-player-ha-proxy-address.patch delete mode 100644 patches/unapplied/server/1040-More-Chest-Block-API.patch delete mode 100644 patches/unapplied/server/1041-Print-data-component-type-on-encoding-error.patch delete mode 100644 patches/unapplied/server/1043-Brigadier-based-command-API.patch delete mode 100644 patches/unapplied/server/1044-Fix-issues-with-Recipe-API.patch delete mode 100644 patches/unapplied/server/1045-Fix-equipment-slot-and-group-API.patch delete mode 100644 patches/unapplied/server/1046-Allow-Bukkit-plugin-to-use-Paper-PluginLoader-API.patch delete mode 100644 patches/unapplied/server/1047-Prevent-sending-oversized-item-data-in-equipment-and.patch delete mode 100644 patches/unapplied/server/1048-Prevent-NPE-if-hooked-entity-was-cleared.patch delete mode 100644 patches/unapplied/server/1049-Fix-cancelling-BlockPlaceEvent-calling-onRemove.patch delete mode 100644 patches/unapplied/server/1050-Add-missing-fishing-event-state.patch delete mode 100644 patches/unapplied/server/1051-Deprecate-InvAction-HOTBAR_MOVE_AND_READD.patch delete mode 100644 patches/unapplied/server/1052-Fix-sending-disconnect-packet-in-phases-where-it-doe.patch delete mode 100644 patches/unapplied/server/1053-Adopt-MaterialRerouting.patch diff --git a/patches/server/0009-MC-Utils.patch b/patches/server/0009-MC-Utils.patch index f3c8e4cada..962f14b733 100644 --- a/patches/server/0009-MC-Utils.patch +++ b/patches/server/0009-MC-Utils.patch @@ -6342,7 +6342,7 @@ index 5d4336210e11ee39521b4096a5f0874329053cdc..526d5b9bd6ce8eade59d3d3cf8bd7ad7 + // 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 419a27a8bdc8adfeb6ea89e3bfe1838a80d75a33..ce0d22452171857e3cf070bf01450a7653ec7142 100644 +index 5b920beb39dad8d392b4e5e12a89880720e41942..319f51eb8adde7584c74780ac0539f4b8ef8fe7f 100644 --- a/src/main/java/net/minecraft/server/level/ChunkMap.java +++ b/src/main/java/net/minecraft/server/level/ChunkMap.java @@ -170,6 +170,62 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider diff --git a/patches/server/0023-Timings-v2.patch b/patches/server/0023-Timings-v2.patch index 5a6f57a089..b94c0b1761 100644 --- a/patches/server/0023-Timings-v2.patch +++ b/patches/server/0023-Timings-v2.patch @@ -978,7 +978,7 @@ index d38ecbc208c34509eaf77751ac45d9ef51a5dce8..b51c3f8c485496734ea58c15377a1215 // 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 ce0d22452171857e3cf070bf01450a7653ec7142..6581566ca4e4fac0691e4f5851f8895d9ac7a38f 100644 +index 319f51eb8adde7584c74780ac0539f4b8ef8fe7f..ddadb0f13b96a39ec89cdaeea7bc02ee62ef2a06 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 @@ diff --git a/patches/server/0066-Chunk-Save-Reattempt.patch b/patches/server/0066-Chunk-Save-Reattempt.patch index 0347b2117c..120ee75594 100644 --- a/patches/server/0066-Chunk-Save-Reattempt.patch +++ b/patches/server/0066-Chunk-Save-Reattempt.patch @@ -19,10 +19,10 @@ index b24e8255ab18eb5b2e4968aa62aa3d72ef33f0eb..12b7d50f49a2184aaf220a4a50a137b2 } } 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 4091d4d68b58bdefb2fdac1815e351d4f7c8a523..b7d0a48f38f0d8ae586012bb4e9a9faec21103c2 100644 +index 40f2f4d052add3b4270d29c843e49fb621e1bc8d..df099d4c7f101f50d40dae99b45c271b02712434 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 -@@ -134,6 +134,11 @@ public class RegionFileStorage implements AutoCloseable { +@@ -134,6 +134,11 @@ public final class RegionFileStorage implements AutoCloseable { protected void write(ChunkPos pos, @Nullable CompoundTag nbt) throws IOException { RegionFile regionfile = this.getRegionFile(pos, false); // CraftBukkit @@ -34,7 +34,7 @@ index 4091d4d68b58bdefb2fdac1815e351d4f7c8a523..b7d0a48f38f0d8ae586012bb4e9a9fae if (nbt == null) { regionfile.clear(pos); -@@ -158,7 +163,18 @@ public class RegionFileStorage implements AutoCloseable { +@@ -158,7 +163,18 @@ public final class RegionFileStorage implements AutoCloseable { dataoutputstream.close(); } } diff --git a/patches/server/0082-Sanitise-RegionFileCache-and-make-configurable.patch b/patches/server/0082-Sanitise-RegionFileCache-and-make-configurable.patch index 8116e8a235..2693eaeb7c 100644 --- a/patches/server/0082-Sanitise-RegionFileCache-and-make-configurable.patch +++ b/patches/server/0082-Sanitise-RegionFileCache-and-make-configurable.patch @@ -11,10 +11,10 @@ The implementation uses a LinkedHashMap as an LRU cache (modified from HashMap). The maximum size of the RegionFileCache is also made configurable. 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 b7d0a48f38f0d8ae586012bb4e9a9faec21103c2..7d4aa3d375bde32e0d2606346202929d481acad0 100644 +index df099d4c7f101f50d40dae99b45c271b02712434..491035aaefff4ee96435ec5d3f9417e28eae0796 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 -@@ -39,7 +39,7 @@ public class RegionFileStorage implements AutoCloseable { +@@ -39,7 +39,7 @@ public final class RegionFileStorage implements AutoCloseable { if (regionfile != null) { return regionfile; } else { diff --git a/patches/server/0165-PlayerNaturallySpawnCreaturesEvent.patch b/patches/server/0165-PlayerNaturallySpawnCreaturesEvent.patch index f5479de5b0..780a4a79f3 100644 --- a/patches/server/0165-PlayerNaturallySpawnCreaturesEvent.patch +++ b/patches/server/0165-PlayerNaturallySpawnCreaturesEvent.patch @@ -9,7 +9,7 @@ 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 6581566ca4e4fac0691e4f5851f8895d9ac7a38f..c96346bd0207537899d266fe2c8f29a1663e10c3 100644 +index ddadb0f13b96a39ec89cdaeea7bc02ee62ef2a06..d04b69838c6f5fd1808782cacb31c6e00087bbac 100644 --- a/src/main/java/net/minecraft/server/level/ChunkMap.java +++ b/src/main/java/net/minecraft/server/level/ChunkMap.java @@ -1101,7 +1101,9 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider diff --git a/patches/server/0227-Add-Debug-Entities-option-to-debug-dupe-uuid-issues.patch b/patches/server/0227-Add-Debug-Entities-option-to-debug-dupe-uuid-issues.patch index a6502e83fc..d443d145a4 100644 --- a/patches/server/0227-Add-Debug-Entities-option-to-debug-dupe-uuid-issues.patch +++ b/patches/server/0227-Add-Debug-Entities-option-to-debug-dupe-uuid-issues.patch @@ -5,7 +5,7 @@ Subject: [PATCH] Add Debug Entities option to debug dupe uuid issues diff --git a/src/main/java/net/minecraft/server/level/ChunkMap.java b/src/main/java/net/minecraft/server/level/ChunkMap.java -index c96346bd0207537899d266fe2c8f29a1663e10c3..e2f176d34443f0d1b00649efa45c65138042a015 100644 +index d04b69838c6f5fd1808782cacb31c6e00087bbac..96b7f0ac35a1e87c3f78a24180b207c32749fb71 100644 --- a/src/main/java/net/minecraft/server/level/ChunkMap.java +++ b/src/main/java/net/minecraft/server/level/ChunkMap.java @@ -1321,6 +1321,7 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider diff --git a/patches/server/0313-Tracking-Range-Improvements.patch b/patches/server/0313-Tracking-Range-Improvements.patch index ce3b2f8004..c72f88f8c8 100644 --- a/patches/server/0313-Tracking-Range-Improvements.patch +++ b/patches/server/0313-Tracking-Range-Improvements.patch @@ -8,7 +8,7 @@ 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 e2f176d34443f0d1b00649efa45c65138042a015..3784fbe3548727ab5ad8cfefef2d8d594a76123f 100644 +index 96b7f0ac35a1e87c3f78a24180b207c32749fb71..795c81c8f6fa59eded8b5a5084a8acb46d118fdb 100644 --- a/src/main/java/net/minecraft/server/level/ChunkMap.java +++ b/src/main/java/net/minecraft/server/level/ChunkMap.java @@ -1613,6 +1613,7 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider diff --git a/patches/server/0335-Prevent-Double-PlayerChunkMap-adds-crashing-server.patch b/patches/server/0335-Prevent-Double-PlayerChunkMap-adds-crashing-server.patch index 580844dd61..2f32768c21 100644 --- a/patches/server/0335-Prevent-Double-PlayerChunkMap-adds-crashing-server.patch +++ b/patches/server/0335-Prevent-Double-PlayerChunkMap-adds-crashing-server.patch @@ -7,7 +7,7 @@ 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 3784fbe3548727ab5ad8cfefef2d8d594a76123f..5732aded2e4dbeea84dbe6ebac71c2ad5ce4729a 100644 +index 795c81c8f6fa59eded8b5a5084a8acb46d118fdb..1709821c73362b2ae54681ec1d59b40bfa9335b3 100644 --- a/src/main/java/net/minecraft/server/level/ChunkMap.java +++ b/src/main/java/net/minecraft/server/level/ChunkMap.java @@ -1308,6 +1308,13 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider diff --git a/patches/server/0346-Fire-PlayerJoinEvent-when-Player-is-actually-ready.patch b/patches/server/0346-Fire-PlayerJoinEvent-when-Player-is-actually-ready.patch index fff7495c48..56b3cbc72f 100644 --- a/patches/server/0346-Fire-PlayerJoinEvent-when-Player-is-actually-ready.patch +++ b/patches/server/0346-Fire-PlayerJoinEvent-when-Player-is-actually-ready.patch @@ -31,7 +31,7 @@ delays anymore. 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 5732aded2e4dbeea84dbe6ebac71c2ad5ce4729a..d1247df5c51b0d377a27ea7cc5b5a2d1f1bf9b32 100644 +index 1709821c73362b2ae54681ec1d59b40bfa9335b3..68a1cc5f4f7f5997dfb7d40647e3e027c23ffb14 100644 --- a/src/main/java/net/minecraft/server/level/ChunkMap.java +++ b/src/main/java/net/minecraft/server/level/ChunkMap.java @@ -1315,6 +1315,7 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider diff --git a/patches/server/0613-Oprimise-map-impl-for-tracked-players.patch b/patches/server/0613-Oprimise-map-impl-for-tracked-players.patch index ad5326c8f4..2cadeeb2a0 100644 --- a/patches/server/0613-Oprimise-map-impl-for-tracked-players.patch +++ b/patches/server/0613-Oprimise-map-impl-for-tracked-players.patch @@ -7,7 +7,7 @@ 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 d1247df5c51b0d377a27ea7cc5b5a2d1f1bf9b32..cf7c7813d528429a18dc25051df7fc06dc159930 100644 +index 68a1cc5f4f7f5997dfb7d40647e3e027c23ffb14..77f064fb4437c1d98cf91dde98d4d88b28afa7c8 100644 --- a/src/main/java/net/minecraft/server/level/ChunkMap.java +++ b/src/main/java/net/minecraft/server/level/ChunkMap.java @@ -1529,7 +1529,7 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider diff --git a/patches/server/0643-Only-write-chunk-data-to-disk-if-it-serializes-witho.patch b/patches/server/0643-Only-write-chunk-data-to-disk-if-it-serializes-witho.patch index 148e6899d1..80c053acc6 100644 --- a/patches/server/0643-Only-write-chunk-data-to-disk-if-it-serializes-witho.patch +++ b/patches/server/0643-Only-write-chunk-data-to-disk-if-it-serializes-witho.patch @@ -44,10 +44,10 @@ index 12b7d50f49a2184aaf220a4a50a137b217c57124..f1237f6fd6414900ffbad0caee31aa83 public void close() throws IOException { ByteBuffer bytebuffer = ByteBuffer.wrap(this.buf, 0, this.count); 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 7d4aa3d375bde32e0d2606346202929d481acad0..36e914b26de070035f195f67c65ee1df0d10daf0 100644 +index 491035aaefff4ee96435ec5d3f9417e28eae0796..4c1212c6ef48594e766fa9e35a6e15916602d587 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 -@@ -147,10 +147,17 @@ public class RegionFileStorage implements AutoCloseable { +@@ -147,10 +147,17 @@ public final class RegionFileStorage implements AutoCloseable { try { NbtIo.write(nbt, (DataOutput) dataoutputstream); @@ -66,7 +66,7 @@ index 7d4aa3d375bde32e0d2606346202929d481acad0..36e914b26de070035f195f67c65ee1df } catch (Throwable throwable1) { throwable.addSuppressed(throwable1); } -@@ -158,10 +165,7 @@ public class RegionFileStorage implements AutoCloseable { +@@ -158,10 +165,7 @@ public final class RegionFileStorage implements AutoCloseable { throw throwable; } @@ -78,7 +78,7 @@ index 7d4aa3d375bde32e0d2606346202929d481acad0..36e914b26de070035f195f67c65ee1df } // Paper start - Chunk save reattempt return; -@@ -208,4 +212,13 @@ public class RegionFileStorage implements AutoCloseable { +@@ -208,4 +212,13 @@ public final class RegionFileStorage implements AutoCloseable { public RegionStorageInfo info() { return this.info; } diff --git a/patches/server/0755-Fix-a-bunch-of-vanilla-bugs.patch b/patches/server/0755-Fix-a-bunch-of-vanilla-bugs.patch index 881132c6fa..2990ef17bd 100644 --- a/patches/server/0755-Fix-a-bunch-of-vanilla-bugs.patch +++ b/patches/server/0755-Fix-a-bunch-of-vanilla-bugs.patch @@ -85,7 +85,7 @@ index 6854ca4d4fec2b4fa541c3fabf63787665572609..e7b444a10b244828827b3c66c5346520 } diff --git a/src/main/java/net/minecraft/server/level/ChunkMap.java b/src/main/java/net/minecraft/server/level/ChunkMap.java -index cf7c7813d528429a18dc25051df7fc06dc159930..ef46d904fa49a779c235971883380b3e33e6dba1 100644 +index 77f064fb4437c1d98cf91dde98d4d88b28afa7c8..ccbd527803a2a4e911a01f815cc9c7ab785af836 100644 --- a/src/main/java/net/minecraft/server/level/ChunkMap.java +++ b/src/main/java/net/minecraft/server/level/ChunkMap.java @@ -1091,7 +1091,7 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider diff --git a/patches/server/0784-Player-Entity-Tracking-Events.patch b/patches/server/0784-Player-Entity-Tracking-Events.patch index 4b16731def..bdc7e8779e 100644 --- a/patches/server/0784-Player-Entity-Tracking-Events.patch +++ b/patches/server/0784-Player-Entity-Tracking-Events.patch @@ -5,7 +5,7 @@ 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 ef46d904fa49a779c235971883380b3e33e6dba1..8eae75993ad60226a86456487f3b3a59999ab423 100644 +index ccbd527803a2a4e911a01f815cc9c7ab785af836..e2521e1a56df8dcb1de815e5973de952408d3b8b 100644 --- a/src/main/java/net/minecraft/server/level/ChunkMap.java +++ b/src/main/java/net/minecraft/server/level/ChunkMap.java @@ -1601,7 +1601,11 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider diff --git a/patches/server/0873-Configurable-entity-tracking-range-by-Y-coordinate.patch b/patches/server/0873-Configurable-entity-tracking-range-by-Y-coordinate.patch index 3c28b2c60f..a437b50f0f 100644 --- a/patches/server/0873-Configurable-entity-tracking-range-by-Y-coordinate.patch +++ b/patches/server/0873-Configurable-entity-tracking-range-by-Y-coordinate.patch @@ -6,7 +6,7 @@ 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 8eae75993ad60226a86456487f3b3a59999ab423..38df456d3646c384d17ae9aec60c18fcd0651b4b 100644 +index e2521e1a56df8dcb1de815e5973de952408d3b8b..6c5557aad2455b79bb2adf8939eb9a6127ccc3c3 100644 --- a/src/main/java/net/minecraft/server/level/ChunkMap.java +++ b/src/main/java/net/minecraft/server/level/ChunkMap.java @@ -1593,6 +1593,15 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider diff --git a/patches/server/0905-Don-t-check-if-we-can-see-non-visible-entities.patch b/patches/server/0905-Don-t-check-if-we-can-see-non-visible-entities.patch index 43404ba162..109e1a443b 100644 --- a/patches/server/0905-Don-t-check-if-we-can-see-non-visible-entities.patch +++ b/patches/server/0905-Don-t-check-if-we-can-see-non-visible-entities.patch @@ -5,7 +5,7 @@ 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 38df456d3646c384d17ae9aec60c18fcd0651b4b..cf4517e57169856acd0782e5ced4eb8c045b8d78 100644 +index 6c5557aad2455b79bb2adf8939eb9a6127ccc3c3..469f1dcb22c06025681e727e281b5b53f2b21c1f 100644 --- a/src/main/java/net/minecraft/server/level/ChunkMap.java +++ b/src/main/java/net/minecraft/server/level/ChunkMap.java @@ -1604,7 +1604,7 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider diff --git a/patches/server/0934-Reduce-allocation-of-Vec3D-by-entity-tracker.patch b/patches/server/0934-Reduce-allocation-of-Vec3D-by-entity-tracker.patch index 357d185f0a..86bd3e7f29 100644 --- a/patches/server/0934-Reduce-allocation-of-Vec3D-by-entity-tracker.patch +++ b/patches/server/0934-Reduce-allocation-of-Vec3D-by-entity-tracker.patch @@ -18,7 +18,7 @@ index a043ac10834562d357ef0b5aded2e916e2a0d056..74276c368016fcc4dbf9579b2ecbadc9 @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 cf4517e57169856acd0782e5ced4eb8c045b8d78..6129720c9da217745fcd281186de7894597c267c 100644 +index 469f1dcb22c06025681e727e281b5b53f2b21c1f..2ce7da9707d7c1a48b5609ae51a516d599d7aee8 100644 --- a/src/main/java/net/minecraft/server/level/ChunkMap.java +++ b/src/main/java/net/minecraft/server/level/ChunkMap.java @@ -1587,10 +1587,14 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider diff --git a/patches/server/0961-Protect-Bedrock-and-End-Portal-Frames-from-being-des.patch b/patches/server/0961-Protect-Bedrock-and-End-Portal-Frames-from-being-des.patch new file mode 100644 index 0000000000..0fd49bd9ed --- /dev/null +++ b/patches/server/0961-Protect-Bedrock-and-End-Portal-Frames-from-being-des.patch @@ -0,0 +1,169 @@ +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 69914a048987c21ee2ed2c489aab269862fda8f2..bff83fe413c7baef4ba56a3270ea4463a58c792f 100644 +--- a/src/main/java/net/minecraft/world/level/Explosion.java ++++ b/src/main/java/net/minecraft/world/level/Explosion.java +@@ -193,6 +193,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 4d6409875771413de7ae20def2aaad4049709c30..dd519eacd6d4bea5447bea471f0ac6540d9bb49c 100644 +--- a/src/main/java/net/minecraft/world/level/Level.java ++++ b/src/main/java/net/minecraft/world/level/Level.java +@@ -449,6 +449,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..45704653310efe9cb755a644674b54b8722c2c84 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.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.BARRIER && ++ 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 936b56c116de63b38a416d5bab4223a88d0469d0..6c4a339be29bb9c07b741a1ca12de2217c8687ba 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 +@@ -173,7 +173,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; + +@@ -253,7 +253,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) { +@@ -888,6 +888,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); + } +@@ -991,7 +997,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/0962-Fix-tripwire-disarming-not-working-as-intended.patch b/patches/server/0962-Fix-tripwire-disarming-not-working-as-intended.patch new file mode 100644 index 0000000000..fa7f6bde06 --- /dev/null +++ b/patches/server/0962-Fix-tripwire-disarming-not-working-as-intended.patch @@ -0,0 +1,22 @@ +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/0963-Add-config-for-mobs-immune-to-default-effects.patch b/patches/server/0963-Add-config-for-mobs-immune-to-default-effects.patch new file mode 100644 index 0000000000..5989f9bd5f --- /dev/null +++ b/patches/server/0963-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 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 f0127f1b55999aa4a841341ad02cbcde45702b50..e675f1e3e5b6f9e1aa0d928ebb9abe76458edb38 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/0964-Deep-clone-nbt-tags-in-PDC.patch b/patches/server/0964-Deep-clone-nbt-tags-in-PDC.patch new file mode 100644 index 0000000000..6a8a671064 --- /dev/null +++ b/patches/server/0964-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 758e01a05f5ca88f72ea3d54a7cdc49bbfadfaf5..737b325f87bcaa8871dbed5deec01215a1f11bf7 100644 +--- a/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaItem.java ++++ b/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaItem.java +@@ -318,7 +318,7 @@ class CraftMetaItem implements ItemMeta, Damageable, Repairable, BlockDataMeta { + this.damage = meta.damage; + this.maxDamage = meta.maxDamage; + this.unhandledTags = meta.unhandledTags; +- this.persistentDataContainer.putAll(meta.persistentDataContainer.getRaw()); ++ this.persistentDataContainer.putAll(meta.persistentDataContainer.getTagsCloned()); // Paper - deep clone NBT tags + + this.customTag = meta.customTag; + +@@ -1641,7 +1641,7 @@ class CraftMetaItem implements ItemMeta, Damageable, Repairable, BlockDataMeta { + if (this.customTag != null) { + clone.customTag = this.customTag.copy(); + } +- 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/0965-Support-old-UUID-format-for-NBT.patch b/patches/server/0965-Support-old-UUID-format-for-NBT.patch new file mode 100644 index 0000000000..a38c2a7f7b --- /dev/null +++ b/patches/server/0965-Support-old-UUID-format-for-NBT.patch @@ -0,0 +1,63 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Aikar +Date: Mon, 29 Jun 2020 03:26:17 -0400 +Subject: [PATCH] Support old UUID format for NBT + +We have stored UUID in plenty of places that did not get DFU'd + +So just look for old format and load it if it exists. + +diff --git a/src/main/java/net/minecraft/nbt/CompoundTag.java b/src/main/java/net/minecraft/nbt/CompoundTag.java +index df246d69591e1a5822a0109c99b0f67996da71fa..4e005b7b062e3231f564d284887ea1c2783a4e7d 100644 +--- a/src/main/java/net/minecraft/nbt/CompoundTag.java ++++ b/src/main/java/net/minecraft/nbt/CompoundTag.java +@@ -232,6 +232,12 @@ public class CompoundTag implements Tag { + } + + public void putUUID(String key, UUID value) { ++ // Paper start - Support old UUID format ++ if (this.contains(key + "Most", net.minecraft.nbt.Tag.TAG_ANY_NUMERIC) && this.contains(key + "Least", net.minecraft.nbt.Tag.TAG_ANY_NUMERIC)) { ++ this.tags.remove(key + "Most"); ++ this.tags.remove(key + "Least"); ++ } ++ // Paper end - Support old UUID format + this.tags.put(key, NbtUtils.createUUID(value)); + } + +@@ -240,10 +246,20 @@ public class CompoundTag implements Tag { + * You must use {@link #hasUUID(String)} before or else it will throw an NPE. + */ + public UUID getUUID(String key) { ++ // Paper start - Support old UUID format ++ if (!contains(key, 11) && this.contains(key + "Most", net.minecraft.nbt.Tag.TAG_ANY_NUMERIC) && this.contains(key + "Least", net.minecraft.nbt.Tag.TAG_ANY_NUMERIC)) { ++ return new UUID(this.getLong(key + "Most"), this.getLong(key + "Least")); ++ } ++ // Paper end - Support old UUID format + return NbtUtils.loadUUID(this.get(key)); + } + + public boolean hasUUID(String key) { ++ // Paper start - Support old UUID format ++ if (this.contains(key + "Most", net.minecraft.nbt.Tag.TAG_ANY_NUMERIC) && this.contains(key + "Least", net.minecraft.nbt.Tag.TAG_ANY_NUMERIC)) { ++ return true; ++ } ++ // Paper end - Support old UUID format + Tag tag = this.get(key); + return tag != null && tag.getType() == IntArrayTag.TYPE && ((IntArrayTag)tag).getAsIntArray().length == 4; + } +diff --git a/src/main/java/net/minecraft/world/item/component/ResolvableProfile.java b/src/main/java/net/minecraft/world/item/component/ResolvableProfile.java +index 78863e72239a0f3535bc85758479da84d58c11c1..38bbe39a5cd96710b208d70ed78619057bb6e6fa 100644 +--- a/src/main/java/net/minecraft/world/item/component/ResolvableProfile.java ++++ b/src/main/java/net/minecraft/world/item/component/ResolvableProfile.java +@@ -20,9 +20,10 @@ public record ResolvableProfile(Optional name, Optional id, Proper + instance -> instance.group( + ExtraCodecs.PLAYER_NAME.optionalFieldOf("name").forGetter(ResolvableProfile::name), + UUIDUtil.CODEC.optionalFieldOf("id").forGetter(ResolvableProfile::id), ++ UUIDUtil.STRING_CODEC.lenientOptionalFieldOf("Id").forGetter($ -> Optional.empty()), // Paper + ExtraCodecs.PROPERTY_MAP.optionalFieldOf("properties", new PropertyMap()).forGetter(ResolvableProfile::properties) + ) +- .apply(instance, ResolvableProfile::new) ++ .apply(instance, (s, uuid, uuid2, propertyMap) -> new ResolvableProfile(s, uuid2.or(() -> uuid), propertyMap)) // Paper + ); + public static final Codec CODEC = Codec.withAlternative( + FULL_CODEC, ExtraCodecs.PLAYER_NAME, name -> new ResolvableProfile(Optional.of(name), Optional.empty(), new PropertyMap()) diff --git a/patches/server/0966-Fix-shield-disable-inconsistency.patch b/patches/server/0966-Fix-shield-disable-inconsistency.patch new file mode 100644 index 0000000000..4e5bb64de4 --- /dev/null +++ b/patches/server/0966-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 d535bb7adff273c9d4cdaac73f7dfe5bbd663c15..890d453fa5ee4b167ec83cce8e2882e2e4585b14 100644 +--- a/src/main/java/net/minecraft/world/entity/LivingEntity.java ++++ b/src/main/java/net/minecraft/world/entity/LivingEntity.java +@@ -2325,7 +2325,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/0967-Don-t-lose-removed-data-components-in-ItemMeta.patch b/patches/server/0967-Don-t-lose-removed-data-components-in-ItemMeta.patch new file mode 100644 index 0000000000..a46f8877b4 --- /dev/null +++ b/patches/server/0967-Don-t-lose-removed-data-components-in-ItemMeta.patch @@ -0,0 +1,47 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Jake Potrebic +Date: Fri, 26 Apr 2024 21:33:20 -0700 +Subject: [PATCH] Don't lose removed data 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 737b325f87bcaa8871dbed5deec01215a1f11bf7..8a5785565d71f3f74a9ce0179a26dbff5530023d 100644 +--- a/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaItem.java ++++ b/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaItem.java +@@ -196,6 +196,13 @@ class CraftMetaItem implements ItemMeta, Damageable, Repairable, BlockDataMeta { + return this; + } + ++ // Paper start - support removing component types ++ Applicator remove(DataComponentType type) { ++ this.builder.remove(type); ++ return this; ++ } ++ // Paper end - support removing component types ++ + DataComponentPatch build() { + return this.builder.build(); + } +@@ -424,7 +431,9 @@ class CraftMetaItem implements ItemMeta, Damageable, Repairable, BlockDataMeta { + + Set, Optional>> keys = tag.entrySet(); + for (Map.Entry, Optional> key : keys) { +- if (!CraftMetaItem.getHandledTags().contains(key.getKey())) { ++ if (key.getValue().isEmpty()) { ++ this.unhandledTags.remove(key.getKey()); ++ } else if (!CraftMetaItem.getHandledTags().contains(key.getKey())) { + key.getValue().ifPresentOrElse((value) -> { + this.unhandledTags.set((DataComponentType) key.getKey(), value); + }, () -> { +@@ -832,9 +841,9 @@ class CraftMetaItem implements ItemMeta, Damageable, Repairable, BlockDataMeta { + } + + for (Map.Entry, Optional> e : this.unhandledTags.build().entrySet()) { +- e.getValue().ifPresent((value) -> { ++ e.getValue().ifPresentOrElse((value) -> { + itemTag.builder.set((DataComponentType) e.getKey(), value); +- }); ++ }, () -> itemTag.remove(e.getKey())); + } + + CompoundTag customTag = (this.customTag != null) ? this.customTag.copy() : null; diff --git a/patches/server/0968-Handle-Large-Packets-disconnecting-client.patch b/patches/server/0968-Handle-Large-Packets-disconnecting-client.patch new file mode 100644 index 0000000000..ea20a101a7 --- /dev/null +++ b/patches/server/0968-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 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/0969-Fix-ItemFlags.patch b/patches/server/0969-Fix-ItemFlags.patch new file mode 100644 index 0000000000..249bd1854a --- /dev/null +++ b/patches/server/0969-Fix-ItemFlags.patch @@ -0,0 +1,200 @@ +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 fca0cfba14dd2cc6f24b56eaf269594b2d87fd04..8734f0b777432cd8639094b75a3da1b9595823ed 100644 +--- a/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaEnchantedBook.java ++++ b/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaEnchantedBook.java +@@ -39,7 +39,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 + } + }); + } +@@ -54,7 +54,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 8a5785565d71f3f74a9ce0179a26dbff5530023d..dbcf9a27112ac525722fd9b80ec736797864bfdf 100644 +--- a/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaItem.java ++++ b/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaItem.java +@@ -250,6 +250,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; +@@ -330,6 +336,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) { +@@ -428,6 +438,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) { +@@ -622,7 +646,16 @@ class CraftMetaItem implements ItemMeta, Damageable, Repairable, BlockDataMeta { + ByteArrayInputStream buf = new ByteArrayInputStream(Base64.getDecoder().decode(unhandled)); + try { + CompoundTag unhandledTag = NbtIo.readCompressed(buf, NbtAccounter.unlimitedHeap()); +- this.unhandledTags.copy(DataComponentPatch.CODEC.parse(MinecraftServer.getDefaultRegistryAccess().createSerializationContext(NbtOps.INSTANCE), unhandledTag).result().get()); ++ // Paper start ++ final net.minecraft.core.component.DataComponentPatch patch = net.minecraft.core.component.DataComponentPatch.CODEC.parse(net.minecraft.server.MinecraftServer.getDefaultRegistryAccess().createSerializationContext(net.minecraft.nbt.NbtOps.INSTANCE), unhandledTag).result().get(); ++ CraftMetaItem.getOrEmpty(patch, CraftMetaItem.CAN_PLACE_ON).ifPresent(data -> { ++ this.canPlaceOnPredicates = List.copyOf(data.predicates); ++ }); ++ CraftMetaItem.getOrEmpty(patch, CraftMetaItem.CAN_BREAK).ifPresent(data -> { ++ this.canBreakPredicates = List.copyOf(data.predicates); ++ }); ++ this.unhandledTags.copy(patch.forget(type -> type == CraftMetaItem.CAN_PLACE_ON.TYPE || type == CraftMetaItem.CAN_BREAK.TYPE)); ++ // Paper end + } catch (IOException ex) { + Logger.getLogger(CraftMetaItem.class.getName()).log(Level.SEVERE, null, ex); + } +@@ -840,6 +873,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().ifPresentOrElse((value) -> { + itemTag.builder.set((DataComponentType) e.getKey(), value); +@@ -914,7 +956,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.hasBlockData() || this.hasRepairCost() || !this.unhandledTags.build().isEmpty() || !this.persistentDataContainer.isEmpty() || this.hideFlag != 0 || this.isHideTooltip() || this.isUnbreakable() || this.hasEnchantmentGlintOverride() || this.isFireResistant() || this.hasMaxStackSize() || this.hasRarity() || this.hasFood() || this.hasTool() || this.hasDamage() || this.hasMaxDamage() || this.hasAttributeModifiers() || this.customTag != null); ++ return !(this.hasDisplayName() || this.hasItemName() || this.hasLocalizedName() || this.hasEnchants() || (this.lore != null) || this.hasCustomModelData() || this.hasBlockData() || this.hasRepairCost() || !this.unhandledTags.build().isEmpty() || !this.persistentDataContainer.isEmpty() || this.hideFlag != 0 || this.isHideTooltip() || this.isUnbreakable() || this.hasEnchantmentGlintOverride() || this.isFireResistant() || this.hasMaxStackSize() || this.hasRarity() || this.hasFood() || this.hasTool() || this.hasDamage() || this.hasMaxDamage() || this.hasAttributeModifiers() || this.customTag != null || this.canPlaceOnPredicates != null || this.canBreakPredicates != null); // Paper + } + + // Paper start +@@ -1583,6 +1625,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); + } + +@@ -1627,6 +1671,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; + } +@@ -1670,6 +1716,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); +@@ -1787,6 +1841,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()) { + Tag unhandled = DataComponentPatch.CODEC.encodeStart(MinecraftServer.getDefaultRegistryAccess().createSerializationContext(NbtOps.INSTANCE), this.unhandledTags.build()).getOrThrow(IllegalStateException::new); + try { +@@ -1797,6 +1861,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.persistentDataContainer.isEmpty()) { // Store custom tags, wrapped in their compound + builder.put(CraftMetaItem.BUKKIT_CUSTOM_TAG.BUKKIT, this.persistentDataContainer.serialize()); +@@ -1936,6 +2008,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/0970-Fix-helmet-damage-reduction-inconsistencies.patch b/patches/server/0970-Fix-helmet-damage-reduction-inconsistencies.patch new file mode 100644 index 0000000000..1eecc61d78 --- /dev/null +++ b/patches/server/0970-Fix-helmet-damage-reduction-inconsistencies.patch @@ -0,0 +1,21 @@ +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 helmet damage reduction inconsistencies + +Affect the falling stalactite damage type where the +reduction is not applied like in Vanilla + +diff --git a/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java b/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java +index 0b1741cd68d5066114a35cc14ed08b57f4f08fb2..f769f249ada432ee400055deabdb5e4aeaa88c05 100644 +--- a/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java ++++ b/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java +@@ -1213,7 +1213,7 @@ public class CraftEventFactory { + Map> modifierFunctions = new EnumMap<>(DamageModifier.class); + modifiers.put(DamageModifier.BASE, rawDamage); + modifierFunctions.put(DamageModifier.BASE, CraftEventFactory.ZERO); +- 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/0971-Revert-to-vanilla-handling-of-LivingEntity-actuallyH.patch b/patches/server/0971-Revert-to-vanilla-handling-of-LivingEntity-actuallyH.patch new file mode 100644 index 0000000000..bfa6943204 --- /dev/null +++ b/patches/server/0971-Revert-to-vanilla-handling-of-LivingEntity-actuallyH.patch @@ -0,0 +1,34 @@ +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 890d453fa5ee4b167ec83cce8e2882e2e4585b14..81b70e2dcf31ef651256a0ddf928c6370458c3dd 100644 +--- a/src/main/java/net/minecraft/world/entity/LivingEntity.java ++++ b/src/main/java/net/minecraft/world/entity/LivingEntity.java +@@ -2210,7 +2210,7 @@ public abstract class LivingEntity extends Entity implements Attackable { + } + + // CraftBukkit start +- protected boolean actuallyHurt(final DamageSource damagesource, float f) { // void -> boolean, add final ++ protected boolean actuallyHurt(final DamageSource damagesource, float f) { // void -> boolean, add final // Paper - return false ONLY if event cancelled + if (!this.isInvulnerableTo(damagesource)) { + final boolean human = this instanceof net.minecraft.world.entity.player.Player; + float originalDamage = f; +@@ -2382,12 +2382,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/0972-improve-checking-handled-tags-in-itemmeta.patch b/patches/server/0972-improve-checking-handled-tags-in-itemmeta.patch new file mode 100644 index 0000000000..f47891b8bc --- /dev/null +++ b/patches/server/0972-improve-checking-handled-tags-in-itemmeta.patch @@ -0,0 +1,806 @@ +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/CraftItemStack.java b/src/main/java/org/bukkit/craftbukkit/inventory/CraftItemStack.java +index f6e84cccb0e805f73efe2c9625986c94099bb0d4..3c0d3faddcfcba74d90e1d5fccb60b891ef7c458 100644 +--- a/src/main/java/org/bukkit/craftbukkit/inventory/CraftItemStack.java ++++ b/src/main/java/org/bukkit/craftbukkit/inventory/CraftItemStack.java +@@ -156,10 +156,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); +@@ -312,6 +313,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, CraftItemStack.getType(this.handle), 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(); +@@ -324,14 +338,19 @@ public final class CraftItemStack extends ItemStack { + } + public static ItemMeta getItemMeta(net.minecraft.world.item.ItemStack item, Material material) { + // Paper end ++ // Paper start - handled tags on type change ++ return getItemMeta(item, material, null); ++ } ++ public static ItemMeta getItemMeta(net.minecraft.world.item.ItemStack item, Material material, final java.util.Set> extraHandledDcts) { ++ // Paper end - handled tags on type change + if (!CraftItemStack.hasItemMeta(item)) { + return CraftItemFactory.instance().getItemMeta(material); // Paper + } + switch (material) { // Paper + case WRITTEN_BOOK: +- return new CraftMetaBookSigned(item.getComponentsPatch()); ++ return new CraftMetaBookSigned(item.getComponentsPatch(), extraHandledDcts); // Paper + case WRITABLE_BOOK: +- return new CraftMetaBook(item.getComponentsPatch()); ++ return new CraftMetaBook(item.getComponentsPatch(), extraHandledDcts); // Paper + case CREEPER_HEAD: + case CREEPER_WALL_HEAD: + case DRAGON_HEAD: +@@ -346,7 +365,7 @@ public final class CraftItemStack extends ItemStack { + case WITHER_SKELETON_WALL_SKULL: + case ZOMBIE_HEAD: + case ZOMBIE_WALL_HEAD: +- return new CraftMetaSkull(item.getComponentsPatch()); ++ return new CraftMetaSkull(item.getComponentsPatch(), extraHandledDcts); // Paper + case CHAINMAIL_HELMET: + case CHAINMAIL_CHESTPLATE: + case CHAINMAIL_LEGGINGS: +@@ -368,28 +387,28 @@ public final class CraftItemStack extends ItemStack { + case NETHERITE_LEGGINGS: + case NETHERITE_BOOTS: + case TURTLE_HELMET: +- return new CraftMetaArmor(item.getComponentsPatch()); ++ return new CraftMetaArmor(item.getComponentsPatch(), extraHandledDcts); // Paper + case LEATHER_HELMET: + case LEATHER_CHESTPLATE: + case LEATHER_LEGGINGS: + case LEATHER_BOOTS: + case WOLF_ARMOR: +- return new CraftMetaColorableArmor(item.getComponentsPatch()); ++ return new CraftMetaColorableArmor(item.getComponentsPatch(), extraHandledDcts); // Paper + case LEATHER_HORSE_ARMOR: +- return new CraftMetaLeatherArmor(item.getComponentsPatch()); ++ return new CraftMetaLeatherArmor(item.getComponentsPatch(), extraHandledDcts); // Paper + case POTION: + case SPLASH_POTION: + case LINGERING_POTION: + case TIPPED_ARROW: +- return new CraftMetaPotion(item.getComponentsPatch()); ++ return new CraftMetaPotion(item.getComponentsPatch(), extraHandledDcts); // Paper + case FILLED_MAP: +- return new CraftMetaMap(item.getComponentsPatch()); ++ return new CraftMetaMap(item.getComponentsPatch(), extraHandledDcts); // Paper + case FIREWORK_ROCKET: +- return new CraftMetaFirework(item.getComponentsPatch()); ++ return new CraftMetaFirework(item.getComponentsPatch(), extraHandledDcts); // Paper + case FIREWORK_STAR: +- return new CraftMetaCharge(item.getComponentsPatch()); ++ return new CraftMetaCharge(item.getComponentsPatch(), extraHandledDcts); // Paper; + case ENCHANTED_BOOK: +- return new CraftMetaEnchantedBook(item.getComponentsPatch()); ++ return new CraftMetaEnchantedBook(item.getComponentsPatch(), extraHandledDcts); // Paper + case BLACK_BANNER: + case BLACK_WALL_BANNER: + case BLUE_BANNER: +@@ -422,7 +441,7 @@ public final class CraftItemStack extends ItemStack { + case WHITE_WALL_BANNER: + case YELLOW_BANNER: + case YELLOW_WALL_BANNER: +- return new CraftMetaBanner(item.getComponentsPatch()); ++ return new CraftMetaBanner(item.getComponentsPatch(), extraHandledDcts); // Paper + case ARMADILLO_SPAWN_EGG: + case ALLAY_SPAWN_EGG: + case AXOLOTL_SPAWN_EGG: +@@ -503,11 +522,11 @@ public final class CraftItemStack extends ItemStack { + case ZOMBIE_SPAWN_EGG: + case ZOMBIE_VILLAGER_SPAWN_EGG: + case ZOMBIFIED_PIGLIN_SPAWN_EGG: +- return new CraftMetaSpawnEgg(item.getComponentsPatch()); ++ return new CraftMetaSpawnEgg(item.getComponentsPatch(), extraHandledDcts); // Paper + case ARMOR_STAND: +- return new CraftMetaArmorStand(item.getComponentsPatch()); ++ return new CraftMetaArmorStand(item.getComponentsPatch(), extraHandledDcts); // Paper + case KNOWLEDGE_BOOK: +- return new CraftMetaKnowledgeBook(item.getComponentsPatch()); ++ return new CraftMetaKnowledgeBook(item.getComponentsPatch(), extraHandledDcts); // Paper + case FURNACE: + case CHEST: + case TRAPPED_CHEST: +@@ -609,15 +628,15 @@ public final class CraftItemStack extends ItemStack { + case CRAFTER: + case TRIAL_SPAWNER: + case VAULT: +- return new CraftMetaBlockState(item.getComponentsPatch(), CraftItemType.minecraftToBukkit(item.getItem())); ++ return new CraftMetaBlockState(item.getComponentsPatch(), CraftItemType.minecraftToBukkit(item.getItem()), extraHandledDcts); // Paper + case TROPICAL_FISH_BUCKET: +- return new CraftMetaTropicalFishBucket(item.getComponentsPatch()); ++ return new CraftMetaTropicalFishBucket(item.getComponentsPatch(), extraHandledDcts); // Paper + case AXOLOTL_BUCKET: +- return new CraftMetaAxolotlBucket(item.getComponentsPatch()); ++ return new CraftMetaAxolotlBucket(item.getComponentsPatch(), extraHandledDcts); // Paper + case CROSSBOW: +- return new CraftMetaCrossbow(item.getComponentsPatch()); ++ return new CraftMetaCrossbow(item.getComponentsPatch(), extraHandledDcts); // Paper + case SUSPICIOUS_STEW: +- return new CraftMetaSuspiciousStew(item.getComponentsPatch()); ++ return new CraftMetaSuspiciousStew(item.getComponentsPatch(), extraHandledDcts); // Paper + case COD_BUCKET: + case PUFFERFISH_BUCKET: + case SALMON_BUCKET: +@@ -625,17 +644,17 @@ public final class CraftItemStack extends ItemStack { + case ITEM_FRAME: + case GLOW_ITEM_FRAME: + case PAINTING: +- return new CraftMetaEntityTag(item.getComponentsPatch()); ++ return new CraftMetaEntityTag(item.getComponentsPatch(), extraHandledDcts); // Paper + case COMPASS: +- return new CraftMetaCompass(item.getComponentsPatch()); ++ return new CraftMetaCompass(item.getComponentsPatch(), extraHandledDcts); // Paper + case BUNDLE: +- return new CraftMetaBundle(item.getComponentsPatch()); ++ return new CraftMetaBundle(item.getComponentsPatch(), extraHandledDcts); // Paper + case GOAT_HORN: +- return new CraftMetaMusicInstrument(item.getComponentsPatch()); ++ return new CraftMetaMusicInstrument(item.getComponentsPatch(), extraHandledDcts); // Paper + case OMINOUS_BOTTLE: +- return new CraftMetaOminousBottle(item.getComponentsPatch()); ++ return new CraftMetaOminousBottle(item.getComponentsPatch(), extraHandledDcts); // Paper + default: +- return new CraftMetaItem(item.getComponentsPatch()); ++ return new CraftMetaItem(item.getComponentsPatch(), extraHandledDcts); // Paper + } + } + +diff --git a/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaArmor.java b/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaArmor.java +index 13b91cddffbe8ae6f07ce5c0ae45beba151e1aca..569f7157a625b981bff43650e9dd0a8c1831a29d 100644 +--- a/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaArmor.java ++++ b/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaArmor.java +@@ -65,8 +65,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 59bdac414e8205ed608f79ef0d1502acd826d216..53df7e876c9f3e67aa2326fa1a5ce5e90ab7efd6 100644 +--- a/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaArmorStand.java ++++ b/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaArmorStand.java +@@ -47,8 +47,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 3377fdd445db33b2ee1735942b391c6bfa92ab91..44d8aa7123ac22cf9a22720ecadc8c5f63bafc0a 100644 +--- a/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaAxolotlBucket.java ++++ b/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaAxolotlBucket.java +@@ -37,8 +37,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 d53df6f114c285b880167385807775e400c80fc9..1ac3bec02fce28d5ce698305a7482a9eccbb1867 100644 +--- a/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaBanner.java ++++ b/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaBanner.java +@@ -72,8 +72,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 9034905aabf057f387b65957a254d056b12e0519..12911233c01d0ac1af9adbd157d56d28361fc76f 100644 +--- a/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaBlockState.java ++++ b/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaBlockState.java +@@ -161,8 +161,8 @@ public class CraftMetaBlockState extends CraftMetaItem implements BlockStateMeta + this.blockEntityTag = te.blockEntityTag; + } + +- 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((nbt) -> { +diff --git a/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaBook.java b/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaBook.java +index 4da38ebb7fdbdb0f8fa422ebcd2e3eec2b2be846..a395c7ce952f4a60a5edf80e8731afa6388d18ea 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 c7360e2b2d6e50abc371c21b09cdadd63892f439..3f78a0935d738854182254b345064e3c225dcd5f 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 dfaec374cf35107714b6f49d58c508dba94e7d3d..f8c02fe01fd95aa5de8523c9ad452d91f5d3c16f 100644 +--- a/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaBundle.java ++++ b/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaBundle.java +@@ -35,8 +35,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 40d55374a78bcbaa958cf0010c46071c6dc833f9..12d128e0246e66aa4e53fef870ee9125fa1687bf 100644 +--- a/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaCharge.java ++++ b/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaCharge.java +@@ -30,8 +30,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 c74d597633d023bd12c10bd4801bc103eb2beef1..2c9ca54267579a210d4ea192517fc0fbce8e467a 100644 +--- a/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaColorableArmor.java ++++ b/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaColorableArmor.java +@@ -29,8 +29,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 820b4e611342fb62c61a8b3b19e19967da35cbe0..bbca26f5debb263b04516e68f6e49f68a38fa5b1 100644 +--- a/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaCompass.java ++++ b/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaCompass.java +@@ -51,8 +51,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 de7b06f9da17418cf0065249438a4182043160e6..a3fa95377e083e51ad7596d21eeb08172bdb18b2 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 8734f0b777432cd8639094b75a3da1b9595823ed..eb80239949e54c0a698ad4e2d9262242ecb28e41 100644 +--- a/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaEnchantedBook.java ++++ b/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaEnchantedBook.java +@@ -33,8 +33,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 b444bd26d6c3def3494d3cc0520e462408272be3..8e0dd4b7a7a25a8beb27b507047bc48d8227627c 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 dbcf9a27112ac525722fd9b80ec736797864bfdf..5cdb9e07f79355e4590984b32be554053754ef5b 100644 +--- a/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaItem.java ++++ b/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaItem.java +@@ -342,7 +342,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; + }); +@@ -453,11 +453,18 @@ 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 (key.getValue().isEmpty()) { + this.unhandledTags.remove(key.getKey()); +- } else if (!CraftMetaItem.getHandledTags().contains(key.getKey())) { ++ } else if (!handledTags.contains(key.getKey())) { // Paper - improve checking handled data component types + key.getValue().ifPresentOrElse((value) -> { + this.unhandledTags.set((DataComponentType) key.getKey(), value); + }, () -> { +@@ -1983,68 +1990,75 @@ 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.BLOCK_DATA.TYPE, +- CraftMetaItem.REPAIR.TYPE, +- CraftMetaItem.ENCHANTMENTS.TYPE, +- CraftMetaItem.HIDE_ADDITIONAL_TOOLTIP.TYPE, +- CraftMetaItem.HIDE_TOOLTIP.TYPE, +- CraftMetaItem.UNBREAKABLE.TYPE, +- CraftMetaItem.ENCHANTMENT_GLINT_OVERRIDE.TYPE, +- CraftMetaItem.FIRE_RESISTANT.TYPE, +- CraftMetaItem.MAX_STACK_SIZE.TYPE, +- CraftMetaItem.RARITY.TYPE, +- CraftMetaItem.FOOD.TYPE, +- CraftMetaItem.TOOL.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, +- 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, +- CraftMetaBlockState.BLOCK_ENTITY_TAG.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.BLOCK_DATA.TYPE, ++ CraftMetaItem.REPAIR.TYPE, ++ CraftMetaItem.ENCHANTMENTS.TYPE, ++ CraftMetaItem.HIDE_ADDITIONAL_TOOLTIP.TYPE, ++ CraftMetaItem.HIDE_TOOLTIP.TYPE, ++ CraftMetaItem.UNBREAKABLE.TYPE, ++ CraftMetaItem.ENCHANTMENT_GLINT_OVERRIDE.TYPE, ++ CraftMetaItem.FIRE_RESISTANT.TYPE, ++ CraftMetaItem.MAX_STACK_SIZE.TYPE, ++ CraftMetaItem.RARITY.TYPE, ++ CraftMetaItem.FOOD.TYPE, ++ CraftMetaItem.TOOL.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(/*CraftMetaBlockState.BLOCK_ENTITY_TAG.NBT, */CraftMetaBanner.PATTERNS.TYPE)); // banner uses same tag as block state ++ 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 bd44481a7d794943cb8695bea2a773a4562f0fae..20638aa593e0a6c78e4bfdb936e69f3d36e18f4e 100644 +--- a/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaKnowledgeBook.java ++++ b/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaKnowledgeBook.java +@@ -31,8 +31,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 b97e9a8f163adbb30d2e7db16aeb99544fcb2916..157a7b7351f48e68d2923c72ed3bbe3dcae21383 100644 +--- a/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaLeatherArmor.java ++++ b/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaLeatherArmor.java +@@ -35,8 +35,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 6b34a8d33faa49ffa9082995e67af10d3cb38c03..d829f4da371b44e7480896118547734be400a314 100644 +--- a/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaMap.java ++++ b/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaMap.java +@@ -45,8 +45,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 4eb2993903f5fa9fb9fd65282a42f26b3aa1e7bd..f33f1d250a3a4068df79cd1eb37baffda981aab3 100644 +--- a/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaMusicInstrument.java ++++ b/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaMusicInstrument.java +@@ -27,8 +27,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 custom instruments +diff --git a/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaOminousBottle.java b/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaOminousBottle.java +index 19f1425ae86e1b8b8fd46a5c6a193d1b77aeefe9..7197c4f5698fd041c4db6d0f6a80c55f77661789 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; + }); +diff --git a/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaPotion.java b/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaPotion.java +index e2aa305dcaf94d76fa3b74fc33b4d8bbc6d92b2b..db7f71af22d904de08d4badaa7f66d1286d5bf16 100644 +--- a/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaPotion.java ++++ b/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaPotion.java +@@ -61,8 +61,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/CraftMetaSkull.java b/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaSkull.java +index a08c57770c658bb289c96b69b966d98af72eef67..7bdc94c3ba7d8a0d74c2d88edbb32112a90c5980 100644 +--- a/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaSkull.java ++++ b/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaSkull.java +@@ -69,8 +69,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((resolvableProfile) -> { + this.setProfile(resolvableProfile.gameProfile()); +diff --git a/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaSpawnEgg.java b/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaSpawnEgg.java +index 1c2b0407b51906a255e6d240fab969578743938e..b98e656c0bb382667bd186a500c5505f1ed3f7cd 100644 +--- a/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaSpawnEgg.java ++++ b/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaSpawnEgg.java +@@ -119,8 +119,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 8fc3cd507d333d2bdea759d7c102a56e88ad5f5a..f6b3798cf06f94d7e3e76d1b6e83236ded5b21e0 100644 +--- a/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaSuspiciousStew.java ++++ b/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaSuspiciousStew.java +@@ -34,8 +34,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 911bdce0795a6b11cd1d5ad5211202456e5225d4..b5392a3a6f6f3d0a54549e6bb93f28590ee048f0 100644 +--- a/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaTropicalFishBucket.java ++++ b/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaTropicalFishBucket.java +@@ -39,8 +39,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 51e2acf125bdff2ba6d8fd8af9f22e233d7c74a7..6bed0a5c8d9f1ca72678cdf4699128e441a24541 100644 +--- a/src/test/java/org/bukkit/craftbukkit/inventory/DeprecatedItemMetaCustomValueTest.java ++++ b/src/test/java/org/bukkit/craftbukkit/inventory/DeprecatedItemMetaCustomValueTest.java +@@ -96,7 +96,7 @@ public class DeprecatedItemMetaCustomValueTest extends AbstractTestingBase { + 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..d9692972e3ad089885d43711b6a7fb3e96da59b1 +--- /dev/null ++++ b/src/test/java/org/bukkit/craftbukkit/inventory/MetaHandledTagsTest.java +@@ -0,0 +1,32 @@ ++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.AbstractTestingBase; ++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 ++class MetaHandledTagsTest extends AbstractTestingBase { ++ ++ @Test ++ public void checkAllMetasHaveHandledTags() { ++ try (final ScanResult result = new ClassGraph() ++ .whitelistPackages("org.bukkit.craftbukkit.inventory") ++ .enableAllInfo().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 30da18cbc878fb1ac2a134f3bcbfcb8d7bec3938..6f94c7a19f2f598a836ec7db30332dd95f8675a6 100644 +--- a/src/test/java/org/bukkit/craftbukkit/inventory/PersistentDataContainerTest.java ++++ b/src/test/java/org/bukkit/craftbukkit/inventory/PersistentDataContainerTest.java +@@ -130,7 +130,7 @@ public class PersistentDataContainerTest extends AbstractTestingBase { + CraftMetaItem.Applicator compound = new CraftMetaItem.Applicator(); + itemMeta.applyToItem(compound); + +- assertEquals(itemMeta, new CraftMetaItem(compound.build())); ++ assertEquals(itemMeta, new CraftMetaItem(compound.build(), null)); // Paper + } + + @Test +@@ -463,7 +463,7 @@ public class PersistentDataContainerTest extends AbstractTestingBase { + + @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()); +@@ -476,7 +476,7 @@ public class PersistentDataContainerTest extends AbstractTestingBase { + 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/0973-General-ItemMeta-fixes.patch b/patches/server/0973-General-ItemMeta-fixes.patch new file mode 100644 index 0000000000..4f8cacd3ea --- /dev/null +++ b/patches/server/0973-General-ItemMeta-fixes.patch @@ -0,0 +1,1494 @@ +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 + +diff --git a/src/main/java/net/minecraft/world/item/ItemStack.java b/src/main/java/net/minecraft/world/item/ItemStack.java +index 486c3769a0e6a1ecb5530a35e2591f78776619b9..b800b03ae034b276740c3b41555a52b778ad9aad 100644 +--- a/src/main/java/net/minecraft/world/item/ItemStack.java ++++ b/src/main/java/net/minecraft/world/item/ItemStack.java +@@ -1275,6 +1275,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 6207c6063cd11ccb1177fe7016c49c02a3416990..cd3b952a228c09077c2e74183a34ddb32811280b 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 +@@ -139,6 +139,11 @@ public abstract class BlockEntity { + CompoundTag nbttagcompound = new CompoundTag(); + + this.saveAdditional(nbttagcompound, registryLookup); ++ // 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 397eb1a101bd60f49dbb2fa8eddf28f6f233167f..2c61e8d5bbab59c691f4cb003041e7e50e406b85 100644 +--- a/src/main/java/org/bukkit/craftbukkit/block/CraftBlockEntityState.java ++++ b/src/main/java/org/bukkit/craftbukkit/block/CraftBlockEntityState.java +@@ -135,6 +135,19 @@ public abstract class CraftBlockEntityState extends Craft + return nbt; + } + ++ // 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 3c0d3faddcfcba74d90e1d5fccb60b891ef7c458..ddaa96a313bc92249f240deec1796150adc5d3aa 100644 +--- a/src/main/java/org/bukkit/craftbukkit/inventory/CraftItemStack.java ++++ b/src/main/java/org/bukkit/craftbukkit/inventory/CraftItemStack.java +@@ -328,7 +328,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 com.mojang.authlib.GameProfile gameProfile) { ++ itemStack.set(DataComponents.PROFILE, new net.minecraft.world.item.component.ResolvableProfile(gameProfile)); ++ } ++ }; ++ // Paper end - support updating profile after resolving it + ((CraftMetaItem) itemMeta).applyToItem(tag); + itemStack.applyComponents(tag.build()); + } +@@ -683,15 +690,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 com.mojang.authlib.GameProfile gameProfile) { ++ item.set(DataComponents.PROFILE, new net.minecraft.world.item.component.ResolvableProfile(gameProfile)); ++ } ++ }; ++ // 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 44d8aa7123ac22cf9a22720ecadc8c5f63bafc0a..9665dc043b257bb0d6f7b40fd938ff549ab685c6 100644 +--- a/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaAxolotlBucket.java ++++ b/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaAxolotlBucket.java +@@ -124,14 +124,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 1ac3bec02fce28d5ce698305a7482a9eccbb1867..b494568f833dc21d4e2447ac3e5c5002288b5533 100644 +--- a/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaBanner.java ++++ b/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaBanner.java +@@ -107,6 +107,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 12911233c01d0ac1af9adbd157d56d28361fc76f..3faef745d9b5d3baaea8c20c6f8e06f7bb529665 100644 +--- a/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaBlockState.java ++++ b/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaBlockState.java +@@ -142,9 +142,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; ++ // 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 != Material.SHIELD) ? this.material : CraftMetaBlockState.shieldToBannerHack(); ++ } ++ // Paper end + private CompoundTag internalTag; + + CraftMetaBlockState(CraftMetaItem meta, Material material) { +@@ -153,41 +168,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; ++ // Paper end + } + + CraftMetaBlockState(DataComponentPatch tag, Material material, final Set> extraHandledDcts) { // Paper + super(tag, extraHandledDcts); // Paper + this.material = material; + ++ // Paper start - move to separate method to be re-called ++ this.updateBlockState(tag); ++ } ++ ++ private void updateBlockState(final DataComponentPatch tag) { ++ // Paper end + getOrEmpty(tag, CraftMetaBlockState.BLOCK_ENTITY_TAG).ifPresent((nbt) -> { +- this.blockEntityTag = CraftMetaBlockState.getBlockState(material, nbt.copyTag()); ++ 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 + } + } + +@@ -200,7 +235,10 @@ public class CraftMetaBlockState extends CraftMetaItem implements BlockStateMeta + } else { + this.material = Material.AIR; + } +- this.blockEntityTag = CraftMetaBlockState.getBlockState(this.material, this.internalTag); ++ // Paper start ++ if (this.internalTag != null) { // legacy ++ this.setBlockState(CraftMetaBlockState.getBlockState(this.material, this.internalTag)); ++ } + this.internalTag = null; + } + +@@ -208,13 +246,21 @@ public class CraftMetaBlockState extends CraftMetaItem implements BlockStateMeta + void applyToItem(CraftMetaItem.Applicator tag) { + super.applyToItem(tag); + +- if (this.blockEntityTag != null) { +- tag.put(CraftMetaBlockState.BLOCK_ENTITY_TAG, CustomData.of(this.blockEntityTag.getSnapshotNBTWithoutComponents())); ++ // 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)); ++ } + +- for (TypedDataComponent component : this.blockEntityTag.collectComponents()) { +- tag.putIfAbsent(component); +- } ++ 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 +@@ -223,14 +269,29 @@ 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 +@@ -244,9 +305,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(); +- } ++ // Paper start ++ hash = 61 * hash + this.blockEntityTag.hashCode(); ++ hash = 61 * hash + this.components.hashCode(); ++ // Paper end + return original != hash ? CraftMetaBlockState.class.hashCode() ^ hash : hash; + } + +@@ -258,19 +320,19 @@ public class CraftMetaBlockState extends CraftMetaItem implements BlockStateMeta + if (meta instanceof CraftMetaBlockState) { + CraftMetaBlockState that = (CraftMetaBlockState) meta; + +- return Objects.equal(this.blockEntityTag, that.blockEntityTag); ++ return Objects.equal(this.blockEntityTag, that.blockEntityTag) && Objects.equal(this.components, that.components); // Paper + } + return true; + } + + @Override + boolean notUncommon(CraftMetaItem meta) { +- return super.notUncommon(meta) && (meta instanceof CraftMetaBlockState || this.blockEntityTag == null); ++ return super.notUncommon(meta) && (meta instanceof CraftMetaBlockState || (this.blockEntityTag.isEmpty() && this.components.isEmpty())); // Paper + } + + @Override + boolean isEmpty() { +- return super.isEmpty() && this.blockEntityTag == null; ++ return super.isEmpty() && this.blockEntityTag.isEmpty() && this.components.isEmpty(); // Paper + } + + @Override +@@ -281,27 +343,53 @@ public class CraftMetaBlockState extends CraftMetaItem implements BlockStateMeta + @Override + public CraftMetaBlockState clone() { + CraftMetaBlockState meta = (CraftMetaBlockState) super.clone(); +- if (this.blockEntityTag != null) { +- meta.blockEntityTag = this.blockEntityTag.copy(); +- } ++ // 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) { +@@ -331,7 +419,23 @@ public class CraftMetaBlockState extends CraftMetaItem implements BlockStateMeta + Class blockStateType = CraftBlockStates.getBlockStateType(stateMaterial); + Preconditions.checkArgument(blockStateType == blockState.getClass() && blockState instanceof CraftBlockEntityState, "Invalid blockState for " + 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() { +diff --git a/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaBook.java b/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaBook.java +index a395c7ce952f4a60a5edf80e8731afa6388d18ea..e213ac74f8f4a62b7b8b2b7889250f5cdeb348fe 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 3f78a0935d738854182254b345064e3c225dcd5f..218df87c596d47b431dbbf2aa42822ef174f948f 100644 +--- a/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaBookSigned.java ++++ b/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaBookSigned.java +@@ -116,8 +116,8 @@ public class CraftMetaBookSigned extends CraftMetaItem implements BookMeta { + } + } + +- this.resolved = SerializableMeta.getObject(Boolean.class, map, CraftMetaBookSigned.RESOLVED.BUKKIT, true); +- this.generation = SerializableMeta.getObject(Integer.class, map, CraftMetaBookSigned.GENERATION.BUKKIT, true); ++ this.resolved = SerializableMeta.getBoolean(map, CraftMetaBookSigned.RESOLVED.BUKKIT); // Paper - General ItemMeta fixes ++ this.generation = SerializableMeta.getObjectOptionally(Integer.class, map, CraftMetaBookSigned.GENERATION.BUKKIT, true).orElse(0); // Paper - General ItemMeta Fixes + } + + @Override +@@ -129,7 +129,7 @@ public class CraftMetaBookSigned extends CraftMetaItem implements BookMeta { + 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(FilteredText.passThrough(this.title == null ? "" : this.title)), this.author == null ? "" : this.author, this.generation, list, this.resolved)); + } + } + +@@ -358,7 +358,13 @@ public class CraftMetaBookSigned extends CraftMetaItem implements BookMeta { + } + + if (this.pages != null) { +- builder.put(CraftMetaBookSigned.BOOK_PAGES.BUKKIT, ImmutableList.copyOf(this.pages)); ++ // Paper start - deserialization expects json ++ final List jsonPages = new ArrayList<>(this.pages.size()); ++ for (final Component page : this.pages) { ++ jsonPages.add(CraftChatMessage.toJSON(page)); ++ } ++ builder.put(CraftMetaBookSigned.BOOK_PAGES.BUKKIT, ImmutableList.copyOf(jsonPages)); ++ // Paper end - deserialization expects json + } + + if (this.resolved) { +diff --git a/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaBundle.java b/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaBundle.java +index f8c02fe01fd95aa5de8523c9ad452d91f5d3c16f..4447c754458e9fdead003b4044bf2bee6fcbaaa2 100644 +--- a/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaBundle.java ++++ b/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaBundle.java +@@ -42,7 +42,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); + } + }); +@@ -55,7 +55,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); + } + } +@@ -116,7 +116,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 2c9ca54267579a210d4ea192517fc0fbce8e467a..4e423c4790d7b03c283c9a5fa94bce4a1153445e 100644 +--- a/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaColorableArmor.java ++++ b/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaColorableArmor.java +@@ -22,16 +22,30 @@ public class CraftMetaColorableArmor extends CraftMetaArmor implements Colorable + Material.WOLF_ARMOR + ); + +- 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) { +@@ -42,7 +56,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 +@@ -68,16 +86,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 +@@ -97,7 +115,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 bbca26f5debb263b04516e68f6e49f68a38fa5b1..aacc4d010f4dfa4d9d11332b802205a6f35b6de3 100644 +--- a/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaCompass.java ++++ b/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaCompass.java +@@ -36,7 +36,7 @@ public class CraftMetaCompass extends CraftMetaItem implements CompassMeta { + private int lodestoneX; + private int lodestoneY; + private int lodestoneZ; +- private boolean tracked = true; ++ private Boolean tracked = null; // Paper - tri-state + + CraftMetaCompass(CraftMetaItem meta) { + super(meta); +@@ -80,7 +80,7 @@ public class CraftMetaCompass extends CraftMetaItem implements CompassMeta { + this.setLodestone(lodestone); + } + } +- this.tracked = SerializableMeta.getBoolean(map, CraftMetaCompass.LODESTONE_TRACKED.BUKKIT); ++ this.tracked = SerializableMeta.getObjectOptionally(Boolean.class, map, CraftMetaCompass.LODESTONE_TRACKED.BUKKIT, true).orElse(null); // Paper - tri-state + } + + @Override +@@ -146,12 +146,12 @@ public class CraftMetaCompass extends CraftMetaItem implements CompassMeta { + } + + boolean hasLodestoneTracked() { +- return !this.tracked; ++ return this.tracked != null; // Paper - tri-state + } + + @Override + public boolean isLodestoneTracked() { +- return this.tracked; ++ return this.tracked != null && this.tracked; // Paper - tri-state + } + + @Override +diff --git a/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaCrossbow.java b/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaCrossbow.java +index a3fa95377e083e51ad7596d21eeb08172bdb18b2..0f5a64fc5eb619e18f5eeb97f377c9d593e4a38f 100644 +--- a/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaCrossbow.java ++++ b/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaCrossbow.java +@@ -122,7 +122,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 8e0dd4b7a7a25a8beb27b507047bc48d8227627c..77489c3ffaa3a72d4cf105499a77150fca6d8526 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); + } + } +@@ -86,19 +86,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(); +@@ -154,7 +149,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 + } + + @Override +@@ -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); + } + } +@@ -186,9 +190,13 @@ class CraftMetaFirework extends CraftMetaItem implements FireworkMeta { + } + + List effects = new ArrayList<>(); +- for (FireworkEffect effect : this.effects) { +- effects.add(CraftMetaFirework.getExplosion(effect)); ++ // Paper start - fix NPE with effects list being null ++ if (this.effects != null) { ++ for (FireworkEffect effect : this.effects) { ++ effects.add(CraftMetaFirework.getExplosion(effect)); ++ } + } ++ // Paper end + + itemTag.put(CraftMetaFirework.FIREWORKS, new Fireworks(this.power, effects)); + } +@@ -218,7 +226,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 + } + + boolean hasPower() { +@@ -234,7 +242,7 @@ class CraftMetaFirework extends CraftMetaItem implements FireworkMeta { + if (meta instanceof CraftMetaFirework that) { + + return (this.hasPower() ? that.hasPower() && this.power == that.power : !that.hasPower()) +- && (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; +@@ -252,7 +260,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; +@@ -262,7 +270,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)); + } + +@@ -287,6 +295,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(); + } +@@ -296,6 +305,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; + } +@@ -314,7 +327,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 +@@ -349,7 +362,7 @@ class CraftMetaFirework extends CraftMetaItem implements FireworkMeta { + @Override + public void setPower(int power) { + Preconditions.checkArgument(power >= 0, "power cannot be less than zero: %s", power); +- Preconditions.checkArgument(power < 0x80, "power cannot be more than 127: %s", power); ++ Preconditions.checkArgument(power <= 0xFF, "power cannot be more than 255: %s", power); // Paper - set correct limit + this.power = power; + } + } +diff --git a/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaItem.java b/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaItem.java +index 5cdb9e07f79355e4590984b32be554053754ef5b..31972619256c09bce46312b55153ddaef11cb236 100644 +--- a/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaItem.java ++++ b/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaItem.java +@@ -180,9 +180,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(com.mojang.authlib.GameProfile gameProfile) {} // Paper - support updating profile after resolving it + + Applicator put(ItemMetaKeyType key, T value) { + this.builder.set(key.TYPE, value); +@@ -307,7 +308,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); + } + +@@ -343,6 +344,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; + }); +@@ -769,7 +775,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()) { +@@ -931,10 +937,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; + } + +@@ -963,7 +967,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.hasBlockData() || this.hasRepairCost() || !this.unhandledTags.build().isEmpty() || !this.persistentDataContainer.isEmpty() || this.hideFlag != 0 || this.isHideTooltip() || this.isUnbreakable() || this.hasEnchantmentGlintOverride() || this.isFireResistant() || this.hasMaxStackSize() || this.hasRarity() || this.hasFood() || this.hasTool() || 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.hasBlockData() || this.hasRepairCost() || !this.unhandledTags.build().isEmpty() || !this.persistentDataContainer.isEmpty() || this.hideFlag != 0 || this.isHideTooltip() || this.isUnbreakable() || this.hasEnchantmentGlintOverride() || this.isFireResistant() || this.hasMaxStackSize() || this.hasRarity() || this.hasFood() || this.hasTool() || this.hasDamage() || this.hasMaxDamage() || this.attributeModifiers != null || this.customTag != null || this.canPlaceOnPredicates != null || this.canBreakPredicates != null); // Paper + } + + // Paper start +@@ -1059,6 +1063,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 +@@ -1183,6 +1188,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 { +@@ -1198,6 +1204,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 { +@@ -1339,7 +1346,7 @@ class CraftMetaItem implements ItemMeta, Damageable, Repairable, BlockDataMeta { + + @Override + public FoodComponent getFood() { +- return (this.hasFood()) ? new CraftFoodComponent(this.food) : new CraftFoodComponent(new FoodProperties(0, 0, false, 0, Optional.empty(), Collections.emptyList())); ++ return (this.hasFood()) ? new CraftFoodComponent(this.food) : new CraftFoodComponent(new FoodProperties(0, 0, false, FoodProperties.DEFAULT_EAT_SECONDS, Optional.empty(), Collections.emptyList())); // Paper - create a valid food properties + } + + @Override +@@ -1395,7 +1402,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) { +@@ -1408,6 +1415,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; + } + +@@ -1415,10 +1423,12 @@ 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); + } + +@@ -1429,8 +1439,11 @@ class CraftMetaItem implements ItemMeta, Damageable, Repairable, BlockDataMeta { + 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()) { +@@ -1440,6 +1453,7 @@ class CraftMetaItem implements ItemMeta, Damageable, Repairable, BlockDataMeta { + iterator.remove(); + continue; + } ++ this.checkAttributeList(); // Paper - moved down + this.attributeModifiers.put(next.getKey(), next.getValue()); + } + } +@@ -1447,13 +1461,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(); + +@@ -1473,7 +1487,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(); + +@@ -1495,7 +1509,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(); + Tag nbt = DataComponentPatch.CODEC.encodeStart(MinecraftServer.getDefaultRegistryAccess().createSerializationContext(NbtOps.INSTANCE), patch).getOrThrow(); +@@ -1504,7 +1518,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(); + +@@ -1544,6 +1558,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; +@@ -1569,6 +1584,8 @@ class CraftMetaItem implements ItemMeta, Damageable, Repairable, BlockDataMeta { + + @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; + } + +@@ -1585,6 +1602,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; + } + +@@ -1616,7 +1634,7 @@ class CraftMetaItem implements ItemMeta, Damageable, Repairable, BlockDataMeta { + && (this.hasCustomModelData() ? that.hasCustomModelData() && this.customModelData.equals(that.customModelData) : !that.hasCustomModelData()) + && (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)) + && (Objects.equals(this.customTag, that.customTag)) + && (this.persistentDataContainer.equals(that.persistentDataContainer)) +@@ -1676,8 +1694,8 @@ 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.hasDamage() ? this.damage : 0); +- hash = 61 * hash + (this.hasMaxDamage() ? 1231 : 1237); +- hash = 61 * hash + (this.hasAttributeModifiers() ? this.attributeModifiers.hashCode() : 0); ++ 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; +@@ -1697,7 +1715,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) { +@@ -1911,7 +1929,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; + } + +@@ -1993,7 +2011,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, +@@ -2061,7 +2079,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 157a7b7351f48e68d2923c72ed3bbe3dcae21383..70c0d4cc85c045d040a35cd406f3f7ce9b6a58fa 100644 +--- a/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaLeatherArmor.java ++++ b/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaLeatherArmor.java +@@ -28,16 +28,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) { +@@ -48,7 +62,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 +@@ -72,16 +90,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 +@@ -101,7 +119,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; + } +@@ -121,14 +139,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()) { +@@ -151,6 +171,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 d829f4da371b44e7480896118547734be400a314..0ff40e10ce8bab8010222ef7697d585430aeda03 100644 +--- a/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaMap.java ++++ b/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaMap.java +@@ -30,7 +30,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); +@@ -58,7 +58,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 + } +@@ -102,7 +102,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 + } + } + +@@ -127,6 +127,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; + } + +@@ -187,12 +188,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 7197c4f5698fd041c4db6d0f6a80c55f77661789..062ef890b42075adb5663453806dda3ad89a6aa0 100644 +--- a/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaOminousBottle.java ++++ b/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaOminousBottle.java +@@ -75,6 +75,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 db7f71af22d904de08d4badaa7f66d1286d5bf16..e32143e140db50a11cf602cf622d68b787c0d647 100644 +--- a/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaPotion.java ++++ b/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaPotion.java +@@ -47,7 +47,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) + + CraftMetaPotion(CraftMetaItem meta) { + super(meta); +@@ -70,7 +70,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 + } +@@ -126,7 +126,7 @@ class CraftMetaPotion extends CraftMetaItem implements PotionMeta { + super.applyToItem(tag); + + 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 + + List effectList = new ArrayList<>(); + if (this.customEffects != null) { +@@ -295,12 +295,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/CraftMetaSkull.java b/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaSkull.java +index 7bdc94c3ba7d8a0d74c2d88edbb32112a90c5980..ab9b3279db914fb4d6666a4dacbf5e401d2bcb87 100644 +--- a/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaSkull.java ++++ b/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaSkull.java +@@ -137,10 +137,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, new ResolvableProfile(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 b98e656c0bb382667bd186a500c5505f1ed3f7cd..63088d50c1ec69d1a829fd0783cf109f7981f43f 100644 +--- a/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaSpawnEgg.java ++++ b/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaSpawnEgg.java +@@ -212,6 +212,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); + } + +@@ -229,7 +230,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 b5392a3a6f6f3d0a54549e6bb93f28590ee048f0..7514aa6f206c4b82fecd112783f96bb9dd73c01f 100644 +--- a/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaTropicalFishBucket.java ++++ b/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaTropicalFishBucket.java +@@ -126,6 +126,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); + } + +@@ -139,6 +140,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); + } + +@@ -152,6 +154,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 8b407a33b04af6ae396ada0b8aca7dc246d314ef..d204845cf0b9de00589593469755cb8e42e0aa67 100644 +--- a/src/main/java/org/bukkit/craftbukkit/inventory/SerializableMeta.java ++++ b/src/main/java/org/bukkit/craftbukkit/inventory/SerializableMeta.java +@@ -123,4 +123,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/CraftFoodComponent.java b/src/main/java/org/bukkit/craftbukkit/inventory/components/CraftFoodComponent.java +index 63fc9138dc3388ceb9acf672b3f75ba0976e8e54..cad52601583c1f304f94de954c4d18a6005906e3 100644 +--- a/src/main/java/org/bukkit/craftbukkit/inventory/components/CraftFoodComponent.java ++++ b/src/main/java/org/bukkit/craftbukkit/inventory/components/CraftFoodComponent.java +@@ -109,6 +109,7 @@ public final class CraftFoodComponent implements FoodComponent { + + @Override + public void setEatSeconds(float eatSeconds) { ++ Preconditions.checkArgument(eatSeconds > 0, "Eat seconds must be positive"); // Paper - validate eat_seconds + this.handle = new FoodProperties(this.handle.nutrition(), this.handle.saturation(), this.handle.canAlwaysEat(), eatSeconds, this.handle.usingConvertsTo(), this.handle.effects()); + } + +@@ -134,6 +135,7 @@ public final class CraftFoodComponent implements FoodComponent { + + @Override + public FoodEffect addEffect(PotionEffect effect, float probability) { ++ Preconditions.checkArgument(0 <= probability && probability <= 1, "Probability cannot be outside range [0,1]"); // Paper + List effects = new ArrayList<>(this.handle.effects()); + + FoodProperties.PossibleEffect newEffect = new net.minecraft.world.food.FoodProperties.PossibleEffect(CraftPotionUtil.fromBukkit(effect), probability); +diff --git a/src/test/java/org/bukkit/craftbukkit/inventory/DeprecatedItemMetaCustomValueTest.java b/src/test/java/org/bukkit/craftbukkit/inventory/DeprecatedItemMetaCustomValueTest.java +index 6bed0a5c8d9f1ca72678cdf4699128e441a24541..8e03e14d0e65bfdf2196a08220d1408b1297aa0d 100644 +--- a/src/test/java/org/bukkit/craftbukkit/inventory/DeprecatedItemMetaCustomValueTest.java ++++ b/src/test/java/org/bukkit/craftbukkit/inventory/DeprecatedItemMetaCustomValueTest.java +@@ -93,7 +93,7 @@ public class DeprecatedItemMetaCustomValueTest extends AbstractTestingBase { + 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/ItemMetaTest.java b/src/test/java/org/bukkit/craftbukkit/inventory/ItemMetaTest.java +index d6018439015583fa0344c7c01b2e60a13de29795..aabe3730fa582f442ee0544dd1a9f3123f719c68 100644 +--- a/src/test/java/org/bukkit/craftbukkit/inventory/ItemMetaTest.java ++++ b/src/test/java/org/bukkit/craftbukkit/inventory/ItemMetaTest.java +@@ -66,7 +66,7 @@ import org.junit.jupiter.api.Test; + + public class ItemMetaTest extends AbstractTestingBase { + +- static final int MAX_FIREWORK_POWER = 127; // Please update ItemStackFireworkTest if/when this gets changed. ++ static final int MAX_FIREWORK_POWER = 255; // Please update ItemStackFireworkTest if/when this gets changed. // Paper - it changed + + @Test + public void testPowerLimitExact() { +diff --git a/src/test/java/org/bukkit/craftbukkit/inventory/PersistentDataContainerTest.java b/src/test/java/org/bukkit/craftbukkit/inventory/PersistentDataContainerTest.java +index 6f94c7a19f2f598a836ec7db30332dd95f8675a6..54ffbfd91a03efa2d0d271ed10db4209a2309638 100644 +--- a/src/test/java/org/bukkit/craftbukkit/inventory/PersistentDataContainerTest.java ++++ b/src/test/java/org/bukkit/craftbukkit/inventory/PersistentDataContainerTest.java +@@ -127,7 +127,7 @@ public class PersistentDataContainerTest extends AbstractTestingBase { + 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 +@@ -473,7 +473,7 @@ public class PersistentDataContainerTest extends AbstractTestingBase { + 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/0974-Expose-hasColor-to-leather-armor.patch b/patches/server/0974-Expose-hasColor-to-leather-armor.patch new file mode 100644 index 0000000000..0cdf66fe04 --- /dev/null +++ b/patches/server/0974-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 4e423c4790d7b03c283c9a5fa94bce4a1153445e..1e8a76f6dd54931eec261653a7bd51b6d18d3c68 100644 +--- a/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaColorableArmor.java ++++ b/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaColorableArmor.java +@@ -134,4 +134,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 70c0d4cc85c045d040a35cd406f3f7ce9b6a58fa..0860d85fb9c6d0567f678569efb3c560f58612a9 100644 +--- a/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaLeatherArmor.java ++++ b/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaLeatherArmor.java +@@ -183,4 +183,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/0975-Added-API-to-get-player-ha-proxy-address.patch b/patches/server/0975-Added-API-to-get-player-ha-proxy-address.patch new file mode 100644 index 0000000000..90f7a32331 --- /dev/null +++ b/patches/server/0975-Added-API-to-get-player-ha-proxy-address.patch @@ -0,0 +1,56 @@ +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 96355e1da8feb6687ea0069dda4a82fcd7e25e8a..1f696644b958538e9f5d568a2e4bba69d74a191e 100644 +--- a/src/main/java/net/minecraft/server/network/ServerConnectionListener.java ++++ b/src/main/java/net/minecraft/server/network/ServerConnectionListener.java +@@ -138,6 +138,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 35ff3eae4f68fd1fe9bacbeacfd826a5022f8899..744ddef0ee833fc5caebe4036638812383f126f3 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java +@@ -275,6 +275,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/0976-More-Chest-Block-API.patch b/patches/server/0976-More-Chest-Block-API.patch new file mode 100644 index 0000000000..020d97d086 --- /dev/null +++ b/patches/server/0976-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 5f9858ef8d0ec1a74d469ab4426eb1db068873fd..ca92d49ef2010ba00c623491671dcde8ebe697c1 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 + BlockEntity blockEntity = world.getBlockEntity(pos); + if (playerEnderChestContainer != null && blockEntity instanceof 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.sidedSuccess(world.isClientSide); + } else if (world.isClientSide) { + return InteractionResult.SUCCESS; +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: 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 87dd1f570fd294daf826ef7e6403776e42ed4f61..cee4a0639b3c73e300a8450f8a831cb4a71958ba 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/0978-Brigadier-based-command-API.patch b/patches/server/0978-Brigadier-based-command-API.patch new file mode 100644 index 0000000000..be6bba86f9 --- /dev/null +++ b/patches/server/0978-Brigadier-based-command-API.patch @@ -0,0 +1,2831 @@ +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..27509813a90980be1dfc7bde27d0eba60adfc820 +--- /dev/null ++++ b/src/main/java/io/papermc/paper/command/brigadier/PaperCommands.java +@@ -0,0 +1,193 @@ ++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.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.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, final boolean override) { ++ final boolean hasChild = this.getDispatcher().getRoot().getChild(node.getLiteral()) != null; ++ if (!hasChild || 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) ++ .then( ++ Commands.argument("args", StringArgumentType.greedyString()) ++ .suggests((context, suggestionsBuilder) -> { ++ final String[] args = StringUtils.split(suggestionsBuilder.getRemaining()); ++ 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..72966584089d3fee9778f572727c9b7f4a4d4302 +--- /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 net.kyori.adventure.chat.SignedMessage; ++import net.minecraft.commands.CommandSourceStack; ++import net.minecraft.commands.arguments.MessageArgument; ++import org.jetbrains.annotations.NotNull; ++ ++import java.util.concurrent.CompletableFuture; ++ ++public record SignedMessageResolverImpl(MessageArgument.Message message) implements SignedMessageResolver { ++ ++ @Override ++ public String content() { ++ return this.message.text(); ++ } ++ ++ @Override ++ public @NotNull CompletableFuture resolveSignedMessage(final String argumentName, final CommandContext erased) throws CommandSyntaxException { ++ final CommandContext type = erased; ++ final CompletableFuture future = new CompletableFuture<>(); ++ ++ final MessageArgument.Message response = type.getArgument(argumentName, SignedMessageResolverImpl.class).message; ++ MessageArgument.resolveChatMessage(response, type, 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..93edb22c8500e79f86b101ef38955bca45a8d3a9 +--- /dev/null ++++ b/src/main/java/io/papermc/paper/command/brigadier/argument/VanillaArgumentProviderImpl.java +@@ -0,0 +1,354 @@ ++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.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.math.Position; ++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 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.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 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 Position.block(pos.getX(), pos.getY(), pos.getZ()); ++ }); ++ } ++ ++ @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